如果这篇博客帮助到你,可以请我喝一杯咖啡~
CC BY 4.0 (除特别声明或转载文章外)
一 初始化列表
C++ 11 支持 内置类型和自定义类型 的列表初始化
// 两种形式
vector<int> v1 = { 1, 2, 3 };
vector<int> v2{ 1, 2, 3 };
int i1 = { 1 };
int i2{ 1 };
pair<int, int> p = { 1, 1 };
map<int, int> m = { {1, 1}, {2, 2}, };
自定义类型
class A
{
public:
A(int a, int b)
:_a(a)
,_b(b)
{}
A(int a)
:_a(a)
, _b(0)
{}
private:
int _a;
int _b;
};
template<class T>
class Vector
{
public:
Vector(size_t n)
:_array(new T[n])
,_size(0)
,_capacity(n)
{}
Vector(const initializer_list<T>& ilst)
:_array(new T[ilst.size()])
,_size(0)
,_capacity(ilst.size())
{
for (const auto& e : ilst)
{
_array[_size++] = e;
}
}
private:
T* _array;
size_t _size;
size_t _capacity;
};
// 下面这两种意思是等价的
A a1{ 1, 2 };
A a2(1, 2);
// 单参构造函数的隐式类型转换
// 5 先构造成匿名对象,然后 a3 调用拷贝构造函数
A a3 = 5;
// 需要注意的是,自己实现的序列式容器(比如你自己实现一个 vector)是无法天然支持列表初始化的
// 需要提供参数为 initializer_list 的构造函数
Vector<int> V{ 1, 2, 3, 4, 5 };
二 类型推导
auto
auto 编译时根据初始化表达式进行类型推导
map<string, string> m;
auto it = m.begin();
decltype
decltype 运行时类型推导
decltype(2 - 1) d1;
// 根据函数返回值推导类型
decltype(func()) d2;
cout << "d2 type: " << typeid(d2).name() << endl; // int
三 显式缺省函数 & 删除默认函数
default
:让编译器显示生成一个默认函数
delete
:把一个函数声明成删除函数,不能再被使用
class B
{
public:
B() = default;
B(const B& b) = delete; //防拷贝
B& operator=(const B& b) = delete;
};
四 右值
1. 左值和右值
左值:可以放在 = 左侧,可以取地址 右值:不能放在 = 左侧,不能取地址
常量,临时变量,匿名变量,将亡值 都是右值
2. 左值引用和右值引用
左值引用:既可以引用左值,也可以引用右值
右值引用:只能引用右值
/*
* 左值引用:既可以引用左值,也可以引用右值
*/
int a = 3;
int& ra = a;
// 左值引用可以引用右值,需要加上 const
const int& rc = 10;
/*
* 左值引用:只能引用右值
*/
int&& r = 10;
// 匿名对象的引用
A&& a1 = A();
const A& a2 = A();
3. 编译器的优化
对于下面这个类:
class A
{
public:
// A() = default;
A()
{
cout << "A()" << endl;
}
// A(const A& a) = default;
A(const A& a)
{
cout << "A(const A& a)" << endl;
}
private:
};
如果我们试图用匿名对象来创建一个对象时:
void test4()
{
A&& a1 = A();
const A& a2 = A();
/*
* 输出:
* A()
* A()
*
* 说明编译器进行了优化,只调用了 a1,a2 的构造函数
* 如果没有进行优化,编译器应该调用的是:匿名对象的构造函数 + a1,a2 的拷贝构造
*/
}
4. 移动构造 & 移动赋值
class String
{
public:
String(const char* str = "")
:_str(new char[strlen(str) + 1])
,_size(strlen(str))
{
strcpy(_str, str);
cout << "String(const char* str = "")" << endl;
}
String(const String& str)
: _str(new char[strlen(str._str) + 1])
, _size(str._size)
{
strcpy(_str, str._str);
cout << "String(const String& str)" << endl;
}
~String()
{
if (_str)
{
delete[] _str;
}
}
private:
char* _str;
int _size;
};
String getStr(const char* s)
{
String str(s);
return str;
}
void test5()
{
String str = getStr("Shepard");
/*
* 输出:
* String(const char* str = )
* String(const String& str)
*
* 说明:
* 走了 getStr() 中 str 的构造函数 和 test5() 中 str 的拷贝构造
* getStr() 中返回的 str 是一个将亡值,是一个右值。
* 编译器进行了优化,没有构造返回的临时变量
*
* 问题:
* 尽管编译器进行了优化,这仍然存在一个问题:
* getStr 中返回的 str 并没有真正使用,它的存在只是为了构造 test5 中的 str 对象
* 那么先为其开资源,然后再删除似乎是没有什么意义的
*/
}
移动构造
class String2
{
public:
String2(const char* str = "")
:_str(new char[strlen(str) + 1])
, _size(strlen(str))
{
strcpy(_str, str);
cout << "String2(const char* str = "")" << endl;
}
String2(const String2& str)
: _str(new char[strlen(str._str) + 1])
, _size(str._size)
{
strcpy(_str, str._str);
cout << "String2(const String& str)" << endl;
}
// 移动构造
String2(String2&& str)
: _str(str._str) // 将 str 的资源给 _str
, _size(str._size)
{
str._str = nullptr; // str 的资源将由 _str 清理
cout << "String2(String2&& str)" << endl;
}
~String2()
{
if (_str)
{
delete[] _str;
}
}
private:
char* _str;
int _size;
};
String2 getStr2(const char* s)
{
String2 str(s);
return str;
}
void test6()
{
String2 str = getStr2("Shepard");
/*
* 输出:
* String2(const char* str = )
* String2(String2&& str)
*
* 说明:
* 在构造 test6 中的 str 时,调用的是 String2 的参数为右值引用拷贝构造函数
*
* 这个拷贝构造函数的功能:
* 它直接将右值(将亡值)的资源给了 test6 中的 str,从而避免了test6 中的 str 再次
* 申请资源,进而提高了效率
*/
// 调用深拷贝构造,因为 str 并不是“没有用”的值
String2 str2(str);
/*
* String2("Wang") 是一个临时的匿名对象,它的生命周期只有 1 行
* 所以如果用其拷贝构造 str3 ,将调用 移动构造
* 但是这里编译器会做优化,所以调用的是 str3 的拷贝构造
*/
String2 str3 = String2("Wang");
}
移动赋值
String2& operator=(const String2& str)
{
if (&str != this)
{
delete[] _str;
_str = new char[strlen(str._str) + 1];
_size = str._size;
strcpy(_str, str._str);
}
cout << "String2& operator=(const String2& str)" << endl;
return *this;
}
// 移动赋值
String2& operator=(String2&& str)
{
if (&str != this)
{
swap(str._str, _str);
_size = str._size;
}
cout << "String2& operator=(String2&& str)" << endl;
return *this;
}
cout << "============ 赋值运算符重载函数1 ============" << endl;
// 创建了一个匿名对象来给 str3 赋值。一共创建 3 个对象
str3 = getStr2("CET-6");
cout << "============ 赋值运算符重载函数2 ============" << endl;
// 直接用将亡对象给 str3 赋值
str3 = String2("CET-4");
/*
* 输出:
*
* ============ 赋值运算符重载函数1 ============
* String2(const char* str = )
* String2(String2&& str)
* String2& operator=(String2&& str)
* ============ 赋值运算符重载函数2 ============
* String2(const char* str = )
* String2& operator=(String2&& str)
*/
5. 右值引用引用左值
函数:
std::move
void test1()
{
int a = 10;
// std::move 把变量的属性变为右值
int&& ra = std::move(a);
}
.
class String2
{
public:
String2(const char* str = "")
:_str(new char[strlen(str) + 1])
, _size(strlen(str))
{
strcpy(_str, str);
cout << "String2(const char* str = "")" << endl;
}
String2(const String2& str)
: _str(new char[strlen(str._str) + 1])
, _size(str._size)
{
strcpy(_str, str._str);
cout << "String2(const String& str)" << endl;
}
// 移动构造
// 右值引用只能匹配右值
String2(String2&& str)
: _str(str._str) // 将 str 的资源给 _str
, _size(str._size)
{
str._str = nullptr; // str 的资源将由 _str 清理
cout << "String2(String2&& str)" << endl;
}
~String2()
{
if (_str)
{
delete[] _str;
}
}
private:
char* _str;
int _size;
};
class Person
{
public:
Person(const char* s)
:_str(s)
{
cout << "Person(const char* s)" << endl;
}
Person(const Person& p)
:_str(p._str)
{
cout << "Person(const Person& p)" << endl;
}
Person(Person&& p)
// 将 p._str 变成右值,然后进行 _str 的移动构造
:_str(move(p._str))
{
cout << "Person(Person&& p)" << endl;
}
private:
String2 _str;
};
Person getPerson()
{
Person p("Shepard");
return p;
}
void test1()
{
int a = 10;
// std::move 把变量的属性变为右值
int&& ra = std::move(a);
//cout << "============== 使用 move 前 ==============" << endl;
// Person p(getPerson());
/*
* 输出:
* String2(const char* str = )
* Person(const char* s)
* String2(const String& str)
* Person(Person&& p)
*
* 注意到:
* Person 调用的是移动拷贝构造但是 String2 调用的却不是移动拷贝构造
* 对于 String2 来说,并不能识别 Person._str 为将亡值
* 这时候就需要使用使用 move 函数将其转为右值
*/
cout << "============== 使用 move 后 ==============" << endl;
Person p(getPerson());
/*
* ============== 使用 move 后 ==============
* String2(const char* str = )
* Person(const char* s)
* String2(String2&& str)
* Person(Person&& p)
**/
}
移动语义不能随意使用 需要保证属性被修改的左值后面不会再被使用。
6. 完美转发
完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数
void Fun(int& x) { cout << "lvalue ref" << endl; }
void Fun(int&& x) { cout << "rvalue ref" << endl; }
void Fun(const int& x) { cout << "const lvalue ref" << endl; }
void Fun(const int&& x) { cout << "const rvalue ref" << endl; }
// PerfectForward 函数模板
void testPerfectForward() {
PerfectForward(10); // rvalue ref
int a = 1;
PerfectForward(a); // lvalue ref
PerfectForward(std::move(a)); // rvalue ref
const int b = 8;
PerfectForward(b); // const lvalue ref
PerfectForward(std::move(b)); // const rvalue ref return 0;
}
情况1
如果,函数模板的形式如下,直接将值作为参数:
template<typename T>
void PerfectForward(T t) { Fun(t); }
输出为:
lvalue ref
lvalue ref
lvalue ref
lvalue ref
lvalue ref
PerfectForward()
函数按值传递,t 为左值
情况2
template<typename T>
void PerfectForward(const T& t) { Fun(t); }
函数参数按照 const
引用传递,输出:
const lvalue ref
const lvalue ref
const lvalue ref
const lvalue ref
const lvalue ref
完美转发
// 模板参数:T&& -> 表示未定类型
template<typename T>
void PerfectForward(T&& t) { Fun(std::forward<T>(t)); }
.
rvalue ref
lvalue ref
rvalue ref
const lvalue ref
const rvalue ref
7. 右值引用的作用
最主要的作用: 实现移动语义(移动构造和移动赋值)
可以给中间变量起别名
int main() { string s1("hello"); string s2(" world"); string s3 = s1 + s2; // s3是用s1和s2拼接完成之后的结果拷贝构造的新对象 string&& s4 = s1 + s2; // s4就是s1和s2拼接完成之后结果的别名 return 0; }
完美转发应用的并不是右值引用,而是模板中的 T&&
表示未定义类型
五 lambda 表达式
1. 引入
内置类型的比较:
#include<algorithm>
void testSort()
{
int array[] = { 9, 5, 2, 7 };
// 默认从小到大排序
sort(array, array + sizeof(array) / sizeof(array[0]));
for (auto& e : array)
{
cout << e << " ";
}
cout << endl;
// 从大到小排序需要提供一个仿函数对象
sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
for (auto& e : array)
{
cout << e << " ";
}
cout << endl;
}
自定义类型的比较 :
方法 1:重载 >, <
运算符:
class A
{
private:
int _a;
public:
A(int a)
:_a(a)
{}
// 这里需要提供 const 成员函数,因为是 const 对象调用此函数
bool operator<(const A& ref) const
{
return _a < ref._a;
}
bool operator>(const A& ref) const
{
return _a > ref._a;
}
};
void testSort2()
{
A array[] = { {9}, {5}, {2}, {7} };
// 自定义类型的比较需要提供仿函数对象
// 方法 1:重载 > < 运算符
sort(array, array + sizeof(array) / sizeof(array[0]));
// 从大到小排序
sort(array, array + sizeof(array) / sizeof(array[0]), greater<A>());
}
方法 2:提供仿函数类(也需要重载 >, < 运算符)
class A
{
private:
int _a;
public:
A(int a)
:_a(a)
{}
// 这里需要提供 const 成员函数,因为是 const 对象调用此函数
bool operator<(const A& ref) const
{
return _a < ref._a;
}
bool operator>(const A& ref) const
{
return _a > ref._a;
}
};
struct LessA
{
bool operator()(const A& a1, const A& a2)
{
return a1 < a2;
}
};
struct GreaterA
{
bool operator()(const A& a1, const A& a2)
{
return a1 > a2;
}
};
void testSort2()
{
A array[] = { {9}, {5}, {2}, {7} };
// 方法 2:提供一个仿函数类(同样也需要重载 > < 运算符)
sort(array, array + sizeof(array) / sizeof(array[0]), LessA());
sort(array, array + sizeof(array) / sizeof(array[0]), GreaterA());
}
上面的几种写法都过于麻烦,我们需要使用 lambda
表达式来简化。
2. lambda 表达式语法
[capture-list] (parameters) mutable -> return-type { statement }
[] {} // 最简单的 lambda 表达式
// mutable 将捕捉列表中的变量属性改为非 const ,默认情况下是 const
// 使用 mutable 将参数列表给出
// 返回值可以省略,由编译器自动推导
[a, b] () mutable
{
a = 100; // 可修改
return a + b;
};
// func 类型为函数指针
auto func = [](int a, int b) ->int {a = 1; b = 2; return a + b; };
// 可以直接使用
func(a, b);
捕捉列表说明 :
[var]
:表示值传递方式捕捉变量var[=]
:表示值传递方式捕获所有父作用域中的变量(包括this)[&var]
:表示引用传递捕捉变量var[&]
:表示引用传递捕捉所有父作用域中的变量(包括this)[this]
:表示值传递方式捕捉当前的this指针
例 1:
void testLambda1()
{
int a = 1, b = 2;
auto func = [=](int num)mutable->int {
a = 10;
b = 20;
// c = 30; // 不能捕捉到 c
return a + b + num;
};
int c = 3;
func(10);
// a, b 的值没有发生变化
}
例 2
void testLambda2()
{
int a = 1, b = 2;
// 不需要加 mutable 也可以修改捕捉列表中的变量
auto func = [&](int num)->int {
a = 10;
b = 20;
return a + b + num;
};
int c = 3;
func(50);
cout << a << b << endl;
// a, b 的值被改变
}
例 3
void testLambda3()
{
int a = 1, b = 2;
// [=, &a] 其他变量以值的形式捕捉,a 以引用的形式捕捉
// [=, a] 这种写法是错误的,会造成重复
auto func = [=, &a](int num)mutable->int {
a = 10;
// b = 20; // b 不能被改变
return a + b + num;
};
int c = 3;
func(30);
cout << a << b << endl;
}
例 4
int global = 10;
auto _func = [=] {cout << global << endl; };
void testLambda4()
{
int a = 1, b = 2;
auto func = [=, &a](int num)mutable->int {
a = 10;
return a + b + global + num;
};
int c = 3;
cout << func(30) << a << b << endl;
_func();
}
但是,如果我们显式的在捕捉列表中指明 global 全局变量,则会报错:
int global = 10;
// Error 不能显示的指明带有静态存储期限的变量
auto _func = [global] {cout << global << endl; };
void testLambda4()
{
int a = 1, b = 2;
// Error 不能显示的指明带有静态存储期限的变量
auto func = [b, &a, global](int num)mutable->int {
a = 10;
return a + b + global + num;
};
int c = 3;
cout << func(30) << a << b << endl;
_func();
}
又出现了一个问题:
// 即使不显示指明 global ,依然自动捕捉
auto func = [b, &a](int num)mutable->int {
a = 10;
return a + b + global + num;
};
lambda
表达式之间不能相互赋值,但是可以拷贝
auto func = [b, &a](int num)mutable->int {
a = 10;
return a + b + num;
};
auto func2 = [b, &a](int num)mutable->int {
a = 10;
return a + b + num;
};
// lambda 表达式之间不能相互赋值
func = func2;
// 可以拷贝
auto func3 = func;
auto func4(func);
3. lambda 表达式实现原理
C++ 实现 lambda
表达式:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()
。
4. 简化引入中的例子
现在,我们使用 lambda
表达式完成对引入中代码的简化:
void UseLambda()
{
A array[] = { {9}, {5}, {2}, {7} };
// 从小到大
sort(array, array + sizeof(array) / sizeof(array[0]),
[](const A& a1, const A& a2)->bool
{
return a1 < a2;
});
// 从大到小
sort(array, array + sizeof(array) / sizeof(array[0]),
[](const A& a1, const A& a2)->bool
{
return a1 > a2;
});
}
六 线程库
RAII
// RAII:
// 在构造函数中初始化
// 在析构函数中释放资源
class ThreadManage
{
private:
thread& _thread;
public:
ThreadManage(thread& t)
:_thread(t)
{}
~ThreadManage()
{
// 如果线程还存在,则等待其结束
if (_thread.joinable())
_thread.join();
}
};
void func1()
{
cout << "func1" << endl;
}
void func2(int a)
{
cout << "func2" << a << endl;
}
void func3()
{
cout << "func3" << endl;
}
void testThread()
{
thread t1(func1);
thread t2(func2, 5);
thread t3(func3);
// 三个局部变量,函数结束时,自动调用其析构函数
// 析构函数中调用 join 函数回收资源
ThreadManage tm1(t1);
ThreadManage tm2(t2);
ThreadManage tm3(t3);
}
当线程函数为类成员函数时
class ThreadClass
{
public:
void func(int a)
{
cout << a << endl;
}
};
void testThread2()
{
ThreadClass tc;
// 如果函数为成员函数,则要写完整的作用域 + this 指向的对象 + 类函数参数
thread t1(&ThreadClass::func, &tc, 10);
t1.join();
}
当线程函数的参数为引用类型时
void refadd(int& a)
{
a += 10;
}
void testThread3()
{
int a = 0;
// 引用传参时,因为线程有自己独立的栈,如果直接传入 a ,修改的时线程栈中拷贝的 a 。而不是 testThread3 中的 a
// 如果要修改, 则应该将参数的格式改为ref(a)
thread t(refadd, ref(a));
t.join();
cout << a << endl;
}
线程函数的参数为指针类型时
可以直接作为参数
void pointeradd(int* a)
{
*a += 10;
}
void testThread3()
{
int a = 0;
// 指针类型可以直接作为参数
thread t2(pointeradd, &a);
t2.join();
cout << a << endl;
}
线程函数的提供:
- 函数指针
lambda
表达式- 仿函数对象
jionable()
函数判断线程是否是有效的,如果是以下任意情况,则线程无效:
采用无参构造函数构造的线程对象
线程对象的状态已经转移给其他线程对象
线程已经调用
jion
或者detach
结束