首页 > 其他分享 >c语言笔记(鹏哥)课件+上课板书汇总(深入指针1)

c语言笔记(鹏哥)课件+上课板书汇总(深入指针1)

时间:2024-11-15 20:51:26浏览次数:3  
标签:变量 int 鹏哥 课件 地址 pa 内存 板书 指针

深入指针(1)

⽬录:
一、 内存和地址
二、指针变量和地址
三、 取地址操作符
四、指针变量类型的意义(这一讲到这)
五、const修饰指针
六、 指针运算
七、 野指针
八、 assert断⾔
九、 指针的使⽤和传址调⽤

内存和地址

引例:假设有一个宿舍楼,你在一个房间里,宿舍楼里每一间房间都有一个编号,有人来找你就可以通过房间号快速的找到你

如果把上⾯的例⼦对照到计算机中,⼜是怎么样呢?

计算机内存是宿舍楼,每一个内存单元是房间,每个内存单元的地址就是房间编号,数据被存储在相应地址编号的内存单元(相应房号的房间)里,当cpu中央处理器想找到需要处理数据时就通过地址(房号)来找到内存单元(房间),进而找到里面存放的数据。

那么接下来了解一下计算机相应的硬件内容更深入理解cpu怎么通过地址找到内存数据,然后怎么将处理好的数据传送会原来的地址处的内存单元的。

  • 计算机上CPU(中央处理器)需要处理的数据是在内存中读取的,经过处理后的数据也会放回内存中

  • 电脑上内存是 8GB/16GB/32GB(下面有介绍GB)=等,那这些内存空间如何⾼效的管理呢?

  • 其实是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。

计算机中常⻅的单位:(这里介绍一下计算机中常见内存存储单位bit,byte,KB,MB,GB.TB,PBEB,ZB,YB,BB,NB)

⼀个⽐特位可以存储⼀个2进制的位1或者0

  1. bit - ⽐特位 (最小的内存存储单位)
  2. Byte - 字节
  3. KB
  4. MB
  5. GB
  6. TB
  7. PB
  8. EB
  9. ZB
  10. YB
  11. BB
  12. NB

他们之间的进制转换是这样的

1Byte = 8 bit
1KB = 210Byte
1MB = 210KB
1GB = 210MB
1TB = 210GB
1PB = 210TB
…以后都是210转换机制

  • 计算机cpu和内存相互传输数据示意图:

在这里插入图片描述
如图所示:
内存被分为一个个小的内存单元,每一个内存单元空间都是1Byte(长条),每一个字节里面有8个bit位可以存放一个二进制数(0或1),每一个内存单元也有编号(eg:10001100),这个编号就是在这8个bit位里放上0或1组成的二进制码,有了这个编号cpu快速找到要处理的数据在内存中的位置,处理后的数据有可以快速的返回原来的位置

  • 生活中我们把房号叫做地址,c语言中我们把内存单元编号叫做地址,在c语言中地址也叫指针

那么我们可以这样理解:
内存单元的编号 = 地址 = 指针

  • 实际内存空间的样子

在这里插入图片描述
*
在这里插入图片描述

那么内存被分为一个个内存单元,每个内存单元都有自己的编号(0x代表十六进制数,0x00000001就是十六进制的1),类比宿舍楼分为好多房间,每个寝室都是8人寝室有自己的房间号,cpu可以通过内存单元编号快速找到要处理数据的位置,处理后快速返回原来位置。

  • 深入理解数据在内存中如何由cpu和内存中传输的
    (选看涉及硬件不要求):
    在这里插入图片描述
  • 计算机内是有很多的硬件单元的,⽽硬件单元是要互相协同⼯作的。所谓的协同,⾄少相互之间要能够进⾏数据传递。
  • 硬件与硬件之间互相独立(比如cpu和内存),他们由线连接,很明显数据传输也是通过线,今天我们只关心一组线叫地址总线
  • 在这里控制总线执行两种指令(读操作:把内存中的值读到cpu里面去;写操作:)
  • 数据由内存到cpu:首先控制总线发送控制命令读操作R(read),cpu通过地址总线找到内存中地址,将地址处的数据由数据总线传到cpu
  • 数据由cpu到内存:首先控制总线发送控制命令写操作(write),cpu通过地址总线找到内存中地址,将cpu处的数据由数据总线传到内存相应位置

其实内存单元的地址编号并不是你给他编的,而是通过计算机硬件设计完成的,就类似于一个会弹钢琴的人,钢琴上没标“剁、来、咪、发、唆、拉、西”这样的信息,但演奏者照样能够准确找到每⼀个琴弦的每⼀个位置,这是为何?因为制造商已经在乐器硬件层⾯上设计好了,并且所有的演奏者都知道。本质是⼀种约定出来的共识!

对于硬件的编址,我们可以简单的理解为:32位机器有32根地址总线,每根线只有两态,表⽰0,1【电脉冲有⽆】,那么⼀根线,就能表⽰2种含义,2根线就能表⽰4种含义,依次类推。32根地址线,就能表⽰232种含义,每⼀种含义都代表⼀个地址。地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传⼊CPU内寄存器

指针变量和地址

  1. 在C语⾔中创建变量其实就是向内存申请空间,⽐如:
#include <stdio.h>
int main()
{
 int a = 10;
 return 0;
}

在这里插入图片描述

  • 内存窗口:按f10调试之后点上方调试,窗口,内存
  • 在内存窗口输入&a ,enter回车之后就会出现a的地址处

图中采用的是4列地址,在内存窗口的右上角可以调整,int是4字节,内存中地址是16进制,1列两个16进制数,1个16进制数是4个2进制数(bit位),一列(2个16进制数)正好是1字节,4列正好是4字节。根据不用情况可以自己调整列数,下图就显示一列

如图0x0075FD20、0x0075FD21、0x0075FD22、0x0075FD23就是4个1字节内存单元的地址也是存放整形变量a的地址。可以看到0a就是把10的值存到了0x0075D20,也就是变量a的首地址处。

取地址操作符

  • 得到a的地址只需要使用&a即可
#include <stdio.h>
int main()
{
 int a = 10;
 &a;//取出a的地址
 printf("%p\n", &a);
 return 0;
}

在这里插入图片描述
由运行结果我们可以看出取地址操作符取出的是变量存储在内存中的首地址。

在这里插入图片描述

  • &a取出的是a所占4个字节中地址较⼩的字节的地址。
  • 那么a在内存中的存储就是如上图所示,a所占地址就是由下至上0x006FFA2C,0x006FFA2D,0x006FFA2E,0x006FFA2F这四个字节
  • 所以只要知道第一个字节地址就可以顺藤摸瓜找到其他的地址。

指针变量类型的意义

1.指针变量

当我们用取地址操作符取出首地址后,可以把它存进指针变量中以便后续操作。指针变量也是变量,是用来存储地址的,被存储在指针变量的内容都被认为是地址。

#include <stdio.h>
int main()
{
 int a = 10;
 int * pa = &a;//取出a的地址并存储到指针变量pa中
 printf("%p",pa);
 return 0;

运行结果是:
在这里插入图片描述

2. 如何拆解指针类型

int* pa = &a; 如我们所见,pa的类型是int* 指针类型

如何看变量的类型:
就是把创建变量的语句,去掉变量名后等号左边剩余部分就是变量的类型。
例如:
int a = 10;
int arr [5] ;
char ch;
int* pa;
a的类型是int,arr类型是int[5], ch的类型是char, pa的类型是int*

*代表pa是指针变量,前面的int代表的是指向整形类型的变量a(存放整形变量a的首地址)

int a = 10;
int* pa = &a;

在这里插入图片描述

  • 请看以下代码:可以看出来地址中存储的是w的ASCII码值也就是77,后面那个w是编译器试图解析内存中放入的值,意义不大
  • *代表cp是指针变量,char代表的是cp指向的是字符型变量c(存放字符类型c的首地址)
char c = 'w';
char* cp = &c;

在这里插入图片描述

  • 类似于这样的过程:

创建变量a,int类型申请空间4个字节内存单元存放20这个整形变量,取地址取出的是4个里面最小的地址0099FC74。将这个地址存放在pa指针变量中那么通过地址就能找到变量a

(地址是0099FC74,0099FC75,0099FC76,0099FC77)

在这里插入图片描述

指针变量的大小

指针变量(地址)的大小取决于编译器的平台环境,x86环境下是32bit所以是4字节,x64环境是64bit也就是8字节,因为地址以十六进制存储,所以x86环境下是0x(是16进制数的意思)+8位二进制数字,在64位环境中就是0x+16位二进制数字,那么我们实践一下

#include <stdio.h>
int main()
{
	int a = 10;
	int* pa = &a;
	printf("%d", sizeof(pa));
	return 0;
}
  • 在debug x86环境下运行结果是4
  • 在debug x64环境下运行结果是8

那么指针类型不一样是否会导致指针变量(地址)的长度的变化呢,我们来实践一下

#include <stdio.h>
int main()
{
	printf("%d\n", sizeof(int*));
	printf("%d\n", sizeof(short*));
	printf("%d\n", sizeof(long*));
	printf("%d\n", sizeof(double*));
	printf("%d\n", sizeof(float*));
	return 0;
}
  • 在debug x86环境下运行结果是44444
  • 在debug x64环境下运行结果是88888

由此可见,指针变量的长度与指针类型无关,与编译器所处的平台有关,只要平台一样,指针变量长度都一样。

那么为什么要区分指针类型呢?这里留一个疑问往下看为你解答

解引用操作符

当我们使用取地址操作符拿到了变量的地址,将它存放进指针后,当需要使用这个变量的时候,应使用解引用操作符通过指针类型的解释,结合指针中存储的地址找到解引用后的变量,这个变量就是我们找到的指针指向的对象,也是我们进行操作的变量。

int main()
{
	int a = 10;
	int* pa = &a;
	*pa = 0;
	printf("%d", a);
	return 0;
}
  • 运行结果是0
  • 这里的*pa就是解引用pa,意思是通过pa中存放地址来找到其指向的int类型的变量a,然后修改a的值为0。

其实这⾥是把a的修改交给了pa来操作,这样对a的修改,就多了⼀种的途径,也就是借助了指针来特殊使用变量(可以想象成借刀杀人,上级想干掉一个人但是不方便亲自出手,所以找了他的属下来进行秘密多种形式的暗杀)这样写代码(暗杀方式)就会更加灵活,后期慢慢就能理解了。你可能会想为什么不直接修改a的值而是要通过pa来修改a的值呢,留一个疑问后续解答

上面我提到了两个问题:

  1. 指针类型有什么用?
  2. 为什么要利用指针变量来修改变量的值,而不是直接修改?

通过接下来的指针的解引用和指针加减整数就可以解释了。

指针的解引用

观察一下两段代码在内存中值的变化:

#include<stdio.h>
int main()
{
	int n = 0x11223344;
	int* pa = &n;
	*pa = 0;
	return 0;
}
  • 观察运行时n的地址中存储的n值发生的变化

    在这里插入图片描述
#include<stdio.h>
int main()
{
	int n = 0x11223344;
	char* cp = &n;//&n取出的是首地址在x86环境下都是4字节,0x+八位数,可以使用char*来接收
	*cp = 0;
	return 0;
}
  • 观察运行时n的地址中存储的n值发生的变化
    在这里插入图片描述
    在这里插入图片描述

经过调试我们可以看到内存中,代码1会将n的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。

结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。

可以这样去理解:
*pa = 0;就是通过pa地址找到对应的上一句代码int *pa = &n;中的int类型的n,在计算机潜意识里就继续把n当成整形类型来操作所以直接就操作了4个字节。
*cp = 0;就是通过cp地址找到对应上一句代码char *cp = &n;中的char类型的cp,在计算机潜意识就把后来的n变量当成了字符类型来操作,所以只操作了1个字节。

指针±整数

先看⼀段代码,调试观察地址的变化

int main()
{
	int a = 20;
	int* pa = &a;
	char* cp = &a;
	printf("&a = %p\n", &a);
	printf("pa = %p\n", pa);
	printf("cp = %p\n", cp);
	printf("&a+1 = %p\n", &a+1);
	printf("pa+1 = %p\n", pa+1);
	printf("cp+1 = %p\n", cp+1);
	return 0;

}

在这里插入图片描述
可以发现&a到&a+1变化4个字节,pa到pa+1变化4个字节,cp到cp+1变化了1个字节(计算机潜意识把cp当做是1字节的char类型去访问并操作)

  • 深入理解一下在内存中的样子:指针指向的位置变化
    在这里插入图片描述

结论:指针类型决定了指针向前或向后走多大一步(距离)

* 拓展:xxx* pa--------xxx类型指针pa,加n其实加的就是加nsizeof(xxx),走nsizeof(xxx)步

在这里插入图片描述应用拓展:
在这里插入图片描述

有一个整形数组,要使用指针来访问它的所有元素,先使用int* pa找到首地址,在解引用访问第一个元素(4个字节),在对地址加1,指针指向第二个元素的位置,解引用访问这个元素,依次类推,加一一次(地址变化)就解引用一次(访问元素),加一一次就解引用一次,直到把所用的元素都遍历完。

void* 指针

void*被称为无具体类型指针,或泛型指针。这种指针可以接收任意类型的地址,但是也有局限性不能够直接进行指针的加减,解引用操作。

  • 如图所示这个代码会出现不兼容问题
int main()
{
	int n = 0x11223344;
	int* pa = &n;//&n就是int*,传给int*类型的pa没毛病
	char* cp = &n;//cp是字符类型的指针,将int*类型传给cp有些不兼容问题
	return 0;
}

在这里插入图片描述

  • 我们可以采用一种无具体类型指针void*来接收任意类型的地址,因为其他端不知道会传给我什么类型的变量,所以拿一种最随和的void *来存放地址
  • 但是void类型指针不能进行加减,解引用操作,因为不知道类型所以没办法确定步长和访问多大空间。
    在这里插入图片描述
    在这里插入图片描述
    那么 void 类型的指针到底有什么用呢?
  • ⼀般 void* 类型的指针是使⽤在函数参数的部分,⽤来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果。使得⼀个函数来处理多种类型的数据,在《深⼊理解指针(4)》中我们会讲解。

标签:变量,int,鹏哥,课件,地址,pa,内存,板书,指针
From: https://blog.csdn.net/2401_87219716/article/details/143745326

相关文章