C++ 函数实参传递 (argument passing)

argument [ˈɑːɡjumənt]:n. 实参
parameter [pəˈræmɪtə(r)]:n. 形参

每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化。形参初始化的机理与变量初始化一样。形参的类型决定了形参和实参交互的方式。如果形参是引用类型,它将绑定到对应的实参上;否则,将实参的值拷贝后赋给形参。

当形参是引用类型时,它对应的实参被引用传递 (passed by reference) 或者函数被传引用调用 (called by reference)。和其它引用一样,引用形参也是它绑定的对象的别名,引用形参是它对应的实参的别名。

当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象。这样的实参被值传递 (passed by value) 或者函数被传值调用 (called by value)。

1. 传值参数 (passing arguments by value)

当初始化一个非引用类型的变量时,初始值被拷贝给变量。此时,对变量的改动不会影响初始值。

int n = 0;
int i = n;  // i is a copy of the value in n
i = 42;     // value in i is changed, n is unchanged

传值参数的机理完全一样,函数对形参做的所有操作都不会影响实参。

1.1. 指针形参

指针的行为和其它非引用类型一样。当执行指针拷贝操作时,拷贝的是指针的值。拷贝之后,两个指针是不同的指针。因为指针使我们可以间接地访问它所指的对象,所以通过指针可以修改它所指对象的值。

int n = 0, i = 42;
int *p = &n, *q = &i;  // p points to n, q points to i
*p = 42;               // value in n is changed, p is unchanged
p = q;                 // p now points to i, values in i and n are unchanged

指针形参的行为与之类似:

// function that takes a pointer and sets the pointed-to value to zero
void reset(int *ip) {*ip = 0;  // changes the value of the object to which ip pointsip = 0;   // changes only the local copy of ip, the argument is unchanged (只改变了 ip 的局部拷贝,实参未被改变)
}

调用 reset 函数之后,实参所指的对象被置为 0,但是实参本身并没有改变:

#include <iostream>// function that takes a pointer and sets the pointed-to value to zero
void reset(int *ip) {*ip = 0;  // changes the value of the object to which ip points (改变指针 ip 所指对象的值)ip = 0;   // changes only the local copy of ip, the argument is unchanged (只改变了 ip 的局部拷贝,实参未被改变)
}int main() {int i = 42;reset(&i);  // changes i but not the address of istd::cout << "i = " << i << std::endl;  // prints i = 0return 0;
}
i = 0
请按任意键继续. . .

在 C++ 语言中,建议使用引用类型的形参替代指针。

2. 传引用参数 (passing arguments by reference)

对于引用的操作实际上是作用在引用所引的对象上:

int n = 0, i = 42;
int &r = n;  // r is bound to n (i.e. r is another name for n)
r = 42;      // n is now 42
r = i;       // n now has the same value as i
i = r;       // i has the same value as n

引用形参的行为与之类似。通过使用引用形参,允许函数改变一个或多个实参的值。

和其它引用一样,引用形参绑定初始化它的对象。当调用这一版本的 reset 函数时,val 绑定我们传给函数的实参对象,此时改变 val 也就是改变 val 所引对象的值。调用这一版本的 reset 函数时,我们直接传入对象而无须传递对象的地址:

#include <iostream>// function that takes a reference to an int and sets the given object to zero
void reset(int &val) {// val is just another name for the object passed to resetval = 0;  // changes the value of the object to which val refers
}int main() {int num = 42;reset(num);  // num is passed by reference, the value in num is changedstd::cout << "num = " << num << std::endl;  // prints num = 0return 0;
}
num = 0
请按任意键继续. . .

在上述调用过程中,形参 val 仅仅是 num 的又一个名字。在 reset 内部对 val 的使用即是对 num 的使用。

2.1. 使用引用避免拷贝

拷贝大的类类型对象或者容器对象比较低效,甚至有的类类型 (包括 IO 类型在内) 根本就不支持拷贝操作。当某种类型不支持拷贝操作时,函数只能通过引用形参访问该类型的对象。

一个函数比较两个 std::string 对象的长度。因为 std::string 对象可能会非常长,所以应该尽量避免直接拷贝它们,这时使用引用形参是比较明智的选择。如果函数无须改变引用形参的值,最好将其声明为常量引用。

// compare the length of two strings
bool isShorter(const std::string &s1, const std::string &s2) {return s1.size() < s2.size();
}

2.2. 使用引用形参返回额外信息

一个函数只能返回一个值,然而有时函数需要同时返回多个值,引用形参为我们一次返回多个结果提供了有效的途径。

3. const 形参和实参 (const parameters and arguments)

顶层 (top-level) const 作用于对象本身。

const int ci = 42;   // we can not change ci, const is top-level
int i = ci;          // 正确: when we copy ci, its top-level const is ignored
int * const p = &i;  // const is top-level, we can not assign to p (const 是顶层的,不能给 p 赋值)
*p = 0;              // 正确: changes through p are allowed, i is now 0

当用实参初始化形参时会忽略顶层 const,形参的顶层 const 被忽略了。当形参有顶层 const 时,传给它常量对象或者非常量对象都是可以的。调用 fun 函数时,既可以传入 const int 也可以传入 int。

void fun(const int i) { /* fun can read but not write to i */ }

忽略掉形参的顶层 const 可能产生意想不到的结果:

void fun(const int i) { /* fun can read but not write to i */ }
void fun(int i) { /* . . . */ }  // error: redefines fun(int)

在 C++ 语言中,允许我们定义若干具有相同名字的函数,不过前提是不同函数的形参列表应该有明显的区别。因为形参的顶层 const 被忽略掉了,所以在上面的代码中传入两个 fun 函数的参数可以完全一样。因此第二个 fun 是错误的,尽管形式上有差异,但实际上它的形参和第一个 fun 的形参没什么不同。

3.1. const + 指针或引用形参

形参的初始化方式和变量的初始化方式是一样的,可以使用非常量初始化一个底层 const 对象,但是反过来不行;同时一个普通的引用必须用同类型的对象初始化。

int i = 42;
const int *cp = &i;  // 正确: but cp can't change i
const int &r = i;    // 正确: but r can't change i
const int &r2 = 42;  // 正确: C++ 允许我们用字面值初始化常量引用。
int *p = cp;         // 错误: types of p and cp don't match
int &r3 = r;         // 错误: types of r3 and r don't match
int &r4 = 42;        // 错误: can't initialize a plain reference from a literal (不能用字面值初始化一个非常量引用)

要想调用引用版本的 reset 函数,只能使用 int 类型的对象,而不能使用字面值、求值结果为 int 的表达式、需要转换的对象或者 const int 类型的对象。类似的,要想调用指针版本的 reset 函数只能使用 int*

// function that takes a reference to an int and sets the given object to zero
void reset(int &val) {// val is just another name for the object passed to resetval = 0;  // changes the value of the object to which val refers
}// function that takes a pointer and sets the pointed-to value to zero
void reset(int *ip) {*ip = 0;  // changes the value of the object to which ip pointsip = 0;   // changes only the local copy of ip, the argument is unchanged (只改变了 ip 的局部拷贝,实参未被改变)
}
int i = 0;
const int ci = i;
std::string::size_type ctr = 0;
reset(&i);   // calls the version of reset that has an int* parameter
reset(&ci);  // 错误: can't initialize an int* from a pointer to a const int object (不能用指向 const int 对象的指针初始化 int*)
reset(i);    // calls the version of reset that has an int& parameter
reset(ci);   // 错误: can't bind a plain reference to the const object ci (不能把普通引用绑定到 const 对象 ci 上)
reset(42);   // 错误: can't bind a plain reference to a literal (不能把普通应用绑定到字面值上)
reset(ctr);  // 错误: types don't match, ctr has an unsigned type (类型不匹配,ctr 是无符号类型)

3.2. 尽量使用常量引用

把函数不会改变的形参定义成 (普通的) 引用是一种比较常见的错误,这么做会给函数的调用者一种误导,即函数可以修改它的实参的值。此外,使用引用而非常量引用也会极大地限制函数所能接受的实参类型。不能把 const 对象、字面值或者需要类型转换的对象传递给普通的引用形参。(We can not pass a const object, or a literal, or an object that requires conversion to a plain reference parameter.)

#include <iostream>// returns the index of the first occurrence of c in str (返回 str 中 c 第一次出现的位置索引)
// the reference parameter num counts how often c occurs (引用形参 num 负责统计 c 出现的总次数)
std::string::size_type find_char(const std::string &str, const char &c, std::string::size_type &num) {auto ret = str.size();  // position of the first occurrence, if any (如果有的话,第一次出现的位置)num = 0;                // set the occurrence count parameterfor (decltype(ret) i = 0; i != str.size(); ++i) {if (str[i] == c) {if (ret == str.size()) {ret = i;    // remember the first occurrence of c (记录 c 第一次出现的位置)}++num;          // increment the occurrence count (出现的次数加 1)}}// count is returned implicitly in num (出现的次数通过 num 隐式地返回)return ret;
}int main() {std::string str("yongqiang");size_t num = 0;auto index = find_char(str, 'g', num);std::cout << index << " -> " << num << std::endl;return 0;
}
3 -> 2
请按任意键继续. . .

find_char 函数正确地将它的 std::string 类型的形参定义成常量引用 (const std::string &)。假如我们把它定义成普通的 std::string &

std::string::size_type find_char(std::string &str, const char &c, std::string::size_type &num);

则只能将 find_char 函数作用于 std::string 对象,类似下面这样的调用

const std::string str("yongqiang");
size_t num = 0;
auto index = find_char(str, 'g', num);

将在编译时发生错误。

1>d:\vulkan_workspace\sample\sample\sample.cpp(24): error C2664: 'unsigned int find_char(std::string &,const char &,unsigned int &)': cannot convert argument 1 from 'const std::string' to 'std::string &'

假如其它函数正确地将它们的形参定义成常量引用,那么第二个版本的 find_char 无法在此类函数中正常使用。

bool is_sentence(const std::string &str) {// if there is a single period at the end of str, then str is a sentence (如果在 str 的末尾有且只有一个句号,则 str 是一个句子)std::string::size_type num = 0;return ((find_char(str, '.', num) == str.size() - 1) && (num == 1));
}

如果 find_char 的第一个形参类型是 std::string &,那么上面这条调用 find_char 的语句将在编译时发生错误。原因在于 str 是常量,但 find_char 被不正确地定义成只能接受普通引用。

解决该问题的一种思路是修改 is_sentence 的形参类型,但是这么做只不过转移了错误而已,结果是 is_sentence 函数的调用者只能接受非常量 std::string 对象了。正确的修改思路是改正 find_char 函数的形参。如果实在不能修改 find_char,就在 is_sentence 内部定义一个 std::string 类型的变量,令其为 str 的副本,然后把这个 std::string 对象传递给 find_char。

4. 数组形参 (array parameters)

数组的两个特殊性质对我们定义和使用作用在数组上的函数有影响,这两个性质分别是:不允许拷贝数组,使用数组时通常会将其转换成指针。因为不能拷贝数组,所以我们无法以值传递的方式使用数组参数。因为数组会被转换成指针,所以当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。

尽管不能以值传递的方式传递数组,但是我们可以把形参写成类似数组的形式:

// each function has a single parameter of type const int* (每个函数都有一个 const int* 类型的形参)
void print(const int*);
void print(const int[]);    // shows the intent that the function takes an array (函数的意图是作用于一个数组)
void print(const int[10]);  // dimension for documentation purposes at best (这里的维度表示我们期望数组含有多少元素,实际不一定)

尽管表现形式不同,但上面的三个函数是等价的:每个函数的唯一形参都是 const int* 类型的。当编译器处理对 print 函数的调用时,只检査传入的参数是否是 const int* 类型:

int i = 0, j[2] = { 0, 1 };
print(&i);  // 正确: &i is int*
print(j);   // 正确: j is converted to an int* that points to j[0]

如果我们传给 print 函数的是一个数组,则实参自动地转换成指向数组首元素的指针,数组的大小对函数的调用没有影响。和其它使用数组的代码一样,以数组作为形参的函数也必须确保使用数组时不会越界。因为数组是以指针的形式传递给函数的,所以一开始函数并不知道数组的确切尺寸,调用者应该为此提供一些额外的信息。

4.1. 使用标记指定数组长度

管理数组实参的第一种方法是要求数组本身包含一个结束标记,使用这种方法的典型示例是 C 风格字符串。C 风格字符串存储在字符数组中,并且在最后一个字符后面跟着一个空字符。

函数在处理 C 风格字符串时遇到空字符停止:

// prints a null-terminated array of characters
void print(const char *cp) {if (cp) {// if cp is not a null pointer (若 cp 不是一个空指针)while (*cp) {// so long as the character it points to is not a null character (只要指针所指的字符不是空字符)std::cout << *cp++;  // print the character and advance the pointer (输出当前字符并将指针向前移动一个位置)}}
}

这种方法适用于那些有明显结束标记且该标记不会与普通数据混淆的情况,但是对于像 int 这样所有取值都是合法值的数据就不太有效了。

4.2. 使用标准库规范

管理数组实参的第二种技术是传递指向数组首元素和尾后元素的指针,这种方法受到了标准库技术的启发。

// print ints in the given range
void print(const int *beg, const int *end) {// print every element starting at beg up to but not including end (输出 beg 到 end 之间,不含 end 的所有元素)while (beg != end) {std::cout << *beg++ << " ";  // print the current element and advance the pointer (输出当前元素并将指针向前移动一个位置)}
}

while 循环使用解引用运算符和后置递减运算符输出当前元素并在数组内将 beg 向前移动一个元素,当 beg 和 end 相等时结束循环。

为了调用这个函数,我们需要传入两个指针:一个指向要输出的首元素,另一个指向尾元素的下一位置。

#include <iostream>// prints a null-terminated array of characters
void print(const char *cp) {if (cp) {// if cp is not a null pointer (若 cp 不是一个空指针)while (*cp) {// so long as the character it points to is not a null character (只要指针所指的字符不是空字符)std::cout << *cp++;  // print the character and advance the pointer (输出当前字符并将指针向前移动一个位置)}}
}// print ints in the given range
void print(const int *beg, const int *end) {// print every element starting at beg up to but not including end (输出 beg 到 end 之间,不含 end 的所有元素)while (beg != end) {std::cout << *beg++ << " ";  // print the current element and advance the pointer (输出当前元素并将指针向前移动一个位置)}
}int main() {print("yongqiang!"); // calls first version of printstd::cout << std::endl;// j is converted to a pointer to the first element in j (j 转换成指向它首元素的指针)// the second argument is a pointer to one past the end of j (第二个实参是指向 j 的尾后元素的指针)int j[2] = { 0, 1 };print(std::begin(j), std::end(j));  // library begin and end functionsstd::cout << std::endl;// equivalent call, directly calculate the begin and end pointersprint(j, j + 2);std::cout << std::endl;return 0;
}
yongqiang!
0 1
0 1
请按任意键继续. . .

只要调用者能正确地计算指针所指的位置,那么上述代码就是安全的。在这里,我们使用标准库 begin 和 end 函数提供所需的指针。

4.3. 显式传递一个表示数组大小的形参

第三种管理数组实参的方法是专门定义一个表示数组大小的形参。

#include <iostream>// "const int ia[]" is equivalent to "const int* ia"
// size is passed explicitly and used to control access to elements of ia
void print(const int ia[], size_t size) {for (size_t i = 0; i != size; ++i) {std::cout << ia[i] << std::endl;}
}int main() {int j[] = { 0, 1 };  // int array of size 2print(j, std::end(j) - std::begin(j));return 0;
}
0
1
请按任意键继续. . .

这个版本的程序通过形参 size 的值确定要输出多少个元素,调用 print 函数时必须传入这个表示数组大小的值。只要传递给函数的 size 值不超过数组实际的大小,函数就是安全的。

4.4. 数组形参和 const

当函数不需要对数组元素执行写操作的时候,数组形参应该是指向 const 的指针。只有当函数确实要改变元素值的时候,才把形参定义成指向非常量的指针。

4.5. 数组引用形参 (array reference parameters)

C++ 语言允许将变量定义成数组的引用,基于同样的道理,形参也可以是数组的引用。此时,引用形参绑定到对应的实参上,也就是绑定到数组上。

// 正确: parameter is a reference to an array, the dimension is part of the type (形参是数组的引用,维度是类型的一部分)
void print(int (&arr)[10]) {for (auto elem : arr) {std::cout << elem << std::endl;}
}

&arr 两端的括号必不可少:

fun(int &arr[10])   // 错误: declares arr as an array of references
fun(int(&arr)[10])  // 正确: arr is a reference to an array of ten ints (arr 是具有 10 个整数的整型数组的引用)

因为数组的大小是构成数组类型的一部分,所以只要不超过维度,在函数体内就可以放心地使用数组。但是,这一用法也无形中限制了 print 函数的可用性,我们只能将函数作用于大小为 10 的数组。

int i = 0, j[2] = { 0, 1 };
int k[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
print(&i);  // 错误: argument is not an array of ten ints (实参不是含有 10 个整数的数组)
print(j);   // 错误: argument is not an array of ten ints (实参不是含有 10 个整数的数组)
print(k);   // 正确: argument is an array of ten ints (实参是含有 10 个整数的数组)

4.6. 传递多维数组

在 C++ 语言中实际上没有真正的多维数组,所谓多维数组其实是数组的数组。

和所有数组一样,当将多维数组传递给函数时,真正传递的是指向数组首元素的指针。因为我们处理的是数组的数组,所以首元素本身就是一个数组,指针就是一个指向数组的指针。数组第二维 (以及后面所有维度) 的大小都是数组类型的 一部分,不能省略。

// matrix points to the first element in an array whose elements are arrays of ten ints (matrix 指向数组的首元素,该数组的元素是由 10 个整数构成的数组)
void print(int (*matrix)[10], int rowSize) { /* . . . */ }

上述语句将 matrix 声明成指向含有 10 个整数的数组的指针。

再一次强调,*matrix 两端的括号必不可少:

int *matrix[10];    // array of ten pointers (10 个指针构成的数组)
int (*matrix)[10];  // pointer to an array of ten ints (指向含有 10 个整数的数组的指针)

我们也可以使用数组的语法定义函数,此时编译器会一如既往地忽略掉第一个维度,所以最好不要把它包括在形参列表内。

// equivalent definition (等价定义)
void print(int matrix[][10], int rowSize) { /* . . . */ }

matrix 的声明看起来是一个二维数组,实际上形参是指向含有 10 个整数的数组的指针。

下标引用 ([]) 优先级高于间接访问 (*)。

References

https://yongqiang.blog.csdn.net/
(美) Stanley B. Lippman, (美) Josée Lajoie, (美) Barbara E. Moo 著, 王刚, 杨巨峰 译. C++ Primer 中文版[M]. 第 5 版. 电子工业出版社, 2013.
https://www.informit.com/store/c-plus-plus-primer-9780321714114

C++ 函数实参传递 (argument passing)相关推荐

  1. 《C++ Primer 第五版》(第6.1~6.3节) 函数形参和实参传递,可变参数列表和函数返回值

    1.函数形参和实参传递问题 函数参数传递有两种:值传递(变量,指针),引用传递(使用别名). 在形参和实参的传递过程中,牵涉到大的类类型对象.容器类型对象或者不支持拷贝操作的对象时,不适合采用值传递, ...

  2. python——函数(定义函数、传递实参、返回值、传递列表、传递任意数量的实参、将函数存储在模块中、函数编写指南)

    目录 定义函数 传递实参 返回值 传递列表 传递任意数量的实参 将函数存储在模块中 函数编写指南 定义函数 #------定义函数---------- def user():print('hello! ...

  3. 【Groovy】Groovy 动态语言特性 ( Groovy 中函数实参自动类型推断 | 函数动态参数注意事项 )

    文章目录 前言 一.Groovy 中函数实参自动类型推断 二.函数动态参数注意事项 三.完整代码示例 前言 Groovy 是动态语言 , Java 是静态语言 ; 本篇博客讨论 Groovy 中 , ...

  4. 在setInterval函数中传递参数

    本文翻译自:Pass parameters in setInterval function Please advise how to pass parameters into a function c ...

  5. 函数形参传递概念及问题分析

    普通函数参数 下面程序试图改变main函数中a和b的值 #include<stdio.h> void fun(int x,int y) { int c; c=a; a=b; b=c; } ...

  6. 函数运行 形参实参变化 内存空间_可能python创始人都不知道的,python函数实参形参讲解...

    今天我们来学习一下python函数的用法 函数是带名字的代码块,我们可以直接调用函数,无需反复板鞋完成该函数的代码 1.下面我们来看一个简单的函数 首先定义一个函数,def是定义的意思,后面是函数名 ...

  7. php中的函数调简单 传入参数即可,php函数与传递参数的简单示例

    这篇文章主要为大家详细介绍了php函数与传递参数的简单示例,具有一定的参考价值,可以用来参考一下. 感兴趣的小伙伴,下面一起跟随512笔记的小玲来看看吧! 本文实例讲述了函数的调用与函数定义语法,并讲 ...

  8. linux中probe函数中传递的参数来源(上)

    点击打开链接 上一篇中,我们追踪了probe函数在何时调用,知道了满足什么条件会调用probe函数,但probe函数中传递的参数我们并不知道在何时定义,到底是谁定义的,反正不是我们在驱动中定义的(当然 ...

  9. 需要在函数中传递的变量

    要在函数中传递的变量, 大致可以分为,单个数值的,一维数值的,或者二维数值的, 1)单个数值,  形如:float m_fElement;  (1) 在函数中定义float *m_fElement,调 ...

最新文章

  1. 【翻译】VisualStudio11中的CSS编辑器改进(asp.net 4.5系列)-ScottGu
  2. Linux和unix中 awk 的print浅谈
  3. 有趣的MS Live Labs
  4. 【Breadth-first Search 】279. Perfect Squares
  5. python中求包含5的数_Python 内置函数 ( ) 可以返回列表、元组、字典、集合、字符串以及 range 对象中元素个数。_学小易找答案...
  6. java布尔方法_Java布尔方法返回
  7. LINUX安装TensorRT及特别注意事项
  8. caffe的finetuning是如何更新网络参数的?
  9. crackme用来测试程序设计人员的逆向工程技能的小程序。
  10. Linux编程基础期末冲刺——第3章用户组与用户组管理
  11. 《论文阅读》PV-RCNN: Point-Voxel Feature Set Abstraction for 3D Object Detection
  12. Win10系统提升开机速度方法
  13. Python+Appium自动化测试-通过坐标定位元素
  14. Android Binder机制(1):Binder架构分析
  15. docker 安装snipe-it
  16. PPT无法插入页码解决办法
  17. 阿里云个人域名备案流程
  18. FLASH脚本基础入门讲解1
  19. TYPEC无协议芯片最高可输出5V3A
  20. ERP系统-应收应付子系统-应付/应收报表

热门文章

  1. msfvenom基本介绍
  2. 孙子兵法——三十六计
  3. Xposed去除抖音Toast教程
  4. CocosCreator之微信小游戏的聊天/提示气泡制作
  5. 微信 第三方开放平台 获取小程序授权并绑定小程序到开放平台(都是坑,留下帮助后人) 一
  6. 当前计算机硬盘容量的计量单位是GB,当前计算机硬盘容量的计量单位是GB,它相当于________字节...
  7. 百度地图 路书动态加载规划
  8. 硼碳氮纳米管的制备(碳纳米管包裹磁性金属复合纳米结构/多孔氧化物掺杂的碳纳米管包裹的碳纳米球/碳氮纳米管包裹纳米金属粒子/碳纳米管包裹Ni纳米线复合材料)
  9. windows日志查看与清理
  10. 计算机学院校园文化标语,智慧校园文明宣传标语