1. 自动类型推导 (auto)
C++11 引入了 auto 关键字
,可以根据初始值的类型自动推导变量的类型,从而减少了手动声明类型的繁琐。例如:
std::vector<int> vec = {1, 2, 3, 4};
auto it = vec.begin(); // 自动推导类型为 std::vector<int>::iterator
auto 的引入使代码更加简洁和易读,特别是在遇到长类型名称时,极大地提高了开发效率
2. 范围循环 (Range-based for Loop)
C++11 引入了范围循环
,可以轻松遍历容器中的元素,而不需要手动编写迭代器代码。例如:
std::vector<int> vec = {1, 2, 3, 4};
for (auto value : vec) {
std::cout << value << " ";
}
范围循环使代码更加简洁,避免了循环中易出现的迭代器错误。
3.std::initializer_list
文档介绍
std::initializer_list
是 C++11 引入的一种模板类,用于支持初始化列表(initializer list)。它允许使用大括号 {}
!!!将一组值直接传递给构造函数或函数
,从而简化代码书写。
int main()
{
vector<int> v = { 1,2,3,4 };//大括号传给构造函数
list<int> lt = { 1,2 };
// 这里{"sort", "排序"}会先初始化构造一个pair对象
map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
// 使用大括号对容器赋值
v = {10, 20, 30};
return 0;
class MyClass {
public:
MyClass(std::initializer_list<int> list) {
for (auto& elem : list) {
// 处理每个元素
}
}
};
MyClass obj = {1, 2, 3, 4}; // 通过初始化列表构造对象
4.STL中一些变化
4.1 array
C++11 中引入的 std::array 是标准模板库(STL)中的一个容器类,提供了一个固定大小的数组,具有数组的简单性和 STL 容器的功能。它被定义在头文件 中。
std::array
的主要特点包括:
1.固定大小
:std::array 的大小在编译时确定,一旦定义后不能改变
。它是栈分配的数组,适合需要固定大小的数组场景。
std::array<int, 5> arr = {1, 2, 3, 4, 5}; // 定义一个包含5个整数的array
2.安全性
:与 C 风格的数组相比,std::array 提供了更好的边界安全性,可以使用 at()
方法访问元素,该方法会进行边界检查
int x = arr.at(2); // 如果越界,抛出异常
4.1 forward_list(单链表)
C++11 中的 std::forward_list
是一个单向
链表容器,定义在头文件 <forward_list> 中。与 std::list(双向链表)相比,std::forward_list
仅支持单向链表结构,更加轻量。
std::forward_list<int> flist = {1, 2, 3};
flist.push_front(0); // 在头部插入元素
5.右值引用和移动语义
5.1左值引用和右值引用
无论左值引用还是右值引用,都是给对象取别名。
什么是 左值
?什么是 左值引用
?
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可获取它的地址+可以对它赋值,
1.左值可以出现赋值符号的左边,
2.右值不能出现在赋值符号左边。
定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址
。左值引用就是给左值的引用,给左值取别名。
int main()
{
// 以下的p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
return 0;
}
什么是 右值
?什么是 右值引用
?
右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址
int main()
{
double x = 1.1, y = 2.2;
// 以下几个都是常见的右值
10;
x + y;
fmin(x, y);
// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;
return 0;
}
5.2左值引用与右值引用比较
左值引用
总结:
左值引用
只能引用左值,不能引用右值。- 但是
const左值引用
既可引用左值,也可引用右值。
右值引用
总结:
1.
右值引用
只能右值,不能引用左值。
2. 但是右值引用
可以move
以后的左值 。
int main()
{
// 右值引用只能右值,不能引用左值。
int&& r1 = 10;
// error C2440: “初始化”: 无法从“int”转换为“int &&”
// message : 无法将左值绑定到右值引用
int a = 10;
int&& r2 = a;
// 右值引用可以引用move以后的左值
int&& r3 = std::move(a);
return 0;
}
5.3右值引用使用场景和意义
左值引用的使用场景:
void func1(bit::string s)
{}
void func2(const bit::string& s)
{}
int main()
{
bit::string s1("hello world");
// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值
func1(s1);
func2(s1);
// string operator+=(char ch) 传值返回存在深拷贝
// string& operator+=(char ch) 传左值引用没有拷贝提高了效率
s1 += '!';
return 0;
}
做
参数
和做返回值
都可以提高效率。
左值引用的短板:
但是当函数返回对象是一个
局部变量
,出了函数作用域就不存在了
,就不能使用左值引用返回, 只能传值返回
。
但都需要进行拷贝构造,生成新空间,释放旧空间
右值引用和移动语义解决上述问题:
移动构造本质是将
参数右值的资源窃取过来
,占位已有,那么就不 用做深拷贝了,所以它叫做移动构造
,就是窃取别人的资源来构造自己。
// 移动构造
string(string&& s)
:_str(nullptr)
,_size(0)
,_capacity(0)
{
cout << "string(string&& s) -- 移动语义" << endl;
swap(s);
}
int main()
{
bit::string ret2 = bit::to_string(-1234);
return 0;
}
这里没有调用深拷贝的拷贝构造,而是调用了移动构造,移动构造中没有新开空间,拷贝数据,所以效率提高了。
不仅仅有移动构造,还有移动赋值:
// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动语义" << endl;
swap(s);
return *this;
}
int main()
{
bit::string ret1;
ret1 = bit::to_string(1234);
return 0;
}
// 运行结果:
// string(string&& s) -- 移动语义
// string& operator=(string&& s) -- 移动语义
右值被右值引用的结果是左值!!!!!!!!!!!
5.4模板中的&& 万能引用
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;
}
结果输出全是左值!!!
解决方法:
std::forward 完美转发
在传参的过程中保留对象原生类型属性
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; }
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& 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;
}
6.新的类功能
原来C++类中,有6个默认成员函数:
- 构造函数
- 析构函数
- 拷贝构造函数
- 拷贝赋值重载
- 取地址重载
- const 取地址重载
C++11 新增了两个:7.移动构造函数和8.移动赋值运算符重载。
针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:
- 如果你没有自己实现移动构造函数,且没有实现析构函数,拷贝构造,拷贝赋值重载中的
任意一个
。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造, 如果实现了就调用移动构造,没有实现就调用拷贝构造。- 如果你没有自己实现移动赋值重载函数,且没有实现析构函数拷贝构造、拷贝赋值重载中的
任意一个
,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)- 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
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:
bit::string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1;
Person s3 = std::move(s1);
Person s4;
s4 = std::move(s2);
return 0;
}
剩下的内容等到下一期再详细讲解