Relearning C - Chapter 1


Chapter 1 - A Tutorial Introduction

1.1 Getting Started

The only way to learn a new programming language is by writing programs in it. The first program to write is the same for all languages:

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

The main function is declared with the return type of an int. The return value is 0 after running the printf statement. Printf is included in the stdio header file.

1.2 Variables and Arithmetic Expressions

The next program uses the formula C = (59)(F-32) to print the following table of Fahrenheit temperatures and their centigrade or Celsius equivalents.

 1 -17
 20 -6
 40 4
 60 15
 80 26
 100 37
 120 48
 140 60
 160 71
 180 82
 200 93
 220 104
 240 115
 260 126
 280 137
 300 148

The program itself still consists of the definition of a single function named main.. It is longer than the one that printed hello, world, but not complicated. It introduces several new ideas, including comments, declarations, variables, arithmetic expressions, loops, and formatted output.

#include <stdio.h>

main() {
    int fahr, celsius;
    int lower, upper, step;

    lower = 0;
    upper = 300;
    step = 20;

    fahr = lower;
    while (fahr <= upper) {
        celsius = 5 * (fahr-32) / 9;
        printf("%d\t$d\n", fahr, celsius);
        fahr = fahr + step;
    }
}

The C Programming Language provides several other data types besides int and float, including:

Type Explanation
char character - a single byte
short short integer
long long integer
double double-precisions floating point

The size of these objects is also machine-dependent. There are also arrays, structures and u8nions of these basic types, pointers to them, and functions that return them, all of which we will meet in due course.

1.3 The for Statement

There are plenty of different ways to write a program for a particular task. Let’s try a variation on the temperature converter.

#include <stdio.h>

main() {
    int fahr; 
    for (fahr = 0; fahr <= 300; fahr = fahr + 20) {
        printf("%3d %6.1f\n", fahr, (5.0/9.0)*(fahr-32));
    }
}

The choice between while and for is arbitrary, based on which seems cleaer. The for is usually appropriate for loops in which the initialisation and increment are single statements and logically related, since it is more compacy than while and it keeps the loop control statements together in one place.

Exercise 1-5. Modify the temperature conversion program to print the table in reverse order, that is, from 300 degrees to 0.

#include <stdio.h>

main() {
    int fahr;
    for (fahr = 300; fahr >= 0; fahr = fahr - 20) {
        printf("%3d %6.1f\n", fahr, (5.0/9.0)*(fahr-32));
    }
}

1.5 Character Input and Output

We are going to consider a family of related programs for processing character data. You will find that many programs are just expanded versions of the prototypes that we discuss here.

The model of input and output supported by the standard library is very simple. Text input or output, regardless of where it originates or where it goes to, is dealt with as streams of characters. A text stream is a sequence of characters divided into lines; each line consists of zero or more characters followed by a newline character. It is the responsibility of the lbirary to make each input or output stream confirm this model; the C programmer using hte library need not worry about how lines are represented outside the program.

The standard library provides several functions for reading or writing one character at a time, of which getchar and putchar are the simplest.. Each time it is called, getchar reads the next input characters from a text stream and returns that as its value That is, after

c = getchar();

the variable c contains the next character of input. The characters normally come from the keyboard.

The function putchar prints a character each time it is calle:

putchar(c);

prints the content of the integer variable c as a character, usually o nthe screen. Calls to putchar and printf may be interleaved; the output will appear in the order in which the calls are made.

1.5.1 File Copying

Given getchar and putchar, you can write a surprising amount of useful code without knowing anything more about input and output. The simplest example is a program that copies its input to its output one character at a time:

read a character
   while (character is not end-of-file indicator)
    output the character just read
    read a character

Converting into C gives:

#include <stdio.h>

main() {
    int c;
    c = getchar();
    while (c != EOF) {
        putchar(c);
        c = getchar();
    }
}

The program for copying would be written more concisely by experienced C programmers. In C, any assignment, such as

c = getchar();

is an expression and has a value, which is the value of the left hand side after the assignment. This means that an assignment can appear as part of a larger expression, If the assignment of a character to c is put inside the test part of a while loop, the copy program can be written this way:

#include <stdio.h>

main() {
    int c;
    
    while ((c = getchar()) != EOF) {
        putchar(c);
    }
}

The while gets a character, assigns it to c, and then tests whether the character was the end-of-file signal. If it was not, the body of the while is executed, printing the character. The while then repeats. When the end of the input is finally reached, the while terminates and so does main.

1.5.3 Line Counting

The next program counts input lines. As we mentioned above, the standard library ensures that an input text stream appears as a sequence of lines, each terminated by a newline. Hence, counting lines is just counting newlines:

#include <stdio.h>

main() {
    int c, nl;

    nl = 0;
    while ((c = getchar()) != EOF) {
        if (c == '\n') {
            ++nl;
        }
    }
    printf("%d\n", nl);
}

Exercise 1-8. Write a program to count blanks, tabs, and newlines.

#include <stdio.h>

main() {
    int c, nl, blanks, tabs;
    nl = 0;
    tabs = 0;
    blanks = 0;
    while ((c = getchar()) != EOF) {
        if (c == '\n') {
            nl++;
        } if (c == '\t') {
            tabs++;
        } if (c == ' ') {
            blanks++;
        }
    }
    printf("Blanks: %d\n", blanks);       // Print count of blanks
    printf("Tabs: %d\n", tabs);           // Print count of tabs
    printf("Newlines: %d\n", newlines);   // Print count of newlines
}

1.6 Arrays

This is how to write a program that counts the number of occurrences of each digit, of white space characters (blank, tab, newline), and of all other characters.

#include <stdio.h>

main() {
    int c, i, nwhite, nother;
    int ndigit[10]; // declare an array of integers that can store up to 10 elements.

    nwhite = nother = 0;
    for (i = 0; i < 10; i++) {
        ndigit[i] = 0;
    }
    while ((c = getchar()) != EOF) {
        if (c >= '0' && c <= '9') {
            ++ndigit[c-'0'];
        } else if (c == ' ' || c == '\n' || c == '\t') {
            ++nwhite;
        } else {
            ++nother;
        }
    }
    printf("digits = ");
    for (i = 0; i < 10; ++i) {
        printf("%d", ndigit[i]);
    }
    printf(", white space = %d, other = %d\n", nwhite, nother);
}

Exercise 1-13. Write a program to print a histogram of the lengths of words in its input. It is easy to draw the histogram with the bars horizontal; a vertical orientation is more challenging.

#include <stdio.h>

#define MAX_WORD_LENGTH 20  // Maximum word length tracked
#define IN  1               // Inside a word
#define OUT 0               // Outside a word

int main() {
    int c, state, word_length;
    int lengths[MAX_WORD_LENGTH + 1] = {0};  // Array to store word length counts
    int max_count = 0;  // Track the maximum count for scaling

    state = OUT;
    word_length = 0;

    // Count word lengths
    while ((c = getchar()) != EOF) {
        if (c == ' ' || c == '\n' || c == '\t') {
            if (state == IN) {  // End of a word
                state = OUT;
                if (word_length > 0) {
                    if (word_length <= MAX_WORD_LENGTH) {
                        lengths[word_length]++;
                    } else {
                        lengths[MAX_WORD_LENGTH]++;  // Group overflow lengths
                    }
                }
                word_length = 0;
            }
        } else {
            state = IN;
            word_length++;
        }
    }

    // Find the maximum count for scaling
    for (int i = 1; i <= MAX_WORD_LENGTH; i++) {
        if (lengths[i] > max_count) {
            max_count = lengths[i];
        }
    }

    // Print the vertical histogram
    for (int row = max_count; row > 0; row--) {
        for (int i = 1; i <= MAX_WORD_LENGTH; i++) {
            if (lengths[i] >= row) {
                printf("  # ");
            } else {
                printf("    ");
            }
        }
        printf("\n");
    }

    // Print the x-axis labels
    for (int i = 1; i <= MAX_WORD_LENGTH; i++) {
        printf("%3d ", i);
    }
    printf("\n");

    return 0;
}

Exercise 1-14. Write a program to print a histogram of the frequencies of different characters in its input.

#include <stdio.h>

#define MAX_CHARS 128  // Maximum number of ASCII characters

int main() {
    int c, i, max_freq = 0;
    int frequencies[MAX_CHARS] = {0};  // Array to store character frequencies
    int used_chars[MAX_CHARS] = {0};  // Tracks which characters are used
    int unique_count = 0;  // Count of unique characters

    // Read input and count frequencies
    while ((c = getchar()) != EOF) {
        if (c >= 0 && c < MAX_CHARS) {
            if (frequencies[c] == 0) {
                used_chars[unique_count++] = c;  // Add to the list of used characters
            }
            frequencies[c]++;
        }
    }

    // Find the maximum frequency for scaling
    for (i = 0; i < unique_count; i++) {
        if (frequencies[used_chars[i]] > max_freq) {
            max_freq = frequencies[used_chars[i]];
        }
    }

    // Print the histogram
    for (int row = max_freq; row > 0; row--) {
        for (i = 0; i < unique_count; i++) {
            if (frequencies[used_chars[i]] >= row) {
                printf("  # ");
            } else {
                printf("    ");
            }
        }
        printf("\n");
    }

    // Print x-axis labels
    for (i = 0; i < unique_count; i++) {
        printf("  %c ", used_chars[i]);
    }
    printf("\n");

    return 0;
}

1.7 Functions

In C, a function is equivalent to a subroutine or function in Fortran. It allows for encapsulation and keep code clean.

Here is the function power and a main program to exercise it, so you can see the whole structure at once.

#include <stdio.h>

int power(int m, int n);

int main() {
    int i;

    for (i = 0; i < 10; ++i) {
        printf("%d %d $d\n", i, power(2,i), power(-3, i));
    }
    return 0;
}

int power(int base, int n) {
    int i, p;
    p = 1;
    for (i = 1; i <= n; ++i) {
        p = p * base;
    }
    return p;
}

A function definition has the form:

return-type function-name(parameter declarations, if any) {
    declarations
    statements
}

1.9 Character Arrays

The most common type of array in C is the array of characters.

while (there is another line)
    if (it is longer than the previous longest)
        (save it)
        (save its length)
print longest line
#include <stdio.h>
#define MAXLINE 1000

int getline(char line[], int maxline);
void copy(char to[], char from[]);

int main() {
    int len;
    int max;
    char line[MAXLINE];
    char longest[MAXLINE];

    max = 0;
    while ((len = getline(line, MAXLINE)) > 0) {
        if (len > max) {
            max = len;
            copy(longest, line);
        }
    }
    if (max > 0) {
        printf("%s", longest);
    }
    return 0;
}

int getline(char s[], int lim) {
    int c, i;

    for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i) {
        s[i] = c;
    }
    if (c == '\n') {
        s[i] = c;
        ++i;
    }
    s[i] = '\0';
    return i;
}

void copy(char to[], char from[]) {
    int i;
    i = 0;
    while ((to[i] = from[i]) != '\0') {
        ++i;
    }
}

A string in C is stored as such:

h e l l o \n \0

Exercise 1-19. Write a function reverse(s) that reverses the character string s.

#include <stdio.h>
#include <string.h>

#define MAXLINE 1000

void reverse(char s[]) {
    int length = strlen(s);
    printf("strlen: %d\n", strlen(s));
    int i, temp;

    // Example with hello.
    printf("length: %d\n", length);
    for (i = 0; i < length / 2; i++) {
        temp = s[i]; // temp = h (first iteration)
        s[i] = s[length - i - 1]; // s[0] = s[6 - 0 - 1], s[0] = s[5] = \n
        s[length - i - 1] = temp; // temp = s[6 - 0 - 1], temp = \n
    }
}

int main() {
    char line[MAXLINE];

    while (fgets(line, MAXLINE, stdin) != NULL) {
        size_t len = strlen(line);
        if (len > 0 && line[len - 1] == '\n') {
            line[len - 1] == '\0';
        }
        reverse(line);
        printf("%s\n", line);
    }
    return 0;
}