0
点赞
收藏
分享

微信扫一扫

【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​​,其定义在同名的头文件中

【C++Primer】第6章:函数_开发语言

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


【C++Primer】第6章:函数_后端_02

和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新标准规定,函数可以返回花括号包围的值的列表。


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

主函数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. 找到了好几个,但都不是明显的最佳选择,此时也将发生错误,称为二义性错误

重载与作用域

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

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

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

6.5 特殊用途语言特性

默认实参

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

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

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

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

默认实参声明

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

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

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

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

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

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

内联函数

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

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

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

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

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

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

【C++Primer】第6章:函数_数组_06

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

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. 确定最佳匹配

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

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

实参类型转换

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

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表示的函数类型指针转换成指针

返回指向函数的指针

【C++Primer】第6章:函数_开发语言_08

实例:编写两个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;
}

【C++Primer】第6章:函数_后端_09


举报

相关推荐

0 条评论