【C++】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对象指针作为函数参数和返回值

下面是天缘随手写一个例子,不太严谨。主要示意一下类指针对象作为返回值和参数使用。

 

示例:

类的内存分配简单总结,代码片段如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <iostream> using namespace  std;   /** * yanggang * http://blog.ithomer.net * 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位系统int4个字节,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占用空间大小

1 2 3 4 5 6 7 8 9 10 11 class ClassA {      private :        void prin1() {       }        void prin2() {      } };   cout<< sizeof (ClassA)<<endl;        // 1

 

2) ClassA 只有一个char(验证编译器对其规则

1 2 3 4 5 6 7 8 9 10 11 12 class ClassA {      private :          char C;        void prin1() {       }        void prin2() {      } };   cout<< sizeof (ClassA)<<endl;        // 1

 

3)ClassA 有一个char和一个虚函数(或一个long型)(验证编译器对其规则二

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class ClassA {      private :          char C;        void prin1() {       }        void prin2() {      }        virtual void  prin3() {      } };   cout<< sizeof (ClassA)<<endl;        // 16

 

4)ClassA 有一个char和一个虚函数(或一个long型),且对调char和虚函数的先后顺序(验证编译器对其规则三

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class ClassA {        void prin1() {       }        void prin2() {      }        virtual void  prin3() {      }        private :          char C; };   cout<< sizeof (ClassA)<<endl;        // 16

 

5)ClassA 有一个char,一个int,一个虚函数(或一个long型)(验证编译器对其规则四

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class ClassA {      private :          char C;          int A;        void prin1() {       }        void prin2() {      }        virtual void  prin3() {      } };   cout<< sizeof (ClassA)<<endl;        // 16

 

6)ClassA 有一个char,一个int(验证编译器对其规则五

1 2 3 4 5 6 7 8 9 10 11 12 13 class ClassA {      private :          char C;          int A;        void prin1() {       }        void prin2() {      } };   cout<< sizeof (ClassA)<<endl;        // 8

 

7)ClassA 只有一个int(验证编译器对其规则六

1 2 3 4 5 6 7 8 9 10 11 12 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个字节。

    原文作者:漫步云端ly
    原文地址: https://blog.csdn.net/ly930156123/article/details/78855134
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞