关于枚举、结构体、全局变量、局部变量、常量这些,自己经常遗忘,然后每次查找的都略微有些出入且冗余,并不能快速反应过来。这篇文章主要还是作为自己知识点的一个梳理,可能有较多错误,见谅!!
一、枚举
1.1枚举的应用
枚举的应用可以简单概括为两类
- 用来限制 枚举变量 ,其值只能为枚举标识符中的一个。
enum {
Monday = 1,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
} day;
通过上述枚举,可以将枚举变量day限制为周一到周日7个值。当枚举变量day = a;这个非枚举标识符时,则会报错。
- 作为宏定义的集合,使得代码看上去更具备可读性
当代码中有大量 #define
宏定义的整数常量时,可以改用 enum
以提高可读性。
/*************************************/
// 定义一组错误代码
#define ERROR_NONE 0 // 无错误
#define ERROR_TIMEOUT 1 // 超时错误
#define ERROR_OVERFLOW 2 // 数据溢出
#define ERROR_INVALID 3 // 非法操作
/*************************************/
//使用enum后
typedef enum {
ERROR_NONE = 0, // 无错误
ERROR_TIMEOUT, // 超时错误(1)
ERROR_OVERFLOW, // 数据溢出(2)
ERROR_INVALID // 非法操作(3)
} ErrorCode;
1.2枚举使用注意事项
- 枚举内的数据均为常量,存储在flash当中
- 枚举内标识符,下一个标识符的值为上一个标识符+1
//情况1
typedef enum {
ERROR_NONE, // 无错误
ERROR_TIMEOUT, // 超时错误
ERROR_OVERFLOW, // 数据溢出
ERROR_INVALID // 非法操作
} ErrorCode;
//情况2
typedef enum {
ERROR_NONE = 5, // 无错误
ERROR_TIMEOUT, // 超时错误
ERROR_OVERFLOW, // 数据溢出
ERROR_INVALID // 非法操作
} ErrorCode;
//情况3
typedef enum {
ERROR_NONE, // 无错误
ERROR_TIMEOUT, // 超时错误
ERROR_OVERFLOW = 5, // 数据溢出
ERROR_INVALID // 非法操作
} ErrorCode;
对于上述情况1来讲:
EEROR_NONE = 0;
ERROR_TIMEOUT = 1,
ERROR_OVERFLOW = 2,
ERROR_INVALID = 3;
而对于情况2来讲:
EEROR_NONE = 5;
ERROR_TIMEOUT = 6,
ERROR_OVERFLOW = 7,
ERROR_INVALID = 8;
对于情况3来讲:
EEROR_NONE = 0;
ERROR_TIMEOUT = 1,
ERROR_OVERFLOW = 5,
ERROR_INVALID = 6;
二、结构体
结构体定义时并不会分配内存,只有在创建结构体变量时才会分配内存。
struct PidController {
int Kp, Ki, Kd;
int err, err_last, integral;
}; //这时并不会分配内存
struct PidController pid1; // 只有在创建结构体变量时才会分配内存
2.1关键词typedef
typedef的作用是给数据类型起别名,使得代码看上去更加简洁
在使用结构体时,我们常常还会使用关键词typedef来使得结构体使用更加方便简洁
//不使用typedef关键词定义结构体
struct PidController {
int Kp, Ki, Kd;
int err, err_last, integral;
};
struct PidController pid1; // 必须带上 struct 关键字
//使用typedef关键词定义结构体
typedef struct {
int Kp, Ki, Kd;
int err, err_last, integral;
} PidController;
PidController pid1; // 直接用 PidController 作为类型,不需要写 struct 关键字
对于定义结构体来讲,其本质还是在定义一个大的数据类型——struct 结构体名,如上面代码中:struct PidController就是一个数据类型,与int、unsigned int、float并无太大的区别。
通过使用typedef可以使得 struct 结构体名 简化成 结构体名。
下面我将统一采用typedef关键词来简化
2.2不同结构体变量的不同使用方式
下面将统一以该结构体为例:
typedef struct {
int Kp, Ki, Kd;
int err, err_last, integral;
} PidController;
2.2.1普通结构体变量
对于普通结构体变量来讲,其定义为:
PidController pid1;
如需调用结构体内的成员:
pid1.Ki = 1;
pid1.err = 2;
2.2.2结构体指针变量
对于结构体指针变量的使用,往往需要提前定义好一个普通的结构体变量
PidController pid1;
PidController *pPid = &pid1; // 让指针指向这个变量
如需调用结构体内的成员:
pPid->Kp = 15;
pPid->Ki = 25;
pPid->Kd = 35;
//或
(*pPid).Kp = 15;
(*pPid).Ki = 25;
(*pPid).Kd = 35;
使用结构体指针变量的好处:通过指针传递可以减少内存的开销
关于结构体指针,大多数使用场景是:
void updatePid(PidController *pid) // 传递指针,不拷贝数据
{
pid->Kp = 20; // 直接修改原来的结构体
}
int main()
{
PidController pid = {10, 10, 10}; //提前定义好普通结构体变量
PidController *pPid = &pid
updatePid(pPid );
}
当然还有一个更加简洁的方法那就是
void updatePid(PidController *pid) // 传递指针,不拷贝数据
{
pid->Kp = 20; // 直接修改原来的结构体
}
int main()
{
PidController pid = {10, 10, 10}; //提前定义好普通结构体变量
updatePid(&Pid );
}
2.2.3结构体数组
对于结构体数组来讲,其定义为:
PidController pidArr[3];
如需调用结构体内的成员:
pidArr[0].Ki = 1;
pidArr[0].err = 2;
pidArr[1].Ki = 5;
pidArr[1].err = 2;
pidArr[2].Kp = 10;
对于结构体数组来讲,其优点为:所有结构体对象在内存中是连续存储的。
2.2.4结构体指针数组
/暂缺,等我后续补充
三、全局变量
全局变量:在函数外定义的变量,其生命周期是整个程序执行时间,在程序启动时就会分配内存。
过多的使用全局变量是一个并不太好的编程习惯,过多的全局变量会使得代码的可维护性差,并且耦合程度高,使得代码复用变得困难。
3.1全局变量的应用
关于全局变量的使用很简单,即在.c文件中的函数外部定义即可。
其正确使用如下:
//led.c文件中
uint16_t led_state = 0;
void led_init()
{
}
//main.c文件中
extern uint16_t led_state;
int main()
{
}
3.2全局变量使用的常见错误
关于全局变量的使用,还存在这一些错误操作。如:
//在led.h文件中
uint16 led_state = 0;
//在main.c文件中
extern uint16 led_state = 0;
错误操作1:
当在.h文件中定义变量时,假如该.h文件被#include引用,则会造成全局变量在多个编译单元中重复定义,链接器会因为符号重复定义而报错。
头文件中是用来声明的,并不能用来定义。
错误操作2:
extern关键词是告诉编译器该变量在其他地方,并不能用来定义,其也是只能用来声明。
(这些是我之前经常出错的点)
3.3关键词static
关键词static:将全局变量的作用域限制在当前文件内,即使其他文件 #include
了该头文件,也无法访问此全局变量。它不会暴露给其他源文件。
通过该关键词,可以将全局变量限制在当前文件中,避免在其他文件中被意外访问或修改。
四、局部变量
4.1局部变量的定义
局部变量可以直接理解为在函数内部定义的变量
void calculate() {
int result = 0; // 这是一个普通的局部变量
for (int i = 0; i < 10; i++) {
result += i;
}
}
上述代码中的变量result为局部变量,其每当程序进入函数calculate则会为变量result分配内存(sram),执行完函数之后改内存就会释放掉。可以一定程度的节约单片机资源。
4.2关键词static
提到局部变量就不得不说到关键词static,使用static关键词修饰之后,局部变量count的生命周期变成了整个程序运行期间。简单来说就是程序执行完之后,这个变量依旧还存在,并不会被清除。
void calculate() {
static int count; // 静态局部变量
count ++;
}
对于这个关键词,最常见的应用就是计进入某个函数的次数。
如上述程序的变量count,其数值就是为程序进入函数calculate的值。
五、常量
在c语言当中,常量的定义为:“在程序执行过程中不能被修改的量”
常量的定义有两种方法:
- 通过关键词const来定义
- 通过宏定义#define
5.1使用const关键词定义的优缺点
- 优点
- 通过const定义的变量是具备有符号、无符号等数据类型
- 通过const定义的变量是可以在调试器中查看和修改的
const定义的变量依旧存储在内存(sram)当中,。
关键词const的作用是让 变量 变成只读属性,其任是一个变量。
5.2使用#define宏定义的优缺点
- 优点
- 宏定义不占用存储空间,即不存储在sram当中,也不存储在flash当中。仅仅是编译前的文本替换
- 缺点
- 通过宏定义的常量,其无法再调试器中查看和修改,不方便我们调试。
- 并没有有符号,无符号这些类型,其只是简单的文本替换。