7.指针-进阶

前言

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

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

  3. 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。

  4. 指针的运算。

1. 字符指针

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

一般使用:

int main()
{
    char ch = 'w';
    printf("%c\n", ch);
    char *pc = &ch;
    *pc = 'e';
    printf("%c\n", ch);
   
    return 0;
}

int main()
{

	const char* p = "abcdef";//把字符串首字符a的地址,赋值给了p
	//char arr[] = "abcdef";
	//*p = 'w';     因为字符串"abcdef"是常量字符串 不能解引用之后被修改,因此用const来修饰 *p

	printf("%s\n", p);
	return 0;
}
思考打印的结果是什么
int main()
{
    // 把字符串首字符a的地址,赋值给了p
    // 常量字符串是不会被修改的,因此首元素的地址也是相同的
	const char* p1 = "abcdef";
	const char* p2 = "abcdef";

    // 将"abcdef"用来初始化数组,其实就是将常量区的字符串拷贝了一份,再进行数组的初始化
    // 因此两个数组首元素的地址是不一样的,因为它们是常量字符串的两份拷贝
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";

	if (p1 == p2)
		printf("p1==p2\n");
	else
		printf("p1!=p2\n");

	if (arr1 == arr2)
		printf("arr1 == arr2\n");
	else
		printf("arr1 != arr2\n");

	return 0;
}

image-20220731221050032

2. 指针数组(存放指针的数组)

int* arr1[10];   //整型指针的数组
char* arr2[4];   //一级字符指针的数组
char** arr3[5];  //二级字符指针的数组
  • 下面的代码可以很好的帮助我们理解指针数组
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* parr[3] = { arr1, arr2, arr3 };

	
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			//*(p+i) --> p[i]
			//printf("%d ", *(parr[i] + j));  两种打印方法都可以,是等价的
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}
  • 结合下面的图来分析上述代码

image-20220731222051275

3. 数组指针(指向数组的指针)

3.1 数组指针的定义

数组指针 : 指向数组的指针

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

int* p1[10];
int (*p2)[10];    //p2的类型为 int(*)[10]


int *p1[10];
// 因为[]的优先级高于*,因此p1会先与[]结合,所以 p1[10]是一个数组,数组元素的类型为int*,因此为指针数组

int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组,数组每个元素为int类型。所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

3.2 &数组名VS数组名

数组名通常表示的都是数组首元素的地址
但是有2个例外:
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
2. &数组名,这里的数组名表示的依然是整个数组,所以&数组名取出的是整个数组的地址
int main()
{
    int arr[10] = {0};
	// 数组首元素的地址
	printf("%p\n", arr);
    // 数组首元素的地址
	printf("%p\n", &arr[0]);
    // 整个数组的地址
	printf("%p\n", &arr);
	
	return 0;
}

image-20220731225021997

  • 下面这个代码可以区分arr和&arr的用法

int main()
{
    int arr[10] = {0};

	printf("%p\n", arr);
	printf("%p\n", arr+1);

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

	printf("%p\n", &arr);
	printf("%p\n", &arr+1);        //整个数组的地址+1,是跳过整个数组

	return 0;
}

image-20220731225200113

int main()
{              
	int arr[10] = {0};
    // 数组指针,这个指针指向一个数组
	int (*p)[10] = &arr;  
    return 0;             
}                         

// p(指针变量)的类型为int(*)[10]; p里面存放的是整个数组的地址; p+1 指针是跳过整个数组;
//  其实*p也相当于数组名,也就是数组首元素的地址
    

int main()
{
    char* arr[5] = {0};
	char* (*pc)[5] = &arr;
    
    
    char ch = 'w';
	char* p1 = &ch;
	char** p2 = &p1;
    
    return 0;
}

3.3 数组指针的使用

  • 先介绍一种不常用的数组指针的使用

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

	int (*p)[10] = &arr;
	
	int i = 0;
    // sz是数组元素的个数
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
        // p是指向数组的,*p其实就相当于数组名,数组名又是数组首元素的地址
        // 所以*p本质上是数组首元素的地址
		printf("%d ", *(*p+i));		                       
	}

	return 0;
}
  • 上面的代码使用起来非常的别扭,不如直接使用下面的代码来打印
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };

    // p就是指针变量,存储的是数组首元素的地址
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
    
    return 0;
}
  • 那么数组指针应该怎样来使用呢?请阅读以下代码
// int* p
// int arr[3][5],其实是一个二维数组的指针(接收的是二维数组首行的地址)
void print1(int arr[3][5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
            // arr[0] -> *(arr+0)   两者是等价的; arr[0]就是二维数组的首行地址
            // arr+0是一个指针变量,它指向二维数组的首行地址, *(arr+0) 拿到首行元素的地址
            // arr[i][j] -> *(*(arr+i)+j)
			printf("%d ", arr[i][j]); 
		}
		printf("\n");
	}
}

// int (*p)[5] 接收的是第一行元素的地址
void print2(int (*p)[5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{                                      
			//printf("%d ", *(*(p + i) + j));  //两种打印方法是等价的
			printf("%d ", p[i][j]);            // p[i]就是第i行元素的首元素地址
		}
		printf("\n");
	}
}


int main()
{
    // arr是一个二维数组
	int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
	print2(arr, 3, 5);
	
	return 0;
}
  • 对上述传参,和打印参数的分析如下

image-20220801140640668

image-20220801140846464

  • 学习了指针数组和数组指针,我们分析一下下面代码的意思
int arr[5];        		// 整形数组arr
int *parr1[10]; 		// 整型的指针数组parr1

int (*parr2)[10];		// 数组指针parr2
// *说明parr2是一个指针;   去掉parr2,可以看出类型为:int (*)[10],说明parr2是一个指向数组的指针,且这个数组存放的元素类型为int

// 如下图所示
int (*parr3[10])[5];  // 存放数组指针的数组(int (*)[5] 是数组指针,parr3[10]是数组)
// 首先[]的优先级比*要高,因此parr3先于[10]结合,说明parr3[10]这是一个整体,是一个数组;parr3[10]前面有*,说明parr3[10]是一个指针数组
// 去掉parr3[10],可以看到int (*)[5],所以这个指针数组parr3[10],存放的指针指向的是一个数组,这个数组存放的元素类型为int

image-20240324210017778

4. 数组参数、指针参数

4.1 一维数组传参

#include <stdio.h>
void test(int arr[])
{}

void test(int arr[10])
{}

void test(int *arr)
{}

// int arr[10] 传参对应 int arr[]
// int* arr2[20] 传参对应 int* arr2[20]
void test2(int* arr[20])
{}

void test2(int **arr)
{}

int main()
{
    // 对于int arr[10],数组首元素的地址类型为int*
 	int arr[10] = {0};      //数组可以进行如上传参
    
    // 对于int* arr2[20],数组首元素的地址类型为int**
 	int* arr2[20] = {0};    //指针数组可以进行如上传参
    
	test(arr);
 	test2(arr2);
    
    return 0;   
}

4.2 二维数组传参

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



// 二维数组的数组名,表示的是首行的地址,也就是第一行元素的地址
// 第一行是一个有五个整型元素的一维数组,因此传参应该用数组指针来接收
// 第一行的类型为 int [5]
void test(int *arr)        //错误传参
{}
void test(int* arr[5])     //错误传参
{}

// arr指向一个数组,这个数组有5个元素,元素类型为int
// arr就是二维数组的首行地址
// *(arr+0) 就是首行首元素的地址;   *(*(arr+0)+0) 就是首行首元素
void test(int (*arr)[5])   //正确传参
{
    printf("%p\t", arr);
	printf("%p\t", *arr);
	printf("%p\t", **arr);
}
void test(int **arr)       //错误传参
{}

int main()
{
 	int arr[3][5] = {{1,2,3},{2,3,4},{4,5,6}};
	 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; 
}

/*
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
比如:
test1函数能接收什么参数?
*/

void test1(int *p)
{}

// 经过思考得
int main()
{
    int arr[] = {1,2,3,4,5};
    test1(arr);
    
    int a = 10;
    int* ptr = &a;
    test(&a);
    test1(ptr);
    
}

4.4 二级指针传参

#include <stdio.h>

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 test(char **p) 
{
    
}
int main()
{
 	char c = 'b';
 	char* pc = &c;
 	char** ppc = &pc;
	char* arr[10];
 
 	test(&pc);
 	test(ppc);
     
    //指针数组也传参,也可以用二级指针接收,
    // 指针数组的每个元素都为char*, arr又为首元素的地址,所以数组首元素地址的类型就是char**,因此可以用二级指针来接收
	test(arr);   
   
	return 0; 
}

5. 函数指针(指向函数的指针)

  • 对比数组和函数
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	//  数组
    int a = 10;
	int* pa = &a;
	*pa = 20;
	printf("%d\n", *pa);

    int arr[5] = {0};
    //&数组名 就是 取出整个数组的地址
    
    int (*p)[5] = &arr;//数组指针,这个指针p指向一个数组

    
    //  函数
    //&函数名 就是 取出的就是函数的地址呢?
    printf("%p\n", &Add);
    printf("%p\n", Add);
    //对于函数来说,(&函数名)和(函数名)都是函数的地址(可以自己在编译器上运行)
   
    // int (*pf)(int, int) = &Add;
    // pf就是一个函数指针,指向一个函数,这个函数的范围值为int,两个参数的类型是(int, int)
    int (*pf)(int, int) = Add;
    // 取函数的地址,放入函数指针,

    // *pf就是拿到函数
    int ret = (*pf)(2, 3);
    int ret = Add(2, 3);
    int ret = pf(2, 3);
  	//  *pf和pf都可以来调用Add函数;调用时(有无* 都可以);
  	// pf存放函数的地址,Add也就是函数的地址
  	// 因此Add(2, 3)就相当于pf(2, 3)
    
    printf("%d\n", ret);
    return 0;
}
  • 阅读两段有趣的代码:
//代码1 
( *(void (*)() )0 )();

注:void (*p)(); 其中p为函数指针, void (*)()为函数指针类型,这个函数指针指向的函数没有参数,返回值为void
    0 看做是整型  类型为int
    0 也可以看做是地址 0x00 00 00 00
因此,我们可以得出以下结论:
    以上代码是一次函数的调用,调用的是函数地址为0的函数。
    1.(void (*)())0; 就是将0强转为一个函数指针(指向的这个函数无参数,返回类型为void);
    2.(void (*)())0简化为函数指针p,那么 ( *(void (*)() )0 )() 就是(*p)()
    // 综上,( *(void (*)() )0 )() 就是调用函数地址为0的函数指针
    

//代码2
    void (* signal(int,void (*)(int)) )(int);
注: signal是函数名;	
    // 函数为signal(int , void (*)(int);   返回值为void (*)(int)
    因此我们可以得出结论:以上代码是一次函数声明
    1.声明的signal函数的第一个参数的类型是int,第二个参数的类型是函数指针void (*)(int),
    且该函数指针指向的函数参数的类型为int,函数返回类型是void2.signal函数的返回类型是void (*)(int);
     
注:这个函数声明太复杂了,我们可以使用typedef将其简化如下
   typedef  void(* pf_t)(int) ; / /void(*)(int)类型重命名为 pf_t
  则原代码就相当于 pf_t signal(int, pf_t)                         
    

函数指针的用途

简单的写一个满足加法、减法、乘法、除法的计算器

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 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;
}

//计算
//回调函数


// 使用函数指针pf来调用,相对应的函数
void calc(int (*pf)(int , int))
{
	int x = 0;
	int y = 0;
	int ret = 0;

	printf("请输入2个操作数:>");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("%d\n", ret);
}

int main()
{
	int input = 0;

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		
		switch (input)
		{
		case 1:
			calc(Add);
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mul);
			break;
		case 4:
			calc(Div);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;
}

  • 思考 上述代码使用了函数指针 ,带来了那些便利?

由下图,我们可以得出结论,如果不使用函数指针,调用每一个函数的代码,都会有大量重复的代码。

image-20220815204218144

进阶第二部分

6. 函数指针数组

数组是一个存放相同类型数据的存储空间,比如:

int* arr[10];
//数组的每个元素是int*

6.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;
}


int main()
{
	int (*pf)(int, int) = Add;  //pf是函数指针
    //arr就是函数指针的数组
	int (*arr[4])(int, int) = { Add, Sub, Mul, Div };
    //arr[4]数组中每个元素的类型为 int (*)(int,int)
    
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = arr[i](8, 4);  //调用函数指针数组中的函数
		printf("%d \n", ret);
	}

	return 0;
}


  • 附图一张,便于大家理解函数指针数组

image-20220815212950354

6.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 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;
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;

	//函数指针的数组
	//转移表
	int (*pfArr[])(int, int) = {0, Add, Sub, Mul,  Div};

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);//pfArr[1]:即调用函数指针数组中下标为1的函数
			printf("%d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}

	} while (input);

	return 0;
}

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

int main()
{
	//函数指针数组
	int (*pfArr[])(int, int) = { 0, Add, Sub, Mul,  Div };

	//指向【函数指针数组】的指针
    // (*ppfArr) 说明ppfArr是一个指针,指向函数指针数组int (* [5])(int, int)
	int (*(*ppfArr)[5])(int, int) = &pfArr;

	return 0;
}

8. 回调函数

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

8.1 冒泡排序

9 8 7 6 5 4 3 2 1
第一趟之后就是: 8 7 6 5 4 3 2 1 9   // 第一趟进行8次比较,第二趟进行7次比较
第二趟之后就是: 7 6 5 4 3 2 1 8 9
// 9个数一共需要9趟

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		//假设数组已经按照从小到大排好序了,这时flag为1
		int flag = 1;
		//一趟冒泡排序的过程
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			// 如果下标
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;

				// 说明此时还没有排序完成
				flag = 0;
			}
		}

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

int main()
{
	int arr[] = { 10,2,3,4,5,6,7,8,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);

	int i = 0;
	while (sz)
	{
		printf("%d\t", *(arr + i));
		i++;
		sz--;
	}
	return 0;
}

8.2 qsort 函数

  • 再使用qsort函数之前 我们需要了解如下的知识点
// c语言库中的快速排序接口
//  void qsort(void* base,//你要排序的数据的起始位置
//  	       size_t num,//待排序的数据元素的个数
//  	       size_t width,//待排序的数据元素的大小(单位是字节)
//  	       int(* cmp)(const void* e1, const void* e2)//函数指针-比较函数
//           );


int main()
{
	int a = 10;
	//char* pa = &a;// int*和char*类型不同
	void* pv = &a;//void*是无具体类型的指针,可以接受任意类型的地址
	//void*是无具体类型的指针,所以不能解引用操作,也不能+-整数

	return 0;
}

image-20220815221337075

image-20220815221309475

image-20220815221438620

  • 利用qsort来排序
//qsort -这个函数可以排序任意类型的数据

//  void qsort(void* base,//你要排序的数据的起始位置
//  	       size_t num,//待排序的数据元素的个数
//  	       size_t width,//待排序的数据元素的大小(单位是字节)
//  	       int(* cmp)(const void* e1, const void* e2)//函数指针-比较函数
//           );

#include <stdlib.h>

//比较2个整型元素
//e1指向一个整数
//e2指向另外一个整数

// 第一种cmp_int 函数
int cmp_int(const void* e1, const void* e2)
{
    // 根据compare函数的返回值制定的规则
	if (*(int*)e1 < *(int*)e2)                //因为void* 不能够被解引用,
        return -1;                            //又因为已知比较数据的类型为int,所以强转为int*
    else if (*(int*)e1 > *(int*)e2)
        return 1;
    else
        return 0;
}

// 第二种cmp_int 函数(简洁并且和第一种的效果相同)
int cmp_int(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);
}

void test1()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	//0 1 2 3 4 5 6 7 8 9
	//把数组排成升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	//bubble_sort(arr, sz);

	qsort(arr, sz, sizeof(arr[0]), cmp_int);

	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

8.2.1我们也可以利用qsort函数来对结构体排序

首先我们需要了解一个比较字符串的函数strcmp。

image-20220817143722493

image-20220817144156136

// 这是一个学生的结构体
struct Stu
{
	char name[20];
	int age;
};


// 1.可以通过每个学生的姓名来排序
// strcmp
// abbdef
// abbqwerttt    (a的ASCILL值小于b,所以b比a大)
// 这两个字符串通过strcmp比较,首先是对两个字符串的首元素进行比较,其次第二个元素,以此类推;

int cmp_stu_by_name(const void* e1, const void* e2)
{
	//strcmp --> (>0   ==0   <0)
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}


// 2.也可以通过年龄来排序
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}


void test2()
{
	//测试使用qsort来排序结构数据
	struct Stu s[] = { {"zhangsan", 15}, {"lisi", 30}, {"wangwu", 25} };
	int sz = sizeof(s) / sizeof(s[0]);
	
    //qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}



int main() 
{
  //test1();
  //test2();
    return 0;
}

8.2.2 模仿qsort来改造我们自己写的bubble_sort


//  void qsort(void* base,//你要排序的数据的起始位置
//  	       size_t num,//待排序的数据元素的个数
//  	       size_t width,//待排序的数据元素的大小(单位是字节)
//  	       int(* cmp)(const void* e1, const void* e2)//函数指针-比较函数
//           );

// 由qsort 得
// void bubble_sort(void*base,  //你要排序的数据的起始位置
//                  int sz,     //待排序的数据元素的个数
//                  int width,  //待排序的数据元素的大小(单位是字节)
//                  int(*cmp)(const void*e1, const void*e2) /函数指针-比较函数
//                  );

 //1. 当对数字进行比较时,调用 cmp_int
int cmp_int(const void* e1, const void* e2)
{
  return (*(int*)e1 - *(int*)e2);
}


 //2.当对字符串进行比较时,调用cmp_stu_by_name
int cmp_stu_by_name(const void* e1, const void* e2)
{
	//strcmp --> (>0   ==0   <0)
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}


// 交换函数
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++;
	}
}

//  width: 传参对象的大小,单位为byte
//  由于并不知道使用者会传参什么类型的对象的地址,
// (注char* arr+1,每次跳过1byte; int* arr+1,每次跳过4byte)
// 1.因此(char*)base+j*width, (char*)base+(j+1)*width)分别是我们将比较或者将交换的两个对象的地址
// 2.char*类型,每次解引用只能解引用一个字节,因此对于任何类型的对象,我们都可以一个字节一个字节的交换。
//   所以我们需要将传递的对象的地址强制转化为char*类型


void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void*e2))
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		int flag = 1;//假设数组是排好序
		//一趟冒泡排序的过程
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
            // cmp() 返回值如果大于0,说明e1 > e2
			if (cmp((char*)base+j*width, (char*)base+(j+1)*width)>0)
			{
				// 使用交换函数,交换两个元素的位置
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}


// 对数组进行排序
void test3()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	//0 1 2 3 4 5 6 7 8 9
	//把数组排成升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	//bubble_sort(arr, sz);

	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);

	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}


// 对结构体进行排序
void test4()
{
	//测试使用qsort来排序结构数据
	struct Stu s[] = { {"zhangsan", 15}, {"lisi", 30}, {"wangwu", 25} };
	int sz = sizeof(s) / sizeof(s[0]);
	
    bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	//bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}


int main()
{
    test3();
    test4();
        
    return 0;
}
  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值