[C++学习笔记04]构造函数与析构函数
- 构造函数
通常情况下构造函数应声明为公有函数,否则它不能像其他成员函数那样被显式地调用;
构造函数被声明为私有有特殊的用途;
如果程序中未声明一个任意构造函数,则系统自动产生出一个默认构造函数(无参构造);
如果程序中声明了一个任意构造函数,则系统不再产生出一个默认构造函数;
全局对象的构造先于main函数。 - 析构函数
析构函数不能被重载;
如果没有定义析构函数,编译器会自动生成一个默认析构函数。 - 构造函数与析构函数的直接调用
int main(void) { Test(); // 没有定义对象,可以直接调用构造函数 // 产生一个临时对象,如果没有定义变量接收,那么自动就调用析构函数 //~Test(); // Error,“Test”不定义该运算符或到预定义运算符可接收类型的转换 Test test; // 定义了对象,通过该对象只能直接调用析构函数 test.~Test(); //OK //test.Test(); // Error, 位于“.”运算符右边时非法 return 0; }
- 构造与析构的顺序
先构造的对象后析构,当然是同一种情况下,比如都是栈上构造的对象。 - 转换构造函数
1.单个参数的构造函数;
2.将其它类型转换为类类型;
3.类的构造函数只有一个参数是非常危险的,因为编译器可以使用这种构造函数把参数的类型隐式转换为类类型
int main(void) { //Test t(10); // 充当的是普通构造函数的功能 Test t2; t2 = 10; // 将10这个整数赋值给t2对象 // 1.编译器调用类型转换构造函数将整数10转换成Test类类型(生成一个临时对象) // 2.将临时对象赋值给t2(调用的是=运算符) // 3.释放临时对象 cout << "main end.." << endl; return 0; }
- 赋值与初始化区别
class Test { public: Test() { num_ = 0; cout << "Default Construction." << endl; } Test(int num) { num_ = num; cout << "A single parameter" << endl; } ~Test() { cout << "Default Deconstruction." << endl; } // 重载 = 操作 Test& operator=(const Test &other) { cout << "operator = " << endl; this->num_ = other.num_; return *this; } private: int num_; }; int main(void) { Test t = 10; // 初始化,相当于Test t(10);只是调用一个参数的构造函数(普通构造函数) t = 20; // 赋值操作,调用转换构造函数生成临时对象,将临时对象赋值给t,再销毁临时对象 return 0; }
- explicit
只提供给类的构造函数使用的关键字;
编译器不会把声明为explicit的构造函数用于隐式转换,它只能在程序代码中显示创建对象。
int main(void) { // 在带一个参数的构造函数之前声明explicit,explicit Test(int num); //Test t1 = 10; // Error, 无法从“int”转换为“Test” // 此时Test t1 = 10将不等价与Test t1(10); Test t1(10); // OK //t1 = 20; // 1.Error,不能转换生成临时对象,错误类型是没有int参数的=重载 // 2.当我们重载=为Test& operator=(const int other);此时将可以,不会再生成临时Test对象 return 0; }
- 构造函数初始化列表
构造函数的执行分为两个阶段
初始化段
普通计算段(函数体内的语句,赋值等操作)
class Test { public: Test() : num_(0) // 初始化操作 { //num_ = 0; // 赋值操作 cout << "Default Construction." << endl; } Test(int num) : num_(num) // 初始化操作 { //num_ = num; // 赋值操作 cout << "A single parameter" << endl; } ~Test() { cout << "Default Deconstruction." << endl; } private: int num_; };
- 对象成员及其初始化
当一个类中定义的成员变量是另一个类类型,最好使用初始化列表初始化,因为会出现那个类没有默认构造函数等情况,你直接赋值是不行的。
必须使用初始化列表初始化的情况1.#include <iostream> using namespace std; class Object { public: // Object类无默认构造函数 Object(int num) : num_(num) { cout << "Object Construction " << num_<<" ..." << endl; } ~Object() { cout << "Object Deconstruction " << num_ << " ..." << endl; } private: int num_; }; class Container { public: // Container类的Object对象无默认构造函数,必须要使用初始化列表初始化 // 这是一种必须使用初始化列表初始化的情况 Container(int num1, int num2) : obj2_(num1), obj1_(num2) { //obj_ = num; // Error cout << "Container Construction ..." << endl; } ~Container() { cout << "Container Deconstruction ..." << endl; } private: // obj1_比obj2_先构造,析构顺序相反,与初始化列表的顺序无关 Object obj1_; Object obj2_; }; int main(void) { /** Object 20 构造->Object 10 构造->Container 构造函数->Container 析构函数->Object 10析构->Object 20析构 */ Container c(10, 20); return 0; }
-
const成员、引用成员初始化
必须使用初始化列表初始化的情况2.
class Object { public: // 类中有const常量或者引用的时候必须在初始化列表初始化 // 必须在初始化列表初始化的情况2 Object(int num) : num_(num), rnum(num) { //num_ = num; // Error //rnum = num; // Error cout << "Object Construction " << num_ << " ..." << endl; } ~Object() { cout << "Object Deconstruction " << num_ << " ..." << endl; } private: const int num_; int &rnum; };
- 类中的常量-枚举
根据以上的例子我们可以看出const的常量在每个对象中的值是不相同的,如果要每个对象都相的常量,可以使用枚举类型,当然个人觉得还是可以使用static const。
通过使用static const实现:
// 通过使用static const来实现所有对象的常量 class Object { public: // 无法通过构造函数初始化静态类数据 Object(int num) //: num_(num) { cout << "Object Construction " << num_ << " ..." << endl; } ~Object() { cout << "Object Deconstruction " << num_ << " ..." << endl; } private: static const int num_; }; const int Object::num_ = 200;
通过使用enum实现:
// 通过使用枚举来实现所有对象的常量 class Object { public: enum TYPE { TYPE_A = 10, TYPE_B = 20 }; /* enum TYPE_ { TYPE_A, // Error, 枚举中的枚举数不能重复定义 TYPE_B };*/ }; int main(void) { Object obj; // 引用枚举的三种方式 cout << Object::TYPE::TYPE_A << endl; cout << Object::TYPE_A << endl; cout << obj.TYPE_A << endl; return 0; }
- 拷贝构造函数
关于拷贝构造函数
功能:使用一个已经存在的对象来初始化一个新的同一类型的对象声明:只有一个参数并且参数为该类对象的引用如果类中没有说明拷贝构造函数,则系统自动生成一个缺省复制构造函数,作为该类的公有成员
拷贝构造函数调用的几种情况
#include <iostream> using namespace std; class Test { public: Test(int num = 0) : num_(num) { cout << "Default Construction " << num_ << endl; } Test(const Test &t) : num_(t.num_) { cout << "Copy Construction " << num_ << endl; } ~Test() { cout << "Deconstruction " << num_ << endl; } private: int num_; }; void Func1(const Test t) { } void Func2(const Test &t) { } Test Func3(const Test &t) { return t; } const Test &Func4(const Test &t) { return t; } int main(void) { Test t1; // Test t2(t1); // 1. 使用存在的对象初始化另一个对象,不会产生临时对象 //Test t2 = t1; // 1. 使用存在的对象初始化另一个对象,不会产生临时对象 //Func1(t1); // 2. 做函数参数,实参传给形参的过程调用拷贝构造,新参不是引用以及指针,产生临时对象,立即析构 //Func2(t1); // 引用不会调用拷贝构造 //Func3(t1); // 3. 做函数返回值,在return调用拷贝构造,没有对象来接收,立即析构掉 //Test t2 = Func3(t1); // 3. 做函数返回值,在return调用拷贝构造,有对象来接收,匿名对象变为有名对象,不会立即析构 /* Test t2; t2 = Func3(t1); //这种情况会产生临时对象,然后赋值,赋值完临时对象立即析构 */ //Test &t2 = Func3(t1); // 3. 做函数返回值,在return调用拷贝构造,引用去引用了临时对象,临时对象不会立即析构 //Test t2 = Func4(t1); // 1.在初始化时调用拷贝构造 const Test &t2 = Func4(t1); // 当然不会拷贝构造 cout << "......" << endl; return 0; }
-
深拷贝与浅拷贝
当类中有动态分配内存的指针成员变量时候,要进行深拷贝操作,如果默认使用浅拷贝,对象拷贝的时候,多指针变量指向同一块内存空间,当析构的时候,多次delete,会出错。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <cstring> using namespace std; class String { public: String(char *str="") { str_ = allocAndCpy(str); } String(const String &s) { str_ = allocAndCpy(s.str_); } ~String() { delete []str_; } void display() { cout << str_ << endl; } char *allocAndCpy(char *str) { int len = strlen(str) + 1; char *s = new char[len]; memset(s, 0, len); strcpy(s, str); return s; } String &operator=(const String &s) { // 因为这是赋值,所以本身已经有内存空间了,要先释放之前分配的内存 if (this == &s) // 这里只是判断自己赋值给自己 return *this; delete[]this->str_; this->str_ = allocAndCpy(s.str_); return *this; } private: char *str_; }; int main(void) { String str1("AAAA"); str1.display(); // 在我们没提供拷贝构造函数之前 //String str2 = str1; // 我们没有提供拷贝构造函数,使用默认的拷贝构造函数 // 发生运行时错误 // 在我们提供了拷贝构造函数之后 //String str2 = str1; // 不会出现运行时错误 //不光默认拷贝构造会出现浅拷贝,默认的=操作也是浅拷贝 // 我们没提供=操作重载之前 //String str2; //str2 = str1; // 发生运行时错误 // 我们提供=操作重载之后 String str2; str2 = str1; // 不会出现运行时错误 return 0; }
-
禁止拷贝
某些对象是独一无二的,不允许拷贝,只需要将拷贝构造函数与=重载的操作放到private下即可。 - 空类默认产生的成员
空类的大小为1个字节,默认产生的成员如下:
class Empty {}; Empty(); // 默认构造函数,不提供任意的构造函数(包括拷贝构造函数)才提供 Empty( const Empty& ); // 默认拷贝构造函数,不提供拷贝构造函数才提供 ~Empty(); // 默认析构函数 Empty& operator=( const Empty& ); // 默认赋值运算符 Empty* operator&(); // 取址运算符 const Empty* operator&() const; // 取址运算符 const