Unit 2 · 8 teaching hours · 24 min read
Elements of C: Tokens, Data Types, Operators and Console Input/Output
What the syllabus asks for 7
- Character set and tokens
- Data types
- Variables and constants
- Operators
- Formatted output with printf
- Input with scanf, getchar, gets-family
- Eight common mistakes with data types, operators and input/output
This chapter is the vocabulary and grammar of C: the smallest meaningful pieces (tokens), the kinds of data, the operators that combine them, and how to read and print values. Every later chapter assumes all of it.
- The six token classes and the rules for identifiers.
- Every basic data type, its size and range, and when conversions happen silently.
- Constants,
constand#define. - All operators, precedence and associativity, and the classic pitfalls (
/on integers,=vs==,i++ + ++i). printf,scanf,getchar,putcharwith format specifiers.
2.1 Character set and tokens
The C character set is letters (A–Z, a–z), digits (0–9), special characters (+ - * / % = < > ! & | ^ ~ ( ) [ ] { } , ; : . ' " # ? \ _) and white space (blank, tab, newline). A token is the smallest unit the compiler recognises:
| Token class | Examples |
|---|---|
| Keywords | int, return, while, struct (32 in C89, reserved; cannot be used as names) |
| Identifiers | total, _count, student_1 |
| Constants | 25, 3.14, 'A', 0x1F |
| String literals | "Nepal" |
| Operators | +, ==, &&, ++ |
| Punctuators | ;, ,, { }, ( ) |
The 32 keywords of C89: auto break case char const continue default do double else enum extern float for goto if int long register return short signed sizeof static struct switch typedef union unsigned void volatile while.
Identifier rules. Letters, digits and underscore only; must not start with a digit; case-sensitive; not a keyword; at least the first 31 characters are significant. 2fast, my-name, int, total marks are invalid; fast2, my_name, Int, total_marks are valid.
Choose meaningful names: average beats a.
Text is stored as numbers
A computer never stores the picture of a letter. It stores a small whole number, and the number is turned back into a shape only when it is displayed.
The ASCII chart in Plate 2.1 is the table that every C compiler uses for the type char, so 'A' is simply the number 65.
Because the capital and small letters sit exactly 32 places apart, one bit (the bit of value 32) decides the case of a letter. This is why ch + 32 turns 'A' into 'a' and why '7' - '0' gives the number 7 instead of the character.

2.2 Data types
| Type | Typical size (64-bit GCC) | Range | Specifier |
|---|---|---|---|
char |
1 byte | −128 to 127 | %c (or %d) |
unsigned char |
1 | 0 to 255 | %u |
short |
2 | −32,768 to 32,767 | %hd |
int |
4 | −2,147,483,648 to 2,147,483,647 | %d |
unsigned int |
4 | 0 to 4,294,967,295 | %u |
long |
8 on Linux/Mac, 4 on Windows | %ld |
|
long long |
8 | about ±9.22 × 10¹⁸ | %lld |
float |
4 | about 6–7 significant digits | %f |
double |
8 | about 15–16 significant digits | %lf (in scanf), %f (in printf) |
Sizes are implementation-defined; the standard only guarantees char is 1 byte and short ≤ int ≤ long. Old Turbo C (16-bit) had a 2-byte int ranging −32,768 to 32,767, which is why older exam answers say that. Use sizeof(type) to ask the compiler.
An n-bit signed integer in two's complement ranges from −2ⁿ⁻¹ to 2ⁿ⁻¹ − 1; unsigned from 0 to 2ⁿ − 1. Overflow of a signed integer is undefined behaviour; overflow of an unsigned wraps around modulo 2ⁿ.
#include <stdio.h>
#include <limits.h>
int main(void)
{
printf("char=%zu short=%zu int=%zu long long=%zu\n",
sizeof(char), sizeof(short), sizeof(int), sizeof(long long));
printf("float=%zu double=%zu\n", sizeof(float), sizeof(double));
printf("INT_MAX=%d\n", INT_MAX);
unsigned char u = 255;
u = u + 1;
printf("255 + 1 in an unsigned char = %d\n", u);
return 0;
}
char=1 short=2 int=4 long long=8
float=4 double=8
INT_MAX=2147483647
255 + 1 in an unsigned char = 0
Derived types are built from these (arrays, pointers, structures); user-defined types come from typedef and enum; void means "no value".
When a number does not fit
Every type has a fixed number of bits, so every type has a largest value. When a bigger value is forced into a smaller type, C does not stop the program: it keeps only the low bits and carries on with a wrong answer.
In 1996 the first Ariane 5 rocket was lost about 37 seconds after lift-off partly because a 64-bit floating-point value was converted into a 16-bit signed integer that was too small to hold it.
Plate 2.2 repeats the same mistake on a classroom scale with (short) casts that were run through gcc.

Decimal fractions are not exact
A float stores binary fractions, and 0.1 has no finite binary form, exactly as one third has no finite decimal form. The stored value is a tiny bit off, and the tiny error grows when it is added again and again.
In February 1991 a Patriot battery at Dhahran had been running for about 100 hours counting time in tenths of a second; its clock had lost about a third of a second, enough for it to miss an incoming missile.
Plate 2.3 recomputes that error from the 24-bit chopped value of 0.1 that the system used.

2.3 Variables and constants
A variable is a named memory location whose value can change. Declare before use: int age;. Initialise at declaration: int age = 19;. An uninitialised local variable holds garbage, not zero.
Constants.
| Kind | Examples | Notes |
|---|---|---|
| Integer | 25, -7, 025 (octal = 21), 0x1F (hex = 31), 100L, 7u |
a leading 0 means octal: a classic trap |
| Real | 3.14, 2.5e3 (= 2500), 1.5f |
a real literal is double unless suffixed f |
| Character | 'A' (65), '0' (48), 'a' (97), '\n' |
a character constant is really an int code |
| String | "BCA" |
array of characters ending with the hidden null character '\0', so it occupies 4 bytes |
Escape sequences: \n newline, \t tab, \\ backslash, \" quote, \' apostrophe, \0 null, \a alert, \b backspace, \r carriage return.
Symbolic constants. #define PI 3.14159 is textual replacement done by the preprocessor (no type, no memory, no semicolon). const double PI = 3.14159; is a real typed variable the compiler forbids you to change. Prefer const.
Beware: #define SQ(x) x*x makes SQ(2+3) become 2+3*2+3 = 11, not 25; write #define SQ(x) ((x)*(x)).
2.4 Operators
| Group | Operators |
|---|---|
| Arithmetic | + - * / % |
| Relational | < <= > >= == != (result is 1 or 0) |
| Logical | && || ! |
| Assignment | = += -= *= /= %= <<= >>= &= ^= |= |
| Increment/decrement | ++ -- (prefix and postfix) |
| Bitwise | & | ^ ~ << >> |
| Conditional | ?: |
| Others | sizeof, comma ,, address &, dereference *, member . -> |
Integer division and remainder. 7 / 2 is 3; 7 % 2 is 1; -7 / 2 is -3 (truncates towards zero in C99); -7 % 2 is -1 (the sign follows the dividend). % works only on integers. 7 / 2.0 is 3.5.
A frequent bug: float avg = (a + b) / 2; with integer a, b loses the fraction before the assignment.
Relational and logical. C has no separate Boolean in C89; any non-zero value is true. && and || short-circuit: in x != 0 && y / x > 2 the division is never executed when x is 0.
The classic mistake is if (x = 5), which assigns 5 and is always true; the test is ==.
Increment/decrement. y = x++ copies x then increments; y = ++x increments then copies. Never modify the same variable twice in one expression (i = i++ + ++i is undefined behaviour; there is no "correct" output).
Bitwise operators work on each bit. 5 & 3 = 1 (0101 & 0011 = 0001); 5 | 3 = 7; 5 ^ 3 = 6; ~5 = -6; 5 << 1 = 10 (multiply by 2); 20 >> 2 = 5 (divide by 4). Test bit k: (n >> k) & 1.
Set bit k: n | (1 << k). Clear: n & ~(1 << k). Toggle: n ^ (1 << k).
Conditional operator. max = (a > b) ? a : b; is the one-line form of an if-else.
Precedence and associativity (high to low):
| Rank | Operators | Associativity |
|---|---|---|
| 1 | () [] -> . postfix ++ -- |
left to right |
| 2 | unary + - ! ~ ++ -- * & sizeof (type) |
right to left |
| 3 | * / % |
left to right |
| 4 | + - |
left to right |
| 5 | << >> |
left to right |
| 6 | < <= > >= |
left to right |
| 7 | == != |
left to right |
| 8 | & then ^ then | |
left to right |
| 9 | && then || |
left to right |
| 10 | ?: |
right to left |
| 11 | = += -= etc. |
right to left |
| 12 | , |
left to right |
Memory device: "Unary, Multiply, Add, Shift, Relate, Equate, Bit, Logic, Condition, Assign, Comma". Note & binds looser than ==, so x & 1 == 0 means x & (1 == 0); write (x & 1) == 0.
Type conversion.
Implicit in a mixed expression the smaller type is promoted (char/short → int → unsigned → long → float → double); assigning a double to an int truncates.
Explicit cast (double)a / b converts a first, giving real division.
#include <stdio.h>
int main(void)
{
int a = 7, b = 2;
printf("%d %d %d\n", a / b, a % b, -a / b);
printf("%.2f %.2f\n", (double)a / b, (float)(a / b));
int x = 5;
int y = x++ + 10;
int z = ++x * 2;
printf("x=%d y=%d z=%d\n", x, y, z);
printf("%d %d %d %d\n", 5 & 3, 5 | 3, 5 ^ 3, 20 >> 2);
int m = (a > b) ? a : b;
printf("max=%d, 8 > 3 > 1 is %d\n", m, 8 > 3 > 1);
return 0;
}
3 1 -3
3.50 3.00
x=7 y=15 z=14
1 7 6 5
max=7, 8 > 3 > 1 is 0
Check the last line: 8 > 3 is 1, then 1 > 1 is 0. Check x: starts 5; x++ uses 5 (y = 15) and x becomes 6; ++x makes 7, z = 14.
2.5 Formatted output with printf
printf("format", args...) copies the format text and replaces each %… with the next argument.
| Spec | Prints | Example → output |
|---|---|---|
%d / %i |
int | printf("%d", 42) → 42 |
%u |
unsigned | |
%ld, %lld |
long, long long | |
%f |
float/double (6 decimals) | 3.141593 |
%.2f |
2 decimals | 3.14 |
%e |
scientific | 3.141593e+00 |
%c |
character | printf("%c", 65) → A |
%s |
string | |
%x, %X, %o |
hex, octal | printf("%x", 255) → ff |
%p |
pointer address | |
%% |
a percent sign |
Width, precision, flags: %5d right-aligns in 5 columns, %-5d left-aligns, %05d pads with zeros (00042), %8.3f is width 8 with 3 decimals, %+d always shows the sign, %10s pads a string, %.3s prints at most 3 characters. printf returns the number of characters printed.
A type mismatch such as printf("%d", 3.5) prints garbage; the compiler (with -Wall) warns.
2.6 Input with scanf, getchar, gets-family
scanf("%d", &n); reads an integer into n. The & is mandatory for ordinary variables because scanf needs the address to store into; omit it and the program crashes or corrupts memory.
| To read | Use |
|---|---|
| int | scanf("%d", &i) |
| float | scanf("%f", &f) |
| double | scanf("%lf", &d) (note l) |
| char | scanf(" %c", &c) (the space before %c skips leftover white space) |
| word | scanf("%s", name) (stops at white space; no & because an array name is already an address) |
| a line | fgets(line, sizeof line, stdin) |
scanf returns the number of items successfully read, so if (scanf("%d", &n) != 1) detects bad input. Never use gets: it cannot limit the length, was removed in C11, and is the textbook source of buffer overflows. Use fgets.
getchar() reads one character and returns it as an int (EOF at end of input); putchar(c) writes one. scanf("%d %d", &a, &b) reads two numbers separated by any white space.
After scanf("%d") the newline stays in the buffer, which is why a following scanf("%c") without the leading space appears to be "skipped".
#include <stdio.h>
int main(void)
{
int a, b;
char op;
scanf("%d %c %d", &a, &op, &b);
if (op == '+')
printf("%d + %d = %d\n", a, b, a + b);
else if (op == '/' && b != 0)
printf("%d / %d = %.3f\n", a, b, (double)a / b);
else
printf("unsupported\n");
printf("[%5d][%-5d][%05d][%8.3f][%x]\n", a, a, a, 3.14159, 255);
return 0;
}
12 / 8
12 / 8 = 1.500
[ 12][12 ][00012][ 3.142][ff]
Where does the Enter key go?
When you type 25 and press Enter, the keyboard sends the three characters 2, 5 and newline, not just a number. scanf("%d") takes the digits and stops at the newline, which stays in the input buffer waiting for the next reader.
A following scanf("%c") then receives that newline (code 10) instead of the letter you meant to type.
Adding a space in the format, as in " %c", tells scanf to skip white space first; Plate 2.4 shows the buffer and both outcomes from a real run.

2.7 Eight common mistakes with data types, operators and input/output
- Using
=instead of==. Inif (x = 5)the value 5 is assigned to x and the condition is always true. The compiler may not report an error, so the program quietly takes the wrong branch. Write the constant first (5 == x) or compile with-Wallto catch it. - Integer division. In C,
7 / 2is 3, not 3.5, because both operands are integers and the fraction is cut off. To get 3.5 make one operand a float, for example7 / 2.0or(float)7 / 2. A marks-average program that divides twointvalues will always give a wrong answer for this reason. - Wrong format specifier.
printf("%d", 3.14)prints garbage, because%dexpects anint. Use%fforfloatanddoubleinprintf,%lffordoubleinscanf, and%cfor a character. The compiler warns about this only if warnings are turned on. - Forgetting
&inscanf.scanf("%d", n)passes the value of n instead of its address, so the input is written to a random place and the program may crash. Writescanf("%d", &n). For a string array the name already is an address, so no&is used. - Leaving a newline in the buffer. After
scanf("%d", &n)the Enter key stays in the input, so a followinggetchar()orscanf("%c")reads that newline and seems to skip the input. A space before the specifier,scanf(" %c", &ch), removes the leftover white space. - Overflow and wrong type size. An
intthat holds 2,147,483,647 becomes negative when 1 is added. Factorials and sums over many Nepali census records easily pass this limit, so uselong longfor large totals. Always check the range of the type before choosing it. - Comparing floating-point numbers with
==. Because 0.1 cannot be stored exactly in binary,0.1 + 0.2 == 0.3is false. Compare the difference with a small tolerance, for examplefabs(a - b) < 1e-9. This matters in billing programs that add many decimal amounts. - Confusing
++forms and precedence.i = i++is undefined, anda = b++ + ++bhas no fixed answer. Alsoa & b == cmeansa & (b == c), not(a & b) == c. Use one increment per statement and brackets for mixed operators.
Formula and fact sheet
- Identifier: letters, digits,
_; not starting with a digit; not a keyword; case-sensitive. - Sizes:
char1,short2,int4 (modern),long long8,float4,double8 bytes. Signed n-bit range: −2ⁿ⁻¹ … 2ⁿ⁻¹ − 1. 7/2 = 3,7%2 = 1,-7/2 = -3,-7%2 = -1,7/2.0 = 3.5.x++uses then increments;++xincrements then uses.=assigns,==compares.&&,||short-circuit.(x & 1) == 0tests even.n << k= n × 2ᵏ.scanfneeds&(not for arrays);%lffor double inscanf;" %c"skips white space.#define SQ(x) ((x)*(x))– always parenthesise macro arguments.
Chapter summary
- Tokens are keywords, identifiers, constants, strings, operators, punctuators.
- Pick the type by the range you need; integer arithmetic truncates, so cast before dividing when you want a real result.
- Precedence rules decide grouping; parentheses remove doubt. Never modify one variable twice in an expression.
printfformats,scanfparses; checkscanf's return value and preferfgetsfor lines.
Practice
A. Short questions
- Which of these are valid identifiers:
_total,9lives,float,Roll_No,a-b? - What are the values of
17 / 5,17 % 5,-17 / 5,-17 % 5,17 / 5.0? - What is the difference between
#define N 10andconst int n = 10;? - Explain why
scanf("%d", n)(without&) is a bug. - What does
printf("%5.2f|%-6d|%c", 3.14159, 42, 66)print? - Differentiate prefix and postfix increment with an example.
- Distinguish
=from==, and&from&&.
B. Predict the output (assume 4-byte int)
int a = 10; printf("%d %d %d", a++, a, ++a);Explain why you should not write this.int x = 6; printf("%d", x > 5 && x < 10 || x == 0);printf("%d", 5 + 3 * 2 - 8 / 4 % 3);int n = 12; printf("%d %d", n & 1, n >> 2);float f = 7 / 2; printf("%.1f", f);
C. Programs. Write a program that (13) reads a three-digit number and prints the sum of its digits; (14) converts a temperature from Celsius to Fahrenheit (F = 9C/5 + 32), taking care of integer division; (15) swaps two numbers with and without a third variable; (16) reads the radius and prints area and circumference to 2 decimals.
Answers and worked solutions
1. Valid: _total, Roll_No. Invalid: 9lives (starts with a digit), float (keyword), a-b (contains -).
2. 3, 2, −3, −2, 3.4.
3. #define is textual substitution by the preprocessor with no type and no storage; const int n is a typed object checked by the compiler, visible to a debugger, obeying scope rules.
4. scanf stores through an address. Passing the value of an uninitialised n as an address writes to a random location: crash or silent corruption.
5. 3.14|42 |B (width 5 with a leading space; -6 left-justifies 42 in 6 columns; 66 is 'B').
6. int x = 5, y; y = x++; gives y = 5, x = 6. y = ++x; gives y = 6, x = 6.
7. = assigns; == tests equality. & is bitwise AND of each bit (6 & 3 = 2); && is logical AND of truth values and short-circuits (6 && 3 = 1).
8. Undefined behaviour: the order of evaluation of function arguments is unspecified, and a is modified and read without a sequence point. Any answer is "allowed"; write one change per statement.
9. 6 > 5 && 6 < 10 is true, so the whole is 1. (&& binds tighter than ||.)
10. 3 * 2 = 6; 8 / 4 = 2, 2 % 3 = 2; 5 + 6 - 2 = 9.
11. 12 & 1 = 0 (12 is 1100); 12 >> 2 = 3. Output 0 3.
12. 7 / 2 is integer division = 3, converted to 3.0. Output 3.0, not 3.5.
13. int n, s = 0; scanf("%d", &n); s += n % 10; n /= 10; s += n % 10; n /= 10; s += n; printf("%d", s);
14. float f = 9.0f * c / 5 + 32; (writing 9 / 5 * c gives 1 * c because 9 / 5 is 1).
15. With a temp: t = a; a = b; b = t;. Without: a = a + b; b = a - b; a = a - b; (or a ^= b; b ^= a; a ^= b;; both fail if a and b are the same object).
16. printf("%.2f %.2f", 3.14159 * r * r, 2 * 3.14159 * r); with r a double read using %lf.
Model answers: medium (5 marks) and long (10 marks) questions
Q (5 marks). What is a data type? Explain the basic data types of C with their size, range and format specifier.
A data type tells the compiler how much memory a variable needs and how the stored bits are to be read. For example, the same 32 bits mean 1,078,530,011 as an int but 3.14159 as a float.
C has four basic types: char, int, float and double, which can be changed by the modifiers short, long, signed and unsigned (Figure 2.1). A char takes 1 byte and holds one character or a small number (-128 to 127).
An int takes 4 bytes on a modern PC and holds whole numbers from about -2.1 billion to +2.1 billion. A float takes 4 bytes and gives 6 to 7 correct digits; a double takes 8 bytes and gives 15 to 16 digits.
| Type | Size | Range (typical) | Format |
|---|---|---|---|
char |
1 B | -128 to 127 | %c |
int |
4 B | ±2,147,483,647 | %d |
unsigned int |
4 B | 0 to 4,294,967,295 | %u |
long long |
8 B | ±9.2 × 10¹⁸ | %lld |
float |
4 B | ±3.4 × 10³⁸ | %f |
double |
8 B | ±1.8 × 10³⁰⁸ | %lf (%f in printf) |
For a student's roll number use int, for a grade letter use char, and for a CGPA such as 3.62 use float or double. The operator sizeof(type) gives the exact size on your machine.
Q (10 marks). Explain the different types of operators in C with examples. Describe operator precedence and associativity, and evaluate the expression x = 10 + 4 * 3 % 5 - 2 > 8 && !0 .
Introduction. An operator is a symbol that tells the compiler to perform an operation on one or more operands. An expression is a combination of operands and operators that yields a value. C has a rich set of operators, which makes programs short.
Types of operators.
- Arithmetic:
+ - * / %.17 % 5is 2 and17 / 5is 3 for integers. - Relational:
< <= > >= == !=. The result is 1 (true) or 0 (false). - Logical:
&& || !.(a > 0 && b > 0)is true only when both parts are true; the right side of&&is skipped if the left side is false (short-circuit). - Assignment:
= += -= *= /= %=.x += 5meansx = x + 5. - Increment and decrement:
++and--. Iny = x++the old value is used first; iny = ++xthe new value is used. - Bitwise:
& | ^ ~ << >>.5 & 3is 1 and5 << 1is 10. - Conditional (ternary):
c ? a : b.max = (a > b) ? a : b; - Special:
sizeof, comma,, address&, indirection*, and type cast(float).
Precedence and associativity. Precedence decides which operator is applied first when an expression has several; associativity decides the order for operators of equal precedence (Figure 2.2).
Multiplication, division and modulus come before addition and subtraction. Relational operators come before &&, and && before ||.
Assignment is the lowest and associates from right to left, so a = b = 5 gives both variables the value 5.
Evaluation of the expression. Step 1: 4 * 3 = 12. Step 2: 12 % 5 = 2 (same level as *, left to right). Step 3: 10 + 2 = 12, then 12 - 2 = 10. Step 4: 10 > 8 = 1. Step 5: !0 = 1. Step 6: 1 && 1 = 1. Finally the assignment stores x = 1.
| Level | Operators | Order |
|---|---|---|
| 1 (high) | ( ) [ ] ++ -- (postfix) |
left to right |
| 2 | ! ~ ++ -- sizeof (type) |
right to left |
| 3 | * / % |
left to right |
| 4 | + - |
left to right |
| 5 | < <= > >= then == != |
left to right |
| 6 | && then \|\| |
left to right |
| 7 (low) | ?: = += |
right to left |
Conclusion. Knowing the operators and their precedence lets a programmer write correct expressions; when the order is unclear, brackets cost nothing and make the meaning certain.