目录
一、问题
二、解答
1、数据类型关键字(12个)
(1) 声明和定义的区别
(2) 数据类型关键字
• char:声明字符型变量
1、声明字符变量
2、字符数组
3、ASCII码表示
4、指针与字符数组
5、多字节字符集(如UTF-8)
• int:声明整型变量
• short:声明短整型变量
• long:声明⻓整型变量
• float:声明单精度浮点型变量。
• double:声明双精度浮点型变量
• signed:声明有符号类型变量。
1、声明一个有符号整型变量
2、其他有符号整型类型
1、原码
2、反码
3、补码
补码的存在主要有这⼏种意义
1、简化硬件设计
2、溢出检测
3、自然排序
4、零的唯一表示
5、更高效的减法运算
• unsigned:声明⽆符号类型变量
1、声明无符号整型变量
2、其他无符号整型类型
• struct:声明结构体变量
1、声明结构体类型
2、声明结构体变量
• union:声明联合数据类型
• enum:声明枚举类型
• void:声明空类型指针
2、控制语句关键字(12个)
• if
• else
• switch、case和default
• for
• do
• while
• break
• continue
• goto
• return
3、存储类关键字(5个)
• auto
• extern
• register
• static
• const
4. 其他关键字(3个)
• sizeof
• typedef
• volatile
5、附:预处理指令
1、宏定义与取消定义
2、 文件包含
3、条件编译
4、行号与文件名信息
5、特殊标记
6、 其他预处理操作符
ANSI标准C定义的宏
• #include:#include
• # 运算符
• ## 运算符
• #pragma
一、问题
在C 语言中常常提到关键字,那么什么是关键字?C 语言又有哪些关键字呢?
二、解答
关键字是由C语言规定的具有特定意义的字符串,通常也称为保留字。用户定义的标识符不应与关键字相同。
C语言的关键字分为以下几类:
(1)类型说明符。用于定义、说明变量、函数或其他数据结构的类型。
(2)语句定义符。用于表示一个语句的功能。
(3)预处理命令字。用于表示一个预处理命令
1、数据类型关键字(12个)
C语⾔中的数据类型主要有下⾯⼏种。实际上,数据类型可以理解为固定⼤⼩内存块的别名,给变量指定类型就是告诉编译器给该变量分 配多⼤的内存空间,⽽变量相当于是内存块的⻔牌号。
(1) 声明和定义的区别
C语言声明变量与定义变量的区别-CSDN博客
(2) 数据类型关键字
• char:声明字符型变量
这个关键字主要用于表示单个字符数据,通常占用1个字节(8位)的存储空间。
在不同的编译器和系统上,默认情况下 char 类型可以是带符号(signed char)或无符号(unsigned char),具体取决于实现,但在C11标准中规定了至少有三种不同的char类型:signed char、unsigned char 和 char,其中 char 的行为要么与 signed char 相同,要么与 unsigned char 相同,但不是两者同时成立。
以下是使用 char 关键字的一些典型用法:
1、声明字符变量
char ch; // 声明一个未初始化的字符变量ch
char letter = 'A'; // 声明并初始化为大写字母A
2、字符数组
char str[] = "Hello, World!"; // 声明并初始化一个包含字符串的字符数组
3、ASCII码表示
• C语言中的字符实际上是其对应的ASCII码值,例如 'A' 在内存中存储的是ASCII码65。
• 可以直接将整数赋给 char 类型变量,只要该整数代表的有效ASCII码落在 char 类型可表示的
范围内。
4、指针与字符数组
char message[100];
char *ptr = message; // 指针变量可以指向字符数组的首地址
5、多字节字符集(如UTF-8)
虽然 char 通常用来处理单字节字符,但在支持多字节字符集(如UTF-8编码的环境)下,多个
连续的 char 变量可能组成一个多字节字符。
总之,在C语言中,char 关键字是字符处理的基础,它不仅用于基本的文本字符,还可以用于底层数据操作,尤其是在需要处理原始字节流的场合。
ASCII对照表如下
ASCII值 | 控制字符 | ASCII值 | 字符 | ASCII值 | 字 符 | ASCII值 | 字符 |
0 | NUT | 32 | (spa ce) | 64 | @ | 96 | 、 |
1 | SOH | 33 | ! | 65 | A | 97 | a |
2 | STX | 34 | " | 66 | B | 98 | b |
3 | ETX | 35 | # | 67 | C | 99 | c |
4 | EOT | 36 | $ | 68 | D | 100 | d |
5 | ENQ | 37 | % | 69 | E | 101 | e |
6 | ACK | 38 | & | 70 | F | 102 | f |
7 | BEL | 39 | , | 71 | G | 103 | g |
8 | BS | 40 | ( | 72 | H | 104 | h |
9 | HT | 41 | ) | 73 | I | 105 | i |
10 | LF | 42 | * | 74 | J | 106 | j |
11 | VT | 43 | + | 75 | K | 107 | k |
12 | FF | 44 | , | 76 | L | 108 | l |
13 | CR | 45 | - | 77 | M | 109 | m |
14 | SO | 46 | . | 78 | N | 110 | n |
15 | SI | 47 | / | 79 | O | 111 | o |
16 | DLE | 48 | 0 | 80 | P | 112 | p |
17 | DCI | 49 | 1 | 81 | Q | 113 | q |
18 | DC2 | 50 | 2 | 82 | R | 114 | r |
19 | DC3 | 51 | 3 | 83 | S | 115 | s |
20 | DC4 | 52 | 4 | 84 | T | 116 | t |
21 | NAK | 53 | 5 | 85 | U | 117 | u |
22 | SYN | 54 | 6 | 86 | V | 118 | v |
23 | TB | 55 | 7 | 87 | W | 119 | w |
24 | CAN | 56 | 8 | 88 | X | 120 | x |
25 | EM | 57 | 9 | 89 | Y | 121 | y |
26 | SUB | 58 | : | 90 | Z | 122 | z |
27 | ESC | 59 | ; | 91 | [ | 123 | { |
28 | FS | 60 | < | 92 | / | 124 | | |
29 | GS | 61 | = | 93 | ] | 125 | } |
30 | RS | 62 | > | 94 | ^ | 126 | ` |
31 | US | 63 | ? | 95 | _ | 127 | DEL |
转义字符
转义字符 | 含义 | ASCII码值(⼗进制) |
\a | 警报 | 007 |
\b | 退格(BS) ,将当前位置移到前 ⼀列 | 008 |
\f | 换⻚(FF),将当前位置移到下⻚ 开头 | 012 |
\n | 换⾏(LF) ,将当前位置移到下 ⼀⾏开头 | 010 |
\r | 回⻋(CR) ,将当前位置移到本 ⾏开头 | 013 |
\t | ⽔平制表(HT) (跳到下⼀个 TAB位置) | 009 |
\v | 垂直制表(VT) | 011 |
\ | 代表⼀个反斜线字符"" | 092 |
' | 代表⼀个单引号(撇号)字符 | 039 |
" | 代表⼀个双引号字符 | 034 |
\? | 代表⼀个问号 | 063 |
\0 | 数字0 | 000 |
\ddd | 8进制转义字符,d范围0~7 | 3位8进制 |
\xhh | 16进制转义字符,h范围0~9, a~f,A~F | 3位16进制 |
• int:声明整型变量
在C语⾔标准中并没有明确规定整型数据的⻓度,整型数据在内存中所占的字节数与操作系统有关系。(⼀般为4字节)
在C语言中,int 关键字是一个基本的数据类型声明符,用于定义整数类型的变量、函数参数或函数返回值。int 表示“有符号整型”(signed integer),它在内存中占用的大小是编译器和系统相关的,但通常情况下,按照ANSIIC标准:
• 在32位系统上,一个int通常占4个字节(32位)。
• 它能表示的数值范围是有局限的,对于大多数系统,一个int类型的最小值约为 -2,147,483,648,
最大值约为 2,147,483,647。
另外,C语言还支持其他与int相关的整数类型:
• unsigned int:无符号整型,表示非负整数,相同的存储空间内可以存储更大的正整数值。
• short int 或简写为 short:短整型,通常比 int 占用更少的空间,因此数值范围也较小。
• long int 或简写为 long:长整型,至少与 int 同样大,但在某些系统上可能更大。
• unsigned short, unsigned long, long long int 等也是常见的整数类型变种。
例如,在C程序中声明一个整数变量:
int myInteger;
这将创建一个名为 myInteger 的变量,它可以存储任何符合该系统上 int 类型所允许的整数值。
%d:带符号的十进制整数(适用于int类型)
%o:无符号八进制整数。
%x 或 %X:无符号十六进制整数(小写或大写)
%u:无符号的十进制整数(适用于unsigned int)
• short:声明短整型变量
在C语言中,short 关键字用于声明短整型(short integer)变量。短整型数据类型占用的内存空间小于标准整型(int),因此它可以用来存储较小范围内的整数,这对于节省内存空间是有利的,尤其是在资源有限的嵌入式系统或其他对内存要求严格的环境中。
尽管C语言标准没有严格规定short的确切位数,但通常情况下:
• 在大多数32位和64位系统上,一个short类型的变量占用2个字节(16位)。
• 其表示数值的范围因系统而异,对于有符号短整型(默认情况),一般可以表示从大约 -32768 到 32757 之间的整数。
声明一个短整型变量的语法如下:
short variableName;
例如:
short score;
score = 100; // 声明并初始化一个短整型变量score,赋值为100
此外,还可以使用 unsigned short 来声明无符号短整型变量,该类型的变量只能存储非负整数,并且由于不保留符号位,其最大值会比有符号短整型大一倍。
• long:声明⻓整型变量
⻓度⼀般不短于int型数据。(Windows为4字节;Linux为4字节(32位),8字节(64位)。)
%d:带符号的十进制整数(适用于int类型)。
%hd:带符号的短整数(适用于short int类型)。
%ld:带符号的长整数(适用于long int类型)。
%lld:带符号的长长整数(适用于long long int类型)。
%hu:无符号的短整数(适用于unsigned short int)。
%u:无符号的十进制整数(适用于unsigned int)。
%lu:无符号的长整数(适用于unsigned long int)。
%llu:无符号的长长整数(适用于unsigned long long int)。
• float:声明单精度浮点型变量。
在C语言中,float 关键字用于声明单精度浮点型变量。这种数据类型能够存储带有小数部分的数值,适用于需要表示一定范围内小数的场合。
单精度浮点型(float)变量在内存中通常占用4个字节(32位),其中一部分用来表示符号(正负号)、
一部分表示尾数、另一部分表示指数。按照IEEE 754标准,单精度浮点数的有效数字约为6-7位,其取值范围
大约为:
• 最小正数(非零):约 1.17549435e-38
• 最大正数:约 3.40282347e+38
• 正负无穷大(Inf)
• 非数字(NaN)
声明一个单精度浮点型变量的例子如下:
float floatingNumber;
这行代码定义了一个名为 floatingNumber 的变量,它可以存储一个单精度浮点数值。例如:
float pi = 3.14159265f;
这里声明并初始化了一个 float 类型的变量 pi,赋值为圆周率近似值。注意,在赋值时使用后缀
f 或 F 来确保编译器将该常量视为单精度浮点数,而不是默认的双精度(double)。
• double:声明双精度浮点型变量
在C语言中,double 是一个关键字,用于声明双精度浮点型变量。这种数据类型可以存储比 float 类型更大的范围和更高精度的浮点数。每个 double 类型变量通常占用 8 字节(64位)内存空间。
double 类型的主要用途是在需要更高精度数学计算或更大数值范围时使用。其数值范围大约是 ±5.0e-324 到 ±1.7e+308(具体数值可能因不同的编译器实现而略有差异),并且它能够提供约15位的有效数字精度。
在 C 标准库中的输入输出函数(如 scanf() 和 printf())中,用于格式化双精度浮点数的转换说明符包括:
• %lf:在 printf() 函数中用来以十进制形式输出 double 类型的值。
• %le 或 %lg:以科学计数法(小写 e 表示指数)输出 double 类型的值。
• %lE:同样以科学计数法输出,但指数部分使用大写 E。
• signed:声明有符号类型变量。
在C语言中,signed 关键字用于声明整型变量为有符号类型。这意味着使用 signed 关键字修饰的整数类型可以存储正数、零以及负数。如果不特别指定 signed 或 unsigned,C语言默认的基本整型(如 int)就是有符号的。
例如:
1、声明一个有符号整型变量
signed int num; // 这里 signed 是可选的,因为 int 本身默认是 signed 类型
2、其他有符号整型类型
signed short int s; // 短整型,可以表示较小范围内的有符号整数
signed long int l; // 长整型,可以表示较大范围内的有符号整数
对于不同的整型类型,其具体的取值范围依赖于编译器和目标计算机平台。以32位系统为例,signed int 的通常范围是从 -2^31 到 2^31-1(即 -2,147,483,648 到 2,147,483,647),而 unsigned int 在同样的字节长度下则表示从0到2^32-1(即0到4,294,967,295)的非负整数。
缺省时,编译器默认为signed有符号类型。在计算机中,所有的数据都是以01的⼆进制形式来存储的,对于有符号数来说如何表示⼀个数值的正负是⼀个问题,因此便有了原码、反码和补码。
- 在计算机系统中,为了表示负数和正数,以及执行加减运算时简化硬件设计,引入了原码、反码和补码三种不同的二进制编码方式。以下是它们的基础概念:
1、原码
原码是最直观的表示方法,直接将一个数的绝对值转换为二进制形式,最高位通常用来表示符号,
0代表正数,1代表负数。
例如:
• 正数:+5 的原码是 00000101
• 负数:-5 的原码是 10000101
2、反码
对于正数,反码与原码相同;而对于负数,除了符号位保持不变外,其余各位都要按位取反
(即0变1,1变0)。
• 正数:+5 的反码仍是 00000101
• 负数:-5 的反码是 11111010
3、补码
补码是通过在反码的基础上对最低位再加1得到的。对于正数,补码与原码和反码相同;对于负数,
同样先求反码,然后对所有位(包括符号位)逐位加1。
• 正数:+5 的补码还是 00000101
• 负数:-5 的补码是 11111011 (从其反码 11111010 加1得到)
采用补码的主要原因是它能简化算术运算,特别是加法运算。在补码系统下,加法运算可以统一处理正数加法和负数加法,且能够自动产生溢出标志,因此现代计算机系统普遍使用补码来表示和存储整数数据。
-
补码的存在主要有这⼏种意义
1、简化硬件设计
• 在计算机系统中,加法运算是最基本且频繁的操作之一。采用补码表示法后,正数和负数的加
减运算可以统一使用相同的电路实现,不需要额外设计针对正数和负数不同的运算逻辑,极大地简化了
硬件设计。
2、溢出检测
• 补码表示法下进行加减运算时,如果结果超出当前数据类型的取值范围(即发生溢出),可以通
过检查符号位是否改变自动判断出来,无需专门的溢出检测电路。
3、自然排序
• 补码表示使得二进制数值在内存中的顺序与它们对应的十进制数值大小的自然顺序一致,方便比
较和排序操作。
4、零的唯一表示
• 在原码和反码表示法中,存在两个不同的编码表示0(+0和-0),而在补码表示法中,0只有一个
唯一的二进制表示,这避免了不必要的复杂性。
5、更高效的减法运算
• 通过补码,减法运算可以转换为加法运算。例如,要计算 a - b,只需要计算 a + (-b) 的补
码形式即可,其中 -b 是 b 的补码取反加1的结果。
综上所述,补码的存在极大地方便了计算机硬件对整数进行高效、准确和统一的处理,从而成为现代计算机系统中普遍采用的整数表示方式。
• unsigned:声明⽆符号类型变量
在C语言中,unsigned 关键字用于声明整型变量为无符号类型。这意味着使用 unsigned 修饰的整数类型不能存储负数,而只能表示从0开始的非负整数值。
以下是一些使用 unsigned 的例子:
1、声明无符号整型变量
unsigned int ui; // 声明一个无符号整型变量
2、其他无符号整型类型
unsigned short us; // 无符号短整型,适合存储较小的非负整数
unsigned long ul; // 无符号长整型,可以存储更大的非负整数
unsigned char uc; // 无符号字符型,通常用于表示8位非负整数或纯数据(如图像像素)
对于不同类型的无符号整数,它们能表示的值范围是0到该类型的最大可能值。例如,在32位系统中
• unsigned int 能表示从0到4,294,967,295 (即2^32 - 1) 的整数。
• unsigned short 能表示从0到65,535 (即2^16 - 1) 的整数。
• unsigned char 能表示从0到255 (即2^8 - 1) 的整数。
使用无符号类型的好处在于能够利用整个存储空间来表示更大的非负数值,尤其是在处理不可能出现负数的情况时(如索引、计数、颜色代码等)。然而,需要注意的是,如果试图对无符号变量赋予负值,编译器将会自动将其转换为相应正数的模值形式(即按照无符号类型的最大取值范围进行计算)。
• struct:声明结构体变量
在C语言中,struct 关键字用于声明结构体类型和结构体变量。首先,需要定义一个结构体类型,然后才能声明该类型的变量。
1、声明结构体类型
struct 结构体标签名 {
类型1 成员变量1;
类型2 成员变量2;
...
类型N 成员变量N;
};
例如:
// 定义一个表示学生的结构体类型
struct Student {
char name[50]; // 姓名
int age; // 年龄
float gpa; // GPA
};
2、声明结构体变量
// 声明并初始化结构体变量
struct Student John = {"John Doe", 20, 3.8};
// 或者先声明后初始化
struct Student Jane;
Jane.age = 22;
strcpy(Jane.name, "Jane Smith");
Jane.gpa = 3.9;
此外,在C99标准及以后的版本中,可以使用匿名结构体(不带标签)和直接声明并初始化结构体变量
的方法:
// 直接声明并初始化结构体变量,无需给结构体类型命名
struct {
char name[50];
int age;
float gpa;
} Mary = {"Mary Johnson", 19, 4.0};
通过结构体,程序员能够以一种组织良好的方式将多个相关数据项组合在一起,方便管理和操作这些数据。
• union:声明联合数据类型
在C语言中,union 是一种用户定义的数据类型,它允许将多个不同类型的变量共享同一块内存空间。这意味着在同一时间内,联合体(union)的任何一个成员变量可以存储数据,但所有成员不能同时存储独立的数据,因为它们都指向相同的内存区域。
使用 union 关键字创建联合体的基本语法如下:
// 定义一个联合体类型
union UnionName {
类型1 成员变量1;
类型2 成员变量2;
...
类型N 成员变量N;
};
// 示例:
union DataType {
int integerValue;
float floatValue;
char characterValue[5];
};
// 声明并初始化联合体变量
union DataType data;
// 使用联合体的某个成员
data.integerValue = 42; // 此时整数值占据内存空间
printf("Integer value: %d\n", data.integerValue);
data.floatValue = 3.14; // 当重新赋值为浮点数时,整数值会被覆盖
printf("Float value: %.2f\n", data.floatValue);
// 注意:此时字符数组characterValue的内容也会被floatValue所覆盖
联合体的主要用途是在不同的时间点根据需要以不同的数据类型访问同一块内存区域,从而节省内存。然而,由于其特性,必须确保在同一时刻仅对一个成员进行读写操作,否则可能导致数据混乱或未定义的行为。
• enum:声明枚举类型
在C语言中,enum 是一个关键字,用于定义枚举类型(enumerated type)。枚举类型允许程序员为一组相关的整数值指定有意义的名字,这样可以增加代码的可读性和维护性。通过 enum 关键字创建的枚举类型中的每个成员都有一个唯一的整数值,通常默认情况下从0开始依次递增。
基本语法如下:
// 定义枚举类型
enum EnumTypeName {
Member1,
Member2,
...
MemberN
};
// 示例:
enum Color { // 创建名为Color的枚举类型
RED,
GREEN,
BLUE
};
// 声明并使用枚举变量
enum Color myColor = RED; // 声明一个Color类型的枚举变量myColor,并初始化为RED
// 或者在C99及以后的标准中可以使用typedef简化枚举类型名
typedef enum {
RED,
GREEN,
BLUE
} Color_t;
Color_t yourColor = GREEN; // 现在可以直接用Color_t代替enum Color
// 访问枚举值
printf("My color is %d\n", myColor); // 输出:My color is 0 (假设RED对应0)
虽然枚举成员的值默认从0开始自动增量,但也可以显式地给枚举成员赋值:
enum DayOfWeek {
MONDAY = 1,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY // 在此示例中,SUNDAY将默认得到6,因为它是最后一个未显式赋值的成员
};
在这个例子中,MONDAY被赋予了值1,后续没有显式赋值的成员会在此基础上依次加1。枚举类型在编译时确定,因此在运行时访问枚举成员是非常高效的。
• void:声明空类型指针
(void类型指针可以接受任何类型指针的赋值,⽆需类型转换),声明函数⽆返回值或⽆参数等。
在C语言中,void 关键字用于表示“无类型”或“通用类型”。当它与指针结合时,可以声明一个 void* 类型的指针,这通常称为“空指针”或“万能指针”。
声明一个 void* 类型的指针如下:
void* ptr;
这样的指针不直接指向任何特定类型的对象。它可以被赋值为任何其他类型指针的值,因此它常用于需要处理任意类型数据的函数(例如内存分配函数 malloc() 返回的就是 void* 类型的指针)。
然而,由于编译器不知道 void* 指向的具体类型,所以不能直接对 void* 进行解引用或算术操作。
在使用之前,必须将 void* 强制转换回适当的目标类型:
int *intptr = (int*)ptr; // 将 void* 转换为 int*
double *dblptr = (double*)ptr; // 将 void* 转换为 double*
在实际编程中,void* 主要用于函数参数传递和内存管理,以实现泛型编程的目的,但需要注意的是,在进行类型转换时程序员需确保转换是安全有效的。
2、控制语句关键字(12个)
• if
条件语句,根据提供的条件执行相应的代码块。
• else
条件语句中的否定分⽀,在if后使⽤或与if配合使用,表示当if的条件不满足时执行的代码块。
也可以与if一起形成else if结构以检查多个条件。
• switch、case和default
用于基于表达式的值选择执行多个可能的代码路径,每个case代表一个可能的值,而default则
在所有case都不匹配时执行。
• for
循环结构,包含初始化、条件判断和迭代更新三个部分。
• do
循环语句中的循环体,结合while使用时为do...while:先执行循环体,再检查条件是否满足,至少
执行一次循环体。
• while
循环结构,依赖于循环开始前的条件判断来决定是否执行循环体。
• break
结束当前循环或开关结构的执行。
• continue
跳过本次循环剩余的语句,直接进入下一次循环的条件判断。
• goto
无条件跳转到同一函数内的标签位置(不推荐广泛使用)。
• return
从函数中返回,并可携带一个返回值给调用者;若函数无返回值,则仅表示结束该函数的执行。
3、存储类关键字(5个)
• auto
声明自动变量,表明变量具有自动存储持续期,即在函数或块(如循环体)内定义的局部变量,默认
情况下编译器会为这些变量分配栈上的存储空间,并且当离开它们的作用域时,这些变量将被自动销毁。
从C11标准开始,auto关键字已不再用于声明局部变量的默认存储类别,因为所有局部变量默认都是auto
类型,故现在auto常用于类型推断(C++11及以后版本)。
• extern
声明外部变量,它指示变量是在其他文件或本文件的其他位置定义过的全局变量。使用extern可以在
当前文件中引用已经定义在其他地方的全局变量,而不进行新的定义。
• register
声明寄存器变量,请求编译器尽可能地将变量存储到CPU寄存器而不是内存中,以提高访问速度。
但是否真正放入寄存器由编译器决定,而且可用寄存器的数量有限,所以并非声明为register的变量一定能
存储在寄存器中。
• static
1、在函数内部使用时,声明静态局部变量,这意味着即使函数结束,该变量的值也不会被销毁,下一
次调用函数时其值会被保留。
2、在函数外部或文件作用域使用时,声明静态全局变量,这种变量的作用范围仅限于定义它的文件,不
会被其他文件通过extern直接访问,称为文件作用域静态变量。
• const
1、在C语言中,声明只读变量或指针指向的内容不可修改。对于基本类型的const变量,在初始化后
就不能再更改其值。
2、C和C++中的区别在于C++中对const更灵活的使用,比如可以用来实现常量引用、定义常量成员函
数等,并支持顶层const和底层const的区别(指出是对象本身不可变还是对象所指数据不可变)。
4. 其他关键字(3个)
• sizeof
这是一个C语言的关键字,它用于计算一个类型或变量在内存中所占用的字节数。
例如:
int a;
printf("%zu\n", sizeof(a)); // 输出int类型的大小,通常是4个字节(取决于编译器和平台)
也可以直接对类型使用sizeof来获取类型大小:
printf("%zu\n", sizeof(int)); // 输出int类型的大小
• typedef
该关键字用于为已存在的数据类型创建一个新的名称,即给数据类型取别名。这有助于提高代码可读
性和简化复杂的类型定义。
typedef unsigned long ulong; // 创建了一个新的类型别名ulong,表示unsigned long类型
ulong myVariable; // 现在可以使用新类型名来声明变量
• volatile
此关键字用于修饰变量,告知编译器该变量的值可能在程序执行过程中由硬件、中断服务程序或其他
并发线程修改,因此编译器不能对此变量进行优化(例如删除看似冗余的读写操作)。这对于处理硬件寄存
器、信号量等多线程共享资源或者实时系统中的变量非常有用。
volatile int flag; // 指示编译器不要对flag进行优化,因为它可能被外部因素改变
5、附:预处理指令
ANSI标准定义的C语⾔预处理指令
ANSI C(即ISO/IEC 9899:1989或通常称为C89)定义了一系列预处理指令,这些指令由预处理器在编译阶段执行。以下是ANSI C标准中预处理指令的总结:
1、宏定义与取消定义
• #define:用于定义宏,可以是对象型宏(简单文本替换)或函数型宏(带参数的宏)。
#define CONSTANT_NAME value
#define MACRO(param1, param2) expression
• #undef:用于取消已经定义过的宏。
#undef CONSTANT_NAME
2、 文件包含
• #include:用于将另一个源代码文件的内容插入当前文件进行编译。
#include <stdio.h> // 包含系统头文件
#include "myheader.h" // 包含用户自定义头文件
3、条件编译
• #if、#elif、#else、#endif:构成条件编译结构,根据条件决定是否编译特定部分的代码。
#if defined(MACRO)
// 当MACRO被定义时,编译这部分代码
#elif (expression)
// 如果上述条件不满足且expression为真,则编译这部分代码
#else
// 上述所有条件都不满足时,编译这部分代码
#endif
• #ifdef 和 #ifndef:简化了对某个标识符是否定义的检查。
#ifdef MACRO
// 如果MACRO已定义,则编译这部分
#ifndef MACRO
// 如果MACRO未定义,则编译这部分
#endif
4、行号与文件名信息
• __LINE__:表示当前行号。
• __FILE__:表示当前源文件的名称。
5、特殊标记
• _Pragma(注:这是C99及后续标准引入的扩展):允许在预处理器级别使用pragma指令。
_Pragma("message(\"Hello from pragma\")")
6、 其他预处理操作符
• 在宏定义中使用的#和##运算符:
• #(字符串化操作符):把宏参数转换成字符串字面量。
• ##(连接操作符):用于在宏展开过程中连接两个标记形成新的标记。
注意:上述信息基于历史上的ANSI C标准(C89),在现代C标准(如C99、C11)中,预处理器功能有所增强,并添加了一些新特性,例如_Pragma关键字以及对于预定义宏的更多支持。
ANSI标准C定义的宏
ANSI C标准定义了一些预处理器宏,这些宏由编译器自动提供并可以用于源代码中获取关于编译时的信息。以下是ANSI C标准(ISO C89或C90)中预定义的宏:
1. __DATE__:
包含当前日期作为一个字符串,格式通常是 "Mmm dd yyyy",例如:“Feb 25 2024”。
2. __FILE__:
包含当前源文件名作为一个字符串。
3. __LINE__:
表示当前行号的整数值。
4. __STDC__:
如果编译器遵循ANSI C标准,则此宏被定义为非零值。这个宏常用来检查是否在符合标准的环境中编译代码。
另外,在后续的C标准(如C99、C11等)中,还可能定义了额外的预处理宏,例如:
1. __STDC_VERSION__:
当编译器支持某个具体C标准版本时,这个宏将被定义为一个特定的整数值,表示该标准版本号。
2. __TIME__:
包含当前时间作为一个字符串,格式通常是 "hh:mm:ss",例如:“14:32:47”。
这些宏在编写调试信息、条件编译、错误报告等方面非常有用,因为它们允许程序员在运行时刻获取编
译时的相关信息。请注意,不同的编译器可能会有细微差异,但通常都会遵守ANSI C和后续C标准关于预定义
宏的规定。
• #include:#include <stdio.h>
#include 是预处理器指令,用于将指定文件的内容插入到当前源文件中。
有两种形式:
1、包含系统头文件:使用尖括号 <file> 形式,编译器会从标准库目录中查找头文件(例如 stdio.h、
string.h 等)。
2、包含用户自定义头文件:使用双引号 "file" 形式,编译器会首先在当前源代码所在目录查找头文件。
头文件通常包含函数声明、宏定义和类型声明等,是实现模块化编程的关键工具之一。
• # 运算符
在C语言的宏定义中,# 符号是一个预处理运算符,称为字符串化操作符。它用于将宏参数转换为字符
串字面量。
例如:
#define STRINGIFY(x) #x
#define MACRO(s) STRINGIFY(s)
int main() {
printf("%s\n", MACRO(MyString)); // 输出 "MyString"
}
上述例子中,STRINGIFY(x) 宏定义接收一个参数,并将其转换成字符串常量;MACRO(s) 则进一步
调用 STRINGIFY() 将传入的参数名转为字符串。
• ## 运算符
## 是另一个预处理器符号,称为连接或粘合操作符,它用于将两个标记连接成一个单独的标记。这通常
在宏定义中用于创建新的标识符或者消除宏参数与后续文本之间的空白。
例如:
#define CONCAT(a, b) a##b
#define JOIN(c, d) CONCAT(c, d)
int main() {
enum { JOIN(VALUE_, 5) = 5 }; // 宏展开后变成:enum { VALUE_5 = 5 };
}
上述例子中,CONCAT(a, b) 宏定义会把 a 和 b 连接成一个新的名字,JOIN(c, d) 则通过传递给
CONCAT 来连接 c 和 d,最终生成一个新的枚举值名称 VALUE_5。
• #pragma
#pragma 是一个非标准但被广泛支持的预处理器指令,允许程序员向编译器提供特殊指令,这些指令对特定的编译器具有特定的意义。不同编译器可能会有不同的 #pragma 指令集,以下是一些常见的用法:
1、#pragma message
#pragma message("自定义消息")
这个指令会在编译时输出一条用户自定义的消息到编译器的输出窗口,通常用于在编译过程中提示开发
者一些信息,而不影响程序的行为。
2、#pragma once
#pragma once
用于头文件(header file)中,作为一种防止头文件被多次包含的机制。当编译器遇到此指令时,会
确保该文件在整个编译单元中只被包含一次,从而避免因多重包含而产生的符号重复定义错误。
这可以作为 #ifndef/#define/#endif 防止重复包含的传统方法的一个替代方案。
3、代码段分割/内存布局:
#pragma code_seg(["section-name"["section-class"]])
在某些编译器中,可以用这个指令来指定一段代码应该放在特定的内存区域或节(section)。这对于
控制代码的物理布局、优化内存访问以及实现特定平台的安全特性等场景有用。
4、数据对齐
#pragma pack(n)
用来改变结构体(struct)或联合体(union)成员的默认内存对齐方式,n 表示字节对齐数。例如,
设置为1则所有成员按照一字节对齐,这对于跨平台通信或与硬件接口编程时特别重要。
5、编译器依赖检查
#pragma GCC dependency "filename"
在GCC编译器中,可以使用类似的指令来声明当前源文件依赖于另一个文件,如果依赖文件比当前文件
新,则会产生警告信息。
6、资源管理
#pragma resource "*.dfm"
在某些开发环境(如Borland C++ Builder)中,这条指令用于将资源文件(比如描述窗体外观的DFM
文件)链接到项目中。
7、优化选项
不同编译器可能有相应的 #pragma 指令来开启或关闭特定的优化特性,如循环展开、函数内联等。
总之,对于具体的 #pragma 指令及其参数,应当参考所使用的编译器文档来获得准确的用法说明。由于其非标准化特性,在不同的编译环境下可能具有极大的差异性。