C++primer 第6章

6.1函数基础

函数定义包括:返回类型、函数名字、形参列表和函数体。

调用运算符是(),它作用域表达式,该表达式是函数或者指向函数的指针;圆括号内是实参列表,用实参初始化函数的形参。

调用表达式的类型就是返回值类型。

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

6.1.1局部对象

在C++中,名字有作用域,对象有声明周期。

函数体是一个语句块。块构成一个新的作用域,可以在其中定义变量。形参和函数体内部定义的变量称为局部变量。局部变量对函数而言是“局部”的,仅在函数的作用域内可见,同时局部变量还会隐藏(hide)在外层作用域中同名的其他所有声明中。

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

自动对象

       只存在于块执行期间的对象称为自动对象。块的执行结束后,块中创建的自动对象的值就变成未定义的了。

形参是一种自动对象。函数开始时为形参申请存储空间,一旦函数终止,形参也就被销毁。

局部静态对象

       局部变量的生命周期贯穿函数调用以及之后的时间。局部静态对象在程序的执行路径第一次经过对象定义语句时初始化,并且知道程序终止才销毁,在此期间即使对象所在的函数结束执行,也不会对它有影响。

       在控制流第一次经过ctr的定义前,ctr被创建并初始化为0.每次调用ctr加1并返回新值。每次执行count_calls函数时,ctr的值都已经存在且等于上次函数退出时ctr的值。

6.1.2函数声明

函数只定义一次,可以声明很多次。

声明不需要函数体,用分号替代。声明不需要形参的名字,只需要类型,写了名字容易理解。

变量、函数在头文件声明。在源文件中定义。

定义函数的源文件,应该把含有函数声明的头文件包含起来,编译器负责验证函数的定义和声明是否匹配。

6.1.3分离式编译

分离式编译,允许我们把程序分割到多个文件,每个文件单独编译。

编译和链接多个源文件

举例,假设fact函数的定义位于一个名为fact.cc的文件中,它的声明位于名为Chapter6.h的头文件中。显然与其他所有用到fact函数的文件一样。fact.cc应该包含Chapter6.h头文件。另外,我们在factMain.cc的文件中创建main函数,main函数将调用fact函数。要生成可执行文件,必须告诉编译器我们用到的代码在哪里。

一起编译

$CC factMain.cc fact.cc -o main        #生成main可执行文件

分离式编译:

$CC -c factMain.cc                           #生成factMain.o

$CC -c fact.cc #生成fact.o                #生成fact.o

$CC factMain.o fact.o -o main           #把对象文件链接在一起,形成main可执行文件

6.2参数传递

每次调用函数时,都会重新创建它的形参,并用传入的实参对形参初始化。

如果形参是引用类型,它将绑定到对应的实参(形参是实参的别名);如果形参是值类型,则将实参的值拷贝后赋值给形参(拷贝后,形参和实参是相互独立的)。

6.2.1传值参数

传值参数,函数对形参的操作,不会影响实参。

指针形参

当执行拷贝指针操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的,但是通过它们可以修改同一个对象。

6.2.2传引用参数

对于引用的操作,实际上是作用在引用所引的对象上。

使用引用,避免了拷贝。

拷贝大的类类型对象或者容器对象比较低效,甚至有的类根本不支持拷贝操作。

使用引用,返回额外信息。

一个函数只能返回一个值,然而有时函数需要同时返回多个值。

通过传引用的方式,就可避免返回多个不同类型的值。

6.2.3const形参和实参

当形参是const时。顶层const作用域对象本身。

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

能够读取i,但是不能向i写值。

调用func函数时,既可以传const int,也可以传int。

c++允许定义相同名字的函数,不过前提是不同函数的形参列表有明显的区别。因为顶层const被忽略掉了,所以上面2个func的参数是完全一样的。

指针或引用形参与const

我们可以使用非const实参初始化底层const对象/形参,但是反过来不行。(不能用底层const实参初始化非const对象/形参,因为非const形参难免会修改const对象)

尽量使用常量引用

把函数不会改变的形参定义成普通引用(非const引用),是不对的。因为,这样我们就不能把const对象、字面值或需要类型转换的对象传递给普通的引用形参。

6.2.4数组形参

不允许拷贝数组;使用数组时会将其转换成指针。

因为不能拷贝数组,所以无法以值传递的方式使用数组参数。因为数组会转换成指针,所以当我们为函数传递一个数组时,实际上传递的是数组首元素的指针。

print、print1、print2三者等价。形参都是const int*。如果我们传入print函数是一个数组,则实参自动转换成指向数组首元素的指针,数组的大小对函数的调用没有影响。

以数组作为形参的函数也必须确保:使用数组不越界。

为此,需要额外信息,才能确定数组的尺寸,才能保证不越界。

使用标记,表示数组结束

字符数组以’\0’表示结束。int[] 不知道。

使用标准库规范

传递指向数组首元素和尾后元素的指针。

用到begin(),end()

显示传递数组大小的形参

数组形参和const

只有当函数确实要修改元素值的时候,才把形参定义为非const的指针。

数组引用形参

C++允许将变量定义成数组的引用,形参也可以是数组的引用。

但是,这样就限制了数组的长度为7。

使用模板类型参数:

编译器会使用字面量常量的大小代替N。(16.1.1 P580)

6.2.5main:处理命令行选项

int main(int argc, char* argv[]) { . . . }

第二个形参argv是数组,它的元素是指向 C风格字符串的指针;

第一个形参argc表示数组中字符串的数量。

当实参传递给main函数之后,argv的第一个元素指向程序的名字或者一个空字符串,接下来的元素依次传递命令行提供的实参。最后一个指针之后的元素值保证为0。

若命令行为:

prog -d -o ofile data0

则,argc=5,argv应该包含

argv[0]=”prog”

argv[1]=”-d”

argv[2]=”-o”

argv[3]=”ofile”

argv[4]=”data0”

argv[5]=0;

6.2.6含有可变形参的函数

有时,我们无法预知应该向函数传递几个实参。

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

initializer_list形参

实参数量未知,但是类型都相同。

 

6.3返回类型和return语句

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

值是如何被返回的

返回一个值的方式 和 初始化一个变量或形参的方式完全一样:返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果。

不要返回局部对象的引用或指针

函数完成后,它所占用的存储空间也随之被释放。因此,函数终止意味着局部变量的引用将指向不再有效的内存区域。

返回局部对象的引用、局部对象的指针,都是错误的。

引用返回左值

函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值其他返回类型得到右值

可以像其他左值那样来使用返回引用的函数的调用,我们能为返回类型是非const引用的函数的结果赋值:

 

返回值是引用,因此调用是个左值,能出现在赋值运算符=的左侧。引用绑定的是有名字的、已经初始化的变量(非const),所以是左值,不是临时变量。

6.3.3 返回数组指针

因为数组不能被拷贝,因此函数不馁返回数组。不过,可以返回数组的指针或引用。

用类型别名可以省事。

  

使用尾置返回类型

?*?

decltype

decltype不负责把数组类型转换成对应的指针,需要手动加* 。

6.4函数重载

同一作用域内,函数名字相同,但是形参列表不同,称之为重载函数。main不能重载

重载和const形参

一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来。

重复定义。

重复定义

如果形参是某种类型的指针或引用,则通过区分其指向的是const对象还是非const对象可以实现函数重载,此时的const是底层的。

  

当我们传递一个非const对线或指向非const对象的指针,编译器会优先选用非const版本的函数。

const_cast和重载

 

old版本的输入、输出都是const引用,实际上并不方便使用,因为我们操作的string通常是非const,且我们希望输出结果也是非const,最好是引用,省去拷贝。

因此新版本采用string&,并使用const_cast强制转换。把非const引用,转换成const引用,调用old版本。新版本输出结果再由const引用转换成非const引用。

6.4.1重载与作用域

不同的作用域中,无法重载同名函数。

6.5特殊用途语言特性

默认实参

函数的声明习惯是放在头文件,一般只声明一次。在给定作用域中,一个形参的默认实参只能赋予一次。该函数后续声明,只能为前面没有默认值的形参添加默认实参,而且该形参右侧所有形参必须有默认值。      后续声明不能改前面声明过的默认实参。

6.5.2内联函数和constexpr函数

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

inline内联,在每个函数调用点展开。内联机制用于优化规模较小、流程直接(没有循环)、频繁调用(省得频繁地找该函数)。就是在许多调用处插入该函数,插入很多备份。

constexpr函数

能用于常量表达式的函数。返回值及所有形参类型都得是字面值类型,且有return。

编译器能在程序编译时 验证返回值是不是常量表达式constexpr。执行初始化任务时,编译器把constexpr函数的调用替换成结果值。为了能在编译过程中随时展开,constexpr函数被隐式地指定为内联函数。

我们允许constexpr函数返回值并非一个常量。

constexpr函数不一定返回常量表达式。

6.5.3调试帮助

assert(expr);

首先对expr求值,若真则程序继续执行;若假,则程序终止。

__FILE__                     存放文件名的字符串字面值

__LINE__             存放当前行号的整形字面值

__TIME__            存放文件编译时间的字符串字面值

__DATE__            存放文件编译日期的字符串字面值

6.6函数匹配

当几个重载函数的形参数量相等,且某些形参类型可以由其他类型转换得来时,很难确定究竟是调用了那个重载函数。

确定候选函数和可行函数

第一步:选定本次调用对应的重载函数集,集合中的函数称为候选函数。(同名;在调用处可见。)

第二步:考察调用提供的实参,从候选函数中选出可行函数。特征(1形参数量与实参数量相等;2每个实参类型与形参类型相同,或能转换成形参的类型。)

第三步:从可行函数中选出与本次调用最匹配的函数。

6.6.1实参类型转换

编译器将实参类型到形参类型的转换划分了5级:

       1精确匹配。包括:类型相同;实参从数组类型或函数类型,转换成对应的指针类型;向实参添加顶层const或删除实参的顶层const。

       2通过const转换实现的匹配。允许将非const指针/引用,转换成const指针/引用。

       3通过类型提升实现的匹配。小数类型转换成大数。

       4通过算术类型转换。运算对象转换成最宽的类型。

       5通过类类型转换实现匹配。

6.7函数指针

函数指针指向的是函数,而非对象。函数指针指向某种特点类型,函数的类型由它的返回类型和形参共同决定,与函数名无关。

 

当我们把函数名作为一个值使用时,该函数自动转换成指针。

可以直接使用指向函数的指针调用该函数,不需要提前解引用指针。

  

指向不同函数类型的指针不存在转换规则。

重载函数的指针

编译器通过指针类型决定选用哪个函数,指针类型必须与重载函数中的某一个精确匹配。

函数指针的形参

不能定义函数类型的形参,但是形参可以是指向函数的指针

返回指向函数的指针

编译器不会自动地将函数返回类型当成对应指针类型处理。

如果我们明确知道返回的函数是哪一个,可以使用decltype简化书写函数指针返回类型的过程。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值