C++ Primer 函数传参

函数传参问题,先区分一下实参和形参

形参(形式参数):在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参。

实参(实际参数):函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参。

形参和实参的功能是传递数据,发生函数调用时,实参的值会传递给形参。详细可参考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;
}
上一篇:"C++ Primer 读书笔记" 第二章 变量与基本类型


下一篇:【C++Primer Chapter9】 内存模型和名称空间(2)