欢迎光临
我们一直在努力

C语言从入门到进阶——第11讲:深入理解指针(1)

文章目录

  • 1. 内存和地址
    • 1.1 内存
    • 1.2 究竟该如何理解编址
  • 2. 指针变量和地址
    • 2.1 取地址操作符&
    • 2.2 指针变量和解引用操作符(*)
      • 2.2.1 指针变量
      • 2.2.2 如何拆解指针类型
      • 2.2.3 解引用操作符(*)
    • 2.3 指针变量的大小
  • 3. 指针变量类型的意义
    • 3.1 指针的解引用
    • 3.2 指针+-整数
    • 3.3 void* 指针
  • 4. 指针运算
    • 4.1 指针+- 整数
    • 4.2 指针 – 指针
    • 4.3 指针的关系运算

1. 内存和地址

1.1 内存

计算机的CPU处理数据时,数据需从内存读取,处理后需放回内存,为高效管理内存,会将其划分为一个个1字节大小的内存单元。

计算机存储单位的换算关系为: 1Byte = 8bit 1KB = 1024Byte 1MB = 1024KB 1GB = 1024MB 1TB = 1024GB 1PB = 1024TB

每个内存单元都有唯一的编号,这个编号在计算机中称为地址,C语言中将其命名为指针,即:内存单元的编号 = 地址 = 指针。CPU可通过该编号快速定位并访问对应的内存空间。

1.2 究竟该如何理解编址

计算机中各硬件单元需通过线路连接实现数据交互,CPU与内存之间的地址总线是实现内存编址的关键。

编址并非记录每个字节的地址,而是通过硬件设计实现的约定:

  • 32位机器有32根地址总线,每根总线只有0和1两种状态,32根总线可表示(2^{32})种不同含义,每种含义对应一个内存地址;
  • 64位机器有64根地址总线,可表示(2^{64})个内存地址。

CPU将地址信息通过地址总线下达给内存,内存根据地址找到对应数据后,再通过数据总线将数据传入CPU寄存器。 在这里插入图片描述

2. 指针变量和地址

2.1 取地址操作符&

在C语言中创建变量,本质是向内存申请对应大小的存储空间。例如定义int a = 10;,会向内存申请4个字节的空间用于存放整数10,如图这4个字节各自拥有唯一的地址:0x006FFD70、0x006FFD71、0x006FFD72、0x006FFD73

在这里插入图片描述

取地址操作符&:用于获取变量在内存中的地址,取出的是变量所占多个字节中地址较小的字节的地址。通过printf打印地址时,需使用格式控制符%p。 示例代码:

#include <stdio.h>
int main()
{
int a = 10;
&a;//取出a的地址
printf("%p\\n", &a);//打印a的地址
return 0;
}

输出结果:

0x006FFD70

2.2 指针变量和解引用操作符(*)

2.2.1 指针变量

通过取地址操作符获取的地址是一个数值,需要专门的变量来存储,用于存放地址的变量即为指针变量。 示例代码:

#include <stdio.h>
int main()
{
int a = 10;
int * pa = &a;//将a的地址存储到指针变量pa中
return 0;
}

指针变量的本质也是变量,其核心作用是存放内存地址,存放在其中的值会被编译器默认理解为地址。

2.2.2 如何拆解指针类型

指针变量的类型为 基类型 * ,例如int * pa中pa的类型是int *:

  • *表示该变量是指针变量;
  • int表示该指针变量指向的是整型类型的变量。 在这里插入图片描述

不同类型的变量,其地址需用对应基类型的指针变量存储,例如char类型变量的地址,需用char *类型的指针变量存储。

2.2.3 解引用操作符(*)

解引用操作符(*) 用于通过指针变量中存放的地址,找到该地址指向的内存空间,进而对该空间的数据进行读写操作。 示例代码:

#include <stdio.h>
int main()
{
int a = 100;
int* pa = &a;
*pa = 0;//通过pa的地址找到a,将a的值修改为0
return 0;
}

使用指针修改变量值,为变量操作提供了新的途径,让代码编写更灵活。

2.3 指针变量的大小

指针变量的大小由平台的地址总线位数决定,与指针的基类型无关:

  • 32位平台:地址由32个bit位组成,需4个字节存储,因此指针变量的大小为4个字节;
  • 64位平台:地址由64个bit位组成,需8个字节存储,因此指针变量的大小为8个字节。

示例代码及验证结果:

#include <stdio.h>
int main()
{
printf("%zd\\n", sizeof(char *));
printf("%zd\\n", sizeof(short *));
printf("%zd\\n", sizeof(int *));
printf("%zd\\n", sizeof(double *));
return 0;
}

  • X86(32位)环境输出:

4
4
4
4

  • X64(64位)环境输出:

8
8
8
8

结论:同一平台下,所有类型的指针变量大小均相同,与指向的变量类型无关。

3. 指针变量类型的意义

指针变量的大小与类型无关,但不同类型的指针具有特殊意义,主要体现在解引用、指针±整数的运算规则上,同时存在一种特殊的void*指针类型。

3.1 指针的解引用

指针的类型决定了解引用时的操作权限,即一次能访问的字节数:

  • char*类型指针:解引用仅能访问1个字节;
  • int*类型指针:解引用能访问4个字节。

示例对比:

//代码1:int*指针解引用
#include <stdio.h>
int main()
{
int n = 0x11223344;
int *pi = &n;
*pi = 0;//将n的4个字节全部改为0
return 0;
}

//代码2:char*指针解引用
#include <stdio.h>
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
*pc = 0;//仅将n的第一个字节改为0
return 0;
}

3.2 指针±整数

指针的类型决定了指针向前/向后走一步的字节距离,即指针±整数时,跳过的字节数等于其基类型的大小:

  • char*类型指针:+1跳过1个字节;
  • int*类型指针:+1跳过4个字节。

本质上,指针±整数,跳过的是一个指针指向的元素的大小。 示例代码及运行结果:

#include <stdio.h>
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\\n", &n); //输出:00AFF974
printf("%p\\n", pc); //输出:00AFF974
printf("%p\\n", pc+1);//输出:00AFF975
printf("%p\\n", pi); //输出:00AFF974
printf("%p\\n", pi+1);//输出:00AFF978
return 0;
}

3.3 void* 指针

void*类型是一种无具体类型的指针(泛型指针),具有以下特性:

  • 兼容性:可以接收任意类型变量的地址,不会出现类型不兼容的编译器警告;
  • 局限性:不能直接进行解引用操作,也不能直接进行指针±整数的运算;
  • 用途:主要用于函数参数部分,接收不同类型数据的地址,实现泛型编程的效果,让一个函数可处理多种类型的数据。
  • 示例代码(void*指针接收地址):

    #include <stdio.h>
    int main()
    {
    int a = 10;
    int* pa = &a;
    char* pc = &a;
    void* pv = &a;
    return 0;
    }

    在这里插入图片描述 可见将int类型的变量的地址赋值给char*类型的指针变量,编译器给出了如图警告;而使用void*类型就不会有这样的问题。

    示例代码(void*指针直接运算):

    #include <stdio.h>
    int main()
    {
    int a = 10;
    void* pa = &a;
    *pa = 10;
    pa+1;
    return 0;
    }

    在这里插入图片描述 可见void*类型的指针可以接收不同类型的地址,但是无法直接进行指针运算,会出现如图报错。

    4. 指针运算

    指针的基本运算有三种:指针±整数、指针-指针、指针的关系运算,所有运算均基于内存地址的线性特性,尤其适用于数组的操作(数组在内存中连续存放)。

    4.1 指针± 整数

    指针±整数的运算可通过数组首元素的地址±整数n,得到数组第n个元素的地址。 示例代码:

    #include <stdio.h>
    int main()
    {
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int *p = &arr[0];//获取数组首元素地址
    int i = 0;
    int sz = sizeof(arr)/sizeof(arr[0]);//计算数组元素个数
    for(i = 0; i < sz; i++)
    {
    printf("%d ", *(p + i));//通过指针+-整数访问数组元素
    }
    return 0;
    }

    4.2 指针 – 指针

    指针-指针的结果为两个指针之间的元素个数,要求两个指针必须指向同一块连续的内存空间(如同一个数组、同一个字符串)。 该运算可用于实现字符串长度统计等功能,示例代码(模拟实现strlen函数):

    #include <stdio.h>
    int my_strlen(char *s)
    {
    char *p = s;//记录字符串首地址
    while(*p != '\\0' )//遍历到字符串结束符
    p++;
    return ps;//指针-指针,得到字符个数
    }
    int main()
    {
    printf("%d\\n", my_strlen("abc"));//输出:3
    return 0;
    }

    4.3 指针的关系运算

    指针的关系运算即对两个指针变量存储的地址进行大小比较,可用于遍历连续的内存空间(如数组),通过判断指针是否到达空间末尾,控制循环结束。 示例代码:

    #include <stdio.h>
    int main()
    {
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int *p = &arr[0];//数组首元素地址
    int sz = sizeof(arr)/sizeof(arr[0]);//数组元素个数
    while(p < arr + sz) //指针关系运算:判断是否到达数组末尾
    {
    printf("%d ", *p);//访问当前指针指向的元素
    p++;//指针后移,指向下一个元素
    }
    return 0;
    }

    赞(0)
    未经允许不得转载:171主机测评 » C语言从入门到进阶——第11讲:深入理解指针(1)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址