10.4 Structures as Function Arguments
A structure variable is a scalar and can be used wherever any other scalar can be used.Thus it is legal to pass a structure as an argument to a function, but this technique is often inappropriate.
结构变量是一个标量,它可以用于其他标量可以使用的任何场合。因此,把结构作为参数传递给一个函数是合法的,但这种做法往往并不适宜。
The following code fragments are from a program written to operate an electronic cash register. Here is the declaration for a structure that contains information about an individual transaction.
下面的代码段取自一个程序,该程序用于操作电子现金收入记录机。下面是一个结构的声明,它包含单笔交易的信息。
typedef struct {
char product[PRODUCT_SIZE];
int quantity;
float unit_price;
float total_amount;
} Transaction;
When a transaction occurs, there are many steps involved, one of which is printing the receipt. Letʹs look at some different ways to perform this task.
当交易发生时,需要涉及很多步骤,其中之一就是打印收据。让我们看看怎样用几种不同的方法来完成
void
print_receipt( Transaction trans )
{
printf( "%s\n", trans.product );
printf( "%d @ %.2f total %.2f\n", trans.quantity,
trans.unit_price, trans.total_amount );
}
If current_trans is a Transaction structure, we could call the function like this:
如果current_trans 是一个Transaction 结构,我们可以像下面这样调用函数:
print_receipt( current_trans );
This approach produces the correct result, but it is inefficient because the call‐by‐value argument passing of C requires that a copy of the argument be given to the function.If PRODUCT_SIZE is 20 and we are using a machine with four‐byte integers and floats,this particular structure occupies 32 bytes. To pass it as an argument, 32 bytes must be copied onto the stack and then discarded later.
这个方法能够产生正确的结果,但它的效率很低,因为C 语言的参数传值调用方式要求把参数的一份拷贝传递给函数。如果PRODUCT_SIZE 为20 ,而且在我们使用的机器上整型和浮点型都占4 个字节,那么这个结构将占据32 个字节的空间。要想把它作为参数进行传递,我们必须把32 个字节复制到堆找中,以后再丢弃。
Compare the previous function with this one:
把前面那个函数和下面这个进行比较:
void
print_receipt( Transaction *trans )
{
printf( "%s\n", trans->product );
printf( "%d @ %.2f total %.2f\n", trans->quantity,
trans->unit_price, trans->total_amount );
}
which would be called in this manner:
这个函数可以像下面这样进行调用:
print_receipt( ¤t_trans );
Here, a pointer to the structure is passed. The pointer is smaller than the entire structure and therefore more efficient to push on the stack. The price paid for passing a pointer is that we must use indirection in the function to access the members of the structure. The bigger the structure, the more efficient it is to pass a pointer to it.
这次传递给函数的是一个指向结构的指针。指针比整个结构要小得多,所以把它压到堆栈上效率能提高很多。传递指针另外需要付出的代价是我们必须在函数中使用间接访问来访问结构的成员。结构越大,把指向它的指针传递给函数的效率就越高。
On many machines, you can improve the efficiency of the pointer version by declaring the parameter to be a register variable. On some machines, this declaration requires an extra instruction at the beginning of the function to copy the argument from the stack (where it was passed) to the register in which it will be used. But if the function performs indirection on the pointer more than two or three times, then the savings realized in the indirections will be greater than the cost of the additional instruction.
在许多机器中,你可以把参数声明为寄存器变量,从而进一步提高指针传递方案的效率。在有些机器上,这种声明在函数的起始部分还需要二条额外的指令,用于把堆栈中的参数(参数先传递给堆栈)复制到寄存器,供函数使用。但是,如果函数对这个指针的间接访问次数超过两三次,那么使用这种方法所节省的时间将远远高于一条额外指令所花费的时间。
A drawback of passing a pointer is that the function is now able to modify the values in the calling programʹs structure variable. If it is not supposed to do this you can use the const keyword in the function to prevent such modifications. Here is what the function prototype looks like with these two changes:
向函数传递指针的缺陷在于函数现在可以对调用程序的结构变量进行修改。如果我们不希望如此,可以在函数中使用const 关键字来防止这类修改。经过这两个修改之后,现在函数的原型将如下所示:
void print_receipt( register Transaction const *trans );
Letʹs move on to another step in processing a transaction; computing the total amount due. You would expect that the function compute_total_amount would modify the total_amount member of the structure. There are three ways to accomplish this task. Letʹs look at the least efficient way first. The following function
让我们前进一个步骤,对交易进行处理:计算应该支付的总额。你希望函数comput_total_amount能够修改结构的total_amount 成员。要完成这项任务有三种方法,首先让我们来看一下效率最低的那种。下面这个函数
typedef struct {
char product[PRODUCT_SIZE];
int quantity;
float unit_price;
float total_amount;
} Transaction;
Transaction
compute_total_amount( Transaction trans )
{
trans.total_amount =
trans.quantity * trans.unit_price;
return trans;
}
would be called in this manner:
可以用下面这种形式进行调用:
current_trans = compute_total_amount( current_trans );
A copy of the structure is passed as an argument and modified. Then a copy of the modified structure is returned, so the structure is copied twice.
结构的一份拷贝作为参数传递给函数并被修改。然后一份修改后的结构拷贝从函数返回,所以这个结构被复制了两次。
A slightly better method is to return only the modified value rather than the entire structure. This approach is used by the second function.
一个稍微好点的方法是只返回修改后的值,而不是整个结构。第2 个函数使用的就是这种方法。
float
compute_total_amount( Transaction trans )
{
return trans.quantity * trans.unit_price;
}
However, this function must be invoked in this manner
但是,这个函数必须以下面这种方式进行调用:
current_trans.total_amount =
compute_total_amount( current_trans );
This version is better than returning the entire structure, but the technique only works when a single value is to be computed. If we wanted the function to modify two or more members of the structure, this approach fails. Besides, there is still the overhead of passing the structure as an argument. Worse, it requires that the calling program have knowledge of the contents of the structure, specifically, the name of the total field.
这个方案比返回整个结构的那个方案强,但这个技巧只适用于计算单个值的情况。如果我们要求函数修改结构的两个或更多成员,这种方法就无能为力了。另外,它仍然存在把整个结构作为参数进行传递这个开销。更糟的是,它要求调用程序知道结构的内容,尤其是总金额字段的名字。
The third approach, passing a pointer, is better:
第3 种方法是传递一个指针,这个方案显然要好得多:
void
compute_total_amount( register Transaction *trans )
{
trans->total_amount =
trans->quantity * trans->unit_price;
}
This function is called like this:
这个函数按照下面的方式进行调用:
compute_total_amount( ¤t_trans );
Now, the total_amount field in the callerʹs structure is modified directly; there is no need to pass the entire structure into the function or to copy the modified structure as the return value. This version is more efficient than either of the other two functions.In addition, the caller no longer needs to know about the internals of the structure, so modularity is also improved.
现在,调用程序的结构的字段total_amount 被直接修改,它并不需要把整个结构作为参数传递给函数,也不需要把整个修改过的结构作为返回值返回。这个版本比前两个版本效率高得多。另外,调用程序无需知道结构的内容,所以也提高了程序的模块化程度。
When should you pass a structure, rather than a pointer, as an argument to a function? Rarely. Only when a structure is extremely small (the size of a pointer, or smaller) is it as efficient to pass the structure as it is to pass a pointer to it. For most structures, it is more efficient to pass a pointer. If you want the function to be able to modify any of the structureʹs members, a pointer is also preferred.
什么时候你应该向函数传递一个结构而不是一个指向结构的指针呢?很少有这种情况。只有当一个结构特别的小(长度和指针相同或更小)时,结构传递方案的效率才不会输给指针传递方案。但对于绝大多数结构,传递指针显然效率更高。如果你希望函数修改结构的任何成员,也应该使用指针传递方案。
With very early K&R C compilers, you couldnʹt pass structures as arguments—the compiler simply did not allow it. Later K&R compilers did allow structure arguments.However, these compilers did not support const, so the only way to prevent a function from modifying a structure argument was to pass a copy of the structure.
在非常早期的K&R C 编译器中,你无法把结构作为参数传递给函数——编译器就是不允许这样做。后期的K&RC 编译器允许传递结构参数。但是,这些编译器都不支持const ,所以防止程序修改结构参数的唯一办法就是向函数传递一份结构的拷贝。