类的 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分钟即可运行实现效果 文章目录 示例效果配…...
程序的翻译环境和执行环境
程序环境和预处理🦖程序的翻译环境和执行环境🦖详解编译链接🐳 翻译环境🐳 详解编译过程🐳 运行环境🦖预处理详解🐳 预定义符号🐳 #define🦀 #define 定义标识符…...
2023最新量化优选股票参考(2.9)
还是周一发的那些股票(可以看我周一的文章),安心持仓就好,跑赢指数是大概率的事情,也大概率获得正收益。 其实我知道大家都没法全天一直看盘操作,毕竟要工作,我也是一样,没法一直看盘…...
深眸科技以科技赋能智慧物流搭建,实现周转箱拆垛作业智能化
数字化时代下市场竞争的核心要素转化为科技的竞争,智能化技术的投入是企业占据市场竞争绝对优势的重要支撑。深眸科技凭借轻辙视觉引擎实现周转箱拆垛作业的智能化突破。人力成本增加,企业积极转变特别是在后疫情时代,人力成本迅猛增加&#…...
R数据分析:孟德尔随机化中介的原理和实操二
delta方法 上面的流程跑通之后,对于中介分析,我们需要报告间接效应的估计值和置信区间,还有中介比例的估计值和置信区间,类似下面的这样: 但是其实我们是光跑孟德尔是得不到上面的需要的值的(比如间接效应…...
【SQL开发实战技巧】系列(十二):三问(如何对字符串字母去重后按字母顺序排列字符串?如何识别哪些字符串中包含数字?如何将分隔数据转换为多值IN列表?)
系列文章目录 【SQL开发实战技巧】系列(一):关于SQL不得不说的那些事 【SQL开发实战技巧】系列(二):简单单表查询 【SQL开发实战技巧】系列(三):SQL排序的那些事 【SQL开发实战技巧…...
数据库模式(schema)是什么?
在数据库的术语中,模式(schema)是一个逻辑概念,用于组织数据库中的对象。模式中的对象通常包括表、索引、数据类型、序列、视图、存储过程、主键、外键等等。 模式可以为数据库对象提供逻辑隔离功能,不用应用程序可以…...
出现failed to load steamui.dll如何解决?好的修复方法推荐
当你电脑突然出现failed to load steamui.dll的时候,你是否一脸懵逼?根本不知道发生啥时候,突然就会这样报错,其实造成这个原因,主要是因为问题出在steam上,我们还是有很多种方法可以解决的,今天…...
js 原生事件触发
var event nullevent new Event(input);document.querySelectorAll("input[placeholder点击网址 选择远端数据字典网址]")[0].dispatchEvent(event)...
Nacos安装配置(二)
目录 一、概述 二、Nacos 安装 A)Debian11 1)软件环境 2)下载源码或者安装包 3)mysql配置 4)启动服务器 B) Debian11 1) 安装JDK 2) 安装Maven 3) 安装Nacos2 4) 修改访问参数(/conf/applicati…...
【Linux基础知识】
Linux基础知识 Linux基础知识 系统目录结构 /bin: 命令和应用程序。 /boot: 这里存放的是启动 Linux 时使用的一些核心文件,包括一些连接文件以及镜像文件。 /dev : dev 是 Device(设备) 的缩写, 该目录下存放的是 Linux 的外…...
济宁有没有专门做网站的/营销型网站模板
今天终于将交叉编译环境搭建完成,第一次用linux的一些命令,还真不习惯,不过用多了也不错,Ubuntu8.04是在虚拟机下安装的,下面简单记录自己安装的过程,以便日后查阅.Step1:下载cross-3.2.tar.bz2,然后复制到/home文件下:sudo cp cross-3.2.tar.bz2 /home再解压到/usr/local文件夹…...
澳门网站建设运营app/yandex搜索引擎
count是一种最简单的聚合函数,一般也是我们第一个开始学习的聚合函数.很多人认为count(1)执行的效率会比count()高,原因是count()会存在全表扫描,而count(1)可以针对一个字段进行查询。其实不然,count(1)和count(*)都会对全表进行…...
专业建设网站外包/微信朋友圈的广告怎么投放
自动驾驶“梦之队”Aurora Innovation 再传捷报。 据雷锋网新智驾获得的消息,Aurora 敲定了价值 5.3 亿美元的 B 轮投资,领投的是硅谷传奇风投公司红杉资本,其他投资者中还有亚马逊等巨头的身影。拿到这笔投资后,Aurora 估值已经…...
网站推广软件哪个最实惠/怎么投放广告
分类 爬虫分为定向与不定向基本操作 简单来说就是通过指定的url取出数据发送http请求:基于正则表达式匹配获取内容用BeautifulSoup可以先用requests获取网页内容 之后用BeautifulSoup解析 BeautifulSoup(text,http.parser) 之后可以用find寻找对应项,get…...
网站怎么做能提升ip流量/网络推广平台有哪些
废话篇那晚,我和FeignClient注解的深度交流了一次,爽!主要还是在技术群里看到有同学在问相关问题,比如: contextId是干嘛的?name相同的多个Client会报错?然后觉得有必要写篇文章聊聊FeignClient的使用&…...
手机端网站制作教程/提升网站权重的方法
原标题: 广西科技大学鹿山学院--土木工程VR实训中心一、项目概述广西科技大学鹿山学院土木工程 VR实训基地中心(以下简称“中心”)主要是对该校土木工程系的土木工程专业进行设计与规划的,中心旨在借助先进的虚拟现实技术,结合土木工程、建筑…...