C 语言奇幻之旅 - 第09篇:C 语言指针

发布于:2025-02-10 ⋅ 阅读:(52) ⋅ 点赞:(0)

目录

引言

欢迎来到 C 语言奇幻之旅的第 9 篇!今天我们将深入探讨 C 语言中最强大且最具挑战性的概念之一 —— 指针。指针是 C 语言的灵魂,它赋予了程序员直接操作内存的能力。掌握指针,你将能够编写出高效、灵活的代码。本文将带你从指针的基本概念出发,逐步深入到指针与数组、指针与函数的高级用法。无论你是初学者还是资深开发者,相信本文都能为你带来新的启发。


1. 指针的基本概念

1.1 指针的定义与初始化

指针是一个变量,它存储的是另一个变量的内存地址。通过指针,我们可以间接访问和操作这个变量。

1.1.1 指针的定义

在 C 语言中,指针的定义格式如下:

数据类型 *指针变量名;

例如,定义一个指向 int 类型变量的指针:

int *p;

这里,p 是一个指向 int 类型变量的指针。

1.1.2 指针的初始化

指针在定义后,通常需要初始化为某个变量的地址。可以使用取地址运算符 & 来获取变量的地址:

int a = 10;
int *p = &a;

这里,p 指向了变量 a 的地址。

1.1.3 指针的解引用

通过指针访问它所指向的变量,称为解引用。解引用使用 * 运算符:

int a = 10;
int *p = &a;
int b = *p;  // b 的值为 10
1.1.4 示例代码
#include <stdio.h>

int main() {
    int a = 10;
    int *p = &a;

    printf("a 的值为: %d\n", a);  // 结果为:10
    printf("p 指向的值为: %d\n", *p);  // 结果为:10

    *p = 20;
    printf("修改后 a 的值为: %d\n", a);  // 结果为:20

    return 0;
}
1.1.5 程序执行步骤
步骤 变量值 条件评估 输出 操作
初始化 a=10, p=&a 进入 main 函数
打印 a 的值 a=10, p=&a 10 打印 a 的值
打印 p 指向的值 a=10, p=&a 10 打印 *p 的值
修改 p 指向的值 a=10, p=&a *p = 20
打印修改后的 a 的值 a=20, p=&a 20 打印 a 的值
1.1.6 栈帧结构(仿真内存)
// 栈帧结构(仿真内存)
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 a (int) = 10
// 变量 p (int*) = 0x1000
// ----------------------------

// 执行 *p = 20
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 a (int) = 20
// 变量 p (int*) = 0x1000
// ----------------------------

1.2 指针的运算

指针的运算主要包括指针的加减运算和指针的比较运算。

1.2.1 指针的加减运算

指针的加减运算是以指针所指向的数据类型的大小为单位的。例如,int 类型的大小通常为 4 字节,因此对 int 指针进行加 1 操作,指针的值会增加 4

int arr[3] = {10, 20, 30};
int *p = arr;

printf("p 指向的值为: %d\n", *p);  // 结果为:10
p++;
printf("p 指向的值为: %d\n", *p);  // 结果为:20
1.2.2 指针的比较运算

指针的比较运算通常用于判断两个指针是否指向同一个内存地址,或者判断指针的相对位置。

int arr[3] = {10, 20, 30};
int *p1 = &arr[0];
int *p2 = &arr[2];

if (p1 < p2) {
    printf("p1 在 p2 之前\n");  // 结果为:p1 在 p2 之前
}
1.2.3 示例代码
#include <stdio.h>

int main() {
    int arr[3] = {10, 20, 30};
    int *p = arr;

    printf("p 指向的值为: %d\n", *p);  // 结果为:10
    p++;
    printf("p 指向的值为: %d\n", *p);  // 结果为:20

    int *p1 = &arr[0];
    int *p2 = &arr[2];

    if (p1 < p2) {
        printf("p1 在 p2 之前\n");  // 结果为:p1 在 p2 之前
    }

    return 0;
}
1.2.4 程序执行步骤
步骤 变量值 条件评估 输出 操作
初始化 arr[3]={10,20,30}, p=arr 进入 main 函数
打印 p 指向的值 p=arr 10 打印 *p 的值
p++ p=arr+1 p 指向 arr[1]
打印 p 指向的值 p=arr+1 20 打印 *p 的值
初始化 p1p2 p1=arr, p2=arr+2 初始化指针
比较 p1p2 p1=arr, p2=arr+2 p1 < p2 p1p2 之前 打印比较结果
1.2.5 栈帧结构(仿真内存)
// 栈帧结构(仿真内存)
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 数组 arr[3] = {10, 20, 30}
// 变量 p (int*) = 0x1000
// 变量 p1 (int*) = 0x1000
// 变量 p2 (int*) = 0x1008
// ----------------------------

// 执行 p++
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 p (int*) = 0x1004
// ----------------------------

2. 指针与数组

2.1 指针与数组的关系

在 C 语言中,数组名实际上是一个指向数组第一个元素的指针。因此,我们可以通过指针来访问数组元素。

2.1.1 通过指针访问数组元素
int arr[3] = {10, 20, 30};
int *p = arr;

printf("arr[0] = %d\n", *p);  // 结果为:10
printf("arr[1] = %d\n", *(p + 1));  // 结果为:20
printf("arr[2] = %d\n", *(p + 2));  // 结果为:30
2.1.2 数组名与指针的区别

虽然数组名和指针都可以用来访问数组元素,但它们之间有一些重要的区别:

特性 数组名 指针
类型 数组类型 指针类型
可修改性 不可修改 可修改
大小 数组大小 指针大小(通常为 48 字节)
赋值 不能赋值 可以赋值
2.1.3 栈帧模拟

在函数 main 的栈帧中,数组 arr 和指针 p 的内存布局如下:

栈帧布局:
+-------------------+
| arr[0] = 10       |  <-- 地址 0x1000
+-------------------+
| arr[1] = 20       |  <-- 地址 0x1004
+-------------------+
| arr[2] = 30       |  <-- 地址 0x1008
+-------------------+
| p = 0x1000        |  <-- 地址 0x2000
+-------------------+
  • arr 是一个包含 3int 元素的数组,存储在地址 0x10000x1008
  • p 初始指向 arr[0],值为 0x1000

3. 指针与函数

3.1 指针作为函数参数

指针可以作为函数的参数,通过指针参数,函数可以修改调用者的变量。

3.1.1 指针参数的示例
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 10, y = 20;
    swap(&x, &y);
    printf("x = %d, y = %d\n", x, y);  // 结果为:x = 20, y = 10
    return 0;
}
3.1.2 程序执行步骤
步骤 变量值 条件评估 输出 操作
初始化 x=10, y=20 进入 main 函数
调用 swap x=10, y=20 进入 swap 函数
交换值 temp=10, *a=20, *b=10 交换 xy 的值
返回 main x=20, y=10 返回 main 函数
打印结果 x=20, y=10 x=20, y=10 打印 xy 的值
3.1.3 栈帧结构(仿真内存)
// 栈帧结构(仿真内存)
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 x (int) = 10
// 变量 y (int) = 20
// ----------------------------

// 调用 swap(&x, &y)
// ----------------------------
// 栈帧:swap 函数
// ----------------------------
// 参数 a (int*) = 0x1000
// 参数 b (int*) = 0x1004
// 变量 temp (int) = 10
// ----------------------------

// 执行 swap 函数体
// temp = *a = 10
// *a = *b = 20
// *b = temp = 10

// 返回 main 函数
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 x (int) = 20
// 变量 y (int) = 10
// ----------------------------

3.2 函数指针

函数指针是指向函数的指针,通过函数指针,我们可以动态调用不同的函数。

3.2.1 函数指针的定义
返回值类型 (*函数指针名)(参数列表);

例如,定义一个指向 int 类型函数,参数为两个 int 的函数指针:

int (*p)(int, int);
3.2.2 函数指针的使用
int add(int a, int b) {
    return a + b;
}

int sub(int a, int b) {
    return a - b;
}

int main() {
    int (*p)(int, int);
    p = add;
    printf("add(10, 20) = %d\n", p(10, 20));  // 结果为:30

    p = sub;
    printf("sub(20, 10) = %d\n", p(20, 10));  // 结果为:10

    return 0;
}
3.2.3 程序执行步骤
步骤 变量值 条件评估 输出 操作
初始化 p=add 进入 main 函数
调用 add p=add 30 调用 add(10, 20)
调用 sub p=sub 10 调用 sub(20, 10)
3.2.4 栈帧结构(仿真内存)
// 栈帧结构(仿真内存)
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 p (函数指针) = 0x1000
// ----------------------------

// 调用 add(10, 20)
// ----------------------------
// 栈帧:add 函数
// ----------------------------
// 参数 a (int) = 10
// 参数 b (int) = 20
// 返回值 (int) = 30
// ----------------------------

// 调用 sub(20, 10)
// ----------------------------
// 栈帧:sub 函数
// ----------------------------
// 参数 a (int) = 20
// 参数 b (int) = 10
// 返回值 (int) = 10
// ----------------------------

4. 指针与字符串

4.1 字符串的本质

在 C 语言中,字符串是以 \0 结尾的字符数组。字符串可以通过字符数组或字符指针来表示。

4.1.1 字符数组表示字符串
char str[] = "Hello";
4.1.2 字符指针表示字符串
char *str = "Hello";
4.1.3 示例代码
#include <stdio.h>

int main() {
    char str1[] = "Hello";
    char *str2 = "World";

    printf("str1: %s\n", str1);  // 结果为:Hello
    printf("str2: %s\n", str2);  // 结果为:World

    return 0;
}
4.1.4 程序执行步骤
步骤 变量值 条件评估 输出 操作
初始化 str1="Hello", str2="World" 进入 main 函数
打印 str1 str1="Hello" Hello 打印 str1 的值
打印 str2 str2="World" World 打印 str2 的值
4.1.5 栈帧结构(仿真内存)
// 栈帧结构(仿真内存)
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 str1 (char[]) = "Hello"
// 变量 str2 (char*) = 0x2000
// ----------------------------

4.2 指针操作字符串

通过指针可以方便地操作字符串,例如遍历字符串、修改字符串等。

4.2.1 遍历字符串
char *str = "Hello";
while (*str != '\0') {
    printf("%c", *str);
    str++;
}
4.2.2 修改字符串
char str[] = "Hello";
char *p = str;
*p = 'h';  // 修改第一个字符
printf("%s\n", str);  // 结果为:hello
4.2.3 示例代码
#include <stdio.h>

int main() {
    char str[] = "Hello";
    char *p = str;

    while (*p != '\0') {
        printf("%c", *p);
        p++;
    }
    printf("\n");

    p = str;
    *p = 'h';
    printf("%s\n", str);  // 结果为:hello

    return 0;
}
4.2.4 程序执行步骤
步骤 变量值 条件评估 输出 操作
初始化 str="Hello", p=str 进入 main 函数
遍历字符串 p=str *p != '\0' H 打印 *p 的值
遍历字符串 p=str+1 *p != '\0' e 打印 *p 的值
遍历字符串 p=str+2 *p != '\0' l 打印 *p 的值
遍历字符串 p=str+3 *p != '\0' l 打印 *p 的值
遍历字符串 p=str+4 *p != '\0' o 打印 *p 的值
修改字符串 p=str *p = 'h'
打印修改后的字符串 str="hello" hello 打印 str 的值
4.2.5 栈帧结构(仿真内存)
// 栈帧结构(仿真内存)
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 str (char[]) = "Hello"
// 变量 p (char*) = 0x1000
// ----------------------------

// 执行 *p = 'h'
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 str (char[]) = "hello"
// ----------------------------

5. 指针的高级用法

5.1 多级指针

多级指针是指指向指针的指针。例如,int **p 是一个指向 int 指针的指针。

5.1.1 多级指针的示例
int a = 10;
int *p = &a;
int **pp = &p;

printf("a 的值为: %d\n", **pp);  // 结果为:10
5.1.2 程序执行步骤
步骤 变量值 条件评估 输出 操作
初始化 a=10, p=&a, pp=&p 进入 main 函数
打印 a 的值 a=10, p=&a, pp=&p 10 打印 **pp 的值
5.1.3 栈帧结构(仿真内存)
// 栈帧结构(仿真内存)
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 a (int) = 10
// 变量 p (int*) = 0x1000
// 变量 pp (int**) = 0x2000
// ----------------------------

5.2 动态内存分配

动态内存分配是 C 语言中常用的技术,通过 mallocfree 函数实现。

5.2.1 动态内存分配的示例
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *p = (int *)malloc(sizeof(int) * 3);
    p[0] = 10;
    p[1] = 20;
    p[2] = 30;

    printf("p[0] = %d\n", p[0]);  // 结果为:10
    printf("p[1] = %d\n", p[1]);  // 结果为:20
    printf("p[2] = %d\n", p[2]);  // 结果为:30

    free(p);
    return 0;
}
5.2.2 程序执行步骤
步骤 变量值 条件评估 输出 操作
初始化 p=动态分配的内存 进入 main 函数
赋值 p[0]=10, p[1]=20, p[2]=30 赋值动态内存
打印 p[0] p[0]=10 10 打印 p[0] 的值
打印 p[1] p[1]=20 20 打印 p[1] 的值
打印 p[2] p[2]=30 30 打印 p[2] 的值
释放内存 p=动态分配的内存 释放动态内存
5.2.3 栈帧结构(仿真内存)
// 栈帧结构(仿真内存)
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 p (int*) = 0x1000
// ----------------------------

// 动态分配内存
// ----------------------------
// 堆内存:0x1000 - 0x100C
// ----------------------------
// p[0] = 10
// p[1] = 20
// p[2] = 30
// ----------------------------

// 释放内存
// ----------------------------
// 堆内存:已释放
// ----------------------------

5.3 函数指针数组

函数指针数组是一个数组,其元素都是函数指针。通过函数指针数组,可以实现状态机或回调机制。

5.3.1 函数指针数组的示例
#include <stdio.h>

void func1() {
    printf("Function 1\n");
}

void func2() {
    printf("Function 2\n");
}

void func3() {
    printf("Function 3\n");
}

int main() {
    void (*funcs[3])() = {func1, func2, func3};

    for (int i = 0; i < 3; i++) {
        funcs[i]();
    }

    return 0;
}
5.3.2 程序执行步骤
步骤 变量值 条件评估 输出 操作
初始化 funcs={func1, func2, func3} 进入 main 函数
调用 func1 funcs[0]=func1 Function 1 调用 func1
调用 func2 funcs[1]=func2 Function 2 调用 func2
调用 func3 funcs[2]=func3 Function 3 调用 func3
5.3.3 栈帧结构(仿真内存)
// 栈帧结构(仿真内存)
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 funcs (函数指针数组) = {0x1000, 0x2000, 0x3000}
// ----------------------------

// 调用 func1
// ----------------------------
// 栈帧:func1 函数
// ----------------------------
// 输出:Function 1
// ----------------------------

// 调用 func2
// ----------------------------
// 栈帧:func2 函数
// ----------------------------
// 输出:Function 2
// ----------------------------

// 调用 func3
// ----------------------------
// 栈帧:func3 函数
// ----------------------------
// 输出:Function 3
// ----------------------------

5.4 指针与结构体

结构体是 C 语言中用于组织多个不同类型数据的复合数据类型。通过指针可以方便地操作结构体。

5.4.1 结构体指针的示例
#include <stdio.h>

struct Point {
    int x;
    int y;
};

int main() {
    struct Point p = {10, 20};
    struct Point *ptr = &p;

    printf("p.x = %d, p.y = %d\n", ptr->x, ptr->y);  // 结果为:p.x = 10, p.y = 20
    ptr->x = 30;
    printf("修改后 p.x = %d\n", p.x);  // 结果为:30

    return 0;
}
5.4.2 程序执行步骤
步骤 变量值 条件评估 输出 操作
初始化 p={10, 20}, ptr=&p 进入 main 函数
打印 p 的值 p={10, 20}, ptr=&p p.x=10, p.y=20 打印 ptr->xptr->y 的值
修改 p.x p={30, 20}, ptr=&p ptr->x = 30
打印修改后的 p.x p={30, 20}, ptr=&p 30 打印 p.x 的值
5.4.3 栈帧结构(仿真内存)
// 栈帧结构(仿真内存)
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 p (struct Point) = {10, 20}
// 变量 ptr (struct Point*) = 0x1000
// ----------------------------

// 执行 ptr->x = 30
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 p (struct Point) = {30, 20}
// ----------------------------

5.5 指针与链表

链表是一种常见的数据结构,通过指针将多个节点连接在一起。

5.5.1 链表的示例
#include <stdio.h>
#include <stdlib.h>

struct Node {
    int data;
    struct Node *next;
};

int main() {
    struct Node *head = (struct Node *)malloc(sizeof(struct Node));
    head->data = 10;
    head->next = (struct Node *)malloc(sizeof(struct Node));
    head->next->data = 20;
    head->next->next = NULL;

    struct Node *current = head;
    while (current != NULL) {
        printf("%d ", current->data);  // 结果为:10 20
        current = current->next;
    }

    free(head->next);
    free(head);
    return 0;
}
5.5.2 程序执行步骤
步骤 变量值 条件评估 输出 操作
初始化 head=动态分配的内存 进入 main 函数
赋值 head->data=10, head->next=动态分配的内存 赋值链表节点
赋值 head->next->data=20, head->next->next=NULL 赋值链表节点
遍历链表 current=head current != NULL 10 打印 current->data 的值
遍历链表 current=head->next current != NULL 20 打印 current->data 的值
释放内存 head=动态分配的内存 释放链表内存
5.5.3 栈帧结构(仿真内存)
// 栈帧结构(仿真内存)
// ----------------------------
// 栈帧:main 函数
// ----------------------------
// 变量 head (struct Node*) = 0x1000
// ----------------------------

// 动态分配内存
// ----------------------------
// 堆内存:0x1000 - 0x1008
// ----------------------------
// head->data = 10
// head->next = 0x2000
// ----------------------------

// 堆内存:0x2000 - 0x2008
// ----------------------------
// head->next->data = 20
// head->next->next = NULL
// ----------------------------

// 释放内存
// ----------------------------
// 堆内存:已释放
// ----------------------------

结语

通过本文的学习,你应该对 C 语言中的指针有了更深入的理解。指针是 C 语言中最强大的工具之一,掌握它将使你在编程中如虎添翼。希望本文能激发你对 C 语言的学习兴趣,并帮助你在开发技能上更上一层楼。


希望这篇博客能为你提供有价值的信息,并激发你对 C 语言学习的兴趣。如果有任何问题或建议,欢迎随时告诉我!😊

上一篇:第08篇:C 语言数组与字符串
下一篇:第10篇:C 语言结构体与联合体


网站公告

今日签到

点亮在社区的每一天
去签到