•类的非静态成员初始化手段:
① 通过默认成员初始化器 ② 用构造函数的成员初始化器列表 ③ 在构造函数体内进行赋值操作。
默认成员初始化器:
初始化器列表:
和构造函数在一起,初始化器列表在构造函数申明后,以冒号开头,后跟一系列以逗号分隔的成员初始化器,再之后才是构造函数的函数体,例如:
使用初始化器列表时,首先会按声明顺序初始化成员,然后执行构造函数函数体。
成员初始化器:
成员初始化器包括默认成员初始化器和初始化器列表
必须使用成员初始化器的情况:
1.常量成员,因为常量只能初始化不能赋值,所以要使用初始化器
2.引用成员,引用必须在定义的时候初始化,并且不能重新赋值,所以也要使用初始化器
用初始化列表时数据成员初始化的顺序:
是按照它们在类中声明的顺序进行初始化的,而不是按照它们在初始化器列表出现的顺序初始化的。
出错例子 :
这里i的值是未定义的。因为虽然j在初始化器列表里面出现在i前面,但是i先于j 定义,所以先初始化i,而i由j初始化,此时j尚未初始化,所以导致i的值未定义。
•因此要写代码养成按照成员声明的顺序进行初始化的好习惯。
类的非静态数据成员的初始化的综合例子:
1)静态存储区域。这块内存在程序编译的时候就已经分配好,在程序的整个运行期间都存在。例如全局变量,static 变量。
2)栈。函数在被执行时内部局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。
3)堆,亦称动态内存分配。程序在运行的时候用malloc 或new 申请的存在堆上。
结构体存储在栈中;类的实例化可以存储在栈中,也可以存储在堆中
⚫ 效果:用一个已存在的该类对象初始化新创建的对象。
如在已经定义了obj对象的前提下,创建同类的objb对象:
C objb(obj); //或 C objb = obj ; 两者等价(注意:不是赋值运算)
⚫ 定义形式:形参类型为该类类型本身且参数传递方式为按引用传递。
比如C::C(const C& obj);
• 该参数传递方式为按引用传递,避免在函数调用过程中生成形参副本。
• 该形参声明为const,以确保在拷贝构造函数中不修改实参的值
⚫ 存在性:每个类都必须有拷贝构造函数:
• 用户可根据自己的需要显式定义拷贝构造函数。
• 若用户未提供,则该类使用由系统提供的缺省拷贝构造函数
• 可用=default,也可用 =delete 弃置该函数。
• 缺省拷贝构造函数会按照初始化顺序,对对象的各基类和非静态成员进行完整的逐成员复制,完成新对象的初始化。即逐一调用成员的拷贝构造函数, 如果成员是基础类型,则复制值。
拷贝构造函数的调用:
1. 显式调用:
用声明 C objb(obj) 或 new C(obj) 会触发拷贝构造函数
2. 隐式调用:
•函数的形参是对象时。把对象作为实参,传递给被调函数的形参对象,会调用拷贝构造函数。 (注意形参对象生存期结束被撤销时,还会调用析构函数)
•(理论上)函数返回结果时对象时。会自动生成一个临时对象来保存函数返回结果,创建此临时对象用到拷贝构造函数。 (注意当函数调用表达式结束后,撤销该临时对象时,会调用析构函数)
但实际g++有编译优化(叫复制省略),即将函数返回的临时对象直接写在接受者的地址上,不再自动创建临时对象,因此g++编译优化下不会调用拷贝构造函数。
需要自定义拷贝构造函数的情况:
(对于不含指针成员的类,使用编译器的默认拷贝构造函数即可),但只要含指针成员,就要用自定义拷贝构造函数。
因为缺省拷贝构造函数使用浅复制策略,浅拷贝只复制成员指针的值,而不复制指向的对象实体,导致新旧对象成员指针指向同一块内存。
含指针成员的类应重写以下内容:
• 用深复制策略,在拷贝构造函数中给指针分配内存
• “=” 操作重写,完成对象深复制策略
深复制策略:(用new分配内存给指针)
重写“=”操作:(注意这个“=”不是赋值运算)