函数
C++ 是一种静态强类型语句(第 2.3 节),对于每一次的函数
调用,编译时都会检查其实参。
形参的初始化与变量的初始化一样:如果形参具有非引用类型,
则复制实参的值,如果形参为引用类型(第 2.5 节),则它只
是实参的别名。
非引用形参表示对应实参的局部副本。对这类形参的修改仅仅
改变了局部副本的值。一旦函数执行结束,这些局部变量的值
也就没有了。
函数的形参可以是指针,此时将复制实参指针。
因为在 C 语言中,具有 const 形参或非 const 形参的函数并无区别。
复制实参并不是在所有的情况下都适合,不适宜复制实参的情况包括:
当需要在函数中修改实参的值时。
当需要以大型对象作为实参传递时。对实际的应用而言,复制对象所付出
的时间和存储空间代价往往过在。
当没有办法实现对象的复制时。
对于上述几种情况,有效的解决办法是将形参定义为引用或指针类型。
从 C 语言背景转到 C++ 的程序员习惯通过传递指针来实现对实参的访问。在 C++ 中,使用引用形参则更安全和更自然。
引用形参的另一种用法是向主调函数返回额外的结果。
如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为 const 引用。
问题的关键是非 const 引用形参只能与完全同类型的非 const 对象关联。
应该将不修改相应实参的形参定义为 const 引用。如果将这样的形参定义
为非 const 引用,则毫无必要地限制了该函数的使用。
[color=red]应该将不需要修改的引用形参定义为 const 引用。普
通的非 const 引用形参在使用时不太灵活。这样的形
参既不能用 const 对象初始化,也不能用字面值或产
生右值的表达式实参初始化。[/color]
int main(int argc, char *argv[]) { ... }或者int main(int argc, char **argv) { ... }
第一个形参 argc 则用于传递该数组中字符串的个数。
当将实参传递给主函数 main 时,argv 中的第一个字符串(如果有的话)
通常是程序的名字。接下来的元素将额外的可选字符串传递给主函数 main。
7.2.7. 含有可变形参的函数
C++ 中的省略符形参是为了编译使用了 varargs 的 C 语言程序。
省略符暂停了类型检查机制。它们的出现告知编译器,当调用函数时,可以
有 0 或多个实参,而实参的类型未知。省略符形参有下列两种形式:
void foo(parm_list, ...);
void foo(...);
一般情况下,返回类型是 void 的函数使用 return 语句是为了引起函数的
强制结束,这种 return 的用法类似于循环结构中的 break 语句的作用。
允许主函数 main 没有返回值就可结束。
如果程序控制执行到主函数 main 的最后一个语句都还没有返回,那么编译器会隐式地插入返回 0 的语句。
函数的返回值用于初始化在调用函数处创建的临时对象。在求解表达式时,
如果需要一个地方储存其运算结果,编译器会创建一个没有命名的对象,这就是
临时对象。
在英语中,C++ 程序员通常用 temporary 这个术语来代替 temporary object。
理解返回引用至关重要的是:千万不能返回局部变量的引用。
当函数执行完毕时,将释放分配给局部对象的存储空间。此时,对局部对象
的引用就会指向不确定的内存。
给函数返回值赋值可能让人惊讶,由于函数返回的是一个引用,因此这是正
确的,该引用是被返回元素的同义词。如果不希望引用返回值被修改,返回值应该声明为 const。
只有当定义它的函数被调用时才存在的对象称为自动对象。
局部变量所对应的自动对象在函数控制经过变量定义语句时创建。
形参也是自动对象。形参所占用的存储空间在调用函数时创建,而在函数结束时撤销。
一个变量如果位于函数的作用域内,但生命期跨越了这个函数的多次调用,
这种变量往往很有用。则应该将这样的对象定义为 static(静态的)。
这种对象一旦被创建,在程序结束前都不会撤销。当定义静态局
部对象的函数结束时,静态局部对象不会撤销。在该函数被多次调用的过程中,
静态局部对象会持续存在并保持它的值。
7.6. 内联函数
一般来说,内联机制适用于优化小的、只有几行的而且经常被调用的函数。
大多数的编译器都不支持递归函数的内联。
内联函数应该在头文件中定义,这一点不同于其他函数。
inline 函数的定义对编译器而言必须是可见的,以便编译器能够在调用点
内联展开该函数的代码。此时,仅有函数原型是不够的。
类的成员函数可以访问该类的 private 成员。
每个成员函数都有一个额外的、隐含的形参 this。
用这种方式使用 const 的函数称为常量成员函数。
const 对象、指向 const 对象的指针或引用只能用于调用其
const 成员函数,如果尝试用它们来调用非 const 成员函数,
则是错误的。
在成员函数中,不必显式地使用 this 指针来访问被调用函数所属对象的成员。
对这个类的成员的任何没有前缀的引用,都被假定为通过指针 this 实现的引用。
在类的定义外面定义成员函数必须指明它们是类的成员
函数名:
Sales_item::avg_price
使用作用域操作符指明函数 avg_price 是在类 Sales_item 的作用域范围内定义的。
在任何函数定义中,返回类型和形参表必须和函数声明(如果有的话)一致。对于成
员函数,函数声明必须与其定义一致。如果函数被声明为 const 成员函数,那
么函数定义时形参表后面也必须有 const。
构造函数是特殊的成员函数
Sales_item(): units_sold(0), revenue(0.0) { }
在冒号和花括号之间的代码称为构造函数的初始化列表。
构造函数的初始化列表为类的一个或多个数据成员指定初值。它跟在构造函数的形参表之后,以冒号开关。
合成的默认构造函数一般适用于仅包含类类型成员的类。而对
于含有内置类型或复合类型成员的类,则通常应该定义他们自
己的默认构造函数初始化这些成员。
出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。
typedef 给已存在的数据类型提供别名,但并没有创建新的数据类型。
7.8.1. 重载与作用域
在函数中局部声明的名字将屏蔽在全局作用域内声明的同名名字。
一般来说,局部地声明函数是一种不明智的选择。函数的声明
应放在头文件中。
在 C++ 中,名字查找发生在类型检查之前。
在实际应用中,调用重载函数时应尽量避免对实参做强制类型转换:
需要使用强制类型转换意味着所设计的形参集合不合理。
7.9. 指向函数的指针
函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某个特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关.
bool (*pf)(const string &, const string &);
这个语句将 pf 声明为指向函数的指针,它所指向的函数带有两个 const
string& 类型的形参和 bool 类型的返回值。
typedef bool (*cmpFcn)(const string &, const string &);
该定义表示 cmpFcn 是一种指向函数的指针类型的名字。该指针类型为“指
向返回 bool 类型并带有两个 const string 引用形参的函数的指针”。在要使
用这种函数指针类型时,只需直接使用 cmpFcn 即可,不必每次都把整个类型声
明全部写出来。
可使用函数名对函数指针做初始化或赋值.
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 type
直接引用函数名等效于在函数名上应用取地址操作符
cmpFcn pf1 = lengthCompare;
cmpFcn pf2 = &lengthCompare;
函数指针只能通过同类型的函数或函数指针或 0 值常量表达
式进行初始化或赋值。
function and pointer types match exactly,返回值和参数都要一致。
指向函数的指针可用于调用它所指向的函数。可以不需要使用解引用操作
符,直接通过指针调用函数:
cmpFcn pf = lengthCompare;
lengthCompare("hi", "bye"); // direct call
pf("hi", "bye");
// equivalent call: pf1 implicitly
dereferenced
(*pf)("hi", "bye");
// equivalent call: pf1 explicitly
dereferenced
如果指向函数的指针没有初始化,或者具有 0 值,则该指针不
能在函数调用中使用。只有当指针已经初始化,或被赋值为指
向某个函数,方能安全地用来调用函数。
int (*ff(int))(int*, int);
要理解该声明的含义,首先观察:
ff(int)
将 ff 声明为一个函数,它带有一个 int 型的形参。该函数返回
int (*)(int*, int);
它是一个指向函数的指针,所指向的函数返回 int 型并带有两个分别是
int* 型和 int 型的形参。
使用 typedef 可使该定义更简明易懂:
// 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
允许将形参定义为函数类型,但函数的返回类型则必须是指向
函数的指针,而不能是函数。
指向重载函数的指针
C++ 语言允许使用函数指针指向重载的函数:
extern void ff(vector<double>);
extern void ff(unsigned int);
void (*pf1)(unsigned int) = &ff; // ff(unsigned)
指针的类型必须与重载函数的一个版本精确匹配。如果没有精确匹配的函
数,则对该指针的初始化或赋值都将导致编译错误
C++ 是一种静态强类型语句(第 2.3 节),对于每一次的函数
调用,编译时都会检查其实参。
形参的初始化与变量的初始化一样:如果形参具有非引用类型,
则复制实参的值,如果形参为引用类型(第 2.5 节),则它只
是实参的别名。
非引用形参表示对应实参的局部副本。对这类形参的修改仅仅
改变了局部副本的值。一旦函数执行结束,这些局部变量的值
也就没有了。
函数的形参可以是指针,此时将复制实参指针。
因为在 C 语言中,具有 const 形参或非 const 形参的函数并无区别。
复制实参并不是在所有的情况下都适合,不适宜复制实参的情况包括:
当需要在函数中修改实参的值时。
当需要以大型对象作为实参传递时。对实际的应用而言,复制对象所付出
的时间和存储空间代价往往过在。
当没有办法实现对象的复制时。
对于上述几种情况,有效的解决办法是将形参定义为引用或指针类型。
从 C 语言背景转到 C++ 的程序员习惯通过传递指针来实现对实参的访问。在 C++ 中,使用引用形参则更安全和更自然。
引用形参的另一种用法是向主调函数返回额外的结果。
如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为 const 引用。
问题的关键是非 const 引用形参只能与完全同类型的非 const 对象关联。
应该将不修改相应实参的形参定义为 const 引用。如果将这样的形参定义
为非 const 引用,则毫无必要地限制了该函数的使用。
[color=red]应该将不需要修改的引用形参定义为 const 引用。普
通的非 const 引用形参在使用时不太灵活。这样的形
参既不能用 const 对象初始化,也不能用字面值或产
生右值的表达式实参初始化。[/color]
int main(int argc, char *argv[]) { ... }或者int main(int argc, char **argv) { ... }
第一个形参 argc 则用于传递该数组中字符串的个数。
当将实参传递给主函数 main 时,argv 中的第一个字符串(如果有的话)
通常是程序的名字。接下来的元素将额外的可选字符串传递给主函数 main。
7.2.7. 含有可变形参的函数
C++ 中的省略符形参是为了编译使用了 varargs 的 C 语言程序。
省略符暂停了类型检查机制。它们的出现告知编译器,当调用函数时,可以
有 0 或多个实参,而实参的类型未知。省略符形参有下列两种形式:
void foo(parm_list, ...);
void foo(...);
一般情况下,返回类型是 void 的函数使用 return 语句是为了引起函数的
强制结束,这种 return 的用法类似于循环结构中的 break 语句的作用。
允许主函数 main 没有返回值就可结束。
如果程序控制执行到主函数 main 的最后一个语句都还没有返回,那么编译器会隐式地插入返回 0 的语句。
函数的返回值用于初始化在调用函数处创建的临时对象。在求解表达式时,
如果需要一个地方储存其运算结果,编译器会创建一个没有命名的对象,这就是
临时对象。
在英语中,C++ 程序员通常用 temporary 这个术语来代替 temporary object。
理解返回引用至关重要的是:千万不能返回局部变量的引用。
当函数执行完毕时,将释放分配给局部对象的存储空间。此时,对局部对象
的引用就会指向不确定的内存。
给函数返回值赋值可能让人惊讶,由于函数返回的是一个引用,因此这是正
确的,该引用是被返回元素的同义词。如果不希望引用返回值被修改,返回值应该声明为 const。
只有当定义它的函数被调用时才存在的对象称为自动对象。
局部变量所对应的自动对象在函数控制经过变量定义语句时创建。
形参也是自动对象。形参所占用的存储空间在调用函数时创建,而在函数结束时撤销。
一个变量如果位于函数的作用域内,但生命期跨越了这个函数的多次调用,
这种变量往往很有用。则应该将这样的对象定义为 static(静态的)。
这种对象一旦被创建,在程序结束前都不会撤销。当定义静态局
部对象的函数结束时,静态局部对象不会撤销。在该函数被多次调用的过程中,
静态局部对象会持续存在并保持它的值。
7.6. 内联函数
一般来说,内联机制适用于优化小的、只有几行的而且经常被调用的函数。
大多数的编译器都不支持递归函数的内联。
内联函数应该在头文件中定义,这一点不同于其他函数。
inline 函数的定义对编译器而言必须是可见的,以便编译器能够在调用点
内联展开该函数的代码。此时,仅有函数原型是不够的。
类的成员函数可以访问该类的 private 成员。
每个成员函数都有一个额外的、隐含的形参 this。
用这种方式使用 const 的函数称为常量成员函数。
const 对象、指向 const 对象的指针或引用只能用于调用其
const 成员函数,如果尝试用它们来调用非 const 成员函数,
则是错误的。
在成员函数中,不必显式地使用 this 指针来访问被调用函数所属对象的成员。
对这个类的成员的任何没有前缀的引用,都被假定为通过指针 this 实现的引用。
在类的定义外面定义成员函数必须指明它们是类的成员
函数名:
Sales_item::avg_price
使用作用域操作符指明函数 avg_price 是在类 Sales_item 的作用域范围内定义的。
在任何函数定义中,返回类型和形参表必须和函数声明(如果有的话)一致。对于成
员函数,函数声明必须与其定义一致。如果函数被声明为 const 成员函数,那
么函数定义时形参表后面也必须有 const。
构造函数是特殊的成员函数
Sales_item(): units_sold(0), revenue(0.0) { }
在冒号和花括号之间的代码称为构造函数的初始化列表。
构造函数的初始化列表为类的一个或多个数据成员指定初值。它跟在构造函数的形参表之后,以冒号开关。
合成的默认构造函数一般适用于仅包含类类型成员的类。而对
于含有内置类型或复合类型成员的类,则通常应该定义他们自
己的默认构造函数初始化这些成员。
出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数。
typedef 给已存在的数据类型提供别名,但并没有创建新的数据类型。
7.8.1. 重载与作用域
在函数中局部声明的名字将屏蔽在全局作用域内声明的同名名字。
一般来说,局部地声明函数是一种不明智的选择。函数的声明
应放在头文件中。
在 C++ 中,名字查找发生在类型检查之前。
在实际应用中,调用重载函数时应尽量避免对实参做强制类型转换:
需要使用强制类型转换意味着所设计的形参集合不合理。
7.9. 指向函数的指针
函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某个特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关.
bool (*pf)(const string &, const string &);
这个语句将 pf 声明为指向函数的指针,它所指向的函数带有两个 const
string& 类型的形参和 bool 类型的返回值。
typedef bool (*cmpFcn)(const string &, const string &);
该定义表示 cmpFcn 是一种指向函数的指针类型的名字。该指针类型为“指
向返回 bool 类型并带有两个 const string 引用形参的函数的指针”。在要使
用这种函数指针类型时,只需直接使用 cmpFcn 即可,不必每次都把整个类型声
明全部写出来。
可使用函数名对函数指针做初始化或赋值.
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 type
直接引用函数名等效于在函数名上应用取地址操作符
cmpFcn pf1 = lengthCompare;
cmpFcn pf2 = &lengthCompare;
函数指针只能通过同类型的函数或函数指针或 0 值常量表达
式进行初始化或赋值。
function and pointer types match exactly,返回值和参数都要一致。
指向函数的指针可用于调用它所指向的函数。可以不需要使用解引用操作
符,直接通过指针调用函数:
cmpFcn pf = lengthCompare;
lengthCompare("hi", "bye"); // direct call
pf("hi", "bye");
// equivalent call: pf1 implicitly
dereferenced
(*pf)("hi", "bye");
// equivalent call: pf1 explicitly
dereferenced
如果指向函数的指针没有初始化,或者具有 0 值,则该指针不
能在函数调用中使用。只有当指针已经初始化,或被赋值为指
向某个函数,方能安全地用来调用函数。
int (*ff(int))(int*, int);
要理解该声明的含义,首先观察:
ff(int)
将 ff 声明为一个函数,它带有一个 int 型的形参。该函数返回
int (*)(int*, int);
它是一个指向函数的指针,所指向的函数返回 int 型并带有两个分别是
int* 型和 int 型的形参。
使用 typedef 可使该定义更简明易懂:
// 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
允许将形参定义为函数类型,但函数的返回类型则必须是指向
函数的指针,而不能是函数。
指向重载函数的指针
C++ 语言允许使用函数指针指向重载的函数:
extern void ff(vector<double>);
extern void ff(unsigned int);
void (*pf1)(unsigned int) = &ff; // ff(unsigned)
指针的类型必须与重载函数的一个版本精确匹配。如果没有精确匹配的函
数,则对该指针的初始化或赋值都将导致编译错误