目录
1.类的6个默认成员函数
2.构造函数
2.1概念
2.1什么时候要自己写构造函数
2.2构造函数不可以使用重载的情况
3.析构函数
3.1什么时候需要自己写析构函数
4拷贝构造函数
4.1什么时候需要自己写拷贝构造函数?
4.2常见的拷贝构造函数调用场景
5.运算符重载函数
5.1.运算符重载函数和构造函数使用区别:
5.2赋值重载函数
6.取地址与取地址重载(第五个&第六个)
在C语言中,当我们想使用结构体时且当结构体成员变量为指针变量(如:顺序表,链表等等)我们需要使用动态内存时,比较正规的方法时建立初始化函数,在函数中实现初始化。如:
void SListPrint(SListNode* plist)
{
SListNode* cur = plist;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
typedef int SLTDateType;
typedef struct SListNode
{
SLTDateType data;
struct SListNode* next;
}SListNode;
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
而尴尬的是,我们经常会忘记初始化或者销毁动态空间,而导致代码无法运行或者内存泄漏等问题。
而在c++中编译器会自动为类生成6个默认成员函数。
- 函数名与类名相同,且无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
而构造函数我们一般分为默认构造函数,和需要传参的构造函数
- 默认构造函数(3种):(1) 类自己生成的函数(2)无参 (3)全缺省的函数
- 特征: (不传参就可以调用)
下面我们先简单写一个构造函数。
class Date
{
public:
// 1.无参构造函数
Date()
{
cout << "Date()"<
可以看出当我们在实例化内置类型对象的时候编译器会自动调用构造函数.
需要自己写的情况:
- 一般情况下,有内置类型成员(int char 等),且对于这些成员的初始值有一定需求的需要自己写构造函数。
- 有需要动态开辟空间的成员,如链表,顺序表等等。
不需要自己写的情况:
- 当内置类型成员都有缺省值时,且初始化符合要求,可以考虑让编译器自己生成
- 全部都是自定义类型成员(例如:Stack),可以考虑让编译器自己生成。
注意:
当构造函数的调用存在歧义时,在编译时会出现错误。注意:默认构造函数只能有一个
// 构成函数重载
// 但是无参调用存在歧义
Date()
{
_year = 1;
_month = 1;
_day = 1;
}
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
默认析构函数:与默认构造函数类似,编译器对内置类型成员不做处理,对自定义类型会去调用它的析构函数。
需要自己写的情况:
- 有动态申请资源时,需要自己写析构函数释放空间。(防止内存泄漏)
不需要自己写的情况:
注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
对于第二条我们首先要引入函数栈帧的相关知识,这里只简单讲讲在函数的调用时若有传参,则会产生一个临时变量拷贝所传的值。也就是说当我们使用传值调用时会产生一个临时变量来拷贝传入的值,而这一步拷贝又需要调用拷贝构造函数,所以就会引发无穷调用。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// Date(const Date& d) // 正确写法
Date(const Date& d) // 错误写法:编译报错,会引发无穷递归
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
需要自己写的情况:
- 自定义类型必须使用拷贝构造(深拷贝)
不需要自己写的情况:
- 内置类型直接拷贝(浅拷贝/值拷贝)
例:Date类中都是内置类型,默认生成的拷贝构造函数为浅拷贝可以直接用;而Stack类为自定义类型,其中有a指针指向一块新开辟的空间。此时需要自己写拷贝构造函数。
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
class Date
{
public:
Date(int year, int minute, int day)
{
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d)
{
cout << "Date(const Date& d):" << this << endl;
}
~Date()
{
cout << "~Date():" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
Date Test(Date d)
{
Date temp(d);
return temp;
}
int main()
{
Date d1(2022,1,13);
Test(d1);
return 0;
}
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
// 全局的operator==
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
void Test ()
{
Date d1(2018, 9, 26);
Date d2(2018, 9, 27);
cout<<(d1 == d2)<
这里将d1==d2,转换成d1.operator==(d2)
- 参数类型:const T&,传递引用可以提高传参效率
- 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回*this :要复合连续赋值的含义
Date& operator=(const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
之所以返回Date&是因为要满足连续赋值。
2. 赋值运算符只能重载成类的成员函数不能重载成全局函数
引入: 内置类型取地址时有取地址操作符,而自定义类型呢?于是出现了取地址重载。它用到的场景非常少,可以说取地址重载——补充这个语言的完整性,更加系统。
这两个默认成员函数一般不用重新定义 ,编译器默认会生成
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如不想让别人获取到指定的内容! (设为nullptr)