非引用形参
const形参
在调用函数时,如果该函数使用非引用的非const 形参,则既可给该函数传递const 实参也可传递非 const 的实参。这种行为源于 const 对象的标准初始化规则。因为初始化复制了初始化式的值,所以可用const 对象初始化非 const 对象,反之亦然。
如果将形参定义为非引用的const 类型:
void fcn(const int i) { /* fcn can read but not write to i */ }
则在函数中,不可以改变实参的局部副本。由于实参仍然是以副本的形式传递,因此传递给fcn的既可以是const 对象也可以是非const 对象。
函数中const形参与非const形参无区别。
void fcn(const int i) { /*fcncan read but not write toi */ }
void fcn(int i) { /* ... */ } //error: redefinesfcn(int)
这种用法是为了支持对 C 语言的兼容,因为在 C 语言中,具有const 形参或非const 形参的函数并无区别。
int main()
{
int a=5;
const int b=4;
fun1(a,b);
}
/*************************************************
//const非引用形参
//程序输出结果为5 10
*************************************************/
void fun1(const int a,int b)
{
//a=10;//error C3892: “a”: 不能给常量赋值
b=10;
cout<<a<<" "<<b;
}//endfun1
/*************************************************
//error C2084: 函数“void fun1(const int,int)”已有主体
*************************************************/
//void fun1(int a,int b)
//{
// cout<<a<<" "<<b;
//}
指针形参
函数的形参可以是指针,此时将复制实参指针。与其他非引用类型的形参一样,该类形参的任何改变也仅作用于局部副本。如果函数将新指针赋给形参,主调函数使用的实参指针的值没有改变。事实上被复制的指针只影响对指针的赋值。如果函数形参是非const 类型的指针,则函数可通过指针实现赋值,修改指针所指向对象的值:
void reset(int *ip)
{
*ip = 0; // changes the value of the object to which ip points
ip = 0; // changes only the local value of ip; the argument is unchanged
}
如果保护指针指向的值,则形参需定义为指向 const 对象的指针:
void use_ptr(const int *p) { // use_ptr may read but not write to *p }
指针形参是指向const 类型还是非const 类型,将影响函数调用所使用的实参。我们既可以用int* 也可以用const int* 类型的实参调用 use_ptr 函数;但仅能将 int* 类型的实参传递给reset 函数。这个差别来源于指针的初始化规则。可以将指向const 对象的指针初始化为指向非const 对象,但不可以让指向非 const 对象的指针向 const 对象。
复制实参的局限性
复制实参并不是在所有的情况下都适合,不适宜复制实参的情况包括:-
1.当需要在函数中修改实参的值时
-
-
2.当需要以大型对象作为实参传递时。对实际的应用而言,复制对象所付出的时间和存储空间代价往往过大
-
-
-
-
3.当没有办法实现对象的复制时。
-
-
引用形参
传递指向指针的引用
假设我们想编写一个与前面交换两个整数的swap 类似的函数,实现两个指针的交换。已知需用* 定义指针,用 & 定义引用。现在,问题在于如何将这两个操作符结合起来以获得指向指针的引用。这里给出一个例子:
// swap values of two pointers to int
void ptrswap(int *&v1, int *&v2)
{
int *tmp = v2;
v2 = v1;
v1 = tmp;
}
形参
int *&v1的定义应从右至左理解:v1 是一个引用,与指向 int 型对象的指针相关联。也就是说,v1 只是传递进ptrswap 函数的任意指针的别名。
int main() { 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; return 0; }
编译并执行后,该程序产生如下结果:
Before ptrswap(): *pi: 10 *pj: 20 After ptrswap(): *pi: 20 *pj: 10
//修改了pi和pj指向的地址,并不修改i,j
即指针的值被交换了。在调用ptrswap 时,pi 指向i,而 pj 则指向 j。在 ptrswap 函数中,指针被交换,使得调用ptrswap 结束后,pi 指向了原来pj 所指向的对象。换句话说,现在 pi 指向 j,而 pj 则指向了 i。
vector 和其他容器类型的形参
通常,函数不应该有 vector 或其他标准库容器类型的形参。调用含有普通的非引用vector 形参的函数将会复制vector 的每一个元素。
从避免复制vector 的角度出发,应考虑将形参声明为引用类型。然而事实上,C++ 程序员倾向于通过传递指向容器中需要处理的元素的迭代器来传递容器:
// 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]) { /* ... */ }
虽然不能直接传递数组,但是函数的形参可以写成数组的形式。虽然形参表示方式不同,但可将使用数组语法定义的形参看作指向数组元素类型的指针。上面的三种定义是等价的,形参类型都是
int*。
通过引用传递数组
和其他类型一样,数组形参可声明为数组的引用。如果形参是数组的引用,编译器不会将数组实参转化为指针,而是传递数组的引用本身。在这种情况下,数组大小成为形参和实参类型的一部分。编译器检查数组的实参的大小与形参的大小是否匹配。
// ok: parameter is a reference to an array; size of array is fixed void printValues(int (&arr)[10]) { for (size_t i = 0; i != 10; ++i) { cout << arr[i] << endl; } }
多维数组的传递
和其他数组一样,多维数组以指向 0 号元素的指针方式传递。多维数组的元素本身就是数组。除了第一维以外的所有维的长度都是元素类型的一部分,必须明确指定:
// first parameter is an array whose elements are arrays of 10 ints void printValues(int (matrix*)[10], int rowSize);
上面的语句将matrix 声明为指向含有 10 个int 型元素的数组的指针。
我们也可以用数组语法定义多维数组。与一维数组一样,编译器忽略第一维的长度,所以最好不要把它包括在形参表内:
// first parameter is an array whose elements are arrays of 10 ints void printValues(int matrix[][10], int rowSize);
传递给函数的数组的处理
就如刚才所见的,非引用数组形参的类型检查只是确保实参是和数组元素具有同样类型的指针,而不会检查实参实际上是否指向指定大小的数组。
有三种常见的编程技巧确保函数的操作不超出数组实参的边界。
第一种方法是在数组本身放置一个标记来检测数组的结束。C 风格字符串就是采用这种方法的一个例子,它是一种字符数组,并且以空字符 null 作为结束的标记。处理 C 风格字符串的程序就是使用这个标记停止数组元素的处理。
第二种方法是传递指向数组第一个和最后一个元素的下一个位置的指针。
void printValues(const int *beg, const int *end)
{ while (beg != end) { cout << *beg++ << endl; } }
一个指向要输出的第一个元素,另一个则指向最后一个元素的下一个位置。
第三种方法是将第二个形参定义为表示数组的大小,这种用法在 C 程序和标准化之前的 C++ 程序中十分普遍。// const int ia[] is equivalent to const int* ia // size is passed explicitly and used to control access to elements of ia void printValues(const int ia[], size_t size) { for (size_t i = 0; i != size; ++i) { cout << ia[i] << endl; } }