前言
本篇文章主要讲C++类与对象中涉及默认成员函数的内容,所谓默认成员函数其实就是类与对象中六大函数的统称,六大函数分别是构造函数、析构函数、拷贝构造函数、赋值重载、取地址重载以及const修饰对象的取地址重载。本文主要集中讲前四个,后面有时间会专门写一篇有关其地址重载和const修饰对象的取地址重载的文章。
构造函数
概念及作用:构造函数是类与对象中用于为新建的对象赋初始值的一种特殊函数。
构造函数的特点:
1、构造函数无返回值
2、构造函数的函数名与类名相同
3、只能定义在public作用域下
4、无需手动调用来初始化对象,编译器在创建新对象时自动调用构造函数用于初始化
构造函数的分类:
1、默认构造函数
2、有参构造函数
3、拷贝构造函数(这里仅作介绍,下面会详细讲)
1、默认构造函数
默认构造函数有两种:1、无参构造函数,2、有参且参数全缺省(所有参数都有默认值)
默认构造函数的功能:利用类来实例化对象时,编译器自动创建默认构造函数来初始化新对象。
如图上所示,编译器直接指明包含有多个默认构造函数,也就是说除了无参默认构造外,有参且全缺省的构造函数也是默认构造。
2、有参构造函数
有参构造函数是能够按照用户意愿对成员属性赋初值的函数,在利用类创建对象时编译器会直接生成并调用,但编译器自动生成的有参构造可能会存在隐患,如某些属性未初始化,或者本来需要在堆区申请空间的编译器直接在栈区申请了等等......因此,有参构造需要手动添加,当手动添加完后编译器就不会自动再申请一个有参构造了,当然也不会调用自动生成的有参构造。
2.1、有参构造的的特点:
1、有参构造函数可以有多个参数
2、有参构造参数不可全缺省
3、有参构造函数可以发生重载
有参构造函数的功能:利用类实例化对象时,可以按照用户需求来初始化新对象的成员属性。
2.2、有参构造的初始化列表:
语法:构造函数():属性1(值1),属性2(值2),属性3(值3).....{}
作用:C++提供初始化列表的方式来初始化成员属性,利用初始化列表来初始化属性能够提高代码的简洁性与可读性。
注意:初始化列表的初始化顺序是按照定义的顺序,而非按照列表上的顺序,务必注意!!!
下面详细带大家看看这个注意点防止遗忘
如图,按照初始化列表顺序初始化的逻辑来看,这串代码的意思是先拿age1初始化m_age2,接着再拿m_age2来初始化m_age1,显然是没什么问题的。但打印出来的是乱码,也就是说m_age2并没有被初始化就用来为m_age1赋值了,也就是说是先执行了图中的1,再执行2,而这恰好是属性的定义顺序,这也就证明了属性的定义顺序决定了初始化列表的赋值顺序。
以上代码修改后如下:
3、拷贝构造函数
拷贝构造函数是利用一个已存在的对象来创建并初始化新对象时编译器自动调用的函数,是特殊的有参构造函数。
拷贝构造函数的功能:利用一个已存在的对象来初始化新对象
拷贝构造的特点:
1、可以有多个参数
2、第一个参数必须为该类对象的引用
3、使用拷贝构造创建对象时自动调用
以上是构造函数的所有内容,值得一提的是拷贝构造函数并没有讲完,这里只是对拷贝构造函数的实现和使用进行讲解,后文会专门开一个专题来详细叙述有关拷贝函数的一些细节。
析构函数
析构函数的特点 :
1、无参数且无返回值
2、函数名是类名前加上~
3、一个类只有一个析构函数
4、定义在public作用域下
5、对象的生命周期结束时,系统会自动调用析构函数
析构函数的功能:在对象销毁时,清理该对象在内存中占用的资源,防止资源浪费
语法:~类名(){}
拷贝构造
前面演示了拷贝构造的实现和调用,下面来深究拷贝构造的细节语法
拷贝构造的调用时机:
1.利用已知的对象创建新对象时,会调用拷贝构造
2.传值调用函数时,会因拷贝对象时调用拷贝构造
3.传值返回函数中的局部对象时,会调用拷贝构造
//拷贝构造的传参
class Person
{
public:Person(int age){m_age = age;}Person(const Person& p){cout << "拷贝函数的调用" << endl;m_age = p.m_age;}Person& operator=(const Person& p){//赋值重载m_age = p.m_age;cout << "赋值重载" << endl;return *this;}
private:int m_age;
};void fun(Person p)//传值传参会调用拷贝构造
{;
}Person func()
{Person p(10);return p;//此处调用拷贝构造
}void test()
{Person p1(10);Person p2(p1);//1.利用已知对象创建新对象fun(p1);//2.利用传值方式调用函数func();//3.利用传值的方式返回对象
}
以传值的方式调用函数和以传值的方式返回对象都会调用拷贝构造,为了提高效率一般都会用传引用的方式来传递参数以及传返回值。
那么问题来了,既然传值调用函数需要用到拷贝构造函数,那如果我们将拷贝构造中的第一个参数改为传值而非上面代码中的传引用会怎么样?
如图所示,当我们把&符号删去后出现了,拷贝构造不能带有"Person"类型的参数这个错误,这是因为如果语法允许的话就会陷入无限调用拷贝构造的循环中。因此写拷贝构造时必须要传引用也是为了解决这个问题。
深拷贝构造与浅拷贝构造
浅析深拷贝与浅拷贝问题:
1.浅拷贝:仅完成简单的赋值拷贝工作
2.深拷贝:先在堆区重新申请空间,在进行拷贝工作
在没有自己实现好拷贝构造时,调用编译器自动生成的拷贝构造函数来创建并初始化新对象,这个操作就是浅拷贝,因为编译器自动生成的拷贝构造只实现了值的简单传递,当有对象属性中有动态内存指针时,也只是把指针简单拷贝,这样导致的最终问题是销毁对象时会出现重复释放的操作。
为了解决深浅拷贝的问题就必须自己写拷贝构造的实现逻辑
如图上所示,深拷贝构造是先向堆区申请空间后在完成拷贝,而浅拷贝则只是将指针的值赋给新对象
运算符重载
概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
常见的运算符重载:
1.加号运算符重载operator+
2.左移运算符重载operator<<
3.递增运算符重载operator++
4.赋值运算符重载operator=
5.关系运算符重载operator>
6.函数调用运算符重载operator()
1.加号运算符重载
2.左移运算符重载
3.递增运算符重载
4.赋值运算符重载
再谈深拷贝与浅拷贝
赋值运算符重载也涉及到深拷贝与浅拷贝问题,例如当对对象进行赋值操作时,如果该对象含有指向堆区的指针就不能简单地进行简单的赋值操作了,而应该先申请空间再赋值。
5.关系运算符重载
作用:让两个自定义类型对象进行对比操作
6.函数调用运算符重载
·函数运算符()也可以重载
·由于重载后使用的方式非常像函数调用,因此也成为仿函数
·仿函数没有固定的写法,非常灵活
最后值得注意的是,运算符重载也可以实现函数重载,什么意思呢?也就是说既可以用==来判断字符是否相等,也可以用来判断整形是否相等,只要提供相应版本的重载实现即可。
总结
以上是类与对象中的所有内容了,本章主要讲了四种默认成员函数,分别为构造、析构、拷贝构造、以及赋值运算符重载。构造和拷贝构造是在创建对象时初始化对象属性的一种特殊的函数,其中拷贝构造也是特殊的有参构造。接着是析构函数,析构函数是在对象销毁前用于释放内存空间的,主要负责善后工作,前面三个函数都是编译器自动调用的,无需用户手动调用。最后是赋值重载,在没有实现赋值重载时,编译器自动生成的是浅拷贝版本的,如果存在动态开辟的空间可能会出现重复释放的风险,因此一般情况下都需要我们手动实现赋值重载。