【秋招】嵌入式面试八股文-C语言 关键字/变量
本文为C语言部分,具体整篇目录可以看前言!
面试不是死记硬背,你死记硬背,技术面试官能一下就瞧出来,所以要从本质上去理解一些内容所以,八股有些地方我写的很详细,不要嫌我啰嗦,因为这些地方的八股必须要真的去理解,而不单单是背诵,相信大家看完后,就能理解相应的知识点了,而不仅仅是死记硬背
1 volatile关键字
- volatile的意思是”易变的”,这个关键字主要是防止编译器对变量进行优化即告诉编译器每次存取该变量的时候都要从内存去存取而不是使用它之前在寄存器中的备份详细分析一下什么是编译器优化,以及为什么使用这个关键字
1.1 关于编译器优化
- 首先理解CPU(寄存器)读取规则:
int a, b; // 为a,b申请内存 a = 1; // 1 -> CPU // CPU -> 内存(&a) b = a; //内存(&a)-> CPU // CPU -> 内存(&b)
- 如上图代码所示,a = 1这个程序,先将1写入CPU,再从CPU中将1写入a所在的内存地址中; b = a是先从内存中将a的值取出到CPU,再从CPU将值存入b的内存地址中
int a, b, c; // 为a,b,c申请内存并初始化 b = a; // 内存(&a) -> CPU // CPU -> 内存(&b) c = a; // * 内存(&a)-> CPU * // CPU -> 内存(&b)
- 如上图代码所示,上边的程序如果按第一段代码所说的顺序执行,则c = a语句在编译时是可以被编译器优化的,即注释部分(* 内存(&a) -> CPU *)的内容不被执行,因为在b = a这个语句中,a已经被移入过寄存器(CPU),那么在执行c = a时,就直接将a在寄存器(CPU)中传递给c这样就减少了一次指令的执行,就完成了优化
- 上面就是编译器优化的原理过程,但是这个过程,有时会出现问题,而这个问题也就volatile存在的意义!
1.2 volatile的引入
- 上边程序中,如果在执行完b =a后,a此时的值存放在CPU中但是a在内存中又发生了变化(比如中断改变了a的值),但是存在CPU中的a是原来未变的a,按理应该是已经变化后的a赋值给c,但是此时却导致未变化的a赋值给了c
- 这种问题,就是编译器自身优化而导致的为了防止编译器优化变量a,引入了volatile关键字,使用该关键字后,程序在执行时c = a时,就会先去a的地址读出a到CPU,再从CPU将a的值赋予给c这样就防止了被优化
volatile int a = 1, b, c; // 为a,b,c申请内存并初始化 b = a; // 内存(&a) -> CPU // CPU -> 内存(&b) c = a; // 内存(&a)-> CPU // CPU -> 内存(&c)
1.3 哪些情况下使用volatile
- 并行设备的硬件寄存器存储器映射的硬件寄存器通常加volatile,因为寄存器随时可以被外设硬件修改当声明指向设备寄存器的指针时一定要用volatile,它会告诉编译器不要对存储在这个地址的数据进行假设
- 一个中断服务程序中修改的供其他程序检测的变量volatile提醒编译器,它后面所定义的变量随时都有可能改变因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象
- 多线程应用中被几个任务共享的变量
2 static关键字
面试问题1:static关键词的作用?
- static是被声明为静态类型的变量,存储在静态区(全局区)中,其生命周期为整个程序,如果是静态局部变量,其作用域为一对{}内,如果是静态全局变量,其作用域为当前文件静态变量如果没有被初始化,则自动初始化为0
面试问题2:为什么 static变量只初始化一次?
- 对于所有的对象(不仅仅是静态对象),初始化都只有一次,而由于静态变量具有“记忆”功能,初始化后,一直都没有被销毁,都会保存在内存区域中,所以不会再次初始化存放在静态区的变量的生命周期一般比较长,它与整个程序“同生死、共存亡”,所以它只需初始化一次而auto变量,即自动变量,由于它存放在栈区,一旦函数调用结束,就会立刻被销毁
- static修饰的全局变量,只能在本文件被调用;修饰的函数也只能在本文件调用
3 const关键字
3.1 定义变量
定义(局部变量或全局变量)为常量,例如:
3.2 修饰指针
第一种和第二种是常量指针;第三种是指针常量;第四种是指向常量的常指针
//第一种 const int *p1; //p本身不是const的,而p指向的变量是const //第二种 int const *p2; //p本身不是const的,而p指向的变量是const //第三种 int* const p3; //p本身是const的,而p指向的变量不是const //第四种 const int* const p4; //p本身是const的,而p指向的变量也是cosnt
3.3 面试问题1:什么是常量指针?
- 常量指针说的是不能通过这个指针改变变量的值,但是还是可以通过其他的方式来改变变量的值的
- 常量指针指向的值不能改变,但是这并不是意味着指针本身不能改变,常量指针可以指向其他的地址
- (图片点开观看会很清晰)
- 上图中,p1是定义的常量指针,p1指向a的地址,*p1 = 15是不行的,因为不能通过常量指针去改变变量的值,如果去掉const则是可以的没有const时,利用*p1可以去对a的值进行修改,如下图所示(图片点开观看会很清晰)
3.4 面试问题2:什么是指针常量?
- 指针常量是指指针本身是个常量,不能在指向其他的地址,需要注意的是,指针常量指向的地址不能改变,但是地址中保存的数值是可以改变的,可以通过其他指向该地址的指针来修改
3.5 面试问题3:什么是指向常量的常指针?
- 是指针常量与常量指针的结合,指针指向的位置不能改变并且也不能通过这个指针改变变量的值,但是依然可以通过其他的普通指针改变变量的值
3.6 修饰函数的参数
- 表示在函数体内不能修改这个参数的值
3.7 修饰函数的返回值
- 如果给用const修饰返回值的类型为指针,那么函数返回值(即指针)的内容是不能被修改的,而且这个返回值只能赋给被const修饰的指针例如(图片点开观看会很清晰):
- 如果用const修饰普通的返回值,如返回int变量,由于这个返回值是一个临时变量,在函数调用结束后这个临时变量的生命周期也就结束了,因此把这些返回值修饰为const是没有意义的
3.8 typedef和define有什么区别?
typedef与define都是替一个对象取一个别名,以此来增强程序的可读性,但是它们在使用和作用上也存在着以下4个方面的不同
(1)原理不同
- #define是C语言中定义的语法,它是预处理指令,在预处理时进行简单而机械的字符串替换,不做正确性检査,不管含义是否正确照样代入,只有在编译已被展开的源程序时,才会发现可能的错误并报错。
- 例如,#define Pl 3.1415926 ,当程序执行 area=Pr * r 语句时,PI会被替换为3.1415926于是该语句被替换为 area=3.1415926*r*r 如果把#define语句中的数字9写成了g,预处理也照样代入,而不去检查其是否合理、合法。
- typedef是关键字,它在编译时处理,所以 typedef具有类型检查的功能它在自己的作用域内给一个已经存在的类型一个别名,但是不能在一个函数定义里面使用标识符 typedef
- 例如,typedef int INTEGER ,这以后就可用 INTEGER来代替int作整型变量的类型说明了,例如:INTEGER a,b;
- 用 typedef定义数组、指针、结构等类型将带来很大的方便,不仅使程序书写简单而且使意义更为明确,因而增强了可读性例如: typedef int a[10]; 表示a是整型数组类型,数组长度为10然后就可用a说明变量,例如:语句a s1,s2;完全等效于语句 int s1[10],s2[10].同理, typedef void(*p)(void)表示p是一种指向void型的指针类型
(2)功能不同
- typedef用来定义类型的别名,这些类型不仅包含内部类型(int、char等),还包括自定义类型(如 struct),可以起到使类型易于记忆的功能
- 例如: typedef int (*PF)(const char *, const char*) 定义一个指向函数的指针的数据类型PF,其中函数返回值为 int,参数为 const char*typedef还有另外一个重要的用途,那就是定义机器无关的类型。例如,可以定义一个叫REAL的浮点类型,在目标机器上它可以获得最高的精度:typedef long double REAL ,在不支持 long double的机器上,该 typedef 看起来会是下面这样: typedef double real ,在 double都不支持的机器上,该 typedef看起来会是这样: typedef float REAL #define不只是可以为类型取别名,还可以定义常量、变量、编译开关等
(3)作用域不同
- #define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用,而 typedef有自己的作用域(图片点开观看会很清晰)
(4)对指针的操作不同
- (图片点开观看会很清晰)
- INTPTR1 pl, p2和INTPTR2 p3,p4的效果截然不同 INTPTR1 pl, p2进行字符串替换后变成 int*p1,p2 ,要表达的意义是声明一个指针变量p1和一个整型变量p2,而INTPTR2 p3,p4,由于 INTPTR2是具有含义的,告诉我们是一个指向整型数据的指针,那么p3和p4都为指针变量,这句相当于 int*pl,*p2 .从这里可以看出,进行宏替换是不含任何意义的替换,仅仅为字符串替换;而用 typedef 为一种数据类型起的别名是带有一定含义的。(图片点开观看会很清晰)
- 上述代码中, const INTPTR1 p1表示p1是一个常量指针,即不可以通过p1去修改p1指向的内容,但是 p1可以指向其他内容而对于 const INTPTR2 p2,由于 INTPTR2表示的是个指针类型,因此用 const去 限定,表示封锁了这个指针类型,因此p2是一个指针常量,不可使p2再指向其他内容,但可以通过p2修 改其当前指向的内容 INTPTR2 const p3同样声明的是一个指针常量
4 变量
4.1 定义常量谁更好?# define还是 const?
- define既可以替代常数值,又可以替代表达式,甚至是代码段,但是容易出错,而 const的引入可以增强程序的可读性,它使程序的维护与调试变得更加方便具体而言,它们的差异主要表现在以下3个方面:
- define只是用来进行单纯的文本替换,define常量的生命周期止于编译期,不分配内存空间,它存在于程序的代码段,在实际程序中,它只是一个常数;而const常量存在于程序的数据段,并在堆栈中分配了空间,const常量在程序中确确实实存在,并且可以被调用、传递
- const常量有数据类型,而define常量没有数据类型编译器可以对const常量进行类型安全检査,如类型、语句结构等,而define不行
- 很多IDE支持调试 const定义的常量,而不支持 define定义的常量由于const修饰的变量可以排除 程序之间的不安全性因素,保护程序中的常量不被修改,而且对数据类型也会进行相应的检查,极大地提高了程序的健壮性,所以一般更加倾向于用const来定义常量类型
4.2 全局变量和局部变量的区别是什么?
- 全局变量的作用域为程序块,而局部变量的作用域为当前函数
- 内存存储方式不同,全局变量(静态全局变量,静态局部变量)分配在全局数据区(静态存储空间),后者分配在栈区
- 生命周期不同全局变量随主程序创建而创建,随主程序销毁而销毁,局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在了
- 使用方式不同通过声明为全局变量,程序的各个部分都可以用到,而局部变量只能在局部使用
4.3 全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?
- 可以,在不同的C文件中以extern形式来声明同名全局变量
- 可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错
4.4 局部变量能否和全局变量重名?
- 能,局部会屏蔽全局
- 局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量,对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内
5 C语言常用字符串
5.1 strlen
:获取字符串的长度
该函数返回字符串的长度(不包括终止的 \0
字符)。
#include <stdio.h> size_t my_strlen(const char *str) { size_t length = 0; while (str[length] != '\0') { length++; } return length; } int main() { const char *str = "Hello, World!"; printf("Length: %zu\n", my_strlen(str)); return 0; }
5.2 strcmp
:比较两个字符串
该函数用于逐个比较两个字符串,直到发现不相等的字符或到达字符串的结尾。它返回:
0
:如果两个字符串相等。负值
:如果第一个字符串小于第二个字符串(字典顺序)。正值
:如果第一个字符串大于第二个字符串。
#include <stdio.h> int my_strcmp(const char *str1, const char *str2) { while (*str1 && (*str1 == *str2)) { str1++; str2++; } return *(unsigned char *)str1 - *(unsigned char *)str2; } int main() { const char *str1 = "apple"; const char *str2 = "banana"; int result = my_strcmp(str1, str2); if (result == 0) { printf("Strings are equal.\n"); } else if (result < 0) { printf("str1 is less than str2.\n"); } else { printf("str1 is greater than str2.\n"); } return 0; }
5.3 strcpy
:复制字符串
该函数用于将源字符串的内容复制到目标字符串中。
#include <stdio.h> char *my_strcpy(char *dest, const char *src) { char *original_dest = dest; while (*src) { *dest = *src; dest++; src++; } *dest = '\0'; // 确保目标字符串以 \0 结尾 return original_dest; } int main() { char dest[20]; const char *src = "Hello, World!"; my_strcpy(dest, src); printf("Copied string: %s\n", dest); return 0; }
5.4 strcat
:连接两个字符串
该函数将源字符串的内容连接到目标字符串的末尾。
#include <stdio.h> char *my_strcat(char *dest, const char *src) { char *original_dest = dest; // 移动到目标字符串的末尾 while (*dest) { dest++; } // 复制源字符串到目标字符串末尾 while (*src) { *dest = *src; dest++; src++; } *dest = '\0'; // 确保目标字符串以 \0 结尾 return original_dest; } int main() { char dest[40] = "Hello, "; const char *src = "World!"; my_strcat(dest, src); printf("Concatenated string: %s\n", dest); return 0; }
5.5 strchr
:查找字符在字符串中的第一次出现
该函数返回指向字符第一次出现位置的指针,或者如果字符不在字符串中,则返回 NULL
。
#include <stdio.h> char *my_strchr(const char *str, int ch) { while (*str) { if (*str == ch) { return (char *)str; } str++; } return NULL; // 如果没有找到 } int main() { const char *str = "Hello, World!"; char *result = my_strchr(str, 'o'); if (result) { printf("Found 'o' at position: %ld\n", result - str); } else { printf("'o' not found.\n"); } return 0; }
5.6 strstr
:查找子字符串
该函数返回指向第一次出现子字符串的位置的指针。如果找不到子字符串,返回 NULL
。
#include <stdio.h> char *my_strstr(const char *haystack, const char *needle) { if (!*needle) return (char *)haystack; while (*haystack) { const char *h = haystack; const char *n = needle; while (*h && *n && *h == *n) { h++; n++; } if (!*n) return (char *)haystack; haystack++; } return NULL; } int main() { const char *haystack = "Hello, World!"; const char *needle = "World"; char *result = my_strstr(haystack, needle); if (result) { printf("Found substring at: %s\n", result); } else { printf("Substring not found.\n"); } return 0; }
5.7 strtok
:分割字符串
该函数用于将字符串分割成一系列的标记(token),通常用于按指定的分隔符分割字符串。
#include <stdio.h> #include <string.h> int main() { char str[] = "Hello, World! Welcome to C!"; char *token = strtok(str, " ,!"); // 使用空格、逗号、感叹号作为分隔符 while (token) { printf("Token: %s\n", token); token = strtok(NULL, " ,!"); } return 0; }#晒一晒我的offer##嵌入式笔面经分享##嵌入式##25秋招##嵌入式软件开发面经#
双非本,211硕。本硕均为机械工程,自学嵌入式,在校招过程中拿到小米、格力、美的、比亚迪、海信、海康、大华、江波龙等offer。八股文本质是需要大家理解,因此里面的内容一定要详细、深刻!这个专栏是我个人的学习笔记总结,是对很多面试问题进行的知识点分析,专栏保证高质量,让大家可以高效率理解与吸收里面的知识点!掌握这里面的知识,面试绝对无障碍!