C++学习:6补充
Posted 想文艺一点的程序员
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++学习:6补充相关的知识,希望对你有一定的参考价值。
目录
C++11新特性
如何查看我们现在使用的是什么 C++ 标准?
1、auto
- 可以从初始化表达式中推断出变量的类型,大大简化编程工作
- 属于编译器特性,不影响最终的机器码质量,不影响运行效率
2、decltype
- 可以获取变量的类型
3、nullptr(null pointer:空指针)
- 可以解决NULL的二义性问题
在 C++ 11 之前,NULL 既能代表指针、也可以代表整数 0 这样写代码会报错、
所以在 C++ 11 之后,我们使用 nullptr 来特指指针。 NULL 为整数 0.
4、快速遍历:
之前的数组遍历:
int array[] = {0,1,2,3,4,5};
int i = 0;
for(i=0;i<sizeof(array); i++){
cout<< array[i] << endl;
}
C++ 11 之后的遍历:
- 我们不需要理会数组的大小,编译器会挨个将元素赋值给 item 元素。
int array[] = {0,1,2,3,4,5};
for(int item = array){
cout<< item << endl;
}
5、lambda表达式
lambda 表达式的结构很复杂,有很多种形式,它的本质就是一个函数。
发明它的目的:为了将函数的作用域,放到函数体里面。
(1)最简单的形式:[capture list] {function body} ,理解:
- 这个是一个特殊的表达式,里面包含 捕获列表 + 函数实体
- 它的本质就是一个函数。
接下来给 lambda 表达式进行赋值
- 函数实体既没有返回值,也没有函数实体。
- 使用函数指针指向它。
更为简便的赋值:
调用 lambda 函数实体
(2)添加函数的参数列表:[capture list] (params list) {function body}
- 中间添加了参数列表,这个函数的参数列表。
(3)添加函数的返回值:[capture list] (params list) -> return type {function body}
- -> return type ,return type 为这个函数的返回值类型。
- 但是 -> return type 可以进行省略。
6、lambda 捕获变量
描述:
解决:
也可以进行隐式捕获:(而且默认是 值捕获)
注意点:
(1)默认都是 值捕获:
(2)地址捕获 :将 a 的地址传递进来。
7、Lambda表达式 - mutable
问题描述:如果我们想在 lambda 表达式当中,修改传进行来的变量值。
(1)使用 地址捕获:
(2)使用 mutable
注意:
C++14
1、泛型 lambda
2、对捕获的变量进行初始化
C++17
1、可以进行初始化的if、 switch语句 、
- 变量 a 只为 if else 服务
- 变量 b 只为 switch 服务
其他的新的语法特性还没有流行起来。
异常
在编程当中我们经常遇到的错误类型:
- 语法错误:编译器就会提示我们
- 逻辑错误:是程序员自己留下的错误。
- 异常:在程序运行当中可能出现,也可能不出现。
比如说在程序在运行过程当中,内存空间不足。
这个情况比较夸张,但是在实际开发的时候,肯定有可能出现。
- 每个程序分别写自己的代码,申请的次数多了,肯定会造成内存不足。
- 异常没有被处理,会导致程序终止 。
1、捕捉
使用 try 和 catch 指令:
1、将可能发生异常的代码,放到 try 里面去。
2、一旦发生异常,就会自动到对应的 catch 当中执行代码。
- 从每次产生异常,都会执行 catch 当中的代码
改进:我们出现异常之后,break 跳出。
分析流程:
- 如果没有 break ,在执行完循环之后,也会跳出循环。
- 有 break 的话,发生异常之后立马会调出循环。
2、主动抛出
-
上面的异常是系统自身抛出的异常。
-
有的时候,可能需要我们自己去主动抛出异常。
举例:0 做被除数,我们要主动抛出异常
catch 和 throw 的捕捉类型必须匹配。
总结:
1、将可能发生异常的代码,放到 try 里面去。
2、一旦发生异常,就会自动到对应的 catch 当中执行代码。
异常抛出的声明:
3、自定义异常类型
- 通过类来定义异常
- 通过面向对象的思想,这有什么优势呢?
- 自己可以添加很多的与异常相关的成员函数。
4、标准异常
智能指针
传统指针存在的问题
- 需要手动管理内存
- 容易发生内存泄露(忘记释放、出现异常等)
- 释放之后产生野指针
内存泄漏:该释放内存的时候,没有释放,造成内存越来越少。
指针的释放必须放到最后,并且还得赋值为 nullptr ,防止野指针。
智能指针就不需要这么做:
- auto_ptr 是一个模板类,需要传入指向的对象类型
- p 是一个对象,相当于将 newPerson()的返回值传给 p 的构造函数。
- p 在栈空间,当 test( ) 执行完毕之后,p就会被释放,然后堆空间的内容也会跟着释放。
注意:
-
智能指针发明的目的:在栈空间的指针销毁时,将堆空间的内存进行释放。
-
智能指针必须指向堆空间的对象,因为栈空间的对象不需要我们智能指针进行释放,系统会自动释放。
-
如果指向栈空间的对象,那么就会造成二次释放。
智能指针就是为了解决传统指针存在的问题
- auto_ptr:属于C++98标准,在C++11中已经不推荐使用(有缺陷,比如不能用于数组)
- shared_ptr:属于C++11标准
- unique_ptr:属于C++11标准
1、自己实现智能指针
目的:在自己销毁的时候,释放堆空间。
初步实现:
缺点:
- p1 并不是指针,而是一个对象,真正的指针是 m_pointer 成员变量。
- 所以很不方便。
改进:将运算符进行重载
存在的问题:不能存放数组。
- 不能释放全部元素
改进:
2、shared_ptr
hared_ptr 的设计理念:
1、多个shared_ptr可以指向同一个对象,当最后一个shared_ptr在作用域范围内结束时,对象才会被自动释放。
可以通过一个已存在的智能指针初始化一个新的智能指针
- p1 、 p2、p3、p4 都销毁的时候, new 的Person 内存才能够进行释放。
2、针对数组的用法
3、一个shared_ptr会对一个对象产生强引用(strong reference)
- 每个对象都有个与之对应的强引用计数,记录着当前对象被多少个shared_ptr强引用着
- 可以通过shared_ptr的 use_count 函数获得强引用计数
- 当有一个新的shared_ptr指向对象时,对象的强引用计数就会+1
- 当有一个shared_ptr销毁时(比如作用域结束),对象的强引用计数就会-1
- 当一个对象的强引用计数为0时(没有任何shared_ptr指向对象时),对象就会自动销毁(析构)
多一个指向,强引用个数就会 + 1.少一个,就会减一
两次释放的情况:
4、shared_ptr的循环引用
会导致内存泄漏:
-
Person 类当中有一个Car 类型的智能指针, m_car (成员变量)
-
Car 类当中有一个 Person 类型的智能指针, m_person (成员变量)
-
在主函数,栈空间当中创建两个对象,Person 对象 ,Car 对象。
没有循环调用的情况:
有循环调用的情况:(你中有我,我中有你)
- 缺点:内存泄漏,堆空间无法释放
内存分布图:
-
Person 对象当中的 m_car 指向 Car
-
Car 对象当中的 m_person 指向 Person
-
Car 和 Person 的强引用计数都是 2
- 当栈空间释放之后,
- Car 和 Person 的强引用计数都是 1 ,因为还有强引用计数的存在,所以堆空间不会销毁
- shared_ptr 会产生强引用,只要有一个引用没有释放,那么就不会销毁堆空间。
解决办法:使用弱引用 weak_ptr
3、weak_ptr
- weak_ptr会对一个对象产生弱引用
- weak_ptr可以指向对象,解决shared_ptr的循环引用问题
内存空间:
4、unique_ptr
- unique_ptr也会对一个对象产生强引用,它可以确保同一时间只有1个指针指向对象
- 当unique_ptr 销毁时(作用域结束时),其指向的对象也就自动销毁了
- 可以使用std::move函数转移unique_ptr的所有权
了解即可
以上是关于C++学习:6补充的主要内容,如果未能解决你的问题,请参考以下文章