深入理解并打败C语言难关之一————指针(4)

前言:

  我们在前面的几讲中已经讲了指针的很多内容了,现在我们开始层层递进,要探寻更多的指针喽,不多废话了,直接进入正题,开始今天的指针之旅喽!


  目录:

1.字符指针变量

1.1常量字符串

1.2一个有趣的题目

2.数组指针变量 

2.1数组指针变量是什么?

2.2我们如何对数组指针变量初始化

3.二维数组传参的本质

4.函数指针变量

4.1函数指针变量是什么?

4.2函数指针变量的创建和初始化

4.3函数指针变量的使用


正文:

1.字符指针变量 

  在开启文章之前,我先对字符串的打印提一嘴,对于字符串的打印,我们知道用到的占位符是%s,此时仅仅提供字符串首字符地址就可以了,可一定不要解引用字符指针,这样仅仅会打印第一个元素的地址,并且VS2022还会给你报警告!!!,一定要记住这个,小编在刚开始起草这篇文章的时候就忘记这部分知识点了,导致我自己重新复习了一遍知识,所以我们在学习的时候一定要温习以前的知识,温故而知新,可以为师矣!

  我们知道,字符型的常量用char表示,那么根据指针的知识,我们知道便可以知道字符指针变量是char * 来表示,我们知道,字符串的创建可以通过一个数组进行创建,就类似:

#include<stdio.h>
int main()
{char arr[20] = {0};arr[20] = {"hello world"};return 0;
}

  在这里数组里面放置了字符串 ,这个我相信读者朋友们都是明白的,下面我给出一串代码,大家来思考一个问题(我放到题目里面了):

​
#include<stdio.h>
int main()
{char* p = "hello world";   //猜一猜这里是把一个字符串放进指针变量里吗?printf("%s", p);
}​

  先自己思考一下,三,二,一——————其实这里是把字符串第一个元素的地址放入了指针变量p里面,之后打印环节是通过第一个元素的地址开始往后打印,直到遇到\0停下,下面来看看这个字符串真正的样子:

 

  通过这个图可以清晰的知道p指向的地方在哪里,我们把后面的字符串叫做常量字符串,下面我们来进入常量字符串的环节:

4.1常量字符串

  我们把;类似上面的代码的后面的字符串,叫做常量字符串,这是我们写字符串的另一种形式,第一种是通过数组的方式来存放字符串,现在这种为通过数组指针来传递字符串,大家一定要记住这两种书写方式,接下来我们来讲一下常量字符串的一个特性,将之前请欣赏下面一组代码:

int main()
{char* p = "abcdef";*p = 'a';printf("%c", *p);   //这个代码可以正常实现吗return 0;
}

  大家觉得这个代码可以实现吗?下面我们来运行一下:

 

  我们发现运行起来居然没有结果!那么这是为什么呢?对于我们代码中出现的未知错误,我们可以通过调试来检测一下代码的重要性(这里展示出了调试的重要性,我们在平常代码出错的时候记得自己调试,而不是一味的去查询网络,询问别人) :

 

  我们发现我们无法修改字符串常量的值,由此我们可以知道字符串常量的一个性质:字符串常量是无法被改变,它是固定的,它是忠诚的,所以我们再平常使用字符串常量的时候一定要记得这个性质!既然我们已经了解了字符串常量了,下面来看一个有趣的题目,可以让我们更好的理解这部分的知识

1.2.一个有趣的题目:

  下面请看下面的代码:

#include<stdio.h>
int main
{ char arr1[] = "nihao shijie";char arr2[] = "nihao shijie";const char* arr3 = "nihao shijie";const char* arr4 = "nihao shijie";if(arr1 == arr2)printf("arr1 and arr2 is same!\n");elseprintf("arr1 and arr2 is diffcule\n");  //猜一猜最终会打印出什么?if(arr3 == arr4)printf("arr3 and arr4 is same!\n");else printf("arr3 and arr4 is diffcult!\n");return 0;
}​​

  大家先思考一下这个问题(一定要思考!),三,二,一 ———— 这个题的答案是下图所展示的:

  可能现在很多读者朋友会有疑惑:为什么arr1 和 arr2是不同的呢?arr3 和 arr4为什么是相同的呢?下面我来解释一下大家的疑惑:首先我们可以清晰的看出来,arr1 和 arr2是一个字符数组,虽然它们指向的都是同一个字符串,但是每一次数组的建立,都是开辟一块新的空间,所以二者的地址都不一样,所以这个是不相同的! 但是对于后面两个字符串,在C语言中,会把常量字符串固定在一个内存中,所以它们指向的内容是同一个内存,所以他们是相同的,这个题目一定要记住牢牢掌握,我当时学习这个题目的时候就出错了,所以我特地把它写到文章,读者朋友们一定要牢记!

 

小结:

  大家一定要好好掌握常量字符串,至少做到看见它知道它是什么东西,而不是啥也不会,下面来进入下一篇章

2.数组指针变量

2.1.数组指针变量是什么

  在讲这个之前,大家一定要把数组指针和指针数组区分开,虽然同样是四个字,但是位置一交换那整体的意思就不一样了,后者是一个数组,那前边的是什么呢?我们可以类比记忆,存放整形地址的叫整形指针,存放浮点型的是浮点型指针,那么结果显然意见了,数组指针就是存放数组地址的指针,它的本质是指针,指针数组本事是数组,这两个双胞胎一定要区分开?

  那么我们如何写数组指针呢?下面给出两个代码,大家来看看二者各自是什么呢?

int* p1[10];
int(*p2)[10];

  我们之前就讲述了指针数组的创建,所以我们很显然的认识出了p1是指针数组,那么通过排除我们可以知道第二个指的是数组指针,那么为什么是这样创建呢?下面我们通过图文的方式来帮助大家进行理解:

 

 

 (上面的图片有一句话出错课,[10]是指针所指向的数组元素有10个)

通过图文的形式我们可以很清晰的知道为什么数组指针是这样的创建的,我们进行完创建后就要初始化了,下面我们来进行初始化的环节: 

 

2.2.我们如何对数组指针变量初始化

  其实初始化是蛮简单的,我们以及了解到了数组指针是什么了,初始化就是把它翻译成代码就好了,下面来展示一下数组指针如何初始化:

#include<stdio.h>
int main()
{int arr[5] = { 0 };int(*p)[5] = &arr;
}

  其实这个初始化和整型的初始化本质都是一样的,都是取地址罢了,不过一个是取数的地址,一个是取数组的地址而已,下面小编来提问一个问题,数组指针的类型是什么呢?这个时候我们可以通过类比进行记忆,int *p的类型是int *,char *p的类型是char,那么数组指针的类型自然是int(*)[10],我们将名字去掉以后就是它的类型,我们可以通过调试窗口来验证我们的说法:

   很显然,我们的说法是正确的,我们现在已经了解到了数组指针的创建和初始化了,那么在进入下一篇文章之前,不知道大家是否还记得我以前写的文章中,&arr代表的是整个数组,当时我并没有很详细的解释,现在我们学了数组指针,这个问题就好解决了,因为&arr的类型是int(*)[10],所以它代表的是整个元素的地址,所以我们让它加一的时候它会跳过一个数组的字节!所以说,知识都是环环相扣的,前面许多不懂得知识我们学到后面就迎刃而解了!

小结:

  大家一定要把数组指针和指针数组区分好,以后我们会经常使用它们的!

3.二维数组传参的本质(差点忘记写这部分呢)

  小编在之前的一篇文章中,我记着应该是讲指针(3)的时候就讲过一维数组传参的本质,光说一维数组的话,二维数组我们就白学了,它也是需要被宠幸(bushi)的,在说这个之前,我们也是需要说二维数组数组名代表的是什么:

3.1二维数组数组名

  我们知道,一维数组的数组名是数组首元素的地址,那么二维数组的数组名也是数组首元素的地址吗?大家先来自己思考一下,我给出一段代码以及运行图,来解释一下二维数组的数组名到底是不是二维数组第一个元素的地址:

int main()
{int arr[3][4] = { 0 };printf("%p\n", arr);printf("%p\n", &arr[0][0]);printf("%p\n", arr + 1);printf("%p\n", &arr[0][0] + 1);printf("%p\n", &arr[0][1]);printf("%p\n", &arr[1][0]);  //猜猜我为啥会写这个return 0;
}

   我们会发现如果我们仅仅光展示前两行代码的时候,大家肯定认为此时二维数组的数组名就是二维数组首元素的地址,但是我们发现,当指针加一的时候,二者突然又不一样了!这里很多读者朋友就会疑惑了,这俩为啥不相同,这里为了让大家更好的理解,小编贴心的又打出了两个代码,这个时候我们就发现,&arr[0][0] + 1的地址和&arr[0][1]的地址是一样的,arr + 1和&arr[1][0]的地址是一样的,这似乎向我们反映了一个事情,arr似乎指的是第一行的地址,也就是首行的地址,那么事实是否就是这样呢?其实,这个就是正确的,二维数组中,数组名就是数组受行的地址,为了帮助读者朋友们更好的理解,小编用图文进行解释:

  对于具体的解释我已经放到图文里面,读者朋友们先记住,二维数组的数组名就是数组首行元素的地址!现在我们已经明白了这个小的知识点,下面我们来进行中重要部分呢,二维数组进行传参的本质: 

 

3.2.二维数组传参的本质

  我们知道哦在一维数组传参的时候传过去的是数组名,是首元素的地址,我们在传参二维数组的时候,同样也是传的数组名,但是数组首行的地址,那么我们形参可以怎么写呢?这里就用到了我们刚学的一部分内容,数组指针,我们可以把传过去的首行元素看做成一个一维数组的地址,此时我们可以通过数组指针来接受它,具体的代码如下图:

void suibian(int(*p)[2], int sz)  //是不是感觉到知识是换换相扣的呢?
{///......
}
int main()
{int arr[3][2] = { 0 };int sz = sizeof(arr) / sizeof(arr[0]);suibian(arr, sz);return 0;
}

  是不是感觉知识是环环相扣的呢》我么在数组指针讲完后趁热打铁,讲到了二维数组传参的本质,这有助于我们更好的理解数组指针,当然,对于二维数组的传参,我们也可以写成这样类型的:

void suibian(int arr[3][2], int sz)  //是不是感觉到知识是换换相扣的呢?
{///......
}

  其实这两种写法都是可以的,你想用什么就用什么! 

4.函数指针变量

4.1.函数指针变量是什么?

  这里我们同样也可以类比记忆,上面我们刚讲数组指针是存放数组地址的指针,整形指针是存放整形的指针,所以函数指针,就是存放函数地址的指针,这里向我们透露出了一个信息,函数也是有地址的,下面我们通过一串代码来看看函数的地址是什么:

#include<stdio.h>
void add()
{//内容我就不写了
}
int main()
{printf("%p",&add);return 0;
}

 

  可以看到函数确实会存在地址,这里我们也是get到了一个新的知识点,下面小编出个题考考大家,前面我们学习了数组名代表着数组首元素的地址,那么函数名是否也是一个地址呢?下面我们来进行代码展示:

void add() {}
int main()
{printf("%p\n", add);  //这里会不会也和数组名和取地址数组名一样,两者指的类型不同呢?printf("%p\n", &add);   
}

 

  上图可以看出,函数名同样也是指的函数的地址,正如我在代码中提出的问题一样,函数名和&函数名指向的是一样吗?这里小编也不多废话了,其实函数名和&函数名是一模一样的,只不过根据个人的写法不同罢了,这里一定要记住,待会要用到!

 

4.2.函数指针变量的创建和初始化

4.2.1函数指针的创建

那么既然我们知道函数指针是什么了,下面我们要对它进行创建了 :

int (*p)(int ,int) 

  可能很多读者朋友对整个还是很疑惑的,看不懂这是什么东西,下面我们通过画图来进行进一步的解释: 

 

  通过上图我们可以知道函数指针到底是如何进行创建的,这个和数组指针的创建是有一点相似的,所以也可以类比记忆,同样的,我们也要了解函数指针的类型到底是什么,其实它和数组指针,整形指针的类型记忆方法一样,我们把指针的名字去掉就是代表的是什么类型了,所以函数指针的类型是:int(*)(),下面我们通过调试来证明我说的:

   上面的代码证实了我说的正确性,所以我们也要记住函数指针的类型,这里也算是个小小的重点,我们现在已经讲了函数指针是如何进行创建的,下面我们来进行函数指针的初始化:

4.2.1函数指针的初始化

  其实这部分的知识很简单,既然函数指针指的是存放函数地址的指针,那么我们在对其使用的时候,直接对函数进行取地址操作就好了,下面是代码的展示:

	int(*p)(int, int) = &add; //这里的add是指的是一个add函数,记住

  上面便是对函数指针进行初始化,这部分知识算是简单的 ,读者朋友们一定要掌握好,我们既然讲了函数指针,那么我们就要对函数指针进行使用,下面进入最后一个小节,对函数指针进行使用

4.3.函数指针变量的使用

  我们在使用指针变量的时候,往往伴随着解引用操作符*的使用,所以我们在使用函数指针变量的时候,也需要用到解引用操作符,上面的图解解释了int * 是返回类型,所以我们是不需要写它的,我们仅仅使用剩下的就好了,下面是对函数指针使用时的代码:

int add(int x,int y) {return x + y;
}
int main()
{int(*p)(int, int) = &add;int c = (*p)(3, 4); printf("%d", c);
}

   可以看出此时函数被正确的运用了,所以我们在使用函数指针的时候记住忽略前面的int *就好了,之后正常写就好,不知道你是还记得我们在前面说过函数名就是函数地址,这时候就要形成闭环了,我将前面的目的就是为了这里,请读者朋友们想想看,我们是否可以通过指针名直接访问函数呢?下面来看看代码展现

int add(int x,int y) {return x + y;
}
int main()
{int(*p)(int, int) = &add;int c = p(3, 4); printf("%d", c);
}

   我们很快便可以发现,原来指针名就可以直接访问函数,回想一下,我们在之前使用函数的时候,是不是直接通过函数名就调用函数了?其实我们是通过地址来访问函数的,知识又再次形成了闭环,可能有些读者朋友们会想,为什么在使用函数指针的时候要需要括号呢?其实是很简单解释的,如果没有括号,p首先会和后面的括号结合,我们在之前就说了,这就是调用函数,解引用操作符操作的是地址,而不是常量,所以这么写是明显错误的锕,大家一定要记住正确的格式!

 


总结:

  今天我们讲了许多重要的内容,大家一定要好好的理解并运用,现在指针的知识我们已经讲了一大半了,我感觉我指针已经忘记很多了,今天的博客还是我通过复习得来的,这更加的说明了我们一定要温故而知新,知识就是要这样的,我们需要重复的去记忆,才能有助于我们学习,我也不多废话了,如果文章有误,请您在评论区指出,我会认真倾听你们的意见,我们下一篇博客见喽! 

 

 

 

 

 

 

   

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

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

相关文章

k8s pv使用nfs挂载券需要授权

以下是一个/etc/exports文件的示例&#xff0c;它设置了一个名为/shared的目录&#xff0c;允许192.168.1.0/24网段的所有客户端以读写权限访问该目录&#xff1a; 首选创建/shared目录 mkdir -p /shared ​​​​​​​ /shared 192.168.1.0/24(rw,sync,no_root_squash) …

MEGALODON:突破传统,实现高效无限上下文长度的大规模语言模型预训练和推理

在人工智能领域&#xff0c;尤其是在自然语言处理&#xff08;NLP&#xff09;中&#xff0c;大模型&#xff08;LLMs&#xff09;的预训练和推理效率一直是研究的热点。最近&#xff0c;一项突破性的研究提出了一种新型神经网络架构——MEGALODON&#xff0c;旨在解决传统Tran…

python怎么连接以太坊,python实现数据上传以太坊

目录 python怎么连接以太坊 python实现数据上传以太坊 python怎么连接以太坊 要在Python中连接以太坊网络,通常你需要使用以太坊的客户端(如Geth或Parity)以及一个Python库来与这些客户端进行交互。最常用的Python库之一是web3.py,它提供了与以太坊网络交互的功能。 以…

PyTorch 索引与切片-Tensor基本操作

以如下 tensor a 为例&#xff0c;展示常用的 indxing, slicing 及其他高阶操作 >>> a torch.rand(4,3,28,28) >>> a.shape torch.Size([4, 3, 28, 28])Indexing: 使用索引获取目标对象&#xff0c;[x,x,x,....] >>> a[0].shape torch.Size([3, 2…

认识与学习JSP

JSP核心技术 什么是JSP JSP全称是Java Server Pages&#xff0c;它和servle技术一样&#xff0c;都是SUN公司定义的一种用于开发动态web资源的技术。JSP/Servlet规范。JSP实际上就是Servlet JSP这门技术的最大的特点在于&#xff0c;写jsp就像在写html&#xff0c;但它相比htm…

阿里新发布的UniAnimate现高效人像动画生成;在ComfyUI中使用Stable 3模型;音频版的gpt2o;将 PDF 文档转换为音频播客

✨ 1: UniAnimate 阿里新发布的UniAnimate通过统一的视频扩散模型&#xff0c;实现高效人像动画生成&#xff0c;支持长视频生成 UniAnimate 是一种专注于一致性人像动画生成的统一视频扩散模型。该模型通过映射参考图像、姿势指导和噪声视频到一个共同特征空间&#xff0c;实…

ZED双目相机环境配置

官方资料&#xff1a;stereolabs/zed-python-api: Python API for the ZED SDK (github.com) 1&#xff0c;配置ZED相机环境 1.安装CUDA 查看电脑是否安装CUDA&#xff0c;安装过程可参考以下博文&#xff1a; 如何选择匹配的CUDA版本&#xff1a;https://blog.csdn.net/iam…

MoCo v3(ICCV 2021)

paper&#xff1a;An Empirical Study of Training Self-Supervised Vision Transformers official implementation&#xff1a;https://github.com/facebookresearch/moco-v3 出发点 本文并没有提出一种新的方法&#xff0c;而是对计算机视觉领域最近进展中的一个重要且基础…

C++杂记

文章目录 前言cin1. getline(cin, str)2. cin.getline(arr, number)3. cin.get()4. cin >> variable_name5. cin.clear(); 动态内存1. 可以在动态声名数组的同时进行初始化。 数组1. &arr[0]2. &arr3. 数组输入指定长度和非数字时&#xff0c;停止 前言 在写《C…

C++中的组合模式

目录 组合模式&#xff08;Composite Pattern&#xff09; 实际应用 文件系统 组织结构 图形对象 总结 组合模式&#xff08;Composite Pattern&#xff09; 组合模式是一种结构型设计模式&#xff0c;它将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使…

沃尔玛自养号测评:优势与技术要求解析

沃尔玛自养号测评是一种卖家在沃尔玛平台上提升店铺权重和排名的营销手段。传统运营策略的局限性日益显现&#xff0c;如营销手段单一、难以应对市场竞争等。因此&#xff0c;许多卖家为了提升店铺权重和排名&#xff0c;选择了自养号测评这一技术手段。 以下是对沃尔玛自养号…

关于BERT和embedding

embedding到一个低维向量&#xff0c;但是需要回到onehot高维表示&#xff0c;所以大部分填词游戏最后都需要加上一个MLP接头。 word2vec如此简单的结构&#xff0c;学习到的是embedding 基于计数的统计方法和word2vec融合就形成了glove词嵌入模型 总结&#xff1a;通过各种…

消费者消费数据时报错:INVALID_REPLICATION_FACTOR

今天部署了kafka集群&#xff0c;三台服务器&#xff0c;启动后&#xff0c;生产者发送数据&#xff0c;消费者接收数据的时候报错&#xff0c;INVALID_REPLICATION_FACTOR。 查了很多资料&#xff0c;说是要改kafka下config目录的server.properties,可能是副本数太小&#xff…

WPS中XLS表格使用的技巧记录

我遇到一个问题&#xff0c;xls表格中有一列数据的格式是会计专用&#xff0c;金额前面有货币符号&#xff0c;但是我想复制这列数据为普通的文本&#xff0c;并且在后面添加.00&#xff0c;有什么方法可以做到。 TEXT(B2, “0”) & “.00”

【第5章】Stable Diffusion大模型(简介/两种版本/安装/模型推荐/使用方式)ComfyUI基础入门教程

🍩 大模型简介 大模型,又称为Stable Diffusion模型,所有Stable Diffusion的绘图都是以该模型能力为基础上进行的。 发展到现在,大模型已经更新了很多个版本,大家听说过的可能有Stable Diffusion 1.4(简称SD1.4),Stable Diffusion 1.5(简称SD1.5),Stable Diffusion 2.0…

【react】如何合理使用useEffect

useEffect 是 React Hooks API 的一部分,它允许你在函数组件中执行副作用操作,比如数据获取、订阅或者手动更改 DOM。合理使用 useEffect 可以帮助你管理组件的生命周期行为,同时避免不必要的渲染和性能问题。以下是一些关于如何合理使用 useEffect 的建议: 明确依赖项: 当…

【计算机视觉】人脸算法之图像处理基础知识(四)

图像的几何变换 图像的几何变换是指在不改变图像内容的前提下对图像的像素进行空间几何变换。主要包括图像的平移变换、镜像变换、缩放和旋转等。 1.插值算法 插值通常用来放缩图像大小&#xff0c;在图像处理中常见的插值算法有最邻近插值法、双线性插值法、二次立方、三次…

sap怎么批量给信息记录打上删除标识

1.MEMASSIN-----事务代码 2.选择完成字段 3.根据条件查询需要冻结的信息记录 4.输入查询条件 5.全部勾选完成标识&#xff0c;点击保存&#xff0c;即可冻结完成

特殊矩阵:零矩阵(Zero)幺矩阵(Ones)单位矩阵(Identity)随机矩阵(Random)#matlab

在MATLAB中&#xff0c;通用的特殊矩阵主要包括以下几种&#xff0c;每种都有其特定的函数来创建&#xff1a; 零矩阵&#xff08;Zero Matrix&#xff09; 使用zeros函数创建,&#xff0c;元素全为0。 格式&#xff1a; zeros(m, n): 创建一个m行n列的零矩阵。 zeros(n):…

盘点国内外免费AI视频工具,助你先人一步拥抱AI

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 6月13日&#xff0c;Luma AI 在 X 平台&#xff08;原 Twitter&#xff09;宣布其视频生成模型 Dream Machine 开放测试&#xff0c;并提供免费试用&#xff0c;这在海外 AI 圈掀起了一…