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=&a | int * |
常量指针 | 指向常量的指针 | int (*p)=&a | int const * |
一维数组指针 | 指向一维数组首元素的指针 | int *p=a | int * |
二维数组指针 | 指向二维数组 | int (*p)[4]=a | int (*)[4] |
函数指针 | 指向函数的指针 | int (*p)()=add | int (*)() |
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关键词:静态
作用:隐藏,控制作用域
-
在函数体内,局部变量加static,其生命周期为整个源程序,作用域仍旧是该函数(延长生命周期)。
-
在函数体外,全局变量加static,其生命周期为整个源程序,作用域为该文件,无法被其他源文件访问(本地化)
-
static变量被存储到静态数据区,默认为0;
总结:static关键词用来控制作用域,所有全局变量在定义的时候都要考虑是否需要加static
除此之外,在C++的类里面:
- 被static修饰的类变量或者方法,静态变量属于整个类而不是某个对象,可以通过类名.变量名直接引用,不需要new一个对象出来
- 静态变量在类实例化对象之前就已经分配空间了,所以类的静态成员函数不能使用类的非静态成员
-
-
const关键词:只读
作用:语义约束,告诉编译器某值是不变的
-
const修饰全局变量,变量会放到常量区,作用域为本文件,其它文件不可访问(加了extern后可以)
-
const变量必须在定义时初始化,之后任何对const修饰变量的修改都是不合法的
-
修饰函数返回值,返回的内容不能被修改。(一般是用在指针传递中,则函数返回指针指向的内容不能被修改)
-
与指针合用的两个产物:常量指针与指针常量
-
常量指针:
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关键词:
- volatile修饰的关键词会被存到主存中,对所有线程可见(共享内存)
- volatile修饰的关键词容易被意想不到地改变,需要用互斥机制保护
- 禁止指令重排序(不深究)
进阶
-
一个参数可以既是const还是volatile吗?可以,比如说一个只读的状态存储器
-
一个指针可以是volatile吗?可以,比如一个中断服务子程序修改一个buffer指针
##4. 引用和指针的区别
- 引用必须被初始化,指针不需要
- 引用初始化之后不能被改变,指针可以改变所指向的对象
- 不存在指向空值的引用,可以存在指向空值的指针
- 引用可以视作一个常量指针,用于提高程序可读性
5. 程序的内存分配
一个通过C/C++编写的程序,其内存分为以下几个部分
-
栈区:由编译器自动分配释放,存放函数参数值、局部变量值等
-
堆区:由程序员分配释放,程序结束后由OS回收(操作系统控制的链表结构)
-
全局区(静态变量区):初始化的全局变量和静态变量在一块区域
-
常量区:存放常量及字符串等
-
程序代码区:存放函数体的二进制代码
进阶:
栈溢出:一般是无限递归导致,可以增加错误检测机制或者改用循环
6.编译原理
简述一下编译过程:
- 预处理:插入头文件,展开宏定义
- 编译:包括词法分析、语法分析、词义分析、代码优化和中间代码产生等,产生汇编文件
- 汇编:将汇编文件转化为二进制目标文件
- 链接:调用静态库(编译时加载)或者动态库(运行时加载),产生可执行文件
7. 野指针
野指针的成因一般有两种:
-
指针变量没有被初始化:指针变量在刚创建时不会自动成为NULL指针,可能指向非法的内存。
-
指针变量被free或者delete之后,没有置为NULL,让人误以为p是一个合法的指针:free和delete只能释放指向的内存,但指向这块空间的指针仍然存在,只不过指向的是垃圾,在后面解引用会导致错误
8. sizeof()问题
-
基本数据类型:内存大小是和系统相关的,所以在不同的系统下取值可能不同。
-
结构体的sizeof
结构体的sizeof涉及到字节对齐问题。
为什么需要字节对齐?计算机组成原理教导我们这样有助于加快计算机的取数速度,否则就得多花指令周期了。为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上,依次类推。这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。
字节对齐的细节和编译器的实现相关,但一般而言,满足三个准则:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除。
2) 结构体的每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要,编译器会在成员之间加上填充字节(internal adding)。
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员后加上填充字节(trailing padding)。
-
联合体的sizeof
结构体在内存组织上市顺序式的,联合体则是重叠式,各成员共享一段内存;所以整个联合体的sizeof也就是每个成员sizeof的最大值。
-
数组的sizeof
数组的sizeof等于数组所占用的内存字节数
注意:1)当字符数组表示字符串时,其sizeof值将’/0’计算进去。
2)当数组为形参时,其sizeof值相当于指针的sizeof值。
-
指针的sizeof
指针是用来记录另一个对象的地址,所以指针的内存大小当然就等于计算机内部地址总线的宽度。在32位计算机中,一个指针变量的返回值必定是4。 -
函数的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( )不是真正的入口点,而是一个标准的函数,这个函数名与具体的操作系统有关。