本教程面向大学一年级学生,按基础语法 → 程序设计 → 数据结构 → 指针与内存 → 文件与工程 → 综合实践的路线展开。完成课程后,你将能够读懂、独立编写中等规模的 C 程序,理解指针、内存与编译原理,并能用 C 完成小型项目。
在动手写代码之前,先建立全局认知 —— C 的本质、C 在计算机体系中的位置,以及一条循序渐进的学习路线。
C 语言是一种通用、过程式、编译型的编程语言,由 Dennis Ritchie 于 1972 年在贝尔实验室为重写 UNIX 操作系统而设计。它具有以下特点:
一句话总结:C 是连接高级语言与计算机硬件的"枢纽语言",学会它能让你真正理解程序如何在机器上运行。
认识 C 的诞生、标准演进,以及"Hello, World!"背后的每一个零件。
程序 = 数据结构 + 算法,本质上是一组告诉计算机"做什么、按什么顺序做"的指令。C 程序的生命周期通常为:
C 诞生于 1970 年代,1989 年由 ANSI 标准化(ANSI C / C89),随后 ISO 跟进(C90)。其后主流标准演进:
本教程以 C17 / C23 现代 C 视角组织内容,但兼顾 C99 兼容性(编译器默认模式)。
几乎所有 C 教材的第一个例子,让我们逐行拆解:
#include <stdio.h> // 引入标准输入输出头文件
int main(void) // 主函数:程序入口
{
printf("Hello, World!\n"); // 输出字符串到屏幕
return 0; // 返回 0 表示正常退出
}#include <stdio.h>:预处理指令,告诉编译器把 stdio.h(标准 I/O 头文件)的内容"粘贴"进来;int main(void):主函数,每个 C 程序必须有且仅有一个 main,是程序入口;{ }:复合语句块,函数体边界;printf(...):标准库函数,把字符串打印到标准输出;\n:转义字符,代表换行;return 0;:返回 int 类型的退出码,0 表示正常;;:每条语句以分号结尾(复合语句的 { } 后不需要)。gcc hello.c -o hello && ./hello,在 Windows + MinGW 下同理。C 提供两种注释方式:
// 单行注释:以 // 开头,到行尾结束(C99 引入)
/*
* 多行注释:以 /* 开头,*/ 结尾
* 常用于函数说明、临时屏蔽代码块
*/
int main(void) {
// printf("暂时不执行\n");
return 0;
}/* /* */ */ 这种写法会让编译器在第一个 */ 处认为注释结束,导致后面的 */ 报错。从一段文本到一个可运行的二进制,C 程序需要"翻译官"和"工具箱"。
| 编译器 | 平台 | 特点 |
|---|---|---|
| GCC | Linux/macOS/Windows (MinGW) | GNU 出品,最常用的开源 C 编译器,本课程默认使用 |
| Clang | macOS (默认) / Linux / Windows | Apple 主力,错误提示友好,编译速度快 |
| MSVC | Windows | Visual Studio 自带,Windows 平台原生 |
| TCC | 全平台 | Tiny C Compiler,几百 KB,编译极快,适合教学 |
# 1. 用文本编辑器写一个 hello.c(见 1.3)
# 2. 编译:gcc 把 hello.c 翻译成可执行文件 hello
$ gcc hello.c -o hello
# 3. 运行:在当前目录下执行 ./hello
$ ./hello
Hello, World!
# 常用编译选项
$ gcc -Wall -Wextra -O2 hello.c -o hello # 开启警告、优化
$ gcc -g hello.c -o hello # 生成调试信息(供 gdb 使用)
$ gcc -std=c17 hello.c -o hello # 指定 C 标准版本小贴士:Windows PowerShell 下用 .\hello 运行;Linux/macOS 下通常用 ./hello。
xcode-select --install;gcc ... && ./a.out;C:\mingw64\bin 加入 PATH;hello-c/
├── src/
│ └── main.c // 程序入口与业务逻辑
├── include/
│ └── utils.h // 自定义头文件(可选)
├── Makefile // 或 CMakeLists.txt,自动化编译
└── README.md随着代码量增长,会进一步引入多文件、头文件保护、Makefile 等工程化概念。
C 程序的"词汇表"——所有合法单词都由字符集构造,关键字是语言的保留字。
int student; // 由字母开头的单词
int student_name; // 下划线连接多个单词
int score1; // 字母后接数字
// ❌ 错误:不能以数字开头
// int 1score;
// ❌ 错误:不能使用关键字
// int return;
// ⚠️ 区分大小写:Student 和 student 是两个不同的标识符关键字是 C 语言预定义的有特殊含义的单词,不能用作标识符。常见关键字按功能分组:
| 类别 | 关键字 |
|---|---|
| 数据类型 | char int float double void short long signed unsigned _Bool |
| 控制语句 | if else switch case default while do for break continue return goto |
| 存储与作用域 | auto static extern register |
| 构造类型 | struct union enum typedef sizeof |
| 常量与限定 | const volatile inline restrict |
| 预处理 | #define #include #ifdef #ifndef #endif |
// 1) 整数常量
int a = 100; // 十进制
int b = 0x1F; // 十六进制 (31)
int c = 0755; // 八进制 (493)
long d = 1000L; // long 类型
unsigned e = 42U; // 无符号
// 2) 浮点常量
double pi = 3.14159;
double avo = 6.02e23; // 科学计数法
// 3) 字符常量:用单引号
char g = 'A'; // ASCII 65
char nl = '\n'; // 换行符
// 4) 字符串常量:用双引号
char msg[] = "Hello"; // 末尾自动加 '\0'#include <stdio.h>
int main(void)
{
int age;
char name[32];
printf("请输入您的姓名和年龄:");
scanf("%s %d", name, &age); // 注意 &取地址
printf("你好,%s!今年 %d 岁。\n", name, age);
return 0;
}| 占位符 | 含义 | 占位符 | 含义 |
|---|---|---|---|
| %d | 十进制整数 (int) | %ld | long 整数 |
| %f | 浮点数 (double) | %c | 单个字符 |
| %s | 字符串 | %x / %X | 十六进制整数 |
| %u | 无符号整数 | %p | 指针地址 |
| %lf | double 读入 | %% | 输出一个 % |
scanf("%d", &x) 中必须传 地址(&x),否则会段错误;scanf("%lf", &d) 中 double 必须用 %lf(读入时),而 printf 中 double 既可用 %f 也可用 %lf。C 是一门强类型语言:每个变量都有明确类型,决定了占用内存大小与可执行的操作。
| 类型 | 含义 | 典型大小 (字节) | 取值范围(示意) |
|---|---|---|---|
| char | 字符 / 1 字节整数 | 1 | -128 ~ 127 |
| short | 短整型 | 2 | -32768 ~ 32767 |
| int | 整型 | 4 | 约 ±2.1 × 10⁹ |
| long | 长整型 | 4 或 8 | 与平台相关 |
| long long | 更长整型 (C99) | 8 | 约 ±9.2 × 10¹⁸ |
| float | 单精度浮点 | 4 | 约 ±3.4 × 10³⁸(6~7 位有效数字) |
| double | 双精度浮点 | 8 | 约 ±1.8 × 10³⁰⁸(15~16 位有效数字) |
| _Bool / bool | 布尔 (0/1) | 1 | 需 <stdbool.h> |
提示:实际大小可用 sizeof(int) 查询,需要固定宽度整数请用 <stdint.h>:int32_t、uint64_t 等。
#include <stdio.h>
// 宏常量:预处理阶段做文本替换
#define PI 3.1415926
#define MAX_LEN 256
int main(void)
{
// 变量:值可以改变
int age = 18;
float score = 95.5;
char grade = 'A';
// const 常量:运行时不可修改
const int MAX_SCORE = 100;
age = 19; // 重新赋值 ✔
// MAX_SCORE = 200; // ❌ 编译错误:不能修改 const
printf("PI=%.4f, MAX_SCORE=%d\n", PI, MAX_SCORE);
return 0;
}(目标类型)表达式,例如 (int)3.7 得 3(截断小数部分);int a = 10;
double b = (double)a / 3; // 3.333... (true division)
int c = a / 3; // 3 (integer division)
// ⚠️ 精度损失:double → int 时小数部分被截断(非四舍五入)
int d = (int)3.99; // d == 3| 类别 | 运算符(部分) | 示例 |
|---|---|---|
| 算术 | + - * / % | a + b * c |
| 关系 | > < >= <= == != | a == b |
| 逻辑 | && || ! | a > 0 && b > 0 |
| 赋值 | = += -= *= /= %= <<= >>= &= |= ^= | a += 3 |
| 自增自减 | ++ -- | i++ (后置), ++i (前置) |
| 条件 | ?: | max = a > b ? a : b; |
| 位运算 | & | ^ ~ << >> | flags & 0x0F |
| 逗号 | , | for(i=0, j=0; ...) |
a == b 中间不能写成 a = b(赋值);== vs = 是 C 语言最常见的笔误之一。10 / 3(int 类型)的结果是?(int)3.99从"会写语句"到"会写程序"的关键一步——让代码具备判断与重复执行的能力。
语句按书写顺序依次执行。最简单的程序:输入 → 处理 → 输出。
#include <stdio.h>
int main(void) {
int r;
printf("请输入半径:");
scanf("%d", &r);
double area = 3.1415926 * r * r;
printf("半径=%d, 面积=%.2f\n", r, area);
return 0;
}if (score >= 90)
printf("A\n");
if (score >= 60)
printf("及格\n");
else
printf("不及格\n");
if (score >= 85)
printf("优秀\n");
else if (score >= 70)
printf("良好\n");
else if (score >= 60)
printf("及格\n");
else
printf("不及格\n");switch (choice) {
case 1:
printf("新建\n");
break;
case 2:
printf("打开\n");
break;
case 3:
case 4:
printf("保存 / 另存为\n");
break;
default:
printf("无效选项\n");
}break 跳出,否则会"贯穿"到下一个 case(少数情况是有意为之)。int i = 0;
while (i < 5) {
printf("%d\n", i);
i++;
}int n;
do {
printf("输入正数:");
scanf("%d", &n);
} while (n <= 0);for (int i = 0; i < 10; i++) {
printf("%d ", i);
}
printf("\n");break:立即跳出当前循环或 switch;continue:跳过本次剩余语句,进入下一轮;goto label;:跳转到大一通常不推荐使用,容易破坏结构。#include <stdio.h>
int main(void) {
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++)
printf("%dx%d=%-2d ", j, i, j*i);
printf("\n");
}
return 0;
}关键:循环嵌套体现"行 × 列"二维结构——外层控制行,内层控制列。
for(int i=0;i<5;i++){ if(i==2) continue; printf("%d ",i); }函数是 C 程序的基本构件——把复杂问题拆成可复用、可独立测试的小单元。
#include <stdio.h>
// 函数定义:返回类型 + 名字 + 参数列表 + 函数体
int add(int a, int b) {
return a + b;
}
// 没有返回值:void
void say_hello(const char* name) {
printf("Hello, %s!\n", name);
}
int main(void) {
say_hello("Tom");
int s = add(3, 4); // 函数调用
printf("3+4=%d\n", s);
return 0;
}如果函数的定义出现在调用之后,需要先声明(告诉编译器函数签名):
int add(int, int); // 函数原型(参数名可省略)
int main(void) {
printf("%d\n", add(1, 2));
return 0;
}
int add(int a, int b) { return a + b; }C 的函数参数默认值传递:形参是实参的副本,函数内修改形参不影响实参。
void swap_wrong(int a, int b) {
int t = a; a = b; b = t; // 仅交换副本,原变量不变
}
void swap_ok(int *a, int *b) {
int t = *a; *a = *b; *b = t; // 通过地址真正交换
}记忆:想让函数修改外部变量,必须传地址(指针)。这是后续"指针"章节的核心铺垫。
| 变量类型 | 作用域 | 生命周期 | 说明 |
|---|---|---|---|
| 局部变量 | 所在块 { } 内 | 块结束时销毁 | 函数内定义的变量 |
| 全局变量 | 从定义处到文件末 | 程序整个运行期 | 函数外定义,默认初值 0 |
| static 局部 | 所在块内 | 整个程序运行期 | 值在调用之间保留 |
| static 全局 | 当前 .c 文件 | 整个程序运行期 | 不暴露给其他文件 |
| extern | 整个程序(多文件) | 整个程序运行期 | 声明在其他文件定义的全局变量 |
递归 = 终止条件 + 递归步骤。三个经典例子:
long factorial(int n) {
if (n <= 1) return 1; // 终止条件
return n * factorial(n - 1); // 递归步骤
}
// factorial(5) = 5*4*3*2*1 = 120数组 = 同类型元素的连续存储。字符串 = 以 '\0' 结尾的字符数组。
int a[5]; // 声明:5 个 int,未初始化
int b[5] = {1, 2, 3, 4, 5}; // 全部初始化
int c[5] = {0}; // 全为 0(部分初始化其余补 0)
int d[] = {10, 20, 30}; // 自动推导长度 = 3
a[0] = 100; // 通过下标访问(下标从 0 开始)
printf("%d\n", a[4]); // 最后一个元素
printf("size=%zu\n", sizeof a); // 20 (5×4)a[5]、a[-1] 是未定义行为 —— 后果难以预料。int matrix[3][4] = { // 3 行 × 4 列
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++)
printf("%4d", matrix[i][j]);
printf("\n");
}C 没有真正的"字符串变量",字符串是以 '\0' 结尾的 char 数组。
char s1[] = "Hello"; // 自动加 \0,实际长度 6
char s2[10] = {'H','i','\0'}; // 手动构造
printf("%zu\n", strlen(s1)); // 5 (不计 \0)
strcpy(s2, "Hi"); // 复制(不安全,建议用 strncpy)
strcat(s1, ", world!"); // 拼接
printf("%d\n", strcmp("abc", "abd")); // <0
// 字符分类与转换(ctype.h)
printf("%d %c\n", isalpha('A'), toupper('b')); // 非零 B| 函数 | 作用 |
|---|---|
| strlen(s) | 返回字符串长度(不含 \0) |
| strcpy(dst, src) / strncpy | 字符串复制 |
| strcat(dst, src) / strncat | 字符串拼接 |
| strcmp(a, b) / strncmp | 字典序比较 |
| strchr(s, c) / strrchr | 查找字符 |
| strstr(s, sub) | 查找子串 |
| isalpha / isdigit / isspace | 字符分类 |
| toupper / tolower | 大小写转换 |
指针 = 存储"内存地址"的变量。掌握 * 与 &,你就掌握了 C 的灵魂。
每个变量都占用一段内存,地址就是这段内存的"门牌号"。指针变量保存的就是这种地址。
int a = 10; // 整型变量 a,值为 10
int *p = &a; // p 是指针,指向 a 的地址
printf("a = %d\n", a); // 10
printf("&a = %p\n", &a); // a 的地址
printf("p = %p\n", p); // 同上(p 保存的就是 &a)
printf("*p = %d\n", *p); // 通过 p 间接访问:10
*p = 99; // 通过指针修改 a
printf("a 现在是 %d\n", a); // 99数组名 a 在大多数表达式中会退化为指向首元素的指针,因此 a[i] 等价于 *(a+i)。
int a[5] = {10, 20, 30, 40, 50};
int *p = a; // 等价于 int *p = &a[0];
printf("%d\n", *(p + 2)); // 30 (p 移动 2 个 int 长度)
printf("%d\n", p[2]); // 30 (下标写法)
p++; // p 现在指向 a[1]
printf("%d\n", *p); // 20int *p; p+1 实际地址加 4 字节(典型 int 大小)。当数据大小在编译期未知时,使用堆动态分配内存:
#include <stdlib.h>
int main(void) {
int n;
scanf("%d", &n);
int *p = malloc(sizeof(int) * n); // 申请 n 个 int
if (p == NULL) return 1; // 必须检查失败
for (int i = 0; i < n; i++)
p[i] = i * i; // 像普通数组一样用
free(p); // 用完必须释放
p = NULL; // 避免悬空指针
return 0;
}| 函数 | 作用 |
|---|---|
| malloc(size) | 申请 size 字节,未初始化,返回 void* |
| calloc(n, size) | 申请 n×size 字节并清零 |
| realloc(p, new) | 调整已分配内存大小 |
| free(p) | 释放内存(p 必须来自 malloc/calloc/realloc) |
| 错误类型 | 说明 | 防范 |
|---|---|---|
| 野指针 | 未初始化的指针 | 声明时即初始化:int *p = NULL; |
| 悬空指针 | 指向已 free 的内存 | free 后立即 p = NULL; |
| NULL 解引用 | 对 NULL 指针使用 *p | 解引用前检查 |
| 内存泄漏 | malloc 后忘了 free | 谁申请谁释放; 工具:valgrind / ASan |
| 越界访问 | 数组 / 缓冲区下标越界 | 始终检查下标范围 |
| 重复释放 | 对同一指针 free 两次 | free 后置 NULL |
valgrind --leak-check=full ./a.out;GCC/Clang 编译加 -fsanitize=address 可捕获大多数内存错误。int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int main(void) {
int (*fp)(int, int) = add; // 函数指针
printf("%d\n", fp(3, 2)); // 5 (像函数一样调用)
fp = sub;
printf("%d\n", fp(3, 2)); // 1
}
int **pp; // 二级指针:指向"指向 int 的指针"int a=5; int *p=&a;,表达式 *p 的含义是?int a[5]; int *p = a;,哪个表达式等同于 a[3]?把相关数据"打包"成一个新的复合类型 —— 让代码更具表达力。
#include <string.h>
#include <stdio.h>
// 定义结构体类型
struct Student {
char name[32];
int age;
float score;
};
int main(void) {
struct Student s1 = {"Tom", 18, 95.5};
strcpy(s1.name, "Jerry"); // 修改成员
s1.age = 19;
struct Student *p = &s1;
printf("%s %d %.1f\n", p->name, p->age, p->score);
}访问成员的两种写法:s.age(变量)或 p->age(指针)。
typedef struct {
int id;
char name[32];
} Student; // 现在 Student 是一个类型名
Student s = {1, "Alice"}; // 不再写 struct Studentenum Color { RED, GREEN, BLUE }; // 默认 0,1,2
enum Color c = GREEN;
union Data { // 共用一块内存,按最大成员解释
int i;
float f;
char c[4];
};让程序与磁盘交互;用宏和条件编译写出可移植、可维护的代码。
#include <stdio.h>
int main(void) {
// 写文本
FILE *fp = fopen("out.txt", "w");
if (!fp) return 1;
fprintf(fp, "Hello, %d\n", 42);
fclose(fp);
// 读文本
char buf[128];
fp = fopen("out.txt", "r");
fgets(buf, sizeof buf, fp); // 读一行
fclose(fp);
return 0;
}| 模式 | 含义 |
|---|---|
| "r" / "w" / "a" | 读 / 写(清空)/ 追加,文本模式 |
| "rb" / "wb" / "ab" | 同上,二进制模式 |
| "r+" / "w+" / "a+" | 读写 |
#define PI 3.14159
#define SQR(x) ((x)*(x)) // 函数式宏(注意括号)
#define MAX(a,b) ((a)>(b)?(a):(b))
#ifdef DEBUG
printf("[debug] x=%d\n", x); // 仅在 DEBUG 定义时编译
#endif#ifndef UTILS_H
#define UTILS_H
// 函数声明
int add(int, int);
void greet(const char*);
#endif#pragma once 也能起到头文件保护的作用,被现代编译器广泛支持。C 标准库就是 C 语言的"自带工具箱"——按头文件使用,常用的不到 10 个。
| 头文件 | 常用内容 | 典型函数 |
|---|---|---|
| <stdio.h> | 标准 I/O | printf, scanf, fopen, fclose, fgets, fputs, fprintf, fscanf, fread, fwrite |
| <stdlib.h> | 通用工具 | malloc, calloc, realloc, free, rand, srand, exit, atoi, qsort, bsearch |
| <string.h> | 字符串与内存 | strlen, strcpy, strncpy, strcat, strcmp, strchr, strstr, memcpy, memset |
| <math.h> | 数学函数 | sqrt, pow, sin, cos, log, log10, fabs, ceil, floor |
| <time.h> | 时间日期 | time, clock, difftime, mktime, strftime |
| <ctype.h> | 字符分类 | isalpha, isdigit, isspace, islower, isupper, tolower, toupper |
| <stdbool.h> | 布尔类型 (C99) | bool, true, false |
| <stdint.h> | 固定宽度整数 | int8_t, int16_t, int32_t, int64_t, uint8_t ... |
| <limits.h> | 类型极值 | INT_MAX, INT_MIN, CHAR_BIT ... |
| <float.h> | 浮点极值 | DBL_MAX, FLT_EPSILON ... |
掌握 5 ~ 8 个基础算法,足以应对大一编程作业与面试入门。
int linear_search(int a[], int n, int key) {
for (int i = 0; i < n; i++)
if (a[i] == key) return i;
return -1;
}int binary_search(int a[], int n, int key) {
int lo = 0, hi = n - 1;
while (lo <= hi) {
int mid = (lo + hi) / 2;
if (a[mid] == key) return mid;
else if (a[mid] < key) lo = mid + 1;
else hi = mid - 1;
}
return -1;
}// 冒泡排序:相邻元素两两比较交换,每轮把最大元素"冒"到末尾
void bubble_sort(int a[], int n) {
for (int i = 0; i < n - 1; i++)
for (int j = 0; j < n - 1 - i; j++)
if (a[j] > a[j + 1]) {
int t = a[j]; a[j] = a[j+1]; a[j+1] = t;
}
}// 朴素递归 Fibonacci(教学示例,效率极低)
int fib(int n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
// 迭代版:O(n) 时间 O(1) 空间(推荐)
long fib_iter(int n) {
if (n < 2) return n;
long a = 0, b = 1;
for (int i = 2; i <= n; i++) {
long c = a + b;
a = b; b = c;
}
return b;
}写出能跑的代码只是开始 —— 写出易调试、可维护的代码才是工程能力。
| 阶段 | 典型错误 | 示例 |
|---|---|---|
| 编译错误 | 语法 / 类型错误 | 缺分号、未声明标识符、类型不匹配 |
| 链接错误 | 找不到符号 | undefined reference to 'foo' |
| 运行错误 | 程序崩溃 | 段错误(Segmentation fault)、除零、非法内存访问 |
| 逻辑错误 | 结果不对 | 边界条件 off-by-one、算法错误、== 写成 = |
-fsanitize=address,运行时自动报警;gcc -Wall -Wextra、clang-tidy。# 1. 编译带调试信息
$ gcc -g hello.c -o hello
# 2. 启动 gdb
$ gdb ./hello
(gdb) break main # 设置断点
(gdb) run # 运行
(gdb) next # 单步(不进入函数)
(gdb) step # 单步(进入函数)
(gdb) print x # 查看变量
(gdb) continue # 继续执行
(gdb) quit # 退出#define 或 const 给数值起名。把零散语法整合成完整的小项目 —— 这是从"会写代码"到"能交付代码"的关键一跳。
程序随机生成 1 ~ 100 的整数,玩家循环输入猜测,程序给出"大了 / 小了"提示,直到猜中。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
srand((unsigned)time(NULL));
int target = rand() % 100 + 1;
int guess, tries = 0;
do {
printf("猜一个 1-100 的数:");
scanf("%d", &guess);
tries++;
if (guess > target) printf("大了!\n");
else if (guess < target) printf("小了!\n");
} while (guess != target);
printf("恭喜!用了 %d 次猜中。\n", tries);
return 0;
}支持 + - × ÷ 与连续运算,尝试把每个运算封装成函数,再用函数指针数组调度。
每个联系人含姓名、电话、邮箱;支持增删改查、按姓名排序、保存到文件、从文件加载。
结构体数组存储多名学生;统计平均分、最高分、按成绩排序;用文件持久化。
多文件工程(main.c + book.c/h + io.c/h);借阅 / 归还状态机;链表或数组两种实现可选。
git 做版本管理,每次功能提交一次;最后一块"索引"——写代码时随手翻一翻,胜过每次去搜。
autobreakcasecharconstcontinuedefaultdodoubleelseenumexternfloatforgotoifinlineintlongregisterrestrictreturnshortsignedsizeofstaticstructswitchtypedefunionunsignedvoidvolatilewhile
| 优先级 | 运算符 | 结合性 |
|---|---|---|
| 1 (最高) | () [] -> . | 左 |
| 2 | ! ~ ++ -- + - * & (type) sizeof | 右 |
| 3 | * / % | 左 |
| 4 | + - | 左 |
| 5 | << >> | 左 |
| 6 | < <= > >= | 左 |
| 7 | == != | 左 |
| 8 | & | 左 |
| 9 | ^ | 左 |
| 10 | | | 左 |
| 11 | && | 左 |
| 12 | || | 左 |
| 13 | ?: | 右 |
| 14 | = += -= ... | 右 |
| 15 (最低) | , | 左 |
| 转义 | 含义 | 转义 | 含义 |
|---|---|---|---|
| \n | 换行 | \t | 制表符 (Tab) |
| \r | 回车 | \\ | 反斜杠 |
| \' | 单引号 | \" | 双引号 |
| \0 | 空字符 | \xhh | 十六进制字节 |
| \ooo | 八进制字节 | \? | 问号(避免被识别为三字母词) |
if (a = b) — 应写 if (a == b)scanf("%d", x) — 应写 scanf("%d", &x)char s[5] = "hello"; — "hello" 需要 6 字节(含 \0)while (n = 0) — 赋值为 0,循环立即结束;改成 n == 0int *p; *p = 10; — p 指向未初始化地址;应 p = &x;return,返回值未定义