地址变量与函数进阶

指针与函数的高级用法

  • 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类型的处理方式处理并赋值给val
        cout<<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和fp2
	fp1=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=add
    fp2=*getAddFunction(0); // fp2=sub
    int 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++中相当好用的功能之一。此外,我们还介绍了给函数传递未知数量和类型的参数的方法,以及函数要如何使用这些参数。函数指针和指针函数是本节最大的难点,函数指针是一种特殊的指针类型,而指针函数则是返回值类型为指针的函数。函数指针和指针函数搭配起来会有意想不到的效果。可以说,到此为止面对过程编程的主要难点都被我们攻克了,下一节我们一起开始面对对象编程!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Node.js(四)-express

1. 初识express 1.1 express简介 1.1.1 什么是express 官方&#xff1a;Express是基于Node.js平台&#xff0c;快速、开放、极简的web开发框架。 通俗&#xff1a;Express的作用和Node.js内置的http模块类似&#xff0c;是专门用来创建web服务器的。 express的本质&#xff1…

KNN 分类(选择最佳的 K 值,并可视化模型精度与 n_neighbors 的关系)

import matplotlib.pyplot as plt from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier# 导入乳腺癌数据集 cancer load_breast_cancer()# 划分训练集和测试集 X_tra…

函数作用域和块级作用域

(1)Es5之前的作用域 在Es5之前只有全局作用域和函数作用域。 (1)全局作用域 范围:在winodw全局里面都生效 例子: for(var i0;i<5;i){}console.log(window.ii); 返回的结果是:True (2)函数作用域 范围:在这个函数里面生效 例子: function fn(b) {console.log(b);}con…

如何配置Zabbix告警邮件通知并基于GPT提供解决方案?

一、概述 时间来到2023年末&#xff0c;距离Open AI发布GPT-3.5&#xff0c;首次向公众推出ChatGPT已经整整过去了一年。如今&#xff0c;以ChatGPT为代表的人工智能模型已然被应用众多领域&#xff0c;当然也包括IT运维。在IT运维中&#xff0c;通过对接运维监控平台&#xff…

类和对象之拜访对象村

类和对象 1.面向对象的初步认知 ————&#xff08;进入对象村&#xff09;1.1为什么称为对象村1.2面向对象和面向过程 2.类的定义和使用————&#xff08;这个陌生人是谁&#xff1f;&#xff09;2.1简单认识类2.2类的定义格式2.3定义一个狗类 3.类的实例化之new一个“对…

Plugin ‘org.springframework.boot:spring-boot-maven-plugin:‘ not found

IDEA创建spring boot项目的时候&#xff0c;pom.xml 文件中spring-boot-maven-plugin报红。 解决方法&#xff1a; 给spring-boot-maven-plugin指定版本 原因&#xff1a; idea自动找的插件版本是其他版本的&#xff0c;和自己设置的不同&#xff0c;导致idea在本地maven仓库…

深入解析JMeter响应断言功能

JMeter 是一个功能强大的性能测试工具&#xff0c;它可以模拟大量用户并发访问网站或应用程序&#xff0c;以测试其性能和稳定性。在进行性能测试时&#xff0c;我们需要对响应结果进行断言&#xff0c;以确保应用程序或网站的功能和性能符合预期。 在 JMeter 中&#xff0c;响…

Spring之强大的DefaultListableBeanFactory

系列文章目录 如何查看类继承结构参考这里 文章目录 系列文章目录一、DefaultListableBeanFactory的类继承实现结构二、实现接口 一、DefaultListableBeanFactory的类继承实现结构 二、实现接口 AliasRegistry&#xff1a;支持别名功能&#xff0c;一个名字可以对应多个别名B…

SpringBoot整合sentinel

1、引入依赖 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> 2、 配置文件添加 spring:cloud:sentinel:transport:dashboard: ip:8858 项目重启&#x…

[PyTorch][chapter 9][李宏毅深度学习][CNN]

前言&#xff1a; 卷积神经网络&#xff08;Convolutional Neural Networks&#xff09;是一种深度学习模型或类似于人工神经网络的多层感知器&#xff0c;常用来分析视觉图像。卷积神经网络的创始人是着名的计算机科学家Yann LeCun&#xff0c;目前在Facebook工作&#xff0c;…

Time-series forecasting with deep learning: a survey

人们开发了许多深度学习架构来适应不同领域的时间序列数据集的多样性。在本文中&#xff0c;我们调查了一步前进和多水平时间序列预测中使用的常见编码器和解码器设计&#xff0c;描述了如何将时间信息纳入每个模型的预测中。接下来&#xff0c;我们重点介绍混合深度学习模型的…

数据结构【排序篇】

数据结构【排序篇】 文章目录 数据结构【排序篇】前言为什么突然想学算法了&#xff1f;为什么选择码蹄集作为刷题软件&#xff1f; 目录一、插入排序二、交换排序三、 选择排序四、归并排序和基数排序 结语 前言 为什么突然想学算法了&#xff1f; > 用较为“官方”的语言讲…

SpringIOC之support模块DefaultMessageSourceResolvable

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

【ITK库学习】使用itk库进行图像配准:变换Transform(三)

目录 1、itkAffineTransform 仿射变换2、itkBSplineDeformableTransform B样条可变形变换 1、itkAffineTransform 仿射变换 该类实现向量空间的仿射变换&#xff08;例如空间坐标&#xff09; 此类允许定义和操作n维仿射空间&#xff08;及其关联的向量空间&#xff09;对其自…

QT C++调用python传递RGB图像和三维数组,并接受python返回值(图像)

目的&#xff1a; 用QT调用python代码&#xff0c;将QT读取的图像(Qimage)作为参数传入python中&#xff0c;将QT的三维数组作为参数传递给python&#xff0c;python接收QT传入的图像进行计算&#xff0c;将结果返回给QT并显示。 一 .pro 头文件的配置&#xff0c;和lib库的…

在 Mac 上轻松安装和配置 JMeter

Apache JMeter 是一个开源的负载测试工具&#xff0c;可以用于测试静态和动态资源&#xff0c;确定服务器的性能和稳定性。在本文中&#xff0c;我们将讨论如何下载和安装 JMeter。 安装 Java&#xff08;已安装 Java 的此步骤可跳过&#xff09; 要安装 Java&#xff0c;请按…

数字孪生与边缘计算的结合

数字孪生与边缘计算的结合可以在物理实体附近进行实时数据处理和决策&#xff0c;从而提高响应速度、降低延迟&#xff0c;并有效地利用边缘资源。以下是数字孪生在边缘计算中的一些应用&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开…

JavaWeb——后端之SpringBoot基础知识

2. SpringBoot 官网&#xff1a;https://spring.io/ Spring全家桶&#xff1a;Spring已经形成了一种开发生态圈&#xff0c;其提供的若干子项目分别用于完成特定的功能 Spring Boot简化了Spring Framework&#xff0c;不用底层实现那么配置繁琐&#xff0c;可以快速构建应用…

【Java EE初阶八】多线程案例(计时器模型)

1. java标准库的计时器 1.1 关于计时器 计时器类似闹钟&#xff0c;有定时的功能&#xff0c;其主要是到时间就会执行某一操作&#xff0c;即可以指定时间&#xff0c;去执行某一逻辑&#xff08;某一代码&#xff09;。 1.2 计时器的简单介绍 在java标准库中&#xff0c;提供…

ChatGPT怎么帮我上班的

1.解放生产力 1&#xff09;标准格式&#xff0c;完美输出。GPT对于公文等具有一定标准格式的文件&#xff0c;可以进行完美仿写&#xff0c;随随便便以假乱真那都是小菜一碟&#xff0c;这对于经常要开展规范成文的人来说&#xff0c;简直就是个福音&#xff0c;只要前期调教…