| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> C++ 函数学习笔记 -> 正文阅读 |
|
[C++知识库]C++ 函数学习笔记 |
1 函数基础典型的函数定义:返回类型 函数名字 圆括号(由0个或多个用逗号隔开形参列表) {函数体},比如:
典型的函数调用:函数名 圆括号(由0个或多个用逗号隔开的实参列表),比如:
函数调用完成两项工作:
此时,主调函数的执行被暂时中断,被调函数开始执行。 函数执行完后,return 语句完成两项工作:
函数的调用规定实参数量应与形参数量一致,且实参的类型必须与形参类型相匹配,所以形参一定会被初始化。函数的返回类型不能是数组或者函数,但可以是指向数组或函数的指针。 在形参和函数体内部定义的变量统称为局部变量,仅在函数的作用域内可用,程序从函数中退出时即被销毁;在所有函数体之外定义的对象称为全局变量,它们在程序启动时创建,在程序结束时销毁。若要令局部变量的生命周期贯穿函数调用及之后的时间(即在程序结束时才被销毁),可以将其定义为局部静态变量。如下所示:
有一点要注意的是,若循环调用上面的函数,cnt 只有在程序第一次调用 func 函数时会被创建并分配空间,在此后的每次调用都不会重复创建,实际生效的语句是 cnt++,也就是说 cnt 的值在循环调用之后会越来越大。但是,在 func 函数之外的其他任何地方都用不了 cnt 变量,仅仅在 func 函数内 cnt 的值是一直有效的。 对于大的工程项目,在头文件中声明函数和变量,在源文件中进行定义是一个比较合理且容易管理项目的方法。 2 参数传递当形参是引用类型时,我们说它对应的实参被引用传递或者函数被传引用调用,此时,引用形参是实参的别名;当形参为非引用类型时,形参和实参是两个相互独立的对象,此时称实参被值传递或者函数被传值调用。 2.1 const 形参和实参当形参有顶层 const 时,传给它常量对象或者是非常量对象都是可以的。例如:
调用 function 函数时,既可以传入 int,也可以传入 const int。实参初始化形参时会忽略掉顶层 const,这时就会出现一种情况:C++ 中的重载函数要求形参列表不一致,但由于顶层 const 被忽略了,所以下面的两条语句会被视作同一个函数从而报错:
2.2 引用形参不推荐把函数中不会改变的形参定义成普通的引用(即非常量引用)。使用非常量引用也会极大地限制函数所能接受的实参类型,因为常量引用可以接受各种类型的实参,但非常量引用不能接受 const 对象、字面值或者需要类型转换的对象。 2.3 数组形参数组的两个性质:不允许拷贝数组以及使用数组时通常会将其转换成指针使得我们无法以值传递的方式使用数组形参,当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。 可以把形参写成类似数组的形式:
有三种方式管理数组的指针形参防止越界:
该方法要求数组本身就包含一个结束标记,比如 C 语言字符串的末尾有一个空字符 ‘\0’。这种方法适用于那些有明显结束标记且该标记不会与普通数据混淆的情况。
该方法向函数传递数组首元素和尾后元素的指针,如下面的函数:
为了调用这个函数就需要传入两个指针,一个指向首元素,另一个指向为尾元素的下一个位置:
在函数的形参列表定义一个表示数组大小的形参。因此上面的 print 函数可改写成如下形式:
同理,如果函数不需要改变数组元素时,指针形参应写成指向常量的指针。 2.4 数组引用形参见下面的例子:
注意:&arr 两端的括号必不可少
该写法的缺陷在于它无形中限制了函数的可用性,比如上述的 print 函数就只能作用于大小为10的数组。 2.5 多维数组形参C++ 中没有真正的多维数组,只有数组的数组。将多维数组传递给函数时,传递的其实是指向数组首元素的指针。因为处理的是数组的数组,所以首元素本身就是一个数组,指针就是一个指向数组的指针。
*matrix 两端的括号必不可少
2.6 可变形参要编写能处理不同数量实参的函数,主要有两种方法。
如果函数的实参数量未知但全部实参的类型都相同,就可以使用 initializer_list 类型的形参。它是一种标准库类型,主要提供以下操作:
类似于 vector,initializer_list 也是一种模板类型。定义 initializer_list 对象时必须说明列表中的元素类型:
不同于 vector 的地方在于 initializer_list 中的元素都是常量值,无法改变。 因此要给函数传递未知数量的实参时,可以将函数改写成如下的形式:
若想向 initializer_list 形参传递一个值的序列,则需要把序列放在花括号中:
省略符形参是为了便于 C++ 访问某些特殊的 C 代码而设置的,它们使用了名为 varargs 的 C 标准库功能。省略符形参只能出现在形参列表的最后面,有以下两种形式:
前者指定了部分形参的类型,只有这部分形参需要进行类型检查(编译器要检查实参和形参的类型是否一致),而省略符形参对应的实参无需类型检查。 3 返回类型和 return 语句return 语句的两种类型:
3.1 无返回值第一种形式只能用在返回类型是 void 的函数,但返回 void 的函数不要求非得有 return 语句。即使没有的话,它们也会在函数体的最后一句隐式的执行 return。而 return 用在返回 void 的函数中的主要作用是退出函数。所以也可以将其用在函数执行过程中来提前退出函数。 但返回 void 的函数也不是不能用第二种形式,此时 expression 必须是另一个返回 void 的函数,返回其他类型则会报错。 3.2 有返回值有返回值的函数必须使用第二种形式,返回值的类型必须与函数的返回类型相同,或者能隐式地转换成函数的返回类型。 不要返回局部对象的引用或指针,因为函数一旦完成,它所占用的内存空间也随之被释放掉,也意味着局部对象的引用或指针将引用一个不再存在的对象或指向不再有效的内存区域。 3.3 返回引用调用一个返回引用的函数得到的是左值,其他的返回类型则是右值。因此可以让一个函数返回引用。特别地,可以为返回类型是非常量引用的函数的结果赋值(如果返回类型是常量引用就不能给调用的结果赋值):
注意到 function 前有个 &,表明函数返回的是 char & 类型,因此 function(s, 0) 返回的实际上是 string 对象 s 的0号位置上字符的引用,将其作为左值修改为 A,从而输出的结果如下: 3.4 列表初始化返回值函数可以返回花括号包围的值的列表。类似于其他返回结果,此处的列表也用来对变量进行初始化。如果返回的列表是空,临时量就执行值初始化;否则,返回的值由函数的返回类型决定。如下:
(确实是没想到 vector 也能作为返回类型,但仔细一想想好像也没什么不可以的)如果函数返回的是内置类型,则花括号包围的列表最多包含一个值,而且该值所占空间不应该大于目标类型的空间。如果返回的是类类型,由类本身定义初始值如何使用。 3.5 递归如果一个函数调用了它自身,不管这种调用是直接的还是间接的,都称该函数为递归函数。 3.6 返回数组指针因为数组不能像 vector 那样拷贝,所以函数不能返回数组,但是可以返回数组的指针或引用。但直接定义一个返回数组指针或引用的函数很烦琐,最直接的方法就是使用类型别名:
上面第二条语句可以参考C++ 标准库类型学习笔记(一)(vector、string 篇)4.5 类型别名那一块。 3.6.1 声明一个返回数组指针的函数要想在声明 function 时不使用类型别名,就必须牢记被定义的名字后面数组的维度:
返回数组指针的函数声明形式如下:
Type 表示元素的类型,function 是函数名,parameter_list 是形参列表,dimension 表示数组的大小。用一个具体的例子来解释:
可以按照由内到外的顺序来理解该声明:
3.6.2 使用尾置返回类型使用尾置返回类型可以简化上述 func 声明。任何函数都能使用尾置返回,但是这种形式对于返回类型比较复杂的函数最有效,比如返回类型是数组的指针或引用。尾置返回类型跟在形参列表后面并以一个 “->” 符号开头。然后在本应该出现返回类型的地方放一个 auto 表示函数真正的返回类型跟在形参列表之后。
3.6.3 使用 decltype如果知道函数返回的指针将指向哪个数组,就可以使用 decltype 关键字声明返回类型:
decltype 并不负责把数组类型转换成对应的指针,所以 decltype 的结果是个数组,要想表示 arrPtr 返回指向数组的指针还必须在函数声明时加一个 * 符号。 练习编写一个函数的声明,使其返回数组的引用并且该数组包含10个 string 对象。要求分别使用直接声明、类型别名、尾置返回类型以及 decltype。 我估计大多数读者都会疑惑,要求是数组(也就意味着不是 vector),还要求元素对象是 string,那该如何定义呢?我去百度查阅了才知道,原来 string 是可以定义成数组的…比如下面这种:
输出结果如下:
4 函数重载同一个作用域下,几个函数名字相同但形参列表不同,称之为重载函数。目的是减轻程序员起名字、记名字的负担。对于重载的函数来说,必须在形参的数量或者类型上有所不同。但有一种情况也不能算做重载函数,即2.1中提到的,实参初始化形参时会忽略掉顶层 const,一个拥有顶层 const 的形参无法和另一个没有顶层 const 形参区分开来:
但是,如果形参是指针或者引用,那么通过区分其指向(引用)的是常量对象还是非常量对象就可以实现函数重载,因为此时的 const 是底层的:
因为 const 不能转换成其他类型,所以只能把 const 对象传递给 const 形参;相反,因为非常量可以转换成 const,所以上面的四个函数都能作用于非常量对象或者指向非常量对象的指针。但是传递一个非常量对象或者指向非常量对象的指针时,编译器会优先选用非常量的版本。 5 特殊用途语言特性5.1 默认实参有时候,我们希望函数的某些形参在每次调用中都被赋予一个相同的值,这样我们在调用时就不需要每次都传值进去;但偶尔我们也希望自己重新传一个新的值进去计算,从而得到一个其他计算结果。此时就需要用到默认实参。比如我们想声明一个函数,它用来计算高度相等,半径不等的圆柱体的体积:每次计算,我们都只需传入圆的半径即可;也可以传入新的高度进行计算;或者如果需要更精确的结果,就传入精度更高的 π 值:
有一点需要注意,一旦某个形参被赋予了默认值,在它右边的所有形参都必须有默认值。在给定的作用域中一个形参只能被赋予一次默认实参,函数的后续声明只能为之前那些没有默认值的形参添加默认实参,并且该形参右侧的所有形参都必须有默认值。 实参在初始化形参时是按照位置来的,如上面的 Cal_Volume 函数:如果我们不想改变高度,只想传入精度更高的 π,我们也必须填补上高度值,否则 Cal_Volume(2, 3.1415926) 的 3.1415926 会被用来初始化 hight。所以我们要尽量让不怎么使用默认值的形参出现在前面,让经常使用默认值的形参出现在后面。 5.2 内联函数和 constexpr 函数5.2.1 内联函数将函数指定为内联函数,通常就是将它在每个调用点上展开。将函数定义为内联函数需要在返回类型前加上 inline 关键字,比如:
那么下面的语句在调用 compare 函数,在编译过程中会展开成后者的形式:
这样做的目的是消除 compare 函数运行时的开销(涉及到中断的概念了)。一般来说,内联机制用于优化规模较小、流程直接、频繁调用的函数。而且很多编译器也不支持内联递归函数。 5.2.2 constexpr 函数constexpr 函数是指能用于常量表达式的函数。定义 constexpr 函数要遵循几项约定:函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条 return 语句:
func 函数返回的是常量表达式,所以能用它来初始化 constexpr 类型的变量 foo。在执行初始化时,编译器会把对 constexpr 函数的调用替换成结果值,也就是说,constexpr 函数被隐式地指定为内联函数。当然也可允许 constexpr 函数的返回值并非一个常量:
当调用 func 函数传入的实参是常量表达式时,比如 func(2),它的返回值也是常量表达式。但如果传入的是非常量表达式,比如 func(i),就不能够拿来初始化其他的常量表达式 编译器要想展开内联函数,仅仅有声明是不够的,还必须要有函数的定义。多次定义都必须一致,因此通常将内联函数和 constexpr 函数定义在头文件中。 6 函数指针函数指针指向的是函数而非对象。函数指针指向某种特定类型,而函数的类型由它的返回类型和形参类型共同决定:
从声明的名字开始观察,pf 前面有个 * 表明 pf 是指针;右侧的形参列表表明 pf 指向的是函数;左侧的 bool 表明返回类型是布尔值。因此,pf 是一个指向函数的指针,函数的参数是两个 const string 的引用,返回值是 bool 类型。 pf 两端的括号不能少,否则如下:
此时是声明了一个名为 pf 的函数,该函数返回一个 bool 类型的指针。 6.1 使用函数指针可以将函数名作为一个值使用,此时函数自动转换成指针,如下将函数 function 的地址赋给 pf:
可以直接使用指向函数的指针调用函数而无须解引用:
因为指针指向的是函数,所以指向不同函数的指针间不存在什么转换规则。但是可以为指针赋一个 nullptr 或者值为0的整型常量表达式,表示该指针没有指向任何一个函数。 6.2 重载函数的指针指针指向重载函数时,函数的类型一定要表示清楚:
6.3 函数指针形参函数的形参也可以是指向函数的指针,类似数组,此时的形参看起来是函数类型,实际上是当成指针来使用:
使用类型别名可以简化形参的代码,如下所示:
第一条语句实际上是给 bool (const string, const string &) 定义了一个别名 func1,用来指代任何参数是两个 const string 的引用,返回值是 bool 类型的函数。第二条语句是第一条语句的等价语句,decltype 返回的是上述的函数类型。要定义指向函数的指针的代码如下:
同理,第一条语句实际上是给 bool (*)(const string, const string &) 定义了一个别名 pf1,用来指代任何指向该函数类型的指针。第二条语句是第一条语句的等价语句,由于 decltype 返回的结果是函数类型,所以要在 pf2 前加上 * 才能得到指针。 因此可以用类型别名重新声明 test 函数:
6.4 返回指向函数的指针函数不能返回函数,但是能返回指向函数的指针。但是要将返回类型写成指针的形式,因为编译器不会将函数返回类型当成对应的指针。使用类型别名来返回函数的指针:
必须显式地将返回类型指定为指针:
也可用下面的形式直接声明 f1:
由内向外进行理解:f1 有形参列表,所以 f1 是个函数;f1 前面有个 *,所以 f1 返回一个指针;进一步可以看到指针类型本身也包含形参列表,因此指针指向函数,函数的返回类型是 int。 也可以使用尾置返回类型声明一个返回函数指针的函数:
希望本篇博客能对你有所帮助,也希望看官能动动小手点个赞哟~~。 |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 | -2025/1/11 12:49:33- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |