首页 > 其他分享 >嵌入式秋招面试中,一定要掌握的嵌入式c基础——关键字详解(1)

嵌入式秋招面试中,一定要掌握的嵌入式c基础——关键字详解(1)

时间:2024-06-22 19:02:41浏览次数:12  
标签:const 变量 int 嵌入式 详解 static 秋招 main 常量

哈喽,大家好,这里是自律鸽。正如标题所强调的,在嵌入式秋招面试中,被问到c语言相关知识的概率几乎达到了百分之百。我本人也在去年的秋招中深切体验了这一点。因此,我想与大家分享一些面试中常问的嵌入式c基础,这些也都是我在求职过程中积累的宝贵经验。


关键字

1、sizeof:经常被问到与strlen()的区别,虽然它们都与计算内存大小有关,但是它们的作用是不同的。两者的区别在于:
●  sizeof 是一个运算符,而strlen()是一个函数。
●  sizeof计算的是变量或类型所占用的内存字节数,而strlen()计算的是字符串中字符的个数。
●  sizeof的语法sizeof(data type),即sizeof可用于计算任何类型的数据;而strlen()的语法strlen(const char* str),即只能用于计算字符串。
●  sizeof计算字符串的长度,包含末尾的‘\0’;stlen()计算字符串的长度,不包含字符串末尾的‘\0’。
tips:除了基本数据类型一定要用到sizeof(),其他的都可以不用()。这也是sizeof作为运算符,与函数的不同之处。
老规矩,举例说明:

sizeof(int); //输出4,即整型变量占用4个字节
int x;
sizeof x; //输出4
​
char s[]="hello,world!";
//tips:啰嗦一句,当数组s没有指定元素数量时,
//编译器会在末尾自动补'\0',因此数组s中实际有13个字符。
sizeof(s);//输出13,即包含'\0'
strlen(s);//输出12,即不包含'\0'

 2、static:经常被问到作用。
●  在修饰局部变量时,static修饰的静态局部变量只执行初始化一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。即静态局部变量所在的.c文件运行结束才释放,而普通局部变量在函数调用结束后就释放了。
●  在修饰全局变量时,这个全局变量只能在本文件中访问,不能在其他文件中访问,即便是extern外部声明也不可行。
●  在修饰函数时,这个函数只能在本文件中调用,不能被其他文件调用。
●  static修饰的变量存放在全局数据区的静态变量区,包括全局静态变量和局部静态变量,均在全局数据区分配内存。初始化的时候自动初始化为0。
tips:extern与static不能同时使用;因为用static修饰的全局变量被限定了作用域,所以其他文件中可以有同名的全局变量被定声明。
为了让大家更好地理解上述知识,老规矩,举例说明:
static修饰局部变量

#include <stdio.h>  
​
void count() {  
    int counter = 0; // 局部变量  
    counter++;  
    printf("%d\r\n", counter);  
}  
​
int main() {  
    count(); // 第一次调用,输出:This is the 1 time this function has been called.  
    count(); // 第二次调用,输出:This is the 2 time this function has been called.  
    count(); // 第三次调用,输出:This is the 3 time this function has been called.  
    return 0;  
}
输出结果为:1 1 1

 从上述代码可以看出,如果counter没有被声明为static,那么它就是一个普通的局部变量,每次调用count()函数时都会重新初始化。因此,很容易得到输出结果为1 1 1。

#include <stdio.h>  
​
void count() {  
    static int counter = 0; // 静态局部变量  
    counter++;  
    printf("%d\r\n", counter);  
}  
​
int main() {  
    count(); // 第一次调用,输出:This is the 1 time this function has been called.  
    count(); // 第二次调用,输出:This is the 2 time this function has been called.  
    count(); // 第三次调用,输出:This is the 3 time this function has been called.  
    return 0;  
}
输出结果为:1 2 3

 而在该代码中,通过使用static关键字修饰局部变量counter,确保了counter只在首次进入count()函数时初始化一次。这意味着,在随后的每次count()函数调用中,counter的值都会保持其先前的状态,而不会被重置。因此,每当count()函数被调用时,counter的值都会递增。结果自然是1 2 3。
题外话:分享一个单片机的RAM内存小知识帮助理解。

 

上图展示了单片机的RAM内存分配结构,自顶部至底部依次为栈区(Stack)、堆区(Heap)、数据段(Data Segment)以及代码段(Code Segment)。从图中可以清晰地看到,局部变量通常存储在栈区(Stack)。这些临时变量在它们的作用域结束时会自动被释放,例如定义在函数内部的局部变量,每次函数调用时都会重新初始化。

相对而言,静态变量则存储在数据段(Data Segment)。这些变量在程序运行期间会一直存在,直到整个程序结束才会被释放,这意味着它们不会在每次函数调用时重新初始化,而是在整个.c文件执行过程中都保持其值。

static修饰全局变量和函数

// file1.c  
#include <stdio.h>  
​
static int global_static_var = 42; // 只在 file1.c 中可见  
static void helper_function()
{ // 只在 file1.c 中可见  
    printf("Helper function called.\n");  
} 
​
void print_var() {  
    printf("%d\n", global_static_var);  
    helper_function(); // 可以在 file1.c 中调用  
}

 当你在一个源文件中声明了一个全局的static变量时,这个变量仅在该源文件中可见。其他源文件(即使它们包含相同的头文件)也无法访问这个变量。故在另外一个文件file2.c中,你不能直接访问global_static_var。被static关键字修饰的函数亦是如此。

3、const:经常被问到作用,也会问到与#define的区别。
●   作用:用于定义只读的变量,即如果一个变量被const修饰,那么它的值将无法再改变。值得注意的是,const定义的是变量而不是常量。在C99标准中,const定义的变量是全局变量,存放在全局数据区。此外,用const修饰变量时,一定要给变量初始值,否则编译器会报错。
●   与#define的区别:const常量有数据类型,而宏定义常量没有数据类型,只是简单的文本替换。因此,前者具有类型检查,而后者没有;const常量具有作用域,只在定义它的作用域内有效,而宏定义常量没有作用域,可以在文件的任何地方使用;
老规矩,举例说明:
const修饰基本类型变量

#include <stdio.h>  
​
int main() {  
    const int MAX_VALUE = 100; // 一个整型常量  
    printf("MAX_VALUE: %d\n", MAX_VALUE);  
    // MAX_VALUE = 200; // 这行会编译错误,因为MAX_VALUE是常量  
    return 0;  
}

 const修饰指针
△  指向常量的指针(指针可以移动,但指针指向的值不能修改)

#include <stdio.h>  
​
int main() {  
    int value = 10;  
    const int *p = &value; // p指向一个整型常量  
    printf("Value: %d\n", *p);  
    // *p = 20; // 这行会编译错误,因为*p是常量  
    return 0;  
}

△  常量指针(指针本身的值不能修改,但指针指向的值可以修改,除非指针指向的也是一个常量) 

#include <stdio.h>  
​
int main() {  
    int value = 10;  
    int *const q = &value; // q是一个指向整型的常量指针  
    printf("Value: %d\n", *q);  
    // q = &another_value; // 这行会编译错误,因为q是常量指针  
    return 0;  
}

△  指向常量的常量指针(指针本身的值不能修改,指针指向的值也不能修改) 

#include <stdio.h>  
​
int main() {  
    const int value = 10;  
    const int *const r = &value; // r是一个指向整型常量的常量指针  
    printf("Value: %d\n", *r);  
    // *r = 20; // 这行会编译错误,因为*r是常量  
    // r = &another_value; // 这行也会编译错误,因为r是常量指针  
    return 0;  
}

const修饰数组 

#include <stdio.h>  
​
int main() {  
    const int array[] = {1, 2, 3, 4, 5}; // 一个整型常量数组  
    for (int i = 0; i < 5; i++) {  
        printf("%d ", array[i]);  
    }  
    // array[0] = 10; // 这行会编译错误,因为array是一个常量数组  
    return 0;  
}

const修饰结构体成员 

#include <stdio.h>  
​
typedef struct {  
    const int x; // 结构体的一个整型常量成员  
    int y;  
} Point;  
​
int main() {  
    Point p = {10, 20};  
    printf("Point: (%d, %d)\n", p.x, p.y);  
    // p.x = 30; // 这行会编译错误,因为p.x是常量  
    p.y = 40; // 这是允许的,因为p.y不是常量  
    return 0;  
}

 4、volatile:经常被问到作用和理解。
首先在这讲解这个关键字之前,给大家介绍一下编译器的一个优化操作。由于内存访问的速度远不及cpu处理速度,为了提高存取速度,编译器优化时会把内存变量缓存到寄存器中,若变量由其他程序所改变(核心要义:内存中的值被改变、而寄存器中的值未被改变),将会出现不一样的现象。
作用如下:
●   告诉编译器不要缓存变量:当你将一个变量声明为volatile时,编译器会知道这个变量可能在任何时候被外部因素(如硬件、中断服务程序、线程等)该变,因此它不会对这个变量的访问进行优化。
●   确保每次访问都是直接从内存中读取:每次读取volatile变量时,编译器都会生成代码来从内存中读取该变量的值,而不是使用之前存储在寄存器或者其他地方的值。
●   在多线程和硬件交互中特别有用:在多线程环境中,一个线程可能正在修改一个变量,而灵一个线程正在读取该变量。如果不使用volatile,编译器可能会优化读取操作,导致读取到的是旧值。同样,当与硬件交互时,硬件可能会在任何时候该变某个内存位置的值,因此也需要使用volatile来确保读取到的是最新的值。
生硬的知识点还是得用例子来说明:

volatile int flag = 0; // 声明一个volatile整数变量  
​
// 在某个中断服务程序中  
void ISR() {  
    flag = 1; // 改变flag的值  
}  
​
// 在主循环中  
while(1) {  
    if(flag) { // 检查flag的值  
        // 处理flag被设置的情况  
        flag = 0; // 重置flag  
    }  
    // ... 其他代码 ...  
}

在上面的示例中,flag是一个volatile变量,它在中断服务程序中被改变,并在主循环中被检查。由于flag是volatile的,编译器不会对其访问进行优化,从而确保每次读取的都是最新的值。

5、extern:经常被问到作用。
●   当在多个源文件中共享全局变量时,通常在一个源文件中定义该变量(即分配存储空间),而在其他源文件中使用extern声明该变量,以便能够访问它。
举个例子:
示例1:假如我们有两个文件:global.c和main.c
global.c 

#include <stdio.h>  
​
// 定义全局变量  
int globalVar = 100;  
​
void printGlobalVar() {  
    printf("Value of globalVar in global.c: %d\n", globalVar);  
}

main.c 

#include <stdio.h>  
​
// 声明全局变量(在其他文件中定义)  
extern int globalVar;  
​
extern void printGlobalVar();  // 声明在其他文件中定义的函数  
​
int main() {  
    printf("Value of globalVar in main.c: %d\n", globalVar);  
    printGlobalVar();  
    return 0;  
}

在这个例子中,若你想共享全局变量,在文件中用extern声明即可。
示例2:假设我们有一个头文件functions.h和两个源文件functions.c和main.c。
functions.h 

#ifndef FUNCTIONS_H  
#define FUNCTIONS_H  
​
// 声明函数  
extern void printMessage();  
​
#endif // FUNCTIONS_H

 functions.c

#include <stdio.h>  
#include "functions.h"  
​
// 定义函数  
void printMessage() {  
    printf("Hello, World!\n");  
}

main.c 

#include <stdio.h>  
#include "functions.h"  
​
int main() {  
    printMessage();  // 调用在functions.c中定义的函数  
    return 0;  
}

在这个例子中,printMessage函数在functions.c中定义,并在functions.c中通过extern声明。然后,在main.c中包含functions.h,从而可以访问printMessage函数。

完结,撒花。以上是去年我在嵌入式秋招面试中被问到的超高频的知识点,现在全部分享出来,希望对大家有帮忙。文章若有错误,欢迎指出,积极改正~

想看更多精彩内容可关注下面公粽号,谢谢大噶~

 

标签:const,变量,int,嵌入式,详解,static,秋招,main,常量
From: https://blog.csdn.net/ContikiNewer/article/details/139886241

相关文章

  • 详解 ClickHouse 的分片集群
    一、简介分片功能依赖于Distributed表引擎,Distributed表引擎本身不存储数据,有点类似于MyCat之于MySql,成为一种中间件,通过分布式逻辑表来写入、分发、路由来操作多台节点不同分片的分布式数据ClickHouse进行分片集群的目的是解决数据的横向扩容,通过分片把一份完整......
  • 掌握ChatGPT:提示工程入门详解
    随着人工智能的发展,提示工程成为了使用ChatGPT等语言模型的核心技术。对于初学者,理解和运用提示工程是提高与ChatGPT互动效果的关键。什么是提示工程?提示工程是通过设计和优化输入文本(提示)来引导AI生成特定输出的过程。它包括思路链(Chain-of-Thought)、提示链接(PromptChain......
  • MySQL-文件排序原理详解
    目录Usingfilesort文件排序原理详解filesort文件排序方式示例验证下各种排序方式:单路排序的详细过程:双路排序的详细过程:单路排序相对于双路排序具有以下特点:Usingfilesort文件排序原理详解filesort文件排序方式单路排序:是一次性取出满足条件行的所有字段,然后在s......
  • DCF协议详解
    1概述DCF机制是IEEE802.11标准的核心接入机制,网络中所有节点都应该具备该功能。DCF主要采用带有冲突避免的载波侦听多路访问(CarrierSenseMultipleAccessCollisionAvoidance,CSMA/CA)协议,当使用CSMA时,一个想要发送数据的站点首先侦听传输煤质一段定长时间,如果......
  • [MySQL总结] Explain详解、索引最佳优化
    目录ExplainExplain分析示例explain两个变种explain中的列1.id列2.select_type列3.table列4.type列5.possible_keys列6.key列7.key_len列8.ref列9.rows列10.Extra列索引最佳优化实践1.全值匹配2.最左前缀法则3.不在索引列上做任何操作(计算、函数、(自动or......
  • MySQL总结-索引优化实战详解一
    目录一、索引下推优化详解1.联合索引第一个字段用范围不会走索引2.强制走索引3.覆盖索引优化4.in和or在表数据量比较大的情况会走索引,在表记录不多的情况下会选择全表扫描5.likeKK%一般情况都会走索引索引下推(IndexConditionPushdown,ICP)为什么范围查找Mysql没有......
  • C/C++ 堆栈stack算法详解及源码
    堆栈(stack)是一种常见的数据结构,具有"先进后出"(LastInFirstOut,LIFO)的特性。堆栈算法允许在堆栈顶部进行元素的插入和删除操作。堆栈的操作包括:入栈(Push):将元素添加到堆栈的顶部。出栈(Pop):从堆栈的顶部移除元素。取栈顶元素(Top):获取堆栈顶部的元素,但不对其进行删除操作。......
  • C/C++ stack实现深度优先搜索DFS算法详解及源码
    深度优先搜索(DepthFirstSearch,DFS)是一种图遍历算法,它从一个节点开始,通过访问其相邻节点的方式,依次深入到图中的更深层次。Stack(栈)是一种先进后出(LastInFirstOut,LIFO)的数据结构,它非常适合实现DFS算法。首先,我们来解释一下Stack实现DFS算法的原理。DFS算法的核心思想是......
  • 详解 ClickHouse 的副本机制
    一、简介副本功能只支持MergeTreeFamily的表引擎,参考文档:https://clickhouse.tech/docs/en/engines/table-engines/mergetree-family/replication/ClickHouse副本的目的主要是保障数据的高可用性,即使一台ClickHouse节点宕机,那么也可以从其他服务器获得相同的数据......
  • C语言中操作符详解(一)
    众所周知,在我们的C语言中有着各式各样的操作符,并且在此之前呢,我们已经认识并运用了许许多多的操作符,都是诸君的老朋友了昂操作符作为我们使用C语言的一个非常非常非常重要的工具,诸君一定要加以重视,认真理解,学会灵活运用。那么今天,我们就来系统的讲解一下C语言中的各式操作符......