目录
1、main函数之前执行和之后执行的代码可能有哪些?
2、结构体对齐方式?
3、指针和引用的区别?
4、在函数传参时,什么时候传指针,什么时候传引用?
5、堆和栈哪个快?
6、宏定义、函数、typedef的区别?
7、strlen和sizeof的区别?
8、override 和 final 关键字?
9、野指针和空悬指针?
10、深拷贝和浅拷贝?
11、如何用代码判断大小端存储?
1、main函数之前执行和之后执行的代码可能有哪些?
main函数之前:
- 设置栈指针,开辟main函数栈帧,main函数有参数的话进行参数压栈;
- 初始化静态变量和全局变量,也就是 .data 段内容;
- 将未初始化的全局变量进行初始化为0(其中bool型为false),也就是 .bss 段内容;
- 全局对象的初始化,在main函数之前调用构造函数;
main函数之后:
- 全局对象的析构函数会在main函数之后执行;
- 由atexit注册的函数,会在main函数执行之后执行;可以多次调用注册多个,但执行顺序与注册顺序相反;(atexit称为注册函数)
2、结构体对齐方式?
- 结构体内成员按照声明的顺序存储,第一个成员地址和整个结构体地址相同;
- 未特殊说明时,按结构体中size最大的成员对齐,(若有double成员,按8字节对齐);
注意:C++11以后引入两个关键字 alignas 和 alignof
- alignas:可以指定结构体的对齐方式,但是若指定的小于自然对齐的最小单位,则忽略;
- alignof:可以获取类型的对齐方式;
- 若想使用单字节对齐,可以使用 #pragma pack(1);
3、指针和引用的区别?
? ? ? ? 抛出重点:引用是类型更加安全的一种指针,定义一个指针和定义一个引用,二者在汇编代码上是完全一样的。安全是指:定义一个指针可以不用初始化,因此在程序中使用该指针的时候我们首先要判断一下指针是否合法;定义一个引用必须进行初始化,所以更加安全。
- ?指针是一个变量,存储的是一个地址,引用和原来的变量本质上是同一个东西,是原变量的别名;
- 指针可以有多级指针,引用只有一级;
- 指针在初始化之后可以改变指向,引用在初始化之后不可再改变;
- 当把指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,两者指向的地址相同,但不是同一个指针变量,在函数中改变这个变量的指向并不影响实参(也就是值传递),而引用却可以,在函数中对参数的改变就相当于是对实参的直接操作;
- 引用本质是一个指针,所以对引用本身来说,在32位操作系统上大小也是4个字节;
- 不存在指向空值的引用,但存在指向空值的指针;
4、在函数传参时,什么时候传指针,什么时候传引用?
- 需要返回函数内局部变量的内存的时候用指针,使用指针传参需要开辟内存,用完之后记得释放指针,不然会造成内存泄漏;而返回局部变量的引用是没有意义的。
- 对栈空间大小比较敏感(比如递归)的时候使用引用,因为引用传递不需要创建临时变量,开销更小。
- 类对象作为参数传递的时候使用引用,这是C++类对象传递的标准方式。
5、堆和栈哪个快?
? ? ? ? 抛出结论:栈更快
- 操作系统在底层为栈提供支持,会分配专门的寄存器存放栈的地址,栈的出入栈操作也十分简单,并且有专门的指令执行,所以栈的效率高且快;
- 堆的操作是由C/C++函数库提供的,在分配堆内存时需要一定的算法寻找合适大小的内存,并且获取堆的内容需要两次访问,第一次访问指针,第二次根据指针保存的地址访问内存,因此堆比较慢。
6、宏定义、函数、typedef的区别?
- 宏定义在预处理阶段完成文本替换,不存在函数调用的过程;函数在运行阶段需要跳转到具体 的函数,存在函数调用开销;typedef属于编译的一部分,主要用于定义类型别名;
- 宏定义参数没有类型,不进行类型检查;函数和typedef要进行类型检查;
7、strlen和sizeof的区别?
- sizeof是运算符,编译时得到类型的大小;strlen是库函数,运行时得到大小;
- sizeof参数可以是任何数据类型;strlen函数的参数只能是字符指针且末尾是 ‘\0’ 的字符串;
- sizeof的值在编译时期确定,所以不能用来得到动态分配存储空间的大小。
8、override 和 final 关键字?
- override:表明子类重写父类虚函数,如果函数名写错会报错,会提示我们;
- final:通过在类名或者虚函数后面添加final关键字,表明该类不可被继承、不希望该虚函数被重写;
9、野指针和空悬指针?
- 野指针:定义指针变量但未进行初始化,解决办法:初始化或者置空;
- 空悬指针:调用free或delete后,没有置空,解决方法:释放后立即置空或者使用智能指针;
10、深拷贝和浅拷贝?
- 浅拷贝:只拷贝一个指针,并没有开辟新的内存,拷贝的指针和原来的指针指向同一块内存,如果原来的指针所指向的资源进行释放了,那么再释放当前指针的资源就会出现错误;
- 深拷贝:不仅拷贝指针,还会开辟内存存放新的值,即时原先的对象进行资源释放了,也不会影响新的对象。
? ? ? ? 注意:一般在进行类对象的拷贝构造时,若类的成员数据有指向外部资源的变量,则需要我们自己定义对象的拷贝构造,因为类默认提供的拷贝构造是浅拷贝。
11、如何用代码判断大小端存储?
? ? ? ? 注意:地址从低到高
- 若低地址存数据的高字节,则称为大端存储;
- 若低地址存数据的低字节,则称为小端存储;
大小端指的就是低地址存放的是数据的高字节还是低字节。对于我们的操作系统,主机字节序是按照小段存储,也就是先存放低字节,然后存放高字节;但是在网络中,网络字节序是大端存储,(符合我们人类的观察方式,从左往右先看到高位然后地位,可读性高),所以在socket编程中,要进行ip地址的大小端转换。
例如:32bit的数字0x12345678
?通过代码进行判断:
方法1: 通过强制类型转换
#include<iostream>
using namespace std;
int main()
{
int a = 0x1234;
char c = (char)a;
if(c==0x12)
{
cout << "大端存储..." << endl;
}
else if(c==0x34)
{
cout << "小端存储..." << endl;
}
return 0;
}
运行结果:
?方法2:使用联合体
#include<iostream>
using namespace std;
// 巧用联合体
union Test
{
int a;
char c;
};
// a 和 ch 共用4个字节的空间
int main()
{
Test t;
t.a = 0x1234;
if(t.c == 0x12)
{
cout << "大端存储..." << endl;
}
else if(t.c == 0x34)
{
cout << "小端存储..." << endl;
}
return 0;
}
运行结果:
?
|