指针(三)

目录

1. 字符指针变量和字符串

2. 数组指针变量

2.1 数组指针变量是什么?

2.2 数组指针变量怎么初始化

2.3 数组指针的解引用

3. ⼆维数组传参的本质

4. 函数指针变量

4.1 函数指针变量的创建

 4.2 函数指针变量的使用

5. 函数指针数组

5.1指向函数指针数组的指针

6.回调函数


1. 字符指针变量和字符串

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

int mian()
{
	char ch = 'w';
	char* ph = &ch;
    *ph = 'w';
	return 0;
}

我们可以把字符的地址存入字符指针变量中去,

除此之外,我们也可以存入一个字符串。

int main()
{
	const char* pstr = "abcdef g";
	printf("%s\n", pstr);

	return 0;
}

那么我们是把整个字符串都存入这个指针变量了吗?(为了防止字符串被修改,我们const修饰指针,使的即使我们获得了指针却不能修改变量)

我们可以思考一下,在32位环境下的指针变量大小是4个字节,“abcdef g”每一个字符加上空格代表1个字节,已经超过了指针变量能存储的最大值

但是 / 本质是把字符串 abcdef g\0  首字符的地址放到了 pstr 中。
可以把一个常量字符串,想象成一个数组,用法也是和数组一模一样
printf("%c", "abcdef"[3]);我们甚至可以这样写代码,打印的结果是d(数组名表示首元素的地址,我们把字符串,想象成数组,那么字符串的名字加[]就可以当成数组来访问元素)
 

字符串被const修饰:

int main()
{
    char str1[] = "hello";
    char str2[] = "hello";
    const char *str3 = "hello";
    const char *str4 = "hello";
    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是两个不同的字符数组,当一个字符串要存入这个字符数组时,此时数组名就是该数组首元素的地址,地址是,存入字符串的首元素的地址,虽然这两个字符串内容相同,但他们存储的位置不可能相同,所以首元素的地址不可能相同

再来分析三四两行的代码,都是一个常量字符串存入2个指针变量,因为这个字符串被const修饰,在内存中独占一份,不会被修改。

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

2. 数组指针变量

2.1 数组指针变量是什么?

我们在第一章的时候学习了指针数组,意思是一个数组,里面存放的类型都是指针

字符数组--存放字符的数组  char arr[];

整形数组--存放整形的数组  int arr[]

指针数组--存放指针的数组  int* arr[](这里写的是整形指针的数组)

那么数组指针是什么呢?是数组还是指针?

答案:指针!

字符指针--指向字符的指针

整形指针--指向整形的指针

数组指针--指向数组的指针!

思考⼀下:p1, p2分别是什么?
int *p1[ 10 ];
int (*p2)[ 10 ];
数组指针变量
  int (*p)[ 10 ];
解释:p先和*结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以
p是⼀个指针,指向⼀个数组,叫 数组指针。
这⾥要注意:[]的优先级要⾼于*号的,所以必须加上()来保证p先和*结合。

2.2 数组指针变量怎么初始化

数组指针变量是⽤来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的 &数组名 

int arr[ 10 ] = { 0 };
&arr; // 得到的就是数组的地址
如果要存放个数组的地址,就得存放在数组指针变量中,如下:
int (*p)[ 10 ] = &arr
i n t (*p) [ 10 ] = &arr;
|         |      |
|         |      |
|         |      p 指向数组的元素个数
|        p 是数组指针变量名
p 指向的数组的元素类型

2.3 数组指针的解引用

(*p)[10],这样就可以解引用一个数组指针,举例:

int main()
{
	int arr[3] = { 1,2,3 };
	int(*p)[3] = arr;
	printf("%d ", (*p)[0]);
	printf("%d ", (*p)[1]);
	printf("%d ", (*p)[2]);
	return 0;
}

3. ⼆维数组传参的本质

有了数组指针的理解,我们就能够讲⼀下⼆维数组传参的本质了。
过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:
void test(int arr[][4], int x, int y)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };
	test(arr, 3, 4);
	return 0;
}

我们这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?

首先我们再次理解⼀下⼆维数组,⼆维数组起始可以看做是每个元素是⼀维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。那么⼆维数组的首元素就是第⼀行,是个⼀维数组。
所以,根据数组名是数组首元素的地址这个规则,⼆维数组的数组名表示的就是第⼀⾏的地址,是⼀维数组的地址。
那就意味着⼆维数组传参本质上也是传递了地址,传递的是第一⾏这个⼀维数组的地址
根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [4] ,所以第⼀⾏的地址的
类型就是数组指针类型 int(*)[4]
void test(int (*p)[4], int x, int y)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			printf("%d ", *(*(p+i)+j));
                         //(*p[i] + j);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };
	test(arr, 3, 4);
	return 0;
}
or
void test(int (*p)[4], int x, int y)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			printf("%d ", *p[j]);
        }
		printf("\n");
        p++;
	}
}

4. 函数指针变量

4.1 函数指针变量的创建

我们来看这么一串代码

void test()
{
	printf("hh\n");
}

int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}

运行的结果是

我们发现函数的地址也很神奇,居然和数组的地址有如此相像,函数的名字也等于函数的地址

输出的是两个地址,这两个地址是 test 函数的地址。
那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码:
void test()
{
	printf("hh\n");
}

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


int main()
{
	
	void (*pf1)() = &test;
	void (*pf2)() = test;
	
	int(*pf3)(int, int) = Add;
	int(*pf3)(int x, int y) = &Add;
	return 0;
}

分析:

int     (*pf3) ( int x, int y)
|         |         ------------
|         |              |
|         |              pf3 指向函数的参数类型和个数的交代
|          函数指针变量名
pf3 指向函数的返回类型
int (*) ( int x, int y) //pf3 函数指针变量的类型

 4.2 函数指针变量的使用

#include <stdio.h>
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. 函数指针数组

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,
比如:
int * arr [ 10 ];
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
// 数组的每个元素是 int*
int   ( * parr1 [ 10 ]) ();
int  * parr2 [ 10 ] ();
int   ( * ) ()   parr3  [ 10 ];
答案是: parr1
parr1 先和 [] 结合,说明 parr1 是数组,数组的内容是什么呢?
int (*)() 类型的函数指针。

5.1指向函数指针数组的指针

作了解不深究

void test(const char* str)
{
 printf("%s\n", str);
}
int main()
{
 //函数指针pfun
 void (*pfun)(const char*) = test;
 //函数指针的数组pfunArr
 void (*pfunArr[5])(const char* str);
 pfunArr[0] = test;
 //指向函数指针数组pfunArr的指针ppfunArr
 void (*(*ppfunArr)[5])(const char*) = &pfunArr;
 return 0;
}

6.回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。

我们先了解一下,转移表实现一个计算器

#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int x, y;
	int input = 1;
	int ret = 0;
	do
	{
		printf("*************************\n");
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf(" 0:exit \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输入操作数:");
			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;
}

可以进行一些简单算术操作 ,我们只需要设置好几个函数,接着我们选择数字进入需要的函数,来进行运算。但是我们这样的代码太冗长,而且出现了太多重复代码,我们可以用上面学到的知识来进行优化。

void meun()
{
	printf("*************************\n");
	printf(" 1:add 2:sub \n");
	printf(" 3:mul 4:div \n");
	printf(" 0:exit \n");
	printf("*************************\n");
	printf("请选择:");
	
}
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}

int main()
{
	int input = 1;
	int x = 0;
	int y = 0;
	int (*parr[5])(int, int) = { 0,add,sub,mul,div };
	do
	{
		meun();
		scanf("%d", &input);
		if (input>0 && input<=4)
		{
			printf("请输入操作数:\n");
			scanf("%d %d", &x, &y);
			int ret = (*parr[input])(x, y);
			printf("ret = %d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算机\n");
		}
		else
		{
			printf("输入错误\n");
		}
	} while (input);
	
	return 0;
}

我们把函数都存入一个函数指针数组中,需要的时候直接调用就不需要switch语句来写冗长的代码了。

下节我们会介绍qsort库函数是怎么调用函数的和如何实现的,还望指正

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值