目录
【前言】
为了考察自己对于C语言掌握情况,我特意在论坛里面整理了近百份C/C++研发方向的面经,我发现有很多知识点频繁被考察,下面我将这些高频考点分享给大家,铁汁们看看自己能答对多少个。(顺便说一下哈,C语言内容其实是非常多的,这里所说的“学完C语言”,指的仅仅是学完C语法而已)
【声明】
下面所总结的内容可能并不是面面俱到的,后续还会有所补充。
【注意】:博主刚建立了一个社区和QQ学习群,链接和二维码在最下面哦,非常欢迎铁汁们的加入!
一、大小端字节序
1.大小端引入
看看下面这段代码:
#include<stdio.h> int main() { int a = 0x11223344;//以十六进制的形式赋值给a return 0; }
很明显,在内存中存储的顺序不对呀,这是为什么?这里就要介绍大小端了。
2.何为大端小端
大端字节序存储:
当一个数据的低位放到高地址处,数据的高位放到低地址处;
小端字节序存储:
当一个数据的低位放到低地址处,数据的高位放到高地址处
比如上面的栗子:
3.百度真题
下面看看15年百度的一道价值10分的笔试题!
百度2015年系统工程师笔试题(10分):
请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。
概念的话都在上面啦,请看看如何设计小程序来判断...
思路
可以定义一个整型变量,并且初始化为1,所以它转换成十六进制表示形式就是0x00 00 00 01。如果当前机器是小端字节序存储,那么从低地址到高地址就是01 00 00 00;如果当前机器是大端字节序存储,那么从低地址到高地址就是00 00 00 01;所以不同点就在于低地址处一个是01,一个是00,那怎么去判断呢?这里就可以使用前面指针所学的解引用操作,不过对于整型指针来说一次访问4个字节,而我们想要的仅仅读取到1个字节即可。所以这里就需要进行强制类型转换,将int* 转换成char* 类型的,这样的话进行指针解引用操作的时候,一次就只能访问一个字节的内容了。
分析起来很简单,下面看看代码该如何编写:
代码执行
#include<stdio.h> int main() { int a = 1;//00 00 00 01 char* p = (char*)&a; if (1 == *p) { printf("小端\n"); } else { printf("大端\n"); } return 0; }
上面这段代码只是方便大家理解,实际上我们交给面试官看的话,这么写代码就显得很low啦,所以下面才是满分答案:
#include<stdio.h> int check_sys() { int a = 1;//0x00 00 00 01 return (*(char*)&a);//返回0-大端;返回1-小端 } int main() { int ret = check_sys(); if (1 == ret) { printf("小端\n"); } else { printf("大端\n"); } return 0; }
4.作业补充
unsigned int a = 0x1234; unsigned char b = &a; 在32位大端式处理器变量b 等于() A:0x00 B:0x12 C:0x34 D:0x1234 //很明显,答案是A,因为实际上a中放的是0x00001234
问:字符类型有大小端字节序问题吗?
答:没有,因为字符类型只有一个字节,何来字节序一说呢。
二、深入理解static关键字
在我整理百份面经后,我发现,static、extern、const、struct等关键字是大多数公司考察的重点,考的相当频繁,其中不乏知名大厂!
下面详细介绍static关键字:
static —— “静态”之意
C语言中static是用来修饰变量和函数的。
- 修饰局部变量——静态局部变量
- 修饰全局变量——静态全局变量
- 修饰函数——静态函数
1.static修饰局部变量
//代码1 #include<stdio.h> void test() { int a = 1;//局部变量a的作用域在test()中,当a出了作用域就被销毁了,下次调用test()时,又需要重新创建a a++; printf("%d ", a); } int main() { int i = 0; for (i = 0; i < 10; i++) { test(); } return 0; }
//代码2 #include<stdio.h> void test() { //static修饰局部变量a static int a = 1; a++; printf("%d ",a); } int main() { int i = 0; for (i = 0; i < 10; i++) { test(); } return 0; }
看:上面的两块代码几乎一样,只不过第二个代码中自定义函数test()里面用static修饰局部变量,所以出现了这样的差异。
根据代码2的结果推测出每一次调用test(),使用的a都是上一次函数调用时留下的a;第二次调用test()时,由于上次函数调用产生的a没有被销毁,所以不会再次创建a,直接跳到了下一步,a++
【敲黑板】:
static修饰局部变量的时候,其实是改变了变量的存储类型,由栈区存储变成了静态区存储,从而使得静态的局部变量出了自己的作用域也不会被销毁,其实也就是相当于改变了这个变量的生命周期。
这里补充一条小知识点:
内存是一块比较大的空间,在使用内存的时候,会划分出不同的功能区域:栈区、堆区、静态区(这里为了好理解就直接这样说了,后面会详细介绍到这块内容)
2. static修饰全局变量
//代码1 //add.c文件 int g_val = 2018;//g_val是在add.c文件中定义的全局变量 //test.c文件 //如果想使用来自其他文件(外部文件)的全局变量,要先声明一下 extern int g_val; //extern是一个关键字,专门用来声明外部符号的 int main() { printf("%d\n", g_val); return 0; }
上面这个程序是正常编译的,不过下面的这个程序就不行了哦
/代码2 //add.c文件 static int g_val = 2018; //test.c文件 extern int g_val; int main() { printf("%d\n", g_val); return 0; }
第二个程序编译的时候会报错,因为出现连接性错误。
【解释】
一个全局变量在整个工程中的其他子文件内部能被使用,是因为全局变量具有外部链接属性 ,什么叫外部链接属性,一个变量在一个文件中定义,但是在另一个文件中可以使用(访问)叫外部链接属性。当一个全局变量被static修饰的时候。其外部链接属性就变成了内部连接属性;使得这个全局变量只能在自己的源文件内部使用,其他文件不能再使用,因为它不再具有外部链接属性,给我们的感觉是作用域变小了。另外,局部变量只有内部链接属性
3.static修饰函数
//代码1 //add.c文件 int Add(int x, int y) { return x + y; } //test.c文件 extern int Add(int x, int y); int main() { printf("%d\n", Add(2, 3)); return 0; }
上面的程序编译正常,但是下面的程序编译时会出现错误哦。
//代码2 //add.c文件 static int Add(int x, int y) { return x + y; } //test.c文件 extern int Add(int x, int y); int main() { printf("%d\n", Add(2, 3)); return 0; }
这个程序编译时出现连接性错误。
因为函数本身就具有外部链接属性,static修饰函数的时候,函数本来是具有外部链接属性的,但是被static修饰后,就变成了内部连接属性,导致这个函数只能在自己的源文件内部使用,给我们的感觉是改变了作用域。
三、深剖const关键字
1.const修饰变量
【注意】:const修饰的只读变量不可直接被修改
//const修饰的只读变量不可以直接被修改 const int a = 10; a = 20;//错误
问:const修饰的变量真的不能被修改吗?
其实const修饰的变量可以被间接修改掉
//const修饰的变量可以间接被修改 const int i = 10; int* p = &i; *p = 20; printf("%d\n", i);//打印20
这样的话就有一个问题:const修饰变量意义何在?
- 让编译器进行直接修改式检查;
- 告诉其他程序猿(正在修改你代码的或者阅读你代码的)这个变量后面不要改哦,也属于一种”自描述”含义
2.常变量可成为数组一部分吗?
const int n = 10; int arr[n] = {0};
注意,上面的代码在VS编译器(标准C)下直接报错了,但在gcc(GNU扩展)下可以正常编译,所以我们一切都要向标准看齐,这样的话,也就是不可以!上面的定义是错误的。
3.const修饰指针
面试题:const* 和 *const 什么区别?
const修饰指针的时候(有两种):
const放在*的左边(const int* p) ,修饰的是*p,使得*p不能改变(保证指针指向的内容不能通过指针修改),但是指针变量p本身可以改变;
const放在*的右边(int* const p),修饰的是p,使得p 不能改变。但是*p可以被修改
题目描述:
下列选项中哪一种形式声明了一个指向char类型变量的指针P,P的值是不可修改的,但P指向的变量的值是可以修改的?C
A:const char* p;//const修饰*p B:char const* p;//const修饰*p C:char* const p;//const修饰p D:const char* const p;//const既修饰*p,又修饰p
思路:其实一看到题目中说p的值是不可修改的我们就应该想到const放在*的右边。
四、手写求字符串长度函数(strlen)
库函数strlen()原型:
1.方法一:普通解法
//方法一:普通方法 int my_strlen(const char* str) { assert(str);//最好加上断言 int count = 0; while (*str++) { count; } return count; }
2.方法二:递归法
int my_strlen(const char* str) { //找重复:my_strlen(str+1)是原问题的重复 //找边界 if (*str == '\0') { return 0; } return my_strlen(str + 1) + 1; }
3.方法三:指针 - 指针
//方法三:指针 - 指针 int my_strlen(const char* str) { assert(str);//最好加上断言 char* end = str; while (end++) { ; } return end - str; }
五、手写字符串拷贝函数(strcpy)
//本题需要注意的有两点: //一是str1要足够大 //二是要保存str1的首地址 char* my_strcpy(char* str1, const char* str2) { assert(str1 && str2);//最好加上断言 char* ret = str1;//注意哦,记得保存str1的首地址 while (*str1++ = *str2++) { ; } return ret; }
【敲黑板】:本题需要注意的有两点:
- str1要足够大
- 保存str1的首地址
六、遇见安然遇见你,不负代码不负卿!
今天暂且到这里咯,后续会持续更新的哈,目标大厂,从最基本的知识点做起!
如果有所收获,求求来个三连吧~
博主昨天刚建立了一个社区,非常欢迎铁汁们的加入并同步自己的博文到社区里引流,后面我也会在社区里开展一些奖励活动的哟,快来join us!
【招贤纳士】:建立初期,希望大家各显神通,表现积极优秀的童鞋可以搭伙一同管理社区!
最近由于我忙着复习备战期末考试,所以有很多铁汁给我的私信都没有及时回复,实在是对不起,所以我建立了一个QQ群,里面有很多资料,包括C/C++研发方向、Java研发方向的书籍,还有许多算法书,欢迎大家加入,如果有问题可以发到群里面,我们一同探讨,总之一句话,拒绝躺平,冲刺大厂!
QQ群号:926297014