首页 > 其他分享 >指针(全解)—C语言进阶

指针(全解)—C语言进阶

时间:2025-01-11 16:01:47浏览次数:3  
标签:进阶 int 函数 数组名 C语言 地址 数组 全解 指针

  • 当指针变量存储字符串时,将首字母的地址赋值给p,当printf打印时,找到首地址,一直打印到 \0 停止。

  • 字符串为常量字符串,有的编译器会报警告,因为*p存的是a的地址,当我解引用对值修改时,编译器会崩溃,我们可以用const来修饰,此时*p是不能修改的,就保护了字符串。

  • 例题

  • abcdef为常量字符串,常放在内存的只读区,不需要创建多份,p1和p2同时指向他,所以p1=p2
  • 而arr1和arr2为两个独立的数组,需要开辟自己的内存空间,所以不相等

  • 指针数组(数组)

  • 整形数组:存放整型的数组
  • 字符数组:存放字符的数组
  • 指针数组:存放指针的数组

  • 模拟一个二维数组(但不是二维数组,二维数组在本质上是连续存放的,但创建的3个数组内存不是连续存放的)
  • 定义几个数组,将数组名存放在 指针数组中,数组名就是数组的首地址,通过数组首地址就可以拿到数组中的各个元素

  • 通过循环拿到数组的首地址,对首地址进行偏移然后通过解引用得到数组中各个元素的值

  • 数组指针

  • []的优先级高于*号,所以必须加上()
  • 因为[]的优先级高,p1为一个数组,*代表为指针,int表示指向的是int类型
  • 因为()所以p2为指针变量,指向一个大小为10的整型数组

  • 再次讨论数组名

  • 数组名通常为数组元素的首地址
  • 但有两个例外
  • 1.sizeof(数组名)sizeof中只有数组名时,数组名 表示整个数组,计算的是整个数组的大小(单位是字节)
  • 2.&数组名,这里的数组名仍然为整个数组,所以&数组名取出的是整个数组的地址
  • 整个数组的地址+1 会跳过整个数组长度

  • 数组指针来源理解

  • 数组指针是指向数组的地址
  • &arr 整个数组的地址,*p2代表为指针,指向一个10个元素的数组,数组中元素类型为int型
  • 去掉变量名就是变量的类型 所以数组指针类型为 int(*)[10]

  • 练习:

  • &arr 取数组的地址,*pc代表为指针,指向一个5元素的char* 类型的数组
  • 特别注意:与二级指针不同,二级指针是指向一级指针地址的指针。如下面注释所示,p1为一级指针,p2指向一级指针p1的地址,所以p2为二级指针

  • 数组指针在定义时要给指向数组的数组大小,因为在数组的视角知道数组的大小,但在数组指针的角度不知道数组的大小,所以要写清楚大小。

正确更改后:

  • 如果我想打印数组各个元素的值:
  • 错误示范:

建立一个数组指针,计算数组的大小,p指向数组,所以*p解引用出来是 数组名(可以理解为整个数组)而数组名又是数组首元素的地址,对其进行偏移在解引用就可以得到数组每个元素的地址。

  • 正确示范:

定义一个指针直接指向数组的首元素,对指针进行偏移,得到数组的每个元素。

  • 数组传参

  • 1.数组传参,在形参的时候就写成数组来接受

  • 数组名又是数组的首地址所以可以指针传参

arr数组名表示数组首元素的地址

二维数组的首元素就是他第一行的地址,也就是一个以为数组,所以在函数中用一个数组指针类型来接收

用int(*p)[5]来接收二维数组的一行的地址

*p得到二维数组的第一行,什么可以代表一整行呢,那就是数组名,但数组名又是一维数组首元素的地址,所以*(*p + j) 可以遍历数组的一整行,对p进行偏移*(p+i)得到的是数组的每一行的首地址,最后可以使用for循环*((*p+i)+j)来遍历打印二维数组。

也可以写成p[i][j], p[]就是偏移后再解引用,表示方式与上面不同,但含义是相同的。

  • 上面两种数组的传参形式本质上是相同的,在直接写成数组的形式本质上数组名就是指针,当使用第一种写法时,编译器也会转化为第二种写法来计算。

  • Parr3是存放数组指针(指向数组的指针)的数组,parr3存放10个元素,每个元素指向一个空间大小为5的数组。

  • 4.数组参数和指针参数

  • 4.1  一维数组传参

一维数组传参形参部分可以是数组也可以是指针。

数组传参形参部分用数组接收没问题,

Arr2  为一个指针数组,存放20个指向int型的指针。

Arr2 存放的是指向int类型的指针,而arr2数组名又是指向首元素的地址,而二维指针是用来存放一级指针的地址,所以用二级指针**arr来接收没有问题。 

  • 4.2 二维数组传参

    • 数组传参形参部分用数组接收:

没问题,但二维数组传参用数组接收时,不能同时省略行和列,形参的二维数组行可以省略,列不能省略(多维数组只能省略第一维)

  • 数组传参形参部分为指针接收:

第1个:不可以,因为二维数组,数组名为第一行的地址,所以不能用一个指向int类型的指针来接收。

第2个:不可以,因为int * arr[5]为指针数组,接收的类型不能数组要是指针类型

第3个:可以,int (*arr)[5]为数组指针,指向的是二维数组第一行的5个元素

第4个: 不可以,二维指针是用来存放一级指针的地址的,而arr指向的是二维数组第一行的5个元素是一个一维数组

  • 4.3  一级指针传参

  • 一级指针传参,形参只需要一个与传递参数类型的指针来接收就可以。
  • 反过来推:
    • 如果函数参数是一个指向int的一级指针,那我传的参数可以是(1)一个int型变量的地址(2)一个指向int型的一级指针(3)一个存放int型数组的数组名

  • 4.4  二级指针传参

  • 正着想:p为一级指针 pp存放p的地址为二级指针,传参二级指针,用ptr二级指针来接收可以;

函数传参传一级指针的地址&p,用ptr 二级指针来接收也可以。

  • 反过来想:

如果函数形式参数是二级指针,可以传什么实参

(1)一个一级指针的地址

(2)二级指针变量

(3)存放一级指针的指针数组的数组名

  • 5.函数指针

和数组指针进行类比

函数指针—指向函数的指针

数组指针—指向数组的指针

  • &数组名 — 取出的是数组的地址

&函数名 — 取出的是函数的地址

&函数名和直接函数名两种和数组不同,这两种都是取到函数的地址

对于函数来说,&函数名和函数名都是函数的地址

  • 取函数名就是取函数的地址,*pf表示指针类型,(int,int)表示指针传参为int和int类型数据, 前面的int表示函数的返回值为int 类型。
  • 对pf指针解引用找到的是函数然后传参(2,3),函数返回值用ret来接收

  • 这里pf前面的 * 可写可不写,因为还有另外一种写法 int (*pf)(int,int)= Add;

这里就是把Add赋给pf,在函数调用时都可以用Add直接调用,也可以用pf直接调用,但如果要写 * 号就一定要用括号括起来,因为不用括号括起来就是 * pf(),后面()的优先级高于*的优先级,会把函数的返回值解引用,产生错误。

下面3种方法都可以调用函数。说明在函数调用时的*可以不写但在定义时的*要写,*说明pf为指针

  • 例子

可以实参传函数名就是函数的地址,用一个函数指针来接收,在另一个函数中就可以直接解引用使用。

对下面代码的解释:

1.

 void  (*)()是一个函数指针类型,例如void(*p)() 中p是一个函数指针变量去掉p就是变量的类型,

第1步将0强制转化为void(*)()类型:无参,返回值为void的函数就就令这个为a

第2步调用0地址处的这个函数a(),那么这就是一次函数调用。

2.

我们可以用typedef 来重命名一个复杂的函数类型就像用typedef来重命名unsigned int为uint,这里可以为了方便理解重命名void(*)int 但将代码写成typedef void(*)int  pf_t 就会报错所以将代码写成typedef void(*pf_t)int 意思就是把void(*)int类型重命名为pf_t,那么就可以将代码简化为pf_t signal (int ,pf_t),那么这就是一个函数声明。

  • 函数指针的用法

    • 例如写一个计算器,会出现很多重复的代码,这样我们会封装函数,我们如果每个都分别封装还是买没有解决根本的问题,那么我们封装函数形参使用函数指针传指针的地址,就可以解决这个问题

修改后的代码:

  • 函数指针数组

函数指针数组存放函数指针的数组,在函数指针定义的基础上加一个[n]函数指针变量先与[n]结合表示数组,去掉arr[5]就是数组中存放元素的类型int(*)(int, int)

那么我们遍历数组看看函数可不可以被调用

  • 函数指针数组的用法:

对上面计算器的代码用函数指针数组的方式修改

我们建立一个数组,数组中对应下标与计算器中的每个操作对应的数字相同,这样我们就可以使用[intput]来调用相对应的函数操作。

代码的修改如下:

  • 指向函数指针数组的指针

&pfArr取函数指针数组的地址,就是整个数组的地址注意与数组名区分

我们在函数指针数组的基础上修改为指向函数指针数组的指针,将(*ppfArr)表示为一个指针,出来看到[5]表示指向一个存放5个元素的数组去掉(*ppfArr)[5],剩下int(*)(int, int),为存放数组中元素的数据类型。

  • 回调函数

定义:通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传给另一个函数,当这个指针用来调用其所指向的函数时,我们就说这个是回调函数。回调函数不是由该函数的实现方法直接调用,而是在特定的事件或者条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

  • 冒泡排序,简单的冒泡排序只能排序整数。

优化:如果我们发现在一趟中一次数据交换都没有发生,说明该数组就是有序数组,不需要进行数据交换,可以不进行下面的数据比较直接返回,这里我们使用flag来看是否进去数据交换的代码块中,进入则变为0表示数据进行了交换,则不是有序数组。

  • qsort函数

c语言库<stdlib.h>中自带一个qsort比较的函数,可以排序任意类型的数据,算法思想是基于快速排序的思想实现的。

qsort要比较任意类型的数据,在比较不同类型数据时的比较方法也是不同的,所以在qsort最后一个参数传对应类型数据比较的函数地址。

对于形参中比较函数的要求,若e1x小于e2则返回值<0,e1等于e2则返回值 =0,e1大于e2则返回值>0

形参中的比较函数,参数是const void * 不可以直接解引用

  • Void * 的说明

Void*是无具体的类型指针,可以接收任意类型的地址,或者创建指针变量时不知道创建那种类型的指针,先创建void*类型的指针,后面在把其他类型的指针赋值给他,void*类型的指针不可以进行指针操作例如(解引用,和=-整数等)

对比较函数void*进行数据类型的强制转换为(int *)在进行解引用操作

对上面的代码进行简化

若要是使用qsort函数来降序排列

因为qsort默认为升序排列,将参数中的排序函数进行反逻辑就是降序排列,为什么可以这样直接更改逻辑呢?

因为这个比较函数是我自己提供的,我可以随意修改这个逻辑来达到我最终的排序目的。

  • 测试使用qsort函数来排序结构体数据

比较字符串,要用strcmp,而strcmp函数比较的返回值和qsort的形参函数返回值的要求相同。

  • 那么strcmp函数是怎么比较字符串的呢?

例如abduaidba 和abec比较,从第一个字符开始比较,比较到第三个字符e大于d,那么这个strcmp函数就会返回一个小于0的数,正好符合qsort的形参函数返回值的要求

strcmp 的返回值是一个整数,表示两个字符串 s1 和 s2 的比较结果:

如果返回值为 0,表示两个字符串相等。

如果返回值小于 0,表示第一个不同的字符在 s1 中的 ASCII 值小于 s2 中对应的字符。

如果返回值大于 0,表示第一个不同的字符在 s1 中的 ASCII 值大于 s2 中对应的字符。

比较是从左到右逐个字符进行的,直到遇到不同的字符或到达字符串的结束符 \0。如果两个字符串完全相同(包括长度),那么它们被认为是相等的。

  • 使用冒泡排序的思想来实现各种数据类型的排序

不知道要比较什么数据类型的数据,所以用void* 类型来接受初始位置,width(比较数据所占空间的大小),cmp就是上面和qsort的比较函数相同。

函数的主要实现功能为数据交换,在内循环中实现,cmp通过传进来的函数指针来调用函数如果返回值大于0,就实现交换的操作,这里cmp的参数强制转化为最小的char*,然后通过传进来的width来控制指向的位置(通过改变偏移量),sawp函数也是以最小单位一个字节来进行交换的,交换的次数由要比较数据所占内存大小决定。

标签:进阶,int,函数,数组名,C语言,地址,数组,全解,指针
From: https://blog.csdn.net/m0_74696257/article/details/144557947

相关文章

  • 时间复杂度和空间复杂度(全解)——数据结构
    目录1--时间复杂度和空间复杂度计算1.什么是时间复杂度和空间复杂度?1.1算法效率1.2时间复杂度的概念1.3空间复杂度的概念1.4复杂度计算在算法的意义2.1如何计算常见算法的时间复杂度?2.2大O的渐进表示法推导大O阶方法:2.3常见时间复杂度计算举例实例1:实例2:实例......
  • 突破跨境电商瓶颈:亚矩阵云手机应用全解析
    跨境电商运营:机遇与挑战并存近年来,跨境电商行业发展势头迅猛,已成为我国对外贸易和全球经贸领域的新亮点。然而,跨境电商在运营过程中也面临着诸多痛点。其中,多账号管理问题尤为突出。许多电商平台严禁商家使用多个账号进行跨境营销,一旦检测到同一设备或IP下登录多个账号,就可......
  • JWT揭秘:前后端安全与双Token策略全解析
    一、JWT概述1.1什么是JWTJWT(JSONWebToken)是一种开放标准(RFC7519),用于在各方之间安全地传输信息。JWT以紧凑且自包含的方式传递信息,使得用户在前后端交互中可以安全、有效地传达身份验证和授权的信息。JWT通过其数字签名确保数据的完整性和真实性,使得信息在传输过程中可防......
  • P1433 吃奶酪(C语言)
    题目描述房间里放着 n 块奶酪。一只小老鼠要把它们都吃掉,问至少要跑多少距离?老鼠一开始在 (0,0)点处。输入格式第一行有一个整数,表示奶酪的数量 nn。第 2 到第(n+1) 行,每行两个实数,第 (i+1)(i+1) 行的实数分别表示第 ii 块奶酪的横纵坐标 xi,yi。输出格式......
  • 咱们继续学Java——高级篇 第六十三篇:之XSLT转换示例程序全解析
    咱们继续学Java——高级篇第六十三篇:之XSLT转换示例程序全解析在Java编程的学习道路上,我们始终携手共进,不断深入探索知识的领域。此前我们学习了XSL转换(XSLT)的高级应用及在Java中的实现原理,今天我们将全面解析文档中的示例程序TransformTest,深入理解如何在Java中应用XSLT......
  • 嵌入式C语言:二维数组
    目录一、二维数组的定义二、内存布局2.1.内存布局特点2.2.内存布局示例2.2.1.数组元素地址2.2.2.内存布局图(简化表示)2.3.初始化对内存布局的影响三、访问二维数组元素3.1.常规下标访问方式3.2.通过指针访问3.2.1.指向数组首元素的指针3.2.2.指向单行元素......
  • CSS进阶
    CSS进阶@规则at-rule:@规则、@语句、CSS语句、CSS指令import@import"路径";导入另外一个css文件charset@charset"utf-8";告诉浏览器该CSS文件,使用的字符编码集是utf-8,必须写到第一行web字体和图标web字体用户电脑上没有安装相应字体,强制让用户下载该字体使用@f......
  • 回顾c语言中main函数参数的妙用
    代码为:1#include<stdio.h>23intmain(intargc,char**argv)4{5inti=0;6for(i=0;i<argc;i++){7printf("%s\n",*(argv+i));8}9printf("%d\n",argc);10printf("%s\n",*ar......
  • HTML进阶
    HTML进阶iframe元素框架页通常用于在网页中嵌入另一个页面iframe可替换元素通常行盒通常显示的内容取决于元素的属性CSS不能完全控制其中的样式具有行快盒的特点在页面中使用flashobjectembed它们都是可替换元素MIME(MultipurposeInternetMailExtensions)多用......
  • C语言实现字符串替换函数
    #include<stdio.h>#include<stdlib.h>#include<ctype.h>#include<string.h>//字符串替换函数/*********************************************************************Function:my_strstr()*Description:在一个字符串中查找一个子串;*Input:p......