重温C++的设计思想
一、内存管理(RAII)
内存管理分为堆、栈和RAII(Resource Acquisition Is Initialization)。除了C,还有几个语言D、Ada和RAII少数派语言也采用RAII
- RAII依托栈和析构函数,对包括堆内存的资源进行管理,所以不需要GC垃圾回收。
- 对象很大、对象编译器不能确定大小、对象是函数返回值,不能或不应该存储在栈上。
- 操作1:内存分配;操作2:内存释放;操作3:内存垃圾收集。C++通常会做上面的操作1和2;JAVA会做上面的操作1和3,Python会做操作1、2、3
- 栈上分配和释放,只需要移动一下栈指针,由于后进先出的执行过程,所以不可能出现内存碎片、
二、智能指针
C++简单类型称为POD(Plain Old Data),有构造和析构函数称为非POD。
栈展开(stack unwinding):在发生异常时对析构函数的调用。也就是说不管是否发生了异常,析构函数都会得到执行。
std的智能指针(std::unique_ptr,std::shared_ptr),使用智能指针目的之一是减少对象的拷贝:对超出作用域的对象进行释放。
2.1 unique和shared_ptr
一个unique_ptr只能独自拥有一个对象,而多个shared_ptr可以同时共享指针。
多个不同的shared_ptr不仅共享同一个对象,也需要共享计数。
2.2 左值和右值
左值:有标识符、可以取地址的。反之为右值。
常见的左值有:变量、函数、成员;返回左值的表达式(++x,x=1,cout<<''),字符串常量
常见的右值有:返回右值得表达式(x++,x+1,make_shared<int>(42)),非字符串的字面量(42,true)
左值引用(T&)和右值引用(T&&)。左值和右值首先是个值,所有对于指针,因为用值传递,不关心它是左值还是右值。
std::move(ptr)是个右值引用。等价于static_cast<smart_ptr<T>&&>(ptr)。
2.3 内存对象的局部性
C++的对象缺省为值语义。
Class A{
B b_;
C c_;
}
很多语言包括Java和Python会在A对象放B和C的指针。而C++则会把B和C的对象放在A的内存空间。既有优点也有缺点。
优点是保证了内存访问的局限性。局限性在现代处理器架构上是绝对有优势,缺点是复制对象的开销大大增加,所以C++需要移动语义,而Java里根本没有。
对象支持移动需要下列几步:
- 对象有拷贝构造和移动构造(除非你只需要像unique_ptr只打算支持移动,不支持拷贝)
- 对象有swap成员函数
- 对象命名空间里,有一个全局的swap函数swap(T&lhs, T&rhs),这样方便自己和别人调用。
- 实现operator=函数。
三、容器
3.1 连续内存的vector容器
vector保证强异常安全性,如果元素类型没有提供一个保证不抛异常的移动构造函数,vector使用拷贝构造函数。如果自定义类型拷贝构造代价较高,则使用移动构造函数,并标其为noexcept,或者只在容器中放置对象的智能指针。
C++11提供的emplace系列函数,是为了提升容器性能设计。emplace_back比push_back 少额外生成临时对象,少一次拷贝构造和一次析构。
现代处理器架构对连续内存访问速度比不连续内存访问速度快很多,所以vector的连续内存是他的优点。而为了保证连续性,vector的一个缺点是大小增长时导致的元素移动。所以如果有可能,尽可能使用reserve函数保留所需内存。
3.2 部分元素连续的双端队列daque
deque是双端队列(double-ended queue),但是和vector比,多了push_front,emplace_front,和pop_front函数,但是不提供data,capacity和reserve函数。因为deque的内存结构有点像hash桶,元素只是部分连续的,所以无法提供data函数。内存中元素大部分仍然连续,每一段存储大小相等,所以还是可以用数组下标的方式访问。index[i/chunk_size][i%chunk_size]
3.3 list、单向链表
list是双向链表,对比vector,优化插入和删除。
单向链表C++11里面叫forward_list。在元素大小较小的时候,他可以有效的节约内存。
3.4 关联容器
c++的关联容器(set,map,multiset,multimap)是有序的,而在别的语言通常是无序的。名字带multi的允许键重复。不带的不允许键重复。set和multiset只存放键,而map和multimap存放键值对。与序列容器相比,关联容器没有前、后的概念。但是提供insert、emplace等函数。关联容器有find、lower_bound、upper_bound等查找函数,返回是一个迭代器。
3.5 容器共性
容器的共性:容器类都有begin()和end()函数,大部分容器拥有sizie(),push_back()。不必集成一个共同的容器积累,便可以拥有通用地遍历一个容器的方法。
四、返回值优化
c++的返回值优化,对于非值类型,当返回值可能是子对象的情况,使用unique_ptr或shared_ptr,对于移动代价很高的对象,考虑分配在堆上,然后返回一个句柄(unique_ptr),或者从外部传递一个非const的对象引用。
在函数调用复用一个自动容量对象,作为引用传递。
五、标准泛型算法
c++的标准泛型算法:
- sort:排序
- reverse:反转
- count:计数
- find:查找
- max:最大值
- min:最小值
- minmax:最小值和最大值
- next_permutation:下一个排列
- gcd:最大公约数
- lcm:最小公约数
标准算法,一般会做一些约定,只要容器满足这种条件就可以使用这个算法。比如说sort:
参数满足随机访问迭代器,迭代器指向对象可以使用<比较大小,满足严格弱序关系。
迭代器指向的对象可以移动。
5.1 C++算法比C语言的优势
- 他的性能比c语言的qsort更好,是因为编译器对比较操作做了内联,而c语言里面是通过一个额外的函数调用来实现。
- c的qsort函数要求数组内容是可以按比特复制的,c++则要求迭代器执行的内容是可移动的。
六、其他
constexpr和const是编译期常量和运行期常量的意思
lambda表达式:以一对中括号开始,不需要说明返回值(类似auto)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Flink实战教程-自定义函数之标量函数
- Flink实战教程-自定义函数之TableFunction
- Flink教程-flink 1.11 流式数据ORC格式写入file
- Flink教程-使用sql将流式数据写入文件系统
- flink教程-flink 1.11 集成zeppelin实现简易实时计算平台
- flink教程-详解flink 1.11 中的CDC (Change Data Capture)
- flink教程-基于flink 1.11 使 sql客户端支持执行sql文件
- flink教程-详解flink 1.11 中的JDBC Catalog
- flink教程-flink modules详解之使用hive函数
- 面试iOS 机会在自己手中
- Flink教程-将流式数据写入redis
- Flink教程-keyby 窗口数据倾斜的优化
- Flink源码分析之深度解读流式数据写入hive
- 浙大版《C语言程序设计(第3版)》题目集 习题10-1 判断满足条件的三位数
- 差分标记-HDU1556 Color the ball