C语言是一种通用的高级编程语言,它由贝尔实验室的Dennis Ritchie在20世纪70年代初开发。C语言以其简洁、高效、可移植和可扩展的特点而闻名。
在编写C语言代码时,遵循一定的编程规范可以提高代码的可读性和可维护性。常见的编程规范包括:使用有意义的变量名、遵循适当的缩进风格、注释代码、避免使用全局变量等。
3.1 基本数据类型
C语言的基本数据类型包括整型、浮点型、字符型和布尔型。
3.1.1 整型
用于表示整数的数据类型,包括以下几种:
char | 用于表示字符或小整数,占用1个字节 |
int | 用于表示整数,占用2或4个字节,取决于编译器和平台 |
short | 用于表示短整数,占用2个字节 |
long | 用于表示长整数,占用4或8个字节,取决于编译器和平台 |
unsigned修饰符 | 用于表示无符号整数,可以与上述整型数据类型一起使用 |
3.1.2 浮点型
用于表示实数的数据类型,包括以下几种:
float | 用于表示单精度浮点数,占用4个字节 |
double | 用于表示双精度浮点数,占用8个字节 |
long double | 用于表示扩展精度浮点数,占用8或16个字节,取决于编译器和平台 |
3.1.3 字符型
用于表示单个字符的数据类型,包括char。
3.1.4 布尔型
用于表示真或假的数据类型,包括_Bool,其取值可以是0(假)或1(真)。
下图3-1列举了各种基本数据类型的占用空间、取值范围和默认值。此表格无须硬背,大致理解即可,需要时可以通过查表来回忆。
C语言还支持通过结构体、枚举等方式自定义复合数据类型,这些在声明变量时会有一些不同的语法和规则。这将会在后续的章节中进行讲解,本章节我们先讲解基本变量类型。
3.2 标识符
当我们在编写C语言程序时,需要使用标识符来命名变量、函数、结构体等程序实体。标识符是由字母、数字和下划线组成的名称,用于标识程序中的各种实体。以下是关于C语言标识符的详细说明:
标识符命名规则:
- 标识符由字母、数字和下划线组成。
- 标识符必须以字母或下划线开头,不能以数字开头。
- 标识符区分大小写,例如"age"和"Age"被视为不同的标识符。
标识符命名建议:
- 标识符应该具有描述性,能够清晰地表达其所代表的含义。
- 标识符应该遵循命名约定,以增加代码的可读性。例如,变量名使用小写字母,单词之间使用下划线分隔(例如,student_name)。
- 避免使用C语言的关键字作为标识符,以免引起语法错误。
一些规范的标识符:
- 变量名:用于存储数据的标识符,例如:age、name、count等。
- 函数名:用于执行特定任务的标识符,例如:calculate_sum、print_message等。
- 结构体名:用于定义自定义数据类型的标识符,例如:Person、Student等。
- 宏名:用于定义预处理指令的标识符,例如:MAX_SIZE、PI等。
下图3-2列举出了一些正确的标识符示例。
编写C语言程序时,选择合适的标识符名称是提高代码可读性和可维护性的重要因素。
3.3 关键字
C语言是一种结构化的、面向过程的编程语言,它有一些特殊的关键字,这些关键字在编程中具有特殊的含义,不能被用作标识符(变量名、函数名等)。下表3-1是C语言中一些常用的关键字及其详细说明:
关键字 | 详细说明 | 关键字 | 详细说明 | |
auto | 用于声明自动变量。它是默认的存储类别修饰符,用于定义局部变量。 | break | 用于在循环或switch语句中跳出当前代码块。 | |
case | 用于在switch语句中指定不同的分支。 | char | 用于声明字符类型的变量。 | |
const | 用于定义常量,一旦定义后就不能修改。 | continue | 用于跳过当前循环中的剩余代码,然后开始下一次循环。 | |
default | 用于在switch语句中指定默认的分支。 | do | 用于创建一个do-while循环。 | |
double | 用于声明双精度浮点数类型的变量。 | else | 用于在if语句中指定条件不满足时要执行的代码块。 | |
enum | 用于定义枚举类型。 | extern | 用于声明在其他文件中定义的全局变量或函数。 | |
float | 用于声明单精度浮点数类型的变量。 | for | 用于创建一个for循环。 | |
goto | 用于在代码中无条件地跳转到指定的标签位置。 | if | 用于创建一个条件判断语句。 | |
int | 用于声明整数类型的变量。 | long | 用于声明长整数类型的变量。 | |
register | 用于声明寄存器变量,将变量存储在寄存器中以便于快速访问。 | return | 用于从函数中返回值。 | |
short | 用于声明短整数类型的变量。 | signed | 用于声明有符号整数类型的变量。 | |
sizeof | 用于获取变量或数据类型的大小(以字节为单位)。 | static | 用于声明静态变量,它在程序的整个执行过程中都存在。 | |
struct | 用于定义结构体类型。 | switch | 用于创建一个多路分支选择语句。 | |
typedef | 用于给已有的数据类型取一个新的名字。 | union | 用于定义共用体类型。 | |
unsigned | 用于声明无符号整数类型的变量。 | void | 用于声明无返回值的函数或指针。 | |
volatile | 用于声明易变的变量,告诉编译器不要对其进行优化。 | while | 循环语句的循环条件 | |
asm | 用于在C语言中嵌入汇编指令 | fortran | 为Fortran语言链接而设定的条件性支持类型指令符 |
1999年12月16日,ISO推出了C99标准,该标准新增了6个C语言关键字。
关键词 | 说明 |
inline | 用来定义一个类的内联函数,引入它的主要原因是用它代替C中表达式形式的宏定义。 |
restrict | 只可以用于限定和约束指针,并表明指针是访问一个数据对象的唯一且初始的方式。即所有修改该指针指向内存中内容的操作都必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改,这样做的好处是,能帮助编译器进行更好的优化代码,生成更有效率的汇编代码。 |
_Bool | 布尔类型的数据,其值为0或1,主要用来判断条件能否成立的真假。 |
_Complex | 用来表示复数类型。 |
_Imaginary | 用来表示虚数类型 |
_Pragma | 与#pragma指令相同的功能。 |
2011年12月8日,ISO发布C语言的新标准C11,该标准新增了7个C语言关键字。
关键词 | 说明 |
_Alignas | 指定某个变量按照其他数据类型对齐。 |
_Alignof | 指定数据类型内存对齐的字节数。 |
_Atomic | 原子类型说明符和限定符。 |
_Static_assert | 声明在编译时有效,它将测试由用户指定且可以转换为布尔值的整数表达式表示的软件断言。如果表达式的计算结果为零(false),编译器将发出用户指定的消息,并且编译因错误而失败。 |
_Noreturn | 表明调用完成后的函数不返回主调函数,目的是告诉用户和编译器,这个特殊的函数不会把控制返回主调程序,告诉用于以免滥用该函数,通知编译器可以优化一些代码。 |
_Thread_local | 它会影响变量的存储周期,被修饰的变量具有线程周期,这些变量在线程开始的时候被生成,在线程结束的时候被销毁。并且每一个线程都拥有一个独立的变量实例。可以和static和extern关键字联合使用,这将影响变量的链接属性。 |
_Generic | 可以简单地将一组具有不同类型却有相同功能的函数抽象为一个统一的接口。 |
其中存在一些比较常用的关键词,这里我们通过编写嵌入式代码为例来对关键词进行详细说明。因此我们需要在main.c文件中包含串口驱动函数并初始化需要使用的串口。关于底层部分的驱动这里不详细介绍,先要求能够使用即可。详细见下图3-3所示。
上图3-3中代码第20-23行是定时器中断处理函数,以后的代码都将省略该部分代码,但实际代码实现时需注意添加该部分语句。
接着使用Type-C一端连接到PC端(个人电脑上),另一端连接到STM32的串口1接口上,硬件连接示意图见图3-4所示。
打开配套资料中的“串口调试助手”软件,选择正确的串口号(这里需注意选择带CH340字样的COM口,可手动插拔Type-C,观察新增的串口),配置波特率为115200,并点击“打开串口”,具体操作见下图3-5。
- auto
C语言中有几种存储类别,包括auto、register、static和extern。其中auto是默认的存储类别,register用于声明寄存器变量,static用于声明静态变量,而extern用于声明外部变量。因此auto常用于声明自动变量,通常在函数内部使用,大多数情况下会忽略写。示例见下图3-6。
- break
break是C语言中的一个关键字,用于在循环或者switch语句中跳出当前的循环或者switch块。当程序执行到break语句时,程序会立即跳出当前的循环或者switch块,并继续执行循环或者switch之后的代码。示例:在一个for循环中使用break语句可以提前结束循环。见下图3-7。
- case
在C语言中,case是一种用于在switch语句中匹配特定值的关键字。switch语句用于基于不同的条件执行不同的代码块。在下图3-8的示例中,switch语句将根据num的值匹配相应的case标签。
- const
在C语言中,使用关键字const可以声明常量。常量是在程序运行期间不可更改的值。声明常量的语法格式为:
const 数据类型 常量名 = 值;
例如下图3-9所示。
通过使用const关键字声明常量,可以增加程序的可读性和可维护性。常量的值在程序中多次使用时,只需要修改一次即可,而不需要在每个使用处都进行修改。
- continue
在C语言中,continue用于控制循环语句中的循环体执行流程。当continue语句被执行时,它会立即终止当前循环的当前迭代,然后开始下一次迭代。continue语句通常与循环语句(如for、while、do-while)一起使用。当满足某个条件时,你可以使用continue语句跳过当前迭代的剩余代码,并开始下一次迭代。以下是一个使用continue语句的示例,展示了如何跳过奇数打印,代码示例见图3-10。
上述代码中,for循环从1到10迭代,但是当i为奇数时,continue语句会跳过剩余的代码,直接开始下一次迭代。因此,只有偶数会被打印出来。
注意:continue语句只会影响当前循环的迭代,而不会终止整个循环。如果需要终止整个循环,可以使用break语句。
- do
在C语言中,do关键字用于定义一个循环体,即do-while循环。do-while循环与while循环类似,但是它是先执行循环体,再判断条件是否满足。do-while循环的语法格式如下:
do {
// 循环体语句
} while (条件);
其中,循环体语句是需要重复执行的代码块,条件是一个布尔表达式,用于判断是否继续循环。当条件满足时,循环体会一直执行,直到条件不满足时才会停止循环。
注意:do-while循环至少会执行一次循环体,即使条件一开始就不满足。这是因为循环体的执行在条件判断之前。
下面是一个使用do-while循环的例子,它会输出数字1到10。代码示例如图3-11。
这段代码中,i的初始值为1,然后通过do-while循环输出i的值,并将i递增。循环会一直执行,直到i的值超过10为止。
- else
在C语言中,else关键字用于在条件语句中指定一个备选的代码块,当条件为假时执行。else关键字通常与if语句一起使用。else关键字后面可以跟着另一个if语句,形成嵌套的条件语句,这被称为if-else if-else结构。下图3-12是一个示例:
在上面的示例中,如果a大于10,则执行第一个if代码块中的语句。如果a小于10,则执行第二个else if代码块中的语句。如果a等于10,则执行最后一个else代码块中的语句。
注意:else关键字必须与最近的if语句配对使用,因此在嵌套的if-else结构中,每个else都与最近的if配对。
- enum
在C语言中,enum关键字用于定义枚举类型。枚举类型是一种用户自定义的数据类型,用于定义一组具有离散取值的常量。下面是enum关键字的基本语法:
enum 枚举类型名 {
枚举常量1,
枚举常量2,
...
};
在枚举类型中,我们可以定义多个枚举常量,每个常量都有一个关联的整数值。默认情况下,第一个枚举常量的值为0,后续的枚举常量的值会依次递增。下图3-13是一个示例,演示如何使用enum关键字定义和使用枚举类型:
这只是C语言中的一些常用关键字,还有其他关键字如int、float、return等等。熟悉这些关键字将帮助您更好地理解和编写C语言程序。以上是C语言中一些常用的关键字及其详细说明。这些关键字在编程中具有特殊的用途和语法规则,熟练掌握它们对于正确编写C语言程序非常重要。
3.4 变量
在C语言中,变量是用来存储数据的一种命名内存单元。每个变量都有一个特定的数据类型,它决定了变量可以存储的数据的种类和范围。C语言的基本数据类型包括整型(int)、浮点型(float、double)、字符型(char)和布尔型(bool)等。每种类型的描述见下图3-14。
在 C 语言中,如果变量没有显式初始化,那么它的默认值将取决于该变量的类型和其所在的作用域。对于全局变量和静态变量(在函数内部定义的静态变量和在函数外部定义的全局变量),它们的默认初始值为零。
以下是不同类型的变量在没有显式初始化时的默认值:
- 整型变量(int、short、long等):默认值为0。
- 浮点型变量(float、double等):默认值为0.0。
- 字符型变量(char):默认值为'\0',即空字符。
- 指针变量:默认值为NULL,表示指针不指向任何有效的内存地址。
- 数组、结构体、联合等复合类型的变量:它们的元素或成员将按照相应的规则进行默认初始化,这可能包括对元素递归应用默认规则。
需要注意的是,局部变量(在函数内部定义的非静态变量)不会自动初始化为默认值,它们的初始值是未定义的(包含垃圾值)。因此,在使用局部变量之前,应该显式地为其赋予一个初始值。
C 语言中变量的默认值取决于其类型和作用域。全局变量和静态变量的默认值为 0,字符型变量的默认值为 \0,指针变量的默认值为 NULL,而局部变量没有默认值,其初始值是未定义的。
在Keil MDK编译器对每种数据类型的占用大小作了如下定义:
- char占用1个字节
- short int占用2字节
- int、long、long int、float占用4字节
- double占用8字节
在STM32库函数编程中,定义的类型的对应关系如下(在stdint.h与stm32f10x.h中查看),部分定义如下图3-15所示:
变量声明向编译器保证变量以指定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义,在程序连接时编译器需要实际的变量声明。
变量的声明有两种情况:
一种是需要建立存储空间的。例如:int a 在声明的时候就已经建立了存储空间。另一种是不需要建立存储空间的,通过使用extern关键字声明变量名而不定义它。 例如:extern int a 其中变量 a 可以在别的文件中定义的。因此对于变量的初始化来说,除非有extern关键字,否则都是变量的定义。
下面是一些常用的变量声明和初始化的示例,下面示例中都使用了两种方法去初始化,因此会存在报错。实际使用的时候任选其中一种初始化即可。
整型变量声明和初始化,见下图3-16。
浮点型变量声明和初始化,见下图3-17。
字符型变量声明和初始化,见下图3-18。
布尔型变量声明和初始化,见下图3-19。
此外,你还可以使用结构体(struct)和枚举(enum)等方式来定义自己的复合数据类型。这些数据类型的声明和初始化方式会稍有不同,但基本的原理和规则与上述示例相似。
需要注意的是,变量在使用之前必须先声明,声明变量时,需要指定变量的名称和类型。变量的名称可以由字母、数字和下划线组成,但必须以字母或下划线开头。变量的类型决定了变量占用的内存空间和能够存储的数据范围。
3.5 编程规范
C语言编程规范是为了提高代码的可读性、可维护性和可移植性而制定的一系列规则和约定。以下是一些常见的C语言编程规范的详细说明:
3.5.1 缩进和空格
- 使用4个空格进行缩进,而不是制表符。
- 在操作符之前和之后使用空格,例如a = b + c;。
- 在逗号之后使用空格,例如int a, b, c;。
使用Keil5配置缩进大小的演示见下图3-20所示。
3.5.2 命名规范
- 使用有意义的变量和函数名,尽量使用英文单词或常用缩写。
- 变量和函数名使用小写字母和下划线,例如my_variable。
- 宏定义使用全大写字母和下划线,例如MAX_SIZE。
3.5.3 注释规范
- 使用注释来解释代码的功能、目的和实现方法。
- 在代码的关键部分使用注释,帮助他人更好地理解代码。
- 使用块注释(/* ... */)或行注释(// ...)来注释代码。
3.5.4 函数和语句规范
- 函数应该有明确的目的和功能,遵循单一职责原则。
- 函数的参数应该使用有意义的名称,并且应该有适当的注释来解释每个参数的含义和用途。
- 每个语句应该独占一行,以提高代码的可读性。
3.5.5 错误处理
- 对可能出现错误的情况进行适当的检查和处理。
- 使用错误码或异常来表示错误,并提供适当的错误处理机制。
3.5.6 头文件和模块
- 使用头文件来声明变量、函数和宏定义,以提供模块化和代码重用。
- 头文件应该包含所需的其他头文件,并使用头文件保护宏(#ifndef ... #endif)来防止重复包含。
3.5.7 可移植性
- 避免使用与平台相关的特定代码。
- 使用标准的C库函数和数据类型,以确保代码在不同平台上的可移植性。
这些规范只是C语言编程规范的一小部分。在实际开发过程中,可以根据团队或项目的需要进行相应的调整和补充。最重要的是,保持一致性,并确保代码的可读性和可维护性。