1. << > > 输出 输入运算符返回其左侧对象,因此他们可以连用。并且他们没有规定求值顺序(p123)
2. 注释界定符不能嵌套。
3. 读取数量不确定的输入 while (cin>>value ) sum += value ;当时用istream对象作为条件时,会检测流的状态,当流发生错误或遇到EOF时状态无效,条件为假。
在windows中 输入文件结束符的方法是ctrl+z,再敲击enter。
int main(){
int value ,sum = 0 ;
while (cin>>value ) sum += value ;
cout<<sum;
return 0 ;
}
4. for (unsigned int i;i>=0 ;--i)
5.默认初始化:
对于内置类型,当其位于任何函数内部时,不被初始化,其值是未定义的。
6. 如果想声明一个变量而不定义它,就在其类型前加extern ,并且不初始化。在函数体内部显示初始化一个extern 变量会引发错误。
7.类型修饰符(* &等)和变量名写一起较好。
8. 一般地,顶层const 可以表示任何对象是常量,而底层const 和指针引用等复合类型的基本类型部分
9. typedef char *pstring
const pstring cstr = 0
10. auto 在进行类型推断时,会舍弃顶层const 而保留底层const (p62);
auto 用于在一条语句中定义多个变量时,变量的初始值类型应相同。
11. 与auto 不同,decltype 会保留顶层const 。
如果decltype 作用于解引用,则会得到一个引用类型:
int *p = i;
decltype ()的括号中如果又加了一层或多层括号,则得到引用类型:
int i;
decltype ((i)) d;
decltype (i) d;
decltype 作用于左值,得到引用,作用于右值,得到右值的类型。(p121)
Note: 赋值表达式的类型是左值的引用:
int a;a = b的类型是int &;
12. 类内初始值使用花括号或者等号,不能使用圆括号。
13. string 的读操作会自动忽略开始的空白,读取到其后的第一处空白为止。
getline读取换行符但不保存。
14 . 数组名作为begin 和end 函数的参数可以得到类似迭代器的首尾指针。利用这个可以用数组初始化向量。
15. 范围for 访问多维数组(以二维为例):
for (auto &row : a)
for (auto col : row)
16. sizeof 运算符有两种形式:
sizeof (type)
sizeof expr
sizeof 并不计算表达式的值,对于sizeof *p 即使p是一个无效的指针,也可以使用,sizeof 不解引用也能知道其类型。
sizeof 不会将数组名转化为指针,作用于数组时他返回数组的大小(字节数)而非数组元素的个数
17 . 在某一case 语句中初始化(显示或隐式)的变量不能在另一case 语句中使用。原因在于初始化语句可能被跳过。
18. 使用引用形参可以实现“返回”多个“返回值”。
将形参定义为常量引用就可以使用字面值实参。
如果将匹配字符的函数find定义为:
string::size_type find(string &s, char c);
1 ) 那么形如find("string" ,'r' )的调用将导致编译错误。
2 )
void fun(const string &s){
char ch;
find(s,ch);
...
}
形如这样的函数定义也将报错,因为不能用fun中的常量引用来初始化find中的普通引用。
19. 管理数组实参的三种方法(防止数组越界的错误):
1 ) 给数组末尾加一个结束符,以此判断,例如C 风格字符串末尾的'\0'
2 ) 同时传递首元素和末尾元素
3 ) 同时传递收元素和数组长度。
20. 数组引用形参:
void (int (&arr)[10 ])
21. 可变形参:
可变形参通过initializer_list实现,这是与vector类似的模板类型,eg:
void fun (initializer_list<string> lst);
fun ({"string1" ,"string2" });
fun ({"string1" ,"string2" ,"string3" });
22. 返回一个引用类型返回的是左值,返回其他类型返回的是右值。
23. C++ 11 允许返回一个列表:
vector <string > fun(){
return {"s1" ,"s2" };
}
24. 尾置返回类型:
auto fun(int i) -> int (*)[10 ];
使用decltype :
int a[] = {1 ,2 ,3 ,4 ,5 }
decltype (a) *fun(int i);
25. 重载函数中,顶层const 无法与普通类型区分开来。(p208)
26. const_cast 与重载:
假如我们在之前定义了:
const string & shorter(const string & s);
现在我们希望当传入非常量引用时也返回非常量值,那么:
string & shorter(string & s){
auto &r shorter(const_cast <const string &>(s));
return const_cast <string &> (r);
}
27. 只能省略靠后的默认实参;
函数的多次声明可以修改默认实参(把普通形参改成默认实参,不能改变已有的默认实参
void fun(int a, int b, char c = 'c' );
void fun(int a, int b = 2 , char c);
void fun(int a, int b, char c = 'a' );
28 . 预处理宏assert ,assert 定义在cassert中,使用格式为
assert (expr);
当expr表达式为假,assert 输出信息并终止程序执行,为真时什么也不做。可以使用 #define NDEBUG 来关闭调试模式,忽视assert 。
29. C++编译器定义的几个变量名:
__func__ 存放当前函数名;
__LINE__ 存放当前行号的整型字面值;
__FILE__ 存放文件名的字符串字面值;
__TIME__ 存放编译时间的字符串字面值;
__DATA__ 存放编译日期的字符串字面值;
30.
1 ) funcname是一个函数名,pf是一个函数指针,则下列两种赋值方法都是正确的:
pf = funcname;
pf = &funcname;
2 ) 函数指针做形参:和数组类似,虽然函数不能直接作为参数使用,但是函数指针却可以:
void fun(int a, int b, int fun2 (args));
void fun(int a, int b, int (*fun2)(args));
fun(1 ,2 ,funcname);
3 )使用类型别名和decltype 简化声明:
typedef bool fun(const string & , const string &);
typedef decltype (compare) fun;
type bool (*fun)(const string & , const string &);
typedef decltype (compare) *fun;
(p222)
31. 类的成员函数通过一个名为this 的隐式参数来访问调用它的对象。
class .func();
任何自定义的名为this 的参数或变量都是违法的。 在类的内部我们可以使用this (实际上没有这个必要),表示当前类的地址。
32. 常量成员函数:
默认情况下,this 是一个指向非常量对象的常量指针,例如在Sales_data类中, this 的类型是 Sales_data *const , 因此this 不能指向常量对象,也就是说我们不能在常量对象上调用普通成员函数,因此需要引入常量成员函数:在C++中,允许在成员函数参数列表后面紧接着写上const ,将函数定义为常量成员函数,常量成员函数不能改变调用它的对象的值。
常量成员函数如果以引用的形式返回*this ,则返回的是一个常量引用。
33. 编译器首先处理类中成员的声明,再处理成员函数体,因此在成员函数体中可以随意使用成员,而无需考虑他们出现的次序。
34. 我们可以在类的内部声明成员函数,而在类外部定义成员函数,在类外部定义时,应该在函数名前面用::指明作用域(所在的类),在这种情况下,实现内联函数有两种方法:
1 ) 在类内部声明时写上inline 。
2 ) 在类外部定义时写上inline 。
35. 为了在需要的情况下能连续使用.运算符,某些函数需要返回类本身,所以它们的返回类型声明和返回语句分别应为:
classname &func(args);
return *this ;
36. 构造函数初步:
1 ) 构造函数没有返回类型,函数名和类名一样(这可以解释string 等类类型用()初始化的形式)
2 ) 可以显示地定义默认构造函数:Sales_data() = default ;
3 ) 构造函数初始值列表:
Sales_data(const string &s, unsigned n, double p):
bookNo(s),unit_sold(n), revenue(p*n){}
如上所示,冒号以及左花括号之间的部分成为构造函数初始值列表。
37. 可以在类中定义类型别名,这样的类型别名也有public 和private 之分(p243)
38. 可变数据成员:将某数据成员声明为mutable ,则它永远不会是const ,甚至它可以被常量成员函数所修改。
39. 当一个类完全完成之后才算是被定义,因此类的成员不能是自己;但是一个类的名字一旦出现,就被认为已经声明过,因此类的成员可以是自身的指针或者引用。
40. 将类的成员函数声明为友元,必须按照以下的顺序编写程序(假设类A中的func 函数要访问类B):
1)先定义类A,并声明func ,但不定义func
2)定义类B,包括对func 的友元声明
3)完成func 的定义(类外定义,注意指明它所属的类)
如果想声明一组重载成员函数为类的友元,必须对每一个都声明。
41. 在类的外部定义成员函数时,必须用
但是对于函数的返回类型,它出现在类名之前,所以它在需要用到该类中的名字时需要用
42. 构造函数初始化列表的顺序和其实际执行初始化的顺序无关,执行初始化的顺序和其在类中出现的顺序一样。
43. 能通过一个实参调用的构造函数定义了一条从该参数类型到该类类型的转换规则。只允许一步类类型的转换。当构造函数声明为explicit 时不能使用拷贝初始化。(p264)
44. 一般只能在类内部声明静态成员,而在类外部定义它,比如
static double a;
double classname:: a = func();
如果静态成员是字面值常量类型的constexpr ,则可以给它提供const 整型的类内初始值。
45. 如果一个静态成员仅应用于编译器可以替换其值的情况,则它不需要在类外定义,否则需要在类外定义(p271)
46. 与非静态类型相比:静态类型是不完全类型,它的类型可以就是其所在的类类型。静态成员可以做成员函数和构造函数的默认实参,而费静态成员不能。
47. IO流对象不能够拷贝,所以它们之间不能够相互赋值,也不能够直接作为形参和返回值,只用将其引用作为形参和返回值。读写一个IO对象会改变其值,因此他们不能是const 。
48. strm.rdstate()返回的标志位顺序为fallbit eofbit badbit 不包括goodbit, rdstate返回的值时以上三个标志位组成二进制值的十进制表示。
49. 缓冲区刷新:
1 )程序正常结束时。
2 )缓冲区满时。
3 )用endl,flush,ends等操作符显示刷新。
4 )在每个输出操作后,用操作符unitbuf来设置流的内部状态,来清空缓冲区。默认情况下cerr 是设置unitbuf的,因此每次cerr 后会立即刷新。
5 )读写被关联的流时,关联到的流会刷新。
p(282 )
50. 程序崩溃时缓冲区不会刷新,因此有时候在崩溃之前程序的某一部分已经执行过,只是没有打印出来。
51. 在交互式系统中,任何输出提示信息都应该在用户输入之前打印出来,所以cin 和cout 关联,在执行cin 之前,输出缓冲区会被刷新。
cin .(&cout )
注意:每个流最多同时关联到一个流,但多个流可以同时关联到同一个流。
52. 选择顺序容器的一些小技巧:
1 )很多容器在中间位置的插入很慢,比如vector 等,有时候我们可以选择在容器的末尾插入,再用某些算法进行重排,来达到与直接在中间插入相同的效果。
2 )如果必须要在中间插入,则在输入阶段可以选择list 容器,输入完成后将其拷贝到一个vector 里。
53. 如下的构造方法需要注意:
vector <NoDefault> (10 ,init);
vector <NoDefault> (10 );
54. 顺序容器的类型别名:
iterator
const_iterator
size_type
difference_type
value_type
reference
const_reference
...
list <int > ::iterator itr;
利用这些类型别名,我们可以在不清楚容器元素具体类型的时候进行相关操作。
55. 容器的定义和初始化
//默认构造函数
C c
//将c1初始化为c2的拷贝,如果是array,大小必须相同
C c1(c2)
C c1 = c2
//列表初始化,对于array,列表长度需小于等于array长度。
C c{a,b,c...}
C c = {a,b,c...}
//迭代器范围初始化
C c(b,e)
//对于顺序容器的接受元素数量(及其初始值)的构造函数
C seq(n)
C seq(n,q)
56. 容器赋值运算:
c1 = c2;
c = {a,b,c... }
swap(c1,c2)
c1. swap(c2)
seq. assign(b,e)
seq. assign(il)
seq. assign(n,t)
PS:
1 ) 用assign可实现类型的自动转化,比如char* to string ,而赋值运算符则不能
2 ) 出string 外,指向容器的指针或者引用,在该容器与其它容器交换值后并不会失效,但是指向不再是以前的元素。而string 在交换之后,相关指针和引用会失效
3 ) array 的交换是值的交换而非数据结构的交换,时间花费并非常数级
57. 向顺序容器添加元素的操作:
c.push_back(t)
c.emplace_back(args)
c.push_front(t)
c.emplace_front(args)
c.insert(p,t)在迭代器p所指元素之前创建一个值为t或者由args创建的元素,返回新元素的迭代器
c.emplace(p,args)
c.insert(p,n,t)
c.insert(p,b,e)
c.insert(p,il)
PS:
1 ) forward_list有专有版本的insert和emplace,它不支持push_back emplace_back
2 ) vector string 不支持push_front emplace_front
3 ) 向一个vector string deque 插入元素会使所有指向容器的迭代器,指针,引用失效
58. 关于insert和emplace:
1 )deque 提供了vector 一样的随机访问能力,而且有vector 不支持的push_front,deque 在容器首尾插入或删除都只花费常数时间。vector 虽然不支持push_front但是可insert到首元素之前(很耗时)。
2 )插入范围内元素,迭代器范围和插入位置不能指向同一个容器。
3 )insert的返回值是新插入元素的迭代器,因此可以利用这一点反复在同一位置插入元素。
4 )emplace与insert的区别在于,emplace(args)构造出元素并插入。
59. 利用at 进行随机访问,在越界的时候会抛出一个out_of_range异常。
60. 容器操作可能使迭代器失效:
1 ) 向容器添加元素:
a. 对于vector 和string ,如果插入元素后存储空间重新分配,则全部迭代器、引用、指针失效;如果未重新分配,则插入元素之后的迭代器、引用、指针失效。
b. 对于deque 插入到首尾位置之外的位置,迭代器指针引用全部失效,如果是在首尾位置插入,仅仅使迭代器失效。
c.对于list 和forward_list,没有影响
2 ) 删除元素后
a. 对于list 和forward_list,没有影响
b. deque 在首尾之外的位置删除元素,全部失效;删除首元素,没影响;删除尾元素,仅仅是尾后迭代器失效。
c. 对于vector 和string ,被删除元素之后的全部失效。
3 ) 根据不同容器的存储方式,结合迭代器之间的关联性,指针和引用的独立性,以上结论很好理解。 由于以上情况的存在,在改变容器之后紧接着最好更新迭代器,以免出错;因为end返回的迭代器总是会失效,所以最后不要用变量保存它,而是在需要使用的时候重新执行end.()。
61. 泛型算法的一个关键概念:它不依赖于容器类型,仅仅运行在迭代器上。
62. 两种迭代器参数:
1) 第一个和第二个参数表示第一个序列的迭代器范围,第三个参数表示第二个序列的首元素迭代器,这种需要程序员保证第二个序列的长度不小于第一个序列。
2)四个参数按顺序构成两对迭代器范围。
63. 插入迭代器:
vector <int > vec;
auto it = back_insert(vec);
*it = 42 ;
64. 谓词:
谓词是一个可调用表达式,其返回结果是一个能用作条件的值。标准库使用的谓词分为两类:一元谓词和二元谓词。
65. 可调用对象 lambda表达式:
1 )它可以理解为一个未命名的内联函数。
2 )形式: [capture list](parameter list) -> return type {function body}
3 )普通函数不能在另一个函数内部定义,而lambda却可以,这就是其特别之处,它的捕获列表捕获的是它所在函数的局部变量。
4 )值捕获:在lambda创建时就进行捕获值的拷贝。局部变量的值发生变化不会影响所捕获的值。引用捕获:在捕获列表的成员前加&即构成引用捕获,其值会随局部变量的变化而变化。
5 ) 隐式捕获:在方括号里写上& 或 = 表示使用隐式捕获,编译器会自动推断,&表示引用捕获,=表示值捕获。
6 ) 混合捕获:...p352。
7 ) 可变lambda:对于值捕获,在函数体左花括号前加上mutable,表示lambda会改变捕获的值。
8 ) 默认情况下,如果一个lambda函数体内部还包含了除return 之外的语句,在编译器假设其返回void 。
66. 参数绑定:
1 )明确概念:对于多次使用的lambda,可以用一个函数代替它,算法中的迭代器数目不一定和函数的参数数目一致,这样就无法传递,这样就需要参数绑定。
2 )bind函数定义在头文件functional中,他接受一个可调用对象及其参数,返回一个新的可调用对象。
3 )一般形式: auto newCallable = bind(callable,arg_list)
4 )arg_list中可能包含形如 _n的名字,n是一个整数,_n是newCallable的参数。
5 ) 两种using 声明:
a. using std ::placeholders::_1;
b. using std ::placeholders;
6 ) bind实际上可以看做两个函数之间的映射,这两个函数的参数列表常常有区别,也有关联。
7 ) bind的arg_list中,非_n形式的参数实际上实现了lambda捕获列表的功能,采用拷贝的方式传递这些参数。但是有些局部变量不能被拷贝,比如ostream,这个时候就需要用ref()生成对象的引用,而引用是可以被拷贝的。
67.再探迭代器:
1)输入流迭代器一方面可以方便地直接从输入数据构造容器,一方面可以是输入数据直接运用于泛型算法。
2)输出流迭代器可以方便的进行输出而不用写循环,并且解引用和自增运算符对于输出流迭代器可有可无。
3)可以为任何定义了输入运算符>>的类创建istream_iterator对象,也能为任何定义了输出运算符的类创建ostream_iterator对象。
4)为了保持相同的左闭右开区间的关系,返向迭代器和对应的普通迭代器并不是指向相同元素,而是指向相邻的元素。
5) 插入迭代器常常用做泛型算法结构中的单个目标迭代器,输出流迭代器也可以做此用途。
68. map multimap set multiset 都是有序关联容器,在定义的时候需给出比较元素大小的谓词,存储的时候据此按顺序存储。
multiset <Sales_data,decltype (CompareIsbn)*>
bookstore(CompareIsbn)
69. 1 ) 静态内存:储存static 对象,定义在任何函数之外的变量。
2 ) 栈内存:储存定义在函数内部的非static 对象。
3 ) 堆(动态分配的内存):也被称为内存池,自由空间,它用来储存程序中动态分配的对象。
70. 智能指针:
1 )与emplace类似,make_shared函数利用参数来构造给定类型的对象。如:make_shared<string >(5 ,'9' );
2 )用内置指针初始化智能指针的时候,必须用直接初始化,因为智能指针的构造函数是explicit 的。p412
3 )默认情况下,用来初始化智能指针的内置指针必须指向动态分配的内存,因为智能指针会自动释放它们。但是有些时候我们也可以用一个不是指向动态内存的内置指针来初始化智能指针,这种时候要求我们自定义可调用对象来代替delete。
4 )引用计数仅仅局限在智能指针的自身的拷贝上,当孤独的创建两个指向同一块内存的智能指针,它们的计数会独立进行,不会叠加,这常常出现问题。
5 )如果使用get ()返回的指针,那么在最后一个智能指针销毁后,该指针失效。
6 )一个unique_ptr“拥有”它所指的对象,它只能用new 初始化,不支持拷贝和赋值,但是可以通过release和reset转移控制权。不能拷贝的特性有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr,比如返回一个unique_ptr。
7 )智能指针和普通指针的一个重要区别:当指向一个数组或向量时,智能指针指向“整个数组或向量”,而普通指针指向其中一个元素。智能指针的解引用就相当于向量名字,而普通指针的解引用相当于向量元素。
8 ) new 的一个局限表现在它将内存分配和构造结合在一起,这除了会导致一些浪费,还会使没有默认构造函数的类无法使用new 。
71. 拷贝构造函数:
1) 如果一个构造函数第一个参数是自身类类型的引用(必须是引用p442),且其他参数都有默认值,则其为拷贝构造函数。
2) 如果没有定义拷贝构造函数,编译器会为我们合成一个。
72. 拷贝构造函数、拷贝赋值运算符、析构函数具有相同的基本形式,它们三者应该尽量同时地显式定义,p474阐明了原因。具体来说:
1) 需要析构的类几乎可以肯定需要另外两者。
2) 需要拷贝操作的类也需要赋值操作,反之亦然。
3) 当类中有成员是无法拷贝或者是不能拷贝的时候,合成拷贝控制成员会被定义成删除的。p477
73. 类的拷贝语义:
1 ) 这是指让类对象看起来像一个值(拷贝过后相互独立)或一个指针(拷贝过后还有关联,比如指针和它的副本指向同一内存)
2 ) 行为像值的类的拷贝:对于指针的处理,应该用new 分配新的内存地址来使副本和元指针独立。
3 ) 行为像指针的类的拷贝:与2 )不同,指针直接拷贝,并利用一个指针成员作为计数器,指向动态内存,内存中的值就是计数值,在拷贝构造函数的函数体内递增该值。还要在析构函数体内递减该计数值。486
74. 使用移动而非拷贝的原因:
1) 拷贝后原对象会立即被销毁,浪费。
2) IO类、unique_ptr等不能拷贝的对象。
75. 左值引用和右值引用:
1) 常引用(左值引用)可以绑定到右值。事实上:常量左值引用可以绑定到所有类型的值,包括非常量左值、常量左值、非常量右值和常量右值。
2) 返回左值引用的函数,赋值、下标、解引用、前置递增递减都返回右值。返回非引用类型的函数、算术、关系、位、及后置递增递减都返回左值。
3) 对一个移后源对象,可以赋值和销毁,但是不能使用。
4) 右值是可以被安全地移动走的,因为通常,右值在起到作用后会立马被丢弃,不会被继续利用,所以把它移动走是安全的。
76. 移动构造函数:
1 )移动操作不应该抛出任何异常(因为不分配资源)深入原因p474
2 )在移动后,要将移后源对象置于可析构状态。比如用nullptr
3 )当一个类没有定义任何版本的拷贝控制成员,且每一个非static 成员都可以移动时,编译器会合成移动构造函数和移动赋值运算符。
4 )个人理解:很多类都会有指针成员,指向为这个类分配的一些内存。对于拷贝构造,会将这些内存的内容拷贝到另一个类中,而移动构造仅仅是拷贝指针,而将原来类中的指针析构。
5 ) 个人理解:其实在类的编写过程中,左值引用也可以手动的进行指针的拷贝(所有权的交换);但这样在函数重载的层面上无法和拷贝构造函数区分开来,所以引入了专门的右值引用。
6 ) 由于内置类型可以直接移动,而类类型有自己的移动构造函数,所以编译器可以合成移动构造函数。
7 ) 用拷贝构造函数代替移动构造函数几乎可以肯定是安全的,因为前者不会是移后源对象改变,所以在没有移动构造函数是,传入右值引用还是会用拷贝构造。
8 ) 当赋值运算符的参数为非引用类型时,会根据传入的是左值还是右值自动执行拷贝或者移动操作,实现了对两种功能的统一。
9 ) 右值引用不仅可以用于构造函数,也可以用于成员函数。(左值不能原址排序?p484)
77. 运算符重载:
1) 当把运算符定义为成员函数的时候,左侧运算对象必须是该类的一个对象。
78. 继承(部分知识):
1)动态绑定针对于虚函数,根据传入参数的不同,选择函数版本。虚函数的解析过程发生在运行时。
2)可以将派生类对象认为是由基类部分和派生类部分组成。
3)静态类型:对象在声明时采用的类型。是在编译期确定的。
4)动态类型:目前所指对象的类型。是在运行期决定的。
5)如果表达式既不是指针也不是引用,则它的静态类型和动态类型永远一样。
79.虚函数:
1)为什么叫虚函数?因为它有不确定性,在通过引用或指针调用的时候,只有在运行时才能确定调用版本。
2)一个函数一旦被定义为虚函数,以后的派生类中它也一直是虚函数。
3)
. + +