This is the comprehensive and overly long page about writing a basic conditional statement.
Introduction
Conditional statements are the most basic way of managing program control flow. In more literal sense, it allows us to alter what actions the program takes depending on the conditions that we check against.
E.g. an event ticketing system, which will check for the person’s age and conditionally choose an action. Minors will not be allowed to purchase a ticket, elderly will have a reduced price for the ticket and everyone else will pay the full price.
In any sort of conditional statement, we do an evaluation that can only have two possible results – either true or false. In different programming languages they take different forms. In the C89/C90 standard, they use integers for this purpose, where true is 1 and false is 0. In the C99 extension. The keywords true and false can be used when they are either manually defined, or a library stdbool.h is included, which will define them.
Note that in many of the following paragraphs there are also negative examples – what not to do. They use high background color.
The if statement
The if statement has a basic form of
if ( expression ) statement
Where the expression is the condition to be evaluated and statement means all statements that will be executed, if the expression is evaluated to true.
The if statement is evaluated to true when the expression evaluates to a non-zero value! Note that it was said that true considered to be integer value 1, but the conditional statement will be also true when it is any non-zero value (including -5 or 100). More information under tips and tricks.
Let’s look at an example of this. In this case, we have initialized the variable age to 10. The conditional statement checks if the age is less than 18. Since it is, everything in the code block marked by the braces will be executed.
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <stdio.h> int main(void) { int age = 10; if (age < 18) { printf("You are underage!\n"); printf("You are not allowed to the event without parenthal supervision!\n"); } return 0; } |
Try it out
- compile and run the code, see what happens
- Edit the age to be 20, will the printf() statements be executed now?
- Edit the age to be 18 and try it again. This is what is known an edge case. Edge cases must always be tested, as they are common places for mistakes to creep in!
Relational operators
Assume the following declarations and values for the examples:
1 2 3 |
int a = 10; int b = 20; int c = 20; |
Note: The result of relational operators in C is always either integer value 1 (true) or 0 (false).
Operator | Description | Example |
== | Evaluates to true when both operands are equal | (a == c) –> false (b == c) –> true |
!= | Evaluates to true when operands are not equal. ! (exclamation symbol is used for negation) | (a != c) –> true (b != c) –> false |
< | Evaluates to true if left operand is smaller than the right operand | (a < c) –> true (b < c) –> false (c < a) –> false |
<= | Evaluates to true if left operand is smaller than or equal to the right operand | (a <= c) –> true (b <= c) –> true (c <= a) –> false |
> | Evaluates to true if left operand is greater than the right operand | (a > c) –> false (b > c) –> false (c > a) –> true |
>= | Evaluates to true if left operand is greater than or equal to the right operand | (a >= c) –> false (b >= c) –> true (c >= a) –> true |
The if-else statement
To add an alternative actions for the program to execute when the condition is not true, we can add the else keyword. The statements in the else code block will be executed when the expression is evaluated as false.
if ( expression ) statement else statementLet’s take the previously used example and add an else statement to it. Now depending on the value of the age variable, either the statements in the if code block run or the ones in the else code block.
1 2 3 4 5 6 7 8 9 10 |
int age = 30; if (age < 18) { printf("You are underage!\n"); printf("You are not allowed to the event without parental supervision!\n"); } else { printf("Age is verified, you are welcome to participate!\n"); } |
Try it out!
The initialized value for the age variable is now set to 30. The code will:
1. Evaluate the condition if (age < 18). This will be false, as age is set to 30 in this example.
2. The code block between the lines 3 – 6 will be jumped over. It will find the else keyword on line 7 and execute the contents in the braces.
Never leave an empty code block! This is bad style! If you don’t have anything to put in the code block where the if statement evaluates to true, invert the condition.
1 2 3 4 5 6 7 8 |
if (age < 18) // DO NOT DO THIS!!! This is incorrect! { } else { printf("Age is verified, you are welcome to participate!\n"); } |
Handling empty if statement code blocks
You should never have an empty code block. If it happens, there are are two possible options how to solve this.
- Find the opposite condition and use that
- Invert the original condition using the inversion operator (!)
Let’s take the bad example from the previous paragraph and fix it. First let’s try to find an opposite condition. To find people who are not younger than 18, we have to look for people who are over 18 years old or exactly 18 years old.
Common mistake: The opposite of (age < 18) is not age > 18 , but rather (age >= 18) . Watch out for edge cases!
1 2 3 4 |
if (age >= 18) { printf("Age is verified, you are welcome to participate!\n"); } |
An alternative way would be to invert the condition. This is done by the inversion operator (! – exclamation mark). In this case we take the whole original condition, put it in parenthesis and invert it.
1 2 3 4 |
if (!(age < 18)) { printf("Age is verified, you are welcome to participate!\n"); } |
Nested conditionals
Multiple if statements can be nested inside of each other. Be careful with this however, as it can mess up the program structure to the extent where it will take extremely long to untangle and debug it when something goes wrong. Always consider using else if statements, combining multiple conditions using logical operators and other refactoring techniques before writing multiple levels of conditionals. See headings below for this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
if (age >= 18) { if (age < 65) { printf("Tickets for adults: 10 euros\n"); } else { printf("Tickets for seniors: 4 euros\n"); } } else { printf("Minors only allowed with parental supervision\n"); } |
Using if-else if-else
To test against multiple subsets of conditions, we can introduce else if statements. The way it works is that it starts from the first condition and checks them one by one. If one of the conditions is evaluated to being true, the statement(s) within the code block following it are executed. No following expressions are checked once one of them is evaluated to true! If no conditional statements evaluate to true, an else statement will be executed, if it exists.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
int age = 30; if (age < 18) { printf("Minors only allowed with parental supervision\n"); } else if (age >= 65) { printf("Tickets for seniors: 4 euros\n"); } else { printf("Tickets for adults: 10 euros\n"); } |
Logical operators
There are a total of three logical operators defined in the C language. Note, that logical operators are different from bitwise operators.
Assume the following declarations and values for the examples:
1 2 3 |
int a = 0; int b = 10; int c = 20; |
Operator | Operation | Result will be true, when … | Example |
&& | Logical AND
Conjunction |
… both sides are true | (a && b) –> false (b && c) –> true ((a < b) && (b < c)) –> true |
|| | Logical OR
Disjunction |
… at least one side is true | (a || b) –> true ((a > b) || (b > c) –> false ((a < b) || (b > c) –> true |
! | Logical negation
Inversion |
… initial condition is false | (!a) –> true (!b) –> false (!a || !b) –> true (!(a || b)) –> false |
Truth tables
Logical and
A | B | A && B |
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
Logical or
A | B | A || B |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
Logical inversion
A | !A |
0 | 1 |
1 | 0 |
The switch statement
The switch statement will attempt to find an exact match between the expressions. If an expression is matched, it will run either until the end of the switch statement or a break statement. Not that if no break statement is encountered, the statements in the later cases will also be executed. This can either be a bug or wanted behavior, depending on the use case.
The default case is optional, but highly recommended. If no cases match the expression, the statements in the default case will be executed.
1 2 3 4 5 6 7 8 9 10 11 |
switch (expression) { case constant or expression: statements break; case constant or expression: statements break; default: statements } |
Some tips and tricks
The following section is a combination of various ways to use conditional statements efficiently.
Using only variable name as expression
This technique relies on the notion, that integer 0 is considered to be false in C language. Every other (non-zero) value is considered not false ( true).
In this kind of condition, we often use ! (inversion) to get the condition we need quickly.
Try the following code with the initialized value to 0, 1, -1 etc.
1 2 3 4 5 |
int inputDataValidation = 0; if (!inputDataValidation) // any non-zero value will evaluate to true (notice inversion) { printf("The data validation has failed!\n"); } |
Single-line conditionals without braces
In the previous examples, we have shown the the statements to be executed are always within braces. This is not a requirement. In C, both loops and conditional statements can be written without any braces, however in that case, only the following statement will be executed conditionally as a part of it.
The following example is a perfectly valid if-else statement in C and acts as expected.
1 2 3 4 5 |
int age = 30; if (age < 18) printf("You are underage!\n"); else printf("Age is verified, you are welcome to participate!\n"); |
These are often preferred to save space and are frequently used by experienced programmers. Sometimes you will also see that the statement will be on the same line as the conditional statement starts to save even more space.
1 2 3 4 5 6 7 8 |
int age; printf("How old are you?\n"); scanf("%d", &age); if (age < 18) exit(1); printf("Welcome to the event!\n"); |
Problems with single-line conditionals
Less experienced should however be careful when using such compact notions. There are multiple reasons to avoid it when starting to program.
1. Less consistent style
You will have to differentiate and notice various styles of writing the statements. This might be confusing in the beginning. Sometimes you will miss a statement here or there and it will cause bugs that are annoying to locate.
2. More prone to accidental mistakes when statements are added or removed.
You will often make alterations to your code during writing it or later on. Without enough experience you might not remember to add the braces when you add a line.
Let’s take the previous age check where it only notified about being underage and add and add another printf() statement that reminds about requiring parental supervision. We’ll also indent it nicely so it looks like it might be a part of the conditional statement.
1 2 3 4 5 |
if (age < 18) printf("You are underage!\n"); printf("You are not allowed to the event without parental supervision!\n"); printf("The tickets cost 20 euros!"\n); |
In this case when the age is less than 18, all 3 lines will be printed out, which is to be expected. When you forget to test the other case – where the age is 18 or greater, you will not even know that there is a mistake. For those 18 and over, it will still now warn about requiring parental supervision, even though that was not the intent of the developer.
3. Avoid ambiguity when nested conditionals are used.
This is also called the dangling else problem, which is important enough to deserve a separate heading. See below.
The dangling else problem
This one brings us back to the nested if statements. Let’s take an example
1 2 3 4 5 6 |
int age = 20; if (age >= 18) if (age < 65) printf("Price for full ticket: 10 euros\n"); else printf("Minors are not allowed without a guardian\n"); |
Syntactically the following code is correct and compiles. It contains a nested if statement that will print out the full ticket price for everyone who is from 18 to 65. Functionally however it behaves incorrectly. This is what is known a dangling else issue. In more layman’s terms – to which of the if statements does the else belong to? This is made worse by the deliberately indented code, that will make you think it’s correct. However just a reminder – C does not care about whitespace. It could all be written on the same line and it would still compile.
If you have relevant warning messages enabled for the compiler, you will see a warning about this:
warning: suggest explicit braces to avoid ambiguous 'else' [-Wdangling-else]|
Try the code out! Try it with the age set to 20. Then try with the age value of 80. As you can see, the else actually belongs to the last if statement, not the previous one as the indentation might suggest.
Refactoring bad nested conditional statements
Even though it is possible to nest if-else statements together to form constructs where multiple conditions need to be true, in many cases you will end up with badly readable code. This code can become hard to trace, debug and may even have branches that will never or always be true.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include <stdio.h> int main(void) { int age = 20; if (age >= 18) { if (age < 65) { printf("Price for full ticket: 10 euros\n"); } else { printf("Price for elderly: 4 euros\n"); } } else { printf("Minors are not allowed without a guardian\n"); } return 0; } |
This statement can be refactored in many ways. One way would be to reorder the conditions and introduce an else if statement.
1 2 3 4 5 6 |
if (age < 18) printf("Minors are not allowed without a guardian\n"); else if (age >= 65) printf("Price for elderly: 4 euros\n"); else printf("Price for full ticket: 10 euros\n"); |
There are both benefits and disadvantages to this. The benefit would be, that it’s really easy to read. All specific cases are tested first and then what will be left is the generic case.
The downside of this can be if the code needs to be well optimized for speed and the event is mainly for adults. This would make the code for the adult branch most likely to be executed, but all the other conditions will be tested first. This will cause the code to run slower. Refactoring often includes code execution speed or memory footprint optimizations, which tend to lie with theory of probability (according to the nature of our data, which condition is the most likely to occur).
1 2 3 4 5 6 |
if (age >= 18 && age < 65) printf("Price for full ticket: 10 euros\n"); else if (age >= 65) printf("Price for elderly: 4 euros\n"); else printf("Minors are not allowed without a guardian\n"); |
Function calls in conditionals
To optimize code readability, you often will want to separate code into lots of small single-purpose functions. If these functions return a value, it can be checked within the if statement’s condition.
We can use the comparison operators to compare the returned value from the function if it matches our criteria. Let’s take a function that will count the number of commas in a string.
1 2 3 4 5 6 7 8 9 10 11 12 |
int NumOfCommas(char *str) { int count = 0; int i = 0; while (str[i]) { if (str[i] == ',') count++; i++; } return count; } |
Now let’s call this function in an if statement.
1 2 3 4 5 |
char *t = "Hello, world!"; if (NumOfCommas(t) > 0) { printf("The text has at least 1 comma!\n"); } |
You can of course use the previously introduced concepts and make it cleaner
1 2 3 |
char *t = "Hello, world!"; if (NumOfCommas(t)) printf("The text has at least 1 comma!\n"); |
You will see a lot of similarly written code in data validation, where you will have only true/false return values for the functions.
We could have functions such as SentenceBeginsWithChar() , SentenceMoreThanOneWord() , SentenceEndWithPunctuation() , ContainsAVerb() etc – if all are considered true, then it will count it as a possibly a correctly formed sentence.
As usual, this can be combined with other conditional statements. You can also have multiple calls to functions in the conditional statement, as long as you formulate it according to the rules of the language.
Assignments in conditionals
This concept is more used within loops, but also appears in tasks such as data validation. It can be considered a 2-in-1 operation, so instead of doing an assignment before and checking the value later, you combine the two.
Ternary operator
TODO
Ternary operator is essentially a short way of writing an if-else statement. It has the benefit that it can be embedded into code where an if-else statement cannot, hence it’s also sometimes called an inline if. By language syntax, the following is the form for the statement, with symbols such as ? and : separating expressions.
expression ? expression : expression;
However this is not readable unless you know what you are looking at. Let’s write it out a bit more clearly
(condition_to_check) ? expression(s)_if_true : expression(s)_if_false;
De Morgan’s law
TODO