当前位置: 首页 > news >正文

深度理解指针(3)

hello,各位小伙伴们在上期的最后我们了解到了指针数组,是用来存储指针的数组。这期我们将会学习深度理解指针(3)有关指针的内容,仍然与数组分不开,让我们踏上此次列车来进行新的旅途吧!

目录

 字符指针

经典例题

数组指针

二维数组传参本质 

 函数指针变量的创建

小试牛刀 

typedef关键字

函数指针数组


 字符指针

我们通常会这样使用字符指针:

#include<stdio.h>
int main ()
{char ch = 'a';char*pch = &ch;&ch = 'w';return 0;
}

但是还会有一种特殊的使用方式:

#include<stdio.h>
int main ()
{char*ptr = "hello liyaoyao";printf("%s",ptr);
}

 这是什么意思呢?如果难以理解不妨看一下下面与之类似的:

#include<stdio.h>
int main ()
{char arr[] = "hello liyaoyao";char*ptr = arr;//arr和ptr的类型相同,同时指向的对象也相同可以互相替换//printf("%s",arr);printf("%s",ptr);
}

 看来这段代码之后也许小伙伴们心里已经清楚了。char*ptr = "hello liyaoyao";是将字符串的首地址赋给ptr,同时后面为常量字符串是不可以被修改的。但如果以创建一个字符数组的形式,字符串的内容可以被修改,小伙伴们可以试试哦。(程序会崩掉)!

经典例题

#include <stdio.h>
int main()
{char str1[] = "hello liyaoyao.";char str2[] = "hello liyaoyao.";const char* str3 = "hello liyaoyao.";const char* str4 = "hello liyaoyao.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

 想一想结果会是什么呢?答案会在最后揭晓!

数组指针

存放整型地址的变量为int*,存放字符地址的变量为char*,那么存放数组地址的变量为什么呢?

--------数组指针:存放的是数组的地址,能够指向数组的指针变量。

数组指针变量的格式为:

#include<stdio.h>
int main ()
{int arr[10] = { 0 };int (*parr)[10] = &arr;
}//再次提醒:arr为数组首元素地址,&arr[0]也为数组首元素地址,arr为整个数组的地址。

解释:p先跟*结合说明p是一个指针变量,然后指针指向的是大小为10的整型数组,所以p是一个指针,指向一个数组,叫做数组指针变量。

二维数组传参本质 

在前期的扫雷游戏中我们就已经大概了解到了二维数组的传参问题。通常情况下我们会使用下标的形式对二维数组进行访问。

#include<stdio.h>
void Print(int arr[][5],int row,int col)
{int i = 0;for(i = 0;i < 3;i++){int j = 0;for(j = 0;j < 5;j++){printf("%d",arr[i][j]);}}
}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);
}

在函数实参部分arr是二位数组的数组名,表示二维数组首元素地址,我们不禁会想首元素到底是谁,地址又是哪个呢?

我们可以这样理解:二维数组其实是一维数组的数组,那二维数组的首元素地址应该是第一行数组的地址。根据前面介绍的数组指针,我们可以通过修改形参部分来对其进行接收。

函数定义部分:

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("%d",(*(p+i))[j]);printf("%d",p[i][j]);} }
}

 函数指针变量的创建

根据前面的学习我们了解到变量有地址,数组有地址,那么函数是否存在地址呢?

答案是存在!

#include<stdio.h>
int Sub(int x,int y)
{return x - y;
}
int main ()
{printf("%p\n",Sub);printf("%p\n",&sub);
}//两者的输出结果是相同的

对函数取地址有两种使用方式:1,函数名作为函数地址     2,取地址函数名作为函数的地址。

创建函数指针变量与数组指针变量的创建类似。

//……
int function(int x,int y)
{…………
}
int main()
{int (*p)(int x,int y) = &function;
}

 函数指针变量的使用:

//……
int function(int x,int y)
{///
}
int main ()
{int(*p)(int x,int y); int ret = function(2,3);ret = (*p)(2,3);ret = p(2,3);
}//三种使用方式均可以

小试牛刀 

 让我们来看两个有意思的代码并尝试理解它:

1、

#include<stdio.h>
int main ()
{(*(void(*))0)();return 0;
}

我们试着从0开始入手,0的前面有一个括号,括号里面为void(*),在一个数字的前面加上括号就是强制类型转化,即将0强制类型转化成void(*)类型,即0从一个整型转化成了函数的地址(变成了函数指针变量),同时这个函数没有参数。

该段代码出自C陷阱与缺陷。 

2、 

#include<stdio.h>
int main ()
{void(*signal(int , void(*)(int)))(int);return 0;
}

这句代码是函数的声明,signal是函数的名字,signal函数有两个参数一个是int类型,另一个是void(*)(int)类型,即函数指针类型,指向的参数类型是int类型,返回值是void。同时该函数指针指向的参数类型是int类型,返回值是void。

typedef关键字

typedef旨在类型的重定义,将复杂的参数类型定义为简单类型。

//…………
//指针的重定义
typedef int* prt;
int a = 0;
//int *pa = &a;
ptr pa = &a;//…………
//指针数组的重定义
typedef int *ptr_t[10] ;
//int *arr[10];
ptr_t arr;//…………
//数组指针的重定义
typedef int (*ptr_t1)[2];
int arr[2] = {0};
//int (*parr)[2] = &arr;
ptr_t1 parr[2] = &arr;

这样重定义虽然在书写的时候变得简单了,但对于变量类型的理解做出了阻碍,在具体写代码时要看情况合理使用。

函数指针数组

前面我们讲到了指针数组(整型指针数组、字符指针数组),下面我们来学习一下函数指针数组。

定义: 

函数的地址存到一个数组中,那这个数组就叫函数指针数组。

 

#include<stdio.h>
int Add(int x,int y)
{//
}
int Sub(int x,int y)
{//
}
int main ()
{int (*ptr[2])={Add,Sub};return 0;
}

 函数指针数组的用途:转移表

模拟实现一下计算器的+、-、*、\的功能。

通常情况下我们会这样使用:

#include<stdio.h>
Print_menu()
{printf("**************************************\n");printf("*******   1.  add      2.sub    ******\n");printf("*******   3.  mul      4.div    ******\n");printf("************     0.exit     **********\n");printf("**************************************\n");}
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;
}int main()
{Print_menu();int input = 0;int x = 0;int y = 0;do{int ret = 0;printf("请选择>");scanf("%d", &input);switch (input){case 1:printf("请输入两个整数:");scanf("%d %d", &x, &y);ret = Add(x, y);printf("%d", ret);break;case 2:printf("请输入两个整数:");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("%d\n", ret);break;case 3:printf("请输入两个整数:");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("%d\n", ret);break;case 4:printf("请输入两个整数:");scanf("%d %d", &x, &y);ret = Div(x, y);printf("%d\n", ret);break;case 0:break;default:printf("输入错误,请重新输入:");}} while (input);return 0;
}

但我们发现在switch语句中每一种情况的格式是一样的,只有函数不一样,多次书写会造成代码冗余的情况,我们可以使用函数指针数组来进行简化!

主函数部分:

int main()
{Print_menu();int input = 0;int x = 0;int y = 0;int ret = 0;int (*ptr[5])(int x, int y) = { 0,Add,Sub,Mul,Div };do{printf("请选择>");scanf("%d", &input);if (input >= 1 && input <= 4){printf("请输入两个整数:");scanf("%d %d", &x, &y);ret = (*ptr[input])(x, y);printf("%d\n", ret);}else if (input == 0){printf("计算结束!");}else{printf("输入错误请重新输入!\n");}} while (input);return 0;
}

 使用函数指针数组可以将这几个传参相同的函数地址存放在一个数组中,在每次使用时,直接调用即可!

 

经典例题答案公布:

str1 and str2 are not same
str3 and str4 are same

这里的判断部分比较的都是首元素地址,str1和str2都是创建一个新的数组,与数组内容无关,str1与str2在内存中的地址不同所以会输出not same。str3与str4都是字符指针变量,将一常量赋给str1和str2,因为常量内容是一样的,所以不需要在额外申请一段空间来进行存储,所以str1与str2所代表的首元素地址是相同的。

 OK这期就到这里啦!下期我们继续学习指针的内容有关回调函数和qsort函数的内容,拜拜。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 51单片机控制可控硅调温程序
  • webpack--webpack的启用
  • static关键字
  • pyannote源码阅读(一)
  • 英国政府停止使用人工智能
  • Linux 命令重定向介绍
  • electron 两个渲染进程之间通信
  • 教育行业,等保之重
  • 2534. 乘方 [CSP-J 2022]
  • WIN/MAC 图像处理软件Adobe Photoshop PS2024软件下载安装
  • Android开发语言Kotlin简介
  • 硬件寄存器的简单理解
  • http重要的状态码【精简版】
  • 前端理论总结(js)——原型链 // 原型 // 浅拷贝和深拷贝
  • 爬取央视热榜并存储到MongoDB
  • 《深入 React 技术栈》
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • 2017-09-12 前端日报
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • Dubbo 整合 Pinpoint 做分布式服务请求跟踪
  • Java 实战开发之spring、logback配置及chrome开发神器(六)
  • Javascript 原型链
  • Leetcode 27 Remove Element
  • Netty 框架总结「ChannelHandler 及 EventLoop」
  • Node项目之评分系统(二)- 数据库设计
  • React Transition Group -- Transition 组件
  • Spring-boot 启动时碰到的错误
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • webpack项目中使用grunt监听文件变动自动打包编译
  • Yeoman_Bower_Grunt
  • 道格拉斯-普克 抽稀算法 附javascript实现
  • 关于springcloud Gateway中的限流
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 理清楚Vue的结构
  • 扑朔迷离的属性和特性【彻底弄清】
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 微信小程序设置上一页数据
  • 在Docker Swarm上部署Apache Storm:第1部分
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • ​补​充​经​纬​恒​润​一​面​
  • #中的引用型是什么意识_Java中四种引用有什么区别以及应用场景
  • $.extend({},旧的,新的);合并对象,后面的覆盖前面的
  • (1) caustics\
  • (11)MSP430F5529 定时器B
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (C++)栈的链式存储结构(出栈、入栈、判空、遍历、销毁)(数据结构与算法)
  • (floyd+补集) poj 3275
  • (多级缓存)多级缓存
  • (分布式缓存)Redis持久化
  • (附源码)springboot金融新闻信息服务系统 毕业设计651450
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (力扣)循环队列的实现与详解(C语言)
  • (力扣记录)235. 二叉搜索树的最近公共祖先
  • (三) prometheus + grafana + alertmanager 配置Redis监控