如何理解C语言指针变量及其应用

 

在讲指针变量前先明确&,%p等符号的含义,&变量名:获取该变量的地址(指针),%p:输出地址.

指针变量是存储内存地址的变量,它的值是另一个变量的地址,而不是具体的数据值。

1. 指针变量的定义

在C/C++中,定义指针变量的基本格式为:

数据类型 *指针变量名;

例如:

int *p;  // 定义一个指向 int 类型的指针变量 p

这里 p 不是整数变量,而是一个存储整数变量地址的指针。

2. 取地址符(&)与解引用符(*)
    •    &(取地址符):获取变量的地址。
    •    *(解引用符):访问指针指向的内存地址中的值。

示例:

#include <stdio.h>

int main() {
    int a = 10;
    int *p = &a;  // p 存储 a 的地址

    printf("a 的值: %d\n", a);
    printf("a 的地址: %p\n", &a);
    printf("p 的值(即 a 的地址): %p\n", p);
    printf("p 指向的值(即 *p): %d\n", *p); // 访问 a 的值

    return 0;
}

输出示例(地址可能不同):

a 的值: 10
a 的地址: 0x7ffeefbff5a4
p 的值(即 a 的地址): 0x7ffeefbff5a4
p 指向的值(即 *p): 10

3. 指针的作用

(1) 通过指针修改变量值

*p = 20;  // 修改 p 指向的变量 a

示例:

#include <stdio.h>

int main() {
    int a = 10;
    int *p = &a;
    *p = 20;  // 通过指针修改 a 的值
    printf("a 的新值: %d\n", a);  // 输出 20
    return 0;
}

int*p=&a;表示p储存a的地址,对应前面的定义.p获得a的地址,*p解引用,访问a的值(因为p指向的是a的地址).

(2) 指针与数组

指针可以用于操作数组

核心思想是:指针可以存储数组的地址,并通过指针访问数组的元素。由于数组在内存中是连续存储的,所以指针可以通过地址偏移来访问数组中的不同元素

明确一点:数组名字其实就是数组的起始地址

int arr[5] = {10, 20, 30, 40, 50};
printf("%p\n", arr);     // 输出数组的首地址
printf("%p\n", &arr[0]); // 同样是数组的首地址

上面的arr和&arr[0]是相同的,说明数组名本质上是一个指向数组首元素的指针常量。

 

 用指针访问数组

数组元素存储在内存中的方式如下(假设arr的地址是0x1000,每个int占4个字节):

变量      地址          值
arr[0]    0x1000    10
arr[1]    0x1004    20
arr[2]    0x1008    30
arr[3]    0x100C    40
arr[4]    0x1010    50

我们可以使用指针来访问数组:

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // p 指向数组的第一个元素(arr[0])

printf("%d\n", *p);     // 输出 10,等价于 arr[0]
printf("%d\n", *(p+1)); // 输出 20,等价于 arr[1]
printf("%d\n", *(p+2)); // 输出 30,等价于 arr[2]

解释:
    •    p 存储的是 arr[0] 的地址,即 0x1000。
    •    p + 1 让指针移动到 0x1004,即 arr[1] 的位置,解引用 *(p + 1) 取出 20。
    •    p + 2 让指针移动到 0x1008,即 arr[2],解引用 *(p + 2) 取出 30。

等价关系:
    •    arr[i] 等价于 *(arr + i).
    •    p[i] 也等价于 *(p + i).


示例:

printf("%d\n", arr[3]);  // 输出 40
printf("%d\n", *(arr+3)); // 同样输出 40
printf("%d\n", p[3]);    // 依然输出 40
printf("%d\n", *(p+3));  // 依然输出 40

指针遍历数组

既然 p + i 可以访问 arr[i],那么我们可以用指针遍历数组:

#include <stdio.h>

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int *p = arr; // 指针指向数组首元素

    for(int i = 0; i < 5; i++) {
        printf("%d ", *(p + i)); // 依次访问数组元素
    }
    return 0;
}

//输出: 10 20 30 40 50

此外,我们还可以用指针的自增运算遍历数组:

int *p = arr;
while(p < arr + 5) { // 当 p 还没有超出数组范围
    printf("%d ", *p);
    p++; // 让指针指向下一个元素
}

 •  arr + 5 代表超出数组最后一个元素的下一个地址。
 •    p++ 让指针不断向后移动,每次指向数组的下一个元素 

int arr[] = {1, 2, 3};
int *p = arr;  // 指针指向数组首元素
printf("%d\n", *(p+1));  // 输出 2

指针修改数组元素

既然指针能访问数组元素,那么也可以修改数组中的值:

int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; 

*p = 100;       // 修改 arr[0] 为 100
*(p+2) = 300;   // 修改 arr[2] 为 300
p[4] = 500;     // 修改 arr[4] 为 500

printf("%d %d %d\n", arr[0], arr[2], arr[4]); // 输出 100 300 500

等价关系:
    •    arr[0] = 100; 等价于 *p = 100;
    •    arr[2] = 300; 等价于 *(p+2) = 300;
    •    arr[4] = 500; 等价于 p[4] = 500;

指针与二维数组

对于二维数组 int arr[3][4](三行四列),它的存储结构如下:

arr[0] -> [ 10  20  30  40 ]
arr[1] -> [ 50  60  70  80 ]
arr[2] -> [ 90 100 110 120 ]

arr 实际上是一个 指向第一行的指针arr[i] 是指向第 i 行的 指针,所以:
    •    arr[i][j] 等价于 *(*(arr + i) + j),下面有解释

示例:

#include <stdio.h>

int main() {
    int arr[3][4] = {
        {10, 20, 30, 40},
        {50, 60, 70, 80},
        {90, 100, 110, 120}
    };

  int *p = &arr[0][0]; // 指向二维数组的首地址
    
    printf("%d\n", *(p + 5)); // 访问 arr[1][1],即 60
    printf("%d\n", *(*(arr + 1) + 1)); // 访问 arr[1][1],即 60

    return 0;
}

看完这段代码可能会有以下疑问:

1.    为什么 *(p + 5) 会访问 arr[1][1]?
2.    为什么 *(*(arr + 1) + 1) 会访问 arr[1][1]?

详细解释:

二维数组的存储方式

在C语言中,二维数组在内存中是按行存储的(行优先存储),比如:

int arr[3][4] = {
    {10, 20, 30, 40},
    {50, 60, 70, 80},
    {90, 100, 110, 120}
};

它在内存中的排列方式如下(假设 arr[0][0] 的地址是 0x1000,每个 int 占 4 字节):

元素      地址(假设)    值
arr[0][0]    0x1000    10
arr[0][1]    0x1004    20
arr[0][2]    0x1008    30
arr[0][3]    0x100C    40
arr[1][0]    0x1010    50
arr[1][1]    0x1014    60
arr[1][2]    0x1018    70
arr[1][3]    0x101C    80
arr[2][0]    0x1020    90
arr[2][1]    0x1024    100
arr[2][2]    0x1028    110
arr[2][3]    0x102C    120

观察:
    •    arr[0][0] 地址是 0x1000,arr[0][1] 地址是 0x1004(因为 int 占 4 字节)。
    •    arr[1][0] 地址是 0x1010,arr[1][1] 地址是 0x1014,以此类推。

 

2. int *p = &arr[0][0]; 解释

&arr[0][0] 代表 二维数组首元素的地址,所以 p 直接指向了 arr[0][0],即 p = 0x1000。

换句话说,p 现在可以当作一个一维数组的指针,它指向的是整个二维数组在内存中的首地址。

3. *(p + 5) 解析

printf("%d\n", *(p + 5)); 

等价于:
    •    p 是 &arr[0][0],即 p 指向 arr[0][0](地址 0x1000)。
    •    p + 5 让指针向后移动 5 个 int 类型的位置:
    •    p + 1 指向 arr[0][1](0x1004)
    •    p + 2 指向 arr[0][2](0x1008)
    •    p + 3 指向 arr[0][3](0x100C)
    •    p + 4 指向 arr[1][0](0x1010)
    •    p + 5 指向 arr[1][1](0x1014)
    •    *(p + 5) 取出 arr[1][1] 的值 60。

4. *(*(arr + 1) + 1) 解析

printf("%d\n", *(*(arr + 1) + 1));

这个语法更接近二维数组的标准访问方式,我们一步步解析它:

(1) arr 是什么?
    •    arr 代表 二维数组的首地址,它的类型是 int [4](即 指向含 4 个 int 的数组)。
    •    arr + 1 代表 跳过一整行,即 arr[1](arr[1] 其实是 &arr[1][0])。
    •    *(arr + 1) 取出 arr[1],也就是 arr[1] 这一行的首地址(&arr[1][0])。

(2) *(arr + 1) + 1 解析
    •    *(arr + 1) + 1 代表 在 arr[1] 这行上再向右移动 1 列,即 arr[1][1]。

(3) *(*(arr + 1) + 1) 取值
    •    *(*(arr + 1) + 1) 取出 arr[1][1] 的值,即 60。

小结

                       
*(p + 5)         

 访问的元素  :  arr[1][1]   

 计算过程  :

  p 指向 arr[0][0],加 5 后指向 arr[1][1]


*(*(arr + 1) + 1)  

访问的元素: arr[1][1]         

计算过程:

  arr + 1 代表 arr[1],*(arr + 1) + 1 代表 arr[1][1]


    1.    二维数组在内存中是按行存储的,即 arr[0] 在前,arr[1] 紧随其后。
    2.    int *p = &arr[0][0]; 让 p 变成了指向所有元素的指针,p + 5 访问 arr[1][1]。
    3.    arr 本身是一个指向数组的指针,arr + 1 让指针移动到下一行,*(arr + 1) + 1 访问 arr[1][1]。
    4.    这两种方式本质上都访问了相同的内存位置,所以输出的值相同。
 

大小结
    1.    数组名本质是指向首元素的指针,即 arr 等价于 &arr[0]。
    2.    指针可以像数组一样使用,如 p[i] == *(p + i)。
    3.    指针可以修改数组元素,如 *(p+2) = 300 等价于 arr[2] = 300。
    4.    指针遍历数组可以用 p++ 来不断访问下一个元素。
    5.    二维数组的指针操作:arr[i][j] == *(*(arr + i) + j)。

(3) 指针与动态内存分配

int *p = (int*)malloc(sizeof(int));  // 在堆区分配内存
*p = 100;
free(p);  // 释放内存

4. 指针变量的类型

指针类型必须匹配数据类型:

int *p1;  // 指向 int 类型
double *p2;  // 指向 double 类型
char *p3;  // 指向 char 类型

不同类型的指针所占的字节数可能相同,但解引用时必须匹配数据类型。

总结
    1.    指针存储变量的地址,而不是变量的值。
    2.    通过 & 获取变量地址,通过 * 访问指针指向的值。
    3.    指针可以用来修改变量值、操作数组、动态分配内存等。
    4.    指针类型必须与数据类型匹配。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值