面试之CPP基础知识-1


image https://www.cnblogs.com/marjosong/p/13801160.html

#在main执行之前和之后执行的代码可能是什么?

牛客网

全局对象的构造函数在main函数之前调用,析构函数在main函数之后调用。

局部栈对象在定义的时候调用构造函数,出了可见范围的时候调用析构函数。

堆对象在new的时候调用构造函数,delete的时候调用析构。

全局静态对象和全局对象一样。

局部静态对象在定义的时候调用构造,main函数之后调用析构


main函数执行之前,主要就是初始化系统相关资源:

  • 配置堆栈
  • 初始化静态static变量和global全局变量,即.data段的内容
  • 将未初始化的全局变量赋初值:数值型shortintlong等为0boolFALSE,指针为NULL等等,即.bss段的内容
  • 全局对象初始化,在main之前调用构造函数,(可以利用构造函数先执行代码)
  • 将main函数的参数argcargv等传递给main函数,然后才真正运行main函数

main函数执行之后

  • 全局对象的析构函数会在main函数之后执行;
  • 可以用 atexit 注册一个函数,它会在main 之后执行;

https://blog.csdn.net/qq_44844115/article/details/98945612

#结构体内存对齐问题?

  • 结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。
  • 一般按结构体中size最大的成员对齐
  • 对齐后,只需访问一次就可获得值

c++11以后引入两个关键字 alignasalignof。其中alignof可以计算出类型的对齐方式,alignas可以指定结构体的对齐方式。

alignas小于自然对齐的最小单位,则被忽略。

  • 如果想使用单字节对齐的方式,应该使用#pragma pack(push,1)或者使用__attribute__((packed))

#指针和引用的区别

  1. 引用是变量的别名,指针是存储变量地址的变量。
  2. 引用初始化后不能被改变,指针可以改变所指的对象。
  3. 不存在指向空值的引用,但是存在指向空值的指针

#在传递函数参数时,什么时候该使用指针,什么时候该使用引用呢?

  • 需要返回函数内局部变量的时候用指针。

  • 当实参是指针时传递给形参用指针。

  • 对栈空间大小比较敏感(比如递归)的时候使用引用。使用引用传递不需要创建临时变量,开销要更小

  • 类对象作为参数传递的时候使用引用,这是C++类对象传递的标准方式

#堆和栈的区别

  • 申请方式不同。

    • 栈由系统自动分配。

    • 堆是自己申请和释放的。

  • 申请大小限制不同。

    • 栈向低地址扩展,大小固定,可以通过ulimit -a查看,由ulimit -s修改。

    • 堆向高地址扩展,是不连续的内存区域,大小可以灵活调整。

  • 申请效率不同。

    • 栈由系统分配,速度快,不会有碎片。

    • 堆由程序员分配,速度慢,且会有碎片。

栈空间默认是4M, 堆区一般是 1G - 4G

管理方式堆中资源由程序员控制(容易产生memory leak)栈资源由编译器自动管理,无需手工控制
内存管理机制系统有一个记录空闲内存地址的链表,当系统收到程序申请时,遍历该链表,寻找第一个空间大于申请空间的堆结点,删 除空闲结点链表中的该结点,并将该结点空间分配给程序(大多数系统会在这块内存空间首地址记录本次分配的大小,这样delete才能正确释放本内存空间,另外系统会将多余的部分重新放入空闲链表中)只要栈的剩余空间大于所申请空间,系统为程序提供内存,否则报异常提示栈溢出。(这一块理解一下链表和队列的区别,不连续空间和连续空间的区别,应该就比较好理解这两种机制的区别了)
空间大小堆是不连续的内存区域(因为系统是用链表来存储空闲内存地址,自然不是连续的),堆大小受限于计算机系统中有效的虚拟内存(32bit 系统理论上是4G),所以堆的空间比较灵活,比较大栈是一块连续的内存区域,大小是操作系统预定好的,windows下栈大小是2M(也有是1M,在 编译时确定,VC中可设置)
碎片问题对于堆,频繁的new/delete会造成大量碎片,使程序效率降低对于栈,它是有点类似于数据结构上的一个先进后出的栈,进出一一对应,不会产生碎片。(看到这里我突然明白了为什么面试官在问我堆和栈的区别之前先问了我栈和队列的区别)
生长方向堆向上,向高地址方向增长。栈向下,向低地址方向增长。
分配方式堆都是动态分配(没有静态分配的堆)栈有静态分配和动态分配,静态分配由编译器完成(如局部变量分配),动态分配由alloca函数分配,但栈的动态分配的资源由编译器进行释放,无需程序员实现。
分配效率堆由C/C++函数库提供,机制很复杂。所以堆的效率比栈低很多。栈是其系统提供的数据结构,计算机在底层对栈提供支持,分配专门 寄存器存放栈地址,栈操作有专门指令。

#你觉得堆快一点还是栈快一点?

毫无疑问是栈快一点。 因为栈是由操作系统分配的,对其有专门的优化,而堆是由程序员分配的,需要先找到合适的内存块,再进行分配。

#区别以下指针类型?

1
2
3
4
int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)
  • int *p[10]表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。

  • int (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。

  • https://blog.csdn.net/men_wen/article/details/52694069

  • https://blog.csdn.net/lirendada/article/details/122931987

  • int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。

  • int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。

#new / delete 与 malloc / free的异同

相同点

  • 都可用于内存的动态申请和释放

不同点

  • new自动计算要分配的空间大小,malloc需要手动计算
  • new是类型安全的,malloc不是。例如:
1
2
int *p = new float[2]; //编译错误
int *p = (int*)malloc(2 * sizeof(double));//编译无错误
  • new先分配足够空间并调用相关对象的构造函数;delete先调用析构函数释放资源,然后释放该对象所用内存。后者只是分配空间和释放空间。
  • malloc和free返回的是void类型指针(必须进行类型转换),new和delete返回的是具体类型指针。

#new和delete是如何实现的?

  • new的实现过程是:首先调用名为operator new的标准库函数,为其分配该对象的大小的内存;接下来运行该类型的一个构造函数,初始化构造对象;最后返回指向该对象的指针
  • delete的实现过程:对指针指向的对象运行适当的析构函数;然后通过释放该对象所用内存

#有malloc/free,为什么还需要new/delete呢?

类在创建的时候需要调用构造函数,但是malloc只是分配内存,类在销毁的时候需要调用析构函数,但是free只能释放内存。

#被free回收的内存是立即返还给操作系统吗?

不是的,被free回收的内存会首先被ptmalloc使用双链表保存起来,当用户下一次申请内存的时候,会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调用,占用过多的系统资源。同时ptmalloc也会尝试对小块内存进行合并,避免过多的内存碎片。

#宏定义和函数有何区别?

宏定义 #define函数
宏定义,相当于字符替换跳转到函数执行处
预处理器处理-
无类型安全检查有类型安全检查
无返回值带返回值

- 宏定义不要在最后加分号。

#宏定义和typedef区别?

宏定义typedef
宏定义,相当于字符替换定义类型别名
预处理阶段编译阶段
无类型安全检查有类型安全检查
不是语句是语句加分号

#define p_int int * 显示 int*
typedef int *p_int; 显示p_int

#define宏定义和const的区别

宏定义const
宏定义,相当于字符替换限制变量
预处理阶段编译阶段
无类型安全检查有类型安全检查
不分配内存分配内存

#内联函数和宏定义的区别

宏定义inline
调用处字符替换调用处字符替换
预处理阶段编译阶段
无类型安全检查有类型安全检查
不分配内存分配内存
有返回值

#变量声明和定义区别?

  • 声明仅仅是告诉这里有一个变量(把变量的声明的位置及类型提供给编译器,)并不分配内存空间;定义要在定义的地方为其分配存储空间

#strlen和sizeof区别?

sizeofstrlen
运算符库函数
任何类型只能string
编译期编译期
不能得到动态分配的空间大小可以
算上'\0'不算上
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main(int argc, char const *argv[])
{
    const char *str = "name";
    sizeof(str); // 8
    strlen(str); // 4
    char a[5] = "1234";
    sizeof(a); // 5
    strlen(b); // 4
    return 0;
}

一个指针占多少字节?

在64位的编译环境下,指针的占用大小为8字节;

而在32位环境下,指针占用大小为4字节。

一个指针占内存的大小跟编译环境有关,而与机器的位数无关

#常量指针和指针常量区别?

  • 指针常量强调常量,指向一个只读变量,可以写作int const *p或const int *p。

  • 常量指针强调指针,是一个不能改变指向的指针,必须初始化,如int *const p。

#a和&a有什么区别?

假设数组int a[10]; int (*p)[10] = &a;其中:

  • a是数组名,是数组首元素地址,+1表示地址值加上一个int类型的大小。如果a的值是0x00000001,加1操作后变为0x00000005。*(a + 1) = a[1]。
  • &a是数组指针,其类型为int (*)[10],其加1时,系统会认为是数组首地址加上整个数组的偏移(10个int型变量),值为数组a尾元素后一个元素的地址。
  • 不考虑数组,a是变量,&a是变量的地址
  • 二维数组取值:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int a[2][3] = {{2,2,3}, {4,5,6}};
// p指向一个包含3个元素的数组首地址,p+1 = int[3] + int[3]
int (*p)[3] = a;
// *(p+1),指向下一个包含3个元素的数组首地址
cout << *(*(p+1)+1) << *(a[1]+1) << '\n'; // 5
// *p+4,指向第五个地址,因为数组是连续存储的,即第二行第二个元素地址
cout << *(*p+4) << *(a[0]+4) << *(&a[0][0]+4) << a[1][1] << '\n'; // 5
// ptr指向首元素地址,ptr+1 = int + int
int *ptr = &a[0][0];
// 数组指针p是“二级指针”所以要再加*
// ptr是一级指针,直接解引用即可
cout << *(ptr+4) << '\n'; // 5

#C++和Python的区别

包括但不限于:

  • Python是一种脚本语言,是解释执行的,而C++是编译语言,是需要编译后在特定平台运行的。python可以很方便的跨平台,但是效率没有C++高。
  • Python使用缩进来区分不同的代码块,C++使用花括号来区分
  • C++中需要事先定义变量的类型,而Python不需要,Python的基本数据类型只有数字,布尔值,字符串,列表,元组等等
  • Python的库函数比C++的多,调用起来很方便

#C++和C语言的区别

  • C++中new和delete是对内存分配的运算符,取代了C中的malloc和free。
  • 标准C++中的字符串类取代了标准C函数库头文件中的字符数组处理函数(C中没有字符串类型)。
  • C++中用来做控制态输入输出的iostream类库替代了标准C中的stdio函数库。
  • C++中的try/catch/throw异常处理机制取代了标准C中的setjmp()和longjmp()函数。
  • 在C++中,允许有相同的函数名,不过它们的参数类型不能完全相同,这样这些函数就可以相互区别开来。而这在C语言中是不允许的。也就是C++可以重载,C语言不允许。
  • C++语言中,允许变量定义语句在程序中的任何地方,只要在是使用它之前就可以;而C语言中,必须要在函数开头部分。而且C++不允许重复定义变量,C语言也是做不到这一点的
  • 在C++中,除了值和指针之外,新增了引用。引用型变量是其他变量的一个别名,我们可以认为他们只是名字不相同,其他都是相同的。
  • C++相对与C增加了一些关键字,如:bool、using、dynamic_cast、namespace等等

#C++与Java的区别

  • Java语言给开发人员提供了更为简洁的语法;完全面向对象,由于JVM可以安装到任何的操作系统上,所以说它的可移植性强

  • Java语言中没有指针的概念,引入了真正的数组。不同于C++中利用指针实现的“伪数组”,Java引入了真正的数组,同时将容易造成麻烦的指针从语言中去掉,这将有利于防止在C++程序中常见的因为数组操作越界等指针操作而对系统数据进行非法读写带来的不安全问题

  • C++也可以在其他系统运行,但是需要不同的编码(这一点不如Java,只编写一次代码,到处运行),例如对一个数字,在windows下是大端存储,在unix中则为小端存储。Java程序一般都是生成字节码,在JVM里面运行得到结果

  • Java用接口(Interface)技术取代C++程序中的抽象类。接口与抽象类有同样的功能,但是省却了在实现和维护上的复杂性

垃圾回收

  • C++用析构函数回收垃圾,写C和C++程序时一定要注意内存的申请和释放
  • Java语言不使用指针,内存的分配和回收都是自动进行的,程序员无须考虑内存碎片的问题

应用场景

  • Java在桌面程序上不如C++实用,C++可以直接编译成exe文件,指针是c++的优势,可以直接对内存的操作,但同时具有危险性 。(操作内存的确是一项非常危险的事情,一旦指针指向的位置发生错误,或者误删除了内存中某个地址单元存放的重要数据,后果是可想而知的)
  • Java在Web 应用上具有C++ 无可比拟的优势,具有丰富多样的框架
  • 对于底层程序的编程以及控制方面的编程,C++很灵活,因为有句柄的存在