CATEGORII DOCUMENTE |
Asp | Autocad | C | Dot net | Excel | Fox pro | Html | Java |
Linux | Mathcad | Photoshop | Php | Sql | Visual studio | Windows | Xml |
All functions have a type: they return a value of that type whenever they are used. The reason that C doesn't have 'procedures', which in most other languages are simply functions without a value, is that in C it is permissible (in fact well-nigh mandatory) to discard the eventual value of most expressions. If that surprises you, think of an assignment
a = 1;That's a perfectly valid assignment, but don't forget that it has a value too. The value is discarded. If you want a bigger surprise, try this one:
That is an expression followed by a semicolon. It is a well formed statement according to the rules of the language; nothing wrong with it, it is just useless. A function used as a procedure is used in the same way-a value is always returned, but you don't use it:
f(argument);is also an expression with a discarded value.
It's all very well saying that the value returned by a function can be
ignored, but the fact remains that if the function really does return
a value then it's probably a programming error not to do something with it.
Conversely, if no useful value is returned then it's a good idea to be able to
spot anywhere that it is used by mistake. For both of those reasons, functions
that don't return a useful value should be declared to be void
.
Functions can return any type supported by C (except for arrays and functions), including the pointers, structures and unions which are described in later chapters. For the types that can't be returned from functions, the restrictions can often be sidestepped by using pointers instead.
All functions can be called recursively.
Unfortunately, we are going to have to use some jargon now. This is one of the times that the use of an appropriate technical term really does reduce the amount of repetitive descriptive text that would be needed. With a bit of luck, the result is a shorter, more accurate and less confusing explanation. Here are the terms.
declaration
The point at which a name has a type associated with it.
definition
Also a declaration, but at this point some storage is reserved for the named object. The rules for what makes a declaration into a definition can be complicated, but are easy for functions: You turn a function declaration into a definition by providing a body for the function in the form of a compound statement.
formal parameters
parameters
These are the names used inside a function to refer to its arguments.
actual arguments
arguments
These are the values used as arguments when the function is actually called. In other words, the values that the formal parameters will have on entry to the function.
The terms 'parameter' and 'argument' do tend to get used as if they were interchangeable, so don't read too much into it if you see one or the other in the text below.
If you use a function before you declare it, it is implicitly declared to be
'function returning int
'.
Although this will work, and was widely used in Old C, in Standard C it is bad
practice-the use of undeclared functions leads to nasty problems to do with the
number and type of arguments that are expected for them. All functions should
be fully declared before they are used. For example, you might be intending to
use a function in a private library called, say, aax1
. You know that it takes no arguments and returns a double
. Here is how it should be
declared:
and here is how it might be used:
main()Example 4.1
The declaration was an interesting one. It defined return_v
, actually causing a variable to
come into existence. It also declared aax1
without defining it; as we know, functions only become defined when a
body is provided for them. Without a declaration in force, the default rules
mean that aax1
would have
been assumed to be int
, even
though it really does return a double
-which
means that your program will have undefined behaviour. Undefined behaviour is
disastrous!
The presence of void
in
the argument list in the declaration shows that the function really takes no
arguments. If it had been missing, the declaration would have been taken to
give no information about the function's arguments. That way, compatibility
with Old C is maintained at the price of the ability of the compiler to check.
To define a function you also have to provide a body for it, in
the form of a compound statement. Since no function can itself contain the
definition of a function, functions are all separate from each other and are
only found at the outermost level of the program's structure. Here is a
possible definition for the function aax1
.
It is unusual for a block-structured language to prohibit you from defining functions inside other functions, but this is one of the characteristics of C. Although it isn't obvious, this helps to improve the run-time performance of C by reducing the housekeeping associated with function calls.
The return
statement is
very important. Every function except those returning void
should have at least one, each return
showing what value is supposed to
be returned at that point. Although it is possible to return from a function by
falling through the last }
,
unless the function returns void
an unknown value will be returned, resulting in undefined behaviour.
Here is another example function. It uses getchar
to read characters from the program input and returns whatever it sees except
for space, tab or newline, which it throws away.
Look at the way that all of the work is done by the test in the while
statement, whose body was an empty
statement. It is not an uncommon sight to see the semicolon of the empty
statement sitting there alone and forlorn, with only a piece of comment for
company and readability. Please, please, never write it like this:
with the semicolon hidden away at the end like
that. It's too easy to miss it when you read the code, and to assume that the
following statement is under the control of the while
.
The type of expression returned must match the type of the function, or be
capable of being converted to it as if an assignment statement were in use. For
example, a function declared to return double
could contain
and the integral value will be converted to double
. It is also possible to have just
return
without any
expression-but this is probably a programming error unless the function returns
void
. Following the return
with an expression is not
permitted if the function returns void
.
Before the Standard, it was not possible to give any information about a
function's arguments except in the definition of the function itself. The
information was only used in the body of the function and was forgotten at the
end. In those bad old days, it was quite possible to define a function that had
three double
arguments and
only to pass it one int,
when it was called. The program would compile normally, but simply not work
properly. It was considered to be the programmer's job to check that the number
and the type of arguments to a function matched correctly. As you would expect,
this turned out to be a first-rate source of bugs and portability problems.
Here is an example of the definition and use of a function with arguments, but
omitting for the moment to declare the function fully.
Example 4.2
What can we learn from this? To start with, notice the careful declaration
that pmax
returns void
. In the function definition, the
matching void
occurs on the
line before the function name. The reason for writing it like that is purely
one of style; it makes it easier to find function definitions if their names
are always at the beginning of a line.
The function declaration (in main
)
gave no indication of any arguments to the function, yet the use of the
function a couple of lines later involved two arguments. That is permitted by
both the old and Standard versions of C, but must
nowadays be considered to be bad practice. It is much better to include
information about the arguments in the declaration too, as we will see. The old
style is now an 'obsolescent feature' and may disappear in a later version of
the Standard.
Now on to the function definition, where the body is
supplied. The definition shows that the function takes two arguments,
which will be known as a1
and a2
throughout the body
of the function. The types of the arguments are specified too, as can be seen.
In the function definition you don't have to specify the type of
each argument because they will default to int
,
but this is bad style. If you adopt the practice of always declaring arguments,
even if they do happen to be int
,
it adds to a reader's confidence. It indicates that you meant to use that type,
instead of getting it by accident: it wasn't simply forgotten. The definition
of pmax
could have
been this:
Example 4.3
This time, the declaration provides information about the function
arguments, so it's a prototype. The names first
and second
are not an
essential part of the declaration, but they are allowed to be there because it
makes it easier to refer to named arguments when you're documenting the use of
the function. Using them, we can describe the function simply by giving its
declaration
and then say that pmax
prints whichever of the arguments xx
or yy
is the larger. Referring to arguments by their position, which is the
alternative (e.g. the fifth argument), is tedious and prone to miscounting.
All the same, you can miss out the names if you want to. This declaration is entirely equivalent to the one above.
void pmax (int,int);All that is needed is the type names.
For a function that has no arguments the declaration is
void f_name (void);and a function that has one int
, one double
and an unspecified number of other arguments is
declared this way:
The ellipsis () shows that other arguments follow. That's useful because
it allows functions like printf
to be written. Its declaration is this:
where the type of the first argument is 'pointer to
const char
'; we'll discuss
what that means later.
Once the compiler knows the types of a function's arguments, having seen them in a prototype, it's able to check that the use of the function conforms to the declaration.
If a function is called with arguments of the wrong type, the presence of a
prototype means that the actual argument is converted to the type of the formal
argument 'as if by assignment'. Here's an example: a function is used to
evaluate a square root using
Example 4.4
The prototype tells everyone that sq_root
takes a single argument of type double
.
The argument actually passed in the main function is an int
, so it has to be converted to double
first. The critical point is that
if no prototype had been seen, C would assume that the programmer had meant to
pass an int
and an int
is what would be passed. The Standard
simply notes that this results in undefined behaviour, which is as understated
as saying that catching rabies is unfortunate. This is a very serious error
and has led to many, many problems in Old C programs.
The conversion of int
to double
could be done because the
compiler had seen a protoytpe for the function and knew what to do about it. As
you would expect, there are various rules used to decide which conversions are
appropriate, so we need to look at them next.
When a function is called, there are a number of possible conversions that will be applied to the values supplied as arguments depending on the presence or absence of a prototype. Let's get one thing clear: although you can use these rules to work out what to do if you haven't used prototypes, it is a recipe for pain and misery in the long run. It's so easy to use prototypes that there really is no excuse for not having them, so the only time you will need to use these rules is if you are being adventurous and using functions with a variable number of arguments, using the ellipsis notation in the prototype that is explained in Chapter
The rules mention the default argument promotions and compatible type. Where they are used, the default argument promotions are:
float
it is
converted to double
The introduction of prototypes (amongst other things) has increased the need for precision about 'compatible types', which was not much of an issue in Old C. The full list of rules for type compatibility is deferred until Chapter because we suspect that most C programmers will never need to learn them. For the moment, we will simply work on the basis that if two types are the same, they are indisputably compatible.
The conversions are applied according to these rules (which are intended to be guidance on how to apply the Standard, not a direct quote):
At
the point of calling a function, if a prototype is in scope, the
arguments are converted, as if by assignment, to the types specified in the
prototype. Any arguments which fall under the variable argument list category
(specified by the
in the prototype) still undergo the default argument conversions.
It is possible to write a program so badly that you have a prototype in scope when you call the function, but for the function definition itself not to have a prototype. Why anyone should do this is a mystery, but in this case, the function that is called must have a type that is compatible with the apparent type at the point of the call.
The order of evaluation of the arguments in the function call is explicitly not defined by the Standard.
Function prototypes allow the same text to be used for both the declaration and definition of a function. To turn a declaration:
doubleinto a definition, we provide a body for the function:
doubleby replacing the semicolon at the end of the declaration with a compound statement.
In either a definition or a declaration of a function, it serves as a prototype if the parameter types are specified; both of the examples above are prototypes.
The Old C syntax for the declaration of a function's formal arguments is still supported by the Standard, although it should not be used by new programs. It looks like this, for the example above:
Because no type information is provided for the parameters at the point where they are named, this form of definition does not act as a prototype. It declares only the return type of the function; nothing is remembered by the compiler about the types of the arguments at the end of the definition.
The Standard warns that support for this syntax may disappear in a later version. It will not be discussed further.
void
.
operator. void
)
as the argument specification. Functions
taking a variable number of arguments must take at least one named argument;
the variable arguments are indicated by
as shown:
As we have seen, functions always have a compound statement as their body. It is possible to declare new variables inside any compound statement; if any variables of the same name already exist, then the old ones are hidden by the new ones within the new compound statement. This is the same as in every other block-structured language. C restricts the declarations to the head of the compound statement (or 'block'); once any other kind of statement has been seen in the block, declarations are no longer permitted within that block.
How can it be possible for names to be hidden? The following example shows it happening:
int a; /* visible from here onwards */Example 4.5
A name declared inside a block hides any outer versions of the same name until the end of the block where it is declared. Inner blocks can also re-declare that name-you can do this for ever.
The scope of a name is the range in which it has meaning. Scope starts from the point at which the name is mentioned and continues from there onwards to the end of the block in which it is declared. If it is external (outside of any function) then it continues to the end of the file. If it is internal (inside a function), then it disappears at the end of the block containing it. The scope of any name can be suspended by redeclaring the name inside a block.
Using knowledge of the scope rules, you can play silly tricks like this one:
main ()Now f
and f2
can use i
, but main
can't, because the declaration of the variable comes later than that of main
. This is not an aspect that is used
very much, but it is implicit in the way that C processes declarations. It is a
source of confusion for anyone reading the file (external declarations are
generally expected to precede any function definitions in a file) and should be
avoided.
The Standard has changed things slightly with respect to a function's formal parameters. They are now considered to have been declared inside the first compound statement, even though textually they aren't: this goes for both the new and old ways of function definition. So, if a function has a formal parameter with the same name as something declared in the outermost compound statement, this causes an error which will be detected by the compiler.
In Old C, accidental redefinition of a function's formal parameter was a horrible and particularly difficult mistake to track down. Here is what it would look like:
/* erroneous redeclaration of arguments */The pernicious bit is the new declaration of a in the body of the function,
which hides the parameter called a
.
Since the problem has now been eliminated we won't investigate it any further.
1. Stroustrup B. (1991). The C++ Programming Language
2nd edn.
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 819
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved