C++面试八股-基础(二)

10.回调函数的定义及作用,函数指针和指针函数的区别

        简言之,回调函数是一种可以作为参数传递给其他函数的函数,当上层函数触发了某一个特定条件时或者执行了某个操作时,该函数就会调用回调函数。在C/C++中,回调函数通常是函数指针的一种常见使用方式(在其他语言因为没有指针的概念所以传参会灵活简洁一些)。

        回调函数主要用在异步操作、事件驱动编程、轮询、递归操作等情况。例如:

        1)异步操作:当需要执行一个耗时操作(如文件IO或网络等待)时,可以使用回调函数来执行该耗时操作返回后将要执行的操作,此时主线程就可以执行其他的工作,无需一直等待该耗时操作的返回。

        2)事件驱动编程:在GUI开发、网络编程中,回调函数一般会作为界面例如按钮点击、键盘输入或者网络消息等事件的响应执行。

        3)轮询:在某些情况下,程序需要定期检查某个条件是否满足。例如,一个程序可能需要定期检查某个文件是否存在或某个网络连接是否仍然活动。在这些情况下,可以使用回调函数来定义当条件满足时要执行的操作。

        4)递归:在递归函数中,回调函数可以用来处理递归函数的结果,这样就可以使递归函数在每一次返回时可以执行不完全相同的操作。

        函数指针本质上是一个指针,其跟普通指针一样,存储的仍然是一个地址,通过这个地址,就可以间接的调用这个函数。需要注意的是,函数指针的返回类型和参数列表(包含参数类型和参数数量)必须与其指向的函数的返回类型和参数列表完全一致。

        函数指针的定义方式为:

int (*func_ptr)(int, int)

        即必须将指针和函数名用括号括起来。其简单使用方式跟指针类似,即指向已有的函数,然后调用了函数指针就相当于调用了被指向的函数。

int sum(int a, int b)
{
    return a + b;
}

int (*func_ptr)(int, int);

int main()
{
    func_ptr = add; //赋值
    int ret = func_pre(3, 4);//返回7
    return 0;
}

        指针函数本质上仍然是一个函数,其特殊之处在于返回值是一个指针类型的变量。这是与函数指针的本质区别。指针函数返回的指针可以指向任何类型。其定义方式为:

int* func_ptr(int, int)

将括号去掉就是指针函数, 为更明显理解,将*与int放一起,就跟指针的定义一模一样了。

        二者区别于用途:

        1)函数指针一般用作回调函数和虚函数表等高级功能的实现,指针函数一般可用作指定类型的动态数组的内存分配、返回多个值等场景。

        2)函数指针的调用需要通过指针变量的方式进行调用,指针函数像普通函数一样调用即可。

11.数组指针和指针数组的区别

        数组指针:是一个指针,定义方式与函数指针类似,都是 类型 (*指针名称)[数组长度],例如int(*p)[5],表示一个指向包含5个类型为int元素的数组的指针,即{*p1, *p2, *p3, *p4, *p5}。

        数组指针占用空间为一个指针的大小,一般情况下32位为4字节,64位为8字节。

        指针数组:是一个数组,定义方式为 类型* 指针名称[数组长度],例如 int* p[5],表示一个元素类型为int*的长度为5的数组,即{int*, int*, int*, int*, int*}。

        指针数组占用空间为 (元素个数 * 指针大小)。

       用途:

        1) 数组指针一般用来访问二维数组,数组指针的长度与二维数组的长度相同。即

int arr[5][3]; 
int (*p)[3] = arr;

        在这里,p是一个数组指针,指向一个包含3个int类型元素的数组的首地址,这个数组实际上是二维数组arr的第一行。 3个*p分别代表了各自列的5个元素。

      2)  指针数组则常用于处理多个字符串,指针数组的长度与二维数组的长度相同,每个指针元素指向一个字符串的首地址,使得字符串处理更加灵活方便。例如

char *str_B[5] = {'abc', 'bcde', '123a', '&Ghs', 'fssaG'};

str_B是一个包含5个指向字符型数据指针的数组,数组内每个元素都是char*类型的,char*类型就可以指向某个字符串的首地址,这样就可以存储5个字符串的首地址。

12.C++内存模型

        按照作用类型可分为代码区和数据区,按照功能区分可分为代码区、全局数据区、堆、栈、常量区。实际内存结构如下:

低地址.text.data.bss

heap

-->

未使用

<--

stack

env高地址

        1).text段:存放编译后的二进制代码,即机器指令,只读,大小在运行时是固定的。

        2).data段:存储已初始化的全局和静态变量。

        3).bss段:存储未初始化的全局和静态变量,这些变量在程序开始执行前会被初始化为0。

        4)heap:手动分配和管理的区域

        5)stack:由系统自动分配和管理的函数调用栈。

        6)env:存储与程序运行环境相关的信息。

        除了栈从高到低生长之外,其余内存均从低地址向高地址生长。

13.C++编译流程

        1)预处理(preprocessing):编译器首先读取源代码文件,然后处理预处理指令,例如#include(用于将其他文件插入到本文件中)、#define等,处理完毕后生成一个不可见的.i文件,甚至有的编译器不生成.i文件。

        2)编译(compilation):编译器将预处理完毕的代码编译成汇编代码,并且检查语法中的错误信息,如果有错误则生成诊断信息(错误、警告等),编译结束后可生成多个汇编文件。

        3)汇编(assembly):汇编器将汇编完成后的汇编代码转换成机器码,也可称为目标码。机器代码是计算机可以直接执行命令的代码,通常为.o或者.obj文件。

        4)链接(linking):链接器(linker)将目标码以及任何必要的库文件组合成一个可执行文件。链接器也会解析符号的引用,确保其都有定义。如果链接器在解析的过程中发现有符号未引用,则会报错。链接完成后输出的是一个可执行文件(Windows为.exe,Linux为.out)

        5)加载和执行:操作系统加载可执行文件,准备执行,文件入口从main函数开始(全局变量和静态变量的初始化在main函数执行之前就已经完成了,以及其他)

14.动态链接库和静态链接库的作用及区别

        动态链接库后缀名在Windows系统为.dll,在Linux系统为.so,动态链接库包含了一组可重用的代码和数据,这些代码和数据可在多个程序之间共享调用。

        静态链接库后缀名为.lib/.a,是一种编译后的二进制文件,包含了程序运行所需的函数和数据,静态链接库中的代码和数据在程序编译时连接到目标代码中,故程序在运行时是不需要静态链接库的。

        动态链接库优缺点:

        1)提高程序模块化、可维护性和复用性,对于动态链接库内的更改,只需重新编译生成该链接库即可,无需重新编译执行文件;

        2)减少内存和硬盘占用,一个动态链接库支持多个应用程序共同调用;

        3)允许在运行时更新库文件,且不需要重新编译和链接程序;

        4)动态链接库的加载一般比静态链接库的加载方便,但文件运行流畅度比静态链接库差,且可能存在动态链接库所依赖的相关环境缺失导致程序无法运行的问题(在无相关开发环境的电脑中容易出现)。

        静态链接库优缺点:

        1)加载速度(运行流畅度)快,因为依赖项在编译期就已经加载到程序中;

        2)易于部署,因为已经将所有的依赖环境加载到应用程序中,所以适应范围更广;

        3)静态链接库文件大小一般比动态链接库大;

        4)如果多个程序静态链接了相同的库,那么每个程序就会在内存中拷贝该库的副本,造成内存浪费;

        5)如果静态链接库发生更改,则需要编译整个应用程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值