函数传参问题,先区分一下实参和形参
形参(形式参数):在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参。
实参(实际参数):函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参。
形参和实参的功能是传递数据,发生函数调用时,实参的值会传递给形参。详细可参考http://c.biancheng.net/view/1853.html,本篇记录函数调用时,如何书写实参和形参,参考于C++ Primer 第6章第2节
总述:
- 如果形参是引用类型,它将绑定到对应的实参上; 否则,将实参的值拷贝后赋给形参
- 引用传递或引用调用:形参是引用类型,引用形参是它对应的实参的别名
- 值传递或传值调用:当实参的值被拷贝给形参时,形参和实参是两个相互独立的对象
- 传参时,类型要匹配
- 传过来得都是一个具体的对象,根据形参定义形式,从而决定是对对象进行拷贝还是引用
- 引用本身是别名,不是对象
- 函数参数的传递,是遵循赋值操作规则的,可以赋值,就可以传递
传值调用:将初始值拷贝给变量
-
形参为基本变量
-
// 函数声明(这几种声明方式等价,以下类似) void fun(int n); void fun(int); // 在定义的时候,一般不会省略参数名,声明时可以 // 调用 int a = 18; fun(a);
-
形参为指针变量
-
// 函数声明 void fun(int *n); void fun(int *); // 调用 int a = 18; int *p = &a; fun(&a); fun(p);
注:C++最佳实践,使用引用类型的形参代替指针
引用调用:对于引用的操作实际上是在被引用对象上进行的
-
基本数据类型
-
// 函数声明 void fun(int &n); void fun(int &); // 调用 int a = 18; fun(a);
-
引用的好处就是,就是避免拷贝,此外,此外如果函数不会修改引用形参的值,就机将其声明为常量引用,方便阅读(最佳实践),关于引用,可参考:https://blog.csdn.net/y_dd6011/article/details/117261531
-
// 函数声明 void fun1(int &n); void fun2(const int &n); // 调用 int a = 18; fun1(a); // 可能会a的值 fun2(a); // 一定不会修改a的值
-
根据引用的特性,可以返回额外的值
-
// 函数定义 void fun1(int &n){ n = 18; } // 调用 int a; // a没有赋值 fun(a); // 通过函数调用,给a赋值,之后a的值就为18
指针或引用形参与const:关于函数传值,一定绕不开const(简单理解,用const修饰的变量,他的值是不能被改变的)
-
形参中有const:实参初始化形参时会忽略顶层const,即形参的顶层const是被忽略掉的,关于顶层const和底层const,可参考:https://blog.csdn.net/y_dd6011/article/details/117261556
-
// 这两个函数时同名函数,后声明的那个会报错:重复定义了fun(int) void fun(int n); void fun(const int n); // 忽略了顶层const,这么写的好处就是在函数体内,形参数值不会被修改
-
实参中有const:先甄别形参需要什么类型数据,再根据实参的定义方式,再确定最终写法
-
// 函数声明 void fun1(int &n); // 接收int对象 void func1(const int &n); // 接收int对象(也可以是const int类型) void fun2(int *n); // 接收int*对象 void func2(const int *n); // 接收int*对象 // 函数调用:对4个函数,分别传i, &i, ci, &ci 排列组合共16种,以下调用方式是正确的 int i = 10; const int ci = 10; fun1(i); func1(i); func1(ci); fun2(&i); func2(&i); func2(&ci);
传递数组:数组有两个特殊性质,一是不允许数组拷贝,二是使用数组时会将其转化成指针。所以在函数传递的时候,无法进行值传递,实际上是传递指向数组的首元素指针
-
// 传递数组: 除函数名外,其他等价 void pri1(const int*); void pri2(const int[]); void pri3(const int[10]); // 这个10是期望值,和传进来的没关系 // 调用:只要传过去的是一个int类型的指针,都可以 int p = 1, q[] = {1, 0}; pri1(&p); pri2(q) ; pri3(q);
-
数组传过去了,通常还要告知数组的长度,防止越界,以下有三种方式:
-
// 1.在数组末尾加一个标志符,如C语言字符的'/0' // 2.显示指定,传过去一个长度:size_t size // 3.使用C++迭代器库,传递首尾指针(最佳实践): #include <iostream> //#include <iterator> using namespace std; void print(const int *beg, const int *end) { while(beg != end) { cout << *beg++ << endl; } } int main() { int arr[] = {1, 4, 5, 6, 8}; print(begin(arr), end(arr)); }
-
数组形参和const:当数组元素不需要改变时,数组形参应该指向const的指针
-
f(int &arr[10]); // 错误:arr被声明成引用的数组,相当于是 int& arr[10] f(int (&arr)[10]); // arr是具有十个整数的int数组的引用 int k[10]; f(k);
-
传递多为数组:C++没有真正的多维数组,有的只是数组的数组,和普通数组一样,传递的是首元素的指针,首元素也可能是个多维数组,指针指向的就是数组的指针
-
void f(int (*arr)[10]); // 两种方式等价 void f(int arr[][10]);
数量可变形参:
-
类型相同,可以用initializer_list标准库:
-
#include <initializer_list> void print(int id, initializer_list<string> il) { cout << id << ": "; for(const auto &elem : il) cout << elem << " "; cout << endl; } // 可变参数 print(1, {"int"}); print(2, {"int", "char"});
-
类型不同:可以使用可变形参模板来实现,遇见细查
-
省略符(常用于访问特殊的C代码),不常用,放在参数列表的最后一个位置,遇见细查
-
void f(int a, ...); void f(int a...); // 与上面等价,逗号可以省略 void f(...);
小结:
- 函数传参的过程,本质上可以看做是赋值操作,实参给形参赋值,包含类型是否匹配,是否可以转化等
- (个人理解)只有引用是将形参绑定到实参上,其他的都是拷贝操作,比如数组是传递首元素地址,即指针,传递指针是将指针拷贝给形参,但是他们指向的东西没有变,也就达到我们的目的(防止大量数据拷贝,只传个地址)
一些测试源码:
#include <iostream>
#include <initializer_list>
#include <string>
using namespace std;
// 对int类型对象进行引用,只会得到这个对象的别名,并不会产生int&的对象(引用并不是对象,只是对象的别名)
void fun1(int &n); // 接收int对象
void func1(const int &n); // 接收int对象(也可以是const int类型)
void fun2(int *n); // 接收int*对象
void func2(const int *n); // 接收int*对象
// 传递数组: 除函数名外,其他等价
void pri1(const int*);
void pri2(const int[]);
void pri3(const int[10]); // 这个10是期望值,和传进来的没关系
// 可变形参
void print(int id, initializer_list<string> il);
int main()
{
// 指针或引用形参与const
// ---------------------
int i = 10;
const int ci = 10;
// fun1(int &n) 接收一个int
fun1(i); // 正确:传递一个int过去,然后对这个int取别名得到int&
fun1(&i); // 错误:&i是i的地址,类型为int*,和int&不匹配或者不能转化成int&(以下同理)
fun1(ci); // 错误:ci类型为const int与int&不匹配
fun1(&ci); // 错误:&ci引用的对象是ci,ci为const int与int&不匹配
// func1(const int &n) 接收一个int 或 const int
func1(i); // 正确:传递一个int,int可以赋值给const int
func1(&i); // 错误:&i是i的地址,类型为int*,和int不匹配
func1(ci); // 正确:传递一个const int 过去,实参可以给形参赋值
func1(&ci); // [Error] invalid conversion from 'const int*' to 'int' [-fpermissive]
// fun2(int *n) 接收一个int*
fun2(i); // [Error] invalid conversion from 'int' to 'int*' [-fpermissive]
fun2(&i); // 正确
fun2(ci); // [Error] invalid conversion from 'int' to 'int*' [-fpermissive]
fun2(&ci); // [Error] invalid conversion from 'const int*' to 'int*' [-fpermissive]
// func2(int *n) 接收一个int*
func2(i); // [Error] invalid conversion from 'int' to 'int*' [-fpermissive]
func2(&i); // 正确
func2(ci); // [Error] invalid conversion from 'int' to 'int*' [-fpermissive]
func2(&ci); // 正确
cout << i << endl;
cout << ci << endl;
// 传递数组
int p = 1, q[] = {1, 0};
pri1(&p);
pri2(q);
pri3(q);
// 可变参数
print(1, {"int"});
print(2, {"int", "char"});
}
void fun1(int &n)
{
n = 18;
}
void func1(const int &n)
{
int t = n;
}
void fun2(int *n)
{
*n = 18;
}
void func2(const int *n)
{
int t = *n;
}
// 传递数组,函数定义
void pri1(const int*){}
void pri2(const int[]){}
void pri3(const int[10]){}
// 可变形参
void print(int id, initializer_list<string> il)
{
cout << id << ": ";
for(const auto &elem : il)
cout << elem << " ";
cout << endl;
}