C语言超详细指针知识(三)
在经过前面两篇指针知识博客学习之后,我相信你已经对指针有了一定的理解,今天将更新C语言指针最后一篇,一起来学习吧。
1.字符指针变量
在指针类型的学习中,我们知道有一种指针类型为字符指针char*,之前我们是这样使用的:
int main()
{
char ch = 'o';
char* pc = &ch;
*pc = 's';
return 0;
}
其实他还有一种使用方法:
int main()
{
char* pstr = "hello world";
printf("%s\n", pstr);
return 0;
}
观察结果,我们是不是认为将字符串"hello world"存入了字符指针pstr里了,其实并不是,第三行本质上是将"hello world"首字符的地址放到了pstr中。
上⾯代码的意思是把⼀个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。
我们来看一道笔试题:
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++会把常量字符串存储到单独的⼀个内存区域,当几个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。 所以str1和str2不同,str3和str4相同。
2.数组指针变量
上篇博客中我们学习了指针数组,指针数组是一种数组,数组中存放的是地址。
那么数组指针又是指针还是数组呢?
在之前的学习中,我们学习了整型指针变量和字符型指针变量,它们都是指针,由此类推,数组指针应该是:存放数组的地址,能够指向数组的指针变量。
我们先来看一个简单的数组指针变量:
int (*p)[10];
p先与*组合,说明p是一个指针变量,指向的是一个大小为10个整型的数组。
注意:[ ]的优先级要高于*,所以必须加上( )来保证p与*结合,如果没有( ),就变成了int* p[10],这是一个指针数组,该数组里可以存放10个整形指针变量。
了解了数组指针,那么该怎么初始化呢?
经过之前的学习,我们知道&arr表示的是整个数组的地址,所以我们可以这样初始化:
可以看到,&arr和p的类型是完全一致的。
数组指针类型分析:
int (*p) [10] = &arr;
| | || | || | p 指向数组的元素个数| p 是数组指针变量名p 指向的数组的元素类型
3.二维数组传参的本质
根据数组名就是数组首元素地址这个规则,二维数组的数组名表示的应该就是第一行的地址,是一维数组的地址,如图,第一行的一维数组类型是 int[5],所以第一行的地址类型是 int (*p)[5]。
看下面代码:
void test(int (*p)[5], int r, int c)
{
for (int i = 0; i < r; i++)
{
for (int 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} };
test(arr, 3, 5);
return 0;
}
⼆维数组传参,形参的部分可以写成数组,也可以写成指针形式。
4.函数指针变量
4.1函数指针变量的创建与使用
什么是函数指针变量?
通过前面整型变量,数组指针变量的类比,我们不难看出函数指针变量也是一种指针变量。函数指针变量是用来存放函数地址的,通过地址可以调用函数,有些人可能这时候就要问了,函数还有地址吗?我们来做个测试验证一下:
可以看到,我们成功打印出了地址,说明函数就是有地址的,函数名就是函数的地址,当然也可以通过&函数名的方法获取函数地址。
函数指针变量就是将函数的地址存放起来,他与数组指针变量类似,如下:
void test()
{
int i = 0;
}
void (*pf1)() = test;
//void (*pf1)() = &test;
int Add(int x, int y)
{
return x + y;
}
int (*pf2)(int, int) = Add;
//int (*pf2)(int, int) = &Add;
函数指针变量类型分析:
int (*pf3) ( int x, int y)| | ------------| | || | pf3 指向函数的参数类型和个数的交代| 函数指针变量名pf3 指向函数的返回类型int ( * ) ( int x, int y) //pf3 函数指针变量的类型
学会了就要马上应用起来,我们来看一个代码:
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = Add;
printf("%d\n", (*pf)(3, 5));
printf("%d\n", (*pf)(3, 10));
return 0;
}
这是一段很简单的示例,大家自己要多多尝试。
4.2两段有趣的代码
1 ( * ( void (*) ( ) ) 0 ) ( );
2 void (*signal ( int , void (*) (int) ) ) (int);
这两段代码一开始看可能会出现看不明白的情况,我们要学会将它们拆分。
首先,我们来看第一段代码:
1.void (*) ( ):这是一个函数指针类型,表明该指针指向的函数没有参数,并且返回值为void
2.
( void (*) ( ) ) 0 ):把整数0
强制转换为void (*) ( )
类型的函数指针,意味着让这个指针指向内存地址0
。
3.
* ( void (*) ( ) ) 0:对这个函数指针进行解引用操作,从而得到函数本身。
4.
( * ( void (*) ( ) ) 0 ) ( ):调用解引用后得到的函数,也就是调用位于内存地址0
处的函数。
第二段代码:
1.void (*) (int):这是一个函数指针类型,表明该指针指向的函数中有一个参数,类型为int,并且返回值为void
2.signal ( int , void (*) (int) ):这是一个函数,该函数有两个参数,一个参数类型为int,另一个参数类型是一个函数指针
3.void (*signal ( int , void (*) (int) ) ) (int):
signal
函数返回一个函数指针,这个返回的函数指针类型也是void (*)(int)
4.3typedef关键字
typedef unsigned int unit;
//将unsigned int 重命名为unit
typedef int* ptr_t;
typedef int(*parr_t)[5];//新类型名必须在*的右边
typedef void(*pun_t)(int);
我们在重新来看这个代码:
void (*signal ( int , void (*) (int) ) ) (int);
typedef void(*prse)(int);
prse signal(int,prse);
是不是要简单很多了,函数名为signal,有两个参数,一个是int,一个是prse,返回值也是prse。
5.函数指针数组
int* arr[10];
int (*parr[3])();
这就是一个简单的函数指针数组,parr先与[ ]结合,说明parr是数组,存放的是int(*)( )类型的函数指针。
6.转移表
下面我们来做一个函数指针数组的应用:转移表
举例:计算机的一般实现
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("****** 0.exit ******\n");
printf("****** 1.Add ******\n");
printf("****** 2.Sub ******\n");
printf("****** 3.Mul ******\n");
printf("****** 3.Div ******\n");
printf("***********************\n");
}
int main()
{
int input;
int x, y;
do
{
menu();
printf("选择算法->");
scanf("%d", &input);
switch (input)
{
case 0:
break;
case 1:
printf("输入被运算数->");
scanf("%d %d", &x, &y);
printf("%d\n", Add(x, y));
break;
case 2:
printf("输入被运算数->");
scanf("%d %d", &x, &y);
printf("%d\n", Sub(x, y));
break;
case 3:
printf("输入被运算数->");
scanf("%d %d", &x, &y);
printf("%d\n", Mul(x, y));
break;
case 4:
printf("输入被运算数->");
scanf("%d %d", &x, &y);
printf("%d\n", Div(x, y));
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input != 0);
return 0;
}
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("****** 0.exit ******\n");
printf("****** 1.Add ******\n");
printf("****** 2.Sub ******\n");
printf("****** 3.Mul ******\n");
printf("****** 3.Div ******\n");
printf("***********************\n");
}
int main()
{
int input;
int x, y;
int (*pf[5])(int, int) = { 0,Add,Sub,Mul,Div };
do
{
menu();
printf("选择算法->");
scanf("%d", &input);
printf("输入被运算数->");
scanf("%d %d", &x, &y);
printf("%d\n", (*pf[input])(x, y));
} while (input);
return 0;
}
使用函数指针数组之后代码篇幅大大减少。
回调函数就是⼀个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被用来调用其所指向的函数时,被调⽤的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发⽣时由另外的⼀⽅调⽤的,⽤于对该事件或条件进行响应。
针对转移表的改动,我们可以利用回调函数,如下:
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("****** 0.exit ******\n");
printf("****** 1.Add ******\n");
printf("****** 2.Sub ******\n");
printf("****** 3.Mul ******\n");
printf("****** 4.Div ******\n");
printf("***********************\n");
}
void cacl(int (*pf)(int, int))//回调函数
{
int x, y;
printf("输入被运算数->");
scanf("%d %d", &x, &y);
printf("%d\n", (*pf)(x, y));
}
int main()
{
int input;
do
{
menu();
printf("选择算法->");
scanf("%d", &input);
switch (input)
{
case 0:
break;
case 1:
cacl(Add);
break;
case 2:
cacl(Sub);
break;
case 3:
cacl(Mul);
break;
case 4:
cacl(Div);
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input != 0);
return 0;
}