大家好鸭
上期我们谈论了一些关于C++中类的知识,初步介绍一些类的使用方法
本篇文章,我们进一步来谈谈类的其他细节
类的6个默认成员函数
我们先来看一个类
class A
{
};
看着是一个空类,但即使是一个空类,编译器也会生成6个默认成员函数,它们分别是构造函数,析构函数,拷贝构造,赋值重载,还有两个普通对象和const对象的取地址重载
构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证 每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次
class Date
{
public:
Date(int day,int month,int year)
{
_day = day;
_month = month;
_year = year;
}
private:
int _day;
int _month;
int _year;
};
默认构造函数分为两种情况,一种是构造函数没有参数,一种是构造函数全缺省,以上两种情况编译器都视为默认构造函数。
当我们显示写好了之后编译器便不会自动生成默认构造函数
注意:一个类只能有一个默认构造函数
上面一段代码定义了一个日期类Date,有一个与类名相同的函数,那个就是构造函数
构造函数的主要功能就是:在对象实例化时初始化对象
并不是创建对象
构造函数还有以下特点
- 函数名与类名相同
- 无返回值
- 在实例化时自动调用
- 构造函数可以重载
- 没有显示定义时自动生成,定义了就不生成
- 编译器生成的默认构造,对内置类型不处理,对自定义类型调用对应的构造函数
class Date
{
private:
// 基本类型(内置类型)
int _year = ;
int _month ;
int _day ;
//自定义类型
Time A;
};
有可能读者会认为编译器生成的默认构造函数没什么用
当一个类中只有自定义类型时,就可以看出编译器生成的默认构造的优势了
自定义类型调用对应构造,这样这个类就可以用默认生成的就好了,不用直接写构造函数
这样的前提是自定义类型都已经写好自己的构造函数。
细心的读者可能注意到了
构造函数我使用public修饰它,这是为了构造函数可以在类外访问它,可以在类外实例化对象,
当修饰词改成private时,就不能在类外创建对象了
析构函数
析构函数与构造函数恰恰相反,构造函数负责初始化,析构函数负责销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
~Date()
{
}
像这样便是Date类的析构函数,析构函数和构造函数一样,对内置类型不处理,对自定义类型
调用对应的析构函数
写析构函数主要是针对动态申请的空间需要释放
析构函数有如下特点
- 析构函数名是在类名前加上~构成
- 无参数无返回值
- 一个类只有一个析构函数,不可重载,没有显示定义的话,编译器自动生成
- 对象声明周期结束时,编译器自动调用析构函数
拷贝构造函数
只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
简单的讲就是,用一个已知的对象去初始化一个新创建的对象
class Date
{
public:
Date(const Date& d)//拷贝构造
{
_day = d._day;
_month = d._month;
_year = d._year;
}
Date(int year,int month,int day)
{
_day = day;
_month = month;
_year = year;
}
~Date()
{
}
private:
int _day;
int _month;
int _year;
};
使用方法如下
int main()
{
Date A(2000, 1, 1);
Date B(A);
}
需要注意的是:
在拷贝构造的形参必须使用引用传参,否则会引发无穷递归,导致程序崩溃
编译器默认生成的拷贝构造函数只是简单的值拷贝,当有动态开辟的空间时,可能会导致该空间被两次析构,引发程序崩溃
像上图那样,简单的值拷贝会引发这样的问题:两个指针指向同一块空间,当原来的对象析构时将动态开辟的空间释放后,拷贝出来的对象又将空间释放一遍就会报错,这样的情况,拷贝构造就要写深拷贝,为int*b也开辟一块同样大小的空间
拷贝构造有以下特点
- 拷贝构造是构造函数的一个重载形式
- 参数只有一个,且为引用
- 编译器自动生成的拷贝构造函数是只进行值拷贝的浅拷贝,自定义类型是调用对应的拷贝构造
- 在将类做为参数的情况下,会调用拷贝构造构造一个临时对象,再进行传参,所以能采用引用返回的时候尽量采用引用返回
赋值运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
Date& Date::operator++()
{
_day++;
if (_day > GetMonthDay(_year, _month))
{
_day = 1;
_month++;
}
if (_month >= 13)
{
_month =1;
_year++;
}
return *this;
}
像这样就重载了++运算符,自定义了++运算符在自定义类型Date中的使用
前置++和后置++有特殊的地方
Date& Date::operator++()//前置
{
_day++;
if (_day > GetMonthDay(_year, _month))
{
_day = 1;
_month++;
}
if (_month >= 13)
{
_month =1;
_year++;
}
return *this;
}
Date Date::operator++(int)//后置
{
Date tmp(*this);
_day++;
if (_day > GetMonthDay(_year, _month))
{
_day = 1;
_month++;
}
if (_month >= 13)
{
_month = 1;
_year++;
}
return tmp;
}
后置++在参数的地方多了一个int参数用于区分
- 不能创建新操作符
- 重载操作符必须有一个类类型操作符
- 内置类型的操作符不可改变其含义
- 当类成员重载时,形参比操作数少一个,因为有一个隐藏的this指针
- 有5个操作符不可重载分别是 .* :: sizeof ?: .
- 注意是否可以用引用返回
- 赋值运算符只能重载成成员函数,不能重载成全局函数,因为编译器会生成一个默认的重载运算符,不在类中显示定义的话,会与编译器生成的冲突
- 编译器生成的赋值运算符也是简单的值拷贝
const成员
void ab()const
{
}
像这样将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
const Date A(2000, 1, 1);
像这样是const对象
const对象有它的特殊之处,如const Date A,对这样一个类取地址,结果是 const Date*
当const对象作为形参时,会将const Date*传递给函数中的this指针
但普通函数的this指针类型是Date*,这样就出现了类型不兼容的问题
解决办法是将成员函数重载一份const成员函数,当返回值也不想被外界修改时,
可以给返回值也加上const,于是我们会见到这样的代码
const char& operator[](size_t pos) const
取地址和const取地址操作符
这两种操作符一般不用重新定义
本篇到此结束,谢谢大家,若有错误,还请不吝赐教