C3P阅读笔记
第一部分
第二章——变量和基本类型
char类型及其扩展
类型 | 含义 | 最小尺寸 |
---|---|---|
char |
字符 | 8bit |
wchar_t |
宽字符 | 16bit |
char16_t |
Unicode字符 | 16bit |
char32_t |
Unicode字符 | 32bit |
wchar_t
确保可以存放机器最大扩展字符集中的任一个字符,char16_t和char32_t为Unicode字符集服务
类型转换
如果赋予一个取值区间之外的值,如unsigned char
(8bit)表示0-255,赋予-2,则-2对256取模后的余数,实际结果为254(数据类型所占位数而定)
字面值常量
默认的,整形字面值数据类型是带符号数,即int
;浮点型默认数据类型为double
,因为float精度不够且双精度浮点数计算代价与单精度差不多;字符串字面值结尾处会多一个空字符\0
,字符串字面值的实际长度要比它的内容多1;若两个字符串字面值位置紧邻且仅由空格、缩进和换行符分割时,实际上是一个整体
转义序列
1 |
|
如果反斜线后的八进制数字超过三个,只有前三个与反斜线构成转义序列;如果跟着十六进制数字,则所有十六进制数字都参与构成转义序列
指定字面值的类型
前缀/后缀 | 含义 | 类型 |
---|---|---|
u | Unicode16字符 | char16_t |
U | Unicode32字符 | char32_t |
L | 宽字符 | wchar_t |
u8 | UTF-8 | char |
整型后缀 | ||
u/U | 无符号型 | unsigned |
l/L | long |
|
ll/LL | long long |
|
浮点型后缀 | ||
f/F | float |
|
l/L | long double |
整型字面值不能添加浮点型后缀F
变量
如果使用列表初始化且初始值存在信息丢失的风险,则会报错,如int a = {3.14}
- 在任何函数体之外的变量会被初始化为0
- 在函数体内部的内置型变量不会被初始化,其未定义,不可被访问
- 类不初始化则由类自己定义
变量声明规定了变量的类型和名字,定义则为其开辟空间
变量声明:在变量名前添加关键字 extern
,如extern int i;
。但如果包含了初始值,就变成了定义:extern double pi = 3.14;
变量只能被定义一次,但是可以多次声明。定义只出现在一个文件中,其他文件使用该变量时需要对其声明。
名字的作用域
同时存在全局和局部变量时,已定义局部变量的作用域中可用::reused
显式访问全局变量reused。
引用和指针
引用类型的初始值必须是一个对象(不能是字面值),必须在定义时给引用赋初值
指针本身就是一个对象,可以不赋初值
引用自加——引用的对象加一
指针自加——指针指向下一个类型,即向后偏移一个类型的大小
void*
指针
可以存放任意对象的地址,但不能访问对象
- 与其它指针比较
- 当做函数的输入或输出
- 赋值给另一个
void*
指针
指向指针的引用
int i = 0;
int* b = &i;
int * & a = b;
i
是整型变量,b
是指向整型变量i
的整型指针,a
是指向整型指针b
的整型引用
*a = 1;
通过引用对整型变量进行访问和修改
const
限定符
const
对象一旦创建就不能被修改,因此在定义时必须赋初值
const int ci = 0; int i = ci;
const
的常量特征只在被修改时显示,因此可以用常量值赋初值
const
的引用
即为对常量的引用,且无法对引用对象进行修改(常量引用)
const int ci = 30; const int & cr = ci
则cr
为对ci
的常量引用
常量引用初始化时能够用字面值赋值,而引用不能,且能够用任意表达式进行初始化
const
引用的也可以不是常量,因为引用对象本身不是常量,即时不能通过引用修改对象,但能通过其他途径来修改。
常量指针和指针常量
const int ci = 30; int const* cptr =&ci;
则cptr
为指向常量的int
指针,且因为其是指针,可以不用初始化
常量指针指向的也可以不是常量,只是不能通过指针改变对象的值,但仍可以通过其他途径修改。底层const
const int ci = 30; const int *const ptrc = &ci;
则ptrc
为指针常量,是一个指向整型常量的const int
指针常量
int i = 30; int *const ptri = &i;
则prti
为指针常量,是一个指向整型字面量的int
指针常量。顶层const
相同地,指针常量指向的也可以不是常量,只是不能更改指针常量本身的指向,但仍可以通过其他途径修改对象的值
常量表达式
声明为constexpr
的变量一定是一个常量(顶层const
),且必须用常量表达式初始化。一个constexpr
指针的初始值必须为0或者nullptr
或者是存在某个固定地址的对象;定义于所有函数体之外的对象其地址固定不变,此时能用于初始化constexpr
指针。
constexpr int *q = nullptr
则q为指向整数的指针常量
处理类型
using SI = Sales_item;
类型别名
typedef char *pstring;
pstring
为char*
类型,指向char
类型的指针
const pstring cstr = 0;
cstr
为指向char
的指针常量,注意与下一个式子的区别
const char *cstr = 0;
cstr
为指向char
的常量指针
decltype
返回表达式结果或变量对应的类型;如果表达式的内容是解引用操作,则decltype
将得到引用类型;如果表达式或者变量加多一层括号,则也将得到引用类型
const int ci = 0;decltype(ci) x = 0
则x的类型为const int
int i = 30; decltype(*p) c = i
则p的类型为引用类型
decltype ((i)) d = i
则d的类型也为引用类型
预处理器
#indef
已定义时为真#inndef
未定义时为真- 头文件保护符的名称需要唯一,且保持全部大写。养成良好习惯,不论是否该头文件被包含,要加保护符。
第三章——字符串、向量和数组
string对象
如果提供一个字符串字面值给string对象初始化,则该字面值中除了最后的空字符其他所有字符都被拷贝到string对象
string io:
string word; cin>>word
忽略掉开头的空白(包括空格、换行符和制表符),直到遇到下一处空白为止string word; while(cin>>word)
读取到文件结束符或非法输入时停止 Ctrl+Zgetline
:读取一整行,包括空白符,直到换行符,但不会将换行符存到string对象中- string line; while(getline(cin,line))
s.size()
返回的时string::size_type
类型,记住是一个无符号类型的值,不要和int
混用1
2
3
4
5
6void check (size_type i, const string &msg) const
{
if(i>=data->size())
throw out_of_range(msg);
}
当i小于0,会自动转换成正整数,即i = i + UINT_MAX;s1+s2
使用时,保证至少一侧是string类型。string s1 = "hello" + "world" // 错误,两侧均为字符串字面值
cctype
头文件中的标准函数
函数 | 解释 |
---|---|
isalnum(c) |
当c 是字母或数字时为真 |
isalpha(c) |
当c 是字母时为真 |
iscntrl(c) |
当c 是控制字符时为真 |
isdigit(c) |
当c 是数字时为真 |
isgraph(c) |
当c 不是空格但可以打印时为真 |
islower(c) |
当c 是小写字母时为真 |
isprint(c) |
当c 是可打印字符时为真 |
ispunct(c) |
当c 是标点符号时为真 |
isspace(c) |
当c 是空白时为真(空格、横向制表符、纵向制表符、回车符、换行符、进纸符) |
isupper(c) |
当c 是大写字母时为真 |
isxdigit(c) |
当c 是十六进制数字时为真 |
tolower(c) |
当c 是大写字母,输出对应的小写字母;否则原样输出c |
toupper(c) |
当c 是小写字母,输出对应的大写字母;否则原样输出c |
vector对象
1 |
|
如果初始化用的是圆括号,则提供的值是用来构造vector对象;
如果初始化用的是花括号,则表示列表初始化vector对象
1 |
|
当初始化用了花括号的形式,但提供的值又无法列表初始化,则编译器会尝试用默认值初始化vector对象
因为范围for循环语句内不应改变其所遍历序列的大小,因此不能用范围for循环向vector添加元素
size返回值的类型是由vector定义的size_type类型即vector<int>::size_type
用下标访问vector,下标类型应是size_type类型,可以通过auto
或者decltype
获取
1 |
|
### vector迭代器
*iter.mem
等同于iter->mem
vector<int>::iterator it1;
//it1
能够读写元素
vector<int>::const_iterator it2;
//it2只能读元素,不能写
两个迭代器之间的运算只有减法运算,返回值类型为difference_type
difference_type
返回带符号整数,所得结果是右侧迭代器向前移动能追上左侧迭代器的距离
迭代器与整数值之间有加法运算
数组
数组的大小确定不变,不能随意向数组中增加元素
数组声明时,其中维度必须是常量表达式
1 |
|
字符数组在用字符串字面值初始化时,会自动添加表示字符串结束的空字符
不允许数组拷贝初始化,也不允许用数组给其他数组赋值
1 |
|
数组下标类型为size_t
(头文件cstddef),而vector和string的下标类型为size_type
复杂数组
1 |
|
ptrs
是一个数组,包含了10个整型指针——指针数组
parray
是一个指针,指向一个有10个整型元素的数组——数组指针
array
是一个引用,引用对象是一个有10个整型元素的数组
int *(&array)[10]=ptrs
则是一个引用,引用对象是一个有10个整型指针的数组
指针和数组
数组名就是数组首元素的指针
string *p2=nums; //等价于p2 = &nums[0];
auto可以推断数组名得到指针类型
1 |
|
但当用decltype时则不一样
1 |
|
数组的库函数
1 |
|
当两个数组指针相减时,返回的类型为ptrdiff_t
;vector和string返回的类型为difference_type
,都是一种带符号类型
数组下标可以是负数,而vector的下标只能是正整数
1 |
|
C风格的字符串(不推荐使用)
会在字符串最后一个字符后面跟着一个空字符(\0
)
1 |
|
C标准库String函数,定义在<cstring>
中:
函数 | 介绍 |
---|---|
strlen(p) |
返回p 的长度,空字符不计算在内 |
strcmp(p1, p2) |
比较p1 和p2 的相等性。如果p1==p2 ,返回0;如果p1>p2 ,返回一个正值;如果p1<p2 ,返回一个负值。 |
strcat(p1, p2) |
将p2 附加到p1 之后,返回p1 |
strcpy(p1, p2) |
将p2 拷贝给p1 ,返回p1 |
C风格字符串不支持运算符操作,如+,-,==
,因为操作的是指针而非字符串本身,并无意义
与旧代码的接口
允许使用string对象给C风格字符串初始化
1 |
|
使用数组给vector初始化
1 |
|
多维数组
数组的数组,由内而外的顺序理解
使用范围for循环访问多维数组时,外层循环需要使用引用类型,这是为了在内层循环数组被自动转成指针
1 |
|
利用别名代替,使多维数组降维访问
using int_array int[4];
typedef int int_array[4];
编写3个不同版本的程序,令其均能输出ia
的元素。
版本1使用范围for
语句管理迭代过程;版本2和版本3都使用普通for
语句,其中版本2要求使用下标运算符,版本3要求使用指针。
1 |
|
第四章——表达式
左值和右值
当一个对象被用作左值时,使用的是对象的身份(在内存中的位置);
当一个对象被用作右值时,使用的是对象的值
解引用符返回的结果是左值
取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值
1 |
|
表达式的结果是左值,decltype得到引用类型;表达式的结果是右值,decltype得到指针类型
算数运算符
运算对象和运算结果都是右值
1 |
|
参与取余运算的运算对象都为整数类型
(-m)/n,m/(-n)
结果都为-(m/n)
m%(-n)
结果为m%n
,(-m)%n
结果为-(m%n)
逻辑和关系运算符运算对象和运算结果都是右值
递增递减运算符
前置递增运算符将对象本身作为左值返回,后置递增则将对象原始值的副本作为右值返回。后置需要将原始值存储下来以便返回这个未修改的内容,如果不需要修改前的值,应直接使用前置递增
1 |
|
后置递增运算符高于解引用运算符
1 |
|
不应在运算符左右两边都用到一个变量,并且右侧还改变其值
1 |
|
假设iter
的类型是vector::iterator
, 说明下面的表达式是否合法。如果合法,表达式的含义是什么?如果不合法,错在何处?
1 |
|
解:
- (a)合法。返回迭代器所指向的元素,然后迭代器递增。
- (b)不合法。因为
vector
元素类型是string
,没有++
操作。 - (c)不合法。这里应该加括号。
- (d)合法。判断迭代器当前的元素是否为空。
- (e)不合法。
string
类型没有++
操作。 - (f)合法。判断迭代器当前元素是否为空,然后迭代器递增。
sizeof运算符
- 对引用类型执行sizeof运算得到引用对象的类型大小
- 对解引用指针类型执行sizeof运算得到指针指向对象的类型大小,指针可以不有效
- 对数组执行sizeof运算得到整个数组所占空间的大小,而不会把数组转换成指针
- 对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,与对象中元素个数或所占空间大小无关
通过查看STL源码可以看到vector有四个成员变量
_A allocator;
iterator _First, _Last, _End;
所以sizeof(vec)
会返回4个字节即16bit
类型转换
隐式转换
常见的char、bool、short能存在int就会转换成int,否则提升为
unsigned int
wchar_t,char16_t,char32_t
提升为整型中int,long,long long ……
最小的,且能容纳原类型所有可能值的类型当数组被用作
decltype
关键字的参数,或者作为取地址符(&)、sizeof
及typeid
等运算符的运算对象时,转换不发生
显示转换
static_cast:任何明确定义的类型转换,只要不包含底层const,都可以使用。
double slope = static_cast<double>(j);
dynamic_cast:支持运行时类型识别
const_cast:只能改变运算对象的底层const,一般可用于去除const性质。
const char *pc; char *p = const_cast<char*>(pc)
只有其可以改变常量属性,通常用在函数重载reinterpret_cast:通常为运算对象的位模式提供低层次上的重新解释,实际上仍为原类型
第五章——语句
悬垂else
有多个if时,else只与离它最近的尚未匹配的if匹配或者在想要匹配的if语句下加上花括号,以控制执行路径
switch语句
case关键字和它对应的值一起被称为case标签,其必须是整型常量表达式
如果某个case标签匹配成功,将从改标签开始往后顺序执行所有case分支,因此需要添加break语句
若以一个空的default标签作为结束,则必须跟上一个空语句或者空块
for语句
1 |
|
在init-statement中可以定义多个对象,但只能有一条声明语句,即所有变量的基础类型都必须相同
1 |
|
for语句也可以省略掉任意一部分,但要以一条空语句代替,即要保留分号
if(cin.eof())
可以读取eof
文件结束符,可以用Ctrl+z
代替
异常处理
异常处理包括:
- throw表达式:异常检测部分使用
throw
表达式来表示它遇到了无法处理的问题。我们说throw
引发raise
了异常。 - try语句块:以
try
关键词开始,以一个或多个catch
字句结束。try
语句块中的代码抛出的异常通常会被某个catch
捕获并处理。catch
子句也被称为异常处理代码。 - 异常类:用于在
throw
表达式和相关的catch
子句之间传递异常的具体信息
try语句块内声明的变量在块外部无法访问,在catch子句内也无法访问
1 |
|
处理程序中,err.what()
的返回值是C风格字符串,返回初始化runtime_error
对象时的string对象的副本
如果异常抛出,但最终没有找到任何匹配的catch语句,程序转到名为terminate
的标准库函数,一般将导致程序非正常退出
exception |
最常见的问题 |
---|---|
runtime_error |
只有在运行时才能检测出来的问题 |
range_error |
结果超出有意义的值域范围 |
overflow_error |
计算上溢 |
underflow_error |
计算下溢 |
logic_error |
逻辑错误 |
domain_error |
参数对应的结果值不存在 |
invalid_argument |
无效参数 |
length_error |
试图创建一个超出该类型最大长度的对象 |
out_of_range |
使用一个超出有效范围的值 |
对于exception
,bad_alloc
,bad_cast
只能默认初始化而不允许提供初始值
第六章——函数
指针形参
当执行指针拷贝操作时,拷贝的是指针的值,可以通过指针改变所指的对象的值,但不改变指针本身
编写一个函数,使用指针形参交换两个整数的值
1 |
|
传引用参数
使用引用可以避免额外空间开销
还可以利用引用形参返回额外信息
右值实参不能用作引用形参
常量形参
尽量使用常量引用,因为如果是普通引用如字符串,将无法接收字符字面值常量以及常量字符串
不能把const对象、结果为该类型的表达式、字面值或者需要类型转换的对象传递给普通的引用形参
数组形参
数组——不允许拷贝,不允许赋值给其他数组,使用数组时通常会将其转换成指针
数组形参只能以const int *
类型传参,但可以有不同形式
1 |
|
三种形式等价
管理数组指针形参
使用标记指定数组长度
如C风格字符串,自带有空字符,遇到空字符时即停止,但数组没有明显标记
void print(const char *cp)
使用标准库规范
传递指向数组首元素和尾元素的指针,
begin
和end
函数void print(const int *beg,const int *end)
显示传递表示数组大小的形参
void print(const int ia[],size_t size)
数组引用形参
形参可以是数组的引用,且维度是类型的一部分,但绑定了维度就意味着函数只能作用于固定维度的数组
void print(int (&arr)[10])
多维数组形参
数组第二维以及后面所有维度的大小都是数组类型的一部分,不能省略
void print(int (*arr)[10])
指针指向10个整数数组,等价于void print(int arr[][10])
void print(int *arr [10])
则代表10个整型指针的数组
处理命令行选项
int main(int argc,char* argv[])
等价于int main(int argc,char**argv)
其中第一个形参表示数组中字符串的数量,第二个形参表示数组,元素是指向C风格字符串的指针
argv中的实参0保存程序的名字,往后是用户输入的实参,最后一个指针之后的元素值保证是0
可变形参的函数
如果所有的实参类型相同,可以传递一个名为
initializer_list
的标准库类型,表示某种特定类型的值的数组initializer_list
对象中的元素永远是常量值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16#include <iostream>
#include <initializer_list>
int sum(std::initializer_list<int> const& il)
{
int sum = 0;
for (auto i : il) sum += i;
return sum;
}
int main(void)
{
auto il = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; //可变长度
std::cout << sum(il) << std::endl;
return 0;
}如果类型不相同,可以用可变参数模板
省略符,一般只用于与C函数机交互的接口程序
函数返回类型
调用一个返回引用的函数得到左值,其他返回类型得到右值
函数返回类型不能是数组或者函数,但可以是指向数组或者函数的指针
返回数组指针
Type (*function (parameter_list))[dimension]
如int (*func(int i)[10])
表明需要实参类型是int,函数调用结果返回的是指针类型,指针指向的是个大小为10的整型数组,- 使用类型别名:
typedef int arrT[10];
或者using arrT = int[10;]
,然后arrT* func() {...}
- 使用
decltype
:int odd[] = {1,2,3,4,5}; decltype(odd) *arrPtr(int i) {...}
- 尾置返回类型: 在形参列表后面以一个
->
开始:auto func(int i) -> int(*)[10]
编写一个函数声明,使其返回数组的引用并且该数组包含10个string对象
1 |
|
函数重载
不允许两个函数除了返回类型外其他都一样,这不是重载的函数
顶层const不影响传入函数的对象,因此不能用顶层const来进行函数重载,而底层const则可以
1 |
|
const形参重载可借助const_cast
1 |
|
当普通引用传入时,调用第二个版本函数,将普通引用强制转换成const引用,再调用第一个版本函数,返回得到const引用绑定为一个引用上,再将其转换回普通引用返回
若在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体,在不同的作用域中无法重载函数名
特殊用途语言特性
默认实参
string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');
一旦某个形参被赋予了默认值,那么它之后的形参都必须要有默认值
局部变量不能作为默认实参
内联函数
可避免函数调用的开销
inline const string &shorterString(const string &s1,const string& s2)
constexpr函数
函数的返回类型及所有形参的类型都得是字面值类型,不一定返回常量表达式
调试帮助
assert预处理宏
assert(expr)
如果expr为真则忽略,为假则输出信息并终止程序通常用于检查不能发生的条件
NDEBUG预处理变量
CC -D NDEBUG main.c
可以定义这个变量NDEBUG
,等价于在main.c一开始写# define NDEBUG1
2
3
4
5
6
7
8
9void print(){
#ifndef NDEBUG
cerr << "Error:" << __FILE__
<< ": in funciton " << __func__
<< "at line " << __LINE__ << endl
<< " Compiled on " << __DATE__
<< "at " << __TIME__ << endl
#endif
}分别输出报错的文件名的字符串字面值、当前调试的函数名的字符串字面值、行号的整型字面值、编译日期和编译时间的字符串字面值
函数匹配
- 重载函数匹配的三个步骤:1.候选函数;2.可行函数;3.寻找最佳匹配。
- 候选函数:选定本次调用对应的重载函数集,集合中的函数称为候选函数
- 可行函数:考察本次调用提供的实参,选出可以被这组实参调用的函数,新选出的函数称为可行函数
- 寻找最佳匹配:实参转换等级:精确匹配-const转换的匹配-类型提升-算术类型转换-类类型转换
函数指针
函数名作为一个值使用,该函数自动转换成指针
声明一个指向函数的指针,需包括返回类型和形参类型
1 |
|
使用函数指针时,解引用与不解引用指针等价。bool flag = pf("hh","kk");
等价于bool flag = (*pf)("hh","kk");
把函数指针用作形参时,使用函数定义和使用函数指针定义等价
1 |
|
另外,可以用类型别名或者decltype简化需要用到函数指针的函数声明
函数指针当返回值
普通版本
1
int (*f(int))(int*,int);
首先,这是一个函数指针,且函数形参为int类型,指针本身也有形参列表,所以该指针指向函数,函数返回类型为int
类型别名
1
2
3
4
5using F = int(int* ,int); //F是函数类型
using PF = int(*)(int* ,int); //PF是函数指针类型
PF f(int);
F* f(int); //两种均等价于普通版本尾置返回类型
与返回数组指针类型类似
1
auto f(int) ->int(*)(int*,int);
auto和decltype
如果明确知道返回函数,可以使用decltype简化
1
decltype(f1)* f(int* ,int); //f1即为需要返回的函数类型
第七章——类
类的基本思想是数据抽象 和封装
数据抽象依赖于接口 和实现 技术
定义在类内部的函数都是隐式的inline函数
1 |
|
this指针
成员函数通过this隐式参数来访问调用的对象,当调用成员函数时,隐式地先将类对象的地址初始化this
struct *const this
,this是一个指针常量,指针指向不变,即一直保存对象的地址
成员函数
目的并非通用,属于类的实现部分
成员函数的声明需在类内,函数声明在其他成员之后,因此不用考虑函数体和其他成员的次序
默认情况下,this是指向非常量的指针常量,意味着不能将this绑定到常量对象上,则无法正常使用到该成员函数,因此应该在函数声明时,在函数形参列表后函数体前加上const,std::string isbn() const{return bookNo;}
1 |
|
返回this对象的函数
return *this
语句返回调用该函数的对象,令函数能够被连续调用
1 |
|
非成员函数
属于类的接口的组成部分,非成员函数声明与类声明在同一个文件,但不在类内
1 |
|
1 |
|
1 |
|
构造函数
- 构造函数是特殊的成员函数
- 无返回值
- 构造函数放在类的
public
部分 - 直到构造函数完成,对象才会有常量属性,因此构造函数可以向const对象写值
=default
要求编译器合成默认的构造函数- 初始化列表:冒号和花括号之间的代码:
Sales_item(): units_sold(0), revenue(0.0) { }
class和struct默认访问权限不同,struct则在第一个访问说明符之前的成员是public的,class则是private
友元
允许特定的非成员函数访问一个类的私有成员,通常将友元声明成组地放在类定义的开始或者结尾
必须在友元声明之外再对函数进行一次声明
1 |
|
如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员
也可以只对声明另一个类中的某个成员函数为友元,并说明该成员函数的作用域
若想要把一组重载函数声明为友元,则需要对每一个分别声明
类的其他特性
用来定义类型的成员必须先定义后使用
1 |
|
定义在类内部的成员函数都是自动inline的,可以在类内部显式inline声明,也能在类外部定义的时候说明inline
1 |
|
若希望能修改类的const成员函数中的某个成员变量,可以通过变量声明前加入mutable实现
1 |
|
返回*this的成员函数,返回对象本身,则可以连续调用
1 |
|
基于const的重载
若有常量成员函数,即使返回对象本身,也不能与非常量成员函数一起连续调用,则应该对该常量成员函数进行非常量版本的重载
1 |
|
以上,display函数有常量和非常量版本,对于常量版本,调用do_display函数,常量对象再被调用成常量对象,即直接对常量对象操作,常量对象传递到函数内部并打印并返回常量对象;对于非常量版本,调用do_display函数,非常量对象被传递,此时传递的是对该非常量对象的常量调用,传递到函数内部并打印,返回该调用的常量对象,即原来的非常量对象。也就是说,将非常量对象通过多一次的函数调用统一成常量对象的调用,而对常量对象不影响。
每个类都是唯一的类型,类在声明后定义前,是一个不完全类型,可以定义指向给类的指针或引用,也可以声明以该类类型作为参数或返回类型的函数。
Person类程序
编写一个Person类,表示人员的姓名和住址,string来存放,能够返回姓名和住址,能够读取和打印Person对象,构造函数
1 |
|
函数的返回类型通常在函数名前面,因此当成员函数定义在类的外部时,返回类型中使用的名字都位于类的作用域之外
如果成员是const、引用,或者属于某种未提供默认构造函数的类类型,则必须通过构造函数初始值列表为这些成员提供初值
1 |
|
如果一个构造函数为所有参数都提供了默认参数,那么它实际上也定义了默认的构造函数
委托构造函数
委托构造函数使用所属类中其他的构造函数来执行自己的初始化过程,受委托的构造函数的初始值列表和函数体被依次执行,最后再执行委托者的函数体
隐式的类类型转换
如果构造函数只接受一个实参,则实际上定义了转换为此类类型的隐式转换机制,编译器只会自动地执行仅一步类型转换
1 |
|
string实参传入combine成员函数时,string会自动创建一个Sales_data对象
编译器只会执行一步类型转换
1 |
|
通过对成员函数声明explicit可以阻止隐式类型转换,explicit只能出现在类内函数的声明中。但可以使用构造函数进行显式的类型转换
1 |
|
explicit
构造函数只能用于直接初始化,不能用于拷贝形式的初始化
聚合类 (aggregate class)
- 满足以下所有条件:
- 所有成员都是
public
的。 - 没有定义任何构造函数。
- 没有类内初始值。
- 没有基类,也没有
virtual
函数。
- 所有成员都是
- 可以使用一个花括号括起来的成员初始值列表,初始值的顺序必须和声明的顺序一致。
字面值常量类
constexpr
函数的参数和返回值必须是字面值。- 字面值类型:除了算术类型、引用和指针外,某些类也是字面值类型。
- 数据成员都是字面值类型的聚合类是字面值常量类。
- 如果不是聚合类,则必须满足下面所有条件:
- 数据成员都必须是字面值类型。
- 类必须至少含有一个
constexpr
构造函数。 - 如果一个数据成员含有类内部初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果成员属于某种类类型,则初始值必须使用成员自己的
constexpr
构造函数。 - 类必须使用析构函数的默认定义,该成员负责销毁类的对象。
静态成员
- 非
static
数据成员存在于类类型的每个对象中。 - 成员函数可以直接使用静态成员
- 每个
static
数据成员是与类关联的对象,并不与该类的对象相关联。 - 声明:
- 声明之前加上关键词
static
。
- 声明之前加上关键词
- 使用:
- 成员算符**
::
直接访问静态成员:r = Account::rate();
- 也可以使用对象访问:
r = ac.rate();
- 成员算符**
- 定义:在类外部定义时不用加
static
。 - 初始化:
- 通常不在类的内部初始化,而是在定义时进行初始化,如
double Account::interestRate = initRate();
- 如果一定要在类内部定义,则要求必须是字面值常量类型的
constexpr
。
- 通常不在类的内部初始化,而是在定义时进行初始化,如
第二部分
第八章——IO库
标准库定义的IO类型
iostream
头文件:从标准流中读写数据,istream
、ostream
等。fstream
头文件:从文件中读写数据,ifstream
、ofstream
等。sstream
头文件:从字符串中读写数据,istringstream
、ostringstream
IO对象不可复制或赋值
- 1.IO对象不能存在容器里.
- 2.形参和返回类型也不能是流类型
- 3.形参和返回类型一般是流的引用
- 4.读写一个IO对象会改变其状态,因此传递和返回的引用不能是
const
的
条件状态
strm
是一种IO类型,(如istream
), s
是一个流对象
状态 | 解释 |
---|---|
strm:iostate |
是一种机器无关的类型,提供了表达条件状态的完整功能 |
strm:badbit |
用来指出流已经崩溃 |
strm:failbit |
用来指出一个IO操作失败了 |
strm:eofbit |
用来指出流到达了文件结束 |
strm:goodbit |
用来指出流未处于错误状态,此值保证为零 |
s.eof() |
若流s 的eofbit 置位,则返回true |
s.fail() |
若流s 的failbit 置位,则返回true |
s.bad() |
若流s 的badbit 置位,则返回true |
s.good() |
若流s 处于有效状态,则返回true |
s.clear() |
将流s 中所有条件状态位复位,将流的状态设置成有效,返回void |
s.clear(flags) |
将流s 中指定的条件状态位复位,返回void |
s.setstate(flags) |
根据给定的标志位,将流s 中对应的条件状态位置位,返回void |
s.rdstate() |
返回流s 的当前条件状态,返回值类型为strm::iostate |
从给定流中读取数据,直至遇到文件结束符时停止,然后对流进行按所需位复位
1 |
|
管理输出缓冲
缓冲刷新的原因
程序正常结束
缓冲区满
endl显式刷新缓冲区
操纵符unitbuf设置流的内部状态来清空缓冲区,对cerr默认设置unitbuf,即cerr是立即刷新的
1
2cout << unitbuf; //所有输出操作都会立即刷新缓冲区
cout << nounitbuf; //恢复正常缓冲方式当读写被关联的流时,关联到的流的缓冲区会被刷新。如cin和cerr关联到cout,读cin或写cerr都导致cout刷新缓冲区
flush刷新缓冲区,不附加任何额外字符
ends插入一个空字符,再刷新缓冲区
交互式系统通常应该关联输入流和输出流,则所有输出包括用户提示,都会在读操作之前被打印出来
文件输入输出
fstream
是头文件fstream
中定义的一个类型,fstrm
是一个文件流对象
操作 | 解释 |
---|---|
fstream fstrm; |
创建一个未绑定的文件流。 |
fstream fstrm(s); |
创建一个文件流,并打开名为s 的文件,s 可以是string 也可以是char 指针 |
fstream fstrm(s, mode); |
与前一个构造函数类似,但按指定mode 打开文件 |
fstrm.open(s) |
打开名为s 的文件,并和fstrm 绑定 |
fstrm.close() |
关闭和fstrm 绑定的文件 |
fstrm.is_open() |
返回一个bool 值,指出与fstrm 关联的文件是否成功打开且尚未关闭 |
在要求用到基类对象的地方,可以用继承类型对象来替代。则接受iostream引用参数的函数可以用一个fstream类型调用
当一个fstream对象被销毁时,close自动会被调用
文件模式 | 解释 |
---|---|
in |
以读的方式打开 |
out |
以写的方式打开 |
app |
每次写操作前均定位到文件末尾 |
ate |
打开文件后立即定位到文件末尾 |
trunc |
截断文件 |
binary |
以二进制方式进行IO操作。 |
默认情况下,out模式打开会是trunc模式,out模式打开同时指定app模式或者in模式才不会被截断
string流
sstream
是头文件sstream
中任意一个类型。s
是一个string
操作 | 解释 |
---|---|
sstream strm |
定义一个未绑定的stringstream 对象 |
sstream strm(s) |
用s 初始化对象 |
strm.str() |
返回strm 所保存的string 的拷贝 |
strm.str(s) |
将s 拷贝到strm 中,返回void |
istringstream
对整行文本进行读取,对其中行内的单词进行处理,则可以使用istringstream
1 |
|
第九章——顺序容器
value_type
元素类型
reference
元素的左值类型,和value_type &
含义相同
const_reference
元素的const
左值类型,即const value_type &
迭代器
forward list
迭代器不支持递减运算符
将一个容器复制给另一个容器时,类型必须匹配:容器类型和元素类型都必须相同;而当使用迭代器来拷贝一个范围时,就不要求容器类型相同
array容器
定义array时,不仅要指定元素类型,还要指定容器大小,array<int,42> arr
,默认构造的array是非空的,包含了与其大小一样多的元素
不能对内置数组进行拷贝或对象赋值操作,但array可以
1 |
|
赋值和swap
c.assign(b, e)
将c
中的元素替换成迭代器b
和e
表示范围中的元素,b
和e
不能指向c
中的元素
c.assign(il)
将c
中的元素替换成初始化列表il
中的元素
c.assign(n, r)
将c
中的元素替换为n
个值是t
的元素
array容器不能使用assign;由于值列表对象的大小与原对象大小不一样,也不允许用花括号包围的值列表进行赋值。
1 |
|
赋值运算会导致指向左边容器的迭代器、引用和指针都失效,swap则不会(array和string除外)这是因为swap不对元素进行交换,只交换两个容器内部的数据结构,时间复杂度为O(1)。而swap对string会导致失效;对array进行swap会真正交换它们的元素
容器操作——insert
c.insert(p, t)
在迭代器p
指向的元素之前创建一个值是t
的元素,返回指向新元素的迭代器
c.insert(p, n, t)
在迭代器p
指向的元素之前插入n
个值为t
的元素,返回指向第一个新元素的迭代器;如果n
是0,则返回p
c.insert(p, b, e)
将迭代器b
和e
范围内的元素,插入到p
指向的元素之前;如果范围为空,则返回p
c.insert(p, il)
il
是一个花括号包围中的元素值列表,将其插入到p
指向的元素之前;如果il
是空,则返回p
insert
函数将元素插入到迭代器指定位置之前,时间复杂度为O(n),对于list时间复杂度则为O(1)
向vector
,string
,deque
容器中插入元素都会使所有迭代器、指针和引用失效,向deque首尾位置添加元素,迭代器失效,但指针和引用不失效
利用insert的返回值,可以在容器中一个特定位置反复插入元素
1 |
|
容器操作——emplace
emplace_back(args)
在尾部创建一个由args创建的元素
emplace_front(args)
在头部创建一个由args创建的元素
emplace(p,args)
在迭代器p指向的元素之前创建一个由args创建的元素,返回指向新添加元素的迭代器
emplace
开头的函数是新标准引入的,这些操作是构造而不是拷贝元素,传递给emplace
的参数必须和元素类型的构造函数相匹配
1 |
|
上述语句都会创建新的Sales_data对象,在调用emplace_back时,会在容器管理的内存空间中直接创建对象,而调用push_back则会创建一个局部临时对象,并将其压入容器中。
访问元素
操作 | 解释 |
---|---|
c.back() |
返回c 中尾元素的引用。若c 为空,函数行为未定义 |
c.front() |
返回c 中头元素的引用。若c 为空,函数行为未定义 |
c[n] |
返回c 中下标是n 的元素的引用,n 时候一个无符号证书。若n>=c.size() ,则函数行为未定义 |
c.at(n) |
返回下标为n 的元素引用。如果下标越界,则抛出out_of_range 异常 |
访问成员函数返回的是引用
如果希望确保下标是合法的,可以使用at成员函数
删除元素
c.erase(p)
删除迭代器p
指向的元素,返回一个指向被删除元素之后的元素的迭代器,若p
本身是尾后迭代器,则函数行为未定义
c.erase(b, e)
删除迭代器b
和e
范围内的元素,返回指向最后一个被删元素之后元素的迭代器,若e
本身就是尾后迭代器,则返回尾后迭代器
删除deque中除首尾位置之外的任何元素都会使所有迭代器、引用和指针失效,删除deque的尾元素会导致尾后迭代器失效。指向vector、string被删除元素位置之后的迭代器、引用和指针都会失效
forward_list
容器
操作 | 解释 |
---|---|
lst.before_begin() |
返回指向链表首元素之前不存在的元素的迭代器,此迭代器不能解引用。 |
lst.cbefore_begin() |
同上,但是返回的是常量迭代器。 |
lst.insert_after(p, t) |
在迭代器p 之后插入元素。t 是一个对象。返回最后一个插入元素的迭代器 |
lst.insert_after(p, n, t) |
在迭代器p 之后插入元素。t 是一个对象,n 是数量。若n 是0则函数行为未定义 |
lst.insert_after(p, b, e) |
在迭代器p 之后插入元素。由迭代器b 和e 指定范围,不能指向lst内 |
lst.insert_after(p, il) |
在迭代器p 之后插入元素。由il 指定初始化列表。 |
emplace_after(p, args) |
使用args 在p 之后的位置,创建一个元素,返回一个指向这个新元素的迭代器。若p 为尾后迭代器,则函数行为未定义。 |
lst.erase_after(p) |
删除p 指向位置之后的元素,返回一个指向被删元素之后的元素的迭代器,若p 指向lst 的尾元素或者是一个尾后迭代器,则函数行为未定义。 |
lst.erase_after(b, e) |
类似上面,删除对象换成从b 到e 指定的范围。 |
当在forward_list
中添加或删除元素时,需要关注两个迭代器,指向当前元素和指向其前驱
1 |
|
删除奇数元素程序
编写程序,查找并删除forward_list<int>
中的奇数元素
1 |
|
remove_if
是list容器的特定算法,让谓词为真时删除该元素
容器大小管理操作
调用reserve
不会减少容器所占用的内存空间,即capacity
,将会大于或等于当前的容量大小传递给reserve
的参数;resize
只会改变容器中元素的数量,即size
而不改变容器的容量;shrink_to_fit
则会请求退回多余的内存空间,但不保证一定退回
string的额外操作
构造string
操作 | 解释 |
---|---|
string s(cp, n) |
s 是cp 指向的数组中前n 个字符的拷贝,此数组 |
string s(s2, pos2) |
s 是string s2 从下标pos2 开始的字符的拷贝。若pos2 > s2.size() ,则构造函数的行为未定义。 |
string s(s2, pos2, len2) |
s 是string s2 从下标pos2 开始的len2 个字符的拷贝。 |
从一个const char*
创建string时,指针指向的数组必须以空字符结尾,拷贝操作遇到空字符串时停止;如果构造函数中还有计数值作为形参,数组则不必以空字符结尾
substr操作
s.substr(pos, n)
返回一个string
,包含s
中从pos
开始的n
个字符的拷贝。pos
的默认值是0,n
的默认值是s.size() - pos
,即拷贝从pos
开始的所有字符
改变string的其他方法
操作 | 解释 |
---|---|
s.insert(pos, args) |
在pos 之前插入args 指定的字符。pos 可以使是下标或者迭代器。接受下标的版本返回指向s 的引用;接受迭代器的版本返回指向第一个插入字符的迭代器。 |
s.erase(pos, len) |
删除从pos 开始的len 个字符,如果len 被省略,则删除后面所有字符,返回指向s 的引用。 |
s.assign(args) |
将s 中的字符替换成args 指定的字符。返回一个指向s 的引用。 |
s.append(args) |
将args 指定的字符追加到s ,返回一个指向s 的引用。 |
s.replace(range, args) |
删除s 中范围range 中的字符,替换成args 指定的字符。返回一个指向s 的引用。 |
编写一个函数,接受三个string
参数是s
、oldVal
和newVal
。使用迭代器及insert
和erase
函数将s
中所有oldVal
替换为newVal
1 |
|
重写上一题的函数,这次使用一个下标和replace
1 |
|
string的搜索操作
每个操作都接受一个可选的第二参数,表示从什么位置开始搜索
搜索操作 | 解释 |
---|---|
s.find(args) |
查找s 中args 第一次出现的位置 |
s.rfind(args) |
查找s 中args 最后一次出现的位置 |
s.find_first_of(args) |
在s 中查找args 中任何一个字符第一次出现的位置 |
s.find_last_of(args) |
在s 中查找args 中任何一个字符最后一次出现的位置 |
s.find_first_not_of(args) |
在s 中查找第一个不在args 中的字符 |
s.find_first_not_of(args) |
在s 中查找最后一个不在args 中的字符 |
每个搜索操作都返回一个string::size_type
值,表示匹配发生位置的下标。如果搜索失败则返回一个名为string::npos
的static
成员(类型是string::size_type
,初始化值是-1,npos是一个无符号类型,也就是string
最大的可能大小)
string的比较操作
参数形式 | 解释 |
---|---|
s2 |
比较s 和s2 |
pos1, n1, s2 |
比较s 从pos1 开始的n1 个字符和s2 |
pos1, n1, s2, pos2, n2 |
比较s 从pos1 开始的n1 个字符和s2 |
cp |
比较s 和cp 指向的以空字符结尾的字符数组 |
pos1, n1, cp |
比较s 从pos1 开始的n1 个字符和cp 指向的以空字符结尾的字符数组 |
pos1, n1, cp, n2 |
比较s 从pos1 开始的n1 个字符和cp 指向的地址开始n2 个字符 |
数值转换
to_string(val)
一组重载函数,返回数值val
的string
表示。val
可以使任何算术类型。对每个浮点类型和int
或更大的整型,都有相应版本的to_string()
。和往常一样,小整型会被提升。
stoi(s, p, b)
返回s
起始子串(表示整数内容)的数值,p
是s
中第一个非数值字符的下标,默认是0,即函数不保存下标。b
是转换所用的基数,默认是10。返回int
要转换为数值的string中第一个非空表字符必须是数值中可能出现的字符
1 |
|
容器适配器
适配器是使一事物的行为类似于另一事物的行为的一种机制。函数、容器和迭代器都有适配器
三个顺序容器适配器:stack、queue和priority_queue
默认下,stack和queue是基于deque实现的,priority_queue是基于vector实现的,可以在创建一个适配器时将一个命名的顺序容器作为第二个类型参数,实现对默认容器类型的重载
1 |
|
stack需要实现后进先出,即push_back
和pop_back
的操作,则可以使用vector
和deque
实现,而不可以用forward_list
构建
queue需要实现先进先出,即push_back
和pop_front
的操作,那么就不可以使用vector
而可以使用deque
和list
构建
priority_queue需要有随机访问的能力,因此可使用vector
和deque
构建
对于每个容器适配器,只可以使用适配器操作,而不能使用底层容器类型的操作
第十章——泛型算法
泛型算法本身不执行容器操作,只是单独依赖迭代器和迭代器操作实现
算法永远不会改变底层容器的大小。算法可能改变容器中保存的元素的值,也可能在容器内移动元素,但不能直接添加或者删除元素
只读算法
accumulate
(在numeric
中定义),接受三个参数,前两个指出需要求和的元素的范围,第三个是求和的初值。由于在string中定义了“+”运算符,则可以通过accumulate将string元素连接起来;如果传入一个字符串字面值给第三个参数,类型将是const char *
,并没有“+”运算符,则会报错
find_first_of
,输入:两对迭代器标记两段范围,在第一段中找第二段中任意元素,返回第一个匹配的元素,找不到返回第一段的end
迭代器。
对于只读取而不改变元素的算法,通常最好使用cbegin
和cend
equal
:确定两个序列是否保存相同的值,前两个表示第一个序列中的范围,第三个表示第二个序列的首元素。若在传入C风格字符串,则比较的是两个指针指向的地址
只接受单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长
写容器的算法
fill
: fill(vec.begin(), vec.end(), 0);
将每个元素重置为0
fill_n
: fill_n(vec.begin(), 10, 0);
将给定值赋予迭代器指向的元素开始的指定个元素,但要确保容器能容纳要写入的元素
插入迭代器back_inserter
:
用来确保算法有足够的空间存储数据。#include <iterator>
1 |
|
拷贝算法copy
,前两个参数指定输入范围,第三个指向目标序列。传递给copy的目的序列至少要包含与输入序列一样多的元素,返回目的位置迭代器递增后的位置。可以用copy实现对内置数组的拷贝
1 |
|
replace算法将输入序列中所有等于给定值的元素都改为另一个值
1 |
|
上述语句将该序列中所有0替换成42,若希望原序列不变,则用replace_copy
1 |
|
ivec包含ilst的一份拷贝,且将所有0换成42
重排容器元素的算法
1 |
|
向算法传递函数
谓词是一个可调用的表达式,返回结果是一个能用作条件的值
1 |
|
lambda表达式
可以向一个算法传递任何类别的可调用对象,包括函数、函数指针、重载函数调用运算符的类和lambda表达式
lambda
表达式表示一个可调用的代码单元,可以理解成是一个未命名的内联函数
[capture list](parameter list) -> return type {function body}
capture list
捕获列表不可忽略。为空时表明不使用它所在函数中的任何局部变量。捕获列表只用于局部非static变量,lambda可直接使用局部static变量和它所在函数之外声明的名字
return type
是返回类型。可忽略如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void;当需要为一个lambda定义返回类型时,必须使用尾置返回类型
parameter
是参数列表。可忽略function body
是函数体。不可忽略
1 |
|
使用partition算法
1 |
|
lambda捕获和返回
定义lambda
时会生成一个新的类类型和该类型的一个对象
当以引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的
隐式捕获:&告诉编译器采用捕获引用方式,=则表示值捕获方式
1 |
|
混合使用隐式捕获和显式捕获时,捕获列表第一个元素必须是一个&或=,显式捕获的变量必须使用与隐式捕获不同的方式,即如果隐式捕获采用引用方式(&),则显式捕获命名变量必须采用值方式。
可变lambda
如果希望能改变一个被捕获的变量的值(不改变原来的值),就必须在参数列表首加上mutable
1 |
|
参数绑定
标准库bind函数,定义在头文件functional
中,可以看做为一个通用的函数适配器。接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表
auto newCallable = bind(callable, arg_list);
arg_list
中的参数可能包含形如_n
的名字,其中n是一个整数,这些是占位符。_n
代表第n个位置的参数。定义在placeholders
的命名空间中。using std::placeholder::_1;
或using namespace std::placeholders;
使得由placeholders
定义的所有名字都可用
1 |
|
想要将这个函数作为find_if
(接受一个一元谓词)的参数,则可以绑定check_size
的sz参数
1 |
|
auto g = bind(f, a, b, _2, c, _1);
,调用g(_1, _2)
实际上调用f(a, b, _2, c, _1)
非占位符的参数要使用引用传参,必须使用标准库ref
函数或者cref
函数
1 |
|
插入迭代器
插入器是一种迭代器适配器,接受一个容器,生成一个迭代器,能实现向给定容器添加元素
back_inserter
:创建一个使用push_back
的迭代器。front_inserter
创建一个使用push_front
的迭代器。元素总是插入到容器的第一个元素之前inserter
创建一个使用insert
的迭代器。接受第二个参数,即一个指向给定容器的迭代器,元素会被查到迭代器所指向的元素之前
1 |
|
front_inserter
生成的迭代器会将插入的元素序列的顺序颠倒过来,而inserter
和back_inserter
则不会
流迭代器
迭代器可与输入或输出流绑定在一起,用于迭代遍历所关联的 IO 流
1 |
|
ostream_iterator提供可选第二参数,是一个C风格字符串,每各元素输出后都打印此字符串
1 |
|
其中解引用和递增运算可以忽略,不产生作用。
反向迭代器
递增一个反向迭代器会移动到前一个元素
1 |
|
当需要打印出第一个和最后一个单词时
1 |
|
特定容器算法
对于list
和forward_list
,优先使用成员函数版本的算法而不是通用算法
操作 | 解释 |
---|---|
lst.merge(lst2) |
将来自lst2 的元素合并入lst ,二者都必须是有序的,元素将从lst2 中删除。 |
lst.merge(lst2, comp) |
同上,给定比较操作。 |
lst.remove(val) |
调用erase 删除掉与给定值相等(==)的每个元素 |
lst.remove_if(pred) |
调用erase 删除掉令一元谓词为真的每个元素 |
lst.reverse() |
反转lst 中元素的顺序 |
lst.sort() |
使用< 排序元素 |
lst.sort(comp) |
使用给定比较操作排序元素 |
lst.unique() |
调用erase 删除同一个值的连续拷贝。使用== 。 |
lst.unique(pred) |
调用erase 删除同一个值的连续拷贝。使用给定的二元谓词。 |
list和forward_list的splice成员函数版本的参数:
参数 | 解释 |
---|---|
(p, lst2) |
p 是一个指向lst 中元素的迭代器,或者一个指向flst 首前位置的迭代器。函数将lst2 中的所有元素移动到lst 中p 之前的位置或是flst 中p 之后的位置。将元素从lst2 中删除。lst2 的类型必须和lst 相同,而且不能是同一个链表。 |
(p, lst2, p2) |
同上,p2 是一个指向lst2 中位置的有效的迭代器,将p2 指向的元素移动到lst 中,或将p2 之后的元素移动到flst 中。lst2 可以是于lst 或flst 相同的链表。 |
(p, lst2, b, e) |
b 和e 表示lst2 中的合法范围。将给定范围中的元素从lst2 移动到lst 或first 中。lst2 与lst 可以使相同的链表,但p 不能指向给定范围中的元素。 |
- 使用
lst.splice(args)
或flst.splice_after(args)
链表特有版本与通用版本的区别在于链表版本会改变底层的容器
第十一章——关联容器
关联容器中的元素时按照关键字来保存和访问的,支持通过关键字来高效地查找和读取元素
集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
---|---|---|---|---|---|---|
std::map | 红黑树 | key有序 | key不可重复 | key不可修改 | O(logn) | O(logn) |
std::multimap | 红黑树 | key有序 | key可重复 | key不可修改 | O(log n) | O(log n) |
std::unordered_map | 哈希表 | key无序 | key不可重复 | key不可修改 | O(1) | O(1) |
关键字类型的要求
对于有序容器,关键字类型必须定义元素比较的方法。默认是<
对于自定义类型,如果想传递一个比较的函数,所提供的操作必须在关键字类型上定义一个严格弱序(小于等于),可以这样定义,提供关键字类型(Sales_data)以及比较操作类型(函数指针类型)
1 |
|
用compareIsbn
来初始化bookstore
对象,表明在向对象添加元素时,将调用compareIsbn
函数来进行排序。且可以代替&compareIsbn
,当使用一个函数名字时,会自动转换成指针类型
pair类型
在utility
头文件中定义
操作 | 解释 |
---|---|
pair<T1, T2> p; |
p 是一个pair ,两个类型分别是T1 和T2 的成员都进行了值初始化。 |
pair<T1, T2> p(v1, v2); |
first 和second 分别用v1 和v2 进行初始化。 |
pair<T1, T2>p = {v1, v2}; |
等价于`p(v1, v2) |
make_pair(v1, v2); |
pair 的类型从v1 和v2 的类型推断出来。 |
当函数需要返回一个pair时,可以直接对返回值进行列表初始化
return {v.back(),v.back().size()};
关联容器操作
类型别名 | 解释 |
---|---|
key_type |
此容器类型的关键字类型 |
mapped_type |
每个关键字关联的类型,只适用于map |
value_type |
对于map ,是pair<const key_type, mapped_type> ; 对于set ,和key_type 相同。 |
关联容器迭代器
解引用一个关联容器迭代器时,会得到一个类型为容器的value_type
的值的引用
虽然set定义了iterator
和const_iterator
类型,但两种类型都只允许只读访问
遍历map
、multimap
、set
、multiset
时,迭代器按关键字升序遍历元素
使用关联容器定义的专用算法会比泛型算法快很多
insert 操作 |
关联容器 |
---|---|
c.insert(v) c.emplace(args) |
v 是value_type 类型的对象;args 用来构造一个元素。 对于map 和set ,只有元素的关键字不存在c 中才插入或构造元素。函数返回一个pair ,包含一个迭代器,指向具有指定关键字的元素,以及一个指示插入是否成功的bool 值。对于multimap 和multiset 则会插入范围中的每个元素。 |
c.insert(b, e) c.insert(il) |
b 和e 是迭代器,表示一个c::value_type 类型值的范围;il 是这种值的花括号列表。函数返回void 。对于 map 和set ,只插入关键字不在c 中的元素。 |
c.insert(p, v) c.emplace(p, args) |
类似insert(v) ,但将迭代器p 作为一个提示,指出从哪里开始搜索新元素应该存储的位置。返回一个迭代器,指向具有给定关键字的元素。 |
向map
添加元素:
1 |
|
以上四种添加方式等价
1 |
|
这条语句等价于
1 |
|
insert
成功时,返回指向该元素位置的迭代器,并对其值加一操作,即insert
一个{word,1};
insert
失败时,返回的是指向map容器中已有的该元素位置的迭代器,并对其值加一操作
删除元素
操作 | 解释 |
---|---|
c.erase(k) |
从c 中删除每个关键字为k 的元素。返回一个size_type 值,指出删除的元素的数量。 |
c.erase(p) |
从c 中删除迭代器p 指定的元素。p 必须指向c 中一个真实元素,不能等于c.end() 。返回一个指向p 之后元素的迭代器,若p 指向c 中的尾元素,则返回c.end() |
c.erase(b, e) |
删除迭代器对b 和e 所表示范围中的元素。返回e 。 |
下标操作
只有**map
和unordered_map(非const)
有下标运算符和对应的at函数**,set类型不支持下标,multimap
或unordered_multimap
可能有多个值与关键字相关联,也不支持下标操作
如果使用下标操作时,关键字并不在map中,会为它创建一个元素并插入到map中,关联值将进行值初始化
访问查找元素
操作 | 解释 |
---|---|
c.find(k) |
返回一个迭代器,指向第一个关键字为k 的元素,若k 不在容器中,则返回尾后迭代器 |
c.count(k) |
返回关键字等于k 的元素的数量。对于不允许重复关键字的容器,返回值永远是0或1。 |
c.lower_bound(k) |
返回一个迭代器,指向第一个关键字不小于k 的元素。 |
c.upper_bound(k) |
返回一个迭代器,指向第一个关键字大于k 的元素。 |
c.equal_range(k) |
返回一个迭代器pair ,表示关键字等于k 的元素的范围。若k 不存在,pair 的两个成员均等于c.end() 。 |
lower_bound
和upper_bound
不适用于无序容器
打印作者的所有著作
使用find和count函数
1 |
|
使用lower_bound
和upper_bound
1 |
|
使用equal_range
1 |
|
单词转换程序???
here is the link to [istringistream](# istringstream)
通过读取map_file文件获得单词转换规则,在读入input文件过程中将根据映射返回转换后的内容,即打印出来
1 |
|
无序容器
有序容器使用比较运算符来组织元素;无序容器使用哈希函数和关键字类型的==
运算符
无序容器在存储上组织为一组桶(bucket),每个桶保存零个或多个元素。无序容器使用一个哈希函数将元素映射到桶
操作 | 解释 |
---|---|
桶接口 | |
c.bucket_count() |
正在使用的桶的数目 |
c.max_bucket_count() |
容器能容纳的最多的桶的数目 |
c.bucket_size(n) |
第n 个桶中有多少个元素 |
c.bucket(k) |
关键字为k 的元素在哪个桶中 |
桶迭代 | |
local_iterator |
可以用来访问桶中元素的迭代器类型 |
const_local_iterator |
桶迭代器的const 版本 |
c.begin(n) ,c.end(n) |
桶n 的首元素迭代器 |
c.cbegin(n) ,c.cend(n) |
与前两个函数类似,但返回const_local_iterator 。 |
哈希策略 | |
c.load_factor() |
每个桶的平均元素数量,返回float 值。 |
c.max_load_factor() |
c 试图维护的平均比桶大小,返回float 值。c 会在需要时添加新的桶,以使得load_factor<=max_load_factor |
c.rehash(n) |
重组存储,使得bucket_count>=n ,且bucket_count>size/max_load_factor |
c.reverse(n) |
重组存储,使得c 可以保存n 个元素且不必rehash 。 |
无序容器的性能依赖于哈希函数的质量和桶的数量和大小
无序类型对关键字类型的要求
不能直接定义关键字类型为自定义类类型的无序容器,需要提供自定义的哈希值函数以及提供函数替代==
运算符
1 |
|
第十二章——动态内存
- 静态内存用来保存局部
static
对象、类static
对象、定义在任何函数之外的变量(全局)。 - 栈内存用来保存定义在函数内的非
static
对象(局部)。 - 堆内存,又称自由空间,用来存储动态分配的对象,即在程序运行时分配的对象,需要显式地销毁
动态内存管理通过new
为对象分配空间并返回一个指向该对象的指针,通过delete
接收一个动态对象的指针,销毁对象并释放内存。
shared_ptr
shared_ptr和unique_ptr都支持的操作:
操作 | 解释 |
---|---|
shared_ptr<T> sp unique_ptr<T> up |
空智能指针,可以指向类型是T 的对象 |
p |
将p 用作一个条件判断,若p 指向一个对象,则为true |
*p |
解引用p ,获得它指向的对象。 |
p->mem |
等价于(*p).mem |
p.get() |
返回p 中保存的指针,要小心使用,若智能指针释放了对象,返回的指针所指向的对象也就消失了。 |
swap(p, q) p.swap(q) |
交换p 和q 中的指针 |
shared_ptr独有的操作:
操作 | 解释 |
---|---|
make_shared<T>(args) |
返回一个shared_ptr ,指向一个动态分配的类型为T 的对象。使用args 初始化此对象。 |
shared_ptr<T>p(q) |
p 是shared_ptr q 的拷贝;此操作会递增q 中的计数器。q 中的指针必须能转换为T* |
p = q |
p 和q 都是shared_ptr ,所保存的指针必须能互相转换。此操作会递减p 的引用计数,递增q 的引用计数;若p 的引用计数变为0,则将其管理的原内存释放。 |
p.unique() |
若p.use_count() 是1,返回true ;否则返回false |
p.use_count() |
返回与p 共享对象的智能指针数量;可能很慢,主要用于调试。 |
make_shared
用其参数来构造给定类型的对象
1 |
|
每个shared_ptr
都有一个关联的计数器,称为引用计数,记录有多少个shared_ptr
指向相同的对象。当指向一个对象的最后一个shared_ptr
被销毁时,会自动销毁此对象,即释放相关联的内存。
直接管理内存
用new动态分配和初始化对象
1 |
|
对于内置类型来说,值初始化有定义的值,默认初始化的对象的值则是未定义;对于定义了构造函数的类类型,都会通过默认构造函数来初始化
由于要用初始化器的类型来推断要分配的类型,只有当括号中有单一初始化器才可以使用auto
1 |
|
默认下,new不能分配所要求的内存空间,会抛出bad_alloc
的异常,可以将nothrow
传递给new阻止其抛出异常
1 |
|
delete
要求必须指向动态分配的内存或者是一个空指针
shared_ptr和new结合使用
接受指针参数的智能指针构造函数是explicit
的,不能将一个内置指针隐式转换成智能指针,只能直接初始化,即用new返回的指针来初始化
1 |
|
定义和改变shared_ptr的其他方法:
操作 | 解释 |
---|---|
shared_ptr<T> p(q) |
p 管理内置指针q 所指向的对象;q 必须指向new 分配的内存,且能够转换为T* 类型 |
shared_ptr<T> p(u) |
p 从unique_ptr u 那里接管了对象的所有权;将u 置为空 |
shared_ptr<T> p(q, d) |
p 接管了内置指针q 所指向的对象的所有权。q 必须能转换为T* 类型。p 将使用可调用对象d 来代替delete 。 |
shared_ptr<T> p(p2, d) |
p 是shared_ptr p2 的拷贝,唯一的区别是p 将可调用对象d 来代替delete 。 |
p.reset() |
若p 是唯一指向其对象的shared_ptr ,reset 会释放此对象。若传递了可选的参数内置指针q ,会令p 指向q ,否则会将p 置空。若还传递了参数d ,则会调用d 而不是delete 来释放q 。 |
p.reset(q) |
同上 |
p.reset(q, d) |
同上 |
get函数返回一个内置指针,指向智能指针管理的对象。只有在确定代码不会delete指针的情况下才使用。
1 |
|
不使用get()初始化或reset另一个智能指针
1 |
|
p.get()
返回内置指针,然后创建了新的临时shared_ptr
智能指针,也是指向p指向的对象,但这是新的shared_ptr
,此时计数值为1,当执行完该语句后,临时智能指针被销毁,所指向的内存也被释放,则原有的p指针成为空悬指针,再对p进行操作会出现错误
只有shared_ptr<int> p2(p)
,此时p2与p共同指向同一个对象,计数值为2
使用自己的释放操作
如果你使用智能指针管理的资源不是new
分配的内存,记住传递给它一个删除器
1 |
|
当p被销毁时,会调用删除器函数end_connection
,即使发生异常,p同样会被销毁从而关闭连接
unique_ptr
某一个时刻只能有一个unique_ptr
指向一个给定的对象,不支持拷贝或者赋值操作
unique_ptr操作:
操作 | 解释 |
---|---|
unique_ptr<T> u1 |
空unique_ptr ,可以指向类型是T 的对象。u1 会使用delete 来是释放它的指针。 |
unique_ptr<T, D> u2 |
u2 会使用一个类型为D 的可调用对象来释放它的指针。 |
unique_ptr<T, D> u(d) |
空unique_ptr ,指向类型为T 的对象,用类型为D 的对象d 代替delete |
u = nullptr |
释放u 指向的对象,将u 置为空。 |
u.release() |
u 放弃对指针的控制权,返回指针,并将u 置空。 |
u.reset() |
释放u 指向的对象 |
u.reset(q) |
令u 指向q 指向的对象 |
u.reset(nullptr) |
将u 置空 |
可以通过调用release
或reset
将指针的所有权转移给另一个unique_ptr
1 |
|
可以拷贝或赋值一个将要被销毁的unique_ptr
,即通过函数的返回值返回
与shared_ptr
不同,unique_ptr
管理删除器的方式必须在指向类型之后提供删除器类型(可调用对象)
1 |
|
weak_ptr
操作 | 解释 |
---|---|
weak_ptr<T> w |
空weak_ptr 可以指向类型为T 的对象 |
weak_ptr<T> w(sp) |
与shared_ptr 指向相同对象的weak_ptr 。T 必须能转换为sp 指向的类型。 |
w = p |
p 可以是shared_ptr 或一个weak_ptr 。赋值后w 和p 共享对象。 |
w.reset() |
将w 置为空。 |
w.use_count() |
与w 共享对象的shared_ptr 的数量。 |
w.expired() |
若w.use_count() 为0,返回true ,否则返回false |
w.lock() |
如果expired 为true ,则返回一个空shared_ptr ;否则返回一个指向w 的对象的shared_ptr 。 |
创建一个weak_ptr
时,需要用shared_ptr
来初始化它
1 |
|
wk和p指向相同的对象,但wk不影响p的引用计数。wk指向的对象可能会被释放掉
StrBlob和StrBlobPtr程序
1 |
|
定义StrBlob
类,用shared_ptr
存储数组,提供访问元素和始末迭代器接口,声明friend
1 |
|
核查指针类,定义一个weak_ptr
与StrBlob
共享存储数组,要么指向为空,要么指向vector
,但不增加引用数
bool operator!=(const StrBlobPtr& p) { return p.curr != curr; }
重载运算符!=
判断两个指针类是否相等
1 |
|
对两个类的使用,类似于指针与指针指向对象的操作
动态数组
new
一个动态数组:类型名之后加一对方括号,指明分配的对象数目(必须是整型,不必是常量),返回指向第一个元素类型的指针,而不是数组类型
1 |
|
因为在值初始化的括号中未能提供初始化器,因此不能用auto
当用new分配一个大小为0的数组时,返回一个合法的非空指针,但不能解引用,类似于尾后迭代器
delete
一个动态数组:
delete [] p;
智能指针和动态数组
1 |
|
指向数组的unique_ptr
不支持成员访问运算符(点和箭头),可以用下标访问数组中的元素
shared_ptr
不直接支持管理动态数组,需要提供自定义的删除器,且shared_ptr
未定义下标运算符,则需要通过get函数获取一个内置指针来访问数组元素
1 |
|
allocator类
定义在头文件memory
中,将内存分配和对象构造分离开
标准库allocator类及其算法:
操作 | 解释 |
---|---|
allocator<T> a |
定义了一个名为a 的allocator 对象,它可以为类型为T 的对象分配内存 |
a.allocate(n) |
分配一段原始的、未构造的内存,保存n 个类型为T 的对象。 |
a.deallocate(p, n) |
释放从T* 指针p 中地址开始的内存,这块内存保存了n 个类型为T 的对象;p 必须是一个先前由allocate 返回的指针。且n 必须是p 创建时所要求的大小。在调用deallocate 之前,用户必须对每个在这块内存中创建的对象调用destroy 。 |
a.construct(p, args) |
p 必须是一个类型是T* 的指针,指向一块原始内存;args 被传递给类型为T 的构造函数,用来在p 指向的内存中构造一个对象。 |
a.destroy(p) |
p 为T* 类型的指针,此算法对p 指向的对象执行析构函数。 |
allocator伴随算法:
操作 | 解释 |
---|---|
uninitialized_copy(b, e, b2) |
从迭代器b 和e 给定的输入范围中拷贝元素到迭代器b2 指定的未构造的原始内存中。b2 指向的内存必须足够大,能够容纳输入序列中元素的拷贝。返回目的位置迭代器,指向最后一个构造的元素之后的位置 |
uninitialized_copy_n(b, n, b2) |
从迭代器b 指向的元素开始,拷贝n 个元素到b2 开始的内存中。 |
uninitialized_fill(b, e, t) |
在迭代器b 和e 执行的原始内存范围中创建对象,对象的值均为t 的拷贝。 |
uninitialized_fill_n(b, n, t) |
从迭代器b 指向的内存地址开始创建n 个对象。b 必须指向足够大的未构造的原始内存,能够容纳给定数量的对象。 |
1 |
|
文本查询程序
- 当程序读取输入文件时,需记住单词出现的每一行
- 输出需提取每个单词所关联的行号,行号升序出现且只出现一次,还要打印给定行号的文本
对应地,
- 将使用
istringstream
读取文件中的每一行,并拆分成逐个单词,再用vector<string>
进行保存 - 用
map
保存与行号的映射,set
保存一个单词对应的所有行号
将上述定义为一个抽象的解决方案,定义一个保存输入文件的类TextQuery
,包含vector
和map
,还有读取给定输入文件的构造函数和查询函数,查询函数返回单词出现次数,行号以及每行的文本;又将返回的结果定义成另一个类QueryResult
,用来保存输出结果,包含一个print
函数,完成结果的打印
显然,需要在两个类之间共享数据,如果只是将数据拷贝传输,则会浪费;如果是返回迭代器或指针,则可能会导致空悬指针;因此将用到shared_ptr
1 |
|
用infile
初始化TextQuery
对象,构造函数读取输入文件,保存在成员变量vector
中,并建立单词行号映射的map
1 |
|
考虑与QueryResult
类对象共享数据的需求,QueryResult
需要共享保存输入文件的vector
,用于输入同行的文本;QueryResult
还需要共享保存单词关联的行号的set
1 |
|
构造函数分配一个vector
保存输入文件的文本。getline
逐行读取文件并存入vector
中,再用一个istringstream
处理刚读入的一行中的每个单词,内层while
用istringstream
的输入运算符从当前行读取每个单词存入word
中。当map
中不存在word
,即第一次出现该单词,下标运算符创建了一个word
,返回一个空的智能指针,则再将指针reset
,分配新的set
,令其指向该set
,再向set
中插入行号
1 |
|
声明友元函数print
,定义构造函数,将参数保存在数据成员中
1 |
|
query
函数接受一个单词,在map
中定位其对应的行号,并构造一个QueryResult
对象返回,包括string, file, set
的TextQuery
成员,并且共享着相同的数据
1 |
|
qr.lines
是指向记录的行号的智能指针,需要先解引用或者用->
运算符,范围for循环中解引用得到的num
是取出set
的每一个行号,对应类型为set<TextQuery::line_no>>::size_type
,而qr.file
指向记录文本的数组,用qr.file->begin()+num
则指向对应num
行号的文本,同样,解引用即可获得整行文本。另外,程序能正确处理未找到单词的情况,只打印第一条输出语句,次数为0不进入范围for循环