作者:@小萌新
专栏:@C++进阶
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:介绍C++11类的新功能和一些关键字
类的新功能
- 类的新功能
- 默认成员函数
- 类成员变量的初始化
- C++11新关键字
- default
- delete
- final
- override
类的新功能
默认成员函数
在C++98中 类的默认成员函数一般可以认为有六个
这六个默认成员函数分别是
- 构造函数
- 析构函数
- 拷贝构造函数
- 拷贝赋值函数
- 取地址运算符重载
- const取地址运算符重载
之所以称之为默认成员函数意思就是即使我们不写这六个成员函数 他们也会自己生成
在实际的写代码过程中前面四个默认成员函数是比较重要的 我们需要自己实现一遍 而后面两个让系统自己默认生成就好
在C++11中为了提高效率 提出了右值引用的概念 因此默认成员函数也增加了两个 变成了八个
增加了两个默认成员函数分别是
- 移动构造函数
- 移动赋值重载函数
但是他们的默认生成条件有些不同 他们两个的默认生成条件分别是
移动构造函数: 没有自己实现移动构造函数 并且没有自己实现析构函数 拷贝构造函数和拷贝赋值函数
移动赋值重载函数: 没有自己实现移动赋值重载函数 并且没有自己实现析构函数 拷贝构造函数和拷贝赋值函数
这里还有一点需要特别注意的是: 如果我们自己生成了移动构造和移动赋值重载函数 那么就算我们没有生成拷贝构造和拷贝赋值 系统也不会默认生成
也就是说这两对函数之间是有相互作用的 不能单独拆开来看
默认的移动构造和移动赋值会做什么呢
- 默认移动构造 对于内置类型 它会完成浅拷贝 对于自定义类型 如果它实现了移动构造就会调用移动构造 否则就调用拷贝构造
- 默认移动赋值 对于内置类型 它会完成浅拷贝 对于自定义类型 如果它实现了移动赋值就会调用移动赋值 否则就调用拷贝赋值
验证默认移动构造和移动赋值
首先我们先创造出一个简单的string类 直接复用上一篇博客的代码就好
namespace shy
{
class string
{
public:
typedef char* iterator;
// begin迭代器返回第一个元素
iterator begin()
{
return _str;
}
// end迭代器返回最后一个元素后一个元素的位置
iterator end()
{
return _str + _size;
}
// 构造函数
string(const char* str = "")
{
_size = strlen(str); // 初始化设置字符串大小
_capacity = _size;
_str = new char[_capacity + 1]; // 这里要多开一个空间来存储/0
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)
, _size(0)
, _capacity(0)
{
cout << "string(const string& s)" << endl;
string tmp(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); //拷贝构造出一个临时变量
swap(tmp);// 交换这个两个对象
return *this; // 返回左值
}
// 移动赋值
string& operator= (string && s)
{
cout << "string& operatpr=(string&& s)" << endl;
swap(s);
return *this;
}
// 析构函数
~string()
{
delete[] _str; // 释放str的空间
_str = nullptr;
_size = 0;
_capacity = 0;
}
//[]运算符重载
char& operator[](size_t i)
{
assert(i < _size);
return _str[i]; // 返回左值引用
}
// 改变容量 大小不变
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strncpy(tmp, _str, _size + 1); // 这里不使用strcpy的原因是字符串中哟i可能出现/0
delete[] _str;
_str = tmp;
_capacity = n; // 容量改变
}
}
// 尾插字符
void push_back(char ch)
{
// 首先判断容量是否足够
if (_size >= _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
// 尾插到最后 最后加上\0
_str[_size] = ch;
_str[_size + 1] = '\0';
_size++;
}
//+=运算符重载
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
// 返回c类型的字符串
const char* c_str() const //这里加const是修饰this指针 让它的权限变成只读 为了防止后面的只读对象调用这个函数
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
之后我们再写出一个简单的person类 将我们写的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:
shy::string _name; //姓名
int _age; //年龄
};
我们的person类中没有实现移动构造和移动赋值 但是实现了拷贝构造 析构和赋拷贝赋值
所以说移动构造和移动赋值并不会默认生成
那么我们下面就会有一段代码来证明上面的话
Person p1("zhangsan",18);
Person p2 = ::move(p1);
我们这里使用了右值去构造p2 假设p2的移动构造存在的话 那么因为string是我们的自定义类型
所以说它就回去调用string的移动构造
那么我们来看看实际上允许的结果是什么
实际上它调用的是string类的拷贝构造
从这个试验就可以证明并没有默认生成移动构造函数
那么如果要生成移动构造和移动赋值我们就必须将原person代码中的 析构函数 拷贝构造 拷贝赋值全部注释掉
那么注释掉之后我们再来看看效果是什么样子的
接下来我们用另外一段代码试验一下移动赋值
Person p1("zhangsan",18);
Person p2;
p2 = ::move(p1);
我们可以发现 移动构造和移动赋值的生成条件符合我们之前的学习
这里还有一点需要注意的 有些比较古老的编译器可能不支持C++11 所以在这些编译器上无法进行我们上面的试验
类成员变量的初始化
C++98之前的类成员默认构造初始化规则特别奇怪 是这样子的
- 对于自定义类型来说会调用他们的构造函数进行初始化
- 对于内置类型不进行初始化
而在C++11中 对于内置类型打了个补丁
- 对于自定义类型来说会调用他们的构造函数进行初始化
- 对于内置类型我们可以使用缺省值来进行初始化
以我们的person类来说
shy::string _name; //姓名
int _age = 18; //年龄
如果我们使用默认的构造函数 那么他们的年龄就会被默认初始化为18
C++11新关键字
default
default关键字的功能是强制生成默认成员函数
在某些情况下 我们可能需要用到某个默认成员函数 但是它自己并不会默认生成 这个时候就到了我们的default关键字派上用场的时候了
比如说像下面的情况
class Person
{
public:
// 拷贝构造函数
Person(const Person& p)
:_name(p._name)
, _age(p._age)
{}
private:
shy::string _name; //姓名
int _age = 18; //年龄
};
在上面的代码中 我们只生成了person的拷贝构造函数
但是我们直到拷贝构造函数就是一种特殊的构造函数
所以说当我们生成了它的拷贝构造函数后 person类并不会生成默认构造函数了
但是这样子就会出现下面的情况
因为没有合适的默认构造函数而出错
所以这个时候我们就需要编译器生成默认构造函数 于是乎我们应该在类中加上这段代码
Person() = default;
这样子代码就可以运行了
delete
delete的作用是可以禁止生成默认成员函数
在C++98中我们如果构造一个不能被拷贝的类我们只能这样子做
- 将拷贝构造函数私有
- 将拷贝构造函数只声明不定义
这二者缺一不可
如果我们只将构造函数私有那么通过另外的成员函数完全可以在类里面拷贝再返回出被拷贝的类
如果我们只声明不定义那么完全可以再类外面定义再使用
而在C++11中提供了delete关键字
我们如果不想要一个类被拷贝只需要delete修饰将该类的拷贝构造和拷贝赋值
像这样
class CopyBan
{
public:
CopyBan()
{}
private:
CopyBan(const CopyBan&) = delete;
CopyBan& operator=(const CopyBan&) = delete;
};
值得一说的是 我们虽然这里将这两个函数设置为私有了 但是其实不管他们是公开还是私有结果都是一样的
final
final修饰虚函数 表示这个函数不能被重写了
//父类
class Person
{
public:
virtual void Print() final //被final修饰,该虚函数不能再被重写
{
cout << "hello Person" << endl;
}
};
//子类
class Student : public Person
{
public:
virtual void Print() //重写,编译报错
{
cout << "hello Student" << endl;
}
};
override
override修饰虚函数 检查该虚函数是否被重写
如果被override修饰的虚函数没有重写父类的虚函数则报错
//父类
class Person
{
public:
virtual void Print()
{
cout << "hello Person" << endl;
}
};
//子类
class Student : public Person
{
public:
virtual void Print() override //检查子类是否重写了父类的某个虚函数
{
cout << "hello Student" << endl;
}
};
如果不构成重写则报错
构成重写则通过