函数调用:
当函数调用发生时,程序用对应的实参初始化函数的形参,并将控制权转移给被调用函数。主调函数的执行被暂停,被调函数开始执行。
函数体是一个作用域:
函数体是一个作用域。在函数体内定义的变量只在该函数中才可以访问,名字之在该函数的作用域中可见,且只在函数运行时存在。这种变量称为局部变量。
函数形参:
1函数形参表中,f如果两个参数具有相同的类型,则其类型必须重复声明:
int manip(int v1, v2) { /* ... */ } // error
int manip(int v1, int v2) { /* ... */ } // ok
2调用函数时,对于每一个实参,其类型都必须与对应的形参类型相同,或具有可被转换为该形参类型的类型。比如,double可以通过截断(truncation)被隐式转换为int型。
非引用形参-指针形参:
如果函数形参是非const类型的指针,那么函数可以通过指针实现赋值。
如果函数形参是const类型的指针,那么函数不可以修改指针指向的值。
非引用形参-const形参:
如果该函数使用非引用的非 const 形参,则既可给该函数传递 const 实参也可传递非const实参。
引用形参:
引用形参的好处很多,比如将主调函数返回额外的结果,比如避免一些没有必要的复制。
非const类型的引用形参比const类型的引用形参有更多限制。
比如:
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 对象初始化,也不能用字面值或产生右值的表达式实参初始化。
引用形参-指向指针的引用:
顾名思义,一个指向指针的引用。写法很奇特,int *&var。表示一个引用,它和int型对象的指针相关联。这个定义要从右往左理解。
void ptrswap(int *&v1, int *&v2)
{
int *tmp = v2;
v2 = v1;
v1 = tmp;
}
交换后,v1指向原来v2所指的对象,v2指向原来v1所指的对象。
Vector等容器类型的形参:
通常用迭代器做形参。如果非要用vector做形参,那么也应该声明为引用型,否则非引用形参会导致函数复制vector中的每一个元素。
数组形参(指针型):
类比vector,使用指向数组元素的指针来处理数组。
因为使用数组时,一不能复制数组;二使用数组名字时,数组名会自动转化为指向其第一个元素的指针。
以下三种定义是等价的,形参类型都是Int*:
void printValues(int*) { /* ... */ }
void printValues(int[]) { /* ... */ }
void printValues(int[10]) { /* ... */ }
看下面一段代码:
void printValues(const int ia[10])
{
// this code assumes array has 10 elements;
// disaster if argument has fewer than 10 elements!
for (size_t i = 0; i != 10; ++i)
{
cout << ia[i] << endl;
}
}
int main()
{
int i = 0, j[2] = {0, 1};
printValues(&i); // ok: &i is int*; probable run-time error
printValues(j); //ok: argument has type int*;
return 0;
}
以上代码说明:当编译器检查数组形参关联的实参时,它只会检查实参是不是指针、指针的类型和数组元素的类型时是否匹配,而不会检查数组的长度。
引用型数组形参:
如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。在这种情况下,数组大小成为形参和实参类型的一部分。编译器检查数组的实参的大小与形参的大小是否匹配:
// ok: parameter is a reference to an array; size of array is fixed
void printValues(int (&arr)[10]) { /* ... */ }
//&arr 两边的圆括号是必需的,因为下标操作符具有更高的优先级:
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 风格字符串就是采用这种方法的一个例子,它是一种字符数组,并且以空字符 null 作为结束的标记。处理 C 风格字符串的程序就是使用这个标记停止数组元素的处理。
第二种方法是传递指向数组第一个和最后一个元素的下一个位置的指针,类比标准库技术。
return语句:
在void函数类型中,return语句可以返回另一个类型同样是void的函数的调用结果。
含有return语句的的循环后面如果没有提供return语句,将有极大危险。因为如果这个循环根本就没有执行,那么运行时就会出问题。这个问题在编译阶段查不出。
理解返回引用至关重要的是:千万不能返回局部变量的引用,也不能返回指向局部对象的指针。因为当函数执行完毕,这些对象的空间将被释放。