构造函数
拷贝构造函数
析构函数
赋值操作符重载
取地址操作符重载
const修饰的取地址操作符重载
构造函数
构造函数没有this指针。
在创建对象时,由编译器自动调用,并且在对象的生命周期内只调用一次(eg:出生一次),完成对象的构造以及初始化。
成员变量为私有的,要对它们进行初始化,必须用一个公有成员函数来进行。同时这个函数应该有且仅在定义对象
时自动执行一次,这时调用的函数称为构造函数(constructor)。
const不能修饰构造函数。
构造函数体内,对成员变量进行赋初值,而不是初始化。在函数名后面括号内的是成员变量的初始化列表。
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个”成员变量”后面跟一个放在括号中的初始值或表达式。
this为什么不能指向初始化列表中的成员?
因为不清楚各个成员要指向的空间大小。
explicit(可显的)
清除单个参数的构造函数的隐式类型转换。
对于单个参数构造函数,可以将其接收参数转化成类类型对象。用explicit修饰构造函数,抑制由构造函数定义的隐式转换,explicit
关键字类内部的构建声明上,在类的定义体外部的定义上不再重复。
注意:初始化列表仅用于初始化类的数据成员,并不指定这些数据成员的初始化顺序,数据成员在类中定义顺序就是在参数列表中的初始化顺序
尽量避免使用成员初始化成员,成员的初始化顺序最好和成员的定义顺序保持一致。
初始化列表中各个成员变量的出现先后次序与初始化次序无关,按照成员在类中的声明次序进行初始化。
建议:1.初始化列表中的成员次序与成员在类中的声明次序保持一致;
2.尽量避免使用成员初始化成员; (可能造成访问到随机值)
3.每个成员在初始化列表中只能出现一次。
验证初始化和赋值
在成员函数内部对某个成员变量重新赋值,可以赋值成功,说明这并不是初始化,因为初始化只有一次。
类中包含以下成员,一定要放在初始化列表位置进行初始化:
1.引用成员变量(引用需要初始化,但是编译器不知道该变量是什么类型)
2.const成员变量
3.类类型成员(该类有非缺省的构造函数)
编译器合成的构造函数是没有参数的。
默认构造函数:
编译器对是否合成构造函数进行了优化,编译器根据自己的需求选择性的合成构造函数,即如果类中没有显式
定义构造函数,在编译器需要且有能力合成时,会合成一个无参的构造函数。
编译器感觉自己需要(4个场景),一定会合成无参构造函数,一定有其特定的作用。
A(有缺省的构造函数) B(没有显示定义):包含A类的对象。
Time类显式定义了其缺省的构造函数,Date类未显式定义构造函数,其中包含了一个Time类对象。当创建Date类
对象时,编译器需要调用Date类的构造函数完成Date类对象的构造,由于Date未显式定义,此时编译器必须合成:因为Time类的构造函数存在,
合成Date构造函数的目的就是为了完成Data类对象中包含的Time类对象的构造。
但是,如果将Time类缺省的构造函数改成非缺省构造函数编译器就没有能力合成,此时会报错。
时间类对象存在,日期对象也存在,调用时间类对象时,如果没有构造函数就需要合成无参构造函数。
编译器传参(类 类型)优化
传参:const T& (少创建一个对象,提高效率)
返回值:能返回引用,尽量返回引用类型。
注意:无法通过初始化列表初始化静态成员变量。
构造函数是特殊的成员函数,其特征如下:
1. 函数名与类名相同。
2. 无返回值(不能出现返回值)。
3. 对象构造(对象实例化)时系统自动调用对应的构造函数。
4. 构造函数可以重载。
5. 构造函数可以在类中定义,也可以在类外定义。
6. 如果类定义中没有给出构造函数(显示定义),则C++编译器自动产生一个缺省的构造函数(无参函数),但只要我们定义了一个构造函
数,系统就不会自动生成缺省的构造函数。 编译器不知道传什么类型的参数,因此是无参函数。
7. 无参的构造函数和全缺省值的构造函数都认为是缺省构造函数,并且缺省的构造函数只能有一个。
8. 创建对象的时候就有了构造函数。 // call Date::Date
9. 构造函数在类外定义和类内定义的区别就是在类内的构造函数可能被当做内联函数处理。
若缺省参数声明和定义分离,则可以在声明或定义中给默认参数。
析构函数
对象的生命周期结束时,由编译器自动来调用,清理对象中的资源。 析构函数不能被const修饰
当一个对象的生命周期结束时,C++编译系统会自动调用一个成员函数,这个成员函数即析构函数(destructor),
构造函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符~。
2. 无参数无返回值。(可以加void)
3. 一个类有且只有一个析构函数。若未显示定义,系统会自动生成缺省的析构函数。(没有参数,所以不能重载) 只调用一次
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
注意:析构函数体内不是删除对象,而是做一些对象删除前的相关清理工作。(栈空间还在,析构函数用来释放资源)系统自动收回空间。
先析构最后调用的,然后析构之前调用的。(在栈上创建)
类的拷贝构造函数
创建对象时使用同类对象(已存在的对象)来进行初始化,这时所用的构造函数称为拷贝构造函数(Copy Constructor)。
int a = 10;
int b(a);
反汇编代码
int a = 10;
00E2138E mov dword ptr [a],0Ah
int b(a);
00E21395 mov eax,dword ptr [a]
00E21398 mov dword ptr [b],eax
eg: Date d1(2018, 7, 29);
Date d2(d1);
用已经存在的对象去创建新对象时,由编译器自动调用。
拷贝构造函数是特殊的成员函数,其特征如下:
1. 拷贝构造函数其实是一个构造函数的重载,只有一个参数(类 类型对象的引用)。
2. 拷贝构造函数的参数必须使用引用传参,使用传值方式会引发无穷递归调用。
3. 若未显示定义,系统会默认生成缺省的拷贝构造函数。 缺省的拷贝构造函数会按照成员的声明顺序依次拷贝类成员进行初始化。
4. 参数类型是类 类型对象的引用,一般情况下用const 类类型引用(Date &),(拷贝但是不希望改变实参,加const) 只调用一次
5. 若显示定义拷贝构造函数,则建议自己赋值。(带有资源的类,一定要重新赋值。)
为什么不能传值,而要传引用?
Date d2(d1); ---> call Date::Date(d1);
用已经存在的对象去创建新的临时对象,引发无穷递归调用。
d2 = d1; // 浅拷贝
注意:如果形参名和成员变量名冲突时,在成员函数内部加上this指针。
对于类类型的成员,会使用其拷贝构造函数来拷贝,内置类型则直接拷贝。
拷贝构造函数实际上内部采用了浅拷贝。当释放多个资源时,就会报错,相当于free多次,出现野指针。
浅拷贝:复制对象的全部内容(按照对象的存储顺序)。
运算符重载
为了增强程序的可读性,C++支持运算符重载。
运算符重载的特征为:
1. operator + 合法的运算符 构成函数名(举例:重载<运算符的函数名:operator< )。
2. 重载运算符以后,不能改变运算符的 优先级/结合性/操作数个数。
返回*this 生命周期比栈的生命周期长。 不返回对象d的原因:为了连续赋值的顺序。
运算符重载返回一个值,不返回引用,返回引用的话会覆盖掉之前的值。
重载要求至少有个自定义类型的对象。
5个C++不能重载的运算符是哪些?
.* / :: / sizeof / ?: / .
类的赋值操作符重载
赋值运算符的重载是对一个已存在的对象进行拷贝赋值。
1.返回类的引用 Date& operator=(const Date& d) 为了连续赋值
eg: d3.operator=(d2.operator=(d1));
2.是否自己给自己赋值?
通过查看当前对象地址是否与this一样。
++重载
没有参数的是前置++(返回当前对象引用),有一个参数(int)的是后置++
(虽然有参数,但是并没有传参数,push 0,返回旧对象,不是返回引用)。
作业:
Date operator+(int days); // 计算100天之后的日期
1.天数加到临时日期上
2.对临时的日期进行调整,检测临时对象的天数是否超过本月的天数
Date operator-(int days); // 计算100天之前的日期
int operator-(const Date& d); // 计算两个日期之间相差的天数
Date& operator--(); // 前置--重载
Date operator--(int); // 后置--重载
const修饰类成员
1.成员函数
void PrintDate()const --> void PrintDate(const Date * const this)
成员函数加上const后,如果试图修改成员变量的值,编译器就会报错。
在成员函数后面加const,const修饰this指针所指向的对象,也就是保证调用这个const成员函数的对象在函数内
const修饰成员函数,实际上限制的是this指针。
不会被改变。
2.成员变量
必须在构造函数初始化列表中初始化。
1. const对象可以调用非const成员函数和const成员函数吗?
不可以调用非const成员函数,可以调用const成员函数。
2. 非const对象可以调用非const成员函数和const成员函数吗?
都可以调用。
3. const成员函数内可以调用其它的const成员函数和非const成员函数吗?
const成员函数内可以调用其他的const成员函数;
非const成员函数内可以调用其他的const成员函数和非const成员函数。
mutable 可修改const修饰的成员函数中的成员变量。
mutable是为了突破const的限制而设置的,可以用来修饰一个类的成员变量。被mutable修饰的变量,将永远处于可变的状态,
即使是const成员函数中也可以修改这个变量的值。
取地址操作符重载和const修饰的取地址操作符重载
这两个默认的成员函数一般不用重新定义,编译器会默认生成。
友元分为:友元函数和友元类
1. 友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不
属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
友元函数说明:
1>友元函数可访问类的私有成员,但不是类的成员函数
2>友元函数不能用const修饰(不能通过对象调用,没有this指针)
3>友元函数可以在类定义的任何地方声明,不受类访问限定符限制
4>一个函数可以是多个类的友元函数
5>友元函数的调用与普通函数的调用和原理相同(可以不通过对象去调用)
2. 友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个
类中的非公有成员
3. 优缺点:
优点:提高了程序运行效率(不需要开辟栈帧)
缺点:破坏了类的封装性和隐藏性
4. 注意:
1>友元关系不能继承
2>友元关系是单向的,不具有交换性
3>友元关系不能传递
如何知道一个类创建了多少个对象?以下两种方式是否可行,为什么?
1. 在类中添加一个普通的成员变量进行计数 // 计数出错
2. 使用一个全局变量来计数 // 可以,但是不好,破坏了类的封装,任何人都可以修改
静态成员static
static修饰成员变量时,找不到符号表中的变量。
静态的成员变量一定要在类外进行初始化。
对象.静态成员变量名称--->类名::静态成员变量名称
对象.静态成员函数名称--->类名::静态成员函数名称
不能在构造函数初始化列表(属于具体对象的成员(非静态成员变量))
sizeof求类的大小时,不包括静态成员变量的大小。
由于静态成员变量在类中,那么可以直接修改。 (对象.静态成员变量)
静态成员变量存储在.data段
类一旦写完,静态成员变量的空间就有了。
const不能修饰静态成员函数。(没有this指针)
特性
1.静态成员为所有类对象所共享,不属于某个具体的实例;(不依赖于对象)
2.类静态成员即可用类名::静态成员或者对象.静态成员来访问;
3.类静态成员变量必须在类内声明,在类外定义,定义时不添加static关键字;(需要在类外重新定义,加上作用域限定符::)
4.类的静态成员函数没有默认的this指针,因此在它里面不能使用任何非静态成员;
5.静态成员和类的普通成员一样,也有public、protected、private 3种访问级别,也可以具有返回值,const修饰符等参数。
问题
1. 静态成员函数可以调用非静态成员函数吗?
不可以。(静态成员函数参数部分没有this指针,不能访问非静态成员(普通成员变量和成员函数))
2. 非静态成员函数可以调用类的静态成员函数吗?
可以。