C语言-指针

指针是什么

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
在这里插入图片描述

int a = 10;//在内存中开辟一块空间
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
            //将a的地址存放在p变量中,p就是一个之指针变量

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

  • 一个小的单元到底是多大?(1个字节)

  • 如何编址?

    经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
    对于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^64Byte == 2^64/1024KB ==2^64/1024/1024MB==2^64/1024/1024/1024GB == 8GB
    8G的空闲进行编址。

总结:

  • 指针是用来存放地址的,地址是唯一标示一块地址空间的。
  • 指针的大小在32位平台是4个字节,在64位平台是8个字节。

指针和指针类型

指针+ - 整数

int n = 10;
char *pc = (char *)&n;
int *pi = &n;

printf("%p\n", &n);     //000000000061FE0C
printf("%p\n", pc);     //000000000061FE0C
printf("%p\n", pc + 1); //000000000061FE0D 
printf("%p\n", pi);     //000000000061FE0C
printf("%p\n", pi + 1); //000000000061FE10

总结
指针的类型决定了指针向前或者向后走一步有多大(距离单位是字节)(指针的步长)。

int* p; p+1 -->4
char* p; p+1 -->1
double* p; p+1 -->8

指针的解引用

int a = 0x11223344;
int* pa = &a;
*pa = 0;
/*char *pc = &a;
*pc = 0;*/

在这里插入图片描述

总结
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

int* p; *p 能够访问4个字节
char* p; *p 能够访问1个字节
double* p; *p 能够访问8个字节

示例:指针类型的意义

int arr[10] = { 0 };
int* p = arr;//数组名-首元素的地址
//char*p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
    *(p + i) = 1;
}

int* p = arr;

在这里插入图片描述

char*p = arr;

在这里插入图片描述

野指针

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

野指针成因

  1. 指针未初始化

    //int a;//局部变量不初始化,默认是随机值
    int *p;//局部的指针变量,就被初始化随机值
    *p = 20;
    
  2. 指针越界访问

    int a[10] = { 0 };
    int i = 0;
    int* p = a;
    for (i = 0; i <= 12; i++)
    {
        //*p = i;
        //p++;
        *p++ = i;
    }
    
  3. 指针指向的空间释放(详见动态内存开辟)

    int* test()
    {
        int a = 10; //局部变量
        return &a; 
    }
    int main()
    {
        int *p = test();//test指向的空间被释放了
        *p = 20; //访问就是非法的
        return 0;
    }
    

如何避免野指针

  1. 指针初始化

    int b = 0;//
    int a = 10;
    int*pa = &a;//初始化
    int* p = NULL;//NULL- 用来初始化指针的,给指针赋值
    
  2. 小心指针越界

  3. 指针指向空间释放即使置NULL

    int a = 10;
    int *pa = &a;
    *pa = 20;
    //
    pa = NULL;
    
  4. 指针使用之前检查有效性

    if (pa != NULL)
    {
    
    }
    

指针运算

指针± 整数

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
for (i = 0; i < sz; i++)
{
    printf("%d ", *p); // 1 2 3 4 5 6 7 8 9 10
    p++;//p+=1
}
printf("\n");
int* p1 = &arr[9];
for (i = 0; i < 5; i++)
{
    printf("%d ", *p1); //10 8 6 4 2
    p1-=2;
}

指针-指针

char ch[5] = {0};
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 15 };
printf("%d\n", &arr[9] - &arr[0]);//9,指针之间元素的个数
printf("%d\n", &arr[0] - &arr[9]);//|-9|=9,指针之间元素的个数
//printf("%d\n", &arr[9] - &ch[0]);//err

指针的关系运算

#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES])
{
     *vp++ = 0;
}
//下面代码是应该避免这样写,因为标准并不保证它可行。
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
    *vp = 0;
}

标准规定:

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

在这里插入图片描述

指针和数组

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. &arr- &数组名- 数组名不是首元素的地址-数组名表示整个数组 - &数组名 取出的是整个数组的地址
//2. sizeof(arr) - sizeof(数组名) - 数组名表示的整个数组- sizeof(数组名)计算的是整个数组的大小

在这里插入图片描述

结论
数组名表示的是数组首元素的地址。

int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
    *(p + i) = i;
}

for (i = 0; i < 10; i++)
{
    printf("%d ", *(p + i)); //0 1 2 3 4 5 6 7 8 9
}
printf("\n\n");
for (i = 0; i < 10; i++)
{
    printf("&arr[%d] = %p   <====> p+%d = %p\n", i, &arr[i], i, p + i);
}

在这里插入图片描述

二级指针

int a = 10;
int * pa = &a;// *表示pa是指针,int表示pa指向的对象类型是int
int* * ppa = &pa;//ppa就是二级指针,存放一级指针变量的地址
//int* 表示ppa指向的对象类型是int* ,第二个*表示ppa是个指针
**ppa = 20;
printf("%d\n", **ppa);//20
printf("%d\n", a);//20

//int** * pppa = &ppa;// 三级指针

在这里插入图片描述

字符指针

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

一般使用:

char ch = 'w';
char *pc = &ch;
*pc = 'w';
char arr[] = "abcdef";
char* pc = arr;
printf("%s\n", arr);//abcdef
printf("%s\n", pc);//abcdef

还有一种使用方式如下:

char* p= "abcdef";//"abcdef" 是一个常量字符串,存放的是a的地址
printf("%c\n", p);//a
printf("%s\n", p);//abcdef
*p='W';
//printf("%s\n", p);//err,准确的写法const char* p= "abcdef";

在这里插入图片描述
C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。

指针数组

指针数组的定义

//指针数组 是数组,用来存放指针的

int arr[10] = {0};//整形数组
char ch[5] = { 0 };//字符数组
int* parr[4];//存放整形指针的数组 - 指针数组
char* pch[5];//存放字符指针的数组 - 指针数组

指针数组的用途

//实际中不会这么用
int a = 10;
int b = 20;
int c = 30;
int d = 40;
int* arr[4] = {&a, &b, &c, &d};
int i = 0;
for (i = 0; i < 4; i++)
{
    printf("%d ", *(arr[i]));//10 20 30 40
}

在这里插入图片描述

实际用途

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

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

在这里插入图片描述

在这里插入图片描述

数组指针

数组指针是指针?还是数组?

答案是:指针。

//int *p = NULL; //p是整形指针 - 指向整形的指针 - 可以存放整形的地址
//char* pc = NULL;//pc是字符指针 - 指向字符的指针 - 可以存放字符的地址
//数组指针 - 指向数组的指针 - 存放数组的地址
//int arr[10] = {0};
//arr- 首元素地址
//&arr[0] -首元素的地址
//&arr - 数组的地址

int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int (*p)[10] = &arr;//数组的地址要存起来
//上面的p就是数组指针
printf("%p\n", arr );
printf("%p\n", arr + 1);
printf("%p\n", &arr );
printf("%p\n", &arr+1);

在这里插入图片描述

&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

&数组名VS数组名

数组指针的使用

char* arr[5];
char* (*pa)[5] = &arr;;//把数组arr的地址赋值给数组指针变量pa
//但是我们一般很少这样写代码

在这里插入图片描述

一个数组指针的使用:

//参数是数组的形式
void print1(int arr[3][5], int x, int y)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

//参数是指针的形式
void print2(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("%d ", *(p[i] + j));//
			//printf("%d ", *(*(p + i) + j));
			//printf("%d ", (*(p + i))[j]);//*(p + i)找到第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 } };

	//print1(arr, 3, 5);//arr - 数组名 - 数组名就是首元素地址

    //但是二维数组的首元素是二维数组的第一行
    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    //可以数组指针来接收
    print_arr2(arr, 3, 5);
	return 0;
}

**arr[i] == (arr+i) == (p+i) == p[i]

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
int* p = arr;
for (i = 0; i < 10; i++)
{
	printf("%d ", p[i]);//1 2 3 4 5 6 7 8 9 10
	printf("%d ", *(p + i));//1 2 3 4 5 6 7 8 9 10
	printf("%d ", *(arr + i));//1 2 3 4 5 6 7 8 9 10
	printf("%d ", arr[i]);//arr[i] == *(arr+i) == *(p+i) == p[i]
}

去掉数组名,就是存储的元素类型

int arr[5]; //arr 是一个5个元素的整形数组
int *parr1[10]; //parr1 是一个数组,数组有10个元素,每个元素的类型是int* ,parr1是存放指针的数组。指针数组
int (*parr2)[10]; //parr2 是一个指针,该指针指向了一个数组,数组有10个元素,每个元素的类型是int,parr2 是数组指针
int (*parr3[10])[5];//parr3 是一个数组,该数组有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素是int

在这里插入图片描述

数组参数、指针参数

一维数组传参

#include <stdio.h>
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);
}

二维数组传参


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

一级指针传参

#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函数能接收什么参数?
void test2(char* p)
{}
//test2函数能接收什么参数?
int main()
{
	int a = 10;
	int* p1 = &a;
	test1(&a);//ok
	test1(p1);//ok

	char ch = 'w';
	char* pc = &ch;
	test2(&ch);//ok
	test2(pc);//ok
	return 0;
}

二级指针传参

#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);
 int* arr[10];
 test(arr);//指针数组也可以
 return 0;
}

函数指针

&函数名 和 函数名 都是函数的地址

//数组指针 - 是指向数组的指针
//函数指针 - 是指向函数的指针 - 存放函数地址的一个指针

int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}

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

	//int arr[10] = { 0 };
	//int (*p)[10] = &arr;

	//arr;//&arr
	//printf("%d\n", Add(a, b));

	//&函数名 和 函数名 都是函数的地址
	/*
	printf("%p\n", &Add);
	printf("%p\n", Add);
	*/
	int (*pa)(int, int) = Add;
	printf("%d\n", (*pa)(2, 3));//5

	return 0;
}
void Print(char*str)
{
	printf("%s\n", str);
}

int main()
{
	int a;
	int * pa;
	void (*p)(char*) = Print;

	(*p)("hello");
	p("hello");
	p("hello world");
	return 0;
}
//代码1
(*(void (*)())0)();//将0进行强制类型转换成【void (*)() 】-函数指针类型,该函数指针的类型是无参换返回类型是void,0就是一个函数的地址,然后*解引用,调用0地址处的该函数。

//代码2
void (* signal(int , void(*)(int)) )(int);//void(*)(int) 函数指针类型,该函数指针类型传入int返回void 。整个函数指针返回的是void(*  )(int)-函数指针类型

// signal是一个函数声明
// signal函数的参数有2个,第一个是int。第二个是函数指针,该函数指针指向的函数的参数是int,返回类型是void
// signal函数的返回类型也是一个函数指针:该函数指针指向的函数的参数是int,返回类型是void

简化
//typedef void(*pfun_t)(int);
//pfun_t signal(int, pfun_t);
//
//typedef unsigned int uint;

注 :推荐《C陷阱和缺陷》

int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}

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

	int(*pa)(int, int) = Add;
	printf("%d\n", pa(2, 3));
	printf("%d\n", Add(2, 3));
	//printf("%d\n", *pa(2, 3));//err
	printf("%d\n", (*pa)(2, 3));
    printf("%d\n", (**pa)(2, 3));
	return 0;
}

函数指针数组

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* arr[5];

	//需要一个数组,这个数组可以存放4个函数的地址 - 函数指针的数组
	int (*pa)(int, int) = Add;//Sub/Mul/Div

	int(*parr[4])(int, int) = {Add, Sub, Mul, Div};//函数指针的数组
	
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d\n", parr[i](2, 3));//5 -1 6 0
	}

	return 0;
}

函数指针定义

int (*parr1[10])();
int *parr2[10]();//err
int (*)() parr3[10];//err

练习

char* my_strcpy(char* dest, const char* src);
//1.写一个函数指针 pf,能够指向my_strcpy
char* (*pf)(char*, const char*);

//2. 写一个函数指针数组 pfArr,能够存放4个my_strcpy函数的地址
char* (*pfArr[4])(char*, const char*);

函数指针数组的用途:转移表

例子:(计算器)

#include <stdio.h>
//计算器

void menu()
{
	printf("**************************\n");
	printf("**  1. add       2. sub **\n");
	printf("**  3. mul       4. div **\n");
	printf("**  5. xor       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 Xor(int x, int y)
{
	return x ^ y;
}

//回调函数
void Calc(int (*pf)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入两个操作数:>");
	scanf("%d%d", &x, &y);
	printf("%d\n", pf(x, y));
}

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

使用函数指针数组的实现main:

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

	//pfArr 是一个函数指针数组 - 转移表
	int(*pfArr[])(int, int) = {0, Add, Sub, Mul, Div, Xor};
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input >= 1 && input <= 5)
		{
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			int ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出\n");
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);
}

指向函数指针数组的指针

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

int arr[10] = { 0 };
int (*p)[10] = &arr;//取出数组的地址

int(*pf)(int, int);//函数指针
int (*pfArr[4])(int, int);//pfArr是一个数组-函数指针的数组
//ppfArr是一个指向[函数指针数组]的指针
int(*(*ppfArr)[4])(int, int) = &pfArr;
//
//ppfArr 是一个数组指针,指针指向的数组有4个元素
//指向的数组的每个元素的类型是一个函数指针 int(*)(int, int)
//

回调函数

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

void print(char *str) //回调函数
{
	printf("hehe:%s", str);
}

void test( void (*p)(char*) )
{
	printf("test\n");
	p("hello");
}

int main()
{
	test(print);
	//test
	//hehe : hello
	return 0;
}

使用场景

之前写的BubbleSort:只能排序整形数组

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++)//9 8 7 6 5 4 3 2 1 多少对
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

void* 类型的指针 可以接受任意类型的地址

int a = 10;
//int* pa = &a;//
//char* pc = &a;

//char ch = 'w';
void* p = &a;
//*p = 0;
//p++;

//p = &ch;
//void* 类型的指针 可以接收任意类型的地址
//void* 类型的指针 不能进行解引用操作
//void* 类型的指针 不能进行+-整数的操作

qsort函数

在这里插入图片描述

在这里插入图片描述

qsort函数实现冒泡排序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int cmp_int(const void *e1, const void *e2)
{
    //比较两个整形值的
    return *(int *)e1 - *(int *)e2;
}

void test1()
{
    int arr[10] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
    int sz = sizeof(arr) / sizeof(arr[0]);
    qsort(arr, sz, sizeof(arr[0]), cmp_int);
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
}

//比较浮点型
int cmp_float(const void *e1, const void *e2)
{
    /*if (*(float*)e1 == *(float*)e2)
		return 0;
	else if (*(float*)e1 > *(float*)e2)
		return 1;
	else
		return -1;*/

    return ((int)(*(float *)e1 - *(float *)e2));
}

void test2()
{
    float f[] = {9.0, 8.0, 7.0, 6.0, 5.0, 4.0};
    int sz = sizeof(f) / sizeof(f[0]);
    qsort(f, sz, sizeof(f[0]), cmp_float);
    int j = 0;
    for (j = 0; j < sz; j++)
    {
        printf("%f ", f[j]);
    }
}

//比较结构体类型
struct Stu
{
    char name[20];
    int age;
};

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

int cmp_stu_by_name(const void *e1, const void *e2)
{
    //比较名字就是比较字符串
    //字符串比较不能直接用><=来比较,应该用strcmp函数<string.h>
    return strcmp(((struct Stu *)e1)->name, ((struct Stu *)e2)->name);
}

void test3()
{
    struct Stu s[3] = {{"zhangsan", 20}, {"lisi", 30}, {"wangwu", 10}};
    int sz = sizeof(s) / sizeof(s[0]);
    qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
    //第一个参数:待排序数组的收元素地址
    //第二个参数:待排序数组的元素个数
    //第三个参数:待排序数组的每个元素的大小-单位是字节
    //第四个参数:是函数指针,比较两个元素的所用函数的地址-这个函数使用者自己实现
    //           函数指针的两个参数是:待比较的两个元素的地址
}

//实现bubble_sort函数的程序员,他是否知道未来排序的数据类型-不知道
//那程序员也不知道,待比较的两个元素的类型
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++;
    }
}

//因为首元素类型不同,v所以使用void指针
//sz要排序元素个数
//width每个元素的字节数
//两个元素比较的方法不一样,把比较函数的地址传进来,e1,e2要比较的元素
void bubble_sort(void *base, int sz, int width, int (*cmp)(void *e1, 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 test4()
{
    int arr[10] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
    int sz = sizeof(arr) / sizeof(arr[0]);
    //使用bubble_sort的程序员一定知道自己排序的是什么数据
    //就应该知道如何比较待排序数组中的元素
    bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
}

void test5()
{
    struct Stu s[3] = {{"zhangsan", 20}, {"lisi", 30}, {"wangwu", 10}};
    int sz = sizeof(s) / sizeof(s[0]);
    //bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
    bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}

int main()
{

    //test1();
    //test2();
    //test3();
    //test4();
    test5();
    return 0;
}

注:

数组名是首元素的地址

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

eeenkidu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值