C++ Primer

第一章

cout 和 cerr, clog 可被重新定向,与相关文件关联.

第二章

对unsigned赋值时超出类型最大值情况会区模留下剩余.signed大多数情况也有类似操作,单视具体机器而定.

8进制0开头表示,例如

1
2
int a = 024
//a = 20

初始化方式:

1
2
int a = 1;
int b(1);

变量定义与声明是两个概念.每个变量只能被定义一次,但可以被声明多次(extern)

const变量与其他变量不同,如果不加extern则为当前文件的局部变量.即:

1
2
3
4
5
6
7
8
9
file1 
int a;
file2
extern a;
///////
file1
extern const int a = 1;
file2
extern const int a;

但是extern如果初始化了,则视为定义 如:extern int a = 1;

pp.50

引用:当引用初始化后则不可更改.

typedef定义类型的同义词.

1
typedef long long ll

structclass只影响初始访问级别.默认分别为public 和private

编译 可先生成目标文件,再利用目标文件生成可执行程序.这样单个文件发生修改只需重新生成单个文件的.o文件

1
2
3
g++ -c a.cpp
#this will generate a.o
g++ a.o -o a.out

避免多次包含

1
2
3
#ifndef XXX_H
#define XXX_H
#endif

pp.75

第三章

string操作.

string可以与字符串相加,但字符串不能与字符串相加.

1
2
3
string c;
c = "aa" + "bbb" + c; //wrong
c = c + "aa" + "bbb"; //correct

vector

添加元素只能用pushback(),下标操作只能针对已经有的元素.

迭代器iterator

声明

1
2
3
4
5
6
7
8
9
10
vector<int> aa;
vector<int>::iterator iter;
iter = aa.begin();
//find value
*iter;
//inc
iter++;
/////
vector<int>::const_iterator iter;
//value can not be changed!

bitset

位操作.

1
2
3
4
5
6
7
8
bitset<16> a;
//16位
a[1] //0 or 1

bitset<16> a;
a = 7;
a = a<<1;
cout<<a<<endl;

第五章

结合性:复制=操作与其他操作不同,具有从右向左结合性

1
a = b = 1;

i++ and ++i : i++ 比 ++i 多一个变量,因为需要先使用一个变量保存原值以便其他操作使用.

pp.147 操作符优先级 表5-4

delete and delete [] 如果是对象, 没有[]则只有第一个元素盗用析构函数.如果是基础类型,则无区别.

第六章

使用NDEBUG调试.

1
2
3
4
5
6
7
#ifndef NDEBUG
cerr<<""<<endl;
#endif

//if compile it with g++ -NDEBUG xx.cpp , such info doesn't present.

//also assert(XX) will not work.

第七章

利用const引用避免复制.引用保证实参不被复制,const保证不被修改.

引用必需类型相同,以下非法

1
2
3
4
5
6
7
int f(int & val);
short v1 = 0;
f(v1); //error
const int a = 1;
f(a) //error
f(1);//error
f(v1+v2)//error

应该将不需要改变的形参定义为const引用,非const引用形参不能使用const初始化.也不能用字面值或产生右值初始化

1
2
int * m[10]; // array of 10 pointers
int (*m)[10]; //pointer to an array of 10 ints

主函数main不能调用自身

默认实参,只能在声明时.

1
2
3
4
5
// ff.h
int ff(int a = 1);
//ff.cpp
#include "ff.h"
int ff(int a =1){} //error

static局部对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int f()
{
static int a = 0;
a++;
return a;
}
int main()
{
for(int i=0;i<10;i++)
{
cout<<f()<<endl;
}
return 0;
}

大多数编译器不支持递归函数inline,inline函数应该在头文件中定义,这一点不同于其他函数.

class内函数:

编译器隐式地将类内定义的函数定义为inline函数

1
2
3
4
5
class A
{
public:
int f() const; //const 表明this指针为 const A * 类型,即该函数无法修改类内变量.
}

构造函数:与类同名且没有返回类型.如果不定义默认构造函数,系统会自动添加.但是如果定义了构造函数,则系统不会添加无参数的默认构造函数.

重载函数: 参数列表不同的同名函数.仅仅返回值不同不可以.也不能基于形参是否为const重载.

作用域: 函数内声明的函数会屏蔽所有类外同名函数.因为c++名字查找发生在类型检查之前.

最佳匹配:

1
2
3
4
5
6
7
8
void f(int);
void f(double,double =3.3);

f(3.3) // call f(double , double = 3.3)
//another
void f(int)
void f(short)
f('a') // call int

指向函数的指针: bool * pf (int a , int b); pf(1,2);

第八章

文件:

1
2
3
4
ifstream infile("a.txt");
ifstream infile;
infile.open("a.txt");
if(!infile)...

stringstream

1
2
3
4
5
6
7
string line,word;
while(getline(cin,line)){
istringstream stream(line);
while(stream>>word){
//...
}
}

第九章

顺序容器: vector, list, deque(双向队列)

顺序容器适配器:stack, queue, priority_queue

将一个容器复制给另一个容器时,类型必需匹配.

容器内元素类型约束:1,必需支持赋值运算;2,元素类型的对象必需是可以复制的.(最低要求)

有的需要默认构造函数.,例如foo类有一个int输入的构造函数,则:

1
2
3
vector<foo> empty ; //可以, 因为没有构造函数需要执行
vector<foo> bad(10); // 错误,有10个实例需要执行默认构造函数
vector<foo> ok(10,1); //可以,有输入为一个int的构造函数.

在指定容器的容器时候必需使用空格,如:vector< vector<string> >;

vector, deque 支持 - > < >=, 但是list不支持.last,end指向的是末端的下一个元素

rbegin,rend,返回逆序迭代器. insert是在迭代器指向的元素前插入

pp.276 任何inser和push操作都可能会导致迭代器失效.应确保迭代器更新.

vector容器额外预留的空间,以保证新加元素空间上连续.使用.capacity查看预留

适配器让一种已知的容器类型采用另一种不同的抽象工作方式,如stack.

priority_queue, 元素类型要有<比较.

第十章 关联容器

map, set, multimap, multiset

pair.

1
2
3
pair <int,int> a;
a.first=1, a.second =2;
a = makepair(1,2);

map 的 insert 返回一个迭代器和一个bool类型的变量

1
2
3
4
5
6
7
8
9
10
pair<map<string,int>::iterator,bool> ret = word_count.insert(makepair(word,1));

//当下标运算不存在元素时候会新添加,如:

int occures = word_count["aaaa"];
//若不改变元素,则应该使用
m.count(k);
// or
m.find(k); //找不到返回end();
//map可按照key值顺序遍历

set内元素唯一,添加多次相同元素只添加一次.

在multimap中, 一个键所对应的元素必然连续存放,所以先使用count得到对应数目,然后使用find得到首元素即可.

第十一章 泛型算法

泛型指的是它可以操作在多种容器类型上.

1
2
3
4
5
6
7
8
find(); //<numeric>
accumulate();
fill();fill_n();
back_inserter();//<iterator>
replace();
sort();stable_sort();
unique(); //删除**相邻的**重复值
count_if(beg,end,func);

第十二章

不能从const 成员函数返回指向类对象的普通引用,只能返回const引用.

必需将mutable放在其他成员声明之前.

引用和const必需在初始化列表中初始化.

初始化列表的执行次序与定义变量的次序相同.

当构造函数被声明为explicit时,编译器将不使用它作为转换操作符. pp.395

friend 可以访问私有成员.

static数据成员独立于该类的任意对象而存在.

static函数没有this指针.

const static在类的定义体中初始化时,改数据成员仍必需在类的定义体外进行定义. 用常量初始化除外.

第十三章

复制构造函数.

因为有了RVO,这一部分就去他妈的吧!: RVO;
停止RVO ,编译选项 : -fno-elide-constructors

1
string null_block = "aaa";

编译器首先调用接受一个c风格字符串形参的string构造函数,创建一个临时对象.然后使用string复制构造函数将null_block初始化为那个临时对象的副本.

复制构造函数就是接受单个类类型引用形参的构造函数.

1
2
3
4
5
class Foo{
public :
Foo();
Foo(const Foo &); //必需要有引用,通常为const
}

如果需要禁止复制,则应把复制构造函数声明到private中.
如果定义了复制构造函数,也必需定义默认构造函数.

1
2
A a = b; //copy
a = b; // =

第十四章 重载操作符

不能定义新的操作符,重载操作符必需具有至少一类类型或者枚举类型的操作数.

大多重载操作可以定义为普通非成员函数或类的成员函数.区别只在this指针和参数个数.

当设置为非成员函数时候,需要设置为类的友元,这样才能操作数据.

IO操作符重载必需为非成员.因为定义为成员时做对象必需是类对象.类似的,赋值符必需返回*this的引用

第十五章 面向对象编程

面向对象编程基于三个基本概念: 数据抽象,继承和动态绑定

多态型: 继承而相关联的类型

virtual目的是启用动态绑定.基类通常将派生类需要重新定义的函数定义为虚函数.

protected 成员可以被派生类访问但不能被该类型的普通用户访问.(也就是只能访问自己的而不能访问别的类对象的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Item_base{
public :
...
private :
string isbn;
protected :
double price;
}

class Bulk_item : public Item_base{
public:
private:

}

void Bulk_item::memfcn(const Bulk_item & id, const Item_base & b){
double ret = price ; // ok
ret = d.price; // ok
ret = b.price; // error;
}

//protected 同类型是public, 不同类型是private.

一旦函数在基类中声明为虚函数,它就一直为虚函数,派生类无法该函数为虚函数这一事实.派生类重新定义虚函数时,可以使用virtual关键字但是这不是必须的.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class A {
public :
virtual void test(){
cout<<"A"<<endl;
}
void testB(){
cout<<"tB inA"<<endl;
}
};

class B : public A {
public :
void test(){
cout<<"B"<<endl;
}
void testB(){
cout<<"tB"<<endl;
}
};
int main()
{
A a;
B b;
A * pa;
pa = &a;
pa->test();
pa->testB();
pa = &b;
pa->test();
pa->testB();
//pa->testB(); //error
pa->A::test();
//pa->B::testB();//error
return 0;
}

基类指针只能通过访问基类有的东西,或者通过virtual访问到派生类版本.

public 继承 : 基类public->public, protected->protected, private->private

protected继承 : public,protected->protected, private->private

private: all->private

恢复权限:不能比基类更宽松或更严格.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Base{
public :
int size(){};
protected :
int n;
}

class Derived : private Base{
public :
using Base::size;
protected :
using Base::n;
}

友元: 友元关系不能继承.

继承后初始化次序: 首先初始化基类,然后根据声明次序初始化派生类.

如果派生类想使用基类带参数的构造函数,则需要在派生类构造函数初始化列表中显示执行基类带参数构造函数.

只能初始化直接基类.

重构:重新定义类的层次关系.

删除指针如果是基类指针,则析构函数只析构父类部分,因此通常需要将析构函数定义为虚函数.
构造函数和赋值操作符不能是虚构函数.

纯虚函数:

1
2
3
4
clas Disc_item :: public Item_base{
public :
virtual double net_price() const =0;
}

包含纯虚函数的类为抽象基类,不能实例化.