网站建设设计设计公司/seo网上培训多少钱

网站建设设计设计公司,seo网上培训多少钱,h5在线编辑器,java做网站的职业【本节目标】 1. 类的6个默认成员函数 2. 构造函数 3. 析构函数 4. 拷贝构造函数 5. 赋值运算符重载 6. const成员函数 7. 取地址及const取地址操作符重载 1.类的6个默认成员函数 如果一个类中什么成员都没有,简称为空类。 空类中真的什么都没有吗&…

【本节目标】

1. 类的6个默认成员函数

2. 构造函数

3. 析构函数

4. 拷贝构造函数

5. 赋值运算符重载

6. const成员函数

7. 取地址及const取地址操作符重载

1.类的6个默认成员函数 

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

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

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

class Date {};

2. 构造函数

2.1 概念

对于以下Date类:

#include <iostream>
using namespace std;
class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << 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 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置 信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

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

2.2 特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任 务并不是开空间创建对象,而是初始化对象。

其特征如下:

1. 函数名与类名相同。

2. 无返回值。

3. 对象实例化时编译器自动调用对应的构造函数。

4. 构造函数可以重载。

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)”: 未调用原型函数(是否是有意用变量定义的?)Date d3();
}

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

class Date
{
public:/*// 如果用户显式定义了构造函数,编译器将不再生成Date(int year, int month, int day){_year = year;_month = month;_day = day;}*/void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用Date d1;return 0;}

放开前:

放开后:

6. 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会 生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默 认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的 默认构造函数并没有什么用??

解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类 型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看 下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员 函数。

#include  <iostream>
using namespace std;
class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

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

class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
public:void Print(){cout << _year << endl;cout << _month << endl;cout << _day << endl;}
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d;d.Print();return 0;
}

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

class Date
{
public:Date(){_year = 1900;_month = 1;_day = 1;}Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
// 以下测试函数能通过编译吗?
void Test()
{Date d1;
}

答案是不能,会产生歧义编译器不知道调用哪个Date。

3.析构函数

3.1 概念

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的? 析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

3.2 特性

析构函数是特殊的成员函数,其特征如下:

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值类型。

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

#include <iostream>
using namespace std;
#include <stdlib.h>
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...~Stack(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};
int  main()
{Stack s;s.Push(1);s.Push(2);return 0;
}

这里我们可以看到就像构造函数一样,析构函数也是自动调用。只不过构造函数是在创建变量时会初始化变量。而析构函数则是在程序结束时会自动调用析构函数。这里再说一下,多个对象时,多个对象调用构造函数和析构函数的先后顺序。构造函数是创建一个变量就调用一次,谁先创建就先调用谁,而析构函数的顺序是构造函数的顺序的相反。全局与局部也是有差别的,包括static。一般来说是局部的先调用完析构函数再到全局,static改变了对象的生存作用域,需要等待程序结束时才会析构释放对象。

5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器 生成的默认析构函数,对自定类型成员调用它的析构函数。

class Time
{
public:~Time(){cout << "~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 d;return 0;
}
// 程序运行结束后输出:~Time()
// 在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day// 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在// d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:main函数
// 中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函
// 数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time
// 类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁
// main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类

我们可以尝试一下,编译器自己生成的析构函数会不会帮我们释放堆上面的值。

#include <iostream>
using namespace std;
#include <stdlib.h>
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...~Stack(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};
int  main()
{Stack s;s.Push(1);s.Push(2);return 0;
}

 这里我们发现并没有释放堆上面的值。

总结一下:

为什么自定义类型不需要写析构,因为自定义类型会调用它们自己的析构函数。

6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如 Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

4. 拷贝构造函数

4.1 概念

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。

那在创建对象时,可否创建一个与已存在对象一某一样的新对象呢? 拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存 在的类类型对象创建新对象时由编译器自动调用。

4.2 特征

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

1. 拷贝构造函数是构造函数的一个重载形式。

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

#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// Date(const Date& d)   // 正确写法//Date (const Date d)//错误写法,这样写会引发无限递归。Date(const Date& d)// 错误写法:编译报错,会引发无穷递归{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(d1);return 0;
}

这里会解释发生无限递归的原因。这里我们先创建一个什么都没有的func1函数,用调试走到func1的位置。

按f11我们就能发现我们进入了拷贝构造函数内部。

那么为什么会发生这种事呢?原来在函数中自定义类型的传参,每次都会调用一次拷贝函数。那么如果此时拷贝函数也是传值调用,那就会出现无限调用。只有内置类型的传参才不会调用拷贝构造函数,而是直接传参。因此我们这里可以用引用也可以用指针,不过一般推荐用引用。

如果还不理解的朋友可以看下面的图:

总结一下:

3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

这里注意区分一下拷贝构造函数与上面的构造函数与析构函数在未显示定义时处理内置类型时有差别,构造函数与析构函数不处理内置类型,拷贝构造函数会用浅拷贝处理内置类型。

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;
}

这里我们看到编译器自己生成的拷贝构造函数也能完成我们的任务。那这样是不是就不用自己写拷贝构造函数了?其实不然,这里我们再用一个栈来举一个例子。

class Stack
{
public:Stack(int capacity = 4){cout << "Stack()" << endl;_a = (int*)malloc(sizeof(int) * capacity);if (nullptr == _a){perror("malloc申请空间失败");return;}_capacity = capacity;_top = 0;}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_capacity = _top = 0;}private:int* _a = nullptr;int _top = 0;int _capacity;
};
int main()
{Stack st1;Stack st2(st1);return 0;
}

这里我们可以看到st1的_a数组与st2的_a数组居然指向同一个空间,这时我们才发现浅拷贝的问题很大。首先当st1栈与st2栈中的数组都指向同一块空间时,如果这时候st1把值压入栈中,那么st2中也会有st1的值,因为它们两个共用一个空间了。如果我们此时运行程序程序会崩溃,为什么呢?别忘了当程序结束时它会自动调用析构函数来释放我们_a数组的空间。这时候st1就会释放一次_a空间,st2释放一次_a空间。同一块空间两次释放,那么程序就崩溃了。这时候我们就必须自己实现深拷了。

像这样

class Stack
{
public:Stack(int capacity = 4){cout << "Stack()" << endl;_a = (int*)malloc(sizeof(int) * capacity);if (nullptr == _a){perror("malloc申请空间失败");return;}_capacity = capacity;_top = 0;}Stack(const Stack& st){_a = (int*)malloc(sizeof(int) * st._capacity);if (nullptr == _a){perror("malloc申请空间失败");return;}memcpy(_a, st._a, sizeof(int) * st._top);_top = st._top;_capacity = st._capacity;}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_capacity = _top = 0;}private:int* _a = nullptr;int _top = 0;int _capacity;
};
int main()
{Stack st1;Stack st2(st1);return 0;
}

这就是为什么C++在自定义类型传值调用的时候,要调用拷贝构造了。

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

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

4. 拷贝构造函数典型调用场景:

使用已存在对象创建新对象

函数参数类型为类类型对象

函数返回值类型为类类型对象

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个运算符不能重载。这个经常在笔试选择题中出 现。

#include <iostream>
using namespace std;
#include <stdbool.h>
// 全局的operator==
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// private:int _year;int _month;int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}
bool operator<(const Date& d1, const Date& d2)
{if (d1._year < d2._year){return true;}else if (d1._year== d2._year && d1._month < d2._month){return true;}else if (d1._year == d2._year && d1._month == d2._month&&d1._day<d2._day){return true;}return false;
}
int main()
{Date d1(2018, 9, 26);Date d2(2018, 9, 27);d1 < d2;//会转换成operator<(d1,d2);cout << (d1 == d2) << endl;cout << (d1 < d2) << endl;return 0;
}

当operator<变成成员函数时:

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}bool operator<(const Date& x){if (_year < x._year){return true;}else if (_year == x._year && _month < x._month){return true;}else if (_year == x._year && _month == x._month && _day < x._day){return true;}return false;}bool operator==( const Date& x){return _year == x._year&& _month == x._month&& _day == x._day;}private:int _year;int _month;int _day;
};int main()
{Date d1(2018, 9, 26);Date d2(2018, 9, 27);d1 < d2;//会转换成d1.operator<(d2);cout << (d1 == d2) << endl;cout << (d1 < d2) << endl;return 0;
}

这时候要注意几个问题,因为成员函数中会蕴含一个this指针。那么如果像全局那样传参就会出现参数过多的问题。(内置类型是没有运算符重载的) 对于你有意义的你就能重载运算符。

5.2 赋值运算符重载

1. 赋值运算符重载格式 参数类型:const T&,传递引用可以提高传参效率

返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值

检测是否自己给自己赋值

返回*this :要复合连续赋值的含义

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}void operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2025,3,22);Date d2;//赋值运算符重载是已经存在的两个对象之间的复制拷贝d2 = d1;//拷贝构造函数用一个已经存在的对象初始化另一个对象Date d3(d1);return 0;
}

这里注意一下与拷贝构造函数做区分,赋值运算符重载是已经存在的两个对象之间的复制拷贝。拷贝构造函数用一个已经存在的对象初始化另一个对象。

要满足连续赋值的条件就需要有返回值,例如:

 Date d4, d5;
d5 = d4 = d1;

d4=d1的返回值为d4,再把返回值赋给d5。如果赋值重载函数没有返回值那么我们就无法连续赋值。  

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2025,3,22);Date d2;//赋值运算符重载是已经存在的两个对象之间的复制拷贝d2 = d1;//拷贝构造函数用一个已经存在的对象初始化另一个对象Date d3(d1);Date d4, d5;d5 = d4 = d1;return 0;
}

因为出了作用域this指针指向的值不会销毁。所以这里可以用引用返回,这样就可以减少拷贝,有拷贝函数的时候还能减少拷贝函数的调用次数。

2. 赋值运算符只能重载成类的成员函数不能重载成全局函数

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数Date& operator=(Date& left, const Date& right){if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;// 编译失败:// error C2801: “operator =”必须是非静态成员}

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现 一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值 运算符重载只能是类的成员函数。

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;
}

赋值重载函数与拷贝构造函数的行为是一样的:
1.内置类型值拷贝/浅拷贝

2.自定义类型调用它的赋值重载函数。

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

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
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;return 0;
}

6.日期类的实现

#include <iostream>
using namespace std;
class Date
{
public:// 获取某年某月的天数int GetMonthDay(int year, int month){static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30,31 };int day = days[month];if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){day += 1;}return day;}// 全缺省的构造函数Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 拷贝构造函数// d2(d1)Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}// 赋值运算符重载// d2 = d3 -> d2.operator=(&d2, d3)Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}// 析构函数~Date(){_year = 0;_month = 0;_day = 0;}void print(){cout << _year <<"-" << _month <<"-" << _day << endl;}// 日期+=天数Date& operator+=(int day){if (day < 0){return *this -= -day;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);++_month;if (_month == 13){_month = 1;++_year;}}return *this;}// 日期+天数Date operator+(int day){Date tmp(*this);tmp += day;return tmp;}// 日期-=天数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 operator-(int day){Date tmp(*this);tmp -= day;return tmp;}// 前置++Date& operator++(){*this = *this + 1;return *this;}// 后置++Date operator++(int){Date tmp (*this);*this = *this + 1;return tmp;}// 后置--Date operator--(int){Date tmp(*this);*this = *this - 1;return tmp;}// 前置--Date& operator--(){*this = *this - 1;return *this;}// >运算符重载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;}// ==运算符重载bool operator==(const Date& d){return _year == d._year && _month == d._month && _day == d._day;}// >=运算符重载bool operator >= (const Date& d){return ((*this)>d)||((*this)==d);}// <运算符重载bool operator < (const Date& d){return !((*this)>=d);}// <=运算符重载bool operator <= (const Date& d){return !((*this)>d);}// !=运算符重载bool operator != (const Date& d){return !(*this==d);}// 日期-日期  返回天数int operator-(const Date& d){int flag = 1;Date more = *this;Date less = d;if (*this < d){more = d;less = *this;flag = -1;}int day = 0;while (less!=more){++less;++day;}return day*flag;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2025,5,22);Date d2(2000, 4, 5);Date ret= d1 -10 ;ret.print();int ret1 = d1 - d2;cout << ret1 << endl;return 0;
}
//自定义类型的流插入实现
void operator<< (ostream& out)
{}

在日期类我们还能实现自定义类型的流提取,想实现自定义类型的流提取,我们就需要了解内置类型的流提取。

可以看到为什么内置类型的流提取能自动识别,第一运用了运算符重载流符号,第二还用了函数重载,来重载运算符重载。这样流提取的神秘面纱也就被我们揭露了。

那么根据上图我们就能把自定义类型的流提取给实现了。(这里是用日期类的对象实现)

	void operator<< (ostream& out){out << _year << "年" << _month << "月" << _day << "日"<<endl;}
注:这里的函数是成员函数。
void test2()
{Date d1(2025, 5, 24);cout << d1;
}

结果发现编译器报了一个这样的错误:

 这是为什么呢?

首先流提取符号是一个二元符号,而在成员函数中,第一个参数是左操作数,第二个参数是右操作数。也就是Date对象默认做了左操作数,像这样:void operator<< (Date *this,ostream& out);因此我们需要反过来写。像这样:

void test2()
{Date d1(2025, 5, 24);//cout << d1;d1 << cout;
}

但是这样就看起来十分奇怪了,那么有什么办法即使是cout<<d1这样写也能编译通过呢?

首先我们的思路一定是想让cout变成第一个参数,例如这样:

void operator<< (ostream& out,Date *this);

这时候我们就能把函数写到全局。

//自定义类型的流插入实现
void operator<< (ostream& out,const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日"<<endl;
}

 但是这样就有新问题,当我们是成员函数的时候我们能访问私有,但是当我们是全局函数时我们就不能访问私有了。

我们有两个方法,一是写一个公有成员函数来获取年月日。例如:
 

	int getyear(){return _year;}int getmonth(){return _month;}int getday(){return _day;}
void operator<< (ostream& out, Date& d)
{out << d.getyear() << "年" << d.getmonth() << "月" << d.getday() << "日"<<endl;
}

 这时候就不能加const了

第二就是变成友元函数(后面会详细讲解)

friend void operator<< (ostream& out, const Date& d);
void operator<< (ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日"<<endl;
}
void test2()
{Date d1(2025, 5, 24);//cout << d1;cout<<d1;
}

 

这里有人可能会想为什么 ostream& out不加const,因为流插入会把变量流入终端,因此肯定会改变。所以不能用const修饰。

那么连续的流插入是如何实现的呢?(这里与赋值运算符重载有异曲同工之妙)

void test3()
{Date d1(2025, 5, 24);Date d2(2023, 4, 5);Date d3(2022, 5, 5);cout << d1 << d2 << d3;
}

 注:赋值运算符重载是从右往左赋值,流插入是从左往右流入。为什么会有这个差异呢,因为赋值的返回值是赋值符号的左操作数,而流插入是cout。

这里就需要改一下返回值了

ostream& operator<< (ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日"<<endl;return out;
}

注:友元函数那边也需要更改。

 当然有流插入就有流提取:

friend istream& operator>> (istream& in,  Date& d);
istream& operator>> (istream& in,  Date& d)
{in >> d._year >> d._month >> d._day ;return in;
}
void test4()
{Date d1(2025, 5, 24);Date d2(2023, 4, 5);Date d3(2022, 5, 5);cout << d1 << d2 << d3;cin >> d1 >> d2 >> d3;cout << d1 << d2 << d3;
}

 

这里注意一下Date& d前面不能有const了,因为流提取会改变d。当然istream& in前面也不能加const因为流提取会改变cin的状态值。

这里我们的日期类还存在些许问题,例如:
 

void test5()
{Date d1(2025, 13, 0);cout << d1;
}

这里我们就需要在构造函数中检查一下,

Date(int year = 1900, int month = 1, int day = 1)
{if (_month > 0 && _month < 13 && _day>0 && _day <= GetMonthDay(_year, _month)){_year = year;_month = month;_day = day;}else{cout << "非法日期" << endl;assert(false);}}

再把cin也改一下

istream& operator>> (istream& in,  Date& d)
{int year, month, day;in >> year >> month >> day;if (month > 0 && month < 13 && day>0 && day <= d.GetMonthDay(year, month)){d._year = year;d._month = month;d._day = day;}else{cout << "非法日期" << endl;assert(false);}return in;
}


 

7.const成员 

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数 隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

这时我们发现了一个问题d1调用Print的时候不出问题但是,d2又无法调用Printf 

void test7()
{Date d1(2023, 5, 5);d1.print();const Date d2(2023, 5, 5);d2.print();
}

 

 

这时候我们需要在Date* this前面加上const。

权限不能放大但是能缩小。这时候我们发现了this指针是不显示的那么该怎么办呢?C++可以在成员函数后面加上const来修饰this指针 。

我们来看看下面的代码

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}void Print() const{cout << "Print()const" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};
void Test()
{Date d1(2022, 1, 13);d1.Print();const Date d2(2022, 1, 13);d2.Print();
}

请思考下面的几个问题:

1. const对象可以调用非const成员函数吗?

2. 非const对象可以调用const成员函数吗?

3. const成员函数内可以调用其它的非const成员函数吗?

4. 非const成员函数内可以调用其它的const成员函数吗?

8.取地址及const取地址操作符重载

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

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

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

日期类最终代码:

#include <iostream>
#include <assert.h>
using namespace std;
class Date
{friend ostream& operator<< (ostream& out, const Date& d);friend istream& operator>> (istream& in,  Date& d);
public:// 获取某年某月的天数int GetMonthDay(int year, int month){static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30,31 };int day = days[month];if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){day += 1;}return day;}// 全缺省的构造函数Date(int year = 1900, int month = 1, int day = 1){if (month > 0 && month < 13 && day>0 && day <= GetMonthDay(year, month)){_year = year;_month = month;_day = day;}else{cout << "非法日期" << endl;assert(false);}}// 拷贝构造函数// d2(d1)Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}// 赋值运算符重载// d2 = d3 -> d2.operator=(&d2, d3)Date& operator=(const Date& d) {if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}// 析构函数~Date(){_year = 0;_month = 0;_day = 0;}void print() const{cout << _year <<"-" << _month <<"-" << _day << endl;}// 日期+=天数Date& operator+=(int day){if (day < 0){return *this -= -day;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);++_month;if (_month == 13){_month = 1;++_year;}}return *this;}// 日期+天数Date operator+(int day) const{Date tmp(*this);tmp += day;return tmp;}// 日期-=天数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 operator-(int day) {Date tmp(*this);tmp -= day;return tmp;}// 前置++Date& operator++(){*this = *this + 1;return *this;}// 后置++Date operator++(int){Date tmp (*this);*this = *this + 1;return tmp;}// 后置--Date operator--(int){Date tmp(*this);*this = *this - 1;return tmp;}// 前置--Date& operator--(){*this = *this - 1;return *this;}// >运算符重载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;}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);}// <运算符重载bool operator < (const Date& d)const{return !((*this)>=d);}// <=运算符重载bool operator <= (const Date& d)const{return !((*this)>d);}// !=运算符重载bool operator != (const Date& d)const{return !(*this==d);}// 日期-日期  返回天数int operator-(const Date& d){int flag = 1;Date more = *this;Date less = d;if (*this < d){more = d;less = *this;flag = -1;}int day = 0;while (less!=more){++less;++day;}return day*flag;}//int getyear()//{//	return _year;//}//int getmonth()//{//	return _month;//}//int getday()//{//	return _day;//}
private:int _year;int _month;int _day;
};
//自定义类型的流插入实现
//void operator<< (ostream& out, Date& d)
//{
//	out << d.getyear() << "年" << d.getmonth() << "月" << d.getday() << "日";
//}
ostream& operator<< (ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日"<<endl;return out;
}
istream& operator>> (istream& in,  Date& d)
{int year, month, day;in >> year >> month >> day;if (month > 0 && month < 13 && day>0 && day <= d.GetMonthDay(year, month)){d._year = year;d._month = month;d._day = day;}else{cout << "非法日期" << endl;assert(false);}return in;
}
void test1()
{Date d1(2025, 5, 22);Date d2(2000, 4, 5);Date ret = d1 - 10;ret.print();int ret1 = d1 - d2;cout << ret1 << endl;
}
void test2()
{Date d1(2025, 5, 24);//cout << d1;cout<<d1;
}
void test3()
{Date d1(2025, 5, 24);Date d2(2023, 4, 5);Date d3(2022, 5, 5);cout << d1 << d2 << d3;
}
void test4()
{Date d1(2025, 5, 24);Date d2(2023, 4, 5);Date d3(2022, 5, 5);cout << d1 << d2 << d3;cin >> d1 >> d2 >> d3;cout << d1 << d2 << d3;
}
void test5()
{Date d1(2025, 13, 0);cout << d1;
}
void test6()
{Date d1(2025, 12, 1);cin>> d1;cout << d1;
}
void test7()
{Date d1(2023, 5, 5);d1.print();const Date d2(2023, 5, 5);d2.print();
}
int main()
{test7();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/73311.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

React+Ant Design的Layout布局实现暗黑模式切换

目录 效果预览完整代码我遇到的BUG问题代码BUG1&#xff1a;暗黑模式下内容区不变成深色BUG2&#xff1a;光亮模式下的左右区域是深色 补充知识ConfigProvider是什么&#xff1f;Ant Design中的theme如何使用&#xff1f;theme 配置的常见字段主题算法通过 useToken 获取主题 效…

力扣DAY24 | 热100 | 回文链表

前言 简单 √ 是反转链表的衍生题&#xff0c;很快写完了。不过没考虑到恢复链表结构的问题。 题目 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输…

【GL010】C++

1.C中的const关键字有哪些用法&#xff1f; 1.修饰变量&#xff1a;表示变量的值不可修改。 const int a 10; 2.修饰指针&#xff1a; const int* p&#xff1a; // 指针指向的内容不可修改。 int* const p&#xff1a; // 指针本身不可修改。 const int* const…

金融行业 UE/UI 设计:解锁高效体验,重塑行业界面

在数字化浪潮中&#xff0c;金融行业的竞争日益激烈&#xff0c;用户体验&#xff08;UE&#xff09;和用户界面&#xff08;UI&#xff09;设计成为企业脱颖而出的关键。兰亭妙微凭借丰富的经验和创新的方法&#xff0c;为金融行业打造了一套行之有效的 UE/UI 解决方案&#x…

C语言字符函数,字符串函数以及内存函数

那么博主写这一片博客的目的就是为下一篇c的string类做铺垫&#xff0c;那么下面就请期待博主的下一篇文章吧。 目录 1.字符函数 2.字符串函数&#xff08;均在string.h头文件中&#xff09; strlen的使用和模拟实现 strcpy 的使用和模拟实现 strcat 的使用和模拟实现 s…

【Linux】同步原理剖析及模拟BlockQueue生产消费模型

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

<数据集>轨道异物识别数据集<目标检测>

数据集下载链接&#xff1a;https://download.csdn.net/download/qq_53332949/90527370 数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;1659张 标注数量(xml文件个数)&#xff1a;1659 标注数量(txt文件个数)&#xff1a;1659 标注类别数&#xff1a;6 标注类别…

LabVIEW液压振动锤控制系统

在现代工程机械领域&#xff0c;液压振动锤的高效与精准控制日益显得重要。本文通过LabVIEW软件&#xff0c;展开液压振动锤启停共振控制技术的研究与应用&#xff0c;探讨如何通过改进控制系统来优化液压振动锤的工作性能&#xff0c;确保其在复杂工况下的稳定性与效率。 ​ …

【开源宝藏】30天学会CSS - DAY7 第七课 CSS 关键帧打造Preloader 追逐动画

你的代码实现了一个 方形轨迹预加载动画&#xff08;Preloader Animation&#xff09;&#xff0c;其中三个 span 元素沿着一个 22 网格 轨迹循环移动。现在&#xff0c;我们将 拆解核心实现步骤&#xff0c;让你能一步步理解并调整动画效果。 第 0 步&#xff1a;项目概览 你…

GPT-SoVITS本地部署:低成本实现语音克隆远程生成音频全流程实战

文章目录 前言1.GPT-SoVITS V2下载2.本地运行GPT-SoVITS V23.简单使用演示4.安装内网穿透工具4.1 创建远程连接公网地址 5. 固定远程访问公网地址 前言 今天要给大家安利一个绝对能让你大呼过瘾的声音黑科技——GPT-SoVITS&#xff01;这款由花儿不哭大佬精心打造的语音克隆神…

JVM(基础篇)

一.初识JVM 1.什么是JVM JVM全称Java Virtyal Machine&#xff0c;中文译名 Java虚拟机 。JVM本质上是一个运行在计算机上的程序&#xff0c;他的职责是运行Java字节码文件(将字节码解释成机器码)。 2.JVM的功能 解释和运行&#xff1a;对字节码文件中的指令号&#xff0c;实时…

【高并发内存池】第四弹---深入理解PageCache:整体设计、核心实现及Span获取策略详解

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【Linux网络编程】【项目详解】 目录 1、pagecache 1.1、整体设计 1.2、核心实现 1.3、获取Span 1.3.1、获取一个非空的Span 1.3…

深入理解C语言数据结构之快速排序三路划分

在数据结构和算法的世界里&#xff0c;排序算法是基石一般的存在。快速排序作为一种高效的排序算法&#xff0c;以其平均情况下的优秀时间复杂度而被广泛应用。今天&#xff0c;让我们深入探讨快速排序的一种变体——三路划分的快速排序&#xff0c;看看它是如何在C语言中施展魔…

Java实现后量子密码(PQC)与国密算法(SM4)混合加密

以下是使用Java实现一种后量子密码(PQC)与国密算法(SM4)混合加密的示例方案。该方案结合了后量子密码的抗量子特性与国密算法的国产化合规要求,适合需要双重安全保障的场景。 一 . 方案验证 1.代码截图 2.运行测试 二 . 方案设计 密钥交换:使用后量子密码(如Kyber)生…

【SQL Server数据库备份详细教程】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

SpringBoot古典舞在线交流平台设计与实现

随着古典舞文化的普及&#xff0c;越来越多的人希望通过线上平台交流学习。幽络源作为一站式综合平台&#xff0c;致力于为用户提供免费源码、技术教程及网络兼职资源。本文将详细介绍基于SpringBoot的古典舞在线交流平台的设计与实现&#xff0c;帮助开发者快速搭建一个功能完…

Altium Designer数模电学习笔记

模电 电容 **退耦&#xff1a;**利用通交阻直&#xff0c;将看似直流的信号中的交流成分滤除 &#xff08;一般用在给MPU供电&#xff0c;尽量小一些&#xff0c;10nf~100nf~1uf以下&#xff09; **滤波&#xff1a;**也可以理解为给电容充电&#xff0c;让电容在电平为低时…

光谱仪与光谱相机的核心区别与协同应用

一、核心功能与数据维度 ‌光谱仪‌ ‌功能定位‌&#xff1a;专注单点或线状区域的光谱分析&#xff0c;通过色散元件&#xff08;光栅/棱镜&#xff09;分离波长&#xff0c;生成一维或二维光谱曲线&#xff0c;用于量化光强、吸收率等参数‌。 ‌数据维度‌&#xff1a;输…

Pytorch中layernorm实现详解

平时我们在编写神经网络时&#xff0c;经常会用到layernorm这个函数来加快网络的收敛速度。那layernorm到底在哪个维度上进行归一化的呢&#xff1f; 一、问题描述 首先借用知乎上的一张图&#xff0c;原文写的也非常好&#xff0c;大家有空可以去阅读一下&#xff0c;链接放…

在windows下安装windows+Ubuntu16.04双系统(上)

这篇文章的内容主要来源于这篇文章&#xff0c;给文章很详细的介绍了如何从windows下安装windowsubuntu16.04双系统。我刚开始装双系统都是参照这个方法&#xff0c;该作者前后更新了两个版本&#xff0c;在这里对其稍微进行整理一下。 一、准备&#xff1a;&#xff08;这里推…