字符指针变量
字符指针--char* 是指针的类型。
char ch = 'w';
char* pch = &ch;
以上是一般的使用,字符的地址取出,放在字符指针变量中。
再看下一种使用方式
const char* p = "abcdef";
以上的字符串是常量字符串,并且它是常量,不能被修改。这里大家可能会认为是把字符串“abcdef”存放在字符指针中,其实并不是这样,而是把首元素地址放在指针中
这里的常量字符串和数组用法一样,并且存储的地址也是连续的。
我们来看一下下面有趣的题目:
#include <stdio.h>
int main()
{
char str1[] = "abcdef"; //而数组则是数组名为地址,它会开辟新的地址
char str2[] = "abcdef";
const char* str3 = "abcdef"; //指针指向的时常量字符串的首个字符地址
const char* str4 = "abcdef";
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指向的是⼀个同⼀个常量字符串。当⼏个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是⽤相同的常量字符串去初始 化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
总结:
数组是会创建新的地址保存数据
而指针是指向同一个内存的
数组指针变量
首先,我们来类比一下:
整型指针变量:int* p存放的是整型变量的地址,是指向整数数据的指针。
字符指针变量:char* p存放的是字符变量的地址,是指向字符数据的指针。
那数组指针比变量应该就是:存放数组的地址,是指向数组数据的指针。
int arr[5];
int (*p)[5]; //这个就是数组指针变量
注意
在之前我们学习过一个指针数组,它是一个数组,而不是一个变量。
int* p[5]; //这是指针数组
数组中存储了5个指针变量 int*。 首先[]的优先级比*高,所以p先和[]结合,形成了数组,然后再和int *结合,所以形成了指针数组。
而数组指针,实际上是一个指针变量。
int (*p)[5];
先括号把*p结合起来,形成指针,再和[]结合,形成数组指针变量。
二维数组传参的本质
以代码的形式给大家展示:
#include <stdio.h>
void Print(int(*arr)[5], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; 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;
}
问题与解答(递进式理解代码)
1.问题:二维数组传参为什么用数组指针接收?
解答:⾸先我们再次理解⼀下⼆维数组,⼆维数组其实可以看做是每个元素是⼀维数组的数组,也就是⼆维 数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。
2 .问题:为什么打印二维数组的元素时是*(*(arr + i) + j)这种写法?
解答:大家知道一维数组的元素的取法-->*(arr+i)这样遍历所有元素,第一个问题我们得知二维数组的首元素是一个一维数组,所以*(arr + i)只是取出了每一行的一维数组,想要遍历里面元素,需要在加一个变量做遍历下标---> *(*(arr + i) + j).
大家如果不了解数组内容,参考:你真的懂数组吗?从0到1的深层理解
函数指针变量
有了前面数组指针变量的铺垫,相信大家对函数指针变量心里也有了猜测。
其实,函数指针变量就是用来存放函数的地址的,能够通过函数的地址来调用函数。
来思考一下函数是否有地址?
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
//&函数名 和 函数名 拿到的都是函数的地址
int main()
{
printf("%p\n", &Add);
printf("%p\n", Add);
return 0;
}
其实函数是有地址的,函数名就是函数的地址,当然也可以通过 &函数名 的⽅ 式获得函数的地址,俩个方法都可以取到地址。
那大家可能就会问了,那函数地址存储在哪里呢?
函数地址其实是存储在函数指针变量中。
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int(*p)(int, int) = &Add;
int(*p)(int x, int y) = &Add; //x和y写上或者省略都是可以的
return 0;
}
其实函数指针的创建和数组指针变量的创建时差不多的。
一段有趣的代码
( *(void (*)( ) ) 0)( );
大家了解一下即可。
typedef 关键字
typedef 是⽤来类型重命名的,可以将复杂的类型,简单化。
⽐如,你觉得 unsigned int 写起来不⽅便,如果能写成 uint 就⽅便多了,那么我们可以使⽤:
typedef unsigned int uint;
//将unsigned int 重新命名为 unit
如果是指针类型,能否重命名呢?其实也是可以的,⽐如,将 int* 重命名为 ptr_t ,这样写:
typedef int* ptr_t;
但是对于数组指针和函数指针稍微有点区别:
⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:
typedef int(*parr_t)[5]; //新的类型要在*的右边
函数指针类型的重命名也是⼀样的,⽐如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:
typedef void(*pfun_t)(int); //新的类型名必须在*的右边
函数指针数组
数组是⼀个存放相同类型数据的存储空间,我们已经学习了指针数组,
int * arr[10];
//数组的每个元素是int*
那要把函数的地址存到⼀个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[3])();
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?
是 int (*)() 类型的函数指针。
转移表
函数指针数组的⽤途:转移表
#include <stdio.h>
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 input = 0;
int x = 0;
int y = 0;
int ret = 0;
int(*p[5])(int, int) = { 0, add, sub, mul, div };
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);
if (input > 0 && input <= 4)
{
printf("输入操作数:>");
scanf("%d%d", &x, &y);
ret = p[input](x, y);
printf("%d\n", ret);
}
else
{
printf("输入有误\n");
}
} while (input);
return 0;
}