嵌入式C必看(关键字)

嵌入式C语言

3 关键字

3 - 1 杂项

  • sizeof

    sizeof 是C语言中的运算符,用于获取变量或类型的大小(以字节为单位)。在嵌入式C语言中,sizeof 也可以用于获取变量或类型的大小,但需要注意一些特定的情况和约束。

    sizeof(type)
    sizeof expression
        
    //其中,`type` 是一个数据类型,可以是基本数据类型(如 int、float、char 等),也可以是用户自定义的数据类型(如结构体、联合体等);`expression` 是一个表达式,可以是任何有效的表达式,包括变量、常量、运算等。
    
    1. 获取变量的大小:在嵌入式C语言中,可以使用 sizeof 运算符来获取变量的大小,例如 sizeof(int) 可以得到 int 类型的大小。返回值是一个无符号整数(unsigned int)类型,表示变量在内存中占用的字节数。
    2. 获取类型的大小:sizeof 运算符还可以用于获取类型的大小,例如 sizeof(int) 可以得到 int 类型的大小。这对于在嵌入式系统中手动进行内存分配和管理时非常有用。
    3. 编译时计算:sizeof 运算符在编译时计算,而不是运行时计算。这意味着 sizeof 的结果在编译时就会被确定,并在运行时保持不变。
    4. 可能的大小不同:在不同的嵌入式系统中,不同的编译器和目标硬件平台可能对变量和类型的大小有不同的规定,例如对于不同的字节对齐和数据对齐要求。因此,在嵌入式C语言编程中,需要注意对于不同目标硬件平台和编译器的兼容性。
    5. 注意结构体和联合体的大小:在嵌入式C语言中,结构体和联合体的大小可能受到编译器的优化和对齐规则的影响。因此,在使用 sizeof 获取结构体和联合体的大小时,需要考虑编译器和目标硬件平台的约定,以确保正确计算其大小。
  • return

    return 是C语言中的关键字,用于在函数中返回值或者退出函数的执行。在嵌入式C语言中,return 的使用与普通的C语言中一样,但需要注意一些特定的情况和约束。

    1. 函数返回值:在嵌入式C语言中,函数可以通过 return 语句返回一个值给调用者。返回值的类型和值应该与函数的声明和定义中一致。
    2. 函数的返回类型:在嵌入式C语言中,函数的返回类型可以是基本数据类型(如 int、float、char 等)或者用户自定义的数据类型(如结构体、联合体等)。返回类型应该在函数的声明和定义中明确定义。
    3. 函数返回值的传递方式:在嵌入式C语言中,函数的返回值可以通过寄存器、栈或者其他方式传递给调用者,具体的传递方式取决于编译器和目标硬件平台的约定。
    4. 函数的返回值大小:在嵌入式C语言中,函数的返回值大小应该考虑到目标硬件平台的字节对齐和数据对齐的要求,以避免因不合适的数据对齐导致的性能和功能问题。
    5. 函数返回值的错误处理:在嵌入式系统中,错误处理是一个重要的考虑因素。函数的返回值可以用于表示函数执行的结果,例如成功或失败。嵌入式C语言中常常使用特定的错误码或者错误状态来表示函数执行过程中可能发生的错误情况。

3 - 2 数据类型

  • char

    char 是C语言中的一种基本数据类型,用于表示字符数据。在嵌入式C语言中,char 类型通常用于表示单个字符或小范围的整数值。

    以下是关于 char 类型的一些重要知识点:

    1. 大小:char 类型通常占用一个字节(8位)的内存空间。在嵌入式系统中,char 类型的大小通常是1字节,但这并不是C语言标准的要求,因此在不同的嵌入式系统中可能会有不同的实现。
    2. 范围:char 类型可以表示 -128 到 127(有符号 char)或 0 到 255(无符号 char)之间的整数值,取决于编译器的实现和编译选项。在嵌入式系统中,通常使用无符号 char 来表示字符数据。
    3. ASCII编码:char 类型通常用于表示ASCII编码的字符数据,包括字母、数字、标点符号等。ASCII编码是一种常用的字符编码标准,将字符映射为整数值。
    4. 字符串:在C语言中,char 类型的数组通常用于表示字符串,即一系列字符的序列。字符串以空字符(‘\0’)作为结束符,因此在处理字符串时需要注意字符串的结尾。
    5. 输入输出:char 类型可以通过标准输入输出函数(如 scanfprintf)来进行输入和输出操作。可以使用 %c 格式化字符串来读取和打印 char 类型的数据。
    6. 字节操作:在嵌入式系统中,char 类型常用于进行字节级别的操作,例如位操作、字节拷贝、字节填充等。
  • int

    int 是C语言中的一种基本数据类型,用于表示整数数据。在嵌入式C语言中,int 类型通常用于表示整数值,包括正数、负数和零。

    以下是关于 int 类型的一些重要知识点:

    1. 大小:int 类型的大小在不同的嵌入式系统中可能会有不同的实现,通常占用2字节(16位)、4字节(32位)或者更多的内存空间。在嵌入式系统中,int 类型的大小通常根据硬件架构和编译器的实现来确定。
    2. 范围:int 类型可以表示的整数范围取决于其大小,例如16位 int 可表示的整数范围约为 -32,768 到 32,767,32位 int 可表示的整数范围约为 -2,147,483,648 到 2,147,483,647,具体取决于编译器的实现和编译选项。
    3. 运算:int 类型可以进行基本的算术运算,例如加法、减法、乘法、除法和取余等。在进行整数运算时需要注意溢出和截断的问题,特别是在嵌入式系统中,因为硬件限制和资源有限可能会导致溢出和截断的情况。
    4. 格式化输出:int 类型可以通过标准输入输出函数(如 scanfprintf)来进行输入和输出操作。可以使用 %d 格式化字符串来读取和打印 int 类型的数据。
    5. 整数类型修饰符:C语言还提供了一些整数类型修饰符,例如 shortlongunsigned 等,可以用来调整 int 类型的大小和范围,以适应不同的需求。
    6. 整数运算规则:在进行整数运算时,C语言遵循一些特定的规则,例如整数溢出、截断和补码表示等。嵌入式C语言开发者需要了解这些规则,并在编程中遵循它们,以确保正确的运算结果。
    7. 整数常量:在C语言中,整数常量默认为 int 类型,可以通过后缀添加 ULLL 等来表示无符号整数、长整数和长长整数等。
  • long

    long 是C语言中的一种整数数据类型,用于表示较大范围的整数值。在嵌入式C语言中,long 类型通常用于表示比 int 类型更大范围的整数值。

    以下是关于 long 类型的一些重要知识点:

    1. 大小:long 类型的大小在不同的嵌入式系统中可能会有不同的实现,通常占用4字节(32位)或者8字节(64位)内存空间,取决于硬件架构和编译器的实现。
    2. 范围: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)之间,具体取决于编译器的实现和编译选项。
    3. 运算:long 类型可以进行基本的算术运算,例如加法、减法、乘法、除法和取余等,类似于 int 类型。在进行长整数运算时需要注意溢出和截断的问题,特别是在嵌入式系统中。
    4. 格式化输出:long 类型可以通过标准输入输出函数(如 scanfprintf)来进行输入和输出操作。可以使用 %ld%lu 格式化字符串来读取和打印带符号或无符号的 long 类型数据。
    5. 整数类型修饰符:C语言还提供了一些整数类型修饰符,例如 shortunsigned longlong long 等,可以用来调整 long 类型的大小和范围,以适应不同的需求。
    6. 整数运算规则:在进行 long 类型的运算时,也需要遵循C语言的整数运算规则,包括溢出、截断和补码表示等。
    7. 整数常量:在C语言中,整数常量默认为 int 类型,可以通过后缀添加 ULLL 等来表示无符号整数、长整数和长长整数等。例如,12345L 表示长整数常量。
  • short

    short 是C语言中的一种整数数据类型,用于表示较小范围的整数值。在嵌入式C语言中,short 类型通常用于表示比 int 类型更小范围的整数值。

    以下是关于 short 类型的一些重要知识点:

    1. 大小:short 类型的大小在不同的嵌入式系统中可能会有不同的实现,通常占用2字节(16位)内存空间,取决于硬件架构和编译器的实现。
    2. 范围:short 类型可以表示的整数范围通常在 -32,768 到 32,767 之间,具体取决于编译器的实现和编译选项。因为 short 类型的范围较小,因此在表示较小的整数值时可以节省内存空间。
    3. 运算:short 类型可以进行基本的算术运算,例如加法、减法、乘法、除法和取余等,类似于 int 类型。在进行短整数运算时需要注意溢出和截断的问题,特别是在嵌入式系统中。
    4. 格式化输出:short 类型可以通过标准输入输出函数(如 scanfprintf)来进行输入和输出操作。可以使用 %hd%hu 格式化字符串来读取和打印带符号或无符号的 short 类型数据。
    5. 整数类型修饰符:C语言还提供了一些整数类型修饰符,例如 unsigned shortshort int 等,可以用来调整 short 类型的大小和范围,以适应不同的需求。
    6. 整数运算规则:在进行 short 类型的运算时,也需要遵循C语言的整数运算规则,包括溢出、截断和补码表示等。
    7. 整数常量:在C语言中,整数常量默认为 int 类型,可以通过后缀添加 ULLL 等来表示无符号整数、长整数和长长整数等。例如,12345U 表示无符号短整数常量。
  • unsigned

    unsigned 是C语言中的一个类型修饰符,用于声明无符号类型的变量或常量。在嵌入式C语言中,unsigned 通常用于表示无符号整数值,即只能表示非负整数的类型。

    以下是关于 unsigned 类型修饰符的一些重要知识点:

    1. 无符号整数:unsigned 用于声明无符号整数类型,它可以用于 charshortintlonglong long 等整数类型。无符号整数类型不包含负数,范围从0到最大正整数值。
    2. 大小和范围:unsigned 类型的大小和范围取决于编译器的实现和硬件架构,通常与相应的有符号整数类型大小相同,但范围从0到最大正整数值,而不包含负数。例如,unsigned int 通常占用4字节(32位)内存空间,可以表示的范围从0到4294967295。
    3. 运算:unsigned 类型的运算规则与有符号整数类型类似,可以进行基本的算术运算,例如加法、减法、乘法、除法和取余等。在进行无符号整数运算时需要注意溢出和截断的问题,特别是在嵌入式系统中。
    4. 格式化输出:unsigned 类型可以通过标准输入输出函数(如 scanfprintf)来进行输入和输出操作。可以使用 %u 格式化字符串来读取和打印无符号整数。
    5. 整数常量:在C语言中,整数常量默认为 int 类型,可以通过后缀添加 ULLL 等来表示无符号整数、长整数和长长整数等。例如,12345U 表示无符号整数常量。
    6. 与有符号类型的转换:在进行无符号类型与有符号类型之间的转换时需要注意符号位的处理,可能会导致数值的变化和不确定的结果。
  • signed

    signed 是C语言中的一个类型修饰符,用于声明有符号类型的变量或常量。在嵌入式C语言中,signed 通常用于表示有符号整数值,即可以表示正整数和负整数的类型。

    以下是关于 signed 类型修饰符的一些重要知识点:

    1. 有符号整数:signed 用于声明有符号整数类型,它可以用于 charshortintlonglong long 等整数类型。有符号整数类型可以包含正数、负数和零,范围从最小负整数值到最大正整数值。
    2. 大小和范围:signed 类型的大小和范围取决于编译器的实现和硬件架构,通常与相应的无符号整数类型大小相同,但范围从最小负整数值到最大正整数值,包括零。例如,signed int 通常占用4字节(32位)内存空间,可以表示的范围从 -2147483648 到 2147483647。
    3. 运算:signed 类型的运算规则与无符号整数类型类似,可以进行基本的算术运算,例如加法、减法、乘法、除法和取余等。在进行有符号整数运算时需要注意溢出和截断的问题,特别是在嵌入式系统中。
    4. 格式化输出:signed 类型可以通过标准输入输出函数(如 scanfprintf)来进行输入和输出操作。可以使用 %d%i%ld 等格式化字符串来读取和打印有符号整数。
    5. 整数常量:在C语言中,整数常量默认为 int 类型,可以通过后缀添加 ULLL 等来表示无符号整数、长整数和长长整数等。例如,-12345 表示有符号整数常量。
    6. 与无符号类型的转换:在进行有符号类型与无符号类型之间的转换时需要注意符号位的处理,可能会导致数值的变化和不确定的结果。
  • char 和 unsigned char

    在嵌入式系统中,charunsigned char 在使用上有一些区别,主要体现在以下几个方面:

    1. 表示范围:char 是有符号的,而 unsigned char 是无符号的。在标准的 C 语言中,char 的范围可以是 -128 到 127(取决于平台的实现),而 unsigned char 的范围是 0 到 255。这意味着 char 类型可能包含负数,而 unsigned char 类型只包含非负数。
    2. 默认符号性:在嵌入式系统中,编译器对 char 类型的符号性没有明确的规定,因此 char 类型的符号性可能会因编译器和平台而异。这可能导致在不同的编译器和平台上,char 类型的符号性不一致,可能会引发一些问题。而 unsigned char 则是无符号的,不受符号性的影响。
    3. 算术运算:char 类型在进行算术运算时,可能会出现溢出和符号截断的情况。例如,对一个 char 类型的变量进行加法运算,如果结果超过了 char 类型的范围,可能会发生溢出。而 unsigned char 类型则不会出现溢出和符号截断的问题,因为它是无符号的。
    4. 字节操作:在处理二进制数据、字节流等底层操作时,unsigned char 类型更常用,因为它是无符号的,不会出现符号截断和溢出的问题。而 char 类型可能因为符号性和溢出问题导致处理结果不符合预期。

    因此,在嵌入式系统中,通常推荐使用 unsigned char 类型来处理字节数据、二进制数据等底层操作,避免因 char 类型的符号性和溢出问题导致的错误。同时,应当注意编译器和平台对 char 类型符号性的不确定性,避免在不同编译器和平台之间出现不一致的行为。

  • float

    float 是C语言中的一种浮点数类型,用于表示带有小数部分的实数值。在嵌入式C语言中,float 类型通常用于处理需要精确表示小数的应用场景,例如测量数据、传感器数据、控制算法等。

    以下是关于 float 类型的一些重要知识点:

    1. 浮点数表示:float 类型采用IEEE 754标准定义的单精度浮点数格式,占用4字节(32位)内存空间。其中,1位用于表示符号位(正负号),8位用于表示指数部分,23位用于表示尾数部分。
    2. 数值范围:float 类型可以表示的数值范围通常为正负3.4E-38 到正负3.4E+38,可以表示的精度通常为6到7位有效数字。
    3. 运算:float 类型支持基本的算术运算,包括加法、减法、乘法、除法和取余等。但需要注意浮点数运算可能存在精度损失和舍入误差的问题,因此在进行浮点数运算时需要谨慎处理。
    4. 格式化输出:float 类型可以通过标准输入输出函数(如 scanfprintf)来进行输入和输出操作。可以使用 %f%e%g 等格式化字符串来读取和打印浮点数。
    5. 浮点数常量:在C语言中,浮点数常量默认为 double 类型,可以通过后缀添加 f 来表示 float 类型的浮点数常量。例如,3.14f 表示 float 类型的浮点数常量。
    6. 数学库函数:C语言提供了丰富的数学库函数(如 math.h 头文件中的函数),用于进行浮点数的各种数学运算,例如求平方根、三角函数、指数函数等。
    7. 特殊值:float 类型还支持表示特殊的浮点数值,例如正无穷大、负无穷大和NaN(不是一个数字)等,这些特殊值在处理异常情况时可能会有用。
  • double

    float 是C语言中的一种浮点数类型,用于表示带有小数部分的实数值。在嵌入式C语言中,float 类型通常用于处理需要精确表示小数的应用场景,例如测量数据、传感器数据、控制算法等。

    以下是关于 float 类型的一些重要知识点:

    1. 浮点数表示:float 类型采用IEEE 754标准定义的单精度浮点数格式,占用4字节(32位)内存空间。其中,1位用于表示符号位(正负号),8位用于表示指数部分,23位用于表示尾数部分。
    2. 数值范围:float 类型可以表示的数值范围通常为正负3.4E-38 到正负3.4E+38,可以表示的精度通常为6到7位有效数字。
    3. 运算:float 类型支持基本的算术运算,包括加法、减法、乘法、除法和取余等。但需要注意浮点数运算可能存在精度损失和舍入误差的问题,因此在进行浮点数运算时需要谨慎处理。
    4. 格式化输出:float 类型可以通过标准输入输出函数(如 scanfprintf)来进行输入和输出操作。可以使用 %f%e%g 等格式化字符串来读取和打印浮点数。
    5. 浮点数常量:在C语言中,浮点数常量默认为 double 类型,可以通过后缀添加 f 来表示 float 类型的浮点数常量。例如,3.14f 表示 float 类型的浮点数常量。
    6. 数学库函数:C语言提供了丰富的数学库函数(如 math.h 头文件中的函数),用于进行浮点数的各种数学运算,例如求平方根、三角函数、指数函数等。
    7. 特殊值:float 类型还支持表示特殊的浮点数值,例如正无穷大、负无穷大和NaN(不是一个数字)等,这些特殊值在处理异常情况时可能会有用。
  • void

    void 是C语言中的一种特殊类型,用于表示没有返回值或没有参数的函数。在嵌入式C语言中,void 类型通常用于定义函数的返回值类型或函数的参数类型。

    以下是关于 void 类型的一些重要知识点:

    1. 返回值类型:函数可以声明或定义为 void 类型,表示函数没有返回值。这意味着函数不会返回任何数值,或者函数的计算结果不需要作为返回值使用。例如,void func() 表示 func 函数没有返回值。
    2. 参数类型:函数可以接受 void 类型的参数,表示函数不需要接受任何参数。这意味着函数在调用时不需要传递任何参数值。例如,void func(void) 表示 func 函数不接受任何参数。
    3. 函数指针:void 类型可以用于定义函数指针,即指向函数的指针变量。函数指针可以用于传递函数地址,实现函数回调等功能。
    4. 指针类型:void 类型的指针可以用于指向任何类型的数据,包括基本数据类型(如 intfloatdouble 等)和自定义数据类型(如结构体、联合体等)。这种用法称为 “泛型指针”,可以在一些特定场景下灵活使用。
    5. 空指针:void* 类型是一种特殊的指针类型,称为 “空指针”,用于表示指向任何类型的指针,但没有具体的类型信息。可以用于在函数参数中传递不同类型的指针,或者在函数内部进行类型转换等。
    6. 注意事项:使用 void 类型时需要注意类型安全性和类型转换的问题。因为 void 类型没有具体的类型信息,所以在使用时需要确保类型的一致性,并避免潜在的类型错误。

3 - 3 自定义数据类型

  • struct

    struct 是C语言中用于定义自定义的复合数据类型的关键字。通过 struct 关键字可以定义一个包含多个不同数据类型成员的数据结构。

    • 定义结构体:使用 struct 关键字可以定义一个结构体类型,并在定义中指定结构体的成员。例如:
    cCopy codestruct Point {
        int x;
        int y;
    };
    

    上面的例子定义了一个名为 Point 的结构体类型,其中包含了两个 int 类型的成员变量 xy

    • 创建结构体变量:可以使用结构体类型创建结构体变量,也称为结构体的实例。例如:
    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 的联合类型,其中包含了三个不同类型的成员变量 xyz,分别为 intfloatchar 类型。

    • 创建联合变量:可以使用联合类型创建联合变量,也称为联合的实例。例如:
    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 的值被覆盖
    
    1. 联合与结构体的区别:与 struct 不同,union 的所有成员共享同一块内存空间,因此只能同时存储一个成员的值,而 struct 的成员则会占据独立的内存空间。这也是 union 的一个特点,可以在有限的内存空间中存储不同类型的值,但需要注意访问成员时的值覆盖情况。
    2. 联合的应用:联合在嵌入式系统中常用于处理硬件寄存器、通信协议、数据包解析等场景,可以节省内存空间并实现灵活的数据访问方式。

    需要注意联合在访问成员时的值覆盖情况,避免引发未定义行为

  • enum

    enum(枚举)是C语言中一种用于定义命名整数常量的数据类型。enum 用于创建一组具有相互关联的命名常量,这些常量通常用于表示一组离散的取值范围。enum 在编程中常用于表示状态、选项、标志位等。

    以下是关于 enum 的一些重要知识点:

    • 定义枚举类型:使用 enum 关键字可以定义一个枚举类型,并在定义中指定枚举的取值范围。例如:
    cCopy codeenum Color {
        RED,
        GREEN,
        BLUE
    };
    

    上面的例子定义了一个名为 Color 的枚举类型,其中包含了三个命名常量 REDGREENBLUE,它们的取值分别为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 的枚举类型,并指定了每个枚举常量的值。

    1. 枚举的应用:枚举在编程中常用于表示一组相关的选项、状态或标志位,可以提高代码的可读性和可维护性。枚举也可以用于替代使用宏定义的常量,从而避免宏带来的潜在问题。

    需要注意枚举类型的取值范围和枚举常量的默认值,以及在比较枚举变量时使用枚举常量而不是直接比较整数值。

  • 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 指针变量
    
    1. 可读性和可维护性: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 的推导类型是根据初始化表达式的类型进行推导的,因此变量的类型会根据初始化表达式的类型而变化。

    1. auto 的使用场景:auto 通常用于简化变量声明,减少重复的类型声明,并且可以提高代码的可维护性。auto 特性在处理复杂的类型、模板和泛型编程时也非常有用。
  • register

    register 是 C 语言中的一个关键字,用于向编译器建议将某个变量存储在寄存器中,以便在程序中更快地访问变量。然而,在现代编译器中,register 关键字的使用通常被忽略,因为编译器会自动优化变量的存储和访问方式,以提供最佳的性能。

    以下是关于 register 关键字的一些重要知识点:

    • 寄存器变量:register 关键字用于声明一个变量应该存储在寄存器中,以便在程序中更快地访问。例如:
    cCopy code
    register int counter; // 声明一个寄存器变量
    
    1. 优化:register 关键字只是向编译器提供了一个建议,而不是强制要求将变量存储在寄存器中。编译器可以根据具体情况决定是否将变量存储在寄存器中,以便在程序中获得最佳的性能。
    2. 限制和用法:register 关键字有一些限制和用法,例如:
    • register 变量不能有地址运算符 &,因为寄存器变量不存储在内存中,因此没有地址可取。
    • register 变量不能声明为全局变量或静态变量,因为它们在程序的生命周期内需要保持固定的存储位置。
    • register 变量的初始值应该是常量或编译时确定的值,因为寄存器变量在运行时可能不保存其值。

    需要注意的是,register 关键字在现代编译器中的作用已经大大减弱,因为编译器通常会自动进行优化,将变量存储在寄存器或内存中以获得最佳的性能。因此,在编写现代 C 代码时,不必过于依赖 register 关键字,而应该将优化工作交给编译器来完成。

  • static

    static 关键字在 C 语言中有两种用法:用于修饰变量和修饰函数。(访问是局部的,生命是全局的)

static 关键字在 C 语言中用于修饰变量时:

  1. static 修饰局部变量: 当 static 关键字用于修饰局部变量时,它会改变该变量在内存中的存储方式和生命周期。具体而言,使用 static 修饰的局部变量会在程序的整个运行期间都存在,不会随着函数的调用而创建和销毁,而是在程序启动时创建,在程序结束时销毁。这意味着该变量的值在函数调用之间会保持持久性,可以用于在函数调用之间保留状态信息。此外,static 修饰的局部变量的存储位置通常位于静态存储区,而不是栈上,因此其内存地址在程序运行期间是固定的,不会随着函数的调用而变化。
  2. static 修饰全局变量: 当 static 关键字用于修饰全局变量时,它会改变该变量的作用域和可见性。具体而言,使用 static 修饰的全局变量只能在定义它的文件中访问,不能被其他文件访问,称为文件作用域。这样可以限制全局变量的可见性,避免在多个文件中出现同名的全局变量引起的命名冲突

总之,static 关键字对变量在内存的影响包括:改变局部变量的生命周期和存储位置,使其在函数调用之间保持持久性;改变全局变量的作用域和可见性,限制其只能在定义它的文件中访问。

static 关键字在 C 语言中用于修饰函数时,其作用是改变函数的链接属性,从而影响函数在内存中的存储和可见性。

  1. 静态链接属性:使用 static 修饰的函数具有静态链接属性,这意味着它们只能在定义它们的文件中访问,不能被其他文件访问,称为文件作用域。这样可以限制函数的可见性,避免在多个文件中出现同名的函数引起的命名冲突。
  2. 内部链接:使用 static 修饰的函数具有内部链接属性,这意味着它们在编译时会被编译器优化为只在当前编译单元内可见的符号,而不会被放入全局符号表。这样可以避免函数被其他编译单元引用,从而减小了目标文件和最终可执行文件的大小。

总之,static 关键字对函数在内存的影响包括:改变函数的链接属性,限制其只能在定义它的文件中访问,并且具有内部链接属性,只在当前编译单元内可见。

  • const

    const 关键字在 C 语言中用于声明一个常量,表示该变量的值在其初始化后不可被修改。对于常量的修改操作将导致编译时错误。

    (本质当变量被修饰后,编译阶段其不能出现在等号左边)

    const 关键字对变量在内存的影响主要有以下两个方面:

    1. 只读属性:使用 const 修饰的变量在内存中具有只读属性,其值在初始化后不能被修改。这意味着在程序运行时,尝试对 const 变量进行修改操作将导致错误。
    2. 编译时优化:由于 const 变量的值在编译时就已经确定,并且不能被修改,编译器可以进行优化,例如将 const 变量的值直接嵌入到生成的机器代码中,而不需要在运行时读取内存中的变量值。这样可以提高程序的性能和效率。

    需要注意的是,const 关键字并不是完全保证变量不可修改的,因为通过指针或类型转换等方式仍然可以对 const 变量进行修改。但在一般情况下,编译器会对 const 变量进行检查,并在编译时发现对其修改的错误。

  • extern

    extern 关键字在 C 语言中用于声明一个变量或函数的外部链接属性,表示该变量或函数在其他文件中定义或声明,并且在当前文件中只是作为一个引用。

    extern 关键字对变量和函数在内存的影响主要有以下两个方面:

    1. 外部链接属性:使用 extern 声明的变量或函数具有外部链接属性,表示它们在其他文件中定义或声明。这意味着它们可以在其他文件中进行定义,并且可以被其他文件引用和访问。
    2. 声明而不定义:使用 extern 声明的变量或函数在当前文件中只是作为一个引用,并不会在当前文件中分配内存空间。这意味着在当前文件中使用 extern 声明的变量或函数时,编译器会在链接时在其他文件中寻找其定义,并将其引用连接到正确的地址。

    需要注意的是,extern 关键字只是用于声明变量或函数的外部链接属性,并不会为其分配内存空间或提供实际的定义。在程序中使用 extern 关键字时,需要在其他文件中提供相应的定义,以确保链接时能够找到正确的定义。

  • volatile

    volatile 关键字在 C 语言中用于声明一个变量是易变的,即其值可能会在程序运行期间被意外地改变,从而告诉编译器不要对该变量进行优化,以确保每次访问都从内存中读取最新的值,而不是使用寄存器或缓存中的旧值。

    volatile 关键字对变量在内存的影响主要有以下两个方面:

    1. 防止优化:使用 volatile 修饰的变量不会被编译器优化,编译器会在每次访问该变量时从内存中读取最新的值,而不是使用寄存器或缓存中的旧值。这对于那些可能被外部因素改变的变量(例如硬件寄存器)非常有用,可以防止编译器对其进行错误的优化。
    2. 禁用缓存:使用 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 运算符

  • & |

    位运算是一种在嵌入式系统和底层编程中常用的技术,可以通过对二进制位的操作来实现各种功能。以下是一些常见的位运算技巧:

    1. 位与操作用于屏蔽指定位:可以使用位与操作 & 来屏蔽一个或多个指定位,例如将一个整数的最低位设置为0:x = x & ~(1 << n),其中 n 是指定的位数。
    2. 位或操作用于设置指定位:可以使用位或操作 | 来设置一个或多个指定位,例如将一个整数的最低位设置为1:x = x | (1 << n),其中 n 是指定的位数。
    3. 位异或操作用于翻转指定位:可以使用位异或操作 ^ 来翻转一个指定位,例如将一个整数的最低位翻转:x = x ^ (1 << n),其中 n 是指定的位数。
    4. 位移操作用于快速乘以或除以2的幂次方:左移操作 << 可以将一个二进制数向左移动指定的位数,相当于乘以2的幂次方;右移操作 >> 可以将一个二进制数向右移动指定的位数,相当于除以2的幂次方。
    5. 位测试操作用于检查指定位的值:可以使用位与操作 & 结合条件语句来检查一个指定位的值,例如检查一个整数的最低位是否为1:if (x & (1 << n)) { /* 指定位为1 */ } else { /* 指定位为0 */ },其中 n 是指定的位数。
    6. 位计数操作用于统计二进制数中的位数:可以使用位与操作 & 结合循环和右移操作 >> 来统计一个二进制数中有多少个位是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

    需要注意的是,&* 运算符在不同的上下文中有不同的含义,使用时需要根据语境正确理解和使用。例如,& 用于取地址操作,将变量的地址取出;* 用于间接访问操作,通过指针访问指向的内存中的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

恐高宇航员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值