C++ Primer 函数基础

news2025/2/20 10:19:58

欢迎阅读我的 【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

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

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

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

相关文章

【SVN基础】

软件&#xff1a;ToritoiseSVN 代码版本回退&#xff1a;回退到上一个版本 问题&#xff1a;SVN版本已经提交了版本1和版本2&#xff0c;现在发现不需要版本2的内容&#xff0c;需要回退到版本1然后继续开发。 如图SVN版本已经提交到了107版本&#xff0c;那么本地仓库也已经…

kron积计算mask类别矩阵

文章目录 1. 生成类别矩阵如下2. pytorch 代码3. 循环移动矩阵 1. 生成类别矩阵如下 2. pytorch 代码 import torch import torch.nn as nn import torch.nn.functional as Ftorch.set_printoptions(precision3, sci_modeFalse)if __name__ "__main__":run_code 0…

Stable Diffusion 安装教程(附安装包) 【SD三种安装方式,Win+Mac一篇文章讲明白】

“Stable Diffusion的门槛过高、不会安装&#xff1f;没关系&#xff0c;这篇文章教会你如何安装&#xff01;” Stable Diffusion的安装部署其实并不困难&#xff0c;只需简单点击几下&#xff0c;几分钟就能安装好&#xff0c;不管是windows还是苹果mac电脑&#xff0c;关于…

网络安全用centos干嘛 网络安全需要学linux吗

网络安全为啥要学Linux系统&#xff0c;据不完全统计&#xff0c;Linux系统在数据中心操作系统上的份额高达70%。它一般运行于服务器和超级计算机上。 所以我们日常访问的网站后台和app后端都是部署在Linux服务器上的&#xff0c;如果你不会Linux系统操作&#xff0c;那么很多…

jupyter notebook中3种读图片的方法_与_图片翻转(上下翻转,左右翻转,上下左右翻转)

已有图片cat.jpg 相对于代码的位置&#xff0c;可以用./cat.jpg进行读取。 下面是3种读图片的方法。 1.python读图片-pillow 图片文件不适合用open去读取 用open读图片&#xff0c;易引发UnicodeDecodeError: gbk codec cant decode byte 0xff in position 0: illegal multib…

微软官方出品GPT大模型编排工具:7个开源项目

今天一起盘点下&#xff0c;12月份推荐的7个.Net开源项目&#xff08;点击标题查看详情&#xff09;。 1、一个浏览器自动化操作的.Net开源库 这是一个基于 Google 开源的 Node.js 库 Puppeteer 的 .NET 开源库&#xff0c;方便开发人员使用无头 Web 浏览器抓取 Web、检索 Ja…

机器视觉--Halcon变量的创建与赋值

一、引言 在机器视觉领域&#xff0c;Halcon 作为一款强大且功能丰富的软件库&#xff0c;为开发者提供了广泛的工具和算子来处理各种复杂的视觉任务。而变量作为程序中存储和操作数据的基本单元&#xff0c;在 Halcon 编程中起着至关重要的作用。正确地创建和赋值变量是编写高…

03【FreeRTO队列-如何获取任务信息与队列的动静态创建】

一.利用 vTaskList()以及 vTaskGetRunTimeStats()来获取任务的信息 1.现象与开启启用宏 freeRTOSConfig.h //必须启用 #define configUSE_TRACE_FACILITY 1 #define configGENERATE_RUN_TIME_STATS 1 #define configUSE_STATS_FORMATTING_FUNCTIONS…

GBD研究——美国州级地图(附资源)

美国州级别地图 地图源很多&#xff0c;随便下载。不过我试了两个资源&#xff0c;发现有的资源会漏掉阿拉斯加和夏威夷。 就剩大的这块佩奇 出现这样的问题&#xff0c;要么跟数据源有关&#xff0c;要么就是要掉地名来看&#xff0c;是不是没匹配上。 亲自试过&#xff0c…

【微服务学习一】springboot微服务项目构建以及nacos服务注册

参考链接 3. SpringCloud - 快速通关 springboot微服务项目构建 教程中使用的springboot版本是3.x&#xff0c;因此需要使用jdk17&#xff0c;并且idea也需要高版本&#xff0c;我这里使用的是IDEA2024。 环境准备好后我们就可以创建springboot项目&#xff0c;最外层的项目…

第39周:猫狗识别 2(Tensorflow实战第九周)

目录 前言 一、前期工作 1.1 设置GPU 1.2 导入数据 输出 二、数据预处理 2.1 加载数据 2.2 再次检查数据 2.3 配置数据集 2.4 可视化数据 三、构建VGG-16网络 3.1 VGG-16网络介绍 3.2 搭建VGG-16模型 四、编译 五、训练模型 5.1 上次程序的主要Bug 5.2 修改版…

DeepSeek 概述与本地化部署【详细流程】

目录 一、引言 1.1 背景介绍 1.2 本地化部署的优势 二、deepseek概述 2.1 功能特点 2.2 核心优势 三、本地部署流程 3.1 版本选择 3.2 部署过程 3.2.1 下载Ollama 3.2.2 安装Ollama 3.2.3 选择 r1 模型 3.2.4 选择版本 3.2.5 本地运行deepseek模型 3.3.6 查看…

jenkins war Windows安装

Windows安装Jenkins 需求1.下载jenkins.war2.编写快速运行脚本3.启动Jenkins4.Jenkins使用 需求 1.支持在Windows下便捷运行Jenkins&#xff1b; 2.支持自定义启动参数&#xff1b; 3.有快速运行的脚步样板。 1.下载jenkins.war Jenkins下载地址&#xff1a;https://get.j…

3D打印技术:如何让古老文物重获新生?

如何让古老文物在现代社会中焕发新生是一个重要议题。传统文物保护方法虽然在一定程度上能够延缓文物的损坏&#xff0c;但在文物修复、展示和传播方面仍存在诸多局限。科技发展进步&#xff0c;3D打印技术为古老文物的保护和传承提供了全新的解决方案。我们来探讨3D打印技术如…

Vue h函数到底是个啥?

h 到底是个啥&#xff1f; 对于了解或学习Vue高阶组件&#xff08;HOC&#xff09;的同学来说&#xff0c;h() 函数无疑是一个经常遇到的概念。 那么&#xff0c;这个h() 函数究竟如何使用呢&#xff0c;又在什么场景下适合使用呢&#xff1f; 一、h 是什么 看到这个函数你可…

深入浅出 Python Logging:从基础到进阶日志管理

在 Python 开发过程中&#xff0c;日志&#xff08;Logging&#xff09;是不可或缺的调试和监控工具。合理的日志管理不仅能帮助开发者快速定位问题&#xff0c;还能提供丰富的数据支持&#xff0c;让应用更具可观测性。本文将带你全面了解 Python logging 模块&#xff0c;涵盖…

Android WindowContainer窗口结构

Android窗口是根据显示屏幕来管理&#xff0c;每个显示屏幕的窗口层级分为37层&#xff0c;0-36层。每层可以放置多个窗口&#xff0c;上层窗口覆盖下面的。 要理解窗口的结构&#xff0c;需要学习下WindowContainer、RootWindowContainer、DisplayContent、TaskDisplayArea、T…

2025年最新版1688平台图片搜索接口技术指南及Python实现

随着电商行业的蓬勃发展&#xff0c;1688作为国内领先的B2B交易平台&#xff0c;其商品搜索功能对于买家和卖家而言都至关重要。图片搜索作为其中的一种高级搜索方式&#xff0c;能够极大地提升用户的搜索体验和准确性。本文将详细介绍如何通过API接口实现1688平台的图片搜索功…

基于A*算法与贝塞尔曲线的路径规划与可视化:从栅格地图到平滑路径生成

引言 在机器人导航、自动驾驶和游戏开发等领域,路径规划是一个核心问题。如何高效地找到从起点到终点的最优路径,并且确保路径的平滑性和安全性,是许多应用场景中的关键挑战。本文将介绍一种结合A算法和贝塞尔曲线的路径规划方法,并通过Pygame实现可视化。我们将从栅格地图…

使用verilog 实现 cordic 算法 ----- 旋转模式

1-设计流程 ● 了解cordic 算法原理&#xff0c;公式&#xff0c;模式&#xff0c;伸缩因子&#xff0c;旋转方向等&#xff0c;推荐以下链接视频了解 cordic 算法。哔哩哔哩-cordic算法原理讲解 ● 用matlab 或者 c 实现一遍算法 ● 在FPGA中用 verilog 实现&#xff0c;注意…