这里写目录标题
- 新的类功能
- 默认成员函数
- 类成员变量初始化
- 强制生成默认函数的关键字default
- 禁止生成默认函数的关键字delete
- final与override关键字
- override
新的类功能
默认成员函数
原来C++类中,有6个默认成员函数:
- 构造函数
- 析构函数
- 拷贝构造函数
- 拷贝赋值重载
- 取地址重载
- const 取地址重载
C++11 新增了两个:移动构造函数和移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
- 如果你没有自己实现移动构造函数,且没有实现析构函数、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
- 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造 完全类似)
- 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
默认生成的移动构造和移动赋值会做什么?
- 默认生成的移动构造:对于内置类型的成员,编译器会完成值拷贝(浅拷贝),对于自定义类型的成员,如果它实现了移动构造函数,就会调用他的移动构造函数,如果没有实现,就会调用它的拷贝构造函数。
- 默认生成的移动赋值:对于内置类型的成员,编译器会完成值拷贝(浅拷贝),对于自定义类型的成员,如果它实现了移动赋值,就会调用他的移动赋值,如果没有实现,就会调用它的拷贝赋值。
下面我们用一个string类和一个Person类来更加深层的理解一下移动构造和移动赋值:
namespace gtt
{
class string
{
public:
typedef char* iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
string(const char* str = "")
:_size(strlen(str))
, _capacity(_size)
{
//cout << "string(char* str)" << endl;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// s1.swap(s2)
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;
string tmp(s._str);
swap(tmp);
}
// 赋值重载
string& operator=(const string& s)
{
cout << "string& operator=(string s) -- 深拷贝" << endl;
string tmp(s);
swap(tmp);
return *this;
}
// 移动构造
string(string&& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
cout << "string(string&& s) -- 移动语义" << endl;
swap(s);
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动语义" << endl;
swap(s);
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void push_back(char ch)
{
if (_size >= _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
const char* c_str() const
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity; // 不包含最后做标识的\0
};
}
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:
gtt::string _name;
int _age;
};
虽然Person类并没有实现移动构造和移动赋值,但是拷贝构造,拷贝赋值,析构函数都实现,此时在调用的过程中就不会在默认生成移动构造和移动赋值,我们看下面这段代码:
int main()
{
Person s1("张三", 18);
//想要调用Person的移动构造函数
Person s2 = move(s1);
return 0;
}
上述代码中,Person类并没有实现移动构造和移动赋值,此时就会调用我们自己实现的拷贝构造函数,会对我们的s1对象的name成员进行深拷贝,也就会调用string类的拷贝构造函数对name成员进行深拷贝。
但是如果我们将拷贝构造,拷贝赋值,析构函数都注释掉,此时Person类就会默认生成移动构造和移动赋值,就会调用他默认生成的移动构造和移动赋值,对于age这种内置类型就完成值拷贝,对于name这种自定义类型因为我们已经实现了他的移动构造和移动赋值,就会去调用他的移动构造和移动赋值。
类成员变量初始化
默认生成的构造函数,对于自定义类型的成员会调用其构造函数进行初始化,但并不会对内置类型的成员进行处理。于是C++11支持非静态成员变量在声明时进行初始化赋值,默认生成的构造函数会使用这些缺省值对成员进行初始化。
class Person
{
public:
//....
private:
gtt::string _name = "张三";
int _age = 21;
};
强制生成默认函数的关键字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)
{}
private:
gtt::string _name = "张三";
int _age = 21;
};
int main()
{
Person s1("张三", 18);
Person s2 = move(s1);
return 0;
}
最终结果还是会调用我们移动构造函数。
再比如我们自己已经写看拷贝构造函数,此时就不会在生成默认构造函数,我们也可以强制生成:
class Person
{
public:
//强制生成默认构造函数
Person() = default;
//拷贝构造函数
Person(const Person& p)
:_name(p._name)
, _age(p._age)
{}
private:
gtt::string _name = "张三";
int _age = 21;
};
int main()
{
Person s;
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(Person&& p) = delete;
private:
gtt::string _name = "张三";
int _age = 21;
};
final与override关键字
final修饰类
被final修饰的类叫做最终类,最终类无法被继承:
class A final
{
public:
//....
private:
int a = 1;
};
class B : public A //无法继承A,因为被final修饰
{
public:
//....
private:
int b = 2;
};
final修饰虚函数
final修饰虚函数,表示该虚函数不能再被重写,如果子类继承后重写了该虚函数则编译报错:
class A
{
public:
virtual void func() final
{
cout << "hello A" << endl;
}
private:
int a = 1;
};
class B : public A
{
public:
virtual void func()
{
cout << "hello B" << endl;
}
private:
int b = 2;
};
override
override修饰虚函数
override修饰子类的虚函数,检查子类是否重写了父类的某个虚函数,如果没有没有重写则编译报错。
class A
{
public:
void func()
{
cout << "hello A" << endl;
}
private:
int a = 1;
};
class B : public A
{
public:
virtual void func() override
{
cout << "hello B" << endl;
}
private:
int b = 2;
};