指针---C语言

指针详解目录

  • 1.指针定义
    • 1.1指针变量
    • 1.2理解要点
    • 1.3补充理解
  • 2.指针的类型
    • 2.1指针+-整数
    • 2.2指针的解引用
      • 2.2.1同样大小字节的指针解引用
  • 3.野指针
    • 3.1野指针成因
      • 3.1.1指针未初始化
      • 3.1.2指针访问越界
      • 3.1.3指针指向的空间释放
    • 3.2如何规避野指针
  • 4.指针运算
    • 4.1指针+- 整数
    • 4.2指针-指针
    • 4.3指针的关系运算(比大小)
  • 5.指针与数组
    • 5.1数组赋值
    • 5.2字符指针
  • 6.二级指针
    • 6.1二级指针的类型
  • 7.指针数组
    • 7.1用指针数组表示二维数组
  • 8.数组指针
    • 8.1数组指针定义
    • 8.2 &数组名VS数组名
    • 8.3数组指针的使用
  • 9.数组参数、指针参数
    • 9.1一维数组传参
    • 9.2二维数组传参
    • 9.3一级指针传参
    • 9.4二级指针传参
  • 10.函数指针
    • 10.1函数指针介绍与应用
    • 10.2两组有趣的代码
  • 11.函数指针数组
    • 11.1函数指针数组定义
    • 11.2函数指针数组应用举例
  • 12.指向函数指针数组的指针
  • 13.回调函数
    • 13.1 qsort函数
      • 13.1.1qsort排序整型数据
      • 13.1.2 qsort排序结构体数据
    • 13.2模拟实现qsort函数
      • 13.2.1模拟实现qsort函数---基于冒泡排序
      • 13.2.2 整型排序检验
      • 13.2.3 结构体排序检验

指针---C语言_第1张图片

1.指针定义

指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。

要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。让我们分别说明。

1.1指针变量

指针变量
我们可以通过&(取地址操作符)取出变量的内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量,如下。

int main()
{
 int a = 10;//在内存中开辟一块空间,存储a的值
 int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
    //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
//中,p就是一个指针变量。
 return 0;
}

指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。

1.2理解要点

指针理解的2个要点:

  1. 指针是内存中一个最小单元的编号,也就是地址,(仅代表一个字节的地址)。
  2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

1.3补充理解

经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)也就是(1或者0);那么32根地址线产生的地址就会是:

00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
11111111 11111111 11111111 11111111

这里就有2的32次方个地址。 每个地址标识一个字节,那我们就可以给 (2^32Byte => 2^32/1024KB => 2^32 /1024/1024MB =>2^32/1024/1024/1024GB ==>4GB)4G的空间进行编址。
同样的方法,那64位机器,如果给64根地址线,那能编址2^34G的空间。

这里我们就明白:
32位的机器上,地址是32个0或者1组成二进制序列(也就是32个比特位),那地址就得用4个字节(一个字节8个比特位)的空间来存储,所以一个指针变量的大小就应该是4个字节
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

int main()
{
	char* pc = NULL;
	int* pi = NULL;
	short* ps = NULL;
	long* pl = NULL;
	float* pf = NULL;
	double* pd = NULL;

	printf("%d\n", sizeof(pc));
	printf("%d\n", sizeof(pi));
	printf("%d\n", sizeof(ps));
	printf("%d\n", sizeof(pl));
	printf("%d\n", sizeof(pf));
	printf("%d\n", sizeof(pd));

	return 0;
}

指针---C语言_第2张图片指针---C语言_第3张图片

总结: 指针变量是用来存放地址的,地址是唯一标示一个内存单元的。 指针的大小在32位平台是4个字节,在64位平台是8个字节

2.指针的类型

char  *pc = NULL;
int   *pi = NULL;
short *ps = NULL;
long  *pl = NULL;
float *pf = NULL;
double *pd = NULL;

char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int*类型的指针是为了存放 int 类型变量的地址。其他同样。
那么指针类型的意义是什么呢?

2.1指针±整数

int main()
{
 int n = 10;
 char *pc = (char*)&n;//强制类型
 int *pi = &n;
 
 printf("%p\n", &n);
 printf("%p\n", pc);
 printf("%p\n", pc+1);
 printf("%p\n", pi);
 printf("%p\n", pi+1);
 return  0;
}

指针---C语言_第4张图片

总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。

2.2指针的解引用

int main()
{
 int n = 0x11223344;
 int *pi = &n;
 *pi = 0;   //重点在调试的过程中观察内存的变化。
 return 0;
}

指针---C语言_第5张图片

int 类型的指针解引用,将int类型的四个字节内容都改变;

int main()
{
	int n = 0x11223344;
	char* pc = (char*)&n;
	*pc = 0; 	//重点在调试的过程中观察内存的变化。
	return 0;
}

指针---C语言_第6张图片

char 类型的指针解引用,将int类型的一个字节内容都改变;

结论:
指针类型决定了指针在解引用的时候访问几个字节,即决定了指针的步长。
如果是int的指针,解引用访问4个字节;
如果是char
的指针,解引用访问1个字节;
其他类型同样适用。

2.2.1同样大小字节的指针解引用

指针---C语言_第7张图片

int 和float类型都是4个字节,但仍不可通用,都放入100但结果不同。(这与浮点数的存储有关,详情见数据存储章节)

3.野指针

野指针就是:指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。

3.1野指针成因

3.1.1指针未初始化

int main()
{ 
	 int *p;//局部变量指针未初始化,默认为随机值
	 *p = 20;
	 return 0;
}

未初始化不知道指针指向哪个变量的地址。
一个局部变量没初始化的话,放随机值:0xcccccccc

不知道地址时,可用NULL初始化,但此时不可直接解引用,需加上判断,如下

//错误形式
int* p=NULL;//NULL-0
*p=100;//err-不能访问0地址

//正确形式
int* p=NULL;//NULL-0
if(p!=NULL{
	*p=100;
}

3.1.2指针访问越界

int main()
{
    int arr[10] = {0};
    int *p = arr;
    int i = 0;
    for(i=0; i<=11; i++)
   {
        //当指针指向的范围超出数组arr的范围时,p就是野指针
        *(p++) = i;
   }
    return 0;
}

数组下标范围0-9,i超出范围。

3.1.3指针指向的空间释放

使用完了指针,一定要记得释放指针指向的内存。释放后一定要记得设置指针为空指针。

使用free函数在释放指针后,只是单纯的释放了该指针指向的内存空间,而没有将指针赋为空值。所以一定要记得在释放指针后将指针赋为空值。如:

int* p=NULL;//指针定义处
...
free(p);//释放指针
p=NULL; //指针赋为空值

同时避免对局部变量返回其地址,如

int* test()
{
	int a=0;
	return &a;//局部变量的地址,出来后会被释放给内存
}

注:详解会在动态内存开辟进行介绍

3.2如何规避野指针

  1. 指针初始化
  2. 小心指针越界
  3. 指针指向空间释放,及时置NULL
  4. 避免返回局部变量的地址
  5. 指针使用之前检查有效性

4.指针运算

指针± 整数
指针-指针
指针的关系运算

4.1指针± 整数

#define VALUES 5
float values[VALUES];
float* vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[VALUES];)
{
    *vp++ = 0;//*vp;vp++
}

将数组赋值0
补见本文2.1

4.2指针-指针

例如,不用strlen库函数,实现测量字符串长度。

int my_strlen(char* s)
{
    char* p = s;
    while (*p != '\0')
        p++;
    return p - s;//指针-指针
}
int main()
{
    int len = my_strlen("abcdef");
    printf("%d\n", len);
    return 0;
}

4.3指针的关系运算(比大小)

//代码一
for(vp = &values[N_VALUES]; vp > &values[0];)
{
    *--vp = 0;
}//最小地址为&value[0],未与前面的地址比较

//代码二
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
    *vp = 0;
}//会出现&value[0]前面的地址与之比较

两组代码实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。

标准规定
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,
但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

5.指针与数组

数组名表示的是数组首元素的地址。(2种情况除外,详情见数组章节)

int main()
{
    int arr[] = {1,2,3,4,5,6,7,8,9,0};
    int *p = arr; //指针存放数组首元素的地址
    int sz = sizeof(arr)/sizeof(arr[0]);
    int i=0;
    for(i=0; i<sz; i++)
   {
        printf("&arr[%d] = %p   <====> p+%d = %p\n", i, &arr[i], i, p+i);
   }
    return 0;
}

指针---C语言_第8张图片

可见用指针可以对数组进行访址。

5.1数组赋值

数组赋值有以下三种方法:

//int *p=arr;
//arr[i]==*(p+i)==*(arr+i);

int main()
{
	int arr[10] = { 0 };
	int* p = arr; //指针存放数组首元素的地址
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	//第一种
	for (i = 0; i < sz; i++)
	{
		arr[i] = 1;
	}
	//第二种
	for (i = 0; i < sz; i++)
	{
		*p=1;
		p++;
	}
	//第三种
	for (i = 0; i < sz; i++)
	{
        *(p + i)=1;
	}
	return 0;
}

指针-指针(地址-地址)得到的指针和指针之间元素的个数;
但不是所有的指针都能相减,只有指向同一块空间的2个指针才能相减。如下

//正确
int main()
{
	int arr[10] = { 0 };
	int ret = &arr[9] - &arr[0];
	printf("%d\n", ret);//ret=9
	return 0;
}

//错误
int arr[10] = { 0 };
char ch[5] = { 0 };
int ret = &ch[0] - &arr[0];

5.2字符指针

int main()
{
 const char* pstr = "hello bit.";//这里是把字符串的首元素地址放到pstr指针变量里,同数组名
 printf("%s\n", pstr);
 return 0;
}

那么就会又这样的易混点:

int main()
{
	 char str1[] = "hello bit.";
	 char str2[] = "hello bit.";
	 const char *str3 = "hello bit.";
	 const char *str4 = "hello bit.";
	 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语言_第9张图片

可见str1/2/3/4是不一样的,
对于str1和str2:两者是用相同的常量字符串去初始化不同的数组,这时会开辟出不同的内存块,从而数组首元素地址也就不同;
对于str3和str4:两者指向的是同一个常量字符串,C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。

6.二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是二级指针,即用来存放一级指针的地址。

int main()
{
	int a = 10;
	int* pa = &a;//pa是一个一级指针变量,放a的地址
	int** ppa = &pa;//ppa是一个二级指针变量(用来存放一级指针的地址),放的是pa的地址
	**ppa = 100;//*ppa访问到pa,再加*即可访问a
	printf("%d\n", a);

	return 0;
}

指针---C语言_第10张图片

*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a

6.1二级指针的类型

指针---C语言_第11张图片

int a表示a为整型;
int* pa中表示pa为一级指针,指向的对象(a)为整型;
int** ppa中最右侧
表示ppa是指针,左侧和int表示ppa所指向的对象是int* 类型(指针),ppa为二级指针。

7.指针数组

顾名思义,指针数组就是用来存放指针的数组。

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;

	int* pa = &a;
	int* pb = &b;
	int* pc = &c;

	int* parr[10] = { &a,&b,&c };//将a,b,c的地址当成数组元素进行访问,每一个元素都是整型指针
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", * (parr[i]));
	}
	return 0;
}

指针---C语言_第12张图片
例如:

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

7.1用指针数组表示二维数组

我们可以借助指针数组,用一维数组表示出二维数组的形式,如下

int main()
{
	int arr1[4] = { 1,2,3,4 };
	int arr2[4] = { 2,3,4,5 };
	int arr3[4] = { 3,4,5,6 };
	int* parr[3] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 4; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

指针---C语言_第13张图片

8.数组指针

8.1数组指针定义

首先数组指针—是指针,类比其他类型指针我们可能更能清楚的而理解什么是数组指针:

int* pi//指向整型的指针
float* pf//指向单精度浮点数的指针
char* pc//指向字符的指针

int *p1[10];//首先是数组,10个元素都是指向整型的指针,因此是指针数组
int (*p2)[10];//p先和*结合,说明p是一个指针变量,然后指向的是一个大小为10个整型的数组。
//所以p是一个指针,指向一个数组,叫数组指针。

整型指针是用来存放整型的地址
字符指针是用来存放字符的地址
数组指针是用来存放数组的地址

注:int (* p2)[10];中[ ]的优先级要高于* 号的,所以必须加上()来保证p先和*结合。

8.2 &数组名VS数组名

这里仅介绍&数组名和数组名的区别,详细内容见数组章节4.2

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

指针---C语言_第14张图片

由此我们发现:其实&arr和arr,虽然值是一样的,但是意义应该不一样的
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(只是两者相同)

本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型,数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40(十六进制:0x28)。

借用数组指针来解释一下为什么&函数名+1跳过整个数组的大小:
例如 int (* p) [5]
p的类型是: int (* ) [5]
p是指向一个整型数组的,数组有五个元素
p+1->跳过一个5个元素的数组
因此&函数名+1跳过整个数组的元素(单位是字节)

8.3数组指针的使用

由上述我们知道数组指针指向的是数组,那数组指针中存放的应该是数组的地址

int main()
{
	 int arr1[10] = {1,2,3,4,5,6,7,8,9,0};
	 int (*p)[10] = &arr1;//把数组arr的地址赋值给数组指针变量p

     char* arr2[5]={0};//指针数组
     char* (*pc)[5]=&arr2;//&arr2取出的是整个数组的地址,用*pc接收,数组中有五个元素,每个元素是char*类型
	 return 0;
}

实例-用数组指针读取二维数组内容:

首先明确: 一维数组名,表示首元素的地址
二维数组名,同样表示首元素的地址,但是二维数组的首元素是二维数组的第一行

void print_arr(int (*p)[5], int row, int col)
{
	 int i = 0;
	 for(i=0; i<row; i++)
	 {
	     int j=0;
		 for(j=0; j<col; j++)
		 {
			 printf("%d ", p[i][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};
	 print_arr(arr, 3, 5);
	 //这里传递的arr,其实相当于第一行的地址,是一维数组的地址,可以数组指针来接收
	 return 0;
}

指针---C语言_第15张图片
学了指针数组和数组指针看看你能分辨下面代码的意思吗:

int arr[5];//整型数组
int *parr1[10];//整型指针数组
int (*parr2)[10];//数组指针
int (*parr3[10])[5];//存放数组指针的数组,可以放进10个数组的地址,这10个数组每一个可以有5个元素,如下图解释

指针---C语言_第16张图片

9.数组参数、指针参数

总结将数组和指针传给函数的各种情况:

9.1一维数组传参

void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int *arr)//ok
{}

void test2(int *arr[20])//ok
{}
void test2(int **arr)//ok
//指针数组每个元素都是指针,传指针的地址可以用二级指针接收
{}
int main()
{
 int arr[10] = {0};//整型数组
 int *arr2[20] = {0};//指针数组
 test(arr);
 test2(arr2);
}

9.2二维数组传参

void test(int arr[3][5])//ok
{}
void test(int arr[][])//err:二维数组列不可省
{}
void test(int arr[][5])//ok
{}

void test(int *arr)//err
{}
void test(int* arr[5])//err
{}
void test(int (*arr)[5])//ok
{}
void test(int **arr)//err
{}

int main()
{
 int arr[3][5] = {0};
 test(arr);//传的是首行元素地址,即数组的地址
}

总结:二维数组传参,函数形参的设计只能省略第一个[ ]的数字。 因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素(即多少列)。

9.3一级指针传参

当函数的参数部分为一级指针的时候,函数能接收什么参数呢?

void test1(int* p)
{}
void test2(char* p)
{}
int main ()
{
	int a=0;
	int *pi=&a;
	int arr[10];
	test1(&a);//某变量地址
	test1(pi);//指针
	test1(arr)//数组名
    
    char b='a';
    char* pc=&b;
    char arr2[5]="abcd";
    test2(&b);//某变量地址
	test2(pc);//指针
	test2(arr2)//数组名
}

9.4二级指针传参

当函数的参数部分为一级指针的时候,函数能接收什么参数呢?

void test1(int** p)
{}
int main ()
{
	int a=0;
	int* pi=&a;
	int** ppi=&pi; 
	int* arr[5];//指针数组
    
    test(&pi);
    test(ppi);
    test(arr);
}

10.函数指针

10.1函数指针介绍与应用

创建的变量都有地址,那么函数是否也有地址呢?下面我们来看一组代码:

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

指针---C语言_第17张图片

可见函数也是有地址的,而且其与数组也有一定的相似之处(函数名和&函数名都表示函数地址)。
那么可以用指针来保存函数的地址吗?指针就是保持地址的,应该可以;
那么可以用指针表示函数吗?又怎么表示呢?

int add(int x,int y)
{
	return x+y;
}
int main()
{
	int a = 3;
	int b = 4;
	//pf1(pf2)先和*结合,说明pf1(pf2)是指针,指针指向的是一个函数,指向的函数有两个int参数,返回值类型为int。
    int (*pf1)(int,int)=&add;
    int (*pf2)(int,int)=add;
     
	int ret1 = add(a, b);
	int ret2 =(*pf1)(a,b);
	int ret3 =pf2(a,b);//对比add(a,b)
	
	printf("ret1=%d\n", ret1);
	printf("ret2=%d\n", ret2);
	printf("ret3=%d\n", ret3);
	return 0;
}

指针---C语言_第18张图片

10.2两组有趣的代码

//代码1
(*(void (*)())0)();

//代码2
void (*signal(int , void(*)(int)))(int);

详解:


//代码1
(  *( void (*)() )0  )  ();
//void (*)()—函数指针类型
// (类型)强制类型:( void (*)() )0—将0处的地址强制为函数(无参,无返回值)地址,
// *( void (*)() )0—调用函数
//综上:代码1是一次函数调用,调用的是0作为地址处的函数
 
//代码2
void (*signal(int, void(*)(int)))(int);
//void(*)(int)—函数指针类型
//void (*                       )(int)—函数指针类型
//signal(int, void(*)(int))—函数名为signal,参数为int和void(*)(int)
//综上:代码2是一次函数声明
//声明的是signal函数,其返回类型是一个函数指针,该指针指向的函数参数是int,返回类型是void,
// 而signal函数本身的第一个参数的类型为int,第二个参数的类型也是函数指针(指向的函数参数是int,返回类型是void);

//对代码2进行改写:
typedef void(*pf)(int) //将void(*)(int)命名为pf,
//代码2可改写为
pf signal(int,pf)

11.函数指针数组

11.1函数指针数组定义

数组是一个存放相同类型数据的存储空间,那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

int (*parr[10])(int,int);

parr先和 [ ] 结合,说明 parr是数组,数组的内容是int (*)() 类型的函数指针,这就是函数指针数组。
函数指针数组的用途:转移表

11.2函数指针数组应用举例

//仅限整型计算的简易计算器
void menu()
{
	printf("**********************\n");
	printf("*****1.add  2.sub*****\n");
	printf("*****3.mul  4.div*****\n");
	printf("*****0.退出计算器 *****\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 (*parr[5])(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("请输入两个操作数:>");
			int x = 0;
			int y = 0;
			int ret = 0;
			scanf("%d %d", &x, &y);
			ret = parr[input](x, y);
			//parr[input]选择计算方式
			//input=1,2,3,4分别代表加、减、乘、除
			printf("%d\n", ret);
		}
		else
			printf("输入错误,请重新输入:>\n");		
	} while (input);
	return 0;
}

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

指向函数指针数组的指针是一个 指针,指针指向一个数组 ,数组的元素都是函数指针 ;
(感觉在套娃一样,如果想,还可以继续!)

void test()
{
printf("hehe\n");
}
int main()
{
//函数指针pf
void (*pf)() = test;
//函数指针的数组pfArr
void (*pfArr[5])();
pfArr[0] = test;
//指向函数指针数组pfArr的指针ppfArr
void (*(*ppfArr)[5])() = &pfArr;
return 0;
}

13.回调函数

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

13.1 qsort函数

qsort函数C语言编译器函数库自带的排序函数,可以排序任意类型的数据 。qsort详细介绍
qsort 的函数原型是
void qsort(void*base,size_t num,size_t width,int(*cmp)(const void * e1,const void * e2));

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

//补充:
void* 
//是无具体类型的指针,可以接受任意类型的地址
//所以不能解引用、不能+-整数运算

指针---C语言_第19张图片

cmp函数指针介绍,如图中所示,
首先对p1、p2要强制类型转换(viod*类型不可解引用);
其次整个函数返回值由p1、p2大小决定:p1p2返回大于0的值。

13.1.1qsort排序整型数据

#include //头文件
void cmp(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);//升序
	//return (*(int*)e2 - *(int*)e1);//降序
}

int main()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr,sz,4,cmp);//函数中通过函数指针调用函数
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

指针---C语言_第20张图片

13.1.2 qsort排序结构体数据

结构体相关知识见结构体章节。

#include 
#include 

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

int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e1)->name ,((struct stu*)e2)->name);
}
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return (((struct stu*)e1)->age - ((struct stu*)e2)->age);
}
int main()
{
	struct stu s[] = {{"zhangsan",18},{"lisi",12},{"wangwu",25}};
	int sz = sizeof(s) / sizeof(s[0]);
	
	//按照name排序:strcmp对字符串的每一位对应进行比较,直至不同
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	printf("%s %d\n%s %d\n%s %d\n", s[0].name, s[0].age, s[1].name, s[1].age, s[2].name, s[2].age);
	printf("*********************\n");
	
	//按照age排序
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
	printf("%s %d\n%s %d\n%s %d\n", s[0].name, s[0].age, s[1].name, s[1].age, s[2].name, s[2].age);

	return 0;
}

指针---C语言_第21张图片

13.2模拟实现qsort函数

13.2.1模拟实现qsort函数—基于冒泡排序

void Swap(char* p1, char* p2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)//将每字节的内容分别进行交换
	{
		char tmp = *((char*)p1+i);
		*((char*)p1+i)=*((char*)p2+i);
		*((char*)p2+i)=tmp;
	}
}
void my_qsort_by_bubble_sort(void* base, int num,int width,int(*cmp)(const void* e1,const void* e2))
{
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		int flag = 1;
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)base+width*j, (char*)base + width * (j+1))>0)
			//将待比较的两个数的地址放入
			{
				Swap((char*)base + width * j, (char*)base + width * (j + 1),width);
				flag = 0;
			}
		}
		if (flag == 1)//防止已经排好序
			break;
	}
}

13.2.2 整型排序检验

void Swap(char* p1, char* p2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)//将每字节的内容分别进行交换
	{
		char tmp = *((char*)p1+i);
		*((char*)p1+i)=*((char*)p2+i);
		*((char*)p2+i)=tmp;
	}
}
void my_qsort_by_bubble_sort(void* base, int num,int width,int(*cmp)(const void* e1,const void* e2))
{
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		int flag = 1;
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)base+width*j, (char*)base + width * (j+1))>0)
			{
				Swap((char*)base + width * j, (char*)base + width * (j + 1),width);
				flag = 0;
			}
		}
		if (flag == 1)//防止已经排好序
			break;
	}
}
//进行测验
void cmp(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);
}
int main()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	my_qsort_by_bubble_sort(arr, sz,4,cmp);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

指针---C语言_第22张图片

13.2.3 结构体排序检验

void Swap(char* p1, char* p2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)//将每字节的内容分别进行交换
	{
		char tmp = *((char*)p1+i);
		*((char*)p1+i)=*((char*)p2+i);
		*((char*)p2+i)=tmp;
	}
}
void my_qsort_by_bubble_sort(void* base, int num, int width, int(*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		int flag = 1;
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)base + width * j, (char*)base + width * (j + 1)) > 0)
			{
				Swap((char*)base + width * j, (char*)base + width * (j + 1), width);
				flag = 0;
			}
		}
		if (flag == 1)//防止已经排好序
			break;
	}
}

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

int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct stu*)e1)->name ,((struct stu*)e2)->name);
}
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return (((struct stu*)e1)->age - ((struct stu*)e2)->age);
}
int main()
{
	struct stu s[] = {{"zhangsan",18},{"lisi",12},{"wangwu",25}};
	int sz = sizeof(s) / sizeof(s[0]);
	//按照name排序
	my_qsort_by_bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	printf("%s %d\n%s %d\n%s %d\n", s[0].name, s[0].age, s[1].name, s[1].age, s[2].name, s[2].age);
	printf("*********************\n");
	//按照age排序
	my_qsort_by_bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
	printf("%s %d\n%s %d\n%s %d\n", s[0].name, s[0].age, s[1].name, s[1].age, s[2].name, s[2].age);

	return 0;
}

指针---C语言_第23张图片

你可能感兴趣的:(C语言,c语言,开发语言)