Skip to main content

Lecture 1: Variables

Prerequisites: Introduction (programme structure, printf, scanf, compilation workflow).

1. Overview

  • This chapter addresses the question of how C programmes store, name, and manipulate data through the mechanisms of variables, data types, constants, and operators.
  • Upon completion, you will be able to declare variables of appropriate types, perform arithmetic with correct precedence, define constants, and apply both implicit and explicit type conversions.

2. Learning Outcomes

Upon completing this chapter, you should be able to:

  1. Distinguish between variables and constants, and explain the role each plays in a C programme.
  2. Apply the rules for naming identifiers and follow established naming conventions.
  3. Select appropriate data types (int, float, double, char) based on the nature of the data to be stored, and describe their typical sizes and value ranges.
  4. Declare, assign, and initialise variables correctly, understanding the memory model that underpins each operation.
  5. Use the five arithmetic operators (+, -, *, /, %) and evaluate expressions according to the rules of operator precedence and associativity.
  6. Define constants using both #define and const, and articulate the differences between the two approaches.
  7. Explain implicit type conversion (promotion) and perform explicit type casting, recognising situations in which data loss may occur.

3. Key Terms

Term (EN)中文Definition / Notes
Variable变量A named storage location in memory that holds a value which may change during programme execution. Every variable has a type, a name, an address, and a value.
Constant常量A value that cannot be altered after it is defined. May be established via #define (preprocessor macro) or const (typed, read-only variable).
Literal字面量A fixed value written directly in the source code, such as 42 (integer literal), 3.14 (floating-point literal), 'A' (character literal), or "hello" (string literal).
Identifier标识符The name given to a variable, function, or other user-defined entity. Must begin with a letter or underscore, followed by letters, digits, or underscores. Case-sensitive.
Keyword (reserved word)关键字(保留字)A word with a predefined meaning in C (e.g., int, return, while) that cannot be used as an identifier.
Data type数据类型A classification that specifies the kind of value a variable can hold and the amount of memory allocated for it (e.g., int, float, double, char).
Type modifier类型修饰符A qualifier that alters the size or sign of a base type: signed, unsigned, short, long.
Declaration声明The act of informing the compiler of a variable's name and type, e.g., int age;. Memory is reserved but the value is indeterminate.
Assignment赋值The act of storing a value in a previously declared variable, e.g., age = 25;.
Initialisation初始化Declaring and assigning a value in a single statement, e.g., int age = 25;.
Operator precedence运算符优先级The hierarchy that determines the order in which operators are evaluated within an expression.
Associativity结合性The direction (left-to-right or right-to-left) in which operators of equal precedence are evaluated.
Implicit conversion隐式类型转换An automatic type conversion performed by the compiler when operands of different types appear in the same expression (e.g., int promoted to float).
Explicit casting显式类型转换(强制类型转换)A programmer-directed type conversion using the cast operator, e.g., (int)3.14.
ASCIIASCII 字符编码American Standard Code for Information Interchange — a character encoding in which each character is represented by a numeric value (e.g., 'A' = 65).
sizeofsizeof 运算符A compile-time operator that returns the size, in bytes, of a type or variable.

4. Core Concepts

4.1 Variables and Constants

A variable is a named region of memory that stores a value of a particular type. You may think of it as a labelled box: the label is the variable's name, the size of the box is determined by its type, and the contents of the box are its current value. During programme execution the contents may change — hence the term "variable".

A constant, by contrast, is a value that is fixed once established and cannot be modified thereafter. Constants serve two important purposes: they make code more readable (the name PI is more meaningful than the bare number 3.14159) and they prevent accidental modification of values that should remain unchanged.

Consider the following declarations:

int age = 25; /* variable — value may change later */
const float pi = 3.14f; /* constant — value is fixed */

The compiler enforces the immutability of pi; any attempt to write pi = 3.0; after its declaration will produce a compilation error.

Minimal example File: p01_var_const.c Build: gcc p01_var_const.c -o p01_var_const Run: ./p01_var_const

#include <stdio.h>

int main() {
int age = 25;
const float pi = 3.14159f;

printf("Age: %d\n", age);
printf("Pi: %.5f\n", pi);

age = 26; /* permitted — age is a variable */
printf("Age next year: %d\n", age);

/* pi = 3.0; — would cause a compilation error */

return 0;
}

What you should see: The programme prints the initial age, the value of pi, and the updated age.

Takeaway: Variables hold mutable data; constants hold immutable data. Prefer constants for values that should never change.

4.2 Literal Constants

A literal constant is a fixed value that appears directly in the source code. Unlike a variable, a literal has no name and no memory address that you can refer to; it is simply a value embedded in an expression or assignment.

C recognises four principal categories of literals:

  • Integer literals: whole numbers written in decimal (e.g., 42), octal (e.g., 052, prefixed with 0), or hexadecimal (e.g., 0x2A, prefixed with 0x).
  • Floating-point literals: decimal numbers with a fractional part (e.g., 3.14) or in scientific notation (e.g., 3.14e2 for 314.0). By default these are of type double; appending f (e.g., 3.14f) makes them float.
  • Character literals: a single character enclosed in single quotes (e.g., 'A'). Internally stored as an integer equal to the character's ASCII code.
  • String literals: a sequence of characters enclosed in double quotes (e.g., "Hello, World!"). These are stored as arrays of char terminated by a null character '\0' (covered in detail in Lecture 4).

Takeaway: Literals are the raw values from which expressions are built; understanding their types helps avoid subtle bugs in arithmetic and assignment.

4.3 Naming Identifiers

Every variable, function, and user-defined type in C is referred to by an identifier — a name chosen by the programmer. The C language imposes strict rules on what constitutes a valid identifier and, beyond those rules, the programming community observes conventions that promote clarity and consistency.

Rules (enforced by the compiler):

  1. An identifier must begin with a letter (AZ, az) or an underscore (_).
  2. Subsequent characters may be letters, digits (09), or underscores.
  3. No spaces, hyphens, or special characters ($, %, *, #, etc.) are permitted.
  4. Identifiers are case-sensitive: age, Age, and AGE are three distinct names.
  5. Reserved keywords (int, float, return, while, etc.) cannot be used as identifiers.
  6. Since C99, the compiler guarantees significance of at least 63 characters for internal identifiers and 31 for external ones.

Conventions (community practice):

  • Begin variable names with a lowercase letter.
  • Use meaningful, descriptive names: surface_area rather than sa.
  • Separate words with underscores (surface_area) or use camelCase (surfaceArea); be consistent within a project.
  • Use ALL_UPPERCASE for symbolic constants defined with #define (e.g., #define MAX_AGE 200).

Quick self-test — which of the following are legal identifiers?

CandidateLegal?Reason
AREAYesBegins with a letter; all uppercase is valid.
3DNoBegins with a digit.
Last-ChanceNoContains a hyphen.
x_yt3YesLetters, underscore, digit — all permitted.
num$NoContains the special character $.
lucky**NoContains the special character *.
area_under_the_curveYesLong but perfectly valid.
num42YesDigit is not at the start.
#valuesNoBegins with #.
piYesShort, lowercase, meaningful.
%doneNoBegins with %.

Takeaway: Choose names that are valid, meaningful, and consistent with established conventions; your future self (and your collaborators) will thank you.

4.4 Data Types and Their Sizes

Every variable in C has a data type that determines two things: (a) the set of values the variable can represent, and (b) the amount of memory allocated for it. The four fundamental types are:

TypePurposeTypical sizeTypical range
charA single character (or small integer)1 byte−128 to 127 (signed) or 0 to 255 (unsigned)
intAn integer4 bytes−2,147,483,648 to 2,147,483,647
floatA single-precision floating-point number4 bytes≈ ±3.4 × 10³⁸ (6–7 significant digits)
doubleA double-precision floating-point number8 bytes≈ ±1.7 × 10³⁰⁸ (15–16 significant digits)

These sizes are typical on modern 64-bit systems but are not guaranteed by the C standard; they may vary by platform. To determine the actual size on your system, use the sizeof operator.

The header <limits.h> defines macros for the minimum and maximum values of integer types (e.g., INT_MIN, INT_MAX, CHAR_MIN, CHAR_MAX, UINT_MAX), and <float.h> provides analogous macros for floating-point types (e.g., FLT_MIN, FLT_MAX, FLT_DIG).

Minimal example File: p01_sizes.c Build: gcc p01_sizes.c -o p01_sizes Run: ./p01_sizes

#include <stdio.h>
#include <limits.h>
#include <float.h>

int main() {
printf("int: %zu bytes, range [%d, %d]\n",
sizeof(int), INT_MIN, INT_MAX);
printf("unsigned int: %zu bytes, range [0, %u]\n",
sizeof(unsigned int), UINT_MAX);
printf("char: %zu byte, range [%d, %d]\n",
sizeof(char), CHAR_MIN, CHAR_MAX);
printf("float: %zu bytes, precision %d digits\n",
sizeof(float), FLT_DIG);
printf("double: %zu bytes, precision %d digits\n",
sizeof(double), DBL_DIG);

return 0;
}

What you should see: The sizes and ranges of each type on your system, confirming the typical values listed above.

Takeaway: Always choose the narrowest type that can represent your data; use sizeof and the macros in <limits.h> / <float.h> to verify assumptions.

4.5 The ASCII Character Encoding

C does not have a dedicated "character" type in the way that some higher-level languages do. Instead, char is simply a small integer type, and characters are represented by their numeric codes as defined by the ASCII standard. This means that character values can participate in arithmetic: for example, 'A' + 1 evaluates to 66, which is the ASCII code for 'B'.

Some particularly useful ASCII relationships to remember:

  • Uppercase letters 'A' through 'Z' occupy codes 65–90.
  • Lowercase letters 'a' through 'z' occupy codes 97–122.
  • The digit characters '0' through '9' occupy codes 48–57.
  • The difference between a lowercase letter and its uppercase counterpart is always 32 (e.g., 'a' - 'A' == 32).

Minimal example File: p01_ascii.c Build: gcc p01_ascii.c -o p01_ascii Run: ./p01_ascii

#include <stdio.h>

int main() {
int ch = 'B'; /* ASCII value 66 */
int next_ch = ch + 1; /* 67 = 'C' */
int prev_ch = ch - 1; /* 65 = 'A' */

printf("ch = %c (ASCII %d)\n", ch, ch);
printf("next_ch = %c (ASCII %d)\n", next_ch, next_ch);
printf("prev_ch = %c (ASCII %d)\n", prev_ch, prev_ch);

return 0;
}

What you should see: ch = B (ASCII 66), next_ch = C (ASCII 67), prev_ch = A (ASCII 65).

Takeaway: Characters in C are integers; arithmetic on character values is both legal and frequently useful.

4.6 Type Modifiers: signed, unsigned, short, long

The four base types can be qualified with type modifiers that alter either the range of representable values or the amount of memory allocated:

  • signed (default for int and char): the variable can hold both negative and positive values. One bit is used for the sign, reducing the positive range by half.
  • unsigned: the variable holds only non-negative values (zero and positive). The full bit-width is available for magnitude, effectively doubling the positive range compared with the signed variant.
  • short: requests a smaller integer type, typically 2 bytes. Useful when memory is limited and the value range is known to be small.
  • long: requests a larger integer type, typically 8 bytes on 64-bit systems. Necessary when values exceed the range of int.

These modifiers may be combined: unsigned long int is a valid (and common) type. When the base type is int, it may be omitted for brevity: unsigned long is equivalent to unsigned long int.

Takeaway: Use modifiers deliberately — unsigned when negative values are impossible, long when the range of int is insufficient — but always verify sizes with sizeof if portability matters.

4.7 Declaration, Assignment, and Initialisation

Working with variables in C involves up to three distinct operations, which beginners often conflate.

Declaration reserves memory for a variable of a specified type and associates it with a name. The syntax is:

<data_type> <variable_name>;

For example, int age; instructs the compiler to allocate space for an integer and label it age. Crucially, if you do not provide an initial value, the contents of that memory are indeterminate — they contain whatever data happened to be there previously. Reading an uninitialised variable is undefined behaviour and a frequent source of bugs.

Assignment stores a value in a previously declared variable:

<variable_name> = <expression>;

The = operator evaluates the expression on the right-hand side and places the result into the variable on the left-hand side. Only a single variable (an lvalue) may appear on the left.

Initialisation combines declaration and assignment in a single statement:

<data_type> <variable_name> = <value>;

For example, int age = 25; both declares age and sets it to 25. Initialisation at the point of declaration is strongly recommended, as it eliminates the risk of using an uninitialised variable.

Minimal example File: p01_decl.c Build: gcc p01_decl.c -o p01_decl Run: ./p01_decl

#include <stdio.h>

int main() {
/* Declaration */
int my_age;
float salary;
char gender;

/* Assignment */
my_age = 25;
salary = 10000.50f;
gender = 'M';

/* Output */
printf("My Age: %d\n", my_age);
printf("Salary: %.2f\n", salary);
printf("Gender: %c\n", gender);

return 0;
}

What you should see: My Age: 25, Salary: 10000.50, Gender: M.

Takeaway: Declare variables with meaningful names, initialise them immediately where possible, and never read a variable before it has been assigned a definite value.

4.8 Constants: #define versus const

C provides two mechanisms for defining constants, each with distinct characteristics.

#define is a preprocessor directive. Before compilation, the preprocessor replaces every occurrence of the macro name with the specified value — a simple textual substitution. #define constants have no type information, occupy no memory, and are conventionally written in ALL_UPPERCASE.

#define PI 3.14159
#define MAX_AGE 200

const declares a typed, read-only variable. It is processed by the compiler (not the preprocessor), which means the compiler can perform type checking and the debugger can inspect its value. A const variable occupies memory and obeys normal scoping rules.

const int days_in_week = 7;
const float pi = 3.14159f;

The following table summarises the principal differences:

Property#defineconst
Processing phasePreprocessorCompiler
Type checkingNoneFull
Memory allocationNone (textual substitution)Yes
ScopeGlobal (from point of definition)Block or function
DebuggingHarder (name not visible to debugger)Easier
ModifiabilityCan be #undef'd and redefinedCannot be modified

In modern C, const is generally preferred for most constants because of its type safety and debuggability. #define remains appropriate for conditional compilation macros, include guards, and cases where a compile-time constant expression is required in older C standards.

Takeaway: Use const for typed constants within functions; reserve #define for macros, include guards, and platform-level configuration.

4.9 Arithmetic Operators

C provides five arithmetic operators for numeric computation:

OperatorOperationExampleResult
+Addition9 + 211
-Subtraction9 - 27
*Multiplication9 * 218
/Division9 / 24 (integer division)
%Modulus (remainder)9 % 21

Two points deserve special attention:

  1. Integer division truncates towards zero. When both operands are integers, the / operator discards any fractional part. Thus 9 / 2 yields 4, not 4.5. If you need the true quotient, at least one operand must be a floating-point type: 9.0 / 2 yields 4.5.

  2. The modulus operator % is defined only for integer operands. Attempting 2.5 % 1.2 will produce a compilation error. The result of a % b has the same sign as a (in C99 and later).

Minimal example File: p01_arith.c Build: gcc p01_arith.c -o p01_arith Run: ./p01_arith

#include <stdio.h>

int main() {
int a = 9, b = 2;

printf("%d + %d = %d\n", a, b, a + b);
printf("%d - %d = %d\n", a, b, a - b);
printf("%d * %d = %d\n", a, b, a * b);
printf("%d / %d = %d\n", a, b, a / b); /* truncated */
printf("%d %% %d = %d\n", a, b, a % b);

return 0;
}

What you should see: 9 + 2 = 11, 9 - 2 = 7, 9 * 2 = 18, 9 / 2 = 4, 9 % 2 = 1.

Takeaway: Integer division truncates; use floating-point operands when a fractional result is required. The % operator works only with integers.

4.10 Operator Precedence and Associativity

When an expression contains multiple operators, the compiler must determine the order in which they are evaluated. This order is governed by two rules: precedence and associativity.

The following table lists the arithmetic operators in descending order of precedence (highest first):

PrecedenceOperatorsAssociativity
1 (highest)() (parentheses)Left-to-right
2Unary +, unary -Right-to-left
3*, /, %Left-to-right
4 (lowest)Binary +, binary -Left-to-right

When two operators have the same precedence, associativity determines the evaluation direction. For most arithmetic operators, this is left-to-right: 10 - 3 - 2 is evaluated as (10 - 3) - 2 = 5, not 10 - (3 - 2) = 9.

Consider the expression 3 + 5 * 2. Because * has higher precedence than +, the multiplication is performed first: 3 + (5 * 2) = 3 + 10 = 13. If addition should be performed first, use parentheses: (3 + 5) * 2 = 16.

As a practical guideline: when in doubt, add parentheses. They cost nothing at runtime and make your intent explicit.

Practice evaluation (work through these by hand):

  1. 8 + 4 / 28 + 210
  2. 15 % 4 * 23 * 26
  3. (10 - 3) * 2 + 17 * 2 + 114 + 115
  4. -(3 + 5) * 2-(8) * 2-8 * 2-16

Takeaway: Memorise that multiplication, division, and modulus bind more tightly than addition and subtraction; use parentheses liberally to clarify complex expressions.

4.11 Type Conversions and Casting

C is a statically typed language, yet it permits expressions that mix operands of different types. When this occurs, the compiler must reconcile the types, and it does so via type conversion. There are two forms.

Implicit conversion (promotion) occurs automatically when the compiler widens a narrower type to a wider one to preserve information. The general promotion hierarchy is:

char → int → long → float → double → long double

For example, in the expression 5 + 2.0, the integer 5 is implicitly promoted to double before the addition is performed, and the result is 7.0 (a double).

When a wider type is assigned to a narrower variable, the compiler performs an implicit narrowing conversion, which may lose information. For instance:

float x = 2.5f, y = 1.2f;
int result = x + y; /* 3.7 is truncated to 3 */

The compiler may issue a warning for such conversions, and rightly so.

Explicit casting is a deliberate, programmer-directed conversion using the cast operator:

float pi = 3.14159f;
int pi_int = (int)pi; /* pi_int is 3; fractional part discarded */

Casting is necessary when you want to perform integer division on two floating-point values, or when passing values to functions that expect a specific type. It also serves as documentation of intent: it signals to the reader that the potential loss of precision is acknowledged and deliberate.

A cautionary note on overflow: casting to a narrower type can produce unexpected results if the value exceeds the target type's range. For example:

int large = 2147483647; /* INT_MAX */
short small = (short)large; /* undefined/wrapped value */

On a typical system where short is 2 bytes, the value wraps around and the result is -1, which is almost certainly not what was intended.

Minimal example File: p01_cast.c Build: gcc p01_cast.c -o p01_cast Run: ./p01_cast

#include <stdio.h>

int main() {
/* Implicit conversion: int to float */
int age = 25;
float age_float = age;
printf("Implicit: %d -> %f\n", age, age_float);

/* Explicit casting: float to int */
float pi = 3.14159f;
int pi_int = (int)pi;
printf("Explicit: %f -> %d\n", pi, pi_int);

/* Mixed-type expression */
printf("5 / 2 = %d\n", 5 / 2); /* integer division */
printf("5.0 / 2 = %f\n", 5.0 / 2); /* float division */

return 0;
}

What you should see: Implicit: 25 -> 25.000000, Explicit: 3.141590 -> 3, 5 / 2 = 2, 5.0 / 2 = 2.500000.

Takeaway: Implicit conversions widen safely; narrowing conversions (whether implicit or explicit) may lose data. Use explicit casts to document deliberate conversions and heed compiler warnings.

5. Worked Examples

Example 1: Declaring, Assigning, and Printing Variables

Problem: Write a programme that declares an integer variable age, assigns it the value 30, and prints the result.

Steps:

  1. Declare int age;.
  2. Assign age = 30;.
  3. Print using printf with %d.

Reference code File: p01_ex1.c Build: gcc p01_ex1.c -o p01_ex1 Run: ./p01_ex1

#include <stdio.h>

int main() {
int age;
age = 30;
printf("Age: %d\n", age);
return 0;
}

Output:

Age: 30

Why it works: int age; reserves 4 bytes for an integer. The assignment age = 30; writes the value 30 into that memory. The %d format specifier in printf reads the integer value and formats it as a decimal string.

Example 2: Computing the Area of a Circle with Constants

Problem: Define the constant π using #define and the radius using const. Compute and display the area of a circle.

Steps:

  1. Define PI as a preprocessor macro.
  2. Declare radius as a const int.
  3. Compute area = PI * radius * radius.
  4. Print with three decimal places using %.3f.

Reference code File: p01_ex2.c Build: gcc p01_ex2.c -o p01_ex2 Run: ./p01_ex2

#include <stdio.h>

#define PI 3.14159

int main() {
const int radius = 7;
float area = PI * radius * radius;
printf("Area of the circle: %.3f\n", area);
return 0;
}

Output:

Area of the circle: 153.938

Why it works: The preprocessor replaces PI with 3.14159 before compilation. The expression PI * radius * radius involves a double literal multiplied by int values; implicit promotion converts the integers to double for the multiplication. The result is then narrowed to float when stored in area. The %.3f specifier formats the output to three decimal places.

Example 3: Swapping Two Variables

Problem: Given two integer variables a and b, swap their values using a temporary variable and print the result before and after the swap.

Steps:

  1. Declare and initialise a = 5 and b = 10.
  2. Print the values before swapping.
  3. Introduce a temporary variable temp, and perform the three-step swap: temp = a; a = b; b = temp;.
  4. Print the values after swapping.

Reference code File: p01_ex3.c Build: gcc p01_ex3.c -o p01_ex3 Run: ./p01_ex3

#include <stdio.h>

int main() {
int a = 5, b = 10;

printf("Before swap:\n");
printf("a = %d, b = %d\n", a, b);

int temp = a;
a = b;
b = temp;

printf("After swap:\n");
printf("a = %d, b = %d\n", a, b);

return 0;
}

Output:

Before swap:
a = 5, b = 10
After swap:
a = 10, b = 5

Why it works: Without the temporary variable, the first assignment (a = b) would overwrite the original value of a, making it irrecoverable. The temporary variable preserves a's original value so that it can be assigned to b in the third step. This is a fundamental algorithmic pattern that recurs frequently in sorting and data manipulation.

6. Common Mistakes

  1. Using an uninitialised variable: After int x;, the value of x is indeterminate. Reading it before assignment is undefined behaviour and may produce different results on each run. Always initialise variables at the point of declaration when possible.

  2. Confusing = (assignment) with == (equality test): Writing if (x = 5) assigns 5 to x and always evaluates as true, rather than testing whether x equals 5. This error will become especially relevant in Lecture 2 (conditions), but the habit of understanding = as assignment begins here.

  3. Integer division surprise: 5 / 2 yields 2, not 2.5. Beginners frequently expect a fractional result. If a floating-point quotient is needed, ensure at least one operand is a floating-point type: 5.0 / 2 or (float)5 / 2.

  4. Applying % to floating-point operands: The modulus operator is defined only for integers. Writing 2.5 % 1.2 causes a compilation error. Use the fmod function from <math.h> if a floating-point remainder is required.

  5. Ignoring compiler warnings about narrowing conversions: When assigning a float result to an int variable, data is silently truncated. Compile with -Wall (all warnings) to catch such issues: gcc -Wall -o prog prog.c. Treat every warning as an error until you understand and have deliberately accepted the conversion.

7. Practice

7.1 Basic

  • P1: Declare an integer variable age, assign it the value 30, and print it. Acceptance: Output is exactly Age: 30.

  • P2: Declare and initialise a float variable height with the value 169.5. Print it as Height: 169.5 cm (use %.1f). Acceptance: Output matches the format above.

7.2 Required

  • P3: Define PI using #define (value 3.14159) and radius using const int (value 7). Compute and print the area of a circle to three decimal places. Acceptance: Output is Area of the circle: 153.938.

  • P4: Declare two integers num1 = 10 and num2 = 3. Print the quotient and remainder. Input/Output: Output Quotient: 3 and Remainder: 1. Acceptance: Both values correct.

  • P5: Declare three variables with meaningful names (age, height, is_student) following naming conventions, assign values, and print them. Acceptance: Output shows all three values with descriptive labels.

  • P6: Declare variables of types int, float, double, and char. Print the size of each using sizeof. Acceptance: Output matches the sizes on your system (typically 4, 4, 8, 1 bytes).

  • P7: Demonstrate implicit type conversion by assigning an int value to a float variable and printing the result. Acceptance: Output is Float Value: 10.000000.

  • P8: Demonstrate explicit type casting by casting 169.5 (a float) to int and printing the result. Acceptance: Output is Integer Height: 169.

  • P9: Define a const int max_speed = 120; and print its value. Then uncomment a line that attempts to modify max_speed and observe the compiler error. Include the error message as a comment in your submitted code. Acceptance: Programme compiles and prints 120; comment documents the error.

  • P10: Swap two integer variables using a temporary variable and print the values before and after the swap. Acceptance: Output matches the format in Example 3.

7.3 Optional

  • P11: Write a programme that evaluates and prints the result of each of the following expressions, confirming your hand calculations: 8 + 4 / 2, 15 % 4 * 2, (10 - 3) * 2 + 1, -(3 + 5) * 2, 5.0 / 2 + 3, (int)7.8 / 2, 'A' + 2. For each, print the expression as a string alongside the computed result.

  • P12: Write a programme that reads two integers from the user, performs all five arithmetic operations, and prints the results in a neatly formatted table. Handle the case where the second integer is zero (print an error message instead of performing division and modulus).

  • P13: Write a programme that reads a character from the user and prints its ASCII code, the next character in the ASCII table, and the previous character. For example, entering 'B' should display B (66), C (67), A (65).

8. Mini Task

Goal: Create a simple unit converter that demonstrates variables, constants, arithmetic operators, and type conversions.

Deliverables:

  • README.md: Compilation and execution instructions with sample terminal sessions.
  • src/p01_converter.c: A programme that:
    1. Defines conversion constants using #define or const (e.g., #define KM_PER_MILE 1.60934).
    2. Prompts the user to enter a distance in miles (as a float).
    3. Converts the distance to kilometres and prints the result to two decimal places.
    4. Also prints the truncated integer part of the result (using explicit casting) and the fractional remainder.
  • src/p01_swap.c: The variable-swap programme from P10.
  • report.md: A brief reflection (5–10 sentences) on any difficulties encountered, particularly regarding type conversions or unexpected arithmetic results.

9. Further Reading

  1. Kernighan, B. W. & Ritchie, D. M. (1988). The C Programming Language (2nd ed.). Prentice Hall — Chapter 2: "Types, Operators and Expressions".
  2. King, K. N. (2008). C Programming: A Modern Approach (2nd ed.). W. W. Norton — Chapters 4 ("Expressions") and 7 ("Basic Types").
  3. ISO/IEC 9899:2018 (C17 Standard) — §6.2.5 "Types", §6.3 "Conversions", §6.5 "Expressions". Available via national standards bodies or as a working draft at https://www.open-std.org/jtc1/sc22/wg14/.

10. Appendix

A. C Reserved Keywords (Complete List)

The following identifiers are reserved and may not be used as variable or function names. Keywords marked with a standard version were introduced in that revision of the language.

auto, break, case, char, const, continue, default, do, double, else, enum, extern, float, for, goto, if, inline (C99), int, long, register, restrict (C99), return, short, signed, sizeof, static, struct, switch, typedef, union, unsigned, void, volatile, while.

C23 additions: alignas, alignof, bool, constexpr, false, nullptr, static_assert, thread_local, true, typeof, typeof_unqual.

B. Partial ASCII Reference

DecimalCharacterDecimalCharacterDecimalCharacter
48–57'0''9'65–90'A''Z'97–122'a''z'
32Space10Newline (\n)0Null (\0)

A complete ASCII table is available in the lecture slides and in any C reference manual.

C. Useful Compiler Flags

When compiling the exercises in this chapter, consider using the following GCC flags:

gcc -Wall -Wextra -std=c17 -o prog prog.c
  • -Wall: Enable all common warnings.
  • -Wextra: Enable additional warnings beyond -Wall.
  • -std=c17: Compile according to the C17 standard.

These flags help catch uninitialised variables, implicit narrowing conversions, unused variables, and other issues that are easy to overlook.