深入理解并打败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");
   else
      printf("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.mfbz.cn/a/710341.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

除了程序员,你又是谁呢?别说!保护自己能量最好的方式——早读(逆天打工人爬取热门微信文章解读)

你很困的时候&#xff0c;会不会遵循本心直接睡觉呢&#xff1f; 引言Python 代码第一篇 洞见 保护自己能量最好的方式第二篇 视频新闻结尾 引言 现在真的是越来越遵循本心了 昨天晚上10点多 觉得好困 但是又没有洗澡 然后就想着算了 躺一个 没想到一躺 早上6点了 起来速速洗刷…

2024年心理学研究、现代化教育与社会发展国际学术会议(PRMESD 2024)

2024年心理学研究、现代化教育与社会发展国际学术会议(PRMESD 2024) 2024 International Conference on Psychological Research, Modern Education and Social Development 会议地点&#xff1a;南京&#xff0c;中国 网址&#xff1a;www.prmesd.com 邮箱: prmesdsub-con…

浔川计算机v1.1——浔川python科技社

浔川计算机v1.1 import tkinter import math import tkinter.messageboxclass Calculator(object):# 界面布局方法def __init__(self):# 创建主界面,并且保存到成员属性中self.root tkinter.Tk()self.root.minsize(280, 450)self.root.maxsize(280, 470)self.root.title(浔川计…

LabVIEW 32位与64位版本比较分析:性能与兼容性详解

LabVIEW的32位和64位版本在功能、性能、兼容性和应用场景等方面存在差异。本文从系统要求、内存管理、性能、兼容性、驱动支持和开发维护等多个角度进行详细分析&#xff0c;帮助用户选择合适的版本。 一、系统要求 操作系统支持&#xff1a; 32位LabVIEW&#xff1a;可以在32位…

vue+elementUI实现在表格中添加输入框并校验的功能

背景&#xff1a; vue2elmui 需求&#xff1a; 需要在一个table中添加若干个输入框&#xff0c;并且在提交时需要添加校验 思路&#xff1a; 当需要校验的时候可以考虑添加form表单来触发校验&#xff0c;因此需要在table外面套一层form表单&#xff0c;表单的属性就是ref…

ComfyUI 宝藏插件之辅助工具

今天我们就来分享下这个 ComfyUI 辅助脚本工具的功能。 插件安装&#xff0c;小伙伴们直接在管理器里搜索「ComfyUI-Custom-Scripts」&#xff0c;点击安装就可以了&#xff0c;这里再告诉小伙伴们一个小技巧&#xff0c;点击名称可以跳转到插件所在的官网哦。 没有安装管理器…

Tdengine的时序数据库简介、单机部署、操作语句及java应用

Tdengine的时序数据库简介、单机部署、操作语句及java应用 本文介绍了Tdengine的功能特点、应用场景、超级表和子表等概念&#xff0c;讲述了Tdengine2.6.0.34的单机部署&#xff0c;并介绍了taos数据库的常见使用方法及特色窗口查询方法&#xff0c;最后介绍了在java中的应用。…

AI助力密码安全:利用机器学习提升密码安全性

信息安全已经成为了当今数字世界的一个核心问题&#xff0c;随着互联网技术使用场景的不断增加&#xff0c;创建和管理安全的密码已经成为了保证在线账户安全的关键要求。本文将研究和探讨如何利用人工智能&#xff08;AI&#xff09;和机器学习技术来提升密码的安全性。 学习目…

xgo 原理探索

Go 单测 mock 方案 Mock 方法原理依赖优点缺点接口 Mock为依赖项定义接口&#xff0c;并提供接口的 Mock 实现。需要定义接口和 Mock 实现。灵活&#xff0c;遵循 Go 的类型系统&#xff1b;易于替换实现。需要更多的样板代码来定义接口和 Mock 实现。Monkey Patching&#xf…

深度学习网络结构之---Inception

目录 一、Inception名称的由来 二、Inception结构 三、Inception v2 四、Inception v3 1、深度网络的通用设计原则 2.卷积分解&#xff08;Factorizing Convolutions&#xff09; 3.对称卷积分解 3.非对称卷积分解 五、Inception v4 一、Inception名称的由来 Inception网…

推荐一款好用的读论文软件操作方法

步骤&#xff1a; 1. 使用一译 —— 文档和论文翻译、对照阅读、讨论和社区 2.上传自己想要翻译的论文即可。 示例 Planing论文双语翻译 1.1 Parting with Misconceptions about Learning-based Vehicle Motion Planning 中英文对照阅读 1.2 Rethinking Imitation-based Pl…

3.多层感知机

目录 1.感知机训练感知机XOR问题&#xff08;Minsky&Papert 1969&#xff09; AI的第一个寒冬总结 2.多层感知机(MLP)学习XOR单隐藏层&#xff08;全连接层&#xff09;激活函数&#xff1a;Sigmoid激活函数&#xff1a;Tanh激活函数&#xff1a;ReLu 最常用的 因为计算速度…

AMSR-MODIS 边界层水汽 L3 每日 1 度 x 1 度 V1、V2 版本数据集

AMSR-MODIS Boundary Layer Water Vapor L3 Daily 1 degree x 1 degree V1 (AMDBLWV) at GES DISC AMSR-MODIS Boundary Layer Water Vapor L3 Daily 1 degree x 1 degree V2 (AMDBLWV) at GES DISC 简介 该数据集可估算均匀云层下的海洋边界层水汽。AMSR-E 和 AMSR-2 的微波…

使用libcurl实现简单的HTTP访问

代码; #include <stdio.h> #include <stdlib.h> #include <curl/curl.h> // 包含libcurl库 FILE *fp; // 定义一个文件标识符 size_t write_data(void *ptr,size_t size,size_t nmemb,void *stream) { // 定义回调函数&#xff0c;用于将…

MGRS坐标

一 概述 MGRS坐标系统&#xff0c;即军事格网参考系统&#xff0c;是北约(NATO)军事组织使用的标准坐标系统。它基于UTM&#xff08;通用横向墨卡托&#xff09;系统&#xff0c;并将每个UTM区域进一步划分为100km100km的小方块。这些方块通过两个相连的字母标识&#xff0c;其…

华为云开发者社区活动-基于MindNLP的ChatGLM-6B聊天机器人体验

MindNLP ChatGLM-6B StreamChat 本案例基于MindNLP和ChatGLM-6B实现一个聊天应用。支持流式回复。 本活动通过配置环境&#xff0c;模型接入&#xff0c;以及gradio前端界面搭建&#xff0c;实现了聊天机器人的功能。 以下是一些体验记录&#xff1a; 有兴趣的可以通过以下链…

详细解析找不到msvcp120.dll文件的原因及解决方法

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“找不到msvcp120.dll”。这个错误提示通常出现在运行某些程序或游戏时&#xff0c;给使用者带来了困扰。那么&#xff0c;究竟是什么原因导致了这个问题的出现&#xff1f;又该如何解决呢&a…

【每日刷题】Day64

【每日刷题】Day64 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. LCP 67. 装饰树 - 力扣&#xff08;LeetCode&#xff09; 3. 1315. 祖父节点值为偶数的节点和 - 力…

PyQT5 中关于 QCheckBox 的勾选状态的一点小细节

一、QCheckBox 是 PyQt5 中的一个用于创建复选框的控件&#xff0c;以下是其一些常见方法和属性&#xff1a; setChecked: 设置复选框的选中状态。isChecked: 检查复选框是否被选中。text: 设置或获取复选框的文本。state: 获取复选框的状态&#xff08;无、选中、不可用等&am…

公差基础-配合(互换性和测量基础)-2

过盈配合&#xff1a; 配合的种类&#xff1a; 三种&#xff1a;间隙&#xff0c;过渡&#xff0c;过盈配 间隙配合&#xff1a; 过盈配合&#xff1a; 过渡配合&#xff1a; 间隙量&#xff1a;最大间隙减去最小间隙&#xff1b; 配合的公差怎么算&#xff1a; 练习&#xff…