用以记录在读经典书籍《c++ primer》时的笔记。构建一套完整的体系结构。以及将对一些重难点进行其他材料的拓展和补充。
第1章 开始
第一部分C++基础
第2章 变量和基本类型
类型 | 最小尺寸 |
---|---|
char | 8位 |
int | 16位 |
long | 32位 |
long long | 64位(C++11) |
float | 6位有效数字,32位 |
double | 10位有效数字,64位 |
可寻址的最小内存块称为“字节”,存储的基本单元称为“字”
大多数机器的字节由8比特构成,字则由32或64比特组成,也就是4或8字节。
‘’------单引号表示char类型
" " ----双引号表示字符串类型
变量声明的定义的区别:
声明:规定了变量的类型和名字
定义:此外,定义还会申请存储空间,也可能会为变量赋一个初始值。
extern 关键字,用于对外部变量(通常是其他源文件)进行声明,而非定义。
定义只能被定义一次,而声明可以多次。
变量命名规范
- 标识符要能体现实际含义
- 变量名一般用小写字母,如index,不要使用Index或INDEX
- 用户自定义的类名一般以大写字母开头,如Sales_item
- 如果标识符由多个单词组成,则单词间应有明显区分,如student_loan或studentLoan,不要使用studentloan
void *指针
是一种特殊的指针类型,可用于存放任意对象的地址。不能直接操作void*指针所指的对象,因为我们不知道这个对象到底是什么类型,也就无法确定能在这个对象上做哪些操作。
const限定符:定义一种变量,其值不能被修改。
指向常量的指针
不能用于改变其所指对象的值,要想存放常量对象的地址,只能使用指向常量的指针
const double pi = 3.14;
const doubel * ptr = π //ptr可以指向一个双精度常量
const指针
允许把指针本身定义为常量。常量指针必须初始化,并且一旦初始化完成,其值也就不能修改。
int errNumb = 0;
int * const curErr = &errNumb; //curErr 被定义为const指针,将一直指向errNumb
用名词顶层const表示指针本身是个常量,而用名词低层const表示指针所指的对象是一个常量。
类的常量成员函数
string isbn() const { return bookNo;}
这里的const表明该成员函数是常量成员函数,不会修改调用它的对象(不能修改任何数据成员)。
auto类型说明符(C++11)
自动类型匹配。auto让编译器通过初始值来推算变量的类型。
decltype类型指示符(C++11)
希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。
const int ci = 0, &cj = ci;
decltype(ci) x =0; //x的类型是const int
decltype(cj) y =x; //y的类型是const int &,y绑定到变量x
第3章 字符串、向量和数组
标准库类型string可变长字符串序列
string s1; //默认初始化,s1是一个空字符串
string s2 =“value”; //拷贝s1的字面值中除了最后那个空字符外其他所有的字符到s2中。s2为s1的副本,有两份数据
string s3(s1) ; //同上,拷贝,两份数据
string s4(10,'a'); //创建10个a给s4,数据一份
注意:是拷贝的数据,还是单独创建一份数据
string 的empty() 和 size()操作
两个string 对象相加
string s1 = "hello ,";
string s2 = " world";
string s3 = s1 + s2; //s3的内容是hello world
.(点运算符)和->(箭头运算符)的区别
- . (点运算符)用来访问对象的成员。对象必须是实际的对象或者对象的引用。
- ->(箭头运算符)也是用来访问对象的成员,但是对象必须是一个指针,指向类或者结构体的实体
- a->b 实际上是(*a).b 的简写
在实践中,记住:当你有一个对象的实例或引用时,使用 .;当你有一个指向对象的指针时,使用 ->。
标准库类型vector存放某种给定类型对象的可变长序列
vector本质上是标准库定义的类模板。
初始化vector的方法 | 含义 |
---|---|
vector <T> v1 | v1是一个空vector,它潜在的元素是T类型的,执行默认初始化 |
vector<T> v2(v1) | v2中包含v1所有元素的副本 |
vector<T> v2=v1 | 等价于v2(v1),v2中包含v1所有元素的副本 |
vector<T> v3(n,val) | v3包含了n个重复的元素,每个元素的值都是val |
vector<T> v4(n) | v4包含了n个重复地执行了值初始化的对象 |
vector <T> v5{a,b,c...} | v5包含了初始值个数的元素,每个元素被赋予相应的初始值 |
vector<T> v5={a,b,c...} | 等价于上式(C++11) |
注意:在某些情况下,初始化的真实含义依赖于初始化值用的是花括号还是圆括号。
vector <int> v1(10); //v1有10个元素,每个值都是0
vector <int> v1{10}; //v1有一个元素,该元素的值是10
使用圆括号是用来构造(construct) vector对象的,两个值分别说明了vector对象的容量和元素的初值。
vector操作 | 含义 |
---|---|
v.push_back(t) | 向vector对象中添加t元素 |
v.empty() | 如果v不含有任何元素,返回真;否则返回假 |
v.size() | 返回v中元素的个数 |
v[n] | 返回v中第n个位置上元素的引用 |
v1=v2 | 用v2中元素的拷贝替换v1中的元素 |
v1={a,b,c,…} | 用列表中元素的拷贝替换v1中的元素 |
v1==v2 | v1和v2相等当且仅当它们的元素数量相同且对应位置的元素值都相同 |
v1!=v2 | |
<,<=,>,>= | 顾名思义,以字典顺序进行比较 |
迭代器(iterator)
我们已经知道可以使用下标运算符来访问string对象的字符或vector对象的元素,还有一种更通用的机制也可以实现同样的目的,这就是迭代器。
类似于指针类型,迭代器也提供了对对象的简介访问。和指针不同的是,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。比如,这些类型都拥有名为begin和end的成员
- auto a = v.begin()----返回指向第一个元素的迭代器
- auto b = v.end()-----返回尾元素的下一个位置的迭代器
- 特殊的,当容器为空时,begin和end返回同一个迭代器,都是尾后迭代器。
一般来说,我们不清楚(不在意)迭代器准确的类型到底是什么。
标准迭代器的运算符 | 含义 |
---|---|
*iter | 返回迭代器iter所指元素的引用 |
iter->mem | 解引用iter并获取该元素的名为men的成员,等价于(*iter).mem |
++iter | 令iter指示容器中的下一个元素 |
–iter | 令iter指示容器中的上一个元素 |
iter1 ==iter2,iter1 !=iter2 | 判断磊哥迭代器是否相等(不相等),仅当两个迭代器指示的是同一个元素或者它们是同一个容器的尾后迭代器才相等,反之不相等 |
数组和指针
使用数组的时候编译器一般会把它转换成指针。
在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针。
string *p2 = nums; //等价于p2 = &nums[0];
第4章 表达式
及运算符
第5章 语句
范围for循环(C++11) for(i: {});
break语句:跳出循环
continue语句:跳出本次循环
第6章函数
函数重载:函数名同,编译器根据传入参数选择调用哪个函数
内联函数:inline 。在编译时,编译器将代码内联在一起。如果不内联,则程序运算时,调用函数需要跳转,有一定的开销。相当于用空间换了时间。
调试帮助:
assert(表达式) :处理宏,对表达式进行求值,如果表达式为假。则输出信息并终止程序的运行。
用NDEBUG可以屏蔽掉assert ,需要在执行时选择如:
gcc -D NDEBUG main.c
函数指针:指向的是函数而不是对象,相当于调用函数。
第7章类
this:成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象。当我们调用一个成员函数时,用请求该函数的对象地址初始化this。
this 是一个指向调用成员函数的对象的指针。换句话说,每当一个对象的成员函数被调用时,编译器都会隐式地将一个指向该对象的指针作为函数的一个参数传递。这个隐式传递的指针就被命名为 this 指针。
- 成员函数中的 this:只有在类的成员函数内部,你才能使用 this 指针。
- 用途:当局部变量的名称与类的数据成员重名时,可以使用 this 指针来区分它们。通过返回 this 指针,可以使当前对象的引用可链接(允许链式成员函数调用)。
- 常量成员函数:在常量成员函数中,this 指针是一个指向常量的指针,这意味着你不能修改对象的任何数据成员。
定义在类内部的函数是隐式的inline函数
构造函数初始值列表:
Sales_data(const std::string &s, unsigned n,double p):
bookNo(s),units_sold(n),revenue(p*n){ };
友元:允许朋友访问非公有成员。
class关键字默认是private,而struct关键字默认是public
类的静态成员
有的时候类需要它的一些成员与类本身直接相关,而不是与类的各个对象保持关联。例如:一个银行账户类可能需要一个数据成员来表示当前的基准利率。在此例中,我们希望利率与类关联,而非与类的每个对象关联。从实现效率的角度来看,没必要每个对象都存储利率信息。而且更重要的是,一旦利率进行调整,我们希望所有的对象都能使用新值。
通过在声明之前加上关键字static使得其与类关联在一起。
类的静态成员存在于任何对象之外,对象中不包含任何与静态数据成员有关的数据。static数据成员被所有对象共享。
第二部分 C++标准库
第8章IO库
头文件 | 类型 |
---|---|
iostream | istream,wistream 从流读取数据 |
iostream | istream,wistream 向流写入数据 |
iostream | iostream,wiostream 读写流 |
fstream | ifstream,wifstream 从文件读取数据 |
fstream | ofstream,wofstream向文件写入数据 |
fstream | fstream,wfstream 读写文件 |
sstream | istringstrem,wistringstream 从string读取数据 |
sstream | ostringstream,wostringstream 向string写入数据 |
sstream | stringstream,wstringstream 读写string |
第9章顺序容器
所有的容器类都共享公共的接口,不同容器按不方式对其进行拓展。
顺序容器为程序员提供了控制元素存储和访问顺序的能力。
顺序容器类型 | 作用 |
---|---|
vector | 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢 |
deque | 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快 |
list | 双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快 |
forward_list | 单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快 |
array | 固定大小数组。支持快速随机访问。不能添加或删除元素(新加) |
string | 与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快 |
选择容器的原则
- 除非你有很好的理由选择其他容器,否则应使用vector
- 如果你的程序有很多小的元素,且空间的额外开销很重要,则不要使用list或forward_list
- 如果程序要求随机访问元素,应使用vector或deque
- 如果程序要求在容器的中间插入或删除元素,应使用list或forward_list
- 如果程序需要在头尾位置插入或删除元素,但不会在中间位置进行插入或删除操作,则使用deque
- 如果程序只有在读取输入时才需要在容器中间位置插入元素,随后需要随机访问元素,则
-
- 首先,确定是否真的需要在容器中间位置添加元素。当处理输入数据时,通常可以很容易地向vector追加数据,然后再调用标准库的sort函数来重排容器中的元素,从而避免在中间位置添加元素
-
- 如果必须在中间位置插入元素,考虑再输入阶段使用list,一旦输入完成,将list中的内容拷贝到一个vector中。
赋值运算符
下表列出的赋值相关运算符可用于所有容器。赋值运算符将其左边容器中的全部元素替换为右边容器中元素的拷贝。
- 如果必须在中间位置插入元素,考虑再输入阶段使用list,一旦输入完成,将list中的内容拷贝到一个vector中。
运算符 | 功能 |
---|---|
c1=c2 | 将c1中的元素替换为c2中元素的拷贝。c1和c2必须具有相同的类型 |
c={a,b,c,…} | 将c1中元素替换为初始化列表中元素的拷贝(array不适用) |
swap(c1,c2),c1.swap(c2) | 交换c1和c2中的元素。c1和c2必须具有相同的类型。swap通常比从c2向c1拷贝元素快得多 |
assign操作不适用于关联容器和array | |
seq.assign(b,e) | 将seq中的元素替换为迭代器b和e所表示的范围中的元素。迭代器b和e不能指向seq中的元素 |
seq.assign(il) | 将seq中的元素替换为初始化列表il中的元素 |
seq.assign(n,t) | 将seq中的元素替换为n个值为t的元素 |
赋值相关运算会导致指向左边容器内部的迭代器、引用和指针失效。而swap操作将容器内部交换不会导致指向容器的迭代器、引用和指针失效(容器类型为array和string的情况除外)
向顺序容器添加元素:
操作 | 含义 |
---|---|
c.push_back(t),c.emplace_back(args) | 在c的尾部创建一个值为t或由args创建的元素,返回void |
c.push_front(t),c.empalce_front(args) | 在c的头部创建一个值为t或由args创建的元素,返回void |
c.insertp,t),c.empalce(p,args) | 在迭代器p指向的元素之前创建一个值为t或由args创建的元素。 |
访问元素
操作 | 含义 |
---|---|
c.back() | 返回c中尾元素的引用 |
c.front() | 返回c中首元素的引用 |
c[n] | 返回c中下标为n的元素的引用,若越界,则函数行为未定义 |
c.at(n) | 返回下标为n的元素的引用,如果下标越界,则抛出out_of_range异常 |
删除元素
操作 | 含义 |
---|---|
c.pop_back() | 删除c中尾元素。若c为空,该行为未定义 |
c.pop_front() | 删除c中首元素 |
c.erase( p ) | 删除迭代器p所指定的元素。 |
c.erase(b,e) | 删除迭代器b和e所指定范围内的元素。 |
c.clear() | 清空c中所有元素 |
string容器的操作
操作 | 含义 |
---|---|
修改string的操作 | |
s.insert(pos,args) | 在pos之前插入args执行的字符。pos可以是一个下标或一个迭代器。 |
s.erase(pos,len) | 删除从位置pos开始的len个字符。如果len被省略,则删除从pos开始直至s末尾的所有字符 |
s.assign(args) | 将s中的字符替换为args指定的字符,返回一个指向s的引用 |
s.append(args) | 将args追加到s。返回一个指向s的引用 |
s.replace(range,args) | 删除s中范围range内的字符,替换为args指定的字符。range或者是一个下标和一个长度,或者是一对指向s的迭代器。 |
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_last_not_of(args) | 在s中查找最后一个不在args中的字符 |
容器适配器
本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。
默认情况下,stack和queue是基于deque实现的。
第10章 泛型算法(没看)
第11章 关联容器
关联容器和顺序容器有着根本的不同:关联容器中的元素是按关键字来保存和访问的。与之相对,顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。
两个主要的关联容器类型是map和set。map中的元素是键值对。set中的每个元素只包含一个关键字;set支持高效的关键字查询操作—检查一个给定关键字是否在set中。
关联容器类型 | 含义 |
---|---|
按关键字有序保存元素 | |
map | 关联数组;保存键值对 |
set | 关键字即值,即只保存关键字的容器 |
multimap | 关键字可重复出现的map |
multiset | 关键字可重复出现的set |
无序集合 | |
unordered_map | 用哈希函数组织的map |
unordered_set | 用哈希函数组织的set |
unordered_multimap | 哈希函数组织的map;关键字可重复 |
unoreder_multiset | 哈希函数组织的set;关键字可重复 |
第12章 动态内存
第三部分 类设计者的工具
第13章 拷贝控制
第14章重载运算与类型转换
第15章面向对象程序设计
第16章 模板与泛型编程
第四部分 高级主题
第17章 标准库特殊设施
第18章 用于大型程序的工具
第19 章 特殊工具与技术
(持续更新ing)