c_snake_game

第 7 课:指针基础 🔗

指针是 C 语言的灵魂。理解指针是掌握 C 语言的关键!


7.1 什么是指针?

指针是一个存储内存地址的变量。

想象内存是一排房子,每个房子有门牌号(地址):

内存地址:  1000    1004    1008    1012
          ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
数据:     │  42 │ │ 3.14│ │ 'A' │ │1000 │
          └─────┘ └─────┘ └─────┘ └─────┘
                                    ↑
                                   指针(存储 1000 这个地址)

7.2 指针的声明

int number = 42;      // 普通变量
int* ptr;             // 指针变量,可以存储 int 的地址
ptr = &number;        // & 取地址运算符,获取 number 的地址

图示

number (int)          ptr (int*)
┌─────────┐          ┌─────────┐
│   42    │          │  0x1000 │  ← 存储的是 number 的地址
└─────────┘          └─────────┘
    ↑                    │
 0x1000 (地址)           └── 指向 number

7.3 两个重要运算符

& - 取地址运算符

int x = 10;
int* ptr = &x;  // ptr 存储 x 的地址

* - 解引用运算符

int x = 10;
int* ptr = &x;

printf("%d\n", *ptr);  // 输出 10,访问 ptr 指向的值
*ptr = 20;             // 修改 ptr 指向的值为 20
printf("%d\n", x);     // 输出 20,x 被修改了!

7.4 指针的类型

int* int_ptr;         // 指向 int 的指针
float* float_ptr;     // 指向 float 的指针
char* char_ptr;       // 指向 char 的指针
void* void_ptr;       // 通用指针,可以指向任何类型

int x = 10;
int_ptr = &x;         // ✅ 类型匹配

float f = 3.14f;
int_ptr = &f;         // ⚠️ 类型不匹配,会有警告

7.5 指针与数组

数组名本质上是指针:

int arr[5] = {1, 2, 3, 4, 5};

// 数组名就是指向第一个元素的指针
printf("%p\n", arr);      // 数组首地址
printf("%p\n", &arr[0]);  // 同上

// 指针算术
printf("%d\n", *arr);      // 1,第一个元素
printf("%d\n", *(arr+1));  // 2,第二个元素
printf("%d\n", arr[2]);    // 3,第三种写法

指针遍历数组

int arr[5] = {10, 20, 30, 40, 50};
int* ptr = arr;  // 指向数组开头

// 方法 1:指针算术
for (int i = 0; i < 5; i++) {
    printf("%d ", *(ptr + i));
}

// 方法 2:移动指针
for (int i = 0; i < 5; i++) {
    printf("%d ", *ptr);
    ptr++;  // 移动到下一个元素
}

7.6 指针与函数

传值 vs 传指针

// 传值:函数内修改不影响原变量
void increment_by_value(int x) {
    x++;  // 只修改了副本
}

// 传指针:函数内修改影响原变量
void increment_by_pointer(int* x) {
    (*x)++;  // 修改原变量
}

int main(void) {
    int a = 10;
    int b = 10;
    
    increment_by_value(a);
    increment_by_pointer(&b);
    
    printf("a = %d\n", a);  // 10,没变
    printf("b = %d\n", b);  // 11,变了!
    
    return 0;
}

7.7 贪吃蛇中的指针应用

修改游戏状态

typedef struct {
    int score;
    bool running;
} Game;

// ❌ 传值:无法修改原游戏
void reset_game(Game game) {
    game.score = 0;
    game.running = true;
}

// ✅ 传指针:可以修改原游戏
void reset_game_ptr(Game* game) {
    game->score = 0;
    game->running = true;
}

int main(void) {
    Game g = {100, false};
    
    reset_game(g);           // 没用
    reset_game_ptr(&g);      // 有效!
    
    return 0;
}

动态创建蛇

typedef struct Snake {
    int x, y, length;
} Snake;

// 在堆上创建蛇(生命周期由你控制)
Snake* create_snake(int x, int y) {
    Snake* snake = malloc(sizeof(Snake));
    snake->x = x;
    snake->y = y;
    snake->length = 1;
    return snake;
}

// 使用
Snake* my_snake = create_snake(10, 10);
printf("蛇在 (%d, %d)\n", my_snake->x, my_snake->y);

// 用完要释放
free(my_snake);

7.8 指针数组

// 指针数组:数组的每个元素都是指针
int a = 1, b = 2, c = 3;
int* ptr_array[3] = {&a, &b, &c};

printf("%d\n", *ptr_array[0]);  // 1
printf("%d\n", *ptr_array[1]);  // 2
printf("%d\n", *ptr_array[2]);  // 3

7.9 常见错误

错误 1:未初始化的指针

int* ptr;      // ❌ 未初始化,指向随机地址
*ptr = 10;     // ❌ 危险!可能崩溃

int x = 10;
int* ptr = &x; // ✅ 正确

错误 2:野指针

int* ptr = malloc(sizeof(int));
free(ptr);     // 释放内存
*ptr = 10;     // ❌ 野指针!内存已释放

ptr = NULL;    // ✅ 释放后置为 NULL

错误 3:类型不匹配

int x = 10;
float* ptr = &x;  // ⚠️ 类型不匹配

7.10 指针总结

int x = 42;
int* ptr = &x;

// 各种访问方式
printf("%d\n", x);      // 42,直接访问
printf("%p\n", &x);     // 地址,x 的地址
printf("%d\n", *ptr);   // 42,解引用
printf("%p\n", ptr);    // 地址,ptr 存储的值(x 的地址)
printf("%p\n", &ptr);   // 地址,ptr 自己的地址

✅ 本课检查清单


📝 作业

  1. 编写程序,用指针交换两个整数的值:
    void swap(int* a, int* b) {
     // 实现交换
    }
    
  2. 用指针遍历数组,找出最大值

  3. 创建一个结构体指针,修改其成员的值

下一课:内存管理