| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> C语言入门--指针 -> 正文阅读 |
|
[C++知识库]C语言入门--指针 |
这里写目录标题一.为什么会有指针我们把计算机的内存想象成我们大学的一个大型的宿舍楼,但是这个宿舍楼是没有门牌号的,然后我就是宿管,有一天我想找一个人这这个人叫张三,那么如果没有门牌号的的话,我是不是就得一个房间一个房间的找张三这个人,于是我过了好久才找到这个,于是上面的领导又要我找李四这个人,那么我就又得重头开始找李四这个人,等我废了九牛二虎之力找到李四之后,上面的领导又要我找叶超凡这个人,那么这个宿舍管理员是不是就又得从头开始一间宿舍一间宿舍的找叶超凡这个人,那么看到这里想必大家发现了一个问题就是宿舍要是没有门牌号的话,找一个人是不是就很难啊,又费时间又费力,那么我们要是有门牌号呢?比如说我要找张三这个人在401号寝室,那么我是不是就可以直接去那个寝室找这个人,无需再一间一间房间的查找了,那么我们计算机中的内存也是这样我们把每个内存单元添加上对应的编号,等计算机想要访问或者修改这个内存单元中的内容时就可以直接根据这个编号去查找无需一个一个的访问这样是不是大大的增长了我们的计算机的运行速度,那么这个编号又是如何来产生的呢?就拿32位的电脑举个例子,32位的电脑里面也就存在着32根地址线,而这些地址线通电就会产生电脉冲,那么高电压就是1,低电压就是0,那么一共有多少种情况呢?那是不是就是00000…00000(一共32个)到11111…111111(一共32个)这么多个地址,那是不是就是一共有2的32次方个地址编号,那我们再来看这些地址编号可以用来标记一个内存单元,那我们的内存单元的大小又是多少呢?我们先假设一个内存单元的大小是1bit,那我们来看这个内存的大小是多少:2的32次方等于4294967296,那内存也就是4294967296bit=536870912byte=524288kb=512mb=0.5gb那我们看这个假设是不是太小了,那我们再来看,如果一个内存单元是一个字节的话,那我们以此内推的话是不是也就是4gb,那我们这么看的话,我们就发现这个大小就十分的合理。所以我们的一个内存单元的大小就是一个字节。好看到这里想必大家已经有了初步的了解,但是这里出现了一个问题就是我们这里的数字太多了啊我们不可能每次写地址的时候都写32个数字吧,所以这里的地址我们就采用16进制的写法我们的10进制是0,1,2,3,4,5,6,7,8,9所以我们的16进制的是0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f,g这里的a就代表了10后面就依次类推。所以4个2进制的数字就代表了1个16进制的数字所以我们32个2进制的数字是不是就可以用8个16进制的数字进行代替,那我们的地址的编号的书写是不是就方便了很多比如说0x00000001这就用看起来方便了多,要是用2进制那是不是就很难看啊这里就不演示了大家 注意了这里的0x表示的是16进制.既然我们知道了地址是如何产生的,那么我们创建出来了一个变量是不是就相当于在内存中占据了一定的空间,我们前面也说了我们可以通过取地址操作符( & )来得到这个变量的地址,那么是不是就得将这个地址存起来,我们知道变量是可以存储值的,那么我们把存储地址的变量就称为指针变量,但是我们平时口头上就将其称为指针。好看到这里想必大家应该能够知道现在的一个逻辑就是我们的一个指针就对应了一个内存单元,我们一个内存单元的大小就是一个字节,我们把这个地址存到一个变量里面,那么我们就称这个变量为指针变量,这个指针变量在32位的机器下是4个字节的大小,在64位字节的机器下是8个字节的大小,因为我们4个字节就是32个比特位刚好能够将这个地址全部存下去,而8个字节就是64个比特位也就刚好能将64位的地址存下去,那么这里我们来看一下下面的代码:
我们来看一下运行的结果: 二.指针和指针的类型我们将不同类型的变量的地址取出来,然后放到指针里面,那么我们这里就会对应有不同的指针类型来接收这地址,比如说下面的例子:
我们这里创建了三个不同类型的变量,但是我们这里也得创建三个不同类型的指针来接收这些地址这时为啥列,既然都是地址而且大小还是一样的那么我们这里为什么不用同一类型的变量来进行接收呢?那么带着这个问题我们来看看下面的内容。 1.指针加减整数我们将上面的代码添加一点内容:
我们来看看这段代码的运行结果: 2.指针的解引用不同的类型的指针不仅仅是在加减整数的时候会有一些区别,在解引用的时候也会有那么一点点的不同,我们指针的类型并不会决定指针变量的大小,但是他能够决定我们的指针变量解引用时能够访问多少个字节,这句话是什么意思呢?我们通过下面的代码来理解:
首先我们创建一个变量a,并且将其初始化为十六进制的11223344,然后我们创建一个char类型的指针变量pa并且将a的地址强制类型转换为char*类型赋值给这个pa,然后我们再创建一个int*类型的指针变量并且将a的地址赋值给这个指针变量p,那么接下来我们就将这两个指针变量分别进行解引用将其值赋值为0,会发生什么呢?我们通过内存来看看这中间发生的过程:
我们通过监视来看看内存当中分部的情况是什么,我们首先看a 三.野指针我们首先来看看野指针的概念是什么:野指针就是指针指向的位置是不可知的(随机的,不正确的,没有明确限制的) ①野指针的成因1.指针未初始化我们来看看下面的这段代码:
我们创建了一个指针,但是我们并没有将他初始化,然后再将它解引用并且将一个值赋值进去,如果我们这么做的话,我们将程序运行起来就会发生问题: 2.指针的越界访问这个想必大家都非常的熟悉,我们在数组的时候就说过这方面的内容,那么这里我们再来重复的说一下,我们来看一下下面的代码:
我们可以运行一下就可以发现: 3.指针指向的空间释放啊这个原因如果想理解的更清楚的话,得等到我们说到动态内存管理会更好的理解,那么我们这里的话可以通过下面的代码来进行理解:
我们这里调用了一个函数,我们在函数里面创建了一个变量a,然后就将这个变量a的地址作为返回值传回我们的主函数并且我们这里又创建一个指针变量p来进行接收,那么这里我们就存在一个问题,我们这里返回来的地址是我们在函数里面创建变量时的地址,但是这个变量会随着我们函数调用的结束而自动进行销毁,那个地址对应的空间也就会还给我们的操作系统,那么这里我们把这个地址传给我们的主函数并且接收的话,是不是就有点不合理啊,就好比你和你的女朋友分手了你又找到了一个新的女朋友但是你还留着前任的各种照片电话已经家庭住址是不是有点不大合适呢?所以我们就把这种指针指向的空间释放的指针称为野指针,我们可以将这个函数打印出来看看:
我们来看看运行的结果: ②如何规避野指针1.对指针进行初始化这个很好理解我们创建一个指针的时候可以先将我们创建好的一些变量的地址赋值给我们的指针,这样就可以使得我们的指针指向的位置是固定的不是随机的,那么这时候有小伙伴说啊要是我没有变量的地址来给他进行初始化那该这么办呢?这里我们可以先将这个指针置为空(NULL)缓缓燃眉之急,因为我们的NULL对应的地址是是0地址,这个地址是不允许访问的所以我们只用等之后有合适的变量再来把这个变量的地址赋值给这个指针就可以了,就像这样:
2.小心指针越界啊这个就提了很多次了嘛,我们数组的下标是从0到数组的元素的个数减一,不要以为是从0到数组的元素的个数哈。 3.指针指向的空间释放即时置为空这里得等到学了动态内存开辟能够理解的更加的清楚,就是我们可以一些函数开辟一个动态的内存,我们将这个动态的内存的起始位置赋值给我们的一个指针变量,然而我们这个动态的内存在不用的时候是可以被我们人为的还给我们的操作系统的,但是还给了操作系统内存不属于我们这个程序里,但是我们那个指针变量依然记录了那个之前开辟的那个内存的地址,这就会照成一些麻烦出来,所以我们在释放这个内存之后得将这个指针的内容变成空指针这样就找不到原来开辟的那块空间了这样的话就不会造成一些麻烦,这就好比你找了一个新的女朋友,那个新女朋友怕你想前任直接一棍子把你敲失忆了,这样你就彻彻底底的忘记了她,我们这里的置为空指针就是这样的效果。 4,避免返回局部变量的地址这个就很好理解了,对吧我们在调用函数的时候前往不要把在函数里面创建的变量的地址作为返回值传回主函数就可以避免遇到野指针。 5.指针使用之前检查有效性我们上面如果你不知道将这个指针变量初始化为什么值的时候,你可以将这个指针的内容初始化为空指针(NULL),但是这个空指针也是不能访问的,这就好比我知道这野狗喜欢咬人我就将他栓到一棵树旁边避免它到处咬人,但是你非要跑到这颗树旁边撒尿这不是就吃饱了撑着嘛,所以我们这里就可以这么做,在使用这个指针之前检查指针的有效性,那么怎么进行检验呢?我们就可以用if语句看看它是不是空指针,我们来看看下面的代码就知道如何来检验了:
我们在使用之前先用先用if语句判断一下这个指针里面的内容是否为空就可以有效的避免我们解引用一个空指针而报错 四.指针的运算1.指针加减整数这个内容想必看到这里因该已经非常的熟悉了吧,这里就不多赘述了,我们这里直接通过一个例子来看一下巩固一下:
我们可以通过这个指针++得到每一个数组中的元素,将他依次赋值,所以我们这里的指针加一并不是将在地址的数值上的大小单纯的加一,而是跳过一个整型大小的数据,所以我们这里记住了,我们这里的指针加减整数得到的是跳过整数个该类型大小的数据的地址,具体是多少得取决于数据的类型 2.指针-指针指针减指针得到的结果是什么呢?我们知道指针是地址,那么这里地址减地址得到的难道是这两个地址大小的差吗?显然不可能对吧这地址差要的有啥用啊对吧,那么我们可以写这么一段代码来看看这指针-指针得到的结果是什么:
那么看到这里想必大家对指针-指针应该能够很好的理解,那么我们接着往下看。 3.指针的关系运算这个具体是啥意思呢?就是我们的指针其实也是可以用来比较大小的,比如说我们创建一个整型的数组里面有10个元素,那么我们第十个元素的地址就比我们第一个元素的地址大,那么我们就可以该性质来进行一些操作比如说我想将一个有10个整型元素的数组的他的内容初始化为0,那我们就可以这么做:
当然我们这里还可以这么进行比较:
这么写的话我们的理解条件就更低一些,但是这也同样会产生另一个问题就是我们这里的最后一次循环我们的p已经指向了第一个元素的地址了,由于我们循环的结束我们就会来到循环的调整部分,而这时的p就会指向数组的前面的一个地址,那么我们再来拿这个地址与&arr[0]来进行比较的话就会出现一个问题就是我们的标准规定允许指向数组元素的指针与指向数组最后一个元素的后面的那个内存位置的指针进行比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。虽然我们的标准是这么规定的,但是上面的那个代码在绝大多数的编译器下都可以使用,但是我以后还是得避免这样写代码,因为标准并没有保证他可行。 五.指针和数组看到这个内容想必大家对这个已经很熟悉了把,我们数组名就是数组首元素的地址,但是这里有两个除外就是第一个就是取地址数组名( &数组名)那么这个数组名就不再代表首元素的地址,而是表示整个数组的地址,第二个就是sizeof(数组名)这个数组名表示的也不是首元素的地址,而是整个数组。那么既然我们的数组名表示的是数组首元素的地址,那么我们是不是可以将这个数组名赋值给我们的一个指针然后通过这个指针来访问我们数组的元素呢?答案是可以的我们首先创建一个10个元素的数组,将数组的元素初始化为1,2,3…,8, 9, 10,然后再创建一个指针变量来接收这个数组名也就是首元素的地址,再通过for循环对这个指针变量加不同大小的数也就得到数组当中的每个数据最后将其一个一个的打印出来,那么我们的代码如下:
通过这个代码我们就发现一个现象就是我们的指针其实是可以与我们的数组进行相互交换的,我们既可以用数组的形式来打印我们数组的每个元素,我们还可以通过指针的形式来打印我们数组中的每个元素,那么我们数组中的下标引用操作符是不是就可以和我们的指针解引用操作符进行相互替换呢?答案是确实是可以的也就是说arr[1]的作用与*(arr+1)是一模一样的我们在写的时候哪个你看的舒服看的直接你就选择用哪种方法。 六.二级指针什么是二级指针,我们说一级指针是用来储存一个变量的地址的,那二级指针呢?难道是用来储存地址的地址的吗?啊当然不是的哪有地址的地址这个说法啊,我们的二级指针是用来存储一级指针变量的地址的,那么这二级指针变量的类型是什么样的呢?我们可以看一下下面的例子:我们创建一个整型的变量a,将a的地址放到一个整型的指针变量里面近进行储存,虽然是指针变量但是他总归来说还是一个变量,他也有对应的地址,那么我们就再通过取地址操作符将这个指针变量的地址取出来放到一个二级的整型的指针变量里面去,那么这个变量的类型就是int **我们来看一下完整的代码:
那么这里因为是二级指针所以有两个*,那么这两个*有着不一样的意思,右边的一个表示这个变量里面装的是地址,而左边的那个则和前面的int一起表示的是这个地址指向的那个空间的内容的类型是int*型,所以我们这里的两个*各代表着不同的意思,大家可以扩展到三级指针来理解理解,那么我们地址都取好了,那么我们该如何来使用这个二级指针呢?既然我们的一级指针得解引用一次,那么我们的二级指针就得解引用两次,我们下面就通过这个二级指针将我们变量a的值改成20,并且打印出来:
其实我们的二级指针还可以以另一种形式来表示我们的二维数组,我们知道二维数组可以表示成一个一维数组但是一维数组中的每个元素却又是一个数组,那么我们这里就可以这么想我们先创建三个一维数组,因为数组名是首元素的地址。那么我们就可以将这三个一维数组的数组名放到一个数组里面去,再构建出一个一维数组,那么这个数组里面装的就是我们的三个数组的三个首元素的地址,那么这个数组名又表示什么呢?还是首元素的地址,而我们的首元素也是一个地址,所以我们这里的数组名就是一个二级指针,那我们先将上面的内容用代码实现:
那么我们如何用二级指针来模仿我们这个二级数组呢?好既然他是一个指针并且还是一个二级指针,这个p指向的是int * arr[ 3 ]的首元素的地址,他的首元素就arr1这个数组,那么我们二级指针p进行解引用就会得到我们这整个arr1数组,也就是我们说的数组名,那么数组名arr1,又是arr1数组里面的首元素的地址,那么我们再对其进行解引用就可以得到这个arr1数组里面的内容,那么我们这里是得到arr1里面的内容,那我们如何得到arr2里面的内容呢?我们这里的数组名表示的是首元素的地址,那么我们这里的将这个地址加一是不是就得到第二个元素的地址了,那么再跟上面的操作一样是不是就可以得到arr2里面的每个元素了,那么我们的代码如下:
七.字符指针我们先来看看什么是字符指针,我们根据这么形象的名字就知道这个东西是个指针,这个指针指向的内容是字符,比如说下面这样:
我们创建了一个字符变量,将他的值赋值为字符’ w ',让后我们再将这个变量的地址取出来放到我们创建好的字符指针变量pc里面去,再通过解引用pc来改变我们的这个变量力面的值,再将我们变量ch里面的内容打印出来就可以发现我们这里的ch的值确实发生了修改,说明当我们一个变量里面就放的是一个字符的话我们就可以通过地址来修改这个字符变量里面的值,但是这里想必大家肯定讲过另外一种情况就是我们一个字符变量里面装了一个字符串,比如说下面这个情况:
我们上面这个字符变量里面就只装了一个字符,那这里为什么我一个字符变量里面能够装下一个字符串呢?难道这里pstr变成了一个数组了吗?但是按照逻辑来说这里应该是一个指针才对啊,怎么会变成一个数组了呢?所以这种猜想很明显就是错的,那么这里我们所创建的字符指针变量里面放的是我们这个字符串的首元素吧的地址,并不是整个字符串,因为我们这里的字符串他在内存当中是连续存放的,所以我们就可以根据这里的首元素的地址来找到其他元素的地址从而找到这个地址里面对应的内容,比如说这样:
我们就可以通过指针的方式来一个一个访问我们,那么我们之前说过其实我们的[ ]下标应用操作符在某些情况下其实是可以跟我们的指针相互转换的,那么我们这里试一下看能不能行
我们跑一下就知道,答案是完全是可以的,那么这里可能会有仔细的小伙伴们发现了这里的字符指针前面为什么要加一个const啊?那么这里原因很简单因为我们这里的初始化其实是将常量字符串放到外面的自读数据区里面的,这个区域里面的内容是不允许修改的,只允许读取,所以我们这里就这个指针变量的前面加上const来修饰表示不要想着通过指针的解引用来修改这个字符串里面的内容,所以我们这里也就相当于一个保护机制,那么看到这里我们来看一个面试题:
我们来看看这个打印出来的结果是什么,我们首先看到第一个if…else语句他问我们str1和str2的值是否相等,首先我们要知道的是这里的str1和str2是两个数组的数组吧名,也就是首元素的地址,那么我们这里是创建了两个不同的数组,那么我们这里肯定是不相同的啊,有同学就有疑问了说这里两个数组里面的内容都是一样的,因该地址是不相同的啊,那么这里我就要问同学一句,我们这里的内容目前确实是相同的,但是你能确保以后都相同吗,万一我下面要将这两个数组的内容进行修改的话,那么你觉得这两个地址相同合适吗?肯定不合适啊,就好比警察抓小偷你因为和小偷长的特别像就被警擦抓了起来,你会感到公平吗?显然是不公平的嘛对吧虽然我和小偷长的一样但是我们的性格血型基因等等方面都不一样吗凭什么说我是小偷呢?那么我们这里的数组也是一样的到,我们这里是向操作系统申请了两个不同的空间所以我们的地址不一样,所以就会执行下面的对应的else语句,那么我们这里再来看看下面一个的if语句,这里问我们str3与str4的是否相同,好那么这里我们先搞清楚一件事就是我们这里的str3和str4表示的是什么?是地址吗?不是!是这个str3和str4里面的内容,那么既然我们这里if语句比较的就是这两个变量里面的内容是否相同,内容是什么呢?是地址!那么有小伙伴们看到上面的例子就要说了,啊这里肯定是不相同的啊,我们这里会申请两个空间怎么怎么样所以这里面装的地址是不相同的,如果你要是这么想的话,那就错了我们仔细看看这两个变量里面放的是谁的地址?是我们的字符串的首元素的地址,这个字符串放的地方是哪里呢?是我们的只读数据区,那么这个数据区的特点是什么呢?只能读取数据不能修改数据,所以我们这里两个字符指针放的是内容相同的字符串的首元素的地址,那么你觉得我们这里有必要在只读数据区里面申请两个不同的地址来装这同一个字符串吗?答案是完全没必要所以我们这里虽然是创建了两个不同的字符指针变量,但是这两个变量里面放的值是相同的,那么我们最好来做一个对比,我们上面str1和str2为什么不同是因为我们这里的两个数组里面的内容可能会在后面的代码中可以修改成其他的值,所以这里得申请两个不同地址,而我们下面的str3和str4这里面装的是常量字符串放到了我们的只读数据区当中以后不会修改里面的值,所以我们这里就没必要申请两个不同的空间来装相同的字符串,对吧反正内容不会修改,所以我们这段代码运行的结果就是: 八.指针数组看到这个标题首先我想问大家一个问题就是我们这个是指针数组说的是指针还是数组啊?嗯?是不是有点迷惑啊,那么我再问你一个问题:好孩子这个词说的对象是孩子还是好啊,当然是孩子这个对象嘛,那么我们这里也是一样的,我们这里的指针数组说的对象是数组嘛,只不过这个数组里面装的内容是指针而已,那么我们如何来创建一个指针数组呢?我们来看看下面的例子:
我们这个代码当中就创建了三个字符指针,那么我们如何来理解这个代码呢?首先我们的变量名会跟[]先结合,这样就便是这个变量名表示的是数组,然后[ ]里面的数字则表示的是这个数组中的元素个数,那么这里数组名前面的就表示的是数组中元素的类型,比如说arr1前面的就表示这个数组中的每个元素的类型是int *型,也就是一个指向整型的指针,那么我们下面的arr2也是同样的道理表示数组的每个元素是一个指向字符的指针,我们的arr3则与上面的两个有点点不同他表示的意思这个数组中的每个元素的类型是个二级指针,这个二级指针指向的对象是一个char类型的指针,这个char类型的指针指向的对象又是一个char类型变量,那么看到这里想必大家应该对这个指针数组十分的了解了,那么我们接下来就来看看数组指针。 九.数组指针首先我们还是得先搞明白一件事情就是我们这个数组指针是什么?根据上面的内容我们知道这数组指针他是一个指针,这个指针指向的对象是一个数组,那么我们如何来描述这个指针呢?我们来看看下面两个代码:
大家觉得哪个是数组指针呢?因为我们上面才学了指针数组,所以我们这里很明显是第二个表示的数组指针,那么有小伙伴就感觉有点疑惑啊,为啥得加个括号呢?这时因为我们[ ]的优先级大于我们的*的优先级,也就是说如果我们不加括号的话这个这个变量名就会先跟这里的[ ]先结合这样的话就变成一个数组了,并不是指针所以我们就把变量名和*放到一个括号里面这样的话我们的变量就会和我们的*先结合这样就说明我们这里的p2是一个指针变量,我们将这里的*和变量名去掉剩下的就是这个指针指向的内容,我们上面的p2就表示这个指针指向的是一个数组,该数组有10个元素,每个元素的类型是int类型,看到这里想必大家应该能够理解数组指针是如何进行定义的了,那么我们接着往下看 十.数组指针的使用我们首先来看一段代码:
我们创建了一个整型的数组,然后我们将这个数组的地址放到我们的一个数组指针里面去,然后我们就想通过这个数组指针来打印我们的这个数组中的每个元素,那该怎么做呢?首先我们我们明白的一点就是这个指针指向的对象是一整个数组,那么我们对这个这个指向数组的指针进行解引用的话得到的是什么呢?应该是整个数组对吧,那整个数组又表示的是什么呢?就是我们这里的数组名,换句话说我们这里对数组指针解引用得到的就是数组名,而数组名又是首元素的地址,所以我们要想访问这个数组中的每个元素是不是就得对这个数组指针解引用之后加上常数来得到其他元素的地址,再对其解引用是不是就可以得到这个数组中的每个元素了,所以我们这里的代码形式就是
我们一般都是将数组的首元素的地址放到一个对应类型的指针变量里面再对其进行加减整数解引用这样也可以得到我们数组当中的每个元素,这样做的话我们过程就简便的多,而且上面的那个数组指针有点点多此一举的感觉对吧,那么我们这里我们举了一个错误的做法给大家,实际上我们的数组指针真正的用法应该是在传递二维数组的时候使用,我们之前说我们在传递数组的时候可以使用数组来进行接收,比如说下面的代码我们创建了一个二维数组,那么我们把这个二维数组作为参数往函数里面传的时候,就可以以数组的形式来进行接收就比如说这样:
但是我们还知道一点就是我们这个传的是arr就是我们所谓的数组,数组名是一个地址,所以我们这里不仅仅可以用数组来进行接收还可以使用指针的形式来进行接收,那么这个指针的类型是什么呢?我们知道这个数组名是一个地址,他表示的是首元素的地址,但是我们这里是一个二维数组啊,那么这里的首元素表示的就是我们二维数组当中的第一个数组的地址,也就是一个一维数组的地址,那么这里传过来了一个数组的地址,我们是不是就得用数组指针来进行接收啊,所以我们这里就可以这么写:
既然这里我们再来完成通过这个数组的指针来打印整个数组的内容,首先我们这里的arr表示的是是第一个一维数组的地址,那么我们对这个地址进行加一操作的话,就可以得到第二个一维数组的地址,那么我们再对这个数组进行解引用的话是不是就可以得到所谓一维数组的数组名,也就是首元素的地址,那么这时候再对其进行加一加二之类的操作是不是就可以得到这个一维数组当中的其他的元素的地址了,那么再对其进行解引用就可以得到这个元素的本身,那么我们的代码实现如下:
那么看到这里想必大家应该了解了如何使用数组指针,那么这里要提醒大家两个点,第一个就是我们创建一个数组的时候可以不在方括号里面填入数字,但是我们在创建数组指针的时候就必须得加了,不然系统会默认是0.第二点就是p如果是指向一个数组的地址的话,我们对这个指针进行解引用得到的是数组名也就是首元素的地址。那么接下来我们就来看看下面的代码,看看学完这些之后是否能分得清楚这些代码的意思:
我们首先看看第一个:arr这个表示的是什么呢?这就很简单了嘛表示这是一个数组,数组里面有5个元素,每个元素的类型为int类型,同样的道理parr也表示的是一个数组因为[ ]的优先级大于所以这里parr表示的就是一个数组,数组里面有10元素,每个元素的类型为int*,那么第三个就有点不一样了,因为和parr2放到了同一个括号里面所以这里的parr2会先跟*结合这样的话parr2就表示的是一个指针,那么我们将*和变量名去掉剩下的就是这个指针指向的对象,那么我们这里这个指针指向的就是一个数组该数组是10个元素,每个元素的类型是int类型,那我们继续看第四个parr3,因为这里将*和变量名和 [ 10 ]放到了同一个括号里面,所以根据优先级我们的变量名会先跟 [ 10 ]先结合这样的话我们这里的parr3表示的就是一个数组,那么我们将数组名和后面[10]去掉剩下的就是数组当中每个元素的类型,那么剩下的结果就是这样: 十一.数组参数1.一维数组传参那么这里我们就直接来看一个例子:
我们来看看这5个一维数组的传参是否正确首先我们来看前两个,我们说在传递一维数组的时候可以以数组的形式传递过去,然后使用数组的形式进行接收,那么我们这里test2就是这么做的,我们传了一个数组过去在函数里面直接用一个数组进行接收,那么我们还说过在使用数组进行接收的话我们是可以省略掉方括号里面的数字的,所以我们这里test1也是正确的,我们不仅可以使用数组的形式来进行接收我们还可以使用指针的形式来进行接收,因为我们的这里传过来的是arr,是数组名表示的首元素的地址,所以我们就可以使用指针的形式来接收,因为数组arr中的每个元素为int类型所以我们这里就可以用int *的指针来进行接收,所以我们这里的test3的接收形式也是正确的,我们再来看看test4和test5这里传递数组arr2,该数组中的每个元素的类型是int *类型所以我们这里依然可以采用用数组的形式进行接收这样的方法所以我们这里的test4也是正确的,那么我们再来看看test5这里是以指针的形式进行接收,因为我们这里传过来的是数组名arr2,表示的是首元素的地址,而我们这个数组的每个元素又表示的一个整型变量的地址,所以这里的arr2就相当于一个二级指针所以我们这里在进行接收的时候就可以用一个二级指针来进行接收,所以我们这里的test5也是正确的,那么看了五个例子想必大家对一维数组传参这个概念已经非常的明白了我们再来看看二维数组传参又会遇到哪些问题。 2.二维数组传参我们这里依然用例子来讲解二维数组传参:
我们的二维数组和一维数组有一个同样的性质就是我们可以以数组的形式传递也可以以数组的形式进行接收,所以我们这里test1,2,3就是以数组的形式进行接收,我们一维数组在进行接收的时候是可以省略方括号里面的内容的,我们二维数组在进行接收的时候也可以省略,只不过我们的二维数组不能完全省略我们以二维数组的形式进行接收的话是只能省略行的不能省略列所以我们这里的test2是错误的而test1和test3是真确的,我们再来看test4,我们这里传过来的是二维数组的数组名也就是这个二维数组的首元素的地址,所以我们这里是可以采用指针的形式进行接收的,那么我们这里的test4这个接收的指针的形式是正确的吗?当然错了哈因为我们的二维数组的首元素并不是一个简简单单的整型而是一个一维数组,所以我们这里得用数组指针的形式进行接收,那么我们这里的test4他是一个整型指针,所以我们这里的test4就接收错误了,同样的道理est5这里是以指针数组的方式进行接收所以也与传过来的参数类型不同所以test5的接收方式也是错的,我们再来看看test6这个指针的类型就是一个数组指针的类型所以我们的test6这种方式的传递就是正确的,那么我们这里的test7是真确的吗?我们的test7是用二级指针的形式进行接收,但是我们这里传过来的是一个数组指针啊,虽然这个指针指向的是一个数组但是不管怎么说他还是一个一级指针嘛,所以我们这里的传递一级指针用二级指针接收这样肯定是不对的,所以我们的test7的接收方式是错误的,那么看到这里想必大家对二维数组的传参有所了解了,那么我们接着往下看。 十二.函数指针首先我们来了解一下函数指针是什么?当然是一个指针这个指针指向的是一个函数,那么这里可能会有小伙伴感到疑惑了,啊?我们的指针里面装的是地址啊,既然我们的函数指针指向的是函数的话,那么这里首先函数得有地址啊,那么我们c语言的函数有地址吗?答案是有的,我们通过下面的代码可以得到验证:
我们将这段代码运行一下就可以发现我们这里的编译器确实会打印出来地址:
根据我们之前的经验想必大家这里肯定能够回答出来我们这里的pfun1的类型是对的,为什么呢?因为我们的()的优先级是大于我们的*的优先级的,如果你不加括号的话那么这里的pfun1就会先于()相结合这样的话就表示成了一个函数,而这个函数的返回值是void *的类型那么这样的话就不是一个指针了,所以我们就把变量名和*放到同一个括号里面这样的话他们之间就会先结合,这样的话就可以用来表示这是一个指针了,那么我们将这个变量名和*和包围他们的括号去掉剩下的就是这个指针指向的对象,那么这里剩下来的括号就表示这是一个函数,里面的类型就表示这个函数所需要的类型,那么我们这个函数是void不需要类型所以我们这里的括号里面就什么都不用填,括号前面的就是我们这个函数的放回值的类型,因为我们这里就打印了一句话没有返回的值所以我们这里就直接填入一个void,那么我们这里就再换一个例子:
那么这里我们要创建一个指针变量将Add的地址放进去,那应该怎么写呢?那么这里外面将这个指针变量的名字称为p,那么我们首先做的第一步是不是就是将这个变量的名字和*放到一个括号里面,那么这个括号里面还得加一个括号用来表示这个指针指向的是一个函数,而这个括号里面就会填入该函数所需要的参数的类型,因为这里有两个参数并且全部都为int类型,所以我们这里就得填入两个int并且用逗号将其隔开,那么我们这里的返回值的类型也为int所以我们就要在最前面加入一个int就可以了,那么这里该函数的指针变量创建的形式就是这样:
看到这个代码第一个感觉是啥?是不是感觉这个代码很奇怪啊,那么我们如何来分析这个代码呢?首先我们看到这个代码里面有一个数字0,那么这个0在这个代码中的作用是啥呢?首先我们能够想到的一点就是我们的数字在代码中可以当作参数进行传递,那么你觉得这里会是参数吗?很明显不可能为什么因为我们这里好像没有看到函数,那么何来参数这一说法呢?那么我们接着往下想我们数字还可以作为什么?是不是还可以作为地址啊,地址的话我们再对其进行接应用的话是不是就可以得到这个地址对应的内容呢?那么往这个0的前面看我们就会发现前面确实有两个*,但是有个*在括号里面,而另一个确实在外面,那我们先看在括号里面的这个,我们先来想想我们c语言当中的括号有什么作用?首先我们最熟悉的一点就是我们在操作符中提到的括号可以使得在表达式中可以先算括号中的表达式再算其他的表达式,那么这里很显然这个括号并不是这个作用,那么我们的括号还有一个作用就是函数调用,来讲括号里面的值传给我们的函数进行复制并且使用,那么这里很显然也不是的因为我们并没有看到什么参数,那么我们括号还有一个作用就是强制类型转换,哎如果是强制类型转换的话。那么我们这里就还真有一点想法呢!我们来看看这是什么?
我们有了上面的例子的经验,那么看这个题的时候就会简单一点点,首先我们可以看到signal这个变量名,但是这个变量名的类型是什么我们不知道,但是我们发现的是他会先跟后面的括号进行结合,而且我们还发现括号里面装的是一个类型int和一个void(*)(int),这个我们学过也是一个类型,如果一个变量后面紧跟一个括号,并且括号里面只装的类型的话,这说明了什么,这是不是就是我们的函数的声明啊,我们函数在声明的时候可以只写类型不写变量名的,那么我们这里的意思就是声明一个名为signal的函数该函数需要一个int类型的参数和一个函数指针类型的参数,但是我们还记得函数的声明是要写返回值的啊,那么我们这里有写返回值吗?当然有,我们把函数名和函数调用操作符以及里面的参数去掉剩下的就是我们这个函数的返回值那么我们这里去掉就成这样: 十三.函数指针数组看完上面的函数指针这个内容不知道大家有没有一种感觉就是我们这个函数指针没啥用啊,对吧用起来没有啥特殊的效果,反而还麻烦,那么如果你是这样的态度来看待函数指针的话,那么不妨来看看这个内容函数指针数组,看名字我们就知道这是一个数组,那么这个数组里面每个元素的类型是函数指针,我们如何来创建这个函数指针数组呢?就像这样
如果写成这样的话是编译不过去的哈,只能跟我们上面所述的一样,所以这里大家要注意一下。
小伙伴看看这段代码有没有发现一个很明显的问题就是我们这里的switch语句中的分支里面的成分好多都是一样的,除了我们调用的函数不一样其他的操作都是一样,那么这样的话是不是就很影响我们的这代码的整洁啊,明明很简单的内容非要搞得这么复杂,那么这时候有小伙伴们说L那我这里将这些相同的代码放到函数里面是不是就可以做到简洁的作用呢?答案是在switch语句中确实简洁了,但是我们在函数的实现过程就麻烦了啊,何必把一样的代码写好几遍呢?那么我们这里如何来进行简便呢?我们就可以用到函数指针数组和函数指针的知识点:我们先创建一个函数指针数组,将我们这里的各个函数的地址放到这个数组里面,等我们要用到这里面的函数的话就可以直接通过数组的下标进行访问拿到这些函数的地址,那么我们再通过这个地址访问函数就可以了,为了方便我们存储的顺序也和我们选择的顺序一样这样的话我们的代码就会简洁很多,那么我们的代码如下:
我们将代码改成这个样子是不是就感觉简洁多了,那么我们这里是用到了函数指针来调用我们的函数,用函数指针数组来存储我们的函数,那么大家看到这里的话是不是觉得函数指针还是挺有用的它有时候能够简化我们的代码,并且等我们学习的更加的深入我们后面用到的函数指针会越来越多,如果没有函数指针我们上面的代码就得不到简化。 十四.指向函数指针数组的指针这个这个想必大家就可以类比的推理了,那么这个就是一个指针这个指针指向的就是一个数组,该数组中的每个元素的类型都是一个函数指针,那么这里我们如何来声明一个这样的指针呢?我们就直接上例子:
这里就是创建了一个指向函数指针数组的指针,这里我们没有将其初始化,我们就来聊聊这里的创建规律是怎么样的,因为这个在最里面的括号里面所以我们的ppfunarr会先和*进行结合这样就表示这是一个指针,然后我们将这个*和数组名去掉就是这个指针指向的对象,因为这里的方括号的优先级较高所以会先和方括号进行结合这样这个指针就指向的是一个数组了,那么我们再将这个方括号去掉就是我们这里数组中的每个元素的类型了,那么我们这里的每个元素的类型为: 十五.模拟实现qsort函数我们之前说冒泡函数的时候说我们这个函数的功能就是可以将我们的整型数组中的元素按照升序或者降序的方式进行排序,但是我们这个冒泡函数有一个很明显的特点就是它视乎只能针对我们的整型数组,那如果我们是一个结构体是一个其他的类型的数组是否也能够做到使其内容升序或者降序的功能呢?如果对于冒泡函数来说当然不能,但是在我们c语言的库函数当中就有这么一个函数能够做到那就是我们的qsort函数,那么我们这里先来看看这个函数的参数的定义是怎么样的:
那么我们想将这个数组按照年龄的升序进行排序,那该如何去做呢?那么我们这里就可以用到我们的qsort函数,首先第一个参数得是我们的首元素的地址,那么我们这里就可以直接将我们的数组名传过去,因为我们的数组名就是我们首元素的地址,第二个参数就是这个数组的元素个数,那么我们这里就可以通过下面的代码算出我们这个数组当中元素的个数:
然后每个元素的大小是不是就可以直接用我们的sizeof(arr[0])来实现,那么我们先在唯一麻烦的就是我们这里的比较函数该怎么办,那么我们这里首先得再创建一个函数,然后这个函数的两个参数的类型都得是const void类型,然后就是我们的函数体的实现,这里我们首先得说明一件事情就是我们的额指针如果是void类型的话他是不能被解引用的,所以我们在函数里面使用这两个参数的话我们首先得做的事情就是将这两个参数的类型进行强制类型转换,那么我们这里是结构体所以我们这里就转换为结构体类型的指针,既然是这里是结构体类型的地址的话,那么我们这里是不是就可以直接用操作符来访问这个这个结构体里面的内容,再通过if语句进行判断比较,那么我们的代码如下:
既然我们的比较函数实现了,那么接下来就好办的多我们直接看代码:
再来看看我们的运行的结果:
那么这里大家要记住的一点就是我们这里的交换是一个字节一个字节的交换,因为这样我们就能保证不管你传过来什么样的数据都能够做到将其全部的内容都进行交换,那么这里我们的文章就结束了感谢观看。 |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 13:23:19- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |