文章目录
- 类的8个默认成员函数
- 构造函数
- 析构函数
- 拷贝构造
- 移动构造
- 赋值运算符重载
- 移动赋值运算符重载
- 取地址及const取地址操作符重载
- 强制生成默认函数的关键字default
- 禁止生成默认函数的关键字delete
类的8个默认成员函数
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下8个默认成员
函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
class Date {}
;
C++类的默认成员函数包括构造函数、析构函数、拷贝构造函数、移动构造函数、赋值运算符和移动赋值运算符;构造函数用于初始化,析构函数用于清理;拷贝构造函数和移动构造函数均可用于同类对象的初始化;赋值运算符和移动赋值运算符则用于将一个对象赋值给另一个对象;取地址运算符主要用于普通对象和const对象取地址,且很少需要自己实现。
构造函数
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任
务并不是开空间创建对象,而是初始化对象。
构造函数特征如下:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
- 整个生命周期内只调用一次。
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();
}
如果类中没有显式定义构造函数,则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;
}
关于编译器生成的默认成员函数,很多同学会有疑惑:不实现构造函数的情况下,编译器会
生成默认的构造函数。但是看起来默认构造函数又没什么用?对象调用了编译器生成的默
认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的
默认构造函数并没有什么用??
解答:
C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类
型,如:int/char
…,自定义类型就是我们使用class/struct/union
等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员
函数。
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
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。
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;
}
解答:
- 以上程序不能通过编译.因为构造函数冲突,无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。以上程序包含了多个构造函数.
初始化列表
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
在上面的构造函数代码中,我们都是在函数体内初始化的,其实构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值
所有我们就产生了初始化列表
- 初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式。
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
C++11的缺省值,这个缺省值是给初始化列表的
- 如果初始化列表没有显示给值,就用这个缺省值
- 如果给值,就不用这个缺省值
初始化列表是每个成员定义的地方,不管你写不写每个成员都要走初始化列表
- 如果不写初始化列表
自定义类型调用自己的构造
内置类型给成随机值- 如果显示写初始化列表
调用给定的参数
注意:
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
4
- 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,
一定会先使用初始化列表初始化。
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B(int a, int ref)
:_aobj(a)
, _ref(ref)
, _n(10)
{}
private:
A _aobj; // 没有默认构造函数
int& _ref; // 引用
const int _n; // const
};
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后
次序无关.如果不按该规则可能会出现不必要的麻烦.
class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
}
A.输出1 1
B.程序崩溃
C.编译不通过
D.输出1 随机值
正确答案是D.编译不通过。
在构造函数中,初始化列表的顺序应该与变量声明的顺序相同,否则会出现不必要的麻烦。在上面的代码中,变量 _a1 和 _a2 的声明顺序是 _a2 和 _a1,但是初始化列表中的顺序是 _a1 和 _a2,这会导致a2初始化成a1没有赋值前的随机值。因此,选项D是正确的。
析构函数
我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?
析构函数:
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由
编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
析构函数是特殊的成员函数,其特征如下:
- 析构函数名是在类名前加上字符
~
。- 无参数无返回值类型。
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载.- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{
_array = new DataType[capacity];
_capacity = capacity;
_size = 0;
}
//------------------------------------------------------------
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
- 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器
生成的默认析构函数,对自定类型成员调用它的析构函数。
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类生成的默认析构函数
注意:
创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数.
- 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如
Date类;有资源申请时,一定要写,否则会造成资源泄漏。
拷贝构造
在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?
拷贝构造函数
只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特征:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,
因为会引发无穷递归调用。
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) // 错误写法:编译报错,会引发无穷递归
{
_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;
}
- 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
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;
}
注意
:
在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
- 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?
当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
typedef int DataType;
class Stack
{
public:
/*注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请
时,则拷贝构造函数是一定要写的,否则就是浅拷贝。*/
Stack(size_t capacity = 10)
{
_array = new DataType[capacity];
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
delete _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(s1);
return 0;
}
注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
- 拷贝构造函数典型调用场景:
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
- 函数返回值类型为类类型对象
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;
}
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用
尽量使用引用。
移动构造
如果临时对象即将消亡,并且它里面的资源需要被再利用的时候,就可以触发移动构造。
什么时候该触发移动构造?临时对象即将消亡,且它里面的资源需要被再利用(有可被利用的临时对象)。
它用于将资源从一个对象转移到另一个对象,而不需要进行复制操作。这在处理大型对象或具有大量资源的对象时特别有用,因为它可以避免不必要的内存分配和复制操作,从而提高程序的性能。
Date(Date&& d)
- 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任
意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类
型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,
如果实现了就调用移动构造,没有实现就调用拷贝构造。- 拷贝对象需要深拷贝时,自己写移动构造.
- 移动构造函数的语法如下:
class MyClass {
public:
MyClass(MyClass&& other) {
// 移动资源
}
};
其中,MyClass&& other是一个右值引用,表示要被移动的对象。在移动构造函数中,你可以将其他对象的资源转移到新对象中,而不必复制这些资源。
下面是一个简单的示例,展示了如何使用移动构造函数
#include<string>
#include<iostream>
using namespace std;
namespace XmLife
{
class string
{
public:
string(const char* str = "")
:_size(strlen(str))
, _capacity(100)
{
cout << "string() --构造" << endl;
_str = new char[_capacity];
strcpy(_str, str);
}
void swap(string& s)
{
::swap(_str, s._str);
::swap(_size, s._size);
::swap(_capacity, s._capacity);
}
// 拷贝构造
string(const string& s)
:_str(nullptr)
{
cout << "string(const string& s) -- 拷贝构造(深拷贝)" << endl;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
// 移动构造
string(string&& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(string&& s) -- 资源转移" << endl;
swap(s); //转移资源
}
~string()
{
delete[] _str;
_str = nullptr;
}
void push_back(char ch)
{
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
XmLife::string get_string()
{
XmLife::string str;
str.push_back(1);
str.push_back(2);
str.push_back(3);
return str;
}
// 拷贝构造和移动构造
int main()
{
XmLife::string ret = get_string(); //移动构造
XmLife::string s1("1111111"); //构造
XmLife::string s2(s1); //拷贝构造
return 0;
}
get_string()函数返回值是 string,如果大型对象或具有大量资源的对象时,如果使用string传值返回会调用拷贝构造,浪费了内存分配和复制操作,但又因为str是局部对象,出了作用域就销毁了,又不能传引用返回。这时我们的移动构造的特点就来了.他相当于把你之前的资源盗取过来,在把空的资源给你销毁掉。
- 因为str是即将消亡,且它里面的资源需要被再利用,在你
return str
时候会调用移动构造。- 在移动构造里面他会把你str里面的值和自己的内容交换,交换完成后str是空值了,并且返回str时候会销毁.
- 注意: 如果没有移动构造会调用拷贝构造来完成,从而性能会下降。
移动构造函数的作用是避免不必要的内存分配和复制操作,从而提高程序的性能。它可以将一个对象的资源直接转移给另一个对象,而不是通过复制操作创建新的对象。移动构造函数通常使用右值引用作为参数,接收一个将要被移动的对象,并且在移动过程中将原对象的资源设置为空或无效,同时将资源交给新对象。这种机制可以避免不必要的内存分配和复制操作,从而提高程序的性能。特别是在处理大型对象或具有大量资源的对象时,移动构造函数的作用更加显著。
赋值运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似.
函数名字为:
关键字operator后面接需要重载的运算符符号。
函数原型:
返回值类型 operator操作符(参数列表)
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
藏的this- .* :: sizeof ?: . 注意以上5个运算符不能重载。
- 赋值运算符重载
赋值运算符重载格式
- 参数类型: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;
}
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;
};
- 赋值运算符只能重载成类的成员函数不能重载成全局函数
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 =”必须是非静态成员
原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现
一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值
运算符重载只能是类的成员函数。我们可以重载赋值运算符。不论形参的类型是什么,赋值运算符都必须定义为成员函数。
- 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注
意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
重载完成赋值。当然跟上面拷贝构造一样,如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
移动赋值运算符重载
移动赋值运算符的作用是将一个对象的资源转移到另一个对象中,并使原对象处于有效状态。
移动赋值运算符的重载语法如下:
class MyClass {
public:
MyClass& operator=(MyClass&& other) {
// 移动资源
return *this;
}
};
其中,MyClass&& other是一个右值引用,表示要被移动的右操作数对象。在移动赋值运算符中,我们需要将其他对象的资源转移到当前对象中,并使原对象处于有效状态。
- 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中
的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内
置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋
值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造
完全类似)- 移动赋值对象需要深拷贝时,自己写移动赋值重载函数.
移动赋值操作通过移动语义实现对象的转移,避免了不必要的数据拷贝,提高了性能。移动赋值会在以下情况下被触发:
- 当右值(临时对象或具有右值引用的对象)与左值(具有左值引用的对象)进行赋值操作时。
- 当使用std::move函数将左值转换为右值引用后进行赋值操作时。
- 当返回右值引用的函数的结果被赋值给目标对象时。
- 当涉及到内置类型时,移动赋值并不适用,因为内置类型的对象直接存储其值,不涉及资源的转移和释放。
- 内置类型
int main()
{
//内置类型
int a = 10;
int b = std::move(a); // b 现在拥有 a 的值
int* p = new int(10);
int* q = std::move(p); // q 现在拥有 p 所指向的内存
*p = 30;
cout << *p << " ||| " << *q << endl; //30 ||| 30
std::vector<int> vec1;
vec1.push_back(1);
std::vector<int> vec2 = std::move(vec1); // vec2 现在拥有 vec1 的所有权,vec1 是空的
}
- 自定义类型
#include <iostream>
#include <string>
class MyClass {
private:
std::string data;
public:
// 构造函数
MyClass(const std::string& value) : data(value) {
std::cout << "构造函数,值为 " << data << std::endl;
}
// 移动构造函数
MyClass(MyClass&& other) noexcept : data(std::move(other.data)) {
std::cout << "移动构造函数,值为 " << data << std::endl;
}
// 移动赋值运算符
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
data = std::move(other.data);
std::cout << "移动赋值运算符,新值为 " << data << std::endl;
}
return *this;
}
// 析构函数
~MyClass() {
std::cout << "析构函数,值为 " << data << std::endl;
}
// 输出值
void print() const {
std::cout << "值为 " << data << std::endl;
}
};
int main() {
MyClass obj1("Hello");
MyClass obj2("World");
obj1 = std::move(obj2);
obj1.print();
obj2.print();
return 0;
}
输出结果::
构造函数,值为 Hello
构造函数,值为 World
移动赋值运算符,新值为 World
值为 World
值为
析构函数,值为
析构函数,值为 World
使用 std::move 将 obj2 移动赋值给 obj1,从而触发了移动赋值操作。在移动赋值运算符中,我们通过使用 std::move 将 other 的数据成员的值转移到了 data 中,实现了资源的转移。最后,我们分别输出了 obj1 和 obj2 的值,在 obj1 中打印出了新值 “World”,而 obj2 的值为空。
取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
要重载,比如想让别人获取到指定的内容!
强制生成默认函数的关键字default
C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原
因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以
使用default关键字显示指定移动构造生成。
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
Person(const Person& p)
:_name(p._name)
, _age(p._age)
{}
Person(Person&& p) = default;
private:
string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
return 0;
}
禁止生成默认函数的关键字delete
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁
已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即
可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
class Person
{
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
Person(const Person& p) = delete;
private:
string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
return 0;
}