C语言基础--初识指针

news2024/11/15 12:27:31

文章目录

  • 一、初识指针
  • 二、指针和指针类型
    • 指针类型的意义
      • 1)指针的解引用
        • ①问题抛出
        • ②探讨
        • ③总结
      • 2)指针+整数
      • 3)总结
      • 4)举例
  • 三、野指针
    • (1)概念
      • 1) 指针未初始化
      • 2)指针越界访问
      • 3)指针指向的空间释放
    • (2)规避野指针
      • 1)指针初始化
      • 2)小心指针越界
      • 3)指针指向的空间释放即使置NULL
      • 4)指针使用之前检查有效性
  • 四、指针运算
    • (1)指针加减整数
      • 1)指针+整数
      • 2)指针-整数
      • 3)案例
    • (2)指针减指针
      • 1)案例一
      • 2)错误案例
      • 3)案例二
    • (3)指针的关系运算
  • 五、指针和数组
    • (1)回顾数组
    • (2)指针和数组
  • 六、二级指针
    • (1)介绍
    • (2)理解
  • 七、指针数组

一、初识指针

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ETtIUUVi-1672481790098)(D:\Typora图片\clip_image004-16723745275261.jpg)]

指针是什么?

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,该地址指向该变量单元。因此,将地址形象化的称为:指针。意思是,通过它能找到以它为地址的内存单元。

❓ 问:

1、 编号如何产生?

(1) 电脑上有地址线,地址线一旦通电,就能产生电信号,把电信号转为数字信号,数字信号产生的二进制序列(1或0),把数字信号作为二进制的编号,可作为内存单元的一个编号,即内存单元的地址。十六进制显得方便。

(2) 对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号正电/负电(1或0)。

(3) 那么32根地址线产生的地址就会是:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Y0860Ew-1672481790100)(D:\Typora图片\image-20221230145811541.png)]

这里就有二的三十二次方个地址。

每个地址标识一个字节,那我们就可以给(2^32byte==2^32/1024kb==2^32/1024/1024mb==2^32/1024/1024/1024gb==4gb)4G的空闲进行编址。

同样的方法,64位机器,如果给64根地址线,能编址多大空间,自己计算。

这里我们就明白:

1) 在32位机器上,地址是32个0或1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。

2) 在64位机器上,如果有64个地址线,那一个指针变量的大小是8字节,才能存放一个地址。

2、 一个内存单元是多大?

一个字节比较合适。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yo95Coit-1672481790101)(D:\Typora图片\clip_image010-16723745275323.jpg)]

P里面存的是地址,地址也叫指针。p变量是用来存放地址的,所以叫它指针变量,类型是int*

存放地址的,就称为指针变量,而这个指针变量存放的又是地址。

指针就是地址,地址就是指针。

🙅 指针其实就是个变量,变量里面存放的是内存单元的地址,也就可以说:指针就是地址。(存放在指针中的值都被当成地址处理)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZPjp0td4-1672481790101)(D:\Typora图片\clip_image014-16723745275475.jpg)]

🍰 总结:

  • 指针是用来存放地址的,地址是唯一标示一块地址空间的。

  • 指针的大小在32位平台是4个字节,在64位平台是8个字节。

二、指针和指针类型

指针类型的意义

我们来看一下,各种指针类型的大小:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GZRAiFdE-1672481790102)(D:\Typora图片\clip_image018-16723745275474.jpg)]

当前默认是32位平台,所以结果都是4个字节。

1)指针的解引用

①问题抛出

❓ 问:它们指针大小都是4个字节,那为什么要区分?

那指针类型究竟有什么样的意义?

先定义一个整型变量a。

int a = 0x11223344;

将十六进制数0x11223344存入a中。

可以放进去吗?

可以的哦,两个十六进制位占一个字节(1个十六进制是4个二进制位,2个十六进制位就是8个二进制位,即1个字节)。

这样的话,11占一个字节,22占一个字节,33占一个字节,44占一个字节。总共4个字节。

a是整型变量,4个字节的空间。所以是可以存放的。


再将a的地址取出来(&a)。

放在变量pa里面,pa的是指针类型的变量,即int*类型。

如下:

int* pa=&a;

既然在探讨指针类型,那么我们将a的地址放入char*类型的变量pc里面行吗?

如下:

char* pc=&a;

pa和pc都能存放好a的地址吗?

来输出看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iYmWNnr9-1672481790103)(D:\Typora图片\clip_image022-16723745275487.jpg)]

由上图可见,两次输出地址一样

可见,无论是什么类型的,都能存好a的地址。

为啥都能存放?因为pa和pc都是指针变量,都是4字节(32平台)/8字节(64平台)大小。

类型不匹配,只会报警告,类型不兼容。但还是可以存放进去的。

既然无论什么类型都能存放,那指针类型还有什么意义了呢?

一起往下继续探讨吧!

②探讨

接下来,我们分别看一下不同类型存储a地址的内存。

<1>当我们用Int类型来存储时

现在我们将a变量的地址存入一个整型指针pa中。

int a = 0x11223344;
int* pa = &a;
*pa = 0;

F10进行调试,当左边的小黄色箭头指在第二行的时候,打开内存(调试–>窗口–>内存):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jNVCMRz7-1672481790103)(D:\Typora图片\clip_image024-16723745275486.jpg)]

输入&a

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jmYLO1H4-1672481790103)(D:\Typora图片\clip_image027.jpg)]

左边是地址,右边是内存(如下):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p1daDdJk-1672481790104)(D:\Typora图片\clip_image029.jpg)]

可以看见,内存中存放的是:44 33 22 11。

至于为什么是倒着存放的,这里不做探讨。

再次按F10进行调试,左侧黄色箭头指向pa(第三行)。这一行的代码意思是将pa指针指向的a的值变成0。

再次按F10进行调试,左侧黄色箭头指向最后一行(第四行)。

可以发现,右侧的内存中显示,a内存里面的值变成了0。

如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zz4ac9D2-1672481790105)(D:\Typora图片\clip_image031.jpg)]

画个图演示一下a内存中值的变化:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-On1DMrIs-1672481790105)(D:\Typora图片\clip_image033.jpg)]

<2>当我们用char类型来存储时

int a = 0x11223344;
char* pc = &a;	//pc为字符指针
*pc = 0;

现在我们将a变量的地址存入一个字符指针pc中。

F10进行调试,当左边黄色箭头指向第二行时,在右侧输入&a

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3CWFpAQy-1672481790106)(D:\Typora图片\clip_image035.jpg)]

可以看见内存中存放的的确是a变量的值:44332211(顺序不用在意)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ltpZJKXd-1672481790106)(D:\Typora图片\clip_image037.jpg)]

F10继续调试,可以看到程序走完,右侧的内存中的数值变化:

(当我们将0赋值给pc指针指向的a变量,a内存中只改变了部分值)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mVspHWqg-1672481790107)(D:\Typora图片\image-20221230154807686.png)]

内存中4个字节,这里只改变了1个字节。


当用来存储的变量的类型发生变化,我们解引用操作的时候,结果不一样。

以上结果我们看到,用Int类型来存储时,当进行数据改变,原来的都改变了;而用char类型来存储时,只改变了前两个数(1个字节)。

🍰 指针在存储数据的时候,类型无差,都能够存放。但当我们对它进行解引用操作的时候,整型指针可以改4个字节;而字符指针,确实可以从指针访问相应空间,但*pc去访问空间的时候,只能改1个字节

③总结

指针类型决定了指针进行解引用操作的时候,能够访问空间的大小。

不同类型的指针能够访问的空间大小不同:

  int* p;    p能够访问4个字节
  char*  p;    p能够访问1个字节
  double*  p;   p能够访问8个字节

那么学这个类型有什么意义?

指针类型既然决定了指针变量解引用能够一次访问几个字节,那当我们给指针赋值的时候,应该赋一个合理的指针。

比如我们希望从这个位置向后访问一个字节,那我们应该把它交给一个char指针;我们希望一次访问两个字节,那我们应该把它交给一个short类型的指针。

2)指针+整数

上面我们讨论了第一个意义,下面来讨论第二个意义。

如下代码:

int a=0x11223344;
int* pa=&a;
char* pc=&a;
printf("%p\n",pa);
printf("%p\n",pa+1);

printf("%p\n",pc);
printf("%p\n",pc+1);

看一下最终结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uKF8tJi5-1672481790107)(D:\Typora图片\clip_image041.jpg)]

我们可以看到,当指针类型是int类型时,pa+1向后访问了4个字节;

而指针类型是char类型时,pc+1向后访问了1个字节。

指针类型决定了指针加一向后跳几个字节,指针走一步走多远(步长)。

int* p;    p+1  -->  4
char* p;   p+1  -->  1 
double* p;  p+1  -->  8

❓ 指针向前向后一次走多大?

向后走的是指针所指向对象的类型的大小,整型指针向后加的是4,因为向后加一是跳过一个整型(4个字节);字符指针向后加一加的是1个字节,因为向后跳的是1个字符。

3)总结

🍰 总结

  • 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。比如:char的指针解引用就只能访问一个字节,而Int的指针的解引用就能访问四个字节。

  • 指针的类型决定了指针向前或者向后走一步走多大(距离),单位是字节。

4)举例

那我们知道指针类型的意义有什么用?

我们来举个例子:

int arr[10] = { 0 };

将arr数组的地址拿出来,赋值给指针变量p:

int* p=arr;	//arr为数组名,即首元素地址

🚗 需求:把arr数组里面的元素全部改成1。

<1> 用int类型的指针

改变数组第一个元素:*(p+0)=1

p指针指向数组第一个元素地址,解引用之后就是第一个元素,再将1赋值给它即可。

改变数组第二个元素:*(p+1)=1

p指针向后移动一位,指向数组第二个元素地址,解引用之后就是第二个元素,再将1赋值给它。

后面的规律就找到了。

改变数组里面元素:*(p+i)=1。(i=0,1,…)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1IE9FmoC-1672481790108)(D:\Typora图片\image-20221230192809059.png)]

这样分析,就可以写出如下代码:

for (i=0;i<10;i++) { 
	*(p + i) = 1; 
} 

F10进入调试。

看一下监视:(此时arr数组里面都是0)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hk9Y40Eg-1672481790108)(D:\Typora图片\clip_image043.jpg)]

进入循环,当i=0的时候,数组中第一个元素被改成了1。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e491Qtml-1672481790109)(D:\Typora图片\image-20221230192141390.png)]

继续往下进行,当循环结束,数组中所有元素都被改成了1。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P4Lqo0nb-1672481790109)(D:\Typora图片\clip_image047.jpg)]

输出看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LmrtX8Lv-1672481790109)(D:\Typora图片\clip_image049-16723745275488.jpg)]

所有代码如下:

int arr[10] = { 0 }; 
int* p=arr;//arr为数组名,即首元素地址 
int i = 0; 
for (i=0;i<10;i++) { 
   *(p + i) = 1; 
 } 
for(i=0;i<10;i++){
    printf("%d ",arr[i]);
}

我们来看一下内存:(每4个字节改变一下)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tLARwMA6-1672481790110)(D:\Typora图片\clip_image053-16723745275489.jpg)]

<2> 用char类型的指针

刚才我们使用了int类型的指针,那么使用char类型的话,会怎么样呢?

int arr[10] = { 0 }; 
char* p=arr;//arr为数组名,即首元素地址 
int i = 0; 
for (i=0;i<10;i++) { 
   *(p + i) = 1; 
} 

对于*(p+1),指针向后偏移了一个字节。

循环10次,就访问了10个字节。

可数组里面有40个字节!

一个整型4个字节,按照逻辑,只会初始化两个半整型的空间。

监视不容易观察,我们来看一下内存(调试–>窗口–>内存)。

输入arr

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3U2Z1lm0-1672481790112)(D:\Typora图片\clip_image060.jpg)]

调试,可以看见数组里面现在都是0元素:

​     [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jzzlWyDR-1672481790113)(D:\Typora图片\clip_image062.jpg)]

每两个表示一个字节:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eLBvq4Rw-1672481790114)(D:\Typora图片\image-20221230193932575.png)]

运行结束,可以看见,只改变了10个字节:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MasX3QPP-1672481790114)(D:\Typora图片\clip_image064.jpg)]

三、野指针

(1)概念

概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

❓ 问:什么情况会导致野指针?

1) 指针未初始化

#include<stdio.h>
int main(){
    int a;	//局部变量不初始化,默认是随机值
    int* p;	//局部变量指针未初始化,默认为随机值
    *p=20;
    return 0;
}

随机生成一个地址是很可怕的。

通过p随机找到一块内存空间,改变它的值,这是非法操作。

编译器也会报错:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ylohq7I-1672481790114)(D:\Typora图片\clip_image067-167237452754912.jpg)]

2)指针越界访问

当指针指向的范围超出数组arr的范围时,p就是野指针。

#include<stdio.h>
int main(){
    int arr[10]={0};
    int* p=arr;
    int i=0;
    for(i=0;i<=11;i++){
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++)=i;
    }
    return 0;
}

编译器也会出现异常:
在这里插入图片描述

画个图帮助理解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-slbdEGKA-1672481790116)(D:\Typora图片\clip_image076.jpg)]

3)指针指向的空间释放

这里放在动态内存开辟的时候讲解,这里可以简单提示一下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P3FEsxuH-1672481790116)(D:\Typora图片\clip_image078.jpg)]

当我们用编译器运行,是可以得到结果10。

编译器可以识别部分错误,但有些错误很隐蔽,是无法识别的!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ISnThLt5-1672481790116)(D:\Typora图片\image-20221231100755682.png)]

变量a是局部变量,进入test函数创建,出了大括号就会被销毁。

进入test函数的时候,内存开辟了4个字节的空间,当出了test函数,该内存就被还给了操作系统。

当执行return &a的时候,将a的地址返回去了,但此时a的空间被销毁了。

返回的地址,赋值给了p指针。但是该地址不属于当前程序了。

这时候要通过p指针,找到指向的空间,这块空间可能已经分配给别人了。这样贸然访问,是非法操作!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SaNmqJMA-1672481790117)(D:\Typora图片\clip_image082.jpg)]

同样道理,下面的代码也是有问题的:

iint* test(){
    int arr[10]={10};
    return arr;
}
int main(){
    int* p=test();
    printf("%d\n",*p);
}

只要是返回临时变量的地址,都是有问题的!

除了这个临时变量没有被销毁。被static修饰就不会被销毁。

(2)规避野指针

1)指针初始化

一定要记得初始化!!!

int main(){
    int a=10;
    int* pa=&a;	//初始化
}

但我们也会遇到不知道怎么初始化的时候。

这时候就可以给它赋值NULL。(空指针)

如下:

int* p=NULL;	

这个NULL是什么呢?

我们点击NULL,速览定义

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bq5m22UM-1672481790117)(D:\Typora图片\clip_image084.jpg)]

可以看到,NULL就是0:

NULL用来初始化指针的,给指针赋值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KYWlqiRI-1672481790117)(D:\Typora图片\clip_image086.jpg)]

(void*)0:把0强制类型转换成了void*这种类型,本质上还是0。

就相当于之前我们创建变量的时候,不知道赋什么值,就给变量赋值0:

int b=0;

2)小心指针越界

这个没有什么好解释的,上面已经说的很明白了。

3)指针指向的空间释放即使置NULL

指针指向的空间,如果我们还给别人了,我们就可以把指针置为空。

本来指针指向了这块空间,现在我不想用这块空间了,这块空间已经还给别人了。

如果还想让这个指针合法的存在,那就先把它设为空指针。

举个例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jMkwMUbW-1672481790118)(D:\Typora图片\clip_image088.jpg)]

当你不想让一个指针指向其他地方的时候,或者它指向的空间已经还给操作系统的时候,这时候可以把一个指针置成空指针。

避免它未来可能成为野指针。

4)指针使用之前检查有效性

当我们对指针进行初始化,这个指针就是有效的。

当我们不用这个指针的时候,我们把它置成空指针(置成空指针的时候,就不能访问它指向的空间了)。

即:遇到指针初始化,用完之后赋值为NULL空指针。

就像这样:

①用的时候初始化

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

②不用的时候置为空指针

pa=NULL;

所以,

在后边我们要继续使用这个指针的时候,就需要判断一下:这个指针,如果等于空指针就不用;不是空指针就使用。

即:

if(pa==NULL){
    //如果pa是空指针,就不使用
}
if(pa!=NULL){
    //如果pa不是空指针,就可以使用它
}

当pa已经被赋值为空指针,这时候我们强制访问它,并给它赋值为10。

调试看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YFAsqbZE-1672481790118)(D:\Typora图片\clip_image090.jpg)]

代码走着走着就挂了,程序崩溃了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UCzWpEQT-1672481790118)(D:\Typora图片\clip_image092.jpg)]

⛔️ 当指针为NULL的时候,不能访问它!

所以后边使用指针的时候,必须要判断一下。

如果指针不是空指针(说明里面放的是有意义的地址),就可以使用;是空指针就不能使用。

如下判断即可:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-INFpXcpS-1672481790119)(D:\Typora图片\clip_image094.jpg)]

看个小案例,指针被赋值为了空指针,重新给它指向空间(初始化),就可以继续使用了。

如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sQc3h9ft-1672481790119)(D:\Typora图片\clip_image096.jpg)]

四、指针运算

(1)指针加减整数

1)指针+整数

看一个小案例:

int main() { 
   int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; 
  //不用下标来访问,用指针来访问 
   int* p = arr; //数组名就是首元素地址,将arr交给p指针
   int i = 0; 
   int sz = sizeof(arr) / sizeof(arr[0]); //数组元素个数
   for (i = 0; i < sz; i++) { 
     printf("%d ", *p); //第一次打印,p里面放的就是首元素地址
     p=p + 1;//向后跳一个整形 
   } 
    return 0; 
} 

看一下输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7sBDKxbn-1672481790119)(D:\Typora图片\clip_image098.jpg)]

既然p+1,循环10次,可以输出所有元素。

那么p+2也是可以的,输出奇数。

如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HJpf0BNn-1672481790120)(D:\Typora图片\clip_image100.jpg)]

2)指针-整数

既然指针+整数可行,那么指针-整数也是可行的。

这里我们要稍微改动一下代码。

先将第十个元素的地址赋值给指针p,即int* p=&arr[9]

int main() { 
   int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; 
  //不用下标来访问,用指针来访问 
   int* p = &arr[9]; //将第十个元素的地址交给p指针
   int i = 0; 
   int sz = sizeof(arr) / sizeof(arr[0]); //数组元素个数
   for (i = 0; i < sz/2; i++) { 
     printf("%d ", *p); //第一次打印,p里面放的就是首元素地址
     p-=2;//指针向前指
   } 
    return 0; 
} 

输出看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o5PR6yka-1672481790120)(D:\Typora图片\image-20221231112257460.png)]

3)案例

再举个例子:

#define N_VALUES 5
int main(){
    float values[N_VALUES];	//定义一个数组,数组里面5个元素
    float* vp;	//定义一个指针
    for(vp=&values[0];vp<&values[N_VALUES];){
        *vp++ =0;
    }
    return 0;
}

这几行代码的意思,就是最终让数组里面的元素,都变成0:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MZyg553B-1672481790120)(D:\Typora图片\clip_image106.jpg)]

(2)指针减指针

1)案例一

指针-指针,就是地址-地址

比如:

int main(){
    int arr[10]={1,2,3,4,5,6,7,8,9,10};
    printf("%d\n",&arr[9]-&arr[0]);	//让第10个元素的地址减去第1个元素的地址
}

输出看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sz2tSONp-1672481790121)(D:\Typora图片\clip_image108.jpg)]

​ 🍰 指针减去指针:得到的是中间的元素个数。

中间元素有:1,2,3,4,5,6,7,8,9(一共9个)

如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KUUa5VaE-1672481790121)(D:\Typora图片\clip_image110.jpg)]

如果是小地址减去大地址,得到的就是负数(反过来了)。

如下图:(得到了-9)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6pXbCfVN-1672481790121)(D:\Typora图片\clip_image112.jpg)]

所以,

想得到元素个数,一定是大地址减去小地址,而小地址减去大地址的绝对值是最终结果。


2)错误案例

①不同类型

再来看一个错误:

int main(){
    int arr[10]={1,2,3,4,5,6,7,8,9,10};//整型数组
    char ch[5]={0};//字符数组
    printf("%d\n",&arr[9]-&ch[0]);	
}

以上这种情况,是按照整型讨论还是字符?乱套了!!!这种写法最终结果是不可预知的。

当一个指针减去一个指针的时候,那这两个指针一定是指向同一块空间的。

这是错误写法!两个不同类型的指针相减,是没有任何意义的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1qPU1GzD-1672481790122)(D:\Typora图片\clip_image114.jpg)]

②指针相加

⛔️ 注意:两个指针相加也没有意义!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fzKt5fGw-1672481790122)(D:\Typora图片\clip_image116.jpg)]

3)案例二

再举个例子:求字符串长度

现在我们拥有一个数组arr,将数组首元素地址传给了my_strlen函数。

 int my_strlen(char* str) { 
 	
 } 
 int main() { 
     //strlen-求字符串长度 
     //讲“递归”的时候,我们模拟实现了strlen,1、递归的方式 2、计数器的方式 
     char arr[] = "bit"; 
     int len=my_strlen(arr);//把数组的首元素地址放进去了 
     printf("%d\n", len); 
     return 0; 
  } 

🌵 分析:

如果现在有一个指针(start)指向数组第一个元素,还有一个指针(end)指向最后一个元素。

那么,就可以用end指针减去start指针,就可以得到字符串长度了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nD77Goxe-1672481790122)(D:\Typora图片\clip_image118.jpg)]

那么这两个指针如何表示?

start指针很简单,我们传上去的就是数组首元素地址,直接赋值给start指针即可。

char* start=str;

end指针可以利用循环,找到最后的元素\0,停止循环,即可找到最后元素的地址。

char* end=str;
while(*end !='\0'){
    end++;
}

最后返回元素个数(end-start):

return end-start;

看一下编译器输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MvpudxVH-1672481790123)(D:\Typora图片\clip_image120.jpg)]

整体代码如下:

int my_strlen(char* str) { 
 	 char start = str; 
	 char end = str; 
     while (end != '\0') { 
     	end++; 
     } 
     return end - start;//字符个数 
 } 
 int main() { 
     //strlen-求字符串长度 
     //讲“递归”的时候,我们模拟实现了strlen,1、递归的方式 2、计数器的方式 
     char arr[] = "bit"; 
     int len=my_strlen(arr);//把数组的首元素地址放进去了 
     printf("%d\n", len); 
     return 0; 
  } 

(3)指针的关系运算

指针的关系运算即指针比较大小

#define N_VALUES 5 
int main() { 
       float values[N_VALUES]; //创建一个数组,里面5个元素
       float* vp; 
       for (vp = &values[N_VALUES]; vp > &values[0];) { 
         *--vp = 0; 
       } 
       return 0; 
 } 

画个图帮助理解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ymLXt1Pq-1672481790123)(D:\Typora图片\clip_image124.jpg)]

把上面代码改一下,第二种方法:(注意看for循环)

 #define N_VALUES 5 
 int main() { 
   float values[N_VALUES]; 
   float* vp; 
   for (vp = &values[N_VALUES - 1]; vp >= &values[0]; vp--) { 
     *vp = 0; 
   } 
   return 0; 
 } 

画个图演示一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H7v0EoPg-1672481790123)(D:\Typora图片\clip_image128.jpg)]

方法二,实际在绝大部分编译器上是可以顺利完成任务的,然而我们应该避免这样写,因为标准并不保证它可行。

📖标准规定:

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存未知的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d6vtTwud-1672481790124)(D:\Typora图片\clip_image130.jpg)]

所以,尽量使用第一种方法,第二种方法避免使用!

五、指针和数组

(1)回顾数组

先回顾一下:

数组名是什么?

数组名是首元素地址(两个特例)。

数组基础知识传送门:C语言基础–数组_雨翼轻尘的博客-CSDN博客。

既然数组名是首元素地址,那么我们打印arr&arr[0]结果应该是一样的。

如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EXiyqEyc-1672481790124)(D:\Typora图片\clip_image132.jpg)]

arr与**arr[0]**地址相同。所以都是首元素地址,这是无疑的。

绝大多数情况下,数组名都是首元素地址。

有两个例外:

①&arr

整个数组的地址。

②sizeof(arr)

整个数组的大小,单位为字节。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fAtSW250-1672481790124)(D:\Typora图片\clip_image134.jpg)]

arr&arr[0]&arr的区别莫过于这张图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xqckhpfr-1672481790125)(D:\Typora图片\clip_image136.jpg)]

🍰 总结

arr&arr[0]得到的是首元素地址,也仅仅只有首元素地址。

&arr得到的地址,后面包括一整个数组。

可能上面说的比较含糊,如果将它们分别加一,观察分别输出的地址:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dAmcEBaG-1672481790125)(D:\Typora图片\clip_image138.jpg)]

有的小伙伴可能不太会地址的计算,之前博客有写过,这里再说一下吧。

内存中地址是十六进制存储的,1~9,a~f(10~15)。

地址计算:(案例)

00EFF8E0 — > 00EFF908

将后面三项拿出来:8E0 — > 908

怎么计算相差多少呢?如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3SdBU82j-1672481790126)(D:\Typora图片\clip_image140.jpg)]

现在我们来算一下当前的结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NvVAGVtG-1672481790126)(D:\Typora图片\image-20221231143934951.png)]

计算过程如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MZ5Omgww-1672481790126)(D:\Typora图片\249404659015995071.jpg)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RzAZn78j-1672481790127)(D:\Typora图片\528841473471227678.jpg)]

(2)指针和数组

既然数组名表示首元素地址,那么就可以直接将数组名存入指针里面,如下:

int arr[10]={1,2,3,4,5,6,7,8,9,0};
int* p=arr;	//p里面存放的是数组首元素地址

既然可以把数组名当成地址存放到一个指针中,我们就可以使用指针来访问数组。

即,数组可以通过指针进行访问

我们不妨通过两种方法,分别输出每一个元素的地址。

第一种,可以这样输出数组元素的地址:&arr[i];第二种:p+i

具体代码如下:

 int main() { 
   int i = 0; 
   int arr[] = { 1,2,3,4,5,6,7,8,9,0 }; 
   int* p = arr; //p里面存放的是数组首元素地址
   int sz = sizeof(arr) / sizeof(arr[0]); 
   for (i = 0; i < sz; i++) { 
     printf("&arr[%d]=%p<===>p+%d=%p\n", i, &arr[i], i, p + i); 
   } 
   return 0; 
} 

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CGDEKenX-1672481790127)(D:\Typora图片\clip_image146.jpg)]

上面我们可以发现,p+i&arr[i]结果一模一样。

我们就可以直接使用指针p来访问数组了


我们可以做个小案例:

int main(){
 	int i = 0; 
 	int arr[10] = { 0 }; 
 	int* p = arr; 
 	int sz = sizeof(arr) / sizeof(arr[0]); 
 	for (i = 0; i < sz; i++) { 
  		 *(p + i) = i;//把arr数组元素改成0,1,2,3... 
 	} 
	for (i = 0; i < sz; i++) { 
  		 printf("%d ", arr[i]); 	//用数组形式,输出数组里面元素
	} 
	printf("\n"); 
	for (i = 0; i < sz; i++) { 
  	 	printf("%d ", *(p + i)); 	//用指针访问数组的形式,输出数组里面元素
	} 
    return 0;
} 

编译器运行得到:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D0ntHW3x-1672481790128)(D:\Typora图片\clip_image150.jpg)]

🍰 总结

我们可以看到:数组可以通过指针来进行访问。

但数组和指针不是一回事,

数组可以存放一组相同类型的数据,而指针只是可以存放一个地址或是一个数组起始位置/任意位置的地址。

六、二级指针

(1)介绍

平时我们写的指针都是一级指针。

如下:

int main(){
    int a=10;
    int* pa=&a;	//pa就是一级指针变量,int*就是一级指针类型
}

a是一个变量,将a的地址取出来,放进pa里面,pa的类型是int*。pa是一级指针变量。

再来想一下,pa是指针变量,变量创建要在内存中开辟空间

如果现在这样写:&pa,就拿到了pa空间的地址。这块地址也想存起来,怎么办呢?

比如将pa的地址,存放进ppa变量里面,这时候ppa的类型就应该这样写:int**

如下:

int** ppa=&pa;

ppa就是二级指针(存放一级指针的地址)。

同样,如果想要取出ppa的地址,存放进pppa变量里面。pppa变量的类型应该是int***

如下:

int*** pppa=&ppa;

pppa就是三级指针(存放二级指针的地址)。

这里,我们只需要了解二级指针。其他的不用管。

(2)理解

int* pa=&a*表示pa是指针类型的,int表示它指向的对象是int类型。

int* * ppa=&pa;:第二个*表示ppa是指针类型的,int*表示它指向的对象是int**类型。

画个图帮助理解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3DjBwR7v-1672481790128)(D:\Typora图片\clip_image156.jpg)]

二级指针有什么用呢?

比如现在我们想通过ppa拿出a的值,先解引用*ppa,找到pa,然后再解引用**ppa,找到a,输出即可。

如下:

printf("%d\n",**ppa);

看一下结果输出:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z6dEwGWA-1672481790128)(D:\Typora图片\clip_image158.jpg)]

还可以通过ppa改变a的值。

**ppa=20;

输出看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EAA4koGR-1672481790129)(D:\Typora图片\clip_image160.jpg)]

所有代码:

 #define _CRT_SECURE_NO_WARNINGS 
 #include<stdio.h> 
 int main() { 
   int a = 10; 
   int* pa = &a;//pa是一级指针变量,int*是一级指针类型 
   int* * ppa=&pa;//ppa就是二级指针变量 
   **ppa = 20; 
   printf("%d\n", **ppa); 
   printf("%d\n", a); 
    //int** * pppa = &ppa;//pppa就是三级指针变量 
    return 0; 
  } 

七、指针数组

❓ 问:指针数组是指针还是数组?

是数组。是存放指针的数组。

看一个小例子,:

int main(){
    int a=10;
    int b=20;
    int c=30;
    //分别将a,b,c的地址存入指针变量pa,pb,pc中
    int* pa=&a;
    int* pb=&b;
    int* pc=&c;
}

如果我想把a,b,c的地址存起来,就需要三个指针变量pa,pb,pc。

那能不能写一个数组,把他们三个地址都存放起来?数组里面放的都是整型变量的地址

如何写一个指针数组?

之前我们写整型数组:int arr[3],那么写指针数组就可以这样写:int* arr[3]

然后初始化:

int* arr[3]={&a,&b,&c};

拿到指针数组里面的指针也很简单:

*(arr[i]);	//i=0,1,2

输出看一下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DUjyfsMd-1672481790129)(D:\Typora图片\clip_image164.jpg)]

所有代码:

int main(){
    int a=10;
    int b=20;
    int c=30;
    //分别将a,b,c的地址存入指针变量pa,pb,pc中
    int* pa=&a;
    int* pb=&b;
    int* pc=&c;
    int* arr[3]={&a,&b,&c};
    int i=0;
    for(i=0;i<3;i++){
        printf("%d ",*(arr[i]));
    }
    return 0;
}

好啦,初识指针就到这里。

欢迎关注,一位喜欢慢慢生活的博主。

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/131247.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python类中常见内置方法

目录 一.几种常用的类内置方法 魔术方法 _ _str_ _字符串方法 _ _lt_ _小于符号比较方法 _ _le_ _小于等于比较符号方法 _ _eq_ _等于比较符号 一.几种常用的类内置方法 魔术方法 上文提到的_ _init_ _构造方法&#xff0c;是Python类内置的方法之一。 这些内置的类方法…

人工智能-聚类算法

1、聚类算法简介 典型的无监督算法&#xff0c;主要用于将相似的样本自动归到一个类别中。 根据样本之间的相似性&#xff0c;将样本划分到不同的类别中&#xff0c;对于不同的相似度计算方法&#xff0c;会得到不同的聚类结果。常用的相似度计算方法是欧式距离法 聚类算法与…

2022年总结 2023展望

前言 今天是2022年最后一天&#xff0c;姑且简单总结这一年。这一年从头到尾发生了很多翻天覆地的事件。回看去年2021年的年度总结还是有些遗憾&#xff0c;完成度4/7。 回顾 2021 年立下的 flag&#xff1a; 写文章30篇 没有完成&#xff0c;技术和知识是在有断断续续学习&a…

【Linux】多线程

目录 一、什么是线程 1、线程的基本认识 2、Linux线程与接口关系的认识 3、创建线程 4、线程等待 5、线程终止 6、线程分离 二、线程的优点 三、线程的缺点 四、线程与进程的关系 1、线程安全与重入 2、不可重入情况 3、可重入情况 4、可重入与线程安全的联系 五…

算法合集 —— 数组篇

算法 —— 数组 目录算法 —— 数组1.二分查找1.1二分查找习题集2.双指针法2.1双指针法习题集3.滑动窗口3.1滑动窗口习题集4.二维数组4.1二维数组习题集1.二分查找 二分查找适用于&#xff0c;在有序排列的数组中查找某一指定元素。 其原理为范围搜索&#xff1a;如果这个元素…

opencv-python常用函数解析及参数介绍(四)——图像阈值

图像阈值处理前言1.改变图像颜色灰度图HSV图2.图像阈值图像中数值对应的效果函数与参数阈值处理效果前言 在很多任务当中&#xff0c;首要的任务就是对图像进行阈值处理&#xff0c;为后续其他操作做准备&#xff0c;本文将介绍5种阈值处理的方法以及参数设置&#xff0c;同时…

API 概述

API 概述目录概述需求&#xff1a;设计思路实现思路分析1.High-Level API &#xff1a;用于事务边界定义、控制及事务状态查询。2.2. High-Level API5.2.2 GlobalTransactionContextTransactionalTemplateLow-Level API参考资料和推荐阅读Survive by day and develop by night.…

网络协议总结

网络协议总结网络模型网络协议TCP/IP 模型网络接入层封装与解封装实际数据传输举例发送数据包接收数据包网络接口处理IP 模块处理TCP 模块处理应用程序处理网络构成通信介质与数据链路网卡二层交换机路由器 / 三层交换机![在这里插入图片描述](https://img-blog.csdnimg.cn/a8e…

F280049C General-Purpose Input/Out(GPIO)

​ 文章目录GPIO8.1 简介8.2 配置概述8.3 ADC引脚上的数字输入&#xff08;AIO&#xff09;8.4 数字通用I/O控制8.5 输入限定8.5.1 异步输入8.5.2 仅与SYSCLKOUT同步8.5.3 使用采样窗口进行鉴定8.6 SPI信号8.7GPIO和外设引脚复用8.7.1GPIO复用8.7.2 外设复用8.8 内部上拉配置要…

基础架构:一条SQL查询语句是如何执行的?

这是专栏的第一篇文章,我想来跟你聊聊 MySQL 的基础架构。我们经常说,看一个事儿千万不要直接陷入细节里,你应该先鸟瞰其全貌,这样能够帮助你从高维度理解问题。同样,对于 MySQL 的学习也是这样。平时我们使用数据库,看到的通常都是一个整体。比如,你有个最简单的表,表…

DML语句

DML语句目录概述需求&#xff1a;设计思路实现思路分析1.SQL 实例2.UPDATE3.DELETE4.SELECT5.是TRUNCATE参考资料和推荐阅读Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wai…

数字DNA盗窃可能会在2023年到来

攻击者总是在不断发展并寻找访问数字系统的方法。随着人工智能的使用越来越多&#xff0c;量子计算有望很快成为现实&#xff0c;网络威胁格局的变化比以往任何时候都快。 当谈到网络安全时&#xff0c;我们应该始终尝试着眼于即将发生的事情以及它将产生的影响。我们不能只是…

操作系统实验7:终端设备字符显示的控制

实验目的 加深对操作系统设备管理基本原理的认识&#xff0c;实践键盘中断、扫描码等概念&#xff1b;通过实践掌握Linux 0.11对键盘终端和显示器终端的处理过程。 实验内容 本实验的基本内容是修改Linux 0.11的终端设备处理代码&#xff0c;对键盘输入和字符显示进行非常规…

2022-我的年终总结

去年年末&#xff0c;我也写了一篇年终总结。去年这一年是极度繁忙的一年&#xff0c;因各种原因&#xff0c;过年没回家&#xff0c;一个人在宿舍度过了凄凉的春节。而今年是丰收的一年&#xff0c;去年所付出的一切都在今年获得了回报&#xff0c;我也迎来新的生活。 新的生活…

过年春联不可少,python带你制作春联,体验不一样的过年氛围

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 又到了学Python时刻~ 每逢春节&#xff0c;无论城市还是农村&#xff0c; 家家户户都要挑漂亮的红春联贴于门上&#xff0c;辞旧迎新&#xff0c;增加喜庆的节日气氛。 据说这一习俗起于宋代&#xff0c;在明代开始盛…

我是阿豪我的2022年年终总结.

时光如白驹过隙般&#xff0c;飞逝而过&#xff0c;来不及细品岁月的美好。一晃&#xff0c;2022年就过去了&#xff01; 明天新的一年就来了。回忆一下2022一年都干了什么。 3月份背了大概200多道的前端面试题&#xff0c;疯狂的刷面试题&#xff0c;一天不刷几道面试题心里…

ffmpeg从某站提取视频、音频、详解

ffmpeg从某站提取视频、音频、详解 事件背景 准备链接 第一步安装下载 ffmpeg是开源软件&#xff0c;安装网址http://ffmpeg.org/download.html#build-windows 本人用的windows10系统 打开网址后随便你怎么下载都行&#xff0c;Git、或者直接下等等 按图片输入上述网址也…

【攻防世界】Web very_easy_sql

做了web才发现&#xff0c;原来自己是真的什么都不懂啊&#xff0c;不过也好&#xff0c;说明我有很大的进步空间呢 不闲聊了&#xff0c;来看题目 打开是一个登录界面&#xff0c;我们抓包看看返回些什么 返回包有三个需要注意的地方&#xff0c;我都用框框圈起来了 有一个S…

【C++】map 与 set 的介绍与使用、力扣:692. 前K个高频单词

目录 一、关联式容器 二、键值对 三、set 3.1 set 的介绍 3.2 set 的使用 3.3. set 的使用举例 四、map 4.1 map的介绍 3.2 map 的使用 4.3 map的使用举例 五、经典练习题 1.set的使用 2.map的使用 思路一(稳定排序)&#xff1a; 思路二(priority_queue)&#x…

jvm参数说明

-Xmx3550m&#xff1a;设置JVM最大堆内存为3550M。 -Xms3550m&#xff1a;设置JVM初始堆内存为3550M。此值可以设置与-Xmx相同&#xff0c;以避免每次垃圾回收完成后JVM重新分配内存。 -Xss128k&#xff1a;设置每个线程的栈大小。JDK5.0以后每个线程栈大小为1M&#xff0c;之…