C++primer(第五版)第六章(函数)

news2024/11/25 22:44:04

6.1函数基础

函数可以理解成是可以循环使用的代码块.函数的定义通常包含以下部分:返回类型,函数名称,参数列表,函数体.

//以下是一个返回值为int类型,有两个参数的函数的定义
int add(int a,int b){
    return a+b;
}

我们用调用运算符( )来执行函数,在运算符内传入参数.调用时会打断主调函数(正在执行的函数),被调函数(我们调用的函数)开始执行.

上面例子的 int a 和 int b 是函数的形式参数(形参),下面例子的20和30是实际参数(实参)

int a = add(20,30);               //调用add函数,并传入20和30
std::cout << a << std::endl;      //输出50

 执行函数的第一步是隐式地定义并初始化函数的形参(将实参的值传给形参).如果形参不能接收实参的值(类型不一致或是类型不能进行隐式转换即类型不关联),那么函数执行失败.

若函数的参数列表为空(不需要参数),那么有以下两种写法:

int add(){}        //隐式定义空形参列表
int add(void){}    //显示定义空形参列表

形参名是可选的,即我们可以选择不写实参名,意味着我们在函数中无法使用该参数(在后面的函数重载中用于区分),但是调用时必须要写,例如:

//函数定义
void test(int){
    std::cout<<"hello world"<<std::endl;
}
//调用函数
test(10);

6.1.1局部对象

C++中名字有作用域,对象有生命周期.

名字的作用域是程序文本的一部分,名字在其中可见(有效);

对象的生命周期是程序执行过程中该对象存在的一段时间.

形参与函数体内定义的变量称为局部变量,仅在函数体中生效,同时与局部变量同名的全局变量会在函数体内失效.函数结束后局部变量会自动销毁.

局部静态变量可以在函数结束后不销毁,而下次调用函数时保持上次的值.在变量前加上static关键字可以使其变成局部变量;

void test(){
    static int a=0;        //无论函数执行多少次,这条语句只执行一次
    a++;
    std::cout<<a<<std::endl;
}
test();
test();

//控制台输出
//    1
//    2

 6.1.2函数声明

函数声明也被称为函数原型.函数声明可以多次,但函数定义只能一次.若是分文件编写,函数声明写在头文件中,并且有函数声明的头文件应该被包含到定义函数的源文件中.

6.1.3分离式编译

C++支持分离式编译,允许我们把文件分割到多个文件中,每个文件独立编译.(即分文件编写)

如果我们修改了某个源文件,只需要重新编译改动的文件,而不用把全部文件都重新编译(g++).

6.2参数传递

形参初始化的机理与变量初始化一样.形参和实参是两个相互独立的对象,可以说是实参被值传递或者函数被传值调用.

6.2.1传值参数

初始化形参后,对形参的改变不会影响到实参,如果需要形参影响实参,那么将形参类型改成指针或是引用即可.因为本质上是传递了地址,改变了地址上的值,自然会影响到原来的值.在C++中推荐用引用来代替指针.

6.2.2传引用参数

前面说了,执行函数的第一步是隐式地定义并初始化函数的形参,而拷贝大的类型对象或者容器对象比较低效,使用引用类型的形参可以避免拷贝.(有时候刷力扣,暴力解法超时,但是把函数形参改成引用类型就有可能能通过,正是因为使用普通类型的形参直接拷贝实参比较低效).

如果函数无需改变形参的值,那么保险起见最好声明成常量引用.

6.2.3const形参和实参

当用const修饰形参后,实参不必是const,而在函数中,形参无法被改变.而const修饰实参,形参也不必是const.

但若形参为引用类型,那么用const修饰实参时同时也要用const修饰形参.而反过来却不必.

6.2.4数组形参

因为数组会转换成指针,所以我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针.函数体使用形参数组时需要保证数组不越界,但是仅凭一个指针没法确定数组的大小.所以管理指针形参有以下三种常用办法:

使用标记指定数组长度:在数组结尾处包含一个结束标记(例如一个空字符),当函数处理数组时碰到了结束标记则停止.

使用标准库规范:传入指向元素首元素的指针和尾元素的指针,当两个指针相等时停止.

显示传递一个表示数组大小的形参:直接把数组的大小传给函数

传递多维数组时需要牢记C++中没有真正意义上的多维数组,而是数组的数组.

6.2.5 main处理命令行选项

主函数main也是可以传入参数的,主函数可以接收两个参数:(int argc,cahr *argv[])形参名字可以自己随便定义,但是大家都是用的argc和argv.

argc指的是argv中字符串的数量,argv是数组.

6.2.6含有可变形参的函数

如果函数的实参数量未知但是全部实参的类型都一样,可以使用initializer_list类型的形参,用于表示某种特定类型的值的数组(然后我个人通常会直接使用vector,但书中有讲initializer_list类型那就简单介绍一下),initializer_list类型定义在同名的头文件.

和vetcor一样,initializer_list也是一种模板类型,需要说明列表中所含元素的类型:

例如: initializer_list<int> test;

和vector不一样的是,initializer_list对象中的元素永远是常量值,我们无法修改里面元素的值.

6.3返回类型和return语句

return语句终止当前正在执行的函数,并将控制权返回到调用该函数的地方.

6.3.1无返回值函数

函数声明中返回值类型为void的函数即为无返回值函数,函数定义中,函数体不一定需要return语句,因为在这类函数的最后一句后面会隐式执行return;

6.3.2有返回值函数

返回值类型不为void的函数即为有返回值函数,在函数体中的最后应当有return语句,即使它永远不会执行.

//这样的函数定义是错误的,即使我们知道函数一定会有返回值,但是在编译器眼中是没有的
int test(){
    for(int i=0;i<10;i++){
        if(i>=5){
            return 1;
        }
    }
}
//正确的写法是这样的
int test(){
    for(int i=0;i<10;i++){
        if(i>=5){
            return 1;
        }
    }
    return 1;//加上这句,函数定义才是正确的,即使这条语句永远不会执行到
}

不要返回局部对象的引用或指针,因为函数执行结束后局部变量就会被销毁,而返回值所返回的地址所指的数据也会被清理.

如果一个函数直接或是间接调用了自身,那么该函数为递归函数.main不能进行递归.递归函数需要定义好结束循环的条件,否则永远无法结束递归直至程序的栈空间消耗殆尽.

6.3.3返回数组指针

因为数组不能被拷贝,所以函数不能返回数组,但是可以返回数组的指针或是引用(C++中推荐引用).

也可以使用尾置返回类型.形如:

auto fun() -> int{
    //函数体
}

 在原返回值类型处写上auto,然后在参数列表后接箭头->以及真正返回的类型,上例以int为例.

6.4函数重载

如果同一作用域内的几个函数名字相同但是形参列表不同,那么称之为重载函数.main不能被重载.

返回值类型不能作为函数重载的条件.

调用重载函数时,会将名字一样的函数进行匹配,找出最佳匹配的函数然后执行,如果没有可以匹配的形参列表或是有两个以上的形参列表都可以通过隐式转换来匹配时会报错.

如下面的例子.调用test时传入int类型参数则调用第一个,传入两个int类型参数则调用第二个,传入long类型参数则调用第三,传入double类型则调用第三个.

如果把第一个函数定义注释掉,此时传入int类型参数则会报错,因为出现二义性,因为int既可以隐式转换成long类型调用第三个函数,也可以隐式转换成double类型调用第四个函数.

void test(int a) {
	cout << 1 << endl;
}
void test(int a,int b) {
	cout << 2 << endl;
}
void test(long a) {
	cout << 3 << endl;
}
void test(double a) {
	cout << 4 << endl;
}

 6.4.1重载与作用域

一般来说,我们不讲函数声明置于局部作用域.

6.5特殊用途语言特性

6.5.1默认实参

//此函数定义中,b为默认实参,其值默认为10,调用函数可以传入,则将b的默认值10给代替.
//也可以不传入b的值,则b默认为10
int add(int a,int b=10){
    return a+b;
}
//以下两种调用方式都是正确的,第一个不传入形参b的值,则b使用默认值10
//第二个调用则传入形参b的值,则20将代替b的默认值10
add(10);
add(10,20);

 一旦某个形参被赋予了默认值,那么后面的形参都必须有默认值,这个稍微想想就很好理解了,如果有多个形参,其中在中间的某些形参有默认值,那么将无法判断哪些实参的值应该赋给哪些形参,而哪些形参又应该使用默认值.

通常应该在函数声明中指定默认实参,并将该声明放在合适的头文件中.

6.5.2内联函数和constexpr函数

在函数声明的开头(返回值类型的前面)加上关键字inline可以将函数定义为内联函数,内联函数可以避免函数调用的开销.适用于优化规模较小,流程直接,频繁调用的函数.然而内联说明只是向编译器发出的一个请求,编译器可以选择忽略这个请求.

constexpr函数指能用于常量表达式的函数,定义constexpr函数的方法与其他函数类似,不过要遵循以下规定:函数返回值类型以及所有形参的类型都是字面量类型,并且函数体中有且只有一条return语句(只有一条语句,那就是return语句).

内联函数和constexpr函数通常定义在头文件中.

6.5.3调试帮助

当程序编写完成准备发布时,需要屏蔽掉调试代码,可以用到两项预处理功能:assert和NDEBUG.

assert是一种预处理宏,就是一个预处理变量,其行为类似于内联函数.使用时如下:

assert(expr) 如果expr为假(0),则assert输出信息并终止程序执行,反正什么也不做

assert定义在cassert头文件中,预处理名字由预处理器而非编译器管理,因此使用assert时无序使用using,也就不用在assert前面加上std::

assert的行为依赖与NDEBUG的状态,如果定义了NDEBUG,那么assert失效,反之assert工作.

因此我们可以使用定义NDEBUG来关闭调试状态.

 预处理器定义了几个名字方便程序调试:

__func__:当前调用的函数的名字

__FILE__:文件名的字符串字面量

__LINE__:当前行号的整型字面量

__TIME__:文件编译时间的字符串字面量

__DATE__:文件编译日期的字符串字面量

6.6函数匹配

函数匹配的第一步是选定本次调用对应的重载函数集,集合中的函数称为候选函数.

第二步是从候选函数中挑出能被调用的函数,这些被挑出的函数被称为可行函数,如果没找到可行函数,那么编译器报错.

第三步是从可行函数中选出最匹配函数,实参类型与形参类型越接近,它们匹配得越好.

调用重载函数时应当避免强制类型转换.

6.6.1实参类型转换

6.7函数指针

一个指针指向函数时,我们称这个指针为函数指针.

使用时如下:

bool test(int a,int b){……}

bool *pf(int a,int b);    //声明函数指针
pf=test;                  //定义函数指针

pf(10,20)        //调用
(*pf)(10,20)     //调用

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

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

相关文章

如何确保大模型追求“正确”的目标?丨AI安全与对齐圆桌回顾

导读 在智源大会「AI 安全与对齐」论坛上&#xff0c;与会嘉宾针对目前人们关心的 AI 安全控制标准、多智能体强化学习环境下的安全、开源对 AI 安全的影响、对智能涌现安全的思考等问题展开了讨论。 能力越大&#xff0c;责任越大。 嘉宾名单 谢旻希丨主持人&#xff0c;安远A…

高压线路距离保护程序逻辑原理(一)

为了保证高压线路输电的稳定性和可靠性&#xff0c;通常要求高压电网构成多侧电源的环形电网。在这种电网中简单的电压电流保护往往不能满足保护的基本要求&#xff0c;例如方向电流保护往往不能保证有选择性地切除故障。为此&#xff0c;在多侧电源的环形电网的线路上配置了选…

使用Nexus搭建Maven私有库实战

本篇快速演示如何搭建和使用Nexus本地库&#xff0c; 关于Nexus 的基本使用参考&#xff1a; Nexus搭建Maven私有库介绍 实战场景 本篇的实际场景是&#xff1a; 本地开发机器可以连接外部网络测试或正式部署环境只能连接内网项目使用了内部开发的组件库 在搭建搭建Maven私…

如何一次解决两大难题,不用写注释,也不会被他人吐槽没有注释呢?

如何一次解决两大难题&#xff0c;不用写注释&#xff0c;也不会被他人吐槽没有注释呢&#xff1f;&#xfeff; 导读为什么要减少代码中的注释量呢&#xff1f;一、无用型的注释二、絮絮叨叨的注释三、代替代码分层的注释四、不知所云的注释 参考&#xff1a;阿里云开发者社区…

【vs2022】解决 “对程序集签名是出错 - 拒绝访问 ”

背景 拿到一个工程&#xff0c;编译时报错&#xff1a;“对程序集签名是出错 - 拒绝访问” 解决方法 ”C:\ProgramData\Microsoft\Crypto\RSA”目录下面&#xff0c;找到【MachineKeys】文件夹。 右键【MachineKeys】文件夹&#xff0c;在安全属性里面&#xff0c;将当前win…

DETR系列:RT-DETR(一) 论文解析

论文&#xff1a;《DETRs Beat YOLOs on Real-time Object Detection》 2023.4 DETRs Beat YOLOs on Real-time Object Detection&#xff1a;https://arxiv.org/pdf/2304.08069.pdf 源码地址&#xff1a;https://github.com/PaddlePaddle/PaddleDetection/tree/develop/conf…

江西五十铃汽车PMO经理苏建受邀为第十二届PMO大会演讲嘉宾

江西五十铃汽车有限公司PMO经理苏建先生受邀为由PMO评论主办的2023第十二届中国PMO大会演讲嘉宾&#xff0c;演讲议题&#xff1a;PMO全链路建设促进组织变革——传统汽车行业0-1PMO最佳实践探索。大会将于8月12-13日在北京举办&#xff0c;敬请关注&#xff01; 议题简要&…

LangChain:LLM应用程序开发(上)——Models、Prompt、Parsers、Memory、Chains

文章目录 一、Models、Prompt、Parsers1.1 环境配置&#xff08;导入openai&#xff09;1.2 辅助函数&#xff08;Chat API : OpenAI&#xff09;1.3 使用OpenAI API进行文本翻译1.4使用LangChain进行文本翻译1.5 使用LangChain解析LLM的JSON输出1.5.1 LangChain输出为string格…

基于人体姿势估计的舞蹈检测(AI Dance based on Human Pose Estimation)

人体姿势骨架以图形格式表示人的方向。本质上&#xff0c;它是一组坐标&#xff0c;可以连接起来描述人的姿势。骨架中的每个坐标都被称为一个部分(或一个关节&#xff0c;或一个关键点)。两个部分之间的有效连接称为一对(或分支)。下面是一个人体姿势骨架样本。 因此&#xff…

mysql基础3——数据备份与恢复、破解数据库密码

文章目录 一、备份方案二、备份工具mysqldump2.1 备份整张表数据2.2 备份整个库数据 三、全量备份四、差异备份五、数据库密码破解 一、备份方案 备份方案概念特点全量备份对某一个时间点上的所有数据或应用进行完全拷贝。数据恢复快&#xff0c;备份时间长。增量备份在一次全…

抓住重点,谋定而后动

一、我们平常项目有哪几种 有两种常规项目、大项目 1.常规项目 技术团队的重心是把执行做到位&#xff0c;你要更关注过程管控&#xff0c;确保系统交付 2.大项目&#xff1a; 什么是大项目&#xff0c;他有什么特点 大项目时间投入大、人员规模大、系统更大&#xff0c;复…

ResourceBundle读取properties文件

ResourceBundle 常用API 方法签名方法描述public Locale getLocale()获取本地国际化环境ppublic Enumeration getKeys()获取属性文件中所有keypublic final String getString(String key)获取属性文件中key对应的value, 返回值为String, 如果不存在, 则抛出异常public final O…

STM32文档

一、寄存器缩写 二、存储器和总线构架 DMA&#xff0c;全称为&#xff1a;Direct Memory Access&#xff0c;即直接存储器访问 简而言之&#xff0c;DMA就是将一个内存里的数据搬运到另一个内存里&#xff0c;此过程无需CPU直接控制输出 系统架构存储器组织存储器映射&#xf…

解密:Prompt、Token、和completions是什么?

Prompt、Token、和completions 本文是科普向&#xff0c;大家放心阅读 在ChatGPT越来越火的时候&#xff0c;很多开发者都想大展拳脚&#xff0c;但在这之前&#xff0c;我们需要了解一些基础知识&#xff0c;比如你知道什么是token、什么是prompt、什么是Complemention Pro…

vue下基于elementui自定义表单-后端数据设计篇

vue下基于elementui自定义表单-后端篇 自定义表单目前数据表单设计是基于数据量不大的信息单据场景&#xff0c;因为不考虑数据量带来的影响。 数据表有: 1.表单模版表&#xff0c;2.表单实例表&#xff0c;3.表单实例项明细表&#xff0c;4表单审批设计绑定表 以FormJson存…

chatgpt赋能python:关于Python的常见问题及解决方法

关于Python的常见问题及解决方法 在编程领域中&#xff0c;Python已经成为了最流行的编程语言之一。然而&#xff0c;在使用Python编写代码时&#xff0c;难免会遇到一些问题。在本文中&#xff0c;我们将介绍一些常见的Python问题以及如何解决它们。 编码问题 在Python中&a…

idea项目名旁边还有一个项目名——idea笔记

问题描述 我们常常因为想改项目名但是没有改完全从而出现了项目名旁边还有一个项目名 例如&#xff1a; 解决方案&#xff1a; 打开File->project structure 修改前&#xff1a; 修改后&#xff1a;

【数据挖掘】时间序列教程【一】

第一章 说明 对于时间序列的研究&#xff0c;可以追溯到19世纪末和20世纪初。当时&#xff0c;许多学者开始对时间相关的经济和社会现象进行研究&#xff0c;尝试发现其规律和趋势。其中最早的时间序列研究可以追溯到法国经济学家易贝尔&#xff08;Maurice Allais&#xff09;…

微服务04 分布式搜索引擎 elasticsearch DSL数据聚合 自动补全 数据同步 集群 Sentinel

微服务03 分布式搜索引擎 elasticsearch ELK kibana RestAPI 索引库 DSL查询 RestClient 黑马旅游 分布式搜索引擎03 1.数据聚合 聚合&#xff08;aggregations&#xff09;可以让我们极其方便的实现对数据的统计、分析、运算。例如&#xff1a; 什么品牌的手机最受欢迎&#x…

ℰ悟透Qt—Http网络编程

目录 概述实践理论QNetworkAccessManager进行Http编程的基本步骤代码实战(重点片段) 概述 网络访问 API 建立在一个 QNetworkAccessManager 对象之上&#xff0c;该对象保存了发送请求所需的公共配置和设置。它包含代理和缓存配置&#xff0c;以及与此类问题相关的信号和可用于…