第六章 函数
函数
函数基础
函数包括返回类型、函数名字、0个或者多个形参组成的列表、函数体。
int fact(int val) //val是形参
{
...
return (int);
}
int main()
{
int ans = fact(5); //括号是调用运算符、5是实参
return 0;
}
函数调用完成两项工作:用实参初始化对应的形参,将控制权转移给被调用函数,此时主调函数的执行被暂时中断。
形参一定会被初始化。实参是形参的初始值。
局部对象
名字有作用域,对象有生命周期。
- 对象的生命周期是程序执行过程中该对象存在的一段时间。
- 局部变量:形参和函数体内定义的变量统称为局部变量。局部变量仅在函数的作用域内可见,同时局部变量还会隐藏在外层作用域中的同名的其他所有声明中。
- 自动对象:当函数的控制路径经过变量定义语句时创建该对象,当到达定义所在的块末尾是销毁它。我们把只存在于块执行期间的对象称为自动对象。形参是一种自动对象
- 局部静态变量:局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁。可以将局部变量定义成static类型从而获得这样的对象。
函数声明
函数声明也称作函数原型。
建议变量在头文件中声明, 在源文件中定义,函数也应该在头文件中声明,在源文件中定义。
//Chapter6.h
#pragma once
#ifndef CHAPTER6_H
#define CHAPTER6_H
#include <cmath>
int fact(int);
int fact(int add1, int add2);
#endif
//Chapter6.cpp
#include "Chapter6.h"
int fact(int num)
{
return abs(num);
}
int fact(int add1, int add2)
{
return add1 + add2;
}
参数传递
- 如果形参是引用类型,它将绑定到对应的实参上;否则,将实参的值拷贝后赋值给形参。
- 指针形参 :建议使用引用类型代替指针。
传引用参数
拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型不支持拷贝操作,当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。
当函数无需修改引用形参的值时最好使用常量引用。
const形参和实参
当形参有顶层const时, 传给它常量对象或者非常量对象都是可以的。反之,不行。不能将常量对象传给非常量形参。
数组形参
数组的性质:不允许拷贝数组,使用数组是通常会将其转换成指针。
- 不能以值传递的方式传递数组,但是我们可以把形参写成类似数组的形式。
void print(const int*);
void print(const int[]);
void print(const int[10]); //这里的维度表示我们期望含有多少元素,实际不一定。
//以上三个函数的唯一形参都是const int*.
int i = 0, j[2] = {0, 1};
print(&i); //&i 的类型是int *
print(j); //j 转换成int* 并指向j[0];
- 管理指针形参的三种常用技术
- 使用标记指定数组长度(c风格字符串)
- 使用标准库规范(传入首元素和尾后元素的指针)
- 显式传递一个表示数组大小的形参(定义一个表示数组大小的形参)
- 当函数确实要改变元素值时, 才把形参定义成非常量的指针。
- 数组引用形参
int &arr[10]; //错误 : 将arr声明成了引用的数组(有十个引用的数组)。
int (&arr)[10]; //arr是含有十个整数的整型数组的引用。
数组的大小是构成数组类型的一部分,所以只要不超过维度,在函数体内就可以放心的使用数组。但是同样限制了print函数的可用性, 只能将函数作用于大小为10的数组。
有返回值的函数
return:不要返回局部对象或指针
例
const string &mainp()
{
string ret = "abc";
return ret;//错误, return返回的是局部对象的引用
}
const string &mainp()
{
return "HHH"; //错误,字符串字面值转换成一个局部临时string对象,同ret一样都是局部的。
}
当函数结束时临时对象所占的空间也就随之释放掉了,所以两条return都指向了不再可用的内存空间。
返回数组指针
数组不能拷贝,所以函数不能返回数组,但是可以返回数组的指针或者引用.
typedef int arrT[10]; //arrT 是类型别名, 表示的类型是含有10个整数的数组
using arrT = int [10]; //arrT 的等价声明
arrT* func(int i); //func 返回一个指向含有10个整数的数组的指针
如果不使用类型别名, 必须记住,数组的维度要跟随要定义的数组名之后。
int arr[10]; //arr 是一个含有10个整数的数组
int *p1[10]; //p1 是一个含有10个指针的数组
int (*p2)[10] = &arr; // p2 是一个指针, 指向含有10个整数的数组
定义返回数组指针的函数形式: Type (*function (parameter_list))[dimersion]
Type 是元素类型, dimension是数组大小,(*function (parameter_list)) 调用这个函数后会得到一个大小是dimersion的数组。
函数重载
- 如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数。
- 当调用名字相同的函数时,会根据传递的实参类型推断想要的是哪个函数。不允许两个函数除了返回类型外其他所有的要素都相同
Record lookup(const Account&);
bool lookup(const Account&); /错误,只有返回类型不同
- 判断两个形参类型是否相异
Record lookup(const Account &acct); // 给形参起了名字
Record lookup(const Account&); //相同, 省略了形参的名字
Phone phone;
typedef Phone telno;
Record lookup(const Phone&);
Record lookup(const telno); //telno 和 Phone 的类型相同
- 重载和 const 形参
Record lookup (Phone);
Record lookup (const Phone); //顶层const 不影响传入函数的对象,所以和上一个等价
Record lookuo(Phone *);
Record lookup(Phone *const); //同上
Record lookup (Phone&);
Record lookup (const Phone&); //新函数,作用于常量引用
Record lookuo(Phone *); //作用于指向Phone的指针
Record lookup(const Phone*); //新函数, 作用于指向常量的指针
如果形参是某种类型的指针或引用,则通过区分其指向的是常量还是非常量对象可以实现函数重载。此时const 是底层的。
当我们传递一个非常量对象或者指向非常量对象的指针时, 编译器会优先选择非常量版本。
- const_cast 和重载
强制类型转换:cast_name(expression); (type 是转换的目标类型; expression 是要转换的值;cast_name 有四种 : stati_cast, const_cast, dynamic_cast, reinterpret_cast)
const string &shortString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
const string& ss = shortString("aaaa", "bbbb"); //函数的参数和返回类型都是const string 的引用。可以对两个非常量的string 实参调用这个而函数, 但是返回的结果是const string的引用
string &shortString(string &s1, string &s2)
{
auto &r = shortString(const_cast<const string&>(s1),const_cast<const string&>(s2));
return const_cast<string&>(r);
}//首先将它的实参强制转换成对const的引用, 然后调用了shortString 函数的const 版本, const 返回对const string 的引用, 这个引用事实上绑定在了某个初始的非常量实参上, 因此我们可以将其转换回一个普通的string&, 这显然是安全的。
默认实参
函数中,一旦某个形参被赋予了默认值, 那么它后面所有形参都必须有默认值。
局部变量不能作为默认形参。
内联函数和constexpr函数
内联函数相当于在编译过程中将函数展开。内联机制用于优化规模较小,流程直接,调用频繁的函数。
constexpr函数是指能用于常量表达式的函数,定义constexpr要遵循几条规定,函数的返回值类型及所有形参类型都是字面值类型,而且函数体中必须有且只有一条return语句。
内联函数和constexpr函数可以在程序中多次定义。对于某个给定的内联函数或者constexpr函数来说, 它的多个定义必须完全一致, 基于这个原因,内联函数和constexpr函数通常定义在头文件中。
类型转换
- 5种转换类型(第四章)
所有算术类型转换级别都一样。如
void manip (long);
void manip (float);
manip(3.14) //产生二义性
函数指针
函数指针指向的是函数而非对象。
bool lengthCompare (const string &, const string &);
bool (*pf)(const string &, const string &); //未初始化, pf 指向一个函数, 该函数的参数是两个const string的引用, 返回值是bool类型。
// *pf 两端括号不能少。如果不写的话,pf 是一个返回值为bool指针的函数
- 使用函数指针
当我们把函数名作为一个值使用时, 该函数自动转换成指针。此外我们还能直接使用指向函数的指针调用该函数,无须提前解引用指针
pf = lengthCompare;
pf = &lengthCompare; // 两者都是指向名为 lengthCompare 的函数
bool b1 = pf("AA", "BB");
bool b2 = (*pf)("AA", "BB");
bool b3 = lengthCompare("AA", "BB");
- 重载函数指针
当我们使用重载函数时, 上下文必须清晰的界定到底应该选用哪个函数,编译器通过指针类型决定到底选用哪个函数,指针类型必须与重载函数中的某一个精准匹配。 - 函数指针形参
和数组类似,虽然不能定义函数类型的形参,但是形参可以是指向函数的指针。此时,形参看起来是函数类型,实际上却是当成指针使用。
void useBigger(const string &s1, const string &s2, bool pf (const string &, const string &));
// 第三个形参是函数类型, 它会自动的转换成指向函数的指针。
void useBigger(const string &s1, const string &s2, bool (*pf) (const string &, const string &));
//等价的声明: 显式的将形参定义成指向函数的指针。
//我们也可以直接把函数作为实参使用, 此时它会自动转换成指向函数的指针
useBigger(s1, s2, lengthCompare);
类型别名能让我们简化使用了函数指针的代码
typedef bool (*FuncP)(const string &, const string &);
rtpedef decltype(lengthCompare) * FuncP2; //FuncP 和 FuncP2是指向函数的指针
需要注意的是, decltype 返回函数类型, 此时不会将函数类型自动转换成指针类型,因为decltype 的结果是函数类型, 所以只有在前面加上 * 才能得到指针。
返回指向函数的指针
返回指向函数的指针, 必须把返回类型写成指针的形式,编译器不会自动的将函数的返回类型当成对应的指针类型处理。要想声明一个返回一个返回函数指针的函数, 最简单的办法是使用类型别名。
using F = int(int *, int); // F 是函数类型, 不是指针
using PF = int (*) (int *, int); //PF 是指针类型
PF f1(int); //正确,f1 返回类型是指向函数的指针
F f1(int); //错误,F是函数类型,不能返回一个函数
F *f1(int); //显式的指定返回类型是指向函数的指针
加强理解
//直接声明
int (*f1(int))(int *, int);
//由内而外的顺序,f1是一个函数,返回的是一个指针,指针本身也有形参列表, 因此指针指向函数, 函数的返回类型是int。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)