第7章:函数

转载请注明原文出处:http://blog.csdn.net/roddick621

以下的知识点全部都是来自于C++ Primer 第四版,如果需要详细了解的话,可以查看原版的书籍。


函数可以看作程序员定义的操作。与内置操作相同的是,每个函数都会实现一系列的计算,然后生成一个计算结果。但是与操作符不同的是,函数有自己的函数名,而且操作数没有限制。与操作符一样,函数可以重载,这意味着同样的函数名可以对应多个不同的函数。


1.函数的定义

函数又函数名遗迹一组操作数类型唯一地表示。函数的操作数,也即形参,在一对圆括号中声明,形参与形参之间以逗号分隔。函数执行的运算在一个称为函数体的块语句中定义。每一个函数都有一个相关联的返回类型。

(1)函数的调用。

C++语言使用调用操作符(即一堆圆括号)实现函数的调用。调用操作符需要操作数并产生一个结果。调用操作符的操作数是函数名也一组(有可能是空的)由逗号分隔的实参。函数调用的结果类型就是函数返回值的类型,该运算的结果本身就是函数的返回值。

函数调用做了两件事:用对应的实参初始化函数的形参,并将控制权转移给调用函数。主调函数的执行被挂起,被调函数开始执行。

(2)函数体是一个作用域

函数体内定义的变量只有在该函数中才可以访问,是局部变量。

(3)形参和实参

类似与局部变量,函数的形参为函数提供了已命名的局部存储空间。他们之间的差别在于形参是在函数的形参表中定义的,并由调用函数时传递给函数的实参初始化。

实参则是一个表达式。它可以是变量或字面值常量,设置是包含一个或几个操作符的表达式。在调用函数时,所传递的实参个数必须和函数的形参个数完全相同。


1.1 函数的返回类型

函数的返回类型可以是内置类型、类类型或复合类型,还可以使void类型,表示改函数不返回任何值。

函数必须指定返回类型,在定义或者声明函数时,没有显式指定返回类型是不合法的。


1.2 函数形参表

函数形参表可以为空,但不能忽略。


2.参数传递

每次调用函数时,都会重新创建该函数所有的形参,此时所传递的形参将会初始化对应的形参。


2.1 非引用形参。

普通的非引用类型的参数通过复制对象的实参实现初始化。当用实参副本初始化形参时,函数并没有访问调用所传递的实参本身,因此不会修改实参的值。

(1)指针形参

函数的形参可以是指针,此时将复制实参指针。如果函数形参是非const类型的指针,则函数可通过指针实现赋值,修改指针所指向对象的值。


(2)const形参

在使用函数时,如果该函数使用非引用的非const形参,则既可以给该函数传递const实参,也可以传递非const的实参。

如果形参定义为非引用的const类型,则在函数中不可以改变实参的局部副本。由于实参仍然是以副本的形式传递,因此传递给形参的既可以是const对象也可以是非const对象。


(3)复制实参的局限性

不适宜复制实参的情况包括:

  1. 当需要在函数中修改实参的值时。
  2. 当需要以大型对象为实参传递时。
  3. 当没有办法实现对象的复制时。


2.2 引用形参

与所有引用一样,引用形参直接关联到其所绑定的对象,而兵分这些对象的副本。

(1)使用引用形参返回额外的信息。

(2)利用const引用避免复制

如果使用引用形参的唯一目标是避免复制实参,则应将形参定义为const引用。

(3)更灵活的指向const的引用

如果函数具有普通的非const引用形参,则显然不能通过const对象进行调用。

非const引用形参只能与完全同类型的非const对象关联。

应该将不改修改相应实参的形参定义为const引用。

(4)传递指向指针的引用

这里要用*定义指针,用&定义引用。下面的用法就可以讲两个操作符结合起来以获得指向指针的引用。

//swap values of two pointer to int
void ptrswap(int *&v1, int *&v2)
{
    int *tmp = v2;
    v2=v1;
    v1=tmp;
}

形参为int *&v1 。从右至左理解:v1是一个引用,与指向int型对象的指针相关联。


2.3 vector与其他容器类型的形参

从避免复制vector的角度出发,应该考虑形参声明为引用类型。事实上,C++程序员倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器。


2.4 数组形参

数组有两个特殊的性质影响我们定义和使用作用在数组上的函数:一是不能复制数组;二是使用数组名字时,数组名会自动转化为指向其第一个元素的指针。

(1)数组形参的定义

//三种相同的定义方式
void printValue(int *) { /* ... */ }
void printValue(int []) { /* ... */ }
void printValue(int [10]) { /* ... */ }

虽然不能直接传递数组,但是函数的形参可以写成数组的形式。虽然形参边使方式不同,但可将使用数组语法定义的形参看作指向数组元素类型的指针。

(2)形参的长度会引起误解

(3)数组实参

单数形参可定义为引用或非引用类型。大部分情况下,数组以普通的非引用类型传递,此时数组会悄悄地转换为指针。如果不需要修改数组形参的元素时,函数应该将形参定义为指向const对象的指针。

void f (const int *) { /* ... */ }

(4)通过引用传递数组

数组形参可声明为数组的引用。如果形参是数组的引用,编辑器不会将数组转化为指针,而是传递数组的引用本身。在这种情况下,数组大小成为形参和实参类型的一部分。

void printValues ( int (&arr)[10] { /* ... */ }
int main()
{
    int k[10] = {0,1,2,3,4,5,6,7,8,9};
    printValues(k);
    return 0;
}
这个版本的printValues函数只严格地接受10个int行数值的数组。


(5)多维数组的传递

多维数组实际是指向数组的数组。和其他数组一样,多维数组以指向0号元素的指针方式传递。多维数组的元素本身就是数组,除了第一维以外所有维的长度都是元素类型的一部分,必须明确指定。

void printValue ( int (martix*)[10], int rowSize);  //指向10个int型元素的数组的的指针


2.5 传递给函数的数组的处理。

非引用数组形参的类型检查只是确保实参是和数组元素具有同样类型的指针,而不会检查实参实际上是否指向指定大小的数组。

任何处理数组的程序都要确保程序停留在数组的边界内。有三种常见的变成技巧确保函数的操作不超出数组实参的边界。

  1. 在数组本身防止一个标记来检测数组的结束。
  2. 传递指向数组最后一个元素的下一个位置的指针。
  3. 将第二个形参定义为数组的大小。

2.6 main处理命令行选项

主函数的定义:

int main (int argc, char *argv[] ) { ... }
int main (int argc, char **argv ) { ... }
第二个形参argv是一个C风格字符串。第一个形参argc用于传递数组中字符串的个数。


2.7 含有可变形参的函数

在无法列决出传递给函数的所有实参的类型和数目时,可以使用省略符形参。省略符暂停了类型检查机制。他们的出现告知编译器,当调用函数时,可以有0个或多个实参,而实参的类型未知。省略符形参有下列两种形式:

void foo (parm_list , ... );
void foo ( ... );
第一种形式为特定数目的形参提供了声明。在这种情况下,当函数被调用时,对于显式声明的形参对象的实参进行类型检查,而对于省略符对应的实参则暂停类型检查。在第一种形式中,形参声明后面的逗号是可选的。大部分带有省略符形参的函数都利用显示声明的参数中的一些信息,来获取函数调用中提供的其他可选实参的类型和数目。


3.return语句

return语句用于结束当前正在执行的函数,并将控制权返回给调用此函数的函数。return语句有两种形式:

return;
return expression;

3.1 没有返回值的函数。

不带返回值的return语句只能够用于返回类型为void的函数。一般情况下,返回类型是void的函数使用return语句是为了引起函数的强制结束。

返回类型为void的函数不能使用第二种形式的return语句,但是,它可以返回另一个返回类型同样是void的函数的调用结果。


3.2 具有返回值的函数

任何返回类型不是void的函数都必须返回一个值,而且这个返回值的类型必须和函数的返回类型相同,或者能隐式转化为函数的返回类型。尽管C++不能确保结果的正确性,但能保证函数每一次返回适当类型的结果。

(1)主函数main的返回值

允许主函数main没有返回值就可以结束。如果程序控制执行到主函数main的最后一个语句都没有返回,那么编译器会隐式地插入返回0的语句。

(2)返回非引用类型。

函数的返回值用于初始化在调用函数处创建的临时对象。

(3)返回引用。

当函数返回以in用类型时,没有复制返回值,相反,返回的是对象本身。

(4)千万不要返回局部对象的引用

(5)引用返回左值

(6)千万不要返回指向局部对象的指针。


3.3 递归

直接或间接调用自己的函数称为递归函数。递归函数必须定义一个终止条件,否则,函数就会“永远”递归下去。


4.函数声明

函数的声明也可以和函数的定义分离:一个函数只能定义一次,但是可声明多次。

函数声明由返回类型、函数名和形参列表组成。这三个元素被称为函数原型,函数原型描述了函数的接口。

在头文件中提供函数声明。在函数声明放在头文件中,这样可以确保对于指定函数所有声明保持一直。如果函数接口发生变化,则只要修改唯一的声明即刻。

默认实参

默认实参是通过给形参表中的形参提供明确的初始值来指定的。调用函数时,可以省略有默认值的实参,编译器会为我们提供省略的实参提供默认值。但是,如果有一个形参具有了默认实参,那么,她后面所有的形参都必须有默认实参。

(1)默认实参的初始化式

默认实参可以使任何适当类型的表达式。

(2)指定默认实参的约束

既可以在函数声明也可以在函数定义中指定默认实参。但是,在一个文件中,只能为一个形参指定默认实参一次。


5.局部对象

在C++语言中,每个名字都有作用域,而且每个对象都有生命期。名字的作用域是指知道该名字的程序文本区。对象的生命期则是在程序执行过程中对象存在的时间。

在函数中定义的形参和变量的名字只位于函数的作用域中:这些名字只在函数体中可见。

5.1 自动对象

只有当定义它的函数被调用时才存在的对象称为自动对象。自动对象在每次调用函数时创建和撤销。

5.2 静态局部对象

static局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时进行初始化。这种对象一旦被创建,在程序结束前都不会被撤销。当定义静态局部对象的函数结束时,静态局部对象不会撤销。


6.内联函数

(1)内联函数避免函数调用的开销

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

在函数返回类型前面加上关键字inline就可以讲函数指定为内联函数。一般来说,内联函数适用于优化小的、只有几行的而且经常被调用的函数。

(2)讲内联函数放入头文件。

内联函数应该在头文件中定义,因为内联函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码。


7.类的成员函数

成员函数的定义和普通函数的定义类似,也包含了四个部分:

  • 函数返回类型
  • 函数名
  • 用逗号隔开的形参表
  • 包含在一对花括号里面的函数体。

函数原型必须在类中定义,但是,函数体则既可以在类中也可以在类外定义。


7.1 定义成员函数的函数体

类的所有成员都必须在类定义的花括号里声明,此后,就布恩那个再为类增加任何成员。

(1)成员函数有额外的、隐含的形参

调用成员函数时,实际上是使用对象来调用的。

(2)this指针的引入

(3)const成员函数的引入

bool Sales_item::same_isbn(const Sales_item *const this, const Sales_item &rhs) const
{
    return (this->isbn == rhs.isbn);
}
用这种方式使用const的函数成为常量成员函数。由于this是指向const对象的指针,const成员函数不能修改调用该函数的对象。

(4)this指针的引用

在成员函数中,不必显式地使用this指针来访问被调用函数所属对象的成员。


7.2 在类外定义成员函数

定义的时候需要使用作用域操作符指明函数是在那个类的作用域范围内定义。

7.3 编写构造函数

(1)构造函数是特殊的成员函数

构造函数是特殊的成员函数,与其他成员函数不同,构造函数和类同名,而且没有返回类型。

默认构造函数:没有形参的构造函数,说明当定义对象没有为它提供(显式的)初始化式时应该怎么办。

(2)构造函数的意义。

构造函数是放在类的public部分的,通常构造函数会作为类的接口的一部分,如果定义为private,则不能定义类的对象,这样的话,这个类就没有什么用了。

(3)构造函数的初始化列表。

在冒号和花括号之间的代码成为构造函数的初始化列表。构造函数的初始化列表为类的一个或多个数据成员指定初值。它是跟在构造函数的形参表之后,以冒号开头。

Sales_item() : units_sold(0), revenue(0.0) { }
(4)合成的默认构造函数

如果没有为一个类显式定义任何构造函数,编辑器将会自动为这个类生成默认构造函数。

由编译器创建的默认构造函数同城称为合成的默认构造函数。它将依据如同变量初始化的规则初始化类中的所有成员。


8.函数重载

出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则成为重载函数。

函数重载和重复声明的区别:如果两个函数声明的返回类型和形参表完全匹配,则将第二个函数声明视为第一个的重复声明。如果两个函数的形参表完全相同,则返回类型不同,则第二个声明是错误的。函数不能仅仅基于不同的返回类型而实现重载。


8.1 重载与作用域

一般的作用域规则同样适用于重载函数名。如果局部地声明一个函数,则该函数讲屏蔽而不是重载在外层作用域中声明的同名函数。由此推论,每一个版本的重载函数都应该在同一个作用域中声明。


8.2 函数匹配与实参转换

函数重载确定,即函数匹配是讲函数调用与重载函数中的一个函数相关联的过程。匹配结果有三种可能:

  1. 编译器找到与实参最佳匹配的函数,并生成调用该函数的代码。
  2. 找不到形参与函数调用的实参匹配的函数,在这种情况下,编辑器将给出编译错误信息。
  3. 存在多个与黐餐匹配的函数,但没有一个明显的最佳选择。这种情况下也是错误的,该调用具有二义性。

8.3 重载确定的三个步骤

(1)候选函数

确定所考虑的重载函数集合,该集合中的函数成为候选函数。候选函数是与被调用函数同名的函数,并且在调用点上。

(2)选择可行函数

从候选函数中选择一个或多个函数,他们能够用该调用中指定的实参来调用。因此,选出来的函数成为可行函数。可行函数必须满足两个条件:

  1. 函数的形参个数与该调用的实参个数相同
  2. 每一个实参的类型必须与对象形参的类型匹配,或者可悲隐式转换为对应的形参类型。

(3)寻找最佳匹配(如果有的话)

确定与函数调用中使用的实际参数匹配最佳的可行函数。这个过程考虑函数调用中的每一个实参,选择对应形参与之最匹配的一个或多个可行函数。

(4)含有多个形参的重载确定


8.4 实参类型转换

为了确定最佳匹配,编译器讲实参类型到相应形参类型的转换划分等级。

  1. 精确匹配。实参与形参类型相同
  2. 通过类型提升实现的匹配。
  3. 通过标准转换实现的匹配
  4. 通过类类型转换实现的匹配


9.指向函数的指针。

函数指针是指指向函数而非指向对象的指针。函数指针也指向某个特定的类型。函数类型又返回类型已经形参表去诶那个,与函数名无关。

bool (*pf) (const string &, const string &);

(1)用typedef简化函数指针的定义

typedef bool (*comFcn) (const string &,const string &);
(2)指向函数的指针的初始化和赋值

在引用函数名但又没有调用该函数时,函数名将被自动解析为指向函数的指针。

指向不同函数类型的指针之间不存在转换。

(3)通过指针调用函数

指向函数的指针可用于调用它说指向的函数。可以不需要使用解引用操作符,直接通过指针调用函数。

(4)函数指针形参。

函数的形参可以是指向函数的指针。

void useBigger(const string &, const string &, bool(const string &, const string &));
void useBigger(const string &, const string &, bool(*)(const string &, const string &));
(5)返回指向函数的指针。

函数可以返回指向函数的指针。

(6)指向重载函数的指针。


















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值