当前位置: 首页 > article >正文

第11天理解指针

目录

数组指针

指针数组

字符指针数组

二维字符数组

const 修饰变量为常量

指针常量

常量指针

typedef 重命名数据类型

关于typedef的一般理解

具体示例分析

复杂示例分析

总结回顾--指针的奥秘:深入理解内存地址与数据存储

一、指针的本质

二、地址类型转换的意义


数组指针

整型指针        指针指向整型空间

int *p;

数组指针        指针指向数组空间

int (*p)[3]; //本质上是指针


下面是几个例子需要理解 

int a[5]; // a==&a[0] 数组名a是首元素a[0]的地址

int *p=a;// p=a

int *p=&a[0];//int *p = a;int *p = &a[0];这两种写法是等价的,都是让指针 p 指向数组 a 的首元素。此时,a[0]p[0]是等价的,都表示访问数组的第一个元素。

*(a+1)== *(p+1)//*(a + 1)*(p + 1)也是等价的,都表示访问数组的第二个元素。因为 a 在表达式中会被转换为指针,a + 1指向数组的第二个元素,解引用后得到第二个元素的值;同理,p + 1也指向数组的第二个元素,解引用后得到相同的值。

p=a+1; // p=&a[1]此时 p 指向数组 a 的第二个元素


e.g.

int a[5];//定义了一个指向包含 5 个整数的数组

int (*p)[5]=&a; // p=&a,即p指向整个数组

a[0]==(*p)[0]==**p

a[0](*p)[0]**p是等价的。

  1. (*p)表示解引用指针 p,得到一个包含 5 个整数的数组,(*p)[0]表示这个数组的第一个元素
  2.  **p也是先解引用 p得到数组,再解引用这个数组的首元素指针得到第一个元素。

a[4]     ==    (*p)[4]    ==    *((*p)+4)    ==   *((int *)(p+1)-1)    ==   *(*(p+1)-1)   

a[4](*p)[4]*((*p)+4)*((int *)(p+1)-1)*(*(p+1)-1)也是等价的。

  1. (*p)[4]表示解引用 p 指向的数组的第五个元素
  2. *((*p)+4)先解引用 p得到数组,再对这个数组的首元素指针加上 4,然后解引用得到第五个元素
  3. *((int *)(p+1)-1)先将 p + 1转换为指向整数的指针,然后减去 1,再解引用得到数组的最后一个元素
  4. *(*(p+1)-1)先解引用 p + 1得到一个新的数组(这里实际上是越界访问,但从逻辑上理解),再对这个新数组的首元素指针减 1,解引用得到原数组的最后一个元素。

e.g.

int a[2][3];

int *p=&a[0][0]; //将指针 p 指向二维数组 a 的第一个元素a[0],即*a 。

a[1][2]     ==    a[0][5]     ==    *(p+5)

因为二维数组在内存中是按行优先存储的,a[1][2]在内存中的位置相对于 a[0][0]正好偏移了 5 个整数的位置。

int (*p)[3]=&a[0]; // 让 p 指向二维数组 a 的第一行a[0]

a[1][2]     ==    *(*(p+1)+2)     ==    (*(p+1))[2]      ==    *(p[1]+2)     ==    p[1][2]      

   *(p + 1)表示指向二维数组的第二行,*(p + 1)+2表示第二行的第三个元素的指针,解引用得到该元素的值;

(*(p + 1))[2]先解引用 p + 1得到第二行数组,再访问第三列的元素;

*(p[1]+2)p[1][2]也是类似的逻辑。

int (*p)[2][3]=&a;//将 &a赋值给 p,即 p 指向整个二维数组 a

a[1][2]    ==     (*p)[1][2]

(*p)表示解引用指针 p,得到二维数组 a(*p)[1][2]表示这个二维数组的第二行第三列的元素。

学习理解这部分知识,可以:

  1. 画图辅助理解
    • 对于数组和指针的关系,可以画出数组在内存中的布局图。例如,对于一维数组int a[5],可以将其想象成内存中的连续 5 个存储单元,每个单元存储一个int类型的值。当定义int *p = a;时,在图上可以将指针p指向数组a的第一个元素所在的存储单元。这样在考虑p + 1时,就很容易理解它指向了数组中的下一个元素,因为在内存中,p + 1的地址值是p的地址值加上sizeof(int)(假设int类型占 4 个字节,那么p+1的地址比p的地址大 4)。
    • 对于二维数组,比如int a[2][3],可以将其看作是一个有 2 行 3 列的表格。在内存中,它仍然是连续存储的,按照行优先的顺序。如果int *p=&a[0][0];,那么p就像一个游标,从表格的左上角(第一个元素)开始,p + 1就指向表格中的下一个元素。当定义int (*q)[3]=&a[0];时,q可以看作是指向每行(包含 3 个元素的一维数组)的指针,q + 1就指向二维数组的下一行。
  2. 通过代码示例逐步调试
    • 编写简单的测试程序,在程序中输出数组元素的地址和指针的值。直观地看到指针和数组元素地址的变化情况。对于二维数组也可以采用类似的方法,逐步观察指针的移动和元素的访问过程。

从基本概念出发,逐步深入理解

  • 首先要牢记数组名在很多情况下会转换为指向数组首元素的指针这个基本概念。对于指针的算术运算,要理解+1操作对于不同类型指针(如指向int的指针、指向数组的指针等)的实际意义,它是根据指针所指向的类型的大小来移动指针的。然后再深入理解多级指针(如指向数组的指针的指针)的概念,从简单的一维数组指针开始,逐步过渡到二维、三维数组等更复杂的指针应用场景。

指针数组

整型数组 ----数组元素是整型     

指针数组 ----数组元素是指针

int *a[5]; //本质上是数组

a[0] a[1] a[2] 都是指针

int *a[2][3];

a[0] a[1] 都是一维指针数组

a[0][0] a[0][1] a[1][1] 都是指针

字符指针数组

char *a[5]={“123”, “abcdef”, “45789”, “zxcvb”, “000123456”};

char b= ‘A’;

a[0]=&b;

a[1]= “123456”; 字符指针指向首字符地址,不能修改字符串的数据

sizeof(a) == 40

定义了一个字符指针数组 a,它包含五个指针,分别指向五个不同的字符串常量。

接着定义了一个字符变量 b,初始化为 'A'。然后让 a[0]指向 b 的地址,此时 a[0]不再指向字符串,而是指向单个字符 b。这就像把原来指向一个字符串物品堆的标签,改指向了单独的一个字符物品。

之后又让 a[1]指向新的字符串常量 “123456”。由于字符串常量在内存中通常是不可修改的,所以虽然指针可以指向不同的字符串,但不能改变字符串常量本身的内容。就像标签可以指向不同的物品堆,但不能改变物品堆里的物品。

这里的两个赋值语句有一定区别:

  1. 赋值内容不同
    • a[0]=&b;是让a[0]指向一个字符变量b(存储单个字符)。
    • a[1]= “123456”;是让a[1]指向一个字符串常量(包含多个字符和结束符)。
  2. 可修改性不同
    • 通过a[0]可以修改b的值。
    • 不能修改a[1]指向的字符串常量内容,否则可能出错。

最后,sizeof(a)等于 40,因为在这个环境下指针可能占用 8 个字节,而数组 a 有五个指针元素,所以总共占用 40 个字节。就像一个盒子里装了五个特定大小的东西,通过测量盒子大小可以知道这些东西总共占用的空间。

 

二维字符数组

定义一个二维字符数组

char a[3][80]={ “123”, “abcdef”, “45789”};

其中a[0] 最多存储 80 个字符

a[1][0]= ‘h’; //数组是变量的集合,可以更改数据

总长度sizeof(a) == 240

const 修饰变量为常量

const int a=5; // a 是只读变量,const修饰谁,谁就是常量

a=6; // 不合法,因为a被声明为const,不能被赋值修改。

int *p=&a;//用一个指向int类型的指针p指向这个const修饰的变量a的地址。

*p = 6; // 合法,通过指针去修改这个地址上的值

指针常量

数字常量         1         值不能改变

指针常量         int * const p;         p 被 const 修饰,所以 p 的值不能改变

  • int a=5,b=10;
  • int * const p=&a;//定义了一个常量指针p。这里的 “常量指针” 是指指针本身的值(即它所指向的地址)不能被改变,但它所指向的内容可以被改变。
  • *p=6; // 合法,虽然p不能指向其他地址,但它所指向的内容是可以修改的。这里通过*p修改了p所指向的变量(即a)的值,将其从5改为6
  • p=&b; // 不合法,初始化时让p指向了变量a的地址,之后就不能再让p指向其他变量的地址了

常量指针

整型指针         int *p; 指针指向整型

常量指针         char const *p; 指针指向常量         const 修饰*p,*p 不能改变

                        const char *p;

  • char a=57,b=32;
  • char const *p=&a;

//定义了一个指向常量字符的指针p,并初始化为指向a。这意味着不能通过这个指针来修改它所指向的内容。

  • *p=97; // 不合法

//不能通过这个指针来修改p所指向的内容。

  • p=&b; // 合法

//不能通过p修改它所指向的内容,但是可以改变指针p本身的值,让它指向其他的地址

  • *p=97; // 不合法

//虽然,现在p指向b,但仍不能通过这个指针来修改p所指向的内容。

  • printf(“%d\n”,*p); // 合法

//只是读取p所指向的内容并输出其对应的整数值,没有尝试修改它所指向的内容

const int *const p; //p 和*p 都是只读

const int *const p,这里有两个const关键字。第一个const修饰int,表示指针所指向的内容是常量。第二个const修饰指针p本身,表示指针p是一个常量,它存储的地址值不能被改变。

  1. 指针指向的内容只读(*p
    • 另一个const表示指针指向的变量的值不能被修改。即使p指向了某个变量,也不能通过p来改变这个变量的值。 
  2. 指针本身只读(p
    • const修饰指针p,就像把指针 “钉” 在了一个地址上,不能再让它指向别的地方。比如,不能重新给p赋值一个新的变量地址。

typedef 重命名数据类型

关于typedef的一般理解

typedef A B; // A 代表数据类型 B 代表新名称

A m;

B n; //m 和 n 数据类型相同

具体示例分析

typedef int * a;//定义了a作为int*类型的别名

a n; // 等同于int* n;,即定义了一个指向整数的指针n

int a,*b; // int a; int *b;声明了一个整数变量a和一个指向整数的指针变量b

int *a,b; // int *a; int b;声明了一个指向整数的指针变量a和一个整数变量b

typedef int * INT;//定义了一个别名INT代表int*类型。

INT a,b; // INT a; INT b; === int *a; int* b;//即声明了两个指向整数的指针变量ab

复杂示例分析

typedef int*(*F[2])[3];

F m; // 等同于int*(*m[2])[3];,即定义了一个变量m

sizeof(F) == 16

这定义了一个较为复杂的类型别名。F是一个数组类型,这个数组有 2 个元素,每个元素是一个指针,这个指针指向一个包含 3 个整数指针的数组。

从逐步分析这个类型定义:

分析F的长度

  • 先看FF被定义为一个数组类型,名为F的数组有 2 个元素,即F[2]
  • 接着看每个元素的类型,每个元素是一个指针,所以是(*F[2]),指针指向的对象是一个包含 3 个元素的数组。
  • 最后看这个包含 3 个元素的数组的元素类型,这个数组的元素是指向整数的指针,即int*,所以完整的类型就是int*(*F[2])[3],表示F是一个包含 2 个元素的数组,每个元素是一个指针,指向一个包含 3 个指向整数指针的数组。
  • 首先看 int*(*)[3]这个类型:

    • 它表示一个指针,这个指针指向一个包含 3 个指向整数指针(int*)的数组。
    • 在假设指针大小为 8 字节的系统中,这个指针本身占用 8 字节的空间。
  • 接着看 F的类型:

    • F被定义为 int*(*F[2])[3],这意味着 F是一个包含 2 个元素的数组。
    • 每个元素的类型是前面提到的指向包含 3 个指向整数指针的数组的指针。
    • 所以 sizeof(F)就等于 2 倍的这种指针的大小,即 2 * sizeof(int*(*)[3])

所以 sizeof(F) = 2 * sizeof(int*(*)[3]) = 2 * 8 = 16字节。

总结回顾--指针的奥秘:深入理解内存地址与数据存储

在 C 和 C++ 等编程语言中,指针是一个强大而又复杂的概念。它就像是一把钥匙,能够打开内存世界的大门,让我们直接访问和操作计算机内存中的数据。

一、指针的本质

指针的核心作用是保存地址。在计算机内存中,每个数据都存储在特定的空间位置,而这个位置可以用地址来表示。不同类型的指针保存不同类型数据的地址,这意味着指针的类型与它所指向的地址的类型紧密相关。例如,一个指向整数的指针只能保存整数变量的地址,而不能保存其他类型变量的地址。

在定义指针时,我们先确定指针指向的空间变量的类型,然后在变量名前面加上*来表示这是一个指针。例如,int *ptr表示ptr是一个指向整数的指针。当我们使用*解引用指针时,得到的是指针所指向的地址对应的空间变量

此外,指针的大小通常是固定的,并且与计算机的位数有关。在 32 位系统中,指针通常占用 4 个字节;而在 64 位系统中,指针通常占用 8 个字节。

二、地址类型转换的意义

地址类型转换主要是在转换地址所代表的空间大小。这是因为不同类型的数据在内存中占用的空间大小可能不同。例如,一个整数可能占用 4 个字节,而一个浮点数可能占用 8 个字节。当我们进行地址类型转换时,实际上是在告诉编译器如何解释特定地址上存储的数据。

让我们来看一个具体的例子,在地址 0x8000010002 的位置存储一个浮点型数据

float *p=(float *)0x8000010002;

*p=3.14;

(long)p+2 == 0x8000010004

首先,将地址 0x8000010002 强制转换为 float * 类型指针并赋值给 p,使 p 指向该地址的浮点型数据。接着,用 *p 将 3.14 存储到这个地址。然后看 (long)p + 2,由于指针大小与计算机位数有关,假设系统中指针占 8 字节。将 p 强制转换为 long 类型,是把地址值转为长整型数。而 (long)p + 2 是将地址值加 2 字节,因 long 占 8 字节、float 占 4 字节,加 2 字节相当于指向下一个浮点数地址,此时 (long)p + 2 的值为 0x8000010004。 


http://www.kler.cn/news/366729.html

相关文章:

  • Spring Boot驱动的厨艺社交平台设计与实现
  • MATLAB中 exist函数用法
  • 手把手教你安装最强文生图工具ComfyUI
  • k8s 综合项目笔记
  • Python 从入门到实战40(数据分析概述)
  • 正则表达式基本语法(快速认知)
  • Go小技巧易错点100例(十八)
  • [申请] 准备 2024.10.20
  • QT模块--Core
  • Oracle数据库语法的使用
  • envoyFilter导致的webSockets协议无法正常工作
  • Docker Redis集群3主3从模式
  • 网关三问:为什么微服务需要网关?什么是微服务网关?网关怎么选型?
  • 4款免费音频剪辑软件带你开启声音创作之旅
  • 基于SpringBoot的时装购物系统【源码】+【论文】
  • BRIA-RMBG-1.4容器构建指南
  • 2024年最新苹果iOS证书申请创建App详细图文流程
  • Jmeter用户定义变量
  • 2024系统架构师---真题考试知识点
  • 【计算机网络 - 基础问题】每日 3 题(五十八)
  • 数据结构(8.4_1)——简单选择排序
  • YOLOv11算法解析
  • WPF+MVVM案例实战(三)- 动态数字卡片效果实现
  • 已解决 django.db.utils.OperationalError: (1051, “Unknown table
  • Vue3 学习笔记(七)Vue3 语法-计算属性 computed详解
  • 基于SpringBoot的宠物爱好者交流系统的设计与实现(源码+定制+开发)