C++学习历程:类和对象
- 博客主页:一起去看日落吗
- 持续分享博主的C++学习历程
博主的能力有限,出现错误希望大家不吝赐教
- 分享给大家一句我很喜欢的话: 也许你现在做的事情,暂时看不到成果,但不要忘记,树成长之前也要扎根,也要在漫长的时光中沉淀养分。静下来想一想,哪有这么多的天赋异禀,那些让你羡慕的优秀的人也都曾默默地翻山越岭。
中篇是类和对象的重点
目录
- 1. 类的6个默认成员函数
- 2. 构造函数
- 2.1 概念
- 2.2 特性
- 3. 析构函数
- 3.1 概念
- 3.2 特性
- 4. 拷贝构造函数
- 4.1 概念
- 4.2 特征
- 5. 运算符重载
- 5.1 运算符重载
- 5.2 赋值运算符重载
- 5.2.1 赋值运算符重载格式
- 5.2.2 赋值运算符只能重载成类的成员函数不能重载成全局函数
- 5.2.3 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
- 5.3 前置++和后置++重载
- 6. 日期类的实现
- 6.1 Date. hpp
- 6.2 Date.cpp
- 6.3 main.cpp
- 7. const成员
- 8. 取地址及const取地址操作符重载
1. 类的6个默认成员函数
如果一个类中什么成员都没有,简称为
空类
。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面 6 个默认成员函数
。
class Date {};
2. 构造函数
2.1 概念
场景
class Date{public:void Init(int year, int month, int day){ _year = year; _month = month; _day = day;}void Print(){ std::cout << _year << "-" << _month << "-" << _day << std::endl;}private: int _year; int _month; int _day;};int main(){ Date d1; d1.Init(2022, 7, 5); d1.Print(); Date d2; d2.Init(2022, 7, 6); d2.Print(); return 0;}
- 对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
class Date{public:// void Init(int year, int month, int day)// {// _year = year;// _month = month;// _day = day;// } Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; }// Date()// {// _year = 1;// _month = 1;// _day = 1;// } void Print() { std::cout << _year << "-" << _month << "-" << _day << std::endl; }private: int _year; int _month; int _day;};int main(){// Date d1;// d1.Init(2022, 7, 5);// d1.Print();// Date d2;// d2.Init(2022, 7, 6);// d2.Print(); Date d1(2022, 7, 5); d1.Print(); Date d2; d2.Print(); Date d3(2022); d3.Print(); return 0;}
2.2 特性
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是
构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
class Date{public:// 1.无参构造函数Date(){}// 2.带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;};void TestDate(){Date d1; // 调用无参构造函数Date d2(2015, 1, 1); // 调用带参的构造函数// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的" /> 拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
。
4.2 特征
拷贝构造函数也是特殊的成员函数,其特征如下:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
class Date{public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//Date d3(d2);//Date(Date d)//{//_year = d._year;//_month = d._month;//_day = d._day;//}//Date d3(d2);Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}//Date d3(d2);Date(const Date* p){_year = p->_year;_month = p->_month;_day = p->_day;}void Print(){cout << _year << "/"<< _month << "/" << _day <<endl;} private:int _year;int _month;int _day;};int main(){Date d1;Date d2(2022, 1, 1);d1.Print();d2.Print();//拷贝复制一个d2对象Date d3(d2);d3.Print();//指针也可以,但不好Date d4(&p2);d4.Print();return 0;}
- 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做
浅拷贝
,或者值拷贝。
class Time{public:Time(){_hour = 1;_minute = 1;_second = 1;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const Time&)" << endl;}private:int _hour;int _minute;int _second;};class Date{private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;};int main(){Date d1;// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数Date d2(d1);return 0;}
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
- 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。typedef int DataType;class Stack{public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}private:DataType *_array;size_t _size;size_t _capacity;};int main(){Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0;}
注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
- 拷贝构造函数典型调用场景:
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
class Date{public:Date(int year, int minute, int day){cout << "Date(int,int,int):" << this << endl;}Date(const Date& d){cout << "Date(const Date& d):" << this << endl;}~Date(){cout << "~Date():" << this << endl;}private:int _year;int _month;int _day;};Date Test(Date d){Date temp(d);return temp;}int main(){Date d1(2022,1,13);Test(d1);return 0;}
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
5. 运算符重载
5.1 运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof " />5.2.3 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
class Time{public:Time(){_hour = 1;_minute = 1;_second = 1;}Time& operator=(const Time& t){if (this != &t){_hour = t._hour;_minute = t._minute;_second = t._second;}return *this;}private:int _hour;int _minute;int _second;};class Date{private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;};int main(){Date d1;Date d2;d1 = d2;return 0;}
既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。typedef int DataType;class Stack{public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}private:DataType *_array;size_t _size;size_t _capacity;};int main(){Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2;s2 = s1;return 0;}
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现
。
5.3 前置++和后置++重载
class Date{public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 前置++:返回+1之后的结果// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率Date& operator++(){_day += 1;return *this;}// 后置++:// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this+1// 而temp是临时对象,因此只能以值的方式返回,不能返回引用Date operator++(int){Date temp(*this);_day += 1;return temp;}private:int _year;int _month;int _day;};int main(){Date d;Date d1(2022, 1, 13);d = d1++; // d: 2022,1,13 d1:2022,1,14d = ++d1; // d: 2022,1,15 d1:2022,1,15return 0;}
6. 日期类的实现
6.1 Date. hpp
//// Date.hpp// 日期类的实现//// Created by 卜绎皓 on 2022/9/20.//#ifndef Date_hpp#define Date_hpp#endif /* Date_hpp */#pragma once#include#include#includeusing namespace std;class Date{public: //获取某年某月的天数 int GetMonthDay(int year, int month) { assert(month > 0 && month < 13); //默认平年 int monthDays[13] = { 0, 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 monthDays[month]; } Date(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; //判断日期是否合法 if (_year < 0 || _month <= 0 || _month >= 13 || _day <= 0 || _day > GetMonthDay(_year, _month)) { cout << _year << "/" << _month << "/" << _day << "->"; cout << "非法日期" << endl; } } void Print() const { cout << _year << "/" << _month << "/" << _day << endl; } bool operator>(const Date& d) const { if (_year > d._year) return true; else if (_year == d._year && _month > d._month) return true; else if (_year == d._year && _month == d._month && _day > d._day) return true; else return false; } bool operator==(const Date& d) const { return _year == d._year && _month == d._month && _day == d._day; } bool operator>=(const Date& d) const { return *this > d || *this == d;//复用operator>、operator== } bool operator<(const Date& d) const { return !(*this >= d);//复用operator>=,再取反 } bool operator<=(const Date& d) const { return !(*this > d);//复用operator>,再取反 } bool operator!=(const Date& d) const { return !(*this == d);//复用operator==,再取反 } Date operator+(int day) const; Date operator-(int day) const; Date& operator+=(int day); Date& operator-=(int day); Date& operator++(); Date operator++(int); Date& operator--(); Date operator--(int); int operator-(const Date& d) const;private: int _year; int _month; int _day;};
6.2 Date.cpp
//// Date.cpp// 日期类的实现//// Created by 卜绎皓 on 2022/9/20.//#include "Date.hpp"Date Date::operator+(int day) const{ //Date temp(*this); //temp._day += day; //while (temp._day > GetMonthDay(temp._year, temp._month)) //{ // temp._day -= GetMonthDay(temp._year,temp._month); // ++temp._month; // if (temp._month == 13) // { // ++temp._year; // temp._month = 1; // } //} //return temp; //相同逻辑太多,直接复用operator+= (这个比较好) Date temp = *this; temp += day; return temp;}Date& Date::operator+=(int day){ if (day < 0)//d1 + -100; { return *this -= -day;//+= -100到-= 100 } _day += day; //日期不合法,需要进位 while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); ++_month; if (_month == 13) { ++_year; _month = 1; } } return *this; //相同逻辑太多,直接复用operator+ //*this = *this + day; //return *this;}Date Date::operator-(int day) const{ Date temp = (*this); temp -= day; return temp;}Date& Date::operator-=(int day){ if (day < 0) { return *this += -day; } _day -= day; while (_day < 1) { --_month; if (_month == 0) { --_year; _month = 12; } _day += GetMonthDay(_year, _month); } return *this;}Date& Date::operator++(){ *this += 1;//复用operator+= return *this;//返回++后的值}Date Date::operator++(int){ Date temp = *this; *this += 1;//复用operator+= return temp;//返回++前的值}Date& Date::operator--(){ *this -= 1;//复用operator-= return *this;//返回--后的值}Date Date::operator--(int){ Date temp(*this); *this -= 1;//复用operator-= return temp;//返回--前的值}int Date::operator-(const Date& d) const{ //比较大小 Date max = *this, min = d; int flag = 1; if (*this < d) { max = d; min = *this; flag = -1; } int n = 0; while (min != max) { ++min;//利用operator++() ++n; } return n * flag;}
6.3 main.cpp
//// main.cpp// 日期类的实现//// Created by 卜绎皓 on 2022/9/20.//#include"Date.hpp"void TestDate1(){ Date d1(2021, 10, 11);//ok Date d2(2021, 2, 29);//err Date d3(2020, 2, 29);//ok Date d4(2020, 13, 29);//err}void TestDate2(){ Date d1(2021, 10, 11); Date ret; ret = d1 + 100; ret.Print(); ret = d1 += 100; ret.Print(); d1.Print(); ret = d1 + -100; ret.Print();}void TestDate3(){ Date d1(2021, 10, 11); Date ret; ret = d1 - 11; ret.Print(); ret = d1 -= 11; ret.Print(); d1.Print(); ret = d1 - -11; ret.Print();}void TestDate4(){ Date d1(2021, 10, 11); Date ret; ret = ++d1; ret.Print(); d1.Print(); ret = d1++; ret.Print(); d1.Print();}void TestDate5(){ Date d1(2021, 10, 11); Date ret; ret = --d1; ret.Print(); d1.Print(); ret = d1--; ret.Print(); d1.Print();}void TestDate6(){ Date d1(2023, 10, 11); Date d2(2022, 10, 11); cout << d1 - d2 << endl; cout << d2 - d1 << endl;}int main(){ //TestDate1();//日期合法 TestDate2();//+、+=、+ -(负) //TestDate3();//-、-=、- -(负) //TestDate4();//++x、x++ //TestDate5();//--x、x-- //TestDate6();//d1 - d2 return 0;}
7. const成员
- 将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
class Date{public:Date(int year = 0, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print1()//void Print(Date* this){cout << _year << "/" << _month << "/" << _day << endl;}void Print2() const//void Print(const Date* this){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;};int main(){Date d1(2021, 10, 13);//Date d2(2021, 10, 14);//ok//const Date d2(2021, 10, 14);//errd1.Print1();//d2.Print1();//d2.Print(&d2);//errconst Date d3(2021, 10, 14);//okd3.Print2();//d3.Print(&d3);return 0;}
- 成员函数加 const,变成 const 成员函数是有好处的,这样 const 对象可以调用,非 const 对象也可以调用。
- 不是说所有的成员函数都要加 const ,具体要看成员函数的功能,
如果成员函数是修改型 (operrato+=、Push),那就不能加;如果是只读型 (Print、operator+),那就最好加。
- const 对象不可以调用非 const 成员函数
(权限放大)
;非 const 对象可以调用 const 成员函数 (权限缩小)
。 const 成员函数内不可以调用其它非 const 成员函数;非 const 成员函数内可以调用其它 const 成员函数。
8. 取地址及const取地址操作符重载
- 这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date{public :Date* operator&(){return this ;}const Date* operator&()const{return this ;}private :int _year ; // 年int _month ; // 月int _day ; // 日};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!