【C++Primer】第6章:函数

第6章 函数

6.1 函数基础

函数的调用完成两项工作:一是用实参初始化函数对应的形参,二是将控制权转移给被调函数。

被调函数的return语句也完成两项工作:一是返回return语句中的值(如果有的话),二是将控制权从被调函数转移到主调函数。

定义空形参列表

void f1(){}      //隐式的定义形参列表
void f2(void){}  //显式的定义形参列表

函数的返回类型不可以是数组类型或函数类型,但可以是指向数组或函数的指针

形参:函数定义时的参数

实参:函数调用时的参数,函数实际操作对象

局部对象

对于普通局部变量对应的对象来说,当函数的控制路径经过变量定义语句时创建该对象,当到达定义所在的块末尾时销毁它。这种对象叫自动对象

形参是一种自动对象。函数开始时为形参申请存储空间,因为形参定义在函数体作用域之内,所以一旦函数终止,形参也就被销毁。

在所有函数体之外的对象存在于程序的整个执行过程中。此类对象在程序启动时被创建,直到程序结束才会销毁。

局部静态对象在程序执行的路径第一次经过对象定义语句时初始化,并且直到程序终止才会被销毁,在此期间即使对象所在的函数结束执行也不会对它有影响。用static修饰。

函数声明

函数只能定义一次,但是可以声明多次

如果一个函数永远不会被我们用到,那么可以只有声明没有定义

因为函数的声明不包含函数体,所以声明可以不加上形参的名字

6.2 参数传递

传值参数

即值拷贝,

值形参改变不会影响实参

指针形参改变会影响实参

当执行指针拷贝时,拷贝的是指针的值。

传引用参数

形参会修改实参

如果函数无需改变引用形参的值,最好将其声明为常量引用,加const

一个函数只能返回一个值

const形参和实参

当形参有顶层const时,实参初始化形参时会忽略掉顶层const,传给它常量对象或者非常量对象都是可以的

void func(const int i){}
void func(int i){}  //错误,重复定义

形参的初始化和变量的初始化是一样的

数组形参

int *matrix[10];   //10个指针构成的数组
int (*matrix)[10];  //指向含有10个整数的数组的指针
//三个等价
void print(const int*);
void print(const int[]);
void print(const int[10]);

数组的引用

void print(int (&arr)[10]){
    for(auto elem : arr){
        cout << elem << endl;
    }
}

含有可变形参的函数

为了编写能处理不同数量实参的函数,C++11新标准提供了两种主要的方法:如果所有实参类型相同,可以传递一个名为initializer_list的标准库类型;如果实参的类型不同,可以编写一种特殊的函数,也就是可变参数模板。

如果函数的形参数量未知但是类型都相同,可以使用initializer_list,其定义在同名的头文件中

image-20211221171326937

initializer<string> ls;  //元素类型是string
initializer<int> li;     //元素类型是int
image-20211221171614580

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

6.3 返回类型和return语句

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

return;
return expression;

void函数不要求非得写return语句

不要返回局部对象的引用或指针;调用的函数终止局部变量会释放

引用返回左值

char &get_val(string &str, string::size_type ix){
    return str[ix];   //假定索引值是有效的
}

int main(){
	string s("a value");
    cout << s <<endl;    // a value
    get_val(s,0) = 'A';  //s[0]的值改为A
    cout << s << endl;   // A value
    return 0;
}
shorterString("hi", "bye") = "X";   //错误:返回值是一个常量

列表初始化返回值

C++11新标准规定,函数可以返回花括号包围的值的列表。

image-20211221181115010

主函数main可以没有返回值,编译器会隐式地插入一条返回0的return语句。

main函数不能调用自己

返回数组指针

typedef int arrT[10];   //arrT是一个类型别名,它的类型是含有10个整数的数组
using arrT = int[10];   //同上
arrT* func(int i);      //func返回一个指向含有10个整数的数组的指针
int arr[10];  //arr是一个含有10个整数的数组
int *p1[10];  //p1是一个含有10个指针的数组
int (*p2)[10]; //p2是一个指针,指向含有10个整数的数组

尾置返回类型

C++11新标准

//func接受一个int类型的实参,返回一个指针,该指针指向含有10个整数的数组
auto func(int i) -> int(*)[10];

6.4 函数重载

如果同一作用域内的几个函数名字相同但形参列表不同,我们称之为重载函数

main函数不能重载

Record lookup(Phone);
Record lookup(const Phone);  //重复声明
Record lookup(Account&);        //函数作用于Account的引用
Record lookup(const Account&);  //新函数,作用于常量引用

Record lookup(Account*);        //新函数,作用于指向Account的指针
Record lookup(const Account*);  //新函数,作用于指向常量的指针

函数匹配,即重载确定,有三种情况

  1. 编译器找到一个与实参最佳匹配的函数,并生成调用该函数的代码
  2. 一个都找不到,编译器发出无匹配的错误
  3. 找到了好几个,但都不是明显的最佳选择,此时也将发生错误,称为二义性错误

重载与作用域

如果我们在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体。

image-20211221220238549

image-20211221220252420

6.5 特殊用途语言特性

默认实参

一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值

如果想使用默认实参,调用的时候省略该参数就可以

window = screen(, , '?');  //错误:只能省略尾部的实参

在设计含有默认实参的参数时,尽量让不怎么使用默认值的形参出现在前面,而让那些经常使用默认值的形参出现在后面。

默认实参声明

定义只能有一次,声明可以有多次

string screen(sz, sz, char=' ');

string screen(sz, sz, char='*');  //错误:重复声明,试图修改一个已经存在的默认值

string screen(sz=24, sz=80, char=' ');  //正确:添加默认实参

局部变量不能作为默认实参

表达式的类型能转换成形参所需的类型,该表达式就能作为默认实参

内联函数

封装成函数的优点很多,这里就不讲

函数调用的缺点,一次函数调用其实包含着一系列工作:调用前要先保存寄存器,并在返回时恢复;可能需要拷贝实参;程序转向一个新的位置继续执行。

内联函数可以避免函数调用的开销

将函数指定为内联函数,通常就是将它在每个调用点上“内联地”展开。

一般来说,内联机制用于优化规模较小、流程直接、频繁调用的函数

在编写函数时,在返回类型前面加上关键字inline,就声明成了内联函数

image-20211221223043505

其实很多编译器不支持内联函数,而且一个很长的函数怎么内联展开

constexpr函数

第2章讲的是constexpr变量,这里是函数

constexpr函数指能用于常量表达式的函数

定义constexpr函数,函数的返回值类型及所有形参的类型都得是字面值类型

constexpr int new_sz(){return 42;}  //new_sz()是constexpr函数
constexpr int foo = new_sz();       //正确,foo是一个常量表达式

constexpr函数不一定返回常量表达式,那时候报错而已

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

6.6 函数匹配

函数匹配有三个步骤:

  1. 确定重载函数集。候选函数两个特征:一是与被调用的函数同名,二是其声明在调用点可见
  2. 确定可行函数。可行函数有两个特征:一是其形参数量与本次调用提供的实参数量相等,二是每个实参的类型与对应的形参类型相同,或者能转换成形参的类型
  3. 确定最佳匹配

如果没找到可行函数,编译器将报告无匹配函数的错误

如果找不到最佳匹配。编译器最终因二义性拒绝调用请求

实参类型转换

image-20211222111055983

void ff(int);
void ff(short);
ff('a');         //char提升成int,调用ff(int)
void manip(long);
void manip(float);
manip(3.14);       //二义性  double存在两种算术类型转换
Record lookup(Account&);       //函数的参数是Account的引用
Record lookup(const Account&);  //函数的参数是一个常量引用
const Account a;
Account b;

lookup(a);   //调用lookup(const Account&)
lookup(b);   //调用lookup(Account&)

6.7 函数指针

函数指针指向的是函数而非对象

//比较两个string对象的长度
bool lengthCompare(const string &, const string &);

要想声明一个可以指向该函数的指针,只需要用指针替换函数名即可

//pf指向一个函数,该函数返回值bool类型,两个参数
bool (*pf)(const string &, const string &);  //未初始化

使用函数指针

pf = lengthCompare;   //pf指向名为lengthCompare的函数
pf = &lengthCompare;  //等价  &是可选的

调用函数

bool b1 = pf("hello", "goodbye");
bool b1 = (*pf)("hello", "goodbye");          //等价
bool b1 = lengthCompare("hello", "goodbye");  //等价

同样可以为函数指针赋一个nullptr或值为0的整型常量表达式,表示该指针不指向任何一个函数

函数指针形参

函数指针可以作为形参

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);

直接使用函数指针类型显得冗长而繁琐,可以简化:

//Func和Func2是函数类型
typedef bool Func(const string&, const string&);
typedef decltype(lengthCompare) Func2;   //等价

//FuncP和FuncP2是指向函数的指针
typedef bool(*FuncP)(const string&, const string&);
typedef decltype(lengthCompare) *FuncP2;  //等价
void useBigger(const string&, const string&, Func);
void useBigger(const string&, const string&, FuncP2);  //等价

在第一条语句中,编译器自动地将Func表示的函数类型指针转换成指针

返回指向函数的指针

image-20211222114943402

实例:编写两个int加减乘除,返回值为int,vector对象保存指向这些函数的指针,并输出打印每个函数

int add(int a, int b) {
	return a + b;
}
int sub(int a, int b) {
	return a - b;
}
int mul(int a, int b) {
	return a * b;
}

int main() {
	//typedef int(*p)(int a, int b);
	using p = int(*)(int, int);    //等价
	vector<p> v = { add,sub,mul };
	for (auto f : v) {
		cout << f(2, 2) << endl;
	}

	system("pause");
	return 0;
}

image-20211222121125963

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zdb呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值