类的 6 个默认成员函数
文章目录
- 一、构造函数
- 1. 构造函数的定义
- 2. 编译器生成的构造函数
- 3. 默认构造函数
- 4. 初始化列表
- 5. 内置成员变量指定缺省值(C++11)
- 二、析构函数
- 1. 析构函数的定义
- 2. 编译器生成的析构函数
- 3. 自己写的析构函数的执行方式
- 三、拷贝构造函数
- 1. C语言值传递和返回值时存在 bug
- 2. 拷贝构造函数的定义
- 3. 编译器自动生成的拷贝构造函数
- 4. 自己写的拷贝构造函数的执行方式
- 5. 拷贝构造函数的调用场景
- 四、赋值运算符重载
- 1. 运算符重载
- (1) 运算符重载的定义和使用
- (2) 前置++/-- 和 后置++/-- 的运算符重载规定
- (3) 运算符重载也可以实现在全局域中
- 2. 赋值运算符重载的定义
- 3. 编译器自动生成的赋值运算符重载
- 五、取地址运算符重载
- 1. const 成员函数
- 2. 取地址运算符重载
- 六、const 取地址运算符重载
一、构造函数
#include <iostream>using namespace std;class Date
{
public:void Init(){_year = 1970;_month = 1;_day = 1;}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Init();//输出 1970年1月1日d1.Print();Date d2;//输出 -858993460年 - 858993460月 - 858993460日d2.Print();return 0;
}
我们写出的 Date 类,实例化对象后,对象的成员的成员变量默认是随机值,于是每个对象都需要调用初始化函数,难免会有忘记的时候
1. 构造函数的定义
C++ 提供了一个特殊的成员函数 用于初始化对象的成员变量 叫做构造函数,可以很好的解决这个问题
构造函数的特点:
- 实例化对象时,自动调用,在对象的声明周期中只会调用一次
- 函数名和类名相同
- 无返回值,指的是不能写返回值
- 构造函数可以重载,类的对象可以有多种初始化方式
将类中的 Init 函数改为构造函数,并提供多个构造函数
#include <iostream>using namespace std;class Date
{
public://无参构造函数Date(){_year = 1970;_month = 1;_day = 1;//验证是否调用了构造函数cout << "Date()" << " ";}//带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;//验证是否调用了构造函数cout << "Date(int year, int month, int day)" << " ";}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}private:int _year;int _month;int _day;
};
构造函数是在实例化对象的时候自动调用的
int main()
{//调用无参构造函数Date d1;//输出 Date() 1970年1月1日d1.Print();//传参调用构造函数Date d2(2023, 2, 4);//Date(int year, int month, int day) 输出 2023年2月4日d2.Print();return 0;
}
-
为什么不允许 d1.Date() 调用构造函数呢?
如果可以这样调用便和我们写的 Init 函数,然后自己调用没有区别,并且构造函数只能调用一次 -
为什么不传参调用构造函数时不加括号?
如果加上扩号则为 Date d1(); 此时和声明一个函数的返回值是 Date,函数名为 d1,无参的函数一模一样,便会产生歧义
2. 编译器生成的构造函数
如果类中没有构造函数,编译器会自动生成一个无参的构造函数,如果存在构造函数,便不会生成
编译器自动生成的构造函数的行为是什么呢?
- 内置类型的成员变量不做处理
内置类型包括(char short int long float double 型,各种类型的指针,数组元素是内置类型等) - 自定义类型的成员变量调用该类型的默认构造函数(不需要传参的构造函数)
自定义类型(类,结构体,联合体)
#include <iostream>using namespace std;//时间类
class Time
{
public://时间类的构造函数Time(){_hour = _minute = _second = 0;}void Print(){cout << _hour << "时" << _minute << "分" << _second << "秒" << endl;}private:int _hour;int _minute;int _second;
};//日期类
class Date
{
public://无构造函数void Print(){cout << _year << "年" << _month << "月" << _day << "日" << " ";_time.Print();}private:int _year;int _month;int _day;Time _time;
};int main()
{Date d;//默认构造函数的行为:内置类型不处理,自定义类型调用他的默认构造函数//输出 -858993460年-858993460月-858993460日 0时0分0秒d.Print();return 0;
}
3. 默认构造函数
无参构造函数、全缺省构造函数、我们没写编译器自动生成的构造函数,这些都是 不需要传参就可以调用的构造函数,称之为默认构造函数,默认构造函数只能有一个
#include <iostream>using namespace std;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 d1;return 0;
}
上述代码中,由于存在带参数的构造函数,编译器便不会自动生成构造函数,于是就没有默认构造函数可用
因此如果是自己写构造函数,需要提供默认构造函数,一般提供全缺省
//全缺省构造函数
Date(int year = 1970, int month = 1, int day = 1)
{_year = year;_month = month;_day = day;
}
4. 初始化列表
构造函数体内的赋值语句其实并不是成员变量初始化的地方,因为初始化只能有一次,而构造函数体内可以对成员变量进行多次赋值
#include <iostream>using namespace std;class Date
{
public:Date(int year = 1970, int month = 1, int day = 1){//构造函数体内可以对成员变量进行多次赋值//构造函数体内并不是成员变量初始化的地方_year = year;_month = month;_day = day;_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};int main()
{Date d1;return 0;
}
初始化列表才是对成员变量初始化的地方
初始化列表:在构造函数括号后,以冒号开始,逗号分隔的成员列表,每个成员后跟一个放在括号中的初始值或者构造函数中的参数
#include <iostream>using namespace std;//时间类
class Time
{
public:Time()//初始化列表: _hour(0), _minute(0), _second(0){}Time(int hour, int minute, int second)//初始化列表: _hour(hour), _minute(minute), _second(second){}void Print(){cout << _hour << "时" << _minute << "分" << _second << "秒" << endl;}private:int _hour;int _minute;int _second;
};//日期类
class Date
{
public:Date()//初始化列表: _year(1970), _month(1), _day(1){}Date(int year, int month, int day, int hour, int minute, int second)//初始化列表: _year(year), _month(month), _day(day), _time(hour, minute, second){}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << " ";_time.Print();}private:int _year;int _month;int _day;Time _time;
};int main()
{Date d1;d1.Print(); //输出 1970年1月1日 0时0分0秒Date d2(2023, 2, 8, 22, 58, 24);d2.Print(); //输出 2023年2月8日 22时58分24秒return 0;
}
注意:
- 每个成员变量在初始化列表中只能出现一次
初始化只能初始化一次 - 内置类型 如果未出现在初始化列表中则不做处理
- 自定义类型 如果未出现在初始化列表中则调用该类型的默认构造函数
- 类中包含以下成员时,必须放在初始化列表位置进行初始化:
引用成员变量,const 成员变量,自定义类型成员(且该类没有默认构造函数时)
建议尽量使用初始化列表对成员变量初始化,因为无论如何自定义类型都会根据初始化列表来进行初始化的方式
注意:成员变量初始化完成之后才会进入构造函数体中,构造函数体中只能用来赋值,当成员变量存在数组时,就可以在构造函数体内赋值
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
#include <iostream>using namespace std;class A
{
public:A(int a)//根据成员声明顺序//先用随机值初始化_a1,在用 a 初始化 _a2: _a2(a), _a1(_a2){}void Print() {cout << _a1 << " " << _a2 << endl;}private:int _a1;int _a2;
};int main()
{A aa(1);aa.Print(); //输出 -858993460 1return 0;
}
5. 内置成员变量指定缺省值(C++11)
由于编译器自动生成的构造函数对内置类型不做处理,不是很好,到了 C++11 给这里打了补丁,内置类型的成员变量在类中声明时可以指定缺省值
我们没写构造函数时,编译器生成的默认构造函数,内置类型使用缺省值初始化成员变量(C++11)
#include <iostream>using namespace std;class Date
{
public:void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}private:int _year = 1970;int _month = 1;int _day = 1;
};int main()
{Date d1;d1.Print(); //输出 1970年1月1日return 0;
}
我们写了构造函数时,内置类型成员变量在初始化时,先使用初始化列表中指定的值,如果初始化列表中没有指定,才使用成员变量声明时指定的缺省值(C++11),如果都没有,则只定义不初始化(随机值)
#include <iostream>using namespace std;class Date
{
public:Date(): _year(1970){}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}private:int _year = 2023;int _month = 1;int _day;
};int main()
{//_year 即在初识化列表中指定,又在声明时给缺省值//_month 只在声明时给了缺省值//_day 什么都没有给Date d1;d1.Print(); //输出 1970年1月-858993460日return 0;
}
二、析构函数
#include <iostream>using namespace std;typedef int StackDataType;class Stack
{
public:Stack(int capacity = 4): _capacity(capacity), _top(0){_a = (StackDataType*)malloc(sizeof(StackDataType) * _capacity);if (_a == nullptr){perror("malloc");exit(-1);}}void Push(StackDataType x){//扩容...//插入_a[_top] = x;++_top;}//...void Destroy(){if (_a){free(_a);_a = nullptr;_top = _capacity = 0;}}private:StackDataType* _a;int _top;int _capacity;
};int main()
{Stack st;st.Push(1);//使用...return 0;
}
对于像栈这样,需要在堆上开辟内存空间存储数据的类,每个对象使用完时都需要调用销毁函数,否则会导致内存泄漏,和调用初识化函数一样,难免会有忘记的时候
1. 析构函数的定义
C++ 提供了一个特殊的成员函数 用于清理对象资源 叫做析构函数,可以很好的解决这个问题,他的工作和构造函数相反,可以将析构函数的特性和构造函数对比
析构函数的特点:
- 对象生命周期结束时,编译器会自动调用析构函数
- 函数名是在类名前加上字符 ~
- 无参数,无返回值,指的是不能写返回值
- 一个类只能有一个析构函数,析构函数不能重载
将类中的 Destroy 函数改为析构函数
#include <iostream>using namespace std;typedef int StackDataType;class Stack
{
public:Stack(int capacity = 4): _capacity(capacity), _top(0){_a = (StackDataType*)malloc(sizeof(StackDataType) * _capacity);if (_a == nullptr){perror("malloc");exit(-1);}}void Push(StackDataType x){//扩容...//插入_a[_top] = x;++_top;}//...//析构函数~Stack(){if (_a){free(_a);_a = nullptr;_top = _capacity = 0;}//验证是否调用了析构函数cout << "~Stack()" << " ";}private:StackDataType* _a;int _top;int _capacity;
};
析构函数是在对象销毁的时候自动调用的
int main()
{Stack st;st.Push(1);//使用...//可以手动调用析构函数st.~Stack();return 0;
}
输出 ~Stack() ~Stack()
析构函数允许 st.~Stack() 调用,并且无论什么时候,只要在对象销毁时,编译器都会自动调用析构函数
2. 编译器生成的析构函数
如果类中没有析构函数,编译器会自动生成析构函数,那编译器自动生成的析构函数的行为是什么呢?
- 内置类型的成员变量不做处理,也不需要处理(因为不用释放资源)
- 自定义类型的成员变量调用该类型的析构函数
#include <iostream>using namespace std;typedef int StackDataType;//栈
class Stack
{
public:Stack(int capacity = 4): _capacity(capacity), _top(0){_a = (StackDataType*)malloc(sizeof(StackDataType) * _capacity);if (_a == nullptr){perror("malloc");exit(-1);}}void Push(StackDataType x){//扩容...//插入_a[_top] = x;++_top;}//...//析构函数~Stack(){if (_a){free(_a);_a = nullptr;_top = _capacity = 0;}//验证是否调用了析构函数cout << "~Stack()" << " ";}private:StackDataType* _a;int _top;int _capacity;
};//两个栈的类
class Queue
{
public:void Push(StackDataType x){_pushS.Push(x);}//...//没有析构函数private:Stack _pushS;Stack _popS;
};int main()
{Queue q;return 0;
}
输出 ~Stack() ~Stack()
3. 自己写的析构函数的执行方式
我们自己写的析构函数,会先执行析构函数体中的内容,然后再调用自定义类型的析构函数
在上个代码中,在 Queue 类中加上析构函数,输出 ~Queue() ~Stack() ~Stack()
~Queue()
{cout << "~Queue()" << " ";
}
三、拷贝构造函数
1. C语言值传递和返回值时存在 bug
在用 C语言写栈这些数据结构时,我们在进行参数传递时,都是采用传地址的方式,一直以为传值的方式,只是因为效率不高,其实对于像栈这样,需要在堆上开辟内存空间存储数据的结构,以传值的方式传参会存在 bug
#include <stdio.h>
#include <stdlib.h>typedef int StackDataType;//栈结构
typedef struct Stack
{StackDataType* a;int top;int capacity;
}Stack;//初始化
void Init(Stack* ps)
{ps->a = (StackDataType*)malloc(sizeof(StackDataType) * 4);if (ps->a == NULL){exit(-1);}ps->capacity = 4;ps->top = 0;
}//插入
void Push(Stack* ps, StackDataType x)
{ps->a[ps->top] = x;ps->top++;
}//测试
void test(Stack s)
{s.a[0] = 0;
}int main()
{Stack s;Init(&s);Push(&s, 1);printf("%d\n", s.a[0]); //输出 1test(s);printf("%d\n", s.a[0]); //输出 0return 0;
}
根据输出结果,发现 采用值传递的方式调用 test 函数,尽然可以修改栈的数据,为什么呢?
C语言中,值传递是通过按字节的方式将实参拷贝给形参(按字节拷贝称为浅拷贝)
于是在 C++中,为了避免这种情况,如果函数传递的参数是自定义类型时,需要调用该类型的拷贝构造函数,同样的 返回值是自定义类型时,也需要调用该类型的拷贝构造函数
2. 拷贝构造函数的定义
拷贝构造函数是 C++ 提供的一个特殊的成员函数,用于创建一个与已存在对象一某一样的新对象
拷贝构造函数是构造函数的重载,具有构造函数的特性,但是拷贝构造函数的参数只能有一个,并且必须是类类型对象的引用
拷贝构造的参数如果是类类型,采用值传递的方式可以吗?
自定义类型传参时,需要调用拷贝构造函数,此时就会导致无穷递归,所以不可以进行值传递
于是为了避免无穷递归,拷贝构造函数的参数需要传内置类型,因此必须采用地址传递的方式,因此参数可以是类类型的指针,也可以是类类型的引用,由于引用更方便,因此规定拷贝构造函数的参数必须是类类型的引用
#include <iostream>
#include <string.h>using namespace std;typedef int StackDataType;//栈
class Stack
{
public:Stack(int capacity = 4): _capacity(capacity), _top(0){_a = (StackDataType*)malloc(sizeof(StackDataType) * _capacity);if (_a == nullptr){perror("malloc");exit(-1);}}//拷贝构造函数//加上 const 防止内部修改源对象Stack(const Stack& s): _capacity(s._capacity), _top(s._top){//开新空间_a = (StackDataType*)malloc(sizeof(StackDataType) * _capacity);if (_a == nullptr){perror("malloc");exit(-1);}//拷贝数据memcpy(_a, s._a, sizeof(StackDataType) * _capacity);//验证是否会调用拷贝构造cout << "Stack(Stack& s)" << " ";}//虽然可以完成拷贝构造的工作,但这个不是拷贝构造函数//只是构造函数的一个重载形式而已Stack(Stack* s): _capacity(s->_capacity), _top(s->_top){_a = (StackDataType*)malloc(sizeof(StackDataType) * _capacity);if (_a == nullptr){perror("malloc");exit(-1);}//拷贝数据memcpy(_a, s->_a, sizeof(StackDataType) * _capacity);}void Push(StackDataType x){//扩容...//插入_a[_top] = x;++_top;}void Print(){for (int i = 0; i < _top; ++i){cout << _a[i] << " ";}cout << endl;}//...~Stack(){if (_a){free(_a);_a = nullptr;_top = _capacity = 0;}}private:StackDataType* _a;int _top;int _capacity;
};int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Print(); //输出 1 2//两种方式都可以调用拷贝构造函数//Stack s2(s1);Stack s2 = s1;s2.Print(); //输出 Stack(Stack& s) 1 2//调用指针版的构造函数Stack s3(&s1);s3.Print(); //输出 1 2return 0;
}
3. 编译器自动生成的拷贝构造函数
如果类中没有拷贝构造函数,编译器会自动生成拷贝构造函数,那编译器自动生成的拷贝构造函数的行为是什么呢?
- 内置类型的成员变量进行浅拷贝
- 自定义类型的成员变量调用该类型的拷贝构造函数
#include <iostream>
#include <string.h>using namespace std;typedef int StackDataType;//栈
class Stack
{
public:Stack(int capacity = 4): _capacity(capacity), _top(0){_a = (StackDataType*)malloc(sizeof(StackDataType) * _capacity);if (_a == nullptr){perror("malloc");exit(-1);}}//拷贝构造函数//加上 const 防止内部修改源对象Stack(const Stack& s): _capacity(s._capacity), _top(s._top){//开新空间_a = (StackDataType*)malloc(sizeof(StackDataType) * _capacity);if (_a == nullptr){perror("malloc");exit(-1);}//拷贝数据memcpy(_a, s._a, sizeof(StackDataType) * _capacity);}void Push(StackDataType x){//扩容...//插入_a[_top] = x;++_top;}void Print(){for (int i = 0; i < _top; ++i){cout << _a[i] << " ";}cout << endl;}//...~Stack(){if (_a){free(_a);_a = nullptr;_top = _capacity = 0;}}private:StackDataType* _a;int _top;int _capacity;
};class Test
{
public://无拷贝构造函数void Push(StackDataType x){++_size;_st.Push(x);}void Print(){cout << "size:" << _size << " ";_st.Print();}private:int _size = 0;Stack _st;
};int main()
{//Test 类中,可以使用编译器生成的默认构造函数Test t1;t1.Push(1);t1.Push(2);t1.Print(); //输出 size:2 1 2//内置类型进行浅拷贝,自定义类型调用该类型的拷贝构造函数Test t2(t1);t2.Print(); //输出 size:2 1 2return 0;
}
4. 自己写的拷贝构造函数的执行方式
拷贝构造函数的执行方式和构造函数的执行方式是一样的,成员变量先使用拷贝构造函数的初始化列表或成员变量声明时指定的缺省值(C++11) 初始化成员变量,然后执行拷贝构造函数中的内容
因此自定义类型成员变量,需要自己在拷贝构造函数的初始化列表显示指定调用该类型的拷贝构造函数,或者自己在拷贝构造函数体中显示指定调用该类型的拷贝构造
注意:拷贝构造函数也是构造函数,当我们写了拷贝构造函数后,编译器便不会生成构造函数
5. 拷贝构造函数的调用场景
有如下三种场景:
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
#include <iostream>using namespace std;class Date
{
public:Date(int year, int minute, int day){cout << "Date(int, int, int)" << endl;}Date(const Date& d){cout << "Date(const Date& d)" << endl;}~Date(){cout << "~Date():" << endl;}private:int _year;int _month;int _day;
};Date Test(Date d)
{Date temp(d);return temp;
}int main()
{Date d1(2023, 2, 10);Test(d1);return 0;
}
输出结果:
Date(int, int, int)
Date(const Date& d)
Date(const Date& d)
Date(const Date& d)
~Date():
~Date():
~Date():
~Date():
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用
四、赋值运算符重载
1. 运算符重载
(1) 运算符重载的定义和使用
在 C语言中,运算符只支持内置类型,而不支持自定义类型,于是 C++ 为了增强代码的可读性引入了运算符重载,使得自定义类型也可以使用运算符
运算符重载是具有特殊函数名的函数,其返回值类型与参数列表与普通的函数类似
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator运算符(参数列表); 参数列表的个数和运算符的操作数相同,如果有两个操作数,则第一个参数表示左操作数,第二个表示右操作数
==运算符的重载函数 和 使用
#include <iostream>using namespace std;class Date
{
public:Date(int year = 1970, int month = 1, int day = 1): _year(year), _month(month), _day(day){}//== 运算符重载//左操作数是 this,指向调用函数的对象bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2(2023, 2, 6);Date d3(d1);//两种使用运算符重载函数的方式cout << d1.operator==(d2) << endl; //输出 0// << 比 == 优先级高,需要加括号//编译器会解释为 d1.operator==(d3);cout << (d1 == d3) << endl; //输出 1return 0;
}
注意:
- operator 关键字后不能连接语言中没有的运算符
比如:operator@ - 运算符重载函数必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整型 +,不能改变其含义 - .(成员访问运算符)、.*(点星)、::(域作用限定符)、?:(三目运算符)、sizeof
注意:这 5 个运算符不能重载 - 运算符重载不能改变运算符操作数的个数
比如 + 需要两个操作数,则重载的 + 也必须要有两个操作数 - 运算符重载不能改变运算符的优先级和结合性
(2) 前置++/-- 和 后置++/-- 的运算符重载规定
默认表示前置++/-- 的运算符重载,++d1 解释为 d1.operator++();
Date& operator++();
Date& operator--();
前置++/-- 和后置++/-- 都是一元运算符,为了可以让前置++ 和后置++ 形成函数重载,C++ 规定后置++ 重载时多增加一个 int 类型的参数(可以不用写形参名),调用函数时该参数不用传递,编译器自动传递,d1++ 解释为 d1.operator++(整形),int 参数编译器会自动传
Date operator++(int);
Date operator--(int);
(3) 运算符重载也可以实现在全局域中
cout 和 cin 其实分别是 ostream 和 istream 类的对象,流插入 << 和流提取 >> 运算符都在各自的类中重载了,因此 cout 可以自动识别类型,就是根据 << 运算符重载和函数重载达到的
在类中实现流插入 << 和流提取 >> 运算符重载时,通常我们都是 cout 和 cin 在左,由于左操作数必须为第一参数,而类中的成员函数第一参数是 this,此时 << 和 >> 运算符便不能实现在自己写的类中,需要写到全局域中
但是声明在全局域中时,便不能访问类的私有成员,这里可以在类中进行友元函数声明
//返回 ostream 对象,为了可以连续输出
inline ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}//返回 istream 对象,为了可以连续输入
inline istream& operator<<(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}
2. 赋值运算符重载的定义
赋值运算符重载也是 C++ 提供的一个特殊的成员函数,用于将一个已存在对象赋值给另一个已存在的对象
//赋值运算符重载
Date& operator=(const Date& d)
{//自己给自己赋值,不需要处理if(this != &d){_year = d._year;_month = d._month;_day = d._day;}//this 指针指向的对象在上一层栈帧中,可以返回引用,return *this;
}
- 参数传 const 引用,是为了防止源数据被修改以及避免拷贝构造
- 返回值 *this 是为了支持连续赋值,并且满足连续赋值的含义,d1 = d2 = d3
- 返回值传引用,是为了避免拷贝构造
#include <iostream>using namespace std;class Date
{
public:Date(int year = 1970, int month = 1, int day = 1): _year(year), _month(month), _day(day){}//赋值运算符重载Date& operator=(const Date& d){//自己给自己赋值,不需要处理if (this != &d){_year = d._year;_month = d._month;_day = d._day;}//this 指针指向的对象在上一层栈帧中,可以返回引用,return *this;}void Print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2(2020, 9, 1);Date d3(2023, 2, 6);d1.Print(); //输出 1970年1月1日d2.Print(); //输出 2020年9月1日d3.Print(); //输出 2023年2月6日//赋值运算符重载,支持连续赋值d1 = d2 = d3;d1.Print(); //输出 2023年2月6日d2.Print(); //输出 2023年2月6日d3.Print(); //输出 2023年2月6日return 0;
}
3. 编译器自动生成的赋值运算符重载
如果类中没有赋值运算符重载函数,编译器会自动生成赋值运算符重载函数,那编译器自动生成的赋值运算符重载函数的行为是什么呢?
- 内置类型的成员变量进行浅拷贝
- 自定义类型的成员变量调用该类型的赋值运算符重载函数
由于类中没有赋值运算符重载函数时,编译器会自动生成,因此类的赋值运算符重载不可以写在全局中
五、取地址运算符重载
1. const 成员函数
在成员函数括号后用 const 修饰,称为 const 成员函数,实际上是用 const 修饰成员函数参数中的 *this 指针,使得调用成员函数的对象的成员变量在函数中不能被修改,并且 const 对象和非 const 对象均可以调用 const 成员函数
class Date
{
public://const 成员函数,使得调用成员函数的对象的成员变量不会被修改void Print() const{}private:int _year;int _month;int _day;
};
2. 取地址运算符重载
一般不需要自己写,使用编译器生成的取地址运算符重载即可
Date* operator&()
{return this;
}
六、const 取地址运算符重载
一般不需要自己写,使用编译器生成的 const 取地址运算符重载即可
const Date* operator&() const
{return this;
}
空类其实并不是什么都没有,编译器会自动生成这 6 个 默认成员函数
默认成员函数:用户没有显式实现时,编译器会自动生成
相关文章:

类的 6 个默认成员函数
文章目录一、构造函数1. 构造函数的定义2. 编译器生成的构造函数3. 默认构造函数4. 初始化列表5. 内置成员变量指定缺省值(C11)二、析构函数1. 析构函数的定义2. 编译器生成的析构函数3. 自己写的析构函数的执行方式三、拷贝构造函数1. C语言值传递和返回值时存在 bug2. 拷贝构…...

基于Verilog HDL的状态机描述方法
⭐本专栏针对FPGA进行入门学习,从数电中常见的逻辑代数讲起,结合Verilog HDL语言学习与仿真,主要对组合逻辑电路与时序逻辑电路进行分析与设计,对状态机FSM进行剖析与建模。 🔥文章和代码已归档至【Github仓库…...

6年软件测试经历:成长、迷茫、奋斗
前言 测试工作6年,经历过不同产品、共事过不同专业背景、能力的同事,踩过测试各种坑、遇到过各种bug。测试职场生涯积极努力上进业务和技术能力快速进步过、也有努力付出却一无所得过、有对测试生涯前景充满希望认为一片朝气蓬勃过、也有对中年危机思考不…...
OpenMMLab AI实战营第五次课程
语义分割与MMSegmentation 什么是语义分割 任务: 将图像按照物体的类别分割成不同的区域 等价于: 对每个像素进行分类 应用:无人驾驶汽车 自动驾驶车辆,会将行人,其他车辆,行车道,人行道、交…...

【软考】系统集成项目管理工程师(二十)项目风险管理
一、项目风险管理概述1. 风险概念2. 风险分类3. 风险成本二、项目风险管理子过程1. 规划风险管理2. 识别风险3. 实施定性风险分析4. 实施定量风险分析5. 规划风险应对6. 控制风险三、项目风险管理流程梳理一、项目风险管理概述 1. 风险概念 风险是一种不确定事件或条件,一旦…...

2017-PMLR-Neural Message Passing for Quantum Chemistry
2017-PMLR-Neural Message Passing for Quantum Chemistry Paper: https://arxiv.org/pdf/1704.01212.pdf Code: https://github.com/brain-research/mpnn 量子化学的神经信息传递 这篇文献作者主要是总结了先前神经网络模型的共性,提出了一种消息传递神经网络&am…...
Python:每日一题之全球变暖(DFS连通性判断)
题目描述 你有一张某海域 NxN 像素的照片,"."表示海洋、"#"表示陆地,如下所示: ....... .##.... .##.... ....##. ..####. ...###. ....... 其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿…...

企业级安全软件装机量可能大增
声明 本文是学习大中型政企机构网络安全建设发展趋势研究报告. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 研究背景 大中型政企机构是网络安全保护的重中之重,也是国内网络安全建设投入最大,应用新技术、新产品最多的机构…...

为什么要用频谱分析仪测量频谱?
频谱分析仪是研究电信号频谱结构的仪器,用于信号失真度、调制度、谱纯度、频率稳定度和交调失真等信号参数的测量,可用以测量放大器和滤波器等电路系统的某些参数,是一种多用途的电子测量仪器。从事通信工程的技术人员,在很多时候…...

Python环境搭建、Idea整合
1、学python先要下载什么? 2、python官网 3、idea配置Python 4、idea新建python 学python先要下载什么? python是一种语言,首先你需要下载python,有了python环境,你才可以在你的电脑上使用python。现在大多使用的是pyt…...

HTTP请求返回304状态码以及研究nginx中的304
文章目录1. 引出问题2. 分析问题3. 解决问题4. 研究nginx中的3044.1 启动服务4.2 ETag说明4.3 响应头Cache-Control1. 引出问题 之前在调试接口时,代码总出现304问题,如下所示: 2. 分析问题 HTTP 304: Not Modified是什么意思? …...

【GD32F427开发板试用】使用Arm-2D显示电池电量
本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动,更多开发板试用活动请关注极术社区网站。作者:boc 【虽迟但到】 由于快递的原因,11月份申请的,12月1日才收到GD32F427开发板。虽然姗姗来迟,但也没有减少…...
TS第二天 Typesrcipt编译
文章目录自动编译tsconfig.json配置选项include 比较重要excludeextendsfilescompilerOptions 比较重要自动编译 手动模式:每次ts文件修改完,手动编译一次 tsc 01.ts监视模式:ts文件修改完,自动监视编译 tsc 01.ts -w编译所有文…...

基于C#制作一个飞机大战小游戏
此文主要基于C#制作一个飞机大战游戏,重温经典的同时亦可学习。 实现流程1、创建项目2、界面绘制3、我方飞机4、敌方飞机5、子弹及碰撞检测实现流程 1、创建项目 打开Visual Studio,右侧选择创建新项目。 搜索框输入winform,选择windows窗体…...

git修改历史提交(commit)信息
我们在开发中使用git经常会遇到想要修改之前commit的提交信息,这里记录下怎么使用git修改之前已经提交的信息。一、修改最近一次commit的信息 首先通过git log查看commit信息。 我这里一共有6次commit记录。 最新的commit信息为“Merge branch ‘master’ of https:…...

代码解析工具cpg
cpg 是一个跨语言代码属性图解析工具,它目前支持C/C (C17), Java (Java 13)并且对Go, LLVM, python, TypeScript也有支持,在这个项目的根目录下: cpg-core为cpg解析模块的核心功能,主要包括将代码解析为图,core模块只包括对C/C/Ja…...
Linux虚拟机部署Java环境-Jdk-Mysql
Linux虚拟机部署 author hf 1.安装 电脑安装x-shell工具,然后使用堡垒机基础控件windows版进行安装扫描,最后点击自动检测,保证能扫描到X-shell工具的安装路径 使用堡垒机登录快照夏选择工具点击Xshell进行连接 查看linux版本 root:~# ca…...

每日学术速递2.9
CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV、cs.AI、cs.LG、cs.IR 1.Graph Signal Sampling for Inductive One-Bit Matrix Completion: a Closed-form Solution(ICLR 2023) 标题:归纳单比特矩阵完成的图信号采样&am…...

【Linux】进程优先级 | 进程的切换 | 环境变量详解
🤣 爆笑教程 👉 《看表情包学Linux》👈 猛戳订阅 🔥 💭 写在前面:我们先讲解进程的优先级,探讨为什么会存在优先级,以及如何查看系统进程、进程优先级的修改。然后讲解进程的切…...

leaflet 实现左卷帘效果 (代码示例045)
第045个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中实现左卷帘效果,这里主要引用了leaflet-side-by-side这个插件,直接调用的话,CSS方面有些问题,需要自行调整一下。 直接复制下面的 vue+leaflet源代码,操作2分钟即可运行实现效果 文章目录 示例效果配…...

PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...

iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...

android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...

解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...

系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文通过代码驱动的方式,系统讲解PyTorch核心概念和实战技巧,涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...
十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建
【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...

mac:大模型系列测试
0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何,是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试,是可以跑通文章里面的代码。训练速度也是很快的。 注意…...