wordpress搭电影网站,可以放钓鱼网站的免费空间,网站建设与运营的论文的范本,自己搭建域名服务器文章目录 1.字符指针变量1.1 字符指针变量类型是什么1.2字符指针变量的两种使用方法#xff1a;1.3字符指针笔试题讲解1.3.1 代码解剖 2.数组指针变量2.1 什么是数组指针2.2 数组指针变量是什么#xff1f;2.2.3 数组指针变量的举例 2.3数组指针和指针数组的区别是什么#… 文章目录 1.字符指针变量1.1 字符指针变量类型是什么1.2字符指针变量的两种使用方法1.3字符指针笔试题讲解1.3.1 代码解剖 2.数组指针变量2.1 什么是数组指针2.2 数组指针变量是什么2.2.3 数组指针变量的举例 2.3数组指针和指针数组的区别是什么2.3.1 数组指针和指针数组区别举例 2.4 数组指针变量是怎么初始化 3.二维数组传参的本质4.函数指针变量4.1 函数指针变量的创建4.1.1什么是函数指针变量呢 4.2 函数指针变量的使用4.3 两段有趣的代码4.4. typedef关键字4.4.1 typedef关键字是什么4.4.2 typedef 关键字用法举例4.4.2.1 举例14.4.2.1 举例24.4.2.1 举例34.4.2.1 举例4 5.函数指针数组5.1 什么是函数指针数组5.2 如何定义函数指针数组5.3 函数指针数组举例 6.转移表6.1.1用函数指针数组作为转移表实现计算器的代码逻辑6.1.2用函数指针作为转移表实现计算器的代码逻辑 在上一篇博客中 C语言-指针讲解2 我们更为深层地给大家介绍了指针的其他基础用法 让下面我们来回顾一下讲了什么吧 1.野指针指向的位置是不可知的。我们只有做到 对指针初始化小心数组越界当指针变量不再使用时应及时置NULL,指针使用之前检查有效性。
才能在写代码避免出现野指针的情况。 2.assert.h头文件定义了宏assert(),用于在运行中确保程序符合指定程序如果不符合就报错运行运行。这个宏常常被称为“断言”。 3.学习指针的用处以及指针传址调用的用法 4.除了sizeof(数组名)和数组名其他情况下数组名都是数组首元素的地址。 5.二级指针是存放一级指针的指针二级指针是存放一级指针的地址。二级指针的相关运算。 6.存放指针的则称作指针数组指针数组每个元素都是用来存放地址的。 7.指针数组模拟二维数组的实例 那么这次博主会给大家介绍指针进阶的地方具体内容看下图所示: 1.字符指针变量
1.1 字符指针变量类型是什么 我们知道字符变量类型是char。 那同理字符指针变量类型是char*。 1.2字符指针变量的两种使用方法
int main()
{char ch w;char *pc ch;//用指针变量pc来接收ch变量的地址*pc w;//通过对指针变量pc进行解引用操作访问到ch变量的值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中。 如下图所示 通过此图我们更能直观地看到上面代码的意思实际上是把一个字符串的首字符h的地址存放到指针变量pstr中。 1.3字符指针笔试题讲解
下面来自一道《剑指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(str1str2)printf(str1 and str2 are same\n);elseprintf(str1 and str2 are not same\n);if(str3str4)printf(str3 and str4 are same\n);elseprintf(str3 and str4 are not same\n);return 0;
}大家不妨看看这个代码运行结果是什么输出的是哪两条语句
我们不妨看一下vs的运行结果 看到这里也许有同学会有疑问为什么输出的是那两句。接下来我们给大家仔细分析一下。
1.3.1 代码解剖 如下图所示 之前我们讲过除sizeof(数组名)和数组名其他情况下数组名都是数组首元素的地址。 因此这行代码if(str1str2) 本质上就是两个地址间进行比较。虽然这里的str1和str2数组存的内容是相同的但本质上它们都是用相同的常量字符串去初始化不同的数组这样就会开辟出不同的内存块。所以str1和str2不同。 而这里的str3和str4指向的是同一个常量字符串。通常C/C会把常量字符串存储到单独的一个内存区域 所以当几个指针指向同一个字符串的时候它们实际上会指向同一块内存。所以str3和str4是相同的。 2.数组指针变量
2.1 什么是数组指针 数组指针顾名思义它就是一种指针里面存放的是数组的地址。 2.2 数组指针变量是什么 根据前面的指针介绍我们知道 1.整形指针变量int * ptr,存放的是整型变量的地址能够指向整形数据的指针。 2.浮点型指针变量float * pf;存放浮点型变量的地址能够指向浮点型数据的指针。 那数组指针变量就是存放的是数组的地址能够指向数组的指针变量。 2.2.3 数组指针变量的举例
我们来看一下下面哪个代码是数组指针变量
int *p1[10];
int (*p2)[10];大家可以先思考一下 这里的数组指针变量是这个
int (*p)[10];解释如下 p先和*结合说明p是一个指针变量然后它指向的是一个大小为10个整型的数组。所以p是一个指针指向一个数组指针叫数组指针。 可能有同学会有疑问为什么p要和*先结合呢 我们来看下图所示 从图中我们可以清晰看到[]的优先级是比* 要高的。所以我们必须加上()来保证p先和*结合。 2.3数组指针和指针数组的区别是什么
这两个概念在词语虽然很相近但事实是两个不同的事实。
2.3.1 数组指针和指针数组区别举例
代码如下
int *a[5]和int (*a)[5];分析**我们从上面代码和刚刚给出操作符优先级的图可以得知它们之前相差一个括号根据优先级的关系(()[]*)如果不加括号a会先与[]结合加了括号a则会和 *结合由此可见加括号为的是改变运算的结合顺序。 int *a[5]没有括号a会先与[]结合所以它是一个数组。int (*a)[5]加了括号a会与 *结合所以它是一个指针。 根据上面分析我们得出以下结论 int * [5]是指针数组它本质上是一个数组数组里面存放的是指针指针类型是int*指向的是一个整形数组。 int( * a)[5]是数组指针它本质是一个指针里面存放的是数组的地址指向的是数组的类型是int[5]。 2.4 数组指针变量是怎么初始化 我们知道:数组指针变量是用来存放数组地址的那如何获取数组的地址呢我们之前学习的数组名,这个数组名可以把整个数组的地址取出来。 1. int arr[10]{0};
2. arr//得到的是数组的地址如果我们要存放个数组的地址就得存放在数组指针变量中 代码如下:
int (*p)[10] arr我们在调试中也能看到arr和p的类型是完全一致的。 因此那个数指针组类型解析如下: 3.二维数组传参的本质
有了数组指针的理解我们就能够讲一下二维数组传参的本质了。 过去我们有一个二维数组传参给一个函数的时候我们是这么写的
#include stdio.h
void test(int a[3][5], int r, int c)
{int i 0;int j 0;for(i0; ir; i){for(j0; jc; 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;
}这里实参是二维数组形参也可以写成二维数组的形式那还有什么其他的写法呢 首先我们可以再次理解一下二维数组:二维数组起始可以看做每个元素是一维数组的数组也就是二维数组的每个元素是一个一维数组。那么二维数组的首元素就是第一行,是个一维数组。 如下图所示 所以我们根据数组名是数组首元素的地址这个规则二维数组的数组名表示的就是第一行的地址是一维数组的地址。根据上面的例子第一行的一维数组的类型就是int[5],所以第一行的地址的类型就是数组指针类型int(*)[5]。那就意味着二维数组传参本质也是传递了地址传递的是第一行这个一维数组的地址,那么形参也是可以写成指针形式的。 代码如下:
#include stdio.h
void test(int (*p)[5], int r, int c)
{int i0;int j0;for(i0;ir;i){for(j0;jc;j){printf(%d , *(*(pi)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;
}可能有同学对上述代码不理解什么意思
printf(%d , *(*(pi)j));我们就来解释一下吧~ 我们知道二维数组的数组名是第一行数组的地址。至于我们为什么要j?这是因为我们要拿到二维数组中每个元素的地址而如果说不加j的话我们总是拿到都是二维数组所在那行的第一个元素地址这显然是不合理的。另外我们看到外层还有一个*,因为是( * (pij只是拿到的是每个元素的地址但我们目的是要拿到它每个元素的值因此我们还要再对它进行解引用操作。也就是在最外层的括号左边再加个 * 才能获取二维数组每个元素的值。 总结二维数组传参形参的部分可以写成数组也可以写成指针形式。 4.函数指针变量
4.1 函数指针变量的创建
4.1.1什么是函数指针变量呢 通过前面我们介绍整型指针数组指针的时候我们类比关系不难的得出结论 函数指针变量是用来存放函数地址未来通过地址来调用函数的。 那么函数是否有地址呢 我们不妨测试下面这几行代码
#include stdio.h
void test()
{printf(hehe\n);
}
int main()
{printf(test: %p\n, test);printf(test: %p\n, test);return 0;
}输出结果如下 我们可以看到vs编译器确实打印出来了地址。 因此我们可以得出以下结论 函数是有地址的。函数名就是函数的地址可以通过函数名的方式获得函数的地址。 但如果我们要把函数的地址存放起来那应该怎么做呢? 我们应该创建函数指针变量来存放函数的地址。 其实函数指针变量的写法其实和数组指针非常类似。 代码如下所示
void test()
{printf(hehe\n);
}void (*pf1)() test;
void (*pf2)() test;int Add(int x, int y)
{return xy;
}int(*pf3)(int, int) Add;
int(*pf3)(int x, int y) Add;//x和y写上或者省略都是可以的函数指针类型解析
4.2 函数指针变量的使用 这里我们来给大家演示一下函数指针指向的函数的例子。 include stdio.hint Add(int x, int y){return xy;}int main(){int(*pf3)(int, int) Add;printf(%d\n, (*pf3)(2, 3));printf(%d\n, pf3(3, 5));return 0;}4.3 两段有趣的代码 接下来有两段有趣的函数指针的代码给大家讲一下代码里面的逻辑~ 代码1
(*(void (*)())0)();这个代码的含义可能很多人都无法理解但没事接下来我将把代码拆解来给大家讲解一下。 我们先看下图 解剖代码1 从上图我们知道:它其实把0这个整数强制转换为(void (*)()这种函数指针类型想让0当做这种函数的地址。 当我们分析到这样子这个代码的意思也就一目了然了。 它这个代码1本质的意思就是: 调用0地址处的函数函数的参数为无参,返回的类型为void。 代码2
void (*signal(int , void(*)(int)))(int);我们继续来分析一下这个代码看看它是什么意思。 同样地我们可以把这个代码拆解成这样方便大家可以理解。 解剖代码2 从上面我们的代码分析图可以得知 声明的signal函数有2个参数第一个参数是int类型的第二个参数是函数指针类型的该函数指向的是int类型返回的类型是void。signal函数的返回类型也是一个函数指针该函数指针向的函数,参数是int,返回的类型也是void。 另外这两段代码均出自《C陷阱和缺陷》这本书大家有兴趣的话可以找来看看~ 4.4. typedef关键字
4.4.1 typedef关键字是什么 typedef关键字顾名思义就是用来类型重名名的。可以将复杂的类型简单化。 4.4.2 typedef 关键字用法举例
4.4.2.1 举例1 1.比如我们觉得数据类型 unsingned int写起来不方便如果写成uint就方便多了那我们可以怎么写呢 具体如下图所示 4.4.2.1 举例2 如果是指针类型能否重名呢其实也是可以的比如将double*重命名为ptr要怎么写呢? 具体写法如下所示 4.4.2.1 举例3 那我们能否对数组指针和函数指针进行重命名呢这个也是可以的。但是这个跟之前的重名稍微有点区别: 如果我们数组类型是int(*)[10],要重命名为tmp那我们可以这样写 typedef int(*tmp)[10];//新的类型名必须在*右边这里就是将int(*)[10]类型重命名为tmp
int main() {int(*arr)[10];tmp v;return 0;
}如下图所示: 同时我们通过vs调试发现变量v也是数组指针int(*)[10]类型。 4.4.2.1 举例4
同理函数指针类型的重命名也是一样的比如将void(*)(int)类型重命名为ptr_t,就可以这样写:
1 typedef void(*pfr_t)(int);//新的类型名必须在*的右边如果说我们要简化之前的这个代码我们该怎么写呢 void (*signal(int,void(*)(int)))(int);如下图所示 从图中我们发现当我们把函数指针类型重命名为parr_tvs编译器是没有发生任何报错的。 5.函数指针数组
我们已经到知道 数组是一个存放相同类型数据的存储空间并且我们之前也学习了指针数组。 比如 int *arr[10];
//数组的每个元素是int*5.1 什么是函数指针数组 如果我们把函数的地址存到一个数组中那这个数组就叫函数指针数组。 5.2 如何定义函数指针数组 我们来看下这行代码 int (*parr1[3])();代码分析 由于我们前面介绍过[]的优先级比*要高因此[]会先和parr1结合说明parr1是数组。那数组的内容是什么呢答案是int(*)(); 5.3 函数指针数组举例
如下图所示 代码分析 从图中我们发现由于函数指针p1和p2都是相同的类型且返回的数据类型为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子语句很多代码都是相似的这样会显得有点冗余。如果这个计算器要增加更多的计算功能比如 a%b,ab,a^b,a|b。那这样的话就Switch的case子语句就会写得更多这样整个代码逻辑就会变得复杂了。 那如何优化这个计算器的代码逻辑呢
6.1.1用函数指针数组作为转移表实现计算器的代码逻辑
这个代码我们可以改进成这样
#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;
}void menu() {printf(*************************\n);printf( 1:add 2:sub \n);printf( 3:mul 4:div \n);printf( 0:exit \n);printf(*************************\n);
}int main()
{int input 0;int(*ptr[5])(int x,int y) {0,add,sub,mul,div};//转移表//至于这里为什么函数指针数组要写5个,主要是因为数组的下标是从0开始的而这里存的add~div函数地址最好用下标1-4来表示这样可以方便后续计算。do{menu();printf(请选择);scanf(%d, input);if (input 1 input 4) {int x 0;int y 0;int ret 0;printf(请输入两个操作数);scanf(%d %d, x, y);ret (*ptr[input])(x, y);printf(%d\n, ret);}else if (input 0) {printf(退出计算器。\n);}else {printf(输入有误请重新输入\n);}} while (input);return 0;
}分析代码 从上面这行代码 int(*ptr[5])(int x,int y) {0,add,sub,mul,div};我们用函数指针数组ptr[5]来把add,sub,mul,div这四个函数的地址分别存进函数指针数组里面去。这里就相当于一个跳板也就是我们说的转移表后续根据用户输入input的值通过函数数组指针ptr下标的值来调用所对应的函数。而那个变量x和y是调用函数时所传的参数。 6.1.2用函数指针作为转移表实现计算器的代码逻辑
代码如下
#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;
}void menu() {printf(*************************\n);printf( 1:add 2:sub \n);printf( 3:mul 4:div \n);printf( 0:exit \n);printf(*************************\n);
}void calc(int(*ptr)(int x, int y)) {int x 0;int y 0;int ret 0;printf(请输入两个操作数);scanf(%d %d, x, y);ret (*ptr)(x, y);printf(%d\n, ret);
}int main()
{int input 0;do{menu();printf(请选择:);scanf(%d, input);switch (input) {case 1:calc(add);break;case 2:calc(sub);break;case 3:calc(mul);break;case 4:calc(div);break;case 0:printf(退出计算器。\n);break;default: {printf(输入有误请重新输入\n);}}} while (input);return 0;
}分析代码 从上述代码中我们是封装了一个calc函数。根据用户输入input的值进到switch所对应的case的子语句中并把对应的函数地址传给calc函数。然后在calc函数它的参数是函数指针类型返回类型的是int。由于我们只用在calc函数内部进行打印因此只用在calc函数中左边写上个void类型即可。 接着进入calc函数内部根据用户输入变量x和y的值将函数指针ptr的地址和参数x,y的值调用所对应的函数并把最终计算的结果返回到ret从而实现计算出两个操作数的结果出来。 **好了今天的指针讲解3到这就结束了。 ** **如果觉得博主讲得不错的话欢迎大家支持一下博主谢谢大家~ **