参数传递:火车式 vs. 卡车式






3.09/5 (11投票s)
本文介绍了使用复合数据类型向函数传递参数的优点。
引言
本文描述了用于向函数传递参数的形式或格式的优点和缺点。本文介绍的有用技术可用于 C、C++ 等语言,以及任何支持指针和复合数据类型(如 struct)的语言。
向函数传递参数的方式
从概念上讲,参数可以通过两种方式传递给函数,包括这两种方式的组合。
- 按状态:这种参数传递方式规定了被调用函数对参数所做的修改是否对函数的调用者可用。因此,这种参数传递方式**关注函数调用之前和之后参数的状态/值。
- 按值传递:被调用函数对参数所做的修改**无法**被函数的调用者看到。
- 按引用传递:被调用函数对参数所做的修改**可以**被函数的调用者看到。
- 还有其他技术,如按名称传递等,其描述可以在互联网上轻松找到。
- 按格式传递:这种参数传递方式,即按格式传递,并不是什么新鲜事。事实上,它在日常开发中就已经被使用。这种参数传递方式**规定了参数是单独(水平)传递给函数,还是集体(垂直)传递给函数**。
- 火车式传递:单独(水平)传递参数或**水平格式传递**可以想象成参数是以**火车的形式**传递的,因为火车以水平格式一节一节地载着它的货物(车厢/转向架)。
- 卡车式传递:集体(垂直)传递参数或**垂直格式传递**可以想象成参数是以**卡车的形式**传递的,因为卡车将货物一上一下地装载,以垂直格式。
在此之下,有不同的实际技术/方法可以向函数传递参数。
//------ called function ------//
int func(int P1, char P2)
{
P1 = 20; //...tried to modify
P2 = 'b'; //...tried to modify
return 1;
}
//------ called function ------//
//-------- caller code --------//
//original data
int Data_P1 = 10;
char Data_P2 = 'a';
func(Data_P1, Data_P2);
//Data_P1 = 10 ...not modified, since passed by Value
cout << "Data_P1 = " << Data_P1 << endl;
//Data_P2 = a ...not modified, since passed by Value
cout << "Data_P2 = " << Data_P2 << endl;
//-------- caller code --------//
//------ called function ------//
int func(int &P1, char *P2)
{
P1 = 20; //...tried to modify
*P2 = 'b'; //...tried to modify
return 1;
}
//------ called function ------//
//-------- caller code --------//
//original data
int Data_P1 = 10;
char Data_P2 = 'a';
func(Data_P1, &Data_P2);
//Data_P1 = 20 ...modified, since passed by Reference
cout << "Data_P1 = " << Data_P1 << endl;
//Data_P2 = b ...modified, since passed by Reference
cout << "Data_P2 = " << Data_P2 << endl;
//-------- caller code --------//
因此,在当前的讨论中,“格式”一词可以指水平或垂直。
在此之下,有不同的实际技术/方法可以向函数传递参数。
让我们详细了解这种“按格式”参数传递方法及其优缺点。
参数传递“火车式”
int func(int P1, float P2, abc P3, char P4, xyz P5);
从图中可以看出,有许多参数通过函数 func
一个接一个地传递,以**火车**的形式水平传递,因此得名**“火车式参数传递”**。
缺点
- 当函数参数列表中的参数被添加、删除或修改时,函数签名会被修改。
- 如果参数
P1
的数据类型从int
修改为float
,则函数签名会被修改。 - 如果参数
P2
被删除,则函数签名会被修改。 - 如果新参数
P6
被添加到参数列表中,则函数签名会被修改。
int func(/*int*/ float P1, float P2, abc P3, char P4, xyz P5);
//...signature is modified
int func(int P1, /*float P2,*/ abc P3, char P4, xyz P5);
//...signature is modified
int func(int P1, float P2, abc P3, char P4, xyz P5, pqr P6);
//...signature is modified
优点
- 可以轻松地将某些参数按值传递,某些参数按引用传递。
//...passing P1 by Value
//...passing P2 by Reference
//...passing P3 by Value
//...passing P4 by Reference
//...passing P5 by Value
int func(int P1, float &P2, abc P3, char *P4, xyz P5);
参数传递“卡车式”
int func(PARAMETERTRUCK stParam);
//where PARAMETERTRUCK is defined as :
struct PARAMETERTRUCK
{
int P1;
float P2;
abc P3;
char P4;
xyz P5;
};
从图中可以看出,只有一个(复合/容器式)参数被传递给函数 func
。此传递的参数可以看作是一辆**卡车**,它装载了所有参数,因此得名**“卡车式参数传递”**。
优点
- 即使函数参数列表中的参数被添加、删除或修改,函数签名也不会被修改。
- 参数
P1
的数据类型从int
修改为float
,但函数签名不会被修改。 - 参数
P2
被删除,但函数签名不会被修改。 - 新参数
P6
被添加到参数列表中,但函数签名不会被修改。
struct PARAMETERTRUCK
{
/*int*/ float P1;
float P2;
abc P3;
char P4;
xyz P5;
};
int func(PARAMETERTRUCK stParam); //...signature not modified
struct PARAMETERTRUCK
{
int P1;
/*float P2;*/
abc P3;
char P4;
xyz P5;
};
int func(PARAMETERTRUCK stParam); //...signature not modified
struct PARAMETERTRUCK
{
int P1;
float P2;
abc P3;
char P4;
xyz P5;
pqr P6;
};
int func(PARAMETERTRUCK stParam); //...signature not modified
缺点
- 所有参数都可以按值或按引用传递。
int func(PARAMETERTRUCK stParam); //by Value
int func(PARAMETERTRUCK &stParam); //by Reference
int func(PARAMETERTRUCK *stParam); //by Reference
我们可以克服这个缺点,并允许混合传递一些参数按值传递,一些参数按引用传递。
我们来看看如何做到。
//------ called function ------//
//Define the Parameter Truck as follows :
struct PARAMETERTRUCK
{
int P1; //...parameter that needs to be passed by value
char *P2; //...use pointer for parameter
// that needs to be passed by Reference
};
//... always passing Parameter Truck "by Value".
//... still preventing the function signature from getting modified.
int func(PARAMETERTRUCK stParam)
{
stParam.P1 = 20; //...tried to modify
*stParam.P2 = 'b'; //...tried to modify
return 1;
}
//------ called function ------//
//-------- caller code --------//
//original data
int Data_P1 = 10;
char Data_P2 = 'a';
PARAMETERTRUCK stData;
stData.P1 = Data_P1; //...pass by Value
stData.P2 = &(Data_P2); //...pass by Reference
func(stData);
//continue using original data some of which
//are modified in function func as per requirement
//Data_P1 = 10 ...not modified, since passed by Value
cout << "Data_P1 = " << Data_P1 << endl;
//Data_P2 = b ...modified, since passed by Reference
cout << "Data_P2 = " << Data_P2 << endl;
//-------- caller code --------//
结论
通过使用**卡车式**参数传递技术,我们可以防止函数签名在修改其参数列表时被修改。同时,我们可以通过使用指针来传递某些参数按值传递,某些参数按引用传递。
因此,尽量少使用水平参数列表,并使用 struct
传递参数,即“卡车”。
有一个强有力的理由支持以上说法。因为,良好的编程实践(单一职责原则)表明,一个实体(在我们的例子中是“函数”)应该只负责执行一项任务/内聚的功能,一个函数接收到的所有参数都应该参与或贡献于函数试图提供的目标/功能。因此,最好将所有参数放在一个名称/目标下进行传递。
历史
- 版本 1.0:文章初次上传。