Back to COMP1511

指针基础

Pointers Fundamentals

课程概述 | Course Overview

指针是C语言中最重要也是最强大的特性之一。理解指针是掌握C语言的关键,它让我们能够:

1. 什么是指针?

指针是一个变量,它存储的是另一个变量的内存地址。换句话说,指针"指向"内存中的某个位置。

关键概念:

  • 变量:存储数据值
  • 指针:存储变量的地址
  • 地址:内存中的位置标识符

内存模型可视化:

内存布局示例:

0x1000
42
← 变量 x
ptr
0x1000
← 指针 ptr,指向 x
// 声明和初始化示例
int x = 42;           // 普通变量
int *ptr = &x;        // 指针变量,指向 x 的地址

printf("x 的值: %d\n", x);           // 输出: 42
printf("x 的地址: %p\n", &x);        // 输出: 0x1000 (示例地址)
printf("ptr 的值: %p\n", ptr);       // 输出: 0x1000 (与 &x 相同)
printf("ptr 指向的值: %d\n", *ptr);  // 输出: 42 (与 x 相同)

2. 取地址操作符 &

& 操作符用于获取变量的内存地址。它是"取地址"的意思。

int num = 100;
int *pointer;

pointer = #  // 将 num 的地址赋给 pointer

printf("num 的值: %d\n", num);              // 输出: 100
printf("num 的地址: %p\n", &num);           // 输出: 0x7fff5fbff6ac (示例)
printf("pointer 存储的地址: %p\n", pointer); // 输出: 0x7fff5fbff6ac (与上面相同)

💡 重要提示:

每次程序运行时,变量的地址可能会不同,这是正常的。重要的是理解指针和变量之间的关系。

不同数据类型的指针:

int    i = 10;      int    *int_ptr = &i;
float  f = 3.14;    float  *float_ptr = &f;
char   c = 'A';     char   *char_ptr = &c;
double d = 2.718;   double *double_ptr = &d;

// 打印各种类型的地址
printf("int地址:    %p\n", int_ptr);
printf("float地址:  %p\n", float_ptr);
printf("char地址:   %p\n", char_ptr);
printf("double地址: %p\n", double_ptr);

3. 解引用操作符 *

* 操作符用于访问指针指向的变量的值。这个过程叫做"解引用"或"间接访问"。

两种用法:

  • 声明时int *ptr; - 声明一个指向int的指针
  • 使用时*ptr - 访问指针指向的值
int x = 50;
int *ptr = &x;

// 通过指针读取值
printf("x 的值: %d\n", x);      // 输出: 50
printf("*ptr 的值: %d\n", *ptr); // 输出: 50 (相同的值)

// 通过指针修改值
*ptr = 75;  // 通过指针改变 x 的值

printf("修改后 x 的值: %d\n", x);      // 输出: 75
printf("修改后 *ptr 的值: %d\n", *ptr); // 输出: 75

解引用过程可视化:

解引用步骤:

1. ptr 存储地址
ptr
0x1000
↓ 解引用 (*ptr)
2. 访问地址 0x1000 的值
0x1000
50

4. 指针与数组

在C语言中,数组名本身就是指向数组第一个元素的指针。这种特性使得指针和数组密切相关。

int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr;  // arr 等价于 &arr[0]

// 以下三种访问方式是等价的:
printf("第一个元素: %d\n", arr[0]);    // 数组下标
printf("第一个元素: %d\n", *arr);      // 数组名解引用
printf("第一个元素: %d\n", *ptr);      // 指针解引用

// 访问其他元素
printf("第二个元素: %d\n", arr[1]);    // 数组下标
printf("第二个元素: %d\n", *(arr+1));  // 指针算术
printf("第二个元素: %d\n", *(ptr+1));  // 指针算术

指针算术:

数组在内存中的布局:

0x1000
10
0x1004
20
0x1008
30
0x100C
40
0x1010
50

arr[0] arr[1] arr[2] arr[3] arr[4]

int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr;

// 指针算术示例
printf("ptr 指向: %d\n", *ptr);       // 10
printf("ptr+1 指向: %d\n", *(ptr+1)); // 20
printf("ptr+2 指向: %d\n", *(ptr+2)); // 30

// 移动指针
ptr++;  // ptr 现在指向 arr[1]
printf("移动后 ptr 指向: %d\n", *ptr); // 20

💡 关键理解:

当对指针进行 +1 操作时,实际移动的字节数取决于指针的类型。对于 int* 指针,+1 意味着移动 sizeof(int) 个字节(通常是4字节)。

5. 指针与函数

指针在函数中有两个主要用途:传值 vs 传引用返回多个值

5.1 传值 vs 传引用

// 传值 (Pass by Value) - 不能修改原变量
void increment_value(int x) {
    x++;  // 只修改副本,不影响原变量
    printf("函数内 x = %d\n", x);
}

// 传引用 (Pass by Reference) - 可以修改原变量
void increment_pointer(int *x) {
    (*x)++;  // 修改指针指向的值
    printf("函数内 *x = %d\n", *x);
}

int main() {
    int num = 10;
    
    printf("调用前: num = %d\n", num);        // 10
    
    increment_value(num);                      // 传值
    printf("传值后: num = %d\n", num);        // 10 (没有改变)
    
    increment_pointer(&num);                   // 传引用
    printf("传引用后: num = %d\n", num);      // 11 (改变了)
    
    return 0;
}

5.2 函数返回多个值

// 使用指针返回多个值
void calculate(int a, int b, int *sum, int *product, int *difference) {
    *sum = a + b;
    *product = a * b;
    *difference = a - b;
}

int main() {
    int x = 15, y = 5;
    int sum, product, difference;
    
    // 传递变量的地址
    calculate(x, y, &sum, &product, &difference);
    
    printf("和: %d\n", sum);           // 20
    printf("积: %d\n", product);       // 75
    printf("差: %d\n", difference);    // 10
    
    return 0;
}

5.3 数组作为函数参数

// 数组参数实际上是指针
void print_array(int arr[], int size) {
    // 或者写成: void print_array(int *arr, int size)
    for (int i = 0; i < size; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }
}

void modify_array(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        arr[i] *= 2;  // 修改原数组的值
    }
}

int main() {
    int numbers[4] = {1, 2, 3, 4};
    
    printf("原数组:\n");
    print_array(numbers, 4);
    
    modify_array(numbers, 4);
    
    printf("修改后:\n");
    print_array(numbers, 4);  // 输出: 2, 4, 6, 8
    
    return 0;
}

6. 常见错误与调试

⚠️ 常见错误 1:未初始化的指针

// 错误示例
int *ptr;        // 指针未初始化
*ptr = 10;       // 危险!可能导致程序崩溃

// 正确示例
int x;
int *ptr = &x;   // 先初始化指针
*ptr = 10;       // 安全

⚠️ 常见错误 2:空指针解引用

// 错误示例
int *ptr = NULL;
*ptr = 5;        // 程序崩溃!

// 正确示例
int *ptr = NULL;
if (ptr != NULL) {
    *ptr = 5;    // 先检查再使用
} else {
    printf("指针为空,无法使用\n");
}

⚠️ 常见错误 3:指针类型不匹配

// 错误示例
int x = 10;
float *ptr = &x;  // 类型不匹配警告

// 正确示例
int x = 10;
int *ptr = &x;    // 类型匹配

🛠️ 调试技巧:

  • 始终初始化指针
  • 使用前检查指针是否为 NULL
  • 确保指针类型与目标变量类型匹配
  • 使用 printf 打印地址和值来调试
  • 使用静态分析工具检查代码

7. 实践练习

练习 1:交换两个变量

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 10, y = 20;
    printf("交换前: x=%d, y=%d\n", x, y);
    
    swap(&x, &y);
    
    printf("交换后: x=%d, y=%d\n", x, y);
    return 0;
}

练习 2:查找数组中的最大值和最小值

void find_min_max(int arr[], int size, int *min, int *max) {
    *min = *max = arr[0];
    
    for (int i = 1; i < size; i++) {
        if (arr[i] < *min) {
            *min = arr[i];
        }
        if (arr[i] > *max) {
            *max = arr[i];
        }
    }
}

int main() {
    int numbers[] = {64, 34, 25, 12, 22, 11, 90};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    int min, max;
    
    find_min_max(numbers, size, &min, &max);
    
    printf("最小值: %d\n", min);
    printf("最大值: %d\n", max);
    
    return 0;
}

练习 3:字符串长度计算

int string_length(char *str) {
    int length = 0;
    while (*str != '\0') {  // 直到遇到字符串结束符
        length++;
        str++;              // 移动指针到下一个字符
    }
    return length;
}

int main() {
    char message[] = "Hello, World!";
    int len = string_length(message);
    
    printf("字符串 '%s' 的长度是: %d\n", message, len);
    
    return 0;
}

总结 | Summary

核心概念:

  • 指针存储变量的地址
  • & 操作符获取地址
  • * 操作符解引用
  • 指针算术与数组
  • 函数参数传递

最佳实践:

  • 始终初始化指针
  • 检查 NULL 指针
  • 匹配指针类型
  • 小心指针算术
  • 使用调试技巧

掌握指针是C语言编程的重要里程碑。通过大量练习和实际应用,你将能够熟练使用指针来构建更高效、更强大的程序。

继续学习