指针功能详解 和 qsort的实现(快排实现)

本文详细介绍了C语言中的指针概念,包括字符指针、指针数组、数组指针、一维及二维指针传参、函数指针、函数指针数组、指向函数指针数组的指针以及回调函数。通过实例解析了各种指针的使用方法和注意事项,帮助读者深入理解C语言指针的运用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一,字符指针

二,指针数组 

 三,数组指针

四,一维指针和二维指针传参

五,函数指针

六,函数指针数组

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

八,回调函数(qsort)

总结


一,字符指针

int main()
{
	char n = 'i';
	char* pn = &n;
	*pn = 'I';
	printf("%c", n);
	return 0;
}

这是我们常用的字符指针用法,但是还可以这样用。

	char* pn = "hello";

这里有一个大家都很容易误解的误区,就是以为"hello"存放在pn里面,这很显然是不对的。

我们用一道题来解释:

int main()
{
	char str1[] = "hello";
	char str2[] = "hello";
	char* p1 = "hello";	
	char* p2 = "hello";
	printf("地址相同的是:\n");
	if (str1 == str2)
	{
		printf("str1 str2\n");
	}
	if (p1 == p2)
	{
		printf("p1 p2\n");
	}
	return 0;
}

 

这里的解释是:系统为str1和str2是开辟了不同的空间,开辟了一份空间存放字符串常量"hello",指针变量p1和p2指向相同的"hello"。

我们可以得出:字符串常量需要空间存放,字符串数组刚好有空间,所以可以存放,而指针是存放地址的,所以字符指针是存放字符串常量的首元素地址,而非这个字符串。

显然,当p1指向字符串常量时,*p是不能修改的,而p1指向str1字符串数组时,*p是可以修改的(但是修改str1的值不建议这样做,很可能会报错,因为字符串赋值和修改的原理都是一个一个字符进行修改的) 

二,指针数组 

顾名思义,存放指针的数组。数组中每个元素都存放一个地址。

char* arr[5];

数组指针的用法实例:

 模拟可变长的二维数组,但是不好打印,所以我特地这样设置。

int main()
{
	int a1[] = { 1,2,3,4,5 };
	int a2[] = { 2,3,4,5 };
	int a3[] = { 3,4,5 };
	int a4[] = { 4,5 };
	int a5[] = { 5 };
	int* a[5] = { a1,a2,a3,a4,a5 };
	for (int i = 0; i < 5; i++)
	{
		for (int j = 0; j < 5-i; j++)
		{
			printf("%d ", a[i][j]);
            //等效的写法
			//printf("%d ", *(a[i] + j));
			//printf("%d ", *(*(a + i) + j));
		}
		printf("\n");
	}
	return 0;
}

 三,数组指针

我们知道了指针数组本质是数组,那么数组指针又是什么呢?显然,数组指针是指针

我们先来看看这些指针: 

类型例子
charchar* p;
intint* p;

那数组指针呢?它是这样的

【类型名】 (*变量名)[指向数组的个数]int (*p)[5],char (*p)[10]
int main()
{
	char b[3];
	char(*p)[3] = &b; //这里一点是&b,而不是b,因为要取整个数组的指针大小
    //格式严格对应
	int a[5] = { 3,1,4,5,3 };
	int(*p)[5] = &a;

	return 0;
}

简单点理解,可以将int(*p)[5]int a[5]类比,你会发现,*p就是a,又因为数组名就是地址,所以我们可以将其相似等换。不过事实确实十分相似,这里拿二维数组来举例,pa就相当于a的一行。pa+1就是a的下一行。

四,一维指针和二维指针传参

假设有这样一个函数:

void func(int *p) {}

那么我们在主函数里该传相应的什么值呢?一维指针有三种传参方式:

一维指针变量地址数组名

int* p;

func(p);

int a;

func(&a);

int b[10];

func(b);

 本质:传地址过去 

那二维指针呢?

void func(int **p) {}

同样有三种:

二维指针指针的地址指针数组

int** p;

func(p);

int* p;

func(&p);

int *a[10];

func(a);

本质:地址的地址

五,函数指针

我们经常使用函数,但你有没有思考过,函数是如何被调用的,是否也是通过地址调用的。没错,函数也是有地址的,并且我们还可以定义指针来存放函数的地址,这类指针就是函数指针。

  • 形式

假设我们有这么一个函数

void func(int a,char b){}

那么它的指针就是这样的

void (* pf)(int a,char b) = &func;

这里说明一下:函数名就相当于函数地址,&func和fun是等效的

我们让 函数指针 和 数组指针 比较一下,是不是很像(这里以整型为例)

★函数指针数组指针
格式int (*  )(int  ,char  )int (*  )[10]
例子

int (*pa) (int ,char);

int (*a)[10]
  • 函数指针的调用
int sub(int a, int b)
{
    return a - b;
}
int main()
{
    int (*ps)(int, int) = sub;
    printf("%d ", sub(5, 1));
    printf("%d ", (&sub)(5, 1));
    printf("%d ", ps(5, 1));
    printf("%d ", (*ps)(5, 1));
    printf("%d ", (****ps)(5, 1));
    return 0;
}

 这里解释一下:ps的调用有很多效果一样的形式,ps、*ps、****ps、和sub、&sub的效果都是一样的。所以大家以后见到了不用惊讶,都是基操,效果一样。

那我们来看个奇怪的代码巩固一下函数指针的内容: 

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

把0这个地址强制转换成void(*)()类型的函数指针,然后调用,这个0地址的函数 。

void( *signed( int , void(*)(int ) ) ) (int);

这是一个函数声明:

突破口在*signed(int,void(*)(int))这里

声明了一个函数名为signed,参数为int和void(*)(int),返回值是void(*)(int)类型的函数。

void(*)(int)参数是int,返回值是void

由于这个代码很复杂,不好读,我们可以typedef一下 ,使其简化

typedef void(* pf )(int);
pf signed(int, pf);

六,函数指针数组

前面我们介绍了函数指针,那当我们遇到多个相同类型参数的函数想将他们放在一起时,我们这个时候就可以使用函数指针数组,同指针数组一样,本质还是存放函数指针的数组。

指针数组int* a[10]int* a[10] = {地址1,地址2,地址3,...}
函数指针int (*p)(int ,int)int (*p)(int , int ) = 函数名
★函数指针数组int (*pa[10])(int ,int)int (*pa[10])(int ,int) = {函数1,函数2,函数3,...}

这里都用了int来举例便于理解,其他类型如法炮制。

  • 用法

当我们要写很多相同类型的函数时,我们就可以用函数指针数组来管理。

下面就一个简易的,具有加减乘除功能的计算器代码,用函数指针数组来管理四个函数。

float add(int a, int b)
{
	return a + b;
}
float subtract(int a, int b)
{
	return a - b;
}
float multiply(int a, int b)
{
	return a * b;
}
float divide(int a, int b)
{
	assert(b!=0);
	return (float)a / b;
}

int main()
{
	menu();
	float(*pf[5])(int, int) = { 0,add,subtract,multiply,divide };
	int input;
	do
	{
		printf("选择功能:");
		scanf("%d", &input);
		if (input)
		{
			int n, m;
			printf("计算的两个值:");
			scanf("%d%d", &n, &m);
			float value = pf[input](n, m);
			printf("%f\n", value);
		}
		else
			printf("感谢使用!\n");
	} while (input);
	return 0;
}

这里要注意的是:函数指针数组里的函数都是同一类的函数(参数,返回值都得一致)

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

这个嘛就是,套娃了。我们对比的来了解这个指针

假设有这些(同类)函数

int add (int a,int b){ },int sub (int a,int b){ }

int mul (int a,int b){ },int div (int a,int b){ }

函数指针int ( *p )( int  , int ) =add
函数指针数组int ( *pArr[ 5 ] )( int  , int ) = {add,sub,mul,div}
★函数指针数组的指针int ( *(*pa) [ 5 ] )( int  , int ) = & pArr

总之,这个指针我们只需要了解他的定义方法,后面的什么指针的指针都是一个模样的。套娃嘛,往里套就行了 

八,回调函数

定义:回调函数就是一个通过函数指针调用的函数。

通俗点讲,就是通过一个函数为踏板去调用目的函数。类似函数的嵌套调用,函数里调用别的函数,但是形参是函数指针,实参是函数的地址(即函数名)。


举个栗子:(这里的add和divide函数是上面计算器的函数,我就没有放出来了)

void calculation(float (*p)(int, int))
{
	printf("输入两个值:");
	int n, m;
	scanf("%d%d", &n, &m);
	float value = p(n, m);
	printf("%d ? %d = %f\n", n, m, value);
}
int main()
{
	calculation(add);
	calculation(divide);
	return 0;
}

我们也可以将这个回调函数理解成中转站,里面有个形参存放这目的函数的指针。


再来看一个例子:C语言里自带的排序函数qsort()

这是qsort()函数参数定义 

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

可以看到,qsort()的排序原理是快排,快排是需要 前后下标 的来函数递归来实现的,因为qsort()函数的形参缺少首元素和最后一个元素的下标,只有长度,所以我就在my_qsort函数调用quick()。 

void my_qsort(void* a, int num, int type_size, int (*compare)(const void*, const void*))
{
	int r = num - 1;
	int l = 0;
	quick(a, l, r, type_size,compare);
}
void quick(void* arr, int left, int right, int type_size, int (*compare)(const void*, const void*))
{
	char* a = (char*)arr; //初始为最小字节单位,便于乘上type_size来转换成不同的类型
	if (left > right || right <= 0)
		return;
	int l = left;
	int r = right;
	char* key = a + l * type_size; // 设定基值是左边第一个,这里取地址得值。
	while (l < r)
	{
		// 比较的地方就用 compare
		while (compare(a + r * type_size, key) >= 0 && l < r)
			r--;
		while (compare(a + l * type_size, key) <= 0 && l < r)
			l++;
		// 交换用swap函数,交换的是数据的二进制值
		swap(a + l * type_size, a + r * type_size, type_size);
	}
	//交换 key 和 a[r](此时r == l)
	swap(key, a + r * type_size, type_size);
	quick(a, left, l - 1, type_size, compare);
	quick(a, r + 1, right, type_size, compare);
}
void swap(char* n, char* m, int width)
{
	for (int i = 0; i < width; i++)
	{
		char temp = *n;
		*n = *m;
		*m = temp;
		n++;
		m++;
	}
}

排序 int 类型

int compare_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

void test_int()
{
	int a[10] = { 0 };
	printf("原    :");
	int len = sizeof(a) / sizeof(a[0]);
	for (int i = 0; i < len; i++)
	{
		a[i] = rand() % 10;
		printf("%d ", a[i]);
	}
	printf("\n快排后:");
	my_qsort(a, len, sizeof(a[0]), compare_int);
	for (int i = 0; i < len; i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
}

 排序char类型

int compare_char(const void* e1, const void* e2)
{
	return *(char*)e1 - *(char*)e2;
}
void test_char()
{
	char a[10] = "gfedcba";
	printf("前:");
	puts(a);
	my_qsort(a, strlen(a), sizeof(a[0]), compare_char);
	printf("后:");
	puts(a);
}

排序字符串类型 

int compare_str(const void* e1, const void* e2)
{
	return strcmp(*(char**)e1, *(char**)e2); 
    // 字符串比较时是优先比较前面的字符大小,在相同字符的情况下比较再比较长度
    // 所以**是取第一个字符的地址比较
}

void test_str()
{
	char *a[5] = { "zhangsan","lisi","wangwu","ergou","niuniu" };
	printf("前:");
	for (int i = 0; i < sizeof(a) / sizeof(a[0] ); i++)
	{
		printf("%s ", a[i]);
	}
	printf("\n");
	my_qsort(a, sizeof(a)/sizeof(a[0]), sizeof(a[0]), compare_str);
	printf("后:");
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
	{
		printf("%s ", a[i]);
	}
	printf("\n");
}

 值得注意的是:每种类型的compare函数都不一样,需要按照所需的类型来实现。

调用主函数后:

int main()
{
	srand((unsigned int)time(NULL));
	test_int();
	test_char();
	test_str();
	return 0;
}

运行结果 

总结

指针这方面的知识还需要多下手,多写代码实现功能,理解起来就会更快更轻松,映象也会更深刻。纸上得来终觉浅,绝知此事要躬行。多敲代码多复习。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值