全局变量和局部变量
生存周期:从定义位置开始到该变量被释放
作用域:变量的有效作用范围
定义位置:函数体内 函数体外
存储区域:
静态区 栈区 堆区 代码常量区
全局变量:
生存周期:从定义位置开始到文件结束
作用域:定义该变量的整个文件
定义位置:函数体外,经常定义在主函数的上方
存储区域:静态区 ---- 自动申请,自动释放
局部变量:
生存周期:从定义位置开始到定义它的函数结束
作用域: 定义该变量的函数体内
定义位置:函数体内
存储区域:栈区 --- 变量先入后出
当全局变量 和 局部变量重名时,以局部变量为主
typedef
作用:给数据类型取别名
u8 u16 u32
定义格式:
typedef 数据类型 别名;
预处理指令
以“#”号开头的预处理命令
预处理就是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所做的工作
1.宏定义
允许用一个标识符来表示一个字符串,称为“宏”
使用格式:
#define 标识符 字符串
注意:是使用标识符原样替换字符串
两种情况:
(1)不带参的宏定义
eg:
#define MAX 255
#define PI 3.1415
(2)带参宏定义
#define f(x) x+2
2.文件包含
作用:包含一个头文件,之后就可以使用该文件中的函数
形式:
#include <头文件.h> ---- 本身就有的文件
#include "头文件.h" ---- 自定义的头文件
这两种形式仅仅是优先搜索路径不同
3.条件编译
预处理程序提供了条件编译的功能,可以按不同的条件去编译不同的程序部分,因此产生
不同的目标代码文件
条件编译有三种形式,分别介绍如下。
1)第一种形式。
#ifdef 标识符
程序段1
#else
程序段2
#endif
它的功能是,如果标识符已被“#define”命令定义过,则对“程序段 1”进行编译;否则对“程序段 2”进
行编译。如果没有“程序段 2”(它为空),本格式中的#else 可以没有,即可以写为如下形式:
#ifdef 标识符
程序段
#endif
#endif一定要存在
2)第二种形式。
#ifndef 标识符
程序段1
#else
程序段2
#endif
与第一种形式的区别是将“ifdef”改为“ifndef”。它的功能是,如果标识符未被“#define”命令定义过则对
“程序段 1”进行编译,否则对“程序段 2”进行编译。这与第一种形式的功能正相反。
条件编译 和 if-else 的不同
条件编译时未参与编译的程序段不会被差错,因为程序的查错在编译阶段
if-else 根据条件决定哪部分代码被执行,哪部分代码不被执行,但是,哪怕代码没有执行,也是
会参与编译的
在嵌入式C语言中,经常会使用第二种形式编写头文件
以学生管理系统为例:
将相关的函数全部封装在student.c当中,将student.c 中的函数在student.h文件中声明
头文件编写格式:
#ifndef 标识符
//该标识符只要是工程中没有出现过的标识符即可,一般写成文件名的大写形式
#define 标识符
//函数声明
#endif
3)第三种形式
#if 常量表达式
程序段1
#else
程序段2
#endif
它的功能是,如果常量表达式的值为“真”(非 0),则对“程序段 1”进行编译,否则对“程序段 2”进行
编译。因此,可以使程序在不同条件下完成不同的功能。
static
关键字:修饰变量存储在静态区
变量定义在函数体外,为全局变量,存储在静态区
如果在定义时没有初始化,则静态区会自动初始化全局变量,默认初始值是0
变量定义在函数体内:局部变量。局部变量如果没有使用 static 修饰,就是默认
动态局部变量(使用auto修饰),自动分配,自动释放,此时是存储在栈区,栈区
的特点先入后出,不会自动初始化空间,也就是说,局部变量使用前需要考虑是否
要初始化;
局部变量也可以使用static去修饰,变成静态局部变量,存储在静态区,被static
修饰的变量只会被定义一次 和 初始化一次
注意:无论是动态局部变量 还是 静态局部变量,它们的作用域没有改变,仍然是
在定义它的函数当中,但是被声明成静态局部变量,它的生存周期改变了,会随着
文件的结束而释放
对于static关键字,在C语言中,主要应用分为:
①在函数体内,将变量声明成静态变量,那么该变量在程序运行过程中的值是可保留的
②在模块内(在函数体外) --- 全局变量,一个被声明成静态变量的变量,在模块内可以
被其他函数访问,但是不能被模块外的其他函数访问
/*
补充:extern
声明变量为外部变量
*/
③在模块内一个被声明成静态的函数只能被该模块内的其他函数调用,不能被其他模块的函数调用,
也就是说,这个函数被限制在声明它的模块的本地范围使用
const
关键字:修饰变量的空间只读
int a = b;
//a : 变量,对a的空间进行写操作
//b :变量/常量,对b的空间进行读操作
如果使用 const 对空间进行修饰,则该空间只能读,不能写
对const修饰的空间必须要初始化,否则之后不能赋值
修饰普通变量
int a;
---> int const a;
const int a;
没有区别,都是就是变量的空间只读
修饰指针
#include <stdio.h>
int main(void)
{
int a = 3;
int b = 4;
int c = 5;
const int *p = &a;
int const *q = &b;//①②相同,const 修饰指针指向对象的空间 常量指针
int * const k = &c;//const 修饰指针自身的空间 指针常量
*p = 6;//不合法
p = &b;//合法
*q = 7;//不合法
q = &c;//合法
*k = 8;//合法
k = &a;//不合法
return 0;
}
const 修饰指针时,如果修饰的是指针指向对象的空间,则指针指向对象的空间不能被
重新赋值;如果修饰的是指针自身的空间,则指针不能重新指向
const int * const p;
//指针指向对象的空间 --- 只读
//指针自身的空间 --- 只读
指针常量 + 常量指针 = 常量指针常量
const 修饰空间的原则:离谁近,谁只读
const作用:
1.可以定义变量,修饰函数参数、函数返回值
2.让编译器保护那些希望不被改变的变量
被const修饰的对象,受到强制保护的,可以防止其他代码无意识的改变,减少bug
3.为阅读代码的人提供有用信息,表示该变量的某种特性
共用体
共用体(union,或称为联合体) 是一个能在同一个存储空间里 (但不同时) 存储不同类型数据的数据类型。
//共用体 和 结构体 的类型非常相似
定义结构体类型的形式:
struct 结构体类型名{
成员列表;
};
定义共用体类型的格式:
union 共用体类型名{
成员列表;
};
定义共用体变量的格式:
union 共用体类型名 共用体变量名;
//union 共用体类型名 是一种数据类型
共用体:成员共同使用同一块空间的数据类型
union stu2{
int score;
char sex;
char name[10];
};
共用体进行初始化:
union stu2 didi = {80};
对共用体变量进行初始化时,所赋值的数据将会以第一个成员的格式存储到空间当中,
对共用体的任意一个成员赋值,都会影响其他的成员
结构体 和 共用体的异同:
相同点:
①使用方法类似,都是先定义类型,再定义变量
②定义变量的方式也是类似的,有四种方法
③指定成员的方法也是一样的,变量名.成员名
不同点:
①定义时,使用的关键字不同,结构体:struct,共用体:union
②对于共用体赋值时,只需要赋值一个成员即可(第一个成员)
对于结构体赋值时,则需要对每一个成员赋值
③共用体成员占用一块空间,成员的起始地址相同,结构体每一个成员
都有自己独立的空间
枚举型
枚举类型的定义格式:
enum 类型名 {标识符1,标识符2,……,标识符n};
eg:
enum color {blue,pink,yellow,white,black,green,red};
定义了一种名为color的数据类型,这种数据类型定义的数据,只能是
blue,pink,yellow,white,black,green,red的数值中的一种
定义枚举类型时,{}中的标识符相当于整型常量,默认从0开始计数
定义枚举变量的格式:
enum 枚举类型 变量名;
枚举变量的赋值:
变量名 = 标识符;
枚举型的优点:
提供一些标识符去替代整型变量,提高代码的可阅读性
如何改变枚举型中标识符的值???
定义枚举类型时,直接对标识符赋值,该标识符以及它后面的标识符将全部改变(依次+1)