嵌入式c语言基础面试问题(四)

目录

1.memmcpy 和 strcpy 的区别? 及使用注意(memmove)?

2.strtok、strcpsn 等特殊字符串处理函数的作用?

3. 一维数组名的作用?一维数组的地址取值的意义?

4.二维数组名的作用?二位数组地址取值的意义?

5.数组指针变量的作用?使用场景?

6.什么是动态数组?什么是零长数组?应用场景?

7.指针数组的使用注意事项?作为参数传递如何定义形参?

8.函数的作用?如何编写高质量的函数?

9.传值与传地址的区别(如何选择) 

10.主函数的形参分别代表的意义?及主函数的调用时机? 主函数被谁调用?那个函数调用主函数。

11.const作用

12.resrtict修饰形参作用?

13.函数返回值的注意事项,如何返回多个值?有几种方法?

14.return 0   VS    exit(1)

15.函数指针变量的作用(回调函数的作用)

16.复杂的声明的定义及初始化?(右左法则)

17. 数组做指针的注意事项

18.指针与数组的区别?


1.memmcpy 和 strcpy 的区别? 及使用注意(memmove)?

第一,他们的复制内容不同,strcpy 只能复制字符串,而 memcpy 可以复制任何内容。

第二,他们的复制方法不同,strcpy 不需要指定长度,遇到字符串结束符杠零结束,空间 不够就会产生内存溢出。而 memcpy 的第三个参数是用来限制复制的长度,更加安全。

第三,用途不同,一般来说复制字符串使用 strcpy,而复制其他数据类型使用 memcpy,对 于数据中包含杠零只能用 memcpy。 在使用 strcpy 时,要注意复制字符串的长度,避免内存溢出。 在使用 memcpy 时,要保证拷贝区域和目标区域没有重叠区部分。

在有重叠内存的情况 下,最好使用 memmove 能够保证字符串在覆盖之前将重叠区域的字节拷贝到目标区域 中。

2.strtok、strcpsn 等特殊字符串处理函数的作用?

strlen():返回字符串长度
strcat():链接两个字符串
strcpy():字符串拷贝
strcmp():比较字符串
strtok():分割字符串
strstr():在字符串中找到第一次出现指定字符串的位置,返回指定字符串+后面
strrchr():在字符串中找到最后一次出现指定字符的位置,返回指定字符串+后面
strcpsn():返回字符串中第一次出现指定字符串内容的位置

3. 一维数组名的作用?一维数组的地址取值的意义?

一维数组名相当于指针常量, 数组名不能做++,保存一维数组中首元素的地址。

&a:对一维数组的数组名取地址,表示一维数组的地址;

*(&a):对一维数组的地址取值等于数组首元素的地址

a[i]== *(a + i);&a[i] == a + i;

4.二维数组名的作用?二位数组地址取值的意义?

二维数组是一个一维数组指针常量,保存的时首个一维数组的地址。 对数组名取地址就是数组的地址,对二维数组的地址取值就是二维数组中首个一维数组的地址。

&aa:二维数组的地址
aa:首个一维数组的地址
*(&aa) = aa:对二维数组的地址取值等于二维数组首个一维数组的地址
*aa:二维数组的首个一维数组的首元素地址
注意定义二维数组时,可以省略行的大小,但不能省略列的大小

5.数组指针变量的作用?使用场景?

数组指针变量的语法作用是保存数组的地址,数组指针变量一般用于函数传参
void printf1(char *str);
void printf2(char (*ktr)[]);
void printf3(char (*ptr)[][100]);
void printf4(char **ctr);
char str[100] = {0};
char ktr[2][100] = {0};
char ptr[2][2][100] = {0};
char *ctr[3];

6.什么是动态数组?什么是零长数组?应用场景?

动态数组

定义:
动态数组不是一种数据类型,而是一种程序设计技术,它允许程序员在运行时创建可以改变大小的数组。使用动态分配内存的方法,可以根据实际需要增加或减少数组的存储容量。

实现方式:
在 C 语言中,可以使用 `malloc()`, `calloc()`, `realloc()` 等函数分配和调整动态数组的大小;在 C++ 中,则可以使用 `new` 和 `delete` 操作符,或者利用标准模板库中的 `std::vector` 类来管理动态数组。

**应用场景:**
- 数据量未知或数据量需要在运行时改变的情况。
- 实现自动扩展容量的数据结构,例如栈、队列、堆等。
- 处理大量数据,需要在使用前确定确切数量的情况。

零长数组

**定义:
零长数组是一个长度为0的数组。在某些编程语言规范中并不直接支持零长数组,因为数组至少需要有一个元素。但在 C 语言中,它被用作一种特殊的语言扩展,用于某些特殊的编程技巧。

**声明方式:
在 C 语言中,零长数组通常会在结构体声明的最末尾,例如:
struct header {
    size_t length;
    char data[0]; // 零长数组
};
这种结构体加零长数组的方式允许结构体表示可变长度的数据。

**应用场景:
- 结构体表示不定长的数据,零长数组通常作为占位符使用,实际上是紧跟结构体的一块内存空间。
- 避免指针的额外间接访问,可以利用零长数组直接访问结构体后面的数据,提高访问效率。
- 内存分配时,能够将结构体和数据块一次性分配,提高内存分配效率。

**注意点:
零长数组在 C99 之后被标准化为“柔性数组成员”(Flexible Array Member),规范要求这种数组必须是结构体的最后一个成员,且结构体中必须至少有一个其他成员。柔性数组成员的声明方式一般为 `type name[];`(不指定元素个数)。
struct header {
    size_t length;
    char data[]; // 柔性数组成员
};
柔性数组成员没有显式定义长度,可以在运行时通过动态内存分配来指定任意的长度。

在使用动态数组和零长数组时,需要注意内存的正确分配和释放,以及数组的界限检查,以避免内存泄漏和越界错误等问题。

7.指针数组的使用注意事项?作为参数传递如何定义形参?

指针数组本质是一个数组;只不过他里面存放的元素类型是指针。

使用时要注意指针数组里每个元素都是野指针,为了避免野指针导致内存泄漏,必须初始化。

指针数组传参是将数组名作为数组的首元素地址进行实参传递,在形参接实参的时候要注意指针数组的类型是二级指针,所以要用二级指针,或者直接使用指针数组类型的形参。

8.函数的作用?如何编写高质量的函数?

函数可以提高代码质量(提高复用性、维护性、扩展性、可读性);还可以进行模块开 发,多人合作。

编写高质量的函数的目标是高内聚低耦合,我们需要做到以下几点

第一,函数之间减少调用关系,少调用第三方函数

第二,函数内部需要做入口参数检查和返回值的异常处理,提高函数接口的安全性

第三,提高函数名的命名规范,函数的实现功能尽量是单一的,函数的入口参数要进行 const 属性判断,提高函数的可读性。

第四,所写的函数可以进行单独的测试。

第五,在写入或者打印函数的调用后,添加 fllush 函数,避免缓冲区的影响。

9.传值与传地址的区别(如何选择) 

传实参变量名,不能修改实参的值(只能访问变量对应内存空间保存的地址,可以修改变 量指向内存空间得值,但不能修改变量对应内存空间的值)

传实参变量的地址,即能使用也能修改实参的值(也就是既能修改变量对应空间的值,也 能修改变量指向内存空间的值)

传值的优点:不需要使用地址运算符或指针,保证函数不会修改原始数据

传值的缺点:必须足够大的空间,如果是大型结构,将花费大量的时间和内存空间。

传址的优点:遍历是大型数据,访问数据更快,内存空间跟小。

传址的缺点:必须记得使用地址运算符或指针,避免函数修改值,要加 const。

char *ptr = (char*)mallloc(1024);
strcpy(ptr,"hello world");
func(ptr);//hLllo world
func(&ptr);//Lllo world
void func(char *ptr)//void func(**ptr)
{
ptr++; *ptr = 'L';
}

10.主函数的形参分别代表的意义?及主函数的调用时机? 主函数被谁调用?那个函数调用主函数。

主函数有两个形参,第一个形参 argc 表示命令参数的个数,第二个形参 argv 是保存函数参 数的指针数组,指向字符串常量。

在C和C++中,主函数(main函数)可以具有参数,这些参数用以接收程序执行时从外部传入的命令行参数。主函数的两个标准形参分别是int argcchar *argv[],分别代表参数计(Argument Count)和参数向量(Argument Vector)它们的意义如下:

  1. int argc: 这是一个整数,代表了命令行参数的个数。它至少为1,因为第一个参数始终是程序本身的名称,位于argv[0]

  2. char *argv[]: 这是一个字符指针数组,每个元素都是一个指向C字符串(以null结尾的字符数组)的指针,这些字符串包括了所有的命令行参数。argv[0]是程序的名称,argv[1]是第一个参数,一直到argv[argc-1]是最后一个参数。数组的第argc个元素(argv[argc])是一个空指针(NULL),标识数组的结束。

有些情况下,还会看到第三个可选参数,是一个指向环境变量的指针数组。这第三个参数通常写为char *envp[]char **envenvp数组中每个指针都指向一个环境变量(通常是"key=value"的形式),最后一个指针是NULL

主函数运行前要进行预处理、编译、汇编、链接操作,链接结束后会将启动代码链接进去。主函数被启动代码调用,而启动代码是由编译器添加到程序中的,也是程序和操作系统的桥梁,事实上,int main()描述的是 main()是操作系统之间的接口。

事实证明 main 函数只是一个程序的入口,也相当于一个普通的函数,也能被自身调 用,也能被其他函数调用。这和一般的函数之间互相调用的概念是一样的。不过需要 注意的是,main 函数不管是自身的调用还是被其他函数调用,都要设置函数终止的条 件,这个递归函数有点相似,不然就会陷入死循环。

11.const作用

定义常量值

const int MAX_SIZE = 100;

以上代码定义了一个整型常量`MAX_SIZE`,并给它赋值为100。尝试修改这个常量的值将会导致编译时错误。

定义常量指针

const char *ptr = "Hello, World!";

以上代码定义了一个指向常量字符的指针`ptr`。这意味着通过`ptr`我们不能改变它指向的值("Hello, World!"字符串内容不能被修改),但是我们可以改变`ptr`指向的地址。

定义指向常量的指针

char const *ptr = "Hello, World!";

这与第2点效果相同,`const` 关键字将 `ptr` 的目标内容标记为常量。

定义常量指针(指针本身和指向的值都不可变)

const char *const ptr = "Hello, World!";

以上代码定义了一个指向常量字符的常量指针`ptr`。这表示`ptr`本身和它指向的地址的值都不能改变。无论是尝试改变`ptr`指向的内容还是指针`ptr`的值都会导致编译错误。

作为函数参数,以防止修改传入的值

void func(const int x) {
    // x = 5; // 错误:不能修改 x
}

对函数参数使用`const`可以防止函数内部代码修改参数的值,这有助于防止程序中的bug和确保数据的完整性。

C++中的const成员函数

class MyClass {
public:
    int getValue() const {
        // 成员函数中不能修改任何成员变量
        return m_value;
    }
private:
    int m_value;
};

在C++中,如果成员函数后面添加了const关键字,那么该成员函数内部就不能修改类的任何成员变量。有助于提供对象的只读接口,增强代码的健壮性。

const关键字是一个强大的工具,它可以帮助我们编写更加清晰和健壮的代码。通过预防意外的修改,它也能提高代码的安全性,特别是在大型和复杂的项目中。在编程实践中,尽可能地使用const是一个很好的习惯。

12.resrtict修饰形参作用?

restrict 是 c99 新增的关键字,restrict 指针是限定指针指向不同内存空间的,是一个限定符,是编译器用来优化的。使用者可以通过restrict限定符提示调用者某些指针是指向不同空间的,相同空间会被优化成不同空间。使用时要注意编译器并不能完全优化不同的空间,谨慎使用 restrict。

13.函数返回值的注意事项,如何返回多个值?有几种方法?

有 3 种方法,一般情况下会通过函数的多次调用(static)多次返回值;还可以通过函数的传出参数;使用结构体也可以返回多个值。

使用结构体

#include <stdio.h>

// 定义一个结构体来保存多个返回值
typedef struct {
    int min;
    int max;
} MinMax;

// 函数返回MinMax类型的结构体
MinMax get_min_max(int *array, int size) {
    MinMax result;
    result.min = array[0];
    result.max = array[0];
    
    for (int i = 1; i < size; i++) {
        if (array[i] < result.min) result.min = array[i];
        if (array[i] > result.max) result.max = array[i];
    }
    
    return result; // 返回结构体
}

int main() {
    int numbers[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    MinMax min_max = get_min_max(numbers, size);
    printf("Min: %d, Max: %d\n", min_max.min, min_max.max);
    return 0;
}

 通过函数的指针参数:

#include <stdio.h>

// 函数通过指针参数返回最大和最小值
void get_min_max(int *array, int size, int *min, int *max) {
    *min = *max = array[0];
    for (int i = 1; i < size; i++) {
        if (array[i] < *min) *min = array[i];
        if (array[i] > *max) *max = array[i];
    }
}

int main() {
    int numbers[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    int min, max;
    get_min_max(numbers, size, &min, &max);
    printf("Min: %d, Max: %d\n", min, max);
    return 0;
}

多次调用static

#include <stdio.h>

// 这个函数每次被调用时都会返回数组中的下一个元素
int getNextValue(void) {
    static int values[] = {3, 1, 4, 1, 5};
    static size_t index = 0;
    const size_t numValues = sizeof(values) / sizeof(values[0]);

    if (index < numValues) {
        return values[index++];
    } else {
        // 所有的值都已经返回完毕,可以重置index或做其他处理
        // 例如,可以返回一个特殊值表示数组的结束,或者重置index
        return -1; // 假设-1不是有效值且不包含在values数组中
    }
}

int main() {
    int value;
    // 不断调用getNextValue并打印结果,直到函数返回-1
    while ((value = getNextValue()) != -1) {
        printf("%d ", value);
    }
    printf("\n");
    return 0;
}

上面的代码通过在函数内部使用静态局部数组values和静态局部索引index来实现了getNextValue函数的多次调用。每次调用时,它都会返回数组中的下一个值,直到返回了所有的值。

然而,需要特别小心的是,静态变量的状态是在函数调用之间共享的,这会导致一些问题,比如:

  • 并发环境下的线程安全问题。
  • 静态局部变量将保持其值直到程序结束,不能响应不同的函数调用产生不同的结果序列。
  • 函数失去了无状态的性质,可能会产生意料之外的副作用。

通常不建议使用这种方式返回多个值,除非在特定上下文中有明确的需求,并且能够充分理解并管理上述提到的限制和问题。在C语言中使用struct或者指针参数通常是更安全、更清晰的多值返回方法。

14.return 0   VS    exit(1)

return 0 表示结束当前程序,0 表示正常退出,如果没有 return 0 ,系统会从头去检查一遍 在退出。return 0 还具有刷新缓存区的作用。

exit(1)表示结束整个程序,1 表示异常退出,注意的是,异常退出不去释放缓存区,不 回收资源,不释放空间(推荐 goto 代替)

15.函数指针变量的作用(回调函数的作用)

函数指针变量的作用:

间接调用: 通过函数指针,你可以间接调用函数,这可以使代码更加模块化和灵活。

传递函数作为参数: 函数指针可以作为参数传递给其他函数。这样,你可以根据需要将不同的功能插入到工作流程中。

实现表驱动代码: 函数指针数组可以用来实现表驱动的方法,从而根据索引或条件动态调用不同的函数。

实现回调机制: 回调函数是指由库或框架在某个事件或条件发生时调用的函数。你将一个函数的指针提供给库或框架,当特定事件发生时它可以回调(调用)这个函数。

回调函数的作用:

自定义操作: 你可以创建一个通用的库或框架,用户可以通过提供自定义的回调函数实现特定的功能。

异步处理: 在异步操作完成时执行的的函数通常作为回调函数提供,例如I/O操作、网络请求等。

事件驱动编程: 在图形用户界面(GUI)编程或其他事件驱动的编程范式中,当用户执行某个操作(例如单击按钮)时,回调函数负责响应该事件。

避免阻塞: 有时候,调用某函数会导致程序阻塞。在这种情况下,提供一个回调函数,让库或框架在后台处理任务并在完成时调用,这样可以避免程序的前端阻塞。

增强代码可重用性: 回调函数使得库或框架可以用于多种场景,只需要用户变更回调函数就能构建出不同的行为。

16.复杂的声明的定义及初始化?(右左法则)

复杂的声明和定义要遵循右左法则:
第一步:先找到变量名
第二步向右看:
        分号(变量);方括号(数组);(圆括号(函数),看到圆括号)--- 该往左看
第三步向左看:
        如果是一个变量:向左看到什么类型,说明该变量保存的是什么类型,看到* 说明指针变量/ /
        确定是一个变量,就要确定该变量的类型
        如果是一个数组:向左看,看到什么类型说明就是什么类型数组(元素类型)。
        如果是一个函数:看()里的参数类型,再向右看,看返回值的类型
确定是一个指针变量,就要确定该变量指向什么类型,指向()说明保存函数类型,指向[ ] 说明保存数组地址。
确定是一个数组,就要确定数组里元素的类型
确定是一个函数,就要确定函数的形参和返回值类型。

17. 数组做指针的注意事项

对于一维数组来说,数组作为函数参数传递,实际上传递了一个指向数组的指针,当数组名作为函数参数时,在函数体内数组名自动退化为指针。此时调用函数时,相当于传址,而不是传值,会改变数组元素的值。 对于高维数组来说,可以用二维数组名作为实参或者形参,在被调用函数中对形参数组定 义时可以指定所有维数的大小,也可以省略第一维的大小说明,但不能省略第二维的大小说明。

18.指针与数组的区别?

第一,在空间分配上,数组是静态分配空间,空间是物理连续的,且空间利用率低。指针 变量是需要动态分配内存空间,内存空间在堆上分配与释放,程序员管理。

第二,数组名是指针常量,一维数组名是首个元素的地址,二维数组名是首个一维数组的 地址。指针是一个变量,可以指向任意一块内存,使用时要避免野指针的产生造成内存泄 漏。

第三,数组的访问效率高,数组的访问方式等于指针的访问方式取*   //    [ ] = *()

第四,数组使用容易造成数组越界,指针的使用容易产生野指针造成内存泄漏。

第五,函数传参时,使用万能指针可以提高代码的通配性。数组作为形参传递时会默认退 化成相应的指针。

第六,数组只提供了一种简单的访问机制,指针可以对地址直接操作来访问硬件的。

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值