Chapter 2: Core Concepts: Data Types, Variables, and Operators
Now that your development environment is set up, it’s time to dive into the fundamental building blocks of C programming. In this chapter, we will explore:
- Data Types: How C classifies different kinds of information (numbers, characters, etc.).
- Variables: How to store and name data in your programs.
- Operators: Symbols that perform operations on data (like addition, assignment, comparison).
These concepts are the ABCs of any programming language, and mastering them in C will provide a solid foundation for more complex topics.
2.1 Data Types
In C, every variable must have a data type. This type tells the compiler two crucial things:
- How much memory to allocate for the variable.
- How to interpret the bits stored in that memory location.
C provides a set of basic (primitive) data types:
2.1.1 Integer Types
These are used to store whole numbers (numbers without a fractional part).
| Type | Description | Typical Size (Bytes) | Range (example) |
|---|---|---|---|
char | Used for single characters. Can also be used for small integer values. signed char and unsigned char explicitly specify signedness. | 1 | signed: -128 to 127, unsigned: 0 to 255 |
short | Short integer. signed short and unsigned short. Guarantees at least 16 bits. | 2 | signed: -32,768 to 32,767, unsigned: 0 to 65,535 |
int | The most common integer type. signed int and unsigned int. Guarantees at least 16 bits, but typically 32 bits on modern systems. | 2 or 4 | signed: -2,147,483,648 to 2,147,483,647 (for 32-bit), unsigned: 0 to 4,294,967,295 |
long | Long integer. signed long and unsigned long. Guarantees at least 32 bits. | 4 or 8 | signed: -2,147,483,648 to 2,147,483,647 (for 32-bit) |
long long | Very long integer (introduced in C99). signed long long and unsigned long long. Guarantees at least 64 bits. | 8 | signed: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
_BitInt(N) (C23) | Bit-precise integer type for N bits, where N is an integer literal from 1 to LLONG_MAX. Can be signed or unsigned. Useful for memory-constrained devices. | ceil(N/8) | Depends on N |
Important Notes:
- The exact size and range of
short,int, andlongcan vary between systems and compilers, but they adhere to minimum size guarantees.charis always 1 byte. - Prefixing with
unsignedmeans the variable can only store non-negative values, doubling its positive range. By default, integer types aresigned. - Suffixes are used to specify the type of integer literals:
Lorlforlong(e.g.,123456789L)LLorllforlong long(e.g.,9876543210LL)Uoruforunsigned(e.g.,100U)- Combinations:
100UL(unsigned long),200ULL(unsigned long long)
- C23 Binary Literals and Digit Separators:
- You can now write binary numbers with
0bor0Bprefix:0b10101010. - You can use a single quote
'as a digit separator for readability:1'000'000,0x'FF'FF,0b'1111'0000.
- You can now write binary numbers with
2.1.2 Floating-Point Types
These are used to store numbers with a fractional part (real numbers).
| Type | Description | Typical Size (Bytes) | Precision (Decimal Digits) |
|---|---|---|---|
float | Single-precision floating-point number. | 4 | ~6-7 |
double | Double-precision floating-point number (most common). | 8 | ~15-17 |
long double | Extended-precision floating-point number. | 10 or 16 | ~18-19 (depends on system) |
Important Notes:
doubleis generally preferred for floating-point calculations due to its higher precision.- Floating-point literals are
doubleby default (e.g.,3.14). UseforFsuffix forfloat(e.g.,3.14f) andlorLforlong double(e.g.,3.14L).
2.1.3 Character Type
char: Stores a single character (like ‘A’, ‘b’, ‘7’, ‘$’). Internally, characters are stored as integer values (ASCII or Unicode representation). Acharis an integer type 1 byte wide.
2.1.4 _Bool (C99) / bool (C23)
_Bool(C99): Used to store boolean values (true or false). It behaves like an integer type, where0is false and any non-zero value is true.bool(C23): C23 introducesbool,true, andfalseas keywords, meaning you no longer need to include<stdbool.h>to use them.
Code Example: Data Type Sizes and Values
Let’s see the sizes of these data types on your system and declare some variables.
#include <stdio.h> // For printf
#include <limits.h> // For integer limits (e.g., INT_MAX, CHAR_MIN)
#include <float.h> // For float limits (e.g., FLT_MAX, DBL_MAX)
#include <stdbool.h> // For C99 bool, true, false (optional in C23)
int main() {
printf("--- Size of Data Types (in bytes) ---\n");
printf("Size of char: %zu byte(s)\n", sizeof(char));
printf("Size of short: %zu byte(s)\n", sizeof(short));
printf("Size of int: %zu byte(s)\n", sizeof(int));
printf("Size of long: %zu byte(s)\n", sizeof(long));
printf("Size of long long: %zu byte(s)\n", sizeof(long long));
printf("Size of float: %zu byte(s)\n", sizeof(float));
printf("Size of double: %zu byte(s)\n", sizeof(double));
printf("Size of long double: %zu byte(s)\n", sizeof(long double));
printf("Size of _Bool: %zu byte(s)\n", sizeof(_Bool)); // Or sizeof(bool) in C23
printf("\n--- Integer Values and Ranges ---\n");
int age = 30;
unsigned int population = 8000000000U; // U suffix for unsigned
long int distance_to_sun = 149600000000L; // L suffix for long
long long big_number = 123456789012345LL; // LL suffix for long long
signed char ascii_val = 'A'; // 'A' is 65 in ASCII
_BitInt(10) small_int = 512; // C23 feature: 10-bit integer
printf("Age: %d\n", age);
printf("World Population: %u\n", population); // %u for unsigned int
printf("Distance to Sun: %ld km\n", distance_to_sun); // %ld for long int
printf("A very big number: %lld\n", big_number); // %lld for long long
printf("ASCII value 'A': %d\n", ascii_val);
// Using C23 binary literal and digit separator
int binary_value = 0b1010'1010;
printf("Binary 0b1010'1010: %d\n", binary_value);
// _BitInt(N) example (requires C23 compiler)
printf("10-bit integer value: %d\n", small_int);
printf("\n--- Floating Point Values ---\n");
float pi_float = 3.1415926535f; // f suffix for float
double pi_double = 3.14159265358979323846;
long double pi_long_double = 3.14159265358979323846L; // L suffix for long double
printf("Pi (float): %.10f\n", pi_float); // .10f for 10 decimal places
printf("Pi (double): %.15lf\n", pi_double); // %lf for double
printf("Pi (long double): %.20Lf\n", pi_long_double); // %Lf for long double
printf("\n--- Character Value ---\n");
char grade = 'A';
printf("Grade: %c\n", grade); // %c for character
printf("\n--- Boolean Value ---\n");
// In C99, use #include <stdbool.h> and `bool` keyword.
// In C23, `bool`, `true`, `false` are keywords by default.
bool is_student = true;
bool has_passed = false;
printf("Is student? %d\n", is_student); // Prints 1 for true
printf("Has passed? %d\n", has_passed); // Prints 0 for false
// Printing max/min values from <limits.h> and <float.h>
printf("\n--- System Limits ---\n");
printf("Max int: %d\n", INT_MAX);
printf("Min char: %d\n", CHAR_MIN);
printf("Max float: %e\n", FLT_MAX); // %e for scientific notation
return 0;
}
To compile and run this code:
# For C99/C11/C17 compilers
gcc -std=c17 data_types.c -o data_types
# For C23 compiler (if available and configured)
# The exact flag might vary, but often -std=c23 or -std=gnu23
# For GCC 13.x or later:
gcc -std=c23 data_types.c -o data_types
Then run:
./data_types
Note on sizeof and %zu: The sizeof operator returns the size in bytes. Its return type is size_t, which is an unsigned integer type. The correct format specifier for size_t in printf is %zu.
2.2 Variables
A variable is a named storage location in memory used to hold a value. Before you can use a variable, you must declare it.
2.2.1 Declaring Variables
When you declare a variable, you specify its data type and its name.
dataType variableName;
Examples:
int score; // Declares an integer variable named 'score'
float price; // Declares a float variable named 'price'
char initial; // Declares a character variable named 'initial'
double temperature; // Declares a double variable named 'temperature'
2.2.2 Initializing Variables
Initializing a variable means giving it an initial value when it is declared. If you don’t initialize a variable, it will contain a “garbage” value (whatever bits were previously in that memory location).
dataType variableName = initialValue;
Examples:
int score = 100;
float price = 99.99f; // Remember the 'f' for float literals
char initial = 'J';
double temperature = 25.5;
unsigned long count = 0UL;
bool is_active = true; // In C23, or use `_Bool is_active = 1;`
You can also declare multiple variables of the same type on one line:
int x, y, z; // Declares three integer variables
int a = 10, b = 20; // Declares and initializes two integer variables
Important: In C, variables must be declared before they are used. Typically, declarations are placed at the beginning of a function or block, though C99 and later standards allow declarations almost anywhere.
2.2.3 Assigning Values to Variables
You can change the value of a variable at any point after it has been declared using the assignment operator =.
int num_students = 25; // Declaration and initialization
num_students = 30; // Assignment: change the value
Code Example: Variables
#include <stdio.h>
#include <stdbool.h> // For bool, true, false in C99/C11/C17
int main() {
// Declaring and initializing variables
int student_id = 1001;
char grade_letter = 'B';
float average_score = 85.5f;
double max_gpa = 4.0;
bool is_enrolled = true; // true is 1, false is 0
printf("Student ID: %d\n", student_id);
printf("Grade Letter: %c\n", grade_letter);
printf("Average Score: %.1f\n", average_score);
printf("Maximum GPA: %.1lf\n", max_gpa);
printf("Is Enrolled: %d\n", is_enrolled);
// Reassigning values
student_id = 1002;
grade_letter = 'A';
average_score = 92.3f;
is_enrolled = false;
printf("\n--- After Reassignment ---\n");
printf("New Student ID: %d\n", student_id);
printf("New Grade Letter: %c\n", grade_letter);
printf("New Average Score: %.1f\n", average_score);
printf("Is Enrolled now: %d\n", is_enrolled);
// Declaring multiple variables
int x, y, z;
x = 10;
y = 20;
z = x + y;
printf("x + y = %d\n", z);
// Using C23 auto keyword for object definitions (limited type inference)
// This allows you to omit the type name when initializing.
// The type is deduced from the initializer.
// Requires -std=c23 or equivalent compiler flag.
#if __STDC_VERSION__ >= 202311L
auto inferred_int = 42;
auto inferred_float = 3.14f;
auto inferred_char = 'K';
printf("Inferred int: %d\n", inferred_int);
printf("Inferred float: %.2f\n", inferred_float);
printf("Inferred char: %c\n", inferred_char);
#else
printf("\n(C23 'auto' keyword for declarations not supported by current compiler standard)\n");
#endif
return 0;
}
Compile and Run:
# For older C standards, without C23 auto keyword
gcc variables.c -o variables
# For C23 compiler (if auto keyword is used)
gcc -std=c23 variables.c -o variables
Then run:
./variables
Exercise 2.1: Variable Declaration and Initialization
Declare variables to store the following information and initialize them with appropriate values:
- Your current age (integer).
- Your height in meters (floating-point, double).
- The first letter of your last name (character).
- Whether you have a driver’s license (boolean).
- The number of stars in a galaxy (unsigned long long, a very large number).
Then, print out the values of these variables using printf. Choose the correct format specifiers for each type.
Hint:
%dforint%cforchar%lffordouble%dforbool(prints 0 or 1)%lluforunsigned long long
2.3 Operators
Operators are special symbols that perform operations on variables and values (called operands). C has a rich set of operators.
2.3.1 Arithmetic Operators
These are used for mathematical calculations.
| Operator | Description | Example | Result (if a=10, b=3) |
|---|---|---|---|
+ | Addition | a + b | 13 |
- | Subtraction | a - b | 7 |
* | Multiplication | a * b | 30 |
/ | Division | a / b | 3 (integer division) |
% | Modulo (remainder) | a % b | 1 |
Important Note on Division:
- When both operands of
/are integers, C performs integer division, truncating any fractional part.10 / 3results in3. - If at least one operand is a floating-point type, floating-point division is performed:
10.0 / 3results in3.333....
2.3.2 Assignment Operators
These are used to assign values to variables. The most basic is =, but there are shorthand compound assignment operators.
| Operator | Example | Equivalent to |
|---|---|---|
= | x = 10 | |
+= | x += y | x = x + y |
-= | x -= y | x = x - y |
*= | x *= y | x = x * y |
/= | x /= y | x = x / y |
%= | x %= y | x = x % y |
2.3.3 Increment and Decrement Operators
These are unary operators (++ and --) used to increase or decrease a variable’s value by 1.
- Prefix (
++x,--x): The operation is performed before the value is used in the expression. - Postfix (
x++,x--): The operation is performed after the value is used in the expression.
| Operator | Description | Example | Result (if x=5) |
|---|---|---|---|
++ | Increment by 1 | x++ | x becomes 6 (value used is 5) |
++x | x becomes 6 (value used is 6) | ||
-- | Decrement by 1 | x-- | x becomes 4 (value used is 5) |
--x | x becomes 4 (value used is 4) |
Code Example: Operators
#include <stdio.h>
int main() {
int a = 20;
int b = 7;
int result_int;
double c = 20.0;
double d = 7.0;
double result_double;
printf("--- Arithmetic Operators ---\n");
result_int = a + b;
printf("%d + %d = %d\n", a, b, result_int); // 20 + 7 = 27
result_int = a - b;
printf("%d - %d = %d\n", a, b, result_int); // 20 - 7 = 13
result_int = a * b;
printf("%d * %d = %d\n", a, b, result_int); // 20 * 7 = 140
result_int = a / b;
printf("%d / %d = %d (Integer Division)\n", a, b, result_int); // 20 / 7 = 2 (truncates)
result_double = c / d;
printf("%.1lf / %.1lf = %.2lf (Floating Point Division)\n", c, d, result_double); // 20.0 / 7.0 = 2.86
result_int = a % b;
printf("%d %% %d = %d (Modulo)\n", a, b, result_int); // 20 % 7 = 6
printf("\n--- Assignment Operators ---\n");
int x = 10;
printf("Initial x = %d\n", x);
x += 5; // x = x + 5;
printf("x after x += 5: %d\n", x); // x is 15
x -= 3; // x = x - 3;
printf("x after x -= 3: %d\n", x); // x is 12
x *= 2; // x = x * 2;
printf("x after x *= 2: %d\n", x); // x is 24
x /= 4; // x = x / 4;
printf("x after x /= 4: %d\n", x); // x is 6
x %= 5; // x = x % 5;
printf("x after x %%= 5: %d\n", x); // x is 1
printf("\n--- Increment/Decrement Operators ---\n");
int i = 5;
int j = 5;
int k;
printf("Initial i = %d, j = %d\n", i, j);
// Postfix increment: use then increment
k = i++;
printf("k = i++ (k = %d, i = %d)\n", k, i); // k = 5, i = 6
// Prefix increment: increment then use
k = ++j;
printf("k = ++j (k = %d, j = %d)\n", k, j); // k = 6, j = 6
// Postfix decrement
k = i--;
printf("k = i-- (k = %d, i = %d)\n", k, i); // k = 6, i = 5
// Prefix decrement
k = --j;
printf("k = --j (k = %d, j = %d)\n", k, j); // k = 5, j = 5
return 0;
}
Compile and Run:
gcc operators.c -o operators
./operators
Exercise 2.2: Calculations with Operators
Write a C program that performs the following calculations and prints the result for each:
- Calculate the area of a rectangle with length
15and width7. - Calculate the remainder when
200is divided by13. - Given an initial score of
75, increase it by10, then double the result, and finally decrease it by5. Print the score at each step. - Declare a
doublevariabletemperatureand set its value to28.5. Increment it by1.5using an assignment operator. Print the final temperature.
Example for (1):
Area = length * width
Exercise 2.3: Data Type Conversions (Mini-Challenge)
What will be the output of the following C code snippets? Try to predict before running them. Explain why.
Snippet A:
#include <stdio.h>
int main() {
int num1 = 17;
int num2 = 5;
float result = num1 / num2;
printf("Result A: %.2f\n", result);
return 0;
}
Snippet B:
#include <stdio.h>
int main() {
int num1 = 17;
int num2 = 5;
float result = (float)num1 / num2; // Type cast num1 to float
printf("Result B: %.2f\n", result);
return 0;
}
Snippet C:
#include <stdio.h>
int main() {
char ch = 'X';
printf("Character: %c, ASCII Value: %d\n", ch, ch);
return 0;
}
Explanation:
- Type Casting: In C, you can explicitly convert one data type to another using a process called type casting. You put the desired type in parentheses before the variable or expression you want to convert, like
(dataType)expression. This is important when you want to force floating-point division, for example. - Implicit Conversions: C also performs implicit type conversions (coercion) in certain situations, such as when assigning a smaller type to a larger type, or during mixed-type arithmetic operations. In arithmetic, the “smaller” type is usually promoted to the “larger” type to prevent loss of data.
This chapter covered the absolute basics of data storage and manipulation in C. You now understand different data types, how to declare and assign values to variables, and how to perform fundamental operations using C’s rich set of operators. In the next chapter, we’ll learn how to make our programs make decisions and repeat actions using control flow statements.