C语言指针数组:从原理到实战应用

指针数组:概念与基础

指针数组是一种特殊的数据结构,它允许我们以一种高效且灵活的方式管理和操作数据。指针数组,简单来说,就是一个数组,其数组中的每个元素都是指针。这些指针可以指向各种类型的数据,如整数、字符、结构体等,但它们都具有相同的类型,即指向同一种数据类型的指针。
假设我们有一个需求,需要存储多个字符串。如果使用普通数组,我们可能需要定义一个二维字符数组,如下所示:

char strings[3][10] = {"apple", "banana", "cherry"};

在这个例子中,strings是一个二维数组,它可以存储 3 个字符串,每个字符串的最大长度为 10。然而,这种方式存在一些问题。首先,它会浪费大量的内存空间,因为每个字符串的实际长度可能不同,但我们必须为每个字符串分配相同的最大长度。其次,对字符串进行操作时,如排序、查找等,会变得比较复杂。
在这种情况下,可以使用指针数组解决此类问题。

char *strings[3] = {"apple", "banana", "cherry"};

在这个例子中,strings是一个指针数组,它包含 3 个指针,每个指针指向一个字符串。这种方式不仅节省了内存空间,因为每个指针只需要存储字符串的首地址,而不需要存储整个字符串,而且对字符串的操作也变得更加方便。如可以通过简单地交换指针的值,来实现字符串的排序,而不需要移动整个字符串的内容。
指针数组还常用于函数指针调用。在 C 语言中,函数指针是指向函数的指针变量,它可以用来调用函数。通过将多个函数指针存储在一个指针数组中,可以根据需要动态地选择并调用不同的函数,从而实现灵活的程序设计。例如,在一个图形绘制程序中,我们可以定义一个函数指针数组,每个函数指针指向一个绘制不同图形的函数,如绘制圆形、矩形、三角形等,可以根据用户的输入,从指针数组中选择相应的函数来绘制图形。

指针数组的声明与初始化


在 C 语言中,声明指针的语法:
类型 *数组名[数组大小]。
其中类型表示指针所指向的数据类型,数组名是指针数组的名称,数组大小确定这个数组可以容纳多少个指针。
例如要声明一个包含 5 个指向int类型数据的指针数组,可以这样写:

int *ptr_array[5];

在上述例子中,ptr_array就是一个指针数组,它可以存储 5 个指向int类型数据的指针。需要注意的是,由于[]运算符的优先级高于*运算符,所以ptr_array先与[]结合,形成一个数组,然后再与*结合,表示这个数组中的元素都是指针。
指针数组的初始化方式有两种方式:一种是在声明指针数组时直接进行初始化。例如有 5 个int类型的变量value1、value2、value3、value4、value5,初始化语句为:

int value1 = 1, value2 = 2, value3 = 3, value4 = 4, value5 = 5;
int *ptr_array[5] = {&value1, &value2, &value3, &value4, &value5};

在上述例子中,声明ptr_array指针数组的同时,将它的每个元素分别初始化为value1、value2、value3、value4、value5的地址。
另一种初始化方式是在函数中进行初始化。这种方式相对灵活,适用于在程序运行过程中根据不同的条件来初始化指针数组。例如:

int value1 = 1, value2 = 2, value3 = 3, value4 = 4, value5 = 5;
int *ptr_array[5];
ptr_array[0] = &value1;
ptr_array[1] = &value2;
ptr_array[2] = &value3;
ptr_array[3] = &value4;
ptr_array[4] = &value5;

在这个例子中,先声明了ptr_array指针数组,然后在后续的代码中分别对它的每个元素进行初始化 。

指针数组在字符串处理中的应用

例如在一个文本处理程序中,可能需要读取多个文本行,并对这些文本行进行排序、查找等操作。使用指针数组可以轻松地实现这些功能,而不需要使用复杂的二维数组。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LINES 100
#define MAX_LEN 256

// Function to compare two strings for qsort
int compare(const void *a, const void *b) {
    return strcmp(*(const char ==&zwnj;**)a, *(const char **&zwnj;==)b);
}

// Function to print the lines
void print_lines(char *lines[], int count) {
    for (int i = 0; i < count; i++) {
        printf("%s\n", lines[i]);
    }
}

// Function to find a line
int find_line(char *lines[], int count, const char *target) {
    for (int i = 0; i < count; i++) {
        if (strcmp(lines[i], target) == 0) {
            return i;
        }
    }
    return -1;
}

int main() {
    char *lines[MAX_LINES];
    char buffer[MAX_LEN];
    int count = 0;

    // Read lines from standard input
    printf("Enter lines of text (end input with an empty line):\n");
    while (fgets(buffer, MAX_LEN, stdin) && buffer != '\n' && count < MAX_LINES) {
        // Allocate memory for the line
        lines[count] = malloc(strlen(buffer) + 1);
        if (lines[count] == NULL) {
            perror("Failed to allocate memory");
            return 1;
        }
        // Copy the line into the allocated memory
        strcpy(lines[count], buffer);
        // Remove the newline character if present
        lines[count][strcspn(lines[count], "\n")] = 0;
        count++;
    }

    // Sort the lines
    qsort(lines, count, sizeof(char *), compare);

    // Print the sorted lines
    printf("\nSorted lines:\n");
    print_lines(lines, count);

    // Find a specific line
    char target[MAX_LEN];
    printf("\nEnter a line to find:\n");
    if (fgets(target, MAX_LEN, stdin)) {
        // Remove the newline character if present
        target[strcspn(target, "\n")] = 0;
        int index = find_line(lines, count, target);
        if (index != -1) {
            printf("\nFound line: %s\n", lines[index]);
        } else {
            printf("\nLine not found.\n");
        }
    }

    // Free allocated memory
    for (int i = 0; i < count; i++) {
        free(lines[i]);
    }

    return 0;
}

代码说明
‌读取输入‌:程序从标准输入读取多行文本,直到输入一个空行(只包含换行符)为止。每行文本都动态分配内存,并存储在指针数组 lines 中。
‌排序‌:使用 qsort 函数对文本行进行排序。compare 函数用作 qsort 的比较函数,比较两个字符串的字典顺序。
‌打印‌:打印排序后的文本行。
‌查找‌:允许用户输入一个要查找的文本行,并使用 find_line 函数在排序后的文本行中查找该文本行。如果找到,则打印该行;否则,打印“未找到”。
‌释放内存‌:程序结束前,释放为每行文本分配的内存。

指针数组在函数指针中的应用


函数指针数组允许我们将多个函数指针存储在一个数组中,从而实现更加灵活和高效的编程。函数指针数组的每个元素都是一个函数指针,这些指针可以指向不同的函数,但这些函数必须具有相同的返回类型和参数列表。
假设要实现一个简单的计算器程序,能够进行加法和减法运算。可以定义两个函数,一个用于加法运算,另一个用于减法运算,然后使用函数指针数组来管理这两个函数。具体代码如下:

#include <stdio.h>
// 加法函数
int add(int a, int b) {
    return a + b;
}
// 减法函数
int subtract(int a, int b) {
    return a - b;
}
int main() {
    // 定义函数指针数组,每个元素指向一个函数
    int (*func_ptr_array[2])(int, int) = {add, subtract};
    int num1 = 10, num2 = 5;
    // 调用第一个函数(加法函数)
    int result1 = func_ptr_array[0](num1, num2);
    printf("加法结果: %d\n", result1);
    // 调用第二个函数(减法函数)
    int result2 = func_ptr_array[1](num1, num2);
    printf("减法结果: %d\n", result2);
    return 0;
}

示例代码首先定义两个函数add和subtract,分别用于加法和减法运算。然后再定义了一个函数指针数组func_ptr_array,它包含两个元素,每个元素都是一个指向函数的指针。这两个指针分别指向add函数和subtract函数。
在main函数中,通过func_ptr_array[0]和func_ptr_array[1]来调用这两个函数。func_ptr_array[0]指向add函数,所以func_ptr_array[0](num1, num2)就相当于调用add(num1, num2);func_ptr_array[1]指向subtract函数,所以func_ptr_array[1](num1, num2)就相当于调用subtract(num1, num2)。

指针数组在动态内存分配中的应用

动态内存分配允许我们在程序运行时根据实际需求分配内存,从而提高内存的使用效率。指针数组在动态内存分配中扮演着关键的角色,它可以帮助我们更灵活地管理和操作动态分配的内存块。
假设我们需要创建一个包含 10 个整数的动态数组,可以使用指针数组来实现这个需求,代码如下:

#include <stdio.h>
#include <stdlib.h>
int main() {
    int *dynamic_array[10];
    // 动态分配内存并将指针存入数组
    for (int i = 0; i < 10; i++) {
        dynamic_array[i] = (int *)malloc(sizeof(int));
        if (dynamic_array[i] == NULL) {
            printf("内存分配失败\n");
            // 释放已分配的内存
            for (int j = 0; j < i; j++) {
                free(dynamic_array[j]);
            }
            return 1;
        }
        *dynamic_array[i] = i;
    }
    // 访问动态分配的内存
    for (int i = 0; i < 10; i++) {
        printf("%d ", *dynamic_array[i]);
    }
    printf("\n");
    // 释放动态分配的内存
    for (int i = 0; i < 10; i++) {
        free(dynamic_array[i]);
    }
    return 0;
}

示例代码首先声明了一个指针数组dynamic_array,它包含 10 个指针,每个指针都指向一个动态分配的整数内存块。

然后使用for循环来动态分配内存。在每次循环中,使用malloc函数为dynamic_array[i]分配一个int类型大小的内存块,并将返回的指针赋值给dynamic_array[i]。如果内存分配失败,malloc函数会返回NULL,若分配失败,需要释放之前已经分配的内存,并返回错误信息。

接着使用另一个for循环来访问动态分配的内存。在这个循环中,通过*dynamic_array[i]来访问每个内存块中的值,并将其打印出来。

最后使用第三个for循环来释放动态分配的内存。在释放内存时,需要确保每个分配的内存块都被正确释放,以避免内存泄漏。

需要注意的是,在动态内存分配中,释放内存是非常重要的一步。如果我们在程序中分配了内存,但没有释放,就会导致内存泄漏,这会浪费系统资源,甚至可能导致程序崩溃。

指针数组在数据结构中的应用拓展

指针数组在链表、栈、队列等数据结构中也有着广泛的应用。

在链表中,指针数组可以用于连接链表节点,实现更高效的数据存储和操作。链表是一种动态数据结构,它的每个节点包含数据和指向下一个节点的指针。通过使用指针数组,可以更灵活地管理链表节点的内存分配和连接关系。例如,在一个双向链表中,每个节点除了包含指向下一个节点的指针外,还包含指向前一个节点的指针。我们可以使用指针数组来存储这些指针,从而简化链表的操作。

假设我们有一个双向链表节点的结构体定义如下:

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

我们可以使用指针数组来管理双向链表的节点,代码示例如下:

#include <stdio.h>
#include <stdlib.h>
struct Node {
    int data;
    struct Node *prev;
    struct Node *next;
};
int main() {
    struct Node *nodes[3];
    // 动态分配内存给节点
    for (int i = 0; i < 3; i++) {
        nodes[i] = (struct Node *)malloc(sizeof(struct Node));
        nodes[i]->data = i + 1;
        nodes[i]->prev = NULL;
        nodes[i]->next = NULL;
    }
    // 连接节点形成双向链表
    nodes[0]->next = nodes[1];
    nodes[1]->prev = nodes[0];
    nodes[1]->next = nodes[2];
    nodes[2]->prev = nodes[1];
    // 遍历双向链表(正向)
    struct Node *current = nodes[0];
    while (current!= NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    printf("\n");
    // 遍历双向链表(反向)
    current = nodes[2];
    while (current!= NULL) {
        printf("%d ", current->data);
        current = current->prev;
    }
    printf("\n");
    // 释放内存
    for (int i = 0; i < 3; i++) {
        free(nodes[i]);
    }
    return 0;
}

示例代码首先定义了一个包含 3 个指针的指针数组nodes,每个指针都将指向一个双向链表节点。然后使用for循环动态分配内存给每个节点,并初始化节点的数据和指针。再通过修改节点的prev和next指针,将这些节点连接成一个双向链表。最后分别从链表的头部和尾部开始遍历链表,输出节点的数据。
在栈和队列数据结构中,指针数组也非常有用。栈是一种后进先出(LIFO)的数据结构,队列是一种先进先出(FIFO)的数据结构。通过使用指针数组,可以更高效地实现栈和队列的操作。例如,在一个链式栈中,我们可以使用指针数组来存储栈中每个节点的地址,从而方便地进行入栈和出栈操作。在一个链式队列中,我们可以使用指针数组来存储队列中每个节点的地址,从而方便地进行入队和出队操作。
以链式栈为例,假设我们有一个链式栈节点的结构体定义如下:

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

我们可以使用指针数组来实现链式栈的操作,代码示例如下:

#include <stdio.h>
#include <stdlib.h>
struct StackNode {
    int data;
    struct StackNode *next;
};
void push(struct StackNode **stack, int data) {
    struct StackNode *newNode = (struct StackNode *)malloc(sizeof(struct StackNode));
    newNode->data = data;
    newNode->next = *stack;
    *stack = newNode;
}
int pop(struct StackNode **stack) {
    if (*stack == NULL) {
        printf("栈为空\n");
        return -1;
    }
    struct StackNode *temp = *stack;
    int popped = temp->data;
    *stack = (*stack)->next;
    free(temp);
    return popped;
}
int main() {
    struct StackNode *stack = NULL;
    struct StackNode *stackArray[3];
    // 入栈操作
    push(&stack, 10);
    push(&stack, 20);
    push(&stack, 30);
    // 将栈中的节点地址存入指针数组
    struct StackNode *current = stack;
    int i = 0;
    while (current!= NULL && i < 3) {
        stackArray[i] = current;
        current = current->next;
        i++;
    }
    // 通过指针数组访问栈中的节点
    for (int j = 0; j < 3; j++) {
        if (stackArray[j]!= NULL) {
            printf("通过指针数组访问栈中节点数据: %d\n", stackArray[j]->data);
        }
    }
    // 出栈操作
    printf("出栈元素: %d\n", pop(&stack));
    printf("出栈元素: %d\n", pop(&stack));
    printf("出栈元素: %d\n", pop(&stack));
    return 0;
}

示例代码定义了一个链式栈节点的结构体StackNode,并实现了push和pop函数来进行入栈和出栈操作。在main函数中,首先创建了一个空栈stack和一个指针数组stackArray。然后通过push函数将三个元素入栈,并将栈中的节点地址存入指针数组stackArray。接着通过指针数组访问栈中的节点,并输出节点的数据。最后通过pop函数将栈中的元素依次出栈。