C++类和动态内存分配
2018-02-04 21:33
162 查看
C++ Primer Plus读书笔记
1.类静态成员
类中可以有一些静态变量,不管这个类创建了多少个对象,这个变量始终都是保持不变的,因为它是静态变量,这就给一些在所有对象中都有相同值的类提供了方便。在进行类的变量定义时,将数组定义成指针的形式,可以不在定义时就将大小分配,而是在构造函数中使用new对其分配空间。
2.析构函数的调用
析构函数的调用时机:当在成员函数中创建对象时,该函数执行完毕之后会调用析构函数!
当对象是动态变量时,执行完定义该对象的程序块时,将调用该对象的析构函数。
当对象是静态变量时,程序结束时将调用对象的析构函数。
当对象是用new创建的时,仅当显示使用delete删除对象时,析构函数才会被调用。
3.特殊成员函数
C++自动提供的成员函数:默认构造函数
默认的构造函数里面是没有内容的
默认析构函数
类似与默认构造函数,是没有内容的
复制构造函数
用于将一个对象复制到新创建的对象中,它用于初始化过程中,而不是常规的赋值过程中。它的函数原型为:
class_name(const class_name &) //ZhuMeng(const ZhuMeng &)
新建一个对象并将其初始化为现有对象时,复制构造函数将被调用
默认的复制构造函数复制的是成员的值。
赋值运算符
这种运算符的原型为:
class_name & class_name::operator=(const class_name &);
地址运算符
4.这里介绍一种错误
头文件#ifndef ZHUMENG_H_ #define ZHUMENG_H #include<iostream> class ZhuMeng { private: int shi; int fen; int miao; static int numm; public: ZhuMeng(); ZhuMeng(int , int , int ); explicit ZhuMeng(int ); ~ZhuMeng(); ZhuMeng operator+(ZhuMeng &) const; friend std::ostream & operator<<(std::ostream & o, const ZhuMeng & outp); }; #endif
实现文件
#include"ZhuMeng.h" int ZhuMeng::numm = 0; ZhuMeng::ZhuMeng() { shi = 0; fen = 0; miao = 0; numm++; } ZhuMeng::ZhuMeng(int shi, int fen, int miao) { this->shi = shi; this->fen = fen; this->miao = miao; numm++; } ZhuMeng::ZhuMeng(int shit) { this->shi = shit; numm++; } ZhuMeng::~ZhuMeng() { numm--; } ZhuMeng ZhuMeng::operator+(ZhuMeng & aaa) const { std::cout << this->shi << std::endl; ZhuMeng sum; sum.shi = this->shi + aaa.shi; sum.fen = this->fen + aaa.fen; sum.miao = this->miao + aaa.miao; return sum; } std::ostream & operator<<(std::ostream & o, const ZhuMeng & outp) { o<<outp.shi<<std::endl<<outp.fen<<std::endl<<outp.miao<<std::endl; o<<"left num: "<<outp.numm; return o; }
执行文件
#include<iostream> #include"ZhuMeng.h" using namespace std; ZhuMeng operator*(int all, ZhuMeng all_2) { cout<<"success"<<endl; } void func(ZhuMeng a) { cout<<a<<endl; } int main() { ZhuMeng default_zhumeng; ZhuMeng a1(13, 24, 10); ZhuMeng a2;//=a1; **** #1 func(default_zhumeng); func(a1); func(default_zhumeng); func(a1); func(a1); return 0; }
输出的结果为:
0 0 0 left num: 2 13 24 10 left num: 1 0 0 0 left num: 0 13 24 10 left num: -1 13 24 10 left num: -2
下面从以下几个角度来分析
这里的numm在类中作为静态变量,初始值定义在cpp文件中,静态变量在内存中一直存在,作为某个类所有对象之间共享的一个变量。
numm的值为什么会变成负数呢?
在重载<<运算符的时候,将一个ZhuMeng的对象作为对象传给这个函数
函数的参数传递方式是按值传递,因此编译器内部是将实参的值赋给形参,也就是用到了复制构造函数,这时候会调用实参的析构函数,以及赋值所用到的复制构造函数,并不是使用构造函数,而在C++中默认的赋值运算符是没有让numm++的,因此numm的值会-1。因此想要正确地执行所想的功能,这里应该是定义一个复制构造函数
ZhuMeng(const ZhuMeng & s) { numm++; }
1部分的代码有什么问题呢?
这部分代码,把等号加上,就是进行了赋值操作,而默认的赋值函数没有记性numm++操作,同时这个赋值也是不安全的,如果有使用new delete而创建删除的变量,则很有可能会出现错误,因为指针和内存区域也进行了复制,而每次把对象当做实参进行传递之后会调用析构函数,会将前面使用new的变量删除,同时后面如果使用两次的话,会重复删除同一块区域导致意想不到的错误。因此解决方法是显式定义赋值运算符(也就是重载 = 运算符)
ZhuMeng & ZhuMeng::operator=(const ZhuMeng & a) { numm++; //这里重新定义那些使用new分配空间的变量,并将a的对应值赋给他们即可 return *this; }
还有一个问题:如果在类的函数中,某些数据是使用new delete函数进行创建的话,则需要在复制构造函数和赋值运算符中创建新的内存区域。如果不这样做,则每一次调用析构函数的时候,删除的都是同一个内容(因为默认的复制和赋值函数都是这样处理的),当一个变量被delete两次的时候,就会发生未知的错误!!切记。
5.构造函数中使用new时应该注意的事项
如果在构造函数中使用new来初始化指针成员,则需要在析构函数中使用deletenew和delete必须相互兼容,new对应delete,new[]对应delete[]
如果有多个构造函数,则必须以同样的方式使用new,要么都带[],要么都不带,因为只有一个析构函数,所有的构造函数都必须与他兼容。
可以在构造函数中将指针初始化为0(C++11为nullptr),因为delete和delete[]都可以用于空指针。
应当定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象。
应当定义一个赋值运算符,通过深度复制的方式将一个对象复制给另一个对象。
下面是几个错误的例子
String String() { str = "123";//没有使用new }
String String(const char * s) { str = new char;//因为是字符串,没有使用[]是错误的 }
String String(const char * s) { len = strlen(s); str = new char[len+1];//这个是正确的,在长度后面留一个\0的位置 }
5.关于返回对象
1 返回指向const对象的引用使用const引用的常见原因是旨在提高效率,但对于何时可以采用这种方式存在一些限制。如果函数返回(通过调用对象的方法或将对象作为参数)传递给它的对象,可以通过返回引用来提高效率。
注意:
返回对象将调用复制构造函数,而返回引用则不会
引用指向的对象应该在调用函数执行时存在
2 返回指向非const对象的引用
重载赋值运算符
这个是为了提高效率
重载<<运算符(与cout一起使用)
这个是必须这样做
3 返回对象
如果返回的对象是被调用函数中的局部变量,则不应该按引用的方式返回它,因为在被调用函数执行完毕时,局部对象将执行其析构函数,因此,当控制权回到调用函数时,引用指向的对象将不再存在。
4 返回const对象
主要是为了防止某些函数的左值被用于计算,比如a+b=c,将c赋给加法的结果,当然这样是没有意义的,如果返回const对象则可能会避免发生这种错误。
总结:如果方法或函数要返回局部对象,则应该返回对象,而不是指向对象的引用。在这种情况下,将使用复制构造函数来生成返回的对象。如果方法或函数要返回一个没有公有复制构造函数的类的对象,则必须返回一个指向这种对象的引用。如果返回引用和返回对象都可以的时候则最好使用返回引用的方法,效率会更高。
6.指向对象的指针
使用new获取指向对象的指针,使用delete则会调用对象的析构函数ZhuMeng * pointer = new ZhuMeng();//括号里针对不同构造函数有不同的写法
定位new运算符
char * buffer = new char[512]; ZhuMeng *pc3, *pc4; pc3 = new (buffer) ZhuMeng(); pc4 = new (buffer + sizeof(ZhuMeng)) ZhuMeng();
使用定位new运算符创建对象时,是使用一个新的对象来覆盖用于第一个对象的内存单元。因此,如上面两行代码所示,必须在原有的地址上有一个偏移量,才能保证两个部分不重叠。如果类动态地为成员分配内存,则会出现错误。
另外一点是使用delete,如果是用定位new运算符创建的对象,则在删除的时候,如果使用了delete pc3,则删掉的是buffer,因为new/delete系统知道已经分配的是512个字节的内存,而对定位new运算符的操作则是一无所知。由于pc3和buffer指向的内容是一样的,因此使用pc3删掉的也是buffer。这样的话,pc3和pc4的析构函数也不会执行,需要显式地调用析构函数。
使用delete [] buffer的时候是将整个buffer删除(对应的new [])而不会为每一pc调用析构函数(pc3和pc4的析构函数都不会被调用)。
pc4->~ZhuMeng();//释放部分的内存 pc3->~ZhuMeng(); delete [] buffer;//将整个buffer都给释放掉
7.类的成员初始化列表
成员初始化列表是放在构造函数定义的名称后面,用冒号指出ZhuMeng::ZhuMeng(int qs):shi(qs) { //...... }
成员初始化列表就是上面代码片中的
shi(qs),将qs赋给shi。
打个比方,如果shi是一个const int型的常量,则只能给它初始化而不能给他赋值,如果将
shi = qs写在构造函数中,则会报错,因为这是赋值,不是初始化,把这个类成员初始化列表写在构造函数后边,则会在执行到构造函数之前,也就是创建对象的时候进行初始化,而不是对其进行赋值,这样就会有很大的好处。
只有构造函数可以使用这种初始化列表的方法,并且对于const类的成员,必须使用这种语法,否则,则必须要在类内进行初始化。另外,被声明为引用的类成员也必须要使用这种语法。因为引用与const类似,都是只能在声明的时候进行初始化。
在类内进行初始化与使用初始化列表进行初始化是等价的,也就是下面这两种描述是相同的。
class ZhuMeng{ const int quan = 20; }
ZhuMeng::ZhuMeng():quan(20) { //... }
相关文章推荐
- C++类和动态内存分配(1)
- c++类和动态内存分配
- C++类和动态内存分配
- c++类和动态内存分配
- 初始化C++类成员和在你的MFC应用中加入位置栏
- C++类学习
- 关于C++类成员的初始化
- 三论在C++类中实现Windows窗口的创建
- 浅谈C++类(7)--析构函数
- C++类成员的初始化顺序
- 初始化C++类成员和在你的MFC应用中加入位置栏
- 关于初始化C++类成员
- c++类中变量初始化顺序与声明顺数一致
- 用未公开的MFC类加强动态内存分配
- C++类中重载操作符的问题
- C++类和接口的设计原则探讨
- 精转:c++类的多态与虚函数的使用
- C++类模板的三种特化
- C++类的复制控制 笔记
- 在c++类中使用函数指针数组