深入理解并打败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.rhkb.cn/news/349949.html

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

相关文章

英语学习笔记36——Where ... ?

Where … ? ……在哪里&#xff1f; 词汇 Vocabulary beside prep. 在……旁边 同义词&#xff1a; near by 构成&#xff1a;be side side n. 边 搭配&#xff1a;side walk 人行道 例句&#xff1a;Bobby在我旁边。    Bobby is beside me. off prep. 离开&#xff…

《站在2024年的十字路口:计算机专业是否仍是高考生的明智之选?》

文章目录 每日一句正能量前言行业竞争现状行业饱和度和竞争激烈程度[^3^]新兴技术的影响[^3^]人才需求的变化[^3^]行业创新动态如何保持竞争力 专业与个人的匹配度判断专业所需的技术能力专业核心课程对学生的要求个人兴趣和性格特点专业对口的职业发展要求实践和经验个人价值观…

高考志愿填报选专业,兴趣爱好和就业前景哪个优先?

每个人都有自己的兴趣与爱好&#xff0c;而高考志愿填报是在为自己选择职业方向。最理想的状态就是把自己的兴趣和爱好与自己的职业统一起来&#xff0c;让兴趣和爱好促进职业的发展&#xff0c;为职业增添动力。但现实生活中&#xff0c;这种理想的状态并不是每个人都能达到的…

探索未来边界:前沿技术引领新纪元

目录 引言 一、人工智能与深度学习&#xff1a;智慧生活的引擎 1.医疗应用 2.智能家居 3.自动驾驶 二、量子计算&#xff1a;解锁宇宙的密钥 1.量子比特示意图 2.量子计算机实物图 3.分子模拟应用 三、生物技术&#xff1a;生命科学的革新 1.CRISPR-Cas9基因编辑图 2.合成生…

Python 学习 用Python第二册 第9章内容解八皇后问题

----用教授的方法学习 目录 1.八皇后问题 2.状态表示(抽象) 3.检测冲突 4.基线条件 5.递归条件 6.结尾 1.八皇后问题 深受大家喜爱的计算机科学谜题&#xff1a;你需要将8个皇后放在棋盘上&#xff0c;条件是任何一个皇后都不能威胁其他皇后&#xff0c;即任何两个皇后…

灾备建设中虚拟机细粒度恢复的含义及技术使用

灾备建设中为了考虑虚拟机恢复的效率与实际的用途&#xff0c;在恢复上出了普通的恢复虚拟机&#xff0c;也有其余的恢复功能&#xff0c;比如瞬时恢复&#xff0c;细粒度恢复等。这里谈的就是细粒度恢复。 首先细粒度恢复是什么&#xff0c;这个恢复可以恢复单个备份下来的文…

mysql中 什么是锁

大家好。上篇文章我们讲了事务并发执行时可能带来的各种问题&#xff0c;今天我们来聊一聊mysql面试必问的问题–锁。 一、解决并发事务带来问题的两种基本方式 1. 并发事务访问相同记录的情况 并发事务访问相同记录的情况大致可以划分为3种&#xff1a; 读-读情况&#xf…

ripro主题如何使用memcached来加速

ripro主题是个很不错的资源付费下载主题。主题自带了缓存加速开关&#xff0c;只要开启了缓存加速功能&#xff0c;正常情况下能让网站访问的速度提升很大。 但好多人这么做了却发现没啥加速效果&#xff0c;原因就在于wordpress里缺少了memcache文件。只需要把object-cache.ph…

蒂姆·库克解释Apple Intelligence和与ChatGPT合作的区别|TodayAI

在2024年全球开发者大会&#xff08;WWDC 2024&#xff09;上&#xff0c;苹果公司首席执行官蒂姆库克&#xff08;Tim Cook&#xff09;隆重介绍了公司的最新人工智能&#xff08;AI&#xff09;计划——Apple Intelligence&#xff0c;并宣布了与OpenAI的ChatGPT的合作。虽然…

AI 客服定制:LangChain集成订单能力

为了提高AI客服的问题解决能力&#xff0c;我们引入了LangChain自定义能力&#xff0c;并集成了订单能力。这使得AI客服可以根据用户提出的问题&#xff0c;自动调用订单接口&#xff0c;获取订单信息&#xff0c;并结合文本知识库内容进行回答。这种能力的应用&#xff0c;使得…

操作系统安全:Windows系统安全配置,Windows安全基线检查加固

「作者简介」:2022年北京冬奥会网络安全中国代表队,CSDN Top100,就职奇安信多年,以实战工作为基础对安全知识体系进行总结与归纳,著作适用于快速入门的 《网络安全自学教程》,内容涵盖系统安全、信息收集等12个知识域的一百多个知识点,持续更新。 这一章节我们需要知道W…

Pycharm社区版搭建Django环境及Django简单项目、操控mysql数据库

Web应用开发&#xff08;Django&#xff09; 一、配置Django环境 1、先通过Pycharm社区版创建一个普通的项目 2、依次点击”file"-->"Settings" 3、点击"Project:项目名"-"Python Interpreter"-"号" 4、在搜索框输入要安装的…

同三维T80005JEHVA 4K视频解码器

同三维T80005JEHVA视频解码器 可解1路4K30HDMI/VGA/CVBS1路3.5音频 可解电台音频网络流&#xff0c;可同时解4个网络流&#xff0c;分割输出 可预设十个流&#xff0c;任意切换1路流输出 <!--[endif]----><!--[if !vml]--> <!--![endif]----> 介绍&…

弹幕逆向signature、a_bogus

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 本文章未经许可禁止转载&a…

每日5题Day18 - LeetCode 86 - 90

每一步向前都是向自己的梦想更近一步&#xff0c;坚持不懈&#xff0c;勇往直前&#xff01; 第一题&#xff1a;86. 分隔链表 - 力扣&#xff08;LeetCode&#xff09; /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;…

企业化运维(2)_nginx

###1.nginx源码安装部署### ###2.平滑升级### &#xff08;1&#xff09;版本升级 当服务器在运行时&#xff0c;需要升级的情况下&#xff0c;平滑升级即就是不断开服务器就可以进行升级&#xff0c;最大限度保证数据的完整性。 下载nginx新版本软件&#xff0c;正常执行./c…

Day51 代码随想录打卡|二叉树篇---二叉搜索树的最小绝对差

题目&#xff08;leecode T530&#xff09;&#xff1a; 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 方法&#xff1a;本题计算二叉搜索树的最小绝对差&#xff0c;涉…

WordPress插件数据库批量替换内容工具插件

1、安装插件后&#xff0c;我们就可以在后台菜单看到工具操作界面 2、目前支持网站内容、标题、评论指定字符的快速替换 3、可以快速解决以往我们需要从MYSQL数据库命令替换的烦恼

Linux编辑器 vim使用 (解决普通用户无法进行sudo提权问题)

文章目录 一.vim是什么命令模式底行模式 二.关于vim暂停问题三.注释批量化注释批量化去注释 四.解决普通用户无法进行sudo提权问题五.vim的配置 一.vim是什么 用过VS的都知道&#xff0c;拥有着编辑器编译器调试.编写C&#xff0c;C&#xff0c;python等的功能。就是集成 Linu…

I/O Stream设计实验

实验要求和目的 深入理解java输入输出流相关类的基本用法&#xff0c;并且可以掌握Java程序的编写和调试。 实验环境 Java语言&#xff0c;PC或android平台 实验具体内容 设计和编写以下程序&#xff1a; 程序1&#xff1a; 从键盘读入多行字符串&#xff08;英文&#xf…