目录
1. 类的6个默认成员函数
对于类的成员函数,有6个重要的函数,即使没有显示定义,编译器也很会默认生成这6个函数,根据它们的任务,可分成三种:
初始化和清理任务:
构造函数->完成初始化工作
析构函数->完成资源的清理工作
拷贝和复制任务:
拷贝构造函数->用同类对象初始化创建对象
赋值重载函数->将一个对象赋值给另一对象
取地址重载:
两个&运算符重载函数->普通对象和const对象的取地址
2. 构造函数
当类的对象创建时,就会调用构造函数来初始化对象的数据成员,但是构造函数的任务不是为对象开辟空间,而是初始化对象。
构造函数特性:
1. 函数名与类名相同
2. 无返回值,也不写void
3. 构造函数不用显式调用(对象实例化时编译器自动调用对应的构造函数初始化对象)
4. 构造函数可以重载(类可有多个构造函数)5.构造函数不能被声明成const(初始化过程后对象才有常属性)
当用户未显示定义构造函数时,编译器会合成一个默认的无参构造函数,只要用户显示了一个便不生成。这个合成的默认构造函数会默认初始化内置类型的成员(如果有默认值,则按默认值初始化,C++11支持的),对于自定义类型的成员则去调用它们的默认构造函数。
注意:内置类型就是C++语言提供的数据类型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型。
当不传递参数去创建对象时,会调用默认构造函数,显然构造函数有且只能有一个。
有以下三种:
1.我们没定义构造函数,编译器合成的默认函数。
2.我们定义的全缺省构造函数
3.我们定义的无参构造函数。
如果我们定义了两个,调用时就会发生歧义。
虽然编译器会合成默认构造函数,更多时候则要自己写,其一,我们如果自己定义了构造函数,没有定义构造函数,类就没有默认构造函数。其二,编译器的默认构造函数初始化的值是默认的,可能不是我们想要的。其三,如果类的自定义类型的成员没有默认构造函数,编译器也无法合成默认构造函数。
3. 析构函数
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象的销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
析构函数特性:
1.函数名:类名+~
2.无返回值,也不写viod
3.一个类只能有一个析构函数,故不能重载,若不显式定义,编译器会默认合成一个。
4.不可显式调用,对象生命周期结束,编译器自动调用。
编译器合成的析构函数,对于内置类型的成员不处理,对于自定义类型的成员也去调用它的析构函数,但与编译器合成的默认构造函数调用顺序相反:如下例
如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数即可。
4. 拷贝构造函数
有时候需要用已存在的对象来拷贝生成一份新的对象,这时拷贝构造函数就派上用场了。
拷贝构造函数特征:
1.拷贝构造函数也是构造函数,它是构造函数的重载形式。
2.只有一个形参且为同类对象的引用(一般为const修饰),不能是传值的方式,不然传参时将无穷递归调用该函数。
3.若未显式定义,编译器将默认生成一个浅拷贝的默认构造函数(按字节序去拷贝)
4.若类中有申请资源,用编译器合成的默认拷贝构造函数,完成的是浅拷贝,不能满足需求,还会引发问题,这时应当自己定义。
深浅拷贝
例如成员指针变量指向一块堆区的空间,当调用默认的拷贝构造函数时,完成的是浅拷贝,生成的新的对象的成员指针变量于原来的指针变量一样,也就是指向了同一块堆区空间,这样当修改新对象时,原来的对象也将发生改变,当析构函数释放这一堆区空间时,两个对象将分别释放各自指针指向的堆区空间,导致两次释放的问题。
要解决浅拷贝引发的问题,就要进行深拷贝,重新开辟新的对象的指针指向的堆区空间,再将原来的对象指针指向的内容拷贝进来,这样就能解决浅拷贝的两个问题。
示意图:浅拷贝
深拷贝:
![](https://i-blog.csdnimg.cn/blog_migrate/cbdfd2de04b1557cd7078704f75b4c0e.png)
5. 赋值运算符重载
运算符重载:
为增强代码的可读性,C++引入运算符重载,让自定义类型也能进行运算。
运算符重载特征:
1.函数原型是:返回值类型 operator操作符(参数列表),与普通函数类似
2.函数名是operator操作符,也能函数重载(函数重载和运算符重载不是一回事)
3.不能用别的符号进行运算符重载,只能用除.* :: sizeof ?: .五个运算符以外的运算符来重载。
4.不能改变内置类型操作符的含义,例如整形的加减的含义,不能改变。
5.与普通类的成员函数一个,作为类的成员函数时,第一个参数默认为隐藏的this指针。
倘若要访问类的私有成员,一般重载为类的公有成员函数;或者重载成全局函数,再作为类的友元函数去访问,但这样破坏了封装。但是因为成员函数首参数隐藏this指针的问题,让重载的二元操作符的左操作数固定为this,有时不太合乎需求,就重载成全局函数。
赋值运算符的重载
赋值运算符的重载是特殊的,它只能重载成类的成员函数。因为倘若重载成全局函数,会与类内默认生成的赋值运算符重载冲突,与拷贝构造函数类似,默认生成的赋值运算符重载完成的是浅拷贝,倘若会产生问题,就当自己定义一个深拷贝的赋值运算符重载。
如果用户没有显式实现时,编译器会生成一个默认赋值运算符重载,在调用该函数时,类的内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
注意:用 = 去初始化对象,调用的是拷贝构造函数而不是进行赋值,赋值运算符运用于两个已经存在的对象。
前置++和后置++
前置后置含义不同的一元操作符的重载,前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载,C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递,以区分前置和后置。
因为前置++要返回对象++之后的值,为了提高效率,通常用引用返回,故前置++返回的是原对象的别名,能++++对象,这样调用。而后者++要返回的是对象++之前的值,因为实现后置++函数时,对象须在返回前++,故通过临时对象拷贝原对象,++原对象,再返回临时对象的方式实现,故进行值返回,返回临时对象的引用会发生越界访问的问题,因为临时对象返回值后就析构了。
例如:
#include<iostream>
using namespace std;
class A {
public:
A& operator++()
{
++a;
return *this;
}
A operator++(int)
{
A tmp=A(*this);
++a;
return tmp;
}
void print()
{
cout << "a==" << a<<endl;
}
private:
int a=0;
};
int main()
{
A a1;
A a2;
++++a1;
A a3=a2++;
a1.print();
a2.print();
a3.print();
return 0;
}
输出:
![](https://i-blog.csdnimg.cn/blog_migrate/1aad9c22b20b05859c7fc065548c5cc3.png)
6. const成员函数
这个成员函数不是返回值是const修饰的,而是this指针被const修饰,这样一来this指向的对象不可改变,但是this是隐藏的参数,C++将const放在函数名和()的后面,例如:
假设类名是A,对于A类的普通对象,this指针类型是A*const this,如果用它去调用const成员函数,原本A是可读可写的权限,现在变成只读的权限,权限缩小了,可以调用,倘若是一个常对象(也就是const修饰的对象),那么它的this指针类型就是const A *const this,常对象去调用const成员函数没什么问题,因为参数this的类型一模一样,若调用非const成员函数就会发生权限放大的问题,例如:当const对象a4调用非const成员函数print时编译器报错了>>
总之,常对象只能调用const成员函数,调用const成员函数就不能修改this指向的对象。
7. 取地址及const取地址操作符重载
当定义了类后,如果实例化了普通对象和const对象,对它们使用 & 取地址便能获得对象的地址,例如:
事实上是编译器默认生成了两个&取地址操作符重载函数(两个函数构成函数重载),因为const对象不能调用非const成员函数,所以各生成了一个,以适应需求,用户也可根据想实现的功能显示去写,例如: