C++类与对象(中)【详细】
创始人
2024-06-03 14:21:19
0

类与对象(中)

目录

  • 类与对象(中)
    • 类的6个默认成员函数
    • ==Type One:初始化和清理==
      • 1.构造函数:初始化
      • 2.析构函数:销毁
        • 注意1:
        • 注意2:
    • ==Type Two:拷贝复制==
      • 3.拷贝构造函数
        • 注意1:
        • 注意2:
        • 深拷贝
      • 4.赋值运算符重载
        • 示例1:
        • 示例2:
        • 示例3:
        • 示例4:
        • 注意:
      • 5.赋值重载
      • ==6.日期类的实现==
        • 声明:
        • 实现:
      • 7.const成员
    • ==Type Three:取地址重载==
      • 8.取地址重载

类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员 函数。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

特殊成员函数,我们不写,编译器会自己生成一个;我们自己写了,编译器就不会生成

class Date();

image-20230228190024031


Type One:初始化和清理

1.构造函数:初始化

C语言使用时,经常忘记Init(初始化)就使用,忘记Destroy

//原来我们需要使用下面方式来初始化
Class Date
{public:void Init(int year,int month,int day){_year=year;_month=month;_day=day;}
}

能不能在对象定义的时候就将其初始化呢?—— 构造函数

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次。

其特征如下:

1. 函数名与类名相同。
2. 无返回值。(void都不用写)
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载:提供多种构造函数,多种初始化方式

构造函数的作用是初始化,具体表现如下:

Class Date
{private:int _year;int _month;int _day;public://初始化Date(int year,int month,int day){_year=year;_month=month;_day=day;}  
}
int main()
{Date d1(2023,1,28);Date d2(2023,1,29);Date d3;
}

image-20230128144047990

当然,由于初始化代表的是日期,所以在初始化时我们检验输入的日期的合法性:

Date(int year = 1, int month = 1, int day = 1)
{_year = year;_month = month;_day = day;//检查日期是否合法if (!(year >= 1&& (month >= 1 && month <= 12)&& (day >= 1 && day <= GetMonthDay(year, month)))){cout << "非法日期" << endl;}
}

因此当我们输入两个非法日期后,会出现:

image-20230228141947268


再例如我们使用构造函数来实现栈时:

class Stack
{public://栈的初始化(构造函数)Stack(int capacity=4){_a=malloc(sizeof(int)*capacity);if(a==nullptr){perror("malloc fail");exit(-1);}_top=0;_capacity=capacity;}//...
}

如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成

假如我们未定义构造函数,那么编译器自动生成结果如下:

image-20230130150959347

关于编译器生成的默认成员函数,对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用?

解答: C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如: int/char… ,自定义类型就是我们使用class/struct/union等自己定义的类型,对于默认生成的构造函数,不会对内置类型处理,对于自定义类型会调用它的默认构造。

因此总结一下:上述的Stack和Date的构造函数需要自己写,因为要按照自己的意愿进行初始化

2.析构函数:销毁

构造函数让我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

即与我们常用的Destory函数功能相似

其特征如下:

1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时(出作用域),C++编译系统系统自动调用析构函数。
//析构函数:类名前加上字符 ~
~Stack()
{free(_a);_capacity=0;_size=0;
}

因此总结一下:这里的Stack就需要自己写析构函数,释放对应空间

面向需求:编译器默认生成就可以满足,就不用自己写,不满足就需要自己写

注意1:

C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值

例如:

class MyQueue {
public:void push(int x){_pushST.Push(x);}
public:Stack _pushST;   //自定义类型Stack _popST;    //自定义类型size_t _size;    //内置类型;unsigned int
};

初始化为0:

image-20230226194154483

注意2:

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

例如:

//无参的构造函数
Date()
{_year = 1;_month = 2;_day = 3;
}
//全缺省的构造函数
Date(int year = 1, int month = 1, int day = 1)
{_year = year;_month = month;_day = day;
}

指定了多个默认构造:

image-20230226195616043

不传参数就可以调用的构造函数就叫默认构造

我们将无参构造函数去掉:

image-20230226200257625

这里的Date(int year , int month , int day)构造函数需要传递参数,由上述定义可知,我们自定义了构造函数------>系统不会生成构造函数------>需要传递参数------>不是默认构造函数,因此会出现这类错误

Type Two:拷贝复制

3.拷贝构造函数

引入:

int main()
{Date d1(2023,2,26);   //构造//拷贝一份d1Date d2(d1);           //拷贝构造 -- 拷贝初始化
}

拷贝构造函数也是特殊的成员函数,其特征如下:

1.拷贝构造函数是构造函数的一个重载形式
2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。

例如,我们定义Date类,通过拷贝构造函数实现如下:

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// Date(Date d)   // 错误写法Date(Date& d)   // 正确写法:编译报错,会引发无穷递归{_year = d._year;_month = d._month;_day = d._day;}
public:int _year;int _month;int _day;
};
int main() {Date d1(2023, 2, 27);Date d2(d1);                  //拷贝构造 -- 拷贝初始化cout << d1._year << "," << d1._month << "," << d1._day << endl;cout << d2._year << "," << d2._month << "," << d2._day << endl;return 0;
}

image-20230227093629731

理解:拷贝构造函数的参数只有一个且必须是类类型对象的引用

image-20230227094533687

如上述程序,我们定义func1和func2两个函数,分别是传值传参(形参是实参的拷贝)和引用传参(形参是实参的别名),因此在每次传值传参时会调用拷贝构造,会引起无穷递归

image-20230227131141535

拷贝构造------>调用该函数------>传参------->传参又是一个拷贝构造------>…(无穷递归)

注意1:

形参加const,防止写反,即:

Date(const Date& d)   // 正确写法:编译报错,会引发无穷递归{_year = d._year;_month = d._month;_day = d._day;}

注意2:

在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗? 当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

①对于自定义类型Date,默认拷贝构造如下:

image-20230227131841216

该默认拷贝构造函数实现了我们的目标

②对于自定义类型Stack(栈),默认拷贝构造如下:

image-20230227132209302

这里为什么报错呢,是因为在st1结束后会自动调用析构函数,而在调用完析构函数后,由于st2是st1的拷贝它们指向同一块空间,因此此时st2所指向的区域已被销毁形成野指针,此时默认生成的拷贝构造函数不能实现了我们的目标

编译器默认生成的拷贝构造函数为:浅拷贝

深拷贝

为了解决浅拷贝在某类场景下不适用的情况,我们引用的深拷贝,以Stack为例,如下:

//st2(st1)
Stack(const Stack& st)
{//开一个与st1相同大小的空间_a = (int*)malloc(sizeof(int) * st._capacity);if (_a == nullptr){perror("malloc fail");exit(-1);}//拷贝该空间上的值memcpy(_a, st._a, sizeof(int) * st._top);_top = st._top;_capacity = st._capacity;
}

可表示为下图:

image-20230227133220948

总结:

需要些析构函数的类,都需要写深拷贝的拷贝构造(Stack)

不需要写析构函数的类,默认生成的浅拷贝的拷贝构造就可以用(date)


4.赋值运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号

函数原型: 返回值类型 operator操作符(参数列表)

示例1:

例如我们在Date类中比较两个日期是否相等:

bool operator==(const Date& d1,const Date& d2)
{return d1._year==d2._year&&d1._month==d2._month&&d1._day==d2._day;
}

即:

image-20230227135245332

这里的:

d1==d2;    //编译器会转换成operator==(d1,d2);

上述对于比较的结果,可更改为:(注意运算符的优先级)

image-20230227135706111


问题:若属性是私有的,怎么访问呢?

image-20230227135933445

方式一:设置一个共有的get方法

方式二:放到类里面

//更改如下:
bool operator==(const Date& d)
{return _year==d._year&&_month==d._month&&_day==d._day;
}

示例2:

我们再来尝试Date类日期间大于小于的比较:

//放在类里面的
bool operator>(const Date& d)
{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;}return false;
}

示例3:

Date类日期间的大于等于:

bool operator>=(const Date& d)
{//this代表d1的地址,因此对this解引用即指向d1//复用上述已经书写的大于和等于的判定return *this>d||*this==d;
}

示例4:

日期加天数怎么办呢?(例如算某天过100天之后的日期)

函数名:operator+=
参数:(int day)
返回类型:Date

得到每个月的天数;天满了进月,月满了进年

Step1:得到每个月的天数(判定是平年还是闰年)

int GetMonthDay(int year,int month)
{static int monthDayArray[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;}else{return monthDayArray[month];}
}

Step2:加上天数

Date operator+=(int day)
{_day+=day;//日进while(_day>GetMonthDay(year,month)){_day-=GetMonthDay(year,month);_month++;//月进if(_month==13){_year++;_month=1;}}return *this;
}

注意这里*this的作用

因此结果如下:

image-20230227170645274

很巧妙的是,在编写这篇文章时(2023年2月27日),再加100天就是6月7日,恰好是今年高考的百日誓师!

我们上述所书写的operator+、operator>等函数功能,我们依然可以通过定义普通的函数名实现,那么我们定义运算符重载的意思是什么呢?

运算符重载的真正意义是增加可读性!

注意:

1.不能通过连接其他符号来创建新的操作符:比如operator@

2.重载操作符必须有一个类类型参数(针对自定义类型的)

3.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义

4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

5.image-20230228181157954注意以上5个运算符不能重载

5.赋值重载

区分:

Date d2(d1);   //拷贝构造(本质是初始化)   一个初始化另一个马上要创建的对象
d3=d1;         //赋值重载(复制拷贝)      已经存在两个对象直接的拷贝

6.日期类的实现

在上述的例子中,日期类出现的频率极高,可见它是一个非常经典的例子,因此我们在此将日期类的全部用法实现,具体如下:

✍首先,由于日期类所涉及到的函数接口较多,因此我们定义Date.h头文件包含日期类的接口,而Date.cpp文件为我们具体实现日期类的方式

为什么这里我们只在Date.h中提供接口呢(声明)?

答:全局函数在.h文件中会导致重定义,是由于Date.cpp和Test.cpp文件包含了Date.h,因此定义在.h中会在符号表拷贝多次,自己与自己发生重定义,因此才将声明与实现分离;或者置为静态的(static)

.h文件中尽量不含全局变量和函数,否则会出现链接问题

声明:

#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;class Date
{//友元声明(类中的任意位置)friend inline ostream& operator<<(ostream& out, const Date& d);friend inline istream& operator>>(istream& in, Date& d);
private:int _year;int _month;int _day;
public:// 获取某年某月的天数int GetMonthDay(int year, int month);// 全缺省的构造函数Date(int year = 2000, int month = 1, int day = 1);// 拷贝构造函数// d2(d1)Date(const Date& d);//日期类打印void Print() const;// 赋值运算符重载// d2 = d3 -> d2.operator=(&d2, d3)Date& operator=(const Date& d);// 析构函数~Date();// 日期+=天数Date& operator+=(int day);// 日期+天数Date operator+(int day) const;// 日期-天数Date operator-(int day) const;// 日期-=天数Date& operator-=(int day);// 前置++Date& operator++();// 后置++Date operator++(int);// 前置--Date& operator--();// 后置--Date operator--(int);// >运算符重载bool operator>(const Date& d) const;// ==运算符重载bool operator==(const Date& d) const;// >=运算符重载bool operator >= (const Date& d) const;// <运算符重载bool operator < (const Date& d) const;// <=运算符重载bool operator <= (const Date& d) const;// !=运算符重载bool operator != (const Date& d) const;// 日期-日期 返回天数int operator-(const Date& d) const;
};// 流插入的重载
inline ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}// 流提取的重载
inline istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}

实现:

#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"
using namespace std;//1.月份内的天数
int Date::GetMonthDay(int year, int month)
{int monthDay[] = {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;}else{return monthDay[month];}
}//2.构造函数
//这里最好写一个全缺省的构造函数,这样即使不传参对象也会被初始化
Date::Date(int year, int month, int day)
{//这里的缺省值已经在声明的时候给出了_year = year;_month = month;_day = day;
}//3.拷贝构造函数
//拷贝构造函数必须是传值引用传参,并且最好加上const防止拷贝的时候实参被改变
//这里的日期类可以不用书写,编译器会自动给出
Date::Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}//4.析构函数
Date::~Date()
{_year = 0;_month = 0;_day = 0;
}//5.打印日期
void Date::Print() const
{cout << _year << "/" << _month << "/" << _day << endl;
}//6.赋值重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;return *this;
}//7.日期+=天数
//这个属于赋值运算符重载,由内置类型+=符号运算,会改变左操作数的值;
//因此在返回时我们直接返回运算之后的对象即可
Date& Date::operator+=(int day)
{if (day < 0){return *this -= -day;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);_month++;if (_month > 12){_month = 1;_year++;}}return *this;
}//8.日期+天数
//复用上述日期+=天数即可
//需要注意这里不会改变日期原本的值
//而是返回一个临时产生的对象
Date Date::operator+(int day) const
{Date tmpObject(*this);     //产生临时对象return tmpObject += day;   //复用
}//9.日期-=天数
//同理,会改变日期,直接返回即可
Date& Date::operator-=(int day)
{if (day < 0){return *this += -day;}_day -= day;while (_day <= 0){_month--;_day += GetMonthDay(_year, _month);//月份减完年减一if (_month < 1){_month = 12;_year--;}}return *this;
}//10日期-天数
//同理,复用日期-=天数即可
Date Date::operator-(int day) const
{Date tmpObject(*this);return tmpObject -= day;
}//11.==运算符重载
//意思是判断日期是否相等,返回的是0/1(bool型)
bool Date::operator==(const Date& d) const
{return _year == d._year&& _month == d._month&& _day == d._day;
}//12.>运算符重载
//比较日期的大小:先比年-年相等比月-年月相等比日
bool Date::operator>(const Date& d) const
{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;
}//13.>=、<、<=等运算符重载
//均可以通过复用>、==来实现
bool Date::operator>=(const Date& d) const
{return *this > d || *this == d;
}bool Date::operator<(const Date& d) const
{return !(*this > d);
}bool Date::operator<=(const Date& d) const
{return !(*this > d || *this == d);
}bool Date::operator!=(const Date& d) const
{return !(*this == d);
}//14.日期-日期
//日期-日期是有意义的
//首先需要区分日期间的大小日期,我们直接小的日期每次循环+1天
//循环次数即为间隔天数
int Date::operator-(const Date& d) const
{//默认是当前对象日期大//很经典的找大的方法:先假设Date min = d;Date max = *this;int flag = 1;if (*this < d){max = d;min = *this;flag = -1;}//计算相差天数int count = 0;while (min != max){min++;count++;}return count * flag;
}//15.前置和后置++/--
//前置++,先加再用,因此直接对该对象加减即可
Date& Date::operator++()
{//引用返回return *this += 1;
}
//后置++,先用再加,返回加之前的值
//规定:后置++多一个参数,是为了与前置++做出区分,构成重载,无其它作用
Date Date::operator++(int)
{Date tmpObject(*this);*this += 1;//传值返回return tmpObject;
}
// 前置--:返回减后的值
Date& Date::operator--()
{return *this -= 1;
}
//后置--:后置会多两次拷贝
Date Date::operator--(int)
{Date tmpObject(*this);*this -= 1;return tmpObject;
}

7.const成员

凡是内部不改变成员变量,其实也是*this对象数据的,这些成员函数都应该加const

Type Three:取地址重载

8.取地址重载

这两个默认成员函数一般不用重新定义,编译器默认会生成

class Date
{
public:Date* operator&(){return this;}const Date* operator&() const{return this;}
private:int _year;  // 年int _month; // 月int _day;   // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

特殊需求:要求这个类的对象不让取地址

Date* operator&()
{return nullptr;
}const Date* operator&() const
{return nullptr;
}

相关内容

热门资讯

读什么什么有感的英文 读什么什么有感的英文英语读后感标题 “读XXX有感”用英语说是 “Reading after XX...
秦岚个人资料身高体重 秦岚个人资料身高体重身高:165公分 体重:46公斤秦岚 生日:七月十七日 星座:巨蟹座 出生地:沈...
双鱼和天秤会纠缠一辈子,既相配... 双鱼和天秤会纠缠一辈子,既相配又相克,为什么?双鱼座的人和天秤座的人都是比较细心的,而且特别敏感,有...
独自一人在外怎样和别人相处? 独自一人在外怎样和别人相处?我觉得独自一个人在外面一定要好好的照顾自己,应该找一份工作,找一个住的地...
朱自清散文集有哪些写的好,值得... 朱自清散文集有哪些写的好,值得背诵的?《背影》、《 春》、《 荷塘月色》、《 匆匆》都是不错的佳...
大家最讨厌的电视剧的哪一个主角... 大家最讨厌的电视剧的哪一个主角?我觉得最讨厌的电视剧主角是容嬷嬷。都挺好,里面的苏大强就是越看越别扭...
69DT伤害怎么才能上1300... 69DT伤害怎么才能上1300 !我加点是4L1M!现在60了!伤害才800!我没大号,想买梦幻币买...
孩子上课不认真听讲 孩子上课不认真听讲我的孩子七周半,已经上二年级了,但是上课不认真听讲总是搞小动作,说了很多次也不听,...
《红脸儿》的主要内容 《红脸儿》的主要内容  红脸儿主要内容:   小说以散淡而富有诗意的语言回顾了“我”与3个小伙伴之间...
异地恋的成功例子 异地恋的成功例子 情侣异地恋8年终成正果 两人存下186张火车票见证爱情一对河南的情侣在大学恋爱时便...
小狗吃了死耗子怎么办 小狗吃了死耗子怎么办你好,没事的,放心吧,你的小狗是宠物狗还是土狗,若是宠物狗的话可能会给它造成身体...
请问有没有死亡万花筒广播剧资源... 请问有没有死亡万花筒广播剧资源?死亡万花筒,我有呀!死亡万花筒广播剧,地·址:9525.video(...
徐缺有哪些女人 徐缺有哪些女人徐缺是小说《最强反套路系统》中的角色,他有许多女性关系,其中包括:1. 林小红:徐缺的...
假如我是四大名著中的人物作文9... 假如我是四大名著中的人物作文900假如你是的林黛玉的话那你就会好好读书,不至于连900个字都写不出了...
西游记81难? 西游记81难?西游记的81难是师徒四人取经回来在河中落水经书被淹了的事
魔兽世界风暴王子问题! 魔兽世界风暴王子问题!现在3.05这版本 王子第4阶段的屏障 是不是可以被MS驱散? 屏障驱散后是...
如何评价张杰的少年中国说 如何评价张杰的少年中国说我觉得非常棒,张杰的家庭条件不好。从小就非常努力。刻苦学习音乐,经过拼搏奋斗...
智取生辰纲中杨志是怎样的人?他... 智取生辰纲中杨志是怎样的人?他失败的原因是什么?简短些志有智慧,但是他忽略了一个重要的因素:团队的合...
有一本书,名字忘记了.好象是美... 有一本书,名字忘记了.好象是美国人写的.梭罗《瓦尔登湖》 如果你用的是新教材,应该是这篇吧是 海明威...
四岁孩子看什么书 四岁孩子看什么书可以看一些带有简单数字的书、色彩鲜艳的图画、动物图画等,培养他的数字感和色彩感,尽量...