Function headers
There is little difference between function headers in C and
method headers in Java;
though Java does have a few modifiers, such as public
,
that wouldn’t make sense in a language like C that isn’t
object oriented.
Because early versions of C supported “old-style”
function definitions, a C function with no parameters should be declared
with a parameter list of void
.
int always5(void) { return 5 ; }
In C, the static
modifier for a function definition
specifies that the function can only be used in the translation
unit (roughly the file) in which it is defined.
As you know, static
means quite a bit more in Java.
Function prototype
In Java, the methods of a class can be defined in any order. This is
not the case with C functions.
A C function must be declared before it is used.
This is usually done with a function prototype.
The function prototype is similar to the method definitions
found in a Java interfacer
. The function prototype
specifies the name, parameters and return type of function.
However, in C the parameter names can be omitted.
int switchRead(int) ; void ledWrite(int) ;
You will find function prototypes at the beginning of the C man pages for many C functions. These prototypes will be provided in include files that will also be mentioned in the man page. Here’s an example taken from the man page for C’s cosine functions.
#include <math.h> double cos(double x); float cosf(float x); long double cosl(long double x);
Note that your program should contain only the #include
statement, not the actual prototypes.
Programming with prototypes
If you are creating a C (or C++) library, you must create an appropriate .h file for your library. If you are writing a C program, you must include a function’s prototype or definition before a function call.
Macros
One possible implementation for the switchRead
routine recently discussed in
lab
would be the following:
int switchRead(int pin) { return digitalRead(pin) == LOW ; }
This implementation assumes that switch is “on” with the
pin input is LOW
. However, it is possible to wire the switch
so that it is “on” when the input is HIGH
.
We could add a second parameter to switchRead
which specifies
when the switch should be considered “on” or
we could have two separate functions switchRedLowIsOn
and
switchRedHighIsOn
for the two possibilities or
we could use macros.
Changing the switch with a macro definition
The experienced C programmer might use a macro to
allow the switchRead
to be configured with a
#define
. Here’s an example in which switchRead
is defined with a macro
where SWITCH_ONVALUE
is used to configure the switch.
In this case, by default the switch is considered “on”
when SWITCH_ONVALUE
is undefined.
#ifndef SWITCH_ONVALUE #define SWITCH_ONVALUE LOW #endif int switchRead(int pin) { return digitalRead(pin) == SWITCH_ONVALUE ; }
This use of macros is ugly, but common.
Avoiding a function call with a macro
Unfortunately, many C programmers will not stop there. They will insist that calling a one-line function is simply too inefficient and replace the function with a macro that accepts arguments.
#if !defined(SWITCH_ONVALUE) #define SWITCH_ONVALUE LOW #endif #define switchRead(_pin) (digitalRead(_pin) == SWITCH_ONVALUE)
This means that a statement like
switch_value = switchRead(inputAPin) ;
would be textually transformed into the statement similar to
switch_value = (digitalRead(inputAPin) == LOW) ;
before the code is compiled to
avoid the call overhead of a function invocation.
It all seems a bit much, especially since modern C implementations
have a const
modifier for variables and an
inline
modifier for functions which often results in
equivalent machine code.
const int switchOnValue = LOW ; inline int switchRead(int pin) { return digitalRead(pin) == switchOnValue ; }
However, there really isn’t any way to completely
avoid macros. Within the
Arduino include files, there are macros to define HIGH
and LOW
.
#define HIGH 0x1 #define LOW 0x0