1.字符指针变量
字符指针char*,与整型指针int*类似,只是字符指针指向的对象是一个char类型的数据,用于存放char类型数据的地址。
一般的使用:
#include <stdio.h>
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = 'w';
return 0;
}
还有一种使用方式:
#include <stdio.h>
int main()
{
const char* pstr = "hello bit.";
printf("%S\n", pstr);
return 0;
}
上述的代码表示把首字符‘h’的地址放到了pstr中。并不是把'hello bit.'字符串放到pstr中。
《剑指offer》中收录了⼀道和字符串相关的笔试题:
#include <stdio.h>
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.数组指针变量
2.1数组指针变量是什么
之前学习的指针数组,是一种数组,数组中存放的是地址(指针)。而数组指针变量是指针(变量)。
数组指针变量:存放的应该是数组的地址,能够指向数组的指针变量。
int* p1[10];
int(*p2)[10];
p2先和*结合,说明p2是一个指针变量,然后指向的是一个大小为10个整型的数组。所以p2是一个指针,指向一个数组,叫数组指针。
p1先和[10]结合,说明它是一个存放是个元素的数组,前面的int* 是数组中存放的元素类型。叫做指针数组。
注:[]的优先级要高于*号,所以必须加上()来保证先和*结合。
2.2数组指针怎么初始化
int arr[10] = {0};
&arr; //得到的就是数组的地址
如果要存放数组的地址,就得存放在数组指针中,如下:
int(*p)[10] = &arr;
上述代码是把数组arr的地址存入p指针变量中,而这个数组是一个可以存放十个整型数据的数组。除开(*p),int [10] 表示的就是这个数组的类型。
3.二维数组传参的本质
过去我们有一个二维数组的需要传参给一个函数的时候,我们这样写:
#include <stdio.h>
void test(int a[3][5], int r, int c)
{
int i = 0;
int j = 0;
for (i = 0; i < r; i++)
{
for (j = 0; j < c; j++)
{
printf("%d ", a[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;
}
这里的形参写成二维数组的形式,接下来介绍用数组指针的形式表示这个形参。
首先,二维数组其实可以看作每个元素是一维数组的数组,也就是二维数组的每个元素是一个一维数组,那么二维数组的首元素就是第一行,是一个一维数组。
根据数组名是数组首元素的地址(除了sizeof(数组名)和&数组名,这两种情况的数组名表示整个数组的地址),二维数组的数组名表示的就是第一行的地址,是一维数组的地址。第一行的一维数组的类型就是int [5],所以第一行的地址的类型就是数组指针类型int(*)[5]。那就意味着二维数组传参本质上就是传递了地址,传递的是第一行这个一维数组的地址。则上述函数test()的形参int a[3][5]可以修改为int (*p)[5].
总结:二维数组传参,形参的部分可以写成数组,也可以写成指针形式,和一维数组传参类似。
4.函数指针变量
4.1函数指针变量的创建
根据前面学习整型指针,字符指针,数组指针类比,不难得出结论:函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的。函数是有地址的,并且函数名就是函数的地址,当然也可以通过&函数名的方式获得函数的地址。函数指针变量的写法其实和数组指针非常类似。如下:
#include <stdio.h>
void test()
{
printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)() = test;
int Add(int x, int y)
{
return x+y;
}
int (*pf3)(int, int) = Add;
int (*pf4) (int x, int y) = &Add; //x和y写上或者省略都是可以的
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和8。因为函数名对应的就是&函数名,*和&是一对性质相反的操作符,所以上述对函数指针是否带有解引用符合*都是对函数指针的解引用。
4.3typedef关键字
typedef是用来类型重命名的, 可以将复杂的类型简单化。比如觉得unsigned int 写起来不方面,可以把它重命名为uint:
typedef insigned int uint;
//将unsigned int 重命名为uint
指针类型也是可以重命名的,比如将int* 重命名为ptr_t。
//重命名int*为ptr_t;
typedef int* ptr_t;
//如果我们有数组指针类型int(*)[5],需要重命名为parr_t
typedef int(*parr_t)[5]; //新的类型名必须在*的右边
//如果将void(*)(int)类型重命名为pfun_t,就可以这样写
typedef void(*pfun_t)(int);
5.函数指针数组
函数指针数组可以类比与指针数组,函数指针数组本质上是一个数组,数组中存放的是函数指针(即函数的地址)。
定义函数指针数组:
int (*parr1[3])();
parr1先和[]结合,说明parr1是数组。去掉parr1[3],剩下的就是数组中存放的数据类型,则为int (*)(),该类型是一个返回值为int且没有参数的函数指针类型。
函数指针数组在实际的应用中并不多见,所以在这不做过多介绍。
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;
}
上述代码创建一个菜单列表,可以选择加减乘除这四种算数运算并在选择之后输入两个数字可以得到对应的结果。可以看出在switch语句中每一个case都有很多相同的代码,于是我们可以考虑用函数指针数组来实现转移表:
#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;
int(*p[5])(int x, int y) = { 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 <= 4 && input >= 1))
{
printf( "输⼊操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y); //通过函数指针数组来调用函数
printf( "ret = %d\n", ret);
}
else if(input == 0)
{
printf("退出计算器\n");
}
else
{
printf( "输⼊有误\n" );
}