C语言面经

C语言面经

1.函数指针与指针函数

  • 函数指针:一个指针,指向一个函数

    在C语言中,一个函数总是占用一段连续的内存区,函数名就是该内存区的首地址,我们可以把这个首地址赋予一个指针变量,通过指针变量来找到并调用这个函数。这个变量称为”函数指针变量“

    一般形式为: 类型说明符 (*指针变量名)();

    比如:int (*p)(int *x, int *y);

    这里的p不是函数名,而是一个指针变量,它指向一个函数,这个函数有两个指针类型的参数,函数的返回值是int

    int *(*p)(int *x, int *y);

    同样的,p是指向一个返回值为int * 类型的函数的指针

Q:函数指针如何使用?

int max(int a,int b){
    if(a>b)return a;
    else return b;
}
main(){
    int max(int a,int b);
    int(*pmax)(); //定义一个指针
    int x,y,z;
    pmax=max;//指向函数
    printf("input two numbers:\n");
    scanf("%d%d",&x,&y);
    z=(*pmax)(x,y);  //等价于z=max(x,y)
    printf("maxmum=%d",z);
}

使用函数指针的好处:

可以将实现同一功能的多个模块统一起来标识,更容易进行后期维护

  • 指针函数

    指针函数指的是函数的返回值是一个指针

    比如:int *p(int a,int b);

可以做一个总结:

名称含义定义形式指针p的类型
变量指针指向变量的指针int *p=&aint *
常量指针指向常量的指针int (*p)=&aint const *
一维数组指针指向一维数组首元素的指针int *p=aint *
二维数组指针指向二维数组int (*p)[4]=aint (*)[4]
函数指针指向函数的指针int (*p)()=addint (*)()

2. 回调函数

把函数作为返回值或者传入参数,是函数指针最重要的意义。

举个栗子:(函数指针作为传入参数)

BOOL compareGreater(int number, int compareNumber) { 
	return number > compareNumber;
} 

//把一个比较函数作为参数传入到另一个函数里面
void compareNumberFunction(int *numberArray, int count, 
						 int compareNumber, BOOL (*p)(int, int))
{
	for (int i = 0; i < count; i++) { 
		if (p(*(numberArray + i), compareNumber)) { 
			printf("%d\n", *(numberArray + i)); 
		} 
	}
}

再举个栗子:函数指针作为函数返回值

typedef int(*FUNC)(int, int); //定义一个FUNC代表 int(*)(int, int) 类型

//即上面需要返回的BFunction函数,执行加法操作
int add(int a, int b)
{
	return a + b;
}

FUNC AFunction(const char * ch, int(*p)(int, int)) //这实际上就是 “类型名称 对象名称”,看起来就比较自然了
{
	if (ch == "add")  //只有传入“add”的时候才返回加法函数,否则返回null
	{
		return p;
	}
	else
	{
		return NULL;
	}
}


int main()
{
	//返回的类型要与定义的BFunction兼容,也是“类型名称 对象名称”的表示
	FUNC p = AFunction("add", add);
	int result = p(1000, 2000);
	printf("the result is : %d\n", result);

	getchar();
	return 0;
}

3. 关键词的作用

  • static关键词:静态

    作用:隐藏,控制作用域

    1. 在函数体内,局部变量加static,其生命周期为整个源程序,作用域仍旧是该函数(延长生命周期)。

    2. 在函数体外,全局变量加static,其生命周期为整个源程序,作用域为该文件,无法被其他源文件访问(本地化)

    3. static变量被存储到静态数据区,默认为0;

      总结:static关键词用来控制作用域,所有全局变量在定义的时候都要考虑是否需要加static

      除此之外,在C++的类里面:

      • 被static修饰的类变量或者方法,静态变量属于整个类而不是某个对象,可以通过类名.变量名直接引用,不需要new一个对象出来
      • 静态变量在类实例化对象之前就已经分配空间了,所以类的静态成员函数不能使用类的非静态成员
  • const关键词:只读

    作用:语义约束,告诉编译器某值是不变的

    1. const修饰全局变量,变量会放到常量区,作用域为本文件,其它文件不可访问(加了extern后可以)

    2. const变量必须在定义时初始化,之后任何对const修饰变量的修改都是不合法的

    3. 修饰函数返回值,返回的内容不能被修改。(一般是用在指针传递中,则函数返回指针指向的内容不能被修改)

    4. 与指针合用的两个产物:常量指针与指针常量

      • 常量指针:const int * p 或者 int const * p;

        读作p is a pointer to int const ,p是一个指向整型常量的指针,

        只能防止通过指针来修改变量,可以用其他方式改变指向变量的值,也可以改变指针的指向;

      • 指针常量:int *const p

        读作 const p is a pointer to int, p是一个指向int的常量指针

        指针的指向不能变,即不能指向其它地址;

      • 指向常量的常指针:const int* const p;

        以上两种的结合,指向不能改变,也不能通过该指针改变变量的值,但依然可以通过其它方式改这个值

  • volatile关键词:

    1. volatile修饰的关键词会被存到主存中,对所有线程可见(共享内存)
    2. volatile修饰的关键词容易被意想不到地改变,需要用互斥机制保护
    3. 禁止指令重排序(不深究)
      进阶
    • 一个参数可以既是const还是volatile吗?可以,比如说一个只读的状态存储器

    • 一个指针可以是volatile吗?可以,比如一个中断服务子程序修改一个buffer指针

##4. 引用和指针的区别

  1. 引用必须被初始化,指针不需要
  2. 引用初始化之后不能被改变,指针可以改变所指向的对象
  3. 不存在指向空值的引用,可以存在指向空值的指针
  4. 引用可以视作一个常量指针,用于提高程序可读性

5. 程序的内存分配

一个通过C/C++编写的程序,其内存分为以下几个部分

  1. 栈区:由编译器自动分配释放,存放函数参数值、局部变量值等

  2. 堆区:由程序员分配释放,程序结束后由OS回收(操作系统控制的链表结构)

  3. 全局区(静态变量区):初始化的全局变量和静态变量在一块区域

  4. 常量区:存放常量及字符串等

  5. 程序代码区:存放函数体的二进制代码

进阶:

栈溢出:一般是无限递归导致,可以增加错误检测机制或者改用循环

6.编译原理

简述一下编译过程:

  1. 预处理:插入头文件,展开宏定义
  2. 编译:包括词法分析、语法分析、词义分析、代码优化和中间代码产生等,产生汇编文件
  3. 汇编:将汇编文件转化为二进制目标文件
  4. 链接:调用静态库(编译时加载)或者动态库(运行时加载),产生可执行文件

7. 野指针

野指针的成因一般有两种:

  1. 指针变量没有被初始化:指针变量在刚创建时不会自动成为NULL指针,可能指向非法的内存。

  2. 指针变量被free或者delete之后,没有置为NULL,让人误以为p是一个合法的指针:free和delete只能释放指向的内存,但指向这块空间的指针仍然存在,只不过指向的是垃圾,在后面解引用会导致错误

8. sizeof()问题

  1. 基本数据类型:内存大小是和系统相关的,所以在不同的系统下取值可能不同。

  2. 结构体的sizeof

    结构体的sizeof涉及到字节对齐问题。

    为什么需要字节对齐?计算机组成原理教导我们这样有助于加快计算机的取数速度,否则就得多花指令周期了。为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上,依次类推。这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。

    字节对齐的细节和编译器的实现相关,但一般而言,满足三个准则:

    1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除。

    2) 结构体的每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要,编译器会在成员之间加上填充字节(internal adding)。

    3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员后加上填充字节(trailing padding)。

  3. 联合体的sizeof

    结构体在内存组织上市顺序式的,联合体则是重叠式,各成员共享一段内存;所以整个联合体的sizeof也就是每个成员sizeof的最大值。

  4. 数组的sizeof
    数组的sizeof等于数组所占用的内存字节数

注意:1)当字符数组表示字符串时,其sizeof值将’/0’计算进去。

	   2)当数组为形参时,其sizeof值相当于指针的sizeof值。 
  1. 指针的sizeof
    指针是用来记录另一个对象的地址,所以指针的内存大小当然就等于计算机内部地址总线的宽度。在32位计算机中,一个指针变量的返回值必定是4。

  2. 函数的sizeof

    sizeof也可对一个函数调用求值,其结果是函数返回值类型的大小,函数并不会被调用。

    对函数求值的形式:sizeof(函数名(实参表))

    注意:1)不可以对返回值类型为空的函数求值。

    ​ 2)不可以对函数名求值。

    ​ 3)对有参数的函数,在用sizeof时,须写上实参表。

9. argc和argv的作用

argc在C语言中表示运行程序时传递给main()函数的命令行参数个数。

argv在C语言中表示运行程序时用来存放命令行字符串参数的指针数组。

argc、argv用命令行编译程序时有用。主函数main中变量(int argc,char *argv[ ])的含义如下:

1、main(int argc, char *argv[ ], char **env)是UNIX和Linux中的标准写法。

2、argc: 整数,用来统计你运行程序时送给main函数的命令行参数的个数

3、* argv[ ]: 指针数组,用来存放指向你的字符串参数的指针,每一个元素指向一个参数。其中argv[0] 指向程序运行的全路径名,argv[1] 指向在DOS命令行中执行程序名后的第一个字符串,argv[2] 指向执行程序名后的第二个字符串,argv[argc]为NULL。

4、argc、argv是在main( )函数之前被赋值的,编译器生成的可执行文件,main( )不是真正的入口点,而是一个标准的函数,这个函数名与具体的操作系统有关。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值