C 基础语法
C 语言是一种通用的编程语言,广泛应用于系统编程、嵌入式开发和高性能计算等领域。
C 语言具有高效、灵活、可移植性强等特点,是许多其他编程语言(如 C++、Java、Go)的基础,至今仍是操作系统、驱动程序和底层库开发的主流选择。
在 C 语言中,令牌(Token)是程序的基本组成单位,编译器通过对源代码进行词法分析,将代码分解成一个个的令牌。令牌是编译器能识别的最小有意义单元,整个 C 程序都由这些令牌构成。
C 语言的令牌主要包括以下几种类型:
- 关键字(Keywords):语言预留的具有特殊含义的单词,如
int、if、return。 - 标识符(Identifiers):程序员自定义的名称,用于变量、函数、数组等。
- 常量(Constants):程序中固定不变的值,如整数
42、浮点数3.14。 - 字符串字面量(String Literals):由双引号括起来的字符序列,如
"Hello"。 - 运算符(Operators):用于执行运算的符号,如
+、==、&&。 - 分隔符(Separators):用于分隔语句和代码块的符号,如
;、{}。
C 程序的基本结构
这是一个简单的 C 语言程序,可以输出 "Hello, World!":
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
以上代码组成结构如下:
- 预处理器指令:如
#include和#define,在编译前由预处理器处理。 - 主函数:每个 C 程序都有且只有一个
main()函数,是程序的执行入口。 - 变量声明:声明程序中使用的变量,C89 要求声明必须在代码块最开头。
- 函数定义:定义程序中使用的函数,描述函数的具体实现逻辑。
更复杂一点的 C 程序结构说明(后面章节会对每个知识点展开说明):
#include <stdio.h> // 头文件包含
#define PI 3.14159 // 宏定义
// 函数声明
int add(int a, int b);
int main() { // 主函数
// 变量声明
int num1, num2, sum;
// 用户输入
printf("Enter two integers: ");
scanf("%d %d", &num1, &num2);
// 函数调用
sum = add(num1, num2);
// 输出结果
printf("Sum: %d\n", sum);
return 0; // 返回 0 表示程序成功执行
}
// 函数定义
int add(int a, int b) {
return a + b;
}
头文件包含
- 头文件通常在程序的开头使用
#include指令包含。头文件提供了函数和库的声明,如标准输入输出库<stdio.h>、标准库<stdlib.h>等。它们定义了函数、宏、常量等使程序能够使用预定义的库函数。 - 系统头文件使用尖括号
<>,自定义头文件使用双引号"",两者的搜索路径不同。 - 示例:
#include <stdio.h>(系统头文件)、#include "myheader.h"(自定义头文件)
宏定义
- 宏是通过
#define指令定义的符号常量或代码片段。宏在编译前由预处理器进行文本替换,不占用运行时内存,常用于定义常量或简单的内联代码逻辑。 - 宏没有类型检查,使用时需谨慎;对于有类型要求的常量,推荐使用
const变量替代。 - 示例:
#define PI 3.14159、#define MAX(a, b) ((a) > (b) ? (a) : (b))
函数声明
- 在 C 语言中,函数的声明(也称原型)必须在函数定义或调用之前出现。声明提供了函数的返回类型、函数名和参数列表,以便编译器在调用点进行类型检查。
- 函数声明可以放在头文件中,供多个源文件共享使用。
- 示例:
int add(int a, int b);
主函数
main()函数是 C 程序的入口点,每个 C 程序都必须包含一个main()函数。程序从main()开始执行,返回值通常为0表示程序成功执行,非零值表示出现了错误。main()函数也可以接收命令行参数:int main(int argc, char *argv[]),其中argc是参数个数,argv是参数字符串数组。- 示例:
int main() { ... }
变量声明
- 在 C 程序中,所有变量必须在使用前声明其类型。变量可以在
main()函数中声明(局部变量),也可以在函数外部全局声明(全局变量)。 - 声明变量时可同时进行初始化,未初始化的局部变量包含不确定值(垃圾值),而全局变量和静态变量会被自动初始化为
0。 - 示例:
int count = 0;、float price = 9.99;、char grade = 'A';
语句和表达式
- 语句是 C 程序的基本执行单元,通常是函数调用、赋值、控制语句(如
if、for等)或表达式。表达式是由变量、操作符和常量组成的代码片段,能够计算出一个值。 - C 语言中,赋值本身也是一个表达式,具有值(即被赋的值),因此可以写出
a = b = c = 0;这样的链式赋值。 - 示例:
printf("Enter two integers: "); sum = add(num1, num2);
控制流语句
- 控制流语句用于控制程序执行的顺序,包括条件判断(
if/else、switch)和循环(for、while、do-while)。还有跳转语句break、continue、goto和return。 - 合理使用控制流语句是写出逻辑清晰程序的关键,应避免滥用
goto以保持代码可读性。 - 示例:
if (num1 > num2) { printf("num1 is greater than num2"); }
函数定义
- 函数定义包含完整的函数体,描述了函数的具体实现。一个函数由返回类型、函数名、参数列表和函数体组成。良好的函数设计应遵循"单一职责"原则,每个函数只做一件事。
- C 语言的函数不支持重载,每个函数名在同一作用域中必须唯一。函数可以递归调用自身。
- 示例:
int add(int a, int b) { return a + b; }
返回语句
return语句用于终止函数的执行,并将控制权连同返回值一起交还给调用者。void类型的函数可以使用不带值的return;提前退出,也可以省略return语句。main()函数的返回值会传递给操作系统,0通常表示成功,可使用标准宏EXIT_SUCCESS和EXIT_FAILURE(定义在<stdlib.h>)。- 示例:
return 0;、return EXIT_SUCCESS;
分隔符
分隔符用于分隔语句、表达式和代码块,帮助编译器理解程序的结构,常见的分隔符包括:
- 逗号 ,:用于分隔变量声明、函数参数或初始化列表中的多个元素。
- 分号 ;:用于结束一条语句,是 C 语言中最常见的分隔符。
- 括号:
- 圆括号
():用于函数调用的参数列表、控制流语句的条件,以及强制表达式求值顺序。 - 花括号
{}:用于定义函数体、控制流代码块,以及复合字面量与数组/结构体的初始化。 - 方括号
[]:用于数组的声明和元素访问(下标运算)。
- 圆括号
在 C 程序中,分号 ; 是语句结束符,每个语句必须以分号结束,它表明一个逻辑实体的结束。缺少分号是初学者最常见的语法错误之一。
例如,下面是两个不同的语句:
printf("Hello, World! \n");
return 0;
一个单独的分号也可以作为一个空语句,表示什么都不做,常用于某些循环结构中:
while (condition); // 空循环体,等待条件变化
注释
C 语言有两种注释方式:
// 单行注释
以 // 开始的单行注释(C99 引入),这种注释从 // 起到行尾结束,可以单独占一行,也可以放在代码行的末尾。
/* 单行注释 */ /* 多行注释 多行注释 多行注释 */
/* */ 这种格式的注释可以单行或多行,适合用于函数说明、模块头部等较长的注释场景。
// 这是单行注释
/*
这是多行注释
它可以跨越多行
*/
int main() {
// 打印一条消息
printf("Hello, World!\n");
return 0;
}
使用注释时需注意以下几点:
- 不能在注释内嵌套注释(
/* /* */ */是非法的)。 - 注释不能出现在字符串字面量或字符常量内部。
- 注释是写给人看的,应简洁明了,重点解释"为什么"而非"做了什么",代码本身应该能表达"做什么"。
- 良好的注释习惯是专业编程的重要组成部分,有助于代码维护和团队协作。
标识符
标识符是程序中变量、函数、数组、类型等的名字,由程序员自行定义。标识符由字母(大写或小写)、数字和下划线组成,但第一个字符必须是字母或下划线,不能是数字。
一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)。
C 标识符内不允许出现标点字符,比如 @、$ 和 %。C 是区分大小写的编程语言。因此,在 C 中,Manpower 和 manpower 是两个不同的标识符。下面列出几个有效的标识符:
mohd zara abc move_name a_123 myname50 _temp j a23b9 retVal
以下是一些无效的标识符示例及原因:
2abc // 错误:以数字开头 my-name // 错误:包含连字符 - float // 错误:与关键字重名 my name // 错误:包含空格
命名建议:标识符应具有描述性,能清晰表达其用途。常见的命名风格有下划线命名法(my_variable)和小驼峰命名法(myVariable),在同一项目中保持一致即可。以双下划线 __ 开头的标识符通常由编译器或标准库保留,应避免在用户代码中使用。
常量
常量是固定值,在程序执行期间不会改变。C 语言中定义常量主要有两种方式:使用 const 关键字或使用 #define 宏定义。
常量可以是整型常量、浮点型常量、字符常量、枚举常量等:
const int MAX = 100; // 整型常量 const float PI = 3.14159; // 浮点型常量 const char NEWLINE = '\n'; // 字符常量(转义字符)
字符常量中常用的转义字符包括:
'\n':换行'\t':水平制表符'\\':反斜杠'\'':单引号'\0':空字符(字符串结束标志)
使用 const 定义的常量有类型信息,编译器可以进行类型检查,相比 #define 宏更安全,是 C99 及之后推荐的方式。
字符串字面量
字符串字面量是由双引号括起来的字符序列,用于表示文本数据。
字符串字面量在内存中以字符数组的形式存储,末尾会自动添加一个空字符 \0(即 null 终止符),标志字符串的结束。
char greeting[] = "Hello, World!"; // 编译器自动计算长度(包含 \0)
以下是关于字符串字面量的几点重要说明:
- "Hello" 在内存中实际占 6 个字节:
'H'、'e'、'l'、'l'、'o'、'\0'。 - 字符串字面量存储在只读内存区域,不可修改;若需修改,应将其复制到字符数组中。
- 两个相邻的字符串字面量会被编译器自动拼接:
"Hello, " "World!"等同于"Hello, World!"。 - 字符
'A'(字符常量)和字符串"A"(字符串字面量)是不同的,后者包含'A'和'\0'两个字符。
运算符(Operators)
运算符用于执行各种操作,如算术运算、逻辑运算、比较运算等。运算符作用于操作数,形成表达式并产生结果。
C 语言中的运算符种类繁多,常见的包括:
- 算术运算符:
+,-,*,/,%(其中/对整数做整除,%取余数) - 关系运算符:
==,!=,>,<,>=,<=(返回 1 表示真,0 表示假) - 逻辑运算符:
&&(与),||(或),!(非),支持短路求值 - 位运算符:
&(按位与),|(按位或),^(按位异或),~(按位取反),<<(左移),>>(右移) - 赋值运算符:
=,+=,-=,*=,/=,%=(复合赋值,如a += b等价于a = a + b) - 自增/自减运算符:
++(自增),--(自减),分前缀(先运算再取值)和后缀(先取值再运算)两种形式 - 其他运算符:
sizeof(计算类型或变量字节大小),?:(三目运算符),&(取地址),*(解引用),->(指针访问成员),.(直接访问成员)
int a = 5, b = 10; int sum = a + b; // 算术运算符 +,sum = 15 int isEqual = (a == b); // 关系运算符 ==,isEqual = 0(假) int result = a << 1; // 位运算左移,result = 10(相当于 a * 2) a++; // 后缀自增,a 变为 6 int max = (a > b) ? a : b; // 三目运算符,max = b = 10
运算符具有优先级和结合性:优先级决定运算顺序,结合性决定同级运算符的运算方向。当不确定优先级时,建议使用圆括号明确分组,以提高代码可读性。
关键字
下表列出了 C 中的保留字。这些保留字不能作为常量名、变量名或其他标识符名称,它们在 C 语言中具有特定的语法含义。
| 关键字 | 说明 |
|---|---|
| auto | 声明自动变量(局部变量默认存储类型,现代 C 中很少显式使用) |
| break | 跳出当前循环或 switch 语句 |
| case | switch 语句中的分支标签 |
| char | 声明字符型变量或函数返回值类型(通常占 1 字节) |
| const | 定义只读常量,被 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 | 计算数据类型或变量所占的字节数(编译期运算符) |
| static | 声明静态变量(局部静态变量生命周期延续到程序结束;全局静态变量限制为文件作用域) |
| struct | 声明结构体类型,将不同类型的数据组合成一个整体 |
| switch | 多分支选择语句,根据表达式的值跳转到对应 case |
| typedef | 为已有数据类型定义新的别名,提高代码可读性 |
| unsigned | 声明无符号类型变量(只能表示非负数,范围更大) |
| union | 声明共用体类型,所有成员共享同一块内存空间 |
| void | 声明函数无返回值或无参数;也用于声明无类型指针 void* |
| volatile | 声明变量可能被程序外部因素(如硬件或中断)改变,禁止编译器优化该变量的读写 |
| while | while 循环语句,适合循环次数未知但终止条件明确的场景 |
C99 新增关键字
| _Bool | _Complex | _Imaginary | inline | restrict |
C99 新增的关键字主要用于:布尔类型支持(_Bool)、复数支持(_Complex、_Imaginary)、内联函数优化(inline)、指针别名限制优化(restrict)。
C11 新增关键字
| _Alignas | _Alignof | _Atomic | _Generic | _Noreturn |
| _Static_assert | _Thread_local |
C11 新增的关键字主要用于:内存对齐控制(_Alignas、_Alignof)、原子操作与多线程支持(_Atomic、_Thread_local)、泛型选择(_Generic)、静态断言(_Static_assert)和标记无返回值函数(_Noreturn)。
C 中的空格
只包含空格的行,被称为空白行,可能带有注释,C 编译器会完全忽略它。
在 C 中,空格用于描述空白符(空格键)、制表符(Tab)、换行符和注释。空格分隔语句的各个部分,让编译器能识别语句中的某个元素(比如 int)在哪里结束,下一个元素在哪里开始。因此,在下面的语句中:
int age;
int 和 age 之间必须至少有一个空格字符(通常是一个空白符),这样编译器才能够区分它们。另一方面,在下面的语句中:
fruit = apples + oranges; // 获取水果的总数
fruit 和 =,或者 = 和 apples 之间的空格字符不是必需的,但是为了增强可读性,您可以根据需要适当增加一些空格。
良好的空格和缩进习惯是高质量代码的重要组成部分,建议遵循以下规范:
- 使用一致的缩进(通常是 4 个空格或 1 个制表符)来表示代码块层级。
- 在运算符两侧添加空格(如
a + b),使表达式更易读。 - 在逗号后添加空格(如
int a, b, c;),区分多个元素。 - 适当使用空行分隔逻辑相关的代码段,提高整体可读性。