C语言中的指针第1篇(指针的基本概念、指针指针的概念、指针指针的应用、动态内存分配、函数指针、指针指针与数据结构、指针指针与多维数组、总结、指针指针与动态内存分配)

        指针是C语言中的一个重要概念,它存储的是变量的内存地址,通过指针可以直接访问和操作内存中的数据。而当我们需要处理指针本身时,就会用到指针的指针,即指针指针。指针指针听起来可能有些复杂,但其实理解起来并不难,下面我们就来详细探讨一下。 

一、指针的基本概念

        在C语言中,指针是一个变量,它的值为另一个变量的地址。也就是说,指针是用来存储内存地址的变量。我们可以通过指针来访问和操作内存中的数据。

例如:

int a = 10;

int *p = &a; // p是一个指针,存储的是变量a的内存地址

printf("%d\n", *p); // 输出a的值,即10

        在上面的代码中,`*p`表示的是指针`p`所指向的内存地址中的值,也就是变量`a`的值。

二、指针的指针的概念

        指针的指针,顾名思义,就是指向指针的指针。也就是说,指针的指针是一个变量,它的值为另一个指针的地址。通过指针的指针,我们可以访问和操作指针本身。

例如:

int a = 10;

int *p = &a; // p是一个指针,存储的是变量a的内存地址

int pp = &p; // pp是一个指针指针,存储的是指针p的内存地址

printf("%d\n", pp); // 输出a的值,即10

        在上面的代码中,`pp`表示的是指针指针`pp`所指向的内存地址中的指针所指向的内存地址中的值,也就是变量`a`的值。

三、指针指针的应用

        指针指针在实际编程中有很多应用,比如处理二维数组、动态分配内存等。下面我们以一个处理二维数组的例子来说明指针指针的应用。

        假设我们有一个3x3的二维数组,我们想要打印出数组中的每个元素。我们可以使用指针指针来实现这个功能。

#include <stdio.h>
int main() {
    int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    int (*p)[3] = arr; // p是一个指向包含3个整数的数组的指针
    int pp = (int )p; // 将p强制转换为指针指针类型,并赋值给pp 
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", *(*(pp + i) + j)); // 通过指针指针访问数组元素并打印
        }
        printf("\n");
    }  
    return 0;
}

        在上面的代码中,我们首先定义了一个3x3的二维数组`arr`。然后,我们定义了一个指向包含3个整数的数组的指针`p`,并将`arr`的地址赋值给`p`。接着,我们将`p`强制转换为指针指针类型,并赋值给`pp`。最后,我们通过双重循环和指针指针来访问数组中的每个元素,并打印出来。

        需要注意的是,在将`p`强制转换为指针指针类型时,我们假设了数组在内存中是连续存储的,并且每个元素的大小都是相同的。这个假设在大多数情况下是成立的,但在某些特殊情况下可能会出现问题。因此,在使用指针指针处理二维数组时,我们需要谨慎处理。

        通过上面的例子,我们可以看到指针指针在处理二维数组时的灵活性和方便性。当然,指针指针还有很多其他的应用场景,比如动态分配内存、构建链表等。只要我们掌握了指针和指针指针的基本概念和使用方法,就可以在实际编程中灵活运用它们来解决各种问题。好的,我们可以进一步讨论指针指针的其他应用,比如动态内存分配和函数指针。

四、动态内存分配

        在C语言中,动态内存分配是一个重要的概念,它允许我们在运行时根据需要分配或释放内存。指针指针在动态内存分配中扮演着关键角色。

        例如,我们可以使用`malloc`函数来动态分配一个整数数组的内存,并使用指针指针来管理这个数组。

#include <stdio.h>
#include <stdlib.h>
int main() {
    int array; // 定义一个指向指针的指针
    int n = 5; // 假设我们要分配的数组大小为5
    // 动态分配一个指针数组
    array = (int )malloc(n * sizeof(int *));
    if (array == NULL) {
        perror("Memory allocation failed");
        return 1;
    }
    // 为每个指针分配内存并初始化
    for (int i = 0; i < n; i++) {
        array[i] = (int *)malloc(sizeof(int));
        if (array[i] == NULL) {
            perror("Memory allocation failed");
            // 释放之前分配的内存
            for (int j = 0; j < i; j++) {
                free(array[j]);
            }
            free(array);
            return 1;
        }
        array[i][0] = i + 1; // 初始化数组元素
    }
    // 打印数组元素
    for (int i = 0; i < n; i++) {
        printf("%d ", array[i][0]);
    }
    printf("\n");    
    // 释放内存
    for (int i = 0; i < n; i++) {
        free(array[i]);
    }
    free(array);    
    return 0;
}

        在上面的代码中,我们首先定义了一个指向指针的指针`array`。然后,我们使用`malloc`函数动态分配了一个指针数组,并为每个指针分配了内存。接着,我们初始化数组元素并打印它们。最后,我们释放了所有分配的内存。

        需要注意的是,动态内存分配需要谨慎处理,因为如果分配的内存没有及时释放,就可能导致内存泄漏。另外,释放了内存后,要确保不再使用这块内存,否则可能会出现未定义的行为。

五、函数指针

        函数指针是指向函数的指针,而函数指针的指针就是指向函数指针的指针。虽然这个概念可能有些绕,但它在某些情况下非常有用,比如回调函数、函数表等。

        下面是一个简单的函数指针的例子:

#include <stdio.h>
// 定义一个函数类型
typedef void (*FunctionPtr)(int);

// 两个简单的函数
void printNumber(int num) {
    printf("Number: %d\n", num);
}

void printNumberSquared(int num) {
    printf("Squared Number: %d\n", num * num);
}

int main() {
    FunctionPtr funcPtr; // 定义一个函数指针
    int num = 5;    
    // 将函数赋值给函数指针
    funcPtr = printNumber;
    funcPtr(num); // 输出: Number: 5    
    funcPtr = printNumberSquared;
    funcPtr(num); // 输出: Squared Number: 25    
    // 函数指针的指针
    FunctionPtr *funcPtrPtr = &funcPtr;
    (*funcPtrPtr)(num); // 输出: Squared Number: 25,因为funcPtr最后指向的是printNumberSquared
    
    return 0;
}

        在上面的代码中,我们首先定义了一个函数类型`FunctionPtr`,它是指向接受一个`int`参数并返回`void`的函数的指针。然后,我们定义了两个简单的函数`printNumber`和`printNumberSquared`。在`main`函数中,我们创建了一个`FunctionPtr`类型的变量`funcPtr`,并将它指向`printNumber`函数。接着,我们通过`funcPtr`调用了`printNumber`函数。然后,我们将`funcPtr`指向`printNumberSquared`函数,并再次通过`funcPtr`调用了它。最后,我们定义了一个指向函数指针的指针`funcPtrPtr`,并通过它调用了当前`funcPtr`所指向的函数。

        函数指针和函数指针的指针在高级编程和库设计中非常有用,它们允许我们以更灵活和抽象的方式处理函数。

        通过上面的讨论,我们可以看到指针指针在C语言编程中的广泛应用。好的,我们继续深入探索指针指针在C语言中的应用。

六、指针指针与数据结构

        指针指针经常用于构建和操作复杂的数据结构,比如树、图或者更高级的数据结构。这些数据结构通常由指针连接在一起,而指针指针允许我们方便地操作这些指针。

        例如,考虑一个简单的链表结构。每个链表节点包含一个数据元素和一个指向下一个节点的指针。如果我们想要插入或删除节点,就需要修改节点的指针。这时,指针指针就能派上用场。

#include <stdio.h>
#include <stdlib.h>
// 定义链表节点结构体
typedef struct Node {
    int data;
    struct Node* next;
} Node;

// 插入节点到链表头部
void insert(Node head, int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = *head;
    *head = newNode;
}

// 删除链表的头部节点  
void delete(Node head) {
    if (*head == NULL) {
        printf("List is empty.\n");
        return;
    }
    Node* temp = *head;
    *head = (*head)->next;
    free(temp);
}

// 打印链表
void printList(Node* head) {
    Node* current = head;
    while (current != NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    printf("\n");
}

int main() {
    Node* head = NULL; // 初始化链表为空    
    // 插入一些节点
    insert(&head, 1);
    insert(&head, 2);
    insert(&head, 3);    
    // 打印链表
    printList(head); // 输出: 3 2 1    
    // 删除节点
    delete(&head);    
    // 再次打印链表
    printList(head); // 输出: 2 1    
    // 释放链表内存(这里为了简单起见,只释放了头部节点,实际中需要遍历链表释放所有节点)
    free(head);
    return 0;
}

        在上面的代码中,我们定义了一个链表节点结构体`Node`,它包含一个整型数据`data`和一个指向下一个节点的指针`next`。我们使用指针指针`Node head`来操作链表的头部。`insert`函数接受一个指向头指针的指针,并创建一个新节点插入到链表头部。`delete`函数也接受一个指向头指针的指针,并删除链表头部的节点。`printList`函数则用来打印链表的内容。

        通过使用指针指针,我们可以非常方便地修改链表的头部节点,而不需要返回新的头指针或者传递额外的参数。

七、指针指针与多维数组

        虽然我们在前面已经讨论过使用指针指针处理二维数组的情况,但指针指针在处理更高维数组时同样非常有用。通过递归地使用指针指针,我们可以动态地创建和操作任意维度的数组。

        然而,这种处理方式相对复杂,并且容易出错。在实际应用中,我们通常会使用其他数据结构(如结构体数组、动态数组库等)或者高级编程语言提供的多维数组支持来简化处理过程。

八、总结

        指针指针是C语言中一个强大且灵活的工具,它允许我们直接操作指针本身,从而实现更高级的内存管理和数据结构操作。然而,使用指针指针也需要谨慎,因为不正确的操作可能导致内存泄漏、野指针等问题。因此,在使用指针指针时,我们需要确保对C语言的内存管理规则有深入的理解,并仔细检查我们的代码以确保其正确性和安全性。

九、指针指针与函数指针

        指针指针不仅可以用于操作指针变量本身,还可以与函数指针结合使用,实现更高级的功能。函数指针是指向函数的指针,而指针指针则是指向函数指针的指针。

        通过指针指针,我们可以动态地改变函数指针的指向,从而实现在运行时选择调用不同的函数。这在实现回调函数、插件机制、动态链接库等方面非常有用。

下面是一个简单的示例,演示了如何使用指针指针来改变函数指针的指向:

#include <stdio.h>
// 定义两个简单的函数
void func1() {
    printf("Function 1 called.\n");
}

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

// 使用指针指针改变函数指针的指向
void changeFunctionPointer(void (funcPtr)()) {
    *funcPtr = func2; // 将函数指针指向func2
}

int main() {
    void (*currentFunc)(); // 声明一个函数指针
    currentFunc = func1; // 初始时将函数指针指向func1    
    // 调用当前指向的函数
    currentFunc(); // 输出: Function 1 called.    
    // 使用指针指针改变函数指针的指向
    changeFunctionPointer(¤tFunc);    
    // 再次调用当前指向的函数
    currentFunc(); // 输出: Function 2 called.    
    return 0;
}

        在上面的代码中,我们定义了两个简单的函数`func1`和`func2`。`changeFunctionPointer`函数接受一个指向函数指针的指针作为参数,并改变其指向的函数。在`main`函数中,我们首先将`currentFunc`指向`func1`,然后调用它。接着,我们调用`changeFunctionPointer`来改变`currentFunc`的指向,使其指向`func2`,并再次调用它。

十、指针指针与动态内存分配

        指针指针在动态内存分配中也非常有用,特别是当我们需要动态地创建和操作二维数组或多维数组时。通过使用指针指针和`malloc`或`calloc`等函数,我们可以在运行时根据需要分配内存空间。

        下面是一个使用指针指针动态创建二维数组的示例:

#include <stdio.h>
#include <stdlib.h>
int main() {
    int rows = 3;
    int cols = 4;
    
    // 使用指针指针动态创建二维数组
    int array = (int)malloc(rows * sizeof(int*));
    for (int i = 0; i < rows; i++) {
        array[i] = (int*)malloc(cols * sizeof(int));
    }    

    // 初始化数组
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            array[i][j] = i * cols + j;
        }
    }
    
    // 打印数组内容
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d ", array[i][j]);
        }
        printf("\n");
    } 
   
    // 释放内存
    for (int i = 0; i < rows; i++) {
        free(array[i]);
    }
    free(array);    
    return 0;
}

        在上面的代码中,我们首先定义了二维数组的行数和列数。然后,我们使用`malloc`为每一行分配内存空间,并将返回的指针存储在`array`指针数组中。接下来,我们遍历数组并初始化其元素。最后,我们打印数组的内容,并释放分配的内存。

 

 

  • 20
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我来挖坑啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值