目录默认成员函数构造函数——初始化函数构造函数的特点带参构造函数的实例化使用编译器自动生成的构造函数构造函数总结补充初始化列表初始化列表的初始化顺序问题析构函数析构函数的特点编译器自动生成的析构函数拷贝构造函数拷贝构造函数的特点编译器自动生成的拷贝构造函数拷贝构造函数总结补充类型转换 与 拷贝消除explicit关键字多参数的隐式类型转换应用赋值拷贝函数/赋值重载函数赋值拷贝函数显示实现编译器自动生成的赋值拷贝函数取地址运算符重载默认成员函数如果一个类中什么成员都没有简称为空类。空类中并不是什么都没有任何类即使在什么都不写时编译器会自动生成以下6个默认成员函数。默认成员函数用户如果没有显式实现编译器会自动生成的成员函数在类中用户可以自己实现默认成员函数编译器也就不会再自动生成。构造函数——初始化函数注意构造函数的任务并不是开空间创建对象而是初始化对象class Date{public:void Init(){_year 1;_month 1;_day 1;}Date(){_year 1;_month 1;_day 1;}//由用户实现的构造函数private:int _year;int _month;int _day;}上述代码中Init()与Date()功能一样那为什么要引入构造函数呢原因在于构造函数可以在对象实例化时被编译器自动调用构造函数无法手动调用以保证每个数据成员都有一个合适的初始值并且在对象整个生命周期内只调用一次因为构造函数无法手动调用只会在定义对象时自动调用一次。构造函数的特点1. 函数名与类名相同。2. 无返回值。3. 对象实例化时编译器自动调用对应的构造函数。4.构造函数可以重载。5.构造函数禁止程序员显式调用只能在定义对象时自动调用一次对对象进行初始化带参构造函数的实例化使用class Date{public://注意构造函数支持函数重载Date(int year 1;int month 1;int day 1)//全缺省构造函数{_year year;_month month;_day day;}Data()//不带参的构造函数{_year 1;_month 1;_day 1;}//虽然全缺省构造函数和不带参构造函数构成函数重载但是无参调用时存在调用歧义private:int _year;int _month;int _day;}int main(){Date d1(2,2,2);//Date d2();//书写形式无法与函数声明区分//Date d3;//调用歧义return 0;}d1,d2,d2实例化时状况各有不同Date d1(2,2,2)调用带参构造函数Date d2()非正常使用原因在于可能与函数声明混淆Date d3调用默认构造函数不带参构造函数/全缺省构造函数 / 编译器生成构造函数本类中前两种默认构造函数重载实现存在调用不明确的问题由d3可以看出全缺省构造函数 可以覆盖 不带参构造函数的功能并且全缺省构造函数调用灵活便捷所以一般情况下类中都会实现一个全缺省构造函数。编译器自动生成的构造函数引入概念1. C将数据分为内置类型基本类型和自定义类型内置类型基本类型int / char / double / 指针 ......自定义类型class / struct / union ......2.默认构造函数默认构造函数无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数总结不传参就可以调用的构造函数就是默认构造函数并且三种默认构造函数不能共存一个类中。上文提到若用户未显示实现构造函数编译器会自动生成构造函数那编译器生成的构造函数究竟会将对象初始化成什么样子呢class Date{public:void Print(){cout _year - _month- _day endl;}private:int _year;int _month;int _day;};int main(){Date d1;d1.Print();return 0;}打印结果-858993460--858993460--858993460由此可以看出编译器自动生成构造函数实际上并未对对象进行初始化为什么(部分编译器可能会初始化成0)编译器自动生成的构造函数对于内置类型成员变量没有规定要不要处理有的编译器可能会处理对于自定义类型成员变量才会调用它自己的默认构造函数进行初始化若此时不存在默认构造函数则报错。class A{//...};classB{public:B(): _a(){//_a.A();}private:int _b;A _a;};注意C11 中针对编译器自动生成的构造函数不对内置类型成员进行初始化的缺陷又打了补丁即内置类型成员变量在类中声明时可以给缺省值如下class Date{private:int _year 1;int _month 1;int _day 1;};//首先根据 缺省值 进行初始化缺省值应用于初始化列表如果函数体内重新赋值则覆盖该初始化。构造函数总结1. 一般情况构造函数都需要我们自己显示得去实现2. 只有少数情况下可以让编译器自动生成构造函数如MyQueue补充初始化列表首先直接给出示例class MyQueue {public:// 初始化列表本质可以理解为对象中的每个成员定义的地方MyQueue(int n, int rr)//初始化列表初始化区域: _pushst(n) // Stack 无默认构造必须初始化列表初始化, _popst(n) // Stack 无默认构造必须初始化列表初始化, _x(1) // 普通成员可在初始化列表初始化, _ref(rr) // 引用成员必须在初始化列表初始化, _c(1) // const成员必须在初始化列表初始化{//函数体内赋值区域_size 0; // 普通成员可在函数体赋值}private:// 自定义类型成员若无默认构造必须显示在初始化列表初始化Stack _pushst;Stack _popst;// 普通成员int _x;int _size;// 引用成员必须在初始化列表初始化int _ref;//const 成员必须在初始化列表初始化const int _c;};总结必须在初始化列表初始化的成员变量没有默认构造函数的自定义类型的成员必须显式传参构造。引用类型的成员必须在初始化时绑定对象无法在函数体赋值。const修饰的成员必须在初始化列表初始化。因为三者的定义与初始化无法分开所以必须在初始化列表类似定义的地方进行初始化不论写不写初始化列表甚至无论写不写构造/拷贝构造构造函数的初始化列表都是存在的与构造绑定每个成员变量也都会先走一遍初始化列表。若一些初始化成员未在初始化列表显示写出来则对于内置类型有缺省值用缺省值没有则不做处理具体看编译器对于自定义类型则调用它的默认构造没有默认构造则编译报错。再走函数体赋值缺省值会被初始化列表中显示写的值覆盖//C11中自定义类型也可以设置缺省值class AA{public:AA(int a1 1,int a2 1): _a1(a1), _a2(a2){}private:int _a1;int _a2;};class B{public:B():a(1),b((int*)malloc(40)),aa({ 1,2 })//使用{1,2}构造AA类型临时对象临时对象拷贝构造aa优化为使用{1,2}直接构造aa{}private:int a 1;int b (int*)malloc(40);AA aa { 1,2 };//缺省值只会应用于初始化列表在本行无任何意义};对于上述代码大家可能会产生一个疑问自定义类型在初始化列表进行初始化时难道都是先调用构造创建临时变量再调用拷贝构造其实不是这样的自定义类型在初始化列表都是直接调用构造/拷贝构造的只有外部缺省值传递给初始化列表才会构造临时变量再进行拷贝构造。如上例aa({ 1,2 })如果不传缺省值使用aa(1, 2)就可以直接调用它的构造函数进行初始化对于自定义类型成员无论是否存在默认构造函数都要在初始化列表初始化只是一个无需传参所以可以隐式实现一个必须传参所以必须显示实现。自定义类型的定义与初始化无法分离无法脱离定义单独调用构造函数实践中尽可能使用初始化列表初始化不方便的再使用函数体赋值初始化列表的初始化顺序问题成员变量在类中声明次序就是其在初始化列表中的初始化顺序与其在初始化列表中的先后次序无关。class A { public: A(int a) :_a1(a) ,_a2(_a1) {} void Print() { cout_a1 _a2endl; } private: int _a2; int _a1; }析构函数注意析构函数不是完成对对象本身的销毁局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数完成对象中资源的清理工作malloc,fopen等。class Stack{public:void Destory(){if (_array){free(_array);_array NULL;_capacity 0;_size 0;}}~Stack(){if (_array)//此处判断用处支持用户显示调用后续编译器自动调用不会进入if{free(_array);_array NULL;_capacity 0;_size 0;}}//由用户实现的析构函数private:int* _array;int _top;int _size;}析构函数的特点1. 析构函数名是在类名前加上字符 ~。2.无参数无返回值类型。3.一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。注意析构函数不能重载4. 对象生命周期结束时C编译系统自动调用析构函数用户可自己调用析构函数,先定义的后析构后定义的先析构符合栈析构函数用于完成资源清理在一些不需要消耗资源的类中如Date类就不需要析构函数,即用户无需自己去实现析构函数。编译器自动生成的析构函数与构造函数类似1. 对内置类型不做处理2. 对自定义类型调用它自己的析构函数析构是否需要显示实现的总结需要显示实现有资源需要显示处理如Stack / List不需要显示实现a.没有内存需要清理如Dateb.没有需要资源清理的内置成员如MyQueue拷贝构造函数拷贝构造函数的特点1. 拷贝构造函数是构造函数的一个重载形式2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用使用传值方式编译器直接报错因为会引发无穷递归调用class Date{public:Date(int year 1, int month 1,int day 1){_year year;_month month;_day day;}Date(const Date* d)//普通构造函数不影响编译器自动生成拷贝构造函数{_year d-_year;_month d-_month;_day d-_day;}Date(constDated)//拷贝构造函数{_year d._year;_month d._month;_day d._day;}private:int _year;int _month;int _day;};int main(){Date d1;Date d2(d1);//调用普通构造函数//下面两行等价均调用拷贝构造函数Date d3(d1);Date d4 d1;return 0;}为什么不能使用传值调用//单个类类型形参编译器将其视作错误的拷贝构造函数而不是普通构造函数Date(Date d){_year d._year;_month d._month;_day d._day;}//改变形参列表可以将其变为 普通构造函数 / 拷贝构造函数关键原因在于自定义类型传值传参需要调用拷贝构造函数由此引发无穷递归调用编译器自动生成的拷贝构造函数若未显式定义编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝这种拷贝叫做浅拷贝或者值拷贝。注意在编译器生成的默认拷贝构造函数中内置类型是按照字节方式直接拷贝的而自定义类型是调用其拷贝构造函数完成拷贝的。拷贝构造函数总结无需实现拷贝构造函数1. 无资源管理如Date2. 类成员为自定义类型成员和 无指向资源的内置类型成员如MyQueue3. 一般情况下不需要写析构函数就不需要写拷贝构造函数需要实现拷贝构造函数1. 类成员存在内置成员指针/一些值指向资源需要深拷贝进行拷贝如StackQueueList等补充类型转换 与 拷贝消除class A {public:A(int a): _a(a){cout A(int) 构造函数被调用 endl;}A(const A other): _a(other._a){cout 拷贝构造函数被调用 endl;}private:int _a;};int main(){A aa1(1); // 直接构造A aa2 aa1; // 拷贝构造A aa3 3;//使用3构造一个A类型的临时对象隐式类型转换再用这个临时对象拷贝构造aa3constA raa 3//raa引用的是类型转换中用3构造的临时对象不涉及拷贝构造return 0;}编译器遇到连续构造拷贝构造 - 优化为直接构造一般情况下如果不优化也是正常现象explicit关键字若需要禁止内置类型隐式类型转换为自定义类型可以在构造函数前加上explicit关键字多参数的隐式类型转换class A{public:A(int a1 1,int a2 1): _a1(a1), _a2(a2){}private:int _a1;int _a2;};int main(){A aa { 5,6 };return 0;}应用liststring lt;string s1(1111);lt.push_back(s1); // 传入已存在的 string 对象lt.push_back(1111); // 传入 const char*隐式转换为string类型临时变量赋值拷贝函数/赋值重载函数首先区别于拷贝构造函数赋值拷贝函数用于已经初始化变量的赋值拷贝构造函数用于未初始化变量的构造。Date d1(2026, 3, 26);Date d2 d1;//拷贝构造Date d3;d3 d1;//赋值拷贝赋值拷贝函数显示实现class Date{public:Date(int year 1, int month 1, int day 1){_year year;_month month;_day day;}Dateoperator(const Date d)//返回*this从而支持连续赋值(连续赋值从右向左){//判断自己给自己赋值避免损失注意使用地址判断if (this ! d){_year d._year;_month d._month;_day d._day;}return *this;//*this出了当前函数作用域不会析构生命周期未到最好传引用返回减少拷贝和析构//自定义类型传值返回需要调用拷贝构造生成临时变量临时变量生命周期结束时调用析构函数}private:int _year;int _month;int _day;};int main(){Date d1;Date d2;Date d3(2026, 3, 26);d1 d2 d3;return 0;}注意赋值拷贝函数作为默认成员函数不能实现为全局函数编译器自动生成的赋值拷贝函数用户没有显式实现时编译器会生成一个默认赋值运算符重载以值的方式逐字节拷贝。注意内置类型成员变量是直接赋值的而自定义类型成员变量需要调用对应类的赋值拷贝函数完成赋值。类似拷贝构造取地址运算符重载class A {public:// 普通对象返回 A*A* operator() {cout A* operator() endl;return this;}// const 对象返回 const A*const A* operator()const{ //const修饰this指针构成重载cout const A* operator() const endl;return this;}private:int a;};实践中无需程序员手动实现直接由编译器自动生成即可