c++初阶------类和对象(六大默认构造函数的揭破)-1
https://developer.aliyun.com/article/1499142
- 深拷贝
#include<iostream> using std::cout; using std::endl; using std::cin; class Stack { public: Stack(int capacity = 3) { _a = (int*)malloc(sizeof(int) * capacity); if (_a == NULL) { perror("malloc"); exit(-1); } _capacity = capacity; _top = 0; } //深拷贝 Stack(const Stack& st) { _a = (int*)malloc(sizeof(int) * st._capacity); if (_a == NULL) { perror("malloc"); exit(-1); } _capacity = st._capacity; _top = st._top; } private: int *_a; int _top; int _capacity; }; int main() { Stack d1(5); Stack d2(d1); return 0; }
创建两个一模一样的空间,深拷贝就是要进行一比一的复制,浅拷贝在这里会公用一块地址,
浅拷贝的示意图:
注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请
时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
简单的来说,如果涉及到地址就要考虑一下是否需要深拷贝,如是简单的拷贝值的化,拷贝函数可以写也可以不写,不写的话,编译器会默认构造一个拷贝函数,(浅拷贝)
#include<iostream> using std::cout; using std::endl; using std::cin; class Stack { public: //构造函数 Stack(int capacity = 3) { _a = (int*)malloc(sizeof(int) * capacity); if (_a == NULL) { perror("malloc"); exit(-1); } _capacity = capacity; _top = 0; } //深拷贝 Stack(const Stack& st) { _a = (int*)malloc(sizeof(int) * st._capacity); if (_a == NULL) { perror("malloc"); exit(-1); } _capacity = st._capacity; _top = st._top; } //析构函数 ~Stack() { free(_a); cout << "释放了空间" << endl; _a = nullptr; _top = 0; _capacity = 0; } private: int *_a; int _top; int _capacity; }; class MyQueue { private: Stack _d1; Stack _d2; int _size = 0; }; int main() { MyQueue q1; MyQueue q2(q1);//调用了拷贝构造 return 0; }
这里的情况就是q1会开辟成员变量的空间,调用了MyQueue的默认构造函数,然后_d1和_d2调用Stack的自定义的默认构造.
q1和q2之间的联系,之间调用的是拷贝函数,调用MyQueue的默认拷贝构造函数,而Stack调用的就是自定义的拷贝构造函数
一些场合
自定义当返回值
#include<iostream> using std::cout; using std::endl; using std::cin; class Stack { public: //构造函数 Stack(int capacity = 3) { _a = (int*)malloc(sizeof(int) * capacity); if (_a == NULL) { perror("malloc"); exit(-1); } _capacity = capacity; _top = 0; } //深拷贝 Stack(const Stack& st) { cout << "拷贝" << endl; _a = (int*)malloc(sizeof(int) * st._capacity); if (_a == NULL) { perror("malloc"); exit(-1); } _capacity = st._capacity; _top = st._top; } //析构函数 ~Stack() { free(_a); cout << "释放了空间" << endl; _a = nullptr; _top = 0; _capacity = 0; } private: int *_a; int _top; int _capacity; }; class MyQueue { private: Stack _d1; Stack _d2; int _size = 0; }; Stack fun() { Stack d1; return d1; } int main() { fun(); return 0; }
可以看出返回值,不会直接返回d1,而是d1的拷贝,也就是d1的临时对象
如果换成
Stack& fun() { static Stack d1; return d1; }
可以把图中的Test函数换成我们的fun函数,看懂了就可以了。
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
赋值运算符重载
运算符重载
前面我们学习C语言,知道内置类型(int 、char…)可以直接使用各种运算符,但是自定义类型是不可以的
#include<iostream> using std::cout; using std::endl; using std::cin; class Date { public: //构造函数 Date(int year, int month, int day) { cout << "构造函数" << endl; _year = year; _month = month; _day = day; } //浅拷贝 Date(const Date& d1) { cout << "拷贝" << endl; _year = d1._year; _month = d1._month; _day = d1._day; } //析构函数 ~Date() { cout << "析构函数" << endl; } int _year; int _month; int _day; }; //相等 bool Equal(Date x, Date y) { return x._year == y._year && x._month == y._month && x._day == y._day; } //大于 bool Greater(Date x, Date y) { if (x._year > y._year) { return true; } else if (x._year == y._year && x._month > y._month) { return true; } else if (x._year == y._year && x._month == y._month && x._day > y._day) { return true; } else return false; } int main() { Date a(2024,1,3); Date b(2024, 1, 5); cout << Equal(a, b) << endl; return 0; }
我们如果要比较这些自定义类型就有可能要写成函数,但是不太方便,所以c++改变了一些
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
//相等 bool operator==(Date x, Date y) { return x._year == y._year && x._month == y._month && x._day == y._day; } //大于 bool operator>(Date x, Date y) { if (x._year > y._year) { return true; } else if (x._year == y._year && x._month > y._month) { return true; } else if (x._year == y._year && x._month == y._month && x._day > y._day) { return true; } else return false; }
函数名就更改过成这样的,使用这个函数方法有两种,一种是直接写函数名直接调用
另一种
这里使用a ==b 就是 operator==(x, y)的简写,需要注意的是 << 的优先级比 == 、>…高。
函数重载和运算符重载两者毫不相关,函数重载允许参数不同的同名函数,而运算符重载允许自定义类型直接使用运算符
我们还可以在类里面写
#include<iostream> using std::cout; using std::endl; using std::cin; class Date { public: //构造函数 Date(int year, int month, int day) { cout << "构造函数" << endl; _year = year; _month = month; _day = day; } //浅拷贝 Date(const Date& d1) { cout << "拷贝" << endl; _year = d1._year; _month = d1._month; _day = d1._day; } //析构函数 ~Date() { cout << "析构函数" << endl; cout << _year << '-' << _month << '-' << _day << endl; } //相等 bool operator==(Date& y) { return _year == y._year && _month == y._month &&_day == y._day; } private: int _year; int _month; int _day; //大于 bool operator>(Date& y) { if (_year > y._year) { return true; } else if (_year == y._year && _month > y._month) { return true; } else if (_year == y._year && _month == y._month && _day > y._day) { return true; } else return false; } void exchengtime(int* a) { if ((_year % 4 == 0 && _year % 100 != 0) || (_year % 400 == 0)) a[2] = 29; else a[2] = 28; } Date& operator+=(int day)//这里返回引用是防止对象拷贝返回 { int a[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; _day += day; exchengtime(a); while (_day > a[_month]) { _day -= a[_month]; _month++; if (_month > 12) { _year++; exchengtime(a); _month %= 12; } } return *this; } }; int main() { Date a(2024,1,3); Date b(2024, 1, 5); cout << (a == b) << endl; // a.operator==(b) -> a.operator==(&a, b) a += 363; return 0; }
赋值运算符重载
有时候就是我们会看见到i = 5; j = 20;
我们有时需要对对象进行赋值
#include<iostream> //using namespace std; using std::cout; using std::endl; using std::cin; class Date { public: //构造函数 Date(int y, int m, int d) { _year = y; _month = m; _day = d; } //拷贝 Date(const Date& d1) { _year = d1._year; _month = d1._month; _day = d1._day; } //析构函数 ~Date() { cout << _year << endl; cout << _month << endl; cout << _day << endl; } Date& operator=(const Date& d1) { //防止自己给自己赋值(特别是深拷贝的时候) if (this != &d1) { _year = d1._year; _month = d1._month; _day = d1._day; } return *this; } private: int _year; int _month; int _day; }; int main() { int a = 10; cout << a << endl; Date d1(2024, 1, 15); Date d2(2024,1,16); Date d3(2024,1,17); d3 = d1 = d2; return 0; }
这样就可以不使用到拷贝构造,
还需要注意的是operator=是默认成员函数,不写编译器会默认生成,跟拷贝构造的行为类似,对内置类型进行值拷贝,自定义类型调用他的赋值
赋值运算符重载格式:
1. 参数类型:const T&,传递引用可以提高传参效率
2. 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
3. 检测是否自己给自己赋值
4. 返回*this :要复合连续赋值的含义
全部代码总和:
test.h
#pragma once #include<iostream> #include<assert.h> using std::cout; using std::endl; using std::cin; class Date { public: //构造函数 Date(int y = 2024, int m = 1, int d = 15); //拷贝 Date(const Date& d1); //析构函数 ~Date(); int Getmonthday(int year, int month); bool operator<(const Date& d1); bool operator>(const Date& d1); bool operator==(const Date& d1); bool operator<=(const Date& d1); bool operator>=(const Date& d1); Date& operator=(const Date& d1); Date& operator+=(int day); Date operator+(int day); private: int _year; int _month; int _day; };
test.cpp
#include"Date.h" Date::Date(int y, int m, int d) { _year = y; _month = m; _day = d; if (_year < 0 || _month < 1 || _month >12 || _day > Getmonthday(_year, _month) || _day < 0) { cout << _year << "-" << _month << "-" << _day << endl; cout << "非法日期" << endl; exit(-1); } } int Date::Getmonthday(int year, int month) { int monthday[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) { monthday[2] = 29; } return monthday[month]; } //拷贝 Date::Date(const Date& d1) { _year = d1._year; _month = d1._month; _day = d1._day; } //析构函数 Date::~Date() { cout << _year << endl; cout << _month << endl; cout << _day << endl; } Date& Date::operator=(const Date& d1) { //防止自己给自己赋值(特别是深拷贝的时候) if (this != &d1) { _year = d1._year; _month = d1._month; _day = d1._day; } return *this; } Date& Date::operator+=(int day) { *this = *this + day; return *this; } Date Date::operator+(int day) { Date tmp(*this); int monthday[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; tmp._day += day; if ((tmp._year % 4 == 0 && tmp._year % 100 != 0) || (tmp._year % 400 == 0)) { monthday[2] = 29; } else { monthday[2] = 28; } while (tmp._day - monthday[tmp._month] > 0) { tmp._day -= monthday[tmp._month]; tmp._month++; if (tmp._month > 12) { tmp._month %= 12; tmp._year++; if ((tmp._year % 4 == 0 && tmp._year % 100 != 0) || (tmp._year % 400 == 0)) { monthday[2] = 29; } else { monthday[2] = 28; } } } return tmp; } bool Date::operator<(const Date& d1) { if (_year < d1._year) { return true; } if (_year == d1._year && _month < d1._month) { return true; } if (_year == d1._year && _month == d1._month && _day < d1._day) { return true; } return false; } bool Date::operator==(const Date& d1) { return _year == d1._year && _month == d1._month && _day == d1._day; } bool Date::operator<=(const Date& d1) { return (*this) < d1 || (*this) == d1; } bool Date::operator>(const Date& d1) { return !((*this) <= d1); } bool Date::operator>=(const Date& d1) { return !((*this)< d1); }
test1.cpp
#include"Date.h" int main() { Date d1(2024, 1, 3); Date d2(2024,1,16); Date d3(2020,1,17); int a = d1 == d2; cout << a << endl; a = d1 >= d2; cout << a << endl; a = d1 <= d2; cout << a << endl; d3 += 366; return 0; }
需要注意的是代码中的**operator+(int day)**函数 和 **operator+=(int day)**函数的效率是很低效的
这张图里面可以说明,上图的代码的对象拷贝是次数多,造成了效率的低效,
对象的赋值进行了一次拷贝对象
*this + day 创建了一个临时的 Date 对象,表示当前对象的值加上 day 天的结果。*this = … 将上面提到的临时对象的值赋给了当前对象 *this。这个赋值操作涉及到对象的拷贝。赋值操作符 = 的行为是根据类的定义而定的,,它会执行对象的成员逐个拷贝。
实现 - 和-=
这里的实现方法和上面的方法是不一样的,
Date& Date::operator-=(int day) { _day -= day; int monthday[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if ((_year % 4 == 0 && _year % 100 != 0) || (_year % 400 == 0)) { monthday[2] = 29; } else { monthday[2] = 28; } while (_day <= 0) { _month--; if (_month <= 0) { _year--; if ((_year % 4 == 0 && _year % 100 != 0) || (_year % 400 == 0)) { monthday[2] = 29; } else { monthday[2] = 28; } _month = 12; } _day += monthday[_month]; } return *this; } Date Date::operator-(int day) { Date tmp(*this); tmp -= day; return tmp; }
可以看看这里,这里的情况就是,在operator-函数里面套用operator-=,这样的效率就变高了
实现++
前置++
Date& Date::operator+=(int day) { _day += day; int monthday[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if ((_year % 4 == 0 && _year % 100 != 0) || (_year % 400 == 0)) { monthday[2] = 29; } else { monthday[2] = 28; } while (_day - monthday[_month] > 0) { _day -= monthday[_month]; _month++; if (_month > 12) { _month %= 12; _year++; if ((_year % 4 == 0 && _year % 100 != 0) || (_year % 400 == 0)) { monthday[2] = 29; } else { monthday[2] = 28; } } } return *this; } Date& Date::operator++() { *this += 1; return *this; }
后置++
后置++不能和上面写的一样,因为要构成重载,我们只能从参数那里下手,不然就会无法构成重载,
Date Date::operator++(int) { Date tmp(*this); *this += 1; return tmp; }
编译器链接的时候,函数的名字只和函数的名字和函数的参数类型有关,跟函数的形参名字没有关系。
就是说c++的函数在链接的时候有自己的函数名修饰规则,
c++初阶------类和对象(六大默认构造函数的揭破)-3