拷贝构造函数
只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
深拷贝与浅拷贝
对于内置对象,编译器可以直接拷贝,这种拷贝是按字节序进行拷贝的被称作浅拷贝或值拷贝。浅拷贝有个问题就是,如果拷贝对象是指向一个空间的,那么拷贝后的对象也是指向这一空间的,但是显然这是不合理的。这时我们就需要深拷贝了。深拷贝会自己开辟一块和拷贝对象指向空间相似的空间。
特性
拷贝构造函数是构造函数的一个重载形式;
拷贝构造函数的参数只有一个且必须是类类型对象的引用。如果使用传值方式编译器直接报错,因为这会导致无穷递归调用;
class Date { public: Date(int year = 2000, 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; }
若未显示定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象是浅拷贝;
//这是一段错误的代码,未显示定义拷贝构造函数 class Stack { public: Stack(size_t capacity = 4) { _a = (int*)malloc(sizeof(int) * capacity); if (NULL == _a) { perror("malloc申请空间失败"); exit(-1); } _capacity = capacity; _top = 0; } ~Stack() { if (_a) { free(_a); _a = NULL; _capacity = 0; _top = 0; } } void Push(int data) { _a[_top] = data; _top++; } void Print() { for (int i = 0; i < _top; i++) cout << _a[i] << " "; cout << endl; } private: int* _a; int _top; size_t _capacity; }; int main() { Stack st1; Stack st2(st1); st1.Push(1); st1.Push(2); st1.Push(3); st2.Print(); return 0; }
类中如果没有涉及资源申请时,拷贝构造函写不写都可以,但是如果涉及到资源申请时,拷贝构造函数就一定要写。
class Stack { public: Stack(size_t capacity = 4) { _a = (int*)malloc(sizeof(int) * capacity); if (NULL == _a) { perror("malloc申请空间失败"); exit(-1); } _capacity = capacity; _top = 0; } ~Stack() { if (_a) { free(_a); _a = NULL; _capacity = 0; _top = 0; } } Stack(const Stack& st) { _a = (int*)malloc(sizeof(int) * st._capacity); if (nullptr == _a) { perror("malloc申请空间失败"); exit(-1); } memcpy(_a, st._a, sizeof(int) * st._top); _top = st._top; _capacity = st._capacity; } void Push(int data) { _a[_top] = data; _top++; } void Print() { for (int i = 0; i < _top; i++) cout << _a[i] << " "; cout << endl; } private: int* _a; int _top; size_t _capacity; }; int main() { Stack st1; st1.Push(1); st1.Push(2); st1.Print(); Stack st2(st1); st2.Push(3); st2.Print(); Stack st3 = st1; st3.Push(4); st3.Print(); return 0; }
使用场景
使用已经存在的对象创建新对象;
函数参数类型是类类型对象;
函数返回值类型为类类型对象。
赋值运算符重载
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数原型:返回值类型 operator操作符(参数列表)
不能通过连接其他符号来创建新的操作符:比如operator@;
重载操作符必须有一个类类型的参数;
用于内置类型的运算符,其含义不能改变;
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数含有一个隐藏的this;
.*(有个点) :: sizeof ?: . (有个点),这五个运算符不能够重载。
class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //private: 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(2023, 3, 5); Date d2(2023, 3, 4); cout << (d1 == d2) << endl; } int main() { Test(); return 0; }
赋值运算符重载
- 赋值运算符重载格式
参数类型:const T&,传递引用可以提高传参效率;
返回值类型:T&,返回引用可以提高返回的效率,有返回值的目的是为了至此连续赋值;
检测是否自己给自己赋值
返回*this:要符合连续赋值的含义。
class Date { public: Date(int year = 2000, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } Date& operator=(const Date& d) { if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1(2023, 3, 5); Date d2, d3; d2.Print(); d3.Print(); d2 = d3 = d1; d2.Print(); d3.Print(); return 0; }
赋值运算符只能重载成类的成员函数,不能重载成全局函数。
赋值运算符如果不显示实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就会和编译器在类中生成的的默认复制与算符重载冲突,所以赋值运算符只能是类的成员函数。
默认的赋值重载运算符是以值的方式逐字节拷贝。
前置++与后置++的重载
前置++是先++在使用,后置++是先使用在++;
C++规定,后置++重载时多加一个int类型的参数用作区分,但是这个int类型的参数只是起一个站位的作用,不需要我们管,它会由编译器自动传递一个值。
实际上前置++和后置++是一个很不好的设定,之后的很多语言规定只有前置或后置++,还有的语言里面直接没有了++。
class Date { public: Date(int year = 2000, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } //前置++ Date& operator++() { _day += 1; return *this; } //后置++ Date& operator++(int) { Date temp(*this); _day += 1; return temp; } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1; Date d2(2023, 3, 5); d1.Print(); d1 = d2++; d1.Print(); d1 = ++d2; d1.Print(); return 0; }
const函数
将const修饰的“成员函数”称为const成员函数。const修饰类的成员函数,实际上是修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
class Date { public: Date(int year, int month, int day) { _year = year; _month = month; _day = day; } void Print() const { cout << "Print()" << endl; cout << "year:" << _year << endl; cout << "month:" << _month << endl; cout << "day:" << _day << endl << endl; } private: int _year; // 年 int _month; // 月 int _day; // 日 }; int main() { Date d1(2022, 1, 13); d1.Print(); const Date d2(2022, 1, 13); d2.Print(); return 0; }
取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义,编译器会默认生成。