C语言指针基础

指针是C语言的灵魂,也是最让初学者感到困惑的概念之一。简单来说,指针就是存储内存地址的变量。 通过指针,我们可以直接访问和操作内存,实现高效的数组处理、动态内存分配、函数间数据共享等功能。 理解指针是深入掌握C语言的关键。本章将从最基础的概念开始,逐步揭开指针的神秘面纱。

{{-- 1. 内存地址与指针的概念 --}}

📌 内存地址与指针

计算机内存被划分为一个个字节,每个字节都有一个唯一的编号,称为内存地址。当我们定义一个变量时,系统会为其分配内存空间,变量名就是这块内存的别名。 指针变量专门用来存储这些内存地址。


内存地址    | 内容
1000       | 10   (变量 a)
1004       | 1000 (指针 p,存储了变量 a 的地址)
                            

通过指针,我们可以间接访问和修改变量的值。

{{-- 2. 指针的声明与初始化 --}}

📝 指针的声明与初始化

指针变量的声明需要指定它所指向的数据类型,格式为:类型 *指针变量名;

int *p;          // 声明一个指向整型的指针
double *q;       // 指向双精度浮点数的指针
char *r;         // 指向字符的指针

声明指针后,应将其初始化为某个变量的地址或 NULL(空指针),避免使用未初始化的指针。

int a = 10;
int *p = &a;     // p 存储变量 a 的地址
int *q = NULL;   // 初始化为空指针
注意: int* p, q; 只声明了 p 是指针,q 是普通整型变量。建议每个指针变量单独声明,或使用 int *p, *q;
{{-- 3. 取地址运算符 & 和解引用运算符 * --}}

🔍 取地址运算符 & 和解引用运算符 *

  • &:取地址运算符,用于获取变量的内存地址。
  • *:解引用运算符(间接访问运算符),用于访问指针所指向的变量。
#include <stdio.h>

int main() {
    int num = 42;
    int *ptr = #   // ptr 存储 num 的地址

    printf("num 的值: %d\n", num);
    printf("num 的地址: %p\n", &num);
    printf("ptr 存储的地址: %p\n", ptr);
    printf("ptr 指向的值: %d\n", *ptr);   // 通过指针访问 num 的值

    // 通过指针修改变量的值
    *ptr = 100;
    printf("修改后 num 的值: %d\n", num);  // 输出 100

    return 0;
}
{{-- 4. 指针的运算 --}}

➕ 指针的算术运算

指针可以进行加、减运算,其单位是所指向类型的大小。例如,int *p 加 1,地址增加 sizeof(int) 字节。

#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *p = arr;   // 指向数组第一个元素

    printf("p 指向: %d\n", *p);        // 10
    p++;                               // 移动到下一个元素
    printf("p++ 后指向: %d\n", *p);    // 20

    p += 2;                            // 移动两个元素
    printf("p+=2 后指向: %d\n", *p);    // 40

    // 指针相减:得到元素个数差
    int *q = &arr[4];
    printf("q - p = %ld\n", q - p);     // 输出 1(因为 p 指向 arr[3]? 实际上面 p 指向 arr[3](40),q 指向 arr[4](50))先确认位置

    return 0;
}
指针运算规则:
  • 指针 ± 整数 → 指针(指向向前/向后移动指定个元素)
  • 指针 - 指针 → 整数(两个指针之间的元素个数)
  • 指针的比较(==、!=、<、>等)可用于判断指向位置的前后关系(需在同一数组内)
{{-- 5. 指针与数组 --}}

🔗 指针与数组

在C语言中,数组名本质上是一个指向数组首元素的常量指针。因此,可以通过指针访问数组元素。

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *p = arr;      // 等价于 int *p = &arr[0];

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

    // 下标运算符 [] 实际上是 *(arr + i) 的语法糖
    printf("arr[2] = %d, *(arr+2) = %d\n", arr[2], *(arr + 2));

    return 0;
}
重要结论: arr[i] 等价于 *(arr + i),也等价于 *(i + arr),甚至 i[arr] 也是合法的(不推荐使用)。
{{-- 6. 指针作为函数参数(传址调用) --}}

📞 指针作为函数参数:实现传址调用

由于C语言采用值传递,函数内部无法直接修改外部变量的值。通过传递指针,可以在函数内部修改外部变量,实现“传址调用”。

#include <stdio.h>

// 交换两个整数的值(正确版本)
void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

int main() {
    int a = 10, b = 20;
    printf("交换前: a=%d, b=%d\n", a, b);
    swap(&a, &b);
    printf("交换后: a=%d, b=%d\n", a, b);
    return 0;
}
错误示例: 如果试图直接交换指针的值,不会影响外部变量。
void bad_swap(int x, int y) { int t = x; x = y; y = t; } // 只交换了副本
{{-- 7. 指针与字符串 --}}

🔤 指针与字符串

字符串常量本质上是一个指向字符的指针,因此可以使用 char * 指向字符串。

#include <stdio.h>

int main() {
    char *str = "Hello, World!";   // 指向字符串常量(只读)
    printf("%s\n", str);

    // 可修改的字符串应使用字符数组
    char arr[] = "Hello";
    arr[0] = 'h';   // 可以修改
    printf("%s\n", arr);

    // 遍历字符串
    char *p = arr;
    while (*p != '\0') {
        putchar(*p);
        p++;
    }
    putchar('\n');

    return 0;
}
{{-- 8. 多级指针 --}}

🔢 多级指针(指针的指针)

指针变量本身也有地址,可以用另一个指针来存储,即指向指针的指针。

#include <stdio.h>

int main() {
    int a = 10;
    int *p = &a;      // 一级指针
    int **pp = &p;    // 二级指针,存储 p 的地址

    printf("a = %d\n", a);
    printf("*p = %d\n", *p);
    printf("**pp = %d\n", **pp);   // 通过二级指针访问 a

    return 0;
}

多级指针常用于函数中修改指针本身(如动态内存分配后返回指针)。

{{-- 9. const 与指针 --}}

🔒 const 与指针

const 关键字可以限制指针所指向的内容或指针本身是否可修改。

int a = 10, b = 20;
const int *p1 = &a;    // 指向常量的指针:不能通过 p1 修改指向的内容,但 p1 可以指向其他变量
// *p1 = 30;           // 错误!
p1 = &b;               // 正确

int * const p2 = &a;   // 常量指针:p2 本身不可修改,但可通过 p2 修改指向的内容
*p2 = 30;              // 正确
// p2 = &b;            // 错误!

const int * const p3 = &a; // 两者都不可修改
{{-- 10. 常见错误与注意事项 --}}

⚠️ 常见错误与注意事项

  • 使用未初始化的指针:指针变量没有赋值就使用,会指向未知内存,导致程序崩溃。
  • 野指针:指向已释放内存或无效地址的指针,使用前应置为 NULL 并检查。
  • 解引用空指针int *p = NULL; *p = 10; 会导致段错误。
  • 指针类型不匹配:不同类型的指针赋值(如 int *p = &a;char *q = p;)可能导致数据解释错误。
  • 数组越界:指针运算超出数组范围,可能导致数据损坏或程序崩溃。
  • 忘记取地址符scanf("%d", p); 如果 p 是普通变量,应写作 &p;如果 p 是指针,则直接写 p。
  • 返回局部变量的地址:函数返回指向局部变量的指针,函数结束后该内存被回收,指针成为野指针。
{{-- 11. 综合示例 --}}

📋 综合示例:使用指针实现数组排序

#include <stdio.h>

// 交换两个整数
void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

// 冒泡排序,使用指针操作数组
void bubbleSort(int *arr, int n) {
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (*(arr + j) > *(arr + j + 1)) {
                swap(arr + j, arr + j + 1);
            }
        }
    }
}

int main() {
    int nums[] = {64, 34, 25, 12, 22, 11, 90};
    int size = sizeof(nums) / sizeof(nums[0]);

    printf("排序前: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", nums[i]);
    }
    printf("\n");

    bubbleSort(nums, size);

    printf("排序后: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", nums[i]);
    }
    printf("\n");

    return 0;
}

指针是C语言最强大的特性,也是许多高级数据结构(如链表、树)和动态内存管理的基础。掌握指针需要大量的实践,建议多动手编写代码,观察内存变化。 下一章我们将深入探讨指针与数组的关系,以及指针的高级应用。

{{-- 章节导航按钮 --}}