科普:printf格式化打印
1.深入理解字符串
2.字符指针变量
3.数组指针变量
4.函数指针变量
5.函数指针数组
6.函数指针数组的应用
科普:printf格式化打印
%d打印a的地址,打印出随机值(err)
%d打印字符a(arr是a的地址,解引用后就是a),正常打印a的ascll码
%c打印a的地址,打印错误(err)
%c打印字符a,打印出a
%s打印a地址(字符串打印时,printf遇到\0才停),正常打印
%s打印字符a,打印错误(err)
所以,在%d,%c打印时,应传字符,%s打印时,应传地址
1.深入理解字符串
字符串可以理解成一个只读字符数组,数组就是数组名,所以,“abcd”,像这样的形式都可以理解成数组名,而数组名是首元素的地址,所以"abcd"是首元素a的地址。并且,字符串“abcde”是只读数组,所以在内存上是占着一块空间的(代码段)
我们用代码验证:
"abcde"是a的地址,解引用后%c打印字符‘a’
我们站在在个新的角度,重新理解一下这个代码,"abcde"是字符串,字符串是只读字符数组,数组是数组名,数组名是首元素地址,所以,“abcde”是字符‘a’的地址,所以%s从'a'开始打印,打印到字符‘e’,然后遇到‘\0’,打印停止。
单字符是不会有地址的,
字符串中字符的地址,实际上是字符串的地址,
字符变量中的字符的地址,实际上是字符变量的地址
字符数组中字符的地址,实际上是数组的地址
我在文章中常常说某字符的地址,其实是指那个地址装着该字符(直接说第几个元素的地址不好理解),这么说只是方便讲述
例题:结果是什么?
#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,str2是两个不同的数组,在内存上占据着不同的空间,其首元素的地址自然不同
“hello bit.” 是字符串,字符串是数组,数组是数组名,“hello bit”是一个数组名
下面的“hello bit”也是数组名,而且和上面的名字相同,所以,这两个字符串表示同一个数组首元素的地址,所以,str3和str4相同。
讲完字符指针变量,会有例题详解!
2.字符指针变量
什么是字符指针变量?
字符指针变量是一个变量,这个变量里装的是指向字符的指针。
根据前几节的知识,不难想出这个代码,pc就是字符指针变量,里面装着字符变量ch的地址。
我们还可以这么玩:
“abcde”是a的地址所以“abcde”是指向字符‘a’的指针,所以“abcde”是字符指针
第五行实际上是把字符串中字符‘a’的地址传给了字符指针变量pc,%s从‘a’开始打印,直到‘e’,遇到\0,停止打印。
有了这些知识,我们现在看几个例题:
第六行:“abcd”是字符串,字符串是只读字符数组,数组是数组名,【2】用方括号下标访问第3个元素,所以是c
第七行:1.公式转化:arr[i]=*(arr+i),arr是数组名,“abcd”也是数组名,所以可以用该公式直接转换
第八行:2.“abcd”是首元素a的地址,+2跳过两个元素,指向c,解引用后,%c打印出c
3.数组指针变量
什么是数组指针变量?
数组指针变量是变量,里面装着指向数组的指针。
这里p1和p2分别代表什么?
p1:[]的优先级高于*,p1先于[10]结合,成为数组,数组的元素是int*类型的
p2:()的优先级高于[],p2先于*结合,*说明p2是指针,p2再与[10]结合,使得p2指向数组,[10]说明指针p2指向的数组有十个元素,int表示p2指向的数组的元素是int型的。
数组指针变量里装的是指向数组的指针,如何获取数组的地址?
&arr(用&+数组名直接取出数组的地址)
初始化方法:
数组指针类型:去掉数组名,剩下的就是类型p2的类型是int(*)[10].
4.函数指针变量
函数指针变量是变量,里面装着指向函数的指针。
数组名表示数组首元素的地址
函数名表示函数的地址
看下面的代码:
函数名表示函数的地址,test是函数名,test是函数的地址,所以函数调用时可以写地址
&test也是函数的地址,可以用于函数调用,上图得证!
先看第9和第10行,p1先于*结合说明p1是指针,再与()结合,说明p1指向函数,右边的()交代指针指向的函数的参数,void表示p1指向的函数的返回值类型。p2同理,test就是test的地址,所以要得到函数的地址,加不加&都一样,p1和p2是一样的,看结果得证!
看第11行和第12行,调用函数时,可以使用函数的地址,所以两种写法都一样,都可以成功调用函数!看结果得证!!!
函数指针类型解析:
还有另一种写法,图中的x,y是可以省略的,这两种写法是一个意思,后续的博客大多会采用省略的写法。
我们学过很多类型,有整型,实型(浮点型),无符号整型等等
今天,我们看到了数组指针类型,函数指针类型
它们有个共同点,都是类型,所以,它们都是用来修饰变量的
它们是类型,意味着它们能够参与类型强转
我们看看下面这个代码(可跳过,很难)
(*(void(*)())0))();
0是一个整型常量,我们用(void(*)())这个函数指针类型将0强转,使其变为函数指针,再解引用,找到这个函数,最后函数与()结合,进行函数调用。
void(*signal(int,void(*)(int)))(int);
void(*)(int)是函数指针类型,是一个类型,int是整型,也是一个类型,signal先与()结合,说明这是函数声明,去掉signal(int,void(*)(int)),剩下的是返回值类型。
void(*(*signal)(int ,void(*)(int)))(int);
signal先与*结合,说明signal是指针,右边的(int,void(*)(int))交代了函数参数,所以signal是函数指针变量,去掉(*signal)(int ,void(*)(int)),剩下的void(*)(int)是signal指针指向的函数的类型。
typedef关键字
例如:unsigned int写着不方便
我们可以:typedef unsigned int uint;
这样,unit也能表示无符号整型,可以替换unsigned int,注意:typedef重命名后,原来的名字是可以用的,就比如现在,unsigned int和uint都是可以用的,且效果一样!
typedef重命名时,新的名字写在原来的名字创建变量时变量的位置
举几个例子:
typedef int(*ptr_t)[5];
ptr_t就是int(*)[5]这个类型的新名字,使用简化的名字创建变量时,只需将变量写在类型名的右边即可
int(*p1)[5];等价于ptr_t p1;
typedef void(*pfun_t)(int);
void(*p1)(int);等价于void(*p1)(int);
5.函数指针数组
函数指针数组是数组,里面存放的是函数的指针。
int (*parr[3])();
int *parr[3]();err
int(*)() parr[3];err
parr是数组,要先于[]结合,然后将parr【3】想象成数组的元素,数组的元素是函数指针变量,函数指针变量与*结合,说明该变量是指针,再与()结合,说明数组元素指向函数,右边的()交代了函数指针变量指向的函数的参数,去掉(*parr[3])(),剩下的int是函数指针变量指向的函数的返回值类型。
6.函数指针数组的应用
题目,使用函数指针数组实现计算机的功能。
#include<stdio.h>
double add(double x, double y)
{
return x + y;
}
double jian(double x, double y)
{
return x - y;
}
double cheng(double x, double y)
{
return x * y;
}
double chu(double x, double y)
{
return x / y;
}
int main()
{
double x, y;
int input;
scanf("%lf%lf", &x, &y);
printf("+(1),-(2),*(3),/(4)\n");
double(*arr[5])(double, double) = { 0,add,jian,cheng,chu };
scanf("%d", &input);
printf("%.2lf", arr[input](x, y));
return 0;
}