C语言——指针的进阶——第1篇——(第26篇)

本文详细讲解了字符指针、指针数组、数组指针的概念,涉及数组传参和指针传参的区别,以及一级指针和二级指针的使用。文章通过实例分析了函数指针的用法,并探讨了如何正确处理数组指针和函数参数的类型重命名。
摘要由CSDN通过智能技术生成

坚持就是胜利


初级阶段:
1、指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2、指针的大小是固定的 4/8 个字节(32位平台 / 64位平台)。
3、指针是有类型,指针的类型决定了指针的 + - 整数的步长,指针解引用操作的时候的权限。
4、指针的运算。

一、字符指针

在指针的类型中,我们知道有一种指针类型为字符类型 char*
一般使用:

#include <stdio.h>

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

还有一种使用方式如下:

#include <stdio.h>

int main()
{
	char arr[] = "abcde";
	//[a b c d e \0]

	const char* p = "abcde";   //本质是:指针变量p 指向 首字符 a 的地址
	                     //并且 "abcde" 是 "常量字符串",得加上 const

	printf("%s\n", p);   //结果:abcde  

	printf("%c\n", *p);  //结果:a

	return 0;
}
代码:const char* p = "abcde";

特别容易以为是 把字符串 “abcde” 放到字符指针 p 中,
但是本质是把 字符串 abcde 首字符 的地址放到了 p 中。

在这里插入图片描述

1、面试题

#include <stdio.h>

int main()
{
	char str1[] = "abcde";
	char str2[] = "abcde";
	const char* str3 = "abcde";
	const char* str4 = "abcde";

	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++ 会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,它们实际会指向同一块内存。
但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以 str1 和 str2 不同,str3 和 str4 不同。

二、指针数组

指针数组 是一个 存放指针 的数组。

int* arr1[10];  //整型指针的数组
char* arr2[4];  //一级字符指针的数组
char** arr3[5];  //二级字符指针的数组
//使用 指针数组 模拟实现 二维数组

#include <stdio.h>

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	
	int* arr[3] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

在这里插入图片描述
在这里插入图片描述

三、数组指针

1、数组指针的定义

类比:
整型指针——指向整型变量的指针,存放整型变量的地址的指针变量。
字符指针——指向字符变量的指针,存放字符变量的地址的指针变量。

数组指针——指向数组的指针,存放数组的地址的指针变量。

在这里插入图片描述

2、&数组名 VS 数组名

int arr[10];

arr 和 &arr 分别是什么?
arr 是 数组名,数组名 表示 数组首元素 的地址。

那 &arr 数组名 到底是什么?

#include <stdio.h>

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 表示的是:数组的地址,而不是数组首元素的地址。
&arr 的类型是:int(*)[10],是一种数组指针类型
数组的地址 + 1,跳过整个数组的大小,所以,&arr +1 相对于 &arr 的差值是40。

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);   //类型:int *
	printf("%p\n", &arr[0]);  //类型:int *
	printf("%p\n", &arr);   //类型:int(*)[10]   数组指针类型

	return 0;
}

在这里插入图片描述

电脑中 int[10] * ,这么写是错误的,是为了方便你理解,其实应该写成 int( * )[10] , 数组指针类型

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

#include <stdio.h>

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

在这里插入图片描述

总结:(数组名的理解)
数组名是数组首元素的地址。

有2个例外:
(1)sizeof(数组名),这里的数组名不是数组首元素的地址,数组名表示整个数组,sizeof(数组名) 计算的是 整个数组的大小,单位是字节。
(2)&数组名,这里的数组名表示整个数组,&数组名 取出的是 整个数组的地址。
(3)除此之外,所有的地方的数组名都是数组首元素的地址。

3、数组指针的使用

#include <stdio.h>

int main()
{
	char  arr[6] = "abcde";
	char(*p)[6] = &arr;
	printf("%c \n", *(*p));  //p 中放的是 &arr , *p 就是 arr(数组名), *(*p) 就是 *(arr),就是首字母,就是对首字母地址解引用
	printf("%s", p);
	return 0;
}

在这里插入图片描述

#include <stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	putchar('\n');
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(arr + i));
	}
	return 0;
}

用指针数组,来遍历输出数组的每个元素,感觉使用起来很变扭!

#include <stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;  //*&arr 那么*&就互相抵消,*&arr 就是 arr(数组名)
	int i = 0;
	for (i = 0; i < 10; i++)
	{                                //p 中存放的就是 &arr
									 //由于*&arr 就是 arr(数组名)
									 //*p 就是 *(&arr)  就是 arr(数组名)
		printf("%d ", *((*p) + i));  //(*p) 就是 arr(数组名) 
									//(*p)+i  就是 arr + i  
									//*(arr + i) 就是 遍历数组的每一个元素
		putchar('\n');

		printf("%d ", (*p)[i]);    //arr[i]
		                           //效果一样

	}
	return 0;
}

在这里插入图片描述
所以,数组指针 不是像 上面的代码那样使用,这样反而弄巧成拙了。

问:数组指针怎么使用呢?
答:一般在 二维数组 上才方便。

(1)二维数组传参,形参是 二维数组 的形式

//二维数组传参,形参是二维数组的形式

#include <stdio.h>

void Print(int arr[3][5], int r, int c)
{
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; 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;
}

(2)二维数组传参,形参是 指针 的形式

//二维数组传参,形参是指针的形式

#include <stdio.h>

void Print(int(*p)[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 ", *(*(p + 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 };
	
	//arr 是数组名,数组名表示数组首元素的地址

	Print(arr, 3, 5);

	return 0;
}

在这里插入图片描述
在这里插入图片描述

(3)总结

一维数组传参,形参的部分可以是数组(本质还是指针),也可以是指针

//一维数组传参,形参的部分可以是数组(本质还是指针),也可以是指针

void test1(int arr[5], int sz)    //int arr[5] 本质还是指针,只是写成了数组的形式
{}

void test2(int* p, int sz)
{}

int main()
{
	int arr[5] = { 0 };
	test1(arr, 5);
	test2(arr, 5);

	return 0;
}

二维数组传参,形参的部分可以是数组,也可以是指针

//二维数组传参,形参的部分可以是数组,也可以是指针

void test3(char arr[3][5], int r, int c)
{}

void test4(char(*p)[5], int r, int c)
{}

int main()
{
	char arr[3][5] = { 0 };
	test3(arr, 3, 5);
	test4(arr, 3, 5);

	return 0;
}

学了指针数组和数组指针,我们来一起回顾并看看下面的代码的意思:
在这里插入图片描述

四、数组传参和指针传参

在写代码的时候,难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计?

1、一维数组传参

在这里插入图片描述

#include <stdio.h>

void test(int arr[])  //ok?  答:可行
{}                    

void test(int arr[10])  //ok?  答:可行
{}                      //[10]中的10,可以写成100、1000都行,反正 int arr[10] 本质就是指针

void test(int *arr)  //ok?  答:可行
{}

void test2(int *arr[20])  //ok?  答:可行
{}                        //原因:形参实参类型保持一致
 
void test2(int** arr)  //ok?  答:可行
{}                     //int *arr2[20] 就是数组20个元素,每个元素类型都是 int * 
                       //一级指针的地址,就是 二级指针
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
	return 0;
}

2、二维数组传参

在这里插入图片描述

void test(int arr[3][5])  //ok?  答:可行
{}

void test(int arr[][])  //ok?  答:不可行
{}                      //不可以省略行

void test(int arr[][5])  //ok?  答:可行
{}

//总结:二维数组传参,函数形参的设计只能省略第一个 [ ] 的数字
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素
//这样才方便运算

void test(int* arr)  //ok?  答:不可行
{}                   //arr 是数组指针的地址,arr 是数组第一行的地址 arr == &arr[0]

void test(int* arr[5])  //ok?  答:不可行
{}                      //int* arr[5] 是指针数组,有5个元素,每个元素的类型是 int*

void test(int(*arr)[5])  //ok?  答:可行
{}                       //int (*arr)[5]数组指针,元素类型是 int[5],是数组某一行的指针

void test(int **arr)  //ok?  答:不可行
{}                    //arr的类型是 int*,明显错误
                      //二级指针 是 用来接收一级指针的地址的
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

3、一级指针传参

#include <stdio.h>

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

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}

当一个函数的参数部分为一级指针的时候,函数能接受什么参数?

void test(char* p)
{}

char ch = '2';
char* ptr = &ch;
char arr[] = "abcde";

test(&ch);  //第一种
test(ptr);  //第二种
test(arr);  //第三种

4、二级指针传参

区别:
二维数组传参,形参既可以是数组,也可以是指针。
二级指针传参,形参只可以是指针。

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

当函数的参数为二级指针的时候,可以接受什么参数?

void test(char** p)
{}

int main()
{
	char c = 'b';
	char* pc = &c;
	char** ppc = *pc;
	char* arr[10];   //指针数组,类型是 char*
	           
	            //二级指针,是一级指针的地址
	test(&pc);
	test(ppc);
	test(arr);  //是数组元素类型 char* 的地址

	return 0;
}

五、函数指针

函数指针——指向函数的指针
在这里插入图片描述
函数名 是 函数的地址
&函数名 也是 函数的地址
在这里插入图片描述

int (*pf)(int , int) 去掉 指针名 pf ,剩下的就是指针类型 int ( * )(int , int)

在这里插入图片描述

1、举例理解:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

2、分析两段有趣的代码

(1)代码1

//代码1
( *( void (*)() )0 )();

1、将 0 强制转换成 void (*)() 类型的函数指针
2、调用 0 地址处的这个函数

在这里插入图片描述

//0x0012ff40 可以理解为:int
//           也可以理解为:int*

//同理:

//0          可以理解为:int
//           也可以理解为:int*

//void (*p)()  -- p是函数指针
//void (*)() 是函数指针类型

//从0开始切入分析
//0 被强制类型转换为 void (*)() 类型的函数指针
// 0 此时变成了函数指针,也就是说 0 变成 地址了
//* 解引用  ()代表 函数
// 函数指针就是指针,指针就是地址
//调用 0 地址 处的这个函数

//代码1
( *( void (*)() )0 )();

我们自己写的应用程序是不可以访问 0 地址的。
举例的是 系统程序。

(2)代码2

//代码2
void (*signal(int , void(*)(int)))(int);

在这里插入图片描述

//signal 是一个函数声明
//signal 函数有 2 个参数
//第 1 个参数的类型是 int
//第 2 个参数的类型是 void(*)(int) 函数指针类型,该函数指针指向的函数有一个 int 类型的参数,返回类型是 void 
//signal 函数的返回类型也是 void(*)(int) 函数指针类型,该函数指针指向的函数有一个 int 类型的参数,返回类型是 void 


//代码2
void (*signal(int , void(*)(int)))(int);
学习 typedef 类型重命名
typedef unsigned int uint;  //将 unsigned int 重命名为 uint  //正确
typedef int* ptr_r;  //将 int* 重命名为 ptr_r  //正确

数组指针,这种书写格式是错误的

//对 数组指针 进行 类型重命名

typedef int(*)[10] parr_t;  //这么写就是错误的,不能以这种方式来书写

应该改为这样
在这里插入图片描述

//对 数组指针 进行 类型重命名

//错误的书写格式
//typedef int(*)[10] parr_t;  //这么写就是错误的,不能以这种方式来书写

//正确的书写格式
typedef int(*parr_t)[10];

函数指针类型 跟上面的书写格式一致

代码2太复杂,简化为如下形式

原代码:

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

简化后:

typedef void(*pfun_t)(int);  //将 void (*)(int)  重命名为 pfun_t
pfun_t signal(int, pfun_t);

微软雅黑字体
黑体
3号字
4号字
红色
绿色
蓝色

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值