C 语言作为一种诞生于 1970 年代的系统编程语言,其设计哲学强调简洁、高效和对硬件的直接控制,因此有意省略了许多现代高级语言中常见的特性。
它不支持面向对象编程,没有类、继承或多态等机制;也没有异常处理系统,错误通常通过返回值或非标准的 setjmp/longjmp 来处理。内存管理完全由程序员手动负责,缺乏垃圾回收机制。此外,C 语言不具备泛型能力,无法编写类型无关的通用数据结构;没有命名空间,容易导致全局符号冲突;也不支持高阶函数或闭包,函数指针无法捕获上下文环境。字符串和数组在 C 中只是原始的 char* 和固定长度的内存块,既无动态扩容能力,也无边界检查。类型必须显式声明(在 C89 中),不支持类型推导,模块化也仅依赖头文件和预处理器,缺乏现代意义上的封装与接口抽象。
⚠️注:C 语言通过库或编码规范可以模拟部分功能(如用结构体+函数指针模拟 OOP),但语言本身不提供语法支持。
尽管如此,C 语言对后续编程语言的发展产生了极其深远的影响。
它确立了以花括号 {} 表示代码块的结构范式,这一风格被 C++、Java、C#、JavaScript、Go、Rust 等几乎所有主流语言所继承。其 if、for、while 等控制流语句的语法几乎成为行业标准。表达式后加分号、函数定义形式、按值传递配合指针模拟引用等设计,也都被广泛沿用。C 的指针模型虽危险,却深刻影响了底层内存操作的理念;其 main() 函数作为程序入口的约定,至今仍是多数语言的惯例。头文件分离声明与实现的思想,也启发了后来的模块和接口设计。可以说,C 语言不仅提供了高效的系统编程能力,更塑造了现代编程语言的语法骨架——即使许多新语言刻意规避 C 的缺陷(如手动内存管理或弱类型转换),它们在控制结构和基本语法上仍深深烙印着 C 的基因。正因如此,C 被誉为“所有现代编程语言的祖父”。
注释
C 语言有两种注释方式。
/* 单行注释 */
/*
多行注释
多行注释
多行注释
*/
注释不能嵌套
打印(Print)
printf() 是 C 标准库 <stdio.h> 里的函数。调用格式为:
printf("<格式化字符串>", <参量表>);
下面是 printf() 函数的声明。
int printf(const char *format, ...)
其中 format 代表一个格式化字符串,包含普通字符和格式说明符 (Format Specifiers,以 % 开头);... 代表可变参数列表,对应格式说明符中的占位符。 printf 函数在执行成功时返回输出的字符数,失败时返回负数。
◾占位符 (格式说明符)
占位符就是先占住一个固定的位置,等着你再往里面添加内容的符号,广泛用于计算机中各类文档的编辑。格式占位符 % 是在 C/C++ 语言中格式输入函数,如 scanf、printf 等函数中使用。其意义就是起到格式占位的意思,表示在该位置有输入或者输出。
不同的占位符可以描述对应参数的数据类型,所以又被称为格式说明符 (Format Specifiers)。常用格式说明符如下:
| 格式符 | 含义 | 对应类型示例 |
|---|---|---|
%d 或 %i |
有符号十进制整数 | int |
%u |
无符号十进制整数 | unsigned int |
%c |
单个字符 | char |
%s |
字符串(以 \0 结尾) |
char[] 或 char* |
%f |
十进制浮点数(float/double) |
默认显示6位小数 |
%lf |
通常用于 double(但 %f 也可) |
实际上 printf 中 %f 和 %lf 等价 |
%e / %E |
科学计数法(小写/大写 e) | 3.14e+02 |
%g / %G |
自动选择 %f 或 %e(更简洁) |
去掉不必要的零 |
%x / %X |
十六进制(小写/大写) | int(如 ff / FF) |
%o |
八进制整数 | int |
%% |
输出一个百分号 % |
—— |
注意:%lf 在 scanf 中用于 double,但在 printf 中 %f 已能处理 double (因为 float 会被自动提升为 double)。
◾格式控制(宽度、精度、对齐等)
最小字段宽度
printf("%5d", 42); // 输出 " 42"(右对齐,总宽5)
左对齐
printf("%-10s", "Hi"); // "Hi "(左对齐,总宽10)
精度(小数位数或字符串最大长度)
printf("%.3f", 3.14159); // 3.142
printf("%.4s", "Hello"); // "Hell"(最多4个字符)
填充字符(默认是空格,可用 0 填充)
printf("%05d", 42); // "00042"
组合使用
printf("%08.2f", 12.3); // "00012.30"
⚠️要安全地处理用户输入的格式字符串 (如 printf(user_input)),务必避免格式字符串漏洞 (format string vulnerability),永远不要让用户控制 printf 的第一个参数!
变量
◾语言变量名规范
-
C 语言变量名只能是英文字母 (A-Z, a-z) 和数字 (0-9) 或者下划线 (_) 组成。
-
必须是英文字母或者下划线开头,不能是数字开头。
-
变量名区分大小写。传统命名习惯中,用小写字母命名变量,大写字母表示符号常量名。
-
不能使用关键字命名。
◾语言中的变量定义和变量声明
extern int a; // 声明一个全局变量 a,不是定义
int a; // 声明并定义一个全局变量 a
extern int a =0; // 声明并初始化 a (给变量初值一定是定义)
int a =0; // 定义并初始化 a
定义也是声明,extern 声明不是定义。变量只能定义一次,而声明却可以多次。定义会分配存储空间,而声明不会。
◾C 中的左值 (Lvalues) 和右值 (Rvalues)
C 中有两种类型的表达式:
- 左值 (lvalue):指向内存位置的表达式被称为左值 (lvalue) 表达式。左值可以出现在赋值号的左边或右边。
- 右值 (rvalue):术语右值 (rvalue) 指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。
变量是左值,因此可以出现在赋值号的左边。数值型的字面值是右值,因此不能被赋值,不能出现在赋值号的左边。因此 int g = 20; 就是一个有效的语句,而 10 = 20; 就不是。
常量
常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。常量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,也有枚举常量。常量一般使用大写字母和下划线作为标识符 (Identifiers)
const int MAX = 100; // 整型常量
const float PI = 3.14; // 浮点型常量
const char NEWLINE = '\n'; // 字符常量
char myString[] = "Hello, world!"; // 字符串型常量,系统对字符串常量自动加一个 '\0'
⚠️字符常量使用单引号 ',字符串常量使用双引号 "。
在 C 中,有两种简单的定义常量的方式:
◾定义方式一:#define 预处理器
下面是使用 #define 预处理器定义常量的形式:
#define 常量名 常量值
下面的代码定义了一个名为 PI 的常量:
#include <stdlib.h>
#define PI 3.14 // 用 define 定义常量
int main() {
int r = 5;
float C = 2 * PI * r;
float S = PI * r * r;
printf("Circumference is %.2f\n", C); // Circumference is 31.40
printf("Square is %.2f\n", S); // Square is 78.50
return(0);
}
在程序中使用该常量时,编译器会将所有的 PI 替换为 3.14159。
◾定义方式二:const 关键字
使用 const 前缀声明指定类型的常量,用于声明一个只读变量,即该变量的值不能在程序运行时修改。方法如下所示:
const 数据类型 常量名 = 常量值;
下面的代码定义了一个名为 MAX_VALUE 的常量:
#include <stdio.h>
int main() {
const int MAX_VALUE = 100; // 用 const 定义常量
int number = 5;
int result = (number > MAX_VALUE) ? MAX_VALUE : number;
printf("%d\n", result); // 5
return 0;
}
#define 与 const 这两种方式都可以用来定义常量,选择哪种方式取决于具体的需求和编程习惯。通常情况下,建议使用 const 关键字来定义常量,因为它具有类型检查和作用域的优势,而 #define 仅进行简单的文本替换,可能会导致一些意外的问题。
运算符(Operators)
运算符用于执行各种操作,如算术运算、逻辑运算、比较运算等。
◾算术运算符(A=10, B=20)
| 运算符 | 描述 | 实例 (A=10, B=20) |
|---|---|---|
| + | 把两个操作数相加 | A + B 将得到 30 |
| - | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 |
| * | 把两个操作数相乘 | A * B 将得到 200 |
| / | 分子除以分母 | B / A 将得到 2 |
| % | 取模运算符,整除后的余数 | B % A 将得到 0 |
| ++ | 自增运算符,整数值增加 1 | A++ 将得到 11 |
| – | 自减运算符,整数值减少 1 | A– 将得到 9 |
◾关系运算符
| 运算符 | 描述 | 实例 (A=10, B=20) |
|---|---|---|
| == | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 为假。 |
| != | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 |
| > | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 为假。 |
| < | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
| >= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 为假。 |
| <= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 |
◾逻辑运算符
| 运算符 | 描述 | 实例 (A=1, B=0) |
|---|---|---|
| && | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 |
| || | 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 | (A || B) 为真。 |
| ! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 | !(A && B) 为真。 |
◾位运算符 位运算符作用于位,并逐位执行操作
| 运算类型 | 运算符 | 数学符号 | 描述 | 实例 |
|---|---|---|---|---|
| 逻辑与 (AND) | & | ∧ | 按位与操作,按二进制位进行"与"运算。运算规则: 0 ∧ 0 = 0 0 ∧ 1 = 0 1 ∧ 1 = 0 1 ∧ 1 = 1 |
(A & B) 将得到 12,即为 0000 1100 |
| 逻辑或 (OR) | | | ∨ | 按位或运算符,按二进制位进行"或"运算。运算规则: 0 ∨ 0 = 0 0 ∨ 1 = 1 1 ∨ 0 = 1 1 ∨ 1 = 1 |
(A | B) 将得到 61,即为 0011 1101 |
| 异或 (XOR) | ^ | ⊕ | 异或运算符,按二进制位进行"异或"运算。运算规则: 0 ⊕ 0 = 0 0 ⊕ 1 = 1 1 ⊕ 0 = 1 1 ⊕ 1 = 0 |
(A ^ B) 将得到 49,即为 0011 0001 |
| 取反 (NOT) | ~ | ¬ | 对操作数的每一位执行逻辑取反操作。运算规则: ¬ 1 = 0 ¬ 0 = 1 |
(~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。 |
| 左移 | « | 将操作数的所有位向左移动指定的位数。 左边的二进制位丢弃,右边补0。 左移 n 位相当于乘以 2 的 n 次方。 |
A « 2 将得到 240,即为 1111 0000 | |
| 右移 | » | 将操作数的所有位向右移动指定的位数。 正数左补 0,负数左补 1,右边丢弃。 右移n位相当于除以 2 的 n 次方。 |
A » 2 将得到 15,即为 0 |
◾赋值运算符
| 运算符 | 含义 | 等价形式 | 实例 (x=10) |
|---|---|---|---|
= |
赋值 | x = expr |
x = 5 → x=5 |
+= |
加后赋值 | x = x + expr |
x += 3 → x=13 |
-= |
减后赋值 | x = x - expr |
x -= 4 → x=6 |
*= |
乘后赋值 | x = x * expr |
x *= 2 → x=20 |
/= |
除后赋值 | x = x / expr |
x /= 3 → x=3(整数) |
%= |
取模后赋值 | x = x % expr |
x %= 4 → x=2 |
◾其他运算符
| 运算符 | 名称 | 用途说明 | 实例 |
|---|---|---|---|
sizeof |
求大小运算符 | 返回类型/变量占用的字节数 | sizeof(int) → 4 |
?: |
条件(三元)运算符 | 简化 if-else | (a > b) ? a : b |
& |
地址运算符 | 取地址 | &x |
* |
解引用 | 取指针指向的值 | *p |
. |
成员访问 | 通过结构体变量访问成员 | point.x |
-> |
指针成员访问 | 通过结构体指针访问成员 | ptr->x(等价 (*ptr).x) |
最后需要注意的是,运算符存在优先级关系,会直接影响运算符的执行顺序。其中 ( ) 的优先级最高,因此也用括号来对默认的优先级关系进行调整。
数据类型
在 C 语言中,数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间,以及如何解释存储的位模式。可分为以下几种:
- 基本数据类型 它们是算术类型,包括整型(int)、字符型(char)、浮点型(float)和双精度浮点型(double)。
- 枚举类型: 它们也是算术类型,被用来定义在程序中只能赋予其一定的离散整数值的变量。
- void 类型: 类型说明符 void 表示没有值的数据类型,通常用于函数返回值。
- 派生类型: 包括数组类型、指针类型和结构体类型。
在 C 语言中没有内建的 String 类型,所谓的“字符串”在 C 中是通过 字符数组 (char[]) 或 指向字符的指针 (char\*) 来表示的。字符串末尾会自动添加一个空字符 \0。
char greeting[] = "Hello, World!";
数组类型和结构类型统称为聚合类型。函数的类型指的是函数返回值的类型。在本章节接下来的部分我们将介绍基本类型,其他几种类型会在后边几个章节中进行讲解。
整数类型
下表列出了关于标准整数类型的存储大小和值范围的细节:
| 类型 | 存储大小 | 值范围 |
|---|---|---|
| char | 1 字节 | -128 到 127 或 0 到 255 |
| unsigned char | 1 字节 | 0 到 255 |
| signed char | 1 字节 | -128 到 127 |
| int | 2 或 4 字节 | -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647 |
| unsigned int | 2 或 4 字节 | 0 到 65,535 或 0 到 4,294,967,295 |
| short | 2 字节 | -32,768 到 32,767 |
| unsigned short | 2 字节 | 0 到 65,535 |
| long | 4 字节 | -2,147,483,648 到 2,147,483,647 |
| unsigned long | 4 字节 | 0 到 4,294,967,295 |
相同长度整数类型的存储大小通常是相同的,例如 long 和 unsigned long 都是 4 字节大小。
浮点类型
下表列出了关于标准浮点类型的存储大小、值范围和精度的细节:
| 类型 | 存储大小 | 值范围 | 精度 |
|---|---|---|---|
| float | 4 字节 | 1.2E-38 到 3.4E+38 | 6 位有效位 |
| double | 8 字节 | 2.3E-308 到 1.7E+308 | 15 位有效位 |
| long double | 16 字节 | 3.4E-4932 到 1.1E+4932 | 19 位有效位 |
头文件 float.h 定义了宏,在程序中可以使用这些值和其他有关实数二进制表示的细节。下面的实例将输出浮点类型占用的存储空间以及它的范围值:
#include <stdio.h>
#include <float.h>
int main()
{
// %E 为以指数形式输出单、双精度实数。
printf("float 存储最大字节数 : %lu \n", sizeof(float)); // float 存储最大字节数 : 4
printf("float 最小值: %E\n", FLT_MIN ); // float 最小值: 1.175494E-38
printf("float 最大值: %E\n", FLT_MAX ); // float 最大值: 3.402823E+38
printf("精度值: %d\n", FLT_DIG ); // 精度值: 6
return 0;
}
浮点类型不同长度之间的存储大小是 2 倍关系。例如 float 是 double 存储大小的一半,double 是 long double 存储大小的一半。
void 类型
void 类型指定没有可用的值。它通常用于以下三种情况下:
- 函数返回为空:C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如
void exit (int status); - 函数参数为空:C 中有各种函数不接受任何参数。不带参数的函数可以接受一个
void。例如int rand(void); - 指针指向
void类型为void *的指针代表对象的地址,而不是类型。例如,内存分配函数void \*malloc( size_t size );返回指向void的指针,可以转换为任何数据类型。
对于 void 类型大致有个印象就行。
类型转换
类型转换是将一个数据类型的值转换为另一种数据类型的值。C 语言中有两种类型转换。
**隐式类型转换:**隐式类型转换是在表达式中自动发生的,无需进行任何明确的指令或函数调用。它通常是将一种较小的类型自动转换为较大的类型,例如,将 int 类型转换为 long 类型或 float 类型转换为 double 类型。隐式类型转换也可能会导致数据精度丢失或数据截断。隐式类型转换实例:
int i = 10;
float f = 3.14;
double d = i + f; *// 隐式将int类型转换为double类型*
**显式类型转换:**显式类型转换需要使用强制类型转换运算符 (type casting operator),它可以将一个数据类型的值强制转换为另一种数据类型的值。强制类型转换可以使程序员在必要时对数据类型进行更精确的控制,但也可能会导致数据丢失或截断。显式类型转换实例:
double d = 3.14159;
int i = (int)d; *// 显式将double类型转换为int类型*
C 语言中还可以进行强制类型转换和整数提升。
控制语句
C 语言的控制语句只有 if 和 switch 两种(其实硬要算的话可以把三目运算符加上)。
if-else语句
int num = 10;
if (num > 0) {
printf("num是正数");
}
连续使用:
int score = 85;
if (score >= 90) {
printf("优秀");
} else if (score >= 80) {
printf("良好");
} else if (score >= 60) {
printf("及格");
} else {
printf("不及格");
}
if 语句中的初始化语句:
int age = 18;
if (age >= 18) {
printf("已成年");
} else {
printf("未成年");
}
C 语言中的条件语句是有括号的,不要忘记了。
switch-case语句
int day = 3;
switch (day) {
case 1:
printf("星期一");
break;
case 2:
printf("星期二");
break;
case 3:
printf("星期三");
break;
case 4:
printf("星期四");
break;
case 5:
printf("星期五");
break;
case 6:
case 7:
printf("周末")
default:
printf("Invalid");
}
每一个 case 内部代码块都需要手动 break;。很麻烦。
循环语句
C 语言不仅支持 for 循环,还支持 while 和 do…while 循环。甚至支持使用 goto 语句。
while 循环
当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。
int number = 3;
while ( number > 0 ) {
printf("现在的 nubmer 值为:%d\n", number);
number--;
}
有一种比较特殊的写法:
while(i >= 1, --j) // 完全等价于 while(--j)
在 C 中,逗号 , 是一个运算符,其规则是:**从左到右依次计算每个表达式,但整个表达式的值等于最后一个表达式的值。**因此该语句会以逗号后的 j 为 while 的判断条件。
do…while 循环
先执行一次循环主体,其他与 while 语句类似。
int number = 3;
do {
printf("现在的 nubmer 值为:%d\n", number);
number--;
} while( number > 0 );
上面的写法和 while 循环中的示例是等价的。
for 循环
多次执行一个语句序列,简化管理循环变量的代码。
for (int number = 3; number > 0; number--) {
printf("现在的 nubmer 值为:%d\n", number);
}
for 循环最大的优势是:初始化、循环条件、更新循环变量的表达式可以写在同一行。并且 for 循环和 while 循环完全等价。而 do…while 循环会先执行一次循环主体,无论初始化的值是否满足循环条件。
函数
C 语言中的函数定义的一般形式如下:
return_type function_name( parameter list )
{
body of the function
}
在 C 语言中,函数由一个函数头和一个函数主体组成。下面列出一个函数的所有组成部分:
- 返回类型:一个函数可以返回一个值。
return_type是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type是关键字void。 - 函数名称:这是函数的实际名称。函数名和参数列表一起构成了函数签名。
- 参数:参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
- 函数主体:函数主体包含一组定义函数执行任务的语句。
以下是 max() 函数的源代码。该函数有两个参数 num1 和 num2,会返回这两个数中较大的那个数:
/* 函数返回两个数中较大的那个数 */
int max(int num1, int num2)
{
/* 局部变量声明 */
int result;
if (num1 > num2) {
result = num1;
} else {
result = num2;
}
return result;
}
◾函数声明
函数声明会告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。函数声明包括以下几个部分:
return_type function_name( parameter list );
针对上面定义的函数 max(),以下是函数声明:
int max(int num1, int num2);
int max(int, int); // 参数的名称在函数声明中并不重要,因此可以这样简写
当您在一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的。在这种情况下,您应该在调用函数的文件顶部声明函数。
◾函数参数
如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。当调用函数时,有两种向函数传递参数的方式:
- 传值调用:该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。
- 引用调用:通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。
默认情况下,C 使用传值调用来传递参数。一般来说,这意味着函数内的代码不能改变用于调用函数的实际参数。
◾常用函数
abs(-10) // 求 int 类型的绝对值 10
floor(3.14) // 对 double 类型取整 3
ceil(3.14) // 对 double 向上取整 4
((ch = fgetc(fp)) != EOF) // 判断文件结束
C 语言标准库中没有 min() 和 max() 函数,需要自行实现。
附件A(资料性)关键字
auto break case char
const continue default do
double else enum extern
float for goto if
int long register return
short signed sizeof static
struct switch typedef union
unsigned void volatile while
C99 新增关键字
_Bool _Complex _Imaginary inline
restrict
C11 新增关键字
unsigned void volatile while
_Alignas _Alignof _Atomic _Generic
_Noreturn _Static_assert _Thread_local
下表列出了 C 中的保留字。这些保留字不能作为常量名、变量名或其他标识符名称。
| 关键字 | 说明 |
|---|---|
| auto | 声明自动变量 |
| break | 跳出当前循环 |
| case | 开关语句分支 |
| char | 声明字符型变量或函数返回值类型 |
| const | 定义常量,如果一个变量被 const 修饰,那么它的值就不能再被改变 |
| continue | 结束当前循环,开始下一轮循环 |
| default | 开关语句中的"其它"分支 |
| do | 循环语句的循环体 |
| double | 声明双精度浮点型变量或函数返回值类型 |
| else | 条件语句否定分支(与 if 连用) |
| enum | 声明枚举类型 |
| extern | 声明变量或函数是在其它文件或本文件的其他位置定义 |
| float | 声明浮点型变量或函数返回值类型 |
| for | 一种循环语句 |
| goto | 无条件跳转语句 |
| if | 条件语句 |
| int | 声明整型变量或函数 |
| long | 声明长整型变量或函数返回值类型 |
| register | 声明寄存器变量 |
| return | 子程序返回语句(可以带参数,也可不带参数) |
| short | 声明短整型变量或函数 |
| signed | 声明有符号类型变量或函数 |
| sizeof | 计算数据类型或变量长度(即所占字节数) |
| static | 声明静态变量 |
| struct | 声明结构体类型 |
| switch | 用于开关语句 |
| typedef | 用以给数据类型取别名 |
| unsigned | 声明无符号类型变量或函数 |
| union | 声明共用体类型 |
| void | 声明函数无返回值或无参数,声明无类型指针 |
| volatile | 说明变量在程序执行中可被隐含地改变 |
| while | 循环语句的循环条件 |