深刻理解C++标准
前言
C++语言的标准演化与C语言不同,C语言的标准再怎么变化,始终和旧版本没有太大差别。而C++标准的迭代,感觉好像换了一种语言似的。
C++历史发展
C++语言发展大概可以分为三个阶段:
传统类型上的面向对象:封装、继承、多态三大特性突出。
泛型程序设计:因标准模板库(STL)和Boost库出现,泛型程序设计占了比重增大。
产生式编程和模板元编程:Boost以及其他库大量使用产生式编程和模板元编程,使其变得更加复杂。
经历到现在,C++已经是现代最复杂的编程语言。
C++标准
C++98 和 C++03
因为C++03与C++98差距并不大,C++03只是修复了C++98出现的问题,所以放在一块来写。
有个漏洞比较特殊,C++03为了解决这个漏洞,特意要求:std::vector
中的元素必须连续存储。
C++早期的时候,编译的时候会把C++转成C语言然后再转换成二进制,现在这种做法已经被淘汰了。
- 名称空间
名称空间可以有效的避免类名重复,比如以下代码:
1 | namespace myspace{ |
- 强制类型转换
C++引入了四个强制类型转换运算符:
static_cast<> 将值从一种数值类型转换为另一种数值类型
- 分配内存
C++中使用new 和delete 来分配内存,替代了C语言中的malloc和free。
1 | //new 和malloc不同,malloc必须指定要分配多少内存,new 则可以根据数据类型自动分配内存 |
C、C++这种手动分配内存,手动释放非常方便,能更好的利用内存去读取,写入大文件。
- vector 模板类
vector 是一种动态数组,它内部是使用new和delete 管理动态数组的。
1 |
|
- 内联函数
普通函数执行的时候,会跳转到相对应的函数地址进行执行。
内联函数则是编译器将函数里面的内容拷贝出来,放在调用的地址,那么就不会有函数进栈出栈的开销了。
当然内联函数占用内存比普通函数要多,速度比普通函数稍微快一点。
1 | inline double square(double x ){return x*x } |
- 引用变量
引用变量是一种复合类型,是已定义的变量的别名(可以理解成小名)。
在C++11中,把这种引用变量叫做左值引用。
1 | int rats = 10; |
- 左值和右值
在C语言中,左值最初指的是可出现在赋值语句左边的实体,但这是引入关键字const之前的情况。
现在常规变量和const变量都可以视为左值,因为可以通过地址访问它们。
- 函数模板
1 | template<typename T> |
- 非模板函数、模板函数和显式具体化模板函数
非模板函数:即普通的函数
模板函数:即8所描述的函数模板
显式具体化模板函数:原型和定义应以template<>大头,并通过名称来指出类型
具体化优先于模板函数,而非模板函数优先于具体化和常规模板
1 | //非函数模板和模板函数不用说了,主要看看显式具体化模板函数 |
- 类模板
类模板一般放在头文件里面,不能将模板成员函数放在独立的实现文件中。
由于模板不是函数,它们不能单独编译,必须与特定的模板实例化请求一起使用。
使用的时候,引入头文件,定义变量使用即可。
1 |
|
C++ 11
- 初始化方式
C++11可以大括号初始化任何类型,可以使用=号,也可以不用。
字符串类型会被初始化为空字符串,数字类型会被初始化为0。复杂类型需要指定。
1 | int rocs = {}; //设置变量为0 |
- auto声明
auto可以让编译器能够根据初始值的类型推断变量的类型,以前的auto是C语言的关键字,很少使用。
auto主要目的是为了减少些一大串的类型名,比如vector<vector<vector
1 | std::vector<double> source; |
- array 模板类
vector类的功能比数组强大,但效率较低。
如果需要长度固定的数字,使用数组是最佳的选择,但是不那么方便和安全。
array长度是固定的,使用栈空间,效率与数组相同但是比数组更加方便和安全。
1 |
|
- 右值引用
新增了右值引用,区别于之前的左值引用。这种引用可以指向右值,使用&&声明的
1 | double && rref = std::sqrt(36.00); |
- decltype 关键字
decltype关键字的出现可以解决C++98中模板出现的问题
在C++98的时候
1 | template<class T1,class T2> |
C++11新增decltype关键字,可以根据表达式自动推导类型
1 | int x; |
当然decltype也不是万能的,编译器看到decltype这个关键字,会根据现有条件来判断变量类型,如果条件不足,那么会失败的。
比如:
1 | template <class T1,class T2> |
为了解决这个问题,没办法,只能新增一个语法,使用后置返回类型。
1 | double h(int x, float y); |
- 引入关键字 nullptr
在C++98中,有些使用(void*)0来标识空指针,有些使用NULL标识空指针(C语言的宏)。
在C++11中,引入了关键字nullptr,用于表示空指针。建议使用nullptr。
1 | char* str = nullptr; |
- 区间迭代
引入了基于范围的迭代写法,原先的写法:
1 | std::vector<int> arr(5, 100); |
现在的写法
1 | // & 启用了引用 |
- 外部模板
C++11引入了外部模板,扩充了原来的强制编译器在特定位置实例化模板的语法,使其能够告诉编译器何时才可以进行模板的实例化。(缩短了编译时间)
1 | template class std::vector<bool>; // 强行实例化 |
- 类型别名模板
以前不能给模板另起名字,现在在C++里面可以使用using给模板起别名
1 | template <typename T> |
- 默认模板参数
现在C++11支持默认模板参数
1 | template<typename T = int,typename U = int> |
- 委托构造
C++11新增委托构造,使构造函数能调用同一个类的其他的构造函数,从而达到简化代码的目的:
1 | class Base { |
- 继承构造
以前基类想要调用父类的构造函数,那么必须写对应的构造函数才可以调通。如果构造函数太多,导致很麻烦。
1 | class A |
现在只需要使用一条语句就可以搞定
1 | class B:A |
- Lambda表达式
Lambda表达式基本语法:
1 | [caputrue](param) opt -> ret{body;}; |
caputrue 是捕获列表
params是参数表(选填)
opt是函数选项,可以填mutable,exception,attribute(选填)。
ret 是返回值类型(选填)
body是函数体
捕获列表控制了lambda表达式能够访问的外部变量以及如何访问变量。
[] 代表不捕获任何变量
[&]捕获外部作用域中所有变量,按引用捕获
[=]捕获外部作用域中所有变量,按值捕获
[=,&foot]按值捕获外部作用域所有变量,按引用捕获foot变量
[bar]按值捕获bar变量,同时不捕获其他变量
[this]捕获当前类中的this指针,如果已经使用& 或 = ,就会默认添加
函数选项
mutable 在按值捕获外部变量时,表明这些捕获的变量可以修改。(必须写参数的小括号)
exception 说明lambda表达式是否抛出异常以及何种异常
attribute 用于声明属性
- 单向列表容器 std::forward_list
std::forward_list是一个单向列表容器,使用方法和std::list基本类似,但是不提供size方法。
- *无序容器 *
std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。
- 元祖 std::tuple
元祖有三个核心的函数:
std::make_tuple 构造元祖
std::get 获取元祖某个位置的值
std::tie 元祖拆包
1 | auto model = std::make_tuple(10, "A", "张三", 20); |
- 正则表达式
C++提供正则表达式库操作std::string对象,使用std::regex进行初始化,通过std::regex_match进行匹配,从而产生std::smatch对象。
1 |
|
1 | std::regex base_regex("([a-z]+)\\.txt"); |
- 左值转右值 std::move
上面简单介绍了左值和右值,C++可以使用std::move将左值转成右值,转换之后,左值不能再使用。一般用在构造函数,代替原先的拷贝构造。
1 | string(string&& that) |
- 智能指针
C++提供了三种智能指针类型:
- shared_ptr:基于引用计数的智能指针,会统计当前有多少对象在使用该指针,计数为0时自动释放
- weak_ptr:因为循环引用问题,不能单独使用shared_ptr解决,只能引入weak_ptr配合使用,weak_ptr只引用不计数。
- unique_ptr:遵循独占语义的智能指针,在任何时间点,只能唯一被一个unique_ptr所战友,当其离开作用域自动析构。资源的转移只能通过std::move()而不能通过赋值。
std::function和std::bind
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39class Test
{
public:
void Add(std::function<int(int, int)> fun, int a, int b)
{
int sum = fun(a, b);
std::cout << "sum:" << sum << std::endl;
}
};
//Test类中std::function<int(int,int)>表示std::function封装的可执行对象返回值和两个参数均为int类型
int add(int a,int b)
{
std::cout << "add" << std::endl;
return a + b;
}
class TestAdd
{
public:
int Add(int a,int b)
{
std::cout << "TestAdd::Add" << std::endl;
return a + b;
}
};
int main()
{
Test test;
//将函数地址传进去,包含参数
test.Add(add, 1, 2);
TestAdd testAdd;
//将对象的函数地址传进去
//std::placeholders::_1和std::placeholders::_2 为参数占位符
//表示std::bine封装的可执行对象可以接受两个参数
test.Add(std::bind(&TestAdd::Add, testAdd, std::placeholders::_1, std::placeholders::_2), 1, 2);
return 0;
}