根据各方大佬总结的 c 语言面试问题进行了收集,并根据自己的理解进行整理,本篇章属于知识点汇总,如果有需要的内容可以根据目录跳转。
另外八股文等知识梳理的文章,在下整理都要花费十数个小时,若觉得不错的话还请点赞、收藏、关注在下文章吧,感谢感谢!
问:运算符的优先级
答:成员运算符 > 单目运算符 >= 算数运算符 > 移位运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符
#include
int main()
{
/* Write C code in this online editor and run it. */
int a = 20;
printf("%d\n",a); // 20
printf("%d\n",&a); // a的地址值,比如0093F968
int* b = &a;
printf("%d\n",b); // a的地址值
printf("%d\n",*b); // 20
int c = !a;
printf("%d\n",c); // 0
int d = ~a;
printf("%d\n",d); // -21
return 0;
}
% 操作要求左右的值均为整数
问:请说说原码、反码、补码
答: 整形数值在计算机的存储中,最左边的一位代表符号位,0 正 1 负
结束当前循环,退出函数,用在函数体中则返回特定值
无条件跳转,几乎不使用。
volatile int a = 20, b, c;
b = a;
c = a;
上述代码如果不加 volatile 编译器的执行流程如下:
- b=a; 先从a的内存中取值存放到寄存器,再把寄存器的值给存到b的内存
- c=a;把寄存器的值给存到b的内存
这里可以看出编译器对 c=a;进行了优化,不再执行从 a 的内存中取值,而是直接从寄存器中取值 如果这段时间内 a 发生了变化(比如中断触发、多线程共享变量修改了a的值),那么 c 就不能得到最新的值
这时候就需要使用 volatile 告诉编译器,不要对变量 a 进行优化,每次都是从内存中取出 a 的值
问:struct 与 typedef struct 的区别
答:typedef 是类型定义的意思,用在结构体中是给结构体取别名,定义方式如下
#include
#include
typedef struct Student
{
char name[20];
int age;
int sex;
} stu; // stu 即结构体别名
struct Teacher
{
char name[20];
int age;
} Li; // 结构体成员
int main() {
stu Zhangsan = {"zhangsan", 18, 1}; // 使用typedef struct可以直接使用结构体别名创建新的结构体成员
stu Lisi;
strcpy(Lisi.name, "lisi"); // 使用strcpy复制字符串
Lisi.age = 17;
Lisi.sex = 0;
struct teacher Zhang; // 直接使用struct创建结构体成员
return 0;
}
问:结构体大小如何计算
答:
//最大基本数据类型为 double,占 8 字节,其他的结构体变量占用空间均为 8 的整数倍
struct data {
double d1; // 大小为8字节,从地址0开始,占用到地址8
int d2[5]; // 大小为 4x5=20 字节,首地址为8,为8的倍数,占用到地址28
char d3; // 大小为 1,首地址为28,不是8的倍数,偏移地址到32,占用到地址33
}; // 当前结构体大小为33,不是8的倍数,取8的倍数,所以结构体大小为40
union 的使用:
union 的用法和 struct 差不多,区别是同一个 union 成员的不同的成员变量共享同一块内存空间(起始地址相同),同一时刻只能存在一个成员变量
这边可以理解为张三和李四是穿同一条裤子的,张三穿了裤子,李四就没裤子穿了,同一时刻张三和李四最多只有一个人能穿裤子。
举例个代码:
#include
union Age
{
int zhangsan;//成员变量张三
int lisi;//成员变量李四
};
int main() {
//union Age age = {18, 20};//同一时刻只能存在一个成员变量,所以这里运行会报错
union Age age;
age.zhangsan = 18;
age.lisi = 22;
printf("%d, %d\n", age.zhangsan, age.lisi); //运行结果 22, 22,说明后赋的值会将先赋的值覆盖
printf("%p, %p\n", &age.zhangsan, &age.lisi);//获取成员变量的地址值,结果0x7ffc6b832f5c, 0x7ffc6b832f5c,说明起始地址相同
printf("%d\n", sizeof(union Age));//获取联合体大小,结果为4,说明该联合体只有一个int类型大小
return 0;
}
问:union 联合体和 struct 结构体的区别
答:
问:联合体一般可以用来做什么?
答:
在实际的编程当中,为了确定当前计算机使用的存储模式是大端存储还是小端存储,可以使用union进行简单高效的判断
大端存储模式:高字节存放在低位地址,低字节存放在高位地址
小端存储模式:低字节存放在低位地址,高字节存放在高位地址
ps:x86架构和多数ARM、DSP都为小端存储模式,keil C51则为大端存储模式,有些架构还可以自主切换
#include
union GetByte
{
short twoByte; //short 类型占 2 字节
char myByte[2];//myByte[0] 和 myByte[1] 分别占用 1 字节
}getbit;
int main() {
getbit.twoByte = 0x1122;
printf("%x, %x\n", getbit.myByte[0], getbit.myByte[1]); //运行结果 22, 11;说明本计算机是低字节存放在低位,高字节存放在高位,为小端存储
return 0;
}
如果有不理解的地方,可以看看孤烟大佬的视频
int main()
{
enum { a,b=5, c, d=4, e };
printf("%d, %d, %d, %d, %d",a, b, c, d, e);
//输出结果0, 5, 6, 4, 5
}
问:typedef 和 #define 的区别
答:
#define | typedef |
---|---|
预处理指令 | 关键字 |
在编译前不作检查,编译时可能检查到错误 | 有类型检查功能 |
无作用域限制 | 有作用域限制 |
可定义类型、常量、变量等的别名 | 可定义类型的别名 |
#define myptr int* p
myptr a,b;//a 是 p 的指针变量,b 是 int 变量
typedef int* myptr;
myptr a,b;//a 是 int* a, b是 int* b
const int a = 100;
int const b = 10;
a = 200; //报错
char str[b] = "abcdefg";//报错,因为被const修饰的变量b并不是真正意义上的常量,无法分配准确的大小给str
char num[b];//数组不初始化是不会报错的
void test1(const int x, int y)
{
x = 10; //编译时报错
y = 10;
}
const int * test(void)
{
return 1;
}
int main(){
const int *num1 = test();
int *num2 = test();//编译时报错
}
int main(){
//*在const后,即常量指针,表示指针指向的内容不可以修改,但是指针的地址可以更改
const * int a;
int const *b;
//*在const前,即指针常量,表示指针指向的内容可以修改,但是指针的地址不可以更改
int * const c;
//指针的地址和内容都不可以被修改
const int * const d;
}
extern int a;
extern int a = 30; //错误,生命时不能初始化
注意:全局变量最好不要写在 .h 头文件中,如果写在了 .h 文件中,多个 .c 文件对这头文件进行包含,编译器会报错 multiple define 多个定义。
如果有些全局变量经常在多个文件中被需要,普遍的做法如下:
// file1.c 文件
int globalVariable = 10; // 定义全局变量
// golbal.h 文件
#ifndef GLOBAL_H_
#define GLOBAL_H_
extern int globalVariable; // 声明全局变量
#endif /* GLOBAL_H_ */
//file2.c 文件
#include "global.h"
#include
int main() {
printf("%d\n", globalVariable);
return 0;
}
如上,在golbal.h文件中将常出现的变量进行声明,在需要全局变量的文件中包含即可,如果需要增加代码可读性和防止特殊错误,你也可以在 file2.c 中再进行一次声明。
extern 的作用主要是告诉编译器我在其他文件定义了个全局变量,并且已经分配了空间,不需要再为该变量申请空间
extern void function1(int x, int y);
register 只能用于修饰变量(且不能是浮点数),被修饰的变量会被存储在 CPU 内部的寄存器中,而不是内存中。
寄存器有两个特点,一个是运算速度快,一个是不能取地址。
被 register 修饰的变量无法取地址,取地址会编译报错
一般情况下,我们没有特别声明的局部变量,都默认为 auto 类型,存储在栈中
#include
int staticNum()
{
static int num = 0;
return ++num;
}
int main()
{
int sum = 0;
for(int i=0; i<10; i++)
{
sum = staticNum();
}
printf("%d\n", sum); //打印结果为 10
return 0;
}
问:静态全局变量与静态局部变量的区别
答:作用域不同,静态全局变量的作用域在当前文件中,静态局部变量的作用域在当前函数体中。
问:普通全局变量和静态全局变量的去呗
答:作用域不同,静态全局变量的作用域在当前文件中,普通全局变量的作用域为当前整个项目。
注意:
先执行 do {} 中的内容,在执行 while() 里的条件判断是否符合。
问: sizeof() 和 strlen() 的区别
答:
1. sizeof 是关键字,strlen 是函数
2. sizeof 用于计算占用内存大小,strlen 用来计算字符串的长度
3. sizeof 会计算 \0 的空间,strlen 不需要
#include
#include
int main()
{
char c[5] = {1,2,3};
printf("%d\n", sizeof(c)); //5
printf("%d\n", strlen(c)); //3
int i[5] = {1,2,3};
printf("%d\n", sizeof(i)); //20
return 0;
}
4. sizeof 在编译时计算,strlen 在运行时计算
问:如何不适用 sizeof 求数据类型字节的大小
答:
//(char*)强转,便于在指针运算中以字节为单位进行计算。
//&value+1 则能够获取value结束后的地址
//&value 获取value的起始地址
#define mysizeof(value) (char*)(&value+1)-(char*)(&value)
问:strlen(“\0”) == sizeof(“\0”); 是否正确?
答:
printf("%d\n", sizeof("\0")); //2
printf("%d\n", strlen("\0")); //0
printf("%d\n", sizeof('\0')); //4
printf("%d\n", strlen('\0')); //报错空指针
问: sizeof(a++); a最后的结果会改变吗?
答:不会,sizeof 只会求所占内存大小,不会进行表达式运算
int a = 2;
printf("%d\n", sizeof(a++)); //4
printf("%d\n", a); //2
问:计算数组大小
答:
char ch[] = "hello";
char str[10] = {'h','e','l','l','o'};
printf("%d\n",sizeof(ch));//6
printf("%d\n",strlen(ch));//5
printf("%d\n",sizeof(str));//10
printf("%d\n",strlen(str));//5
问:sizeof(void); 结果
答:报错,或者为1