©作者:末央&
©系列:C语言初阶(适合小白入门)
©说明:以凡人之笔墨,书写未来之大梦
目录
一、字符指针
字符指针的使用方式一般都是:
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}
值得注意的是还有一种使用方式是:
int main()
{
const char* pstr = "hello bit.";//这⾥是把⼀个字符串放到pstr指针变量⾥了吗?
printf("%s\n", pstr);
return 0;
}
代码 const char* pstr = “hello bit.”; 特别容易让同学以为是把字符串 hello bit 放
到字符指针 pstr ⾥了,但是本质是把字符串 hello bit. ⾸字符的地址放到了pstr中。
理解上面的概念后我们再来看一道题
#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;
}
题目最后打印的结果是:
str1 and str2 are not same
str3 and str4 are same
注意:常量字符串与普通字符串最大的区别是,常量字符串是不可被修改的字符串,既然不能被修改,那么在内存中没有必要存放两个一模一样的字符串,所以在内存中相同的常量字符串只有一个。
二、数组指针
概念
之前我们已经学习了指针数组,知道指针数组实际上是一个数组,他的每个元素是指针类型。反之,数组指针实际上就是一个指针,一个指向数组的指针。
区别之前我们应该知道,*号的优先级是比[ ]低的所以第一行是先与[ ]结合则是一个数组,每个元素类型是int *,第二行不同的是在用()把 *p2括了起来可以知道()和[ ]的优先级一样,但是根据结合性是从左到右所以先与()结合,则包含了 * 则是一个指针,指向一个数组.所以第二行是数组指针
类型补充
如果我们想知道一个变量的类型就可以把变量名去掉就是他的类型。例如:
int a; //去掉a他的类型就是int
int *arr[10] //去掉arr他的类型就是int* [10]
这里要注意的是数组的类型分别体现了数组元素的类型(数组的类型和数组元素类型不一样)和数组元素个数的概念。
一个指针变量除去了变量名和 * ,便是指针指向的内容的类型。
int a = 10;
int* p = &a;//除去变量名(p)和*,便是P指向的内容(a)的类型->int
int (*p)[10];//去掉 * 和变量名指向内容的类型就是int [10];
三、二维数组传参的本质
实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?
⾸先我们再次理解⼀下⼆维数组,⼆维数组起始可以看做是每个元素是⼀维数组的数组,也就是⼆维
数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组
所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀
维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5] ,所以第⼀⾏的地址的类
型就是数组指针类型 int(*)[5] 。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀
⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:
#include<stdio.h>
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;
}
四、函数指针
函数的地址
函数指针既然是指针,就一定离不开地址。那么函数的地址是什么呢?我们不妨测试以下代码:
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int main()
{
printf("test: %p\n", test);
printf("&test: %p\n", &test);
return 0;
}
那么我们可以知道函数名就是函数的地址,当然也可以通过&函数名来获得函数的地址
函数指针的初始化
那么我们现在有如下一个函数
int Add(int x, int y)
{
return x + y;
}
我们可以定义一个函数指针把他的地址存储起来。我们结合前面数组指针的例子,可以知道:
知道这三点我们再来创建函数指针类型,只要是指针就必须要先与 * 号结合,而括号的优先级高于 * 所以我们要用括号把* 括起来,而又因为我们要指向的函数类型是int (int, int) 对应上面第三点,所以我们可以写为int (*p)(int,int);对应上面第二条,指针变量的类型又是int ( * )(int,int)
知道了怎么创建函数指针类型,那么我们就可以创建一个函数指针来存储上面的函数了。
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = Add; //这里还可以&Add
return 0;
}
注意在数组中&数组名和数组名是不一样的意思,但是在函数中&函数名和函数名都是表示函数的地址。
函数指针的使用
我们可以通过函数指针来使用函数
方法一:
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;
int(*p)(int, int) = &Add;
int ret = (*p)(a, b);//解引用找到该函数
printf("%d\n", ret);
return 0;
}
既然初始化的时候是用&Add赋值函数指针,那么我们调用的时候就可以用*把&抵消,得到Add直接进行传参
方法二:
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int a = 10;
int b = 20;
int(*p)(int, int) = Add;
int ret = p(a, b);//不用解引用
printf("%d\n", ret);
return 0;
}
我们这次在赋值的时候直接把函数名给了函数指针,前面说到函数名和&函数名都表示函数的地址,我们可以不解引用就直接传参。
五、typedef关键字修饰指针
typedef关键字的主要作用就是修改类型标识符的名字。例如:
int a = 10;
typedef int INT;
INT b = 10;
这里的INT就等价与int
那么是否可以修饰指针呢?
int a = 10;
int* pa = &a;
typedef int* INT;
INT b = &a;
printf("%d",*b);
很明显是可以的
但是对于数组指针和函数指针类型标识符的修改有点差别
六、函数指针数组
概念
同理解指针数组,函数指针数组实际上也是一个数组。他的每个元素是一个函数指针。
int* pa[4];
将数组名和[ ]类型去掉就是数组元素的类型
很明显这个数组的元素类型是int*
同理函数指针数组的元素类型应该是一个函数指针类型。我们看前面的一个函数指针如下:
int(*p)(int, int)
去掉变量名就是变量类型:int(*)(int,int);再加上函数指针数组是一个数组所以要与[ ] 先结合
所以就是 int( * pp[4])(int,int)
函数指针数组的创建只需在函数指针创建的基础上加上[ ]即可。
比如,你要创建一个函数指针数组,这个数组中存放的函数指针的类型均为int(*)(int,int),如果你要创建一个函数指针为该类型,那么该函数指针的写法为int(*p)(int,int),现在你要创建一个存放该指针类型的数组,只需在变量名的后面加上[ ]即可,int(*pArr[10])(int,int)。
函数指针的使用(模拟计算器)
#include<stdio.h>
void menu()
{
printf("|-----------------------|\n");
printf("| 1.Add 2.Sub |\n");
printf("| 3.Mul 4.Div |\n");
printf("| 0.exit |\n");
printf("|-----------------------|\n");
}//菜单
double Add(double x, double y)
{
return x + y;
}//加法函数
double Sub(double x, double y)
{
return x - y;
}//减法函数
double Mul(double x, double y)
{
return x*y;
}//乘法函数
double Div(double x, double y)
{
return x / y;
}//除法函数
int main()
{
int input = 0;
double x = 0;//第一个操作数
double y = 0;//第二个操作数
double ret = 0;//运算结果
double(*pArr[])(double, double) = { 0, Add, Sub, Mul, Div };
//函数指针数组-转移表,这里首元素是0是为了方便用户选择
int sz = sizeof(pArr) / sizeof(pArr[0]);//计算数组的大小
do
{
menu();
printf("请输入:>");
scanf("%d", &input);
if (input == 0)
printf("退出程序\n");
else if (input > 0 && input < sz)
{
printf("请输入两个操作数:>");
scanf("%lf %lf", &x, &y);
ret = pArr[input](x, y);
printf("ret=%lf\n", ret);
}
else
printf("选择错误,请重新选择!\n");
} while (input);//当input不为0时循环继续
return 0;
}
代码中函数指针数组第一个元素为0是为了对应菜单选择模块,程序用数组把每个函数的地址都存储起来,再通过input变量来控制要访问的函数和程序的终止。