C++用new和不用new创建类对象区别
起初刚学C++时,很不习惯用new,后来看老外的程序,发现几乎都是使用new,想一想区别也不是太大,但是在大一点的项目设计中,有时候不使用new的确会带来很多问题。
当然这都是跟new的用法有关的。new创建类对象,使用完后需使用delete删除,跟申请内存类似。所以,new有时候又不太适合,比如在频繁调用场合,使用局部new类对象就不是个好选择,使用全局类对象或一个经过初始化的全局类指针似乎更加高效。
一、new创建类对象与不new区别
下面是自己总结的一些关于new创建类对象特点:
- new创建类对象需要指针接收,一处初始化,多处使用
- new创建类对象使用完需delete销毁
- new创建对象直接使用堆空间,而局部不用new定义类对象则使用栈空间
- new对象指针用途广泛,比如作为函数返回值、函数参数等
- 频繁调用场合并不适合new,就像new申请和释放内存一样
二、new创建类对象实例
1、new创建类对象例子:
CTest* pTest = new CTest();
delete pTest;
pTest用来接收类对象指针。
不用new,直接使用类定义申明:
CTest mTest;
此种创建方式,使用完后不需要手动释放,该类析构函数会自动执行。而new申请的对象,则只有调用到delete时再会执行析构函数,如果程序退出而没有执行delete则会造成内存泄漏。
2、只定义类指针
这跟不用new申明对象有很大区别,类指针可以先行定义,但类指针只是个通用指针,在new之前并为该类对象分配任何内存空间。比如:
CTest* pTest = NULL;
但使用普通方式创建的类对象,在创建之初就已经分配了内存空间。而类指针,如果未经过对象初始化,则不需要delete释放。
3、new对象指针作为函数参数和返回值
下面是天缘随手写一个例子,不太严谨。主要示意一下类指针对象作为返回值和参数使用。
示例:
类的内存分配简单总结,代码片段如下:
#include <iostream> using namespace std; /** * yanggang * https://blog.mimvp.com * 2014-08-11 **/ class ClassA { private: int A; int B; void prin1() { } void prin2() { } virtual void prin3() { } }; class ClassB : public ClassA { public: int C; int D; void prin4() { } void prin5() { } virtual void prin6() { } }; int main(int argc, char* argv[]) { cout<<sizeof(ClassA)<<endl; // 16 cout<<sizeof(ClassB)<<endl; // 24 return 0; }
测试环境:
Ubuntu 12.04.5 LTS x86_64 GNU/Linux
g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3 编译参数 -m64
结果是
homer@homer-pc:~/Desktop$ g++ testMem.c -o testMem && ./testMem
16
24
结果为什么是这样?
32位系统int占4个字节,64位系统int也占4个字节(不是8个字节),而一个类中所有的虚函数通过一个虚函数指针管理,类对象的大小只包含这个vptr指针(在64位机器上指针sizeof为8个字节),其他虚函数是放在别的内存空间中管理,vptr指针在64位机器上是8个字节大小(32位机器上是4个字节)。注意到普通成员函数并不占类对象的大小空间,因为普通成员函数通过this指针管理,一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。
sizeof(ClassA)
1)int A 和 int B 各占4个字节,考虑64位机器编译器对其规则,合并为8个字节
2)virtual void prin3() 虚函数的vptr指针,在64位机器编译器上占8个字节
3)合计 sizeof(ClassA)为 8 + 8 = 16个字节
this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。这个this指针会因编译器不同而有不同的放置位置,可能是栈,也可能是寄存器,甚至全局变量。
子类其实不管如何继承,用sizeof()算该类的大小都会把父类中的私有成员变量所占的空间算进去,也就是说,私有变量也在子类中分配了内存,但你却不可以直接访问,这起到一个保护作用,这如同一个珠宝,共有继承就是开放性的展览,而私有继承是把珠宝锁起来,你却不能动,要动珠宝如果有管家(基类的public中定义了一些对其私有变量操作的成员函数,)只能让管家帮你代劳。
sizeof(ClassB)
1)int A 和 int B 各占4个字节,是父类ClassA中的私有变量,合并占用8个字节
2)virtual void prin3() 虚函数的vptr指针,父类ClassA在子类中不会分配空间
3)int C 和 int D 各占4个字节,在子类中会分配空间,合并占用8个字节
4)virtual void prin6() 虚函数的vptr指针,在64位机器编译器上占8个字节
5)合计 sizeof(ClassB)为 8 + 8 + 8 = 24个字节
注明: 上述示例在32位编译器上测试,sizeof(ClassA)和sizeof(ClassB)分别为12和20字节(虚函数指针占用空间少了4字节)
知识延伸:
1) 空 ClassA(验证空Class占用空间大小)
class ClassA { private: void prin1() { } void prin2() { } }; cout<<sizeof(ClassA)<<endl; // 1
2) ClassA 只有一个char(验证编译器对其规则)
class ClassA { private: char C; void prin1() { } void prin2() { } }; cout<<sizeof(ClassA)<<endl; // 1
3)ClassA 有一个char和一个虚函数(或一个long型)(验证编译器对其规则二)
class ClassA { private: char C; void prin1() { } void prin2() { } virtual void prin3() { } }; cout<<sizeof(ClassA)<<endl; // 16
4)ClassA 有一个char和一个虚函数(或一个long型),且对调char和虚函数的先后顺序(验证编译器对其规则三)
class ClassA { void prin1() { } void prin2() { } virtual void prin3() { } private: char C; }; cout<<sizeof(ClassA)<<endl; // 16
5)ClassA 有一个char,一个int,一个虚函数(或一个long型)(验证编译器对其规则四)
class ClassA { private: char C; int A; void prin1() { } void prin2() { } virtual void prin3() { } }; cout<<sizeof(ClassA)<<endl; // 16
6)ClassA 有一个char,一个int(验证编译器对其规则五)
class ClassA { private: char C; int A; void prin1() { } void prin2() { } }; cout<<sizeof(ClassA)<<endl; // 8
7)ClassA 只有一个int(验证编译器对其规则六)
class ClassA { private: int A; void prin1() { } void prin2() { } }; cout<<sizeof(ClassA)<<endl; // 4
总结如下:
1) 空类的 sizeof 为1个字节
2) 只有一个char的类,sizeof为一个字节
3) 类中含有char和虚函数,将以最大的变量或指针为编译器对齐规则,例如:虚函数指针占8个字节(64位编译器),则char虽然只占1个字节,但对齐后空余了7个字节,合并类占8(指针) + 1(char) + 7(对齐的空字节) = 16个字节
4) 对齐规则,跟变量或虚函数的先后顺序无关,只跟最大变量类型或函数指针有关,函数指针跟编译器最大对齐位数有关(不太好理解,请继续往下看)
5) char和int合占8个字节,虚函数指针占8个字节,且以最大的虚函数指针的8字节对其,其中char占一个空余3个字节合并占4个,int占4个字节,按8位规则对齐,合计16字节
6) 一个char和int合并占8个字节,无虚函数,此时以最大变量类型int对齐,因此char占1字节空3字节占4字节
7)一个int占4位,自己便是最大的对齐规则; 2)一个char同理也占1个字节,此处跟编译器最大64位对齐规则无关(即一个int或一个char,不会拓展到占用8字节)
这里,又延伸出了一个很有意思的问题,空类sizeof为什么不为0,而为1?
回答这个问题,需要回到编译器和类的实例化问题上来,在学校看过《深度探索C++对象模型》,类分抽象类和普通类,空类属于普通类,是可以被实例化的。
每个类的实例,在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址,因此1)中的空类ClassA默认会占用1个字节。
版权所有: 本文系米扑博客原创、转载、摘录,或修订后发表,最后更新于 2014-08-12 09:20:32
侵权处理: 本个人博客,不盈利,若侵犯了您的作品权,请联系博主删除,莫恶意,索钱财,感谢!