1. 类的静态成员和非静态成员区别是:
类的静态成员(变量和方法)属于类本身,在类加载的时候就会分配内存,可以通过类名直接去访问;非静态成员(变量和方法)属于类的对象,所以只有在类的对象产生(创建类的实例)时才会分配内存,然后通过类的对象(实例)去访问。
所以类的静态函数无法直接访问普通成员变量,因为类实例化之前, 成员变量还不存在。
当调用一个对象的非静态成员函数时,会把对象的起始地址赋给this指针,而静态成员函数不属于某一对象,属于类本身。
所以非静态成员函数有this指针,而静态静态成员函数没有this指针。
2.静态全局变量和静态局部变量
全局静态变量存在静态存储区,在整个程序运行期间一直存在。
作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。局部静态变量存在静态存储区
作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变;
- string本身就会判断最后一个字节是否为多余的半个汉字,是的话不输出该字节。 如 字符串“我ABC汗DEF”,截取6个字节是“我ABC” , 截取7个字节是“我ABC汗”。
4. malloc和new的区别
1) malloc 返回的类型是void* 需要强制类型转换,new 返回的就是需要的类型;
2)malloc 返回的内存空间初值是不确定的,new返回的是初始化过得
3)malloc是函数 new是运算符
4)new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。
5)new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。
自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。
那么自由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。
6)使用malloc分配的内存后,如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来的内存区域。
new没有这样直观的配套设施来扩充内存。new在内存分配时如果发生内存不足,在抛出异常之前,可以使用由程序员提供的处理例程,被称作new_handle;
------malloc函数工作机制--------------------------------------------------------------------------------
malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲[链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。如果无法获得符合要求的内存块,malloc函数会返回NULL指针,因此在调用malloc动态申请内存块时,一定要进行返回值的判断。
malloc底层实现原理
5. 小技巧
为了节省内存,结构体可以设置为union的,union中多个变量值,同时只有一个有用。
union共享内存地址 可以用union判断当前系统以大端还是小端存储 示例如下:
union MyUnion
{
short a;
char b[sizeof(short)];
};
/*union共享一块内存,以达到节省内存的用途;
*所以当test.a被赋初值时,test.b也是同一块内存区域,可以以char类型读取
*所以就可以判断其是大端还是小端。
*/
int main()
{
MyUnion test;
test.a = 0x0102;
if (test.b[0] == 0x01 && test.b[1] == 0x02)
cout << "small endian" << endl;
else if (test.b[0] == 0x02 && test.b[1] == 0x01)
cout << "big endian" << endl;
else
cout << "UNknow" << endl;
return 0;
}
6. struct和class的区别:
struct默认成员变量默认是public,class 是private的,C++的class是由struct扩展而来的,在C语言中struct是不可以定义成员函数的,但在C++中扩展了struct的功能,也可以定义成员函数。
7. 对象的存储空间计算
权威的结论是:非静态成员变量总和加上为了CPU计算作出的数据对齐处理和支持虚函数所产生的负担。
1). C++中每个空类型的实例占1Byte空间;
2). 静态数据成员不占对象的内存空间;
3). 成员函数不占空间;
4). 构造函数和析构函数不占空间;
5). 类中有一个或多个虚函数,则需要计算一个指向虚函数表的指针所需的空间;(编译器为了支持虚函数,需要一个指向虚函数表的指针,64位机器占8Byte)
每个对象所占用的存储空间只是该对象的非静态数据成员的总和,不包括成员函数和静态数据成员,函数代码是存在对象空间之外的
8. this指针
this指针是指向本类对象的指针,它的值是当前被调用的成员函数所在的对象的起始地址。
class A
{
int x;
int y;
int z;
};
A a;
int sum=a.x+a.y+a.z;
//这里相当于this.x+this.y+this.z;
this指针有以下特点:
1).只能在成员函数中使用,在全局函数、静态成员函数中都不能使用;
2).this指针在成员函数开始前构造,并在成员函数的结束后清除;
3).this指针会因编译器不同而具有不同的存储位置,可能是栈、寄存器或者全局变量;
4).this是类的指针;
5).this指针只有在成员函数中才有定义,所以获得一个对象后,不能通过对象使用this指针,所以也就无法知道一个对象指针的位置。不过,可以在成员函数中指定this指针的位置;
6).普通的类函数都不会擦混构建一个函数表来保存函数指针,只有虚函数才会被放到函数表中;
- 对基类成员和子对象成员的初始化必须在成员初始化列表中进行,新增成员的初始化既可以在成员初始化列表中进行,也可以在构造函数体中进行。
派生类的构造函数和析构函数的调用顺序
1).如果有多个基类,则构造函数的顺序是某类在类派生表中出现的顺序,而不是他们在成员初始化表中的顺序;
2).如果有多个成员类对象,则构造函数的顺序是对象在类中被声明的顺序,而不是他们出现在成员初始化列表中的顺序;
3).首先调用派生类的析构函数,其次在调用成员类对象的析构函数,最后调用基类的析构函数;
析构函数调用情况
1).对象生命周期被销毁时;
2).delete指向对象的指针时,或者delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;
3).对象i是o的成员,o的析构函数被调用时,对象i的析构函数也被调用;
10. 多态
多态性是指不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。意思是:向不同的对象发送同一个消息,不同的对象在接收时产生不同的行为
11. vector
reserve()是为了减少重新分配内存的次数,提升使用效率;
size()返回的是容器元素的真实大小;
resize()是修改容器内元素数量的大小,大则删除,小则扩充;
capacity()是容器在已经分配的内存中可以容纳多少元素,反应此时的真实容量,不是元素个数。
12. 函数对象
函数对象即调用操作符的类,其对象称为函数对象,他们是行为类似函数的对象。表现出一个函数的特征,就是通过“对象名+(参数列表)”的方式使用一个类,其实质是对operator()操作符的重载。
其中
struct CmpByKeyLength
{
bool operator() (const string& k1, const string& k2)
{
return k1.length() < k2.length();
}
}
就是一个函数对象,传到map,vector,set等容器里面,就可以实现自定义的排序顺序。
12. C++编译与链接
- 预处理:主要处理源代码中以“#”开始的预编译指令。
比如删除#define,#if,#endif,处理#include指令,将包含的文件加入进来,者是个递归的过程;还有过滤注释,添加行号和文件名标识;- 编译:把预处理完的文件进行一系列的词法分析、语法分析、语义分析以及优化后产生相应的汇编代码文件。
编译的过程一般分为六步:扫描(词法分析)、语法分析、语义分析、源代码优化、代码生成和目标代码优化。
- 链接:就是把各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确的衔接。
链接过程主要包括地址和空间分配、符号决议和重定位等这些步骤。
静态库和动态库:
1)当某个程序要调用某个动态链接库函数时,操作系统首先会查看所有正在运行的程序,看内存里是否有此库函数的拷贝,如果有,可以共享那一个拷贝,只有没有时才链接载入,这样节省了系统的内存资源;而静态库不同,如果有程序要调用某个静态库函数时,则每个程序都要将这个库函数拷贝到自己的代码中去,这将占用更大的内存资源;
2)将一些程序升级变得简单,只要动态库提供给该程序的接口没变,只要重新用新生成的动态库替换原来就可以了;
3)动态链接库本着“有需求才调入”的原则,可以大大节省系统资源;
4)静态库在编译的时候就会被装载到程序中去,所以执行时使用静态库速度更快。
13. linux程序内存布局空间
初始化数据段也叫常量区,存放一般的常量、字符串常量等。这块内存只有读取权限,没有写入权限,因此它们的值在程序运行期间不能改变。
未初始化数据段也叫全局数据区,存放全局变量、静态变量等。这块内存有读写权限,因此它们的值在程序运行期间可以任意改变。
堆、栈的区别
1)申请方式不同:
栈:由系统自动分配;
堆:程序员自己申请;
2)申请后系统的响应不同:
栈:主要栈的剩余空间大于所申请的空间,系统就会提供内存,否则报异常;
堆:操作系统有一个记录空闲地址的链表,当系统收到程序的申请时,会遍历该链表。寻找第一个大于申请空间的堆节点;
3)申请大小的限制:
栈是向低地址扩展的数据结构,是一块连续的内存区域;栈顶位置和栈的容量是系统预先设定好的。在linux下可以使用ulimit -a查看栈大小的限制,通过ulimit -s修改栈的大小;
堆是向高地址扩展的数据结构,是不连续的内存区域;
4)申请效率不同:
栈由系统自动分配,速度较快;但程序员无法控制;
堆是由new分配的内存,一般速度较慢,容易产生内存碎片,但用起来方便;
5)存储内容不同:
在函数调用时,第一个进栈的是主函数中后的下一条指令的地址,然后是函数的各个参数,大多数编译器,参数由右往左入栈,然后是函数的局部变量;当本次调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址。
堆一般是在堆得头部用一个字节存放堆得大小。堆中具体内容由程序员安排。
14. char 类型
char是C/C++整型数据中比较古怪的一个,其它的如int/long/short等不指定signed/unsigned时都默认是signed,但char在标准中是unsigned
- uint_8与u8、uint16_t与u16、unit_32与u32没有区别
分别是 unsigned char unsigned int unsigned long int的不同写法- unsigned char是无符号字节型,char类型变量的大小通常为1个字节(1字节=8个位),且属于整型。
15. emplace_back 与 push_back的区别
c++开发中我们会经常用到插入操作对stl的各种容器进行操作,比如vector,map,set等。在引入右值引用,转移构造函数,转移复制运算符之前,通常使用push_back()向容器中加入一个右值元素(临时对象)时,首先会调用构造函数构造这个临时对象,然后需要调用拷贝构造函数将这个临时对象放入容器中。原来的临时变量释放。这样造成的问题就是临时变量申请资源的浪费。
引入了右值引用,转移构造函数后,push_back()右值时就会调用构造函数和转移构造函数;
而c++11 新加的emplace_back,在插入的时候直接构造,就只需要构造一次即可,只会调用一次构造函数,相对于push_back的两次调用性能更优。
16. printf右结合,&&优先级低,*p++的意义
int a=6, b=9;
printf("%d", a, b);
以上程序中printf右结合,按顺序将a, b压栈,然后最后 出栈的是a,所以输出的结果是6;
p++的作用是:先解引用,获得p所指向的值,然后将p++,即p指向下一位。
(p)++的作用是:解引用,获得p所指向的值,然后将指向的值自加,指针并不移动。
17. 四种强制类型转换
static_cast
static_cast用于将一种数据类型强制转换为另一种数据类型。
它主要有如下几种用法:
(1)用于类层次结构中基类和派生类之间指针或引用的转换
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的
进行下行转换(把基类的指针或引用转换为派生类表示),由于没有动态类型检查,所以是不安全的
(2)用于基本数据类型之间的转换,如把int转换成char。这种转换的安全也要开发人员来保证
(3)把空指针转换成目标类型的空指针
(4)把任何类型的表达式转换为void类型
注意:static_cast不能转换掉expression的const、volitale或者__unaligned属性。
dynamic_cast
用法:dynamic_cast<type_id> (expression)
(1)其他三种都是编译时完成的,dynamic_cast是运行时处理的,运行时要进行类型检查。
(2)不能用于内置的基本数据类型的强制转换。
(3)dynamic_cast转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回NULL。
(4)使用dynamic_cast进行转换的,基类中一定要有虚函数,否则编译不通过。
- 基类中需要检测有虚函数的原因:类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义。
- 这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,**
- 只有定义了虚函数的类才有虚函数表。**
(5)在类的转换时,在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的。在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
- 向上转换,即为子类指针指向父类指针(一般不会出问题);向下转换,即将父类指针转化子类指针。
- 向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败。
- 在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。Dynamic_cast操作符则可以在运行期对可能产生问题的类型转换进行测试。
const_cast
用法:const_cast<type_id> (expression)
用于强制去掉不能被修改的常数特性,但需要特别注意的是const_cast不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用。
该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
- 常量指针被转化成非常量指针,并且仍然指向原来的对象;
- 常量引用被转换成非常量引用,并且仍然指向原来的对象;
- 常量对象被转换成非常量对象。
reinterpret_cast
用法:reinterpret_cast<type_id> (expression)
主要有三种强制转换用途:改变指针或引用的类型、将指针或引用转换为一个足够长度的整形、将整型转换为指针或引用类型。
- type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。
- 它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。
- 在使用reinterpret_cast强制转换过程仅仅只是比特位的拷贝,因此在使用过程中需要特别谨慎!
18. 右值,左值,和移动语义
右值是等号右边的值,比如常量值是右值
lvalue指在内存中占有确定位置的对象。rvalue指在内存中没有确定位置的对象。
左值可以修改,右值不能修改。
左值持久,右值短暂。
移动语义其实可以理解成static_cast(),将一个左值强转成右值。
使用移动语义的原因是:一般复制时,需要先复制一个副本,也就是复制出来一个右值,然后用这个副本进行拷贝到目的空间;当你使用移动拷贝函数的时候,不需要复制出来一个副本,而是直接把原来的左值转化成右值,然后进行复制,减少了一个拷贝副本的过程。
参考1 左值,右值与移动
参考2 move的原理,vector扩容时的操作
19. const用处
1)const修饰变量
const修饰变量,可以使变量具有常属性,也就是该变量在以后的使用中其值都不能进行改变。
在这里要注意的一点就是const修饰的变量要进行初始化。
2)const和指针
用const修饰指针有以下几种情况
3)保护函数参数不被改变
用const修身函数的参数,可以保证该参数的值在函数内部不被改变。
但是一般const修饰的参数都有哪些呢?
一般用引用或者传地址的方式给函数传递一个参数,如果在函数体内对该参数进行修改,那么就会连实参的值一并进行修改。
4)const修饰函数返回值
const修饰函数的返回值,防止函数的返回值被修改
但是这里要注意的是,必须也用const修饰的变量来接收这个函数的返回值。
5)const修饰类成员函数
const可以修饰类成员函数,防止类成员函数中除了static成员之外的其他成员被修改。
6)const修饰类的成员变量
类的成员变量可以分为静态的和非静态的,如果const修饰的是静态的成员变量,可以在构造函数中对该变量进行初始化;如果const修饰的是静态的成员变量,则需要在类外对该变量进行初始化。
7)const和#define的区别
\#define A 10
const int A=10;
上面两条语句的作用是一样的,但是后面一条语句可以节省内存空间,这是为什么呢?
原因在于在程序中多次使用const类型的变量,只会在第一次分配内存,但是如果使用#define定义的宏,则每次都会分配内存,这样看来使用const会节省很多空间。
8)const和extern
file1.cpp中,const int a;
file2.cpp中,extern int a;
//错误,无法调用,
说明const修饰大的变量只能在该程序的本文件中使用,不能在该程序的其他文件中使用。
要想const定义的对象变量能被其他文件调用,定义时必须使用extern修饰为
extern const int val;
20. map hash_map unordemap
map
头文件#include<map>
内部数据的组织,基于红黑树实现,红黑树具有自动排序的功能,因此map内部所有的数据,在任何时候,都是有序的。
** hash_map**
头文件#include<hash_map>
基于哈希表,数据插入和查找的时间复杂度很低,几乎是常数时间,而代价是消耗比较多的内存。底层实现上,使用一个下标范围比较大的数组来存储元素,形成很多的桶,利用hash函数对key进行映射到不同区域进行保存。
- 插入操作:得到key -> 通过hash函数得到hash值 -> 得到桶号(hash值对桶数求模) -> 存放key和value在桶内
- 取值过程:得到key -> 通过hash函数得到hash值 -> 得到桶号(hash值对桶数求模) -> 比较桶内元素与key是否相等 -> 取出相等纪录的value
- 当每个桶内只有一个元素时,查找时只进行一次比较,当很多桶都没有值时,查询更快。
- 用户可以指定自己的hash函数与比较函数。
unordered_map
头文件#include<undered_map>
C++ 11标准中加入了unordered系列的容器。unordered_map记录元素的hash值,根据hash值判断元素是否相同。map相当于java中的TreeMap,unordered_map相当于HashMap。无论从查找、插入上来说,unordered_map的效率都优于hash_map,更优于map;而空间复杂度方面,hash_map最低,unordered_map次之,map最大。
对于STL里的map容器,count方法与find方法,都可以用来判断一个key是否出现,count统计的是key出现的次数,因此只能为0/1,而find基于迭代器实现,以mp.end()判断是否找到要求的key。
underordermap 和 hash_map的区别
hash_map的rehash简单粗暴,直接就是更新bucket数目为需要的数目就行了,而不会像unordered_map那样子进行数量的优化。
21 指针和引用的区别
1) 引用是别名,指针是地址
2) 指针在运行时可以改变所指向的值,而引用一旦与某个对象绑定后就不再改变
3) 程序为指针变量分配区域,而不为引用分配内存区域。
4) 从理论上来说,对于指针没有级数限制,但是引用只有一级。
5) 程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变
量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改。这是使用指针不安全而使用引用安全的主要原因。从某种意义上来说引用可以被认为是不能改变的指针。