C Review
This lessons covers:
- A general C review
- Compiled languages
- C datatypes, both primitive and composite
- Conditionals and loops
- The C Pre-Processor
- and introducing C++
Compilation
Before recalling the C syntax and semantics, we must first remember the domain we are working in and how programs are ran on a computer system. Computers cannot understand human languages without the power and water supply of a medium-sized town, meaning there must be intermediate steps to running our human-readable code on a computer.
Compilation and Interpretation
The two main methods of getting a computer to run code are compilation and interpretation.
Interpreted languages like Python and JavaScript are executed line-by-line by an interpreter at runtime. When you run a Python script, the interpreter reads each line, translates it to machine instructions, and executes it immediately. This makes development faster since you can test code quickly, but execution tends to be slower since the machine instructions are not always written in the most efficient manner out-of-the-box.
Compiled languages, like C and C++, accomplish this task in a different way. Instead of being executed by and interpreter at runtime, C programs are compiled and converted to low-level machine code before runtime. Essentially, compiled languages are translated into machine code at compile time for the target system to produce an executable that can be ran without recompilation or interpretation. This leads to faster programs and more fine-grained control over what happens during the course of your program
This process involves four primary steps:
- Preprocessing: The C preprocessor (CPP) formats the source code using macros like
#includeor#define, applying any user-defined changes before passing to the next step - Compilation: The compiler translates C source code into assembly language
- Assembly: The assembler then converts the assembly code into machine code, producing object files,
.oor.obj, as a result - Linking: The linker orders the object files properly and combines them into a single executable program that is ready to run on the target system
The following are the commands to compile a simple C program, using the GNU C Compiler:
# Compile source.c into an executable named program
gcc source.c -o program
# Run the executable
./program
-E: Stops after preprocessing stage-S: Stops compilation after generating assembly code-c: Stops compilation after generating object files, preventing linking
Basics
Now that we have reviewed how to compile and run our programs, let's go over the the fundamental building blocks of C programming.
The following is a standard "Hello World!" program written in C:
#include <stdio.h>- This is a preprocessor macro to include thestdio.hfile at the beginning of this source code file.int main(){}- The main function, our main entry point into executing code on the computer. The compiler runs this function in the final executable, making it responsible for orchestrating all other functions.(int argc, const char* argv[])- Command line arguments. When you run the program in the command-line, you may optionally give additional parameters to feed into your program, for example:./program argument1 argument2.argcandargvare variables that contain information about the command-line arguments.argcargc- the number of arguments, including the filename of the executable- In the example above, it would take on the value 3
argv- an array of char*'s to the actual string parameters passed
printf("Hello CS240!\n");- This line represents a statement in C, an individual line of code. All statements in C must end in a semicolon;. This specific statement is calling a function fromstdio.h,printf();which allows the programmer to print information to the screen.return 0;- The return statement. When we definedmain();, we put anintidentifier before the function identifier. This defines the return type that is expected from our function, which is enforced by the compiler. When the return statement is ran the function ends and as will the entire program.
Variables and Data Types
Variables in C must be declared with a specific type before use. Unlike dynamically typed languages, C requires you to explicitly state what kind of data each variable will hold.
The following is an example of declaring variables:
#include <stdio.h>
int main(int argc, const char* argv[])
{
int num_books = 1000; // Declaring an int type with a starting value
float average_length; // Declaring a float type without a starting value
return 0;
}
Basic Data Types
C provides several primitive data types:
- Integer Types:
int,short,long,long long- Typically
intis 4 bytes (32 bits), ranging from -2,147,483,648 to 2,147,483,647 - Can be modified with
unsignedfor non-negative values only
- Typically
- Floating-Point Types:
float,doublefloatis single-precision (4 bytes)doubleis double-precision (8 bytes), preferred for most applications
- Character Type:
char- 1 byte, used for characters and small integers
- Characters are enclosed in single quotes:
'A','z','\n'
- Boolean Type:
boolwith<stdbool.h>- Values are
trueorfalseExample declarations:
- Values are
Composite Data Types
Beyond basic types, C allows you to build more complex data structures:
Arrays: Contiguous blocks of memory holding multiple elements of the same type
int numbers[5] = {10, 20, 30, 40, 50};
char name[20] = "Alice"; // Strings are char arrays
// Access elements by index (zero-indexed)
int first = numbers[0]; // 10
numbers[2] = 35; // Modify third element
Structures: Group related data of different types
struct Student {
char name[50];
int id;
double gpa;
};
struct Student alice;
alice.id = 12345;
alice.gpa = 3.8;
strcpy(alice.name, "Alice Johnson");
Pointers: Variables that store memory addresses
int value = 42;
int *ptr = &value; // ptr stores the address of value
printf("Value: %d\n", *ptr); // Dereference to get value (42)
*ptr = 100; // Modify value through pointer
printf("New value: %d\n", value); // Prints 100
Conditionals
Conditionals allow your program to make decisions based on conditions.
If-Else Statements
int score = 85;
if (score >= 90) {
printf("Grade: A\n");
} else if (score >= 80) {
printf("Grade: B\n");
} else if (score >= 70) {
printf("Grade: C\n");
} else {
printf("Grade: F\n");
}
Switch Statements: Useful for multiple discrete cases
char operation = '+';
int a = 10, b = 5;
switch (operation) {
case '+':
printf("Result: %d\n", a + b);
break;
case '-':
printf("Result: %d\n", a - b);
break;
case '*':
printf("Result: %d\n", a * b);
break;
default:
printf("Unknown operation\n");
}
break statement is crucial in switch cases to prevent fall-through to subsequent cases.
Loops
Loops enable repetition of code blocks.
For Loops: Best when the number of iterations is known
// Print numbers 0 through 9
for (int i = 0; i < 10; i++) {
printf("%d ", i);
}
// Iterate through an array
int arr[5] = {2, 4, 6, 8, 10};
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
While Loops: Best when the number of iterations is unknown
Do-While Loops: Execute at least once, then check condition
The C Pre-Processor (CPP)
The C preprocessor runs before compilation and handles directives that begin with #. These directives modify your source code before it's compiled.
Include Directive: Imports header files
#include <stdio.h> // System header (standard library)
#include "myheader.h" // User-defined header (local file)
Define Directive: Creates macros and constants
#define PI 3.14159
#define MAX_SIZE 100
#define SQUARE(x) ((x) * (x))
// Usage
double area = PI * SQUARE(radius);
SQUARE(x) to avoid unexpected behavior with operator precedence.
Conditional Compilation: Include or exclude code based on conditions
#define DEBUG
#ifdef DEBUG
printf("Debug: x = %d\n", x);
#endif
#ifndef MAX_VALUE
#define MAX_VALUE 1000
#endif
File I/O in C
Programs often need to read from and write to files for persistent data storage. C provides a standard library interface for file operations through <stdio.h>.
Opening and Closing Files
Before reading or writing, you must open a file using fopen():
#include <stdio.h>
int main() {
FILE *file = fopen("data.txt", "r"); // Open for reading
if (file == NULL) {
printf("Error: Could not open file\n");
return 1;
}
// Use the file...
fclose(file); // Always close when done
return 0;
}
File modes determine how the file is accessed:
"r"- Read mode (file must exist)"w"- Write mode (creates new file or overwrites existing)"a"- Append mode (writes to end of file)"r+"- Read and write (file must exist)"w+"- Read and write (overwrites existing file)"a+"- Read and append
Always check if fopen() returns NULL, which indicates the file couldn't be opened. Common reasons include the file not existing (for read mode) or lacking permissions.
Reading from Files
Character by character:
FILE *file = fopen("input.txt", "r");
char ch;
while ((ch = fgetc(file)) != EOF) {
printf("%c", ch);
}
fclose(file);
Line by line:
FILE *file = fopen("input.txt", "r");
char line[256];
while (fgets(line, sizeof(line), file) != NULL) {
printf("%s", line);
}
fclose(file);
fgets() reads up to n-1 characters or until a newline, whichever comes first. The newline character is included in the string if encountered.
Formatted input:
FILE *file = fopen("scores.txt", "r");
char name[50];
int score;
while (fscanf(file, "%s %d", name, &score) == 2) {
printf("%s scored %d\n", name, score);
}
fclose(file);
fscanf() works like scanf() but reads from a file instead of standard input. It returns the number of items successfully read.
Writing to Files
Character by character:
FILE *file = fopen("output.txt", "w");
fputc('H', file);
fputc('i', file);
fputc('\n', file);
fclose(file);
Strings:
FILE *file = fopen("output.txt", "w");
fputs("Hello, World!\n", file);
fputs("Writing to a file.\n", file);
fclose(file);
Formatted output:
FILE *file = fopen("results.txt", "w");
fprintf(file, "Student: %s\n", "Alice");
fprintf(file, "Score: %d\n", 95);
fprintf(file, "GPA: %.2f\n", 3.87);
fclose(file);
fprintf() works like printf() but writes to a file instead of standard output.
Practical Example
Here's a complete program that reads student data from a file and calculates the average score:
#include <stdio.h>
int main() {
FILE *input = fopen("students.txt", "r");
if (input == NULL) {
printf("Error opening input file\n");
return 1;
}
char name[50];
int score;
int total = 0;
int count = 0;
while (fscanf(input, "%s %d", name, &score) == 2) {
printf("%s: %d\n", name, score);
total += score;
count++;
}
fclose(input);
if (count > 0) {
double average = (double)total / count;
FILE *output = fopen("summary.txt", "w");
fprintf(output, "Total students: %d\n", count);
fprintf(output, "Average score: %.2f\n", average);
fclose(output);
printf("\nSummary written to summary.txt\n");
}
return 0;
}
Remember: always close files with fclose() when finished. Failing to do so can result in data loss or resource leaks. The file pointer (FILE *) keeps track of your position in the file and manages buffering for efficient I/O operations.
C and C++
While this course uses both C and C++, it's important to understand their relationship.
C++ was developed as an extension of C, adding features like classes, objects, templates, and exceptions while maintaining most of C's syntax. This means valid C code is often valid C++ code, but not always.
The following are some immediate differences you'll have to work around.
Function Declarations: C++ requires function prototypes before use; C is more lenient
Input/Output: C++ introduces iostream with cin and cout, though C's printf and scanf still work
Type Safety: C++ is stricter about type conversions
// Valid in C, error in C++
int *ptr = malloc(sizeof(int));
// C++ requires explicit cast
int *ptr = (int*)malloc(sizeof(int));
// Better: use C++ new operator
int *ptr = new int;
As we progress through the course, we'll build on your C foundation to explore C++'s object-oriented features, which provide powerful tools for organizing complex programs and implementing sophisticated data structures.
For now, ensure you're comfortable with these C fundamentals. They form the basis for everything we'll build in the coming weeks and we will explore more deeply on these differences later.