DAY11 指针(3)----不同指针变量

1.字符指针变量及其他简单类型指针变量

字符的类型是char

字符指针的类型是char*

表达方式如下:

int main()
{
 char ch = 'w';
 char *pc = &ch;
 *pc = 'w';
 return 0;
}

这是最简单也是最普遍的表达方式。

但是还有另外一种:

char* pstr = "hello bit.";

在这行代码中,貌似将hello bit.这个字符串放进了字符指针变量pstr中,但事实真的如此吗?

实际上,它的本质是把这个字符串的首字符h的地址放进了pstr中 

我们会发现这个概念貌似似曾相识,没错,它跟数组指针的存放方式相差无几。

可以把字符串想象成一个字符数组,因为数组就是地址,取字符串的地址也就可以相当于取数组

当常量字符串出现在表达式中时候,它的值也就是第一个字符的地址。

但是需要注意的是:内容相同的常量字符串只会保存一份地址:

const char *str3 = "hello bit.";
const char *str4 = "hello bit.";

这两个不同的指针变量实际上指向的是同一个常量字符串hello bit.

所以这里的str3==str4。

事实上,

各种简单类型例如整型int,字符型char等的指针变量事实上意义和用法都大同小异:

整型指针变量:用来存放整型变量的地址,

int a = 100;
int *p_a = &a;

字符指针变量:用来存放字符型变量的地址, 

char b="hello world.";
cahr *p_b = &b;

浮点指针变量:用来存放浮点型变量的地址,

float c = 3,14;
float *p_c = &c;

2.数组指针变量

指针数组数组元素是指针的数组

那么数组指针是什么呢?

int *p1[10];
int (*p2)[10];

上方的p1和p2分别代表什么呢?

对它们进行分析:

我们注意到p1的类型实际上是int*,而后方的[10]告诉我们数组的本质,所以可以得出p1是指针数组;

我们注意到p2的类型实际上是int,而括号将*p2同时扩了起来,这说明p2实际上是一个指针变量,接着后面的[10]告诉我们它指向的是一个大小为10个整数的数组,所以p2是一个指针,指向一个数组,这个指针也就称为数组指针

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

那么,

数组指针变量也就应该是:存放的应该是数组的地址,能够指向数组的指针变量。

数组指针变量的初始化

数值指针变量用来存放地址,如果我们需要获取数组的地址也就需要用到:

&数组名

int arr[10] = {0};
&arr;//得到的就是数组的地址

我们存放数组的地址也就存放在指针数组变量中:

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

DAY11 指针(3)----不同指针变量_第1张图片

二维数组的传参本质

在未学习数组指针变量之前,二维数组传参我们使用的是数组

但是在学习了数组指针变量之后,我们应该了解其实写成指针也是可以的。

我们知道:二维数组的元素都是一维数组,那么它的首元素就是第一行数组的地址
所以二维数组的数组名的表示就是第一行一维数组的地址。

那么第一行地址的类型也就是数组指针类型。

也就意味着二维数组传参本质上也就是传递了地址,传递也就是这个一维数组的地址

//void Print(int (*arr)[5], int r, int c)
//{
//	int i = 0;
//	for (i = 0; i < r; i++)//行
//	{
//		int j = 0;
//		for (j = 0; j < c; j++)
//		{
//			printf("%d ", *(*(arr+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);//打印arr数组的内容
//
//	return 0;
//}

所以,二维数组传参,形参部分既可以理解为数组也可以理解为是指针。

指针数组与数组指针

在此再次强调这二者的区别,

指针数组是元素是指针的一类数组,它的本质是数组。

数组指针是指向某个数组的指针,它的本质是指针。

3.函数指针变量

数组指针变量是用来存放数组的地址;

函数指针变量用来存放函数的地址

那么函数的地址是什么?

DAY11 指针(3)----不同指针变量_第2张图片

可以看到add与&add的地址是同一个,这也就说明函数的地址就是函数名的地址

所以如果要存放函数的地址,也就可以创建函数指针变量。

函数指针变量表达式

int (*p)(int x, int  y);//(*p)的括号不能省略,如果省略了那么类型就会由int变为int*

需要注意的是,这里的两个参数名通常是可以去掉的,因为它们实际上作用不大,我们使用函数指针变量的主要目的是将这个函数存放起来。

int (*p)(int,int);

函数指针类型

而对应的函数指针类型:

int (*) (int a, int b); 

函数指针变量各个部分的意义

DAY11 指针(3)----不同指针变量_第3张图片

我们看到,int是pf3指向函数的返回类型,也就是说这里是指原函数的类型;

char* test(int a, char c)
{
	//...
	return NULL;
}


int main()
{
	char* (*pt)(int, char) = test;

	return 0;
}

在上述代码中,通常在主函数中调用这个函数时我们会忽略这个函数的类型其实是char*,所以我们在书写函数指针变量的时候前面也应该是char*而不是char

DAY11 指针(3)----不同指针变量_第4张图片

注意:我们在书写函数指针变量的时候,其实是无需用*来解引用的

因为函数的地址就是函数名的地址,无需再使用解引用操作来调出函数的地址,它本身就已经得知了。

int (*pf)(int,char)=&Add;//这样可行
int (pf)(int,char)=&Add;//这样也可行

函数指针的应用

在了解函数指针变量的基本知识之后,我们就可以试着使用它。

我们知道,指针的重要作用之一就是通过&与*来访问该指针的源头从而返回值

那么我们也可以使用函数指针来实现函数的调用。

int Add(int x, int y)
{
 return x+y;
}
int main()
{
 int(*pf3)(int, int) = Add;
 
 printf("%d\n", (*pf3)(2, 3));
 printf("%d\n", pf3(3, 5));
 return 0;
}//结果也就是5和8(2+3;3+5)

函数指针的隐藏

函数指针的表达式很冗长,有时候它会隐藏于某行代码之中很难辨识。反过来看如果我们不认识函数指针,可能碰到一行复杂难懂的代码我们更会无从下手。

举两个例子:

A ep1

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

DAY11 指针(3)----不同指针变量_第5张图片

这这行代码的括号刨析一下:

我们知道,像(int)3.14这种形式的书写代表着:强制类型转换。也就是说把原本属于浮点型的3.14强制转换成整型。

而上述代码中的(void(*)()),这是一个函数指针类型的书写格式。

所以也就是说上述类型将原本是整型的0强制转换成函数指针类。也就是转换成了一个函数的地址。这个函数没有参数,返回类型是void。

而在整个括号的初始还有一个*号,它代表着对函数指针的解引用

所以,这实际上是一次函数调用

调用0地址处的函数。

B ep 1

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

同样对每个括号包括的内容进行刨析我们可以得知:

上述代码是函数声明;

signal是一个函数

函数的参数类型一个是int整型,另一个是函数指针类型,返回类型是void;

而在signa之外又是一个函数指针变量的格式,所以也就是最外边是一个函数指针变量,这个函数的返回类型是void,函数参数类型是int。

总结:这些看似很复杂的代码,如果我们不了解函数指针的知识的话,是很难理解的。

那么既然函数指针变量这一类的代码如此冗杂,我们该如何化繁为简呢?

函数指针数组

将函数的地址存放在一个数组中,那么这个元素是函数指针的数组也就被称为函数指针数组

函数指针数组的定义

int (*parr1[10])();

parr1与[]在一起说明它是个数组

在外围我们看出数组的元素是int (*)() 类型的函数指针。

应用

int A(int x, int y)
{
	return x + y;
}
 
int B(int x, int y)
{
	return x - y;
}
 
int C(int x, int y)
{
	return x * y;
}
 
int D(int x, int y)
{
	return x/y;
}
 
int main()
{
	int(*p[4])(int, int) = {A,B,C,D};
	int i = 0;
	for (i = 0; i < 4; i++)
	{
	printf("%d\n",p[i](8, 4));
	}
    return 0;
}

4.typedf关键字在指针中的应用

有一种关键字可以重命名类型,可以将复杂的类型简单化

它就是typedf关键字

简单类型重命名

typedef unsigned int uint;
//将unsigned int 重命名为uint

一级指针类型重命名 

typedef int* ptr_t;//指针类型重命名

数组和函数指针类型重命名

typedef int(*parr_t)[5]; //新的类型名必须在*的右边
typedef void(*pfun_t)(int);//新的类型名必须在*的右边

在使用的过程中,我们就可以使用重命名后的名字来代替原本冗长的名字,同时需要注意在不同的类型中使用时用法会有所不同。

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

你可能感兴趣的:(数据结构,c语言)