C++ Primer 函数基础

欢迎阅读我的 【C++Primer】专栏

专栏简介:本专栏主要面向C++初学者,解释C++的一些基本概念和基础语言特性,涉及C++标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级程序设计技术。希望对读者有帮助!

在这里插入图片描述
在这里插入图片描述

目录

  • 6.1函数基础
    • 编写函数
    • 调用函数
    • 形参和实参
    • 函数的形参列表
    • 函数返回类型
    • 局部对象
    • 自动对象
    • 局部静态对象
    • 函数声明
    • 在头文件中进行函数声明
    • 分离式编译
      • 编译和链接多个源文件

6.1函数基础

一个典型的函数(function)定义包括以下部分:返回类型(return type)、函数名字、由0个或多个形参(parameter)组成的列表以及函数体。其中,形参以逗号隔开,形参的列表位于一对圆括号之内。函数执行的操作在语句块中说明,该语句块称为函数体(function body)。

我们通过调用运算符(call operator)来执行函数。调用运算符的形式是一对圆括号,它作用于一个表达式,该表达式是函数或者指向函数的指针;圆括号之内是一个用逗号隔开的实参(argument)列表,我们用实参初始化函数的形参。调用表达式的类型就是函数的返回类型。

编写函数

举个例孔,我们准备编写一个求数的阶乘的程序。n的阶乘是从1到n所有数字的乘积,例如5的阶乘是120。

1 * 2 * 3 * 4 * 5 = 120

程序如下所示:

//val的阶乘是val*(val-1)*(val-2)…*((val-(val-1)*1)
int fact(int val) {
    int ret= 1 ;//局部变量,用于保存计算结果
    while(val>1)
        ret=val--;//把ret和val的乘积赋给ret,然后将val减1
    return ret;//返回结果
}

函数的名字是fact,它作用于一个整型参数,返回一个整型值。在while循环内部,在每次迭代时用后置递减运算符将val的值减1,return语句负责结束fact并返回ret的值。

调用函数

要调用fact函数,必须提供一个整数值,调用得到的结果也是一个整数:

int main()
(
    int j=fact(5);//于等于120,即fact(5)的结果
    cout << "5! is" << j << endl;
    return 0;
}

函数的调用完成两项工作:一是用实参初始化函数对应的形参,二是将控制权转移给被调用函数。此时,主调函数(calling function)的执行被暂时中断,被调用函数(called function)开始执行。

执行函数的第一步是(隐式地)定义并初始化它的形参。因此,当调用fact函数时,首先创建一个名为val的int变量,然后将它初始化为调用时所用的实参。

当遇到一条return语句时函数结束执行过程。和函数调用一样,return语句也完成两项工作:一是返回return语句中的值(如果有的话),二是将控制权从被调函数转移回主调函数。函数的返回值用于初始化调用表达式的结果,之后继续完成调用所在的表达式的剩余部分。因此,我们对fact函数的调用等价于如下形式:

int val=5;   //用字面值5初始化val
int ret=1;   //fact函数体内的代码
while(val>1)
    ret *= val--;
int j=ret;   //用ret的副本初始化j

形参和实参

实参是形参的初始值,第一个实参初始化第一个形参,第二个实参初始化第一个形参,以此类推。尽管实参与形参存在对应关系,但是并没有规定实参的求值顺序。编译器能以任意可行的顺序对实参求值。

实参的类型必须与对应的形参类型匹配,这一点与之前的规则是一致的,我们知道在初始化过程中初始值的类型也必须与初始化对象的类型匹配。函数有儿个形参,我们就必须提供相同数量的实参。因为函数的调用规定实参数量应与形参数量一致,所以形参一定会被初始化。

在上面的例子中,fact函数只有一个int类型的形参,所以每次我们调用它的时候,都必须提供一个能转换成int的实参:

fact("hello");//错误:实参类型不正确
fact();// 错误:实参数量不足
fact(42,10,0);//错误;参数量过多
fact(3.14);//正确;该实参能转换成int类型

因为不能将const char*转换成int,所以第一个调用失败。第二个和第三个调用也会失败,不过错误的原因与第一个不同,它们是因为传入的实参数量不对。要想调用fact函数只能使用一个实参,只要实参数量不是一个,调用都将失败。最后一个调用是合法的,因为double可以转换成int。执行调用时,实参隐式地转换成int类型(截去小数部分),调用等价于

fact(3);

函数的形参列表

函数的形参列表可以为空,但是不能省略。要想定义一个不带形参的函数,最常用的办法是书写一个空的形参列表。不过为了与C语言兼容,也可以使用关键字void表示函数没有形参:

void f1;      //隐式地定义空形参列表
void f2(void);//显式地定义空形参列表

形参列表中的形参通常用逗号隔开,其中每个形参都是含有一个声明符的声明。即使两个形参的类型一样,也必须把两个类型都写出来:

    int f3(int v1,v2){} //错误
    int f4(int vl,int v2){} //正确

任意两个形参都不能同名,而一函数最外层作用域中的局部变量也不能使用与函数形参一样的名称。

形参名是可选的,但是由于我们无法使用未命名的形参,所以形参一般都应该有个名字。偶尔,函数确实有个别形参不会被用到,则此类形参通常不命名以表示在函数体内不会使用它。不管怎样,是否设置未命名的形参并不影响调用时提供的实参数量。即使某个形参不被函数使用,也必须为它提供一个实参。

函数返回类型

大多数类型都能用作函数的返回类型。一种特殊的返回类型是void,它表示函数不返回任何值。函数的返回类型不能是数组类型或函数类型,但可以是指向数组或函数的指针。

局部对象

在C++语言中,名字有作用域,对象有生命周期(life time)。理解这两个概念非常重要。

  • 名字的作用域是程序文本的一部分,名字在其中可见。
  • 对象的生命周期是程序执行过程中该对象存在的一段时间。

如我们所知,函数体是一个语句块。块构成一个新的作用域,我们可以在其中定义变量。形参和函数体内部定义的变量统称为局部变量(local variable)。它们对函数而言是“局部“的,仅在函数的作用域内可见,同时局部变量还会隐藏(hide)在外层作用域中同名的其他所有声明中。

在所有函数体之外定义的对象存在于程序的整个执行过程中。此类对象在程序启动时被创建,直到程序结束才会销毁。局部变量的生命周期依赖于定义的方式。

自动对象

对于普通局部变量对应的对象来说,当函数的控制路径经过变量定义语句时创建该对象,当到达定义所在的块未尾时销毁它。我们把只存在于块执行期间的对象称为自动对象(automatic object)。当块的执行结束后,块中创建的自动对象的值就变成未定义的了。形参是一种自动对象。函数开始时为形参申请存储空间,因为形参定义在函数体作用域之内,所以一旦函数终止,形参也就被销毁。

我们用传递给函数的实参初始化形参对应的自动对象。对于局部变量对应的自动对象来说,则分为两种情况:如果变量定义本身含有初始值,就用这个初始值进行初始化;否则,如果变量定义本身不含初始值,执行默认初始化。这意味着内置类型的未初始化局部变量将产生未定义的值。

局部静态对象

某些时候,有必要令局部变量的生命周期贯穿函数调用及之后的时间。可以将局部变量定义成static类型从而获得这样的对象。局部静态对象(local static object)在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。

举个例子,下面的函数统计它自己被调用了多少次,这样的函数也许没什么实际意义,但是足够说明问题:

size_t count_calls() {
    static size_t ctr=0;//调用结束后,这个值仍然有效
    return ++ctr;
}
int main()
{
    for(size_t i=0;i!=10;++)
        cout<<count_calls()<<endl;
    return 0;
}

这段程序将输出从1到10(包括10在内)的数字。

在控制流第一次经过ctr的定义之前,ctr被创建并初始化为0。每次调用将ctr加1并返回新值。每次执行count_calls函数时,变量ctr的值都已经存在并且等于函数上一次退出时ctr的值。因此,第二次调用时ctr的值是1,第三次调用时ctr的值是2,以此类推。

如果局部静态变量没有显式的初始值,它将执行值初始化内置类型的局部静态变量初始化为0。

函数声明

和其他名字一样,函数的名字也必须在使用之前声明。类似于变量函数只能定义一次,但可以声明多次。唯一的例外是如果一个函数永远也不会被我们用到,那么它可以只有声明没有定义。

函数的声明和函数的定义非常类似,唯一的区别是函数声明无须函数体,用一个分号替代即可。

因为函数的声明不包含函数体,所以也就无须形参的名字。事实上,在函数的声明中经常省略形参的名字。尽管如此,写上形参的名字还是有用处的,它可以帮助使用者更好地理解函数的功能:

我们选择beg和end作为形参的名字以表示这两个选代器划定了输出值的范图

void print(vector<int>::const_iterator beg),
vector<int>::const_iterator end);

函数的三要素(返回类型、函数名、形参类型)描述了函数的接口,说明了调用该函数所需的全部信息。函数声明也称作函数原型(function prototype)。

在头文件中进行函数声明

函数也应该在头文件中声明而在源文件中定义。

看起来把函数的声明直接放在使用该函数的源文件中是合法的,也比较容易被人接受;但是这么做可能会很烦琐而且容易出错。相反,如果把函数声明放在头文件中,就能确保同一函数的所有声明保持一致。而且一旦我们想改变函数的接口,只需改变一条声明即可。

定义函数的源文件应该把含有函数声明的头文件包含进来,编译器负责验证函数的定义和声明是否匹配。

含有函数声明的头文件应该被包含到定义函数的源文件中。

分离式编译

随着程序越来越复杂,我们希望把程序的各个部分分别存储在不同文件中。例如,可以把函数存在一个文件里,把使用这些函数的代码存在其他源文件中。为了允许编写程序时按照逻辑关系将其划分开来,C++语言支持所谓的分离式编译(separate compilation)。分离式编译允许我们把程序分割到几个文件中去,每个文件独立编译。

编译和链接多个源文件

举个例子,假设fact函数的定义位于一个名为fact.cc的文件中,它的声明位于名为Chapter6.h的头文件中。显然与其他所有用到fact函数的文件一样,fact.cc应该包含Chapter6.h头文件。另外,我们在名为factMain.cc的文件中创建main函数,main函数将调用fact函数。要生成可执行文件(executable file),必须告诉编译器我们用到的代码在哪里。对于上述几个文件来说,编详的过程如下所示:

$CC factMain.cc fact.cc # generates factMatn.exe or a.out
$CC factMain.cc fact.cc -o main # generates main or matn.exe

其中,CC是编详器的名字,$是系统提示符,#后面是命令行下的注释语句。接下来运行可执行文件,就会执行我们定义的main函数。

如果我们修改了其中一个源文件,那么只需重新编译那个改动了的文件。大多数编译器提供了分离式编译每个文件的机制,这一过程通常会产生一个后缀名是.obj(Windows)或.o(CUNIX)的文件,后缀名的含义是该文件包含对象代码(object code)。

接下来编译器负责把对象文件链接在一起形成可执行文件。在我们的系统中,编详的过程如下所示:

$CC -c factMain.cc # generates factMain.o
$CC -c fact.cc # generates fact.o
$CC -c factMain.o fact.o # generatesfactMain.exe or a.out
$CC factMain.o fact.o -o main ##generates main or main.exe

你可以仔细阅读编译器的用户手册,弄清楚由多个文件组成的程序是如何编译并执行的。

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

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

相关文章

滤波总结 波形处理原理 如何对一个规律的波形进行滤波 显现出真正的波形 如何设计滤波

需要用到的软件:waveserialport vofa++ 1.波形想用MCU进行采集首先你要考虑到你的采集频率因为如果你的对象波形即你要采集的波形,他过于快速的话有一些MCU它是不能的比如说有一些它的主频才36兆72兆呢你如果遇到一个特别快的波形毫秒级别那他就检测不了 2.…

PyQt组态软件 拖拽设计界面测试

PyQt组态软件测试 最近在研究PyQt,尝试写个拖拽设计界面的组态软件&#xff0c;目前实现的功能如下&#xff1a; 支持拖入控件&#xff0c;鼠标拖动控件位置 拖动控件边缘修改控件大小支持属性编辑器&#xff0c;修改当前选中控件的属性 拖动框选控件&#xff0c;点选控件 控…

算法学习笔记之贪心算法

导引&#xff08;硕鼠的交易&#xff09; 硕鼠准备了M磅猫粮与看守仓库的猫交易奶酪。 仓库有N个房间&#xff0c;第i个房间有 J[i] 磅奶酪并需要 F[i] 磅猫粮交换&#xff0c;硕鼠可以按比例来交换&#xff0c;不必交换所有的奶酪 计算硕鼠最多能得到多少磅奶酪。 输入M和…

把 DeepSeek1.5b 部署在显卡小于4G的电脑上

这里写自定义目录标题 介绍准备安装 Ollama查看CUDA需要版本安装CudaToolkit检查Cuda是否装好设置Ollama环境变量验证是否跑在GPU上ollama如何导入本地下载的模型安装及配置docker安装open-webui启动open-webui开始对话 调整gpu精度 介绍 Deepseek1.5b能够运行在只用cpu和gpu内…

第四十四篇--Tesla P40+Janus-Pro-7B部署与测试

环境 系统&#xff1a;CentOS-7 CPU: 14C28T 显卡&#xff1a;Tesla P40 24G 驱动: 515 CUDA: 11.7 cuDNN: 8.9.2.26创建环境 conda create --name trans python3.10torch 2.6.0 transformers 4.48.3克隆项目 git clone https:/…

「vue3-element-admin」Vue3 + TypeScript 项目整合 Animate.css 动画效果实战指南

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall ︱vue3-element-admin︱youlai-boot︱vue-uniapp-template &#x1f33a; 仓库主页&#xff1a; GitCode︱ Gitee ︱ Github &#x1f496; 欢迎点赞 &#x1f44d; 收藏 ⭐评论 …

LabVIEW太阳能制冷监控系统

在全球能源需求日益增长的背景下&#xff0c;太阳能作为一种无限再生能源&#xff0c;被广泛应用于各种能源系统中。本基于LabVIEW软件和STM32F105控制器的太阳能制冷监控系统的设计与实现&#xff0c;提供一个高效、经济的太阳能利用方案&#xff0c;以应对能源消耗的挑战。 项…

【openresty服务器】:源码编译openresty支持ssl,增加service系统服务,开机启动,自己本地签名证书,配置https访问

1&#xff0c;openresty 源码安装&#xff0c;带ssl模块 https://openresty.org/cn/download.html &#xff08;1&#xff09;PCRE库 PCRE库支持正则表达式。如果我们在配置文件nginx.conf中使用了正则表达式&#xff0c;那么在编译Nginx时就必须把PCRE库编译进Nginx&#xf…

迅为RK3568开发板篇Openharmony配置HDF控制UART-什么是串口

串口&#xff08;Serial Port&#xff09;也叫串行通信接口&#xff0c;通常也叫做 COM 接口&#xff0c;是通用串行数据总线&#xff0c;用于异步通信。该总线双向通信&#xff0c;可以实现全双工传输。 两个 UART 设备的连接示意图如下&#xff0c;UART 与其他模块一般用 2 线…

Anaconda +Jupyter Notebook安装(2025最新版)

Anaconda安装&#xff08;2025最新版&#xff09; Anaconda简介安装1&#xff1a;下载anaconda安装包2&#xff1a; 安装anaconda3&#xff1a;配置环境变量4&#xff1a;检查是否安装成功5&#xff1a;更改镜像源6&#xff1a;更新包7&#xff1a;检查 Jupyter Notebook一.Jup…

HtmlRAG:RAG系统中,HTML比纯文本效果更好

HtmlRAG 方法通过使用 HTML 而不是纯文本来增强 RAG 系统中的知识表示能力。通过 HTML 清洗和两步块树修剪方法&#xff0c;在保持关键信息的同时缩短了 HTML 文档的长度。这种方法优于现有基于纯文本的RAG的性能。 方法 其实主要看下围绕html提纯思路&#xff0c;将提纯后的…

Linux 文件系统:恢复已删除文件的挑战

如今&#xff0c;Linux 操作系统越来越受欢迎。它的明显优势首先是免费。此外&#xff0c;该操作系统提供了种类繁多的版本及其衍生产品&#xff0c;可满足从手机到超级计算机等设备的不同用户需求。 Linux 操作系统使用独有的文件系统&#xff0c;包括 Ext2、Ext3 和 Ext4、X…

三角拓扑聚合优化器TTAO-Transformer-BiLSTM多变量回归预测(Maltab)

三角拓扑聚合优化器TTAO-Transformer-BiLSTM多变量回归预测&#xff08;Maltab&#xff09; 完整代码私信回复三角拓扑聚合优化器TTAO-Transformer-BiLSTM多变量回归预测&#xff08;Maltab&#xff09; 一、引言 1、研究背景和意义 在现代数据科学领域&#xff0c;时间序列…

提供可传递的易受攻击的依赖项

问题如图所示&#xff1a; 原因&#xff1a;okhttp3.version 3.14.9 版本存在部分漏洞&#xff0c;在 maven 仓库是可以看到的 maven 地址&#xff1a; maven 下图中 Vulnerabilities 即为漏洞 处理&#xff1a;换一个无漏洞的版本即可

使用pocketpal-ai在手机上搭建本地AI聊天环境

1、下载安装pocketpal-ai 安装github的release APK 2、安装大模型 搜索并下载模型&#xff0c;没找到deepseek官方的&#xff0c;因为海外的开发者上传了一堆乱七八糟的deepseek qwen模型&#xff0c;导致根本找不到官方上传的……deepseek一开源他们觉得自己又行了。 点击之…

头歌实验--面向对象程序设计

目录 实验五 类的继承与派生 第1关&#xff1a;简易商品系统 任务描述 答案代码 第2关&#xff1a;公司支出计算 任务描述 答案代码 第3关&#xff1a;棱柱体问题 任务描述 答案代码 实验五 类的继承与派生 第1关&#xff1a;简易商品系统 任务描述 答案代码 #incl…

卷积神经网络实战人脸检测与识别

文章目录 前言一、人脸识别一般过程二、人脸检测主流算法1. MTCNN2. RetinaFace3. CenterFace4. BlazeFace5. YOLO6. SSD7. CascadeCNN 三、人脸识别主流算法1.deepface2.FaceNet3.ArcFace4.VGGFace5.DeepID 四、人脸识别系统实现0.安装教程与资源说明1. 界面采用PyQt5框架2.人…

Spring IoC的实现机制是什么?

大家好&#xff0c;我是锋哥。今天分享关于【Spring IoC的实现机制是什么&#xff1f;】面试题。希望对大家有帮助&#xff1b; Spring IoC的实现机制是什么&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring IoC&#xff08;Inversion of Control…

关闭浏览器安全dns解决访问速度慢的问题

谷歌浏览器加载速度突然变慢了&#xff1f;检查安全DNS功能(DoH)是否被默认开启。 谷歌浏览器在去年已经推出安全DNS功能(即DoH) , 启用此功能后可以通过加密的DNS增强网络连接安全性。例如查询请求被加密后网络运营商将无法嗅探用户访问的地址&#xff0c;因此对于增强用户的…

【Spring AI】基于SpringAI+Vue3+ElementPlus的QA系统实现(前端)

整理不易&#xff0c;请不要吝啬你的赞和收藏。 1. 前言 这篇文章是 Spring AI Q&A 系统的前端实现。这篇文章将介绍如何快速搭建一个基于 vue3 ElementPlus 的前端项目&#xff0c;vue3 项目的目录结构介绍&#xff0c;如何在前端实现流式响应&#xff0c;如何高亮显示…