嵌入式面试题 - C语言总结(二)
嵌入式面试题-C语言总结(二)
16.break,continue 区别
- break:跳出当前循环
- continue:结束本次循环
17.🍁谈谈你对指针的理解?(什么是指针?指针的作用?指针的运算)
-
语法
-
指针是一种数据类型,是一个用于存储地址的变量
-
由于地址是固定长度,所以指针变量的长度是固定长度,64位计算机的指针长度占8字节,32位的占4字节
-
指针的作用域,局部指针变量存放在栈上,全局指针变量存放在数据区
-
指针在运算时,必循遵循相同类型的指针变量之间赋值,不同类型的指针变量不能赋值
-
-
作用
-
由于指针变量可以保存地址,所以可以直接操作地址
也就是说,可以直接操作硬件的寄存器地址,从而实现直接访问硬件
-
18.为什么会有不同类型的指针?
-
主要是为了确保指针能够正确地操作内存,并在访问数据时与数据类型保持一致性
(也可以说是为了实现类型安全、确保正确的内存访问,并支持指针运算时的数据大小和内存对齐的要求)
-
指针本身保存的是一个内存地址,而访问这个地址时需要知道数据的类型,这样才能正确地读取或修改内存中的数据
19.🍁什么是野指针?什么是悬空指针?如何避免野指针?
-
野指针:访问非法内存的指针,也就是一些老程序员们常说的指向不可用内存区域的指针
个人认为野指针一般分为两种:一种是没有初始化的指针,一种是指向已经被释放或销毁的内存区域的指针
- 解决方案:初始化指针变量时必须及时初始化(如果没有东西你就置空成为空指针)
- 但是需要注意的是野指针不是NULL指针,是指向非法内存的指针
- 解决方案:初始化指针变量时必须及时初始化(如果没有东西你就置空成为空指针)
-
悬空指针:悬空指针也是野指针的一种,指的是指向一个已经被释放或销毁的内存区域的指针
当内存被释放之后,指针仍然指向那块已经无效的内存区域,就形成了悬空指针
- 解决方案:指针释放之后及时置空,例如C++中,指针
free或者delete之后立即置空
- 解决方案:指针释放之后及时置空,例如C++中,指针
20.memcpy 和 strcpy 的区别?
void *memcpy(void *dest, const void *src, size_t n);
char *strcpy(char *dest, const char *src);
-
1.复制内容不同:
memcpy只能复制字符串
strcpy可以复制任何内容,包括但不限于数组,结构体等,但是不能直接用于文件复制,需结合文件I/O操作 -
2.复制方法不同:
memcpy根据它的第3个参数决定所复制的长度 ,所以比较安全
strcpy不需要指定长度,遇到\0就结束,但是空间不够就会导致内存溢出 -
3.用途不同:一般来说复制字符串使用strcpy,而复制其他数据类型使用memcpy,对于数据中包含
\0只能用memcpyps:执行效率,memcpy > strcpy
21.什么是数组?一维数组名的作用?一维数组取地址的意义?二维数组呢
-
数组:一组地址连续的相同数据类型的元素,即存储在内存中一块连续区域的集合,能够存储多个相同类型的数据元素
-
数组在内存中存是连续存在的
-
静态数组是静态分配空间,空间是物理连续的,但是空间利用率低
-
动态数组:运行期数组内存大小可以改变的数组,空间利用率高于静态数组
-
-
数组的访问是直接访问,所以访问效率高
-
数组不能作形参,会默认退化为对应类型的指针
-
- 一维数组名:指针常量(元素类型的指针),保存的是一维数组首元素的地址
- 二维数组名:指针常量(一维数组指针),保存的是首个一维数组的地址
人话:数组名就是数组首元素的地址(数组的起始位置),二维数组的首元素就是二维数组中第一个一维数组的第一个元素
数组名是一个常量指针,指向数组的第一个元素,数组名本身不能被修改
数组首元素的地址实际上是一个元素指针 -- int *p
-
元素指针:指向数组中单个元素的指针
人话:元素指针指就是指向数组中的某一个元素,别管这个元素是谁,反正指了
- 一维数组取地址:对一维数组的地址取值 = 数组首元素的地址
- 二维数组取地址:对二维数组的地址取值 = 二维数组中首个一维数组的地址
人话:数组取地址表示的是整个数组的地址,例如&a是一个指向整个数组的指针类型
22.常量指针与指针常量
指针常量 int* const p = &a; const修饰的是 p -> 不能修改指针p的朝向了
// p = &b; 这样写就会报错了
-------------------------------------------------------------------------------------
常量指针 const int *p = &a; const修饰的是 *p -> 不能修改值了
// *p = 1000; 这样写就会报错了
-
指针常量:常量指针 是一个 常量,这个常量的值是一个指针,因为指针常量的值是常量,所以不能被修改指针朝向(赋值)
- 指针常量 不能修改 指针朝向,但是可以修改 值
-
常量指针:指针常量 是一个 指针,指针指向的对象是一个常量,这个对象(常量)不能被修改
- 常量指针 不能修改 值,但是可以修改 指针朝向
-
人话:
-
指针常量
int* const p,最后两个字是常量,所以它就是个常量 -
const修饰的是指针(p),这个指针(朝向)就不能被修改了*p,解引用,就是取值&p,取地址
-
常量指针
const int *p,最后两个字是指针,所以它就是个指针 -
const修饰的是值(*p),这个值就不能被修改了
[!CAUTION]
这里提出一点额外的知识,面试的时候你也可以提出来,让面试官刮目相看(但是千万千万不要把自己绕晕了)
关于这一点我非常非常非常非常非常想要吐槽
为什么在网上搜到的关于常量指针和指针常量的说法有两种!?!?!?
甚至在《C++ Primer》中查阅到的资料以及GPT询问得到的结果都是令人疑惑的
指针常量和常量指针的概念和我平时学习的东西是反过来的???啊啊啊啊??????
在《C++ Primer》第五版中,2.4.2指针与const中有详细提到
int* const p这种被称为常量指针,const int *p被称为指向常量的指针
但是,你也不需要去过于纠结这件事情,我在已经在这件事情上花费了两三个小时如果只是纠结于命名,这将毫无意义,你平时学的是什么,那就是什么
你只需要告诉面试官,你平时学到的东西是什么样子的,然后你是怎么查到的
面试官可能会觉得你是一个注重细节,会思考,不会照本宣科的人
好险,差点又把自己绕进去了.JPG -
23.指针数组与数组指针
指针数组 int* a[10]
数组指针 int (*a)[10]
- 指针数组:本质是数组,数组元素全是指针,即:存储指针的数组
- 记得给你的指针数组初始化,不然他们会以野指针的身份在某个夜晚变成bug来找你
- 数组指针:本质是指针,指针指向的是数组,即:指向数组的指针
24.函数的作用是什么?实参与形参的区别是什么?
-
函数的作用,就是为了方便和简化吗,不方便我用它干什么 -
函数作用:提高代码质量,包括复用性、维护性、扩展性和可读性,同时,也方便了模块开发以及多人开发
-
实参(实际用的参数):函数调用时实际传递给函数的值或变量,例如:main函数中参数
-
形参(形式上的参数):函数定义中声明的变量,用于接收函数调用时传递的值,例如:函数中的参数
25.传值和传地址的区别
- 传值:传实参变量名(把实参的值赋给形参),不能修改实参的值
- 传值的优点:不需要使用地址运算符或指针,保证了函数内部无法修改原始数据
- 传值的缺点:对于大型数据结构(如数组或结构体),会复制大量数据,可能导致性能问题(内存占用和时间开销过大)
- 不能在函数中修改实参的值,适用于不需要修改原始数据的场景
- 传地址:传实参的地址,即能使用也能修改实参的值
- 传址的优点:遍历和访问大数据时,遍历效率高,访问速度更快,内存空间使用小
- 传址的缺点:必须记得使用地址运算符或指针,增加了代码复杂度
- 为避免函数修改数据,可以使用 const
- 可以在函数中修改实参的值,适用于需要在函数内修改原始数据的场景
26.传入参数与传出参数
- 传入参数(只读取):不修改实参(传入的参数)的值
- 传出参数(可修改):修改实参(传入的参数)的值
// 传入参数
int add(int a, int b);
// 传出参数
void test(char *dst, int *pp);
dst 是 传出参数
pp 是 传入参数 (const 使其不能修改)
void test(char *dst, const int *pp);
27.命令行参数(主函数参数)argc与argv
// 在终端输入: ./1 aaa bbb
// argc = 3
// argv[0] = ./1
// argv[1] = aaa
// argv[2] = bbb
int main(int argc, char *argv[])
{
}
- argc:表示命令行参数的个数,即 保存参数的个数
- argv:表示命令行参数的字段,即 这些参数都保存在 argv[ ] 这个指针数组里面
- 注意:argv保存的第一个参数是命令本身
- 假设我在终端上编译时输入:
./111 aaa bbb
那么保存的第一个参数就是./111,即./+ 对应的文件名
- 假设我在终端上编译时输入:
- 注意:argv保存的第一个参数是命令本身
28.return 0语句的作用,和exit(1)的区别是什么
-
retuen 0:表示结束当前程序,通常 0 表示正常退出- 如果没有
return 0,系统会从头去检查一遍再退出 return 0还具有刷新缓存区的作用:缓冲区中的内容(如printf输出)会被强制性刷新
- 如果没有
-
exit(1):表示结束整个程序,通常 1 表示异常退出- 注意的是,异常退出也会做一些清理工作,例如,关闭打开的文件流等
- 但是,并不会清理所有程序分配的资源,例如动态分配内存可能不会释放
29.函数指针变量的作用?(回调函数的作用?)
-
函数指针 是 指向函数的指针,保存的函数的地址
-
函数指针变量 是 一个变量,其类型是指向函数的指针,用来保存函数的入口地址(函数地址),可以指向多个不同的函数
- 函数地址通常指的是函数的入口地址,即:函数的地址就是程序中函数代码的起始位置,或者说函数的入口点
-
函数指针变量可以作为形参,指向的函数称之为回调函数(钩子函数)
-
函数指针变量的主要作用是让程序能够动态地选择并调用不同的函数
-
函数指针体现了面向对象编程的思维逻辑 — 多态
-
回调函数是一种通过函数指针传递的机制,它允许一个函数在执行过程中调用其他函数,通常是由外部定义的函数
-
回调函数 的作用是使得外部代码可以在特定时刻被调用,从而增加程序的灵活性和扩展性
-
当函数的功能可能发生变化时,可以使用回调函数,
隐藏函数的实现过程和调用形式,统一第三方调用接口,把不变的交给客户,变化的隐藏起来,
解耦实现者和调用者, 提高代码的维护性和扩展性 -
(建议结合第30点一起食用)
-
// 回调函数写法1: void (*p)() = A; B(p); ----------------------------- // 回调函数写法2: B(A);
-
ps:刚开始学习回调函数的时候,总是在想,啥是回调函数啊,直到后面学完C++之后,才对回调函数有一些清楚的认识
但是真正意义上的理解回调函数是看了印度顶尖程序员Harsha Suryanarayana的视频,
要是大学老师有这位程序员的教学水平,大学生还需要担心就业问题???
但是,我现在依然想吐槽:不是,谁教你那么翻译的啊,中国官方和教材术语在翻译这件事情上从来都没有不让大家失望过
从游戏到专业领域名称,有太多想吐槽的地方了,又想起了当年完坎公不知道哪个翻译居然能把人下巴都惊掉Σ(っ °Д °;)っ
谁告诉你"小而白,洁而美"可以翻译为闪闪发亮的啊喂
话题扯远了,继续回到回调函数上面,
回调函数,英文原文:comeback,打开有道云,翻译结果是复出,可以理解为复用
come,翻译:来;back,翻译:回来
然后翻译工作者就翻译为回调函数???
谁让你这么翻译的,什么中文式翻译,说好的不然我们使用中文式翻译结果你嘎嘎乱用|
学过C++的估计都会使用回调函数,没学过也不打紧,你在一个函数里面调用了另一个函数
好了,你已经会成功使用回调函数了,别过度纠结,就这么简单
30.🍁函数指针与指针函数
-
函数指针:指向函数的指针,保存的函数的地址,可以间接调用函数
-
指针函数:返回值是指针的函数,不能返回局部变量的地址
// 指针函数:返回值是指针的函数 int* func() { static int a = 0; return &a; } ------------------------------------------------------------ // 函数指针:指向的是一个函数 int main() { void(*p)() = func; p(1); }
函数指针 指针 指向一个函数 int (*fun)(int x, int y)
指针函数 函数 返回一个指针 int *fun(int x, int y)
-
函数指针:
int (*fun)(int x, int y)-
fun被括起来了,()的优先级大于*,所以*fun是一个整体
-
是指向int (int x, int y)函数的一个指针
-
-
指针函数:
int *fun(int x, int y)-
fun没有被括起来,而且fun后面的是函数参数
-
所以fun是一个函数,返回的是一个指针
-
参考:【C语言】 “函数指针”和“指针函数” 用法和详解 - CSDN博客
最近把我写的博客给我的朋友看了一下,被吐槽到:好多吐槽啊
我只想说:不会吐槽的程序员不是好程序员!!!
最后还是再叠个甲:博主只是一个即将成为社畜的苦逼大学生,水平有限,
以上问题全是根据自己现有的理解,结合以前的笔记,然后查询大量的资料
最后自己一个人整理,总结出来的,可能会有些许错误,如果存在一些问题,还望各位可以指出
最后再祝愿看的各位可以在来年找到好工作

浙公网安备 33010602011771号