Module 2 · Variables and Data Types
char, bool, and the Types That Are Really Numbers
In this lesson
- Read and print a
charboth as a character and as the number it really is. - Declare a
boolfrom<stdbool.h>and say what counts as true. - Use a comparison as a value, because its value is 1 or 0 and you can add it.
Bob prints his grade with %d instead of %c and gets 65. He restarts the Playground, retypes the program and gets 65 again.
Nothing is broken. He is looking at the same box through a different window, and the box was always holding 65. Module 1 lesson 4 said a char is a small integer; this lesson takes that seriously.
A char is a one byte integer
char is an integer type. It is one byte wide, it holds a small whole number, and printf decides whether you see the number or the shape.
#include <stdio.h>
int main(void)
{
char grade = 'A';
char digit = '7';
char space = ' ';
printf("grade: %c is %d\n", grade, grade);
printf("digit: %c is %d\n", digit, digit);
printf("space: is %d\n", space);
return 0;
}
grade: A is 65
digit: 7 is 55
space: is 32
Single quotes make a character constant, which is one character and is a number. Double quotes make a string, which is a different thing entirely and belongs to Module 10.
Because it is a number, it does arithmetic. grade + 1 is 66, and printed with %c that is B.
So char is the smallest integer type C has, and the fact that it usually holds a letter is a habit, not a rule.
Is a plain char signed or unsigned? The standard does not say
There are three character types, not one: char, signed char and unsigned char. The first is not the same as either of the others.
Whether plain char can hold a negative value is left to the machine. On the Playground, and on every Intel and AMD desktop, it is signed and runs from -128 to 127. On a Raspberry Pi it is unsigned and runs from 0 to 255.
#include <stdio.h>
int main(void)
{
char plain = 200;
signed char definitely_signed = -56;
unsigned char definitely_unsigned = 200;
printf("plain char : %d\n", plain);
printf("signed char : %d\n", definitely_signed);
printf("unsigned char : %d\n", definitely_unsigned);
return 0;
}
plain char : -56
signed char : -56
unsigned char : 200
The first two lines agree here and would not agree on an Arm board, where the first line would read 200.
The compiler flag -funsigned-char changes it too, which some projects set on purpose. The Playground does not pass it, so you get the signed reading.
So the rule is: plain char for letters, unsigned char for a raw byte of data, and never a plain char for a value above 127.
bool, true and false, and the header they come from
Old C had no true-or-false type. Zero meant false and everything else meant true, and people wrote int and remembered.
C99 added a real type called _Bool. That name is ugly on purpose, so that existing programs using bool as their own name would keep building. <stdbool.h> then gives you the friendly names.
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
bool is_enrolled = true;
bool has_paid = false;
printf("enrolled : %d\n", is_enrolled);
printf("paid : %d\n", has_paid);
printf("a bool takes %d byte\n", (int)sizeof(bool));
return 0;
}
enrolled : 1
paid : 0
a bool takes 1 byte
One byte for one bit of information. A machine cannot address anything smaller, so that is the price.
There is no %b for printing a bool. It is an integer type, so %d is correct, and it prints 1 or 0.
In C23, the newest standard, bool, true and false became keywords and the header stopped being necessary. This track is C17, so the header stays.
Anything that is not zero is true
A bool holds only 1 or 0. Assign it anything else and it collapses to one of those two.
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
bool from_seven = 7;
bool from_minus = -7;
bool from_zero = 0;
printf("7 becomes %d\n", from_seven);
printf("-7 becomes %d\n", from_minus);
printf("0 becomes %d\n", from_zero);
return 0;
}
7 becomes 1
-7 becomes 1
0 becomes 0
Read the middle line twice. Negative numbers are true. Only zero is false, and there is exactly one false value in the whole language.
So "is it true" and "is it positive" are different questions, and mixing them up is a bug that Module 5 will let you write.
A comparison is an expression, and its value is 1 or 0
This is the piece most beginners never get told, and it makes half of C easier to read.
a < b is not a special thing that only lives inside an if. It is an expression with a value, of type int, and that value is 1 when the comparison holds and 0 when it does not.
#include <stdio.h>
int main(void)
{
char ch = '5';
printf("is it at least '0' ? %d\n", ch >= '0');
printf("is it at most '9' ? %d\n", ch <= '9');
printf("both of those ? %d\n", (ch >= '0') * (ch <= '9'));
return 0;
}
is it at least '0' ? 1
is it at most '9' ? 1
both of those ? 1
The third line multiplies two answers that are each 1 or 0. The product is 1 only when both are 1, which is exactly "and".
C has a proper operator for that, written &&, and Module 4 introduces it with the rest of the operators. Multiplying works today and shows you what is underneath.
So a comparison is a number, and numbers can be added, multiplied and printed. You will use that in the exercises below.
One paragraph on enum
There is a fourth type that is really a number: enum. It gives readable names to a list of whole numbers, starting at 0 and counting up.
#include <stdio.h>
int main(void)
{
enum weekday { sunday, monday, tuesday, wednesday };
enum weekday today = wednesday;
printf("today = %d\n", today);
return 0;
}
today = 3
wednesday is the fourth name, so it is 3. Module 12 builds on this properly, alongside struct. It is here so that the sentence "the types that are really numbers" is complete.
The smallest program that proves a character is a number: print the same variable twice.
#include <stdio.h>
int main(void)
{
char ch = 'P';
printf("as a character: %c\n", ch);
printf("as a number : %d\n", ch);
printf("the next one : %c\n", ch + 1);
return 0;
}
as a character: P
as a number : 80
the next one : Q
One box, one value, three lines. The specifier is the only thing that changed.
Run in CompilerReading a mark and storing whether it passed. No if anywhere, because none is needed.
#include <stdio.h>
#include <stdbool.h>
int main(void)
{
int marks = 0;
scanf("%d", &marks);
bool has_passed = marks >= 40;
printf("marks : %d\n", marks);
printf("passed : %d\n", has_passed);
return 0;
}
marks : 39
passed : 0
That output is for the input 39. The comparison produced 0, the bool stored 0, and %d printed it. Every step is an ordinary value moving into an ordinary box.
Three characters arrive. How many of them are digits? Adding three comparisons answers it in one line.
#include <stdio.h>
int main(void)
{
char a = 0;
char b = 0;
char c = 0;
scanf("%c%c%c", &a, &b, &c);
int count = (a >= '0') * (a <= '9')
+ (b >= '0') * (b <= '9')
+ (c >= '0') * (c <= '9');
printf("%d\n", count);
return 0;
}
2
That output is for the input 4b9. Each of the three lines is 1 or 0, and they add up to the count.
Module 6 will do this with a loop and any number of characters. Until then, three lines is three lines.
Run in CompilerWhere this is used
- A keyboard. A key press arrives at the operating system as a scan code, a small unsigned integer. The layout table turns that number into the character your program finally reads.
- A JSON parser. Every parser in every language walks the text one
charat a time and compares it against'{','"'and':'. Those comparisons are the whole parser. - SQLite's flags. SQLite's connection structure carries dozens of one-bit settings. They are stored as bits inside one integer rather than as separate
boolfields, which saves memory on every open database. - A game's is_alive. An entity in a game engine carries a handful of
boolfields: alive, visible, on the ground. Each one is a single byte that the main loop reads sixty times a second.
Common mistakes
1. Double quotes around a single character.
char grade = "A";
printf("%d\n", grade);
On the Playground's GCC 12 this is a warning: initialization of 'char' from 'char *' makes integer from pointer without a cast, and the program builds and prints a meaningless number. GCC 14 makes it an error. 'A' is a character; "A" is a string that happens to be one letter long.
2. Two characters in one pair of single quotes.
char grade = 'ab';
printf("%d\n", grade);
GCC 12 gives two warnings with no flags at all: warning: multi-character character constant and warning: overflow in conversion from 'int' to 'char' changes value from '24930' to '98'. A character constant holds one character. The program builds and stores the b.
3. Using bool without the header.
int main(void)
{
bool ok = 1;
return ok;
}
GCC 12 says error: unknown type name 'bool' and helpfully adds note: 'bool' is defined in header '<stdbool.h>'. Add the include. The same program compiles without the header under C23, which is a different standard from the one this track uses.
4. Putting a value above 127 in a plain char.
char byte_value = 200;
printf("%d\n", byte_value);
No message, at either command line, and it printed -56. The same line prints 200 on a machine where plain char is unsigned. If the value is a byte of data rather than a letter, write unsigned char and the question disappears.
Maria is checking a form field one character at a time and wants a plain yes or no answer for each one.
Input. One line holding exactly one character.
Output. One line with 1 if the character is a digit, otherwise 0.
Constraints. The character is printable ASCII, code 32 to 126.
Sample. Input 7 gives 1. Input k gives 0.
#include <stdio.h>
int main(void)
{
char ch = 0;
scanf("%c", &ch);
/* Two comparisons multiplied together, printed with %d. */
return 0;
}
Not graded in this module. Do not use <ctype.h>; the point is that you can already answer this with arithmetic.
Three characters arrive with nothing between them. Count how many are digits, as in Example 3.
Input. One line with exactly three printable characters and no spaces between them.
Output. One line with the count, which is 0, 1, 2 or 3.
Constraints. Each character is printable ASCII, code 33 to 126.
Sample. Input 4b9 gives 2.
#include <stdio.h>
int main(void)
{
char a = 0;
char b = 0;
char c = 0;
scanf("%c%c%c", &a, &b, &c);
/* Three of Exercise 1's answers, added. */
return 0;
}
Not graded in this module. Leave the scanf line exactly as it is; a space before %c would skip the character you are meant to read.
Kenji's desk lamp starts off. Somebody flips the switch n times. Is the lamp on at the end?
Input. One line with one integer n, the number of flips.
Output. One line with 1 if the lamp is on, otherwise 0.
Constraints. 0 <= n <= 1000000000.
Sample. Input 0 gives 0. Input 7 gives 1. Input 1000000000 gives 0.
#include <stdio.h>
int main(void)
{
int n = 0;
scanf("%d", &n);
/* No loop. What is left over when n is divided by 2? */
return 0;
}
Graded as lamp-toggle. The remainder operator % arrives in Module 4; lesson 2's Exercise 4 showed you how to get a remainder with /, * and -.
Common doubts
Why is
'A'65 and not 1?Because ASCII says so, and the whole table is in Module 1 lesson 4. The letters happen to be in order, which is why
ch - 'a'gives a position.Should I use
boolorintfor a yes or no value?bool, in new code. It says what you mean, it collapses every nonzero value to 1, and it costs the same one byte.Can a
charhold a Bangla letter?No. A Bangla letter takes three bytes in UTF-8, so it needs three
charboxes. Module 1 lesson 4 says more, and Module 10 handles text properly.Is
sizeof(bool)always 1?On every mainstream compiler, yes. The standard only says a
_Boolis large enough to hold 0 and 1, so it does not promise the number.Is multiplying two comparisons real C, or a trick?
It is real, legal and occasionally used. Once Module 4 gives you
&&, use that instead: it reads better and it stops early when the first answer is 0.
Key takeaways
- A
charis a one byte integer;%cand%dare two windows on the same value. - Single quotes make one character and a number; double quotes make a string.
- Plain
charmay be signed or unsigned; useunsigned charfor a raw byte. boolneeds<stdbool.h>in C17, holds 1 or 0, and prints with%d.- Zero is the only false value. Negative numbers are true.
- A comparison is an expression worth 1 or 0, so you can store it, print it and add it.
Next you stop taking any of these sizes on trust, and ask the machine itself.
End of lesson 4
Mark it done, and your progress moves with you.
Next: sizeof and limits.h: Asking the Machine Instead of Guessing