指针进阶(万字长文保姆级教程)

指针的概念:

  1. 指针就是一个变量,,用来存放地址,地址唯一标识一块内存空间。
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  3. 指针是有类型的,指针的类型决定了指针+-整数的步长,指针解引用操作时的权限。
  4. 指针的运算。

目录

一、字符指针

1.1 使用路径

1.1.1 字符指针用来存储字符变量的地址

1.1.2 字符指针存放一个字符串的起始地址

 1.2 一道面试题:

二、指针数组

2.2模拟实现二维数组

 三、数组指针

3.1数组指针的定义

3.2 数组指针的使用

3.2.1数组指针在一位数组中的使用

3.2.2 数组指针在二维数组的应用

 3.3 小练习

四、数组参数,指针参数

4.1 一维数组传参

4.2 二维数组传参

4.3 一级指针传参

4.4 二级指针传参

五、函数指针

5.1 基本定义

5.2 对代码的理解

5.2.1代码一

5.2.2 代码二

六、函数指针数组

6.1 函数指针数组的定义

6.2 函数指针数组的用途

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

八、回调函数

         8.1 回调函数示例 

8.2 qsort库函数

8.3 使用冒泡排序的思想模拟qsort函数

一、字符指针

我们知道有一种指针类型是char * 类型,被称为字符指针类型。

字符指针一般有下面两种使用路径:

1.1 使用路径

1.1.1 字符指针用来存储字符变量的地址

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

1.1.2 字符指针存放一个字符串的起始地址

char* ps = "abcdef";

     上述代码指的是:把“abcdef”字符串首字符‘a’的地址放到ps中,一个字符指针可以存放一个字符串的起始地址,注意“abcdef”这个字符串是常量字符串,常量表达式是不能被修改的,所以*ps = ‘a’;是不可以出现在程序中的,否则就会报错,为了避免出现对ps解引用的问题,我们在前面加上const,写成const char* ps = “abcdef”;指明*ps不能被改变。

上述代码的图解是:

 1.2 一道面试题:

#include <stdio.h>
int main()
{
    char str1[] = "hello";
    char str2[] = "hello";
    const char *str3 = "hello";
    const char *str4 = "hello";
    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; 
}

产生上述运行结果的原因是什么呢?

const char *str3 = "hello";     str3里面存放的是常量字符串“hello”的首元素的地址,str4中存放的也是常量字符串“hello”的首元素的地址,常量字符串是不可以修改的,所以在内存中没有必要放两份“hello”,从内存优化的角度,“hello”只会保存一份,所以str3和str4保存的是同一个常量字符串“hello”的地址,str3 == str4。

char str1[] = "hello"; 与  char str2[] = "hello";都是创建一个char类型的数组,数组里放“hello”,str1与str2创建的数组空间不同,所以str1 != str2。

二、指针数组

指针数组是一个存放指针的数组。

int * arr1[10];           //整型指针的数组

char * arr2[4];       //一级字符指针的数组

char ** arr3[4];      //二级字符指针的数组

我们来举一个指针数组的例子:

#include <stdio.h>
int main()
{
	char* arr[5] = { "abcdef","zhangsan","lisi","wangwu","haha" };   //存放指针的数组
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}

     对于每个元素是char*类型的数组来说,它存储的是常量字符串的首字符的地址,%s打印时,只需要知道字符串首元素的地址,就可以打印出字符串,所以运行结果如上。 

2.2模拟实现二维数组

 我们还可以使用指针数组来帮助一维数组模拟实现二维数组。

int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };
	
	int* arr[3] = { 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("\n");
	}
	return 0;
}

 三、数组指针

3.1数组指针的定义

数组指针存放的是数组的地址。

数组指针的类型怎么写呢?

int main()
{
	int arr[10] = { 1,2,3,4,5 };
	int(*p)[10] = &arr;    //取出的是数组的地址,将数组的地址存放到p中,p就是数组指针变量
	//int(*)[10] - > 数组指针类型
	return 0;
}

     注意:在上述的代码中,int (*p)[10]; p先与*结合,说明p是一个指针变量,然后指向的是一个大小为10个整型的数组,所以p是一个指针,指向一个数组,叫做数组指针,[ ]的优先级是要高于*号的,所以必须加上()来保证p先和*结合,否则p先和[]结合就变成指针数组了。

3.2 数组指针的使用

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

3.2.1数组指针在一位数组中的使用

我们传一个数组指针,可以通过这个数组指针来遍历整个数组。

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

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

3.2.2 数组指针在二维数组的应用

      对于一维数组来说,数组名是首元素的地址,那么对于二维数组来说,数组名是什么?

      实际上,对于二维数组来说,数组名也是首元素的地址,但是也有一些区别,对于二维数组,可以将他的一行看做他的一个元素,一个二维数组就可以理解为它有n个元素,每个元素都是有m个整型的一维数组,二维数组的首元素的地址,把二维数组想象成一维数组,一维数组才有首元素,二维数组的首元素就是二维数组的第一行,二维数组的首元素的地址就是二维数组第一行元素的地址。

     我们来用一段代码来加深印象:

void print4(int(*p)[5], int x, int y)
{
	int i = 0;
	for (i = 0; i < x; i++)
	{
		int j = 0;
		for (j = 0; j < y; j++)
		{
			printf("%d ", *(*(p + 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} };
	print4(arr, 3, 5);
	return 0;
}

      上述代码实现了二维数组的打印,在上面函数传参的时候,二维数组的数组名arr代表的是二维数组第一行的元素的地址,即一个一维数组的地址,所以在print4函数接收时需要使用一个数组指针接收参数,其次p = arr;即p = &arr[0];,p是第一行元素的地址,p的类型是int (*)[5],即一个数组指针,数组指针+1跳过一个数组,在上述代码中二维数组的一行元素即代表一个一维数组,即p+1跳过一行元素,*(p+i)就能得到下一行元素的首元素地址。

 3.3 小练习

请解释下面代码的意思:

  1. int arr[5];          整型数组
  2. int *parr1[10];           指针数组
  3. int (*parr2)[10];           指向一个有10个元素的整型数组的指针
  4. int (*parr3[10])[5];      代表数组parr3的每个元素都是指针,指针指向的是一个数组,数组五个元素,每个元素是int类型。即:

四、数组参数,指针参数

4.1 一维数组传参

数组传参,形参可以是数组,也可以是指针。 以下几种传参方式都是正确的:

void test(int arr[])          //形参是数组
{}

void test(int arr[10])        //形参是数组
{}

void test(int* arr)           //形参是指针
{}

void test2(int* arr[10])        //形参是数组
{}

void test2(int** arr)           //形参是指针
{}

int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

4.2 二维数组传参

     二维数组传参需要注意:函数形参的设计只能省略第一个[]的数字,对于一个二维数组来说,可以不知道有多少行,但是必须知道一行多少个元素。

     二维数组传参的时候,可以使用二维数组或数组指针接收。

void test(int arr[3][5])
{}

void test(int arr[][5])
{}

void test(int(*p)[5])
{}

int main()
{
	int arr[3][5] = { 0 };
	test(arr);
	return 0;
}

4.3 一级指针传参

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

4.4 二级指针传参

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

五、函数指针

5.1 基本定义

我们想要把函数的地址保存起来,应该怎么做呢?

有两种方法:

  1. 函数名就是函数的地址
  2. &函数名得到的也是函数的地址
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	printf("%p\n", Add);
	printf("%p\n", &Add);
	//Add与&Add都是函数的地址
	//存放函数地址的指针
	int (*pf)(int, int) = Add;
	int (*pa)(int, int) = &Add;  //pa,pf都是函数指针
	//使用函数指针调用函数
	int sum = (*pf)(3, 5);
	int sum1 = pf(3, 5);
	int sum2 = Add(3, 5);

	return 0;
}

调用函数也有两种方法:

  1. int sum = (*pf)(3, 5);
  2. int sum1 = pf(3, 5);

5.2 对代码的理解

5.2.1代码一

(*(void (*)())0)();

对于上面的代码,我们从内向外来分析:

(void (*)())                 //函数指针类型
(void (*)())0                //代表把0强制类型转换为一个函数的地址,即把0强制类型转换为一个 
                               (void (*)())类型的函数指针(地址)
(*(void (*)())0)();          //调用0地址处的函数
//总体来说,把0直接转换成一个(void (*)())的函数指针,然后去调用0地址处的函数

5.2.2 代码二

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

对于上面的代码,我们从signal入手:

signal(int , void(*)(int)))                  //signal和括号结合,说明signal是一个函数名,函 
                                               数的第一个参数是int类型的,函数的第二个参数是一 
                                               个函数指针类型,该函数指针指向的函数参数是int, 
                                               返回类型是void
void (*signal(int , void(*)(int)))(int);     //signal函数的返回类型也是一个函数指针类型,该函 
                                               数指针指向的函数参数是int类型,返回类型是void
//上述代码是一次函数声明

上述的代码二过于繁琐,我们可以把他简化:

typedef void(*pf_t)(int);      //把刚刚的函数指针类型重命名为pf_t
pf_t signal(int, pf_t);

六、函数指针数组

6.1 函数指针数组的定义

函数指针是用来存放函数的地址的。

函数指针数组即为一个存放函数地址的数组。

函数指针数组的类型怎么写呢?

我们可以根据函数指针类型来改写。

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[2])(int, int) = {Add, Sub};      //函数指针数组

	int ret = pfArr[0](2, 3);
	//通过函数指针数组的元素调用函数

	ret = pfArr[1](2, 3);

	return 0;
}

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;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入2个操作数:");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入2个操作数:");
			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);
}

     如果我们想要再加一个运算,在上述的代码中,我们需要改动许多地方,那么有什么地方可以优化吗?我们想到了函数指针数组。

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;
	do
	{
		menu();
		printf("请选择:");
		scanf("%d", &input);
		int (*pfArr[5])(int, int) = { 0, Add, Sub, Mul, Div };
		if (input >= 1 && input <= 4)
		{
			printf("请输入2个擦作数:");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else
		{
			printf("选择错误,请重新选择\n");	
		}
	} while (input);
}

     上述的代码相对于之前的代码来说,代码的重复率降低,代码的效率提高。

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

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

void test(const char* str)
{
	printf("%s\n", str);
}

int main()
{
	//函数指针pfun
	void (*pfun)(const char*) = test;
	//函数指针数组pfunArr
	void (*pfunArr[5])(const char*);
	pfunArr[0] = test;
	//指向函数指针数组pfunArr的指针ppfunArr
	void (*(*ppfunArr)[5])(const char*) = pfunArr;
	return 0;
}

八、回调函数

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

  8.1 回调函数示例 

     这个时候,我们再拿出上面计算器的代码,发现还可以继续优化,上面的第一个代码,有多处重复出现的代码,出现了一些相似的代码,我们就可以想到能不能将这些相似的代码封装成一个函数,将其封装为一个函数calc,calc中有可能执行加法,可能执行减法......有所差异,把差异的部分提取出来,用参数的形式传进去。


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

void calc(int(*p)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入两个操作数:");
	scanf("%d %d", &x, &y);
	int ret = p(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);
}

     在上述代码中,函数不是直接被调用,而是把函数的地址传给函数cala,然后在calc函数的内部通过函数指针去调用对应的函数。

     在这里,在calc函数内部通过函数指针调用Add函数时,Add函数就被称为回调函数。

     之前的博客里面我们实现了冒泡排序,冒泡排序的思想是两两相邻元素进行比较,一趟冒泡排序处理一个数字,有n个数字要进行n-1次冒泡排序。

如果我们使用冒泡排序的思想,代码如下:

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

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		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;
			}
		}
	}
}
void test1()
{
	//冒泡排序
	//对整形数据进行排序-排序为升序
	int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	print(arr, sz);
}

int main()
{
	//冒泡排序
	test1();
	return 0;
}

      我们发现上述的冒泡排序只能排序两个整型数据,那么进行怎样的改动可以使它能够排序不同类型的数据呢?我们知道排序的数据类型不同,排序需要的比较方法不同,但是其他地方相同,我们把不同的部分提取出来,即把不同的比较方法写成函数使用传参的形式传给另一个函数。我们可以使用c语言库函数qsort来实现不同类型数据的比较,再参照库函数qsort的实现方式来优化上述代码。

8.2 qsort库函数

      我们可以依照qsort库函数的解释来研究qsort函数。

//qsort函数 - 可以排序任意类型的数据
//qsort函数需要的参数及返回类型的分析
//void qsort (void* base,      //待排序数据的起始地址
//            size_t num,      //待排序数据的元素个数
//            size_t size,     //待排序数据元素的大小(单位是字节)
//            int (*compar)(const void*, const void*)       //比较2个元素大小的函数指针
//);  

     注意:qsort函数接收待排序的起始地址是用void * 类型的指针来接收,这有什么好处呢?void * 类型的指针,指无类型的指针,void * 的指针时非常宽容的,可以接收任意类型的地址,之前就满足了我们要比较不同类型数据时接收不同类型指针的要求,但是注意void * 类型的指针是不能对他进行解引用操作的,void *  p = &i;   如果你知道p指向的是整型,想用p去访问一个整型的空间,可以对它进行强制类型转换,将它强制类型转换为整型指针,再对它进行解引用,,其次p++也不可以,只有强制类型转换为想要的类型才能进行++操作。

     我们在使用qsort函数时需要给它传一个比较两个元素大小的函数指针,要求函数的两个参数都是const void*类型,如果我们想要比较两个整型的大小,我们可以写成以下代码:

void cmp_int(const void* e1, const void* e2)
{
	if (*(int*)e1 > (int*)e2)
		return 1;
	else if (*(int*)e1 < (int*)e2)
		return -1;
	else
		return 0;
	//return (*(int*)e1 - *(int*)e2);
}

     使用qsort函数对整型数据进行升序的完整代码如下:

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

void cmp_int(const void* e1, const void* e2)
{
	if (*(int*)e1 > (int*)e2)
		return 1;
	else if (*(int*)e1 < (int*)e2)
		return -1;
	else
		return 0;
	//return (*(int*)e1 - *(int*)e2);
}

//升序
void test2()
{
	int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr,sz);
}

int main()
{
	//冒泡排序
	test1();
	test2();
	return 0;
}

    使用qsort函数对整型数据进行降序的完整代码如下:

 

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

void cmp_int(const void* e1, const void* e2)
{
	if (*(int*)e1 < (int*)e2)
		return 1;
	else if (*(int*)e1 > (int*)e2)
		return -1;
	else
		return 0;
	//return (*(int*)e2 - *(int*)e1);
}

void test2()
{
	int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr,sz);
}

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

     如果我们想要比较两个结构体的内容,我们可以写成以下代码:

     假设结构体为:

struct Stu
{
	char name[20];
	int age;
};

我们想要对结构体变量的姓名部分进行比较:

void cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

我们想要对结构体变量的年龄部分进行比较:

void cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

使用qsort函数对结构体某一部分进行排序的完整代码如下:

struct Stu
{
	char name[20];
	int age;
};

void print(struct Stu arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i].age);
	}
	printf("\n");
}

void cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

void cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

void test3()
{
	struct Stu s[] = { {"zhangsan",20},{"lisi",25} ,{"wangwu",30} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);

	print(s, sz);
}

int main()
{
	test3();
	return 0;
}

     注意:对于qsort函数来说,qsort函数接收的函数指针参数指向的函数如果返回值>0,那么代表两个比较元素需要发生交换,如果返回值<0或者=0,那么代表两个比较元素不需要发生交换,在qsort函数的标准解释中也有相关说明。

8.3 使用冒泡排序的思想模拟qsort函数

     我们对可以进行各种数据类型排序的qsort函数大致了解了,qsort函数的底层排序是快速排序,那我们就想使用冒泡排序的思想来模拟实现一个qsort函数。 

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

void cmp_int(const void* e1, const void* e2)
{
	if (*(int*)e1 > (int*)e2)
		return 1;
	else if (*(int*)e1 < (int*)e2)
		return -1;
	else
		return 0;
	//return (*(int*)e1 - *(int*)e2);
}

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

void bubble_sort2(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 j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}
}

void test2()
{
	int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort2(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr,sz);
}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值