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

C语言————深入理解指针1(通俗易懂)

        C语言越学到后面,越会感到恐慌,听到指针、结构体等等这些,想必很多人不自觉的就会感觉很难,就想打退堂鼓了。哈哈哈哈,被小博猜到了吧!!悄悄告诉你们,小博刚开始学习的时候也是。但是时过一年,小博又是一条好汉,又重新回来拾起了这个曾经把我吓得连连后退的语言了。接下来,跟着小博来学习指针吧!!

一、内存和地址

        指针是什么,这里小博先给你们打个预防针,指针==内存单元的编码==地址,首先你应该接受这个概念。好了,那么内存单元又是什么?内存单元的地址有什么用?

       我们给计算机输入数据,计算机都是要通过内存存到一个空间里面的,等我们需要的时候,计算机再从内存中取出数据供我们使用。数据很多时,计算机如何才能准确的找到我们所需的数据呢?可以把内存看成一栋大楼,里面有很多个房间,每个房间都有门牌号,计算机每次把读到的数据存入对应的房间,但需要的时候,计算机会自动的通过门牌号找到该房间所存的数据。每个房间就是内存单元门牌号就是内存单元的编码,即地址

        听“都瑞咪发嗦啦”,钢琴被谁按动了。你有没有想过为什么每个键能发出不一样的声音,并且弹奏者能够准确定位到每个琴弦的位置?是的,这些都是被制造商提前在硬件层面上设计好的。然而计算机也一样,计算机也是由各种各样的硬件组成,各个硬件之间协调工作,使得他们之间可以相互进行数据传递。学过计算机组成原理的同学都知道,计算机的CPU和内存之间存在大量的数据传输,并且通过“线”进行传输。这里小博带你更深的了解计算机里的内存。       

        计算机中内存可划分为一个一个内存单元,每个内存单元的大小为1个字节,一个字节里有8个比特位其中每个内存单元都有各自的编号。

补充

  • 1 byte(字节)= 8 bit
  • 1 KB = 1024 byte
  • 1 MB = 1024 KB
  • 1 GB = 1024 MB
  • 1 TB = 1024 GB
  • 1 PB = 1024 TB

        计算机中CPU和内存之间通过线进行数据交互,主要有如上三类。当然这里我们主要讲地址线,即CPU是通过地址线找到内存单元的编码的。进行数据传输的时候,肯定不会用一根地址线了,那样多慢了,计算机在被制作的时候,是会被设定好有多少根地址线进行传输的,这些地址线统称为地址总线。如:32位的机器有32根地址线,1根地址线只有0或1两种状态,表示电脉冲有无。依次类推,2根地址线有4种状态,3根有8种.......32根地址线就有2^32种状态每种状态都代表一个地址,就可以通过该地址找到对应的数据。

二、指针变量和地址

1、指针变量

我们的重头戏来了!!首先要弄清楚两概念:

指针:指的是地址

指针变量:是指存放地址的变量

我们通过上面已经知道了什么是地址,这只是一个概念,我们要怎么用计算机语言将地址表示出来呢,当然要通过一个变量表示了,即指针变量

int a = 10;
printf("%p\n", &a);		//&a-----&取地址操作符,单目操作符
int* p = &a;		//p是一个变量(指针变量),是一块空间,表示取出a的地址放到p中去

这里要注意,指针变量p的类型int **相当于指针变量p的标识符int 是指p中存放的地址对应的变量a是int类型,记住一句话,指针变量中的内容就是地址。

2、取地址操作符(&)

上面我们学的 int* p = &a; 是对指针变量的定义,其中&取地址操作符,用来遍历指针变量,

&a表示对a进行取地址

如:

int a = 10;
printf("%p\n", &a);		//&a-----&取地址操作符,单目操作符
int* p = &a;		//p是一个变量(指针变量),是一块空间,表示取出a的地址放到p中去

其中a为整形变量,会向内存空间中申请4个字节,且每个字节都有对应的地址。

然而,printf("%p\n", &a); 打印出的则为:0x006FFD70,即取出的为4个字节中地址较小的字节的地址。

                   

总结&a取出的是a所占4个字节中地址较小的字节的地址

    而我们口头语中说的p是一个指针,其实指的是指针变量,只是我们通常习惯这么说而已。

    3、解引用操作符(*)

    上面我们讲到将变量的地址存起来,当然我们也得找到他。那么问题来了,内存空间那么大,内存单元那么多,指针变量如何才能精准的定位到目标地址?这时候就要用到解引用操作符(*)了,如:

    int a = 10;
    printf("%p\n", &a);		//&a-----&取地址操作符,单目操作符
    int* p = &a;		//p是一个变量(指针变量),是一块空间,表示取出a的地址放到p中去
    *p;     //*---解引用操作符(间接访问操作符)

    也就是说在指针变量的旁边放上一个*,就可以准确的找到a的地址

    如上,我们通过 *p 找到a的地址之后,将其中的变量a改为0,则打印出的结果就为0。

    再次梳理一下上面讲的1、2、3:   

    &a :将整形变量a的地址取出。

    p = &a;   将a的地址取出存放到变量p中,故p称为指针变量

    int* p = &a

    • int* 指针变量p的类型
    • 相当于指针变量p的标识符,
    • int 是指p中存放的地址对应的变量a是int类型

    *p:找到整形变量a的地址。

    其中,和 * 相当于一对的,一个取出,用于存放;一个找到,用于解引用。

    三、指针变量的类型

    1、指针变量的大小

    64位的机器中有64根地址线,这64根地址线中,每一个地址都是由64个二进制位组成的二进制序列,存储起来就需要8个字节的空间,指针变量的大小就是8个字节和类型无关

    如,在x64(64位)环境下运行的结果

    如,在x86 (32位) 环境下运行的结果

    总结:

    • 32位平台下地址是32个bit位,指针变量大小是4个字节。
    • 64位平台下地址是64个bit位,指针变量大小是8个字节。
    • 指针变量的大小和类型无关,在相同的平台下,指针大小都是相同的。

    2、指针变量类型的意义

            有没有发现,指针变量无论是哪种类型,在同一平台下,其大小都是一样的,那么定义这些类型又有什么意义呢?

            来看一下这里两个指针变量的区别,同样是对整形变量a进行取地址,int * 和char * 申请到的内存空间的大小一样大,即指针变量的大小相同。但当对指针变量进行解引用操作的时候,int * 改变了内存空间中的4个字节,而char *则改变了内存空间中的1个字节

    所以说,指针类型决定了指针进行解引用操作的时候访问了几个字节,也就是决定了指针的权限

    3、指针 + - 整数

            当我们定义两个指针变量类型,int * 和char *,分别对其进行+1操作,可发现int *类型的指针变量跳过了4个字节char *类型的指针变量只跳过了1个字节

    因此,指针类型决定了指针进行+1,-1的时候,一次跳过的距离。

    • int * + 1 ——>跳过4个字节
    • char * + 1——>跳过1个字节

    4、void *指针

            根据上文,我们了解到指针变量有很多类型,如:char *,int *,short *等等类型。然而,这里有一种特殊的类型void *类型,可以理解为无具体类型的指针(泛型指针),可以接受任意类型的地址,但无法进行指针的+-整数和解引用运算一般用于,当你取出了一个变量的地址,但不确定放在什么类型的指针变量中,就可以用void *这种类型来替代这个不确定的指针变量的类型。

    这里我们不着重讲了。

    四、const修饰指针

    看到const,你是否似曾相识呢?是的,我只知道他是一个关键字,平常我也没用过啊!!😥😥

    哈哈哈哈,没关系的,小博带你再温习一遍。

    1、const修饰变量

    const常属性(不能被修改的属性),通俗点说,就是一个变量被const修饰后,该变量的值就不能被修改了。如:

    #include <stdio.h>
    int main()
    {int a = 10;const int b = 10;a = 20; //a是可以修改的b = 20; //b是不能被修改的printf("%d %d\n", a, b);return 0;
    }

            如上,被const修饰后的b是不能被修改的,在语法上加了限制,只要我们在代码中对b进行修改,编译器就会报错,致使无法直接修改b

            const int b=10;  这里我们要知道,const修饰了变量b,使b具有了常属性,不能被修改了,但b本质上还是变量,称常变量在VS中,c++语言编译模式下,常变量就是变量,即使b被const修饰,b的值依旧可以被修改)。怎么用呢?当你有一个变量,不想让别人修改的时候,可以直接用const修饰。

            然而,天无绝人之路,你说改不了,我就偏要改!!哈哈哈哈,固执的大学生是这样的。还别说,真有办法,虽然被const修饰后的变量不能被直接修改,但是可以通过找到改变量的地址,用解引用操作,将其中的数值修改如下:

    2、const修饰指针变量

    和普通变量相比,const在修饰指针变量时稍有不同,首先我们需要先理解这张图:

     

            大家一定要把上面的图中各变量的关系搞清楚,首先是有一个变量a中存放的值为10指针变量p中存放的是变量a的地址,然而指针变量p也有自己的地址

    理解了上面的关系,我们再来看const在修饰指针变量时有什么不同,

    首先,位置不同:

    1、const 放在 *右边

    int* const p = &a;
    

    和修饰变量很相似,被const修饰过的指针变量依旧无法直接被修改。

    依旧相似,如果想改变被const修饰过的指针变量,同样可以用解引用操作

    如此以来,当const放在*右边修饰指针变量时,其用法和修饰变量的相似。

    然而我们要知道他的意义,const放在*右边修饰指针变量时,const限制的是指针变量p本身,指针变量不能被修改了,但是可以通过指针变量,修改指针变量指向的内容

    2、const 放在 *左边

    int const * p = &a;
    

            有了上面的基础,该代码就更好理解了,我们直接来总结一下,const在修饰指针变量,放在*的左边,限制的是指针变量的内容,不能通过指针来修改指向的内容,但是可以修改指针变量本身的值(修改指针变量的指向)

    3、* 前后都放const

    如果是int const * const p这种形式,以上两种情况都会受到限制。

    总结来说,const只限制其后面的的内容,共有三种类型:

    int * const p  ——>  p = &n ; //err 

    int const * p  ——>  * p = 100 ;  //err

    int const * const p ——>

    • *p = 100 ; //err
    • p = &n ; //err 

    五、指针运算

    1、指针+、- 整数

    其实在上面的讲解中,我们已将了解过了指针的+、- 运算。如:

    int a=10;
    int * p = &a;
    printf("%d\n", p+1);
    • int * p ——> p + 1 跳过4个字节
    • char * p ——> p + 1 跳过1个字节
    • type * p ——> p + 1 跳过 1*sizeof( type )个字节
    •                         p + n 跳过 n*sizeof( type )个字节

    2、应用举例

    例:给定一个数组,将其打印出来

    方法一:

    按照正常的思路,我们会想到用for循环的方式打印:

    #include <stdio.h>
    int main()
    {int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr[i]);}return 0;
    }

    方法二:

    当然我们也可用指针的方法:

    #include <stdio.h>
    int main()
    {int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int i = 0;int* p = &arr[0];int sz = sizeof(arr) / sizeof(arr[0]);	//计算数组中元素的个数for (i = 0; i < sz; i++){printf("%d ", *(p+i));    //解引用操作}return 0;
    }

    方法三:

    对于方法二稍做修改:

    #include <stdio.h>
    int main()
    {int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int i = 0;int* p = &arr[0];int sz = sizeof(arr) / sizeof(arr[0]);	//计算数组中元素的个数for (i = 0; i < sz; i++){printf("%d ", *p);    //解引用操作p++;}return 0;
    }

    其实,当我们熟悉了数组存放的底层逻辑之后,并不难写出如上代码。即数组在内存中是连续存放的。

    3、指针-指针

    指针1 + 整数 == 指针2

    指针2 - 指针1 == 整数

    即:指针 - 指针的绝对值 ——> 得到的两个指针元素之间的个数 

    指针- 指针计算的前提条件是:两个指针指向的必须是同一块空间

    printf("%d ",&arr[9]-&arr[0]);

    4、指针的关系运算

    指针和指针比较大小

    举个例子来说,依旧用上面的例题:给定一个数组,将其打印出来

    方法四:

    #include <stdio.h>
    int main()
    {int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);int* p = arr;  //&arr[0]while (p < arr + sz){printf("%d ", *p);p++;}return 0;
    }

    这又是一种新的方法,利用了指针的运算关系,打印数组。

    这些方法都比较新颖,还需我们多多积累,反复复盘,深入理解指针和内存的底层逻辑

            好了,关于指针的基础知识,小博先讲这么多,指针涉及到的知识点还有很多,小博还会陆续更新,因为小博也是新手,所以哪里有不对的地方还请多多指教。如果你也是小白,那么跟着小博一起来学习吧!!给自己几天时间,一定可以学会的,加油!!

    这里小博送给大家自己喜欢的一句话:“言语压君子,衣冠镇小人”。

    http://www.xdnf.cn/news/19403.html

    相关文章:

  • Linux-搭建NFS服务器
  • 【PyTorch】基于YOLO的多目标检测(一)
  • 【CNB.COOL】智能花卉分类系统 – 部署指北
  • 由题构造 嵌入汇编(汇编)
  • python调用豆包大模型给人脸生成卡通图像
  • 八大排序--快速排序
  • 福彩双色球第2025100期数据统计
  • hardhat 3 测试框架选择
  • linux系统学习(14.日志管理)
  • 华秋DFM检查PCB设计缺陷、一键导出Gerber、BOM、坐标文件
  • 第八章 光照
  • Qt QNetworkAccessManager 简述及例程
  • C++11——万能模板及完美转发
  • GMTapSDK 扩展使用文档
  • 【开题答辩全过程】以 基于springboot的垃圾分类管理系统为例,包含答辩的问题和答案
  • LSTM原理理解
  • 8.29学习总结
  • 大语言模型(LLM)简介与应用分享
  • Linux-数据库
  • 旅游景点库系统的设计与实现(代码+数据库+LW)
  • 力扣hot100:轮转数组(常规思路与三步反转讲解)(189)
  • mmaction安装的详细说明帖
  • 王立群《读史记-刘邦》读书笔记
  • 嵌入式C学习笔记之编码规范
  • 数学分析原理答案——第七章 习题12
  • AI大模型实战解析-RAG知识库+LangChain项目实战
  • Linux系统的进程管理
  • Unity3D Gizmos 调试可视化
  • Qt中UDP回显服务器和客户端
  • 第二十七天-ADC模数转换实验