拷贝构造和运算符重载【C++】
一,拷贝构造函数
拷贝构造函数是C++ 中一种特殊的构造函数,其作用是用一个已经存在的同类型对象,创建并初始化一个新的对象。它的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,它是 C++ 面向对象编程中管理对象复制、资源的机制。
拷贝构造的特点:
1,拷贝构造函数是构造函数的一个重载。
2,拷贝构造函数的第一个参数必须是类类型的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。C++规定,自定义类型的对象,在直接的拷贝,传值传参,都要调用拷贝构造。如果使用传值传参,会发生下面无穷递归调用的情况。
3,C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
4,拷贝构造函数也可以有多个参数,但是第一个参数必须是类类型对象的引用,后面的参数必须有缺省值。
5,以下是Data类的拷贝构造:
#include<iostream> using namespace std; class Data { public: Data(int year = 1, int month = 1, int day = 1) { this->_year = year; _month = month; _day = day; } void Print()const { cout << _year << "年" << _month << "月" << _day << "日" << endl; } Data(const Data& d)//拷贝构造函数 { this->_year = d._year; _month = d._month; _day = d._day; } ~Data() { _year = _month = _day = 0; } private: int _year; int _month; int _day; }; int main() { Data d1(2026, 5, 17); Data d3(d1); d3.Print(); return 0; }那么有一个问题,Data类只需要对内置类型成员变量完成值拷贝(浅拷贝),需要我们显示写拷贝构造函数吗?是不需要的。若未显示定义拷贝构造,编译器自动生成的拷贝构造函数就会完成这个工作(浅拷贝),所以对于内置类型成员变量是不需要显示写拷贝构造的。
但是像Stack这样的类,需要有开辟的资源,就需要显示写拷贝构造,如果我们不显示写呢,编译器生成的还满足我们的需求吗?根据下面的报错,显然编译器生成的并不满足我们的需求,我们要显示写拷贝构造。
以下是我们实现的一个栈,显示写出拷贝构造函数。
#include<iostream> using namespace std; typedef int SDatatype; class Stack { public: Stack(int n = 5) { this->_arr = (SDatatype*)malloc(n * sizeof(SDatatype)); if (_arr == NULL) { perror("malloc fail!"); exit(1); } _top = 0; _capacity = n; } void SPush(SDatatype x) { if (_top == _capacity) { cout << "空间已满" << endl; exit(1); } _arr[_top++] = x; } void SPop() { if (_top == 0) { cout << "栈内无元素" << endl; exit(1); } _top--; } SDatatype STop() { if (_top == 0) { cout << "栈内无元素" << endl; exit(1); } return _arr[_top - 1]; } Stack(const Stack& st)//拷贝构造函数 { this->_arr = (SDatatype*)malloc(st._capacity * sizeof(SDatatype)); if (_arr == NULL) { perror("copy malloc fail!"); exit(1); } memcpy(_arr, st._arr, sizeof(SDatatype) * st._top); _top = st._top; _capacity = st._capacity; } ~Stack() { if (_arr) free(_arr); _arr = NULL; _top = _capacity = 0; } private: SDatatype* _arr; int _top; int _capacity; }; int main() { Stack st1; st1.SPush(3); st1.SPush(2); st1.SPush(6); cout << st1.STop() << endl; Stack st2(st1); cout << st2.STop() << endl; return 0; }我们如何判断什么时候要显示写拷贝构造呢?这里有个技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写拷贝构造,否则就不需要。
二,运算符重载
学习了运算符重载,就可以丰富我们的Data类了,比如日期减日期,就是相差的天数;日期加(减)天数,就是离这个日期多少天后(前)的日期。
运算符重载特点:
1,当运算符被用于类类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用运算符重载,若没有,则会编译报错。
2,运算符重载的名字是由operator和后面要定义的运算符共同构成,和其他函数一样,它也具有其返回类型和参数列表及函数体。
3,重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数,⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象对象传给第二个参数。
4,如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数⽐运算对象少⼀个。
5,运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致。
6,不能通过连接语法中没有的符号来创建新的操作符:⽐如operator@。
7,.* :: sizeof ? : .注意以上5个运算符不能重载。
8,重载操作符至少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义,如: int operator+(int x, int y)
9,⼀个类需要重载哪些运算符,是看哪些运算符重载后有意义,⽐如Date类重载operator-就有意义,但是重载operator/就没有意义。
10,重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,方便区分。
11,重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调用时就变成了 对象<<cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第二个形参位置当类类型对象。
#include<iostream> using namespace std; class Data { public: Data(int year = 1, int month = 1, int day = 1) { this->_year = year; _month = month; _day = day; } //获取当前月天数 int GetmonthDay(int year, int month); void Print()const { cout << _year << "年" << _month << "月" << _day << "日" << endl; } //两个日期比较 bool operator==(const Data& x2)const { return this->_year == x2._year && _month == x2._month && _day == x2._day; } //日期加天数 Data operator+(int day); //日期加等天数 Data& operator+=(int day); //日期减天数 Data operator-(int day); //日期减等天数 Data& operator-=(int day); Data(const Data& d)//拷贝构造,Data类可以不用写 { this->_year = d._year; _month = d._month; _day = d._day; } ~Data()//析构函数,Data类可以不用写 { _year = _month = _day = 0; } private: int _year; int _month; int _day; }; //日期加天数 Data Data::operator+(int day) { Data tmp = *this; tmp._day += day; while(tmp._day > GetmonthDay(tmp._year, tmp._month)) { tmp._day -= GetmonthDay(tmp._year, tmp._month); tmp._month++; if (tmp._month > 12) { tmp._year++; tmp._month = 1; } } return tmp; } //日期加等天数 Data& Data::operator+=(int day) { this->_day += day; while (_day > GetmonthDay(_year, _month)) { _day -= GetmonthDay(_year, _month); _month++; if (_month > 12) { _year++; _month = 1; } } return *this; } //日期减天数 Data Data::operator-(int day) { Data tmp = *this; tmp._day -= day; while (tmp._day <= 0) { tmp._month--; if (tmp._month == 0) { tmp._year--; tmp._month = 12; } tmp._day += GetmonthDay(tmp._year, tmp._month); } return tmp; } //日期减等天数 Data& Data::operator-=(int day) { this->_day -= day; while (_day <= 0) { _month--; if (_month == 0) { _year--; _month = 12; } _day += GetmonthDay(_year, _month); } return *this; } //获取当前月天数 int Data::GetmonthDay(int year, int month) { static int Month[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 }; if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)) return 29; else return Month[month]; } int main() { Data d1(2026, 5, 17); Data d2(2026, 5, 20); Data d3(d1); //两个日期比较 cout << "两个日期比较" << endl; cout << "d2==d1?" << " " << (d2 == d1) << endl; //日期加天数 cout << "日期加天数" << endl; d3.Print(); d3 = d1 + 100; d3.Print(); d1.Print(); //日期加等天数 cout << "日期加等天数" << endl; d1.Print(); d1 += 100; d1.Print(); //日期减天数 cout << "日期减天数" << endl; Data d4(d2); d4.Print(); d4 = d2 - 1000; d4.Print(); d2.Print(); //日期减等天数 cout << "日期减等天数" << endl; d2 -= 1000; d2.Print(); return 0; }以上代码新加了运算符重载。
