7.1 函数的声明
一个函数只有在声明后才能使用。
7.1.1 函数定义
一个函数可以声明多次,但只能定义一次。
inline描述符是给编译器一个提示(优化提示)。
7.1.2 静态变量
如果一个局部变量被声明为static,那么将只有唯一的一个静态分配的对象,他被用于在该函数的所有调用中表示这个变量。这个对象将只在执行线程第一次到达它的定义时初始化。
7.2 参数传递
当一个函数被调用时,将安排好其形式参数所需要的存储,各个形式参数将用对应的实际参数进行初始化。参数传递的语义与初始化的语义完全相同。
特别是,需要对照着每个形式参数检查与之对应的实际参数的类型,并执行所有标准的或者用户定义的类型转换。
函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题。基本的参数传递机制有两种:值传递和引用传递。以下讨论称调用其他函数的函数为主调函数,被调用的函数为被调函数。
值传递(passl-by-value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
引用传递(pass-by-reference)过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
通过引用方式传递大的对象时,比通过值传递的效率更高。在这种情况下,可以将有关的参数声明为const,来指明使用这个参数仅仅是为了效率,而不是想让调用函数能够修改这个对象的值。
对非const引用参数不允许做类型转换。
例:
void fun (int & i);
fun(2.0f);//err
fun(2);//ok
double d = 20.0;
fun(d);//err
7.2.1 数组参数
如果将数组作为函数的参数,传递的就是到数组的首元素的指针。
数组与其他类型不同,数组不会按照值传递方式传递参数。
7.3 返回值
一个没有声明为void的函数都必须返回一个值。
void函数不能有返回值。
例:int fun() {} //err
void fun() {} //ok
int fun() {return;} //err
void fun() {return;} //ok
一个调用自己的函数被称为递归。
例:(阶乘)
int fac(int n)
{
return (n-1) ? n*fac(n-1) : 1;
}
函数可以出现多个返回语句。
int fac( int n)
{
if(n>1)
return n*fac(n-1);
return 1;
}
返回语句所做的就是去初始化一个具有返回类型的匿名变量。这时将对照函数的返回类型检查返回表达式的类型,并执行所有标准的或者用户定义的转换。
绝对不能返回指向局部变量的指针
一个void函数可以将另一个void的函数作为他的return语句的表达式。
ps:一个函数可以return另一个函数(2个函数返回类型相同或者可以转换)。
7.4 重载函数名
将同一个名字用在不同类型上操作的函数称为重载。
编译器调用重载函数的基本想法:在相同名字的函数集合中,查找并调用在参数上匹配最好的那个函数,如果不存在匹配最好的,就给一个编译错误。
7.4.1 重载和返回类型(重载只关注函数的参数列表)
重载解析中不考虑返回类型。理由是要保持对重载的解析只是针对单独的运算符或者函数调用,与调用环境无关。
7.4.2 重载与作用域(重载必须在同一作用域)
在不同的非名字空间作用域里声明的函数不算是重载。
7.5 默认参数
默认参数的类型将在函数声明时检查,在调用时求值。
只能为排列在参数列表最后的那些参数提供默认参数。
在同一个作用域中随后的声明中,或者改变。
例:
int fun(int =7);
int fun(int =8);//err,默认参数不能改变
int fun(int = 7);//err,默认参数不能重复
7.6 未确定数目的参数(可变参数)
没有办法确定函数个数和类型的时候,可以在参数列表中用省略号(...)代替。省略号表示“还可能有另外一些参数”。
例如:
int printf(const char* ...);
7.7 指向函数的指针(函数指针)
对于一个函数只能做2件事:调用它或者取得它的地址。
void (*efct) (string);//函数指针
在函数指针赋值的时候,必须与完整的函数类型匹配。例如:
void (*pf) (string); //指向void(string)
void f1 (string); //void(string)
int f2(string); //int(string)
void f3 (int); //void(int)
pf = &f1; //ok
pf = &f2; //err,返回类型不匹配
pf = &f3; //err,参数类型不匹配
7.8 宏
关于宏的第一规则:最好不要去使用它,除非不得不。
按照习惯为宏命名时使用大写字母。
宏名字不能重载。例如:
#define PRINT(a, b) cout<<(a)<<(b)
#define PRINT(a, b, c) cout<<(a)<<(b)<<(c) //err,重复定义,不能重载
宏预处理器不能处理递归调用
一些可能有用的宏:
#define CASE break;case
#define FOREVER for(;;)
下面的宏很危险:
#define SQUARE (a) a*a
解析:
int xx = 0;
int y = SQUARE(xx+2); //y=xx+2 * xx+2 与我们想象的不一样
如果要使用宏,在引用全局名字是一定要使用作用域解析运算符::,并在所有可能的地方将出现的宏参数都有用括号括起来。例如:
#define MIN(a,b) ((a)<(b)) ? (a):(b)
const,inline,template,enum,namespace机制都是为了用作预处理器结构的许多传统使用方式的替代品。
例如:
const int answer=12;
template <class T>
inline
T min(T a, T b)
{
return (a<b)?a:b;
}
通过##宏运算符可以拼接2个串,构造一个新串。
例如:
#define NAME (a, b) a##b
int NAME(hello, world) {};//将产生int helloworld();
7.8.1 条件编译
常用形式:
#ifndef __HEAD.H__
#define __HEAD.H__
......
#endif