【C语言——指针进阶】

【C语言——指针进阶】

前言

前面,在指针初阶中我们学习了什么是指针指针和指针类型野指针指针运算指针和数组二级指针指针数组。在本章中,我们将继续来了解指针。本章的重点内容有字符指针,数组指针,指针数组,数组传参和指针传参,函数指针,函数指针数组,指向函数指针数组的指针,回调函数,指针和数组面试题的解析

1.字符指针

在指针类型中我们知道有一种指针类型叫做字符指针char*

字符指针的使用方式一:

int mian()
{
	char ch = 'w';
	char* p = &ch;
	*p = 'c';
	return 0;
}

字符指针的使用方式二:

int main()
{
	char* p = "abcdef";
	printf("%s", p);
	return 0;
}

这里是把字符串放在了指针变量p里面了吗?不是这样的.这里是把字符串的首地址放在了指针变量中.

字符指着指向字符串:

方式一:

	char* p = "abcdef";

易错:

	char* p = "abcdef";
	*p = 'w';

这段代码是错误的,指针变量p解引用找到的是字符串的首元素,首元素是常量,常量不能被赋值.编译器报错: 不可修改的左值.

方式二:

	char arr[] = "abcdef";
	char* p = arr;
	*p = 'w';

这段代码是正确的,因为对指针变量p解引用得到的是数组的首元素,数组是变量,可以被修改.

为了防止方式一中的错误发生,方式一的代码应修改为:

	const char* p = "abcdef";

被const关键词修饰之后,指针变量p指向的内容不可以修改.

重要的事情说三遍:
这里不是将字符串放在了指针变量中,而是将字符串的首元素的地址放在了指针变量p中.
在这里插入图片描述
理解了上面的内容,再来看看这一道面试题:

int mian()
{
	char str1[] = "hello world";
	char str2[] = "hello world";
	const char* str3 = "hello world";
	const char* str4 = "hello world";

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

在这里插入图片描述
解析:

这里的str1和str2是两个数组名,它们在内存中开辟了两块儿空间,所以str1和str2的地址是不一样的,所以str1不等于str2.
str3和str4是两个指针,它们指向同一个字符串,指向了同一块空间,所以str3和str4是相同的.

2.指针数组

在指针初阶中我们已经已经学习了指针数组,指针数组是一个存放指针的数组.

让我们复习一下,下面的指针数组是什么意思.

	int* arr[10];//整型指针数组,用来存放整型的指针
	char* arr1[10];//字符指针数组,用来存放一级字符指针
	char** arr2[10];//二级字符指针数组,用来存放一级字符指针

字符型指针数组:

int main()
{
//在字符指针数组中存放了三个字符串的首地址
	char* arr[] = { "abcdef","hehe","gert" };
	for (int i = 0; i < 3; i++)
	{
		printf("%p\n", arr[i]);
	}
	return 0;
}

在这里插入图片描述
整型指针数组:

int main()
{
	int arr1[] = { 1,2,3,4 };
	int arr2[] = { 5,6,7,8 };
	int arr3[] = { 9,10,11,12 };
	//在整型指针数组中存放三个整型指针
	int* arr[] = { arr1,arr2,arr3 };

	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			printf("%2d ", *(arr[i] + j));
		}
		printf("\n");
	}
	return 0;
}

在这里插入图片描述

3.数组指针

3.1 数组指针的定义

数组指针是指向数组的指针,类比于整型指针,指向整型的指针.数组指针的定义:

int(*p)[10];

*号与p结合,表明p是一个指针变量,除了变量名p,剩下的 int ( *)[10]是它的类型.表明该指针变量指向一个存放10个整型元素的数组.
注意:[]的优先级高于 *,所以必须加上()来保证p先和 *结合.

3.2 &数组名VS数组名

&数组名和数组名有什么区别?
**数组名是首元素的地址,&数组名取出的是整个 数组的地址.**它们在地址上是相同的,意义不同,也就是加减一个整数时,跳过的字节数不同.

例如:

int main()
{
	int arr[10] = { 0 };
	
	printf("arr = %p\n", arr);
	printf("&arr = %p\n", &arr);

	printf("arr+1 = %p\n", arr+1);
	printf("&arr+1 = %p\n", &arr+1);

	return 0;
}

在这里插入图片描述

注意,这里的arr和&arr,两者的地址都是一样的.但是加一之后,arr跳过了4个字节,也就是一个整型的大小.&arr跳过了40个字节,也就是一个数组的大小.所以说,在意义上,arr代表数组首元素的地址,&arr代表整个数组的地址.这一内容.在数组章节详细的叙述过.感兴趣的朋友可以看看.

数组名和&数组名总结

数组名在绝大多数情况下表示的都是数组首元素的地址,但是有两个例外:
1.sizeof(数组名)--------sizeof内部单独放一个数组名时,表示的是整个数组.计算得到的是数组的总大小.
2.&数组名表示的是整个数组,取出的是整个数组的大小.从地址值的角度来看,数组名和&数组名的值是一样的,但是意义不一样.

3.3 数组指针的使用

3.3 1 一维数组传参 形参是一维数组
void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

int main()
{
	int arr[] = { 1,2,3,4,5,6 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);

	return 0;
}
3.3 2 二维数组传参 形参是二维数组
void print(int arr[][3], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][3] = {1,2,3,4,5,6,7,8,9};
	print(arr, 3,3);

	return 0;
}
3.3 3 二维数组传参 形参是数组指针

在这里插入图片描述

二维数组的元素是一维数组,二维数组的数组名表示首元素的地址。即一维数组的地址,数组的地址存放在数组指针中,所以二维数组传参,形参可以是数组指针。

例子:

void print(int (*arr)[3], int row, int col)
{
	int i = 0;
	for (i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			//这里的arr[i]相当于某一行的数组名,加上j表示访问该行的其它元素
			printf("%d ", *(arr[i]+j));
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][3] = {1,2,3,4,5,6,7,8,9};
	print(arr, 3,3);

	return 0;
}

学了指针数组和数组指针之后,看看下面的代码表示的是什么意思

	int arr[5];//表示有5个元素的整型数组
	int* parr1[10];//表示一个指针数组,该数组中存放了10个整型的指针
	int(*parr2)[10];//数组指针,指向存放10个整型元素的数组
	int(*parr3[10])[5];
	//它是一个数组,数组里面有10个元素,数组的类型是int (*)[5];
	//即数组中存放10个数组指针,这些指针指向有5个元素的整型数组,所以它是一个数组指针的数组

4.数组参数 指针参数

4.1 一维数组传参

//正确:一维数组传参,形参是一维数组,不用表明有几个元素,
//因为它不会真的创建一个数组,它仅仅是寻址
void test(int arr[])
{}

//正确:理由同上,一维数组的形参有没有数组元素都可以
void test(int arr[10])
{}

//正确:一维数组传参,传的是数组名,数组名是首元素的地址,
//首元素是整型,整型的地址用整型指针来接受
void test(int *arr)
{}

//正确:一维数组传参,传的是数组名,数组名是首元素的地址;
//首元素是一级指针,一级指针放在一级指针数组中
void test2(int *arr[20])
{}

//正确:二级指针接收一级指针的地址,一维数组传参,传的是数组名,
//数组名是首元素的地址,首元素是一级指针,一级指针的地址存放在二级指针中
void test2(int **arr)
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);

	return 0;
}

总结:
一维数组传参(除指针数组外),形参可以是一维数组,可以是一级指针;一维指针数组传参,形参可以是一维指针数组,也可以是二级指针。

4.2 二维数组传参

//正确:二维数组传参,形参是二维数组
void test (int arr[3][5])
{}

//错误:二维数组的声明可以没有行,不能没有列
void test(int arr[][])
{}

//正确:二维数组的声明可以没有行
void test(int arr[][5])
{}

//错误:二维数组传参,参数是一维数组,应该用数组指针来接收,而不是整型指针
void test(int *arr)
{}

//错误:整型指针数组,存放的是整型一级指针,而传过来的是一维数组的地址
void test(int* arr[5])
{}

//正确:实参是数组,形参是数组指针
void test(int(*arr)[5])
{}

//错误:形参是二级指针,存放的是一级整型指针的地址,而这里是数组的地址
void test(int **arr)
{}

int main()
{
	int arr[3][5] = { 0 };
	test(arr);
	return 0;
}

总结:二维数组传参,形参可以是二维数组,也可以是数组指针

4.3 一级指针传参

void print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
}

int main()
{
	int arr[4] = { 1,2,3,4 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(p, sz);
	return 0;
}

思考:

当一个函数的参数部分是一级指针时,函数能接收什么参数?

答:可以接受地址,数组,一级指针

4.4 二级指针传参

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

思考:
二级指针作为函数参数时,可以接受什么参数呢?

答:二级指针,一级指针的地址,指针数组的数组名

5.函数指针

5.1 什么是函数指针

函数指针,顾名思义,指向函数的指针,也就是存放函数地址的指针,那函数有地址吗

void test()
{
	printf("hehe\n");
}

int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}

在这里插入图片描述
事实证明,函数是有地址的,并且函数名和&函数名得到的结果是一样的。

5.2 函数指针的声明

void test()
{
	printf("hehe\n");
}
void (*p)();//正确
void* p();//错误

函数指针的声明:

void (*test)();

✳与p结合,说明p是一个指针,p是变量名,除了p之外的部分就是类型,p的类型是 void (* )(); 即p是一个函数指针,指向一个没有参数,返回值为void 的函数。

练习一下以下函数指针的声明:

int test(int a,int b)
{}
int (*pt)(int,int)

char test1(int a, char b)
{}
char (*pp)(int,char)

5.3 函数指针的使用

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pt)(int, int) = Add;
	printf("%d ", pt(2, 3));

	return 0;
}

在这里插入图片描述
读下面两段有趣的代码:

代码一:

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

解析:void(*)()是一个函数指针类型。在它的外面套上一层()代表着强制转换。将0这个整型类型的数强制转换成一个函数指针类型。 ✳对0解引用,访问0这个函数指针指向的函数,该函数的参数为0.

代码二、

void (*signal(int, void(*)(int)))(int);

解析:内部 signal(int, void()(int)):signal先和()结合,说明signal是一个函数。它有两个参数,参数类型分别为int型和一个函数指针类型,整个void (signal(int, void()(int)))(int);,除了函数名和参数类型:signal(int, void()(int));剩下的就是函数的返回值类型,也就是void ( )(int);所以,该段代码是一个函数,它的返回值类型是一个函数指针类型,它的参数是一个int型和一个函数指针类型。

代码二太过复杂,如何简化:

typedef void(* ptr)(int);//将ptr定义为void (* )(int)类型
ptr signal(int,ptr);//用ptr类型代替代码二中的void (* )(int)部分

6.函数指针数组

函数指针数组是存放函数指针的数组。

6.1 函数指针数组的定义

int (*parr1[10])();

这里的parr1先和[]结合,表示一个数组,数组的元素类型为int (* )();就是说数组的类型是函数指针类型。数组中每一个元素都是一个函数指针。

6.2 函数指针数组的使用

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("-----------------------------\n");
}
int main()
{
	int input = 1;
	int x, y;
	int (*arr[5])() = { 0,add,sub,mul,div };//巧妙设计:输入0是退出
	while (input)
	{
		printf("----------------------------\n");
		printf(" 1.add                 2.sub\n");
		printf(" 3.mul                 4.div\n");
		printf(" 0.退出\n");
		printf("-----------------------------\n");
		printf("请输入你的选择:\n");
		scanf("%d", &input);
		int ret = 0;
		if (input == 0)
		{
			break;
		}
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = arr[input](x, y);//函数指针数组的使用
		}
		else 
			printf("输入有误");
		printf("ret = %d\n", ret);
	}
	return 0;
}

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

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

int main()
{
	//函数指针
	void (*ptr)(const char*) = test;
	
	//函数指针数组
	void(*ppt[5])(const char*);
	ppt[0] = ptr;

	//指向函数指针数组的指针
	void(*(*pt)[5])(const char*) = &ppt;

	return 0;
}

8.回调函数

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

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 pf(int(*pt)(int, int))
{
	printf("请输入两个操作数:");
	int x = 0;
	int y = 0;
	scanf("%d %d", &x, &y);
	int ret = pt(x, y);//回调函数
	printf("计算结果为:%d\n", ret);
}
void menu()
{
	printf("-----------------------------\n");
	printf("---1.Add          2.sub---\n");
	printf("---3.mul          4.div---\n");
	printf("---0.退出---\n");
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请输入你的选择:> ");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			pf(Add);
			break;
		case 2:
			pf(Sub);
			break;
		case 3:
			pf(mul);
			break;
		case 4:
			pf(div);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}

	} while (input);
	return 0;
}

8.2 回调函数模拟实现qsort函数(采用冒泡排序)

//用回调函数模拟实现qsort函数(采用冒泡排序)

//比较函数
int cmp(const void* s1, const void* s2)
{
	return (*(int*)s1 - *(int*)s2);
}

void swap(const void* p, const void* p1, int sd)
{
	for (int i = 0; i < sd; i++)//一个字节一个字节的交换
	{
		char temp = *((char*)p+i);
		*((char*)p + i) = *((char*)p1 + i);
		*((char*)p1 + i) = temp;
	}
}
void bubble_sort(void* base, int sz, int sd,int(*cmp)(void*,void*))//qsort功能需要的参数
{
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - i - 1; j++)
		{
			if (cmp((char*)base + j * sd, (char*)base + (j + 1) * sd)>0)//如果前者比后者大,则交换
			{
				swap((char*)base + j * sd, (char*)base + (j + 1) * sd, sd);
			}
		}
	}
}
int main()
{
	int arr[] = { 1,3,2,5,6,8,4,9,12};
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

总结:

本篇中我们深刻的讲解了字符数组,指针数组,数组指针:数组指针的定义、数组名和&数组名的区别,数组指针的使用:一维数组传参,二维数组传参的参数问题。数组参数,指针参数。函数指针:什么是函数指针,函数指针的声明,函数指针的使用。函数指针数组:函数指针数组的定义,函数指针数组的使用,指向函数指针数组的指针。回调函数,回调函数实现计算器,回调函数模拟实现qsort函数。该篇内容较为深刻,其中涉及到的知识点,可以翻翻我往期的博客。

一键三连,点个关注,不迷路~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值