C语言基础--初识指针

简介: C语言基础--初识指针

一、初识指针

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

指针是什么?

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

:question: 问:

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*

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

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

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

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

:cake: 总结:

  • 指针是用来存放地址的,地址是唯一标示一块地址空间的。
  • 指针的大小在32位平台是4个字节,在64位平台是8个字节。

二、指针和指针类型

指针类型的意义

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

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

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

1)指针的解引用

①问题抛出

:question: 问:它们指针大小都是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个字节)。

:cake: 指针在存储数据的时候,类型无差,都能够存放。但当我们对它进行解引用操作的时候,整型指针可以改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

:question: 指针向前向后一次走多大?

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

3)总结

:cake: 总结

  • 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。比如:char的指针解引用就只能访问一个字节,而Int的指针的解引用就能访问四个字节。
  • 指针的类型决定了指针向前或者向后走一步走多大(距离),单位是字节。

4)举例

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

我们来举个例子:

int arr[10] = { 0 };

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

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

:red_car: 需求:把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)概念

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

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

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)]

:no_entry: ==当指针为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)]

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

中间元素有: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)]

②指针相加

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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; 
  } 

:cactus: 分析:

如果现在有一个指针(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)]

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

:book:标准规定:

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

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

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

五、指针和数组

(1)回顾数组

先回顾一下:

数组名是什么?

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

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

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

如下图:

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

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

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

有两个例外:

①&arr

整个数组的地址。

②sizeof(arr)

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

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

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

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

:cake: 总结

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)]

:cake: 总结

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

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

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

六、二级指针

(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; 
  } 

七、指针数组

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

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

看一个小例子,:

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;
}

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

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

请添加图片描述

相关文章
|
2月前
|
存储 编译器 C语言
【C语言】【指针1】指针难?看这个就够了!
【C语言】【指针1】指针难?看这个就够了!
|
23天前
|
存储 C语言
【C语言基础】一篇文章搞懂指针的基本使用
本文介绍了指针的概念及其在编程中的应用。指针本质上是内存地址,通过指针变量存储并间接访问内存中的值。定义指针变量的基本格式为 `基类型 *指针变量名`。取地址操作符`&`用于获取变量地址,取值操作符`*`用于获取地址对应的数据。指针的应用场景包括传递变量地址以实现在函数间修改值,以及通过对指针进行偏移来访问数组元素等。此外,还介绍了如何使用`malloc`动态申请堆内存,并需手动释放。
|
26天前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
2月前
|
C语言
C语言------指针
这篇文章是关于C语言中指针的实训,通过示例代码展示了指针的基本概念、定义、赋值、使用和传递,以及指针运算和指针在函数参数中的应用,如交换两个变量的值和找出两个数中的较小值。
C语言------指针
|
1月前
|
存储 安全 C语言
C语言 二级指针应用场景
本文介绍了二级指针在 C 语言中的应用,
|
2月前
|
存储 编译器 C语言
【C语言篇】深入理解指针2
代码 const char* pstr = "hello world."; 特别容易让初学者以为是把字符串 hello world.放 到字符指针 pstr ⾥了,但是本质是把字符串 hello world. 首字符的地址放到了pstr中。
|
2月前
|
存储 程序员 编译器
【C语言篇】深入理解指针1
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。
|
2月前
|
存储 搜索推荐 C语言
C语言中的指针函数:深入探索与应用
C语言中的指针函数:深入探索与应用
|
2月前
|
存储 编译器 C语言
【C语言】指针练习题目
【C语言】指针练习题目
|
2月前
|
C语言 Python
C语言指针(2)
C语言指针(2)
25 5