Pointers on C——7 Functions.2

7.2 Function Declaration


When the compiler encounters a call to a function, it generates code to pass the arguments and call the function, and code to receive the value (if any) sent back by the function. But how does the compiler know what kinds of arguments (and how many)the function expects to get, and what kind of value (if any) that the function returns?

当编译器遇到一个函数调用时,它产生代码传递参数并调用这个函数,而且接收该函数返回的值(如果有的话)。但编译器是如何知道该函数期望接受的是什么类型和多少数量的参数呢?如何知道该函数的返回值(如果有的话)类型呢?


If there isnʹt any specific information given about the function, the compiler assumes that the call has the correct number and types of arguments. It also assumes that the function will return an integer, which usually leads to errors for functions that return nonintegral types.

如果没有关于调用函数的特定信息,编译器便假定在这个函数的调用时参数的类型和数量是正确的。它同时会假定函数将返回一个整型值。对于那些返回值并非整型的函数而言,这种隐式认定常常导致错误。


7.2.1 Prototypes


It is safer to give the compiler specific information about the function, which we can do two different ways. First, if the definition for the function appears earlier in the same source file, the compiler will remember the number and types of its arguments and the type of its return value. It can then check all subsequent calls to the function(in that source file) to make sure they are correct.

向编译器提供一些关于函数的特定信息显然更为安全,我们可以通过两种方法来实现。首先,如果同一源文件的前面已经出现了该函数的定义,编译器就会记住它的参数数量和类型,以及函数的返回值类型。接着,编译器便可以检查该函数的所有后续调用(在同一个源文件中) ,确保它们是正确的。


If a function is defined using the old syntax, with a separate list giving the types of the arguments, then the compiler remembers only the type of the functionʹs return value.No information is saved on the number or types of the arguments. Because of this fact,it is important to use the new function declaration style whenever possible.

如果函数是以旧式风格定义的,也就是用一个单独的列表给出参数的类型,那么编译器就只记住函数的返回值类型,但不保存函数的参数数量和类型方面的信息。由于这个缘故,只要有可能,你都应该使用新式风格的函数定义,这点非常重要。


The second way to give the compiler information about a function is to use a function prototype, which you saw in Chapter 1. A prototype summarizes the declaration at the beginning of the function definition, thus giving the compiler complete information on how the function is to be called. The most convenient (and safest) way to use a prototype is to put it in a separate file and then #include that file wherever it is needed. This technique avoids the possibility of mistyping a prototype.It also simplifies maintenance of the program by only having one physical copy of the prototype. Should the prototype need to be changed, there is only one copy of it to modify.

第2 种向编译器提供函数信息的方法是使用函数原型(function prototyp),你在第1 章已经见过它。原型总结了函数定义的起始部分的声明,向编译器提供有关该函数应该如何调用的完整信息。使用原最方便(且最安全)的方法是把原型置于一个单独的文件,当其他源文件需要这个函数的原型时,就使用#include 指令包含该文件。这个技巧避免了错误键入函数原型的可能性,它同时简化了程序的维护任务,因为这样只需要该原型的一份物理拷贝。如果原型需要修改,你只需要修改它的一处拷贝。


To illustrate, here is a prototype for the find_int function from the previous example:

举个例子,这里有一个find int 函数的原型,取自前面的例子:

int *find_int( int key, int array[], int len );


Note the semicolon at the end: it distinguishes a prototype from the beginning of a function definition. The prototype tells the compiler the number of arguments, the type of each argument, and the type of the returned value. After the prototype has been seen, the compiler will check calls made to the function to make sure that their arguments are correct and that the returned value is used properly. Where mismatches occur (for example, an argument of the wrong type) the compiler will convert the value to the correct type if such a conversion is possible.

注意最后面的那个分号:它区分了函数原型和函数定义的起始部分。原型告诉编译器函数的参数数量和每个参数的类型以及返回值的类型。编译器见过原型之后,就可以检查该函数的调用,确保参数正确、返回值无误。当出现不匹配的情况时(例如,参数的类型错误),编译器会把不匹配的实参或返回值转换为正确的类型,当然前提是这样的转换必须是可行的。


Note that I put argument names in the above prototype. While not required, it is wise to include descriptive parameter names in function prototypes because they give useful information to clients wishing to call the function. For example, which of the following two prototypes do you find more useful?

注意我在上面的原型中加上了参数的名字。虽然它并非必需,但在函数原型中加入描述性的参数名是明智的,因为它可以给希望调用该函数的客户提供有用的信息。例如,你觉得下面这两个函数原型哪个更有用?


char *strcpy( char *, char * );

char *strcpy( char *destination, char *source );


The following code fragment illustrates a dangerous way to use function prototypes.

下面的代码段例子说明了一种使用函数原型的危险方法。


void a()

{

int *func( int *value, int len );

...

}

void b()

{

int func( int len, int *value );

...

}


Look closely at the prototypes and you will see that they are different. The arguments are reversed, and the return values are different types. The problem is that each prototype is written inside the body of a function. They have block scope, so the compiler throws out what it learned from the prototype at the end of each function and never detects the mismatch.

仔细观察一下这两个原型,你会发现它们是不一样的。参数的顺序倒了,返回类型也不同。问题在于这两个函数原型都写于函数体的内部,它们都具有代码块作用域,所以编译器在每个函数结束前会把它记住的原型信息丢弃,这样它就无法发现它们之间存在的不匹配情况。


The Standard states that a function prototype must march any earlier prototype for the same function that is in scope, or else an error message is printed. In this example, though, the scope for the first block does not overlap the second block.Therefore, the mistakes in the prototypes go undetected. One or the other of these prototypes is wrong (maybe both), but the compiler never sees the contradiction so no error messages are produced.

标准表示,在同一个代码块中,函数原型必须与同一个函数的任何先前原型匹配,否则编译器应该产生一条错误信息。但是,在这个例子里,第1 个代码块的作用域并不与第2 个代码块重叠。因此,原型的不匹配就无法被检测到。这两个原型至少有一个是错误的(也可能两个都错) ,但编译器看不到这种情况,所以不会发出任何错误信息。



The code fragment below illustrates the preferred way to use prototypes:

下面的代码段说明了一种使用函数原型的更好方法。

*include "func.h"

void a()

{

...

}

void b()

{

...

}

The file func.h contains the prototype

int *func( int *value, int len );

This techniques is better in several ways.

从几个方面看,这个技巧比前一种方法更好。


1. The prototype now has file scope so that one copy of it applies to the entire source file, which is easier than writing a separate copy in each place from which the function is called.

现在函数原型具有文件作用域,所以原型的一份拷贝可以作用于整个源文件,较之在该函数每次调用前单独书写一份函数原型要容易得多。


2. The prototype is only written once, so there is no chance for disagreements between multiple copies.

现在函数原型只书写一次,这样就不会出现多份原型的拷贝之间的不匹配现象。


3. If the function definition changes, all we have to do is modify the prototype and recompile each source file that includes it.

如果函数的定义进行了修改,我们只需要修改原型,并重新编译所有包含了该原型的源文件即可。


4. If the prototype is also #includeʹd into the file where the function is defined, the compiler will be able to verify that the prototype matches the function definition.

如果函数的原型同时也被#include 指令包含到定义函数的文件中,编译器就可以确认函数原型与函数定义的匹配。


By writing the prototype once, we eliminate the chance that multiple copies of it differ.However, the prototype must match the function definition. Including the prototype in the file where the function is defined lets the compiler verify that they match.

通过只书写函数原型一次,我们消除了多份原型的拷贝间不一致的可能性。然而,函数原型必须与函数定义匹配。把函数原型包含在定义函数的文件中可以使编译器确认它们之间的匹配。


Consider this declaration, which looks ambiguous:

考虑下面这个声明,它看上去有些含糊:

int *func();


It could be either an old‐style declaration (giving the return type of func) or a newstyle prototype for a function with no arguments. Which is it? The declaration must be interpreted as an old‐style declaration in order to maintain compatibility with pre‐ANSI programs. A prototype for a function without arguments is written like this:

它既可以看作是一个旧式风格的声明(只给出func 函数的返回类型),也可以看作是一个没有参数的函数的新风格原型。它究竟是哪一个呢?这个声明必须被解释为旧式风格的声明,目的是保持与ANSI 标准之前的程序的兼容性。一个没有参数的函数的原型应该写成下面这个样子:


int *func( void );


The keyword void indicates that there arenʹt any arguments, not that there is one argument of type void.

关键字void 提示没有任何参数,而不是表示它有一个类型为void 的参数。


7.2.2 Default Function Assumptions


When a call is made to a function for which no prototype has been seen, the function is assumed to return an integer value. This can lead to errors for functions which return nonintegral values.

当程序调用一个无法见到原型的函数时,编译器便认为该函数返回一个整型值。对于那些并不返回整型值的函数,这种认定可能会引起错误。


While it is recommended that all functions be prototyped, it is especially important to prototype functions that return nonintegral values. Remember that the type of a value is not inherent in the value itself, but rather in the way that it is used. If the compiler assumes that a function returns an integral value, it will generate integer instructions to manipulate the value. If the value is actually a nonintegral type, floating‐point for example, the result will usually be incorrect.

所有的函数都应该具有原型,尤其是那些返回值不是整型的函数。记住,值的类型并不是值的内在本质,而是取决于它被使用的方式。如果编译器认定函数返回一个整型值,它将产生整数指令操纵这个值。如果这个值实际上是个非整型值,比如说是个浮点值,其结采通常将是不正确的。


Letʹs look at an example of this error. Imagine a function xyz that returns the float value 3.14 . The bits used to represent this floating‐point number on a Sun Sparc workstation are:

让我们看一个这种错误的例子。假设有一个函数xyz ,它返回float 值3.140 在Sun Sparc 工作站中,用于表示这个浮点数的二进制位模式如下:


01000000010010001111010111000011


Now assume that the function is called like this:

现在假定函数是这样被调用的:


float f;

...

f = xyz();

If there is no prototype for the function, the compiler will assume that it is returning an integer and will generate instructions to convert the value to floating‐point before assigning it to f.

如果在函数调用之前编译器无法看到它的原型,它使认定这个函数返回一个整型值,并产生指令将这个值转换为float.然后再赋值给变量f。


The function returns the bits shown above. The conversion instructions interpret them as the integer 1,078,523,331 and convert this integer value to floatingpoint, and the result is stored in f.

函数返回的位如上所示。转换指令把它们解释为整型值1 078 523 33 1,并把这个值转换为float类型,结果存储于变量f 中。


Why was this conversion done when the value returned was already in floating‐point format? The compiler has no way of knowing it was, because there was no prototype or declaration to tell it so. This example illustrates why it is vital for functions that return values other than integers to have prototypes.

为什么函数的返回值实际上已经是浮点值的形式时,还要执行类型转换呢?编译器并没有办法知道这个情况,因为没有原型或声明告诉它这些信息。这个例子说明了为什么返回值不是整型的函数具有原型是极为重要的。


上一章 Pointers on C——7 Functions.1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Pointers On C brings the power of pointers to your C programs. Designed for professionals and advanced students, Pointers on C provides a comprehensive resource for those needing in-depth coverage of the C programming language. An extensive explanation of pointer basics and a thorough exploration of their advanced features allows programmers to incorporate the power of pointers into their C programs. Complete coverage, detailed explanations of C programming idioms, and thorough discussion of advanced topics makes Pointers on C a valuable tutorial and reference for students and professionals alike. Features and Benefits Provides complete background information needed for a thorough understanding of C. Covers pointers thoroughly, including syntax, techniques for their effective use and common programming idioms in which they appear. Compares different methods for implementing common abstract data structures. Offers an easy, conversant writing style to clearly explain difficult topics, and contains numerous illustrations and diagrams to help visualize complex concepts. Includes Programming Tips, discussing efficiency, portability, and software engineering issues, and warns of common pitfalls using Caution! Sections. Describes every function on the standard C library. For those who need an up-to-date ANSI overview of the C programming language, this book would be an excellent introduction. Pointers are usually a stumbling block for those programming C initially, but the author does an excellent job of detailing the use of pointers in this book. The use of pointers dominates the entire book, and after studying it, readers will have a thorough, practical knowledge of how to take advantage of the performance power of C language, due mostly to its use of pointers. For those programming in a commercial/business environment, where coding practices are strictly enforced, this book would be a good desk reference, as the author includes discussion of sound programming practices throughout the book. The book would also serve well those involved in teaching C in the classroom, as it contains many exercises, ranging from very easy to highly advanced. And for those readers frequently facing legacy code in C, such as scientific programmers, the author cites the differences between the older "Kernighan-Ritchie" C, and the more modern ANSI C, the latter being used in the book. These differences are indicated in the margin of the book, and are of an enormous help for those who must take older code and get it to run on more up-to-date compilers. The author also endeavors to organize the C code for those who are going on to study C++ and the accompanying object-oriented approach to programming. In addition, he emphasizes how to write C code so as to make it more portable. For those writing commercial applications in C that must be used on different platforms, this is a very important issue of course. Particularly well-written is the author's discussion on the storage class of a variable, noting, for those such as I who are pre-disposed to using recursion, that the formal parameters to a function cannot be static if recursion is to be supported. The book is full of examples such as this that give readers insight on the workings of C that fit their particular programming style. He does discuss `goto' statements in relation to function scope and in C statement structures, but, thankfully, recommends such statements never be used. He gives an interesting counterexample to those who say that goto statements must be used to break out of nested loops. Also, the author discusses the difference between L- and R-values, and this is not usually included in beginning books on C. Dynamic memory allocation has been at times a somewhat painful aspect of programming in C, but the author shows how to do straightforwardly in the book. Having a book like this that is predominantly about pointers is quite a blessing for those who are inexperienced with them or for more experienced programmers who are still uncomfortable with their use. It is not uncommon these days to have to write programs in one's professional work that involve triple pointers or even quadruple pointers. In addition, for embedded systems programming, the use of pointer arithmetic is almost mandatory. This also is true for writing applications in cryptography using C. The author does pay careful attention to pointer arithmetic in the book. The performance pay-off for using pointers is undeniable, and so a thorough knowledge of their use and pit-falls is of upmost importance for those C programmers who are involved in writing performance-sensitive applications. The author discusses in detail what can happen when pointers are misused and gives many examples of what to avoid and good hints for the proper use of pointers. He recommends against the use of the `null' pointer in array searching, and recommends a strategy for circumventing them. Some very helpful diagrams are given for explaining pointer expressions. In addition, the author gives helpful hints on when to use pointers and not subscripts when manipulating arrays in C. The performance issues involved in this are extremely important in scientific programming using C. The author gives a very interesting example of the differences in performance using pointers involving a program to copy the contents of one array into another. Arrays of pointers, useful in data mining applications, are also given ample treatment in this book, and the author addresses the issue of when to use a matrix instead of an array of pointers. The author also gives an effective presentation of functions in C, particularly the construction of recursive functions, and he employs some useful diagrams to illustrate how the variables in a recursive function call change on the stack. The performance hit experienced by using recursion versus iterative loops is discussed in a standard way via the Fibonacci series. Those readers raised in the functional programming paradigm will want to pay notice these performance issues when using C to do recursion. Along the same lines, the author shows how to implement functions with variable argument lists in C. This is another topic that is frequently passed over in beginning books on C. The author's treatment of data structures in C is also very nicely done, and he includes again a topic not usually treated in beginning books on C, namely the concept of a self-referential data structure. These are very important in applications in artificial intelligence, and the author shows how to implement them in C using a data structure that points to itself. This leads to a discussion of incomplete declarations. Very helpful diagrams are used again to discuss how to access members of data structures and how to point to data structures. Bit fields, so often used in embedded system applications, are also given a detailed treatment.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值