Saturday, December 10, 2011

C Programming Lecture 8


Procedures and Functions
A function is a section of code that has some separate functionality or does some function that will be reused over and over again.
As a basic example, if you are writing code to print out the first 5 squares of numbers, then the first
5 cubes, then the next 5 squares again, instead of writing something like
for(i=1;  i  <=  5;  i++)
{
printf("%d  ",  i*i);
}
for(i=1;  i  <=  5;  i++)
{
printf("%d  ",  i*i*i);
}
for(i=1;  i  <=  5;  i++)
{
printf("%d  ",  i*i);
}

which duplicates the same loop twice. We may want to separate this code somehow and simply jump to this code when we want its functionality.
This is what precisely functions are for.

More on functions
A function is like a black box. It takes in an input, does something to that input, then spits out an
answer.
Note that a function may not take any inputs at all, or it may not return anything at all. In the above example, if we were to make a function of that loop, we may not need any inputs, and we aren't returning anything at all (Text output doesn't count ­ when we speak of returning we mean to say meaningful data that the program that used the function can use).





We have some terminology to refer to functions:
  A function, call it f, that uses another function g, is said to call g. For example, f calls g to
     
print the squares of ten numbers.
  A function's inputs are known as its arguments
  A function that wants to give f back some data that g calculated is said to return that data.
     
For example, g returns the sum of its arguments.
Writing functions in C
It's always good to learn by example. Let's write a function that will return the square of a number.
int
square(int  x)
{
int  square_of_x;
square_of_x  =  x  *  x;
  
return  square_of_x;  }
To understand how to write such a function like this, it may help to look at what this function does as a whole. It takes in an int, x, and squares it, storing it in the variable square_of_x. Now this
value is returned.
The first int at the beginning of the function declaration is the type of data that the function returns. In this case when we square an integer we get an integer, and we are returning this integer, and so we write int as the return type.
Next is the name of the function. It is good practice to use meaningful and descriptive names for
functions you may write. It may help to name the function after what it is written to do. In this case we name the function "square", because that's what it does ­ it squares a number.
Next is the function's first and only argument, an int, which will be referred to in the function as x. This is the function's input.
Inbetween the braces is the actual guts of the function. It declares an integer variable called
square_of_x that will be used to hold the value of the square of x. Note that the variable
square_of_x can only be used within this function, and not outside. We'll learn more about this sort of thing later, and we will see that this property is very useful.
We then assign x multiplied by x, or x squared, to the variable square_of_x, which is what this
function is all about. Following this is a return statement. We want to return the value of the
square of x, so we must say that this function returns the contents of the variable square_of_x.
Our brace to close, and we have finished the declaration.
Note this should look familiar ­ you have been writing functions already, in fact ­ main is a function that is always written.

In general
In general, if we want to declare a function, we write
type
name(type1  arg1,  type2  arg2,  ...)
{
/*  code  */
}




We've previously said that a function can take no arguments, or can return nothing, or both. What do we write for the type of nothing? We use C's void keyword. void basically means "nothing" ­ so if we want to write a function that returns nothing, for example, we write
void
sayhello(int  number_of_times)
{
int  i;
for(i=1;  i  <=  number_of_times;  i++)
     
printf("Hello!\n");
}

Notice that there is no return statement in the function above. Since there's none, we write void as the return type.
What about a function that takes no arguments? If we want to do this, we can write for example
float
calculate_number(void)
{
float  to_return=1;  int  i;
for(i=0;  i  <  100;  i++)
{
to_return  +=  1;
to_return  =  1/to_return;
}
return  to_return;
}

Notice this function doesn't take any inputs, but merely returns a number calculated by this function.
Naturally, you can combine both void return and void in arguments together to get a valid function,
also.

Recursion
Here's a simple function that does an infinite loop. It prints a line and calls itself, which again prints a line and calls itself again, and this continues until the stack overflows and the program crashes. A function calling itself is called recursion, and normally you will have a conditional that would stop the recursion after a small, finite number of steps.
void  infinite_recursion()
\\  don't  run  that!
{
printf("Infinite  loop!\n");
         
infinite_recursion();
 
}


A simple check can be done like this. Note that ++depth is used so the increment will take place
before the value is passed into the function. Alternatively you can increment on a separate line
before the recursion call. If you say print_me(3,0); the function will print the line Recursion 3 times.
void  print_me(int  j,  int  depth)
{
if(depth  <  j)
{
printf("Recursion!  depth  =  %d  j  =  %d\n",depth,j);//j  always  the  same print_me(j,  ++depth);
}
}






Recursion is most often used for jobs such as directory tree scans, seeking for the end of a linked
list, parsing a tree structure in a database and factorising numbers (and finding primes) among other
things.

Static Functions
If a function is to be called only from within the file in which it is declared, it is appropriate to
declare it as a static function. When a function is declared static, the compiler will now compile to
an
 object file in a way that prevents the function from being called from code in other files.
Example:
static  short  compare(  short  a,  short  b  )
{
return  (a+4  <  b)?  a  :  b;
}

Using C functions
We can now write functions, but how do we use them? When we write main, we place the function outside the braces that encompass main.
When we want to use that function, say, using our calculate_number function above, we can write something like
float  f;
f  =  calculate_number();

If a function takes in arguments, we can write something like
int  square_of_10;
square_of_10  =  square(10);

If a function doesn't return anything, we can just say
say_hello();

since we don't need a variable to catch its return value.

C's Built­in Functions
   rint
 Procedures and functio            s| Libraries 
Preprocessors are a way of making text processing with your C program before they are actually
compiled. Before the actual compilation of every C program it is passed through a Preprocessor.
The Preprocessor looks through the program trying to find out specific instructions called
Preprocessor directives that it can understand. All Preprocessor directives begin with the # (hash)
symbol.
The preprocessor is a part of the compiler which performs preliminary operations (conditionally
compiling code, including files etc...) to your code before the compiler sees it. These
transformations
 are lexical, meaning that the output of the preprocessor is still text.





NOTE: Technically the output of the preprocessing phase for C consists of a sequence of
tokens, rather than source text, but it is simple to output source text which is equivalent to the
given token sequence, and that is commonly supported by compilers via a ­E or /E option ­­
although command line options to C compilers aren't completely standard, many follow
similar rules.

Directives
Directives are special instructions directed to the preprocessor (preprocessor directive) or to the
compiler (compiler directive) on how it should process part or all of your source code or set some
flags
 on the final object and are used to make writing source code easier (more portable for
instance) and to make the source code more understandable. Directives are handled by the
preprocessor, which is either a separate program invoked by the compiler or part of the compiler
itself.

#include
C has some features as part of the language and some others as part of a standard library, which is
a
 repository of code that is available alongside every standard­conformant C compiler. When the C
compiler compiles your program it usually also links it with the standard C library. For example, on
encountering a #include  <stdio.h> directive, it replaces the directive with the contents of the
stdio.h header file.
When you use features from the library, C requires you to declare what you would be using. The first line in the program is a preprocessing directive which should look like this:
#include  <stdio.h>
The above line causes the C declarations which are in the stdio.h header to be included for use in
your program. Usually this is implemented by just inserting into your program the contents of a
header file called stdio.h in a system­dependent location. The location of such files may be
described in your compiler's documentation. A list of standard C header files is listed below in the
Headers table.
The stdio.h header contains various declarations for input/output (I/O) using an abstraction of
I/O mechanisms called streams. For example there is an output stream object called stdout which
is
 used to output text to the standard output, which usually displays the text on the computer screen.
If using angle brackets like the example above, the preprocessor is instructed to search for the include file along the development environment path for the standard includes.
#include  "other.h"
If you use quotation marks ("  "), the preprocessor is expected to search in some additional, usually user­defined, locations for the header file, and to fall back to the standard include paths only if it is not found in those additional locations. It is common for this form to include searching in the same directory as the file containing the #include directive.
NOTE: You should check the documentation of the development environment you are using for any vendor specific implementations of the #include directive.





Headers
The C90 standard headers list:
  assert.h              locale.h               stddef.h
  ctype.h                math.h                stdio.h
  errno.h                setjmp.h             stdlib.h
  float.h                  signal.h               string.h
  limits.h                stdarg.h              time.h

Headers added since C90:
  complex.h              iso646.h               tgmath.h
  fenv.h                       stdbool.h              wchar.h
  inttypes.h               stdint.h                  wctype.h

#pragma
The pragma (pragmatic information) directive is part of the standard, but the meaning of any pragma depends on the software implementation of the standard that is used.
Pragmas are used within the source program.
#pragma  token(s)

You should check the software implementation of the C standard you intend on using for a list of the supported tokens.
For instance one of the most implemented preprocessor directives, #pragma  once when placed at the beginning of a header file, indicates that the file where it resides will be skipped if included several times by the preprocessor.
NOTE: Other methods exist to do this action that is commonly refered as using include
guards.



#define
The #define directive is used to define values or macros that are used by the preprocessor to
manipulate the program source code before it is compiled. Because preprocessor definitions are
substituted before the compiler acts on the source code, any errors that are introduced by #define are difficult to trace.
By convention, values defined using #define are named in uppercase. Although doing so is not a requirement, it is considered very bad practice to do otherwise. This allows the values to be easily identified when reading the source code.
Today, #define is primarily used to handle compiler and platform differences. E.g, a define might hold a constant which is the appropriate error code for a system call. The use of #define should thus be limited unless absolutely necessary; typedef statements and constant variables can often perform the same functions more safely.
Another feature of the #define command is that it can take arguments, making it rather useful as





a pseudo­function creator. Consider the following code:
#define  ABSOLUTE_VALUE(  x  )  (  ((x)  <  0)  ?  ­(x)  :  (x)  )
int  x  =  ­1;
while(  ABSOLUTE_VALUE(  x  )  )  {
}
It's generally a good idea to use extra parentheses when using complex macros. Notice that in the above example, the variable "x" is always within its own set of parentheses. This way, it will be evaluated in whole, before being compared to 0 or multiplied by ­1. Also, the entire macro is surrounded by parentheses, to prevent it from being contaminated by other code. If you're not careful, you run the risk of having the compiler misinterpret your code.
Because of side­effects it is considered a very bad idea to use macro functions as described above.
int  x  =  ­10;
int  y  =  ABSOLUTE_VALUE(  x++  );
If ABSOLUTE_VALUE() was a real function 'x' would now have the value of '­9', but because it
was an argument in a macro it was expanded 3 times (in this case) and thus has a value of ­7.
NOTE: Try to use const and inline instead of #define.
It is common practice when using #define and macros to name them all upper and use "_" separators, this will make clear to a reader that the value is not alterable and in case of a
macro, that the construct requires care, some subtle errors can be created if using enum and macros with the same name. A const is easier to debug, too, since the compiler and linker recognizes a constant variable name, unlike a macro.

Example:

To illustrate the dangers of macros, consider this naive macro

#define  MAX(a,b)  a>b?a:b

and the code

i  =  MAX(2,3)+5;
j  =  MAX(3,2)+5;

Take a look at this and consider what the the value after execution might be. The statements are turned into

int  i  =  2>3?2:3+5;
int  j  =  3>2?3:2+5;
Thus, after execution i=8 and j=3 instead of the expected result of i=j=8! This is why you were cautioned to use an extra set of parenthesis above, but even with these, the road is
fraught with dangers. The alert reader might quickly realize that if a,b contains expressions, the definition must parenthesise every use of a,b in the macro defintion, like this:

#define  MAX(a,b)  ((a)>(b)?(a):(b))





This works, provided a,b have no side effects. Indeed,

i  =  2;
j  =  3;
k  =  MAX(i++,  j++);

would result in k=4, i=3 and j=5. This would be highly surprising to anyone expecting MAX() to behave like a function.

So what is the correct solution? The solution is not to use macro at all. A global, inline function, like this

inline  max(int  a,  int  b)  {  return  a>b?a:b  }

has none of the pitfalls above, but will not work with all types.

(#, ##)
The # and ## operators are used with the #define macro. Using # causes the first argument after the # to be returned as a string in quotes. For example, the command
#define  as_string(  s  )  #  s

will make the compiler turn this command
puts(  as_string(  Hello  World!  )  )  ;

into
puts(  "Hello  World!"  );

Using ## concatenates what's before the ## with what's after it. For example, the command
#define  concatenate(  x,  y  )  x  ##  y
int  xy  =  10;

will make the compiler turn
printf(  "%d",  concatenate(  x,  y  ));

into
printf(  "%d",  xy);

which will, of course, display 10 to standard output.
It is possible to concatenate a macro argument with a constant prefix or suffix to obtain a valid identifier as in
#define  make_function(  name  )  int  my_  ##  name  (int  foo)  {} make_function(  bar  )
which will define a function called my_bar(). But it isn't possible to integrate a macro argument into a constant string using the concatenation operator. In order to obtain such an effect, one can use the ANSI C property that two or more consecutive string constants are considered equivalent to a single string constant when encountered. Using this property, one can write





#define  eat(  what  )  puts(  "I'm  eating  "  #what  "  today."  ) eat(  fruit  )

which the macro­processor will turn into
puts(  "I'm  eating  "  "fruit"  "  today."  )

which in turn will be interpreted by the C parser as a single string constant.

macros
Macros aren't type­checked and so they do not evaluate arguments. Also, they do not obey scope properly, but simply take the string passed to them and replace each occurrence of the macro argument in the text of the macro with the actual string for that parameter (the code is literally copied into the location it was called from).
An example on how to use a macro:
#include  <stdio.h>
#define  SLICES  8
#define  ADD(x)  (  (x)  /  SLICES  )
int  main()
{
int  a  =  0,  b  =  10,  c  =  6;
a  =  ADD(b  +  c);
printf("%d\n",  a); return  0;
}

­­ the result of "a" should be "2" (b + c = 16 ­> passed to ADD ­> 16 / SLICES ­> result is "2")
NOTE:
It is usually bad practice to define macros in headers.

A macro should be defined only when it is not possible to achieve the same result with a
function or some other mechanism. Some compilers are able to optimize code to where calls to small functions are replaced with inline code, negating any possible speed advantage. Using typedefs, enums, and inline (in C99) is often a better option.

#error
The #error directive halts compilation. When one is encountered the standard specifies that the compiler should emit a diagnostic containing the remaining tokens in the directive. This is mostly used for debugging purposes.
#error  message

#undef
The #undef directive undefines a macro. The identifier need not have been previously defined.




if,else,elif,endif (conditionals)
The #if command checks whether a controlling conditional expression evaluates to zero or nonzero, and excludes or includes a block of code respectively. For example:
#if  1
/*  This  block  will  be  included  */
#endif
#if  0
/*  This  block  will  not  be  included  */
#endif

The conditional expression could contain any C operator except for the assignment operators, increment, and decrement operators.
One unique operator used in preprocessing and nowhere else is the defined operator. It returns 1 if the macro name, optionally enclosed in parentheses, is currently defined; 0 if not.
The #endif command ends a block started by #if, #ifdef, or #ifndef.
The #elif command is similar to #if, except that it is used to extract one from a series of blocks of code. E.g.:
#if  /*  some  expression  */
   
:
:
:
#elif  /*  another  expression  */
   
:
/*  imagine  many  more  #elifs  here  ...  */
   
:
#else
/*  The  optional  #else  block  is  selected  if  none  of  the  previous  #if  or
      #elif
  blocks  are  selected  */
:
:
#endif  /*  The  end  of  the  #if  block  */

0 comments: