C语言第十课,指针终级

呵呵,啊哈哈哈哈哈,指针王来咯!
哈哈哈哈

字符指针

※区分指针数组 和 数组指针

在这里插入图片描述

int main()
{
	int* p1[10];
	int(*p2)[10];
	return 0;
}

int* p1[10] , p1先和[ ]结合,那么他是一个数组,所以是 指针数组

int(* p2)[10], p2先和* 结合,告诉我p2是指针,向外一看是一个数组数组的类型是int,数组的个数为10个,所以是 数组指针

指针数组

指针数组是数组,参考下面的代码

int main()
{
	int* arr[10];//指针数组,能够存放10个int* 的指针
	char* arr2[10];//指针数组,能够存放10个char* 的指针,数组名为arr2
	return 0;
}

C语言中,对空指针的定义为 ((void *)0)

int main()
{
    NULL;//#define NULL ((void *)0)
}

数组指针

数组指针,是一种指针。指向数组的指针。

int main()
{
	char arr[5];//字符类型数组
	char(*pa)[5] = &arr;//取arr的地址,放入数组指针,
	return 0;
}

数组指针的用法

下面的代码是之前使用整型指针时常写的:

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

如果使用数组指针:
确实可以,但是很别扭。首先解引用得到数组名,数组名相当于首元素地址,对他进行+i操作,再解引用就可以访问到第(i+1)个元素。

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

其实数组指针不是这么用的,它通常使用在二维数组中。
*(p + i)相当于拿到了二维数组的第i行,也相当于第i行的数组名

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

在这里插入图片描述

考考你

下面代码是什么意思?
int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

int arr[5];//整型数组,有5个元素,int类型,是一个字符数组。

int* parr1[10];//parr1是一个数组,有10个元素,每个元素的类型是int* ,所以是一个指针数组。

int(parr2)[10];//parr2 和结合,说明parr2 是一个指针,该指针指向一个数组,有10个元素。parr2是数组指针。

int(* parr3[10])[5];
parr3和[ ]结合,所以是一个数组,数组是10个元素
数组的每一个元素又是一个 数组指针,类型是int( * )[5]
该类型的指针指向的数组有5个int类型的元素。
parr3

数组名 和 &数组名

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &(arr[0]));
	printf("%p\n", &arr);
	//打出来的三个值一模一样。但是意义不一样

	printf("%p\n", arr+1);
	printf("%p\n", &(arr[0])+1);
	printf("%p\n", &arr+1);//数组的地址+1,跳过整个数组
	//但是+1之后就有区别了。
	return 0;
}

根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型。
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是4x10=40。

小结:
数组名是数组首元素的地址
这里有2个例外:

  1. sizeof(数组名),这里的数组名是表示整个数组的,计算的是整个数组的大小,单位是字节。
  2. &数组名,这里的数组名也表示整个数组,取出的是数组的地址。

除上面2中特殊情况外,所有的数组名都是数组首元素的地址。

数组传参和指针传参

一维数组传参

已知arr和arr2的类型,下面那些自定义的函数可以用来接收参数?

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

void test(int arr[ ]) { } 可以
void test(int arr[10]){ } 可以数组传参的时候,形参写成数组的形式,数组的大小可以选择不写。
void test(int* arr){ } 可以, 传过来的时候是一个int型的地址,我用int*来接收是可以的。

arr2是指针数组,每一个元素都是int* 的类型。
void test2(int* arr[20]){ } 可以
void test2(int** arr){ } 可以,一级指针用二级指针来接收,完全没有问题。

二维数组传参

下面那些自定义的函数可以用来接收参数?

int main()
{
    int arr[3][5] = { 0 };
    test(arr);//应该放到数组指针里面
}

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

void test(int arr[ ][ ]){ } 不行,形参的二维数组,行可以省略,但是列不能省略!

void test(int arr[ ][5]) { }可以,列是不能省略的,因为行省略也可以知道下一行的地址在哪里。

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

void test(int* arr) 不ok,应该放到数组指针里面,整型指针肯定不行

void test(int* arr[5]) 不ok

void test(int(*arr)[5]) ok,指针指向数组,每一个数组5个整型元素。

void test(int** arr) 不ok,二级指针更不行。

一级指针传参

一级指针传参,这个讲过很多次了。
要了解以后用别人的代码的时候,这个地方要传入什么参数。比方说这个例子void test(int* p)需要传入的参数是一级指针(地址)。

void test(int* p)
{}
int main()
{
	int a = 10;
	int* ptr = &a;
	int arr[10] = { 0 };
	test(&a);
	test(ptr);
	test(arr);
	return 0;
}

二级指针传参

当函数参数为二级指针的时候,传参该传什么?

  1. 二级指针
  2. 一级指针的的地址
  3. 指针数组的数组名
void test(int** ptr)
{
    ;
}
int main()
{
    int n = 10;
    int* p = &n;
    int** pp = &p;
    int* arr[5];//这个数组里面放的是指针
    
    test(pp);
    test(&p);
    test(arr);//可以运行,因为是二级指针
    return 0;
}

函数指针

在这里插入图片描述
函数名 和 &函数名拿到的都是函数的地址,也就是说都是一样的。

void test()
{}
int main()
{
    printf("%p\n", test);//拿到的都是函数的地址。
    printf("%p\n", &test);//函数没有首元素的概念,只有函数存放的位置
    return 0;
}

在这里插入图片描述
我们在创建数组指针的时候,就是希望能够通过这个指针找到这个数组。同样的,我们使用函数指针,也是希望通过函数指针找到这个函数。以下为函数指针的定义及调用的例子,并非实际的用法!

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

void test(char* str)
{
	;
}
int main()
{
	//方法一:使用取地址函数名
	int (*pf)(int, int) = &Add;//pf与*先结合,说明是一个指针,指针指向的参数是(int int),返还的参数是int
	int sum = (*pf)(2, 3);//函数调用(),用一种间接的调用方法,这个*加上不加上,都不影响!
	printf("%d\n", sum);
	
	//方法二:使用函数名
	int (*pf1)(int, int) = Add;
	int sum1 = (pf1)(2, 3);//*是一个摆设
	printf("%d\n", sum1);
	return 0;
}

解题

这个是啥?(来自《c陷阱与缺陷》)

int main()
{
	(*(void(*)())0)();
	return 0;
}

解:把0强制类型转化为void(*)()类型的函数指针,再去调用0地址处这个参数为无参,返回的类型是void的函数。

这个又是啥?

int main()
{
	void (*signal(int, void(*)(int)))(int);
	return 0;
}

解:signal是一个函数声明 有两个参数,一个是int 一个是函数指针,这个函数指针指向的参数int,返还值是void。signal 函数的返还类型也是函数指针,该指针的函数参数int,反还类型是void。

对这个代码化简一下:

	typedef void(*pfun_t)(int);//pfun_t是函数指针的重命名,
	//这个函数指针指向的参数int,返还值是void
	pfun_t signal(int, pfun_t);
	//这样就可以重写void (*    signal(int,  void(*)(int) )    )(int)

函数指针数组

在这里插入图片描述
使用函数指针数组来实现计算器。

int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
int mul(int a, int b)
{
    return a * b;
}
int div(int a, int b)
{
    return a / b;
}
void menu()
{
	printf("----1 add     2sub--------------\n");
	printf("----3 mul     4div--------------\n");
	printf("-----------0 exit --------------\n");
	printf("--------------------------------\n");
}
int main()
{
	int input = 1;
	int ret = 0;
	int x = 0, y = 0;
	int (*pfarr[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("请输入两个操作数:\n");
			scanf("%d %d", &x, &y);
			ret = pfarr[input](x, y);
			printf("ret=%d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);
	return 0;
}

指向函数指针数组的指针

在这里插入图片描述

例如:int(* (*ppfA)[4] )(int, int) = &pfA就是指向函数指针数组 的 指针。

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pa)(int, int) = Add;//函数指针

	int(*pfA[4])(int, int);//函数指针数组

	int(*  (*ppfA)[4]  )(int, int) = &pfA;//指向函数指针数组 的 指针

	return 0;
}

回调函数

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

void calc(int (*pf)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("输入两个操作数:>");
	scanf("%d %d", &x, &y);
	ret = (pf)(x, y);
	printf("ret=%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:
			break;
		default:
			printf("输入有误\n");
		}
	} while (input);
	return 0;
}

qsort快速排序

这个是冒泡排序的函数,我们发现它只能够给整型排序。如果我需要对字符串、结构体等类型进行排序,那么这个函数显然是实现不了的!那有什么办法吗?

void bubble_sort(int* arr, int x)
{
	//趟数
	for (int i = 0; i < x-1; i++)
	{
		//每一趟冒泡排序的过程
		for (int j = 0; j < x -1- i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

可以使用qsort快速排序。
在这里插入图片描述
这个compare函数比较抽象
在这里插入图片描述
当e1小于e2 的时候返回-1;当e1大于e2 的时候返回1;当e1等于e2 的时候返回0。

排序整型数组

使用qsort排序整型数组:

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

解释一下:
void* 是一种无类型指针,无具体类型的指针,所以void* 的指针变量可以存放在任意类型的地址,但是void* 的指针不能直接进行解引用操作,更不能进行±操作。

需要把void类型强制类型转化成int类型,然后再解引用,进行比较。

返回的结果甚至可以写成这样:
return * (int * )e1- *(int * )e2;//升序
return * (int * )e2- *(int * )e1;//降序

排序结构体数据
struct Stu
{
	char name[20];
	int age;
	float score;
};

struct Stu arr[ ] = { {“zhangsan”,20,67.4f},{“lisi”,10,70.0f},{“wangwu”,21,95.9f} };
如果现在我想按照成绩排序,我传入比较函数的参数类型一定是void* 类型,将他强制类型转化为结构体指针类型。前面结构体的章节讲过了,结构体指针可以同构 -> 访问里面的内容,结构体可以通过来访问内容。
不记得可以去看一看——>c语言第七课,结构体

int cmp_stu_by_study(const void* e1, const void* e2)
{
	//
	if (((struct Stu*)e1)->score > ((struct Stu*)e2)->score)
	{
		return 1;
	}
	else if (((struct Stu*)e1)->score < ((struct Stu*)e2)->score)
	{
		return -1;
	}
	else
	{
		return 0;
	}
}
void test2()
{
	struct Stu arr[] = { {"zhangsan",20,67.4f},{"lisi",10,70.0f},{"wangwu",21,95.9f} };
	//给出一个比较的方法:成绩
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_study);
	for (int i = 0; i < sz; i++)
	{
		printf("%s %d %.2f\n", arr[i].name, arr[i].age, arr[i].score);
	}
	printf("\n");
}
int main()
{
	test2();
	return 0;
}

如果按照年龄排序:

int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void test2()
{
	struct Stu arr[] = { {"zhangsan",20,67.4f},{"lisi",10,70.0f},{"wangwu",21,95.9f} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	for (int i = 0; i < sz; i++)
	{
		printf("%s %d %.2f\n", arr[i].name, arr[i].age, arr[i].score);
	}
}
int main()
{
	test2();
	return 0;
}

如果按照名字排序,比较的是ASCII码的值:

int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name , ((struct Stu*)e2)->name);
}
void test2()
{
	struct Stu arr[] = { {"zhangsan",20,67.4f},{"lisi",10,70.0f},{"wangwu",21,95.9f} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	for (int i = 0; i < sz; i++)
	{
		printf("%s %d %.2f\n", arr[i].name, arr[i].age, arr[i].score);
	}
}
int main()
{
	test2();
	return 0;
}

指针和数组面试题的解析

多做题、多实践,方可悟道。——>指针面试题传送门

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值