C语言 --- 指针 --- 进阶

在初阶指针部分我们已经学过指针了,可以去我主页去找。

字符指针

我们在解决初阶指针的时候,我们知道了char* 类型的指针。那么我们来看看char* 指针的进阶。
我们来看一个代码:

#include <stdio.h>
int main()
{
	//一般的使用方法
	char ch = 'w';
	char* pc = &ch;
	*pc = 's';
	//进阶的使用方法
	//这种使用方法,是把hello world 的首字母的地址放到了ch1里面
	//hello world 是一个常量,所以不能改变。为避免用户出错,我们
	//可以这样写。
	char* ch1 = "hello world";
	const char* ch2 = "hello world";
	//加上const以后,就不能对ch2所指向的内容修改。
	//ch1和ch2指针变量指向的是同一块空间。
	printf("%s\n", ch1);

	return 0;
}

我们可以画图来解释这个代码
在这里插入图片描述
再来看以下代码

int main()
{
	char arr1[] = "hello world";
	char arr2[] = "hello world";
	char* arr3 = "hello world";
	char* arr4 = "hello world";
	if (arr1 == arr2)
	{
		printf("arr1 and arr2 are same\n");   
	}
	else
	{
		printf("arr1 and arr2 are not same\n"); 
		//arr1 and arr2 are not same arr1和arr2指向的是两个
		//内存空间
	}
	if (arr3 == arr4)
	{
		printf("arr3 and arr4 are same\n");
		//arr3 and arr4 are same     arr3 and arr4 指向的
		//是同一块内存空间
	}
	else
	{
		printf("arr3 and arr4 are not same\n");
	}
	return 0;
}

执行结果:
在这里插入图片描述

指针数组

这个在指针初阶部分已经讲过了,指针数组是一个存放 指针的数组
它的定义是这样的

int* arr1[10]; //整型指针数组
char* arr2[10]; //字符型指针的数组
char** arr3[10]; //二级字符指针的数组

我们来看这个代码:

int main()
{
	char a = 'a';
	char b = 'b';
	char c = 'c';
	//一级指针
	char* p = &a;
	char* p1 = &b;
	char* p2 = &c;
	//二级指针
	char** pp = &p;
	char** pp1 = &p1;
	char** pp2 = &p2;
	char** chp[3] = { pp,pp1,pp2 };
	//访问二级字符指针的数组
	for (int i = 0; i < 3; i++)
	{
		printf("%c\n", **chp[i]); 
		//找到数组元素为二级指针,解引用变为一级指针,再解引用
		//访问到指针所指向的内容。
	}
	return 0;
}

数组指针

数组指针的定义

数组指针就是指向数组的指针,可以简单的理解为它是一个数组类型的指针。
可以看一下这个代码,熟悉数组指针的定义:

int* p1[10];  //指针数组
int (*p2)[10]; 
//数组指针,注意和指针数组的差别。数组和指针谁在后面就是什么。
//解释:p2和*首先结合,p2就变成了指针变量,然后向外看,它是一个数组类型(int [10])的指针变量,简称数组指针。指向的是一个数组。
//[]的优先级要高于*的优先级,所以必须加上()来让p2先和*结合

&数组名VS数组名

我们来看下面这个代码:

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr);
	//打印出来的地址是一样的,但是我们使arr和&arr加上一个1
	//我们再来看打印出的地址。
	printf("%p\n", arr+1);
	printf("%p\n", &arr+1);
	return 0;
}

再看运行结果
在这里插入图片描述
运行结果解释:

实际上:&数组名表示的是整个数组的地址,而不是数组首元素的地址。
整个数组的地址,需要用数组指针来存储。本例的数组指针可以这样定义:
int (*p)[10] = &arr;

数组指针的使用

前面提到了数组指针,接下来我们看它的使用:可以使用数组指针为二维数组传参,但是我们可以使用更容易理解的方法进行传参。请看下面的代码:

void print_arr1(int arr[3][3], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
void print_arr2(int(*arr)[3], int row, int col)
{
	for (int 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_arr1(arr, 3, 3);
	//这里来解释一下二维数组的数组名是什么
	//其实二维数组的数组名就是首行数组的地址。也就是
	//一维数组的地址,数组的地址用数组指针来接受。int(*arr)[3]。
	print_arr2(arr, 3, 3);
	return 0;
}

学习了以上内容,我们来回顾以下代码的意思
int arr[5]; 存放整型数据的数组
int* parr1[10]; 存放整型指针的数组 - 指针数组
int (*parr2)[10]; 存放数组地址的指针 - 数组指针
int (*parr3[10])[5];
最后一个我们可以这样 把它分解为两部分,一部分为:int (* )[5],一部分为:parr3[10]。我们可以把它理解为一个数组,里面存放的数据类型是数组指针。可以称它为数组指针的数组。

数组参数、指针参数

在写代码的时候我们可以把数组或者指针传给函数,那么函数的参数该如何设计请看下面的代码:

一维数组传参:

#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? //这个参数是可以的,它是一个指针数组。而arr2也是一个指针数组,所以我们可以用相同类型的参数进行接收
{}
void test2(int **arr)//ok? //这个也是可以的,指针数组里面存放的是一个变量的地址,然后数组名是数组首元素的地址,而指针的地址就是二级指针。可以用二级指针来接收参数。
{}
int main()
{
 int arr[10] = {0};
 int *arr2[20] = {0};
 test(arr);
 test2(arr2);
}

二维数组传参:

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? //这个是可以的,因为它是一个数组指针。二维数组名是第一行数组的地址,&arr。
{}
void test(int **arr)//ok? //这个是不可以的。二级指针是一个指针的地址,而二维数组名是一个数组地址。
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);
}

二维数组传参,传过去的是一个数组指针,那么形参也应该由数组指针来接收。

一级指针传参

#include <stdio.h>
print(char* p, int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%c ", *(p + i));
	}
}
int main()
{
	char arr[10] = "abcdefghi";
	char* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(p, sz);
	return 0;
}

运行结果:
在这里插入图片描述

当一个函数的参数部分是一个一级指针的时候,函数可以接收一个变量的地址,一个一级指针变量,一维数组。

二级指针传参

#include <stdio.h>
void test(int** ptr)
{
	printf("num = %d\n", **ptr);
}
int main()
{
	int n = 10;
	int* p = &n;
	int** pp = &p;
	test(pp);
	test(&p);
	return 0;
	return 0;
}

运行结果:
在这里插入图片描述

总体来说,当函数的参数为二级指针的时候,函数可以接收一个指针的变量的地址,一个二级指针变量,还有指针数组名。

函数指针

这里我们来类比前面所学的类型
int* arr; int类型的指针
int (*p)[5] 数组指针
我们可以发现,不同的指针变量的类型可以式int,可以是数组,也可以是指针,所以我们可以得到一个规律,数组指针是数组类型的指针,整型指针式整型类型的指针,而函数指针就是函数类型的指针,那么我们就可以这样定义:
返回类型 (指针变量名)(函数的参数列表)
比如:
一个函数的参数列表是一个整型和一个char
指针构成,返回值为空,那么我们就可以这样定义:
void (*p)(int ,char*) = &printf;

我们来思考这么一个问题,&函数名,和函数名所代表的意义是否是相同的。是不是像数组那样是不同的,这个答案是相同的,取地址函数名和函数名的意义是一样的都是表示函数的地址。

我们来用代码来证明一下:
在这里插入图片描述

我们来看两个有意思的代码:

int main()
{
	//代码1:
	//把一个整数0,强制类型转换为函数指针类型,
	//外层解引用相当于调用函数。我们可以
	//通过反汇编来观察

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

	//代码2:
	//signal(int, void(*)(int))是一个函数,它的函数是一个
	//int,另一个是一个函数指针类型,该函数的返回值为空,参数列表
	//为int,然后signal函数的返回类型是一个函数指针类型,该函数的
	//返回值为空参数类型为int;所以这个语句是声明了一个返回类型为函数指针,参数
	//为int和函数指针的一个函数。
	void (*signal(int, void(*)(int)))(int);
	return 0;
}

在这里插入图片描述

我们可以将一个函数指针类型用typedef关键字取一个别名,但是这个别名必须和 * 挨在一块。
请看下面的代码:

typedef void(*DATETYPE)(int)
//typedef void(*)(int) DATA //这样写是错误的

那么代码2就可以写成:

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

函数指针数组

函数指针数组,首先它是一个数组,数组里面存放的是数组指针。
它是这样定义的:
int(*parr1[10])()
parr1[10]是一个数组然后每一个元素是一个指针数组。
我们来看一下函数指针数组的使用。首先,我们来编写一个加减乘除的计算器。代码是这样的:

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;
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	do
	{
		printf("*************************\n");
		printf(" 1:add           2:sub \n");
		printf(" 3:mul           4:div \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret = %d\n", ret);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;
}

我们可以看到以上的代码非常的冗余,那么有没有什么简单的办法呢,那就是函数指针数组,也称转移表。
我们来看修改后的代码:

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;
}
int main()
{
	int input = 1;
	int x = 0;
	int y = 0;
	int ret = 0;
	
	//定义函数指针数组并且初始化
	int(*parr[5])(int, int) = { NULL,add,sub,mul,div };
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			//请输入两个操作数
			printf("请输入两个操作数>\n");
			scanf("%d %d", &x, &y);
			ret = (parr[input])(x, y);
			printf("结果为:%d\n", ret);
		}
		else
		{
			printf("输入有误\n");
		}
	} while (input);
	return 0;
}

好的今天的内容就到这里。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值