文章目录
- C++ 程序设计
- STL 体系结构和内核分析
- 操作符重载和模板知识
- OOP vs. GP
- `分配器` Allocators
- `容器`
- 容器之间的实现关系和分类
- 深度探索 - LIST
- Iterator设计原则和Traits
- 深度探索 - VECTOR
- 深度探索 - ARRAY
- 深度探索 - FORWARD_LIST
- 深度探索 - DEQUE
- 深度探索 - QUEUE/STACK
- 红黑树 RB_TREE
- 深度探索 - SET/MULTISET
- 深度探索 - MAP/MULTIMAP
- 哈希表 HASHTABLE
- 深度探索 - UNORDERED_SET/_MULTISET/_MAP/_MULTIMAP
- `迭代器` iterator
- `算法`
- 算法 (11个例子)
- `仿函数` functors
- `适配器` Adaptors
- 补充知识
- C++2.0 新特性
- Variadic Templates
- Spaces in Template Expressions
- Unifrom Initailization
- Initializer List
- Explicit for ctors
- Range
- default / delete
- Alias Templates
- Template template parameter
- Type Alias, noexcept, override, final
- decltype
- lambdas
- Rvalue reference & Move Semantic
- Perfect forwardings
- Move-aware class
- 新的 容器
- Hash function
- Tuple
- C++ 内存管理
- C++ 并发和多线程(^)
- CRT Startup code 说明(启动码)
C++ 程序设计
杂七杂八
编译/链接/运行
:- 预编译
Preprocessing
主要是对条件预编译指令的处理:宏的展开/头文件引用/把所有的注释都替换为空格/保留所有#pragma(作用是设定编译器的状态或者是指示编译器完成一些特定的动作) - 编译
Compilation
主要是进行代码优化和符号汇总:扫描/语法分析/语义分析/源代码优化/代码生成/目标代码的优化;文本翻译成汇编语言 - 汇编
Assembly
根据对应关系把汇编指令转换为本地操作系统对应的机器码,生成可重定位目标文件 - 链接
Linking
把多个目标文件合并成一个可执行文件- 1 把所有的.o文件的各个段合并起来,其中对符号表段的合并就是符号解析,当一个符号在段中找到多个定义(重定义)或者未找到定义(未定义)就会发生链接错误。符号解析正确后,就会给符号表分配虚拟地址
- 2 进行符号重定向(就是把指令中所有的无效地址均修改成正确的虚拟地址)
- 加载
Loading
上一步所生成的可执行文件终于可以被操作系统加载运行了。操作系统会将这个可执行文件中的代码和数据从磁盘复制到内存中,并跳转到该程序的第一条指令处(也叫做入口点,entry point)开始执行。
- 预编译
struct和class的区别
:- class 是引用类型,它在堆中分配空间,栈中保存的只是引用;而 struct 是值类型,它在栈中分配空间。class是引用类型,struct是值类型;既然 class 是引用类型,class 可以设为 null;但是我们不能将 struct 设为 null
- new 一个类的实例时,在栈(stack)上存放该实例在托管堆(Managed Heap)中的地址,而实例的值保存在托管堆(Managed Heap)中。
- 托管堆分配在被操作系统保留的一段内存区域中,这段内存区域是由 CLR 来管理的,这段内存称之为托管堆。
- struct(结构)是一种值类型,用于将一组相关的变量组织为一个单一的变量实体,可以将其当作 int、char 这样的基本类型对待
- 默认继承权限。如果不明确指定,来自class的继承按照private继承处理,来自struct的继承按照public继承处理;
- 成员的默认访问权限。class的成员默认是private权限,struct默认是public权限。
- 如果没有多态和虚拟继承,在C++中,struct和class的存取效率完全相同
- class有显式的初始化构造函数,struct没有;class当只当变量都是公有变量时才能和struct一样用花括号初始化!
const修饰问题(成员函数)
:修饰变量(类也是变量)const放在前面变量定义的前面,修饰函数放在函数括号之后(放在函数䣌前面其实是在修饰返回值);修饰函数实际上就是在告诉编译器,这个函数不会在内部改变参数;const在设计中一定要考虑到,就是不修改的一定加,不加的认为会修改。函数中的const是属于函数签名的一部分的!PS函数签名不看返回值的类型,引用也不看
- const修饰类成员函数的时候,其实是在修饰this指针
传参的传值形式和引用方式
:引用的底层还是指针,但是被C++设计的更加得漂亮friend问题
:类内为函数声明友元特性(放在函数最前面),那这个函数可以直接访问该类得私有变量;注意同class的各个objects互为friendsthis
:所有成员函数默认带有this
指针,编译器会自己加上- 函数设计中的临时变量问题,返回引用和值问题
拷贝构造和拷贝赋值
:initialization & Assignment;参数都是对应类名- -(Big Three)
- 拷贝构造函数:copy ctor,深拷贝 / copy op=
Stack / Heap
:- stack:存在于某作用域的一块内存空间Memory Space,如一个函数就会形成一个stack;函数内任意的变量都存在于上述stack的;Stack的生命期取决于作用域!
- heap:system heap,操作系统存在的一块global内存空间,程序通过动态分配Dynamic allocated获得若干区域!new完需要delete,内存泄漏,你使用之后,没有即使归还给系统!
- new的过程:(分配内存 + 调用构造)
- delete过程:(调用析构 + 释放内存)
- new char[X] <> delete[]:中括号搭配中括号;有没有中括号,不影响内存的释放,而是有中括号会调用多次析构函数!内存泄漏就会出现在类中(当类中有new对象的时候)
- new和delete可以进行重载
- new表达式/operator new/placement new/
静态特性
:静态函数没有this指针
;调用静态函数的方法1是通过object调用2是通过class name来调用!单例设计模式,把构造函数放在private里面+初始化一个静态实例:
- cout是一种ostream
- 模板
template<typename T>
template<class T>
- 函数模板不需要尖括号提供类型,编译器会自动进行argument deduction 引数推导!
Object Orientated Programming/Design
Inheritance继承 / Composition复合 / Delegation委托Composition
:has-a,下图是一种复合关系- queue拥有着deque,deque这里足够强大,而queue只是拥有deque的部分功能,这种设计模式叫Adapter
- 组合,构造由内往外,析构由外往内
Delegation (Composition by reference)
:有指针指向某个类;前面复合关系两个类的生命周期是一致的!而委托关系这不一定!
Inheritance
:is-a
- 注意一般继承都是 public继承,表示
is a
的关系,而采用 private继承的时候,其设计思想其实算是has a
的关系! 继承+虚函数+多态
- 虚函数
virtual
: 设计非虚函数,虚函数和纯虚函数的动机是希不希望派生类对其进行重写override
,分别是 不希望重写(有默认定义) / 希望重写(有) / 一定要重写(无)!- 模板方法设计模式:把需要单独放在子类中定义实现的先定义成虚函数
- 继承+复合
- 委托+继承
- 23种设计模式 CSDN博客
转换函数和构造函数
- 转换函数:
operator double() const {return (double)(..)};
- non-explicit-one-argument ctor:
- 转换函数 + 非显性单参数构造函数 比较
- explicit-one-argument ctor:explicit关键字90%用在构造函数前面!
point-like classes 智能指针/迭代器
->
操作符可以无限进行,而不是只能使用一次
function-like classes 仿函数
- STL中的
member template 成员模板
Specialization 模板特化
- 和泛化相对,更加局部性的特征化
- partial specialization 模板偏特化:① 个数上的“偏” ② 范围上的“偏”
template template parameter 模板模板参数
- 下图deque已经不算是模板参数了,已经是类型了
Variadic Template 数量不定的模板参数
reference
- 函数中参数加不加引用,不会改变函数的签名!即如果函数定义只有入口参数有没有加&的话,其函数签名会相同!导致冲突!
this
- 通过对象调用函数,this就是这个对象的地址
- this就是给对象提供了一个寻找的方法(每个非静态成员函数都被编译器隐含着一个this指针)
virtual 虚函数/虚指针vptr/虚表vtbl
- 关于继承:继承变量内存+继承函数的调用权
- 含虚函数的类(抽象类)的动态绑定图示
- 下图的下方的代码中的p,可以是this指针,那么下方的代码就是在调用p对应对象的虚函数表中的第n个虚函数,编译器在将this隐含的赋值给了这个成员(虚)函数。
Dynamic Binding
动态绑定相对的是静态绑定,静态绑定直接访问的是call某个内存位置©- C++的函数调用有两个方式:静态绑定和动态绑定;静态绑定就是汇编call+地址;动态绑定就是符合,(对象是指针/指针是向上转型/调用的是虚函数)情况时,就是要动态绑定!
- 再一个例子
const
STL 体系结构和内核分析
STL源码剖析
STL泛型编程的思维,最新的也是加了面向对象思维,class多了很多切分- Container <- Iterators -> Algorithm (Functors)
- C++ standard Library / Standard Template Library
- 6大组件
- STL的区间是取得
前闭后开
的样子! - 容器
- 分配器allocator:和new/malloc区别,需要记住分配的内存大小,以方便之后的dellocator
操作符重载和模板知识
- 四个运算符不能重载:
::
、.
、.*
、?:
- 三个运算符不能创建:
**
、<>
、&|
- 三个运算符重载会失去其特性:
&&
、||
(Short-circuit evaluation),
依次执行返回最左边表达式的值 ->
操作符重载要求必须返回 一个指针或者 一个对象(引用或者值)- 重载不能修改运算符的procedure / grouping / #operands(优先级,类别和操作数)
- 类模板 Class Template
- 函数模板 Function Template
- 成员模板 Member Template
- 变量模板 Variable Template (Since C++14)
- 模板特化 Specialization
- 偏特化 Partial Specialization
OOP vs. GP
- Object Oriented Programming
- Generic Programming
- OOP:旨在将Datas和Methods联系在一起
- GP:旨在将Datas和Methods分开,操作符重载在GP中有重要的地位!
- STL的六大部件:
Containers / Allocators / Algorithms / Iterators / Adapters / Functors
分配器
Allocators
- malloc / new
- VC等很多Allocators,会有很多额外开销OverheadD
- 容器如果使用这种allocator,会多很多内存(Cookie),导致内存占用大,效率低,尽量减少malloc次数
- G2.9,标准库STL里面自定义了alloc数据结构,元素不带Cookie
- G4.9的容器又改回到new和free了 …
容器
容器之间的实现关系和分类
- heap里面有一个vector
- set里面有一个vector
深度探索 - LIST
- G2.9版本,节点指针 void_pointer 定义为 void*
List<T>::iterator
++i (prefix form) / i++ (postfix form)
:++++i 整数可行,等于+2;i++++不可行反正,所以这里postfix形式的++,返回的是值而不是引用,即一个临时变量,所以二次++就会报错(因为临时变量不能被累加)
- LIST迭代器在G2.9和G4.9上面的差别
- G4.9版本的list继承关系更多更混乱!① 内存从4变成8,即 _M_prev: _list_node_base 和 _M_next:_list_node_base两个指针,② 而 2.9版本是 List_node* 一个指针4个字节!
Iterator设计原则和Traits
- iterator是算法和容器的桥梁
- 某个算法函数 为
rotate
- 下图中可以看出 rotate() 需要知道iterator的两个相关对象
- 算法提出问题,迭代器回答问题,问题有:
value_type
表示元素的类型,different_type
表示元素间距离的类型,iterator_category
表示迭代器的类型(随机访问?前向?…)
- 另外还有 reference_type 和 pointer_type 两个"问题",但是这两个问题整个STL库中尚未找到!
- 总结,iterator 得提供5种associated types,下图是算法和迭代器之间的 “直接问答”:
- 当没有用 iterator, 而是一般的pointer,将pointer视为iterator的退化形式!而指针无法直接回答算法的问题,所以加上一个中间层 Traits 来兼容 Pointer
Traits
- 使用偏特化来兼容 pointer 和 算法 的问答行为
- 完整的 iterator_traits
- 各种 Traits !
type / iterator / char / allocator / pointer / array traits
深度探索 - VECTOR
- 两倍增长 (push_back()调用时)
vector的迭代器
- G2.9占用3 x 4 = 12内存(start+finish+end of storage)
- G4.9 也占用 12字节的内存
- vector的迭代器
深度探索 - ARRAY
深度探索 - FORWARD_LIST
- 单向链表
深度探索 - DEQUE
- 双端队列:连续是假象,分段是事实!
- 默认实现采用vector,2倍成长!
- 占用内存:4 * 2 + (4 * 4) * 2 = 40
- _deque_buf_size() 第二个参数使用0,则会使用默认的值
(sz < 512) ? size_t(512 / sz) : size_t(1)
- 一个迭代器占用16字节
deque<T>::insert()
deque如何模拟连续空间
- 全是 deque iterator 的功劳
- front() back() size() empty() []
* -> -
- ++ –
- += -= - + []
- deque 程序结构
- 注意扩充2倍的时候,会把原map放在之后的中段
深度探索 - QUEUE/STACK
- 基于Sequence数据结构的 适配器模式 (C++专门叫适配器模式)
- queue / stack 的底层也可以用list
- queue 不可以选择vector 作为底层 / stack可以:因为vector没有 pop_front()函数
- queue和stack不可使用set或者map作为底层结构!!
红黑树 RB_TREE
- 红黑树:Red-Black tree,平衡二叉搜索数 balanced binary search tree
- 平衡有利于 insert 和 search
- 中序遍历为升序,所以其 迭代器++行为 是按照中序遍历的顺序进行
- RB树应该不能允许程序修改键值(map等数据结构要求),但是程序上没有严格规定
- insert_unique() / insert_equal():前者要求插入的键值不能和已知冲突(不可重复键),后者不要求(可重复键)!
- 红黑树,模板参数有5个:key的类,value的类(包括ket+data),从value中取出key的方法类,比较函数,内存分配器!
- 此外,4.9中插入函数,insert_unique() 变为 _M_insert_unique()!
- Handle + body 模式 桥接模式
- 其实 set/multiset/map/multimap 其实也算是底层采用rb_tree的 适配器模式!
深度探索 - SET/MULTISET
- 以RBtree为底层结构,所以元素有自动排序的特性
- 排序依据为 key,set/multiset 的value(key+value)其实和key合一
- 无法使用其迭代器修改key值!
- set 的insert()函数使用rb_tree()的insert_unique(),其key独一无二
- multiset的insert()函数使用rb_tree()的insert_equal(),其key可以重复
SET
- 其 迭代器 定义成了const_iterator常迭代器,即不允许改变其指向的元素!
- 而 VC中没有identity仿函数
深度探索 - MAP/MULTIMAP
- 同样使用rb_tree为底层结构
- 和set不同在于,其key不等于value
- 同样 map对应 insert_unique(); multimap 对应 insert_equal()
pair<const key T>
,即键值为常,不能修改!
- map的operator[] 是独特的
哈希表 HASHTABLE
- 内存空间足够或者不足时
- 不足的时候,取余下标,出现下标冲突!
- 发生碰撞:拉链法,某个下标上 超过了bucket_size, 就需要扩充大小!
rehashing
- 容器hashtable:GNU-C 是单向链表 VC是双向链表
- 标准库中没有 提供
hash<std::string>
- 得到哈希值后,怎么放到篮子里面?
- load_factor = size / bucket_count
- max_load_factor = 1
- max_bucket_size = 357913941
深度探索 - UNORDERED_SET/_MULTISET/_MAP/_MULTIMAP
迭代器
iterator
- 分类:
- 容器中没用到的迭代器:
- istream_iterator的iterator_category:input_iterator
- ostream_iterator的iterator_category:output_iterator
- iterator_category 对算法的影响:
- iterator traits 和 type traits 对算法的影响
- trivial / no-trivial
- Trivial(平凡)和Non-Trivial(不平凡)是对于class(类)的或者类中的四个函数而言的:构造函数 | 拷贝构造函数 | 赋值函数 | 析构函数;其中Trivial(平凡)的概念本人的理解是无意义的,Trivial是相对于Non-Trivial而言的;
- 对于Non-Trivial而言的,如果上面四种函数满足以下三点任意一项或一项以上:有基类;显式(explict)定义了以上四种函数一种或以上;类里有非静态非POD的数据成员。有意义(Non-Trivial)的函数都有一些“多余”的操作,和系统自动创建的默认缺省函数有些差别。(POD(Plain
Old Data)是指C风格的struct结构体定义的数据结构或者C++的内建类型)- 有意义(Non-Trivial)的类则是含有Non-Trivial的函数。
- 算法源码 对 iterator_category 只有暗示:只在模板参数的命名有了暗示,
template <class RandomAccessIterator>
- 逆向迭代器:reverse iterator,rbegin(),rend() 是一种 iterator Adapter
reverse_iterator
rbegin() { return reverse_iterator(end());}
reverse_iterator
rend() {return reverse_iterator(begin());}
算法
- Algorithm:看不见Container,对其一无所知,一切从iterator提供信息
算法 (11个例子)
accumulate
- 累次处理函数
- binary_op() 二元操作符函数
struct myOp {
int operator() (int x, int y) {return 3 * x + y;}
} myOp;
或者
int myOp(int x, int y) {return 3 * x + y;}
for_each
- 对容器中每个元素采用相同的操作
range-based for statement
replace / replace_if / replace_copy
- 替换
count / count_if
- 计数
find / find_if
sort
binary_search
- 值已经是拍好序的
- 二分查找,是否存在
仿函数
functors
- 写法类似于
- 仿函数的可适配条件 Adaptable
- STL 规定 每个Adaptable Function 都应该挑选适当的继承者,因为可能会问问题,问一些 类型 名
适配器
Adaptors
- A获取B的特性,要不继承,要不内含
- 适配器,采用内含方式(组合方式)
- 适配器修饰什么,就得表现的像什么
- 分类:
- 容器适配器,stack / queue
- 函数适配器,binder2nd,把第二个元素绑定为40
- 新型适配器 bind
- 新型适配器 bind
- 迭代器适配器,reverse_iterator 逆迭代器 / inserter 带插入功能的迭代器
- X 适配器:ostream_iterator 输出流迭代器 / istream_iterator 输入流迭代器
补充知识
一个万用的hash function
- 0x9e3779b9:
- 所有代码
- 哈希表的三种形式:
- 形式三,以偏特化形式实现 Hash Function
tuple
- 多种数据类型的整合 组合
tuple<int, float, string> t1(4, 4.3, "nico");
type triats
- trivial / non-trivials (构造函数 / 析构函数 重不重要!)
- 就是为 类记录各种traits,以便于回答 算法等提出的问题!
- C++ 11 以后更多 。。。
- type traits 的定义!
cout
moveable 元素对于 vector / … 等 速度效能的影响
- std::move(X)
- 写一个 moveable class
- &&:reference of reference
- move 之后,原来的变量不在使用,因为为了避免指针 共享,销毁了前一个原指针(浅拷贝 + 销毁原指针)
- 而深拷贝为什么慢呢?因为深拷贝需要一个一个插入,而浅拷贝只用把头尾指针交换下就行了(共享指针),而原指针作废,所有十分快!
- string也带有move功能
C++2.0 新特性
Variadic Templates
Spaces in Template Expressions
Unifrom Initailization
Initializer List
Explicit for ctors
Range
default / delete
Alias Templates
Template template parameter
Type Alias, noexcept, override, final
decltype
lambdas
Rvalue reference & Move Semantic
Perfect forwardings
Move-aware class
新的 容器
Hash function
Tuple
C++ 内存管理
内存分配的各个层次
- C++ memory primitives
- VC编译器下的new过程是
- 各自的好处:GP中Container和Algorithm可分开操作,之间使用Iterator联系;Algorithm通过Iterator确定操作范围和获取数据;
- list不能用algorithm的全局sort就是因为后者需要操作对象具有随机访问的迭代器!
- 所有algorithm内涉及到的元素本身的操作,无非就是
比大小
!