[C语言]指针进阶

目录

1.字符指针 

2.指针数组

 3.数组指针

思考一下数组指针的形式

 有什么内涵呢

 实际应用:打印二维数组中的每个元素

判断下面代码的意思 

4. 数组参数、指针参数 

一维数组传参

二维数组传参 

一级指针传参

二级指针传参

5.函数指针 

6.函数指针数组 

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

8.回调函数

实例1——计算器的实现 

 实例2——qsort函数的学习和模拟实现

先来学习下qsort函数的内涵

模拟实现 

需要注意的点: 


1.字符指针 

以下代码输出的结果是什么?

回答:h            hello the         hello the

分析:说明字符指针变量可以存放字符串。

第一个h是因为*ps指针指向的就是hello的首字符h,故输出h;后两个因为数组名arr和ps一样都是hello的首字符地址,故均输出hello。

int main()
{
	//本质上是把"hello the"这个字符串的首字符地址存储在了ps中
	char* ps = "hello the";
	char arr[] = "hello the";
	
	printf("%c\n", *ps);//h
	
	printf("%s\n", ps);
	printf("%s\n", arr);
	return 0;
}

 以下代码输出的结果是什么?

回答:应该是1和2不同,3和4相同

分析:数组名代表数组首元素的地址,str1和str2是两个不同的数组,分别需要开辟两个不同的内存空间,str1和str2分别指向两个hello的首地址,因此不同;

"hello bit"是常量字符串,指针变量无法通过解引用操作改变它。对于内存而言既然"hello bit"是常量,那么只需为它开辟一个内存空间即可,地址编号确定,因此3和4指向了同一个地址即常量字符串"hello bit"的首元素地址

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

2.指针数组

定义:指针数组是一个存放指针的数组,本质上是数组,其中存的内容是指针

如下代码中:arr数组中可以存放三个整形指针int*,而a,b,c分别为三个整形数组首元素地址放入其中arr[0],arr[1],arr[2]

int main()
{
	//指针数组
	//数组——数组中存放的是指针(地址)
	//int* arr[3];//存放整型指针的数组
	int a[] = { 1,2,3,4,5 };
	int b[] = { 2,3,4,5,6 };
	int c[] = { 3,4,5,6,7 };
	int* arr[3] = { a,b,c };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(arr[i]+j));
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

 3.数组指针

定义:是一种指针,应该能够指向数组的指针,数组指针中存放的应该是数组的地址

接下来会与之前的指针数组区别:

  • int *p1[10];——指针数组

[ ]的优先级高于*号的,所以必须加上()来保证p先和*结合 

  • int (*p2)[10];

p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针 

思考一下数组指针的形式

首先得是指针(*)→指针指向的数组有多少元素呢([ ]) →指向的数组又是什么类型

int main()
{
	int arr[10] = { 1,2,3,4,5 };
	//arr——是arr[0]的地址
	int(*parr)[10] = &arr;//取出的是数组地址
	//parr就是一个数组指针——其中存放的是数组的地址
	
	double* p[10];
	double* (*pd)[10] = &p;
	return 0;
}

 有什么内涵呢

观察以下代码:

int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10 };
	int(*pa)[10] = &arr;//数组指针
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", (*pa + i));
		printf("%d\n", *(*pa + i));
	}
	return 0;
}

运行后我们看一下:

  1. *说明pa是指针变量,pa应该存放一个数组的地址,pa+1会跳过一个数组的地址大小;
  2. *pa是首元素地址,也就是arr数组名,*pa+1 也就是地址前进4位字节(与int指针类型匹配),反映数组中每个元素的地址
  3. 解引用操作:*(*pa)由此得出arr数组中的元素

 实际应用:打印二维数组中的每个元素

p是数组指针,指向一个数组,*p保存首元素地址(arr数组名),[5]反映指向数组中有5个元素,int表示数组元素类型

对于二维数组的首元素是二维数组的第一行,这里传递的arr,其实相当于第一行的地址,是一维数组的地址

  • (p+i)得到每一行数组的地址
  • *(p+i)得到每一行数组的首元素地址
  • (*(p+i)+j)获取每一行数组中每个元素的地址
  • *(*(p+i)+j)获得每一行数组中的元素数组内容
void print(int(*p)[5],int r,int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%p ", (p + i) );
			printf("%p ", *(p + i) );
			printf("%p ", *(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,3,5);
	return 0;
}

验证一下结果:

判断下面代码的意思 

int arr[5];——有着5个整型元素的数组

int *parr1[10];——存放10个整型指针的数组

int (*parr2)[10];——该指针能指向一个数组,一个数组中10个元素,每个元素类型是int

int (*parr3[10])[5];——parr3是一个存储数组指针的数组,该数组能存放10个指针,每个数组指针能够指向一个数组,数组5个元素,每个元素是int类型

4. 数组参数、指针参数 

一定要分析清楚,实参传过去的是什么。是地址的话是谁的地址,而形参对应又应该用什么形式接收。

一维数组传参

判断哪些可以传参,哪些不能?

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

这里形参的形式都是正确的。

  • test()

1,2:当作数组接收                                                                                              √正确

3:arr为数组名,此时传递的是首元素地址,用int*接收                                     √正确

  • test2()——int* arr2[20]是一个指针数组,存放int*类型的指针

4:传递指针数组,相应类型接收                                                                        √正确

5:arr2是指针数组首元素地址,首元素是int*类型的指针,也就成了二级指针 √正确

二维数组传参 

判断哪些可以传参,哪些不能?

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?


void test(int *arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int (*arr)[5])//ok?
{}
void test(int **arr)//ok?
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);
}
  • arr是一个二维数组,二维数组可以不知道行数,但必须知道列数,也就是一行有多少元素

因此1√正确,2×错误,3√正确

  • arr为首元素地址,代表第一行元素的地址,理解为数组指针,应该指向一个数组

1:理解arr为数组指针(能够指向第一行数组,第一行是有5个整型的数组 ),对应的形参格式错误,只是一级指针                                                                                             ×错误

2:形参接收的应为一个指针数组                                                                         ×错误

3:arr为数组指针,指向的数组内有5个int类型元素                                             √正确

4:传过去的不是二级指针                                                                                    ×错误

一级指针传参

函数形参形式是接收一级指针时,实参传递的是地址即可

二级指针传参

函数形参形式是接收二级指针时,实参可以传递二级指针,即一级指针的地址,也可以传存放一级指针的数组名

void test(char **p)
{
 
}
int main()
{
 char c = 'b';
 char*pc = &c;
 char**ppc = &pc;
 char* arr[10];
 test(&pc);
 test(ppc);
 test(arr);//Ok?
 return 0;
}

 分析:

pc是一级指针,ppc是二级指针,test(ppc)把二级指针ppc进行传参;

test(&pc)传一级指针变量的地址;

test(arr)传存放一级指针的数组,传递了arr数组名,数组名代表首元素的地址,而arr数组每个元素都是char *类型,char *的地址相当于char * *,符合 char(int ** p)中形参的接收要求

5.函数指针 

定义:指向函数的指针,存放函数地址的指针

函数指针的形式:

                                                    int(*pf)(int,int)

 首先应该是指针(例如*pf),指向一个函数,函数的类型如何?

函数包括函数形参的类型,函数的返回类型,因此函数指针也应该指明所指向函数包括的组成部分。

//函数指针——存放函数地址的指针
//&函数名——取到的就是函数的地址
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//&函数名等价于函数名
	printf("%p\n",&(Add));
	printf("%p\n",Add);

	//函数指针变量
	/*int(*pf)(int, int) = &Add;*/
	int(*pf)(int, int) = Add;//意味着Add===pf
	printf("%d\n", (*pf)(3, 5));
	printf("%d\n", pf(3, 5));
	printf("%d\n", Add(3, 5));
	return 0;
}

观察上述代码的结果:

  1. 函数名==&函数名,说明函数名就是函数的地址 !(同时记得数组名 != &数组名)
  2. 以往我们调用函数的形式Add(3,5)与int (*pf) (3,5)效果相同

pf是函数指针变量,存放函数的地址,由此可以得知pf==Add,函数还可以写成pf(3,5)

(*是对形式的理解,实际并没有起作用)

6.函数指针数组 

顾名思义——存放函数指针的数组,把函数的地址存到一个数组,同时是存放同类型的函数指针

int (*pf)(int,int)=Add;
int (*pf1)(int,int)=Sub;
int (*pfArr[2])(int,int);//函数指针数组pfArr

 设计一个计算器来理解:

//函数指针数组
//假设实现一个计算器,计算整型变量能够加减乘除
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("*************  1.Add  ***************\n");
	printf("*************  2.Sub  ***************\n");
	printf("*************  3.Mul  ***************\n");
	printf("*************  4.Div  ***************\n");
	printf("*************  0.退出 ***************\n");
}
int main()
{
	int input = 0;
	do
	{
		int(*pfArr[5])(int, int) = {NULL,Add,Sub,Mul,Div};
		int x = 0;
		int y = 0;
		menu();//计算器界面
		printf("请进行你所需要的操作:>");
		scanf("%d", &input);
		
		if (input >= 1 && input <= 4)
		{
			printf("请进行你所需要的计算的两个数值:>");
			scanf("%d%d", &x, &y);
			printf("%d\n", pfArr[input](x, y));
		}
		else if (input == 0)
		{
			printf("退出程序\n");
			break;
		}
		else
		{
			printf("请重新选择\n");
		}
		
	} while(input);
	return 0;
}

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

//整型数组
int arr[5];
int (*p1)[5]=&arr;
//p1是指向(整型数组)的指针


//整型指针的数组
int* arr[5];
int* (*p2) [5]=&arr;
//p2是指向(整型指针数组)的指针

//函数指针
int (*p)(int,int);
//函数指针数组
int (*p2[4])(int,int);
p3=&p2;//取出的是函数指针数组的地址
//p3是一个指向(函数指针数组)的指针
int (*(*p3)[4])(int,int);

8.回调函数

回调函数就是一个通过函数指针调用的函数

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

快速理解:一个函数本身没有直接被使用,其地址作为另一个函数的形参而被调用

实例1——计算器的实现 

//回调函数
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 Calc(int (*pf)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入2个操作数>:");
	scanf("%d %d", &x, &y);
	return pf(x, y);
}

int main()
{
	int input = 0;
	//计算器-计算整型变量的加、减、乘、除
	//a&b a^b a|b a>>b a<<b a>b

	do {
		menu();

		int ret = 0;
		printf("请选择:>");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
			ret = Calc(Add);
			printf("ret = %d\n", ret);
			break;
		case 2:
			ret = Calc(Sub);
			printf("ret = %d\n", ret);
			break;
		case 3:
			ret = Calc(Mul);//
			printf("ret = %d\n", ret);
			break;
		case 4:
			ret = Calc(Div);//
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误,重新选择!\n");
			break;
		}

	} while (input);
	return 0;
}

 实例2——qsort函数的学习和模拟实现

qsort函数可以排列任何类型的元素,包括整型变量、字符串变量、结构体等

先来学习下qsort函数的内涵

base中存放的是待排序数组第一个元素的首地址

num是排序待数据数组中元素的个数

size一个数组元素字节的大小

int (*compar)(const void*,const void*)比较待排序数据中两个元素的函数

 使用:

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

void test1()
{
	//整型数据的排序
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	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]);
	}
}

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

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

int sort_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name,((struct Stu*)e2)->name);
}

void test2()
{
	//使用qsort函数排序结构体数据
	struct Stu s[] = { {"zhangsan",30},{"lisi",40},{"wangwu",20}};
	int sz = sizeof(s) / sizeof(s[0]);
	
	//按照年龄排序
	/*qsort(s, sz, sizeof(s[0]), sort_by_age);*/
	
	//按照名字排序
	qsort(s, sz, sizeof(s[0]), sort_by_name);
}

int main()
{
	//test1();
	test2();
	return 0;
}

模拟实现 

//模仿qsort函数实现一个冒泡排序的通用算法
//不同类型数据比较方法交给使用者确定
void Swap(char* buf1, char* buf2,int size)
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}


void bubble_sort(void* base, int num, int size,
	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*size,(char*)base+(j+1)*size) > 0)
			{
				//交换
				Swap((char*)base + j * size, (char*)base + (j + 1) * size,size);
			}
		}
	}
}

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

void test3()
{
	//整型数据的排序
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//排序
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	//打印
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}


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

需要注意的点: 

1.字节大小size的作用

作为qsort函数的设计者,字节大小size十分重要,因为不知道使用者是什么类型的数据进行比较;

2.前后两个数据进行比较时,如何得到地址?为什么要强制转换为char*类型的指针

因为使用者会通过形参size告知前一个元素与后一个元素差几个字节。而char*类型的指针为一个字节,因此跳过一个元素:(char*)地址+1*size,跳过两个元素:(char*)地址+2*size;

 3.内部的交换函数如何实现?不知道什么类型的元素,如何将两个元素的内容进行交换呢?

由于我们不知道元素的类型,所以不能将指针转为相对应类型的指针进行交换,但我们知道一个元素的字节大小。因此我们依旧传递为char*类型的指针,通过size一个字节一个字节的交换

 4.思考:为什么使用void*空指针 

因为void*p空指针可以存放任意类型的指针,qsort函数并不明确知道需要排序的是什么类型的数组,因此先用void*接收,具体什么类型由使用者告知。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值