C++11
1. 简介
C++11是C++语言的一个重要版本,于2011年发布。它引入了许多新的特性和改进,使得C++语言更加现代化和强大。所以属性C++11的新特性是很有必要去熟悉的。
2. 列表初始化
2.1 { }初始化
相信大家在初始化数组和结构体的时候,都使用过{}进行初始化
struct Date
{
int y;
int m;
int d;
};
int main()
{
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
Date d = { 2024, 3,31 };
return 0;
}
到了C++11,{}初始化的范围变大了,使其可用于所有的内置类型和用户自定义的类型。
class Person
{
public:
Person(string name,string gender,int age)
:_name(name)
,_gender(gender)
,_age(age)
{}
private:
string _name;
string _gender;
int _age;
};
int main()
{
//不推荐
int a = { 1 };
vector<Person> v = { {"张三","男",20},{"李四","女",20}};
map<string, int> m = { {"苹果",1},{"香蕉",2} };
return 0;
}
2.2 initializer_list
其实支持这种操作的原因就是initializer_list的引入,可以把initializer_list看出一个模版类,{}就可以看作是initializer_list类,所以C++11的STL容器的构造都加了以initializer_list为参数的构造函数。
3. 类型推导
3.1 decltype
decltype 是 C++ 中的一个关键字,用于在编译时获取表达式的类型,而不会实际评估该表达式。它通常用于模板元编程中,在表达式的类型复杂或未知的情况下非常有用。
int main() {
int x = 5;
decltype(x) y = 10; // y 的类型与 x 相同,即 int
std::cout << "y 的类型: " << typeid(y).name() << std::endl;
double z = 3.14;
decltype(z) w = 2.71; // w 的类型与 z 相同,即 double
std::cout << "w 的类型: " << typeid(w).name() << std::endl;
return 0;
}
4. 一些新增的容器
5. final和override
- final修饰的类不能被继承。
- final修饰的虚函数不能被重写。
- override会检测派生类虚函数是否重写了基类的虚函数,如果没有重写就报错。
6. 右值引用
6.1 什么是左值、左值引用?
左值是一个表示数据的表达式,出现在“=”的左边,可以对非const的左值赋值,对左值还可以取地址,左值引用就是左值的别名。
int main()
{
//左值
int a = 10;
int* p = &a;
const int b = 20;
//左值引用
int& a1 = a;
int*& p1 = p;
const int& b1 = b;
return 0;
}
6.2 什么是右值、右值引用?
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引
用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能
取地址。右值引用就是对右值的引用,给右值取别名。
int add(int a, int b)
{
return a + b;
}
int main()
{
int a = 1;
int b = 2;
//右值
10;
a + b;
add(a , b);
//右值引用
int&& r1 = 10;
int&& r2 = a + b;
int&& r3 = add(a, b);
//右值不能取地址
&10;
&(a + b);
&add(a, b);
return 0;
}
6.3 左值引用和右值引用
左值引用总结:
- 左值引用一般不能引用右值
- const 左值引用可以引用右值
右值引用总结:
- 右值引用一般不能引用左值
- 右值引用可以引用move之后的左值
6.4 右值引用的场景和意义
我们先来回忆一下左值引用的作用,作为函数的参数时,可以减少拷贝,对于一些自定义的对象来说可以有效的提高程序效率,作为函数的返回值,如果返回值是自己开辟的空间,也能减少一次拷贝。
但是左值引用有个短板,就是如果返回值是个局部变量,出了作用域系统就自动释放,那么我们只能传值返回了。所以右值引用的出现可以弥补这个问题。我们通过代码来看看是如何解决的。
没使用右值引用的情况:
namespace lbs
{
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);
}
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)
//{
// 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& 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 = nullptr;
size_t _size = 0;
size_t _capacity = 0; // 不包含最后做标识的\0
};
}
lbs::string to_string(int value)
{
bool flag = true;
if (value < 0)
{
flag = false;
value = 0 - value;
}
lbs::string str;
while (value > 0)
{
int x = value % 10;
value /= 10;
str += ('0' + x);
}
if (flag == false)
{
str += '-';
}
std::reverse(str.begin(), str.end());
return str;
}
int main()
{
lbs::string s = to_string(1234);
return 0;
}
这就是我们上面说的左值引用的缺陷,现在我们加上移动构造来看看右值引用是怎么解决这个问题的。
// 移动构造 -- 右值(将亡值)
string(string&& s)
{
cout << "string(string&& s) -- 移动拷贝" << endl;
swap(s);
}
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动拷贝" << endl;
swap(s);
return *this;
}
可以看到移动构造只是把将亡值str的资源使用swap函数交换给了s,所以效率是很高的。完美解决了左值引用的缺陷。
6.5 完美转发
6.5.1 模版中的万能引用
我们先来看一段代码
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t) {
Fun(t);
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
return 0;
}
大家可以先预想一下结果。
是不是很奇怪,为什么全是左值引用?我们先来解释一下万能引用模版
- 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
- 你传左值,参数就是左值,你传右值就是右值
- 因为传参的时候发生了引用折叠 (& &&-> &) (&& && -> &&)
既然这样,那为什么右值变成了左值,这是因为,当右值引用了右值以后就变成了一个左值。
为什么要有这个特性?因为根据右值的特性,右值是不能改变的,但我们的移动构造需要交换资源,这不就矛盾了,所以有了这个特性。那么该如何解决上面代码的问题呢?这时候完美转发就登场了。
6.5.2 完美转发的使用
完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }
template<typename T>
void PerfectForward(T&& t) {
//Fun(t);
//使用完美转发
Fun(std::forward<T>(t));
}
int main()
{
PerfectForward(10); // 右值
int a;
PerfectForward(a); // 左值
PerfectForward(std::move(a)); // 右值
const int b = 8;
PerfectForward(b); // const 左值
PerfectForward(std::move(b)); // const 右值
return 0;
}