Skip to content

Latest commit

 

History

History
118 lines (66 loc) · 6.08 KB

函数.md

File metadata and controls

118 lines (66 loc) · 6.08 KB

函数

参数顺序

定义函数的时参数的顺序是输入参数在前, 输出参数在后.

C/C++ 函数的参数既可以是函数的输入, 也可以是函数的输出, 或者两者皆是. 输入参数通常是值类型或是 const 引用, 输出参数或者输入/输出参数通常是非 const 的指针. 在排列参数顺序时, 将所有的输入参数置于输出参数之前. 特别要注意, 在加入新参数时不要因为它们是新参数就置于参数列表最后, 而是仍然要按照前述的规则, 即将新的输入参数也置于输出参数之前.

这并非一个硬性规定. 输入/输出参数 (通常是类或结构体) 让这个问题变得复杂. 并且, 有时候为了其他函数保持一致, 你可能不得不有所变通.

编写简短的函数

我们倾向于编写简短, 凝练的函数.

我们承认长函数有时是合理的, 因此并不硬性限制函数的长度. 如果函数超过 40 行, 可以思索一下能不能在不影响程序结构的前提下对其进行分割.

即使一个长函数现在工作的非常好, 一旦有人对其修改, 有可能出现新的问题, 甚至导致难以发现的 bug. 使函数尽量简短, 以便于他人阅读和修改代码.

在处理代码时, 你可能会发现复杂的长函数. 不要害怕修改现有代码: 如果证实这些代码使用 / 调试起来很困难, 或者你只需要使用其中的一小段代码, 考虑将其分割为更加简短并易于管理的若干函数.

引用参数

所有按引用传递的参数必须加上 const 修饰.

定义:

在 C 语言中, 如果函数需要修改变量的值, 参数必须为指针, 如 int foo(int *pval). 在 C++ 中, 函数还可以声明为引用参数: int foo(int &val).

优点:

定义引用参数可以防止出现 (*pval)++ 这样丑陋的代码. 引用参数对于拷贝构造函数这样的应用也是必需的. 同时也更明确地不接受空指针.

容易引起误解, 因为引用在语法上是值变量却拥有指针的语义.

结论:

函数参数列表中, 所有引用参数都必须是 const:

void Foo(const string &in, string *out);

实际上, 这在 Google 的代码里是一个强制性约定, 即输入参数是值参或 const 引用, 输出参数为指针. 输入参数可以是 const 指针, 但决不能是非 const 的引用参数, 除非特殊要求, 比如 swap().

有时候, 在输入形参中用 const T* 指针比 const T& 更明智. 比如:

  • 希望传入一个空指针.
  • 函数需要把指针或者对地址的引用赋值给输入形参.

大多时候输入形参往往是 const T&. 如果使用 const T* 则相当于告诉使用者这里会对输入参数做一些特殊处理. 如果用了 const T* 而不是 const T&, 应给出相应的理由, 否则会让人产生困惑.

函数重载

若要使用函数重载, 则必须能让读者一看调用点就胸有成竹, 而不用花心思猜测调用的重载函数到底是哪一种. 这一规则也适用于构造函数.

定义:

你可以写一个接受 const string& 的函数并对这个函数进行重载, 让它也能接收一个 const char* 作为输入参数.

class MyClass {
 public:
  void Analyze(const string &text);
  void Analyze(const char *text, size_t textlen);
};

优点:

通过重载参数不同的同名函数, 可以令代码更加直观. 模板化代码需要重载, 这同时也能为使用者带来便利.

缺点:

如果函数单靠不同的参数类型而重载, 使用者就得十分熟悉 C++ 复杂的匹配规则, 以了解匹配过程具体到底如何. 另外, 如果派生类只重载了某个函数的部分变体, 继承语义就容易令人困惑.

结论:

如果想要重载一个函数, 可以试试改在函数名里加上参数信息, 比如 AppendString(), AppendInt(), 而不是都用 Append(). 如果重载函数的目的是为了支持不同数量的同一类型参数, 则优先考虑使用 std::vector 以便使用者可以用 列表初始化 指定参数.

默认参数

只允许在非虚函数中使用默认参数, 且必须保证默认参数的值始终一致. 默认参数与函数重载 遵循同样的规则. 一般情况下建议使用函数重载, 尤其是在默认函数带来的可读性提升不能弥补下文中所提到的缺点的情况下.

优点:

有些函数一般情况下使用默认参数, 但有时需要又使用非默认的参数. 默认参数为这样的情形提供了便利, 使程序员不需要为了极少的例外情况编写大量的函数. 和函数重载相比, 默认参数的语法更简洁明了, 减少了大量的样板代码, 也更好地区别了 '必要参数' 和 '可选参数'.

缺点:

默认参数实际上是函数重载语义的另一种实现方式, 因此所有不应当使用函数重载的理由也都适用于缺省参数.

虚函数调用的默认参数取决于目标对象的静态类型, 此时无法保证给定函数的所有重载声明的都一样.

默认参数在每个调用点都要进行重新求值, 会造成生成的代码迅速膨胀. 作为使用者, 一般来说也更希望默认的参数在声明时就已经被固定了, 而不是在每次调用时都可能会有不同的取值.

默认参数会干扰函数指针, 导致函数签名与调用点的签名不一致. 而函数重载不会导致这样的问题.

结论:

对于虚函数, 不允许使用默认参数. 因为在虚函数中默认参数不一定能正常工作. 如果在每个调用点默认参数的值都有可能不同, 在这种情况下默认函数也不允许使用. (例如, 不要写像 void f(int n = counter++); 这样的代码.)

在其他情况下, 如果默认参数对可读性的提升远远超过了以上提及的缺点的话, 可以使用默认参数. 如果存在困惑就用函数重载.

函数返回类型后置语法

定义:

C++ 现在允许两种不同的函数声明方式. 以往的写法是将返回类型置于函数名之前. 例如:

int foo(int x);

C++11 引入了这一新的形式. 现在可以在函数名前使用 auto 关键字, 在参数列表之后后置返回类型. 例如:

auto foo(int x) -> int;