文章目录
- 前言
- 一、拷贝构造函数
-
- [1. 概念](#1. 概念)
- [2. 特征](#2. 特征)
- [3. 编译器生成默认拷贝构造函数](#3. 编译器生成默认拷贝构造函数)
- [4. 拷贝构造函数典型使用场景](#4. 拷贝构造函数典型使用场景)
- 二、运算符重载函数
- 三、赋值运算符重载函数
-
- [1. 赋值运算符重载格式](#1. 赋值运算符重载格式)
- [2. 赋值运算符只能重载成类的成员函数不能重载成全局函数](#2. 赋值运算符只能重载成类的成员函数不能重载成全局函数)
- 3.编译器生成一个默认赋值运算符重载
- [4. 运算符重载函数和赋值运算符重载函数的调用](#4. 运算符重载函数和赋值运算符重载函数的调用)
- 四、前置++和后置++重载
- 总结
前言
C++拷贝构造函数、运算符重载函数、赋值运算符重载函数、前置++和后置++重载等的介绍
一、拷贝构造函数
在实际运用中,我们有时候需要操作一个实例化好的类,但是又不想改变这个类,这时候,我们就需要拷贝构造函数。
1. 概念
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
2. 特征
拷贝构造函数也是特殊的成员函数,其特征如下:
- 拷贝构造函数 是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用 ,使用传值方式 编译器直接报错 ,因为会引发无穷递归调用。
拷贝构造函数是构造函数的一个重载形式,所以拷贝构造函数的函数名与类名相同,参数与构造函数不同,无返回值。
如下图:
-
采用传值调用,会引发无穷递归,编译器强制检查报错。
-
应该采用类类型对象的引用,如下:
cpp
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1945, int month = 8, int day = 15)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 6, 22);
Date d2(d1);
return 0;
}
3. 编译器生成默认拷贝构造函数
若未显式定义,编译器会生成默认的拷贝构造函数 。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
简单来说就是,不显示定义,编译器默认生成一个构造函数
- 对于内置类型会进行值拷贝(浅拷贝)。
- 对于自定义类型会调用它的拷贝构造函数。
cpp
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1945, int month = 8, int day = 15)
{
_year = year;
_month = month;
_day = day;
}
~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 9, 1);
Date d2(d1);
return 0;
}
编译器默认生成的拷贝构造函数是值拷贝(浅拷贝),但是有些情况下我们需要深拷贝。比如:
对一个栈,我们不能直接浅拷贝,程序会崩溃。原因如下:
浅拷贝会将一个栈动态申请的空间的地址拷贝个另一个栈的指针,所以两个栈对象的指针指向同一块空间,析构函数会对同一块空间释放两次,程序会崩溃。
cpp
#include <iostream>
using namespace std;
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _a)
{
perror("Stack malloc");
return;
}
_top = 0;
_capacity = capacity;
}
void Push(int x)
{
// 增容
_a[_top] = x;
_top++;
}
~Stack()
{
if (_top)
{
free(_a);
_a = nullptr;
_top = 0;
_capacity = 0;
}
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1(10);
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
Stack st2(st1);
return 0;
}
- 所以,这种Stack的类对象,我们应该自己显示定义拷贝构造函数。
cpp
#include <iostream>
using namespace std;
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _a)
{
perror("Stack malloc");
return;
}
_top = 0;
_capacity = capacity;
}
Stack(const Stack& st)
{
_a = (int*)malloc(sizeof(int) * st._capacity);
if (nullptr == _a)
{
perror("Stack malloc");
return;
}
memcpy(_a, st._a, sizeof(int) * st._top);
_top = st._top;
_capacity = st._capacity;
}
void Push(int x)
{
// 增容
_a[_top] = x;
_top++;
}
~Stack()
{
if (_top)
{
free(_a);
_a = nullptr;
_top = 0;
_capacity = 0;
}
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1(10);
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
Stack st2(st1);
return 0;
}
注意:类中如果没有涉及资源申请时 ,拷贝构造函数是否写都可以 ;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
4. 拷贝构造函数典型使用场景
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
二、运算符重载函数
在介绍运算符重载之前先来看看运算符重载和函数重载的比较
- 函数重载和运算符重载都用了"重载"一词,但是两者重载没有关系
- 函数重载是为了函数名相同,函数参数不同的函数可以同时存在
- 运算符重载是为了自定义类型的数据也可以像内置类型一样使用运算符进行比较或赋值等
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数 ,也具有其返回值类型 ,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。函数名字为:关键字operator后面接需要重载的运算符符号 。
函数原型:返回值类型 operator操作符(参数列表)
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .*、 ::、 sizeof 、?: . 注意以上5个运算符不能重载**
cpp
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1945, int month = 8, int day = 15)
{
_year = year;
_month = month;
_day = day;
}
~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
//private:
int _year;
int _month;
int _day;
};
bool operator<(const Date& x1, const Date& x2)
{
if (x1._year < x2._year)
{
return true;
}
else if (x1._year == x2._year && x1._month < x2._month)
{
return true;
}
else if (x1._year == x2._year && x1._month == x2._month && x1._day < x2._day)
{
return true;
}
else
{
return false;
}
}
bool operator>(const Date& x1, const Date& x2)
{
if (x1._year > x2._year)
{
return true;
}
else if (x1._year == x2._year && x1._month > x2._month)
{
return true;
}
else if (x1._year == x2._year && x1._month == x2._month && x1._day > x2._day)
{
return true;
}
else
{
return false;
}
}
int main()
{
Date d1(1945, 8, 15);
Date d2(1949, 10, 1);
cout << operator<(d1, d2) << endl;
cout << (d1 < d2) << endl;
cout << operator>(d1, d2) << endl;
cout << (d1 > d2) << endl;
return 0;
}
使用运算符重载时,调用运算符重载函数,也可以直接使用运算符比较。
cpp
cout << operator<(d1, d2) << endl;
cout << (d1 < d2) << endl;
cout << operator>(d1, d2) << endl;
cout << (d1 > d2) << endl;
- 一般建议直接使用运算符比较。
上述运算符重载到了全局中,运算符重载也可以重载到类中。
cpp
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1945, int month = 8, int day = 15)
{
_year = year;
_month = month;
_day = day;
}
bool operator<(const Date& x2)
{
if (_year < x2._year)
{
return true;
}
else if (_year == x2._year && _month < x2._month)
{
return true;
}
else if (_year == x2._year && _month == x2._month && _day < x2._day)
{
return true;
}
else
{
return false;
}
}
~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(1945, 8, 15);
Date d2(1949, 10, 1);
cout << d1.operator<(d2) << endl;
cout << (d1 < d2) << endl;
return 0;
}
三、赋值运算符重载函数
1. 赋值运算符重载格式
- 参数类型:const T&,传递引用可以提高传参效率
- 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回*this :要复合连续赋值的含义
cpp
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1945, int month = 8, int day = 15)
{
_year = year;
_month = month;
_day = day;
}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(1945, 8, 15);
Date d2(1949, 10, 1);
//d2.operator=(d1);
d2 = d1;
return 0;
}
赋值运算符可以选择调用赋值运算符重载函数,也可以直接使用赋值运算符,一般建议直接使用赋值运算符。
cpp
d2.operator=(d1);
d2 = d1;
2. 赋值运算符只能重载成类的成员函数不能重载成全局函数
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
cpp
class Date
{
public:
Date(int year = 1945, int month = 8, int day = 15)
{
_year = year;
_month = month;
_day = day;
}
~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
//private:
int _year;
int _month;
int _day;
};
Date& operator=(Date& left, const Date& right)
{
left._year = right._year;
left._month = right._month;
left._day = right._day;
return left;
}
3.编译器生成一个默认赋值运算符重载
用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
注意:
内置类型成员变量是直接赋值的,
而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
4. 运算符重载函数和赋值运算符重载函数的调用
- 内置类型使用运算符进行操作直接转换成指令进行操作。
- 自定义类型若直接使用运算符操作,会调用对应的运算符重载函数。
四、前置++和后置++重载
cpp
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1328, int month = 1, int day = 4)
{
_year = year;
_month = month;
_day = day;
}
// 前置++
Date& operator++()
{
_day += 1;
return *this;
}
// 后置++
Date operator++(int)
{
Date tmp(*this);
_day += 1;
return tmp;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
Date d1(1949, 10, 1);
d = d1++; // d: 1949 10 1 d1: 1949 10 2
d = ++d1; // d: 1949 10 3 d1: 1949 10 3
return 0;
}
- 前置++
前置++:返回+1之后的结果
注意:this指向的对象函数结束后不会销毁 ,故以引用方式返回提高效率。 - 后置++
前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1而temp是临时对象,因此只能以值的方式返回,不能返回引用
总结
C++拷贝构造函数、运算符重载函数、赋值运算符重载函数、前置++和后置++重载等的介绍。