CP264 Notes: C Basics (Cont'd)


Command Line Arguments

See: Using Eclipse / CDT


Functions

Functions are used in C are they are in any other high-level programming language: code reuse, modularized design, organization, and libraries.

Function Definitions and Passing Arguments by Value:

#include <stdio.h>  // library header

int w;               // global variable, visible from anywhere

// prototypes - must appear before their call
void function1();    // function prototype: no parameter, without return value
int function2(int);  // function prototype: int parameter, int return value

int main(int argc, char *argv[]) {
    setbuf(stdout, NULL);
    int x = 2;       // local variable
    w = 1;
    function1();     // function call
    int y = function2(x); // function call with argument and assign return value
    printf("%d, %d\n", w, y);
    return (0);
}

void function1() {   // function definition: no parameter, without return value
    int y = 2;       // local variable
    w += y;
    return;
}

int function2(int x) {    // function definition: int parameter, int return value
    int z = x + 2;
    function1();     // call another function
    return (z);      // return value
}

Pass Arguments by Reference

You cannot change the contents of a parameter permanently in a function. Any changes made to the value of the parameter are lost when the function ends. You can, however, pass the address of a value to a function, and change the value at that address. (You cannot change the address - you can think of it as the value being passed to the function, and you cannot permanently change the value passed to a function.)

To specify pass-by-reference, use:

& reference
the address of a variable
* dereference
the value stored at the address given
// prototypes
void add(int *, int);

int main(int argc, char *argv[]) {
    setbuf(stdout, NULL);
    int n = 2, j = 2;
    add(&n, j);  // pass reference to n, value of j
    printf("n = %d\n", n);  //output: 3
    printf("j = %d\n", j);  //output: 2
    return (0);
}

void add(int *x, int y) {
    *x = *x + 1;  // dereference x - value at address changed
    y = y + 1;    // y will be unchanged
    return;
}

Example: swap values of two variables.

// prototypes
void swap(int*, int*);

int main(int argc, char *argv[]) {
    setbuf(stdout, NULL);
    int i = 5, j = 10;
    printf("before swap i = %d, j=%d\n", i, j);
    swap(&i, &j);
    printf("after swap  i = %d, j=%d\n", i, j);
    return (0);
}

void swap(int *first, int *second) {
    int temp = *first;
    *first = *second;
    *second = temp;
    return;
}

Recursion

C supports recursion. C supports fruitful recursion, in-place recursion, tail recursion, and tree recursion. A given algorithm may require auxiliary recursive functions. Example:

// prototypes
int factorial(int n);

int main(int argc, char *argv[]) {
    setbuf(stdout, NULL);
    int n = 1, y = 1;
    printf("Input an integer: ");
    scanf("%d", &n);
    y = factorial(n);
    printf("%d! = %d\n", n, y);
    return (0);
}

int factorial(int n) {
    int result = 1;

    if (n > 1) {
        result = (n * factorial(n - 1));
    }
    return (result);
}

Libraries

stdio Input/Output Library

Supports the printf (print formatted) function.

void printf("format control string"[, value][, value]… )

Format Control

%[flag][size][.precision]conversion char

From right to left:

conversion char
determines type of value to be printed - required
precision
number of decimal places
size
width of print field
flag
one of:
0 for left 0 fill - numbers only
- for left justification

If a print width is defined, all values are printed right justified within that width unless the - (left justification) flag is set.

conversion char description
%c character
%d decimal (integer) number (base 10)
%e exponential floating-point number
%f floating-point number
%i integer (base 10)
%o octal number (base 8)
%s a string
%u unsigned decimal (integer) number
%x number in hexadecimal (base 16)
%% print a percent sign
\% print a percent sign

Example code (the vertical bars '|' are there only to make the print width clear:

float v = 99.9;
printf("|%f|\n", v);
printf("|%12f|\n", v);
printf("|%12.2f|\n", v);
printf("|%012.2f|\n", v);
printf("|%-12.2f|\n", v);
char *name = "David";
printf("|%s|\n", name);
printf("|%12s|\n", name);
printf("|%-12s|\n", name);

Example result:

|99.900002|
|   99.900002|
|       99.90|
|000000099.90|
|99.90       |
|David|
|       David|
|David       |

(Where did the 00002 come from?)

Supports the scanf (scan formatted) function.

int scanf("format control string"[, reference][, reference]… )

scanf uses the same codes as printf to tell the program how to treat the values being read. The code and the data type must match. The following example reads an integer, a float, and a character from the keyboard - the values are space delimited:

    int a;
    float b;
    char c;
        int count;

    count = scanf("%d %f %c", &a, &b, &c);

The count returned by the function is the number of values correctly read. The previous example should return a 3 if all three values are properly read. If some values don't fit the formats, then scanf returns a value less than 3. Unfortunately, this doesn't tell us which of the values was not read properly. However, it is still helpful. Example:

    setbuf(stdout, NULL);
    printf("Enter int, float, char: ");
    count = scanf("%d %f %c", &a, &b, &c);

    while (count < 3) {
        while (getchar() != '\n') // get rid of non-end-of-line characters
            ;
        printf("Invalid input\n");
        printf("Enter int, float, char: ");
        count = scanf("%d %f %c", &a, &b, &c);
    }

Pre-Processor

Preprocessor directives start with #. The preprocessor resolves all preprocessor directives during pre-processing Four commonly used cases:

Include Files
#include <file>  // predefined directory - on project path

#include "file"  // user-defined file, relative to current directory

Examples

#include  <stdio.h>

#include "myheader.h"
Macro Definitions (#define, #undef)

A macro is an identifier followed by text. Generally, an identifier must start with a letter and may contain only letters, numbers, and the underscore. It is followed by replacement text, i.e. the text that is to be written into the code in place of the identifier text. Macros can have parameters, which are comma-separated inside parentheses (and no spaces). Parameters are replaced by the matching values and variables when replaced by the preprocessor. Example:

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

int x = 10, y = 12;
int c = GETMAX(x, y);  // macro replaced by x > y ? x : y
printf("%d\n", c);

Macros are powerful, but dangerous. In this example:

#define DOUBLE(a) a * 2

int x = 10;
int c = DOUBLE(x);  // macro replaced by x * 2

Imagine now that the call was:

#define DOUBLE(a) a * 2

int x = 10;
int c = DOUBLE(5 + x);  // macro replaced by 5 + x * 2

The replacement is a text replacement - the values are not calculated until runtime. Clearly, 5 + x * 2 is not the same as (5 + x) * 2. One fix is to use parentheses to enforce priority:

#define DOUBLE((a)) a * 2

int x = 10;
int c = DOUBLE(5 + x);  // macro replaced by (5 + x) * 2

which gives us the answer we want. The second is not to use a macro when it is possible such problems may occur.

Conditional Compilation
#if
#ifdef    // if defined
#ifndef   // if not defined
#endif
#else
#elif     // else if

Example

    int a;

#ifdef MAX
    a = MAX;
#else
    a = 0;
#endif

Example

#ifndef MAX
#define MAX 100
#endif
    int a = MAX;
Define Constants

Use #define for constant values in the source code. This helps with repetition and readability. Examples:

#define Pi 3.1415926
#define FALSE 0
#define TRUE 1
#define MAXSIZE 10
#define RESPONSE 'C'
#define PROMPT "Enter C to continue:"