一.关键字
1.static
静态变量
(1)静态全局变量
默认初始化为0
作用域是当前文件,在其他文件不可见,不同文件可以有相同的静态全部变量名
在全局
(2)静态局部变量
在程序运行到该函数时被初始化,默认初始化为0,只被初始化一次
只在当前函数可见,其他代码段中不可见。
(3)静态函数
作用域为当前文件
(4)静态成员变量
类的所有对象共享变量,只被初始化一次
(5)静态成员函数
可以被访问修饰符修饰
只能访问类的静态变量和静态函数
2.const
(1)const变量
变量不可变,必须在声明时就被初始化
(2)const修饰指针
const * int a;//指针指向的变量不可变
const int * a;//指针指向的变量不可变
int * const;//指针指向的地址不可变
int * const * a;//两者都不可变
(3)const修饰函数参数
被修饰的参数在函数内不能被改变
(4)const修饰函数返回值
与修饰变量差不多
(5)const修饰成员变量
成员变量不可变,必须在初始化列表中进行初始化
(6)const修饰成员函数
成员函数不能改变任何成员变量,且只能调用const成员函数
(7)const修饰对象、指针、引用
被const修饰的对象不能改变任何成员变量,只能调用const成员函数。
(8)使用const的优点
【1】编译器类型检查【2】相对于#define会分配内存节省空间,编译器给出const变量地址,只有一份拷贝。而#define会有n分拷贝【3】提高效率,编译器将const变量保存到符号表中,避免了读写内存的操作。
(9)const的底层实现
在C语言中,const 变量被视为只读变量。可通过指针对其值进行改变。
C++中,对于内置类型,会视为常量,保存在符号表中。不可通过指针对其进行改变。
3.union
联合体,可以定义多个变量,多个变量共享同一内存。不考虑地址对齐的情况下,union的长度等于成员变量最长的长度。
同一时刻只表示一种变量,当表示另一种变量时,上次表示的变量失效。
可以存放对象,但是union是共享内存,不允许存放静态、引用类型的变量。所以不能存放有构造函数、析构函数、复制函数等的类的对象。
注:windows 等x86体系结构是小端序。12345678是大端序,87654321是小端序。
4.inline
(1)特性
inline关键字建议编译器将函数进行内联处理,是建议,而不是强制。
inline关键字在函数定义时生效。即只声明没有定义,是不内联的。
一旦内联,则在调用函数时,编译器将其转换为响应代码,如果有多个返回结果,则会使用goto语句。
(2)与define相比的优点
【1】会进行语法以及参数检查【2】可以调试【3】省去了栈的开辟与回收,增加效率
(3)缺点
【1】代码膨胀【2】函数升级时需要重新编译。(不内联的话只需要重新链接)【3】是否内联不可控
(4)注意
【1】使用函数指针会使内联失效(因为需要内存空间)【2】尽量不要把有循环的函数内联【3】构造函数、析构函数和虚函数一般不内联。构造函数和虚构函数会调用父类,意味着大量的代码,不适合内联。虚函数是在运行时决定的,会内联失效。【4】不能强制内联。
5.sizeof
(1)变量或者类型的sizeof,返回内存大小
(2)表达式或者函数的sizeof,返回最终返回值的内存大小
(3)结构体或类的sizeof,返回内存对齐后的大小。空结构体大小为1
(4)指针,32位为4,64位为8
(5)数组,数组的大小。注意,函数参数传递会把数组退化为指针
(6)uinion,返回内存对齐后的大小。
6.explicit
C++提供了关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。声明为explicit的构造函数不能在隐式转换中使用。
二、C++特性
1.重写与重载和重定义
(1)重写
子类重写父类的虚函数,要求参数列表一致。调用时根据对象存储空间类型来决定调用哪个。
被重写的方法不能是static,只能是virtual
(2)重载
函数名字相同但参数列表不同,调用时根据参数列表决定调用哪一个。
(3)重定义
子类定义了与父类名字相同的函数。
2.C++类与C的struct的区别
(1)两者的根本区别在于访问权限,包括内部成员的访问权限,和继承的访问权限。
(2)class和struct之间可以相互继承,继承时候是怎样继承,取决于子类是class还是struct
(3)struct不能够定义模版类型
(4)注意:struct在不添加构造函数等特殊函数的情况下能够用{}初始化,class在public情况下也能用{}初始化
3.delete与free的区别,new和,malloc
(1)delete和new是操作符,malloc和free是函数
(2)new可以初始化,调用构造函数,返回带有类型的指针,delete会调用析构函数,delete只能删除new出的指针。
malloc分配的空间值是随机的,返回void*类型的指针
(3)malloc失败会返回空指针,new失败会抛出异常(可以用std::nothrows抑制异常从而返回空指针)
(4)malloc的底层实现
【1】Linux内存从低往高依次为TEXT段、DATA段、BSS段、heap段、stack段和内核段。【2】堆段分为已经被映射的和未被映射的。malloc从未被映射的区域通过空闲链表依次寻找空闲块,直到找到一个大于要申请空间的内存块,就将内存块一分为二。如果找不到相当大的内存块,就将小的空闲块集合起来。
(5)free是否会直接回收内存
【1】free不会直接释放内存。C语言中,申请和管理内存是由运行库完成的。malloc也并不是每次都找内核申请内存。【2】当free之后,运行库会将内存标记为未使用,但是进程依然具有访问权。【3】申请内存的初始地址的前边4个字节保存着内存块的大小,free就是通过这个来确定内存块有多少的。(但是也存在其他的实现,比如维护一个表)
(6)delete和delete []
【1】当删除基本数据类型的数组时,两者好像没什么区别(为什么?)【2】当删除的是自定义数据类型时,delete只会删除第一个,delete []会查一下首地址之前记录的内存大小,依次调用数组的析构函数。
4.虚函数与纯虚函数的作用
【1】虚函数在父类中被实现,可以在子类中被重写。纯虚函数在父类中是没有被实现的,必须在子类中实现。含有纯虚函数的类是抽象类,抽象类不能被实例化。
【2】父类中的虚函数,在子类中还是虚函数。
【3】那些函数不能被设置为虚函数 ?普通函数、内联函数、构造函数、静态函数、友元函数。
5.C11是什么,C++11特性
Lmabda表达式,auto关键字,overried和finnal,智能指针
6.构造函数和析构函数分别能否是虚函数以及其原因
构造函数不能是虚函数,构造对象时先分配内存,再调用构造函数,而此时虚函数表还没有生成,无法调用。
析构函数可以并常常是虚函数,析构函数如果不是虚函数的话,就不能识别真正的对象类型,产生错误。
7.面向对象的四大原则
抽象、继承、封装、多态
8.指针和引用的区别 +5
(1)引用是内存的别名,指针是一个变量,存储一个地址,指向一个变量
(2)引用不可以为空,必须在声明的时候就被定义。指针可以为空,可以先声明后定义。
(3)指针可以指向指针,引用不能指向引用。
(4)指针需要提领操作,引用不需要
(5)引用一旦被初始化就不能再更改,指针不然。
(6)指针的++ 和引用的++含义是不一样的
(7)sizeof指针和引用的结果不一样
(8)引用内部是基于指针实现的
站在汇编的角度来看,由于指针的使用灵活,使得底层实现中对指针的安全性检查更加严格
9.初始化列表和函数体初始化,顺序
先构造父类,再构造自身。
先初始化列表,初始化列表是按照变量被声明的顺序初始化的,在初始化函数题。
(1)必须使用初始化列表的情况
【1】有const成员变量【2】有引用成员变量【3】成员变量没有默认构造函数【4】父类的构造函数必须在初始化对象中调用。
初始化列表中初始化效率更高,因为直接初始化,不用先初始化再赋值。
10.基本数据类型
bool char short int long float double long double
11.智能指针 +3
原理
12.拷贝构造函数和赋值运算符
如果不用引用,将会循环拷贝
赋值运算符的参数和返回值都是引用,函数内部要判断一下参数是否等于自身,然后删除自身原有的空间并重新赋值。
(13)C++是如何实现面向对象的
三.技巧
1.内存泄漏的处理
2.gdb调试
查看core文件中堆栈信息,bt命令等
3.segmentfault怎么排查
四.底层
1.编译器如何实现多态 +8
(1)多态发生的三个条件
【1】子类继承了父类【2】子类覆写了父类的虚函数【3】父类的指针或者引用指向子类对象
(2)多态的实现
编译器为每一个含有虚函数的类创建一个虚函数表,每一个该类的对象拥有一个指向该表的指针。在对象初始化时,指针被初始化。
所以,父类指针指向子类的对象内存模型,但是虚函数指针还是指向子类的虚函数表,所以在运行时调用的是子类的函数实现,这成为后绑定。
对于普通函数,在编译时就被绑定,这叫早期绑定。
2.内存管理、栈和堆 +5
(1)分区
全局/静态区:存储静态变量
堆:由new申请,delete释放
自由存储区:由malloc申请,free释放。(自由存储区和堆似乎差别不大)
常量:保存常量
栈:由程序自动管理,函数内部变量在栈上分配,函数结束后自动回收。
(2)堆和栈的区别
管理方式:堆由程序员管理,栈由编译器自动管理。
内存大小:堆理论上很大,栈很小
生长方向:堆自下往上,栈自上往下
允许碎片:堆会产生碎片,栈不会有碎片
执行效率:堆是由C++实现的,效率低。
分配方式:堆是动态分配。栈可以静态分配也可以动态分配,动态分配通过alloca,由程序自动回收。STL中的容器就是这样分配的。
3.内存对象模型 +4
这里讲得很清楚http://www.cnblogs.com/tgycoder/p/5426628.html
具体的内存分配不同的编译器实现方式不一样,比如g++ 在vptr和成员变量之间也有间隔。
但是内存布局是一样的
(1)对于普通的C++类
static变量,普通成员函数和static成员函数放在对象外,普通成员变量放在对象内,然后还有vptr指针指向虚函数表,虚函数表的前边拥有一个RTTI指针记录着类的信息。也就是说,sizeof=vptr+成员变量(也是内存中的存放顺序)
(2)对于普通继承的C++类
首先是自身的vptr,虚函数表中包括父类的虚函数(被重写和未被重写)和自身的虚函数,然后是自身的成员变量。sizeof=vptr长度+自身成员变量+父类成员变量。
(3)对于多继承的C++类
每个父类都拥有自己的虚函数表,子类的虚函数放在第一个父类的虚函数表中,所有父类的虚函数表中改变被重写的函数。
sizeof=第一个父类的vptr+第一个父类的成员变量+第二个父类的vptr+第二个父类的成员变量+自身的成员变量。(对于继承两个父类而言)
(4)对于菱形继承的C++类
设,grand,father1继承grand,father2继承grand,son继承father1和father2,则:
sizeof=father1的vptr+grand的成员变量+father1的成员变量+father2的vptr+father2的成员变量+father2的成员变量+son的成员变量
(5)对于单一的虚继承
子类的vptr和成员变量放在前边,中间用4字节的0x00000000隔开,父类的vptr和父类的成员变量放在后边
sizeof=自身的vptr+自身的成员变量+4(0x00000000)+父类的vptr+父类的成员变量
(6)对于菱形虚继承
首先是自身的虚函数和成员变量,然后是第一个父类的虚函数和成员变量,然后是第二个父类的虚函数和成员变量,然后是爷爷的虚函数和成员变量
4.类成员访问权限的底层实现
编译器在编译的时候会检查访问权限,不安全则报错。实际上在程序执行过程中是没有访问权限的,都属于进程内存空间,都可以访问。
(5)extern "C"的作用。
extern修饰变量或者函数的时候声明该变量或是函数可以在其他模块中找到。
extern "C"的时候表明该函数将按照C的规则编译
1.初始化列表
对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表
在初始化列表中的对象直接调用拷贝构造函数,不用调用初始化构造函数,所以效率高。
在构造函数体中的对象会先调用自身的构造函数,再调用拷贝构造函数进行赋值。
对于const成员变量而言,必须在初始化列表中进行初始化。
2.函数返回值
不要返回局部变量的指针或引用
3.使用++i而不是i++,因为前者不需要返回临时对象,效率更高。
4.关于typedef
(1)定义类型的别名,而不是简单的宏替换
char *a,b;//a为char型指针,b为char型
typedef char * PCHAR;
PCHAR a,b;//a和b都是char型指针
(2)在旧的C代码中,用来简化struct的定义
(3)定义与平台无关的类型
typedef unsign short UINT_16;
5.typedef和define的区别
(1)执行时间不同
typedef在编译期间执行,会进行类型检查
define在预处理期间进行简单的字符替换,不会进行类型检查
(2)功能不同
typedef用来定义类型的别名,简化类型定义,与struct结合使用
define可以用来替换类型,常量,变量等各种东西
(3)作用域不同
define没有特定的作用域
tydedef有作用域
(4)与指针结合时效果不同
6.指针常量与常量指针
char * const p;指针常量
const char * p;常量指针
char const *p;常量指针
7.指针数组与数组指针
int (*a)[4];数组指针,又叫行数组,a++将跨越4个int步长
int *a[4];//指针数组,数组a中存储四个int型的指针
int a[3][4];
int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
p=a; //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++; //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]
8.new 操作符
int *p1 = new int[10]; //分配随机值
int *p2 = new int[10]();//会被初始化
未初始化的全部变量会被初始化为0,局部变量是随机值。
9.关于变量析构顺序
C c;
void main()
{
A*pa=new A();
B b;
static D d;
delete pa;
}
先析构程序显式析构的,再析构局部变量。然后是全局变量。。同等级的,谁先被构造,谁后被析构。
全局变量作用域大于静态局部变量
10.虚函数的动态绑定仅在 基类指针或引用绑定派生类对象时发生
void fun(B0 ptr)//普通函数,这里使用的不是按地址传递,就会转化为基类对象
{
ptr.display();
}
1.生成对象时,先调用父类的构造函数再调用子类的构造函数,析构时相反。
2.函数模版实例化由编译器实现,一个类定义中,只要有一个函数模版,则这个类是类模版,类模版的成员函数都是函数模版,类模版实例化后,成员函数也随之实例化。
3.
char a[14];
a="hello world";//数组首地址为常量,不允许被改变
4.instanceof判断一个对象是否为一个类的实例、一个子类的实例、一个实现指定接口的类的实例。
5.析构函数不可重载
6.不能声明为虚函数的有:构造函数,静态成员函数,内联函数,友元函数。
7.构造函数声明为虚函数,防止只析构基类而不析构派生类。
8.二叉树的第i层最多有2^(i-1)个结点
9.二叉树的高度为k,最多有2^k-1,最少有2^(k-1)个结点
10.数据段分为data段(已经初始化且不为0的static和global数据)和bss段(未经初始化或初始化为0的gloabl和static变量)
11.
char * const p;//指针常量
const char * p;常量指针
12.静态变量放在全局数据区,而不是堆栈中。
13.volatile提示编译器不对变量进行存取优化。使用完之后立刻写回硬盘,提示可能有外部程序更改变量的值。
14.
char *p[10];//指针数组
char (*p)[10];数组指针
15.在类方法中,默认参数是静态绑定的,所以绝不重新定义继承而来的缺省参数。
16.选堆快系不稳,选堆归基不变。
17.C++的单例模式
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
mutex m;
class Singleton
{
public:
Singleton * getInstance();
private:
Singleton * instance;
Singleton() {};
Singleton(const Singleton& a) {};
Singleton operator =(const Singleton & a) {};
};
Singleton * Singleton::getInstance()
{
if (instance==NULL)
{
m.lock();
if (instance==NULL)
{
instance = new Singleton();
}
m.unlock();
}
return instance;
}