有不懂的可以翻阅我之前文章!
个人主页:CSDN_小八哥向前冲
所属专栏:CSDN_C++入门
目录
拷贝构造函数
运算符重载
赋值运算符重载
取地址运算符重载
const成员函数
取地址重载
再探构造函数
初始化列表
类型转换
static成员
友元
内部类
匿名对象
对象拷贝时的编译器优化
日期类的实现
Date.h文件
Data.cpp文件
拷贝构造函数
拷贝构造的特点:
- 拷⻉构造函数是构造函数的⼀个重载。
- . 拷⻉构造函数的参数只有⼀个且必须是类类型对象的引⽤,使⽤传值⽅式编译器直接报错,因为语 法逻辑上会引发⽆穷递归调⽤。
- C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥⾃定义类型传值传参和传值返 回都会调⽤拷⻉构造完成。
- 若未显式定义拷⻉构造,编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的拷⻉构造。
- 传值返回会产⽣⼀个临时对象调⽤拷⻉构造,传值引⽤返回,返回的是返回对象的别名(引⽤),没 有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤ 引⽤返回是有问题的,这时的引⽤相当于⼀个野引⽤,类似⼀个野指针⼀样。传引⽤返回可以减少 拷⻉,但是⼀定要确保返回对象,在当前函数结束后还在,才能⽤引⽤返回。
拷贝构造参数必须用引用,否则编译错误,因为会引发无穷递归的拷贝构造!
如图:
运算符重载
运算符重载特点:
- 运算符重载是具有特名字的函数,他的名字是由operator和后⾯要定义的运算符共同构成。和其他 函数⼀样,它也具有其返回类型和参数列表以及函数体。
- 重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数,⼆元 运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。
- 如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算 符重载作为成员函数时,参数⽐运算对象少⼀个。
- .* : : sizeof ?: . ) 注意以上5个运算符不能重载。
- 重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分。 C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,⽅便区分。
- 重载>>和<<时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第⼀个形参位置,第⼀个形参位置是左侧运算对象,调⽤时就变成了对象<<cout,不符合使⽤习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了,第⼆个形参位置当类类型对象。
前置++和后置++的区别:
赋值运算符重载
赋值运算符重载注意事件:
- 赋值运算符重载是⼀个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成 const 当前类类型引⽤,否则会传值传参会有拷⻉。
- 有返回值,且建议写成当前类类型引⽤,引⽤返回可以提⾼效率,有返回值⽬的是为了⽀持连续赋 值场景。
- 没有显式实现时,编译器会⾃动⽣成⼀个默认赋值运算符重载,默认赋值运算符重载⾏为跟默认构 造函数类似,对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型 成员变量会调⽤他的拷⻉构造。
参数和返回值写成引用的形式的原因:
可以提高效率,避免拷贝,因为传值传参会有拷贝!
注意:
- 当只是简单的赋值运算(浅拷贝),那么可以不写这个赋值重载,编译器自身的就能解决!
- 当需要深层次的拷贝(涉及指针指向空间的这种),那么就要自己实现赋值函数!
还有一种情况赋值重载会和拷贝构造混淆:
取地址运算符重载
const成员函数
特点:
- 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后 ⾯。
- const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。
不管变量还是函数利用const 修饰都有一个共同的特点:不变。
值得我们注意的是——不能权限扩大!
取地址重载
取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器⾃动⽣成的就可以够我们⽤了,不需要去显⽰实现。除⾮⼀些很特殊的场景,⽐如我们不想让别⼈取到当 前类对象的地址,就可以⾃⼰实现⼀份,胡乱返回⼀个地址。
这个不怎么常用,我们不做过多的介绍。
再探构造函数
初始化列表
内容:
- 之前我们实现构造函数时,初始化成员变量主要使⽤函数体内赋值,构造函数初始化还有⼀种⽅式,就是初始化列表,初始化列表的使⽤⽅式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成 员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式。
- 每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义 初始化的地⽅。
- 引⽤成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进⾏初始化,否则会编译报错。
- C++11⽀持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显⽰在初始化列表初始化的 成员使⽤的。
- 尽量使⽤初始化列表初始化,因为那些你不在初始化列表初始化的成员也会⾛初始化列表,如果这 个成员在声明位置给了缺省值,初始化列表会⽤这个缺省值初始化。如果你没有给缺省值,对于没 有显⽰在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有 显⽰在初始化列表初始化的⾃定义类型成员会调⽤这个成员类型的默认构造函数,如果没有默认构 造会编译错误。
- 初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序⽆ 关。建议声明顺序和初始化列表顺序保持⼀致。
第三点的原因:
因为这三种类型变量定义时必须要初始化,否则就报错!
初始化列表的使用形式:
初始化列表初始化数据规则:
我们来看个题目:
类型转换
- C++⽀持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
- 构造函数前⾯加explicit就不再⽀持隐式类型转换。
static成员
内容:
- ⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进⾏初始化。(可以在类里面声明)。
- 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
- ⽤static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
- 静态成员函数中可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。
- ⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
- 突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量 和静态成员函数。
- 静态成员也是类的成员,受public、protected、private访问限定符的限制。
- 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员 变量不属于某个对象,不⾛构造函数初始化列表。
看题:
友元
- 友元提供了⼀种突破类访问限定符封装的⽅式,友元分为:友元函数和友元类,在函数声明或者类 声明的前⾯加friend,并且把友元声明放到⼀个类的⾥⾯。
- 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。
- 友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制。
- ⼀个函数可以是多个类的友元函数。
- 友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
- 友元类的关系是单向的,不具有交换性,⽐如A类是B类的友元,但是B类不是A类的友元。
- 友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是B的友元。
- 有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多⽤。
内部类
- 如果⼀个类定义在另⼀个类的内部,这个内部类就叫做内部类。内部类是⼀个独⽴的类,跟定义在 全局相⽐,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
- 内部类默认是外部类的友元类。
- 内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使⽤,那么可以考 虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其 他地⽅都⽤不了。
匿名对象
- ⽤类型(实参)定义出来的对象叫做匿名对象,相⽐之前我们定义的类型对象名(实参)定义出来的 叫有名对象。
- 匿名对象⽣命周期只在当前⼀⾏,⼀般临时定义⼀个对象当前⽤⼀下即可,就可以定义匿名对象。
对象拷贝时的编译器优化
- 现代编译器会为了尽可能提⾼程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传参过程中可以省略的拷⻉。
- 如何优化C++标准并没有严格规定,各个编译器会根据情况⾃⾏处理。当前主流的相对新⼀点的编 译器对于连续⼀个表达式步骤中的连续拷⻉会进⾏合并优化,有些更新更"激进"的编译还会进⾏跨 ⾏跨表达式的合并优化。
我们知道传值传参会进行拷贝构造,如果很多地方我们都不用引用的话,就会出现大量的拷贝构造,这时候编译器会优化,直接跳过临时对象这一步骤,直接构造!
日期类的实现
那么我们类和对象就已经全部搞定!
我们来实现一个日期类巩固一下!比较简单,我们就不做过多解释,直接上代码!
Date.h文件
#include<iostream>
using namespace std;
#include<assert.h>namespace ywc
{class Date{public:friend istream& operator>>(istream& in, Date& d);friend ostream& operator<<(ostream& out, Date& d);Date(int year=1,int month=1,int day=1):_year(year),_month(month),_day(day){if (!CheckDate()){cout << "日期非法" << endl;cout << *this;}}int GetMonthDay(){assert(_month < 13 && _month > 0);int MonthDay[] = { -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;}return MonthDay[_month];}bool CheckDate(){if (_year > 0){if (_month > 0 && _month < 13){if (_day > 0 && _day <= GetMonthDay()){return true;}}}return false;}~Date(){_year = _month = _day = 0;}//日期+/-天数Date operator+(int day);Date operator-(int day);Date& operator+=(int day);Date& operator-=(int day);//前置++Date operator++();Date& operator++(int);//日期-日期int operator-(Date& d);//日期比较大小bool operator==(Date& d);bool operator<(Date& d);bool operator>(Date& d);bool operator<=(Date& d);bool operator>=(Date& d);bool operator!=(Date& d);private:int _year;int _month;int _day;};//全局输入输出函数istream& operator>>(istream& in, Date& d);ostream& operator<<(ostream& out, Date& d);
}
Data.cpp文件
#include"Date.h"namespace ywc
{Date& Date::operator+=(int day){if (day < 0){return *this -= (-day);}_day += day;while (_day > GetMonthDay()){_day -= GetMonthDay();_month++;if (_month == 13){_month = 1;_year++;}}return *this;}Date Date::operator+(int day){Date tmp = *this;return tmp += day;}Date& Date::operator-=(int day){if (day < 0){return *this += (-day);}_day -= day;while (_day <= 0){_month--;if (_month == 0){_year--;_month = 12;}_day += GetMonthDay();}return *this;}Date Date::operator-(int day){Date tmp = *this;return tmp -= day;}int Date::operator-(Date& d){int flag = 1;Date max =*this, min = d;if (max < min){max = d;min = *this;flag = -1;}int n = 0;while (min!=max){++min;++n;}return n*flag;}Date Date::operator++(){Date tmp = *this;*this += 1;return tmp;}Date& Date::operator++(int){return (*this += 1);}bool Date::operator==(Date& d){if (_year == d._year && _month == d._month && _day == d._day){return true;}return false;}bool Date::operator!=(Date& d){return !(*this == d);}bool Date::operator<(Date& d){if (_year < d._year){return true;}else if(_year==d._year){if (_month < d._month){return true;}else if (_month == d._month){if (_day < d._day){return true;}}}return false;}bool Date::operator>(Date& d){return !(*this < d);}bool Date::operator<=(Date& d){return *this < d || *this == d;}bool Date::operator>=(Date& d){return *this > d || *this == d;}istream& operator>>(istream& in, Date& d){in >> d._year >> d._month >> d._day;while (!d.CheckDate()){cout << "日期非法,请重新输入" << endl;in >> d._year >> d._month >> d._day;}return in;}ostream& operator<<(ostream& out, Date& d){out << d._year << "年" << d._month << "月" << d._day << "日";return out;}
}
相信看到这里你已经收获满满了,我们下期见!