【面试笔记】嵌入式软件工程师,汽车电子软件相关

在这里插入图片描述


1. C语言基础

1.1 const

修饰变量
只可访问,不可重新赋值。

const int MAX_VALUE = 100;

void printValue(const int value);

修饰指针

  • 限制指向位置
const int *ptr;
  • 限制指向数据
const int *const ptr;

1.2 static

静态变量
使用 static 关键字声明静态变量时,变量的生命周期会延长到整个程序运行期间,而不仅仅局限在其定义的作用域内。静态变量在第一次被赋值时初始化,并且保留其值直到程序结束。

静态全局变量
使用 static 关键字在全局作用域中声明的变量具有静态存储持续时间,但是其作用域被限制在声明该变量的源文件内。这使得该变量对其他源文件不可见,可以防止命名冲突。

静态函数
使用 static 关键字声明静态函数时,该函数仅在声明所在的源文件中可见,即它具有内部链接性。静态函数的作用域仅限于声明所在的源文件。这种方式可以避免与其他源文件中的同名函数产生冲突。


1.3 回调函数的用法

用于在函数执行过程中调用另一个函数。回调函数允许我们向一个函数传递另一个函数的地址,从而在需要时执行特定的操作。回调函数通常用于事件处理、异步编程、库函数的扩展等场景。

  1. 定义回调函数
    首先定义一个函数作为回调函数,其函数原型应与回调的要求相匹配。例如:
void callbackFunction(int result) {
   
    printf("Callback result: %d\n", result);
}
  1. 在函数中注册回调函数
    在需要的地方将回调函数注册进目标函数中,通常通过函数指针实现。例如:
void performOperation(void (*callback)(int)) {
   
    int result = 100; // 模拟操作结果

    // 执行操作...

    // 调用回调函数
    callback(result);
}
  1. 调用包含回调函数的函数
    最后调用包含回调函数的函数,将回调函数的地址传递给要调用的函数。例如:
int main() {
   
    performOperation(callbackFunction); // 注册回调函数
    return 0;
}

在这个示例中,performOperation 函数执行某个操作后调用了注册的回调函数 callbackFunction,并将结果传递给回调函数进行处理。

通过回调函数,我们可以实现灵活的程序设计,允许函数根据不同情况来调用不同的操作,增加了程序的可扩展性和可重用性。当需要在函数执行过程中动态切换功能时,回调函数是一个非常有用的工具。

使用回调函数有以下一些好处:

  1. 灵活性和可扩展性
    回调函数提供了一种灵活的机制,使得代码可以在不同的场景中进行定制和扩展。通过将特定的功能封装在回调函数中,可以根据需要动态地更改或添加行为,而无需修改主函数的逻辑。
  2. 解耦和模块化
    回调函数有助于将不同的功能模块分离,使代码更具有模块化和可维护性。主函数可以专注于其核心逻辑,而将特定的任务委托给回调函数来处理。这样可以提高代码的可读性和可重用性。
  3. 异步处理和事件驱动
    回调函数常用于异步操作或事件驱动的场景中。例如,在异步 I/O 操作完成或特定事件发生时,可以通过回调函数来处理相应的逻辑。这有助于提高程序的并发性和响应性。
  4. 定制性和扩展性
    回调函数允许用户提供自己的自定义逻辑,以满足特定的需求。这使得程序可以更好地适应各种不同的用例和业务逻辑。
  5. 代码复用
    回调函数可以作为可复用的模块,在多个地方被调用,从而减少代码冗余。

需要注意的是,在使用回调函数时,要确保正确处理回调函数的参数和返回值,并注意线程安全等问题。合理使用回调函数可以提高代码的灵活性和扩展性,但也需要谨慎设计和管理,以避免引入复杂度过高或难以调试的问题。


1.4 宏定义

在 C 语言中,宏定义是一种预处理器指令,用于在编译阶段进行文本替换。它允许你定义一个标识符(通常是一个宏名),并将其与一个特定的文本表达式或代码块关联起来。当在代码中使用该宏名时,编译器会将其替换为相应的文本。
宏定义的常见用法和好处包括:

  1. 常量定义
    使用宏定义可以创建常量,例如定义一些具有特定值的常量,以增强代码的可读性和可维护性。
  2. 代码简化和抽象
    宏定义可以用于简化复杂的表达式或代码块,使其更易于阅读和理解。例如,将常用的计算或操作封装在宏中,以便在多个地方重复使用。
  3. 条件编译
    通过宏定义可以实现条件编译,根据不同的条件编译不同的代码块。这对于处理不同平台、版本或配置的情况非常有用。
  4. 代码移植性
    宏定义可以帮助提高代码的可移植性。例如,可以使用宏来定义平台特定的代码或处理不同编译器的差异。
  5. 提高性能
    在一些情况下,宏定义可以提供一定的性能优势,特别是对于一些简单的计算或操作。
    例如,以下是一个简单的宏定义示例:
#define MAX_SIZE 100

在上面的示例中,MAX_SIZE 是一个宏名,100 是它关联的文本。在代码中使用 MAX_SIZE 时,它将被替换为 100。
需要注意的是,宏定义也有一些潜在的问题和限制:

  1. 宏展开问题
    宏在编译时会进行文本替换,可能会导致一些意外的副作用,例如嵌套宏展开、参数求值顺序等问题。
  2. 缺乏类型检查
    宏不进行类型检查,可能会导致在使用时出现类型不匹配或其他错误。
  3. 可读性问题
    过度使用宏可能会使代码变得难以理解,特别是当宏的定义和使用变得复杂时。

因此,在使用宏定义时,应该谨慎考虑,并确保其使用不会导致代码的可读性和可维护性下降。在一些情况下,使用函数或其他语言特性可能是更好的选择。


1.5 编译、链接过程

在这里插入图片描述
预处理
根据以字符#开头的命令修饰的main.c的C源文件,生成预处理后的C源文件 main.i。
该过程主要进行文本替换、宏展开、删除注释等工作。
对应的gcc命令:

gcc -E main.c main.i

编译
编译器将文本文件main.i翻译(编译)成汇编文件main.s
对应的gcc命令:

gcc -S main.i mian.s

汇编
编译器将main.s翻译成机器语言指令,并把这些指令打包成一种可重定位目标程序的格式,并将结果保存在目标文件main.o中

把一个源文件翻译成目标程序的工作过程分为五个阶段:词法分析、语法分析、语义检查和中间代码生成。主要是进行词法分析和语法分析,又称为源程序分析,分析过程中发现语法错误并给出提示信息。
对应的gcc命令:

gcc -c main.s mian.o

链接
该过程编译器将静态库和动态库的库函数链接到可执行程序中。
静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时就不在需要库文件了,其后缀一般为.a。
动态库则是在程序运行时被链接加载,这样可以节省系统的开销,其后缀一般为.so,gcc在编译时默认使用动态库。


1.6 堆与栈的区别?

请添加图片描述

  1. 栈空间是系统自动分配和回收,堆的空间是用户手动分配回收的;
  2. 栈空间较小,堆空间较大;
  3. 栈的地址空间向下生长,堆则向上生长;
  4. 栈的存储效率更高。
    参考:栈和堆,以STM32为例说明

1.7 简单的字符串算法题,C语言实现

1.7.1 给定一个字符串,按顺序筛选出不重复的字符组成字符串,输出该字符串

参考示例:

#include <stdio.h>
#include <string.h>

void removeDuplicates(char *str) {
   
    int len = strlen(str);
    if (len < 2) return;

    int tail = 1;
    for (int i = 1; i < len; ++i) {
   
        int j;
        for (j = 0; j < tail; ++j) {
   
            if (str[i] == str[j]) break;
        }
        if (j == tail) {
   
            str[tail] = str[i];
            ++tail;
        }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智驾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值