类的新功能
一个类有多少个默认成员函数? c++98:6个 C++11:8个
在C++98中,一个类中有如下六个默认成员函数:构造函数,析构函数,拷贝构造函数,拷贝赋值函数,取地址重载函数,const取地址重载函数,其中前四个成员函数最重要.后面两个成员函数一般不会用到
- 其中这里“默认”的意思就是你不写编译器会自动生成.
新增的默认成员函数
C++11标准中又增加了两个默认成员函数: 移动构造函数和移动赋值重载函数
默认移动构造和移动赋值函数的生成条件
- 移动构造函数的生成条件:如果我们没有自己实现移动构造函数, 并且没有自己实现析构函数、拷贝构造函数和拷贝赋值函数
- 移动赋值重载函数的生成条件:没有自己实现移动赋值重载函数.并且没有自己实现析构函数、拷贝构造函数和拷贝赋值函数
移动构造和移动赋值的生成条件与之前六个默认成员函数不同,并不是单纯的没有实现移动构造和移动赋值编译器就会默认生成
需要注意的是: 如果我们自己实现了移动构造或者移动赋值.就算没有实现拷贝构造和拷贝赋值.编译器也不会生成默认的拷贝构造和拷贝赋值
默认生成的移动构造和移动赋值所做的工作
默认生成的移动构造函数:对于内置类型的成员会完成值拷贝(浅拷贝),对于自定义类型的成员.如果该自定义成员实现了移动构造就调用它的移动构造,如果没有写,就调用它的拷贝构造函数
默认生成的移动赋值重载函数:对于内置类型的成员会完成值拷贝(浅拷贝).对于自定义类型的成员.如果该自定义成员实现了移动赋值就调用它的移动赋值,如果没有写,就调用它的拷贝赋值函数
验证
我们这里模拟实现一个简化版的string,类当中只编写了几个我们需要用到的成员函数.
namespace Mango
{
class string
{
public:
//构造函数
string(const char* str = "")
{
_size = strlen(str); //初始时.字符串大小设置为字符串长度
_capacity = _size; //初始时.字符串容量设置为字符串长度
_str = new char[_capacity + 1]; //为存储字符串开辟空间(多开一个用于存放'\0')
strcpy(_str, str); //将C字符串拷贝到已开好的空间
}
//交换两个对象的数据
void swap(string& s)
{
::swap(_str, s._str); //交换两个对象的C字符串
::swap(_size, s._size); //交换两个对象的大小
::swap(_capacity, s._capacity); //交换两个对象的容量
}
//拷贝构造函数(现代写法)
string(const string& s)
:_str(nullptr), _size(0), _capacity(0)
{
cout << "string(const string& s) -- 深拷贝" << endl;
string tmp(s._str); //调用构造函数.构造出一个C字符串为s._str的对象
swap(tmp); //交换这两个对象
}
//移动构造
string(string&& s)
:_str(nullptr), _size(0), _capacity(0)
{
cout << "string(string&& s) -- 移动构造" << endl;
swap(s);
}
//拷贝赋值函数(现代写法)
string& operator=(const string& s)
{
cout << "string& operator=(const string& s) -- 深拷贝" << endl;
string tmp(s); //用s拷贝构造出对象tmp
swap(tmp); //交换这两个对象
return *this; //返回左值(支持连续赋值)
}
//移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动赋值" << endl;
swap(s);
return *this;
}
//析构函数
~string()
{
delete[] _str; //释放_str指向的空间
_str = nullptr; //及时置空.防止非法访问
_size = 0; //大小置0
_capacity = 0; //容量置0
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
string类的拷贝构造、拷贝赋值、移动构造和移动赋值函数中都打印了一条提示语句,可以通过控制台输出判断是否调用了对应的函数
然后再编写一个简单的Person类.Person类中的成员name的类型就是我们模拟实现的string类
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& operator=(const Person& p)
{
if (this != &p)
{
_name = p._name;
_age = p._age;
}
return *this;
}
//析构函数
~Person()
{}
private:
Mango::string _name; //姓名
int _age; //年龄
};
虽然Person类当中没有实现移动构造和移动赋值,但拷贝构造,拷贝赋值和析构函数都实现了,因此Person类中不会生成默认的移动构造和移动赋值
验证移动构造: 下述代码中,用一个右值去构造s2对象,
int main()
{
Person s1("张三", 21);
Person s2 = std::move(s1); //想要调用Person默认生成的移动构造
//实际上调用的是:string(const string& s) -- 深拷贝
return 0;
}
由于Person类没有生成默认的移动构造函数,因此这里会调用Person的拷贝构造函数,由于拷贝构造函数的参数是const Person& p
既能接收左值也能接收右值,所以不会有问题, 这时在Person的拷贝构造函数中就会调用string的拷贝构造函数对name成员进行深拷贝, 所以这里即使move了,因为没有资源转移,所以s1不会变为空
如何让Person生成默认的移动构造函数:
将Person类中的拷贝构造、拷贝赋值和析构函数全部注释掉!这时用右值去构造s2对象时就会调用Person默认生成的移动构造函数,对于这个移动构造函数来说:
- 对于内置类型成员age会进行值拷贝,对于自定义类型成员name,因为我们的string类实现了移动构造函数,因此它会调用string的移动构造函数进行资源的转移
- 而如果我们将string类当中的移动构造函数注释掉,那么Person默认生成的移动构造函数,就会调用string类中的拷贝构造函数对name成员进行深拷贝
验证移动赋值:验证方式和上面验证移动构造的方式是一样的
int main()
{
Person s1("张三", 21);
Person s2;
s2 = std::move(s1); //想要调用Person默认生成的移动赋值
return 0;
}
类成员变量初始化
对于默认生成的构造函数:自定义类型的成员会调用其构造函数进行初始化,但并不会对内置类型的成员进行处理
C++11支持非静态成员变量在声明时进行初始化赋值,默认生成的构造函数会使用这些缺省值对成员进行初始化
例子:
class Person
{
public:
//...
private:
//非静态成员变量.可以在成员声明时给缺省值
Mango::string _name = "张三"; //姓名
int _age = 20; //年龄
static int _n; //静态成员变量不能给缺省值
};
注意:这里不是初始化.而是给声明的成员变量一个缺省值,真正的初始化是在初始化列表当中!
default关键字
C++11可以让我们更好的控制要使用的默认成员函数.假设在某些情况下我们需要使用某个默认成员函数.但是因为某些原因导致无法生成这个默认成员函数.这时可以使用default关键字强制生成某个默认成员函数
例子1:下面的Person类中实现了拷贝构造函数
class Person
{
public:
//拷贝构造函数
Person(const Person& p)
:_name(p._name), _age(p._age)
{}
private:
Mango::string _name; //姓名
int _age; //年龄
};
int main()
{
Person s; //报错:没有合适的默认构造函数可用
return 0;
}
这时上述代码就无法编译成功了,我们需要回归下构造的定义:
- 如果你写了构造,就不会生成默认构造,虽然我们只写了拷贝构造,但是拷贝构造是特殊的构造函数
因为Person类中编写了拷贝构造函数,导致无法生成默认的构造函数,因为默认构造函数生成的条件是没有编写任意类型的构造函数,包括拷贝构造函数-它是特殊的构造函数,
这时我们就可以使用default关键字强制生成默认的构造函数:
class Person
{
public:
Person() = default; //强制生成默认构造函数
//拷贝构造函数
Person(const Person& p)
:_name(p._name), _age(p._age)
{}
private:
Mango::string _name; //姓名
int _age; //年龄
};
说明一下: 默认成员函数都可以用default关键字强制生成.包括移动构造和移动赋值.
delete关键字
当我们想要限制某些默认函数生成时.可以通过如下两种方式
- 在C++98中.可以将该函数设置成私有,并且只用声明不用定义.这样当外部调用该函数时就会报错.
- 在C++11中.可以在该函数声明后面加上=delete.表示让编译器不生成该函数的默认版本.我们将=delete修饰的函数称为删除函数
例子:要让一个类不能被拷贝
C++98写法:
err版本
class CopyBan
{
public:
CopyBan()
{}
void f()
{
CopyBan a = *this;
}
private:
CopyBan(const CopyBan&)
{}
};
此时虽然可以防止别人在类外进行拷贝,但是在类里面还是可以进行拷贝
正确做法:C++98 防拷贝:1、只声明不实现 2、声明成私有
问:能否只有条件1?
不能!因为别人可以在类外实现了,也可以用!加上条件2之后,就算你在类外实现了,由于是私有的,类外面也用不了,但是对于类里面的,照样可以拷贝!
class CopyBan
{
public:
CopyBan()
{}
void f()
{
CopyBan a = *this;//不报错
}
private:
CopyBan(const CopyBan&);//C++98 防拷贝:1、只声明不实现 2、声明成私有
};
C++11写法:
class CopyBan
{
public:
CopyBan()
{}
void f()
{
CopyBan a = *this;//此时就会报错
}
private:
CopyBan(const CopyBan&) = delete;
};
说明一下: 被=delete修饰的函数可以设置为公有.也可以设置为私有.效果都一样.
final与override关键字
final与override关键字一般都是用在继承和多态当中:
关于final
final修饰类:被final修饰的类叫做最终类.最终类无法被继承
class NonInherit final //被final修饰.该类不能再被继承
{
//...
};
final修饰虚函数:表示该虚函数不能再被重写.如果子类继承后重写了该虚函数则编译报错
//父类
class Person
{
public:
virtual void Print() final //被final修饰.该虚函数不能再被重写
{
cout << "hello Person" << endl;
}
};
//子类
class Student : public Person
{
public:
virtual void Print() //如果子类重写了Print函数,编译报错
{
cout << "hello Student" << endl;
}
};
关于override关键字:文章来源:https://www.toymoban.com/news/detail-432050.html
override修饰虚函数: override修饰子类的虚函数,检查子类是否重写了父类的某个虚函数,如果没有没有重写则编译报错文章来源地址https://www.toymoban.com/news/detail-432050.html
//父类
class Person
{
public:
virtual void Print()
{
cout << "hello Person" << endl;
}
};
//子类
class Student : public Person
{
public:
virtual void Print() override //检查子类是否重写了父类的某个虚函数
{
cout << "hello Student" << endl;
}
};
到了这里,关于【C++】C++11-类的新功能&default&delete&final&override关键字的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!