论坛首页 编程语言技术论坛

函数function

浏览 2964 次
锁定老帖子 主题:函数function
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2008-02-12  
C++

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

C++ 语言使用调用操作符(即一对圆括号)实现函数的调用。

函数体是一个作用域

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

实参则是一个表达式。它可以是变量或字面值常量,甚至是包含一个或几个操作符的表达式。

实参个数必须与函数的形参个数完全相同,实参必须具有与形参类型相同、或者能隐式转换为形参类型的数据类型。

函数的返回类型可以是内置类型(如 int 或者 double)、类类型或复合类型(如 int& 或 string*),还可以是 void 类型,表示该函数不返回任何值。

函数不能返回另一个函数或者内置数组类型,但可以返回指向函数的指针,或指向数组元素的指针的指针

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

函数形参表可以为空,但不能省略。没有任何形参的函数可以用空形参表或含有单个关键字 void 的形参表来表示。

如果两个参数具有相同的类型,则其类型必须重复声明

参数表中不能出现同名的参数

参数传递
形参的初始化与变量的初始化一样:如果形参具有非引用类型,则复制实参的值,如果形参为引用类型,则它只是实参的别名。

普通的非引用类型的参数通过复制对应的实参实现初始化,不会修改实参的值。

非引用形参表示对应实参的局部副本。对这类形参的修改仅仅改变了局部副本的值。一旦函数执行结束,这些局部变量的值也就没有了。

如果函数将新指针赋给形参,主调函数使用的实参指针的值没有改变。
如果函数形参是非 const 类型的指针,则函数可通过指针实现赋值,修改指针所指向对象的值

如果保护指针指向的值,则形参需定义为指向 const 对象的指针

在调用函数时,如果该函数使用非引用的形参(const、非const),则既可给该函数传递 const 实参也可传递非 const 的实参,因为是以副本的形式传递.

具有 const 形参或非 const 形参的函数并无区别
形参与 const 形参的等价性仅适用于非引用形参。
有 const 引用形参的函数与有非 const 引用形参的函数是不同的

不适宜复制实参的情况包括:
1. 当需要在函数中修改实参的值时
2. 当需要以大型对象作为实参传递时
3. 当没有办法实现对象的复制时

每次调用函数,引用形参被创建并与相应实参关联。

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

如果函数具有普通的非 const 引用形参,则显然不能通过 const 对象进行调用。比较容易忽略的是,调用这样的函数时,传递一个右值或具有需要转换的类型的对象同样是不允许的。

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

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

传递指向指针的引用

vector 和其他容器类型的形参

通常,函数不应该有 vector 或其他标准库容器类型的形参。调用含有普通的非引用 vector 形参的函数将会复制 vector 的每一个元素。

事实上,C++ 程序员倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器

数组形参
处理数组的函数通常通过操纵指向数组指向数组中的元素的指针来处理数组。

形参的长度会引起误解

编译器忽略为任何数组形参指定的长度。当编译器检查数组形参关联的实参时,它只会检查实参是不是指针、指针的类型和数组元素的类型时是否匹配,而不会检查数组的长度。

不需要修改数组形参的元素时,函数应该将形参定义为指向 const 对象的指针

通过引用传递数组

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

f(int &arr[10])     // error: arr is an array of references
f(int (&arr)[10])   // ok: arr is a reference to an array of 10 ints

传递给函数的数组的处理

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

三种常见的编程技巧确保函数的操作不超出数组实参的边界。
1. 在数组本身放置一个标记来检测数组的结束,如C 风格字符串采用NULL作为结束标记
2. 传递指向数组第一个和最后一个元素的下一个位置的指针
3. 将第二个形参定义为表示数组的大小

main: 处理命令行选项
int main(int argc, char *argv[]) { ... }
int main(int argc, char **argv) { ... }

将实参传递给主函数 main 时,argv 中的第一个字符串(如果有的话)通常是程序的名字。

含有可变形参的函数
void foo(parm_list, ...);
void foo(...);

当需要传递给省略符形参时,大多数类类型对象都不能正确地复制,省略符暂停了类型检查机制

不带返回值的 return 语句只能用于返回类型为 void 的函数

隐式的 return 发生在函数的最后一个语句完成时

一般情况下,返回类型是 void 的函数使用 return 语句是为了引起函数的强制结束,这种 return 的用法类似于循环结构中的 break 语句的作用

任何返回类型不是 void 的函数必须返回一个值,而且这个返回值的类型必须和函数的返回类型相同,或者能隐式转化为函数的返回类型。

尽管 C++ 不能确保结果的正确性,但能保证函数每一次 return 都返回适当类型的结果。

在含有 return 语句的循环后没有提供 return 语句是很危险的,因为大部分的编译器不能检测出这个漏洞,运行时会出现什么问题是不确定的。

主函数 main 的返回值,返回 0 表示程序运行成功,其他大部分返回值则表示失败,非 0 返回值的意义因机器不同而不同

cstdlib 头文件定义了两个预处理变量EXIT_FAILURE和EXIT_SUCCESS,分别用于表示程序运行成功和失败

如果返回类型不是引用,在调用函数的地方会将函数返回值复制给临时对象。当函数返回非引用类型时,其返回值既可以是局部对象,也可以是求解表达式的结果。

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

理解返回引用至关重要的是:千万不能返回局部变量的引用。

返回引用的函数返回一个左值。因此,这样的函数可用于任何要求使用左值的地方

千万不要返回指向局部对象的指针

直接或间接调用自己的函数称为递归函数。

递归函数必须定义一个终止条件;否则,函数就会“永远”递归下去

主函数 main 不能调用自身。

函数声明由函数返回类型、函数名和形参列表组成。形参列表必须包括形参类型,但是不必对形参命名。这三个元素被称为函数原型,函数原型描述了函数的接口。

函数声明中的形参名会被忽略,如果在声明中给出了形参的名字,它应该用作辅助文档

函数应当在头文件中声明,并在源文件中定义。

义函数的源文件应包含声明该函数的头文件。

可为一个或多个形参定义默认值。但是,如果有一个形参具有默认实参,那么,它后面所有的形参都必须有默认实参。

调用包含默认实参的函数时,可以为该形参提供实参,也可以不提供。如果提供了实参,则它将覆盖默认的实参值;否则,函数将使用默认实参值。

函数调用的实参按位置解析,默认实参只能用来替换函数调用缺少的尾部实参。

默认情况下,局部变量的生命期局限于所在函数的每次执行期间。只有当定义它的函数被调用时才存在的对象称为自动对象。自动对象在每次调用函数时创建和撤销。

局部变量所对应的自动对象在函数控制经过变量定义语句时创建。

形参也是自动对象。形参所占用的存储空间在调用函数时创建,而在函数结束时撤销。

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

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

inline 说明对于编译器来说只是一个建议,编译器可以选择忽略这个。

一般来说,内联机制适用于优化小的、只有几行的而且经常被调用的函数。

大多数的编译器都不支持递归函数的内联

内联函数应该在头文件中定义,这一点不同于其他函数。

内联函数应该在头文件中定义,这一点不同于其他函数。

在头文件中加入或修改 inline 函数时,使用了该头文件的所有源文件都必须重新编译。

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

任何程序都仅有一个 main 函数的实例。main 函数不能重载。

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

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

 


设计带有默认实参的函数,其中部分工作就是排列形参,使最少使用默认实参的形参排在最前,最可能使用默认实参的形参排在最后。

如果默认实参是一个表达式,而且默认值用作实参,则在调用函数时求解该表达式。

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

通常,应在函数声明中指定默认实参,并将该声明放在合适的头文件中。

如果在函数定义的形参表中提供默认实参,那么只有在包含该函数定义的源文件中调用该函数时,默认实参才是有效的。

一般来说,局部地声明函数是一种不明智的选择。函数的声明应放在头文件中。

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

函数匹配与实参转换

重载确定的三个步骤
1. 候选函数
与被调函数同名的函数,并且在调用点上,它的声明可见。
2. 选择可行函数
可行函数必须满足两个条件:第一,函数的形参个数与该调用的实参个数相同;第二,每一个实参的类型必须与对应形参的类型匹配,或者可被隐式转换为对应的形参类型。如果函数具有默认实参,则调用该函数时,所用的实参可能比实际需要的少。默认实参也是实参,在函数匹配过程中,它的处理方式与其他实参一样

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

必须注意的一个重点是较小的整型提升为 int 型

通过类型提升实现的转换优于其他标准转换

整数对象即使具有与枚举元素相同的值也不能用于调用期望获得枚举类型实参的函数

在使用有枚举类型形参的重载函数时,请记住:由于不同枚举类型的枚举常量值不相同,在函数重载确定过程中,不同的枚举类型会具有完全不同的行为。其枚举成员决定了它们提升的类型,而所提升的类型依赖于机器。

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

注意不能基于指针本身是否为 const 来实现函数的重载

指向函数的指针

函数指针是指指向函数而非指向对象的指针

函数类型由其返回类型以及形参表确定,而与函数名无关

函数指针类型相当地冗长。使用 typedef 为指针类型定义同义词,可将函数指针的使用大大简化

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

可使用函数名对函数指针做初始化或赋值

直接引用函数名等效于在函数名上应用取地址操作符

函数指针只能通过同类型的函数或函数指针或 0 值常量表达式进行初始化或赋值。

将函数指针初始化为 0,表示该指针不指向任何函数。

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

指向函数的指针可用于调用它所指向的函数

如果指向函数的指针没有初始化,或者具有 0 值,则该指针不能在函数调用中使用。只有当指针已经初始化,或被赋值为指向某个函数,方能安全地用来调用函数。

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

返回指向函数的指针

允许将形参定义为函数类型,但函数的返回类型则必须是指向函数的指针,而不能是函数。

C++ 语言允许使用函数指针指向重载的函数

指针的类型必须与重载函数的一个版本精确匹配

#include <iostream>
#include <cstdlib>
#include <string>
#include <vector>
using namespace std;

// error: missing return type
// ISO C++ forbids declaration of `test' with no type 
// test(double v1, double v2){ }

     
void process() { /* ... */ }      // implicit void parameter list
//void process(void){ /* ... */ }  equivalent declaration

//int manip(int v1, v2) { /* ... */ }   error
int manip(int v1, int v2) { /* ... */ }  // ok

void fcn(const int i) { /* fcn can read but not write to i */ }
//void fcn(int i) { /* ... */ }     error: redefines fcn(int)

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

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

// pass iterators to the first and one past the last element to print
void print(vector<int>::const_iterator beg,vector<int>::const_iterator end)
{
     while (beg != end) 
     {
         cout << *beg++;
         if (beg != end) 
         {
            cout << " "; // no space after last element
         }
     }
     cout << endl;
}

// 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 printValuesByRef(int (&arr)[10]) 
{ 
     for(size_t index=0; index!=10; ++index)
     {
          cout << arr[index] << " ";
     }
     cout << endl;
}

size_t count_calls()
{
  static size_t ctr = 0; // value will persist across calls
  return ++ctr;
}

// inline version: find longer of two strings
inline const string & shorterString(const string &s1, const string &s2)
{
     return s1.size() < s2.size() ? s1 : s2;
}


void f();
void f(int);
void f(int, int);
void f(double, double = 3.14);


enum Tokens{INLINE = 128, VIRTUAL = 129};
void ff(Tokens);
void ff(int);

void fff(int *){}
//void fff(int *const){}  error redeclaration

// pf points to function returning bool that takes two const string references
bool (*pf)(const string &, const string &);

// declares a function named pf that returns a bool*
bool *pd(const string &, const string &);

// compares lengths of two strings
bool lengthCompare(const string &, const string &);

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

// compares lengths of two strings
bool lengthCompare(const string &, const string &);

/* useBigger function's third parameter is a pointer to function
* that function returns a bool and takes two const string references
* two ways to specify that parameter:
*/
// third parameter is a function type and is automatically treated as a pointer to function
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 (*fs(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 fd(int);  // ff returns a pointer to function

void ff(vector<double>);
void ff(unsigned int);

int main()
{
    short v1 = 0;
    const int v2 = 42;
    int v3 = 33;
    //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

    int i = 10;
    int j = 20;
    int *pi = &i;  // pi points to i
    int *pj = &j; // pj points to j
    cout << "Before ptrswap():\t*pi: "
      << *pi << "\t*pj: " << *pj << endl;
    ptrswap(pi, pj); // now pi points to j; pj points to i
    cout << "After ptrswap():\t*pi: "
      << *pi << "\t*pj: " << *pj << endl;

    int ia[10] = {0,1,2,3,4,5,6,7,8,9};
    printValuesByRef(ia);
    
    for (size_t i = 0; i != 10; ++i)
    {
     cout << count_calls() << endl;
    }
    
    f(5.6);   //calls void f(double, double)

    
    Tokens curTok = INLINE;
    ff(128);    // exactly matches ff(int)
    ff(INLINE); // exactly matches ff(Tokens)
    ff(curTok); // exactly matches ff(Tokens)
    
    cmpFcn pf1 = 0;             // ok: unbound pointer to function
    cmpFcn pf2 = lengthCompare; // ok: pointer type matches function's type
    cmpFcn pf3 = &lengthCompare;  // the same sa above
    pf1 = lengthCompare;        // ok: pointer type matches function's type
    pf2 = pf1;                  // ok: pointer types match
    
    cmpFcn pf = lengthCompare;
    lengthCompare("hi", "bye"); // direct call
    pf("hi", "bye");            // equivalent call: pf1 implicitly dereferenced
    (*pf)("hi", "bye");         // equivalent call: pf1 explicitly dereferenced

    // which function does pf1 refer to?
    void (*pf4)(unsigned int) = &ff; // ff(unsigned)
        
    if(true)
    {
        return EXIT_SUCCESS;
    }  
}


void f()
{
 cout << "f() ..." << endl;     
}
void f(int)
{
 cout << "f(int) ..." << endl;     
}
void f(int, int)
{
 cout << "f(int ,int) ..." << endl;     
}
void f(double, double)
{
 cout << "f(double, double = 3.14) ..." << endl;     
}

void ff(Tokens)
{
 cout << "ff(Tokens) ..." << endl;     
}
void ff(int)
{
 cout << "ff(int) ..." << endl;     
}
bool lengthCompare(const string &s1, const string &s2)
{
     return  s1 < s2 ;
}
void ff(vector<double>)
{
  cout << "ff(vector<double>) ..." << endl;      
}
void ff(unsigned int)
{
  cout << "ff(unsigned int) ..." << endl;      
}

 

论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics