嵌入式C语言基础|世界报道
嵌入式开发中常用的C语言基础语法并不多,因此,对于想学习或者进入嵌入式领域的同学,可以通过快速学习常用的C语言基础,进而着手尝试开发小项目,在开发过程中不断扩展知识库。
(相关资料图)
1、const用法
C语言中使用const修饰变量,功能是对变量声明为只读特性,并保护变量值以防被修改。
修饰变量/数组
当用const修饰定义变量时,必须对变量进行初始化;
const修饰变量可以起到节约空间的效果,通常编译器并不给普通const只读变量分配空间,而是将它们保存在符号列表中,无需读写内存操作,程序执行效率也会提高。
修饰指针
常量指针(常指针),可以理解为常量的指针,即这个是指针,但指向的是个常量,const限定了指针指向空间的值不可修改;
指针常量,本质是一个常量。指针常量的值是指针,这个值因为是常量,所以不能被赋值。const限定了指针不可修改;
对于指针p1, const修饰的是*p1,即p1指向的空间的值不可改变,例如*p1 = 20;就是错误的用法;但是p1的值是可以改变的,例如p1 = &k;则没有任何问题。
对于指针p2, const修饰的是p2,即指针本身p2不可更改,而指针指向空间的值是可以改变的,例如*p2= 15;是没有问题的,而p2 = &i;则是错误的用法。
2、static用法
常见的局部变量和全局变量的特点可简单概况为:
局部变量会在每次声明的时候被重新初始化(如果在声明的时候有初始化赋值),不具有记忆能力,其作用范围仅在某个块作用域可见;
全局变量只会被初始化一次,之后会在程序的某个地方被修改,其作用范围可以是当前的整个源文件或者工程。
static关键词在嵌入式开发中使用频率较高,可以在一定程度上弥补局部变量和全局变量的局限性。
静态局部变量
满足局部变量的作用范围,但是拥有记忆能力,不会在每次生命周期内都初始化一次,这个作用可来实现计数功能,例如:在下面这个函数中,变量num就是静态局部变量,在第一次进入cnt函数的时候被声明,然后执行自加操作,num的值就等于1;当第二次进入cnt函数的时候, num不会被重新初始化变成0,而是保持1,再自增则变成了2,以此类推, 其作用域仍然是cnt这个函数体内。
静态全局变量
将全局变量的作用域缩减到了仅当前源文件可见,其它文件不可见;静态全局变量的优势是增强了程序的安全性和健壮性。
static修饰函数
让函数仅在本文件可见, 其它文件无法对其进行调用,例如在example1.c文件里面进行了如下定义:
那么gt_fun这个函数就只能在example1.c中被调用,在example2.c中就无法调用这个函数。而如果不使用static来修饰这个函数,那么只需要在example2.c中使用extern关键字写下语句extern void gt_fun(void);即可调用gt_fun这个函数。
3、extern关键词
在C语言中, extern关键字用于指明函数或变量定义在其它文件中,提示编译器遇到此函数或者变量的时候到其它模块去寻找其定义,这样被extern声明的函数或变量就可以被本模块或其它模块使用。因而, extern关键字修饰的函数或者变量是一个声明而不是定义,例如:
而在main.c中,如果没有include example.c,但又想使用example.c中定义的变量,则使用extern关键词:
extern关键字还有一个重要的作用,就是如果在C++程序中要引用C语言的文件,则需要用以下格式:
这段代码的含义是,如果当前是C++环境( _cplusplus是C++编译器中定义的宏),要编译花括号{}里面的内容需要使用C语言的文件格式进行编译,而extern “C”就是向编译器指明这个功能的语句。
4、volatile关键词
volatile原意是“易变的”,在嵌入式环境中用volatile关键字声明的变量,在每次对其值进行引用的时候都会从原始地址取值。由于该值“易变”的特性所以,针对其的任何赋值或者获取值操作都会被执行(而不会被优化)。由于这个特性,所以该关键字在嵌入式编译环境中经常用来消除编译器的优化,可以分为以下三种情景:
修饰硬件寄存器;
修饰中断服务函数中的非自动变量;
在有操作系统的工程中修饰被多个应用修改的变量;
在有操作系统(比如RTOS、 UCOS-II、 Linux等)的程序中,如果有多个任务对同一个变量进行赋值或取值,那么这一类变量也应使用volatile来修饰保证其可见性。所谓可见即:当前任务修改了这一变量的值,同一时刻,其它任务此变量的值也发生了变化。
5、enum用法
enum是C语言中用来修饰枚举类型变量的关键字,使用enum关键字可以创建一个新的“类型”并指定它可具有的值。要注意的是,枚举类型是一种基本数据类型,一个枚举常量的占的字节数为4个字节,仅仅恰好和int类型的变量占的字节数相同,并不意味着,枚举类型等同于int型。
在没有显式说明的情况下,枚举常量默认第一个枚举常量的值为0,往后每个枚举常量依次递增1;
在部分显式说明的情况下,未指定的枚举名的值将依着之前最有一个指定值向后依次递增;
一个整数不能直接赋值给一个枚举变量,必须用该枚举变量所属的枚举类型进行类型强制转换后才能赋值;
同一枚举类型中不同的枚举成员可以具有相同的值;
同一个程序中不能定义同名的枚举类型,不同的枚举类型中也不能存在同名的枚举成员(枚举常量)。
枚举类型的目的是提高程序的可读性,其语法与strut的语法类似。只要是能使用整型常量的地方就可以使用枚举常量,例如,在声明数组的时候可以使用枚举常量表示数组的大小,在switch语句中可以把枚举常量作为标签。
6、typedef用法
typedef工具是一个高级数据特性,利用typedef可以为某一类型自定义名称。 这方面与#define类似,但是两者有三处不同:
与#define不同, typedef创建的符号只受限于类型,不能用于值;
tyedef由编译器解释,不是预处理器;
在其受限范围内, typedef比#define更灵活;
假设要用BYTE表示1字节的数组,只需要像定义个char类型变量一样定义BYTE,然后再定义前面加上关键字typedef即可:
随后便可使用 BYTE 来定义变量:
为现有类型创建一个名称,看起来是多此一举,但是它有时的确很有用。在前面的示例中,用BYTE代替unsigned char表明你打算用BYTE类型的变量表示数字而不是字符。使用typedef还能提高程序的可移植性。 用typedef来命名一个结构体类型的时候,可以省略该结构的标签( struct):
使用typedef的第二个原因是: tyedef常用于给复杂的类型命名,例如: 把pFunction声明为一个函数,该函数返回一个指针,该指针指向一个void型。
7、预处理器与预处理指令
预处理指令
根据程序中的预处理指令,预处理器把符号缩写替换成其表示的内容( #define)。预处理器可以包含程序所需的其它文件( #include),可以选择让编译器查看哪些代码(条件编译)。预处理器并不知道C语法,基本上它的工作是把一些文本转换成另外一些文本。
#define 与#undef 用法
每行#define(逻辑行)都由3部分组成。
第1部分是#define指令本身;第2部分是选定是缩写,也称为宏,有些宏代表值;第3部分称为替换列表或替换体。
一旦预处理器在程序中找到宏的示例后,就会用替换体代替该宏。从宏变成最终替换文本的过程称为宏展开。需要注意是:预处理器会严格按照替换体直接替换,不做计算不做优先级处理,例如下面求取平方值的宏定义: