深入原理64式:32 C和C++知识总结

目标:
整理C和C++知识,主要包含如下内容:
1、数组
2、字符串
3、结构体、共用体与枚举
4、运算符及其优先级
5、C预处理器与内存管理
6、函数
7、指针与引用
8、类
9、多态与虚函数表
10、泛型,模板与元编程
11、STL
12、原理
13、makefile与链接库
14、智能指针
15、effective C++
16、线程,锁与队列

第一部分 数组


1 如何定义数组的引用?如何动态声明一维数组并删除?如何动态声明二维数组并删除?
1)假设数组定义如下:
int a[6] = {0, 2, 4, 6, 8 ,10}
则定义数组的引用如下:
int (&p)[6] = a;
2)
int *p = new int[n];
delete []p;
3)
int **a = new int* [m];
for(int i = 0; i < n; i++){
    a[i] = new int [n];
}
for(int i = 0; i < n; i++){
    delete []a[i];
}
delete []a;

2 指针数组与数组指针的区别是什么?请编写与int a[4][10]相同的数组指针?请编写与int a[10]相同的数组指针和指针?
1)指针数组:是一个数组,数组中每个元素是指针。
样例: int *a[10]; 
数组指针:是一个指针,该指针指向一个数组。
样例: int (*p)[10];
记忆技巧:
因为[]的优先级高于*,所以指针必须加上括号。
针有组无
数组指针有括号,指针数组无括号。
2)
int (*p)[10];
p = a;
3)
int (*p)[10] = &a;
int *q = a;
sizeof(*p) = sizeof(a) = 40;
sizeof(*q) = sizeof(a[0]) = 4;

3 文字常量区的字符串是否可以通过指针修改?使用数组名时会自动转换为数组首元素的地址吗?
1)不可以。
char *p = "hello";
p[0] = 'x';
错误。可以通过p[0]访问,但无法修改。
2)会。但是需要注意数组首地址是常量,不能进行赋值操作。
char s[] = "hello";
s += 2;
错误,s是不可修改的。

4 高维数组相关指针如何区分?
1)假设有 int a[4][5];
                        类型            含义
a+i:                    int (*)[5]        指向a[i]第i个元素a[i]的指针,行指针
*(a+i)=a[i]:            int *            指向数组a[i]首元素a[i][0]的指针,列指针
*(*(a+i)+j)=a[i][j]:    int                因为*(a+i)类型为int*,值
&a:                        int (*)[4][5]    


第二部分 字符串


1 什么是字符串常量? 'A'与"A"是否一样?字符数组有什么特点?字符数组有哪几种初始化方式?
1)字符串常量是双引号括起来的字符序列
2) 'A'表示单个字符A,而"A"表示字符串常量,等于A和空字符null两个字符。
3) 字符数组末尾必须添加'\0'作为结束符
4) 
char ca1[] = {'C', '+', '+'};
char ca2[] = {'C', '+', '+', '\0'};
char ca3[] = "C++";
其中ca2与ca3等价,长度为4,ca1长度为3。

2 子串与子序列的区别是什么?
1)子串: 串中任意连续字符组成的组序列。
子序列不要求连续,但顺序与其主串中一致。

3 strlen的作用是什么?请实现strlen。
1)strlen计算'\0'之前的字符个数。
2)
int strlen(const char* str){
    assert (str != NULL);
    int len = 0;
    while((*str++) != '\0'){
        len++;
    }
    return len;
}
注意:
(*str++)中先计算*str,然后str++。

3 memcpy的作用是什么?strcpy与memcpy的区别是什么?memset的作用是什么?
1) 
void *mempy(void* dest, const void* src, size_t n);
从src所指的位置拷贝前n个字节到目标地址dest,返回指向dest的指针。
2) 
strcpy(dest, src): 把从src地址开始且以null结束符的字符串复制到以dest开始的地址空间。
strcpy用于字符串复制,memcpy可以用于任何内容复制,
strcpy复制整个字符串,memcpy指定复制内容的长度。
3) void *memset(void *s, int ch, size_t n)
将s中前n个字节用ch替换并返回s。

第三部分 结构体、共用体与枚举


1 结构体如何定义的?定义结构体时会分配内存空间吗?结构体赋值的限制是什么?
1) typedef struct Node{
    int data;
    Node* next;
}*List;
2)不会。只有定义结构体对象时才会分配空间。
3)结构体赋值不允许跳过前面成员给后面成员赋值。

2 结构体中的位字段是什么?结构体中对齐规则是如何的?
1)位字段是指可以对整型或枚举类型指定占用特定位数。
样例:
typedef struct reg{
    unsigned int SN:4;
    bool good:4;
}

3 共用体的特点是什么?样例是什么?共用体占用内存是怎样的?结构体占用内存是各成员内存量总和吗?
1)在同一时刻,共用体中值存放一个成员。
2) union 共用体名{
    数据类型 成员名;
    数据类型 成员名;
};
3)共用体中占用成员为各个成员占用中最大值。
4)结构体中因为可能存在内存对齐,所以占用内存>=各成员变量内存之和。

4 什么是大端存储格式?请写出0x12345678在大端和小端中分别的存储格式?十六进制和二进制之间转换规则?
低字节存放在哪里?编写如下的内存存放地址格式?
1) 大端存储格式是高字节存放在低地址中。
2) 假设地址从0x4000开始。
大端存储格式:
内存地址:    0x4000    0x4001    0x4002    0x4003
存放内容:    0x78    0x56    0x34    0x12

小端存储格式:
内存地址:    0x4000    0x4001    0x4002    0x4003
存放内容:    0x12    0x34    0x56    0x78
3)4个二进制位等于一个十六进制
十六进制: 4位 0000
2个十六进制: 是一个字节。
因为一个字节=8bit=2位的十六进制。
4)低字节存放在低地址。
大端模式先为高bit位分配空间并存放地址的高bit位。
5)
struct {
    short bit1:4;//假设bit1位a0a1a2a3
    short bit2:9;
    short bit3:3;
};
分析:
a0a1a2a3
------->
高        低
大端模式下: 先为高位分配,也就是从a0开始先分配,
bit1(4位,顺序为a0a1a2a3)    bit2(高4位)    bit2(低5位)    bit3(3位)

5 枚举如何声明?
1) enum weekday{sunday, monday};
其中变量值从0开始。

6 sizeof运算符如何使用?sizeof对函数调用求值的结果是多少?sizeof与strlen的区别是什么?sizeof如何计算数组的大小?
1) sizeof是运算符不是函数,发生在编译时刻,计算操作数的大小。
sizeof(type),返回类型是size_t,实际是unsigned int
2) sizeof对函数调用求值时,返回的结果时函数返回值类型的大小,函数不会被调用。
但函数名称不能计算sizeof值。void类型无法计算。位域成员无法计算。
3)sizeof计算数据所占内存空间,将'\0'计算在内。
strlen不将'\0'计算在内。
C++会给字符串常量末尾自动添加"\0"。
char str[] = "hello";
sizeof(str)=6;
strlen(str)=5;
4)sizeof(数组)=数组的大小
sizeof(指针)=4

7 struct空间计算的规则是什么?请计算如下题目?含有结构体的结构体空间如何计算?
请计算如下含子结构体的题目?请计算含有数组的结构体的题目?
1)原则:
1.1 A)整体空间是占用空间最大的成员所占字节数的整数倍;
B)但若包含子结构体,则是子结构体与父结构体中占用空间最大成员所占字节数的整数倍。
C) 含数组的结构体,数组按照单个变量一一摆放,不是视为整体。
D) 含有位域结构体,
D.1)如果相邻位域字段类型相同,且其位宽之和小于类型的sizeof之和,则后面的字段将紧邻前一个字段存储,
直到不能容纳为止。
D.2) 如果相邻位域字段类型相同,且其位宽之和大于类型的sizeof之和,则后面的字段将从新的存储单元开始,
其偏移量大小为其类型大小的整数倍。
D.3) 若相邻位域字段类型不同,则Dev-C与gcc采取压缩方式。
D.4) 如果位域字段之间穿插非位域字段,则不压缩。
D.5) 整个结构体总大小为最宽基本类型成员大小的整数倍。
E)使用"#pragma pack"结构体空间计算,
#pragma pack(n),编译器将按照n个字节对齐,n为字节对齐数,取值为1,2,4,8,16等,默认为8。
                如果这个值比结构体成员的sizeof值小,那么该成员的偏移量以此 为准,offsetof(item)=min(n, sizeof(item))
                结构体大小是offsetof(item)中最大值的整数倍即可。
#pragma pack(),取消自定义字节对齐方式。
F)空结构体大小为1,用于占位。

1.2 A)数据对齐:按照结构体成员先后顺序排列;
B)但若包含子结构体,当排到子结构体成员时,其前面已经摆放的空间大小必须是该子结构体
中最大类型大小的整数倍,不够则补齐。
2)某计算机存储器按照字节编址,采用小端存放数据,假定int位32位,short位16位,
数据对齐。若程序段如下:
struct{
    int a;
    char b;
    short c;
}record;
record.a = 273;
若record变量的首地址为0xC008,则地址0xC008中内容以及record.c的地址分别是多少?
解:
小端会优先将低位的放在低地址处。
a占据4个字节,char占据1个字节,short占据2个字节,但是因为对齐,导致char和short一共占据4个字节。
----|-|-|--|
a    b    c
a是273等于256 + 16 + 1,对应二进制表示是:
0000 0001 0001 0001
对应十六进制是:
0x 0111
首地址0xC008代表1个字节,存放的是最低位,所以需要十六进制中两位,
所以位0x11。
record.c地址是0xC008加上6个字节,也就是0xC014,即0xC00E
3)
strict S3{
    char c;
    int i;
};
struct S4{
    char c1;
    S3 s;
    char c2;
};
sizeof(S3)=8;
sizeof(S4)=16;
这是因为S3是子结构体,对齐时,会采用子结构体和父结构体中成员中最大成员字节数的整数倍,
因为S3和S4中最大的成员所占字节数是int占据的4字节,
所以c1占据1个字节,s会占据1个字节对齐占据4个字节,并使得c1占据4个字节,i占据4个字节,c2占据1个字节但对齐需要总共4个字节,
所以最终需要4 + 4 + 4 + 4 = 16个字节。
4)
struct s1{
    char a[8];
    int b;
};
则sizeof(s1)=12。
因为数组是按照单个元素计算的。
5)在Linux + gcc环境下,以下结构体的sizeof值是多少?
struct a{
    int f1:3;
    char b;
    char c;
};
分析:
结构体中有位域,需要3位,不足1个字节,占据1个字节,另外两个char分别占据一个字节,
总共3个字节,但是总大小是最宽数据类型int的整数倍,即4的整数倍,所以是4。

8 union空间计算?
整个联合体的sizeof是每个成员sizeof的最大值。

9 enum空间计算?
枚举类型=int=4字节


第四部分 运算符及其优先级


1 前缀运算符与后缀运算符的特点是什么?
前缀运算是先变后用,后缀运算是先用后变。
后置自增只能用于右值表达式。
所以:
(a++) += a;
这句是错误的。

2 ++、--运算符的结合方向是什么?
k=-i++;
因为-与++优先级相同,但是由于自增,自减,符号运算符的结合方向是自右向左,
所以k=-i++=-(i++);

3 有哪些位运算符?
^:按位异或,两位不同为1
~: 取反
<<: 左移
>>: 右移,int右移高位补符号位,unsigned int高位补0

第五部分 C预处理器与内存管理


1 C预处理包含哪些内容?
1)C预处理包含: 宏条文
宏定义与宏替换
条件编译
文件包含

2 如何对常量进行宏定义?如何对带参数的宏定义替换?宏替换的本质是什么?
1) #define 标识符 字符串
2) #define 标识符(参数列表) 字符串
例如: #define FUN(x) ((x)*(x))
注意:如果用宏定义参数,要给每个参数要加括号
3)宏替换的本质是文本替换,不做语法检查,编译前进行,不分配内存。
尽量用const替换宏

3 条件编译的形式有哪些?需要注意什么?如何避免重复包含?
1) 
#if/ifdef/ifndef
#elif
#else
#endif
2)注意重复包含。
3)
#ifndef MA
#define MA
//此处是某个类的定义与相关函数定义
#endif

4 什么是全局变量?如何引用全局变量?如何避免局部变量屏蔽全局变量?
1)全局变量是在函数外部定义的变量,属于一个源程序文件
2)
extern int counter;//引用其他文件定义的全局变量
++counter;
3)使用::或者extern
int counter=3;
int main(){
    exterm int count;
    ......
}

5 static的作用有哪些?类中的static作用是什么?静态数据成员存储在哪里?
静态数据成员为什么不能在类声明中定义?const static成员可以在类的定义提中初始化吗?
静态成员函数与普通成员函数有什么区别?
1)static作用是:隐藏,初始化为0,持久化局部变量的内容,修饰成员函数/变量表示独立于类的对象。
隐藏: 所有未加static前缀的全局变量和函数都有全局可见性
初始化为0:是因为静态变量存储在BSS段,在BSS段中的内存所有字节默认值为0x00
持久化局部变量的内容:静态局部变量生存期为整个源程序。
2)类中的static修饰成员函数/变量表示独立于类的对象。
需要在类的外部进行定义。静态成员属于类,只有一份,被所有对象共享。
3)静态数据成员存储在全局存储区。
4)静态数据成员定义时要分配空间,所以不能在类声明中定义。
样例:
double Student::static_var=1;
5)const static数据成员可以在类的定义中初始化,但必须在类外进行定义。
样例:

6)静态成员函数不与任何对象关联,不具有this指针,无法访问普通数据成员和普通成员函数。
static成员函数不能被声明为const,因为将成员函数声明为const就是承诺不会修改该函数所属的对象。
但是普通函数可以访问静态成员函数与静态成员变量。

6 const作用是什么?const在C与C++中的区别是什么?
1) const把对象转换成常量。
2) C把const认为一个别的地方有存储分配的声明,是外部连接;
C++中const是内部连接
用const代替#define的值替换功能,是因为const有数据类型,const可进行常量折叠来对常量表达式计算求值。

7 指向const的指针和const指针分别是什么意思?const在修饰返回值是起什么作用?
const用来修饰函数参数有什么作用?const在勒种的应用是什么?
1)指向const的指针,样例: const int* cptr;表示是指向const对象的指针,其指向的东西不能修改
const指针表示指针本身是常指针不可修改,需要将const放在*的右边。
样例: 
double d = 1.0;
double const* cptr = &d;
记忆:
const靠近指针,则是常指针;靠近数据类型(例如int)则表示指向const的指针。
2)const修饰返回值,常常用于返回用户自定义的类型。
3)表示函数参数不会改变。
gcc下临时变量都作为常量。
4)const成员函数,例如:
void func2() const;
使得this形参指向的对象为const类型,用于确保该成员函数可以作用于const对象身上。
const数据成员必须在构造函数的成员初始化表中进行初始化。
样例如下:
struct Thing{
    Thing():valueB(1){...}
    int valueA;
    const int valueB;
    static int b;
    const static int c;
};
int Thing::b = 1;
const int Thing:c = 1;

7 堆和栈的区别是什么?C和C++中如何手动申请内存?malloc/free与new/delete的区别是什么?
1)
堆:手动申请与释放,速度慢,自动调用对象的默认构造函数初始化该对象。
栈:编译器自动分配与释放,速度快。
2)
C语言:
char* p1 = (char *)malloc(10);
free(p1);
int *p = (int *)malloc(sizeof(int) * length);
free(p);
C++:
char* p2 = new char[10];
delete []p2;
3)
malloc/free:需要指定分配对象的内存空间大小,无法执行构造函数和析构函数
new:自动计算需要分配空间大小,可用于创建动态对象,调用operator new分配内存,运行该类型的构造函数,
delete:执行对象的析构函数,调用operator delete释放内存。


第六部分 函数 


1C语言中函数传参有哪些类型?C++中函数传参有哪些类型?为什么使用引用传递?编写一个用引用交换两个数?请说出下面的程序结果。
1)C语言中包含值传递和指针传递。
2)C++中是值传递,指针传递和引用传递。
3)引用传递避免实参拷贝,减少空间浪费,对其修改会影响实参本身。
4)
void swap(int &p, int &q){
    int temp = p;
    p = q;
    q = temp;
}
5)
void swap_str(char* a, char *b){
    char* temp = a;
    a = b;
    b = temp;
}
上述程序无法真正交换原来的两个字符串,是因为
只是交换了a,b指针的指向,但是对于原来传入的实参则并内有交换。

2 内联函数作用是什么?
1)内联函数适用于优化只有几行且经常被调用的函数。

3 什么是函数重载?请举例?函数重载中要求参数列表不同具体是指什么?如果仅仅返回类型不同,可以称为函数重载吗?
1)函数名相同但是参数列表不同的函数。
2)
void print(int value){
    cout << value << endl;
}

void print(strin value){
    cout << value << endl;
}
3)参数列表不同具体指参数个数或者参数类型不同。
4)不能。

4 C++程序调用被C编译后的函数,为什么要加extern "C"?
C++为了支持重载会在编译时,对函数名字加入函数返回类型等信息的处理,
但是C语言不会,链接阶段若按照C++函数命名规则取查找C编译的函数就会出错。
告诉编译器该函数是C编译器编译的,用C方式来链接它们。

第七部分 指针与引用


1 指针的算术运算是怎样的?指针+1和-3的含义分别是什么?
1) 指针和整数运算时,会根据指针类型进行调整。
例如:
float + 3 
因为float是4个字节,因此4 * 3 = 12个字节。
2) 指针+1表示指向数组的下一个元素。
指针-3表示指向向左移动3个元素后的元素。

2 typedef的作用是什么?如何使用?
1)typedef用于给已有类型名起别名。
2) typedef 类型名 标识符;
例如:
typedef shared_ptr MyQueue;
typedef float REAL;

3 void*指针的作用是什么?有什么需要注意的?
1) void*指针可以保存任何类型对象的地址。
2) 不允许使用void*指针操纵它所指向的对象。

4 指向指针的指针如何使用?
1)指针包含变量的地址。
指向指针的指针用**表示。
例如:
int a = 1;
int* pa = &a;
int** ppa = &pa;

5 什么是函数指针?用法如何?函数指针有什么作用?
如何用typedef简化函数指针定义?函数指针如何作为形参?
请编写返回指向函数的指针?
如何声明一个指向10个元素的数组的指针,每个元素是一个函数指针。
函数的返回值是int,参数是int*。
1)函数指针是一个指针,该指针指向函数。
2)函数指针在*两边需要加上括号,因为不加上括号,表示函数返回的值是一个指针。
样例:
bool (*pf)(string &, string &);
3)函数指针可以作为参数,用于实现各种策略。
4) 
typedef bool (*cmpFcn)(const string &, const string &);
该定义表示cmpFcn是指向函数的指针类型名字。
cmpFcn pf1=0;
这表示将函数指针初始化为0。
引用函数名但没有调用该函数时,函数名被解释为指向函数的指针,
直接引用函数名等于在函数名上应用取地址操作符。
5)
void big(string &, string &, bool(string &, string &));
等同于
void big(string &, string &, bool(*)(string &, string &));
6)原则: 阅读函数指针声明从里向外
int (*ff(int))(int *, int);
解释:
ff(int)声明ff为一个函数,带有int的形参,该函数返回
int (*)(int *, int);
即返回是一个指向函数的指针。
用typedef定义
typedef int (*PF)(int *, int);
PF ff(int); //函数ff返回一个函数指针
7)
数组指针的写法是:
int (*p)[10];
指针函数的写法是:
int (*pf)(int *);
数组指针中每个元素是指针函数的写法是:
把上述pf去除,用(*p)[10]代替。得到如下内容:x
int (*(*p)[10])(int *);

6 什么是指针函数?
1)是一个函数,返回值是指针。

7 什么是引用?引用有什么特点?引用与指针的区别有哪些?const引用是什么?
const引用的作用是什么?引用做类的数据成员的特点是什么?
1)引用的内容该该引用绑定对象的地址,使用变量时,会根据这个地址找到
绑定对象。
2)定义引用必须跟一个变量绑定且不能修改。
3)引用不能为空,指针可为空;
引用不能改变为对另一个对象的引用,指针可指向另一个对象。
sizeof(引用)得到的是指向变量的大小,sizeof(指针)为指针本身大小。
4)const引用是指向const对象的引用,当引用的对象是const对象时,
引用也必须是const。
const int val = 1024;
const int &refVal = val;
int &refVal2 = val;//错误
5)常引用定义一个普通变量只读属性别名,
不修改,不拷贝。
6)引用类型数据成员必须在初始化列表中初始化
class ConstRef{
    public:
        ConstRef(int ii):i(ii), ci(i), ri(i){}
    private:
        int ii;
        const int ci;
        int &ri;
};


第八部分、类


1 什么是this指针?
1)this指针是隐含的形参,为调用函数对象的地址。

2 什么是构造函数?什么是成员初始化列表?成员变量初始化顺序是怎样的?
哪些变量必须放在成员初始化列表中?
1)构造函数和类同名且没有返回类型,用于初始化。
2)冒号和花括号之间的代码。
样例:
A():i(iVal){}
3)成员变量初始化顺序与变量声明顺序相同,与初始化列表中顺序无关。
4)const成员变量与引用类型成员变量。

3 什么是复制构造函数?分为哪些类型?拷贝构造函数的参数可以不是引用吗?
1)复制构造函数又叫做拷贝构造函数,形参是本类类型对象的引用(常用const修饰)。
2)包含直接初始化和复制初始化。
直接初始化使用圆括号,复制初始化使用=
string a("hello");//直接初始化
string b = a; //复制初始化
3)不是引用,则变成传值方式,传值会调用类的拷贝构造函数,造成无穷递归问题。

4 什么是深复制?什么是浅复制?
1)浅复制: 仅仅复制锁考虑的对象,不复制它所引用的对象。
深复制:把要复制的对象所引用的对象都复制一遍。

5 析构函数的作用是什么?当有虚函数时,析构函数需要成为什么?
当类B从类A继承而来,类A的析构函数不是虚函数,会发生什么?
1)析构函数用于资源回收。
2)析构函数要成为虚析构函数。
3)若基类指针pA指向new生成的派生类对象B时,delete基类指针,会造成派生类不会没有释放。

6 派生类构造函数是什么,请举例?构造函数与析构函数调用顺序是什么?
1)
派生类名(总参数表):基类构造函数(参数列表){
    //函数体
};
样例如下:
2)
单继承:
构造函数顺序:
先调用基类的构造函数,然后调用子类的构造函数;
析构函数顺序:
先调用子类的析构函数,然后调用基类的构造函数。

多继承时:
基类构造函数在类派生列表中的出现次序调用
例如:
ZooAnimal
|
Bear        Endangered
    Panda
则顺序是:
ZooAnimal,Bear,Endangered。

虚继承:
首先调用虚基类的构造函数,徐积累如果有多个则按照出现顺序。

7 操作符重载如何使用?哪些操作符不能重载?实现一个CMyString类,包含拷贝构造函数,
复制操作符重载?复制操作符重载需要注意什么?
1)operaror后跟定义的操作符
2)::和.*和.和?:
即带点的都不能重载。
3)
class CMyString{
    public:
        CMyString(const char* pData=NULL){
            if(pData == NULL){
                m_pData = new char[1];
                *m_pData = '\0';
            }else{
                int len = strlen(pData);
                m_pData = new char[len + 1];//注意加上"/0"
                strcpy(m_pData, pData);
            }
        }
        CMyString(const CMyString &other){
            int len = strlen(pData);
            m_pData = new char[len + 1];
            strcpy(m_pData, other.m_pData);
        }
        ~CMyString(){
            delete []m_pData;
        }
        CMyString& operator =(const CMyString &str){
            //判断是否是同一个对象
            if(this == &str){
                return *this;
            }
            //生成新的之前需要先删除原来的
            delete []m_pData;
            m_pData = NULL;
            m_pData = new char[strlen(str.m_pData) + 1];
            strcpy(m_pData, str.m_pData);
            //返回当前对象,即*this
            return *this;
        }
    private:
        char* m_pData;
};
4)
A)复制操作符重载需要把返回值的类型声明为该类型的引用,
并返回实例自身*this的引用。
之所以这样做,是因为这样才可以连续赋值。
B)参数类型为常量引用,减少一次拷贝构造函数调用,
C)需要释放实例自身已有内存,否则造成内存泄漏
D)判断传入参数是否和当前实例*this是同一个,如果不判断,
释放内存带来严重问题。

8 复制构造函数与赋值运算符的区别是什么?
1)复制构造函数不返回任何值,赋值运算符有返回值。

9 输出操作符<<的重载如何编写?operator new与operator delete的重载过程是怎样的?
请编写对operator new和operator delete的重载?如何限制栈对象的生成?如何限制堆对象的生成?
1)输出操作符接受ostream&作为第一个形参,const对象引用作为第二个形参,
返回对ostream形参的引用
ostream& <<(ostream& os, const ClassType &obj){
    os << // ...
    return os;
};
2)
new的过程:    S1) 调用operator new, S2)执行构造函数,S3)返回对象指针。
delete过程: S1)    执行析构函数, S2)调用operator delete释放内存。
但是new和delete不能重载。
3)
class X{
    public:
        X(){}
        static void* operator new(size_t size){
            return ::operator new(size);
        }
        static void operator delete(void* pointer){
            ::operator delete(pointer);
        }
        ~X(){}
};
4)可以将构造函数和析构函数设置为私有。
5)产生堆对象就是使用new,所以需要禁止new,new会调用operator new,
所以可以将operator new成为private。

10 重载与覆盖,隐藏有何区别?请编写一个重载,覆盖和隐藏的样例。
1)重载:就是函数名同名,参数个数或参数类型不同的一组函数
覆盖:是指在派生类中覆盖基类的同名函数,要求基类必须是虚函数。
并且派生类和基类的函数参数个数,参数类型,返回类型都相同。
隐藏:派生类中与基类存在同名函数,并且基类函数不是虚函数
2)
class A{
    public:
        virtual void fun1(int, int){}
        void fun(int value){
            cout << value << endl;
        }
};

class B : public A{
    public:
        void fun1(int, int){}
        void fun(int value){}
        void foo(int a){}
        void fool(float a){}
};
调用A类的fun函数:
B b;
b.A::fun(2);

11 继承格式是什么?什么是虚继承?基类成员在派生类中的访问属性是怎样的?
1) class <派生类名> : <继承方式> <基类名>, <继承方式2> <基类名2>, ...{
    <派生类新定义成员>
};
2)虚继承时,公共基类在对象模型中只有一份拷贝。
3)父类的protected成员不能被子类对象访问,但是可以被子类函数和友元函数访问。
注意:子类对象和子类成员函数对基类访问不同。

12 类间的转换规则是怎样的?请说明下面代码是否正确?
1) 派生类对象可以赋值给基类对象,即子可等于父,
但是基类对象不可以赋值给子类对象,即父不等于子。
因为派生类包含基类所有信息,基类缺乏派生类信息。
但是允许把基类对象指针/引用强制转换为派生类独享指针/引用
2)
class A{};
class B: public A{};
A a;
B b;

a = b;//正确,子类赋值给父类
b = a;//错误,父类不能赋值给子类
B* pb = (B*)&a;//正确,父类指针可强制转换为子类指针
B& rb = (B&)a;//正确,父类引用可强制转换为子类引用

13 什么是转换构造函数?转换构造函数必须满足哪些条件?如何禁止隐式转换?
1)用单个实参调用构造函数定义形参到该类型的转换。
将数据转换为类的对象。
2)转换构造函数需要构造函数有1个参数,将数据转换为对象。
3)将构造函数声明为explicit,禁止隐式转换。

14 什么是类型转换函数?请编写一个样例?类型转换函数有哪些限制?
1)将类的对象转换为数据。
2)
class Integer {
    public:
        Integer(int=0);//转换构造函数
        operator int();//类型转换函数
    private:
        int real;
};
Integer inte = 1;//调用转换构造函数将1转换为Integer类的对象。
int i = inte;
注意:
上述类型转换函数的函数名是:
operator int,希望转换的目标类型为int
3)类型转换函数不能有参数,不能指定返回类型。

第九部分、多态与虚函数表


1什么是多态?什么是泛型?多态的关键是什么?
1)父类指针指向子类实例,根据具体的子类实例调用不同的方法。
2)用不变的代码实现可变的算法或模板。
3)父类指针指向子类实例。

2 什么是虚函数?虚函数的实现是什么?构造函数为什么不能是虚函数?哪些函数不能成为虚函数?
1)含有virtual关键字修饰的成员函数。
2)虚函数的实现是虚函数表。
3)若父类的构造函数为虚函数,且子类也给出构造函数,则会值执行子类的构造函数。
这样父类就不能构造了。
4)普通函数,静态成员函数,构造函数,友元函数。

3 什么是虚函数表?什么是虚函数表指针?虚函数表的实现是什么样的,请举例?
什么是虚基类表指针?
1)虚函数表是类的虚函数地址表,父类指针操作子类时,指明实际应调用的函数。
2)每个对象被添加了一个指向虚函数表的指针叫做虚函数表指针vptr。
3)假设有类如下:
class Base {
    public:
        virtual void f(){ cout << "Base::f" << endl;}
        virtual void g(){ cout << "Base::g" << endl;}
        virtual void h(){ cout << "Base::h" << endl;}
};
通过Base的实例来得到虚函数表。
A)下面代码的输出结果是什么?
int main(){
    typedef void(*Fun)(void);
    Base b;
    Fun pFun = NULL;
    cout << "虚函数表地址:" << (int*)(&b) << endl;
    cout << "虚函数表第一个函数地址: " << (int*)*(int*)(&b) << endl;
    // Invoke the first virtual function
    pFun = (Fun)*( (int*)*(int*)(&b) );
    pFun();
    pFun = (Fun)*( (int*)*(int*)(&b) + 1 );
    pFun();
    pFun = (Fun)*( (int*)*(int*)(&b) + 2 );
    return 0;
};
分析:
通过把&b转换成int*,取得虚函数表的地址。然后再次取址就的大第一个函数的地址了。
虚函数表地址:         (int*)(&b)
第一个虚函数地址:    (int*)*(int*)(&b)
图如下:
&b        虚函数表
[]------>Base::f()    ||    Base::g()    ||    Base::h()    |*|
[]
[]

B)单继承和多基继承中虚函数的覆盖情况
画出下列类A、B、C、D的对象的虚函数表
class A{
public:
    virtual void a() { cout << "a() in A" << endl;}
    virtual void b() { cout << "b() in A" << endl;}
    virtual void c() { cout << "c() in A" << endl;}
    virtual void d() { cout << "d() in A" << endl;}
};

class B : public A{
public:
    virtual void a() { cout << "a() in B" << endl;}
    virtual void b() { cout << "b() in B" << endl;}
};

class C : public A{
    virtual void a() { cout << "a() in C" << endl;}
    virtual void b() { cout << "b() in C" << endl;}
};

class D : public A{
    virtual void a() { cout << "a() in D" << endl;}
    virtual void d() { cout << "d() in D" << endl;}
};

A的对象:
vptr->A::a    ||    A::b    ||    A::c    ||    A::d
B的对象:
vptr->B::a    ||    B::b    ||    A::c    ||    A::d
C的对象:
vptr->C::a    ||    C::b    ||    A::c    ||    A::d
D的对象:
B::vptr->D::a    ||    B::b    ||    A::c    ||    D::d
C::vptr->D::a    ||    C::b    ||    A::c    ||    D::d

对应图如下:
&d        虚函数表B
[]------>D::a    ||    B::b    ||    A::c    ||    D::d
        虚函数表C
[]------>D::a    ||    C::b    ||    A::c    ||    D::d
[]
[]

总结:
单继承时,仅有1个vptr,子类虚函数会覆盖基类中同名的虚函数。
多基继承时,有几个基类就有几个vptr。覆盖对应基类中的同名虚函数。

C)无虚函数覆盖
class Base{
public:
    virtual void f(){}
    virtual void g(){}
    virtual void h(){}
};

class Derive : public Base{
public:
    virtual void f1(){}
    virtual void g1(){}
    virtual void h1(){}
};
则对于实例: Derive d;的虚函数表如下:
&d        虚函数表
[]------>Base::f()|Base::g()|Base::h()|Derive::f1()|Derive::g1()|Derive::h1()|*|
[]
[]

结论:
虚函数按照声明顺序放在表中;
父类的虚函数在子类的虚函数前面。

4)继承关系可以指定为virtual,虚拟继承解决菱形继承问题。
class istream : virtual public ios {...};
菱形继承:
B,C虚拟继承A,D普通继承B,C
            A
        B        C
            D

虚拟继承的特点:
A)基类不管被派生多少次,永远只会存在一个实体。
B)虚拟继承的子类中,子类增加指针,指向虚基类子对象或指向存放虚基类子对象地址或偏移量的表格,
该指针就是虚基类表指针。

bptr    []    } B类        }
        b    } 成员        }
bptr    []    } C类        } D类对象
        c1    } 成员        }
        d                }
        a    } A类成员    }

注意:
在同时存在vptr和bptr时,编译器会优化为一个指针。

4 虚拟继承时构造函数的书写是怎样的?
1) 虚基继承如下:
B(总参数表):A(参数表)
C(总参数表):A(参数表)
D(总参数表):B(参数表),C(参数表),A(参数表)

5 纯虚函数是什么?什么是抽象类?
1)特殊的虚函数。将实现留给子类。
样例如下:
class Base {
    virtual void func(int a, int b)=0;
};
2)含有纯虚函数的类。
派生类必须实现所有纯虚函数,否则派生类也是抽象类。
只定义protected类型构造函数的类也是抽象类,因为派生类无法创建该类的对象。

6 什么是RTTI?如何提供RTTI?typeid如何使用?typeid限制是什么?
1)RTTI即运行时类型识别,用于识别基类指针所指对象的实际类型。
2)通过typeid和dynamic_cast操作符。
typeid:返回指针/引用所指对象实际类型
dynamic_cast:将基类指针/引用转换为派生类指针/引用。
3)typeid(e),其中e是表达式或类型名。
样例:
Base* bp;
Derived* dp;
if( typeid(bp) == typeid(dp)) {...}
4)只有typeid操作数是带有虚函数的类对象时,才会返回动态信息。

7 C++显式转换有哪些? 用法是怎样的?
reinterpret_cast有什么作用? const_cast有什么作用? 
static_cast有什么作用? dymaic_cast有什么作用?
static_cast与dymaic_cast的区别是什么?
1)有static_cast, dynamic_cast, const_cast, reinterpret_cast。
2) cast_name(expression);
3) reinterpret_cast是强制类型转换,可用于指针转换。
用法如下:
int *ip;
char *pc = (char*) ip;
char *pc2 = reinterpret_cast(ip);
4)const_cast去除常量属性。
用法如下:
const char* p1;
char* p2 = const_cast(p1);
5)隐式转换可以通过static_cast完成,下行转换虽然可以(父类指针/引用转换为子类指针/引用),但不安全。
用法如下:
double d = 329;
int i = static_cast(d);
注意:
基本类型的指针之间不含有隐式转换(void*除外)。
char* 不能隐式转换为int*。
6) dymaic_cast将表达式转换为void*/类的指针/类的引用 类型的对象。用于类的上行/下行转换。
特点如下:
A)涉及运行时类型检查,运行时类型信息存储在虚函数表中,只有定义虚函数的类才有虚函数表
对没有虚函数表的类使用会报编译错误。
B)如果下行转换安全(基类指针/引用指向派生类对象),则返回子类指针/引用;
否则,返回空指针。
7)
dymaic_cast和static_cast都支持上行转换,
但在下行转换时,dymaic_cast比static_cast更安全,因为具有类型检查功能。
dymaic_cast要求操作数必须包含多态类型,static_cast没有该限制。

8 静态成员会对类的大小产生影响吗?空类或者空结构体占据大小是多少?
1)不会。静态成员是类的所有实例共享,不在类实例中。
2)占据一个字节。原因是编译安插进去使得该类的对象在内存中配置独一无二的地址。

9 什么是友元?什么是友元函数?什么是友元类?
1)关键字freind可访问与其有好友关系的类中私有变量
2)
public:
    Time(int, int, int);
    friend void display(Time& t);
    
void display(Time& t){
    cout << t.hour << endl;
};
3) friend 类名;
声明B伪A的友元类,则B中有所有函数都是A的友元函数,
可访问A类中所有成员。
class Date;
class Time{
public:
    void display(Date &);
};
class Date{
public:
    friend void Time::display(Date &);
    //声明Time类中display函数伪本类友元函数
};

第十部分、泛型,模板与元编程


1 什么是泛型编程?为什么使用泛型编程?泛型的基础是什么?
1) 泛型编程指独立于特定类型的编码
2) 使用泛型编程可以让类和函数操纵多种类型的对象。
3) 泛型的基础是模板。

2 模板如何使用?模板的非类型形参作用是什么?模板原理是什么?什么是函数模板?类模板的原理是什么?
1)模板用法如下:
template
T min_tem(T& a, T& b){
    return a < b ? a ? b;
}
2)模板的非类型形参是模板定义内部的常量值。
3)模板会自动推导参数类型。
4)函数模板通过使用不同类型的参数,生成不同类型函数。
5)类模板必须为模板形参指定实参。编译器会根据实际类型代替Type,重新编写新的类。
样例:
template
class Queue{
    public:
        Queue();
        Type &front();
        ......
};
Queue qi;

3 什么是C++元编程?优点是什么?请举例。
1) 元编程则是借助语言提供的模板机制,通过编译器推导,在编译时生成程序。
元编程经过编译器推导得到的程序,再通过编译,产生最终的目标代码。
2)
A.以编译耗时为代价换来卓越的运行期性能
B.提供编译期类型计算
3)编译期数值计算
样例如下:
#include  
template 
class Sumt { 
public: 
  static const int ret = Sumt::ret + N; 
}; 
  
template<> 
class Sumt<0> { 
public: 
  static const int ret = 0; 
}; 
  
int main() { 
  std::cout << Sumt<5>::ret << '\n'; 
  return 0; 
}

参考:
https://blog.csdn.net/Tencent_TEG/article/details/102577552
https://www.jb51.net/article/130935.htm

4 什么是仿函数?请编写一个例子。
1)仿函数(functor)又称为函数对象(function object)是一个能行使函数功能的类。
过作为仿函数的类,都必须重载operator()运算符。
2)代码:
样例1:
class Func{
     public:
         void operator() (const string& str) const {
             cout<          }
 };
Func myFunc;
myFunc("helloworld!");
输出:
helloworld!

样例2:
class ShorterThan {
     public:
         explicit ShorterThan(int maxLength) : length(maxLength) {}
         bool operator() (const string& str) const {
             return str.length() < length;
         }
     private:
         const int length;
};
count_if(myVector.begin(), myVector.end(), ShorterThan(length));//直接调用即可

参考:
https://blog.csdn.net/yuhan61659/article/details/81356051

第十一部分、STL


1 什么是STL?容器元素必须满足的约束条件是什么?被复制的原始值与容器中元素相关吗?
1)STL,即Standard Template Library,标准模板库。
2)元素支持赋值运算和可复制。
3)不相关。

2 顺序容器有哪些?
vector:动态数组
list:链表
deque:双端队列
stack:栈
queue:队列
priority_queue:优先级队列

3 vector是什么?vector常用用法有哪些?
1)vector是动态数组,快速随机访问。
2)成员函数:
capacity():容量
reserve():预留元素的存储空间
size: 容器当前元素个数。
push_back():追加元素到最后
pop_back():删除最后一个元素
back():返回最后一个元素
front():返回第一个元素
erase(a.begin() + 1, a.begin() + 3):删除第一个元素和第二个元素,从第0个算起
insert(a.begin() + 1, 5): 在a的第1个元素(从第0个算起)的位置插入数值5
swap(b):与vector b交换
empty():判断是否为空
clear():清空
vector a(10, 1):定义10个元素,每个元素为1

参考:
https://blog.csdn.net/qinyuehong/article/details/92837359


4 list是什么?list特点是什么?list常用用法有哪些?
1)list时双向换装链表,不能随机访问元素。
增加元素不会使得迭代器失效。
删除元素除了当前被删除元素迭代器,其他迭代器不会失效。
2)
push_front(const T & val): 将val插入链表最前面
pop_front(): 删除链表最前面的元素
push_back(): 末尾插入元素
pop_back(): 删除最后一个元素
insert(a.begin() + 1, 100):在a的起始位置+1个元素后面插入元素100
erase(a.begin() + 1):删除起始位置+1个元素
begin():获取起始迭代器
end():获取末尾的下一个位置
remove(const T & val):  删除和val相等的元素
sort(): 将链表从小到大排序
merge(list & x): 将链表 x 合并进来并清空 x。要求链表自身和 x 都是有序的

参考:
https://www.cnblogs.com/zuixime0515/p/10508079.html
https://blog.csdn.net/u011630575/article/details/79734358

5 deque是什么?deque与vector的区别是什么?
deque特点是什么?常用用法有哪些?
1)deque是双向连续线性空间。
可以在头尾两端分别做元素的插入和删除操作。
2)vector是单向的,deque双向。
vector头部操作效率很差。
deque没有容量概念,它是动态分段连续空间组合而成。
3)deque随机访问元素,开头和末尾添加元素时间O(1),
中间添加/删除元素时间O(n)
4)
操作同上面vector, list

6 queue是什么?常用用法有哪些?
1)队列,先进先出。
2)
push(x):队列添加元素
pop():弹出元素
back():访问队尾元素
size():查看队列中元素个数。
empty():判断是否为空

7 stack是什么?常用用法有哪些?
1)栈,后进先出。
2)
push(x):在栈顶添加元素
pop():移除栈顶元素
size():返回栈中元素个数
top():返回栈顶元素
empty():判断是否为空

8 priority_queue是什么?常用用法有哪些?
1)优先级队列,可以认为是类似堆的数据结构。
只能访问第一个元素,第一个元素是优先级最高的元素。
2)默认是大顶堆
priority_queue ,greater > q; //小顶堆
priority_queue a; //等同于 priority_queue, less > a;
top(): 访问队头元素
empty(): 队列是否为空
size(): 返回队列内元素个数
push(x): 插入元素到队尾(并且排序)
pop(): 弹出队头元素
swap(): 交换内容

参考:
https://www.cnblogs.com/huashanqingzhu/p/11040390.html

9 关联容器特点是什么?
1)关联容器通过键访问元素,顺序容器通过位置访问元素。


10 map是什么?multimap是什么?map常用用法是什么?
1)所有元素的键值会自动排序,所有元素是pair,第一元素为键,第二元素为值。
不允许两个元素有相同键值。
2)multimap允许键值重复。
3)
mapStudent.insert(pair(000, "student_zero"));
mapStudent[123] = "student_first";// 用"array"方式插入
注意: insert插入不允许键重复,但赋值方式可以重复会覆盖。
iter = mapStudent.find("123");//查找元素
if(iter != mapStudent.end())
       cout<<"Find, the value is"<second< else
   cout<<"Do not Find"< iter = mapStudent.find("123");
mapStudent.erase(iter);//迭代器刪除
size()          返回map中元素的个数
count()         返回指定元素出现的次数
empty()         如果map为空则返回true
end()           返回指向map末尾的迭代器
equal_range()   返回特殊条目的迭代器对

参考:
https://blog.csdn.net/u010029439/article/details/89681773

11 set是什么?multiset是什么?set常用用法是什么?
1)set用于去重。只有键没有值。
添加或删除元素时,操作之前的迭代器不会失效。
2)multiset允许键重复。
3)
begin(): 返回set容器的第一个元素
end(): 返回set容器的最后一个元素
clear(): 删除set容器中的所有的元素
empty(): 判断set容器是否为空
max_size(): 返回set容器可能包含的元素最大个数
size(): 返回当前set容器中的元素个数
rbegin(): 返回的值和end()相同
rend(): 返回的值和rbegin()相同

12 如何选择容器?
随机访问元素用vector或deque
中间位置插入或删除元素用list
在容器首尾插入删除元素采用deque
去重用set
键值对用map
top N用priority_queue


第十二部分、原理


1 vector的原理是什么?vector有什么需要注意的?
动态增加大小的原理是什么?insert(position, n, x)是怎么做的?
1)vector是动态数组,有capacity和size两个指标。
capacity是容量,size是当前元素个数。
当元素个数超过容量,则会重新分配更大空间,并复制元素,再释放旧空间。
所以是在堆上,而不是栈上。
2)vector迭代器在内存重新分配时失效。
插入元素后,元素超过capacoty()时,内存重新分配,所有迭代器失效。
删除元素时,指向被删除元素之后的任何元素迭代器失效。
3)动态增加大小的原理:
取原大小两倍空间,拷贝原容器内容至新空间,并释放原空间
4)Inset(position,n,x):函数思路:
s1)若备用空间>=新增元素个数,
s1.1)若插入点后现有元素个数>新增元素个数,则将插入点后的所有元素放到储备空间的最后面,
然后在插入点后插入新增元素;
s1.2)若插入点后现有元素个数<=新增元素个数,
则先在储备空间中复制若干元素,然后将插入点后的老元素复制到储备空间的后面,腾出的地方插入新元素
s2)若备用空间<新增元素个数,分配额外内存为原内存两倍。将旧vector插入点之前元素复制到新空间,
将新增元素复制到新空间,将旧vector插入点之后元素复制到新空间


2 map的原理是什么?平衡二叉树的4种不平衡情况?
什么是红黑树?红黑树的4种插入情形是怎样的?
1)map是通过红黑树实现的。内部是平衡二叉树。
平衡二叉树: 任意节点的左右子树的高度之差的绝对值<=1
2)
2.1)左子节点左子树插入:左旋即可
2.2)右子节点右子树插入:  右旋即可
2.3)左子节点右子树插入:  先左后右旋转即可
2.4)右子节点左子树插入:先右后左旋转即可

2.1),2.2)属于外侧插入,2.3),2.4)属于内侧插入
3)
红黑树:
条件:
1每个节点不是红色就是黑色
2根节点为黑色   
3若节点为红,其子节点必为黑色                         :新增节点的父节点必须为黑
4任一节点至NULL(树尾端)的任何路径,所含黑节点之数必须相同  :新增节点必为红(把其当为子节点)
注意,并没有说明若节点为黑,子节点为红,如果是这样就变成红黑相间了
4)
4种插入情形:
1)S为黑,且X为外侧插入:单旋P,G,再更改P,G颜色
2)S为黑,且X为内侧插入:单旋P,G,更改G,X颜色,再将结果对G单旋,
3)S为红,且X为外侧插入,且GG为黑:单旋P,G,更改X的颜色
4) S为红,且X为外侧插入,且GG为红:单旋P,G,更改X的颜色,持续上述过程,直至不再有父子连续为红情况
解决4)连续上行调整的方法:采用由上而下程序:
思路:设新增节点为A,沿着A的路径,只要看到有某节点X的两个子节点都为红色,就把X改为红色,并把两个子节点改为黑色。

参考:
https://blog.csdn.net/qingyuanluofeng/article/details/12323489
https://blog.csdn.net/weixin_42979679/article/details/90316872

3 unordered_map如何使用?unordered_map原理是什么?
什么是rehash?如何rehash?
1)
#include
unordered_map M;
M[233333] = 666666;
其他成员函数:
at(): 根据Key值查找容器内元素,并返回map元素的引用。
begin(): 指向容器内第一个元素的迭代器。迭代器访问元素时,it->first对应key,it->second对应map(value).
end(): 指向容器内最后一个元素的后一个位置的迭代器。
bucket(): 以key值寻找元素在容器中的位置。
bucket_count(): 返回hash表的插槽值个数,这个函数的值对应构造函数中的n(最小插槽数)参数。
bucket_size(): 这个函数返回每个插槽中的元素数量。
count(): 某个key值对应的map(value)值的数量,因为unordered_map不允许重复元素,所以返回值总是0或1
erase(): 
iterator erase ( const_iterator position );
size_type erase ( const key_type& k );
iterator erase ( const_iterator first, const_iterator last );  
根据不同的索引擦除插槽中的元素.
find(): 查找函数,通过key查找一个元素,返回迭代器类型。

2)主要思想如下:
hash_map内部是一个hash_table一般是由一个大vector,vector元素节点可挂接链表来解决冲突,来实现.
hash_map其插入过程是:
得到key
通过hash函数得到hash值
得到桶号(一般都为hash值对桶数求模)
存放key和value在桶内。
其取值过程是:
得到key
通过hash函数得到hash值
得到桶号(一般都为hash值对桶数求模)
比较桶的内部元素是否与key相等,若都不相等,则没有找到。
取出相等的记录的value。
hash_map中直接地址用hash函数生成,解决冲突,用比较函数解决。
具体步骤:
s1: 初始桶大小是10,rehash为11,初始时加载因子时1,增长因子为2,
根据 std::lower_bound 来找到比 10 大的最小素数是 11,于是就分配为 11 个桶
s2: rehash时,则重新计算,重新装填
s3: 对于string,则用如下哈希函数:
template<>
  struct Fnv_hash<8>
  {
    static std::size_t
    hash(const char* first, std::size_t length)
    {
  std::size_t result = static_cast(14695981039346656037ULL);
  for (; length > 0; --length)
    {
      result ^= (std::size_t)*first++;
      result *= 1099511628211ULL;
    }
  return result;
    }
  };
之所以用上述哈希函数是因为:
这会使得输入的一个非常微小的改动,使最终的 hash 结果发生巨大变化,这样哈希效果更好
3)rehash就是重建hash表
4)rehash用大当前容量乘以加载因子得到阈值,如果容量达到
与之就会进行rehash。

参考:
https://www.cnblogs.com/tldr/p/11364781.html
https://zrj.me/archives/1248
https://www.cnblogs.com/downey-blog/p/10471875.html
https://www.cnblogs.com/qiumingcheng/p/5256185.html

5 什么是bitset?如何使用?
1)位图,每一个元素只能是0或1,每个元素仅用1bit空间。
2)
bitset<10> gBitset: 设置biteset长度为10
reset()或reset( size_t pos ): 重置bitset(全部设为0),如果指定pos,那么只有pos上的位被重置。
set()或set( size_t pos, int val=1 ): 设置bitset上所有的位,然后返回bitset。如果指定pos,那么只有pos上的位被设置
at(size_t pos):返回pos位置上的值

参考:
https://www.jianshu.com/p/380ccac9da99

第十三部分、makefile与链接库


1 什么是编译?什么是链接?整个过程是怎样的?
1)编译检测语法是否正确,函数是否声明,源文件到中间目标文件的过程。
2)链接函数和全局变量,大量中间目标文件合成一个执行文件。
可以使用中间目标文件来链接应用程序,
链接器不管函数所在源文件,只要函数的中间目标文件。
可以给中间目标文件打包,即unix下的.a文件。
3)
源文件->中间目标文件(obj)->执行文件。


2 makefile的作用是什么?什么是make?
makefile规则是什么?什么是伪目标?
如何编写makefile?请编写一个具体的makefile。
1)makefile指定文件编译顺序,自动化编译。等价于编译+链接生成可执行文件。
2)make是解释makefile中命令的工具。
3)
target ... : prerequisties ...
command
...
...
解释:
target: 目标文件
prerequisties: 生成那个target所需的文件
command: make需要执行的命令
原理:
如果prerequisties中有文件比target文件新,则command命令会执行。
样例:
foo.o : foo.c defs.h # foo模块
cc -c -g foo.c
4)伪目标一般没有以来文件,一般用于清理中间文件。
用特殊标记".PHONY"知名目标是伪目标。放在makefile
最后中。
样例如下:
.PHONY: clean
clean:
rm *.o temp
5)一个makefile样例如下:
样例1:
VERSION = 1.0.0     #程序版本号  

SOURCE = $(wildcard ./src/*.c)  #获取所有的.c文件  
OBJ = $(patsubst %.c, %.o, $(SOURCE))   #将.c文件转为.o文件  
INCLUDES = -I./h    #头文件路径  

LIBS = -ldylib      #库文件名字  
LIB_PATH = -L./lib  #库文件地址  

DEBUG = -D_MACRO    #宏定义  
CFLAGS = -Wall -c   #编译标志位  

TARGET = app  
CC = gcc  

$(TARGET): $(OBJ)     
    @mkdir -p output/   #创建一个目录,用于存放已编译的目标  
    $(CC) $(OBJ) $(LIB_PATH) $(LIBS) -o output/$(TARGET).$(VERSION)  

%.o: %.c  
    $(CC) $(INCLUDES) $(DEBUG) $(CFLAGS) $< -o $@  

.PHONY: clean  
clean:  
    rm -rf $(OBJ) output/

样例2:
CFLAGS= -W -Wall
SSLCLIENT= sslclient
SSLSERVER= sslserver
PROGRAM= $(SSLCLIENT) $(SSLSERVER)
vpath %.h ../include
vpath %.cpp ../include
OBJS := $(notdir $(patsubst %.cpp, %.o, $(wildcard *.cpp)))
LDLIBS = -lssl -lcrypto

all : $(PROGRAM)
sslclient : sslclient.o
    $(CC) $(CFLAGS) -o $@ $< $(LDLIBS)
sslserver : sslserver.o 
    $(CC) $(CFLAGS) -o $@ $< $(LDLIBS)

$(OBJS) : 
    $(CC) -c $<

.PHONY : clean
clean :
    -rm $(PROGRAM) $(OBJS)

解释:
CFLAGS: C语言编译器参数
CC: C语言编译程序
$@: 目标文件
$< : 第一个依赖文件
$^: 所有依赖文件


分析:
文件结构调整如下:调整后.h .cpp与makefile都不在一个目录中
build:
makefile
include:
myerr.h
src:
sslserver.cpp
sslclient.cpp

此时需要通过vpath添加查找路径,例如:
vpath %.h …/include :从…/include目录中搜索.h文件
vpath %.cpp …/src : 从…/src目录中搜索.cpp文件
也或使用VPATH直接指定搜索路径
VPATH = …/src …/include

obj推导可以使用函数
$(wildcard pattern…)函数可以把对应模式的文件一一列出来
$(patsubst ,,) 可以做后缀的替换
$(notdir ) 取文件的非目录部分
OBJS := $(notdir $(patsubst %.cpp, %.o, $(wildcard *.cpp)))

解释:
wall表示警告

参考:
http://blog.csdn.net/liang13664759/article/details/1771246
https://blog.csdn.net/chunyexiyu/article/details/86433230
https://blog.csdn.net/sfuncc/article/details/80185007
https://www.zhihu.com/question/55488701


3 什么是静态链接库?如何使用静态链接库?
1)链接器找出程序所需的函数并拷贝到执行文件。
链接成功,则静态程序库就不再需要。
2)
/*add.h */
#ifndef _ADD_H_
#define _ADD_H_
int add(int a, int b);
#endif
-------------------------------------------------------------------------------------------------
/*add.c*/
#include "add.h"
int add(int a, int b)
{
    return a+b;
}
------------------------------
/*main.c*/
#include
#include "add.h"
int main(void)
{
   printf("1 + 2 =%d\n", add(1, 2));
   printf("1 - 2 =%d\n", sub(1, 2));
   return 0;
}
A) 编译成.o文件
gcc -c add.c

B) 由.o文件创建.a静态库
ar crlibmymath.a add.o
ar:静态函数库创建的命令
-c :create的意思
-r :replace的意思,表示当前插入的模块名已经在库中存在,则替换同名的模块。
如果若干模块中有一个模块在库中不存在,ar显示一个错误信息,并不替换其他同名的模块。
默认的情况下,新的成员增加在库德结尾处。
库文件的命名规范是以lib开头(前缀),紧接着是静态库名,以 .a 为后缀名。

C) 在程序中使用静态库
gcc -o main main.c -L. –lmymath
-L 指定函数库查找的位置,注意L后面还有'.',表示在当前目录下查找
-l则指定函数库名,其中的lib和.a(.so)省略。
注意:-L是指定查找位置,-l指定需要操作的库名。
静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包含这些
公用函数的原型声明,然后在用gcc命令生成目标文件时指明静态库名
(是mymath 而不是libmymath.a ),gcc将会从静态库中将公用函数连接到目标文件中。
注意,gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件。
在程序main.c中,我们包含了静态库的头文件add.h,然后在主程序main中直接调用公用函数add()即可。

D) 生成目标程序main,然后运行
./main
1 + 2 = 3

参考:
https://cloud.tencent.com/developer/article/1531843

4 什么是动态链接库?动态链接库有哪两种方式?如何使用动态链接库?
1)os查看程序看内存中是否有此函数拷贝,
如果有,则共享拷贝;
否则,链接载入。
代码使用相对地址,需要使用地址无关代码。
2)隐式链接与显式链接。
3)由 .o创建.so动态库。在动态库名增加前缀lib,但其文件扩展名为.so。
例如:我们将创建的动态库名为mymath,
则动态库文件名就是libmamath.so。用gcc来创建动态库。在系统提示符下键入以下命令得到动态库文件libmamath.so。
动态库(隐式链接)
3.1、由 .o创建.so动态库
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀lib,但其文件扩展名为.so。例如:我们将创建的动态库名为mymath,
则动态库文件名就是libmamath.so。用gcc来创建动态库。
A) 在系统提示符下键入以下命令得到动态库文件libmamath.so。
gcc -fPIC-o add.o -c add.c
gcc -fPIC-o sub.o -c sub.c
gcc -shared-o libmamath.so add.o sub.o
或者:
gcc –c –o add.oadd.c
gcc –c –o sub.osub.c
gcc -shared -fPCI-o libmyhello.so add.o sub.o

这里:
-fpic:产生代码位置无关代码
-shared :生成共享库

B) 隐式方式使用动态库
在程序中隐式使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,
然后在用gcc命令生成目标文件时指明动态库名进行编译。我们先运行gcc命令生成目标文件,再运行它看看结果。
gcc -o main main.c -L. -lmymath
./main
./main: error while loading shared libraries:libmymath.so: cannot open shared object file: No such file or directory
出错了!!!
快看看错误提示,原来是找不到动态库文件libmyhello.so。程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,
否则将提示类似上述错误而终止程序运行。
动态库的搜索路径搜索的先后顺序是:
1.编译目标代码时指定的动态库搜索路径;
2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
3.配置文件/etc/ld.so.conf中指定的动态库搜索路径;//只需在在该文件中追加一行库所在的完整路径如"/root/test/conf/lib"即可,然后ldconfig是修改生效。
4.默认的动态库搜索路径/lib;
5.默认的动态库搜索路径/usr/lib。
为此解决方法:
1. 我们将文件libmyhello.so复制到目录/usr/lib中:
mv libmyhello.so/usr/lib/
2. 将libmyhello.so拷贝到可执行文件main的同一目录下。
再次运行:./main
1 + 2 = 3
1 - 2 = -1                                            
成功了!这也进一步说明了动态库在程序运行时是需要的。

5 同时存在静态和动态库用哪个?静态链接库与动态链接库的区别是什么?
1)同时存在,使用动态库。
2)静态库的缺点是多副本,耗内存;
而动态库则运用时的共享代码。

6 什么是gcc?gcc常用命令有哪些?
1)gcc即GNU C Compiler,c编译器。
2)
gcc -g test.c -o test: 
-g输出调试信息,-o指定输出文件,这里是生成可执行文件


7 什么是gdb?gdb常用命令有哪些?
1)gdb是GNU调试器。
2)
gdb:
b 文件名:设置断点
b 文件名:行号
r: 运行
n: 单步调试
s: 进入函数
p: 打印
c: 继续执行
bt: 查看对战信息
L: 查看文件源码
l 行号: 第n行
q: 退出
-p pid: 调试已运行进程


参考:
https://cloud.tencent.com/developer/article/1531843
https://blog.csdn.net/qingyuanluofeng/article/details/61921994

第十三部分、智能指针


1 什么是auto_ptr?如何使用?有什么需要注意的?auto_ptr的原理是什么?
1)auto_ptr在构造时获取某个对象所有权,析构时释放对象。解决异常抛出时资源泄漏问题。
2)用法如下:
int* p = new int(0);
auto_ptr ap(p);
之后无需关系何时释放p。auto_ptr的析构函数会执行指针的释放。
3)
A)两个auto_ptr不能拥有同一个对象
B)不能用auto_ptr管理数组指针,因为auto_ptr删除指针用的是delete。
C)auto_ptr不能用于STL标准容器。
4)所有权转移,拷贝或赋值的源对象时区对裸指针的所有权。
auto_ptr的拷贝构造函数,赋值函数的参数为引用而不是常引用。
所以void f(auto_ptr ap){...}
将会使得实参的auto_ptr失去所有权。

2 什么是shared_ptr?原理是什么?如何使用?
1)解决多个指针之间共享对象所有权问题,可以放入容器中。
2)shared_ptr使用引用计数,一旦某个对象引用计数变为0,这个独享就会自动删除。
3)std::shared_ptr p1(new int());

参考:
https://blog.csdn.net/shaosunrise/article/details/85228823

第十四部分、effective C++


1. 视C++为一个语言联邦
C++高效编程守则视状况而变化,取决于你使用C++的哪一部分。

2. 尽量以const,enum,inline替代#define
1)对于单纯常量,最好以const对象或enum替换#define
2)对于形似函数的宏,最好改用inline函数替换#define
3)宁可以编译器替换预处理器
4)用define定义的名称并没有进入符号表,无法对其进行跟踪

3.  尽可能使用const
1)如果关键字const出现在星号左边,表示被指物是常量;右边,指针本身是常量;星号两边,被指物和指针都是常量。
2)将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、函数成员本体。
2)编译器强制bitwise constness,但你编写程序时应用使用“概念上的常量性”
3)当const和non-const成员函数有着实际等价的实现时,令non-const版本调用const版本可避免代码重复。
4)const_iterator所指的东西不可被改动。

4.  确定对象被使用前被初始化
1)成员初始化次序固定。父类早于子类,成员变量以其声明次序初始化
2) 为内置型对象进行手工初始化,因为C++不保证初始化它们。
3)构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中声明次序相同。
4)为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。
5)总是使用成员初始化列表
6)类成员初始化次序是基类先于派生类,类的成员变量总是以声明顺序被初始化。

5. 了解C++默默编写并调用哪些函数
1)编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数。
2)对于内含引用成员的类应自己定义copy assignment操作符。

6. 若不想使用编译器自动生成的函数,就应该明确拒绝。
1)将连接期错误移至编译期是可能的(而且那是好事,毕竟越早侦测出错误越好)
2)为驳回编译器自动(暗自)提供的功能,可将相应的成员函数声明为private并且不予实现。使用像noncopyable这样的base class也是一种做法。

7。 为多态基类声明virtual析构函数
1)工厂函数返回的对象必须位于heap,因此为了避免泄漏内存和其他资源,将工厂函数返回的每一个对象适当的delete掉很重要。
2)如果class不含virtual函数,通常表示它并不意图被用做一个base class。当class不企图被当作base class,令其析构函数为virtual往往是馊主意。因为会无端增加一个虚函数指针,多占用空间而且不再与C中的结构体有同样的内存表示。
3)许多人的心得是:只有当class内含至少一个virtual函数,才为他声明virtual析构函数。
4)polymorphic(带多态性质)base class应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
5)classes的设计目的如果不是做为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数。
6)如果你曾经企图继承一个标准容器或任何其他“带有non-virtual析构函数”类,拒绝诱惑吧。

8.别让异常逃离析构函数
1)析构函数绝对不要吐出异常。如果一个析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
2)如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

9.绝不在构造和析构函数中调用virtual函数
1)在基类构造期间,虚函数不是虚函数,这样就会调用基类的成员而不是派生类的,即使是派生类调用的基类构造函数。
2)在调用基类析构函数时,派生类的成员已被析构,这样就会将对象视为基类对象,虚函数不再是虚函数。
3)绝不在构造和析构函数中调用virtual函数,因为这类调用从不下降至派生类。

10.令operator=(赋值运算符)返回一个reference to *this

11.在operator=中处理“自我赋值”,即将值赋值给自己
1)确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
2)确定任何函数如果操作一个以上对象,而其中多个对象是同一个对象时,其行为仍然正确。

12.复制对象时勿忘其每一个成分
1)copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。copying函数指copy构造函数和copy assignment操作符
2)不要尝试以某个copying函数实现另一个copying函数。应该将共同技能放进第三个函数中,并由两个copying函数共同调用。

13.以对象管理资源
1)获得资源后立刻放进管理对象内。
2)管理对象运用析构函数确保资源被释放。
3)auto_ptr和tr1::shared_ptr两者都在其析构函数内做delete而不是delete[]。因此在动态分配而得的array身上使用auto_ptr或tr1::shared_ptr是个馊主意。array可以用vector或string。
4)为防止资源泄露,请使用RAII(Resource Acquisition Is Intialization)对象,它们在构造函数中获得资源并在析构函数中释放资源。
5)两个常被使用的RAII类分别是auto_ptr和tr1::shared_ptr。后者通常是较佳选择,因为其copy行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向null。
6)STL容器要求其元素发挥“正常”复制行为,因此这些容器容不得auto_ptr。
7)RCSP(引用计数型智能指针)提供的行为类似垃圾回收,但无法打破环状引用。

14.在资源管理类中小心coping行为(建立自己的资源管理类)
1)当一个RAII对象被复制,会发生什么?可以有以下两种可能:
      a。禁止复制。例如,互斥锁被复制。
      b。对底层资源祭出“引用计数法”,如tr1::shared_ptr。tr1::shared_ptr的缺省行为是“当引用计数为0时删除其所指物”,
         例如对于互斥锁我们希望的释放动作是解除锁定而非删除,可以指定其所谓的deleter。
2)复制底部资源,即深拷贝
3)转移底部资源的拥有权。若希望只有一个RAII对象指向一个未加工资源(raw resource),可以让资源的拥有权从被复制物转移到目标物。
4)复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
5)普遍而常见的RAII class copying行为是:抑制copying、施行引用计数法。不过其他行为也都可能被实现。
6)并非所有资源都是heap_based,对那种资源而言,智能指针往往不适合作为资源掌管者。

15。在资源管理类中提供对原始资源的访问。
1)auto_ptr和tr1::shared_ptr都提供一个get成员函数,用来执行显式转换,也就是它会返回智能指针内部的原始指针。
2)API往往要求访问原始资源,所以每一个RAII类应该提供一个取得其所管理资源的方法。
3)对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换比较安全,但隐式转换对客户比较方便。

16.成对使用new和delete时要采取相同形式。
1)如果在new表达式中使用[],必须在相应的delete表达式中使用[]。如果在new表达式中不使用[],一定不要在相应的delete表达式中使用[]。

17.以独立语句将new出来的对象置入智能指针
1)以独立语句将new出来的对象置入智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。

18.让接口容易被正确使用,不易被误用。
1)理想上,如果客户企图使用某个接口而却没有获得他所预期的行为,这个代码不该通过编译;如果代码通过了编译,它的作为就该是客户所想要的。
2)任何接口如果要求客户必须记得做某些事情,就是有着“不正确使用”的倾向。
3)促进正确使用的办法包括接口的一致性,以及与内置类型的行为兼容。
4)阻止误用的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。
5)tr1::shared_ptr支持定制型删除器。这可防范DLL问题,可被用来自动解除互斥锁。

19.设计class犹如设计type

20.宁以pass-by-reference-to-const替换pass-by-value
1)尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可避免切割问题。
2)以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言,pass-by-value往往比较适当。

21.必须返回对象时,别妄想返回其reference
1)绝不要返回local stack对象的指针或引用,或堆对象的引用,或返回一个local static对象的指针或引用而有可能同时需要多个这样的对象。

22.将成员变量声明为private
1)切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性。
2)protected并不比public更具封装性

23.宁以non-member、non-friend替换member函数
1)宁以non-member、non-friend替换member函数,这样做可以增加封装性、包裹弹性和机能扩充性

24.若所有参数皆需类型转换,请为此采用non-member函数(例:重载乘法运算符,可以定义为member、friend或non-member,好的是non-member)

25.考虑写出一个不抛异常的swap函数。
1)当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常
2)如果你提供一个成员函数,也应该提供一个非成员函数来调用前者。对于class而非模板,也请特化std::swap
3)调用swap时应针对std::swap应用using声明式,然后调用swap并且不带任何“命名空间资格修饰”
4)为“用户自定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。

26.尽可能延后变量定义式的出现时间
1)尽可能延后变量定义式的出现时间,这样做可增加程序的清晰度并改善程序效率
2)尽量延后直到能够给他初值实参为止,太早的话会先调用default构造,然后再赋值,比copy构造多一次调用。
3)除非你知道赋值成本比“构造+析构”成本低且你正在处理的代码效率高度敏感,你应该把变量定义放在循环里。
循环内:n个构造 + n个析构;   循环外: 1个构造 + 1个析构 + n个赋值。

27.尽量少做转型动作
1)
2)如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_casts。如果有个设计需要转型动作,试着发展无需转型的动作设计
3)如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放在他们自己的代码里。
4)宁可使用c++_style(新式)转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌。

28避免返回handles指向对象内部成分
1)避免返回handles(包括引用、指针、迭代器)指向内部对象。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,
并将发生“虚吊号码牌”(dangling handles)的可能性降到最低。

29.为异常安全而努力是值得的
1)异常安全函数提供以下三个保证之一:
   a。基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏,
      所有对象都处于一种内部前后一致的状态。然后程序的现实状态可能无法预料。
   b。强烈保证:如果异常被抛出,程序状态不改变。调用这样的函数须有这样的认知:如果函数成功,就是完全成功,
      如果函数失败,就会恢复到调用函数之前的状态。
   c。不抛掷保证:承诺绝不抛出异常,因为它们总是能够完成承诺的功能。
2)异常安全函数即使发生异常也不会泄漏资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈性、不抛异常型
3)强烈保证往往能够以copy-and-swap实现出来,但强烈保证并非对所有函数都可实现或具备现实意义
4)函数提供的异常安全保证通常最高只等于其所调用之各个函数的异常安全保证中的最弱者。

30.透彻了解inlining的里里外外
1)inline只是对编译器的一个申请,不是强制命令。这项申请可以隐喻提出,也可以明确提出。隐喻方式是将函数定义于class定义式内,
定义于class内的friend函数也是。
2)将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,
是程序的速度提升机会最大化。
3)不要只因为function templates出现在头文件,就将它们声明为inline
4)虚函数不会被inline,因为虚函数是在运行时动态绑定,而inline是编译时展开
5)指针指向的函数不会被inline,因为无法对其取地址。
6)改变inline函数后,应用到该函数的客户程序都得重新编译,而非inline函数只需重新连接,若实现为dll,连接都不需要。
7)很难在inline函数中设置断点,不好调试

31.将文件间的编译依存关系降至最低。
1)支持编译依存性最小化的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classed和Interface Classes
2)程序库头文件应该以“完全且仅有声明式”的形式存在。这种做法不论是否涉及templates都适用。

32.确定你的public继承塑膜出 is-a 关系
1)public继承意味着 is-a 。适用于base classes身上的每一件事情一定也适用于derived class身上,
因为每一个derived class对象也都是一个base class对象。

33.避免遮掩继承而来的名称
1)derived classes内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。
2)为了让被遮掩的名称再见天日,可使用using声明式或转交函数(forwarding functions)。
3)即使基类和派生类内函数有不同的参数类型,而且不论是虚函数还是非虚函数,基类函数都会被派生类同名函数遮掩。

34.区分接口继承和实现继承
1)接口继承和实现继承不同。在public继承之下,派生类总是继承基类的接口
2)纯虚函数只具体指定接口继承
3)简朴的(非纯)虚函数具体指定接口继承和缺省实现继承
4)非虚函数具体指定接口继承和强制性实现继承

35.考虑虚函数以外的其他选择
1)虚函数的替代方案包括NVI手法及策略设计模式的多种形式。NVI手法自身是一个特殊形式的模板方法设计模式。
2)将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。
3)tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定目标签名式兼容”的所有可调用物。

36.绝对不要重新定义继承而来的非虚函数。

37.绝不重新定义继承而来的缺省参数值
1)绝不重新定义继承而来的缺省参数值,因为缺省参数值是静态绑定的,而虚函数却是动态绑定的。

38.通过复合塑模出has-a或“根据某物实现出”
1)复合的意义和public继承完全不同。
2)在应用域,复合意味着has-a。在实现域,复合意味着is-implemented-in-terms-of(根据某物实现出)。

39.明智而审慎地使用private继承
1)private继承并不意味着is-a关系。
2)private继承意味着只有实现部分被继承,接口部分应略去。
3)private继承意味着is-implemented-in-terms-of(根据某物实现出),同复合语义相似,应该尽可能使用复合,必要时才使用private继承。
4)EBO(Empty Base Optimization),空白基类最优化。派生类继承空白基类后的大小等于派生类本身成员的大小。EBO一般在单一继承下才可行。
这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。
5)两个不存在is-a的类,其中一个需要访问另一个的protected成员,或需要重新定义一个或多个virtual函数,private继承极有可能成为正统设计策略。

40.明智而审慎地使用多重继承
1)如非必要,不要使用虚基类;如果必须使用虚基类,尽可能避免在其中放置数据,这样就不会担心类初始化和赋值所带来的诡异的事情了。
2)多重继承比单一继承复杂。它可能导致新的歧义性,以及对虚继承的需要。
3)虚继承会增加大小、速度、初始化及赋值复杂度等等成本。吐过虚基类不带任何数据,将是最具实用价值的情况。
4)多重继承的确有正当用途。其中一个情节涉及“public继承某个接口类”和“private继承某个协助实现的类”的两相结合。

41.了解隐式接口和编译时多态
1)类和模板都支持接口和多态
2)对类而言接口是显式的,以函数签名为中心。多态则是通过虚函数发生于运行期。
3)对模板参数而言,接口是隐式的,奠基于有效表达式。多态则是通过模板具现化和函数重载解析发生于编译期。

42.了解typename的双重意义
1)任何时候当你想要在模板中指涉一个嵌套从属类型名称,就必须在紧邻它的前一个位置放上关键字typename。
2)声明模板参数时,前缀关键字class和typename可互换。
3)请使用关键字typename标识嵌套从属类型;但不得在base class lists和member initialization list内以它为base class修饰符。

43.学习处理模板化基类内的名称
1)可在派生类模板内通过"this->"指涉基类模板内的成员名称,或借由using声明式,或借由一个明白写出的“基类资格修饰符”完成。

44.将与参数无关的代码抽离模板
1)模板生成多个类和多个函数,所以任何模板代码都不该与某个造成膨胀的模板参数产生相依关系
2)因非类型模板参数而造成的代码膨胀,往往可消除,做法是以函数参数或类成员变量替换模板参数
3)因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码。

45.运用成员函数模板接受所有兼容类型

49.了解new-handler的行为
1)当operator new无法满足某一内存需求时,它会抛出异常。
2)set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
3)nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。

50.了解new和delete的合理替换时机。
1)有许多理由需要写个自定的new和delete,包括改善效能、对heap运用错误进行调试、收集heap使用信息。

53.不要轻忽编译器的警告
1)严肃对待编译器发出的警告信息。努力在你的编译器最高(最严厉)警告级别下争取“无任何警告”的荣誉
2)不要过度倚赖编译器的报警能力,因为不同的编译器对待事情的态度并不相同。一旦移植到另一个编译器上,
你原本倚赖的警告信息有可能消失。

54.让自己熟悉包括TR1在内的标准程序库
1)C++标准程序库的主要机能由STL、iostreams、locales组成。并包含C99标准程序库。
2)TR1添加了智能指针(如tr1::shared_ptr)、一般化函数指针(tr1::function)、hash_based容器、正则表达式以及另外10个组件的支持。
3)TR1自身只是一份规范。为获得TR1提供的好处,你需要一份实物。一个好的实物来源是Boost。

55.让自己熟悉Boost
1)Boost是一个社群,也是一个网站。致力于免费、源码开放、同僚复审的C++程序库开发。Boost在C++标准化过程中扮演深具影响力的角色。
2)Boost提供许多TR1组件实现品,以及其他许多程序库。


参考: https://blog.csdn.net/libinjlu/article/details/44261243

第十五部分、线程,锁与队列


1 如何实现一个线程安全的队列?
原理:
std::queue这个容器已经提供了pop(),push(),empty()等这些读写操作容器的函数,只要在这些函数上面加个锁,就可以使其线程安全。 
代码:
#include
#include
#include
#include
#include
#include

template>
class ThreadSafeQueue {
public:
  ThreadSafeQueue() = default;

  template
  void Push(Element&& element) {
    std::lock_guard lock(mutex_);
    queue_.push(std::forward(element));
    not_empty_cv_.notify_one();
  }

  void WaitAndPop(T& t) {
    std::unique_lock lock(mutex_);
    not_empty_cv_.wait(lock, []() {
      return !queue_.empty();
    });

    t = std::move(queue_.front());
    queue_.pop()
  }

  std::shared_ptr WaitAndPop() {
    std::unique_lock lock(mutex_);
    not_empty_cv_.wait(lock, [this]() {
      return !queue_.empty();
    });

    std::shared_ptr t_ptr = std::make_shared(queue_.front());
    queue_.pop();

    return t_ptr;
  }

  bool TryPop(T& t) {
    std::lock_guard lock(mutex_);
    if (queue_.empty()) {
      return false;
    }

    t = std::move(queue_.front());
    queue_.pop()

      return true;
  }

  std::shared_ptr TryPop() {
    std::lock_guard lock(mutex_);
    if (queue_.empty()) {
      return std::shared_ptr();
    }

    t = std::move(queue_.front());
    std::shared_ptr t_ptr = std::make_shared(queue_.front());
    queue_.pop();

    return t_ptr;
  }

  bool IsEmpty() const {
    std::lock_guard lock(mutex_);
    return queue_.empty();
  }

private:
  ThreadSafeQueue(const ThreadSafeQueue&) = delete;
  ThreadSafeQueue& operator=(const ThreadSafeQueue&) = delete;
  ThreadSafeQueue(ThreadSafeQueue&&) = delete;
  ThreadSafeQueue& operator=(ThreadSafeQueue&&) = delete;

private:
  Container queue_;

  std::condition_variable not_empty_cv_;
  mutable std::mutex mutex_;
};

参考:
https://blog.csdn.net/u011726005/article/details/82670730

2 C++中的多线程和锁如何使用?
#include
#include
#include

void inc(std::mutex &mutex, int loop, int &counter) {
    for (int i = 0; i < loop; i++) {
        mutex.lock();
        ++counter;
        mutex.unlock();
    }
}
int main() {
    std::thread threads[5];
    std::mutex mutex;
    int counter = 0;

    for (std::thread &thr: threads) {
        thr = std::thread(inc, std::ref(mutex), 1000, std::ref(counter));
    }
    for (std::thread &thr: threads) {
        thr.join();
    }

    // 输出:5000,如果inc中调用的是try_lock,则此处可能会<5000
    std::cout << counter << std::endl;

    return 0;
}

参考:
https://www.cnblogs.com/lovebay/p/11582682.html


参考:
王道程序员求职宝典
STL源码剖析
effective C++
https://blog.csdn.net/qinyuehong/article/details/92837359
https://www.cnblogs.com/zuixime0515/p/10508079.html
https://blog.csdn.net/u011630575/article/details/79734358
https://www.cnblogs.com/huashanqingzhu/p/11040390.html
https://blog.csdn.net/u010029439/article/details/89681773
https://blog.csdn.net/qingyuanluofeng/article/details/12323489
https://blog.csdn.net/weixin_42979679/article/details/90316872
https://www.cnblogs.com/tldr/p/11364781.html
https://zrj.me/archives/1248
https://www.cnblogs.com/downey-blog/p/10471875.html
https://www.cnblogs.com/qiumingcheng/p/5256185.html
https://www.jianshu.com/p/380ccac9da99
http://blog.csdn.net/liang13664759/article/details/1771246
https://blog.csdn.net/chunyexiyu/article/details/86433230
https://blog.csdn.net/sfuncc/article/details/80185007
https://www.zhihu.com/question/55488701
https://cloud.tencent.com/developer/article/1531843
https://blog.csdn.net/qingyuanluofeng/article/details/61921994
https://blog.csdn.net/shaosunrise/article/details/85228823
https://blog.csdn.net/libinjlu/article/details/44261243
https://blog.csdn.net/u011726005/article/details/82670730
https://www.cnblogs.com/lovebay/p/11582682.html
https://blog.csdn.net/Tencent_TEG/article/details/102577552
https://www.jb51.net/article/130935.htm
https://blog.csdn.net/yuhan61659/article/details/81356051

你可能感兴趣的:(深入原理64式)