嵌入式C语言
文章目录
3 关键字
3 - 1 杂项
-
sizeof
sizeof
是C语言中的运算符,用于获取变量或类型的大小(以字节为单位)。在嵌入式C语言中,sizeof
也可以用于获取变量或类型的大小,但需要注意一些特定的情况和约束。sizeof(type) sizeof expression //其中,`type` 是一个数据类型,可以是基本数据类型(如 int、float、char 等),也可以是用户自定义的数据类型(如结构体、联合体等);`expression` 是一个表达式,可以是任何有效的表达式,包括变量、常量、运算等。
- 获取变量的大小:在嵌入式C语言中,可以使用
sizeof
运算符来获取变量的大小,例如sizeof(int)
可以得到int
类型的大小。返回值是一个无符号整数(unsigned int
)类型,表示变量在内存中占用的字节数。 - 获取类型的大小:
sizeof
运算符还可以用于获取类型的大小,例如sizeof(int)
可以得到int
类型的大小。这对于在嵌入式系统中手动进行内存分配和管理时非常有用。 - 编译时计算:
sizeof
运算符在编译时计算,而不是运行时计算。这意味着sizeof
的结果在编译时就会被确定,并在运行时保持不变。 - 可能的大小不同:在不同的嵌入式系统中,不同的编译器和目标硬件平台可能对变量和类型的大小有不同的规定,例如对于不同的字节对齐和数据对齐要求。因此,在嵌入式C语言编程中,需要注意对于不同目标硬件平台和编译器的兼容性。
- 注意结构体和联合体的大小:在嵌入式C语言中,结构体和联合体的大小可能受到编译器的优化和对齐规则的影响。因此,在使用
sizeof
获取结构体和联合体的大小时,需要考虑编译器和目标硬件平台的约定,以确保正确计算其大小。
- 获取变量的大小:在嵌入式C语言中,可以使用
-
return
return
是C语言中的关键字,用于在函数中返回值或者退出函数的执行。在嵌入式C语言中,return
的使用与普通的C语言中一样,但需要注意一些特定的情况和约束。- 函数返回值:在嵌入式C语言中,函数可以通过
return
语句返回一个值给调用者。返回值的类型和值应该与函数的声明和定义中一致。 - 函数的返回类型:在嵌入式C语言中,函数的返回类型可以是基本数据类型(如 int、float、char 等)或者用户自定义的数据类型(如结构体、联合体等)。返回类型应该在函数的声明和定义中明确定义。
- 函数返回值的传递方式:在嵌入式C语言中,函数的返回值可以通过寄存器、栈或者其他方式传递给调用者,具体的传递方式取决于编译器和目标硬件平台的约定。
- 函数的返回值大小:在嵌入式C语言中,函数的返回值大小应该考虑到目标硬件平台的字节对齐和数据对齐的要求,以避免因不合适的数据对齐导致的性能和功能问题。
- 函数返回值的错误处理:在嵌入式系统中,错误处理是一个重要的考虑因素。函数的返回值可以用于表示函数执行的结果,例如成功或失败。嵌入式C语言中常常使用特定的错误码或者错误状态来表示函数执行过程中可能发生的错误情况。
- 函数返回值:在嵌入式C语言中,函数可以通过
3 - 2 数据类型
-
char
char
是C语言中的一种基本数据类型,用于表示字符数据。在嵌入式C语言中,char
类型通常用于表示单个字符或小范围的整数值。以下是关于
char
类型的一些重要知识点:- 大小:
char
类型通常占用一个字节(8位)的内存空间。在嵌入式系统中,char
类型的大小通常是1字节,但这并不是C语言标准的要求,因此在不同的嵌入式系统中可能会有不同的实现。 - 范围:
char
类型可以表示 -128 到 127(有符号char
)或 0 到 255(无符号char
)之间的整数值,取决于编译器的实现和编译选项。在嵌入式系统中,通常使用无符号char
来表示字符数据。 - ASCII编码:
char
类型通常用于表示ASCII编码的字符数据,包括字母、数字、标点符号等。ASCII编码是一种常用的字符编码标准,将字符映射为整数值。 - 字符串:在C语言中,
char
类型的数组通常用于表示字符串,即一系列字符的序列。字符串以空字符(‘\0’)作为结束符,因此在处理字符串时需要注意字符串的结尾。 - 输入输出:
char
类型可以通过标准输入输出函数(如scanf
和printf
)来进行输入和输出操作。可以使用%c
格式化字符串来读取和打印char
类型的数据。 - 字节操作:在嵌入式系统中,
char
类型常用于进行字节级别的操作,例如位操作、字节拷贝、字节填充等。
- 大小:
-
int
int
是C语言中的一种基本数据类型,用于表示整数数据。在嵌入式C语言中,int
类型通常用于表示整数值,包括正数、负数和零。以下是关于
int
类型的一些重要知识点:- 大小:
int
类型的大小在不同的嵌入式系统中可能会有不同的实现,通常占用2字节(16位)、4字节(32位)或者更多的内存空间。在嵌入式系统中,int
类型的大小通常根据硬件架构和编译器的实现来确定。 - 范围:
int
类型可以表示的整数范围取决于其大小,例如16位int
可表示的整数范围约为 -32,768 到 32,767,32位int
可表示的整数范围约为 -2,147,483,648 到 2,147,483,647,具体取决于编译器的实现和编译选项。 - 运算:
int
类型可以进行基本的算术运算,例如加法、减法、乘法、除法和取余等。在进行整数运算时需要注意溢出和截断的问题,特别是在嵌入式系统中,因为硬件限制和资源有限可能会导致溢出和截断的情况。 - 格式化输出:
int
类型可以通过标准输入输出函数(如scanf
和printf
)来进行输入和输出操作。可以使用%d
格式化字符串来读取和打印int
类型的数据。 - 整数类型修饰符:C语言还提供了一些整数类型修饰符,例如
short
、long
和unsigned
等,可以用来调整int
类型的大小和范围,以适应不同的需求。 - 整数运算规则:在进行整数运算时,C语言遵循一些特定的规则,例如整数溢出、截断和补码表示等。嵌入式C语言开发者需要了解这些规则,并在编程中遵循它们,以确保正确的运算结果。
- 整数常量:在C语言中,整数常量默认为
int
类型,可以通过后缀添加U
、L
、LL
等来表示无符号整数、长整数和长长整数等。
- 大小:
-
long
long
是C语言中的一种整数数据类型,用于表示较大范围的整数值。在嵌入式C语言中,long
类型通常用于表示比int
类型更大范围的整数值。以下是关于
long
类型的一些重要知识点:- 大小:
long
类型的大小在不同的嵌入式系统中可能会有不同的实现,通常占用4字节(32位)或者8字节(64位)内存空间,取决于硬件架构和编译器的实现。 - 范围:
long
类型可以表示的整数范围比int
类型更大,通常在 -2,147,483,648 到 2,147,483,647(32位long
)或者 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807(64位long
)之间,具体取决于编译器的实现和编译选项。 - 运算:
long
类型可以进行基本的算术运算,例如加法、减法、乘法、除法和取余等,类似于int
类型。在进行长整数运算时需要注意溢出和截断的问题,特别是在嵌入式系统中。 - 格式化输出:
long
类型可以通过标准输入输出函数(如scanf
和printf
)来进行输入和输出操作。可以使用%ld
或%lu
格式化字符串来读取和打印带符号或无符号的long
类型数据。 - 整数类型修饰符:C语言还提供了一些整数类型修饰符,例如
short
、unsigned long
和long long
等,可以用来调整long
类型的大小和范围,以适应不同的需求。 - 整数运算规则:在进行
long
类型的运算时,也需要遵循C语言的整数运算规则,包括溢出、截断和补码表示等。 - 整数常量:在C语言中,整数常量默认为
int
类型,可以通过后缀添加U
、L
、LL
等来表示无符号整数、长整数和长长整数等。例如,12345L
表示长整数常量。
- 大小:
-
short
short
是C语言中的一种整数数据类型,用于表示较小范围的整数值。在嵌入式C语言中,short
类型通常用于表示比int
类型更小范围的整数值。以下是关于
short
类型的一些重要知识点:- 大小:
short
类型的大小在不同的嵌入式系统中可能会有不同的实现,通常占用2字节(16位)内存空间,取决于硬件架构和编译器的实现。 - 范围:
short
类型可以表示的整数范围通常在 -32,768 到 32,767 之间,具体取决于编译器的实现和编译选项。因为short
类型的范围较小,因此在表示较小的整数值时可以节省内存空间。 - 运算:
short
类型可以进行基本的算术运算,例如加法、减法、乘法、除法和取余等,类似于int
类型。在进行短整数运算时需要注意溢出和截断的问题,特别是在嵌入式系统中。 - 格式化输出:
short
类型可以通过标准输入输出函数(如scanf
和printf
)来进行输入和输出操作。可以使用%hd
或%hu
格式化字符串来读取和打印带符号或无符号的short
类型数据。 - 整数类型修饰符:C语言还提供了一些整数类型修饰符,例如
unsigned short
和short int
等,可以用来调整short
类型的大小和范围,以适应不同的需求。 - 整数运算规则:在进行
short
类型的运算时,也需要遵循C语言的整数运算规则,包括溢出、截断和补码表示等。 - 整数常量:在C语言中,整数常量默认为
int
类型,可以通过后缀添加U
、L
、LL
等来表示无符号整数、长整数和长长整数等。例如,12345U
表示无符号短整数常量。
- 大小:
-
unsigned
unsigned
是C语言中的一个类型修饰符,用于声明无符号类型的变量或常量。在嵌入式C语言中,unsigned
通常用于表示无符号整数值,即只能表示非负整数的类型。以下是关于
unsigned
类型修饰符的一些重要知识点:- 无符号整数:
unsigned
用于声明无符号整数类型,它可以用于char
、short
、int
、long
和long long
等整数类型。无符号整数类型不包含负数,范围从0到最大正整数值。 - 大小和范围:
unsigned
类型的大小和范围取决于编译器的实现和硬件架构,通常与相应的有符号整数类型大小相同,但范围从0到最大正整数值,而不包含负数。例如,unsigned int
通常占用4字节(32位)内存空间,可以表示的范围从0到4294967295。 - 运算:
unsigned
类型的运算规则与有符号整数类型类似,可以进行基本的算术运算,例如加法、减法、乘法、除法和取余等。在进行无符号整数运算时需要注意溢出和截断的问题,特别是在嵌入式系统中。 - 格式化输出:
unsigned
类型可以通过标准输入输出函数(如scanf
和printf
)来进行输入和输出操作。可以使用%u
格式化字符串来读取和打印无符号整数。 - 整数常量:在C语言中,整数常量默认为
int
类型,可以通过后缀添加U
、L
、LL
等来表示无符号整数、长整数和长长整数等。例如,12345U
表示无符号整数常量。 - 与有符号类型的转换:在进行无符号类型与有符号类型之间的转换时需要注意符号位的处理,可能会导致数值的变化和不确定的结果。
- 无符号整数:
-
signed
signed
是C语言中的一个类型修饰符,用于声明有符号类型的变量或常量。在嵌入式C语言中,signed
通常用于表示有符号整数值,即可以表示正整数和负整数的类型。以下是关于
signed
类型修饰符的一些重要知识点:- 有符号整数:
signed
用于声明有符号整数类型,它可以用于char
、short
、int
、long
和long long
等整数类型。有符号整数类型可以包含正数、负数和零,范围从最小负整数值到最大正整数值。 - 大小和范围:
signed
类型的大小和范围取决于编译器的实现和硬件架构,通常与相应的无符号整数类型大小相同,但范围从最小负整数值到最大正整数值,包括零。例如,signed int
通常占用4字节(32位)内存空间,可以表示的范围从 -2147483648 到 2147483647。 - 运算:
signed
类型的运算规则与无符号整数类型类似,可以进行基本的算术运算,例如加法、减法、乘法、除法和取余等。在进行有符号整数运算时需要注意溢出和截断的问题,特别是在嵌入式系统中。 - 格式化输出:
signed
类型可以通过标准输入输出函数(如scanf
和printf
)来进行输入和输出操作。可以使用%d
、%i
或%ld
等格式化字符串来读取和打印有符号整数。 - 整数常量:在C语言中,整数常量默认为
int
类型,可以通过后缀添加U
、L
、LL
等来表示无符号整数、长整数和长长整数等。例如,-12345
表示有符号整数常量。 - 与无符号类型的转换:在进行有符号类型与无符号类型之间的转换时需要注意符号位的处理,可能会导致数值的变化和不确定的结果。
- 有符号整数:
-
char 和 unsigned char
在嵌入式系统中,
char
和unsigned char
在使用上有一些区别,主要体现在以下几个方面:- 表示范围:
char
是有符号的,而unsigned char
是无符号的。在标准的 C 语言中,char
的范围可以是 -128 到 127(取决于平台的实现),而unsigned char
的范围是 0 到 255。这意味着char
类型可能包含负数,而unsigned char
类型只包含非负数。 - 默认符号性:在嵌入式系统中,编译器对
char
类型的符号性没有明确的规定,因此char
类型的符号性可能会因编译器和平台而异。这可能导致在不同的编译器和平台上,char
类型的符号性不一致,可能会引发一些问题。而unsigned char
则是无符号的,不受符号性的影响。 - 算术运算:
char
类型在进行算术运算时,可能会出现溢出和符号截断的情况。例如,对一个char
类型的变量进行加法运算,如果结果超过了char
类型的范围,可能会发生溢出。而unsigned char
类型则不会出现溢出和符号截断的问题,因为它是无符号的。 - 字节操作:在处理二进制数据、字节流等底层操作时,
unsigned char
类型更常用,因为它是无符号的,不会出现符号截断和溢出的问题。而char
类型可能因为符号性和溢出问题导致处理结果不符合预期。
因此,在嵌入式系统中,通常推荐使用
unsigned char
类型来处理字节数据、二进制数据等底层操作,避免因char
类型的符号性和溢出问题导致的错误。同时,应当注意编译器和平台对char
类型符号性的不确定性,避免在不同编译器和平台之间出现不一致的行为。 - 表示范围:
-
float
float
是C语言中的一种浮点数类型,用于表示带有小数部分的实数值。在嵌入式C语言中,float
类型通常用于处理需要精确表示小数的应用场景,例如测量数据、传感器数据、控制算法等。以下是关于
float
类型的一些重要知识点:- 浮点数表示:
float
类型采用IEEE 754标准定义的单精度浮点数格式,占用4字节(32位)内存空间。其中,1位用于表示符号位(正负号),8位用于表示指数部分,23位用于表示尾数部分。 - 数值范围:
float
类型可以表示的数值范围通常为正负3.4E-38 到正负3.4E+38,可以表示的精度通常为6到7位有效数字。 - 运算:
float
类型支持基本的算术运算,包括加法、减法、乘法、除法和取余等。但需要注意浮点数运算可能存在精度损失和舍入误差的问题,因此在进行浮点数运算时需要谨慎处理。 - 格式化输出:
float
类型可以通过标准输入输出函数(如scanf
和printf
)来进行输入和输出操作。可以使用%f
、%e
或%g
等格式化字符串来读取和打印浮点数。 - 浮点数常量:在C语言中,浮点数常量默认为
double
类型,可以通过后缀添加f
来表示float
类型的浮点数常量。例如,3.14f
表示float
类型的浮点数常量。 - 数学库函数:C语言提供了丰富的数学库函数(如
math.h
头文件中的函数),用于进行浮点数的各种数学运算,例如求平方根、三角函数、指数函数等。 - 特殊值:
float
类型还支持表示特殊的浮点数值,例如正无穷大、负无穷大和NaN(不是一个数字)等,这些特殊值在处理异常情况时可能会有用。
- 浮点数表示:
-
double
float
是C语言中的一种浮点数类型,用于表示带有小数部分的实数值。在嵌入式C语言中,float
类型通常用于处理需要精确表示小数的应用场景,例如测量数据、传感器数据、控制算法等。以下是关于
float
类型的一些重要知识点:- 浮点数表示:
float
类型采用IEEE 754标准定义的单精度浮点数格式,占用4字节(32位)内存空间。其中,1位用于表示符号位(正负号),8位用于表示指数部分,23位用于表示尾数部分。 - 数值范围:
float
类型可以表示的数值范围通常为正负3.4E-38 到正负3.4E+38,可以表示的精度通常为6到7位有效数字。 - 运算:
float
类型支持基本的算术运算,包括加法、减法、乘法、除法和取余等。但需要注意浮点数运算可能存在精度损失和舍入误差的问题,因此在进行浮点数运算时需要谨慎处理。 - 格式化输出:
float
类型可以通过标准输入输出函数(如scanf
和printf
)来进行输入和输出操作。可以使用%f
、%e
或%g
等格式化字符串来读取和打印浮点数。 - 浮点数常量:在C语言中,浮点数常量默认为
double
类型,可以通过后缀添加f
来表示float
类型的浮点数常量。例如,3.14f
表示float
类型的浮点数常量。 - 数学库函数:C语言提供了丰富的数学库函数(如
math.h
头文件中的函数),用于进行浮点数的各种数学运算,例如求平方根、三角函数、指数函数等。 - 特殊值:
float
类型还支持表示特殊的浮点数值,例如正无穷大、负无穷大和NaN(不是一个数字)等,这些特殊值在处理异常情况时可能会有用。
- 浮点数表示:
-
void
void
是C语言中的一种特殊类型,用于表示没有返回值或没有参数的函数。在嵌入式C语言中,void
类型通常用于定义函数的返回值类型或函数的参数类型。以下是关于
void
类型的一些重要知识点:- 返回值类型:函数可以声明或定义为
void
类型,表示函数没有返回值。这意味着函数不会返回任何数值,或者函数的计算结果不需要作为返回值使用。例如,void func()
表示func
函数没有返回值。 - 参数类型:函数可以接受
void
类型的参数,表示函数不需要接受任何参数。这意味着函数在调用时不需要传递任何参数值。例如,void func(void)
表示func
函数不接受任何参数。 - 函数指针:
void
类型可以用于定义函数指针,即指向函数的指针变量。函数指针可以用于传递函数地址,实现函数回调等功能。 - 指针类型:
void
类型的指针可以用于指向任何类型的数据,包括基本数据类型(如int
、float
、double
等)和自定义数据类型(如结构体、联合体等)。这种用法称为 “泛型指针”,可以在一些特定场景下灵活使用。 - 空指针:
void*
类型是一种特殊的指针类型,称为 “空指针”,用于表示指向任何类型的指针,但没有具体的类型信息。可以用于在函数参数中传递不同类型的指针,或者在函数内部进行类型转换等。 - 注意事项:使用
void
类型时需要注意类型安全性和类型转换的问题。因为void
类型没有具体的类型信息,所以在使用时需要确保类型的一致性,并避免潜在的类型错误。
- 返回值类型:函数可以声明或定义为
3 - 3 自定义数据类型
-
struct
struct
是C语言中用于定义自定义的复合数据类型的关键字。通过struct
关键字可以定义一个包含多个不同数据类型成员的数据结构。- 定义结构体:使用
struct
关键字可以定义一个结构体类型,并在定义中指定结构体的成员。例如:
cCopy codestruct Point { int x; int y; };
上面的例子定义了一个名为
Point
的结构体类型,其中包含了两个int
类型的成员变量x
和y
。- 创建结构体变量:可以使用结构体类型创建结构体变量,也称为结构体的实例。例如:
cCopy code struct Point p1; // 创建一个名为 p1 的 Point 类型的结构体变量
- 访问结构体成员:可以使用成员运算符
.
来访问结构体变量的成员。例如:
cCopy codep1.x = 10; // 访问 p1 结构体变量的 x 成员,并赋值为 10 p1.y = 20; // 访问 p1 结构体变量的 y 成员,并赋值为 20
- 结构体作为函数参数:可以将结构体作为函数的参数传递。可以传递结构体变量本身,或者传递指向结构体的指针。例如:
cCopy codevoid printPoint(struct Point p) { printf("x = %d, y = %d\n", p.x, p.y); } struct Point p1; p1.x = 10; p1.y = 20; printPoint(p1); // 将 p1 结构体变量传递给 printPoint 函数
- 结构体嵌套:可以在结构体中嵌套其他结构体,从而形成更复杂的数据结构。例如:
cCopy codestruct Rectangle { struct Point topLeft; struct Point bottomRight; }; struct Rectangle rect; rect.topLeft.x = 0; rect.topLeft.y = 0; rect.bottomRight.x = 10; rect.bottomRight.y = 20;
- 位域:结构体还可以使用位域(bit field)来定义成员的位宽,用于对某些数据进行位级别的操作。例如:
cCopy codestruct Flags { unsigned int flag1 : 1; // 1位宽 unsigned int flag2 : 2; // 2位宽 }; struct Flags f; f.flag1 = 1; f.flag2 = 3; // 二进制表示 11,超过了 2 位宽的范围,可能会导致未定义行为
- 定义结构体:使用
-
union
union
是C语言中一种特殊的数据类型,类似于struct
,但是所有的成员共享同一块内存空间,只能同时存储一个成员的值。union
的定义方式和使用方式与struct
类似,但是在内存布局和存储方式上有一些不同之处。以下是关于
union
的一些重要知识点:- 定义联合:使用
union
关键字可以定义一个联合类型,并在定义中指定联合的成员。例如:
cCopy codeunion Data { int x; float y; char z; };
上面的例子定义了一个名为
Data
的联合类型,其中包含了三个不同类型的成员变量x
、y
和z
,分别为int
、float
和char
类型。- 创建联合变量:可以使用联合类型创建联合变量,也称为联合的实例。例如:
cCopy code union Data d1; // 创建一个名为 d1 的 Data 类型的联合变量
- 访问联合成员:可以使用成员运算符
.
来访问联合变量的成员。与struct
不同的是,联合只能同时存储一个成员的值,因此在访问一个成员时,其他成员的值会被覆盖。例如:
cCopy coded1.x = 10; // 访问 d1 联合变量的 x 成员,并赋值为 10 printf("x = %d\n", d1.x); // 输出 x 成员的值 d1.y = 3.14; // 访问 d1 联合变量的 y 成员,并赋值为 3.14 printf("y = %f\n", d1.y); // 输出 y 成员的值,注意此时 x 的值被覆盖
- 联合与结构体的区别:与
struct
不同,union
的所有成员共享同一块内存空间,因此只能同时存储一个成员的值,而struct
的成员则会占据独立的内存空间。这也是union
的一个特点,可以在有限的内存空间中存储不同类型的值,但需要注意访问成员时的值覆盖情况。 - 联合的应用:联合在嵌入式系统中常用于处理硬件寄存器、通信协议、数据包解析等场景,可以节省内存空间并实现灵活的数据访问方式。
需要注意联合在访问成员时的值覆盖情况,避免引发未定义行为
- 定义联合:使用
-
enum
enum
(枚举)是C语言中一种用于定义命名整数常量的数据类型。enum
用于创建一组具有相互关联的命名常量,这些常量通常用于表示一组离散的取值范围。enum
在编程中常用于表示状态、选项、标志位等。以下是关于
enum
的一些重要知识点:- 定义枚举类型:使用
enum
关键字可以定义一个枚举类型,并在定义中指定枚举的取值范围。例如:
cCopy codeenum Color { RED, GREEN, BLUE };
上面的例子定义了一个名为
Color
的枚举类型,其中包含了三个命名常量RED
、GREEN
和BLUE
,它们的取值分别为0、1、2。- 创建枚举变量:可以使用枚举类型创建枚举变量,也称为枚举的实例。例如:
cCopy code enum Color c1; // 创建一个名为 c1 的 Color 类型的枚举变量
- 赋值和比较枚举变量:可以使用枚举常量对枚举变量进行赋值和比较。例如:
cCopy codec1 = RED; // 将 c1 的值赋为 RED if (c1 == BLUE) { // 比较 c1 的值是否等于 BLUE // do something }
- 枚举常量的默认值:枚举常量的默认值从0开始,依次递增。但可以通过指定值来自定义枚举常量的值。例如:
cCopy codeenum Season { SPRING = 1, SUMMER = 2, AUTUMN = 3, WINTER = 4 };
上面的例子定义了一个名为
Season
的枚举类型,并指定了每个枚举常量的值。- 枚举的应用:枚举在编程中常用于表示一组相关的选项、状态或标志位,可以提高代码的可读性和可维护性。枚举也可以用于替代使用宏定义的常量,从而避免宏带来的潜在问题。
需要注意枚举类型的取值范围和枚举常量的默认值,以及在比较枚举变量时使用枚举常量而不是直接比较整数值。
- 定义枚举类型:使用
-
typedef
typedef
是 C 语言中的一个关键字,用于为现有的数据类型创建一个新的类型名称。它可以用于定义自定义的数据类型,使代码更加清晰、易读和可维护。以下是关于
typedef
的一些重要知识点:- 类型定义:
typedef
用于给现有的数据类型起一个新的名字,从而创建一个新的类型。例如:
cCopy codetypedef int myInt; // 为 int 类型定义了一个新的类型名 myInt myInt num1 = 10; // 使用 myInt 定义的新类型名声明变量
- 自定义类型:
typedef
可以用于创建自定义的数据类型,使代码更加清晰和易读。例如:
cCopy codetypedef struct { int x; int y; } Point; // 定义了一个名为 Point 的结构体类型 Point p1; // 使用 Point 类型声明结构体变量
- 复杂类型别名:
typedef
还可以用于创建复杂的类型别名,包括指针、数组和函数等。例如:
cCopy codetypedef int* IntPtr; // 定义了一个类型名 IntPtr,表示 int 类型的指针 IntPtr p2; // 使用 IntPtr 类型声明 int 指针变量
- 可读性和可维护性:
typedef
可以用于提高代码的可读性和可维护性,通过为常用的数据类型和复杂的类型起一个有意义的名称,可以使代码更加清晰和易于理解。
需要注意的是,
typedef
只是为现有的类型创建一个新的类型名称,并不创建新的数据类型。因此,typedef
创建的新类型和原始类型在类型检查和内存布局上是相同的。 - 类型定义:
3 - 4 类型修饰符
-
auto
auto
是 C 语言中的一种关键字,用于声明自动存储类的变量。自动存储类是 C 语言中的默认存储类,也是最常用的存储类。以下是关于
auto
的一些重要知识点:- 变量的存储类:存储类用于描述变量在内存中的存储方式和生命周期。C 语言中有四种存储类:自动存储类、静态存储类、寄存器存储类和外部存储类。其中,
auto
是自动存储类,它是默认的存储类,通常可以省略不写。 - 自动存储类:自动存储类的变量在函数或代码块的作用域内创建,当函数或代码块执行完毕时,自动存储类的变量会被自动销毁。例如:
cCopy codevoid func() { auto int a = 10; // 声明一个自动存储类的变量 a,并初始化为 10 // do something with a } // a 的生命周期在 func 函数结束时结束
auto
的推导类型:C11 标准引入了auto
的推导类型特性,可以用于在变量声明时自动推导变量的类型。例如:
cCopy codeauto a = 10; // a 被自动推导为 int 类型 auto b = 3.14; // b 被自动推导为 double 类型 auto c = 'A'; // c 被自动推导为 char 类型
需要注意的是,
auto
的推导类型是根据初始化表达式的类型进行推导的,因此变量的类型会根据初始化表达式的类型而变化。auto
的使用场景:auto
通常用于简化变量声明,减少重复的类型声明,并且可以提高代码的可维护性。auto
特性在处理复杂的类型、模板和泛型编程时也非常有用。
- 变量的存储类:存储类用于描述变量在内存中的存储方式和生命周期。C 语言中有四种存储类:自动存储类、静态存储类、寄存器存储类和外部存储类。其中,
-
register
register
是 C 语言中的一个关键字,用于向编译器建议将某个变量存储在寄存器中,以便在程序中更快地访问变量。然而,在现代编译器中,register
关键字的使用通常被忽略,因为编译器会自动优化变量的存储和访问方式,以提供最佳的性能。以下是关于
register
关键字的一些重要知识点:- 寄存器变量:
register
关键字用于声明一个变量应该存储在寄存器中,以便在程序中更快地访问。例如:
cCopy code register int counter; // 声明一个寄存器变量
- 优化:
register
关键字只是向编译器提供了一个建议,而不是强制要求将变量存储在寄存器中。编译器可以根据具体情况决定是否将变量存储在寄存器中,以便在程序中获得最佳的性能。 - 限制和用法:
register
关键字有一些限制和用法,例如:
register
变量不能有地址运算符&
,因为寄存器变量不存储在内存中,因此没有地址可取。register
变量不能声明为全局变量或静态变量,因为它们在程序的生命周期内需要保持固定的存储位置。register
变量的初始值应该是常量或编译时确定的值,因为寄存器变量在运行时可能不保存其值。
需要注意的是,
register
关键字在现代编译器中的作用已经大大减弱,因为编译器通常会自动进行优化,将变量存储在寄存器或内存中以获得最佳的性能。因此,在编写现代 C 代码时,不必过于依赖register
关键字,而应该将优化工作交给编译器来完成。 - 寄存器变量:
-
static
static
关键字在 C 语言中有两种用法:用于修饰变量和修饰函数。(访问是局部的,生命是全局的)
static
关键字在 C 语言中用于修饰变量时:
static
修饰局部变量: 当static
关键字用于修饰局部变量时,它会改变该变量在内存中的存储方式和生命周期。具体而言,使用static
修饰的局部变量会在程序的整个运行期间都存在,不会随着函数的调用而创建和销毁,而是在程序启动时创建,在程序结束时销毁。这意味着该变量的值在函数调用之间会保持持久性,可以用于在函数调用之间保留状态信息。此外,static
修饰的局部变量的存储位置通常位于静态存储区,而不是栈上,因此其内存地址在程序运行期间是固定的,不会随着函数的调用而变化。static
修饰全局变量: 当static
关键字用于修饰全局变量时,它会改变该变量的作用域和可见性。具体而言,使用static
修饰的全局变量只能在定义它的文件中访问,不能被其他文件访问,称为文件作用域。这样可以限制全局变量的可见性,避免在多个文件中出现同名的全局变量引起的命名冲突
总之,static
关键字对变量在内存的影响包括:改变局部变量的生命周期和存储位置,使其在函数调用之间保持持久性;改变全局变量的作用域和可见性,限制其只能在定义它的文件中访问。
static
关键字在 C 语言中用于修饰函数时,其作用是改变函数的链接属性,从而影响函数在内存中的存储和可见性。
- 静态链接属性:使用
static
修饰的函数具有静态链接属性,这意味着它们只能在定义它们的文件中访问,不能被其他文件访问,称为文件作用域。这样可以限制函数的可见性,避免在多个文件中出现同名的函数引起的命名冲突。 - 内部链接:使用
static
修饰的函数具有内部链接属性,这意味着它们在编译时会被编译器优化为只在当前编译单元内可见的符号,而不会被放入全局符号表。这样可以避免函数被其他编译单元引用,从而减小了目标文件和最终可执行文件的大小。
总之,static
关键字对函数在内存的影响包括:改变函数的链接属性,限制其只能在定义它的文件中访问,并且具有内部链接属性,只在当前编译单元内可见。
-
const
const
关键字在 C 语言中用于声明一个常量,表示该变量的值在其初始化后不可被修改。对于常量的修改操作将导致编译时错误。(本质当变量被修饰后,编译阶段其不能出现在等号左边)
const
关键字对变量在内存的影响主要有以下两个方面:- 只读属性:使用
const
修饰的变量在内存中具有只读属性,其值在初始化后不能被修改。这意味着在程序运行时,尝试对const
变量进行修改操作将导致错误。 - 编译时优化:由于
const
变量的值在编译时就已经确定,并且不能被修改,编译器可以进行优化,例如将const
变量的值直接嵌入到生成的机器代码中,而不需要在运行时读取内存中的变量值。这样可以提高程序的性能和效率。
需要注意的是,
const
关键字并不是完全保证变量不可修改的,因为通过指针或类型转换等方式仍然可以对const
变量进行修改。但在一般情况下,编译器会对const
变量进行检查,并在编译时发现对其修改的错误。 - 只读属性:使用
-
extern
extern
关键字在 C 语言中用于声明一个变量或函数的外部链接属性,表示该变量或函数在其他文件中定义或声明,并且在当前文件中只是作为一个引用。extern
关键字对变量和函数在内存的影响主要有以下两个方面:- 外部链接属性:使用
extern
声明的变量或函数具有外部链接属性,表示它们在其他文件中定义或声明。这意味着它们可以在其他文件中进行定义,并且可以被其他文件引用和访问。 - 声明而不定义:使用
extern
声明的变量或函数在当前文件中只是作为一个引用,并不会在当前文件中分配内存空间。这意味着在当前文件中使用extern
声明的变量或函数时,编译器会在链接时在其他文件中寻找其定义,并将其引用连接到正确的地址。
需要注意的是,
extern
关键字只是用于声明变量或函数的外部链接属性,并不会为其分配内存空间或提供实际的定义。在程序中使用extern
关键字时,需要在其他文件中提供相应的定义,以确保链接时能够找到正确的定义。 - 外部链接属性:使用
-
volatile
volatile
关键字在 C 语言中用于声明一个变量是易变的,即其值可能会在程序运行期间被意外地改变,从而告诉编译器不要对该变量进行优化,以确保每次访问都从内存中读取最新的值,而不是使用寄存器或缓存中的旧值。volatile
关键字对变量在内存的影响主要有以下两个方面:- 防止优化:使用
volatile
修饰的变量不会被编译器优化,编译器会在每次访问该变量时从内存中读取最新的值,而不是使用寄存器或缓存中的旧值。这对于那些可能被外部因素改变的变量(例如硬件寄存器)非常有用,可以防止编译器对其进行错误的优化。 - 禁用缓存:使用
volatile
修饰的变量的值不会被缓存在寄存器或缓存中,而是每次访问都会从内存中读取最新的值。这对于那些需要保证读取最新值的变量(例如多线程环境中的共享变量)非常有用,可以避免缓存导致的读取脏数据。
需要注意的是,
volatile
关键字只用于标识变量的易变性,并不提供任何线程同步的保证。在多线程环境中,如果需要保证对volatile
变量的原子性操作和线程同步,还需要使用其他的线程同步机制,例如互斥锁或原子操作。#include <stdio.h> volatile int counter = 0; // 使用 volatile 关键字声明一个易变的整型变量 void incrementCounter() { while (counter < 5) { printf("Counter: %d\n", counter); counter++; // 对易变变量进行自增操作 } } int main() { incrementCounter(); return 0; }
在上面的例子中,
counter
是一个被声明为volatile
的整型变量。incrementCounter()
函数中的while
循环会不断地读取counter
的值并输出,然后对其进行自增操作,直到counter
的值达到 5。如果没有使用
volatile
关键字,编译器可能会对counter
进行优化,将其缓存在寄存器或缓存中,从而导致while
循环内部的值一直是缓存中的旧值,而不是从内存中读取的最新值。但是使用了volatile
关键字后,编译器会在每次访问counter
时从内存中读取最新值,从而确保while
循环内部使用的是最新的值,保证了程序的正确性。 - 防止优化:使用
3 - 5 运算符
-
& |
位运算是一种在嵌入式系统和底层编程中常用的技术,可以通过对二进制位的操作来实现各种功能。以下是一些常见的位运算技巧:
- 位与操作用于屏蔽指定位:可以使用位与操作
&
来屏蔽一个或多个指定位,例如将一个整数的最低位设置为0:x = x & ~(1 << n)
,其中n
是指定的位数。 - 位或操作用于设置指定位:可以使用位或操作
|
来设置一个或多个指定位,例如将一个整数的最低位设置为1:x = x | (1 << n)
,其中n
是指定的位数。 - 位异或操作用于翻转指定位:可以使用位异或操作
^
来翻转一个指定位,例如将一个整数的最低位翻转:x = x ^ (1 << n)
,其中n
是指定的位数。 - 位移操作用于快速乘以或除以2的幂次方:左移操作
<<
可以将一个二进制数向左移动指定的位数,相当于乘以2的幂次方;右移操作>>
可以将一个二进制数向右移动指定的位数,相当于除以2的幂次方。 - 位测试操作用于检查指定位的值:可以使用位与操作
&
结合条件语句来检查一个指定位的值,例如检查一个整数的最低位是否为1:if (x & (1 << n)) { /* 指定位为1 */ } else { /* 指定位为0 */ }
,其中n
是指定的位数。 - 位计数操作用于统计二进制数中的位数:可以使用位与操作
&
结合循环和右移操作>>
来统计一个二进制数中有多少个位是1,从而实现位计数功能。
- 位与操作用于屏蔽指定位:可以使用位与操作
-
& *
在C语言中,
&
和*
分别有以下含义:&
运算符用于取地址操作:&
用于获取变量的地址,即变量在内存中的存储位置。例如:
cCopy codeint a = 10; int *ptr = &a; // 取得变量 a 的地址,并将其赋值给指针 ptr
在这个例子中,
&a
取得变量a
的地址,然后将其赋值给指针ptr
,使ptr
指向变量a
的内存地址。*
运算符用于间接访问操作:*
用于通过指针访问其指向的内存中的值。例如:
cCopy codeint a = 10; int *ptr = &a; int b = *ptr; // 通过指针 ptr 间接访问变量 a 的值,并将其赋值给变量 b
在这个例子中,
*ptr
将通过指针ptr
访问其指向的内存中的值,即变量a
的值,并将其赋值给变量b
。需要注意的是,
&
和*
运算符在不同的上下文中有不同的含义,使用时需要根据语境正确理解和使用。例如,&
用于取地址操作,将变量的地址取出;*
用于间接访问操作,通过指针访问指向的内存中的值。