在类与对象(上)中介绍定义类的几个基本部分,在这篇博客中,我将会主要介绍类中的成员函数的作用以及运算符重载有关的内容。
目录
一、类的默认成员函数
二、构造函数
三、构析函数
四、拷贝构造
五、赋值运算符重载
1、运算符重载
2、赋值运算符重载
六、const成员变量
一、类的默认成员函数
默认成员函数就是在外面没有主动编写的情况下,编译器会自动生成的成员函数就被称为默认成员函数。一个类中的默认成员函数有六个,分别是:构造函数、构析函数、拷贝构造函数、赋值重载、普通对象取地址、const对象取地址。这六个函数中,前四个是重点,这里也会着重进行讲解。
二、构造函数
构造函数是一种特殊的成员函数,它的作用是在实例化对象时初始化对象。注意的是:构造函数虽然叫构造,但是它的作用并不是为对象开辟空间,因为局部对象的空间在开辟这个函数使用的空间时就已经开辟好了。
首先构造函数的名字一定是和类名相同的;第二构造函数没有返回值;第三在实例化对象的时候系统会自动调用;第四构造函数可以重载;第五,如果没有显示的定义构造函数,那么C++的编译器会自动生成一个无参的默认构造函数,当我们写了任意一个构造函数时,就编译器就不会再生成这个默认构造函数了;第六,无参的构造函数、全缺省构造函数、编译器生成的默认构造函数都被称作默认构造函数,虽然这三种构造函数可以构成函数重载,但是在调用时会存在歧义,所以这三种函数有且只有一个存在,不能同时存在,也就是说不传入实参就能调用的构造叫做默认构造,默认构造只能存在一个。
class Date
{//构造函数与类同名,且没有返回值// 虽然这里的四种情况都是构造函数// 但是只有 1 和 4 是默认构造函数//1、无参的构造函数Date(){_year = 0;_month = 0;_day = 0;}//2、有参数的构造函数Date(int year,int month,int day){_year = year;_month = month;_day = day;}//3、半缺省的构造函数Date(int year, int month=10, int day=1){_year = year;_month = month;_day = day;}//4、全缺省的构造函数Date(int year=1949, int month = 10, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
三、构析函数
构析函数的功能与构造函数相反,是用来完成对象中资源的清理释放工作,同样的,在局部对象所在的函数结束栈帧销毁时,对象会自动销毁,这个时候系统会自动调用这个对象的构析函数。
首先析构函数就是在构造函数的函数名就是类名;第二析构函数没有参数也没有返回值;第三,一个类只能有一个析构函数,当我们没有显示的定义析构函数时,系统会自动生成默认的够细函数,这个自动生成的析构函数对内置类型不会进行处理;第四,对象生命周期结束时,系统会自动调用析构函数;第五,当我们自定义类型中有其他的自定义类型的成员是,对应自定义类型成员也会调用他的析构函数;第六,如果类中没有申请资源时,析构函数可以不写,但是有申请资源时,析构函数一定要写;最后,对一个局部域中的多个对象,后定义的先析构。
class Date
{Date(int year = 1949, int month = 10, int day = 1){_year = year;_month = month;_day = day;}//析构函数无参无返//这里没有申请可以不写析构函数 只是用于演示~Date(){_year = 0;_month = 0;_day = 0;}
private:int _year;int _month;int _day;
};
四、拷贝构造
拷贝构造函数就是一种特殊的构造函数,它的要求是:函数的第一个参数是自身类类型的引用,如果有其他的参数,那么其他的参数都有默认值。
拷贝构造函数就是构造函数的一个重载,同时它的第一个参数必须是自身类类型对象的引用,不能使用传值的方式。因为C++定义在进行传值传参时,会先生成一个临时对象,如果在拷贝构造中使用传值传参,就会先调用拷贝构造生成一个临时对象,这个临时对象又要调用拷贝构造去生成它的参数的那个临时对象,这样就会造成一个无穷递归的错误。
如果没有显示的定义拷贝构造时,编译器会自动生成拷贝构造函数,这个拷贝构造函数只能对内置类型的成员变量完成拷贝,也就是浅拷贝。自定义类型的成员变量会自动调用它的拷贝构造。当一个类的成员变量全是内置类型并且没有指向什么资源的时候,编译器自动生产的拷贝构造函数完成的浅拷贝就能够实现拷贝的功能,但是当类的成员变量指向某些资源,或者浅拷贝不能完成我们的预期时,我们就要自己实现深拷贝了。
class Date
{//拷贝构造的第一参数必须是自身类类型对象的引用//当存在其他参数时,必须带有默认值Date(Date& d,int a=1){_year = d._year;_month = d._month;_day = d._day;}Date(int year = 1949, int month = 10, int day = 1){_year = year;_month = month;_day = day;}~Date(){_year = 0;_month = 0;_day = 0;}
private:int _year;int _month;int _day;
};
//这里函数在返回前,会先把a的值拷贝到一个临时对象中
//在函数结束以后这个临时对象并不会被销毁
int func1()
{int a = 1;return a;
}//这里返回的是a的别名 并没有创建临时对象
//a在函数结束以后就销毁了 所以返回的引用变成了野引用
int& func2()
{int a = 2;return a;
}
五、赋值运算符重载
1、运算符重载
在C++中运算符默认只能处理内置类型,但是我们可以通过对运算符的重载实现对自定义类型的操作。运算符重载有特定的函数名:operator+重载的运算符。比如我们想要重载一下“==”这个功能,来判断两个日期是否相等,那这个函数的函数名就是operator==。这个函数和其他函数一样,有返回值、参数和函数体。重载后的运算符的结合性不会改变,同时重载运算符函数的参数个数和该运算符作用的运算对象的数量一样多,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。当重载运算符函数是成员函数时,它的第一个运算对象默认传给隐式的this指针,所以重载运算符函数作为成员函数的时候,参数比运算对象少一个。我们也不能创造出新的运算符,只能在已经有的运算符中进行重载。最后以下五个运算符不能重载: “.*” “::” “sizeof” “?: ” “.”。
因为++有前置++和后置++,运算符重载函数名都是operator++,无法区分,因此C++规定,在重载后置++时,增加一个int形参,与前置++构成函数重载,方便区分。--同理。在重载<<和>>时,需要重载成全局函数,因为在重载为成员函数时,this指针默认时第一个参数,第一个参数是左侧的运算对象,调用的时候就变成了对象<<out,不符合使⽤习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。
2、赋值运算符重载
赋值运算符重载是一个默认成员函数,它是作用在两个已经存在的对象之间的拷贝赋值。与拷贝构造不同,拷贝构造是一种构造,是用一个已经存在的对象来初始化另外一个对象,而赋值重载作用的对象两个都是已经初始化完成的。
赋值运算符重载必须重载为成员函数,同时赋值运算符重载的参数建议写成const当前类类型对象的引用,这样在传参时不会发生拷贝,同时const还修饰以后,还能直接使用会被隐式类型转化的类型直接赋值。返回值建议写成当前类类型的引用,因为这样返回的时候不会发生拷贝,而已能够支持连续赋值。
如果没有显示的定义赋值重载时,编译器会自动生成一个默认赋值重载函数,这个赋值重载函数只能对内置类型的成员变量完成拷贝,也就是浅拷贝。自定义类型的成员变量会自动调用它的赋值重载。当一个类的成员变量全是内置类型并且没有指向什么资源的时候,编译器自动生产的拷贝构造函数完成的浅拷贝就能够实现拷贝的功能,但是当类的成员变量指向某些资源,或者浅拷贝不能完成我们的预期时,我们就要自己实现赋值重载函数了。
#include<iostream>
using namespace std;class Date
{
public://重载==bool operator==(Date& d){if (_year == d._year && _month == d._month && _day == d._day)return true;return false;}//赋值重载//用当前类类型引用作返回值可以减少拷贝 且可支持连续赋值//用const修饰的当前类类型引用作为参数可 减少拷贝//同时可支持自动识别隐私类型转化Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}Date(Date& d, int a = 1){_year = d._year;_month = d._month;_day = d._day;}Date(int year = 1949, int month = 10, int day = 1){_year = year;_month = month;_day = day;}~Date(){_year = 0;_month = 0;_day = 0;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 7, 13);Date d2(2024, 6, 1);//两种调用方式都可以d1.operator==(d2);//在编译的时候会自动转化成上面的形式d1 == d2;d1.operator=(d2);d1 = d2;//这里隐藏了一个类型转化(Date){ 1999,9,21 }//如果不用const修饰参数传参时就会造成权限放大报错d1 = { 1999,9,21 };Date d3(1888, 2, 20);//如果没有返回值则不能支持联系赋值d1 = d2 = d3;return 0;
}
六、const成员变量
将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后⾯。const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。
#include<iostream>
using namespace std;class Date
{
public://在用const修饰以后,this指针的属性由Date* const this 变成了const Date* const this//在const修饰以前this指针不能改变指向//修饰以后this指针的指向和内容都不能改变void Print() const{cout << _year << "/" << _month << "/" << _day << endl;}Date(int year = 1949, int month = 10, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Print();return 0;
}