C 语言基础:注释、关键字、变量与数据类型
- 1C 语言发展史
- 2C 语言开发环境的安装与配置
- 3C 语言初识:程序运行机制与基本结构
- 4C 语言基础:注释、关键字、变量与数据类型本文
【一】注释
注释用于补充代码的设计意图、使用条件和注意事项,不参与程序运行。C 语言支持 // 单行注释和 /* ... */ 块注释,其中 // 从 C99 开始被正式纳入标准。
【1】单行注释
// 单行注释- 单行注释以
//开始,到当前行末尾结束。它既可以单独占一行,也可以写在代码行末尾。
【2】多行注释
/* 单行注释 *//* 多行注释 多行注释 多行注释 */- 块注释以
/*开始、以*/结束,可以写成一行或跨越多行,适合补充较长的说明。
#include <stdio.h>
// 这是单行注释
/*这是多行注释它可以跨越多行*/
int main(void) { // 打印一条消息 printf("Hello, World!\n"); return 0;}【3】文档注释(文本注释)
文档注释本质上仍是块注释,C 语言标准并没有单独规定“文档注释”语法。Doxygen 等文档生成工具会识别 /** ... */ 中的特定标记,并据此生成接口文档。
/** 本质是多行注释 文档注释的功能 : 添加批注*/#include <stddef.h>
/*** @brief 计算字符串的长度** @param str 指向以空字符结尾的字符串* @return 字符串的长度*/size_t string_length(const char *str);上例只展示函数声明及其文档说明。@brief、@param 和 @return 是 Doxygen 常用标记,不是 C 语言关键字。
【4】注意
-
块注释不能嵌套。例如,
/* 外层 /* 内层 */ 外层 */会在遇到第一个*/时结束,后续内容可能引发编译错误。 -
字符串字面量和字符常量中的
//、/*、*/只是普通字符,不会被识别为注释。例如,"https://example.com"中的//不会开始一段注释。 -
注释是写给人看的,应简洁明了,重点解释“为什么这样做”、约束条件和容易忽略的问题,而不是机械重复代码已经表达的内容。
-
良好的注释习惯是专业编程的重要组成部分,有助于代码维护和团队协作。
【二】关键字
关键字是由 C 语言标准预先赋予特殊含义的单词,不能用作变量名、函数名或其他标识符。不同 C 标准引入的关键字有所不同,下表首先列出 C90 的 32 个经典关键字。
| 关键字 | 说明 |
|---|---|
| auto | 声明自动变量(局部变量默认存储类型,现代 C 中很少显式使用) |
| break | 跳出当前循环或 switch 语句 |
| case | switch 语句中的分支标签 |
| char | 声明字符类型;sizeof(char) 始终等于 1,但 1 字节不一定等于 8 位 |
| const | 添加只读限定,表示不能通过该左值修改对象 |
| continue | 结束本次循环迭代,直接开始下一轮循环 |
| default | switch 语句中的默认分支(所有 case 均不匹配时执行) |
| do | do-while 循环的循环体,循环体至少执行一次 |
| double | 声明双精度浮点型变量或函数返回值类型(通常占 8 字节) |
| else | 条件语句的否定分支(与 if 连用) |
| enum | 声明枚举类型,用于定义一组命名的整型常量 |
| extern | 声明变量或函数在其它文件或本文件的其他位置定义 |
| float | 声明单精度浮点型变量或函数返回值类型(通常占 4 字节) |
| for | for 循环语句,适合已知循环次数的场景 |
| goto | 无条件跳转语句(应谨慎使用,可能降低代码可读性) |
| if | 条件判断语句 |
| int | 声明整型变量或函数返回值类型(通常占 4 字节) |
| long | 声明长整型变量或函数返回值类型 |
| register | 建议编译器将变量存入寄存器以提高访问速度(现代编译器通常自动优化) |
| return | 函数返回语句,可带返回值,也可不带(void 函数) |
| short | 声明短整型变量或函数返回值类型(通常占 2 字节) |
| signed | 声明有符号类型变量(可表示负数,整型默认有符号) |
| sizeof | 获取类型或表达式结果类型所占的字节数;涉及 VLA 时可能在运行期求值 |
| static | 使局部变量具有静态存储期,或使文件作用域名称具有内部链接 |
| struct | 声明结构体类型,将不同类型的数据组合成一个整体 |
| switch | 多分支选择语句,根据表达式的值跳转到对应 case |
| typedef | 为已有数据类型定义新的别名,提高代码可读性 |
| unsigned | 声明无符号类型变量(只能表示非负数,范围更大) |
| union | 声明共用体类型,所有成员共享同一块内存空间 |
| void | 表示无值类型;可声明无返回值函数、无参数函数或通用对象指针 void * |
| volatile | 表示对象可能以编译器无法预知的方式变化,要求相关访问按抽象机规则发生 |
| while | while 循环语句,适合循环次数未知但终止条件明确的场景 |
【1】C99 新增关键字
_Bool | _Complex | _Imaginary | inline | restrict |
|---|---|---|---|---|
C99 新增的关键字主要用于:布尔类型支持(_Bool)、复数支持(_Complex、_Imaginary)、内联函数优化(inline)、指针别名限制优化(restrict)。
_Imaginary与虚数类型属于实现可选功能,并非所有编译器都提供支持。
【2】C11 新增关键字
_Alignas | _Alignof | _Atomic | _Generic | _Noreturn |
|---|---|---|---|---|
_Static_assert | _Thread_local |
C11 新增的关键字主要用于:内存对齐控制(_Alignas、_Alignof)、原子操作与多线程支持(_Atomic、_Thread_local)、泛型选择(_Generic)、静态断言(_Static_assert)和标记无返回值函数(_Noreturn)。
【3】C23 新增的常用关键字
alignas | alignof | bool | constexpr | false | nullptr |
|---|---|---|---|---|---|
static_assert | thread_local | true | typeof | typeof_unqual | _BitInt |
C23 为部分以下划线开头的旧写法提供了更自然的拼写,并加入 constexpr、nullptr、typeof、typeof_unqual 和 _BitInt 等功能。C23 较新,实际使用前应确认编译器版本及其支持情况。
【三】标识符
标识符是变量、函数、数组、类型等程序实体的名称,通常由程序员定义。对于基础学习,可以将规则概括为:标识符由英文字母、数字和下划线组成,但不能以数字开头。
一个常见的标识符以字母 A-Z、a-z 或下划线 _ 开始,后面可以跟零个或多个字母、下划线或数字 0-9。
C 标识符中不能随意使用 @、$、%、空格和连字符等字符。C 语言区分大小写,因此 Manpower 和 manpower 是两个不同的标识符。下面列出几个有效的标识符:
mohd zara abc move_name a_123myname50 _temp j a23b9 retVal以下是一些无效的标识符示例及原因:
2abc // 错误:以数字开头my-name // 错误:包含连字符 -float // 错误:与关键字重名my name // 错误:包含空格命名建议:标识符应具有描述性,能清晰表达其用途。常见的命名风格有下划线命名法(my_variable)和小驼峰命名法(myVariable),在同一项目中保持一致即可。
以下划线开头的名称还涉及标准保留规则:
- 任何位置都不要自定义以双下划线
__开头的标识符。 - 任何位置都不要自定义以下划线加大写字母开头的标识符,如
_Value。 - 文件作用域中,应避免定义任何以下划线开头的标识符。
这些名称通常保留给编译器、标准库和具体实现使用。
【四】常量
常量可以理解为程序中直接使用的固定值,或在程序中不允许通过某个名称修改的对象。学习时需要区分字面量、const 限定对象和宏常量,它们虽然都常被称作“常量”,但语言机制并不相同。
常量可以是整型常量、浮点型常量、字符常量、枚举常量等:
const int MAX = 100; // const 限定的整型对象const double PI = 3.1415926; // const 限定的浮点型对象const char NEWLINE = '\n'; // const 限定的字符型对象其中,100、3.1415926 和 '\n' 是字面量;MAX、PI 和 NEWLINE 是具有类型的 const 限定对象。const 表示不能通过这些名称修改对应对象,但它并不等同于预处理宏。
字符常量中常用的转义字符包括:
\n:换行\t:水平制表符\\:反斜杠\':单引号\0:空字符,数值为 0,常用作 C 字符串的结束标志
与 #define 宏相比,const 限定对象具有明确类型,编译器可以进行类型检查,也能在调试器中观察它。二者用途并不完全相同,应根据使用场景选择。
常见的字面量可以分为以下几类:
42 // 整数字面量3.14 // 浮点数字面量,默认类型为 double3.14f // float 类型的浮点数字面量'A' // 字符常量"Hello" // 字符串字面量C99 引入 _Bool 类型;包含 <stdbool.h> 后,在 C17 及更早标准中通常可以使用 bool、true 和 false 宏。C23 将 bool、true 和 false 纳入语言本身。
#include <stdbool.h>
bool is_ready = true;C 语言不存在名为
null的通用“空常量”。表示空指针时,传统写法使用NULL或整数常量0;C23 还引入了nullptr。空指针与表示字符串结尾的空字符'\0'是两个不同概念。
【1】字符串字面量
字符串字面量是由双引号括起来的字符序列,用于表示文本数据。
字符串字面量在内存中以字符数组形式表示,末尾会自动添加空字符 \0(null character),用于标记字符串结束。
char greeting[] = "Hello, World!"; // 编译器自动计算长度(包含 \0)以下是关于字符串字面量的几点重要说明:
- “Hello” 在内存中实际占 6 个字节:
'H'、'e'、'l'、'l'、'o'、'\0'。 - 修改字符串字面量会产生未定义行为,因此不应通过指针修改它;若需要可修改的字符串,应使用字符数组保存其副本。
- 两个相邻的字符串字面量会被编译器自动拼接:
"Hello, " "World!"等同于"Hello, World!"。 - 字符
'A'(字符常量)和字符串"A"(字符串字面量)是不同的,后者包含'A'和'\0'两个字符。
1. 整数字面量:10、20、0xFF、0772. 浮点数字面量:3.14、-0.5、1.0e3、2.5f3. 字符常量:'a'、'A'、'0'、' '、'!'、'\0'、'\n'4. 字符串字面量:"任意内容"、""(空字符串)5. 布尔值:false 和 true(具体用法取决于采用的 C 标准)
主要用途:1. 参与表达式计算或通过 printf 输出,此时应使用与类型相匹配的格式说明符。2. 为变量提供初始值,例如:int a = 10; char ch = 'a';普通字符常量通常使用单引号,例如 'A'。源代码中的中文字符涉及源文件编码、执行字符集和实现支持,不能简单假设 '我' 能像普通单字节字符一样存入 char。处理中文文本时通常使用字符串及明确的字符编码方案,相关内容会在字符串章节中进一步说明。
【2】自定义常量
自定义常量常见有三种方式:const 限定对象、对象式宏和枚举常量。
(1)使用 const
const int MAX_STUDENTS = 50;const double TAX_RATE = 0.13;这种写法具有明确的数据类型,适合定义在运行过程中不应被修改的值。
(2)使用 #define
#define BUFFER_SIZE 1024#define PI 3.1415926#define 由预处理器执行文本替换,没有变量类型,也不会为宏名称本身分配存储空间。为了便于识别,宏名称通常使用全大写字母和下划线。
宏定义末尾通常不写分号,否则分号也会成为替换内容的一部分:
#define MAX_COUNT 100 // 正确(3)使用枚举常量
enum Weekday { MONDAY = 1, TUESDAY, WEDNESDAY};枚举常量适合表示一组相互关联的命名整数值。在上例中,MONDAY 为 1,后续未显式赋值的成员依次递增,因此 TUESDAY 为 2、WEDNESDAY 为 3。
【五】变量
变量是具有名称、类型和值的程序对象,其保存的值可以在允许的范围内发生变化。“可以变化”表示变量具备修改能力,并不意味着它在程序运行期间一定会被修改。
例如,鸡蛋的单价可能随时间改变,因此可以使用变量保存;某一时刻赋给该变量的 1.0 则是一个固定的字面量。
从入门角度看,可以把变量理解为一块用于存放数据的存储区域:
- 变量类型决定数据的表示方式、允许执行的操作及通常需要的存储空间。
- 变量名用于在源代码中标识这个对象。
- 变量值是当前保存在对象中的数据。
- 变量地址表示对象在存储空间中的位置,可以使用取地址运算符
&获取可寻址对象的地址。
“变量一定真实占用一块独立内存”是便于入门的理解。经过优化后,编译器可能把变量保存在寄存器中,甚至完全消除不必要的变量;程序应依据 C 语言规则使用变量,而不应依赖编译器具体如何存储它。
【1】变量的声明、定义与初始化
int age; // 定义变量,但没有显式初始化int score = 100; // 定义变量并初始化double price = 1.0; // 定义并初始化 double 变量
score = 95; // 赋值:修改变量已有的值基本格式如下:
数据类型 变量名 = 初始值;- 数据类型:规定数据的表示方式、取值范围和可执行的操作。
- 变量名:在程序中访问变量时使用的标识符。
=:在定义中表示初始化,在普通语句中表示赋值。- 初始值:变量创建时获得的第一个值。
初始化与赋值看起来都使用 =,但发生时机不同:初始化发生在对象创建时,而赋值用于修改已经存在的对象。
声明用于向编译器说明名称及其类型,定义还会真正创建对应的对象。变量定义本身也是一种声明;使用 extern 且不带初始化器的写法通常只进行声明:
extern int shared_count; // 声明:定义位于其他位置int shared_count = 0; // 定义:创建对象并初始化同一个作用域中可以存在相互兼容的重复声明,但一个对象通常只能有一个定义。
【2】未初始化变量
函数内部的普通局部变量如果没有显式初始化,其值通常是不确定的。读取这种不确定值通常会导致未定义行为,因此应遵循“先初始化,再使用”的原则。
int count;printf("%d\n", count); // 错误示例:count 尚未初始化可以在定义时给出初始值:
int count = 0;printf("%d\n", count); // 正确:输出 0文件作用域变量以及使用 static 声明的对象,如果没有显式初始化,会按规则进行零初始化:
static int total; // 自动初始化为 0“垃圾值”是日常教学中的形象说法,但不够严谨。标准术语是不确定值;问题也不只是输出不可预测,读取它还可能导致未定义行为。
【3】作用域与生命周期
- 作用域决定变量名可以在源代码的哪些位置被访问。
- **生命周期(存储期)**决定变量对应的对象从何时存在到何时结束。
函数内部定义的普通局部变量通常只能在所在代码块中访问,并在进入代码块时创建、离开代码块时结束。文件作用域变量和静态局部变量通常在整个程序运行期间存在。
int global_count = 0; // 文件作用域变量
void increase(void){ static int calls = 0; // 仅在本函数中可见,但值会跨调用保留 int step = 1; // 普通局部变量
calls += 1; global_count += step;}【六】数据类型
数据类型规定一组数据的表示方式、取值范围以及可以对其执行的操作。C 语言是静态类型语言,变量和函数的类型在编译阶段就需要确定。
C 语言没有“引用数据类型”这一类别。引用是 C++ 等语言中的概念;C 语言通过指针间接访问对象。
从入门角度,可以将常见类型整理如下:
1. 基本类型 a. 字符类型:char、signed char、unsigned char b. 整数类型:short、int、long、long long 及其 signed/unsigned 形式 c. 布尔类型:_Bool(C99);bool 是常用写法 d. 浮点类型:float、double、long double
2. 枚举类型 enum
3. void 类型 表示“无值”,常用于无返回值函数和通用对象指针 void *
4. 派生类型 指针、数组和函数类型
5. 结构与联合类型 struct、union【1】整数类型
整数类型用于表示没有小数部分的数值。signed 类型可表示负数、零和正数,unsigned 类型只表示非负数。
| 类型 | 常见含义 | 说明 |
|---|---|---|
char | 字符或小整数 | 大小始终为 1 字节;是否有符号由实现决定 |
short | 短整型 | 至少 16 位 |
int | 基本整型 | 通常是平台上运算较自然的整数类型,至少 16 位 |
long | 长整型 | 至少 32 位 |
long long | 长长整型 | 至少 64 位 |
C 标准只规定各类型的最小表示范围和相对等级,并不保证 int 永远是 4 字节。实际大小取决于编译器和目标平台,可用 sizeof 运算符查看。
#include <stdio.h>
int main(void){ printf("sizeof(char) = %zu\n", sizeof(char)); printf("sizeof(short) = %zu\n", sizeof(short)); printf("sizeof(int) = %zu\n", sizeof(int)); printf("sizeof(long) = %zu\n", sizeof(long)); printf("sizeof(long long) = %zu\n", sizeof(long long)); return 0;}sizeof 的结果类型是 size_t,使用 printf 输出时应采用 %zu 格式说明符。
【2】浮点类型
浮点类型用于表示带小数部分或范围很大的近似数值:
| 类型 | 常见精度 | 说明 |
|---|---|---|
float | 约 6~9 位十进制有效数字 | 单精度浮点类型 |
double | 约 15~17 位十进制有效数字 | 双精度浮点类型 |
long double | 不低于 double | 具体精度由实现决定 |
浮点数采用有限位数表示,很多十进制小数无法被精确存储,因此不应简单使用 == 判断两个计算所得的浮点数是否相等。
double result = 0.1 + 0.2;printf("%.17f\n", result); // 结果可能不是精确的 0.3【3】布尔类型
C99 引入 _Bool 类型,其值会归一化为 0 或 1。在 C17 及更早标准中,通常包含 <stdbool.h> 后使用更易读的 bool、true 和 false:
#include <stdbool.h>
bool is_valid = true;在条件判断中,数值 0 表示假,任何非零值都表示真。C23 已将 bool、true 和 false 纳入语言本身。
【4】字符类型与字符编码
char 本质上属于整数类型,也常用于保存字符编码单元。字符常量使用单引号,字符串字面量使用双引号:
char grade = 'A';char message[] = "Hello";sizeof(char) 永远等于 1,但 C 标准规定的“字节”不一定是 8 位。可以通过 <limits.h> 中的 CHAR_BIT 查看每字节的位数;在现代通用平台上通常为 8。
【5】固定宽度整数类型
当程序确实需要宽度明确的整数时,可以使用 <stdint.h> 提供的类型,例如 int8_t、uint32_t 和 int64_t。这些类型只有在实现能够提供对应宽度时才会定义。
#include <stdint.h>
uint32_t color = 0xFF3366U;int64_t distance = 9000000000LL;普通业务代码优先使用 int 等基本类型;处理文件格式、网络协议、硬件寄存器等对位宽有明确要求的场景时,再考虑固定宽度类型。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!














