做电商哪几个设计网站比较好,深圳宝安大型网站建设公司,手机网站 制作技术,如何建立一个永久网站目录 1.字符指针
2.指针数组
3.数组指针
4.数组传参与指针传参
一维数组传参
二维数组传参
一级指针传参
二级指针传参
5.函数指针
6.函数指针数组
7.指向函数指针数组的指针#xff08;了解即可#xff09;
8.回调函数
回调函数的应用#xff1a;库函数qsort
…目录 1.字符指针
2.指针数组
3.数组指针
4.数组传参与指针传参
一维数组传参
二维数组传参
一级指针传参
二级指针传参
5.函数指针
6.函数指针数组
7.指向函数指针数组的指针了解即可
8.回调函数
回调函数的应用库函数qsort
模拟实现库函数qsort 1.字符指针
允许用字符串来初始化字符指针
char*pabcdef这个语句是正确的他表示把后面字符串首元素地址放到指针变量p里面去。其中abcdef是一个常量字符串
看下面这个代码 刚上来的两句是创建了两个字符数组并用hello bit来初始化他们这两个数组的创建必然要申请两块不同的内存空间而打印的条件是str1str2而数组名是数组首元素地址所以不满足if的条件因此会打印str1 and str2 are not same。
然后是用常量字符串初始化字符指针的操作对于常量字符串hello bit来讲他存放在内存的代码段他的地址是固定的用他的地址来初始化str3和str4因此str3和str4里面存放的都是常量字符串hello bit的首元素地址因此打印的是str3 and str4 are same。
2.指针数组
指针数组顾名思义就是用来存放指针的数组本质上还是一个数组用来存放一组类型相同的指针。
比如这样一个代码 他们在内存中存放的形式如图 如果拿到了arr[0]也就是arr数组的第一个元素里面存的是arr1他指向了arr1数组也就是说arr[0]就是arr1arr1[0]表示arr1数组的首元素又因为arr1就是arr[0]所以arr[0][0]就是arr1的首元素这看起来像一个二维数组同理arr[1][0]就是arr2数组的首元素。
虽然形式上跟二维数组一样但是指针数组跟二维数组还是有很大区别的二维数组在创建的时候在内存中被分配的空间是连续的而这个指针数组再创建的时候就是存了三个指针这个指针指向了三个数组这三个数组并不一定连续。也就是说对于一个三行五列的二维数组假设数组名为arr来说arr[0][4],arr[1][0]的地址是连续的但是对于上面的指针数组来说arr[0][4]是arr1的第五个元素而arr[1][0]是arr2的首元素由于arr1和arr2在向内存申请空间的时候都是随机申请的大概率不连续因此这个指针数组arr的arr[0][4]和arr[1][0]也是大概率不连续的。
3.数组指针
类比一下我们熟悉的其他类型指针比如int *p*代表p是一个指针int代表p指向的数据是int类型的数组指针也类似比如int *p[10],*代表p是一个指针去掉*p之后剩下的int [10]是其指向数据的类型这是一个数组类型。因此p就叫做数组指针变量里面存放的是一个数组的地址。p的类型我们写作int(*)[10]。
在对数组指针变量进行初始化的时候应该写成 int(*p)[10]arr;
可以这样理解首先得有个指针变量p然后他前面得有个*表示他是一个指针此时如果没有p就会和[]结合因此我们加上括号让p和*结合代表他是一个指针。指向的元素是int [10]类型的存放的内容是arr的地址。同时印证了这个数组指针的类型是int(*)[10],因为去掉指针变量的名字就是他的类型。
数组指针的使用
先来看一个不合适的使用方法 说我们要打印这个数组的每个元素这里代码使用了数组指针的方式首先arr代表取出整个数组的地址arr在一般情况下表示数组首元素地址但是有两个例外一个是arr单独放在sizeof内部另一个数arr这两种情况下arr代表的是整个数组既然是一个数组的地址我们就要放在数组指针里面因此我们创建了一个int(*)[10]类型的指针变量p用来存放arr的地址然后我们要打印arr中的每个元素肯定要对p进行解引用*p拿到了arr可以理解成和*抵消了要想打印arr中的每个元素我们现在知道了arr的首元素地址因为这里拿到arr是通过解引用并不属于上面的两种特殊情况因此这里arr就是数组首元素地址是int*类型的想要打印直接就*arri即可。也就是说打印的时候应该**pi非常的别扭。因为我们想要打印整个数组的内容直接使用一个int*类型的指针即可这里却使用了数组指针显然是不合理的那么数组指针到底应该怎么用
这里直接给出答案数组指针在访问二维数组的时候比较方便。
假如有这样一个二维数组arr 当然这只是为了方便理解才画成这样实际上因为二维数组中每个元素在内存中是连续存放的他们应该排成一条线。
二维数组可以理解成里面有三个一维数组也就是说二维数组是一维数组的数组。而二维数组的数组名也代表首元素的地址首元素又是一个一维数组因此二维数组的数组名是一个一维数组的地址我们如果想要把他存起来应该使用一个数组指针。那么下面这个代码就可以实现二维数组元素的打印 因为arr包含三个一维数组每个一维数组是四个元素因此arr的数组名也就是首元素地址是一个int [4]类型的数组的地址类型是int(*)[4]因此在形参接收的时候应该写int(*p)[4]。
首先p是int(*)[4]类型的数组指针1就会跳过一个int [4]类型的数组p里面存放的是arr首元素的地址也就是第一个一维数组的地址因此*p就拿到了第一个数组相当于拿到了第一个数组的数组名也就是说*p是int*类型的1就会跳过一个int类型。
*pi用来访问所有行*((*pi)j)就能访问所有元素了或者说(*pi)[j]也可以他们是等效的。
当然也可以直接形参直接使用二维数组来接收 实际上形参写成数组形式只是语法允许让我们能够直观的知道接收的是一个数组本质上地址就是要用指针来接收即使写成第二种写法编译器在编译的时候也会编译成我们第一种写法在为形参开辟空间的时候也是为数组指针开辟一块空间。
4.数组传参与指针传参
一维数组传参
下面哪些传参方式是正确的 先看test函数的传参实参是arr也就是一个int类型数组的数组名表示首元素的地址应该用一个int*类型的指针来接收因此形参写成int*arr是对的又因为C语言语法允许数组传参的时候用数组接收因此int arr[]和int arr[10]作为形参也是可以的因为实参传的只是一个地址形参部分是不会真正去创建一个数组的但是语法规定可以用数组来作为形参接收一个数组名我们甚至可以把形参写成int arr[1000]也是对的编译器都会按照形参是int *类型的来理解。
再来看test2的传参arr2是一个指针数组他的数组名是这个指针数组首元素的地址也就是一个指针的地址即二级指针实参传了二级指针形参要拿一个二级指针变量来接收因此形参应该是int **类型的当然语法允许用和实参代表的数组同类型的数组作为形参来接收但是本质上形参还是一个指针。
因此上面所有传参均正确。
二维数组传参 test函数实参是一个二维数组的数组名他代表二维数组arr首元素的地址又因为二维数组arr是由3个一维数组构成的每个一维数组又有五个int类型的元素因此arr的首元素是第一个一维数组的地址存放他应该用一个数组指针因此实参应该写成int(*arr)[5],表示arr是一个指针指向的数组类型是int[5]类型的数组也就是我们所谓的二维数组里面的一维数组当然数组名作为实参传递语法上允许用与arr代表的数组类型相同的数组来作为形参接收这是为了方便理解但是系统是不会为形参这个数组去开辟内存空间的编译的时候仍然会按照数组指针的形式开辟空间。
二维数组在创建的时候可以省略行但是不能省略列因此正确的形参创建形式有int arr[3][5],int arr[][5],int(*arr)[5]。
注除去数组名单独放在sizeof括号里还有数组名这两种情况二维数组的数组名一定是一个数组指针。
一级指针传参
对于指针传参来说如果实参是一个一级指针形参就用同类型的指针来接收即可二级指针同理这非常的简单因此对于指针传参我们来讨论一下如果我们知道了形参是一个一级指针实参可能传递的是什么。 可以接收
1.char a0; test2(a) 一个char类型变量的地址
2.char chabc; test2(ch);一个char[]类型数组的数组名
3.char a0;char* pa; test2(p);一个char*类型的指针变量
二级指针传参 如果形参已经确定是char**p可能接收的是什么实参
可以接收
1.char a0;char *pa;test(p);一个char*类型指针的地址
2.char a0;char *pa;int**ppp;test(pp);一个二级指针
3.char* arr[10];test(arr);一个char*[10]类型数组的数组名
注数组传参形参可以用数组形式接收也可以用指针形式接收但是指针传参形参指针写成指针形式。
5.函数指针
函数指针顾名思义就是指向函数的指针里面存放的是一个函数的地址
来看一段代码 居然是一样的这说明函数名和函数名代表的意思都是函数的地址
既然函数有地址我们就可以把他存到指针变量里面去这个指针变量就是函数指针在初始化的时候我们写作void (*p)(),也就是函数的返回值类型以及形参类型决定了他的函数指针是什么样的类型。比如有一个函数
int addint xint y用来存放他的函数指针在初始化的时候就写作
int(*p)(int,int),去掉指针的名字就是他的类型因此这个函数指针的类型就是int(*)(int,int)
再比如有一个函数void test(charpc,int arr[10])初始化他的函数指针就写作void (*p)(char*,int [10])p的类型就是void(*)(char*,int [10])10可以省略也可以写成int *
有了函数指针我们就可以对他进行解引用来调用函数。如果pf里存放了函数add的地址那么add(3,5)和(*p)(3,5)就是等效的。实际上*都多余了因为我们函数名和函数名其实是一回事我们可以测试一下 因此我们既然把add赋给了pf那就相当于把add赋给了pf也就是说pf就是add因此我们直接pf(3,5)也是可以正常调用add函数的
来看两个有趣的代码 首先我们看到最后有个小括号这肯定代表函数调用然后左边最外层的括号应该就是就是为了分割开这样我们就从内部的0开始入手0前面有一个括号括号里面是一个类型这表示把0强制类型转换成函数指针的类型那么0作为函数指针就代表了一个地址再对他进行解引用就能调用0地址处的函数。之所以最右边调用的小括号里面啥也没有是因为这个函数不需要任何参数。这个代码应用场景可能就是我们知道了0地址处有一个函数想要调用他。 这个代码看起来无从下手因为他好像也并没有函数调用也没有强制类型转换如果我提前告诉你这是一个函数名为signal函数的声明你有没有头绪了呢
解释一下我们就从signal开始入手因为这肯定是一个函数名那么他后面的括号就是他的参数类型分别是int和void(*)(int)也即一个int类型和一个函数指针类型那么知道了函数名函数的参数类型把他们都去掉不就是他的返回类型吗故signal的返回类型是void(*)(int)他的返回值也是函数指针类型。因此上面这段代码是signal的声明。
这个代码之所以难以理解就是因为这个函数指针的类型写起来非常具有迷惑性如果把这个函数指针类型进行类型重定义成一个简单的类型这个代码阅读起来就会非常的轻松。那首先来介绍一下typedef的使用方法如果是把unsigned int类型重命名为uint就写作typedef unsigned int uint如果是把int 类型重命名为ptr_t,就写作typedef int ptr_t
但是如果要把函数指针 int(*)(int)重命名为pint就不能写tepedef int(*)(int) pint而应该写成typedef int(*pint)(int)
要把函数指针void(*)(int)重命名为pf_t,就写作typedef void(*pf_t)(int),这样就可以把上面的代码简化为pf_t signal(int,pf_t)这样读起来就舒服多了一眼就看出这是一个函数的声明。
6.函数指针数组
顾名思义就是这个数组中的每个元素都是函数指针类型。
假如我们要写一个计算器于是我们就写了下面四个函数 我们发现这些函数的参数和返回值都已相同类型的那我们就可以把这些函数放到一个数组里面去怎么放函数只能放他的函数名吧那我们又说函数名其实是函数的地址因此我们放的其实是一些函数的地址也就是函数指针当然要用一个函数指针数组来存放那么如何初始化这个数组类比int arr[4]{0};这表示arr数组里面能放4个int类型的元素
那么我们想要一个能够放四个int(*)(int,int)类型变量的数组就初始化成int(*)(int,int) arr[4]是不是这样理解可以但是语法规定我们要把arr[4]写到*的旁边也就是说我们应该这样初始化函数指针数组
注指针变量在创建的时候指针变量一定是在*的旁边的。
int(*p)(int ,int),*表示p是个指针去掉*和p就是指针指向的元素类型是
intint,int说明指向的元素是函数类型单独去掉变量名就是p的类型也就是说p是int(*)(int,int)类型。
int(*arr[4])(int,int)那么对于简单的计算器这个代码的实现就可以这样写计算器只针对整数 7.指向函数指针数组的指针了解即可
假如arr是一个函数指针数组我们现在arr放到指针p里面则p就是指向函数指针数组的指针他的类型是什么呢?
首先来看函数指针数组的类型应该怎么写。int(*[4])(int,int),是函数指针数组的类型现在p要指向他应该写成int(*(*p)[4])(int,int)arr其中p旁边的那个*表示p是一个指针去掉p就是p的类型所以p的类型是int(**arr[4])(int,int),类型去掉p和他旁边的*就是p指向的元素的类型因此p指向的元素类型是int(*[4])(int,int),也即一个函数指针类型。
8.回调函数
函数指针有一个非常重要的用途就是实现回调函数。回调函数就是通过函数指针调用的函数。比如前面的计算器除了使用函数指针数组来实现还可以使用函数指针来实现。 其中calc函数就通过函数指针回调了加减乘除的函数此时的addsubmuldiv就是回调函数通过函数指针调用谁谁就是回调函数
回调函数的应用库函数qsort
qsort的头文件是stdlib.h 他的四个参数分别是要进行排序的数组base的首地址base数组的元素个数每个元素的大小以及一个函数指针这个函数指针指向了一个函数这个函数的参数是两个void*类型的指针返回类型是int要求这个函数能够比较参数这个函数的参数是两个指针指向的两个元素的大小规定如果elem1指向的元素比elem2指向的元素大那这个函数就返回一个大于零的数反之就返回一个小于零的数如果elem1和elem2指向的元素一样大就返回0。
void*类型的指针可以接收任何类型的地址但是不能直接解引用由于我们不知道未来要比较的两个函数是什么类型的那就只能写成void*类型就可以接收任何类型了在解引用之前强制类型转换即可。
qsort默认是以升序排列的如果要降序排列只需要比较大小的函数返回值上用相反的逻辑即可比如elem1指向的元素如果比elem2大就返回一个小于零的数并且可以排列任何类型的数组。来看一个例子 模拟实现库函数qsort
下面我们将使用回调函数模拟实现qsort由于目前没有学习快速排序因此使用冒泡排序代替。 模拟实现我们就把参数设置成和库函数qsort一样最后一个函数指针指向的是一个能比较两个元素大小的函数需要使用者自己编写。
我们排序使用的是冒泡排序的思想就是比较相邻的两个元素如果前面比后面大就交换那现在的问题是当时冒泡排序是针对整形数组的比较就是直接base[j]和base[j1]进行比较即可但是现在我们想要这个my_sort类型能够对任意类型的数据进行排序就不能简单的写成base[j]和base[j1]因为我们连base的类型都不知道当然也不知道1会跳过几个字节也就不知道1是不是拿到了和base[j]相邻的元素。那么我们干脆一个字节一个字节的操作因为不管什么类型大小至少也是一个字节那如何操作一个字节很简单使用char类型的指针即可。因此不管base代表什么类型的数组我们都先把base强制类型转换成char*类型那么base[j]其实就是(char*)basej*size与他相邻的元素就是(char*)base(j1)*size。
这样我们就拿到了要比较的两个元素但是这两个元素如何比较直接使用大于号显然是不合理的这时候就用到了我们的第四个参数cmp函数我们需要自己写一个能够比较两个元素大小的函数并且规定如果前者大就返回一个大于0的数反之就返回一个小于0的数如果二者相等就返回0。
通过判断cmp函数的返回值我们就知道了需不需要交换这两个元素。那问题又来了如何交换直接创建临时变量吗那临时变量是什么类型的呢但是要交换的数据是什么类型他们最小也有一个字节那么我们直接一个字节一个字节的交换就行什么时候才算交换结束呢当然是交换完size个字节后就结束了。如图是swap函数的交换 这样我们的my_sort函数就可以正常使用了比如我们要排序的是一个整形数组我们就要去写一个能够比较两个整形数据的函数比如这样写 如果我们要比较两个结构体类型的数据呢
假如结构体类型是这样的 如何比较两个struct stu类型变量的大小
由于上面的结构体类型含有两个成员变量那么要比较struct sut类型变量的大小就可以按照name的大小来进行比较也可以按照age的大小进行比较当然如果按照name的大小来进行比较就不能直接相减了因为字符串的比较是用库函数strcmp这个库函数的返回值逻辑与我们的cmp函数一致也是前者大就返回大于零的数后者大就返回小于零的数一样大就返回0因此我们如果按照name的大小来比较的话直接返回strcmp的值就可以如图 注由于空指针是无法进行解引用操作的因此我们在写cmp函数的时候都需要进行强制类型转换要比较的是什么类型的就强制类型转换成对应的指针。