C 语言学习总结

C 语言概述

C 语言是一种通用的、面向过程的计算机编程语言,由丹尼斯·里奇在 20 世纪 70 年代初开发。它具有高效、灵活、可移植等特点,广泛应用于系统编程、嵌入式开发、游戏开发等领域。

学习路线

  1. 基础学习:语法入门、数据类型、运算符和表达式、控制流语句
  2. 进阶学习:数组、字符串、函数、指针、结构体、内存管理
  3. 高级学习:文件操作、预处理、多线程多进程、网络编程
  4. 应用学习:Linux 系统编程、嵌入式开发、底层驱动、逆向安全

基础语法

数据类型

C 语言提供了多种数据类型,用于存储不同类型的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 基本数据类型
char c = 'A'; // 字符型,1字节
int i = 100; // 整型,通常4字节
float f = 3.14f; // 浮点型,4字节
double d = 3.1415926; // 双精度浮点型,8字节
_Bool b = 1; // 布尔型,0或1

// 派生数据类型
int *ptr; // 指针类型
int arr[10]; // 数组类型
struct Student s; // 结构体类型
enum Color color; // 枚举类型

// 类型修饰符
signed int si = -100; // 有符号整型
unsigned int ui = 100; // 无符号整型
short int sh = 100; // 短整型
long int lo = 1000000; // 长整型

运算符

C 语言提供了丰富的运算符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 算术运算符
int a = 10, b = 3;
int sum = a + b; // 加法
int diff = a - b; // 减法
int product = a * b; // 乘法
int quotient = a / b; // 除法
int remainder = a % b; // 取余

// 关系运算符
int is_equal = (a == b); // 等于
int is_greater = (a > b); // 大于
int is_less = (a < b); // 小于
int is_nequal = (a != b); // 不等于

// 逻辑运算符
int logical_and = (a > 0) && (b > 0); // 逻辑与
int logical_or = (a > 0) || (b > 0); // 逻辑或
int logical_not = !(a > 0); // 逻辑非

// 位运算符
int bit_and = a & b; // 按位与
int bit_or = a | b; // 按位或
int bit_xor = a ^ b; // 按位异或
int bit_not = ~a; // 按位取反
int left_shift = a << 2; // 左移
int right_shift = a >> 2; // 右移

// 赋值运算符
int x = 10; // 简单赋值
x += 5; // 加等于
x -= 3; // 减等于
x *= 2; // 乘等于
x /= 4; // 除等于

控制流语句

条件语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// if-else 语句
int num = 10;
if (num > 0) {
printf("正数\n");
} else if (num < 0) {
printf("负数\n");
} else {
printf("零\n");
}

// switch 语句
int day = 3;
switch (day) {
case 1:
printf("星期一\n");
break;
case 2:
printf("星期二\n");
break;
case 3:
printf("星期三\n");
break;
default:
printf("其他\n");
break;
}

循环语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// for 循环
for (int i = 0; i < 5; i++) {
printf("%d ", i);
}
printf("\n");

// while 循环
int j = 0;
while (j < 5) {
printf("%d ", j);
j++;
}
printf("\n");

// do-while 循环
int k = 0;
do {
printf("%d ", k);
k++;
} while (k < 5);
printf("\n");

数组与字符串

数组

数组是相同类型元素的集合,在内存中连续存储:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 一维数组
int arr[5] = {1, 2, 3, 4, 5};

// 二维数组
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};

// 访问数组元素
printf("arr[0] = %d\n", arr[0]);
printf("matrix[1][1] = %d\n", matrix[1][1]);

// 遍历数组
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");

字符串

字符串是字符数组,以空字符’\0’结尾:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 字符串定义
char str1[] = "Hello";
char str2[10] = "World";
char str3[10] = {'H', 'e', 'l', 'l', 'o', '\0'};

// 字符串操作
#include <string.h>

char str4[20];
strcpy(str4, str1); // 复制字符串
strcat(str4, " "); // 连接字符串
strcat(str4, str2); // 连接字符串
printf("%s\n", str4); // 输出: Hello World

int len = strlen(str4); // 获取字符串长度
printf("长度: %d\n", len); // 输出: 11

int cmp = strcmp(str1, str2); // 比较字符串
printf("比较结果: %d\n", cmp); // 输出: 负数

函数

函数是完成特定任务的代码块,提高代码的重用性和可读性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 函数声明
int add(int a, int b);

// 函数定义
int add(int a, int b) {
return a + b;
}

// 函数调用
int main() {
int result = add(5, 3);
printf("5 + 3 = %d\n", result);
return 0;
}

// 带默认参数的函数(C99及以上)
void print_info(char *name, int age) {
printf("姓名: %s, 年龄: %d\n", name, age);
}

// 可变参数函数
#include <stdarg.h>

int sum(int count, ...) {
va_list args;
va_start(args, count);

int total = 0;
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}

va_end(args);
return total;
}

指针

指针是存储内存地址的变量,是 C 语言的核心特性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 指针定义与使用
int num = 10;
int *ptr = &num; // 指针指向num的地址

printf("num的值: %d\n", num); // 输出: 10
printf("num的地址: %p\n", &num); // 输出: num的内存地址
printf("ptr的值: %p\n", ptr); // 输出: num的内存地址
printf("*ptr的值: %d\n", *ptr); // 输出: 10

// 指针运算
int arr[] = {1, 2, 3, 4, 5};
int *p = arr;

printf("*p = %d\n", *p); // 输出: 1
printf("*(p+1) = %d\n", *(p+1)); // 输出: 2
printf("*(p+2) = %d\n", *(p+2)); // 输出: 3

// 指针数组
int *ptr_arr[5];
for (int i = 0; i < 5; i++) {
ptr_arr[i] = &arr[i];
}

// 二维指针
int **pp = &ptr;
printf("**pp = %d\n", **pp); // 输出: 10

结构体与共用体

结构体

结构体用于组合不同类型的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 结构体定义
struct Student {
char name[20];
int age;
float score;
};

// 结构体使用
struct Student s1;
strcpy(s1.name, "张三");
s1.age = 20;
s1.score = 85.5;

// 结构体指针
struct Student *ptr = &s1;
printf("姓名: %s\n", ptr->name);
printf("年龄: %d\n", ptr->age);
printf("分数: %.1f\n", ptr->score);

// 结构体数组
struct Student students[3] = {
{"张三", 20, 85.5},
{"李四", 21, 90.0},
{"王五", 22, 88.5}
};

共用体

共用体的所有成员共享同一块内存空间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 共用体定义
union Data {
int i;
float f;
char c;
};

// 共用体使用
union Data data;
data.i = 100;
printf("data.i = %d\n", data.i); // 输出: 100

// 修改一个成员会影响其他成员
data.f = 3.14f;
printf("data.i = %d\n", data.i); // 输出: 1078523331(float的二进制表示)
printf("data.f = %.2f\n", data.f); // 输出: 3.14

内存管理

C 语言允许程序员直接管理内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdlib.h>
#include <string.h>

// 动态内存分配
int *arr = (int *)malloc(5 * sizeof(int)); // 分配5个int的内存
if (arr == NULL) {
printf("内存分配失败\n");
return 1;
}

// 初始化内存
memset(arr, 0, 5 * sizeof(int));

// 使用内存
for (int i = 0; i < 5; i++) {
arr[i] = i + 1;
printf("%d ", arr[i]);
}
printf("\n");

// 重新分配内存
arr = (int *)realloc(arr, 10 * sizeof(int));
if (arr == NULL) {
printf("内存重新分配失败\n");
return 1;
}

// 释放内存
free(arr);
arr = NULL; // 避免悬空指针

// calloc 分配并初始化为0
int *arr2 = (int *)calloc(5, sizeof(int));
free(arr2);
arr2 = NULL;

文件操作

C 语言提供了文件操作的标准库函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>

// 文件写入
FILE *fp = fopen("test.txt", "w");
if (fp == NULL) {
printf("文件打开失败\n");
return 1;
}

fprintf(fp, "Hello, World!\n");
fprintf(fp, "这是一个测试文件\n");
fclose(fp);

// 文件读取
fp = fopen("test.txt", "r");
if (fp == NULL) {
printf("文件打开失败\n");
return 1;
}

char buffer[100];
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}
fclose(fp);

// 二进制文件操作
struct Student s = {"张三", 20, 85.5};
fp = fopen("student.dat", "wb");
fwrite(&s, sizeof(struct Student), 1, fp);
fclose(fp);

fp = fopen("student.dat", "rb");
struct Student s2;
fread(&s2, sizeof(struct Student), 1, fp);
printf("姓名: %s, 年龄: %d, 分数: %.1f\n", s2.name, s2.age, s2.score);
fclose(fp);

预处理命令

预处理命令在编译前执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 包含头文件
#include <stdio.h>
#include "myheader.h"

// 宏定义
#define PI 3.1415926
#define MAX(a, b) ((a) > (b) ? (a) : (b))

// 条件编译
#define DEBUG

#ifdef DEBUG
#define LOG(msg) printf("[DEBUG] %s\n", msg)
#else
#define LOG(msg) /* 空 */
#endif

// 其他预处理命令
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))

int main() {
float area = PI * 5 * 5;
printf("面积: %.2f\n", area);

int max_val = MAX(10, 20);
printf("最大值: %d\n", max_val);

LOG("程序开始执行");

int arr[] = {1, 2, 3, 4, 5};
int size = ARRAY_SIZE(arr);
printf("数组大小: %d\n", size);

return 0;
}

多线程多进程

多进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
pid_t pid = fork();

if (pid < 0) {
printf("进程创建失败\n");
return 1;
} else if (pid == 0) {
// 子进程
printf("子进程 ID: %d\n", getpid());
printf("子进程的父进程 ID: %d\n", getppid());
} else {
// 父进程
printf("父进程 ID: %d\n", getpid());
printf("子进程 ID: %d\n", pid);
wait(NULL); // 等待子进程结束
}

return 0;
}

多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <pthread.h>

void *thread_func(void *arg) {
int thread_num = *(int *)arg;
printf("线程 %d 执行\n", thread_num);
pthread_exit(NULL);
}

int main() {
pthread_t threads[5];
int thread_nums[5];

// 创建5个线程
for (int i = 0; i < 5; i++) {
thread_nums[i] = i + 1;
if (pthread_create(&threads[i], NULL, thread_func, &thread_nums[i]) != 0) {
printf("线程创建失败\n");
return 1;
}
}

// 等待所有线程结束
for (int i = 0; i < 5; i++) {
pthread_join(threads[i], NULL);
}

printf("所有线程执行完毕\n");

return 0;
}

Linux C 编程

系统调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
// 打开文件(系统调用)
int fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open");
return 1;
}

// 写入文件(系统调用)
const char *str = "Hello from Linux system call!\n";
ssize_t bytes_written = write(fd, str, strlen(str));
if (bytes_written < 0) {
perror("write");
close(fd);
return 1;
}

// 关闭文件(系统调用)
close(fd);

printf("文件写入成功\n");

return 0;
}

编译和调试

编译流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 预处理
gcc -E hello.c -o hello.i

# 编译
gcc -S hello.i -o hello.s

# 汇编
gcc -c hello.s -o hello.o

# 链接
gcc hello.o -o hello

# 一步完成
gcc hello.c -o hello

调试工具 GDB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 编译时添加调试信息
gcc -g hello.c -o hello

# 启动 GDB
gdb hello

# GDB 常用命令
run # 运行程序
break main # 在 main 函数处设置断点
break 10 # 在第10行设置断点
continue # 继续执行
step # 单步执行,进入函数
next # 单步执行,不进入函数
print var # 打印变量值
quit # 退出 GDB

构建工具 Make

Makefile 示例:

1
2
3
4
5
6
7
8
9
10
CC = gcc
CFLAGS = -g -Wall -O2
TARGET = hello
SOURCES = hello.c

$(TARGET): $(SOURCES)
$(CC) $(CFLAGS) $(SOURCES) -o $(TARGET)

clean:
rm -f $(TARGET) *.o

使用:

1
2
make        # 编译
make clean # 清理

总结

C 语言是一门强大而灵活的编程语言,掌握 C 语言对于理解计算机底层原理和学习其他编程语言都有很大帮助。通过学习 C 语言,你可以:

  1. 理解计算机内存管理机制
  2. 掌握指针等底层编程概念
  3. 学习系统编程和网络编程
  4. 为学习其他高级语言打下基础

学习 C 语言需要多写代码、多实践,通过解决实际问题来加深理解和提高编程能力。