MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C语言指针间接访问与变量交互

2022-03-047.2k 阅读

指针基础概念回顾

在深入探讨C语言指针的间接访问与变量交互之前,我们先来回顾一下指针的基本概念。指针是一种特殊的变量,它存储的是另一个变量的内存地址。在C语言中,每个变量在内存中都有一个特定的存储位置,这个位置可以用一个唯一的数字来表示,这就是内存地址。

定义指针变量的一般形式为:类型说明符 *变量名;,例如:

int *ptr;

这里ptr是一个指向int类型变量的指针。需要注意的是,*在这里用于声明指针变量,表明ptr是一个指针,而不是普通的变量。

指针的初始化

指针在使用之前必须初始化,否则它的值是未定义的,这可能会导致程序出现难以调试的错误。初始化指针的常见方式是让它指向一个已定义的变量。例如:

#include <stdio.h>

int main() {
    int num = 10;
    int *ptr = &num;
    printf("The address of num is: %p\n", (void *)ptr);
    printf("The value of num is: %d\n", num);
    return 0;
}

在上述代码中,&num获取num变量的地址,并将其赋值给指针ptr。然后通过printf函数输出num的地址和值。

间接访问运算符*

间接访问运算符*(也称为解引用运算符)用于访问指针所指向的变量的值。例如:

#include <stdio.h>

int main() {
    int num = 10;
    int *ptr = &num;
    printf("The value of num using pointer: %d\n", *ptr);
    return 0;
}

在这个例子中,*ptr表示访问ptr所指向的变量,也就是num,因此输出结果为10

指针间接访问的本质

从内存层面来看,指针间接访问是通过指针存储的内存地址来找到对应的变量存储位置,进而访问其值。当我们定义一个变量时,系统会为其分配一定的内存空间。例如,对于一个int类型的变量,在32位系统上通常会分配4个字节的空间。指针变量本身也会占用一定的内存空间(在32位系统上通常为4个字节,64位系统上为8个字节),用于存储其他变量的内存地址。

假设我们有以下代码:

#include <stdio.h>

int main() {
    int num1 = 10;
    int num2 = 20;
    int *ptr = &num1;
    printf("The value of num1 using pointer: %d\n", *ptr);
    ptr = &num2;
    printf("The value of num2 using pointer: %d\n", *ptr);
    return 0;
}

在内存中,num1num2会被分配不同的内存地址。指针ptr首先存储num1的地址,通过*ptr可以访问num1的值。当ptr被重新赋值为num2的地址后,*ptr就可以访问num2的值了。

通过指针间接修改变量值

指针不仅可以用于访问变量的值,还可以用于修改变量的值。例如:

#include <stdio.h>

int main() {
    int num = 10;
    int *ptr = &num;
    *ptr = 20;
    printf("The new value of num is: %d\n", num);
    return 0;
}

在上述代码中,通过*ptr = 20;语句,我们实际上修改了num的值。因为ptr指向num*ptr就代表num,所以对*ptr的赋值操作等同于对num的赋值操作。

指针与数组的关系

在C语言中,指针和数组有着密切的关系。数组名在大多数情况下会被隐式转换为指向数组首元素的指针。例如:

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *ptr = arr;
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, *(ptr + i));
    }
    return 0;
}

在这段代码中,arr作为数组名,被隐式转换为指向arr[0]的指针,并赋值给ptr。通过*(ptr + i)可以访问数组的各个元素,这与arr[i]的效果是一样的。实际上,arr[i]在编译时会被解释为*(arr + i)

指针与函数参数

指针作为函数参数可以实现函数对调用者变量的修改。在C语言中,函数参数传递默认是值传递,这意味着函数内部对参数的修改不会影响到调用者的变量。但是通过传递指针,函数可以间接访问并修改调用者的变量。例如:

#include <stdio.h>

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int num1 = 10;
    int num2 = 20;
    printf("Before swap: num1 = %d, num2 = %d\n", num1, num2);
    swap(&num1, &num2);
    printf("After swap: num1 = %d, num2 = %d\n", num1, num2);
    return 0;
}

swap函数中,通过传递num1num2的地址,函数内部可以间接访问并交换这两个变量的值。

多级指针

除了一级指针,C语言还支持多级指针。多级指针是指指针指向的是另一个指针。例如,二级指针的定义形式为:类型说明符 **变量名;。下面是一个二级指针的示例:

#include <stdio.h>

int main() {
    int num = 10;
    int *ptr1 = &num;
    int **ptr2 = &ptr1;
    printf("The value of num using double pointer: %d\n", **ptr2);
    return 0;
}

在这个例子中,ptr1是一个指向num的一级指针,ptr2是一个指向ptr1的二级指针。通过**ptr2可以间接访问num的值。

指针数组

指针数组是一个数组,其元素都是指针。例如:

#include <stdio.h>

int main() {
    int num1 = 10;
    int num2 = 20;
    int num3 = 30;
    int *ptrArray[3] = {&num1, &num2, &num3};
    for (int i = 0; i < 3; i++) {
        printf("Value at ptrArray[%d] is: %d\n", i, *(ptrArray[i]));
    }
    return 0;
}

在上述代码中,ptrArray是一个指针数组,它的每个元素都是一个指向int类型变量的指针。

数组指针

数组指针是一个指针,它指向一个数组。例如:

#include <stdio.h>

int main() {
    int arr[3] = {1, 2, 3};
    int (*ptr)[3] = &arr;
    for (int i = 0; i < 3; i++) {
        printf("arr[%d] = %d\n", i, (*ptr)[i]);
    }
    return 0;
}

在这个例子中,ptr是一个指向包含3个int类型元素的数组的指针。(*ptr)[i]用于访问数组中的元素。

指针与结构体

指针在处理结构体时也非常有用。结构体指针可以指向一个结构体变量,通过结构体指针可以访问结构体的成员。例如:

#include <stdio.h>

struct Student {
    char name[50];
    int age;
};

int main() {
    struct Student stu = {"Alice", 20};
    struct Student *ptr = &stu;
    printf("Name: %s, Age: %d\n", ptr->name, ptr->age);
    return 0;
}

在上述代码中,ptr是一个指向stu结构体变量的指针。通过ptr->nameptr->age可以访问结构体的成员。->运算符是结构体指针访问成员的专用运算符,它等价于(*ptr).name(*ptr).age

指针与动态内存分配

动态内存分配是C语言中一项重要的技术,它允许程序在运行时分配和释放内存。malloccallocfree等函数用于动态内存分配和释放。指针在动态内存分配中起着关键作用,因为它们用于指向动态分配的内存块。例如:

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

int main() {
    int *ptr = (int *)malloc(5 * sizeof(int));
    if (ptr == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }
    for (int i = 0; i < 5; i++) {
        ptr[i] = i * 10;
    }
    for (int i = 0; i < 5; i++) {
        printf("ptr[%d] = %d\n", i, ptr[i]);
    }
    free(ptr);
    return 0;
}

在这个例子中,malloc函数分配了一块能容纳5个int类型变量的内存,并返回一个指向该内存块起始地址的指针ptr。我们可以像使用数组一样使用ptr来访问和修改这块内存中的数据。最后,通过free函数释放动态分配的内存,以避免内存泄漏。

指针与字符串

在C语言中,字符串通常以字符数组的形式存储,并且可以使用指针来操作字符串。例如:

#include <stdio.h>

int main() {
    char str[] = "Hello, World!";
    char *ptr = str;
    while (*ptr != '\0') {
        printf("%c", *ptr);
        ptr++;
    }
    return 0;
}

在上述代码中,ptr指向字符串str的首字符。通过*ptr逐个访问字符串中的字符,直到遇到字符串结束符'\0'

指针的运算

指针可以进行一些运算,包括加法、减法和比较运算。

指针加法

指针加法是指针运算中最常见的一种。当指针加上一个整数n时,实际上是将指针移动n个其所指向类型的大小的字节数。例如:

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *ptr = arr;
    ptr = ptr + 2;
    printf("The value at new position is: %d\n", *ptr);
    return 0;
}

在这个例子中,ptr初始指向arr[0]ptr + 2使ptr指向arr[2],因此输出结果为3

指针减法

指针减法通常用于计算两个指针之间的距离,前提是这两个指针指向同一个数组中的元素。例如:

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *ptr1 = &arr[0];
    int *ptr2 = &arr[3];
    int distance = ptr2 - ptr1;
    printf("The distance between ptr1 and ptr2 is: %d\n", distance);
    return 0;
}

在上述代码中,ptr2 - ptr1计算出ptr2ptr1之间相隔的元素个数,输出结果为3

指针比较运算

指针可以进行比较运算,如==!=<>等。比较的是指针所存储的内存地址。例如:

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *ptr1 = &arr[0];
    int *ptr2 = &arr[2];
    if (ptr1 < ptr2) {
        printf("ptr1 points to a lower address than ptr2\n");
    }
    return 0;
}

在这个例子中,通过比较ptr1ptr2的地址,输出相应的信息。

指针的常见错误

在使用指针时,容易出现一些常见的错误,需要特别注意。

未初始化指针

未初始化的指针包含随机值,使用这样的指针可能会导致程序崩溃或产生未定义行为。例如:

#include <stdio.h>

int main() {
    int *ptr;
    printf("The value at ptr is: %d\n", *ptr); // 未初始化指针,未定义行为
    return 0;
}

在上述代码中,ptr未初始化就试图通过*ptr访问其指向的值,这是错误的。

悬空指针

悬空指针是指指向已释放内存的指针。例如:

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

int main() {
    int *ptr = (int *)malloc(sizeof(int));
    *ptr = 10;
    free(ptr);
    printf("The value at ptr is: %d\n", *ptr); // 悬空指针,未定义行为
    return 0;
}

在这个例子中,ptr指向的内存被释放后,ptr就成为了悬空指针,再次访问*ptr会导致未定义行为。

野指针

野指针是指指向一个不确定内存位置的指针,通常是由于指针初始化错误或指针运算超出范围导致的。例如:

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int *ptr = arr;
    ptr = ptr + 10; // 指针运算超出范围,成为野指针
    printf("The value at ptr is: %d\n", *ptr); // 未定义行为
    return 0;
}

在上述代码中,ptr超出了数组arr的范围,成为野指针,访问*ptr会导致未定义行为。

通过对C语言指针间接访问与变量交互的深入探讨,我们了解了指针在C语言中的重要性和广泛应用。从基本概念到各种复杂的应用场景,指针为C语言程序员提供了强大而灵活的编程能力。但同时,指针的使用也需要谨慎,避免出现各种常见错误,以确保程序的正确性和稳定性。在实际编程中,需要不断练习和积累经验,才能熟练掌握指针的使用技巧。