地址变量与函数进阶

指针与函数的高级用法

  • 1.数组
  • 2.函数的重载
  • 3.函数的指针类型参数
  • 4.可变参数函数链表
  • 5.函数指针
  • 6.指针函数
  • 7.内联函数
  • 8.总结

在上节中我们简单谈论了指针变量,这节我们就来讨论指针变量的实际应用。

1.数组

相信有一定C语言基础的小伙伴一定很熟悉这个类型。数组可以连续地存储指定类型的多个元素,并通过下标找到相应的元素:

# include<stdio.h>
# include<iostream>
using namespace std;
int main()
{int a[4]; // 声明一个可以装下4个int类型数字的数组for(int i=0;i<4;i++) // 为数组赋值{a[i]=i*i; }
}

在这个例子中,我们的a就是一个数组的名称,我们用一个表格形象表示存储的现状:

0149
a[0]a[1]a[2]a[3]

赋值结束后,我们可以通过使用下标索引的方式找到其存储内容,比如我想要找到9就可以写成a[3]:

for(int i=0;i<4;i++) // 打印数组
{cout<<a[i]; 
}

看起来和python的列表类型很像,但在Python中,对标C++数组的类型叫做紧凑型数组,并且C++中的数组是不可以用复数作为索引的。我们在声明中写下的**int a[4]**是申请了最多可以装三个int数字的数组,但是由于数组的索引是从0开始的,所以我们在使用数组时,最多只能写到a[3],这一点需要额外注意,因为有些编译环境不会对下标超过索引范围给出错误提示或警告。
现在,我们看看数组类型在存储地址上有什么特点:

int a[3];
for(int i=0;i<3;i++) // 打印a[i]元素所在的地址
{cout<<&a[i]<<" ";cout<<a+i<<endl;
}// 输出为:0x61fe10 0x61fe10
//        0x61fe14 0x61fe14
//        0x61fe18 0x61fe18

从输出中可以看出,a[0],a[1],a[2]的地址是一个公差为4的等差数列,而一个整型数据所占空间刚好为4字节,这说明数组类型是连续的存储空间,每个元素的起始位置紧挨着上个元素的终止位置。还有一点需要注意,如果我们直接打印a,会打印出a数组的首地址,如果对这个地址进行加一操作,它会变成数组下一个元素的存储地址。既然如此,我们就可以用解引用的方式索引到数组的元素:

nt a[3];
for(int i=0;i<3;i++) // 打印a[i]元素所在的地址
{cout<<*(a+i)<<" ";
}
// 输出为:0 1 4 9

2.函数的重载

C语言中,定义一个重名变量会产生编译错误,定义重名函数也是如此;在python中,如果有两个函数名称相同,那么后定义的函数会将先定义的函数覆写,被覆写的函数将无法被调用。但是在C++中,定义同名函数并不会报错,甚至可以通过重载函数的方式避免同名函数无法被引用。这是由于C++识别函数不止依赖函数名称,还会依赖参数列表。简单地说,多个同名函数,只要参数的数量或参数类型有所不同,就会被识别成不同函数,每个函数都可以被调用。但需要注意的是,如果两个同名函数仅仅只有返回值类型或(和)形参名称不同是不可以构成重载的。下面我们举个例子:

void test(int a,char b)
{cout<<a<<" "<<b<<endl;}
void test(char a,int b)
{cout<<a<<" "<<b<<endl;}
void test(int a,char b,double c)
{cout<<a<<" "<<b<<" "<<c<<endl;}
// int test(int num,char letter){} // 无法重载,会报错
int main()
{test(1,'a');test('a',1);test(1,'a',1.1);
}
// 输出为:1 a
//        a 1
//        1 a 1.1

函数重载的设定很大程度上方便了代码的书写。下面我们再来学些难点,并用上函数重载。

3.函数的指针类型参数

上节中我们说到过,用C++中使用return只能返回一个值。那么如果我希望自定义函数中修改的多个值在主函数中依然可以使用,又该怎么办呢?想要回答这个问题我们就需要首先弄清楚形参是如何工作的。
形式参数本质上是对实际参数的拷贝,即申请一个同样大小的空间,将实际参数复制进去,从而让我们在函数体内也能使用实参的值、但是形式参数是完全独立于实际参数的。
在这里插入图片描述

形式参数之所以不能再函数体外部使用,是因为形式参数存储的内容会在离开该函数时被销毁了。但如果我们使用地址值作为形式参数,那么通过形式参数取内容的方式就可以直接找到该形式参数对应地址的内容。如果在这基础上修改了这个地址内容:
在这里插入图片描述

这样一来,尽管形式参数被销毁,我们依然可以通过实际参数找到这个物理地址,读到里面的存储内容,是不是就完成了在主函数中可以使用形式参数修改后的值这个任务了呢?我们来尝试一下:

void add(int a,int b,int* sum)
{*sum=a+b;}
int main()
{int sum;add(1,2,&sum);cout<<sum<<endl;
}
// 输出为:3

这样,我们就可以直接修改主函数中sum参数的数值了,不需要借助return。还是挺神奇的吧~

4.可变参数函数链表

在python中,我们可以借助元组完成传入未指定数量的参数,但是在C++中想要输入任意数量的参数却不是那么简单。我们需要一个连续的空间,并且需要告知计算机开拓的空间大小。听起来很复杂,但也是有固定的书写套路。当我们需要的:

// 首先,我们引用一个头文件:
#include<stdarg.h>
void out(int num,...) // 定义一个使用可变参数的函数,其中第一个参数代表希望传入的参数数量// ...代表在需要接收的参数数量和类型未知
va_list zerro_loc; // 定义一个列表,变宏参数为可变参数。可以简单理解成这里寻找到// num的地址,并可以根据这个地址继续向后开拓空间
va_start(zerro_loc,num); // 列表得以初始化
int val;
for(int i=0;i<num;i++) // 用首参数num作为列表长度标记{val=va_arg(zerro_loc,int); // 当前地址位置向后移动int字节个单位,// 而后将对应长度的地址存储内容取出,取出内容按照int类型的处理方式处理并赋值给valcout<<val<<' '; // 打印接收到的内容va_end(zerro_loc); // 用于栈归位}

如果看了注释也不能理解,可以仅仅是记住这种用法。这种函数链表只能处理可变参数均为int型的任务。并且存在隐患。我们来试着调用一下:

int main()
{out(5,1,2); // 多出的空间也会有储存内容out(4,1,2,3,4); out(5,1,2,3,4,5,6); // 丢失6
}

运行的结果为:
在这里插入图片描述
这种可变参数用起来比较蹩脚,如果我需要处理参数为不同类型且数量未知的任务,又该怎么定义呢?

template <typename T, typename... Args> // 定义参数链表
void printArgs(T con, Args... ar) 
{cout<<con<<endl; // 具体任务的执行算法,这里的具体任务为打印当前参数printArgs(ar...); // 递归调用
}
void printArgs() // 重载printArgs函数,当参数列表为空时终止递归
{cout <<"\n"<<"is the end"<<endl;
}
int main() {printArgs(1,"hello",3.14,'Z'); // 每调用该函数一次,参数列表的第一个元素都会// 被记录在函数中并销毁return 0;
}

运行结果为:
在这里插入图片描述
向上面一样,如果大家不理解具体实现,可以把他当成是一个模板记住就好啦。

5.函数指针

这里应该算得上是C++的深水区了,坚持到这里的小伙伴都是好样的。
在C++里,我们可以把函数本身当做是一种数据类型,既是数据类型,就可以搭配指针变量。首先我们来看一段代码:

int sub(int a,int b)
{return a-b;}
int add(int a,int b)
{return a+b;}
int main()
{int (*fp1)(int,int),(*fp2)(int,int); // 声明两个函数指针fp1和fp2fp1=sub;fp2=add;
}

以上就是C++函数指针的定义和赋值。从这段代码中不难发现,其实函数名本身就是个地址变量,而调用函数可以具象成将参数塞到函数的地址里,并执行函数内容。
我们对以上的代码稍作修改:

int sub(int a,int b)
{return a-b;}
int add(int a,int b)
{return a+b;}
int main()
{int (*fp[2])(int,int); // 声明一个函数指针数组fp[0]=sub;fp[1]=add;cout<<fp[0](1,2)<<endl;    // fp[0](1,2)等价于sub(1,2)cout<<(*fp[1])(1,2)<<endl; //(*fp[1])(1,2)等价于fp[1](1,2)// 这说明函数指针的内容就是函数指针地址cout<<reinterpret_cast<void*>(fp[0])<<endl; // 想要输出函数指针的地址值,需要现将函数指针转换成空指针
}

让我们来看一下输出吧:
在这里插入图片描述

但这样看来,函数指针也就只是给函数换了个名字而已。那么函数指针有什么妙用吗?

6.指针函数

指针函数指的是返回值类型为指针的函数。由于指针已经被我们所熟悉,这里直接给大家上个例子:

int* add(int a,int b)
{a+=b;// return &a; // 必须事先声明指针类型并存储c的地址才能返回,不可以直接返回c的地址值int *p=&a;return p;
}
int main()
{cout<<*add(1,2)<<endl;
}

在上一节中,我提到过函数可以被理解成一种数据类型。既然是数据类型,就可以加个*成为对应的指针类型。那么指针函数的返回值可以是函数指针吗?当然是可以的:

int add(int a, int b) 
{return a + b;}
int sub(int a,int b)
{return a-b;}
int (*getAddFunction(bool a))(int, int) 
{if (a){return add;} // 如果指针函数形参为true则返回add函数地址else  // 如果指针变量形参为假则返回sub函数地址{return sub;}
}int main() {int (*fp1)(int, int),(*fp2)(int, int);fp1=getAddFunction(1); // fp1=addfp2=*getAddFunction(0); // fp2=subint result1 = fp1(3, 4);int result0 = fp2(3,4);cout<<result1<<endl;cout<<result0<<endl;return 0;
}

这就是函数指针的神奇效果啦,在实战中,这样的操作可以解决很多难题的,不懂的小伙伴建议多看几遍。

7.内联函数

使用inline修饰的函数就是内联函数。如果我们的函数声明和定义分开写,那么inline就需要写两次。其标准写法如下例:

inline int add(int a,int b);
inline int add(int a,int b)
{return a+b;}

内联函数不算C++的重点内容,我们只需稍作了解即可。

8.总结

本节我们介绍了数组,并对利用指针的知识对数组进行了分析;介绍了函数的重载,这是个C++中相当好用的功能之一。此外,我们还介绍了给函数传递未知数量和类型的参数的方法,以及函数要如何使用这些参数。函数指针和指针函数是本节最大的难点,函数指针是一种特殊的指针类型,而指针函数则是返回值类型为指针的函数。函数指针和指针函数搭配起来会有意想不到的效果。可以说,到此为止面对过程编程的主要难点都被我们攻克了,下一节我们一起开始面对对象编程!

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

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

相关文章

【C++期末编程题题库】代码+详解18道

适合期末复习c看&#xff0c;或者刚入门c的小白看&#xff0c;有的题会补充知识点&#xff0c;期末复习题的代码一般比较简单&#xff0c;所以语法上没那么严谨。本文所有题目要求全在代码块的最上面。 目录 1、设计复数类 2、设计Computer类 3、实现相加的函数模板 4、圆类…

wait 和 notify 这个为什么要在synchronized 代码块中?

一个工作七年的小伙伴&#xff0c;竟然不知道” wait”和“notify”为什么要在 Synchronized 代码块中 。 好吧&#xff0c;如果屏幕前的你也不知道&#xff0c;请在公屏上刷”不知道“。 对于这个问题&#xff0c;我们来看看普通人和高手的回答。 一、问题解析 1. wait 和 n…

线程同步之:QMutex\QMutexLocker

1、基于互斥量的线程同步类QMutex 2、lock() 与 unlock()必须配对使用。 2.1 lock() unlock() 2.2 tryLock() unlock() 3、QMutexLocker()是另一个简化了互斥量处理的类。在QMutexLocker实例变量的“生命周期”内的代码段 得到保护。 QMutexLocker的构造函数接受要给互斥量…

yolov5旋转目标检测-遥感图像检测-无人机旋转目标检测(附代码和原理)

目前&#xff0c;无人机技术的快速发展带来了遥感图像处理领域的革命性改变。然而&#xff0c;由于无人机在飞行时可能会出现旋转的情况&#xff0c;因此对于旋转目标的检测也成为了一个重要的问题。针对这个问题&#xff0c;yolov5可以提供一种高效的解决方案。 以下是介绍的分…

秋招复习之哈希表

目录 前言 1 哈希表 哈希表常用操作 哈希表简单实现 哈希冲突与扩容 2 哈希冲突 链式地址 开放寻址 线性探测 平方探测 多次哈希 编程语言的选择 3 哈希算法 哈希算法的目标 哈希算法的设计 常见哈希算法 数据结构的哈希值 总结 前言 秋招复习之哈希表。 1 哈希表 「哈希表 h…

万界星空科技云MES,助力客户快速构建数字工厂

一、MES发展趋势 1、定制化趋势 工业2.0、3.0的技术已较为成熟&#xff0c;部分制造业水平较为发达的国家已经率先进入以网络化、智能化为代表的工业4.0发展阶段,MES作为制造业规划层随着物联网等持续发展&#xff0c;为适应定制化时代&#xff0c;整体技术模块化、服务化将重…

防蓝光护眼台灯哪个牌子好?2024护眼灯315合格产品

最近身边的宝妈们都来问我这个已有两个娃的老司机&#xff0c;刚上小学就是近视了&#xff0c;买什么台灯给家里孩子能保护视力&#xff0c;经过小学门口时&#xff0c;真的是戴眼镜的小朋友占多数&#xff0c;搜索了我国的近视数据&#xff0c;中国的人口有14亿人左右&#xf…

Chromedriver 下载和安装指南

1. 确定Chrome浏览器版本 首先&#xff0c;在谷歌浏览器中找到当前版本信息。 打开“设置”&#xff0c;点击“关于谷歌”即可看到版本号。确保后续下载的Chromedriver版本与Chrome浏览器版本一致。或者直接跳转网页地址&#xff1a;chrome://settings/help 2. 下载Chromedri…

ShardingSphere-JDBC初探

引言 为什么使用分库分表&#xff1f; 数据量太大单表放不下&#xff0c;并且公司不希望切换产品&#xff0c;可选的方案不多&#xff0c;ShardingSphere就是不错的选择。 切换产品指的是换成es、clickhouse、hbase这种支持大数据&#xff0c;试想一下切换产品对整个项目的改…

Linux第18步_安装“Ubuntu系统下的C语言编译器GCC”

Ubuntu系统没有提供C/C的编译环境&#xff0c;因此还需要手动安装build-essential软件包&#xff0c;它包含了 GNU 编辑器&#xff0c;GNU 调试器&#xff0c;和其他编译软件所必需的开发库和工具。本节用于重点介绍安装“Ubuntu系统下的C语言编译器&#xff27;&#xff23;&a…

电子化学品,预计2025年会增长到4302亿美元

电子化学品市场是一个庞大的细分市场&#xff0c;它包括了广泛的化学品种类&#xff0c;如涂料、塑料、精细化学品、农药和医药等。这个市场的发展相当迅速&#xff0c;下面我们将从全球市场和中国市场两个方面对其发展趋势进行分析。全球市场分析&#xff1a; 从全球市场的角度…

Redis命令---List篇

目录 1.Redis Lindex 命令 - 通过索引获取列表中的元素简介语法可用版本: > 1.0.0返回值: 列表中下标为指定索引值的元素。 如果指定索引值不在列表的区间范围内&#xff0c;返回 nil 。 示例 2.Redis Rpush 命令 - 在列表中添加一个或多个值简介语法可用版本: > 1.0.0返…

[C#]利用opencvsharp实现深度学习caffe模型人脸检测

【官方框架地址】 https://github.com/opencv/opencv/blob/master/samples/dnn/face_detector/deploy.prototxt 采用的是官方caffe模型res10_300x300_ssd_iter_140000.caffemodel进行人脸检测 【算法原理】 使用caffe-ssd目标检测框架训练的caffe模型进行深度学习模型检测 …

ubuntu 22.04 快速安装Odoo17.0详记

序言:时间是我们最宝贵的财富,珍惜手上的每个时分 如果为阿里云或者腾讯云&#xff0c;第一步可以忽略 1.更换阿里云源 第一步&#xff1a;先备份下原始源 sudo cp /etc/apt/sources.list /etc/apt/sources.list.backup 第二步&#xff1a;修改文件 sudo cp /etc/apt/sou…

最常用的自动化测试框架汇总

在开始学习python自动化测试之前&#xff0c;先了解目前市场上的自动化测试框架有哪些&#xff1f; 随着技术的不断迭代更新&#xff0c;优胜劣汰也同样发展下来。从一开始工具型自动化&#xff0c;到现在的框架型&#xff1b;从一开始的能用&#xff0c;到现在的不仅能用&…

app广告变现——广告预加载机制,提升用户体验

通过广告预加载&#xff0c;开发者可以避免在向用户显示广告时出现延迟。 应用在程序启动时需要请求网络&#xff0c;加载资源会需要等待时间&#xff0c;如果在等待过程中没有及时给用户展现画面或反馈&#xff0c;用户很可能会因为等待时间过长而推出应用。广告预加载在此时…

「解析」Windows 如何优雅使用 Terminal

所谓工欲善其事必先利其器&#xff0c;对于开发人员 Linux可能是首选&#xff0c;但是在家学习的时候&#xff0c;我还是更喜欢使用 Windows系统&#xff0c;首先是稳定&#xff0c;其次是习惯了。当然了&#xff0c;我还有一台专门安装 Linux系统的小主机用于学习Linux使用&am…

从技术角度分析:HTTP 和 HTTPS 有何不同

网络安全问题正变得日益重要&#xff0c;而 HTTP 与 HTTPS 对用户数据的保护十分关键。本文将深入探讨这两种协议的特点、工作原理&#xff0c;以及保证数据安全的 HTTPS 为何变得至关重要。 认识 HTTP 与 HTTPS HTTP 的工作原理 HTTP&#xff0c;全称超文本传输协议&#xf…

2024年我国网络安全发展形势展望

2023年&#xff0c;我国网络安全政策法规陆续出台&#xff0c;网络安全与数据安全产业发展势头强劲&#xff0c;网络安全形势整体向好。展望2024年&#xff0c;世界各国在网络空间中的竞争将变得愈发激烈&#xff0c;我国网络安全领域的法律法规将不断完善&#xff0c;数据安全…

超好玩的烧脑当当狸智能五子棋,锻炼孩子的超级大脑

数码时代&#xff0c;儿童沉迷于电子设备和网络游戏已经成为了常态 作为家长&#xff0c;我们都希望能够找到一种&#xff0c;既有趣又益于孩子成长发展的娱乐方式 ✨「当当狸智能五子棋」&#xff0c;儿童智力成长的好帮手学玩一体锻炼儿童思维 不仅是一种新型休闲娱乐的方…