C语言指针(进阶)

1. 字符指针

在指针类型中我们知道有一种指针类型为字符指针 ( char *)
下面写一段代码进行相关叙述

#include<stdio.h>
int main()
{
	const char *p="abcdef";
	//*p='w'err
	printf("%c\n",*p);
	printf("%s\n",*p);
	return 0;
} 

代码中的“abcdef”是常量字符串,放在常量区,因此直接改变p指针所指向的内容是错误的。因此在char前面加const。
下面分析一道题目:

#include<stdio.h>
int main()
{
	const char*p1="abcdef";
	const char*p2="abcdef";
	
	char arr1[]="abcdef";
	char arr2[]="abcdef";

	if(p1==p2)
	printf("p1=p1\n");
	else
	printf("p1!=p2"\n);
	if(arr1==arr2)
		printf("arr1=arr2\n");
	else
		printf("arr1!=arr2\n");
	return 0;
}

代码运行后输出的结果是什么呢?
让我们来进行分析:
因为p1和p2指向的是常量字符串,因此计算机不会开辟两块空间存放字符串,因此p1=p2;
在这里插入图片描述
第二组则是用字符串创建了一个数组,数组名表示首元素地址,两个数组是开辟了两块独立的地址,因此首元素地址也不相同。

2.指针数组

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

//整型数组
int arr[10]={0};//存放整型的数组
//字符数组
char arr2[5];//存放字符的数组
//指针数组
int *arr[10];//存放整型指针的数组
char *ch[5];//存放字符指针的数组

简单举个栗子:

#include<stdio.h>
int main()
{
	int a=10;
	int b=20;
	int c=30;

	int *p1=&a;
	int *p2=&b;
	int *p3=&c;

	int *arr[3]={p1,p2,p3};
	return 0;
}

感觉没啥用?那就再举个例子:

//打印三个数组的内容
#include<stdio.h>
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 *parr[3]={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");
	}
	return 0;
} 

3.数组指针

3.1数组指针的定义

首先问一个问题,数组指针是数组还是指针?

当然是指针。

首先区分一下数组指针和指针数组

int *p1[ 10 ];

这是一个指针数组,p1的类型是int*,里面存放的是指针,是指针数组。

int (*p2) [ 10 ];

p2和*先结合,说明p2是一个指针,向外看,有[ 10 ],说明指针指向数组,每个元素是int。因此,p2是一个数组指针。
在这里插入图片描述

3.2数组名VS&数组名

先看一组代码

#include<stdio.h>
int main()
{
	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);

	return 0;
}

代码运行结果如下
在这里插入图片描述
看第一行的地址发现,arr,&arr[0],&arr三者的地址是相同的,但是加一后却不太一样,arr和&arr[0]都加了4,但是&arr加了40。
其实也可以理解,arr和&arr[0]的类型都是int*,但为什么&arr向后移动了40?
我们来存放一下&arr

int (*p) [10]=&arr;

p是一个指针,指向了数组,所以p叫数组指针!它的类型是int (*)[ 10 ]。

总结一下,数组名怎么理解呢?
通常情况下,我们说数组名都是数组首元素的地址,但是有两个例外:
1.sizeof(数组名),这里的数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小。
2.&数组名,这里的数组名表示的是整个数组,取出的是整个数组的地址。

注意,这个概念很重要,之后会多次应用。

写代码感受一下数组指针的作用

//数组指针的作用
#include<stdio.h>
void print(int (*p)[], int sz)
{
	int n = 0;
	for (n = 0; n < sz; n++)
	{
		//*p相当于数组名,数组名又是首元素地址
		printf("%d ", *(*p + n));
	}
}
int main()
{
	int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//写一个函数打印arr1的内容
	int sz = sizeof(arr1) / sizeof(arr1[0]);
	print(&arr1, sz);
	return 0;
}

不难发现,这个代码感觉好累,因此数组指针经常会在二维数组中使用。比如:

//数组指针的使用2
#include<stdio.h>
void print(int(*p)[5], int x, int y)
{
	int n = 0;
	for (n = 0; n < 3; n++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(*(p + n) + j));
			//p+n表示指向第n行
			//*(p+n)相当于第n行的数组名
			//数组名表示首元素地址,*(p+n)就是第n行第一个元素地址
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	//写一个函数,打印arr数组
	print(&arr, 3, 5);
	return 0;
}

4.数组参数,指针参数

在写代码时难免要把【数组】或【指针】传给参数,那么函数的参数应该如何设计?

4.1一维数组传参

话不多说,先上代码:

//一维数组传参
#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int* arr[20] = { 0 };
	test(arr);
	test2(arr2);
	return 0;
}

思考一下,这里的test的参数应该怎么写呢?

void test ( int arr[ ] )
{}

提示一下,数组传参本质上传递的是数组首元素的地址,是不会创建数组的,这里‘[ ]'中可以不填任何数,也可以胡填(但是不建议,也不提倡)

思考一下,arr数组名是数组首元素地址,当然也可以写成地址的形式

void test ( int*p )
{}

test2函数的传参

void test2 ( int *arr2[ ] )
{}

当然,还有指针的形式

void test2 ( int **p)
{}

对一级指针取地址,放在二级指针中。

4.2二维数组传参

先上代码:

//二维数组传参
#include<stdio.h>
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
	return 0;
}

思考:这里的test函数用什么类型的参数接收呢?
1.形参写成数组的形式

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

行可以省略,但是列不可以省略

2.形参写成指针的形式

void test ( int ( *p ) [ 5 ] )
{}
在这里插入图片描述

对于二维数组来说,首元素地址表示第一行的地址,但是第一行又包含了5个元素,因此地址是一个数组指针,指向含有五个元素的数组。

4.3一级指针传参

上代码

//一级指针传参
#include<stdio.h>
void test(int* ptr, int sz)
{
	int n = 0;
	for (n = 0; n < sz; n++)
	{
		printf("%d ", *ptr);
			ptr++;
	}
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p=arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	test(p,sz);//p是一级指针
	return 0;
}

4.4二级指针传参

二级指针传参类似一级指针,很简单,什么类型的指针就传什么类型的参数
但是思考,如果参数是一个二级指针,那么可以将什么数据传递过去?

int main()
{
	char ch='s';
	char*pc=&ch;
	char**ppc=&pc;
	char*arr[4];
	
	//char arr1[3][5];
	//test(arr1);
	//err

	test(&pc);
	test(ppc);
	test(arr);
	
	
	return 0;
}

如果是二维数组(如例程中),那么传递的是char (*p)[5]。不是一个二级指针。

5.函数指针

上代码

//函数指针
#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int arr[10];
	int(*p)[10]=&arr;//p是一个指针变量
	//取出函数的地址
	//两种函数地址的表达方式
	printf("%p\n", &Add);
	printf("%p\n", Add);
	int (*pf)(int, int) = &Add;//pf就是函数指针变量
	return 0;
}

代码中举例了Add函数指针的例子,( *pf )表示pf是一个指针,(int,int)表示的是函数的参数的类型,而在最前面的 int 则是函数的返回类型。所以这个函数指针pf的类型就是

int ( * ) ( int , int )

当然,也可以直接使用函数的指针来调用函数。


```c
//函数指针的调用
#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = Add;//pf就是函数指针变量
	int ret=(*pf)(3,2);
	printf("%d\n",ret);
	return 0;
}

仔细想想,(*pf)=Add的意思不就是把Add赋给了pf嘛,所以上面的代码也可以写成

//函数指针的调用
#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = Add;//pf就是函数指针变量
	int ret=pf(3,2);
	printf("%d\n",ret);
	return 0;
}

6.函数指针数组

依然通过代码来比较一下不同类型的指针数组

//函数指针数组
#include<stdio.h>
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()
{
	//字符指针
	char* arr[5];

	//整型指针数组
	int* arr2[5];

	int(*pf1)(int, int) = Add;
	int(*pf2)(int, int) = Sub;
	int(*pf3)(int, int) = Mul;
	int(*pf4)(int, int) = Div;
	//函数指针数组
	int(*pf[4])(int, int) = { Add,Sub,Mul,Div };
	//我们进行一下指针数组的使用
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = pf[i](8, 2);
		printf("%d\n", ret);
	}
	return 0;
}

代码中包含了函数指针数组的创建以及使用。创建函数指针数组时,只需要在函数指针名后加‘ [ ] '。至于函数指针数组的使用,可以看一下接下来的代码。实现一个加减乘除的计算器。

//计算器的实现
#include<stdio.h>
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 menu()
{
	printf("*********************\n");
	printf("****1.add  2.sub ****\n");
	printf("****3.mul  4.div ****\n");
	printf("****0.exit **********\n");
	printf("*********************\n");

}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	//转移表
	int (*pf[5])(int, int)={0,Add,Sub,Mul,Div};
	do
	{
		menu();
		printf("请选择 >\n");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
		}
		else if (input <= 4 && input >= 1)
		{
			printf("输入两个操作数 >");
			scanf("%d%d", &x, &y);
			ret = pf[input](x, y);
			printf("%d\n", ret);
		}
		else
			printf("输入错误,重新输入");
	} while (input);
	return 0;
}

函数指针数组的用途是转移表,这样写出来的计算器代码相比于用switch case写出来的显得不那么冗余。当然在代码中也有小的细节

比如为什么在函数指针数组中要存放一个0?

因为在后面指针数组的使用中直接用input为下标来访问函数,而当input是0的时候是退出计算器,这样一写是不是显得很妙呢?

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

啊这~好乱好乱,没关系,上代码

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int* arr2[5];
	int* (*parr2)[5] = &arr2;

	//函数指针
	int(*pf)(int, int) = Add;

	//函数指针数组
	int(*pfarr[4])(int, int);
	int(*(*p3)[4])(int, int)=&pfarr;//p3就是一个指向函数指针数组的指针
	return 0;
}

简单介绍,禁止套娃
简单应用的一个小例子

#include<stdio.h>
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( * parr[4])(int, int) = {Add,Sub,Mul,Div};
	int(*(*p3)[4])(int, int) = &parr;
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret = (*p3)[i](3, 4);
		printf("%d\n", ret);
	}
	return 0;
}

8.回调函数

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

//回调函数
#include<stdio.h>
void test()
{
	printf("hehe\n");
}
void print_hehe(void(*p)())
{
	p();
}
int main()
{
	print_hehe(test);
	return 0;
}

qsort函数

介绍:
qsort是一个库函数,基于快速排序算法实现的一个排序的函数
这里我们先回忆一下冒泡排序

//冒泡排序
#include<stdio.h>
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 print(int arr[], int sz)
{
	int n = 0;
	for (n = 0; n < sz; n++)
	{
		printf("%d ", arr[n]);
	}
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	//排序为升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	print(arr, sz);
	return 0;
}

冒泡排序看起来很好,但是会发现,它只能排序整型数组。但是qsort函数可以排序任意类型数组。

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

这就是qsort函数的函数声明,我们一起看看它。
base存放的是待排序数据的起始位置;num是数组的元素个数;size是一个元素的字节大小;compar是一个比较函数(用户自定义),后面的两个是待比较的两个元素的地址;但是这个函数返回的是int类型
在这里插入图片描述
如果元素是整型数据,很简单,用“> <"进行比较。但是如果是结构体数据,就不方便直接用>或<进行比较,所以使用者要根据实际情况,提供一个函数,实现两个数据的比较。
先排序一下整型和结构体

#include<stdio.h>
#include<stdlib.h>
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 print(int arr[], int sz)
{
	int n = 0;
	for (n = 0; n < sz; n++)
	{
		printf("%d ", arr[n]);
	}
}
void test1()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	//排序为升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	print(arr, sz);
}
int cmp_int(const void* e1, const void* e2)
{
	//if (*(int*)e1 > *(int*)e2)
	//	return 1;
	//else if (*(int*)e1 = *(int*)e2)
	//	return 0;
	//else
	//	return -1;
	return(*(int*)e1 - *(int*)e2);
}
void test2()
{
	int arr[] = { 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);
	print(arr, sz);
}
struct Stu
{
	char name[20];
	int age;
	double score;
};
//使用qsort排序结构体
int cmpstu(const void*e1,const void*e2)
{
	return(((struct Stu*)e1)->age - ((struct Stu*)e2)->age);
}
void test3()
{
	struct Stu arr[3] = { {"zhangsan",20,55.5},{"lisi",24,88.4},{"wangwu",25,90.0} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmpstu);

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

这里将冒泡排序合qsort排序放在了一起,当然提醒qsort是stdlib.h头文件的库函数,使用时要包含头文件。
在提供元素比较的函数这里有个很巧妙的应用以及几个要注意的点:

  • e1,e2的类型是void,此时不可以直接进行解引用然后进行比较要进行强制类型转换后再进行比较。
  • 在比较整型的时候,使用if else代码会比较冗余,所以比较元素直接进行相减,代码会更简洁

对于qsort函数,我们进行思考,为什么比较函数的元素的类型为const void*?

qsort函数的作者,是不知道我们会用什么类型的数据进行比较的,所以使用void*类型,之后使用者可以根据自己需要的类型进行强制类型转换。

对qsort函数的函数声明进行具体的分析(代码实现):

//qsort函数代码实现
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_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 j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//进行交换
				swap(cmp((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

指针的内容就到此为止啦,内容有些多,大家可以收藏了慢慢看。制作不易,还请大家多多支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Feng,

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

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

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

打赏作者

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

抵扣说明:

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

余额充值