嵌入式学习笔记(2):C语言基础

目录

目录

typedef与define的区别

static、const、volatile的区别和作用

🍨static:定义静态变量或函数

🍨 const:常用于定义常量

 🍨 volatile:是一种类型修饰符

函数指针

🍨什么是函数指针?

🍨如何声明和定义函数指针

🍨示例

🍨解释

🍨应用场景

回调函数

回调函数的工作原理

🍨示例

🍨示例二

🍉解释 

🍉结果

keil5编辑器的编译完整流程

🍨创建项目

🍨设置编译器选项

🍨 预处理(Preprocessing)

🍨编译(Compilation)

🍨 汇编(Assembly)

🍨链接(Linking)

🍨生成输出

🍨 调试

🍨总结

后续添加下面内容

结构体与共用体的区别


typedef与define的区别

define:执行于预处理阶段,只进行简单的字符替换,没有作用域限制,可以对类型/变量/常量等进行替换。

用法:  #define  被替换字符  替换字符

typedef:执行于编译阶段,对已经存在的数据类型取别名。只能在定义的作用域内使用会进行类型检查。

用法:  typedef  被替换的数据类型  替换字符

实例一

#define char* pstr;


const pstr  => const char*   //指针指向一个常量,可以更改指向的地址

typedef   char*   pstr;

const pstr => char* const   //不能更改指向的地址(常量指针)

实例二:作用域#define没有作用域限制,typedef只能用在作用域

//正确使用
typedef int size
void main(){
    size a = 3;
    function ();
}

void function (){
    size a = 3;
}
-------------------------------------------------------------------------------
//错误使用
int main(){
  typedef int size;
  size a = 3; //等同于int a = 3;
  function();
}

int function(){
    size a = 3;  //报错
}

static、const、volatile的区别和作用

🍨static:定义静态变量或函数

1️⃣:在函数中声明变量时, static 关键字指定变量只初始化一次,并在之后调用该函数时保留其状态,此外相较于普通变量,静态变量在函数执行结束后并不会被销毁,而普通变量则相反,根据此条特性静态变量可以作为函数的计数器,记录函数被调用的次数。
2️⃣:在声明变量时,变量具有静态持续时间,并且除非您指定另一个值。
3️⃣ :在全局和/或命名空间范围 (在单个文件范围内声明变量或函数时) static 关键字指定变量或函数为内部链接,即外部文件无法引用该变量或函数,根据此条特性利用好static可以防止其他文件的变量或函数的命名重复而出现报错,增加了代码封装性,移植性
4️⃣:static 关键字 没有赋值时,默认赋值为 0 

5️⃣:static修饰局部变量时,会改变局部变量的存储位置,从而使得局部变量的生命周期变长。

6️⃣:和全局变量有些相似都是存储在静态数据区,但作用域不一样

用法:

static  数据类型  变量名\函数名()

static int a;

static void fun(){};

🍨 const:常用于定义常量

1️⃣:可以保护被修饰的东西,防止意外修改,增强程序的健壮性

2️⃣:编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高

3️⃣:用const修饰变量时,一定要给变量初始化,否则之后就不能再进行赋值了。

4️⃣:修饰函数的参数可以防止修改指针指向的内容、修改指针指向的地址、指针p1和指针p2指向的地址都不能修改

用法:

1️⃣定义常量:const int  a  或者 int const  a 两者意思一样

2️⃣指针常量: 指的是该指针自身是常量,指向的地址不可改变(int * const a)

3️⃣常量指针指的是该指针指向的地址里面存放的数据是一个常量(const int *a)

4️⃣指向常量的常指针:是以上两种的结合,指针指向的位置不能改变并且也不能通过这个指针改变变量的值,但是依然可以通过其他的普通指针改变变量的值。

5️⃣修饰函数的参数:防止传进去修改指针指向的内容

void StringCopy(char *strDestination, const char *strSource);

其中 strSource 是输入参数,strDestination 是输出参数。给 strSource 加上 const 修饰后,如果函数体内的语句试图改动 strSource 的内容,编译器将指出错误。

6️⃣修饰全局变量:全局变量的作用域是整个文件,我们应该尽量避免使用全局变量,因为一旦有一个函数改变了全局变量的值,它也会影响到其他引用这个变量的函数,导致除了bug后很难发现,如果一定要用全局变量,我们应该尽量的使用const修饰符进行修饰,这样防止不必要的人为修改,使用的方法与局部变量是相同的。

 🍨 volatile:是一种类型修饰符

        遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。声明时语法:int volatile vInt; 当要求使用 volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存
#include <stdio.h>
 
void main()
{
    volatile int i = 10;
    int a = i;
 
    printf("i = %d", a);
    __asm {
        mov dword ptr [ebp-4], 20h
    }
 
    int b = i;
    printf("i = %d", b);
}

结果为 

 i = 10
i = 32

函数指针

        函数指针是 C 语言中的一种强大特性,用于存储指向函数的地址,从而可以通过指针来调用函数。这使得可以实现回调函数、函数的动态选择等功能。以下是函数指针的基本概念和用法介绍:

🍨什么是函数指针?

        函数指针是一个指针变量,它存储的是一个函数的地址。通过函数指针,我们可以间接地调用函数、将函数作为参数传递,以及实现更灵活的程序结构。

🍨如何声明和定义函数指针

        要声明一个函数指针,首先要知道所指向的函数的返回类型和参数类型。以下是一个典型的函数指针声明示例:

// 声明一个函数指针类型,指向返回类型为 int,参数为两个 int 的函数  
typedef int (*func_ptr_t)(int, int);

🍨示例

以下是一个完整的示例,演示了如何使用函数指针。

#include <stdio.h>  

// 定义几个简单的函数  
int add(int a, int b) {  
    return a + b;  
}  

int subtract(int a, int b) {  
    return a - b;  
}  

// 定义函数指针类型  
typedef int (*operation_t)(int, int);  

// 函数接受一个函数指针作为参数  
void perform_operation(operation_t op, int x, int y) {  
    printf("Result: %d\n", op(x, y)); // 通过函数指针调用函数  
}  

int main() {  
    // 定义函数指针  
    operation_t op;  

    // 将指针指向 add 函数  
    op = add;  
    perform_operation(op, 5, 3); // 输出: Result: 8  

    // 将指针指向 subtract 函数  
    op = subtract;  
    perform_operation(op, 5, 3); // 输出: Result: 2  

    return 0;  
}

🍨解释

  1. 🍉函数定义:定义了两个函数 add 和 subtract,它们都接受两个 int 参数,并返回一个 int 结果。

  2. 🍉函数指针类型:使用 typedef 声明了一个函数指针类型 operation_t,指向返回类型为 int,参数为两个 int 的函数。

  3. 🍉函数参数perform_operation 函数接受一个函数指针 op 和两个 int 参数 x 和 y,它通过指针调用传入的函数。

  4. 🍉函数指针赋值与调用:在 main 函数中,定义了 operation_t 类型的指针 op,并分别将其指向 add 和 subtract 函数,通过 perform_operation 调用相应的函数。

🍨应用场景

  1. 🍉回调函数:常用于事件处理、信号处理等。
  2. 🍉动态函数调用:可以根据条件选择调用不同的函数。
  3. 🍉实现策略模式:通过函数指针实现不同的算法或策略。

函数指针使得 C 语言的编程更加灵活和高效,但需要小心使用,以避免出现指向无效地址或调用不匹配函数签名的问题。

回调函数

        回调函数是其中一种函数指针的应用,它是一种通过将一个函数(或方法)作为参数传递给另一个函数来实现的编程模式。被传递的函数(即回调函数)可以在未来的某个时间点被调用。这种机制在许多编程场景中非常有用,特别是在事件驱动的编程中。

回调函数的工作原理

  1. 🍉定义回调函数:首先,你需要定义一个将被回调的函数。这个函数通常具有一个特定的签名(即参数类型和返回类型)。

  2. 🍉将回调函数传递给其他函数:接着,这个回调函数被作为参数传递给另一个函数。

  3. 🍉在特定条件下调用回调:被接收的函数可以在适当的时机调用传递的回调函数。

🍨示例

下面是一个简单的 C 语言示例,演示了如何使用回调函数:

#include <stdio.h>  

// 定义回调函数类型  
typedef void (*Callback)();  

// 回调函数实现  
void sayHello() {  
    printf("Hello!\n");  
}  

void sayGoodbye() {  
    printf("Goodbye!\n");  
}  

// 接受回调的函数  
void performAction(Callback callback) {  
    // 在某些条件下调用回调函数  
    callback();  
}  

int main() {  
    // 传递回调函数  
    performAction(sayHello);  // 输出: Hello!  
    performAction(sayGoodbye); // 输出: Goodbye!  

    return 0;  
}

🍨示例二

#include <stdio.h>

typedef void (*fun_callback_t)(int number);//声明回调函数指针

fun_callback_t g_fun_cb = NULL;           //定义回调函数指针

int set_callback(fun_callback_t cb);	   //声明设置回调函数

void my_fun(int number )
{
	number = number*100;
	printf("In {my_fun} number:<%d>\n",number);
}

int main()
{
   printf("callback test start !\n");
   set_callback(my_fun);	//设置一个名称为my_fun的回调函数
	
	for(int i=0; i<5; i++ )
	{
		printf("In {main} i:<%d>\n",i);
		g_fun_cb(i);		//通过回调函数指针调用回调函数
		printf("\n");
	}
	
   return 0;
}

int set_callback(fun_callback_t cb) //设置回调函数实现
{
	if (g_fun_cb != NULL)
	{
		printf("g_fun_cb is not NULL!\n");
		return 0;
	}
	else
	{
	    g_fun_cb = cb;
    	return 1;
	}
}

🍉解释 

先看mian函数通过set_callback(my_fun)将函数my_fun()通过参数传进去,判断回调函数是否为空,如果为空将g_fun_cb的指针指向my_fun(),随后在for循环中调用

🍉结果

0

100

200

300

400 

keil5编辑器的编译完整流程

Keil µVision 5(常称为 Keil 5)是一个广泛使用的嵌入式开发环境,主要用于开发基于 ARM 处理器的应用程序。以下是 Keil 5 编辑器编译的完整流程:

🍨创建项目

  • 🍓新建项目:在 Keil 中创建一个新工程,选择目标微控制器(MCU),这会自动配置一些设置和启动文件。
  • 🍓添加源文件:将 C 或汇编源文件添加到项目中。

🍨设置编译器选项

  • 🍓打开“工程选项”(Project Options),配置编译器的选项,包括优化级别、包含路径、预处理器定义等。

🍨 预处理(Preprocessing)

  • 🍓Keil 会执行源文件的预处理,处理 #include 和 #define 等预处理器指令。这一步通常是自动进行的,Keil 会将源代码的预处理版本传递给编译器。

🍨编译(Compilation)

  • 🍓在此阶段,Keil 会将 C 代码翻译成中间表示(IR)。它会进行:
    • 🍅语法分析:检查代码的语法。
    • 🍅语义分析:验证变量类型和作用域等。
    • 🍅生成中间代码,准备生成汇编代码。

🍨 汇编(Assembly)

  • 🍓将编译生成的中间表示转换为汇编语言,并生成汇编代码。
  • 🍓最后,汇编器会将汇编代码转换为机器代码,生成目标文件(通常是 .obj 文件)。

🍨链接(Linking)

  • 🍓Keil 会将生成的目标文件与库文件链接在一起,以生成最终的可执行文件(通常是 .axf 文件)。
  • 🍓在此阶段,链接器进行符号解析、内存分配和地址重定位。

🍨生成输出

  • 🍓生成最终的程序文件,Keil 还会创建一些帮助文件,如:
    • 🍅Listing 文件(.lst):包含汇编代码和每行对应的机器代码。
    • 🍅Map 文件(.map):显示代码和数据的存储位置。
    • 🍅Hex 文件(.hex):用于编程微控制器的格式。

🍨 调试

  • 🍓在 Keil 中,可以直接使用调试器进行代码调试。连接到微控制器后,可以设置断点、查看变量值、单步执行等。

🍨总结

  • 🍓创建项目:建立新项目和添加源文件。
  • 🍓设置选项:配置编译器和链接器选项。
  • 🍓预处理:处理预处理指令。
  • 🍓编译:编译源代码生成目标文件。
  • 🍓汇编:将目标文件转换为机器码。
  • 🍓链接:将各个目标文件链接成最终执行文件。
  • 🍓输出和调试:生成需要的输出文件并进行调试。

通过了解这些步骤,可以更有效地使用 Keil 进行嵌入式开发。

后续添加下面内容

结构体与共用体的区别

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值