目录
前言:六个默认成员函数
我们写的每个类,编译器都会自动生成6个默认的成员函数。
分别是:
- 默认构造函数
- 默认析构函数
- 拷贝构造(浅拷贝/值拷贝)
- 赋值运算符重载
- 取地址运算符重载(一般不用)
- 加了const的取地址运算符重载(一般不用)
本篇文章,我们将分别介绍这6个默认的成员函数。
Ⅰ.构造函数
1.构造函数引入
#include <iostream>
class Date {
public:
void SetDate(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
printf("%d-%d-%d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1;
d1.SetDate(2022, 3, 8);
d1.Print();
Date d2;
d2.SetDate(2022, 3, 12);
d2.Print();
return 0;
}
首先介绍一下,什么是构造函数?
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员 都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
构造函数的特性:
- 没有返回值。
- 函数名与类名相同
- 对象实例化时编译器自动调用对应的构造函数
- 构造函数可以重载
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象
构造函数的用法:
#include <iostream>
class Date {
public:
/* 无参构造函数 */
Date() {
_year = 0;
_month = 1;
_day = 1;
}
/* 带参构造函数 */
Date(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
printf("%d-%d-%d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1; // 对象实例化,此时触发构造,调用无参构造函数
d1.Print();
Date d2(2022, 3, 9); // 对象实例化,此时触发构造,调用带参构造函数
d2.Print();
return 0;
}
2.构造函数注意事项
一、构造函数是特殊的,不是常规的成员函数,不能直接调
int main(void)
{
Date d1;
d1.Date(); // 不能这么去调,构造函数是特殊的,不是常规的成员函数!
return 0;
}
二、如果通过无参构造函数创建对象,对象后面不用跟括号,否则就成了函数声明。
int main(void)
{
//带参这么调:加括号(),在括号中加参数列表
Date d2(2022, 3, 9);
Date d3(); //误认为是函数声明
// Date d3; 不要 Date d3();
return 0;
}
三、如果你没有自己定义构造函数(类中未显式定义),C++ 编译器会自动生成一个无参的默认构造函数。当然,如果你自己定义了,编译器就不会帮你生成了。
#include <iostream> class Date { public: /* 如果用户显式定义了构造函数,编译器将不再生成 Date(int year, int month, int day) { _year = year; _month = month; _day = day; } */ void Print() { printf("%d-%d-%d\n", _year, _month, _day); } private: int _year; int _month; int _day; }; int main(void) { Date d1; // 这里调用的是默认生成的无参的构造函数 d1.Print(); return 0; }
没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器默认生成的构造函数
如果用户显式定义了构造函数,编译器将不再生成
#include <iostream> class Date { public: // 如果用户显式定义了构造函数,编译器将不再生成 Date(int year, int month, int day) { _year = year; _month = month; _day = day; } void Print() { printf("%d-%d-%d\n", _year, _month, _day); } private: int _year; int _month; int _day; }; int main(void) { Date d1; // 这里调用的是默认生成的无参的构造函数 d1.Print(); return 0; }
运行报错!!!
上文提到无参默认构造函数,那么什么是默认构造函数呢?
3.默认构造函数
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
注意事项:
语法上无参和全缺省可以同时存在,但如果同时存在会引发二义性:
无参的构造函数和全缺省的构造函数都成为默认构造函数,并且默认构造参数只能有一个,语法上他们两个可以同时存在,但是如果有对象定义去调用就会报错。
#include <iostream>
class Date {
public:
Date() {//无参
_year = 2022;
_month = 5;
_day = 28;
}
Date(int year = 2022, int month = 5, int day = 28) {//全缺省
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1; //错误!!
return 0;
}
运行结果如下:报错!!!
- 关于编译器生成的默认成员函数,很多童鞋会有疑惑:在我们不实现构造函数的情况下,编译器会生成 默认的构造函数。但是看起来默认构造函数又没什么用?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++ 里面把类型分为两类:内置类型(基本类型)和 自定义类型。
- C++ 规定:我们不写编译器默认生成构造函数,对于内置类型的成员变量,不做初始化处理。
- 但是对于自定义类型的成员变量会去调用它的默认构造函数(不用参数就可以调的)初始化。
- 如果没有默认构造函数(不用参数就可以调用的构造函数)就会报错!
这里我们故意写个带参的默认构造函数,让编译器不默认生成:
#include <iostream> using namespace std; class A { public: // 如果没有默认的构造函数,会报错。 A(int a) { // 故意给个参 cout << " A() " << endl; _a = 0; } private: int _a; }; class Date { public: private: // 如果没有默认构造函数就会报错 int _year; int _month; int _day; A _aa; }; int main(void) { Date d1; return 0; }
运行结果:报错!!!
总结:
- 初始化,在对象实例化时候自动调用,保证实例化对象一定被初始化。
- 构造函数是默认成员函数,我们不写编译器会自己生成一份,我们写了编译器就不会生成。
- 对于内置类型成员变量不处理。
- 对于自定义类型的成员变量会调用它的默认构造函数。
Ⅱ.析构函数
1.析构函数概念
析构函数与构造函数的功能相反。
构造函数是特殊的成员函数,主要任务是初始化,而不是开空间;析构函数也一样,主要任务是清理,而不是做对象销毁的工作。(局部对象销毁工作是由编译器完成的)
概念:对象在销毁时会自动调用析构函数,完成对象的一些资源清理工作。
2.析构函数的特性
构造函数是特殊的成员函数,主要特征如下:
- 析构函数名是在类名前面加上字符
- 析构函数既没有参数也没有返回值(因为没有参数,所以也不会构成重载问题)
- 一个类的析构函数有且仅有一个(如果不写系统会默认生成一个析构函数)
- 析构函数在对象生命周期结束后,会自动调用。
析构用法:
#include <iostream>
using namespace std;
class Date {
public:
Date(int year = 1, int month = 0, int day = 0) {
_year = year;
_month = month;
_day = day;
}
void Print() {
printf("%d-%d-%d\n", _year, _month, _day);
}
~Date() {
// Date 类没有资源需要清理,所以Date不实现析构函都是可以的
cout << "~Date() 析构调用 " <<_year<< endl; // 测试一下,让他吱一声
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1;
d1.Print();
Date d2(2022, 3, 9);
d2.Print();
return 0;
}
可见,因为析构的顺序在局部的栈中是相反的,栈帧销毁清理资源时 d2 先清理,然后再清理 d1 。所以析构与构造顺序相反
3.默认析构函数
与编译器自动生成的默认构造函数一样,默认析构函数对内置类型不处理,对自定义类型(class、struct自定义的类型)会调用它们的默认析构函数。
在与堆区有关的类需要自己写一个析构函数,完成堆区的释放,不然会内存泄露。
#include <iostream>
using namespace std;
class String {
public:
String(const char* str = "jack") {
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String() {
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
class Person {
private:
String _name;
int _age;
};
int main()
{
Person p;
return 0;
}
总结:
- 完成对象中自愿的清理。如果类对象需要资源清理,才需要自己实现析构函数。
- 析构函数在对象生命周期到了以后自动调用,如果你正确实现了析构函数,保证了类对象中的资源被清理。
- 什么时候生命周期到了?如果是局部变量,出了作用域。全局和静态变量,整个程序结束。
- 我们不写编译器会默认生成析构函数,我们实现了,编译器就不会实现了。
- 对于内置类型成员变量不处理。
- 对于自定义类型的成员变量会调用它的析构函数。
Ⅲ.拷贝构造函数
1.拷贝构造函数的概念与特性
它也是一个特殊的成员函数,所以他符合构造函数的一些特性:
1. 拷贝构造函数是构造函数的一个重载形式。函数名和类名相同,没有返回值。
2. 拷贝构造函数的参数只有一个,并且必须要使用引用传参!(使用传值方式会引发无穷递归调用!)
#include <iostream> class Date { public: Date(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } /* Date d2(d1); */ Date(Date& d) { // 这里要用引用,否则就会无穷递归下去 _year = d._year; _month = d._month; _day = d._day; } void Print() { printf("%d-%d-%d\n", _year, _month, _day); } private: int _year; int _month; int _day; }; int main(void) { Date d1(2022, 3, 9); Date d2(d1); // 拷贝复制 // 看看拷贝成功没 d1.Print(); d2.Print(); return 0; }
如果函数内不需要改变,建议把 const 也给它加上
原因:防止写反
/* Date d2(d1); */
Date(Date& d) {
d._year = _year;
d._month = _month;
d._day = _day;
}
这段代码不会报错,但是结果显而易见是错的(随机值),这里加一个 const 就安全多了,这些错误就会被检查出来了
2.默认拷贝构造
- 若未显示定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
- 内置类型的成员,会完成按字节序的拷贝(把每个字节依次拷贝过去)。
- 自定义类型成员,会再调用它的拷贝构造。
对于内置类型:
编译器自动生成的拷贝构造会完成浅拷贝或者说是值拷贝,默认的拷贝构造函数对象按内存存储按字节序完成拷贝。
#include<iostream>
using namespace std;
class Date {
public:
Date(int year = 0, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
// Date(Date& d) {
// _year = d._year;
// _month = d._month;
// _day = d._day;
// }
void Print() {
printf("%d-%d-%d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1(2002, 4, 8);
// 拷贝复制
Date d2(d1);
// 没有写拷贝构造,但是也拷贝成功了
这里d2调用的默认拷贝构造完成拷贝,d2和d1的值也是一样的。Dated2(d1);
d1.Print();
d2.Print();
return 0;
}
对于自定义类型:
当类与堆区有联系的话,浅拷贝会带来堆区内存重复释放的问题,需要深拷贝解决。
class Stack
{
public:
Stack(int capacity = 4)
{
if (capacity == 0)
{
_a = nullptr;
_capacity = _size = 0;
}
else
{
_a = (int*)malloc(sizeof(int) * capacity);
_size = 0;
_capacity = capacity;
}
}
~Stack()
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
}
private:
int* _a;
int _capacity;
int _size;
};
运行报错!!
原因:
实现栈的时候,栈的结构问题,导致这里如果用默认的拷贝构造,按字节把所有东西都拷过来会产生问题,如果 Stack st1 拷贝出另一个 Stack st2(st1) 会导致他们都指向那块开辟的内存空间,导致他们指向的空间被析构(free)两次,导致程序崩溃
总结:对于常见的类,比如日期类,默认生成的拷贝构造就够用了。但是对于栈这样的类,默认生成的拷贝构造不能用,需要深拷贝
Ⅳ. 运算符重载
运算符重载引入
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。函数名字为:关键字operator后面接需要重载的运算符符号。函数原型:返回值类型 operator操作符(参数列表)
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型或者枚举类型的操作数
- 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
- 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的
- 操作符有一个默认的形参this,限定为第一个形参
- .* 、:: 、sizeof 、? : 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
. (点运算符)
:: (域运算符)
.* (点星运算符,)
?: (条件运算符)
sizeof
虽然点运算符( . )不能重载,但是箭头运算符( -> )是支持重载的
解引用(*)是可以重载的,不能重载的是点星运算符( .* )
举个栗子:
举一个运算符重载的例子: ==
#include <iostream>
using namespace std;
class Date {
public:
Date(int year = 1970, int month = 1, int day = 1) {
this->_year = year;
this->_month = month;
this->_day = day;
}
// private:
int _year;
int _month;
int _day;
};
/* d1 == d2 */
bool operator==(const Date& d1, const Date& d2) {
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
int main(void) {
Date d1(2022, 3, 8);
Date d2(2022, 5, 1);
cout << (d1 == d2) << endl;
return 0;
}
#include <iostream>
using namespace std;
class Date {
public:
Date(int year = 1970, int month = 1, int day = 1) {
this->_year = year;
this->_month = month;
this->_day = day;
}
/* d1 == d2
bool operator==(Date* this, const Date& d2) (打回原形)
*/
bool operator==(const Date& d2) {
return (
this->_year == d2._year
&& this->_month == d2._month
&& this->_day == d2._day
);
}
private:
int _year;
int _month;
int _day;
};
int main(void) {
Date d1(2022, 3, 8);
Date d2(2022, 5, 1);
cout << (d1 == d2) << endl;
return 0;
}
1.赋值运算符
前言:
c++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符 operator=, 对属性进行值拷贝
默认的赋值运算符会给浅拷贝,对于一般类来说是够用的,如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题;
所以说,只有在类中有属性指向堆区,或者有需要重复释放内存的操作时才要自己写赋值运算符
赋值运算符引入
下面我们先来造轮子(做编译器默认生成的赋值运算符)
赋值运算符重载主要有以下四点:
- 参数类型
- 返回值年
- 检查是否给自己复制
- 返回 *this
编译器默认生成复制重载,跟拷贝构造做的事情完全类似:
① 内置类型成员,会完成字节序值拷贝 —— 浅拷贝。
② 对于自定义类型成员变量,会调用它的 operator= 赋值。
#include <iostream>
using namespace std;
class Date {
public:
/* 全缺省的构造函数 */
Date(int year = 0, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
/* 赋值运算符重载:d1 = d3 */
Date& operator=(const Date& d) {
if (this != &d) { // 防止自己跟自己赋值(这里的&d是取地址)
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
return *this; // 返回左操作数d1
}
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1(2022, 3, 10);
Date d2(2022, 7, 1);
Date d3;
d3 = d2 = d1;
d1.Print();
d2.Print();
d3.Print();
return 0;
}
- 定要防止自己和自己赋值!我们这里加 if 语句来判断就是为了防止极端情况下,自己给自己赋值,加上这条判断后就算遇到自己给自己赋值,就会不做处理,直接跳过。
- 因为有连等情况,所以必须要用引用,return *this;来返回自身,这样才能和常规意义上的“=”一致
需要手写赋值运算符情况
当我们new或者malloc了一个空间,需要用析构函数释放时,就需要手写赋值运算符构建深拷贝,否则会重复delete或者free一块内存导致程序崩溃!!
如图我们new了一个对象,申请了编号为0x0011的空间,如果是浅拷贝(默认赋值运算),第二个对象就任然指向0x0011的空间,当我们析构时就回析构两次,也就是delete两次,导致堆区内存重复释放程序崩溃!
解决方法非常简单,就是多申请一块内存,把新对象指向新空间即可,这样就解决了重复释放的问题了(简称深拷贝)
#include <iostream>
using namespace std;
class Person
{
public:
Person(int age)
{
//将年龄数据开辟到堆区
m_Age = new int(age);
}
//重载赋值运算符
Person& operator=(Person& p)
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//编译器提供的代码是浅拷贝
//m_Age = p.m_Age;
//提供深拷贝 解决浅拷贝的问题
m_Age = new int(*p.m_Age);//要new *p.m_Age 这么大的空间
//返回自身
return *this;
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//年龄的指针
int* m_Age;
};
void test01()
{
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1; //赋值操作
cout << "p1的年龄为:" << *p1.m_Age << endl;
cout << "p2的年龄为:" << *p2.m_Age << endl;
cout << "p3的年龄为:" << *p3.m_Age << endl;
}
int main() {
test01();
return 0;
}
赋值运算符只能作为类的成员函数重载,赋值运算符在类中不显式实现时,编译器会生成一份默认的,此时用户在类外再将赋值运算符重载为全局的,就和编译器生成的默认赋值运算符冲突了,故赋值运算符只能重载成成员函数
2.加号运算符重载
两种情况
p2=p1+p2;
p2=p1+C;(C为常数)
成员函数实现:
Person operator+(const Person& p) {
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
全局函数实现:
Person operator+(const Person& p1, const Person& p2) {
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
函数重载:
Person operator+(const Person& p2, int val)
{
Person temp;
temp.m_A = p2.m_A + val;
temp.m_B = p2.m_B + val;
return temp;
}
class Person {
public:
Person() {};
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
//成员函数实现 + 号运算符重载
Person operator+(const Person& p) {
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
public:
int m_A;
int m_B;
};
//全局函数实现 + 号运算符重载
//Person operator+(const Person& p1, const Person& p2) {
// Person temp(0, 0);
// temp.m_A = p1.m_A + p2.m_A;
// temp.m_B = p1.m_B + p2.m_B;
// return temp;
//}
//运算符重载 可以发生函数重载
Person operator+(const Person& p2, int val)
{
Person temp;
temp.m_A = p2.m_A + val;
temp.m_B = p2.m_B + val;
return temp;
}
void test() {
Person p1(10, 10);
Person p2(20, 20);
//成员函数方式
Person p3 = p2 + p1; //相当于 p2.operaor+(p1)
cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;
Person p4 = p3 + 10; //相当于 operator+(p3,10)
cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;
}
int main() {
test();
system("pause");
return 0;
}
3.左移运算符重载
只能在全局;
ostream& operator<<(ostream& out, Person& p) {
out << "a:" << p.m_A << " b:" << p.m_B;
return out;
}
直接背诵输出模板 ,就可以实现输出自定义数据类型
class Person {
friend ostream& operator<<(ostream& out, Person& p);
public:
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
//成员函数 实现不了 p << cout 不是我们想要的效果
//void operator<<(Person& p){
//}
private:
int m_A;
int m_B;
};
//全局函数实现左移重载
//ostream对象只能有一个
ostream& operator<<(ostream& out, Person& p) {
out << "a:" << p.m_A << " b:" << p.m_B;
return out;
}
void test() {
Person p1(10, 20);
cout << p1 << "hello world" << endl; //链式编程
}
int main() {
test();
system("pause");
return 0;
}
同理右移运算符也是一样的
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
4.递增运算符重载
通过重载递增运算符,实现自己的整型数据
前置++:
MyInteger& operator++() {
//先++
m_Num++;
//再返回
return *this;
}
必须传引用因为是对一个数本身++,符合++(++a)情况
后置++:
MyInteger operator++(int) {
//先返回
MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,
//但是返回的是以前的值,达到先返回后++;
m_Num++;
return temp;
}
不需要引用,因为返回的是一个临时变量 ;需要占位参数int来区分前后置
#include <iostream>
using namespace std;
class MyInteger {
public:
MyInteger() {
m_Num = 0;
}
//前置++
MyInteger& operator++() {
//先++
m_Num++;
//再返回
return *this;
}
//后置++
MyInteger operator++(int) {
//先返回
MyInteger temp = *this; //记录当前本身的值,然后让本身的值加1,但是返回的是以前的值,达到先返回后++;
m_Num++;
return temp;
}
int m_Num;
};
ostream& operator<<(ostream& out, MyInteger myint) {
out << myint.m_Num;
return out;
}
//前置++ 先++ 再返回
void test01() {
MyInteger myInt;
cout << ++myInt << endl;
cout << myInt << endl;
}
//后置++ 先返回 再++
void test02() {
MyInteger myInt;
cout << myInt++ << endl;
cout << myInt << endl;
}
int main() {
//test01();
test02();
return 0;
}
5.关系运算符重载
非常简单,如图所示
bool operator==(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
else
{
return false;
}
}
bool operator!=(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
else
{
return true;
}
}
以此类推>= 、 <= 、> 、< 也一样
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
};
bool operator==(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
else
{
return false;
}
}
bool operator!=(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
else
{
return true;
}
}
string m_Name;
int m_Age;
};
void test01()
{
//int a = 0;
//int b = 0;
Person a("孙悟空", 18);
Person b("孙悟空", 18);
if (a == b)
{
cout << "a和b相等" << endl;
}
else
{
cout << "a和b不相等" << endl;
}
if (a != b)
{
cout << "a和b不相等" << endl;
}
else
{
cout << "a和b相等" << endl;
}
}
int main() {
test01();
system("pause");
return 0;
}
(笔记篇:资料来源 - 比特科技)