C语言指针详解(二)

目录

二级指针

字符指针

字符指针使用方法一

 字符指针使用方法二

一道面试题

指针数组

 指针数组的使用方法

数组指针

&数组名 VS 数组名

数组指针的使用方法一

数组指针的使用方法二

函数指针

两段有趣的代码

函数指针数组

函数指针数组的应用—计算器的实现

回调函数

回调函数的应用

void*指针详解


二级指针

指针变量也是变量,是变量就有地址,指针变量的地址存放于二级指针;

int main()
{
  int a=10;
  //a的地址存放于pa中
  int*pa=&a;
  //pa的地址存放于pa中
  int**ppa=&pa;
  return 0;
}

此处,pa是一级指针,ppa是二级指针,代码所对应的内存布局如图

C语言指针详解(二)_第1张图片

通过ppa处存放的pa的地址,即可对ppa进行解引用操作访问pa,即*ppa=pa,然后对pa进行解引用操作即可访问a,即*pa=a,因此 **pa=a;

字符指针

字符指针指针类型为 char*,字符指针如何应用?

字符指针使用方法一

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

 字符指针使用方法二

int main()
{
	const char*p = "abcdef";
    printf("%p\n", p);
    return 0;
}

''abcdef''为常量字符串,常量字符串不允许被修改,存放于内存的可读区(常量区),用const修饰;

const char*p = "abcdef";

容易误解字符串''abcdef''存放于字符指针p里面,但是指针变量p的大小是4个字节 ,字符串里面有7个字符,每个字符需要用1个字节存放,需要7个字节的空间存放
那么指针变量p里面存放的是什么?
p里面存放的是常量字符串首元素的地址

一道面试题

int main()
{
	//数组名代表首元素的地址
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	//字符指针存放常量字符串首元素的地址
	const char* p1 = "abcdef";
	const char* p2 = "abcdef";
	if (arr1 == arr2)
	{
		printf("arr1=arr2\n");
	}
	else
		printf("arr1!=arr2\n");
	if (p1 == p2)
	{
		printf("p1=p2\n");
	}
	else
		printf("p1!=p2\n");
	return 0;
}

输出结果:

C语言指针详解(二)_第2张图片

当创建字符数组接收字符串时,数组会在内存的栈区开辟两块独立的内存空间,分别存放字符串"abcdef",而数组名代表数组首元素的地址,由于是俩块独立的内存空间,所以输出arr1!=arr2,对应的内存布局图如下

C语言指针详解(二)_第3张图片

 由于常量字符串内容相同,而且不允许被修改,故字符串在常量区只保存一份,因为p里面 存放的是常量字符串首元素的地址,所以 p1=p2;对应内存布局如下图

C语言指针详解(二)_第4张图片

指针数组

指针数组
 整型数组,整型数组用来存放整型
 指针数组,指针数组用来存放指针

int main()
{
   int a=10;
   int b=20;
   int c=30;
   int* arr[3]={&a,&b,&c};
   return 0;
}

arr为指针数组,数组里面有3个元素,每个元素的类型为int*,  上述代码所对应的内存布局如下

C语言指针详解(二)_第5张图片

 指针数组的使用方法

使用指针数组模拟实现二维数组,指针数组里面具体元素所对应内存中的位置是不确定的

而二维数组是在内存中连续存放的;

int main()
{
	int arr1[] = { 1, 2, 3, 4, 5 };
	int arr2[] = { 2, 3, 4, 5, 6 };
	int arr3[] = { 3, 4, 5, 6, 7 };
	int*parr[3] = { arr1, arr2, arr3 };
	//遍历数组的每个元素
	int sz = sizeof(parr) / sizeof(parr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		int j = 0;
		int k = sizeof(arr1) / sizeof(arr1[0]);
		for (j = 0; j < k; j++)
		{
			//printf("%d ", *(parr[i] + j));
            //*(parr[i]+j)=parr[i][j]
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

数组指针

类比整形指针, 整型指针是能够指向整型数据的指针,  那么数组指针就是能够指向数组的指针

eg:  int (*p) [10]      p是什么?

根据操作符的优先级和结合性可知,p先和*结合,说明p是一个指针变量,然后指向一个数组,数组里面有10个元素,每个元素的类型为int; 所以p为数组指针

&数组名 VS 数组名

数组名是数组首元素的地址,但是有俩个例外
1. sizeof(数组名)计算的是整个数组的大小,而不是地址的大小;
2. &数组名取出的是整个数组的地址;

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

	printf("%p\n", arr);
	printf("%p\n", &arr);

	printf("%p\n", arr + 1);//跳过4个字节
	printf("%p\n", &arr + 1);//跳过40个字节
	return 0;
}

C语言指针详解(二)_第6张图片

 从上述代码可知,arr为数组名,数组名为首元素的地址,首元素的类型为int,所以arr类型为int*,那么&arr的类型是什么?
数组指针
数组指针 指向数组的指针
数组指针的类型是什么 type(*)[size]  size为指向数组的大小,type为数组元素的类型

数组指针的使用方法一

void print1(int(*p)[5], int sz)
{
	//p是整个数组的地址
	//*p相当于数组名,数组名又是首元素的地址;
	//*p=&arr[0]
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(*p + i));
	}

}
int main()
{
	int arr[5] = { 1, 2, 3, 4, 5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print1(&arr, sz);
	return 0;
}

数组指针的使用方法二

数组指针经常用于二维数组

int  arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};

二维数组是一维数组的数组,二维数组的数组名是什么?

二维数组的数组名表示第一行的地址,二维数组的每个元素是一维数组,那么二维数组的每一行的数组名又是什么?

eg: 第一行的数组名

二维数组第一行的每个元素如下:

int arr[0][0]       int arr[0][1]        int arr[0][2]       int arr[0][3]       int arr[0][4]

去掉int [j]  j=0,1,2,3,4;剩下arr[0],那么第一行的数组名是arr[0];

void print(int(*p)[5],int row,int col)
{

	int i = 0;
	for (i = 0; i < row; i++)
	{
		int j = 0;
		for (j = 0; j < col; 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 } };
	print(arr,3,5);//二维数组的数组名
	return 0;
}

arr为二维数组的数组名,代表第一行的地址,所以传参时用数组指针接收,p就是数组指针,p+i表示指向第i 行,*(p+i)相当于拿到第i行的数组名,数组名又表示首元素的地址

,所以*(p+i)就是第i行第一个元素的地址;

arr[i]=*(arr+i)

arr[i][j] = *(arr+i)[j] = *(*(arr+i)+j)

所以*(*(p+i)+j)= p[i][j]

函数指针

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p\n", Add);
	printf("%p\n", &Add);
	return 0;
}

输出结果:

C语言指针详解(二)_第7张图片

 结论:函数名与&函数名本质相同,都是函数的地址

函数指针

类比数组指针,数组指针能够指向数组的指针, 那么函数指针就是能够指向函数的指针;

函数指针变量的类型是什么?

int (*pf) (int, int)   *先和pf结合,说明pf为指针变量,指针指向了一个函数,函数有俩个参数,俩个参数的类型都是int ,函数的返回类型为int

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//函数名即为函数的地址,而且&函数名=函数名;
	//int(*pf)(int, int) = &Add;
	int(*pf)(int, int) = Add;
	//int ret = (*pf)(2, 3);
	int ret = pf(2, 3);//pf=Add
	printf("%d\n", ret);
	return 0;
}

因为pf为函数指针变量,pf=&Add,对函数指针变量pf进行解引用操作*pf=Add

然后调用函数Add(2,3)=(*pf)(2,3);

又因为 pf=Add ,所以可直接调用函数pf(2,3)=Add(2,3)

因此 Add(2,3) = (*pf)(2,3) = pf(2,3)

两段有趣的代码

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

代码解读:

void(*)()是一个函数指针类型,(void(*)())0表示对0进行了强制类型转换,将0强转为函数指针,意味着0地址处存放一个参数为空,返回类型为void的函数;(*  ( void(*)() ) 0 )()调用0地址处的函数;

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

代码解读:

signal(int,void(*)(int)),signal是一个函数,函数的第一个参数类型是int,函数的第二个参数类型是函数指针,函数指针类型为void (*)(int),去掉signal(int,void(*)(int)),剩下的便是函数的返回类型为void(*)(int);可以这么理解void(*)(int) signal(int, void(*)(int)),但是不可如此书写代码,上述代码太过复杂,如何简化?

typedef   void(* pf_t) ()给函数指针类型 void(*)(int)重命名为pf_t

代码简化如下:

pf_t signal (int , pf_t)

函数指针数组

首先数组是一组相同元素的集合;
类比指针数组,指针数组存放指针的数组;
那么函数指针数组存放函数指针的数组;
要求函数指针的参数,返回类型必须相同;

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

int main()
{
    //函数指针数组,数组每一个元素的类型为函数指针
	//函数指针的类型为 int(*p)(int,int)
	int(*parr[4])(int, int) = { Add, Sub, Mul, Div };
	//调用函数指针数组的每一个函数,来实现对应的操作;
	int sz = sizeof(parr) / sizeof(parr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		int ret = parr[i](2, 3);
		printf("%d\n", ret);
	}
	return 0;
}

函数指针数组的应用—计算器的实现

版本1—未采用函数指针数组

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 menu()
{
	printf("********************\n");
	printf("****1.Add 2.Sub*****\n");
	printf("****3.Mul 4.Div*****\n");
	printf("****  0.exit   *****\n");
	printf("********************\n");
}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	do
	{
		menu();
		printf("请选择:>\n");
		scanf("%d", &input);
		int ret = 0;
		switch (input)
		{
		case 1:
			printf("请输入两个操作数\n");
			scanf("%d%d", &x, &y);
			ret=Add(x,y);
			printf("ret=%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数\n");
			scanf("%d%d", &x, &y);
			ret=Sub(x,y);
			printf("ret=%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数\n");
			scanf("%d%d", &x, &y);
			ret=Mul(x,y);
			printf("ret=%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数\n");
			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;
}

当我们采用switch-case语句实现计算器,case语句里面出现很多冗余代码,代码执行的效率不高,当我们采用函数指针数组,代码执行效率高,而且简洁明了

版本2—采用函数指针数组

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 menu()
{
	printf("********************\n");
	printf("****1.Add 2.Sub*****\n");
	printf("****3.Mul 4.Div*****\n");
	printf("****  0.exit   *****\n");
	printf("********************\n");
}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	do
	{
		menu();
		printf("请选择:>\n");
		scanf("%d", &input);
		int ret = 0;
		int(*parr[5])(int, int) = { 0, Add, Sub, Mul, Div };
		//为使数组下标完美匹配菜单选项; 数组里存放五个元素;
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数\n");
			scanf("%d%d", &x, &y);
			ret = parr[input](x, y);
			printf("ret=%d\n", ret);
		}
		else if (input==0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("选择错误,请重新输入!\n");
		}
	} while (input);
	return 0;
}

回调函数

回调函数就是一个通过函数指针调用的函数。当我们将函数的指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们称之为回调函数;回调函数不是由函数的实现方直接调用,而是在特定的条件发生时由另一方调用的,用于对该条件进行响应;

回调函数定义的通俗理解:

写一个A函数,并没有直接调用A函数,将A函数的地址传给了B函数,B函数的参数里得有一个函数指针接收A函数的地址;在B函数通过函数指针调用A函数,这个A函数叫做回调函数

//A函数
void test()
{
	printf("hehe\n");
}

//B函数
void print(void(*p)())
{
	if (1)//特定的条件发生
	{
		p();
	}
}
int main()
{
	print(test);
	return 0;
}

回调函数的应用

计算器的实现中,case语句里面代码冗余过多,将差异处封装成函数,采用回调函数处理冗余代码

//将冗余代码封装成一个函数,差异体现在调用的函数有所区别
//将差异处作为函数参数处理
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 menu()
{
	printf("********************\n");
	printf("****1.Add 2.Sub*****\n");
	printf("****3.Mul 4.Div*****\n");
	printf("****  0.exit   *****\n");
	printf("********************\n");
}
//通过函数指针回调Add,Sub,Mul,Div函数;
void calc(int(*p)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入两个操作数\n");
	scanf("%d%d", &x, &y);
	int ret=p(x, y);
	printf("%d\n", ret);
}
int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>\n");
		scanf("%d", &input);
		int ret = 0;
		switch (input)
		{
		case 1:
			calc(Add);
			break;
		case 2:
			calc(Sub);
			break;
		case 3:
			calc(Mul);
			break;
		case 4:
			calc(Div);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误,请重新输入!\n");
			break;
		}
		
	}
	while (input);
	return 0;
}

void*指针详解

void* — 无确切类型的指针,可以接收任意指针类型的数据;

int main()
{
	int a = 10;
	char* p = &a;//出现警告,从int*到char*类型不兼容;
	return 0;
}
int main()
{
	int a = 10;
	void *p = &a;//警告消失,说明void*可以接收任意类型的数据
	return 0;
}

void*为无确切类型的指针,不能对void*进行解引用操作,因为没有确切类型,无法得知可以访问几个字节;
同理 不能对void*类型的指针变量进行+-整数的操作

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