1. 列表初始化
1.1 {} 初始化
c++11引入了一切皆可{}
初始化,例如:
class Test1
{
public:
Test1(int d1, int d2)
:data1_(d1), data2_(d2)
{
cout << "Test1(int d1, int d2)" << endl;
}
private:
int data1_;
int data2_;
};
int main()
{
int a = 1;
int b = { 1 };
int c{ 1 };
cout << "a " << a << endl;
cout << "b " << b << endl;
cout << "c " << c << endl;
//本质:调用构造函数
Test1 t1(2, 2);
Test1 t2 = { 2,2 }; //多参数隐式类型转换
Test1 t3{ 2,2 };
//类型转换生成临时对象,临时对象具有常性,所以要加const
const Test1 &rt = { 4, 4 };
int* ptr1 = new int[3] {1, 2, 3};
return 0;
}
//输出:
//a 1
//b 1
//c 1
//Test1(int d1, int d2)
//Test1(int d1, int d2)
//Test1(int d1, int d2)
1.2 initializer_list
class Test1
{
public:
Test1(int d1, int d2)
:data1_(d1), data2_(d2)
{
cout << "Test1(int d1, int d2)" << endl;
}
private:
int data1_;
int data2_;
};
int main()
{
vector<int> v = { 1,2,3,4,5 };
Test1 t1 = { 1,2 };
auto il = { 1,2,3,4,5,6 };
cout << typeid(t1).name() << endl;
cout << typeid(il).name() << endl;
cout << typeid(v).name() << endl;
return 0;
}
//输出:
//Test1(int d1, int d2)
//class Test1
//class std::initializer_list<int>
//class std::vector<int,class std::allocator<int> >
这里的三种{}
初始化,vector
初始化时{}
内参数可以控制多少,而我们Test1
这个类,就只能带2个参数,以为构造只有2个参数。
这里是因为vector
构造重载了initializer_list
而initializer_list
是C++11
新增的(可以理解成容器),它底层的实现逻辑:
//{1, 2, 3, 4, 5} 常量数组 存在常量区
template<class T>
class initializer_list
{
const T *start; //指向常量地址的起始
const T *finish; //结束位置下一个
}
所以将{1, 2, 3}
数组识别成了initializer_list
,本质上调用的是它的构造函数。
2. 声明
2.1 auto
这算是C++中的一个语法糖了,自动推导类型,初始化日常会经常用到
auto要求必须显示初始化
2.2 decltype
decltype
也是一个推导类型,它可以用来定义变量,不初始化
比较:
- auto:自动推导类型,必须初始化——只能用
- typeid(type).name():推导类型,是一个字符串,无法使用——只能看
decltype
:自动推导类型,可以不初始化——能看能用
void Print(size_t sz)
{
cout << "分配内存:" << sz << endl;
}
template<class Func>
class Test2
{
public:
Test2(Func f)
:f_(f)
{}
public:
void callFunc(size_t sz)
{
f_(sz);
}
private:
Func f_;
};
int main()
{
auto pf = malloc; //函数指针,指向了malloc
int* a = (int*)pf(sizeof(int));
Test2<decltype (pf)> t2(pf);
t2.callFunc(sizeof(int));
Test2<decltype(&Print)> tp(&Print);
tp.callFunc(sizeof(long long));
return 0;
}
2.3 nullptr
C++中将NULL
定义成了0,所以引入nullptr
来表示空指针
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
void Func1(int x)
{
cout << "Func1(int x)" << endl;
}
void Func1(int *p)
{
cout << "Func1(int *p)" << endl;
}
int main()
{
Func1(NULL);
Func1(nullptr);
return 0;
}
//输出:
//Func1(int x)
//Func1(int *p)
3. 范围for
这个也算是C++的语法糖,日常经常用,底层是迭代器
对原生数组的支持是编译器的特殊支持,处理成了指针
4. STL的变化
4.1 新容器
这些都是C++11新增的容器unordered_map
和unordered_set
底层是哈希表。
之前有写过模拟实现:基于哈希表对unordered_map和unordered_set的封装
array
算是比较鸡肋的容器,它是一个静态数组~,唯一好处对于越界的检查,比原生数组检查严格,底层调用了operator[]
forward_list
这也一点鸡肋~,它是一个单链表,只支持了头插头删,它的insert
是在当前位置之后插入,因为单链表的尾插尾删和前面插入,都需要找前一个位置,代价较大
4.2 新接口
-
新增了一个
const
版本的迭代器… -
所有容器支持了
{}
列表初始化的构造函数 -
所有容器新增
emplace
系列,即右值引用和模板可变参数 -> 大幅性能提升
5. 右值引用
单独写了一篇博客:C++11新特性【右值引用】
6. 可变参数模板
在学C语言的时候,例如printf
、scanf
都是可变参数
函数可变参数传的是对象,而C++11新增的可变参数模板,传的是类型
//一个一个取出参数
template<class T>
void PrintList(T val)
{
cout << val << " " << endl;
}
template<class T, class ...Args>
void PrintList(T val, Args...args)
{
//输出参数个数
//cout << sizeof...(args) << endl;
cout << val << " ";
PrintList(args...); //递归式调用推参数包的值
}
int main()
{
PrintList(1);
PrintList(1, 2.5);
PrintList(1, 2.5, "hello");
}
//一次性全部取出
class Date
{
public:
Date(int year = 2001, int month = 6, int day = 8)
:year_(year), month_(month), day_(day)
{}
~Date()
{}
private:
int year_;
int month_;
int day_;
};
template<class ...Args>
Date* Create(Args...args)
{
Date* ret = new Date(args...);
return ret;
}
int main()
{
Date* d = Create();
Date* d1 = Create(2024);
Date* d2 = Create(2024, 3);
Date* d3 = Create(2024, 3, 14);
Date* d4 = Create(d3);
return 0;
}
C++11新增的
emplace
系列就是可变参数模板
不用先去构造对象在拷贝构造,可以直接构造了
7. lambda表达式
单独写了一篇博客:C++11——lambda表达式
8. 包装器
8.1 function
C++的调用类型太多,例如函数指针、函数对象、lambda、仿函数表达式等:
template<class F,class T>
T func(F f, T t)
{
static int count = 0;
cout << "count: " << count << endl;
cout << "count: " << &count << endl;
return f(t);
}
int Mul(int data)
{
return data * 2;
}
struct Muls
{
int operator()(int data)
{
return data * 3;
}
};
int main()
{
cout << func(Mul, 10) << endl; //函数指针
cout << func(Muls(),10) << endl; //函数对象
cout << func([](int data) {return data * 4; }, 10) << endl; //lambda表达式
return 0;
}
//输出:
// count: 0
// count: 00007FF7DB82D170
// 20
// count: 0
// count: 00007FF7DB82D174
// 30
// count: 0
// count: 00007FF7DB82D178
// 40
这里的函数模板被实例化成了三份,这里如果想要将这个调用对象存到容器当中,例如vector
,这里就无法存进去,因为不晓得要实例化成那种,包装器就能解决这个问题:
#include<functional> //需包头文件
template<class F,class T>
T func(F f, T t)
{
static int count = 0;
cout << "count: " << count << endl;
cout << "count: " << &count << endl;
return f(t);
}
int Mul(int data)
{
return data * 2;
}
struct Muls
{
int operator()(int data)
{
return data * 3;
}
};
int main()
{
//function<返回类型(参数类型)>
function<int(int)> f1 = Mul;
function<int(int)> f2 = Muls();
function<int(int)> f3 = [](int data) {return data * 4; };
vector<function<int(int)>> vf = { f1,f2,f3 };
int n = 2;
for (auto e : vf)
{
cout << e(n++) << endl;
}
return 0;
}
这里用来包装器之后,只实例化了一份:
8.2 bind
bind
是一个函数模板,可以调整调用参数的顺序
int Sub(int x, int y)
{
return x - y;
}
double Rate(int x, int y, double rate)
{
return (x + y) * rate;
}
class A
{
public:
double Rate(int a, int b, double rate)
{
return (a + b) * rate;
}
static double sRate(int a, int b, double rate)
{
return (a + b) * rate;
}
};
int main()
{
//静态采用& 非静态有this指针
function<double(int, int)> rate1 = bind(&A::Rate, A(), placeholders::_1, placeholders::_2, 1.5);
function<double(int, int)> rate1 = bind(&A::sRate, placeholders::_1, placeholders::_2, 1.5);
//function<double(int, int)> rate1 = bind(Rate, placeholders::_1, placeholders::_2, 1.5);
//function<double(int, int)> rate2 = bind(Rate, placeholders::_1, placeholders::_2, 2.6);
//function<double(int, int)> rate3 = bind(Rate, placeholders::_1, placeholders::_2, 3.7);
//cout << rate1(9, 27) << endl;
//cout << rate2(9, 27) << endl;
//cout << rate3(9, 27) << endl;
//function<int(int, int)> lsub = bind(Sub, placeholders::_1, placeholders::_2);
//function<int(int, int)> rsub = bind(Sub, placeholders::_2, placeholders::_1);
//cout << rsub(10, 5) << endl;
//cout << lsub(10, 5) << endl;
return 0;
}
底层都是仿函数
9. 新的类默认成员函数
在C++11之前,类只有6个默认成员函数:
- 构造
- 拷贝构造
- 赋值重载
- 析构
- 取地址重载
- const取地址重载
最常用的是前四个,C++11新增了2个:
- 移动构造
- 移动赋值运算符重载
默认构造函数,我们不写编译器会自动生成
- 深拷贝的类型需要自己实现
- 浅拷贝的类不需要自己实现
如果没有自己实现移动构造,且没有实现析构、拷贝、拷贝赋值运算符重载中的任意一个。那么编译器会自动生成默认移动构造。默认生成的会对内置类型成员逐字节拷贝,自定义类型成员,需要看这个成员是否需要移动构造,如果实现了调用移动构造,没实现就调用拷贝构造。
这里如果需要自己手动写析构、拷贝、赋值运算符重载,肯定是一个深拷贝的类,那肯定是需要自己去自己写移动构造和移动赋值的;如果这些都不用写,那直接用编译器默认生成的肯定更香一点。
10. 线程库
关系线程这块,有写个原生线程的内容,掌握了原生线程,对于封装线程的使用也就很简单了:
官方文档:thread - C++ Reference (cplusplus.com)、mutex - C++ Reference (cplusplus.com)