Chapter 3 Summary and Quiz
Recap of the operator families, conversions, and precedence rules.
The Expression Machine
You can now read any C expression the way the compiler does: chop it by precedence, break ties with associativity, convert types as operands meet, and refuse to answer where the standard refuses to define. That last skill, knowing where the defined language ends, is what separates this course's answers from an answer key's. The recap, family by family.
The Operator Families
Arithmetic + - * / %: integer / discards the fraction; % gives the remainder and takes integers only. n % 10 and n / 10 peel digits; n % 2 != 0 tests oddness, and n % 2 == 1 does not, because a negative dividend gives a negative remainder. With a negative operand C89 leaves the direction of truncation implementation-defined, while GCC and C99 truncate toward zero.
Relational and logical: comparisons produce int 1 or 0; there is no boolean type. Zero is false, everything else is true. && and || evaluate left to right and stop when the answer is known, a guarantee you can hang a division guard on. = assigns while == compares, and the typo compiles. De Morgan's rule negates a compound condition: !(a && b) is !a || !b, flipping every comparison and swapping the connective.
Assignment shorthand: x += y and family compute-and-store. Prefix ++x yields the new value, postfix x++ the old; standalone they are identical.
Bitwise & | ^ ~ << >>: work them in binary, column by column. Shifts multiply and divide by powers of two; n & 1 isolates the lowest bit. Single & is bits, double && is truth, and ^ is XOR rather than a power operator, which C does not have.
Conditional cond ? a : b: evaluates the condition, then exactly one arm.
The math library: math.h declares sqrt, pow, fabs, floor, ceil, fmod and the rest, all taking and returning double in C89. fmod is the remainder % refuses to compute for floating point values. Link with -lm, and read "undefined reference" as a linker error rather than a mistake in your source.
The Rules Above the Operators
Precedence groups operands: unary, then * / %, then + -, then comparisons, with assignment near the bottom, and the wart worth memorizing: bitwise & ^ | rank below ==, so x & 1 == 0 silently becomes x & (1 == 0). Parenthesize bitwise expressions, always.
Associativity breaks ties: arithmetic left to right (100 - 20 - 5 is 75), assignment right to left (a = b = 0).
Precedence is not execution order. Grouping says whose result feeds whom, never which subexpression runs first. Keep side effects out of compound expressions and the difference can never hurt you.
Conversions: char and short promote to int; mixed operands widen to the larger type; double assigned into int truncates, no rounding. Signed meeting unsigned converts the signed side, which is why a negative count compares greater than a positive unsigned limit, and narrowing loses data three silent ways: long into int keeps the low bits, double into float rounds, negative into unsigned wraps. A cast converts explicitly, and placement decides everything: (double)total / count divides in double, (double)(total / count) launders a truncated quotient.
Floating point is approximate: decimal fractions like 0.1 have no exact binary representation, roundings accumulate, and so 0.1 + 0.2 == 0.3 is false. Never compare floating point values with ==; ask whether fabs(a - b) < tolerance for a tolerance your problem justifies.
The Undefined Line
Four entries from this chapter go in your permanent UB catalogue. Modifying a variable twice in one expression, or modifying and separately reading it, is undefined behaviour: i++ + ++i has no output, whatever a key claims, and the safe discipline is one modification per statement. Shifting by the type's width or more is likewise undefined, and shifting negative values left is undefined from C99 onward (C89 defined it by the bit pattern; avoid it either way). Integer division or remainder by zero is undefined, so guard it with the short-circuit count != 0 && .... And signed integer overflow is undefined, while unsigned arithmetic is defined to wrap. When an exam demands a number for UB, give its textbook's number and keep the truth in your pocket.
Two things that are not undefined, and are worth keeping separate from the list above: floating point division by zero yields infinity or not-a-number under IEEE 754, and every narrowing conversion has a defined, silent, usually unwanted result. Also remember that the sandbox's AddressSanitizer sees memory errors only; arithmetic undefined behaviour passes it undetected, so a clean run proves nothing about the four entries above.
One Expression, Every Rule
A cast placed before division, a truncating division beside it, a parenthesized bitwise test feeding ?:, a remainder, and the equality that fails on values every algebra student would call equal: the whole chapter in six lines. Predict all five outputs, run it, then take the quiz.
Looking Ahead
Chapter 4 returns to the console properly: character I/O with getchar and putchar, the full printf format language, and scanf treated with the respect its return value deserves.
How did you find this lesson?
Your rating helps us improve the content.
Create an account to track your progress and access interactive exercises. Already have one? Sign in.
Chapter 3 Summary and Quiz - Quiz
Test your understanding of the lesson.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!