C++Primer4 笔记
第一章 快速入门
1 std::cin, std::cout, std::endl
using namespace std; // 使用std命名空间
cin >> para; // 从cin流中读取值到para
cout << para; // 将para的值写入到cout流
endl; // 换行并刷新与设备相关连的缓冲区
2 注释不可嵌套
"/*"总是以最近的"*/"作为结束
第一部分 基本语言
第二章 变量和基本类型
1 内置类型
bool - // 我的机器是8位
char 8位
wchar_t 16位
short 16位
int 16位 // 我的机器是32位
long 32位
float 6位有效数字 // 我的机器有7位精度,32位表示
double 10位有效数字 // 我的机器有16位精度,64位表示
long double 10位有效数字 // 我的机器有16位精度,64位表示
· signed和unsigned的整型
用一个符号位表示符号,因此,同样整型的signed和unsigned版本,前者最大值是后者一半-1
· 整型越界赋值取最大界值的模
· 一般来说,int类型运行代价远远低于long类型。double类型计算代价相对于float可以忽略甚至更快。long double类型提供的精度通常没必要。
2 变量
· 左值:可以出现在赋值语句的两侧,右值:只能出现在赋值语句右边。
· 直接初始化和复制初始化
int ival(1024); // 直接初始化
int ival = 1024; // 复制初始化
直接初始化语法更灵活而且效率更高。
复制初始化不同于赋值。_
const对象需要在定义时初始化。
· 变量的初始化
内置类型在函数体外都初始化成0,在函数体内不进行自动初始化。
类类型如果有默认构造函数,则在定义变量时会调用默认构造函数;如果没有默认构造函数,定义变量时必须提供显示初始化式。
· 变量的定义和声明
定义为其分配存储空间,有且仅有一次。
声明可以有多次。需要使用extern说明,该变量的定义在程序其他地方。
· 作用域
全局作用域:定义在所有函数外部;
局部作用域:定义在函数局部;
类作用域:共有,保护,私有
语句作用域:在某个语句中可视(如for, while, 但是我的机器上for里面定义的变量作用域在循环体后也能调用,也许是编译器的原因);
作用域嵌套:局部作用域可以屏蔽全局作用域同样名字的变量。
const对象默认为文件局部作用域。通过指定const变量为extern,可以在整个程序中可视该const对象。
3 引用
引用是它绑定的对象的另一个名字,const引用指向一个右值,也可以指向一个左值,但并不能修改它。
4 typedef
定义类型别名,简化复杂类型的定义。
5 枚举
一个常量集合。(补充)上限:比enum中最大的元素还要大的,最近的2的N次幂的那个数-1. 如最大元素是120, 则上限是127.
下限是比最小的元素还小的,最近的2的N次幂的那个数+1。
6 类
(补充)类通过成员函数(方法)和操作符重载传递消息。
使用struct定义类类型,默认的访问标号是public。
头文件设计:类的定义、extern变量的声明,extern const对象的定义,函数声明。后面还将学到,包括内联函数的定义;加入
#ifndef **_H
#define **_H
// 类声明等
#endif
头文件的引用:
#include <iostream> // 系统标准头文件,编译器会在预定位置查找
#include "mylib.h" // 自定义头文件,编译器从源文件(引用该文件的文件)开始查找
第二章 标准库类型
1 using声明
using namespace::name;
这样可以直接使用命名空间内的名字。(类似java中的import java.util.Hashtable;)。
2 string类型
· 初始化
string s1; // 空的string
string s2(s1); // s2初始化为s1的副本
string s3("value"); // 直接初始化为字面值的副本
string s4(n, 'C'); // 初始化为字符'C'的n个副本构成的字符串
· string::size_type类型
是一种unsigned int/ unsigned long 型,能保证存储任意string对象的长度。
· 关系操作
<, <=, >, >=比较:按照字典排序
· 相加
string可以与字符串字面值连接混合得到一个新的string
· 下标
按下标取出string中某个元素,可以用于左值。
3 vector类型
· 初始化
vector<T> v1; // 类型为T的对象,默认构造函数v1为空
vector<T> v2(v1); // v2初始化为同样类型v1的副本
vector<T> v3(n, i); // n个类型为T的i元素
vector<T> v4(n); // n个T类型元素副本,其值都由默认构造函数初始化而来/如果没有默认构造函数需要提供元素初始值/
如果没有构造函数,产生一个每个成员都进行了值初始化的对像
· 关系操作
按照元素进行比较
· push_back
插入元素到元素集最后面
· 下标
用作左值
4 容器的迭代器类型
· 根据迭代器访问容器内元素
vector<int> ivec(10, 1);
ivec.begin(); // 指向第一个元素
ivec.end(); // 指向最后一个元素的后面的哨兵
ivec::iterator itr; // 根据begin(), end()通过自加或者自减遍历整个容器所有元素, 得到一个左值
ivec::const_iterator c_itr // 遍历但只读的访问所有元素,得到右值
ivec.rbegin(); // 指向最后一个元素
ivec.rend(); // 指向第一个元素的前面的哨兵
ivec::reverse_iterator ritr; // 根据rbegin(), rend()通过自加或者自减遍历整个容器所有元素, 得到一个左值
ivec::const_reverse_iterator c_ritr; // 遍历但制度的访问所有元素,得到右值
· 迭代器的算术操作
类似算术操作,返回结果是vector<T>::size_type或difference_type类型
· 上述概念也适用string容器
5 bitset类型
· 初始化
bitset<n> b; // b有n位,都是0
bitset<n> b(u); // b是unsigned long型u的一个副本,二进制表示后,从低阶位到高阶位依次读入。超过部分将被丢弃
bitset<n> b(s); // b是string对象s中含有的位串的副本,从右到左读入
bitset<n> b(s, pos, n); // b是string对象s从pos开始的n个位的副本
· 操作
控制整个容器的所有元素和某个元素的开关状态
第四章 数组和指针
1 数组
· 数组的定义
数组的维数需用大于等于1的整型字面值,枚举值,用常量表达式初始化的const对象定义。
运行时才知道值的const变量不能用于定义维数。
· 初始化
初始化列表提供的元素个数不能超过维数.
如果维数大于列表中初值个数,剩下的元素,如果是内置类型则初始化为0,如果是类类型则调用默认的构造函数进行初始化。
· 数组名是指向第一个元素的指针
2 指针
指针保存的是某个对象的地址(我的机器指针的size是4byte,一个字长)。
利用"&"可以获得对象的地址,而利用"*p"就可解除指针的引用,获得该指针指向的对象。
· 指针的初始化
指针可初始化为0值常量表达式,指向0(NULL),或类型匹配的对象的地址,或同类型的另一个指针,或另一个对象之后的下一地址(通常表示结束)。
void*指针可以保存任何类型的对象的地址。可通过强制类型转换重新获得实际对应类型地址。
· 指针的操作
自加自减操作可获取连续存放空间下一个元素的地址。每做一次自加,实际移动内存的字节数等于指针指向的数据类型的size。
指针之间的算术操作得到的标准库类型ptrdiff_t的数据。
指针是数组的迭代器。
· 指向const对象的指针
这种指针可以理解为"自以为指向const的指针",它并不能修改所指向的对象。
· const指针
该指针本身不能修改,需在定义时初始化。
typedef string* pstring;
const pstring cstr; // 实际上是 string *const cstr; 定义了一个const指针
3 动态数组
int *p=new int[10]; 申请空间,并用p指向这片空间。与delete[] 配套使用。
4 多维数组
int ia[rowSize][colSize]; //ia是第1行第1个元素的地址;ia[i]是第i行第1个元素的地址
int (*ip)[4] = ia; // ip指向一个多维数组的第1行(就是一个一维数组),该数组有4个元素(4列)
ip = &ia[3]; // ip指向第4行了
第五章 表达式
1 求模
如果:23 % -5 = 3 (这是我的机器),那么 -23 % 5 = -3,因为除得的值是-4 // 求模符号跟分子一致
如果:23 % -5 = -2 (这是我的机器),那么 -23 % 5 = 2,因为除得的值是-5 // 求模符号跟分母一致
2 动态创建对象
new和delete配套使用。
当执行delete p之后, p变成没有定义,但仍然存放了它之前所指向对象的地址,它指向的内存已经释放。变成一个悬垂指针,应该立即置为0。
3 类型转换
· 整型提升:对于所有比int小的整型,都会提为int或unsigned int。bool的true和false将转成1和0;
· long和unsigned int的转换:如果long足够表示unsigned int,则转换成long。否则都转成unsigned long;
· 数组名转换为第一个元素的指针;
· 0转换为bool的false,其他算术值或指针转换为true;
· enum对象或枚举成员至少提升为int;
· const_cast:转换掉表达式的const性质;
· static_cast:编译器隐式提供的任何类型转换都可以由static_cast显示完成。
· 旧式强制转换:在合法使用static_cast或const_cast的地方,旧式强制转换提供了上述一致的功能,如果不能,则执行reinterpret_cast。
type(expr);
(type)expr;
第六章 语句(略)
1 控制流向,各种语言都差不多。
2 预处理变量有条件的调试代码
#ifndef NDEBUG
// debug sentences goes here
#endif
3 assert宏帮助调试
assert(expr);
只要NDEBUG未定义(在程序调试中),assert宏就求解表达式的值。
第七章 函数
1 函数的定义
返回值类型:可以是内置类型,类类型或复合类型和void型(不返回数组,但可以返回指针);
函数名:字母或下划线开头的非标识符;
参数列表:用园括号括起来,指定类型和参数名,并用逗号分隔;此上是函数原型。
函数体:一对花括号的块语句。
2 参数传递
· 非引用形参:通过复制对应的实参实现形参初始化。形参只是一个副本,任何改变该副本的操作都不会影响到传入来的实参。
void fcn(const int i);
void fcn(int i);
可以看成是同样的2个函数。
· 引用形参
返回更多的信息(在return之外);
const引用避免修改实参;
const引用使得函数更灵活:非const引用形参只能与完全同类型的非const对象关联(不能给它传const,否则会编译出错)。
· 数组形参
非引用类型传递数组指针。形参会复制这个实参(指针)的值(是一个地址)。
通过引用传递数组本身,编译器检查实参和形参的大小是否匹配。如
void printValue(int (&arr)[10]); // 圆括号不可少
多维数组(数组的数组)传入0号元素的指针,编译器忽略第一维的长度。如
void printValue(int (matrix*)[10]); // 或
void printValue(int matrix[][10]);
3 return 语句
非引用类型返回值复制给调用函数的对象;
引用类型的返回是对象本身。
不要返回指向局部对象的指针和局部对象的引用。
4 函数声明
· 在头文件中声明;
· 默认实参的排列:使用最少的排在最前;
· 默认实参只能指定一次(一般在函数声明的头文件中);
· 内联函数定义在头文件中。
5 重载函数的确定
· 确定候选重载函数集合
· 选择可行函数
·寻找最佳匹配(如果有的话)
6 实参类型转换
· 精确匹配
· 类型提升
· 标准转换
· 类类型转换
· 这两个函数是一样的
f(int *);
f(int *const);
7 函数指针
· 指向函数类型的指针。函数类型由返回类型和形参表确定。
· 通过指针的解引用(*pf),或直接将指针当函数名用传入参数即可调用该函数。
· 返回指向函数的指针
int (*ff(int))(int*, int); // 相当于:
typedef int (*PF)(int*, int);
PF ff(int);
第八章 标准IO库
1 标准输入输出库
· 对应的宽类型
· IO对象不可赋值和复制
2 条件状态
· 标记给定的IO对象是否可用
· 多种状态的处理:传递二进制位或操作连接的条件状态符:
is.setstate(is.badbit | is.failbit);
3 输出缓冲区的管理
·flush:刷新,不加入数据
·ends:刷新,加入一个null
·endl:刷新并换行
·unitbuf刷新所有输出
·nonitbuf:恢复系统管理缓冲区刷新的方式
·使用tie函数可用使得输入输出流绑在一起。
4 文件模式
· 各种文件流支持的文件模式
ofstream: out, trunc, app, ate, binary
fstream: in, out, trunc, app, ate, binary
ifstream: in, ate, binary
· 打开模式的有效组合
out
out | app
out | trunk
in
in | out
int | out | trunk
5 字符串流
可以通过stringstream进行转换和格式化:
istreamstream is(“abc def 1 ghi”);
string word1, word2, word3;
int i;
is >> word1 >> word2 >> i >> word3;
第二部分 容器和算法
第九章 顺序容器
1 顺序容器的定义
· 初始化
C<T> c; 创建空的容器
C c(c2); 创建容器c2的副本c
C c(b, e); 利用一对迭代器内的元素创建c
C c(n, t); 用n个值为t的元素创建容器c,只适合于顺序容器
C c(n); 创建n个值初始化的元素的容器c。如果是类类型要求有默认构造函数。
· 元素约束:可赋值,可复制
2 迭代器
· vector,deque支持算术操作;而list只支持自增自减和相等不等的运算。
· 迭代器范围是一个左闭合区间[first, last)
3 在顺序容器的操作
· 添加元素:都是添加原元素的副本
c.push_back(t);
c.push_front(t); // 只适用list,deque
c.insert(p, t);
c.insert(p, n, t);
c.insert(p, b, e);
· 容器的比较
如果容器存放的元素可以进行比较:
如果2个容器具有相同的长度和元素,则他们相等; 如果长度不相同,但某容器中的所有元素都包含在令一容器之中的对应的一部分,则后者大;否则比较第一个不相等的元素。
· 调整大小
c.resize(n);
c.resize(n, t);
· 访问元素
c.front();
c.back();
c[n]; // 只适合vector,deque
c.at[n]; // 只适合vector,deque
· 删除元素
c.erase(p);
c.erase(b,e);
c.clear();
c.pop_back();
c.pop_front(); // 不适合vector
· 赋值
c.assign(b, e); // 重新设置c的元素,将迭代器范围内的元素复制到c。b,e必须是别的容器一对迭代器
c.assign(n, t); // 重新赋值为存储n个t
·swap
c1.swap(c2); // 交换内容。c1,c2类型必须相同。
4 vector的自增长
capacity
size
reserve
5 容器选择
·如果要求随机访问:vector或deque
·如果要在中间位置插入或删除元素:list
·如果只是在首尾插入或删除元素:deque
· 如果需要随机访问元素又必须在中间位置插入元素:先使用list,排序后复制到vector
6 string
·初始化
string s(cp, n); //
string s(s2, pos2); //
string s(s2, pso2, len2); //
·操作
s.insert(p, t); //
s.insert(p, n, t); //
s.insert(p, b, e); //
s.assign(b, e); //
s.assign(n, t); //
s.erase(p); //
s.erase(b, e); //
特有的:
s.insert(pos, n, c); //
s.insert(pos, s2); //
s.insert(pos, s2, pos2, len); //
s.insert(pos, cp, len); //
s.insert(pos, cp); //
s.assign(s2); //
s.assign(s2, pos2, len); //
s.assign(cp, len); //
s.assign(cp); //
s.erase(pos, len); //
s.substr(pos, n); //
s.substr(pos); //
s.substr(); //
s.append(args)//
s.replace(pos, len, args); //
s.replace(b, e, args ); //
args 包括:
s2,
s2, pos2, len2
cp
cp, len2
n, c
b2, e2
s.find(args); //
s.rfind(args); //
s.find_first_of(args); //
s.find_last_of(args); //
s.find_first_not_of(args); //
s.find_last_not_of(args); //
args 包括:
c, pos
s2, pos
cp, pos
cp, pos, n
s.compare(s2); //
s.compare(pos1, n1, s2); //
s.compare(pos1, n1, s2, pos2, n2); //
s.compare(cp); //
s.compare(pos1, n1, cp); //
s.compare(pos1, n1, cp, n2); //
7 容器适配器
· 初始化
A<T> a(c); // 基础容器初始化
A<T, Tc> a(c); //其中c的模版类型是Tc,覆盖关联的基础容器
·限制
stack:任意顺序容器
queue:list
priority_queue: vector,deque
·运算
同类型适配器可做比较,依次按元素进行比较。
·stack
s.empty(); //
s.size(); //
s.pop(); //
s.top(); //
s.push(item); //
·queue和priority_queue
q.empty(); //
q.size(); //
q.pop(); //
q.front(); // 只适合队列
q.back(); // 只适合队列
q.top(); // 只适合优先级队列
q.push(); //
第十一章 关联容器
1 pair
pair<T1, T2> p1; //
pair<T1, T2> p1(val1, val2>; //
make_pair(val1, val2);
p1 < p2; // 先比较first,如果相同,比较second
p1 == p2; // 如果first,second都相同,则成立
p.first; //
p.second; //
2 map
·初始化
map<k, v> m; //
map<k, v> m(m2); //
map<k, v> m(b, e); //
·键约束
键不但有一个类型,而且还有一个相关的比较函数(在键类型上定义严格弱排序:当键与自身比较时,导致false;不能出现键k1<k2,同时k2<k1;如果k1<k2,k2<k1都为false,则视为相等)。
·map定义的类型
map<k,v>::key_type; // 键类型
map<k,v>::mapped_type; // 键关联的值类型
map<k,v>::value_type; // 一个pair类型, first元素类型为键类型,second元素类型为值类型
·相关操作
m[k]; // 如果k键不存在,将添加新元素
m.insert(e); // e是value_type
m.insert(beg, end); // 这对迭代器指向的元素都必须是pair,且符合map的键-值结构
m.insert(iter, e);
如果insert的元素对应的键已在容器中,则不做任何操作,返回一个pair <iterator, false>;如果成功返回一个pair <iterator, ture>。
m.count(k);
m.find(k); // 返回一个指向pair的迭代器
m.erase(k);
m.erase(p);
m.erase(b, e);
3 set
·初始化
set<k > s; //
set<k> s(s2); //
set<k> s(b, e); //
·操作
s.insert(); //
s.insert(b, e); //
s.find(k); // 返回指向元素的迭代器
s.count(k); // 返回次数
map,set获得元素后, 键都是const,不能修改。
3 multimap和multiset
一个键对应多个值,这些值都是相邻存放的(根据键上定义的严格弱排序的比较函数)。
·初始化
·操作
mt.find(k); // 返回第一个找到的迭代器位置
mt.count(k); // 返回总数
mt.lower_bound(k); // 返回一个迭代器,指向键不小于k的第一个元素
mt.upper_bound(k); // 返回一个迭代器,指向键大于k的第一个元素
mt.equal_range(k); // 返回一个迭代器的pair,其first, second分别等价于mt.lower_bound(k),mt.equal_range(k)