[primer] chapter-7 Functions

来源:互联网 发布:gotv源码是什么意思 编辑:程序博客网 时间:2024/05/12 12:38

1.参数传递

非引用形参:非引用形参表示对应实参的局部副本

指针形参:此时将复制实参指针。与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本。即指针本身的值没有修改,但是指针所指向内存地址的内容修改。

const形参:对指针来说,把一个 const 对象的地址赋给一个普通的、非 const 对象的指针也会导致编译时的错误。这里有个误区,认为const的值不能用来初始化非const的值。

const int a=10;
int b=a;//ok,这里是按值传递,所以b修改不会改变a
const int *pa=&a;//ok
int *pb=&a;//error,因为对*pb的修改会修改a
pb=pa;//error,仍然是传递a的地址
int *const cpa=&a;//error
int *const cpa=&b;
int *pc=cpa;//ok,只是把cpa存储的地址复制

 

 

非引用const形参:但是编译器会忽略const,所以fct(const int a)与fct(int a)是重复定义。这种用法是为了支持对C语言的兼容

因为在 C 语言中,具有 const 形参或非 const 形参的函数并无区别。

非引用形参均是复制实参,用实参的副本初始化形参。但是往往需要修改实参;复制大对象代价大;有些情况无法复制对象。一般采用引用形参或指针形参

引用形参:按引用传递。指针形参亦可以完成同样的任务,但是c++中推荐使用引用形参

使用引用形参还可以返回多个值;避免复制这一点可以减少 复制代价

     // function takes a non-const reference parameter
     int incr(int &val)
     {
         return ++val;
     }
     int main()
     {
         short v1 = 0;
         const int v2 = 42;
         int v3 = incr(v1);   // error: v1 is not an int
         v3 = incr(v2);       // error: v2 is const
         v3 = incr(0);        // error: literals are not lvalues
         v3 = incr(v1 + v2);  // error: addition doesn't yield an lvalue
         int v4 = incr(v3);   // ok: v3 is a non const object type int
     }
//非const引用必须与类型完全相同的左值对象关联,不能用右值和相关类型初始化,这一点很关键!!!!!!对引用形参有很大影响.

应该将不修改相应实参的形参定义为 const 引用。如果将这样的形参定义为非 const 引用,则毫无必要地限制了该函数的使用。

     // returns index of first occurrence of c in s or s.size() if c isn't in s
     // Note: s doesn't change, so it should be a reference to const
     string::size_type find_char(string &s, char c)
     {
         string::size_type i = 0;
         while (i != s.size() && s[i] != c)
             ++i;                   // not found, look at next character
         return i;
     }
if (find_char("Hello World", 'o')) // error

指向指针的引用:一般不常用吧

     // swap values of two pointers to int
     void ptrswap(int *&v1, int *&v2)
     {
         int *tmp = v2;
         v2 = v1;
         v1 = tmp;
     }
vector和其他容器类型的形参从避免复制 vector 的角度出发,应考虑将形参声明为引用类型。然而,事实上,C++ 程序员倾向于
通过传递指向容器中需要处理的元素的迭代器来传递容器.
数组形参:数组不能复制,所以不能直接使用数组类型的形参(按值传递). 数组类似于容器,所以可以使用指针;数组名为指向数组第一个元素的指针
//虽然不能直接传递数组,但是函数的形参可以写成数组的形式。虽然形参表示方式不同,但可将使用数组语法定义的形参看作指向数组元素类型的指针。
//下面的三种定义是等价的,形参类型都是 int*。
     // three equivalent definitions of printValues
     void printValues(int*) { /* ... */ }
     void printValues(int[]) { /* ... */ }
     void printValues(int[10]) { /* ... */ }
形参的长度会引起误解,当编译器检查数组形参关联的实参时,它只会检查实参是不是指针、指针的类型和数组元素的类型时是否匹配,而不会检查数组的长度。

和其他类型一样,数组形参可定义为引用或非引用类型。大部分情况下,数组以普通的非引用类型传递,,此时数组会悄悄地转换为指针

和其他类型一样,数组形参可声明为数组的引用。如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。在这种情况下,数组大小成为形参和实参类型的一部分。编译器检查数组的实参的大小与形参的大小是否匹配:

     // ok: parameter is a reference to an array; size of array is fixed
     void printValues(int (&arr)[10]) { /* ... */ }
     int main()
     {
         int i = 0, j[2] = {0, 1};
         int k[10] = {0,1,2,3,4,5,6,7,8,9};
         printValues(&i); // error: argument is not an array of 10 ints
         printValues(j);  // error: argument is not an array of 10 ints
         printValues(k);  // ok: argument is an array of 10 ints
         return 0;
     }

任何处理数组的程序都要确保程序停留在数组的边界内。

三种方法:第一种方法是在数组本身放置一个标记来检测数组的结束。C 风格字符串就是采用这种方法的一个例子;第二种方法是传递指向数组第一个和最后一个元素的下一个位置的指针;第三种方法是将第二个形参定义为表示数组的大小,这种用法在 C 程序和标准化之前的 C++ 程序中十分普遍。

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

     void foo(parm_list, ...);
     void foo(...);
return 语句用于结束当前正在执行的函数,并将控制权返回给调用此函数的函数。return在每个函数中均有,没有返回值的函数可以使用return
跳出,若不显示调用return,则会隐式调用return
    // ok: swap acts on references to its arguments
     void swap(int &v1, int &v2)
     {
          // if values already the same, no need to swap, just return
          if (v1 == v2)
              return;
          // ok, have work to do
          int tmp = v2;
          v2 = v1;
          v1 = tmp;
          // no explicit return necessary
     }

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

     void do_swap(int &v1, int &v2)
     {
         int tmp = v2;
         v2 = v1;
         v1 = tmp;
         // ok: void function doesn't need an explicit return
     }
     void swap(int &v1, int &v2)
     {
         if (v1 == v2)
             return false; // error: void function cannot return a value
         return do_swap(v1, v2); // ok: returns call to a void function
     }

返回非引用累型时,按值传递;返回引用是按引用传递。注意,实参付给形参有一个初始化的过程!返回值也是!!!

返回引用的函数返回一个左值!!!返回引用时不能返回局部对象的引用,必须是函数调用之前已经存在对象

     char &get_val(string &str, string::size_type ix)
     {
         return str[ix];
     }
     int main()
     {
         string s("a value");
         cout << s << endl;   // prints a value
         get_val(s, 0) = 'A'; // changes s[0] to A
         cout << s << endl;   // prints A value
         return 0;
     }
//阶乘
int factorial(int val)
{
  if(val>1)
     return factorial(val-1)*val;
  return 1;
}
//最大公约数
int rgcd(int val1, val2)
{
   if(val2!=0)
      return rgcd(val2,val1%val2);
   return val1;
}
默认实参:函数调用的实参按位置解析,默认实参只能用来替换函数调用缺少的尾部实参;
     string screenInit(string::size_type height = 24,
                       string::size_type width = 80,
                       char background = ' ' );
     screen = screenInit(, , '?'); // error, can omit only trailing arguments
     screen = screenInit( '?');    // calls screenInit('?',80,' ')
既可以在函数声明也可以在函数定义中指定默认实参。但是,在一个文件中,只能为一个形参指定默认实参一次.
设计带有默认实参的函数,其中部分工作就是排列形参,使最少使用默认实参的形参排在最前,最可能使用默认实参的形参排在最后。
Local objects
每个名字都有作用域;每个对象均有生命周期
一个变量如果位于函数的作用域内,但生命期跨越了这个函数的多次调用,这种变量往往很有用。则应该将这样的对象定义为 static(静态的)。
如果非静态的则称为自动对象,其生命周期是函数调用时.静态对象,其作用域不变,只是生命周期延长到整个程序
形参,局部变量和静态局部变量的差别:
1.形参的作用域是整个函数体;局部变量和静态局部变量时从定义开始到包含该定义的块结束
2.形参由实参初始化;局部变量和静态局部变量由初始化式初始化,静态变量初始化只进行一次;
3.形参与局部变量的生命周期是自动对象;静态局部变量是整个程序
inline函数:在大多数的机器上,调用函数都要做很多工作;调用前要先保存寄存器,并在返回时恢复;复制实参;程序还必须转向一个新位置执行。
将函数指定为 inline 函数,编译时(通常)就是将它在程序中每个调用点上“内联地”展开
inline 函数的定义对编译器而言必须是可见的,以便编译器能够在调用点内联展开该函数的代码。此时,仅有函数原型是不够的。inline函数在
头文件定义.编译器隐式地将在类内定义的成员函数当作内联函数.类定义在头文件中
     class Sales_item {
     public:
         // operations on Sales_item objects
         double avg_price() const;
         bool same_isbn(const Sales_item &rhs) const
              { return isbn == rhs.isbn; }
     // private members as before
     private:
         std::string isbn;
         unsigned units_sold;
         double revenue;
     };
类函数含有隐含的形参:this,指向调用函数的对象地址。类函数之后加const(常量成员函数),表明this指向的是const对象,不能修改调用它们的对象的数据成员。

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

和其他成员函数一样,构造函数也必须在类中声明,但是可以在类中(隐式为inline)或类外定义;在冒号和花括号之间的代码称为构造函数的初始化列表

     class Sales_item {
     public:
         // operations on Sales_item objects
         double avg_price() const;
         bool same_isbn(const Sales_item &rhs) const
             { return isbn == rhs.isbn; }
         // default constructor needed to initialize members of built-in type
         Sales_item(): units_sold(0), revenue(0.0) { }
     // private members as before
     private:
         std::string isbn;
         unsigned units_sold;
         double revenue;
     };

由于合成的默认构造函数不会自动初始化内置类型的成员,所以必须明确定义 Sales_item 类的默认构造函数。

重载函数

函数不能仅仅基于不同的返回类型而实现重载

   Record lookup(const Account&);
     bool lookup(const Account&); // error: only return type is different
     // each pair declares the same function
     Record lookup(const Account &acct);
     Record lookup(const Account&); // parameter names are ignored
     typedef Phone Telno;
     Record lookup(const Phone&);
     Record lookup(const Telno&); // Telno and Phone are the same type
     Record lookup(const Phone&, const Name&);
     // default argument doesn't change the number of parameters
     Record lookup(const Phone&, const Name& = "");
     // const is irrelevent for nonreference parameters
     Record lookup(Phone);

Record lookup(const Phone); // redeclaration

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

每一个版本的重载函数都应在同一个作用域中声明。

重载函数的函数匹配过程:候选函数(函数名相同);可行函数;最佳匹配;

最佳匹配的规则是:实参类型与形参类型越接近则匹配越佳:其每个实参的匹配都不劣于其他可行函数需要的匹配;至少有一个实参的匹配优于其他可行函数提供的匹配

编译器将实参类型到相应形参类型转换划分等级。转换等级以降序排列如下:精确匹配;整型提升实现的匹配;标准转换实现的匹配(隐式转换--低到高精度;显示转换--精度损失);通过类类型转换实现的匹配

     void ff(int);
     void ff(short);
     ff('a');    // char promotes to int, so matches f(int)
    extern void manip(long);
     extern void manip(float);
     manip(3.14);  // error: ambiguous call
//没有哪个标准转换比其他标准转换具有更高的优先级

虽然无法将整型值传递给枚举类型的形参,但可以将枚举值传递给整型形参. 枚举对象只能用另一个枚举对象或枚举成员初始化

指向函数的指针:函数的类型由返回值和形参表确定;在引用函数名但又没有调用该函数时,函数名将被自动解释为指向函数的指针;指向不同函数类型的指针之间不存在转换

// pf points to function returning bool that takes two const string references
bool (*pf)(const string &, const string &);
typedef bool (*cmpFcn)(const string &, const string &);
// compares lengths of two strings
bool lengthCompare(const string &, const string &);
//可使用函数名对函数指针做初始化或赋值:
     cmpFcn pf1 = 0;             // ok: unbound pointer to function
     cmpFcn pf2 = lengthCompare; // ok: pointer type matches function's type
     pf1 = lengthCompare;        // ok: pointer type matches function's type
     pf2 = pf1;                  // ok: pointer types match
指向函数的指针可用于调用它所指向的函数。可以不需要使用解引用操作符,直接通过指针调用函数.
函数指针可以作为形参;返回类型
     void useBigger(const string &, const string &,
                    bool(const string &, const string &));
     // equivalent declaration: explicitly define the parameter as a pointer to function
     void useBigger(const string &, const string &,
                    bool (*)(const string &, const string &));
    
// ff is a function taking an int and returning a function pointer
     // the function pointed to returns an int and takes an int* and an int
     int (*ff(int))(int*, int);
     // PF is a pointer to a function returning an int, taking an int* and an int
     typedef int (*PF)(int*, int);
     PF ff(int);  // ff returns a pointer to function
原创粉丝点击