C语言指针进阶:各类型指针变量详解

目录

  • 1. 字符指针变量
  • 2. 数组指针变量
    • 2.1 什么是数组指针变量
    • 2.2 数组指针变量的初始化
  • 3. 二维数组传参的本质
  • 4. 函数指针变量
    • 4.1 函数指针变量的创建
    • 4.2 函数指针变量的使用
    • 4.3 代码分析
      • 4.3.1 typedef 关键字
  • 5. 函数指针数组
  • 6. 转移表


正文开始。

1. 字符指针变量

我们可以通过指针来指向一个字符变量
例如:

int main()
{char ch = 'a';char* pch = &ch;return 0;
}

还可以这样:

int main()
{const char* pstr = "hello world!";return 0;
}

上述第三行代码,很容易让人理解为是把字符串hello world!放到了字符指针pstr里了。但其实这里本质是把字符串hello world!首字符的地址放到了pstr里。

值得注意的是:当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。这也不难理解,某一字符串是独一无二的,没必要为同一个东西再开辟空间。

例如:

#include <stdio.h>
int main()
{char str1[] = "hellw world!";char str2[] = "hellw world!";const char *str3 = "hellw world!";const char *str4 = "hellw world!";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;
}

运行结果:
在这里插入图片描述
从这里就可以看出,几个指针指向同一个字符串的时候,他们会指向同一块内存;但用相同的字符串去初始化不同的数组时,就会开辟处不同的内存块。

2. 数组指针变量

2.1 什么是数组指针变量

在上一篇文章中,我们学习了指针数组,它是存放指针的数组。
类比指针数组,不难理解,数组指针变量就是指向数组的指针变量

下面我们来看两种变量:

//指针数组 - 存放指针的数组
int* p1[10];//数组指针变量 - 指向数组的指针变量
int (*p2)[10];

理解:

  • *的优先级低于[]
  • int* p1[10]p1先与[10]结合,代表数组类型,int *指明数组中元素的类型为整型指针
  • int (*p2)[10]p2先与 * 结合,代表指针类型,int [10]代表所指向对象的类型为存放了十个整型元素的数组

2.2 数组指针变量的初始化

数组指针变量是用来存放数组地址的,我们可以通过取地址操作符&来获取数组地址。

int main()
{int arr[10] = { 0 };//数组指针变量的初始化int (*p)[10] = &arr;return 0;
}

3. 二维数组传参的本质

在我们将一个二维数组传递给一个函数时,我们是这样写的:

#include <stdio.h>void Print(int arr[2][3], int row, int col)
{int i = 0;int j = 0;for(i = 0; i< row; i++){for(j = 0; j < col; j++){printf("%d ",arr[i][j]);}printf("\n");}
}int main()
{int arr[2][3] = {{1,2,3}, {2,3,4}};Print(arr,2,3);return 0;
}

我们再重新理解下二维数组,二维数组定义时,通常像这样int arr[2][3],这里我们可以看作是这样的int (arr[2])[3],即一维数组里面存放的元素是一维数组,这样就把一个二维数组拆分成了两个一维数组来看。例如第一行的一维数组的类型就是int [3],所以第一行的地址的类型就是数组指针类型int(*)[5]。通过这一点,我们不难理解,二维数组传参本质上也是传递了参数,传递的是第一行这个一维数组的地址

那么,我们二维数组传参,也可以写成这样:

#include <stdio.h>void Print(int (*p)[3], int row, int col)
{int i = 0;int j = 0;for(i = 0; i < row; i++){for(j = 0; j < col; j++){printf("%d ", *(*(p + i) + j));//p + i == &arr[i]//*(p + i) == arr[i] 相当于第i行一维数组的数组名//*(p + i) + j == &arr[i][j]//*(*(p + i) + j) == arr[i][j]}printf("\n");}
}
int main()
{int arr[2][3] = {{1,2,3}, {2,3,4}};Print(arr,2,3);return 0;
}

上述代码运行结果:
在这里插入图片描述

4. 函数指针变量

函数指针变量,顾名思义,它就是存放函数地址的指针变量

在学习函数指针变量前,我们要了解到,函数名就是函数的地址。

例如:

#include <stdio.h>
//函数地址 -- &Add
//函数地址 -- Add
int Add(int x, int y)
{return x + y;
}
int main()
{printf("&Add = %p\n", &Add);printf("Add  = %p\n", Add);return 0;
}

上述代码运行结果:
在这里插入图片描述

4.1 函数指针变量的创建

当我们想把函数的地址存放起来,这是就需要用到函数指针变量了,函数指针变量的写法与数组指针非常类似。例如:

#include <stdio.h>int Add(int x, int y)
{return x + y;
}
int main()
{int (*p1)(int, int) = Add;
//或int (*p2)(int x, int y) = Add;
//Add也可写为&Add,它们都代表函数的地址//int    (*p1)     (int, int)
// |       |       ——————————
// |       |            |
// |       |            |
// |       |       p1指向函数的参数类型和个数的声明
// |       函数指针变量名
// p1指向函数的返回类型
//
// p1的类型 -- int (*) (int x, int y)return 0;
}

4.2 函数指针变量的使用

我们可以通过函数指针调用指针指向的函数

#include <stdio.h>int Add(int x, int y)
{return x + y;
}
int main()
{//函数指针变量定义int (*p1)(int, int) = Add;//函数指针变量使用printf("%d\n",(*p1)(3,4));printf("%d\n", p1(5,1));return 0;
}

上述代码运行结果:
在这里插入图片描述

4.3 代码分析

我们来看下面两个代码

代码1:

(*(void (*)())0))();
//void (*)() -- 函数指针类型,所指向对象无形参,无返回值//(void (*)())0 -- 将0强制转换类型为void (*)()类型,即将0地址处存放函数的地址//*(void (*)())0) -- 解引用函数指针//(*(void (*)())0))() -- 调用函数指针变量指向的函数

代码2:

void (*signal(int, void(*)(int)))(int);
//void(*)(int) -- 函数指针变量,指向的函数无返回值,参数类型为int//signal(int, void(*)(int)) -- 函数名为signal的函数,第一个参数类型为int,第二个参数类型为void(*)(int)//void (*signal(int, void(*)(int)))(int) -- 函数signal(int, void(*)(int))的声明,它的返回值类型为void (*)(int)

4.3.1 typedef 关键字

上面我们写的代码二的作用就是声明一个函数,可以看出来,这个函数声明非常的复杂,我们可以通过typedef关键字来重命名类型,简化类型

例如:

typedef int i;
//将 int 重命名为 itypedef unsigned int uint;
//将 unsigned int 重命名为 uinttypedef int(*parr)[5];
//将 int (*)[5] 重命名为 parrtypedef void(*pfun)(int);
//将 void(*)(int) 重命名为 pfun

若要简化代码2,我们可以这样写:

typedef void(*pfun)(int);
//将 void(*)(int) 重命名为pfunpfun signal(int, pfun);

5. 函数指针数组

函数指针数组,就是存放函数指针变量的数组

定义如下:

int (*parr[4])();
//  parr -- 数组名
//  [4] -- 数组元素个数
//  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;
}

使用转移表实现计算器:

#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 (*arr[5])(int, int) = {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 >= 1 && input <= 4){printf("输入操作数:");scanf("%d%d", &x, &y);ret = *(arr[input])(x,y);//调用函数指针数组中的元素printf("ret=%d\n", ret);}else if(input == 0){printf("程序退出\n");}else{printf("输入错误\n");}} while(input);return 0;
}

完。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/3208.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【WP】猿人学12_入门级js

https://match.yuanrenxue.cn/match/12 Fiddler分析发现&#xff0c;所有请求只是 page已经 m不一样 这个m看起来就很像 base64&#xff0c;解码发现确实如此 下面直接构建Python代码&#xff1a; import base64import requestsdef base64_encode_string(input_string):try:#…

搞嵌入式到底属于程序员吗?

搞嵌入式到底属不属于程序员呢&#xff1f;毫无疑问&#xff0c;当然算啊&#xff01;而且我十分赞同另一位朋友所说的&#xff1a;嵌入式程序员是难得的全栈型程序员。尽管嵌入式领域方向众多且繁杂&#xff0c;但他们同样也是会写代码的程序员。 嵌入式行业主要分为硬件和软…

LeetCode-219. 存在重复元素 II

题目描述 给你一个整数数组 nums 和一个整数 k &#xff0c;判断数组中是否存在两个 不同的索引 i 和 j &#xff0c;满足 nums[i] nums[j] 且 abs(i - j) < k 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&…

【写一个简单的service and client(C++)例子——services】

文章目录 1、概要2、引言3、服务开始的地方及步骤3.1 创建工作空间3.2 创建功能包3.3 更新package.xml3.4 编写service 节点3.4.1 添加可执行文件3.4.2 添加 install&#xff08;TARGETS…&#xff09; 部分 3.5 编写client 节点3.5.1 添加可执行文件 3.6 编译运行3.7 运行结果…

高端制造企业生产设备文件管理,怎样保证好用不丢失文件?

高端制造业在市场经济中占据重要角色&#xff0c;在高端制造业企业内部&#xff0c;生产设备又是最关键的一环环&#xff0c;它们不仅负责完成生产任务&#xff0c;同时也会产生大量的文件。这些数据反映了设备的运行状态、生产效率、能源消耗以及产品质量等多个方面&#xff0…

网络协议安全:OSI七层模型分层及作用,数据封装与解封过程,数据传输过程。

「作者简介」&#xff1a;2022年北京冬奥会中国代表队&#xff0c;CSDN Top100&#xff0c;学习更多干货&#xff0c;请关注专栏《网络安全自学教程》 这一章节我们需要知道OSI分哪七层&#xff0c;每层的作用&#xff0c;知道数据在七层模型中是怎样传输的&#xff0c;封包和解…

C/C++ 入门(7)vector类(STL)

个人主页&#xff1a;仍有未知等待探索-CSDN博客 专题分栏&#xff1a;C 请多多指教&#xff01; 目录 一、标准库中的vector 1、了解 2、vector常用接口 二、vector的实现 1、框架 2、构造、析构函数 3、操作函数 三 、问题 1、由于赋值而引起的浅拷贝 2、因为类没…

岭回归(概念+实例)

目录 前言 一、基本概念 1. 引言 2. 岭回归的原理 3. 数学表达式 4. 岭回归的优点 5. 岭回归的局限性 6. 实际应用 二、具体实例 前言 “岭回归”这个词源于英文“Ridge Regression”&#xff0c;是一种用于处理回归分析中多重共线性&#xff08;multicollinearity&am…

Linux-软件安装--jdk安装

jdk安装 前言1、软件安装方式二进制发布包安装rpm安装yum安装源码编译安装 2、安装jdk2.1、使用finalShell自带的上传工具将jdk的二进制发布包上传到Linux2.2、解压安装包2.3、配置环境变量![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/61ba9750e2e34638a39575c5…

电脑自带dll修复在哪里打开?教你如何快速修复dll丢失问题

MSVCP140.dll文件作为Windows操作系统中不可或缺的一环&#xff0c;对众多基于C编译的应用程序的正常运行起着关键作用。在我深入研究和处理与该文件相关问题的过程中&#xff0c;积累了丰富的认知和实践经验。以下是我对MSVCP140.dll文件的总体介绍以及针对其丢失问题的解决方…

C++ 验证一下,你对递归是不是一知半解

公众号:编程驿站 公众号:编程驿站 1. 前言 无递归,不算法。无论怎样强调递归的重要性,都不为过。受限于计算机的思维能力,计算机的计算找答案的过程就是在不停试错、纠正错误的过程,类似于爱迪生发明灯炮。递归能帮助我们在不知道计算边界的情形下试错。 多函数求解过…

echarts树图-实现拓扑图效果

使用echarts树图来实现拓扑图效果&#xff0c;其效果如下&#xff1a; 代码如下&#xff1a; const data {name: XXX公司,children: [{name: 网络主机,children: [{name: 普通路由器,children: [{name: 智能网关},{name: 192.168.1.0/24}]}]},{name: 企业路由器},{name: 三…

MySQL-----多表查询(一)

目录 一.多表关系&#xff1a; 1.1 一对多(多对一)&#xff1a; 1.2 多对多: 1.3 一对一: 二.多表查询概述&#xff1a; 三.连接查询&#xff1a; 3.1内连接&#xff1a; 3.2外连接&#xff1a; 3.3自连接查询&#xff1a; 3.4联合查询&#xff1a; 一.多表关系&…

Vast+产品展厅 | Vastbase G100数据库是什么架构?(1)

Vastbase G100是海量数据融合了多年对各行业应用场景的深入理解&#xff0c;基于openGauss内核开发的企业级关系型数据库。 了解Vastbase G100的架构&#xff0c;可以帮助您确保数据库系统的高效、可靠和安全运行。 “Vast产品展厅”将分两期&#xff0c;为您详细讲解Vastbas…

划分数据集2,详细说明

看完了这个之后划分数据集&#xff0c;训练自己的数据集。-CSDN博客 我再详细说一下自己标注的文件放在什么位置 我发的文件里有这几个文件 在dataset里面有 自己的数据集分为&#xff0c;图片部分和标注文件部分 打开VOCdevkit文件夹 里面有三个文件夹 自己的图片的话&…

(C++) 内类生成智能指针shared_from_this介绍

文章目录 &#x1f601;介绍&#x1f914;类外操作&#x1f605;错误操作&#x1f602;正确操作 &#x1f914;类内操作&#x1f62e;std::enable_shared_from_this<>&#x1f62e;奇异递归模板 CRTP&#xff08;Curiously Recurring Template Pattern&#xff09;&#…

carrier开利触摸屏ICVC控制面板维修CEPL130445

开利离心机19XR空调ICVC显示面板维修CEPL130445-03-R/04-R/02-R 人机界面触摸维修故障有&#xff1a;花屏、白屏、按触摸屏无反应或反应慢(触摸不好)、内容错乱、无背光、背光暗、有背光无字符、不能通信、按键无反应等&#xff08;可更换液晶屏&#xff09;黑屏、对触摸屏触摸…

QML 中的状态

Qt hello – 专注于Qt的技术分享平台 状态描述了当前用户界面样子&#xff0c;QML中一个状态定义了一组属性的改变&#xff0c;并且会在一定条件下被触发。 假设有这么一个场景&#xff0c;红黄绿三个灯&#xff0c;用一个按钮&#xff0c;点击后依次切换三个灯亮起。使用QWi…

js如何获取对象的属性值

获取对象的属性值&#xff0c;有两种方式。 方式一&#xff1a; 对象.属性名 let obj {name:张三,age:23 }; console.log(obj.name); //张三方式二&#xff1a; 对象[属性名] let obj {name:张三,age:23 }; console.log(obj[name]); //张三 两种方式有什么不同&am…

AWR报告采集

一、windows下采集 自动负载信息库&#xff08;Automatic Workload Repository&#xff0c;AWR&#xff09;是在Oracle 10g中被引入的&#xff0c;缺省地被安装到Oracle10g数据库中&#xff0c;用于收集关于该特定数据库的操作统计信息和其他统计信息。AWR的采样工作由后台进程…