【C语言】 指针与数据结构操作

操作一般变量

指针的基本概念与定义

指针是C语言的核心特性之一,它是一种特殊类型的变量,用于存储内存地址。通过指针,我们可以间接访问和操作内存中的数据,这为动态内存管理、数组操作、函数调用等提供了极大的灵活性。

指针的定义与初始化:

c 复制代码
// 定义整型变量
int a = 10;

// 定义指向整型的指针变量
int *p;

// 将变量a的地址赋给指针p
p = &a;

// 或者定义时直接初始化
int *q = &a;

指针操作的基本步骤

  1. 确定目标变量数据类型及其地址类型

    • 对于变量 int a;,其数据类型为 int,地址类型为 int *
  2. 基于地址类型定义指针变量

    c 复制代码
    int *pointer;  // 定义int型指针
  3. 将目标变量地址赋值给指针

    c 复制代码
    pointer = &a;  // &为取地址运算符
  4. 通过指针访问目标变量

    c 复制代码
    *pointer = 20;  // *为解引用运算符,等价于 a = 20

指针操作的完整示例

c 复制代码
#include <stdio.h>

int main() {
    // 示例1:操作double类型变量
    double a = 0.0;
    double *p = &a;     // p指向a
    *p = 3.14159;       // 通过指针修改a的值
    printf("a = %.5f\n", a);  // 输出: a = 3.14159
    
    // 示例2:操作int类型变量
    int b = 0;
    int *q = &b;
    *q = 100;           // 等价于 b = 100
    printf("b = %d\n", b);    // 输出: b = 100
    
    // 示例3:多级指针
    int **r = &q;       // r是指向指针的指针
    **r = 200;          // 等价于 *q = 200, 等价于 b = 200
    printf("b = %d\n", b);    // 输出: b = 200
    
    return 0;
}

指针操作的注意事项

  1. 指针类型必须匹配:指针的类型必须与它指向的变量类型一致

  2. 空指针与野指针

    c 复制代码
    int *p = NULL;    // 正确的空指针初始化
    int *q;           // 未初始化,是野指针(危险!)
  3. 指针的大小:所有数据指针在32位系统中占4字节,在64位系统中占8字节

  4. const修饰符与指针

    c 复制代码
    const int *p;      // 指向常量的指针,不能通过p修改指向的值
    int *const p;      // 常量指针,不能修改p的指向
    const int *const p; // 指向常量的常量指针

操作数组的元素

指针与一维数组

数组名本身就是一个指向数组首元素的常量指针。通过指针操作数组可以提高访问效率,是C语言中常见的编程技巧。

基本操作步骤:

c 复制代码
int arr[5] = {1, 2, 3, 4, 5};

// 步骤1:定义与数组元素类型匹配的指针
int *p;

// 步骤2:将数组首元素地址赋给指针
p = arr;        // 等价于 p = &arr[0]

// 步骤3:通过指针访问数组元素
for(int i = 0; i < 5; i++) {
    printf("arr[%d] = %d\n", i, *(p + i));
}

指针的算术运算

指针支持有限的算术运算,这是指针操作数组的基础:

c 复制代码
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;

// 指针加法
printf("*p = %d\n", *p);           // 输出: 10
printf("*(p+1) = %d\n", *(p+1));   // 输出: 20

// 指针减法
int *q = &arr[4];
printf("q-p = %ld\n", q - p);      // 输出: 4(两个指针之间元素的个数)

// 指针自增/自减
p++;
printf("*p = %d\n", *p);           // 输出: 20

指针与二维数组

二维数组在内存中仍然是连续存储的,但指针操作稍复杂:

c 复制代码
#include <stdio.h>

int main() {
    int a[3][4] = {
        {0, 1, 2, 3},
        {4, 5, 6, 7},
        {8, 9, 10, 11}
    };
    
    // 方法1:使用指向一维数组的指针
    int (*p)[4] = a;  // p指向包含4个int元素的数组
    
    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 4; j++) {
            printf("a[%d][%d] = %d\t", i, j, p[i][j]);
            // 等价于: *(*(p + i) + j)
        }
        printf("\n");
    }
    
    printf("\n");
    
    // 方法2:将二维数组视为一维数组操作(不推荐,但可行)
    int *q = &a[0][0];  // 获取第一个元素的地址
    
    for(int i = 0; i < 12; i++) {
        printf("q[%d] = %d\n", i, q[i]);
    }
    
    return 0;
}

动态数组与指针

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    int n;
    printf("请输入数组大小: ");
    scanf("%d", &n);
    
    // 动态分配内存
    int *arr = (int*)malloc(n * sizeof(int));
    
    if(arr == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }
    
    // 使用指针初始化数组
    for(int i = 0; i < n; i++) {
        arr[i] = i * 10;  // 等价于 *(arr + i) = i * 10
    }
    
    // 输出数组元素
    for(int i = 0; i < n; i++) {
        printf("arr[%d] = %d\n", i, *(arr + i));
    }
    
    // 释放内存
    free(arr);
    arr = NULL;  // 避免悬空指针
    
    return 0;
}

操作函数(函数指针)

函数指针的基本概念

函数指针是指向函数的指针变量,它存储的是函数代码的起始地址。通过函数指针,我们可以动态调用不同的函数,实现回调机制等高级功能。

函数指针的定义步骤:

  1. 确定函数类型:函数类型由返回值类型和参数类型共同决定

    c 复制代码
    // 原函数声明
    int add(int x, int y);
    // 函数类型:int (int, int)
  2. 定义函数指针 :在函数类型基础上添加 (*指针名)

    c 复制代码
    int (*func_ptr)(int, int);  // 定义函数指针
  3. 初始化函数指针:将函数地址赋给指针

    c 复制代码
    func_ptr = add;      // 直接使用函数名
    // 或
    func_ptr = &add;     // 使用取地址运算符

函数指针的使用示例

c 复制代码
#include <stdio.h>

// 函数声明
int add(int x, int y);
int subtract(int x, int y);
int multiply(int x, int y);
int divide(int x, int y);

int main() {
    // 定义函数指针
    int (*operation)(int, int);
    
    int a = 10, b = 5, result;
    
    // 使用函数指针调用add函数
    operation = add;
    result = operation(a, b);
    printf("%d + %d = %d\n", a, b, result);
    
    // 使用函数指针调用subtract函数
    operation = subtract;
    result = operation(a, b);
    printf("%d - %d = %d\n", a, b, result);
    
    // 直接调用和通过指针调用是等价的
    result = (*operation)(a, b);  // 传统方式
    result = operation(a, b);     // 简化方式(推荐)
    
    return 0;
}

// 函数定义
int add(int x, int y) {
    return x + y;
}

int subtract(int x, int y) {
    return x - y;
}

int multiply(int x, int y) {
    return x * y;
}

int divide(int x, int y) {
    if(y != 0) {
        return x / y;
    }
    return 0;
}

函数指针数组

函数指针可以存储在数组中,这在实现状态机、命令模式等场景中非常有用:

c 复制代码
#include <stdio.h>

// 定义几个测试函数
void func1() { printf("执行函数1\n"); }
void func2() { printf("执行函数2\n"); }
void func3() { printf("执行函数3\n"); }

int main() {
    // 定义函数指针数组
    void (*func_array[3])() = {func1, func2, func3};
    
    // 通过数组索引调用不同的函数
    for(int i = 0; i < 3; i++) {
        printf("调用func_array[%d]: ", i);
        func_array[i]();
    }
    
    return 0;
}

函数指针详解

函数指针的定义与typedef

为了提高代码可读性,通常使用typedef为函数指针类型创建别名:

c 复制代码
#include <stdio.h>

// 使用typedef定义函数指针类型
typedef int (*MathFunc)(int, int);

// 函数声明
int add(int x, int y);
int max(int x, int y);

int main() {
    // 使用类型别名定义函数指针
    MathFunc func_ptr;
    
    func_ptr = add;
    printf("10 + 5 = %d\n", func_ptr(10, 5));
    
    func_ptr = max;
    printf("max(10, 5) = %d\n", func_ptr(10, 5));
    
    return 0;
}

int add(int x, int y) {
    return x + y;
}

int max(int x, int y) {
    return (x > y) ? x : y;
}

复杂函数指针类型

c 复制代码
#include <stdio.h>

// 函数指针作为参数
typedef void (*Callback)(int, void*);

// 函数指针作为返回值
typedef Callback (*GetCallback)(void);

// 回调函数示例
void progress_callback(int progress, void* user_data) {
    printf("进度: %d%%, 用户数据: %p\n", progress, user_data);
}

// 返回回调函数的函数
Callback get_default_callback() {
    return progress_callback;
}

int main() {
    // 获取回调函数
    GetCallback getter = get_default_callback;
    Callback cb = getter();
    
    // 使用回调函数
    int user_data = 12345;
    cb(50, &user_data);
    
    return 0;
}

函数指针与qsort函数

函数指针在标准库函数中广泛应用,qsort就是一个典型例子:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

// 比较函数:用于qsort
int compare_int(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}

int compare_string(const void* a, const void* b) {
    return strcmp(*(const char**)a, *(const char**)b);
}

int main() {
    // 整数数组排序
    int int_array[] = {9, 5, 7, 1, 3, 8, 2, 6, 4};
    int int_count = sizeof(int_array) / sizeof(int_array[0]);
    
    qsort(int_array, int_count, sizeof(int), compare_int);
    
    printf("排序后的整数数组: ");
    for(int i = 0; i < int_count; i++) {
        printf("%d ", int_array[i]);
    }
    printf("\n");
    
    // 字符串数组排序
    const char* str_array[] = {"banana", "apple", "orange", "grape", "cherry"};
    int str_count = sizeof(str_array) / sizeof(str_array[0]);
    
    qsort(str_array, str_count, sizeof(char*), compare_string);
    
    printf("排序后的字符串数组: ");
    for(int i = 0; i < str_count; i++) {
        printf("%s ", str_array[i]);
    }
    printf("\n");
    
    return 0;
}

如何用函数指针调用函数

函数指针调用的多种形式

c 复制代码
#include <stdio.h>

int multiply(int x, int y) {
    return x * y;
}

int main() {
    int (*func_ptr)(int, int);
    
    // 赋值方式1:使用函数名
    func_ptr = multiply;
    
    // 赋值方式2:使用取地址运算符
    func_ptr = &multiply;
    
    int a = 5, b = 6;
    
    // 调用方式1:像普通函数一样调用(推荐)
    int result1 = func_ptr(a, b);
    
    // 调用方式2:使用解引用运算符(传统方式)
    int result2 = (*func_ptr)(a, b);
    
    // 调用方式3:使用双重解引用(合法但不常用)
    int result3 = (*(*func_ptr))(a, b);
    
    printf("结果1: %d\n", result1);
    printf("结果2: %d\n", result2);
    printf("结果3: %d\n", result3);
    
    return 0;
}

函数指针作为函数参数(回调函数)

回调函数的基本模式

回调函数是一种强大的编程模式,允许函数将其部分逻辑交给调用者定义:

c 复制代码
#include <stdio.h>
#include <math.h>

// 定义回调函数类型
typedef double (*TransformFunc)(double);

// 接受回调函数作为参数的函数
void transform_array(double arr[], int size, TransformFunc func) {
    for(int i = 0; i < size; i++) {
        arr[i] = func(arr[i]);
    }
}

// 各种变换函数
double square(double x) { return x * x; }
double cube(double x) { return x * x * x; }
double square_root(double x) { return sqrt(x); }
double reciprocal(double x) { return 1.0 / x; }

int main() {
    double numbers[] = {1.0, 2.0, 3.0, 4.0, 5.0};
    int count = sizeof(numbers) / sizeof(numbers[0]);
    
    printf("原始数组: ");
    for(int i = 0; i < count; i++) {
        printf("%.2f ", numbers[i]);
    }
    printf("\n");
    
    // 使用不同的回调函数
    transform_array(numbers, count, square);
    printf("平方后: ");
    for(int i = 0; i < count; i++) {
        printf("%.2f ", numbers[i]);
    }
    printf("\n");
    
    // 重置数组
    double numbers2[] = {1.0, 2.0, 3.0, 4.0, 5.0};
    transform_array(numbers2, count, square_root);
    printf("开方后: ");
    for(int i = 0; i < count; i++) {
        printf("%.2f ", numbers2[i]);
    }
    printf("\n");
    
    return 0;
}

带上下文信息的回调函数

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 回调函数类型,带用户数据参数
typedef void (*DataCallback)(const char* data, void* user_data);

// 数据处理函数
void process_data(const char* input, DataCallback callback, void* user_data) {
    printf("处理数据: %s\n", input);
    
    // 模拟数据处理
    char* processed = malloc(strlen(input) + 10);
    sprintf(processed, "已处理: %s", input);
    
    // 调用回调函数
    callback(processed, user_data);
    
    free(processed);
}

// 回调函数1:打印数据
void print_callback(const char* data, void* user_data) {
    printf("打印回调: %s (用户数据: %d)\n", data, *(int*)user_data);
}

// 回调函数2:保存数据到文件
void save_callback(const char* data, void* user_data) {
    FILE* file = (FILE*)user_data;
    fprintf(file, "%s\n", data);
    printf("数据已保存到文件\n");
}

int main() {
    // 示例1:使用打印回调
    int user_value = 42;
    process_data("测试数据", print_callback, &user_value);
    
    // 示例2:使用保存回调
    FILE* file = fopen("output.txt", "w");
    if(file) {
        process_data("需要保存的数据", save_callback, file);
        fclose(file);
    }
    
    return 0;
}

指针练习 - 字符串函数实现

字符串长度函数

c 复制代码
#include <stdio.h>

// 计算字符串长度(标准实现)
size_t xyd_strlen(const char* str) {
    const char* p = str;
    while(*p != '\0') {
        p++;
    }
    return p - str;  // 指针相减得到长度
}

// 计算字符串长度(带安全检查)
size_t xyd_strlen_safe(const char* str, size_t max_len) {
    const char* p = str;
    size_t len = 0;
    
    while(len < max_len && *p != '\0') {
        p++;
        len++;
    }
    
    return len;
}

int main() {
    char str[] = "Hello, World!";
    
    printf("字符串: %s\n", str);
    printf("长度: %zu\n", xyd_strlen(str));
    printf("安全长度(限制5): %zu\n", xyd_strlen_safe(str, 5));
    
    return 0;
}

字符串拷贝函数

c 复制代码
#include <stdio.h>

// 字符串拷贝
char* xyd_strcpy(char* dest, const char* src) {
    if(dest == NULL || src == NULL) {
        return NULL;
    }
    
    char* d = dest;
    const char* s = src;
    
    while((*d++ = *s++) != '\0') {
        // 空循环体,所有操作都在条件中完成
    }
    
    return dest;
}

// 带长度限制的字符串拷贝
char* xyd_strncpy(char* dest, const char* src, size_t n) {
    if(dest == NULL || src == NULL || n == 0) {
        return dest;
    }
    
    char* d = dest;
    const char* s = src;
    size_t i;
    
    // 拷贝最多n-1个字符
    for(i = 0; i < n - 1 && *s != '\0'; i++) {
        *d++ = *s++;
    }
    
    // 确保目标字符串以'\0'结尾
    *d = '\0';
    
    return dest;
}

int main() {
    char dest1[20];
    char dest2[20];
    const char* src = "Hello, World!";
    
    xyd_strcpy(dest1, src);
    printf("strcpy: %s\n", dest1);
    
    xyd_strncpy(dest2, src, 5);
    printf("strncpy(5): %s\n", dest2);
    
    return 0;
}

字符串拼接函数

c 复制代码
#include <stdio.h>

// 字符串拼接
char* xyd_strcat(char* dest, const char* src) {
    if(dest == NULL || src == NULL) {
        return dest;
    }
    
    char* d = dest;
    
    // 找到dest的结尾
    while(*d != '\0') {
        d++;
    }
    
    // 追加src
    while((*d++ = *src++) != '\0') {
        // 空循环体
    }
    
    return dest;
}

// 带长度检查的字符串拼接
char* xyd_strncat(char* dest, const char* src, size_t n) {
    if(dest == NULL || src == NULL || n == 0) {
        return dest;
    }
    
    char* d = dest;
    
    // 找到dest的结尾
    while(*d != '\0') {
        d++;
    }
    
    // 追加最多n个字符
    size_t i;
    for(i = 0; i < n && *src != '\0'; i++) {
        *d++ = *src++;
    }
    
    // 添加终止符
    *d = '\0';
    
    return dest;
}

int main() {
    char str1[50] = "Hello, ";
    char str2[50] = "Hello, ";
    
    xyd_strcat(str1, "World!");
    printf("strcat: %s\n", str1);
    
    xyd_strncat(str2, "World!", 3);
    printf("strncat(3): %s\n", str2);
    
    return 0;
}

字符串比较函数

c 复制代码
#include <stdio.h>

// 字符串比较
int xyd_strcmp(const char* str1, const char* str2) {
    while(*str1 && *str2 && *str1 == *str2) {
        str1++;
        str2++;
    }
    
    return *(unsigned char*)str1 - *(unsigned char*)str2;
}

// 不区分大小写的字符串比较
int xyd_stricmp(const char* str1, const char* str2) {
    while(*str1 && *str2) {
        char c1 = *str1;
        char c2 = *str2;
        
        // 转换为小写比较
        if(c1 >= 'A' && c1 <= 'Z') c1 += 32;
        if(c2 >= 'A' && c2 <= 'Z') c2 += 32;
        
        if(c1 != c2) {
            break;
        }
        
        str1++;
        str2++;
    }
    
    char c1 = *str1;
    char c2 = *str2;
    
    if(c1 >= 'A' && c1 <= 'Z') c1 += 32;
    if(c2 >= 'A' && c2 <= 'Z') c2 += 32;
    
    return c1 - c2;
}

// 带长度限制的字符串比较
int xyd_strncmp(const char* str1, const char* str2, size_t n) {
    if(n == 0) {
        return 0;
    }
    
    while(--n && *str1 && *str2 && *str1 == *str2) {
        str1++;
        str2++;
    }
    
    return *(unsigned char*)str1 - *(unsigned char*)str2;
}

int main() {
    const char* str1 = "Hello";
    const char* str2 = "hello";
    const char* str3 = "Hello, World!";
    
    printf("strcmp(\"%s\", \"%s\") = %d\n", str1, str2, xyd_strcmp(str1, str2));
    printf("stricmp(\"%s\", \"%s\") = %d\n", str1, str2, xyd_stricmp(str1, str2));
    printf("strncmp(\"%s\", \"%s\", 3) = %d\n", str1, str3, xyd_strncmp(str1, str3, 3));
    
    return 0;
}

编程建议

  1. 始终初始化指针

    c 复制代码
    int *p = NULL;  // 好的实践
    int *q;          // 坏的实践
  2. 检查指针有效性

    c 复制代码
    if(pointer != NULL) {
        // 使用指针
    }
  3. 避免悬空指针

    c 复制代码
    free(ptr);
    ptr = NULL;  // 释放后立即置空
  4. 使用const修饰符

    c 复制代码
    const char* str = "Hello";  // 不能通过str修改字符串内容
相关推荐
VT.馒头15 小时前
【力扣】2695. 包装数组
前端·javascript·算法·leetcode·职场和发展·typescript
刘琦沛在进步16 小时前
【C / C++】引用和函数重载的介绍
c语言·开发语言·c++
爱敲代码的TOM17 小时前
数据结构总结
数据结构
CoderCodingNo17 小时前
【GESP】C++五级练习题 luogu-P1865 A % B Problem
开发语言·c++·算法
大闲在人17 小时前
7. 供应链与制造过程术语:“周期时间”
算法·供应链管理·智能制造·工业工程
VekiSon17 小时前
Linux内核驱动——杂项设备驱动与内核模块编译
linux·c语言·arm开发·嵌入式硬件
小熳芋17 小时前
443. 压缩字符串-python-双指针
算法
Charlie_lll17 小时前
力扣解题-移动零
后端·算法·leetcode
chaser&upper17 小时前
矩阵革命:在 AtomGit 解码 CANN ops-nn 如何构建 AIGC 的“线性基石”
程序人生·算法