迭代器
取中间值
mid = beg + (end-beg)/2;
而不是mid = (beg + end)/2;
- 没有两个迭代器相加的operator
- 可能overflow
数组
复杂的数组声明
int (*Parray)[10]
首先是指针,然后是10个数组,数组里是整数。是一个指向一个包含十个int的数组的指针。int *(&array)[10]
首先是个引用,10个数组,是整数指针,所以是一个有10个整数指针数组的引用。
begin函数
C++11中可以使用begin()
和end()
来获得。在iterator
头文件中。
指针也是迭代器
比较字符串
如果是数组,比较的是指针。string
比较的是值。
函数
内联函数
inline
将它在每个调用点上“内联地”展开,可以消除函数运行时的开销。
函数指针
bool (*pf) (const string &)
注意,函数指针的括号不可以升略。
debug技巧
__func__
存放当前调试的函数名
__FILE__
存放文件名的字符串字面值
__LINE__
存放当前行号的字面值
__TIME__
存放文件编译时间
__DATE__
存放编译的日期
类
类的基本思想是数据抽象(data abstraction)和封装(encapsulation)。
数据抽象是一种依赖于接口(interface)和实现(implementation)分离的编程技术。
const
成员函数
常量成员函数(const member function)
允许把const
关键字放在成员函数的参数列表之后,紧跟在参数列表后面的const
表示this
是一个指向常量的指针。这样使用const
的成员函数被称为常量成员函数。
编译器处理类:
- 编译成员的声明
- 成员函数体
所以无需在意类中的其他成员的出现顺序。
构造函数
名字和类名一样,没有返回类型。
|
|
构造函数初始值列表(constructor initialize list)
|
|
访问控制与封装(隐藏)
class
和struct
的区别:class
的默认权限是private
,struct
的默认权限是public
。
友元(friend)
类允许其他类或函数访问它的非公有成员。
可变数据成员(mutable data member)
const
成员函数可以改变一个可变成员的值,任何函数都可以改变它的值。
顺序容器
简介
容器类型 | 特点 |
---|---|
vector | 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。 |
deque | 双端队列。支持快速随机访问。头尾插入/删除很快 |
list | 双向链表。只支持双向顺序访问。在list中任何位置插入/删除速度很快。 |
forward_list | 单向链表。只支持单向顺序访问。任何位置插入/删除速度很快。 |
array | 固定大小的数组。支持快速随机访问。不能添加或删除 |
string | 与vector相似的容器,专门用于保存字符。随机访问快。在尾部插入/删除快。 |
string
和vector
将元素保存在连续的内存空间内。
容器库
迭代器
实际上,end是最后一个元素的后面,所以范围:[begin, end)
基于此的一些假定:
- 如果
begin
和end
相等,范围为空。 - 如果不等,则范围至少包括一个元素且为
begin
所指向的元素。 - 可以对
begin
递增若干次,使得begin
==end
赋值
相同但是不同容量的时候,用assign
。
seq.assign(b, e)
可以将seq
中的元素替换成b和e所表示的范围中的元素。
容器元素是拷贝
string
string
的搜索操作:find(args)
, find_first_of(args)
, find_last_not_of(args)
一个写法:while( pos = name.find_first_of(numbers, pos) != string.npos)
find_first_of()
解决的是查找与给定字符串中任何一个字符匹配的位置。
rfind(args)
和 find_last_of(args)
的区别:
rfind
找的是匹配的全部字符,find_last_of()
是匹配任意一个字符。
提取一句话中的数字
对于形如pi = 3.14
的语句:
d = stod(s2.substr(s2.find_first_of("+-.0123456789")))
容器适配器
适配器(adaptor)
是标准器中的一个通用概念。适配器有一些基本的要求,符合要求以后啥都可以。
泛型算法(generic algorithm)
多数在algorithm
中,少数在numeric
中。这些算法不直接操作容器,而是遍历由两个迭代器指定的一个元素范围来进行操作。
只读算法
find
和accumulate
(位于numeric
库中)。
int sum = accumulate(vec.begin(), vec.end*(), 0);
第三个参数的类型决定了函数中使用哪个加法运算符和返回值的类型。最好使用cbegin()
和cend()
。
equal
用来确定两个序列是否保存相同的值。假设第二个至少和第一个一样长
写容器元素的算法
fill
接受一对迭代器表示范围,一个值用来赋值。
fill_n
从迭代器开始计数n
个。这些都不能用来给空的写,除非使用插入迭代器(insert iterator)。
back_inserter
是定义在iterator
的插入迭代器,接受一个指向容器的引用,返回一个与容器绑定的插入迭代器。通过给这个迭代器赋值的时候,赋值运算符会调用push_back
将一个具有给定值的元素添加到容器中。
copy
,replace
,replace_copy
重排容器元素
sort
函数。stable_sort
函数可以维持相等元素的原有顺序。
利用unique
标准库去重:
|
|
向算法传递函数
谓词(predicate)是一个可调用的表达式,其返回结果是一个能用作条件的值。一元谓词,二元谓词是针对参数的个数。
lambda
表达式:
\[capture list\](parameter list) -> return type { function body }
lambda
通过将局部变量包含在捕获列表中指出将会使用这些变量。只用局部非static变量。
通常是不能改变值的,但是可以在参数列表首加上mutable
就可以了。
如果一个lambda
体包含return
之外的任何语句,编译器假定此lambda
返回void
。
[](int i) -> int { if( i<0 ) return -i; else return i; }
find_if
, for_each
算法
隐式捕获:[=]
表示值捕获,[&]
表示引用捕获。
bind
函数
定义在functional
中,可看作一个通用的函数适配器。它接受一个可调用对象,生成一个新的可调用对象来是一个原对象的参数列表。
auto newCallable = bind(callable, arg_list);
例如:bind(check_size, _1, sz);
使用_1
表示第一个参数,然后check_size
的第二个参数绑定到sz
的值上。不过可能是using std::placeholders::_1
。
引用的问题,可以用标准库的ref
函数。
插入迭代器
接受一个容器,生成一个迭代器,能实现向给定容器插入元素。插入器有三种类型:back_inserter, front_inserter, inserter.
iostream
迭代器
istream_iterator
读取输入流,ostream_iterator
向一个输出流写数据。
|
|
可以使用算法操作流迭代器
|
|
ostream_iterator
还有更好用的功能,可以加一个值,每个值后面都输出一个。
反向迭代器
反向迭代器会反着处理。需要调用reverse_iterator
的base
成员函数来完成“掉头”的转换。
泛型算法结构
五个迭代器类别(iterator category)
类别 | 描述 |
---|---|
输入迭代器 | 只读,不写;单遍扫描,只能递增 |
输出迭代器 | 只写,不读;单遍扫描,只能递增 |
前向迭代器 | 可读写;多遍扫描,只能递增 |
双向迭代器 | 可读写;多遍扫描,可递增递减 |
随机访问迭代器 | 可读写;多遍扫描,支持全部迭代器计算 |
关联容器
标准库一共有8个关联容器,两大种:set
和map
。有multi
前缀表示关键字可重复出现。有unordered
表示是用哈希组织的。
对于有序类型,关键字类型必须定义元素比较的方法。
pair
类型
pair
的数据类型是public
的,两个成员分别命名为first
和second
。
几个类型别名:key_type
, mapped_type
, value_type
。
解引用一个容器迭代器的时候,我们会得到一个类型为value_type
的值的引用,对map
而言,是pair
类型。
set
和map
的关键字都是const
的。
insert
的返回值是一个pair
,pair.first
是指向具有给定关键字的元素,second
成员是一个bool
值。
|
|
访问元素
c.find(k)
:返回一个迭代器,指向第一个关键字为k
的元素。
c.count(k)
:返回关键字为k
的元素的数量。
c.lower_bound(k)
:返回一个迭代器,指向第一个关键字不小于k
的元素。
c.upper_bound(k)
:指向第一个关键字大于k
的元素。
c.equal_range(k)
:返回一个迭代器pair
,表示关键字等于k
的元素的范围。若不存在,返回c.end()
如果我们想知道一个元素是否在map
中,而不想插入这个元素,用find
更好。
无序容器
无序容器在存储上组织为一个桶,每个桶保存零个或多个元素。无序容器还提供了一组管理桶的函数。
动态内存
静态内存:局部static
对象、类static
数据成员以及定义在任何函数之外的变量。
栈内存:定义在函数内的非static
对象。
堆/自由空间:动态分配的对象。
动态内存的管理
通过一对new
和delete
操作。
很容易危险:1. 没有释放导致内存泄漏。2. 提前删除导致引用非法内存的指针。
智能指针
新的标准库提供了两种智能指针(smart pointer)
shared_ptr
, unique_ptr
, weak_ptr
.
shared_ptr
类
智能指针也是模板,需要提供指针可以指向的类型。
shared_ptr
独有的操作:
make_shared<T> (args)
<memory>
头文件中。
返回一个shared_ptr
,指向一个动态分配的类型为T
,用args
初始化的对象。
拷贝与赋值
shared_ptr
会记录有多少个其他的shared_ptr
指向相同的对象,引用计数(reference count)。
当一个对象的最后一个shared_ptr
被销毁时,shared_ptr
会自动销毁此对象。通过调用析构函数来完成。
new
与delete
默认情况下,动态分配的对象是默认初始化的。
如果分配失败,new
会抛出std::bad_alloc
错误。可以new (nothrow) int
,此时分配失败会返回空指针。
delete
后需要重置指针值。
shared_ptr
接受指针参数的智能指针构造函数是explicit
的,不能把一个内置指针隐式转为一个智能指针,必须使用直接初始化形式。
|
|
一个危险的情况:
|
|
一旦把指针交给了智能指针,就由智能指针来负责了。
另一个危险的情况:永远不要用get
初始化另一个智能指针或为另一个智能指针赋值,两者的计数都是1,所在程序块结束后,会销毁的。
使用自己的释放操作
删除器(deleter)函数能够完成对shared_ptr
中保存的指针进行释放的操作。在创建一个shared_ptr
的时候,可以传递一个(可选的)指向删除器函数的参数。
|
|
unique_ptr
某个时刻只能有一个unique_ptr
指向一个给定对象,当它被销毁时,它所指向的对象也被销毁。没有make_shared
类似的操作。unique_ptr
不支持拷贝、赋值(p1赋值给p2)。
但是可以通过使用release
或reset
将指针的所有权从一个(非const
)unique_ptr
转移给另一个unique
。
但是对于编译器知道返回的对象将要被销毁的时候,是可以拷贝的。
weak_ptr
一个弱指针,不控制所指向的对象的生存期。因为它指向的不一定可以用,所以需要调用lock
来看一下。
动态数组
c++
语言定义了new
表达式语法,可以分配并初始化一个对象数组。标准库包含一个名为allocator
的类,允许我们将分配和初始化分离。
new
类
|
|
分配一个数组会得到一个与元素的类型的指针。
shared_ptr
不支持直接管理动态数组,必须要提供自己定义的删除器。
allocator
类
在头文件memory
中,它帮助我们将内存分配和对象构造分离开。它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。
allocator
分配的内存是未构造的,我们按需要在此内存中构造对象。
|
|
还可以拷贝和填充未初始化内存的算法
|
|
拷贝控制
一个类通过定义五种特殊的成员函数来控制这些操作,包括:拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符和析构函数。
拷贝、赋值与销毁
拷贝构造函数(copy constructor)
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则是拷贝构造函数。
直接初始化实际上是要求编译器使用普通的函数匹配来选择我们提供的参数最匹配的构造函数。当我们使用拷贝初始化时, 我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换。
拷贝初始化的发生:
- 使用=定义变量时
- 将一个对象作为实参传递给一个非引用类型的形参。
- 从一个返回类型为非引用类型的函数返回一个对象。
- 用花括号初始化一个数组中的元素或一个聚合类中的成员。
某些标准库容器在insert
和push
时也会对元素进行拷贝初始化。
拷贝构造函数自己的参数必须是引用类型
拷贝赋值运算符
重载运算符(overloaded operator)
本质是函数,其名字由operator
关键字后接表示要定义的运算符的符号组成。
析构函数
一个波浪号接类名构成。
析构部分是隐式的,智能指针是类类型,所以也有析构函数。
调用析构的时机
- 变量在离开作用域时
- 当一个对象被销毁时,其成员被销毁
- 容器被销毁时,其元素被销毁
- 动态分配的对象,当对指向它的指针引用
delete
运算符时被销毁 - 对于临时对象,当创建它的完整表达式结束时被销毁
面向对象程序设计
核心思想:数据抽象、继承和动态绑定。
继承(inheritance)
通过继承联系在一起的类构成一种层次关系。基类和派生类。
对于某些基类希望它的派生类各自定义适合自身的版本,就可以声明成虚函数(virtual function)。
派生类必须通过使用类派生列表明确指出是从哪些基类继承而来。
动态绑定(dynamic binding)
基类通常应该定义一个虚析构函数。
任何构造函数之外的非静态函数都可以是虚函数。
基类希望它的派生类有权访问该成员,同时禁止其他用户访问,用protected
来说明。
派生类经常但不总是override它继承的虚函数。
可以把基类的指针或引用绑定到派生类对象中的基类的部分上,这种转换通常称为派生类到基类的类型转换。
静态成员在整个继承体系中只存在该成员的唯一定义。
存在继承关系的类型之间的转换规则
- 从派生类到基类的类型转换只对指针或引用类型有效。
- 基类向派生类不存在隐式类型转换。
- 和任何其他成员一样,派生类到基类的类型转换也会由于权限受限而变得不可行。
虚函数
对虚函数的调用可能在运行时才被解析
动态绑定只有当我们通过指针或引用调用虚函数时才会发生。
多态性
多种形式。有继承关系的多个类型称为多态类型,我们可以使用这些类型而无须在意他们的差异。引用或指针的静态类型与动态类型不同这一事实正是C++语言支持多态性的根本所在。
override
用来标记对虚函数的覆盖。
Tips
- C++中,名字查找发生在类型匹配前。
- 内联说明只是向编译器发送一个请求,编译器可以忽略。
- 在类内部的函数是隐式的
inline
函数。 - 没什么特别好的理由,就用
vector
- 除了
array
之外,swap
不对任何元素进行拷贝、删除或插入操作,可以保证在常数时间内完成。 - 泛型算法不会执行容器的操作,所以不会直接添加或者删除元素。
- 在新版本的C++中, 可以用花括号来创建
pair
类型。 ->
相当于*
后.
。- 如果把
shared_ptr
存放于一个容器,而后不再需要全部元素,只使用其中一部分,要记得用erase
删除不再需要的那些元素。