C语言指针相关知识(第三篇章)(非常详细版)


前言


前面已经两篇指针文章大致介绍了指针的基础知识,和简易的深入理解,在这片文章我主要想介绍几种比较重要的指针变量类型——字符指针变量`、数组指针变量、函数指针变量;以及一种特殊的数组——函数指针数组

一、字符指针变量

(一)、基本概念与两种表达形式

对于字符指针变量顾名思义指针变量指向的数据类型为字符类型。

  • 一种是单字符的
    定义代码如下:
#include<stdioh
int main()
{
char ch ='w';
char *pc=&ch;
*pc='w';
return ;
}
这里字符指针变量pc指向的是字符‘w’;
* 一种是字符串类型的
定义代码如下:
```c
#include<stdio.h>
int main()
{
const char*pstr = "hello bit.";
printf("%s\n",pstr);
return 0;

}

在这里我们虽然形式上看上去是把一个字符串放到指针变量pstr中,但其实本质上我们只是把字符串hello bit.首字符的地址放到了pstr里。

(二)、“字符串”型字符指针变量的深入理解

  • C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串,去初始化不同的数组的是时候就会开辟出不同的内存块。
  • 用字符指针去存储常量字符串的时候我们是不可以修改常量字符串的内容哒,这是规定,所以为了避免出错,我们通常前面+上一个const来限制我们通过解引用来修改字符串中的内容。
    展示一段关于“字符串”型的代码:
#include <stdio.h>
 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;
 }

运行结果如下:
在这里插入图片描述
这里str1和str2不同,而str3和str4相同正好说明了上面第一条结论。

二、数组指针变量

(一)、基本概念与初始化

  • 前面的博客中我有提到指针数组,它是一种数组,而数组中存放的是地址(指针)。而今讲的数组指针是一种指针。
  • 前面我们熟悉了整型指针变量(intpint)存放的是整型变量的地址,能够指向整型数据的指针;浮点型指针变量(floatpf)存放的是浮点型变量的地址,能够指向浮点型数据的指针,而今天我们所讲的数组指针变量自然而然地就是存放数组的地址,能够指向数组的指针变量。
  • 数组指针变量定义的基本形式:
int (*p)[10];

对于变量p。它先和*结合,代表是一种指针变量,而后指针指向的是一个大小为10个的整型数组,所以p是一个指针,它指向一个数组,叫做数组指针变量。
注意[]的优先级是要高于 * 的所以我们必须用()来保证p先和 * 结合起来。

  • 数组指针的初始化:
#include<stdio.h>
int arr[10]={0};
int(*p)[10]=&arr;

在这里我们通过调试得知
在这里插入图片描述
p指针变量与&arr的类型是完全一致的,这就完成了初始化的过程
这里我们对数组指针类型再来解析理解一下:
在这里插入图片描述
初始化的过程说白了就是给数组指针变量赋一个数组的地址。

(二)、二维数组传参的本质

  • 二维数组传参,形参可以写成数组,也可以写成指针形式(数组指针)
  • 在我接触数组指针之前,我在写一个函数涉及到二维数组传参的时候经常以二维数组的形式接收,例如以下代码
 #include <stdio.h>
 void test(int  a[3][5], int r, int c)
 {
 int i = 0;
 int j = 0;
 for(i=0; i<r; i++)
 {
 for(j=0; j<c; j++)
 {
 printf("%d ", a[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}};
 test(arr, 3, 5);
 return 0;
 }

这是一个打印二维数组的简单代码,我们通过一个打印函数test来实现,这里二维数组arr作为实参,我们用a[3][5]二维数组形参来接受,(注意这里a的列数必须要写出,行数可以省略)

  • 但是在学习数组指针之后,我们可以另辟蹊径,采用数组指针的形式来接受。
    首先我们先来深入理解一下二维数组,二维数组可以看作是每一个元素是一维数组的数组,是多个一维数组组合而成,在地址存储上是连续不断滴,也就是二维数组的每一个元素就是一维数组。那么二维数组的首元素就是第一行,即是一个一维的数组;
    我们以前提到过数组名在处理&操作和sizeof的情况下代表的是数组首元素的地址,故而二维数组的数组名表示的就是第一行的地址,即一维数组的地址,那我们应该用数组指针来接收。
    以下面这个数组arr为例子
    在这里插入图片描述
    第一行的一维数组的类型就是int[5],所以第一行的地址类型就是数组指针类型int(*)[5].那就意味着二维数组传参本质也是传递着地址,传递的是第一行这个一维数组的地址。
    我们用数组指针来实现打印二维数组:
 #include <stdio.h>
 void test(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("%d ", *(*(p+i)+j));//先对p进行解引用找到一维数组的,然后再进行解引用找到一维数组中的值。
 }
 printf("\n");
 }
 }
int main()
 {
 int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6},{3,4,5,6,7}};
 test(arr, 3, 5);
 return 0;
 }

故而二维数组传参,形参的部分可以写成数组,也可以写成指针形式。

三、函数指针变量

(一)、基本概念与变量的创建

  • 通过前面学习其他类型的指针,通过类比我们不难得出函数指针就是存放函数的地址的,之后我们通过地址解引用就能调用函数。(也可以不用解引用直接通过地址调用函数)
  • 函数也是有地址的,函数名就是函数的地址,当然&函数名的方法也可以获得函数的地址,这两种方法是等价的,这与数组的地址获取有着较大的区别。
  • 函数指针的定义:
int (*pf3) (int x,int y);

在这里插入图片描述
int 代表pf3指针指向的函数的返回类型,pf3前面+* 代表的是指针变量,后面(int x,int y)代表的是pf3指向函数的参数类型和个数交代。

(二)、函数指针变量的使用

  • 主要就是通过函数指针调用指针指向的函数。
#include<stdio.h>
int Add(int x, int y)
 {
 return x+y;
 }
int main()
 {
 int(*pf3)(int, int) = Add;//定义函数指针pf3指向函数Add.
 }
 printf("%d\n", (*pf3)(2, 3));//我们通过函数指针调用函数
 printf("%d\n", pf3(3, 5));//这里是没有解引用情况下,我们通过函数指针调用函数
 return 0;

结果如下:
在这里插入图片描述
以上不管有没有解引用函数指针,我们都可以成功的调用相关函数,说明我们可以通过函数地址来调用相应函数,指针前面加多少解引用操作符都没有影响。这也是函数指针的一个特点。*******pf3等价于pf3.

  • 分析两段非常有趣的代码:
( * (void (*) () ) 0  )();

在这里插入图片描述
这段代码的意思是一次函数调用,调用0地址处存放的那个函数

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

在这里插入图片描述
这段代码是函数的声明代码由返回类型 函数名 函数参数组成。

(三)、typedef关键字

  • typedef 是用来类型重命名的,可以将复杂的类型,书写简单化
  • 例如:unsigned int 写起来太长不太方便,这是我们可以用typdef关键字来操作
typedef unsigned int uint;

我们在写unsigned int 时侯直接写成uint就可以
如果是指针类型

typedef int* ptr_t;

将int* 重命名成ptr_t即可;
但是对于指针类型有两类特殊的类型:数组指针和函数指针类型
对于数组指针类型:我们重命名,新的类型名必须在 * 的右边

typedef int(*parr_t)[5];

我们将int(*)[5]数组指针类型重命名成parr_t.
对于函数指针类型:我们重命名的时候,新的类型名也必须在 * 的右边

typedef void(*pfun_t)(int);

我们将void()(int )函数指针类型重命名成pf_t。
这样对于上面我们分析的有趣的代码第二条:
void (signal(int , void()(int)) )(int);
就可以简化:
我们将void(
)(int)利用typedef 重命名成pfun_t

typedef void(*pfun_t)(int );

简化后的代码为:

pfun_t signal(int ,pfun_t);

四、函数指针数组

(一)、基本概念

  • 前面已经学过指针数组,即数组中存放的是地址(指针),同理,函数指针数组,那么数组中存放的就是函数的地址。
  • 函数指针的定义:
int (*parr1[3])();

parr1先与[]结合,先证明parr1是一个数组,而数组的内容就是int(*)()函数指针类型
故而组合而成如上的形式。

(二)、函数指针数组的应用——转移表

我们利用函数指针来实现一个计算器

#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 meau()
{
	printf("*******1.Add 2.Sub******************\n");
	printf("*******3.Mul 4.Div******************\n");
	printf("*******0.退出游戏 ******************\n");
}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	int(*parr[5])(int, int) = {0,Add,Sub,Mul,Div};//定义函数指针数组,方便我们修改
	do
	{
		meau();
		printf("请输入选择的计算类型:\n");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入要计算的两个数据x y:\n");
			scanf("%d %d", &x, &y);
			ret =parr[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算\n");
		}
		else
		{
			printf("输入错误,请重新输入input\n"),;
		}
	} while (input);

}
int main()

{
	int arr[] = {1,2,3,4,5,1,2,3,4,6};
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		int change = 0;
		for (int j = 0; j < sz; j++)
		{
			if (j == i)
			{
				continue;
			}
			if (arr[j] == arr[i])
			{
				change = 1;
			}
		}
		if (change == 0)
		{
			printf("%d ", arr[i]);
		}
	}
}

我们通过函数指针数组更加方便地对加减乘除进行操作,减小了代码地行数。

总结

本文主要介绍几种重要的指针变量类型——字符指针变量、数组指针变量、函数指针变量,以及一种特殊的数组——函数指针数组。如有错误,请批评指正,制作不易,请多多支持

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值