C 语言说明文档
C 语言说明文档
1. 引言
C 语言是一种通用的、过程式的编程语言,由 Dennis Ritchie 于 1972 年在贝尔实验室开发,用于重新实现 Unix 操作系统。C 语言设计简洁、高效,提供了对底层内存的精细控制,同时具备高度的可移植性。它广泛应用于系统软件、嵌入式开发、操作系统、编译器、数据库以及高性能计算等领域。C 语言是许多现代编程语言(如 C++、Java、C#、Python 等)的基础,学习 C 语言有助于深入理解计算机的工作原理。
2. 基本语法结构
2.1 程序入口
C 程序的执行从 main 函数开始。main 函数可以定义为无参数或带参数,标准形式如下:
int main(void) {
// 代码
return 0;
}
或带命令行参数:
int main(int argc, char *argv[]) {
// argc 为参数个数,argv 为参数数组
return 0;
}
2.2 语句与块
每条语句以分号 ; 结束。使用花括号 {} 将多条语句组合成一个块(复合语句),块可以出现在任何允许单条语句的地方。
2.3 注释
- 单行注释:
// 这是单行注释 - 多行注释:
/* 这是多行注释 */(不能嵌套)
3. 数据类型
C 语言提供基本数据类型和构造类型。
3.1 基本数据类型
| 类型 | 关键字 | 典型大小(字节) | 取值范围(以 32 位系统为例) |
|---|---|---|---|
| 字符型 | char |
1 | -128 ~ 127 或 0 ~ 255(取决于有无符号) |
| 短整型 | short |
2 | -32768 ~ 32767 |
| 整型 | int |
4 | -2147483648 ~ 2147483647 |
| 长整型 | long |
4(Windows)或 8(Linux) | 视平台而定 |
| 长长整型 | long long |
8 | -9.22e18 ~ 9.22e18 |
| 单精度浮点型 | float |
4 | 约 ±3.4e-38 ~ ±3.4e38 |
| 双精度浮点型 | double |
8 | 约 ±1.7e-308 ~ ±1.7e308 |
| 无符号版本 | unsigned 前缀(如 unsigned int) |
同对应类型 | 仅非负值,范围翻倍 |
3.2 派生类型
- 数组:
int arr[10]; - 指针:
int *p; - 结构体:
struct { int a; float b; } s; - 联合体:
union { int i; float f; } u; - 枚举:
enum color { RED, GREEN, BLUE };
3.3 类型修饰符
const:定义常量,不可修改。volatile:告知编译器变量可能被意外修改,禁止优化。register(已过时,现代编译器自动优化):建议将变量存储在寄存器中。restrict(C99):指针限定符,表示该指针是访问其指向数据的唯一方式。
4. 变量与常量
4.1 变量声明
变量需要先声明后使用,声明指定类型和名称:
int age;
float salary;
char grade;
可以在声明时初始化:
int count = 0;
double pi = 3.14159;
4.2 作用域与存储类别
- 局部变量:在函数或块内声明,作用域限于该块。
- 全局变量:在所有函数之外声明,作用域为整个文件(可通过
extern跨文件使用)。 - 静态变量:使用
static关键字,局部静态变量保持值在函数调用间不变;全局静态变量限制其作用域为本文件。 - 外部变量:使用
extern声明变量在其他文件中定义。
4.3 常量
使用 const 关键字或 #define 预处理指令定义常量:
const int MAX = 100;
#define PI 3.14159
字面常量:整型(10, 0xFF)、浮点(3.14f)、字符('A')、字符串("Hello")。
5. 运算符
5.1 算术运算符
+(加)、-(减)、*(乘)、/(除)、%(取模,仅用于整数)
5.2 关系运算符
==(等于)、!=(不等于)、>、<、>=、<=,返回 1(真)或 0(假)。
5.3 逻辑运算符
&&(逻辑与)、||(逻辑或)、!(逻辑非)。短路求值。
5.4 位运算符
&(按位与)、|(按位或)、^(按位异或)、~(按位取反)、<<(左移)、>>(右移)。
5.5 赋值运算符
=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=。
5.6 自增自减
++(自增)、--(自减),前缀(先变后用)和后缀(先用后变)。
5.7 条件运算符
三元运算符:条件 ? 表达式1 : 表达式2。
5.8 逗号运算符
表达式1, 表达式2, ...,依次求值,结果为最后一个表达式的值。
5.9 sizeof 运算符
返回变量或类型所占字节数,结果为 size_t 类型(无符号整数)。
5.10 类型转换
- 隐式转换:由编译器自动完成,遵循“向更高精度”的原则。
- 显式转换(强制类型转换):
(类型)表达式。
6. 控制流
6.1 条件语句
if语句:
if (条件) {
// 代码
} else if (条件2) {
// 代码
} else {
// 代码
}
switch语句:
switch (表达式) {
case 常量1:
// 代码
break;
case 常量2:
// 代码
break;
default:
// 代码
break;
}
注意:每个 case 后通常需要 break 以避免“穿透”。
6.2 循环语句
while循环:
while (条件) {
// 代码
}
do-while循环(至少执行一次):
do {
// 代码
} while (条件);
for循环:
for (初始化; 条件; 增量) {
// 代码
}
任何部分都可以省略,但分号必须保留。
6.3 跳转语句
break:退出当前循环或switch。continue:跳过本次循环剩余部分,进入下一轮。goto:无条件跳转到标签位置(不推荐使用)。return:从函数返回。
7. 函数
7.1 函数定义
返回类型 函数名(形参列表) {
// 函数体
return 返回值;
}
若无返回值,使用 void。
7.2 函数声明(原型)
在调用函数之前,必须提供其原型,以便编译器检查参数类型和数量。原型通常放在头文件中或源文件顶部。
int add(int a, int b);
7.3 参数传递
- 传值:将实参的副本传递给形参,函数内修改不影响原变量。
- 传地址:通过指针传递变量的地址,函数内可通过指针修改原变量。
7.4 递归
函数可以调用自身,但必须确保有终止条件,避免无限递归。
7.5 内联函数(C99)
使用 inline 关键字建议编译器将函数展开,以减少调用开销。
7.6 函数指针
可以声明指向函数的指针,用于回调或动态调用。
int (*funcPtr)(int, int); // 指向返回int且有两个int参数的函数
funcPtr = &add;
int result = funcPtr(3, 4);
8. 数组
8.1 一维数组
声明:类型 数组名[常量表达式]; 例如:int numbers[10];
访问:下标从 0 开始,numbers[0] 到 numbers[9]。
初始化:
int a[5] = {1, 2, 3, 4, 5};
int b[] = {1, 2, 3}; // 自动确定大小
int c[5] = {0}; // 全部初始化为0
8.2 多维数组
以二维为例:int matrix[3][4]; 表示 3 行 4 列。
初始化:
int m[2][3] = {{1,2,3}, {4,5,6}};
数组名在大多数表达式中会隐式转换为指向第一个元素的指针。
8.3 字符串
C 语言没有单独的字符串类型,使用字符数组表示字符串,并以空字符 \0 结尾。
声明和初始化:
char str1[6] = "Hello"; // 实际长度6(含'\0')
char str2[] = "World";
char *str3 = "C Language"; // 字符串字面量,只读
标准库提供 <string.h> 中的字符串操作函数,如 strlen、strcpy、strcat、strcmp 等。
9. 指针
9.1 指针基础
指针存储内存地址。声明:类型 *指针变量名; 例如:int *p;
取地址运算符 &:获取变量的地址。
解引用运算符 *:访问指针指向的内存。
int x = 10;
int *p = &x;
*p = 20; // 修改 x 的值为 20
9.2 指针与数组
数组名可作为指向其首元素的指针。arr[i] 等价于 *(arr + i)。
9.3 指针运算
可以对指针进行加减整数运算,移动步长取决于指针指向的类型大小。
9.4 指向指针的指针
int **pp; 用于多级间接访问。
9.5 空指针与野指针
- 空指针:指向地址 0 的指针,通常用
NULL或(void*)0表示,表示不指向任何有效对象。 - 野指针:未初始化或指向已释放内存的指针,访问可能导致未定义行为。
9.6 const 与指针
const int *p;:指向常量的指针(所指内容不可变,指针可变)。int * const p;:常量指针(指针本身不可变,所指内容可变)。const int * const p;:两者都不可变。
9.7 void 指针
void * 可以指向任意类型的数据,但解引用前必须先强制转换为具体类型。常用于通用内存操作(如 malloc)。
10. 结构体与联合体
10.1 结构体
结构体将不同类型的数据组合成一个整体。
定义:
struct Student {
char name[50];
int age;
float score;
};
声明结构体变量:
struct Student s1;
struct Student s2 = {"Alice", 20, 85.5};
访问成员:. 运算符,如 s1.age。
指向结构体的指针:使用 -> 访问成员,如 p->age。
10.2 类型定义(typedef)
使用 typedef 为类型起别名,简化书写:
typedef struct Student Student;
Student s3;
// 或直接定义
typedef struct {
int x;
int y;
} Point;
Point p1;
10.3 结构体内存对齐
编译器可能在结构体成员之间插入填充字节,以提高访问效率。可以使用 #pragma pack 或 __attribute__((packed)) 调整对齐。
10.4 联合体
联合体允许在同一内存位置存储不同类型的数据,但同一时刻只能使用其中一种成员。大小取决于最大成员的大小。
union Data {
int i;
float f;
char str[20];
};
10.5 枚举
enum 定义一组命名的整型常量,默认从 0 递增。
enum Weekday { MON, TUE, WED, THU, FRI, SAT, SUN };
enum Weekday today = WED;
11. 动态内存管理
C 标准库 <stdlib.h> 提供四个函数用于动态内存管理:
void *malloc(size_t size):分配大小为size字节的连续内存,初始值不确定。若分配失败返回NULL。void *calloc(size_t num, size_t size):分配num个大小为size字节的元素,并将内存初始化为 0。void *realloc(void *ptr, size_t new_size):重新调整之前分配的内存块大小,可能移动数据。若ptr为NULL,则等同于malloc。void free(void *ptr):释放之前分配的内存,避免内存泄漏。释放后指针应置为NULL。
示例:
int *arr = (int*)malloc(10 * sizeof(int));
if (arr == NULL) { /* 处理错误 */ }
// 使用 arr...
free(arr);
arr = NULL;
12. 文件输入输出
C 标准库 <stdio.h> 提供了文件操作函数。文件操作流程:打开文件 → 读写操作 → 关闭文件。
12.1 文件指针
使用 FILE * 类型表示文件流。
12.2 打开与关闭
FILE *fopen(const char *filename, const char *mode);
模式包括:"r":只读,文件必须存在。"w":只写,若存在则清空,不存在则创建。"a":追加,若不存在则创建。"r+":读写,文件必须存在。"w+":读写,若存在则清空,不存在则创建。"a+":读写,追加模式。
二进制模式加b,如"rb"。
int fclose(FILE *stream);:关闭文件。
12.3 读写操作
- 字符级:
fgetc、fputc、ungetc。 - 字符串级:
fgets(读取一行,带换行符)、fputs。 - 格式化:
fprintf、fscanf。 - 二进制块:
fread、fwrite。
示例:
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) { perror("打开失败"); return; }
char buffer[100];
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}
fclose(fp);
12.4 文件定位
fseek(FILE *stream, long offset, int whence);whence 为SEEK_SET、SEEK_CUR或SEEK_END。ftell(FILE *stream);返回当前文件位置。rewind(FILE *stream);回到文件开头。feof(FILE *stream);检查是否到达文件末尾。
13. 预处理器
预处理器在编译之前处理以 # 开头的指令。
13.1 宏定义
#define 定义宏,可以是常量或带参数的宏。
#define PI 3.14159
#define SQUARE(x) ((x)*(x)) // 注意括号
带参数的宏需谨慎使用,避免副作用。
13.2 条件编译
根据条件包含或排除代码块。
#ifdef DEBUG
printf("调试信息\n");
#endif
#ifndef HEADER_H
#define HEADER_H
// 头文件保护
#endif
#if defined(WIN32)
// Windows 特定代码
#elif defined(LINUX)
// Linux 特定代码
#else
// 其他
#endif
13.3 文件包含
#include <filename> 搜索系统头文件路径;#include "filename" 先搜索当前目录,再搜索系统路径。
13.4 其他指令
#undef:取消宏定义。#error:产生编译错误消息。#pragma:编译器特定指令(如#pragma once)。
13.5 预定义宏
__LINE__:当前行号。__FILE__:当前文件名。__DATE__:编译日期。__TIME__:编译时间。__STDC__:若编译器符合 ANSI C,则定义为 1。
14. 标准库概述
C 标准库提供了丰富的函数,通过包含相应头文件使用。主要头文件及功能:
<stdio.h>:输入输出(printf、scanf、文件操作等)。<stdlib.h>:通用工具(内存分配、转换、随机数、退出等)。<string.h>:字符串处理(复制、连接、比较、查找等)。<math.h>:数学函数(sin、cos、sqrt、pow等)。<time.h>:时间和日期(time、clock、difftime等)。<ctype.h>:字符分类(isalpha、isdigit、toupper等)。<stdbool.h>(C99):布尔类型bool及true/false。<stdint.h>(C99):精确宽度整数类型(如int32_t)。<limits.h>:基本类型的取值范围(如INT_MAX)。<float.h>:浮点数属性(如FLT_MAX)。<assert.h>:断言assert,用于调试。<errno.h>:错误码定义,配合perror和strerror使用。
15. 常见编程实践与最佳建议
- 初始化变量:声明时尽量初始化,避免使用未初始化的值。
- 检查返回值:特别是
malloc、fopen等可能失败的函数,务必检查返回值。 - 防止内存泄漏:动态分配的内存必须用
free释放,并确保所有路径都释放。 - 数组边界检查:C 不检查数组越界,程序员需确保索引不超出范围。
- 指针安全:使用前确保指针非空,且指向有效内存;释放后置为
NULL。 - 使用
const修饰不修改的参数:提高代码可读性和安全性。 - 函数设计:函数应单一职责,长度适中,参数不宜过多。
- 头文件保护:使用
#ifndef/#define或#pragma once避免重复包含。 - 可移植性:避免依赖特定平台的大小端、字节对齐或类型大小,使用
sizeof和标准类型(如int32_t)。 - 注释与文档:为复杂逻辑和公开接口编写注释,便于维护。
- 编译选项:开启编译器警告(如
-Wall -Wextra),并认真对待警告。 - 代码风格:保持一致缩进、命名规范(如 snake_case 或 camelCase),提高团队协作效率。
16. 结语
C 语言是一门强大而简洁的编程语言,它赋予程序员对底层内存和硬件的直接控制,同时也要求程序员具备严谨的内存管理和错误处理意识。掌握 C 语言不仅有助于编写高性能的程序,更能为理解操作系统、编译原理和计算机体系结构打下坚实基础。通过不断实践、阅读优秀代码和深入理解语言特性,您将能够熟练运用 C 语言解决各种实际问题。
- 点赞
- 收藏
- 关注作者
评论(0)