C语言:指针的第二层(进阶篇)

目录

1. 字符指针

 2.指针数组

3.数组指针 

3.1 数组指针的定义

3.2 &数组名VS数组名 

3.3 数组指针的使用

4.数组参数、指针参数

4.1 一维数组传参

4.2 二维数组传参

4.3 一级指针传参

5.函数指针

6.函数指针数组

7.指向函数指针数组的指针

8.回调函数



 我们在初步学习C语言时,就已经初步的接触过指针这个东西的存在,也对指针有了大概的了解

        1.指针就是个变量,是用来存放内存地址的,内存中地址唯一标识一块内存空间!

        2.指针的大小是固定的4/8个字节(32位系统平台/64位系统平台)

        3.指针是有具体类型的,指针的类型决定了在 + / - 整数时指针所偏移的步长(或称跨度),            以及指针解引用时访问多少个字节的权限

        4.可以用来指针运算

这一篇中,继续来探索新的指针更深入层次的主题 


1. 字符指针


在初识指针之后都知道指针中有一种类型叫字符指针 - char*;

对于指针一般的使用:

int main()
{
	char ch = 'W';//创建char变量ch
	char* pc = &ch; //取出变量ch的内存地址,将其放进字符指针变量pc中,pc的内容就是ch的地址
	*pc = 'A';//通过指针解引用找到ch 将其内容值修改
	return 0;
}

对字符指针还有一种使用:

int mian()
{
	char* pc = "hello word";//一个思考,这是把一个字符串给放到pc指针中去了吗?
	printf("%s\n", pc);
	return 0;
}

第二个代码块中,char* pc = "hello word"; 是特别容易让大家以为是把一个字符串hello word放到字符指针pc中去了,但是其实这个代码本质上只是把 hello word 首字符 的地址放到了pc中

来看一道有趣的题目

#include <stdio.h>
int main()
{
	char str1[] = "hello word";
	char str2[] = "hello word";
	char* str3 = "hello word";
	char* str4 = "hello word";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");

	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");

	return 0;
}

在每个人看到的第一眼,都会觉得输出的结果为 str1 and str2 are same /str3 and str4 are not same     但其实,结果却大大相反!! 先看看结果

这里最终输出的是:

应该会有很多人很诧异, 为什么str1 和str2是不相等的?

其实很简单,str1和str2 两个是数组,数组中存储的字符串 是一样的,但是大家不要忘记了,数组名代表数组首元素地址,既然是两个数组那么必然就拥有着两块不同的空间,虽然存储的内容一样,但是它们的地址 那可是完全不一样,下面的判定语句则是地址与地址是否相等,很显然 输出的结果一定是else

那如果按这么说,那么str3为什么会和str4相等?  是因为str3和str4它们不是数组,它们是指针变量,只不过指向的是同一个字符串hello word,那么这个字符串是常量字符串,常量字符串的特性:可以访问,可以使用,但不可修改 那么既然都不可修改 内存中就没必要存储两份一模一样的常量字符串,那么str3和str4 存储的都是这一个常量字符串中h的地址 经过if语句判定 输出的结果为 same~


 2.指针数组


在初步认识指针中,也学习了一下指针数组,指针数组本质上就是一个用于存放多个同类型指针集合的数组

	int* arr1[10]; //整形指针的数组
	char* arr2[4]; //一级字符指针的数组
	char** arr3[5];//二级字符指针的数组

指针数组使用起来还是非常简单的:

int main()
{
	//int* arr1[10]; //整形指针的数组
	//char* arr2[4]; //一级字符指针的数组
	//char** arr3[5];//二级字符指针的数组
	//创建三个数组
	int a[] = { 1,2,3,4,5 };
	int b[] = { 6,7,8,9,10 };
	int c[] = { 11,12,13,14,15 };
	//创建指针数组 并且把存进三个数组的指针
	int* tmp[3] = { a,b,c };
	//利用指针数组,打印这三个指针指向的空间内容
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", * (tmp[i] + j));//写法1;
			//printf("%d ", *(*(tmp + i) + j));//写法2;
			//printf("%d ", tmp[i][j]);//写法3;

		}
		printf("\n");
	}
	return 0;
}

在这段代码中,我创建了3个int类型数组,并且都存储5个元素

再而创建了一个指针数组,用于存放这三个数组的指针,那么在之前的学习中我们都知道,数组名代表着首元素的地址,那么既然代表着首元素的地址,它就是个地址 地址其实就是指针,也就可以理解为数组名其实就是一个一级指针, 创建指针数组时 tmp先和[ ]结合,代表它是个数组,数组中有3个元素,每个元素类型为int* 足以证明它是个指针数组,那么再把三个数组的数组名放进去,其实存放的就是三个一级指针

在下面的打印中,printf其实有三种写法,第一种写法中 tmp[i]其实等价于*(tmp+i)拿到了tmp数组中某个指针指向的那块空间,然后在外面又进行了 +j 解引用 就是拿到了那个位置的元素 将其打印  这也就等价于写法2: *(*(tmp + i) + j 写法2又等价于写法3:tmp[i][j]


3.数组指针 


3.1 数组指针的定义

数组指针到底是指针?还是数组?

答案是:指针.

我们已经熟悉:

整形指针: int * pint; 能够指向整形数据的指针。

浮点型指针: float * pf; 能够指向浮点型数据的指针。

那数组指针应该是:能够指向数组的指针。

下面代码哪个是数组指针?

int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

解释:

int *p1[10]; 在我们学过的优先级中,[ ]的优先级是高于*的 所以p1先和[ ]结合,这时候p1是个数组,它有10个元素,每个元素的类型是int* 所以它是个指针数组!

int (*p)[10] ; 这里 ( )的优先级是最高的,那么p先和*进行结合,这时候p是个指针, 把p拿掉后还剩下 int  [10]; 证明指针指向一个数组,数组有10个元素,每个元素是int类型; 

//这里要注意: [ ] 的优先级是要高于*的,所以必须加上( )来保证p先和*结合.

3.2 &数组名VS数组名 

对于下面的数组:

int arr[10];

arr 和 &arr 分别是什么?

其中,arr是数组名,数组名表示的是首元素的地址.

那&arr数组名到底是什么?  看一段代码:

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    printf("%p\n", arr);
    printf("%p\n", &arr);
    return 0; 
}

运行结果如下: 

可见数组名和&数组名打印的地址是一样的。

难道两个是一样的吗?

我们再看一段代码:

#include <stdio.h>
int main()
{
 int arr[10] = { 0 };
 printf("arr = %p\n", arr);
 printf("&arr= %p\n", &arr);
 printf("arr+1 = %p\n", arr+1);
 printf("&arr+1= %p\n", &arr+1);
 return 0; 
}

 它的运行结果如下

根据上面的代码我们发现,其实&arrarr,虽然值是一样的,但是意义应该不一样的。

实际上&arr 表示的是数组的地址,而不是数组首元素的地址。

数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40

3.3 数组指针的使用

那数组指针是怎么使用的呢?

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

看代码:

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
    //但是我们一般很少这样写代码
    return 0; 
}

为什么我说一般很少这样写代码? 因为数组指针用在一维数组上,其实并不是那么恰当, 最适合一维数组的指针,就是一级指针,而非数组指针,不信的话 看看下面代码数组指针对于一维数组的打印:

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int(*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
	int* a = arr; //把数组首元素地址付给一级整形指针a;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *((*p) + i));//数组指针打印一维数组的方式
		printf("%d ", *(a + i));//一级指针打印一维数组的方式
	}

	return 0;
}

在这段代码中,明显用一级指针来进行一维数组打印,不论是书写,还是可读性 都会更加的好,用数组指针也是可以,只不过显得有点杀鸡焉用牛刀 一般数组指针,都是用于二维数组身上

一个数组指针的使用:

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col) {
    int i = 0;
    for(i=0; i<row; i++)
   {
        for(j=0; j<col; j++)
       {
            printf("%d ", arr[i][j]);
       }
        printf("\n");
   }
}
void print_arr2(int (*arr2)[5], int row, int col) {
    int i = 0;
    for(i=0; i<row; i++)
   {
        for(j=0; j<col; j++)
       {
            printf("%d ", arr2[i][j]);
       }
        printf("\n");
   }
}
int main()
{
    int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
   print_arr1(arr, 3, 5);
    //数组名arr,表示首元素的地址
    //但是二维数组的首元素是二维数组的第一行
    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    //可以数组指针来接收
    print_arr2(arr, 3, 5);
    return 0; 
}

解释: 

在arr1函数中,二维数组传参,使用二维数组接收肯定是没问题 这个都不需要过多的去讲解

重点在于arr2,二维数组传参,使用指针数组接收,那么arr这个二维数组其实可以看成是3个元素每个元素的类型是一个一维数组 每个一维数组有5个元素 每个元素为int类型 那么arr的首元素地址 就是第一个一维数组的地址 那么这时候用于数组指针来接收再合适不过 这个数组指针变量每+1 就跳过一个一维数组,因为在arr中 这三个一维数组是连续存放的! 可以看下面的图进行进一步理解

 

 学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:

int arr[5]; //arr首先和[ ]结合 所以它是个数组有5个元素每个元素是int类型;
int *parr1[10];//parr1首先和[ ]所以它是个数组有10个元素每个元素是int* 类型;
int (*parr2)[10];//parr2首先和*结合所以它是指针,去掉名字之后就剩下int[10]; 证明指针指向一个数组 这个数组10个元素,每个元素int类型;
int (*parr3[10])[5];//parr3首先和[ ]结合是数组,10个元素,去掉已结合的之后剩下int(*)[5],证明它的每个元素是数组指针;

4.数组参数、指针参数


在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计?

4.1 一维数组传参

#include <stdio.h>
void test(int arr[])//ok?  -ok
{} //这个肯定没问题,一维数组传参,形参用一维数组接收
void test(int arr[10])//ok? --ok
{}/*这个也没问题,只是比上面的形参在[]中多了个10但是不会真的去多创造10个元素空间出来~所以这个
10只是摆设*/
void test(int *arr)//ok? --ok
{}/*数组名代表首元素地址,只要是一维数组首元素地址,那么它就是个一级指针 所以用一级指针接收
是OK的*/
void test2(int *arr[20])//ok?  --ok
{} /*数组传参用同类型数组接收,合适的不能再合适*/
void test2(int **arr)//ok?
{}/*数组名代表首元素地址,这个数组的元素类型是int* 那么这个数组的首元素地址肯定是个二级指针
 所以OK*/
int main()
{
 int arr[10] = {0};
 int *arr2[20] = {0};
 test(arr);
 test2(arr2);
}

4.2 二维数组传参

void test(int arr[3][5])//ok? --ok
{}//数组传参数组接收 OK~
void test(int arr[][])//ok?  --err
{}//二维数组只能省略行 不能省略列!包括传参
void test(int arr[][5])//ok?  --ok
{}//行可以省略 列不能省略 这个是ok的
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?  --err
{}//这个肯定不行,二维数组首元素地址是数组指针类型,怎么可以用一维数组接收!
void test(int* arr[5])//ok?  --err
{}//这是个一级指针数组,类型都不对 更不可能接收
void test(int (*arr)[5])//ok?  --ok
{}//二维数组首元素地址是数组指针类型,用数组指针是可以接收并且使用的
void test(int **arr)//ok?  --err
{}//数组指针不能用二维数组接收
int main()
{
 int arr[3][5] = {0};
 test(arr);
}

4.3 一级指针传参

#include <stdio.h>
void print(int *p, int sz) {
 int i = 0;
 for(i=0; i<sz; i++)
 {
 printf("%d\n", *(p+i));
 }
}
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9};
 int *p = arr;
 int sz = sizeof(arr)/sizeof(arr[0]);
 //一级指针p,传给函数
 print(p, sz); //这里一级指针只是传参,不是传值,一级指针里面存的内容就是个地址 所以函数参数用一级指针接收
 return 0; 
}
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

比如: 

void test1(int *p)
{}
//test1函数能接收什么参数?
//解答: &int类型变量, int类型一级指针传参,int类型一维数组传参,
void test2(char* p)
{}
//test2函数能接收什么参数?
//解答: &char类型变量, char类型一级指针传参,char类型一维数组传参,

同样的,二级指针,三级指针,四级指针等等~这些大家可以多去根据这上面的例子思考一下


5.函数指针


我们先看一段代码:

#include <stdio.h>
void test()
{
 printf("hehe\n");
}
int main()
{
 printf("%p\n", test);
 printf("%p\n", &test);
 return 0; 
}

 这段代码的输出结果为:

足以证明,函数名和&函数名 是相等的

这里有一个知识点: 数组名≠&数组名  但是函数名=&函数名 函数没有所谓首元素地址,所以函数名就是地址,取地址函数名和函数名是等价的

既然函数名就是函数地址,那么它也是可以放进指针里的,这样的指针被称为指向函数的指针简称:函数指针 

函数指针的创建:

#include <stdio.h>
void test(int a,int b)
{
	printf("hehe\n");
}
int main()
{
	void (*pf) (int, int) = test; //或者写成=&test都可以;
	//(*pf)先结合代表它是个指针,去掉已结合的就剩下 void (int,int) 是个函数
	//证明指针指向了一个函数,这个函数的参数类型是int,int 返回类型是void
	return 0;
}

函数指针的调用:

#include <stdio.h>
void test(int a,int b)
{
	printf("hehe\n");
}
int main()
{
	void (*pf) (int, int) = test; 
	(*pf)(3, 5);//写法1 等价于 test(3,5) 那么既然两个互相等价 就有了第二种写法
	pf(3, 5);//写法2  写法1中的解引用符号,其实就是个摆设,pf和test是互相等价的 使用pf也就调用了test 所以没必要加上解引用
	//错误写法 : *pf(3,5)千万不能写成这样,写成这样是先调用函数
	//然后把函数的返回值解引用 这样是错误写法!!!
	return 0;
}

 注意,只有函数指针使用的时候可以省略掉*解引用操作符!


6.函数指针数组


函数既然可以存进指针,那么函数指针,自然也可以放进一个函数指针的数组中去

函数指针数组创建:

//  类型(*数组名[个数])(函数形参.....) = {函数名(或者函数指针),函数名(或者函数指针),函数名(或者函数指针);
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int (*pff[4])(int, int) = { add,sub,mul,div };//函数指针数组的创建
	//解析: pff先和[4]结合,它是个数组,有4个元素
	//去掉已结合元素之后剩下 int(*)(int,int),4个元素每个元素为函数指针
	//每个函数指针指向一个函数,每个函数返回类型为int,参数类型为int,int
	return 0;
}

函数指针数组的使用(计算器):

先用已学习过的知识进行计算器的创建再利用函数指针数组在基础上进行改造

void menu()
{
	printf("*******************************\n");
	printf("********1.add     2.sub********\n");
	printf("********3.mul     4.div********\n");
	printf("*********** 0.exit ************\n");
	printf("*******************************\n");
}
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int input = 0;//选项
	do
	{
		menu();//打印菜单
		printf("请选择选项:>");
		scanf("%d", &input);
		int a = 0;//计算数1
		int b = 0;//计算数2
		int ret = 0;//接收返回值
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:");
			scanf("%d %d", &a, &b);
			ret = add(a, b);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:");
			scanf("%d %d", &a, &b);
			ret = sub(a, b);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:");
			scanf("%d %d", &a, &b);
			ret = mul(a, b);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:");
			scanf("%d %d", &a, &b);
			ret = div(a, b);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出程序!");
			break;
		default:
			printf("输入有误,请重新输入\n");
			break;
		}


	} while (input);
	return 0;
}

这份代码中,虽然可读性很好,但是它的switch中每个选项的代码大部分都是重复的,显得很冗余,如果这个计算器要加更多的功能,那么它的分支岂不是要更多重复的代码,这对于维护来说并不是件好事.

函数指针数组的写法: 

void menu()
{
	printf("*******************************\n");
	printf("********1.add     2.sub********\n");
	printf("********3.mul     4.div********\n");
	printf("*********** 0.exit ************\n");
	printf("*******************************\n");
}
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int input = 0;//选项
	do
	{
		menu();//打印菜单
		printf("请选择选项:>");
		scanf("%d", &input);
		int a = 0;//计算数1
		int b = 0;//计算数2
		int ret = 0;//接收返回值
		int (*ppf[5])(int, int) = { NULL,add,sub,mul,div };//第一个元素放NULL是为了让选项和对应功能可以直接用上
		if ((input <= 4 && input >= 1))//功能判定区
		{
			printf("请输入两个操作数:");
			scanf("%d %d", &a, &b);
			ret = ppf[input](a, b);//等价于 ret = (*(ppf+input)) (a,b);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出程序\n");
		}
		else
		{
			printf("输入有误,请重新输入\n");
		}
	} while (input);
	return 0;
}

 这份代码中,利用了函数指针数组,把几个函数串联起来,并通过调用传参直接进行计算,省去了很多重复且冗余的代码,对于未来如果要添加新的功能也好或者进行修改维护也好,少了很多要重新写入的代码,只要增加或者删减对应功能修改功能判定区的值和数组中功能数量即可,这种使用方式也有另一种名称 :转移表;


7.指向函数指针数组的指针


指向函数指针数组的指针是一个 指针
指针指向一个 数组 ,数组的元素都是 函数指针
定义函数指针数组的指针:
void test(const char* str) {
 printf("%s\n", str);
}
int main()
{
 //函数指针pfun
 void (*pfun)(const char*) = test;
 //函数指针的数组pfunArr
 void (*pfunArr[5])(const char* str);
 pfunArr[0] = test;
 //指向函数指针数组pfunArr的指针ppfunArr
 void (*(*ppfunArr)[10])(const char*) = &pfunArr;
//ppfunArr先和*结合,证明它是个指针,该指针指向一个数组,10个元素
//数组每个元素是一个函数指针类型,所以它是一个指向函数指针数组的指针
 return 0; 
}

这个目前还是先认识一下,对于怎么使用后期我们再慢慢去实践~指针大家应该都会觉得很像俄罗斯套娃,一层套一层,没完没了,这就比较考验大家思维的逻辑性了


8.回调函数


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

回调函数的使用: 

以刚刚的计算器为例

void menu()//回调函数写法
{
	printf("*******************************\n");
	printf("********1.add     2.sub********\n");
	printf("********3.mul     4.div********\n");
	printf("*********** 0.exit ************\n");
	printf("*******************************\n");
}
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
int Cofe(int (*pf)(int, int))
{
	int a = 0;//计算数1
	int b = 0;//计算数2
	printf("请输入两个操作数:");
	scanf("%d %d", &a, &b);
	return pf(a, b);
}
int main()
{
	int input = 0;//选项
	do
	{
		menu();//打印菜单
		printf("请选择选项:>");
		scanf("%d", &input);
		int ret = 0;//接收返回值
		switch (input)
		{
		case 1:
			ret = Cofe(add); 
			printf("%d\n", ret);
			break;
		case 2:
			ret = Cofe(sub);
			printf("%d\n", ret);
			break;
		case 3:
			ret = Cofe(mul);
			printf("%d\n", ret);
			break;
		case 4:
			ret = Cofe(div);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出程序!");
			break;
		default:
			printf("输入有误,请重新输入\n");
			break;
		}


	} while (input);
	return 0;
}

这段代码中与之前不同的是,我把冗余的代码用一个函数进行封装,该函数的参数是一个函数指针类型,那么我们把对应的代码块功能函数进行传参,在Cofe函数中进行输入操作数,计算以及返回结果用ret进行接收,那么每个case中只要负责接收结果进行打印即可, 这里面就利用到了回调函数机制,通过函数Cofe 调用别的函数进行计算并且返回值对Cofe函数的调用做出一个回复,这就是回调函数

以库函数qsort为例并模仿qsort函数: 

qsort:快速排序,是库函数中的一种函数,它可以快速排序任意类型数组

(我们之前写的所有排序方法,都是得挑类型,而qsort不挑类型,给它什么类型数组它都能排序)

qsort函数声明:

void qsort (void* base, size_t num, size_t size,
            int (*compar)(const void*,const void*));

可以看到qsort有四个参数,其中 void* base 是代表要排序数组的开始地址, size_t num是要排序数组中元素个数,size_t size是要排序数组中元素的大小(字节), 最后一个函数指针则是这个qsort函数的精华,这个函数指针是由qsort使用者创建并提供的一个函数,用于qsort进行快速排序(升序/降序)(如果返回的数值>0 则是升序 <0 则是降序)

qsort的使用 

#include <stdlib.h>
#include <stdio.h>

int cmp(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;//这个是操作者根据自己要排序数组类型进行强制类型转换
	//因为void*类型是个无具体类型的指针,直接对它进行解引用它不知道会访问多少个字节
	//直接对void*类型解引用会报错:非法间接寻址
	//void* 就像个垃圾桶,你给它什么类型的指针它都可以接收
}
void print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	printf("\n");
}

int main()//qsort快速排序使用
{
	int arr[] = { 4,6,8,0,1,3,5,2,9 };//创建数组
	int sz = sizeof(arr) / sizeof(arr[0]);//数组元素个数
	print(arr, sz);//对未排序之前的数组打印
	qsort(arr, sz, sizeof(arr[0]), cmp);
	print(arr, sz);//对排序之后的数组打印
}

这个程序的输出结果为:

模仿qsort实现一个简单通用的冒泡排序(升序): 

int cmp(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

void Swap(char* e1, char* e2, int width)
//这个函数中有3个参数 e1代表交换元素1的起始地址,e2代表交换元素2的起始地址,width是两个元素的大小
//将指针设置为char*是为了能够更加融合更多类型元素
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *e1;
		*e1 = *e2;
		*e2 = tmp;
		e1++;
		e2++;
		//对于各种类型元素,这个循环是把各类型元素中每个字节进行顺序交换 char*一次只访问一个字节
		//width就是表明这些元素是多大的字节,只需要交换多少个字节即可,用于限制作用
	}

}

void my_qsort(void* base, int sz, int width, int(*pf)(const void* e1, const void* e2))
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;\
		//每趟的排序
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) //因为void*是无具体类型的指针,所以强制转换为char*类型指针
			//char*类型指针一次可以访问一个字节,这时候就可以利用到我们传过来的width 该数组每个元素的大小
			//再+j*width 和+(j+1)*width 的地址调用cmp传过去进行计算并返回判断,这里就用到了回调函数机制
			//如果满足 进行交换
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);//交换函数
			}

		}
	}
}
void print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	printf("\n");
}

int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };//创建数组
	int sz = sizeof(arr) / sizeof(arr[0]);//数组元素个数
	print(arr, sz);//对未排序之前的数组打印
	my_qsort(arr, sz, sizeof(arr[0]), cmp);
	print(arr, sz);//对排序之后的数组打印
	return 0;
}

程序的输出结果为: 


以上就是指针进阶篇的所有内容,感谢您的观看~希望您能从我这对指针有更深的理解

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值