目录
1.类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员
函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
2. 构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,构造函数的任务是初始化对象。并且在对象整个生命周期内只调用一次。
其有四大特征
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
class Date
{
public:
// 1.无参构造函数
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2024,7,13);
return 0;
}
注意,如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
同时我们还可以运用之前在c++入门中缺省值的知识来进行一些优化。我们构造一个全缺省的构造函数
Date(int year=1, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
因此我们可以构造
Date d1;
Date d2(2024);
Date d3(2024,7);
Date d4(2024,7,13);
注意,全缺省的函数与无参的函数,在语法上构成函数重载,但是在实际调用时会产生错误,因为编译器不知道调用哪一个
关于系统自动生成的构造函数
1.我们不写才会生成,我们写了任意一个构造函数就不会生成了
2.不会处理内置类型的成员,例如int double...,是随机值,所有指针,包括自定义类型的指针也都是内置类型,一般会初始化为nullptr。(但是c++11支持声明的时候给缺省值)
这个缺省值是给自动生成的构造函数使用的,但是当我们自己写了构造函数但是没有初始化那个内置类型时,也会使用缺省值
3.会处理自定义类型的成员,会去调用这个成员的默认构造函数
我们用栈来实现队列,不需要自己写队列的构造函数,自动生成的构造函数会帮我们调栈的构造函数
总结:一般情况下都需要我们去写构造函数,除非成员全都是自定义函数 。
注意点:无参的构造函数,全缺省的构造函数,编译器默认生成的构造函数都被称为默认构造函数(不传参就可以直接用)。他们三个只能存在一个,多个并存会出现二义性。
3.析构函数
是与构造函数相配套的函数,对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
析构函数也有四大特征
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
和构造函数类似,析构函数不会处理内置类型成员
但是对于自定义类型,会调用这个成员的析构函数
后定义的函数会先调用析构函数
小总结:c++构造函数和析构函数最重要的特点是自动调用。
4.拷贝构造
我们先由一个现象引出拷贝构造
在c语言中(Stack已经使用typedef)
我们_array是通过malloc开辟空间的,这时候我们发现,传参的时候s仅仅只是将s1中_array变量中储存的地址拿了过去,只是将main栈帧里面存的东西原模原样复制到func2栈帧中而已,他们的_array中存的都是指向堆区的同一块空间的指针。这是一种浅拷贝
但是如果我们在c++中这样使用,当我们调用结束,系统编译器自动调用析构函数时,同一块空间就会出现被释放两次的情况,就会出现错误。
为了避免这种状况,c++中传值传参要调用拷贝构造。
拷贝构造函数的定义
拷贝构造函数:只有单个形参,该形参是对相同类型对象的引用(一般常用const修饰,例如Date(const Date &d) ),在用于已存
在的类型对象创建新对象时由编译器自动调用。
拷贝构造的特征
1. 拷贝构造函数是构造函数的一个重载形式
2. 拷贝构造函数的参数只有一个且必须是同类型对象的引用,使用传值方式编译器直接报错,
因为会引发无穷递归调用。
如果是传值,如下图
我们首先要传值传参,把d1传到d,就要调用我们自己的拷贝构造函数,把d1拷贝到d
然后我们这个拷贝构造函数又要传值传参,把d1传给d,就又要调用我们自己的拷贝构造函数
因此进行循环
3. 若未显式定义,编译器会生成默认的拷贝构造函数,它对于内置类型进行浅拷贝,对于自定义类型调用它的拷贝构造。 因此当我们没有写任何一个拷贝构造函数时,传值传参时候的现象于c语言中一模一样。这也是c++兼容c语言的体现
小知识
1.深拷贝举例
2. 拷贝构造另一等价写法
Date d3 = d1
与Date d3(d1)是等价的
3.传引用传参是不需要拷贝构造的
4.传值返回创建临时变量时,也会调用拷贝构造
(临时变量sizeof较小的话会用寄存器返回,如果较大,会在main函数中提前开辟空间)
(并且临时变量具有常性)
五、赋值运算符重载
1.运算符重载
运算符重载之后会使我们的代码可读性更强
函数名字为:关键字operator后面接需要重载的运算符符号。例如:operator > ,operator +
函数原型:返回值类型 operator操作符(参数列表) 例如
bool operator==(const Date& d1, const Date& d2)
注意点
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个自定义类型参数,用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
3.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
藏的this
例如 bool operator==(const Date& d) d1.opetator ==(d2)
当然也可隐式 d1 == d2 二者等价
4. .* :: sizeof ?: (三目操作符) . 注意以上5个运算符不能重载。这个经常在笔试选择题中出
2.赋值运算符重载
仍然用日期类进行举例
void operator= (const Date& d)//这里const防止被误修改 不传引用也可以,但是传引用不用再开空间,效
//率更高
{
_year = d._year;
_month = d._month;
_day = d._day;
}
使用时
d1.operator=(d2);
d1 = d2;
与拷贝构造有点像,但是与拷贝构造不同的是,我们要现有两个对象,然后把一个对象的值赋到另一个对象中
拷贝对象是依靠一个已经存在的对象初始化另一个对象
不过我们还可以继续优化
Date& operator= (const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
这样就可以支持连续赋值了
d1 = d2 = d3;
而且一般出了作用域还在,我们就返回引用。
为了防止有人写
d1 = d1;
这样的代码我们可以添加一个if判断一下
Date& operator= (const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
再小小地补充一个知识
Date operator= (const Date d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
传值传参和传值返回的时候都要调用拷贝构造,一个是把实参传给形参,另一个是创建一个临时变量 。
最后,对于赋值重载,我们不写它也会自动生成,它对于内置类型会进行浅拷贝,对于自定义类型会调用它的赋值重载函数
知识补充
函数如果声明和定义分开,缺省值只能在声明的时候给。声明和定义分开可以让我们更容易地去看这个类的结构。
声明的时候给了缺省值,编译的时候就可以编译通过了,然后在链接的时候通过修饰过后的函数名去找这个函数
在.cpp中定义函数的时候,函数名前面要加类名: