7.1函数的定义
1. 函数的调用
函数调用做了两件事情:用对应的实参初始化函数的形参,并将控制权转移给被调用函数,主调函数的执行被挂起,被调函数开始执行。
函数的运行以形参的定义和初始化开始。
3.形参和实参
函数的形参为函数提供了已命名的局部存储空间。形参是在函数的形参表中定义的,并由调用函数时传递给函数的实参初始化。
实参则是一个表达式。所传递的实参个数必须与函数的形参个数相同。实参必须具有与形参类型相同或者能隐式转换为形参类型的数据类型。
7.1.1 函数返回类型
函数不能返回另一个函数或者内置数组类型,但可以返回指向函数的指针,或指向数组的指针。
7.1.2 函数形参表
参数表中不能出现同名参数。类似的,局部于函数的变量也不能使用与函数的任意参数相同的名字。
参数类型检查:
C++是一种静态强类型语言,对于每一次的函数调用,编译时都会检查其实参
函数的形参表为编译器提供了检查实参需要的类型信息。
7.2 参数传递
每次调用函数时,都会重新创建该函数所有的形参,此时所传递的实参将会初始化对应的形参
如果形参具有非引用类型,则复制实参的值,如果形参为引用类型,则他只是实参的别名
7.2.1 非引用形参
普通的非引用类型的参数通过复制对应的实参实现初始化。当用实参副本初始化形参时,函数并没有访问调用所传递的实参本身,因此不会修改实参的值。
1.指针形参
可以将const对象的指针初始化为指向非const对象,但不可以让非const对象的指针指向const对象。
2. const形参
因为初始化复制了初始化式的值,所以可以用const对象初始化非const对象,反之亦然。
如下两个函数
void fcn(const int i)
void fcn(int i)
编译器将const形参视作普通int类型,上面两个相当于重复定义
3.复制实参的局限性
复制实参不是在所有情况下都适合,不适合复制实参的情况包括:
当需要在函数中修改实参值
当需要以大型对象作为实参传递时,复制对象所付出的时间和空间代价巨大
当没有办法实现对象的复制时
7.2.2引用形参
如果函数具有普通的非const引用,不能通过const对象进行调用。调用这样的函数时,同样不能传递一个右值(字符串字面值)或者具有需要转换类型的对象
7.2.3 vector和其他容器类型的形参
调用含有普通的非引用vector形参的函数会复制vector的每一个元素
7.2.4 数组形参
数组有两个性质,影响我们定义和使用作用在数组上的函数:一是不能复制数组
二是使用数组名字时,数组名会自动转换为指向数组第一个元素的指针
1.数组形参的定义
可以用一下三种方式指定数组形参:
void printvalues(int *)
void printvalue(int [])
void printvalue(int [10])
当编译器检查数组形参关联的实参时,它只会检查实参是不是指针、指针的类型和数组元素的类型是否匹配,则不会检查数组的长度
4.通过引用传递数组
同其他类型一样,数组形参可声明为数组的引用。如果形参是数组的引用,编译器不会将数组转化为指针,而是传递数组的引用本身。数组大小称为实参和形参的一部分。编译器会检查数组实参的大小和形参的大小是否匹配:
void printvalue(int (&arr)[10]) //(&arr)必须有括号
这个函数只严格地接受含有10个int型数值的数组。
5.多维数组的传递
void printvalue(int (*matrix)[10],int rowsize)
*matrix两边的括号是必须的
也可以表示成以下形式
void printvalue(int matrix[][10],int rowsize)
数组中的每个元素本身就是含有10个int对象的数组
7.2.7含有可变形参的参数
在无法列举传递给函数的所有实参的类型和数目时,可以使用省略符形参。省略符形参在暂停了编译器的类型检查机制
7.3 return语句
隐式的return语句发生在最后一个赋值语句之后。
2. 返回非引用类型
如果函数返回值不是引用,在调用函数的地方会将函数返回值复制给临时对象。当函数返回非引用类型时,其返回既可以是局部对象,也可以是求解表达式的结果。
3. 返回引用
当函数返回引用类型时,没有复制返回值。返回的是对象本身。
4.千万不要返回局部对象的引用
5.引用返回左值
返回引用的函数返回一个左值
6.千万不要返回指向局部对象的指针
一旦函数结束,局部对象被释放,返回的指针就变成了指向不再存在的对象的悬垂指针。
7.4 函数声明
函数必须在调用前声明。一个函数只能定义一次但是可以声明多次
函数声明由:函数返回类型、函数名和形参表组成。形参列表必须包含形参类型但是不必对形参命名
在头文件中提供函数声明
将提供函数声明的头文件包含在定义该函数的源文件中,可使编译器能检查函数的声明和定义是否一致。如果函数定义和函数声明的形参列表一致,但是返回类型不一致,编译器会发出警告或出错信息
默认实参
调用函数时可以省略有默认值的实参。
如果有一个形参有默认实参,那么它后面的所有形参都要有默认实参。
默认实参只用来替换函数调用缺少的尾部实参。
设计带有默认实参的函数,使最少使用默认实参的形参排在最前面,最可能使用默认实参的形参排在最后
如果默认实参是一个表达式,而默认值用作实参,则在调用函数时求解表达式
2.指定默认实参的约束
既可以在函数声明也可以在函数定义中指定默认实参
但是在一个文件中,只能为一个形参指定默认实参一次
7.5 局部对象
名字的作用域是知道该名字的程序文本区。对象生命期则是在程序执行过程中对象存在的时间
在函数中定义的形参和变量的名字只在函数作用域中:变量名从声明或定义的地方开始到包围它的作用域结束处是可用的。
7.5.1自动对象
只有当定义它的函数被调用时才存在的对象称为自动对象。自动对象在每次调用函数时创建和撤销
局部变量多对应的自动对象在函数控制经过变量定义语句时创建。对于未初始化的内置类型局部变量,其初值不确定。
形参也是自动对象。形参所占用的存储空间在调用函数时创建,在函数结束时撤销。
7.5.2 静态局部对象
一个变量位于函数的作用域内,但生命期却跨过了函数的多次调用,这样的对象应该定义为static
static局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化。这种对象一旦被创建,在程序结束前就不会被撤销。
7.6 内联函数
1.内联函数避免函数调用的开销
将函数指定为内联函数就是在它程序的每个调用点上内联地展开。
内联机制适用于优化小的、只有几行的而且经常被调用的函数
2. 内联函数应该在头文件中定义。
内联函数的定义必须对编译器是可见的,以便编译器能够在调用点内联地展开函数的代码。此时,仅有函数原型是不够的。
7.7 类的成员函数
类的成员函数必须在类中定义。但是函数体可以再类中也可以再类外定义。
7.7.1 定义成员函数的函数体
编译器隐式地将在类内定义的函数当作内联函数
1. 成员函数含有隐含的、额外形参
调用成员函数时,实际上是使用对象来调用的。
每个成员函数都有一个额外的、隐含的形参将成员函数和调用该成员函数的对象捆绑在一起
2. this指针的引用
每个成员函数都有一个额外的、隐含的形参this。在调用成员函数时,形参this初始化为调用函数的对象的地址。
3. const 成员函数的引入
const改变了隐含的this形参的类型。隐含的this指针是一个指向total对象的const Sales_item*类型的指针
用这种方式使用const的函数称为常量成员函数。由于由于this是指向const对象的指针,const成员函数不能修改调用该成员函数的对象
const对象、指向const对象的指针或引用只能用于调用其const成员函数。
4. 合成的默认构造函数
没有为一个类显示定义任何构造函数,编译器将自动为这个类生成默认的构造函数
由编译器创建的默认构造函数通常称为合成的默认构造函数。对于具有类类型的数据成员则会调用该成员所属类的默认构造函数实现初始化。内置成员函数初值依赖于对象如何定义。如果对象在全局域中定义或定义为静态局部对象,则这些成员初始化为0.如果是在局部作用域中定义,则这些成员没有初始化。
7.8重载函数
出现在相同作用域的两个函数,如果具有相同的名字而形参表不同,则成为重载函数
函数重载和重复声明的区别
如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,但是返回类型相同,则第二个声明是错误的
函数不能仅仅基于不同的返回类型而实现重载。
形参列表只有默认实参不同,并没有改变形参的个数。
复制形参时并不考虑形参是否为const(函数操纵只是副本)
注意:形参与const形参的等价仅适用于非引用形参。有const引用的形参和非const引用的形参是不同的(指针也不同)
7.8.1 重载与作用域
如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域中声明的同名函数
每一个版本的重载函数都在同一个作用域中声明。
当调用函数时,编译器首先检索这个名字的声明,一旦找到这个名字,编译器不再继续检查这个名字是否在外层作用域中存在
在C++中,名字查找发生在类型检查之前。
7.8.2 函数匹配和实参转换
函数重载确定,即函数匹配是将函数调用与重载函数集合中的一个函数相关联的过程
匹配结果有三种:
(1)与实参的最佳匹配的函数,并生成调用该函数的代码。
(2)找不到与函数调用的实参匹配的函数,在这种情况下,编译器将给出编译错误信息。
(3)存在多个与实参匹配的函数,但是没有一个是明显的最佳选择。这种情况也是错误的,该调用具有二义性。
7.8.3 重载确定的三个步骤
1.候选函数
候选函数是与被调函数同名的函数,并且在该调用点上,它的声明可见。
2. 选择可行函数
第二步是从候选函数中选择一个或多个函数,它们能够用该调用中指定的实参来调用。因此选出的函数称作可行函数
可行函数必须满足两个条件:
第一:函数的形参个数与该调用函数的实参个数相同;(有默认实参时,所用的实参可能比实际的少)
第二:每一个实参的类型必须与对应形参的类型匹配或可以被隐式地转换为对应的形参类型
如果没有找到可行函数,则该调用错误
3.寻找最佳匹配
确定与函数调用中实际参数匹配最佳的可行函数。这个过程考虑函数调用中的每一个实参,选择对应形参与之最匹配的一个或多个可行参数。实参类型与形参类型之间的精确匹配比需要转换的匹配好。
4.含有多个形参的重载确定
如果有且仅有一个函数满足下列条件,则匹配成功:
(1)其每个实参的匹配都不劣于其它可行函数需要的匹配
(2)至少有一个实参的匹配优于其它可行参数提供的匹配
7.8.4实参类型转化
编译器将实参类型到相应形参类型的转换划分等级: (转换等级以降序排列)
精确匹配。实参与形参类型相同
通过类型提升
通过标准转换
通过类类型转换
2.参数匹配和枚举类型
无法将整型值传递给枚举类型的形参
但是可以将枚举值传递给整型形参。
3. 重载和const形参
仅当形参是引用或者指针时,形参是否为const才有影响
可以基于函数的引用形参是指向const对象还是非const对象实现函数重载
7.9 指向函数的指针
bool (*pf)(const string &,cosnt string &);
pf为指向函数的指针,它所指向的函数带有两个const string&类型的形参和bool类型的返回值
将函数指针初始化为0,表示指针不指向任何函数。
4.函数指针形参
函数的形参可以是指向函数的指针,这种形参可以有两种形式编写:
void usebigger(const string &,const string &,bool(const string &,const string &));
void usebigger(const string &,const string &,bool (*)(const string &,const string &));
5. 返回指向函数的指针
函数可以返回指向函数的指针
int (*ff(int)) (int *,int);
ff声明为一个函数,它有一个int型的形参,该函数返回
int (*)(int*,int)
它是一个指向函数的指针,所指向的函数返回int型并带有两个分别是int *和int的形参