【搞透C语言指针】那年我双手插兜, 不知道指针是我的对手

 【搞透C语言指针】那年我双手插兜, 不知道指针是我的对手_第1张图片

☃️内容专栏:【C语言】进阶部分

☃️本文概括: 征服C语言指针!一篇文章搞清楚指针的全部要点。

☃️本文作者:花香碟自来_ 

☃️发布时间:2023.3.3

目录

一、字符指针

二、指针数组

三、数组指针

1.数组指针的定义

2.数组名和&数组名

3.数组指针的使用

四. 数组传参和指针传参

1.一维数组传参

2.二维数组传参

3.一级指针传参 

4.二级指针传参

五. 函数指针

1.函数指针

2.利用函数指针

3.阅读两段有趣的代码

六. 函数指针数组

1.函数指针的使用 

2.函数指针数组的应用

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

八. 回调函数

九、qsort函数的应用

1.利用qsort来排序整型数组

2 利用qsort来排序结构体数据

十、以冒泡排序的思想,模拟实现qsort函数


一、字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* 

一般这么使用:

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    return 0;
}

但是还有一种方式

字符串常量

int main()
{
    const char* pstr = "hello world.";//这里是把一个字符串放到pstr指针变量里了吗?
    printf("%s\n", pstr);
    return 0;
}

以上特别容易以为是把字符串 hello world.放到字符指针 pstr 里了,其实是把字符串的首字符地址h的地址存放到字符指针变量 pstr 当中,如果当32位平台,4个字节大小的指针变量pstr能够装得下字符串吗?显然,不能。

需要注意的一点,观察以下图:

 【搞透C语言指针】那年我双手插兜, 不知道指针是我的对手_第2张图片

 好了,小伙伴们理解了吗?下面来看一道来自于《剑指offer》当中的一道题:

#include
int main()
{
    char str1[] = "hello world.";
    char str2[] = "hello world.";
    const char *str3 = "hello world.";
    const char *str4 = "hello world.";

    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
}

【搞透C语言指针】那年我双手插兜, 不知道指针是我的对手_第3张图片

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

二、指针数组

指针数组就是存放指针的数组,int* arr1[10] 表示存放整型指针的数组,char* arr2[20] 表示存放字符指针的数组。

应用:

int main()
{
    /* char* arr[] = { "abcdef", "hehe", "qwer" };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%s\n", arr[i]);
	} */

	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	//arr[i] == *(arr+i)
	//arr是一个存放整型指针的数组
	int* arr[] = { arr1, arr2, arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			//printf("%d ", arr[i][j]);
			printf("%d ", *(arr[i]+j));
		}
		printf("\n");
	}
	return 0;
}

三、数组指针

1.数组指针的定义

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

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

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

//下面代码哪个是数组指针?
int main()
{
    int *p1[10];
    int (*p2)[10];
    //p1, p2分别是什么?
    return 0;
}

解释:int (*p)[10]; //解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针。

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

2.数组名和&数组名

我们知道,数组名绝大部分情况下是数组首元素的地址。

但是有两种特殊情况:

1. sizeof(数组名) - sizeof内部单独放一个数组名的时候,数组名表示的整个数组,计算得到的是数组的总大小。
2. &arr - 这里的数组名表示整个数组,取出的是整个数组的地址,从地址值的角度来讲和数组首元素的地址是一样的,但是意义不一样。

观察以下代码

#include
int main()
{
	int arr[10] = { 0 };
	//printf("%d\n", sizeof(arr));
	printf("%p\n", arr);//其类型是int * 
	printf("%p\n", arr+1);//4

	printf("%p\n", &arr[0]);//int* 
	printf("%p\n", &arr[0]+1);//4

	printf("%p\n", &arr);//其类型是int(*)[10]
	printf("%p\n", &arr+1);
	int (*p)[10] = &arr;
	//p是一个数组指针
	//int(*)[10] 
	return 0;
}

【搞透C语言指针】那年我双手插兜, 不知道指针是我的对手_第4张图片

 解释:根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。 实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)

本例中 arr 和 &arr[0] 表示的都是数组首元素的地址,arr + 1和&arr[0] +1 需要看数组首元素地址的类型是int *,所以加一跳过一个整型的大小。而&arr 的类型是: int(*)[10] ,是一种数组指针类型 数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40。

3.数组指针的使用

那数组指针是怎么使用的呢? 既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

#include
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };

	int sz = sizeof(arr) / sizeof(arr[0]);
	int (* p)[10] = &arr;
	int i = 0;
	//p  --- &arr
	//*p --- *&arr
	//*p --- arr
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *((*p) + i));
	}
	//虽然对,但是有点别扭,不推荐 
	//一般还是将数组名存放到一个整型指针中再访问使用
	//int* p = arr;
	//for (i = 0; i < sz; i++)
	//{
	//	printf("%d ", *(p + i));
	//}
	return 0;
}

 一个数组指针的使用(二维数组传参):

#include
void print(int(*arr)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			//*(arr + 0) == arr[0] == &arr[0][0] 为第一行数组首元素的地址……以此类推
			printf("%d ", *(*(arr + i) + j));
			//printf("%d ", arr[i][j]);//和以上代码表示一样
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
	//二维数组的数组名,也表示首元素的地址
	//二维数组可以理解为起始是一维数组的数组
	//二维数组的首元素是第一行(一维数组)
	//首元素的地址就是第一行的地址,是一个一维数组的地址
 
	print(arr, 3, 5);
	return 0;
}

 辨别一下以下代码 

int arr[5];   //整型数组

int *parr1[10]; //指针数组

int (*parr2)[10]; //数组指针

int (*parr3[10])[5]; //存放数组指针的数组


四. 数组传参和指针传参

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

1.一维数组传参

对于函数test(),一维整型数组传参,形参部分可以是数组的形式接收,也可以是指针的形式接收。

对于函数test2(),指针数组传参,形参部分可以是指针数组的形式接收,也可以是二级指针的形式接收。

#include 
void test(int arr[])// ture
{}
void test(int arr[10])// ture
{}
void test(int* arr)// true
{}
void test2(int* arr[20])// true 
{} 
void test2(int** arr)// true 
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

2.二维数组传参

如果是数组,行可以省略,但是列不能省略。如果是指针,传过去的是第一行的地址,形参部分接收的就应该是数组指针。

void test(int arr[3][5])//ture
{}
void test(int arr[][])//false
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int arr[][5])//ture
{}
void test(int *arr)//false
{}
void test(int* arr[5])//false
{}
void test(int (*arr)[5])//ture
{}
void test(int **arr)//false
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);
}

3.一级指针传参 

#include 
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函数能接收什么参数?

  1.  一级指针
  2. 整型变量的地址
  3. 整型的一维数组名

4.二级指针传参

#include 
void test(int** ptr)
{
     printf("num = %d\n", **ptr); 
}
int main()
{
     int n = 10;
     int*p = &n;
     int **pp = &p;
     test(pp);
     test(&p);
     return 0;
}

思考:当函数的参数为二级指针的时候,可以接收什么参数? 

  void test2(int **p)

  {}

 //test2函数能接收什么参数?

  1. 二级指针变量
  2. 一级指针变量的地址
  3. 整型的指针数组的数组名

五. 函数指针

1.函数指针

首先看一段代码

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

输出的是两个地址,这两个地址是 test 函数的地址。 那我们的函数的地址要想保存起来,怎么保存?

void (*pfun)();

解释pfun就是一个函数指针。pfun先和*结合,说明pfun是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

⚠️注意:函数名和&函数名是一样的,都表示的是函数的地址。

2.利用函数指针

int Add(int x, int y)
{

	return x + y;
}
#include
int main()
{
	//函数指针变量
	int (*pfun)(int, int) = Add;

	//调用函数
	int ret1 = (*pfun)(3, 5); //函数的地址存放到函数指针当中
	printf("%d\n", ret1);
	//我们平常写的函数调用
	int ret2 = Add(4, 7);
	printf("%d\n", ret2);
	//那么就有以下表示方法
	int ret3 = pfun(8, 9);
	//所以我们知道 pfun和*pfun是一样的,这里的*只是帮助理解,加多少个*都只是"摆设"
	printf("%d\n", ret3);
	return 0;
}

那么假设一个函数是这么写的 char* test(int c,float* pf),那么这个函数指针该如何表示的呢?

char*(*pt)(intfloat*) = test;

讲到这里,有人会质疑,以上的应用不相当于脱裤子放p,多此一举吗?那函数指针到底是如何应用的呢,答案当然是有的,在函数需要调用另一个函数的时候,那么就需要传入函数的地址了,在形参部分利用函数指针来接收,就需要具体用到函数指针了,这里就不多细讲,感兴趣的同学可以写一段代码,深切体会一下。

3.阅读两段有趣的代码

两段代码源于《C陷阱与缺陷》这本书

  (*(void (*)())0)(); //code1

  void (*signal(int , void(*)(int)))(int); //code2

解释code1:对于code1代码,(*(void (*)())0)(),是将0强制类型转换为void(*)()类型的函数指针,这就意味着0地址处放置着一个函数,该函数没有参数,返回类型为void,然后调用0地址处的这个函数。

 解释code2:对于code2代码,void (* signal(int , void(*)(int)) )(int),这个代码是一个函数的声明。

分析:首先我们可以确定signal这个符号按优先级来说,先和右边的括号结合,里面放了一个参数 int,另一个参数void(*)(int),由此我们可以判断,signal是一个函数,它的一个参数为整型变量,另一个参数为一个函数指针,该函数指针指向的是一个参数为int,返回类型为void的函数。那么signal函数还缺什么呢?缺返回类型,绿色的加粗部分的void(*)(int)表示的就是signal返回类型。

其实大概可以理解为:// void (*) (int) signal(int , void(*)(int)),但是语法格式并不支持,于是signal只能和*结合在一起。

那么我们可以用typedef这个关键字来简化这段代码,使代码变得容易理解。

typedef int* ptr_t; 
typedef void(*pf_t)(int);//将void(*)(int) 类型重命名为pf_t

pf_t signal(int, pf_t); //将 void(*signal(int,void(*)(int)))(int)的简化

六. 函数指针数组

要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr[10])() 

解释:parr1就是一个数组,数组的内容就是int (*)()类型的函数指针

1.函数指针的使用 

写一个简单的计算器

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
 
void menu()
{
	printf("******************************\n");
	printf("****   1. add    2.sub   *****\n");
	printf("****   3. mul    4.div   *****\n");
	printf("****   0. exit           *****\n");
	printf("******************************\n");
}
#include
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

以上代码有没有发现有些许冗余了,未来如果有需要添加整型的其他运算,如<< >> & | ^ ……等运算,代码太冗长,那有没有什么办法将代码给简化一下呢?直接上代码:

2.函数指针数组的应用

以下代码改为了转移表的形式,转移表主要用指针数组来实现,每个元素存一个函数指针,我们只要通过已定义好的编号来进行访问。

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
} 
void menu()
{
	printf("******************************\n");
	printf("****   1. add    2.sub   *****\n");
	printf("****   3. mul    4.div   *****\n");
	printf("****   0. exit           *****\n");
	printf("******************************\n");
}
#include
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	//转移表
	int (*pfarr[5])(int,int) = { NULL,Add,Sub,Mul,Div };
							    //0   1    2   3   4
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pfarr[input](x, y);
			printf("%d \n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("输入有误\n");
		}
	} while (input);
	return 0;
}

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

指向函数指针数组的指针是一个 指针,该指针指向一个 数组 ,数组的元素都是 函数指针

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int main()
{
	int (*pf)(int, int) = Add;
	//函数指针数组
	int (* pfArr[4])(int, int) = {Add, Sub};
	//
	int (*(* ppfArr)[4])(int, int) = &pfArr;//ppfArr是一个指向函数指针数组的指针变量

	return 0;
}

 以上代码中的int (*(* ppfArr)[4])(int, int)就是一个指向函数指针数组的指针,(* ppfArr)可以看出ppfArr是一个指针变量,加上[4]说明是一个数组指针,指针指向的是一个数组,该数组的元素是什么呢?剩余部分就是数组的元素类型了(函数指针),由此,我们将其一步步拆开看,  其实就是一个指针,指向的是一个数组,数组的每个元素类型是函数指针。

八. 回调函数

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

什么意思呢?

我们将上面函数指针数组部分写的简单计算器,觉得冗余的代码,应用在此场景,作出修改编写如下。

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
void menu()
{
	printf("******************************\n");
	printf("*****1.add   2.sub************\n");
	printf("*****3.mul   4.div************\n");
	printf("********0.exit ***************\n");
	printf("******************************\n");
}
void  Calc(int (*pf)(int,int))
{
	int i = 0, j = 0;
	printf("请输入两个整数:>");
	scanf("%d %d", &i, &j);
	printf("%d\n", pf(i, j));
}
#include
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			Calc(Add);
		case 2:
			Calc(Sub);
		case 3:
			Calc(Mul);
		case 4:
			Calc(Div);
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("输入有误\n");
			break;
		}

	} while (input);

	return 0;
}

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

九、qsort函数的应用

qsort 为quick sort的简写,意为快速排序,主要用于对各种类型的数组进行排序,需要引用头文件<stdlib.h>

下面我们来看看它的用法

首先我们在 https://legacy.cplusplus.com/ 网站的旧版本,找到对qosrt函数具体规则的描述。

在搜索框搜索qsort,显示如下:

【搞透C语言指针】那年我双手插兜, 不知道指针是我的对手_第5张图片

 观察以上图的绿色字体部分,就是官方对于qsort函数的声明


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

解释:

  • base -- 指向要排序的数组的第一个元素的指针。
  • num -- 由 base 指向的数组中元素的个数。
  • size -- 数组中每个元素的大小,以字节为单位。
  • compar -- 用来比较两个元素的函数

qsort可以排序任意类型的数据

如果读者知道冒泡排序的算法,是如何实践的,我们可能就知道在比较两个整型的时候,可以用大于小于操作符来比较,在比较字符串的时候,我们可以使用strcmp函数,但是对于两个结构体数据,指定的比较的标准是什么呢?

所以我们不妨设计统一的标准,使各种类型数组元素都可以进行排序,具有通用性。

qsort在声明函数时的参数int (*compar)(const void*, const void*)就是让使用者,指定一个比较的方法(使用者写一个函数,对应此时的函数指针的标准),那么compar函数指针的具体规则是什么呢?

函数指针指向的函数,比较的是两个元素的大小,而p1和p2是待排序的两个元素的地址。

如果p1指向的元素小于p2指向的元素,则返回值是一个小于0的数值;

如果p1指向的元素等于p2指向的元素,则返回值是一个等于0的数值;

如果p1指向的元素大于p2指向的元素,则返回值是一个大于0的数值。

(0默认排序为升序,若想要排序为降序,将p1与p2调换即可)

【搞透C语言指针】那年我双手插兜, 不知道指针是我的对手_第6张图片

话不多说,下面直接上代码。

1.利用qsort来排序整型数组

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
//void表示无具体类型的指针,他可以接收任何类型的地址
int compar(const void* p1, const void* p2)
{
	//使用者在使用的时候需要访问几个字节,
	//就需要进行强制类型转换,为指向的元素类型是什么类型的指针
	//如元素地址是int*类型,就需要强制转换为int*
	return *(int*)p1 - *(int*)p2;   //若将p1和p2调换,则打印的是降序顺序
}
#include
#include
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
    //qsort默认排成升序
	qsort(arr, sz, sizeof(arr[0]), compar);
	print_arr(arr,sz);
	return 0;
}

将结果打印:

2 利用qsort来排序结构体数据

首先编写一个简单的结构体

 struct Stu
{
	char name[20];
	int age;
};
 
int main()
{
	struct Stu s[] = { {"zhangsan",20},{"lisi",18},{"wangwu",30}};
	return 0;
}

 按照年龄的大小来排序(升序)

struct Stu
{
	char name[20];
	int age;
};
//按照年龄来排序
int cmp_stu_by_age(const void* p1,const void* p2)
{
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
#include
#include
int main()
{
	struct Stu s[] = { {"zhangsan",20},{"lisi",18},{"wangwu",30}};
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
	return 0;
}

按F10调试代码,

【搞透C语言指针】那年我双手插兜, 不知道指针是我的对手_第7张图片 排序后的结果正是我们想要的结果

【搞透C语言指针】那年我双手插兜, 不知道指针是我的对手_第8张图片

 数组从小到大排列,age分别是18,20,30

 按照名字的大小来排序(升序)

字符串比较大小需要用到strcmp函数,在比较字符串时, 按照一位一位进行比较,本质比较的是其字符的ACSII码值,若字符串的第一位字符串的ASCII码值与另一个字符串的第一位字符的ASCII码值相等,继续向后比较两者的第二位ASCII码值、第三位……以此类推。

strcmp()函数的比较规则如下:                                                         需要引用头文件

  1. 如果字符串1的第n位的ASCII码值大于字符串2的第n位的ASCII码值 则输出结果:1,表示字符串1 > 字符串2;
  2. 如果字符串1字符串2每一位的ASCII码值都相等,而且长度相同, 则输出结果:0 表示字符串1 == 字符串2;
  3. 字符串1的第n位的ASCII码值小于字符串2的第n位的ASCII码值,则输出结果:-1 表示字符串1 < 字符串2;
struct Stu
{
	char name[20];
	int age;
};
//按照名字来排序
int cmp_stu_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
#include
#include
#include
int main()
{
	struct Stu s[] = { {"zhangsan",20},{"lisi",18},{"wangwu",30}};
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	return 0;
}

 调试代码

【搞透C语言指针】那年我双手插兜, 不知道指针是我的对手_第9张图片

 排序后的结果正是我们想要的结果

【搞透C语言指针】那年我双手插兜, 不知道指针是我的对手_第10张图片

 字符串相比较,两两元素从第一个字符的ASCII码值进行比较,按照元素的大小,'l'<'w'<'z',第一个字符比较出了结果,就不需要进行比较第二个字符ASCII码值的大小了。

十、以冒泡排序的思想,模拟实现qsort函数

我们知道了qsort函数的用法,下面我们就使用冒泡排序的思想实现一个类似于qsort函数的适用于各种类型的冒泡排序。

先在main函数里,将排序的数组,和打印内容罗列下来。 

#include
int main()
{
	int arr[] = { 3,1,5,2,4,9,8,6,5,7 };
	int sz = sizeof(arr) / sizeof(*arr); //sizeof(*arr)表示首元素
	int i = 0;
	printf("排序前:>");
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	    bubble_sort(arr, sz, sizeof(*arr), cmp_int);//实现通用的冒泡排序
	printf("排序后:>");
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;

}

 下面编写bubble_sort函数的内部逻辑:

//以冒泡排序思想模拟实现
//bubble_sort可以排序任意类型数组的数据
void bubble_sort(void* base,int num,int width,int (*cmp)(const void* p1, const void* p2))
{
	int i = 0;
	//趟数
	for (i = 0; i < num - 1; i++)
	{
		int flag = 1;//假设该趟数有序
		//每趟冒泡排序的过程
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			//两两元素进行比较
			//将base强转为char* + j乘以每个元素的大小, 相当于跳过了j*width个字节
			//将base强转为char* + (j + 1)乘以每个元素的大小, 相当于跳过了(j+1)*width 个字节
			if (cmp_int((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				flag = 0;//有交换就置为0
				//交换
				//封装一个Swap函数,用来交换元素
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}

		}
		if (flag == 1)
			break;
	}
}

 将两个元素的交换部分,封装一个Swap()函数,专门在这个函数里面编写。

//传过来的是char*类型,如果要修改元素的大小,则需要将元素的每个字节进行交换
//所以这里也就是为什么要将width传递过来的原因了。
void Swap(char* buf1, char* buf2,int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

最后,使用者自己编写一个比较元素的大小的函数。

int cmp_int(const void* p1,const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

 最后将整体代码放在下面。

int cmp_int(const void* p1,const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}
//传过来的是char*类型,如果要修改元素的大小,则需要将元素的每个字节进行交换
//所以这里也就是为什么要将width传递过来的原因了。
void Swap(char* buf1, char* buf2,int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}
//以冒泡排序思想模拟实现
//bubble_sort可以排序任意类型数组的数据
void bubble_sort(void* base,int num,int width,int (*cmp)(const void* p1, const void* p2))
{
	int i = 0;
	//趟数
	for (i = 0; i < num - 1; i++)
	{
		int flag = 1;//假设该趟数有序
		//每趟冒泡排序的过程
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			//两两元素进行比较
			//将base强转为char* + j乘以每个元素的大小, 相当于跳过了j*width个字节
			//将base强转为char* + (j + 1)乘以每个元素的大小, 相当于跳过了(j+1)*width 个字节
			if (cmp_int((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				flag = 0;//有交换就置为0
				//交换
				//封装一个Swap函数,用来交换元素
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}

		}
		if (flag == 1)
			break;
	}
}
#include
int main()
{
	int arr[] = { 3,1,5,2,4,9,8,6,5,7 };
	int sz = sizeof(arr) / sizeof(*arr); //sizeof(*arr)表示首元素
	int i = 0;
	printf("排序前:>");
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	    bubble_sort(arr, sz, sizeof(*arr), cmp_int);//实现通用的冒泡排序
	printf("排序后:>");
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;

}

最后,将代码打印达到了我们实现的效果。


 创作不易,请给波支持点赞+关注再走吧,嘿~~~

你可能感兴趣的:(【C语言】进阶部分,c语言,开发语言)