当前位置: 首页 > article >正文

类与对象(中)(详解)

【本节目标】

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

相关文章:

类与对象(中)(详解)

【本节目标】 1. 类的6个默认成员函数 2. 构造函数 3. 析构函数 4. 拷贝构造函数 5. 赋值运算符重载 6. const成员函数 7. 取地址及const取地址操作符重载 1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&…...

Spring Boot框架中常用注解

以下是Spring Boot框架中常用注解的详细说明&#xff0c;包括名称、用途、用法、使用位置及扩展示例&#xff0c;按功能模块分类整理&#xff1a; 一、核心启动与配置注解 1. SpringBootApplication 用途&#xff1a;主启动类注解&#xff0c;整合了 Configuration、EnableAu…...

ResNet与注意力机制:深度学习中的强强联合

引言 在深度学习领域&#xff0c;卷积神经网络&#xff08;CNN&#xff09;一直是图像处理任务的主流架构。然而&#xff0c;随着网络深度的增加&#xff0c;梯度消失和梯度爆炸问题逐渐显现&#xff0c;限制了网络的性能。为了解决这一问题&#xff0c;ResNet&#xff08;残差…...

notify_one() 会阻塞吗?

notify_one() 不会阻塞。它是用于唤醒一个等待中的线程&#xff0c;通常是通过条件变量&#xff08;std::condition_variable&#xff09;来使用的。调用 notify_one() 会使一个处于等待状态的线程被唤醒并继续执行&#xff0c;但它本身并不会阻塞。 当调用 notify_one() 时&a…...

Flutter项目之页面实现以及路由fluro

目录&#xff1a; 1、项目代码结构2、页面编写以及路由配置main.dart(入口文件)page_content.dartindex.dartapplication.dartpubspec.yamllogin.dartdio_http.dart 3、Fluro路由routes.dartnot_found_page.dart(路由优化&#xff0c;找不到页面时展示此页面) 4、注册页面 1、项…...

《Python实战进阶》第31集:特征工程:特征选择与降维技术

第31集&#xff1a;特征工程&#xff1a;特征选择与降维技术 摘要 特征工程是机器学习和数据科学中不可或缺的一环&#xff0c;其核心目标是通过选择重要特征和降低维度来提升模型性能并减少计算复杂度。本集聚焦于特征选择与降维技术&#xff0c;涵盖过滤法、包裹法、嵌入法等…...

大模型在支气管哮喘手术全流程风险预测与治疗方案制定中的应用研究

目录 一、引言 1.1 研究背景与意义 1.2 研究目标与方法 1.3 研究创新点 二、支气管哮喘概述 2.1 定义与发病机制 2.2 分类与临床表现 2.3 诊断标准与方法 三、大模型技术原理与应用现状 3.1 大模型的基本原理 3.2 在医疗领域的应用案例分析 3.3 适用于支气管哮喘预…...

C++类与对象的第二个简单的实战练习-3.24笔记

哔哩哔哩C面向对象高级语言程序设计教程&#xff08;118集全&#xff09; 实战二 Cube.h #pragma once class Cube { private:double length;double width;double height; public:double area(void);double Volume(void);//bool judgement(double L1, double W1, double H1);…...

react自定义hook

自定义hook&#xff1a; 用来封装复用的逻辑&#xff0c;&#xff0c;自定义hook是以use开头的普通函数&#xff0c;&#xff0c;将组件中可复用的状态逻辑抽取到自定义的hook中&#xff0c;简化组件代码 常见自定义hook例子&#xff1a; 封装一个简单的计数器 import {useS…...

Rk3568驱动开发_设备树点亮LED_10

设备树中添加节点 在设备树文件中添加led节点&#xff0c;添加完后需要重新编译内核&#xff0c;因为单独编译这个设备树文件生成的dtb文件目前不能直接做替换&#xff0c;所以要编译内核将编译好的boot.img文件烧录到设备里&#xff0c;boot.img里包含新添加的设备树节点&…...

大数据学习(82)-数仓详解

&#x1f34b;&#x1f34b;大数据学习&#x1f34b;&#x1f34b; &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 用力所能及&#xff0c;改变世界。 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一…...

Unity学习之Shader(Phong与Blinn-Phong)

三、Lesson3 1、关键名称 向量 • nDir&#xff1a;法线方向&#xff0c;点乘操作时简称n&#xff1b; • lDir&#xff1a;光照方向&#xff0c;点乘操作时简称l&#xff1b; • vDir&#xff1a;观察方向&#xff0c;点乘操作时简称v&#xff1b; • rDir&#xff1a;光反…...

uniapp笔记-swiper组件实现轮播图

思路 主要就是参考 swiper | uni-app官网 实现轮播图。 实例 新建一个banner.vue通用组件。 代码如下&#xff1a; <template><view>轮播图</view> </template><script> </script><style> </style> 随后在index.vue中导…...

【C++ 继承】—— 青花分水、和而不同,继承中的“明明德”与“止于至善”

欢迎来到ZyyOvO的博客✨&#xff0c;一个关于探索技术的角落&#xff0c;记录学习的点滴&#x1f4d6;&#xff0c;分享实用的技巧&#x1f6e0;️&#xff0c;偶尔还有一些奇思妙想&#x1f4a1; 本文由ZyyOvO原创✍️&#xff0c;感谢支持❤️&#xff01;请尊重原创&#x1…...

FPGA_YOLO(二)

上述对cnn卷积神经网络进行介绍,接下来对YOLO进行总结,并研究下怎么在FPGA怎么实现的方案。 对于一个7*7*30的输出 拥有49个cell 每一个cell都有两个bbox两个框,并且两个框所包含的信息拥有30个 4个坐标信息和一个置信度5个,剩下就是20个类别。 FPGA关于YOLO的部署 1…...

蓝桥杯学习-14子集枚举,二进制枚举

子集枚举 一、回溯3-子集枚举&#xff08;递归实现指数型枚举&#xff09; 一旦涉及选与不选&#xff0c;删和不删&#xff0c;留和不留-->两种状态-->就要想到子集枚举例题1–递归实现指数型枚举19685 其实看不懂这个题目&#xff0c;好奇怪的题目。根据老师的解析来写…...

人工智能时代大学教育范式重构:基于AI编程思维的能力培养路径研究

人工智能技术的快速发展正在重塑高等教育的内容与方法。本文以AI编程教育为切入点&#xff0c;通过文献分析与案例研究&#xff0c;探讨AI时代大学教育的核心能力需求与教学范式转型路径。研究发现&#xff0c;AI编程中蕴含的系统性思维训练、项目架构能力和元认知能力培养机制…...

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

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

结构型——享元模式

享元模式 享元模式的核心思想是通过共享技术减少大量细粒度对象的创建&#xff0c;降低内存占用并提升性能。换句话说&#xff0c;它通过分离对象的内部状态&#xff08;可共享的固有属性&#xff09;和外部状态&#xff08;随场景变化的属性&#xff09;实现对象复用。 特点…...

淘宝API关键词接口详解(实战案例)

以下为您详解淘宝API关键词接口的调用方法及实战案例&#xff1a; 一、接口定义与核心功能 淘宝关键词API是开放平台提供的标准化数据服务接口&#xff0c;允许开发者通过关键词检索商品全维度信息。其核心功能包括&#xff1a; 精准检索&#xff1a;支持商品标题、属性、类…...

Pyecharts功能详解与实战示例

一、Pyecharts简介 Pyecharts是一个基于Python的开源数据可视化库&#xff0c;它基于百度的Echarts库&#xff0c;提供了丰富的图表类型和强大的交互功能。通过Pyecharts&#xff0c;你可以轻松创建各种精美的图表&#xff0c;如折线图、柱状图、饼图、散点图、地图等&#xf…...

传统金融和分布式金融

文章目录 传统金融和分布式金融一、传统金融机构的核心问题深度剖析1. 支付与清算系统的结构性缺陷2. 金融排斥&#xff08;Financial Exclusion&#xff09;的根源3. 中心化风险的爆发与传导 二、DeFi的技术突破与创新机制1. 支付与清算&#xff1a;区块链的底层重构2. 普惠金…...

EasyUI数据表格中嵌入下拉框

效果 代码 $(function () {// 标记当前正在编辑的行var editorIndex -1;var data [{code: 1,name: 1,price: 1,status: 0},{code: 2,name: 2,price: 2,status: 1}]$(#dg).datagrid({data: data,onDblClickCell:function (index, field, value) {var dg $(this);if(field ! …...

C语言:扫雷

在编程的世界里&#xff0c;扫雷游戏是一个经典的实践项目。它不仅能帮助我们巩固编程知识&#xff0c;还能锻炼逻辑思维和解决问题的能力。今天&#xff0c;就让我们一起用 C 语言来实现这个有趣的游戏&#xff0c;并且通过图文并茂的方式&#xff0c;让每一步都清晰易懂 1. 游…...

操作系统必知的面试题

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…...

清华大学.智灵动力-《DeepSeek行业应用实践报告》附PPT下载方法

导 读INTRODUCTION 今天分享是由清华大学.智灵动力&#xff1a;《DeepSeek行业应用实践报告》&#xff0c;主要介绍了DeepSeek模型的概述、优势、使用技巧、与其他模型的对比&#xff0c;以及在多个行业中的应用和未来发展趋势。为理解DeepSeek模型的应用和未来发展提供了深入的…...

数据库三级填空+应用题(1)

填空 35【答案】TOP 3 WITH TIES 【解析】希望选出商品数量最多的前3类商品&#xff0c;并获得相应的商品类别和数量。with ties一般是和Top 、 order by相结合使用,表示包括与最后一行order by后面的参数取值并列的结果。 36在SQL Server 2008中&#xff0c;每个数据页可存储8…...

可视化图解算法:链表的奇偶重排(排序链表)

1. 题目 描述 给定一个单链表&#xff0c;请设定一个函数&#xff0c;将链表的奇数位节点和偶数位节点分别放在一起&#xff0c;重排后输出。 注意是节点的编号而非节点的数值。 数据范围&#xff1a;节点数量满足 0≤n≤105&#xff0c;节点中的值都满足 0≤val≤10000 要…...

Atlas 800I A2 双机直连部署DeepSeek-R1-w8a8

一、环境信息 1.1、硬件信息 Atlas 800I A2 * 2 1.2、环境信息 操作系统&#xff1a;openEuler 22.03 LTS NPU驱动&#xff1a;Ascend-hdk-910b-npu-driver 24.1.0 linux-aarch64.run NPU固件&#xff1a;Ascend-hdk-910b-npu-firware 7.5.0.3.220.run MindIE镜像&#xff…...

如何确保异步任务在 HTTP 返回后继续执行?context.WithoutCancel

文章目录 如何确保异步任务在 HTTP 返回后继续执行&#xff1f;问题分析如何确保异步任务在 HTTP 返回后继续执行&#xff1f;&#xff08;1&#xff09;使用独立的 context&#xff08;2&#xff09;手动传递父 ctx 中的值&#xff08;3&#xff09;使用 context.WithoutCance…...