深入理解指针(3)

 一 , 字符指针变量

在我们所了解的指针类型中,我们知道有一种指针类型为 字符指针 char* 

int main()
{
	char a = 'a';
	char* pa = &a;
	printf("%c\n", *pa);
	return 0;
}

一般来说,我们是这样去存放字符地址 

//字符指针变量
int main()
{
	//char a = 'w';
	//char* pa = &a;

	//char* pa = "abcdef";
	//这种赋值是把字符串首字符的地址赋值给pa --- 常量字符串
	
	char arr[] = "abcdef";
	char* p = arr;
	//p 存放的数组首元素的空间
	return 0;
}

 我们看代码

char* pa = "abcdef";

这里并不是把字符串放在字符指针pa里,其本质是把字符串 abcdef 的首字符a 的地址放在了pa中。

例题1:

int main()
{
	char* pa = "abcdef";
	*pa = 'w';
	return 0;
}

 如果我们写下了这组代码,程序会崩,运行不了,因为常量字符串是不可以被更改!我们可以借助const 进行修改

int main()
{
	const char* ps  = "abcdef";
	//限制了*ps
	return 0;
}

 例题2:

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

 

 这里的str3 和str4 指向的是同一个常量字符串。C/C++会把常量字符串存储到单独的一个区域,当几个指针指向同一个字符串的时候,他们实际上指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候,就会开辟出不同的内存块。

 

二 , 数组指针变量

2.1 数组指针变量是什么? 

 首先我们得先把数组指针与指针数组区分开:

指针数组: 存放指针(地址)的数组

数组指针: 存放的是数组指针(地址)

我们可以类比一下:

 所以数组变量应该是:存放的是数组的地址,能够指向数组的指针变量 

int main()
{
	//字符指针
	char a = 'a';
	char* pa = &a;

	//整型指针
	int b = 10;
	int* pb = &b;

	//数组指针
	int arr[10] = { 0 };
	int(*pc)[10] = &arr;//数组的地址
	//pc就是数组指针变量

	char ch[5];
	char(*pd)[5] = &ch;

	int* arr2[6];
	int*(*p1)[6] = &arr;

	int arr[10] = { 0 };
	int(*p)[10] = &arr;//p 是数组指针变量
	//int (*) [10]
	return 0;
}

 哪一个是数组指针

int *p1[10];

int (*p2)[10];

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

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

2.2 数组指针变量的初始化

首先,我们要获取数组的地址  :  通过  & (数组名) 

然后,创建一个数组指针变量存放数组的地址 

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

  |             |           |     

  |             |           |

  |             |      p指向数组元素的个数

  |          p是数组指针的变量名

p指向的数组的元素类型 

 

三 ,二维数组传参的本质

 在过去打印二维数组的时候,是这样写的:

void Print(int arr[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 ", 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);
	return 0;
}

然后我们发现,实参是数组名,然后根据:数组名是数组首元素的地址,我们可以联想到,实参是可以用指针变量 形式的。

 随后我们思考,二维数组的首元素是什么?根据二维数组的定义,我们 其实可以把它看成是一维数组的数组,也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是一个一维数组。

所以,根据数组名是数组首元素的地址这个规则,二维数组的数组名,表示的就是第一行的地址 ,传递的是第一行这个一维数组的地址。所以形参可以写成指针形式。

void Print(int(*p)[5], int c, int l)//传递的是第一行一维数组的地址,然后第一行的有5个元素
{
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d ", p[i][j]);
		}
		printf("\n");
	}
}

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

四 ,函数指针变量

 4.1 函数指针变量的创建

前面学习了整型指针,数组指针,,字符指针的时候,我们知晓指针变量是用来存放变量的地址的,方便未来使用。以此类推,我们不难得出结论,函数指针变量应该是用来存放函数地址,未来通过地址来调用函数。

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	printf("%p\n", &Add);
	printf("%p\n", Add);

	return 0;
}

通过这着一串代码,我们知道,函数也是有地址的,而且与数组不同的是,&函数名与函数名的地址相同。

取到地址后,接下来就是创建变量存放地址了

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	printf("%p\n", &Add);
	printf("%p\n", Add);
	int (*pa)(int, int) = Add;
	int (*pa)(int x, int y) = &Add;//x,y或者省略都是可以的
	return 0;
}

 int                  (*pa)            (int x, int ,y)

  |                        |                       |

  |                        |                 pa指向函数的参数类型和个数

  |                 函数变量名

pa指向函数的返回类型

去掉函数指针变量名就是函数指针变量的类型

int ( * )(int x ,int y)

4.2 函数指针变量的使用

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	//函数指针变量的创建
	int (*pa)(int, int) = Add;
	
	//函数指针变量的调用
	printf("%d\n", pa(10, 20));
	printf("%d\n", (*pa)(20, 30));
	return 0;
}

因为我们知道无论是    &函数名,还是  函数名 都可以用来表示函数的地址,所以在调用函数指针变量的时候, * 可写可不写。

4.3 两端有趣的代码

“来自《C陷阱和缺陷》的这本书”

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

 

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

 

4.4 typedef 关键字

typedef  是用来类型重命名的,可以将复杂的类型,简单化 

 1.  unisgned int 写起来不方便的时后 , 可以把它重命名成 XXX

typedef unsigned int unit;
int main()
{
	//unsigned int a = 5;
	unit a = 5;
	//将unsigned 重命名为unit
	return 0;
}

2.  如果是指针的类型,也是可以重命名的

typedef int* ptr_t;
int main()
{
	//int* pa;
	ptr_t pa;
	return 0;
}

 对于数组指针和函数指针的重命名,稍微要写区别,但是我们要记住一点,就是名字要放在 * 的右边!!!

 3. 数组指针变量重命名

typedef   void (*pa) [5];

4. 函数重命名

typedef    void(*pa) ( int )

然后如果想要简化代码二:

typedef  void ( * pa )(int);

pa signal( int ,  pa) 

int* p1, p2;           //p1 是int* , p2 是 int


 prt_t p3, p4        //p3,p4都是int* 

五 , 函数指针数组

存放函数指针的数组。

int Add(int x, int y)
{
	return x + y;
}
int sub(int x, int y)
{
	return x - y;
}

int main()
{
	int (*p1)(int, int) = Add;
	int (*p1)(int, int) = sub;
	//把Add sub 这些函数的地址存放在一个数组中
	//存放函数指针的数组  --- 函数指针数组
	int (*pArr[4])(int, int) = { Add,sub };

	//指针数组
	char* ch[5];
	int* arr[6];
	return 0;
}

函数指针数组在函数指针变量的基础上,加了 [  ] 与数字,去掉变量名,就是函数指针数组的类型。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值