C++Primer读书笔记(第七章)

来源:互联网 发布:斗战神精炼软件 编辑:程序博客网 时间:2024/06/05 03:44

第七章函数

7.1 函数的定义

7.2 参数传递

7.2.1 非引用形参

(1)指针形参

函数并不能改变实参指针的值,但是可以改变其所指对象的值。若要保护指针指向的值,则形参需要定义为指向const对象的指针:

         void  use_ptr (const  int *p) {… }

         此时,实参可以是const  int  *q或者是int  *q,但是对于void  use (int *p) {… },不允许实参是const  int  *q,因为不允许指向非const对象的指针指向const对象。

(2)const形参

         如果将形参定义为非引用的const类型,在函数中不能改变实参的局部副本。不过,令人吃惊的是,尽管函数的形参是const,但编译器却将函数的形参声明为普通类型,这是为了兼容C语言。

         void  fcn (const int  i) {}              //原函数

         void  fcn (int i) {}           //编译器之后的函数

7.2.2 引用形参

         当需要修改实参值,或者复制对象代价太大时候,往往用引用形参。

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

         将额外信息用引用形参表示。查找函数,返回指向该元素的迭代器以及出现次数:

         vector<int>::const_iterator  find_val (vector<int>::const_iterator  beg,

vector<int>::const_iterator end,  int  value, vector<int>::size_type &occurs )

         {

                   vector<int>::const_iterator  res_iter = end;

                   occurs= 0;

                   for( ; beg != end; ++beg)

                   {

                            if(value == *beg)

                            {

                                     ++occur;                              //保存次数

                                     if(res_iter == end)            //res_iter保存指向第一个值为value的迭代器

                                               res_iter= beg;

                            }

                   }

                   return  res_iter;

         }

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

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

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

         int  incr (int &val) { return  ++val; }

         short  v1 = 0;          const  int  v2= 42;       int  v3 = 2;      int  v4;

         v4= incr (v1);            v4 = incr (v2);            //error,不会进行类型转换

         应该将不需要修改的引用形参定义为const引用。普通的非const引用形参在使用时不灵活。既不能用const对象初始化,也不能用字面值或产生右值的表达式实参初始化。

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

         void  ptrswap (int *&v1, int  *&v2) { …}

         int  *&v1;表示:v1是一个引用,与指向int型对象的指针相关联。

7.2.3 vector和其它容器类型的形参

         通常,函数不应该有vector或其它标准库容器类型的形参。调用含有普通的非引用vector形参的函数将会复制vector的每一个元素。但也不推荐用const引用,更倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器。

7.2.4数组形参

         void  printValues (int  *p) {} //更好,因为与实际效果最接近

         void  printValues (int  p[]) {}

         void  printValues (int  p[10]) {}

         上述3种形参表示效果相同,C++编译器自动忽略形参的数组长度,当编译器检查数组形参关联实参时,它只会见擦实参是不是指针、指针的类型和数组元素的类型是否匹配,而不会检查数组的长度。当不需要修改数组形参的元素时,应该定义为指向const对象的指针。

         数组的形参可以声明为数组的引用int  (&arr)[10],这时,编译器不会将数组实参转化为指针,而是传递函数的引用本身。这样,数组的大小就成为形参和实参类型的一部分,就会检查其是否匹配,这更安全。

         多维数组在参数传递时,除了第一维以外的所有维的长度都是元素类型的一部分,必须明确指定。

7.2.5 传递给函数的数组处理

         任何处理数组的程序都要确保程序停留在数组的边界内,有3种编程技巧确保:

         (1)在数组本身放置一个标记来检测数组的结束。C风格字符串就是例子,用null。

         (2)使用标准库规范,传递指向数组的第一个和最后一个元素的下一个位置的指针。

         (3)显示传递表示数组大小的形参。

7.2.6 main:处理命令行选项

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

         第一个形参argc用于传递数组argv中字符个数,第二个形参argv是一个C风格字符串数组。用命令行执行程序时,如果输入如下命令:

         prog  -d -o  ofile  data0                     //prog是该可执行文件文件名

         那么argc为5,argv[]中元素为:”prog”,”-d”,”-o”,”ofile”,”data0”。

7.2.7 含有可变形参的函数

         void  foo (parm_list, …);                  //parm_list是显示声明的形参

         void  foo(…);

         C++中的省略符形参是为了编译使用了varargs的C语言程序,只能讲简单数据类型传递给含有省略符形参的函数。

7.3 return语句

7.3.1 没有返回值的函数

         return;

         return  fun (…);      //void  fun (…) {…} 返回另一个返回值为void的函数调用结果。

7.3.2 具有返回值的函数

         return  expression;

         在含有return语句的循环后面没有提供return语句是很危险的,编译器不能检测这个问题。

(1)主函数main的返回值

         返回类型不是void的函数必须返回一个值,有个例外:允许main函数没有返回值就结束,编译器会隐式插入返回0的语句。

         return  EXIT_FAILURE;            //运行失败

         return  EXIT_SUCCESS;           //运行成功,都在cstdlib头文件中定义

(2)返回非引用类型

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

(3)返回引用

         函数返回引用类型时,没有复制返回值,而是返回对象本身。千万不能返回局部变量的引用,因为当函数执行完毕,将释放分配给局部对象的存储空间。

(4)引用返回左值

         char  &get_val (string  &str, string::size_type  ix) { return  str[ix]; }

         get_val(s,0) = ‘A’;             //给函数返回值赋值,因为函数返回的是一个引用。

7.3.3 递归

         递归函数必须定义一个终止条件,main函数不能调用自身。

7.4 函数声明

         函数声明由函数返回类型、函数名和形参列表组成,这3个元素成为函数原型。提倡在头文件中提供函数声明,在源文件中定义。函数声明时候,形参只需要提供类型,不必指明形参名,就算指定,编译器也会忽略。

         默认实参是通过给形参表中的形参提供初始值来指定。既可以在函数声明也可以在函数定义中指定默认实参。但是在一个文件中,只能为一个形参指定默认实参一次。通常,应该在函数声明中指定默认实参,并且放在头文件中。如果在函数定义的实参表中提供默认实参,那么只有在包含该函数定义的源文件中调用该函数时,默认实参才有效。

         //ff.h

         int  ff(int = 0);

         //ff.c

         #include“ff.h”

         int  ff (int i = 0) { …}     //error,在一个文件中多次指定默认实参

7.5 局部对象

         默认情况下,局部变量的生命周期局限在所在函数的每次执行期间。如果一个变量位于函数作用域内,但生命周期却跨越了这个函数的多次调用,这种变量应该定义为static(静态局部变量)。这种对象一旦定义,在程序结束前都不会被撤销。

7.6 内联函数

         一些小操作定义为函数有很多优点,但是存在的开销大的缺点,这时,就可以用内联函数来避免函数调用的开销。内联函数只需要在普通函数定义前面加上inline关键字。内联函数会在编译时,自动在每个函数调用点“内联地”展开。内联函数应该在头文件中定义,这点不同于其他任何函数。

7.7 类的成员函数

7.7.1 定义成员函数的函数体

(1)this指针

每个成员函数都含有额外的、隐含的形参this,将该成员函数与调用该函数的类对象捆绑在一起。在调用成员函数时,形参this初始化为调用函数的对象的地址。

const对象、指向const对象的指针或引用只能用于调用其const成员函数,如果尝试用它们来调用非const成员函数,则是错误的。

7.7.2 在类外定义成员函数

         double  Sales_item::avg_price () { }

7.7.3 构造函数

         函数名与类名相同,没有返回类型。

         Sales_item(): units_sold(0), revenue(0.0) { }           //中间为初始化列表

         由编译器创建的默认构造函数通常称为合成的默认构造函数,一般适用于仅包含类类型成员的类。而对于含有内置类型或复合类型成员的类,通常应该定义它们自己的默认构造函数初始化这些成员。

7.7.4 类代码文件的组织

         类定义应置于名为type.h的头文件中,而类外成员函数的定义则一般存储在type.cc的源文件中,type为类名。

7.8 重载函数

         出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。任何程序都仅有一个main函数实例,不可以被重载。

7.8.1 重载与作用域

         一般的作用域规则同样适用于重载函数名。如果局部地声明一个函数,则该函数将屏蔽而不是重载在外层作用域声明的同名函数。

         C++中,名字查找发生在类型检查之前。

7.8.2 函数匹配与实参转换

         编译器实现调用与函数的匹配时,结果有三种可能:

(1)      找到与实参最佳匹配的函数;

(2)      找不到匹配的,给出编译错误信息;

(3)      存在多个匹配,调用具有二义性。

7.8.3重载确定的三个步骤

         (1)确定该调用所考虑的重载函数集合,该集合中的函数称为候选函数;

         (2)从候选函数中选择一个或多个可行函数;

         (3)确定最佳匹配,若有多个可行函数,却无最佳匹配则出现二义性。

7.8.4 实参类型转换

         为了确定最佳匹配,编译器将实参到相应形参类型的转换划分等级。转换等级以降序排列如下:(1)精确匹配;(2)通过类型提升(5.12.2);(3)通过标准转换(5.12.3);(4)通过类类型转换。

         仅当形参是引用或指针时,形参是否为const才有影响。

7.9 指向函数的指针

         函数指针是指指向函数的指针,函数类型由返回类型以及形参表确定,而与函数名无关。

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

         将pf声明为一个指向函数的指针,它所指向的函数带有两个const string &类型的形参和bool类型的返回值。

         可以使用typedef简化函数指针的定义:

         typrdef  bool (*cmpFcn) (const  string  &, const string  &);

         cmpFcn  pf;    //等效于上面的定义

         函数指针只能通过同类型的函数或函数指针或0值常量表达式进行初始化或赋值。指向不同函数类型的指针之间不存在转换。

         非零值且初始化后的函数指针,可以直接调用所指向的函数,不需要解引用符。

         函数的形参可以是指向函数的指针,有两种形式编写:

         void  useBiger (const  string &, bool (const  string &, const  string &) );

         void  useBiger (const  string &, bool (*) (const  string &, const  string &) );

         函数的返回可以是指向函数的指针:

         int( *ff(int) ) (int *, int);            //函数ff带有1个int形参,返回类型是int(*)(int *, int)

         typedef  int (*PF) (int *, int);          PF ff (int);      //更容易理解

0 0