Changing and controlling code flow in C
C has a few native statements to control the flow of your code within the program, instead of just piling up instructions one after another. Some may argue about other statements which i do not present here, and i plan to cover some of them later, however lets take a look at the most common of them.
if statement
The if statement will take one expression and execute other statements inside its body if this expression is evaluated as true ( greater than 0 is true and 0 is false). If evaluated as false it will just skip whatever is inside its body(iside the brackets):
if (expression) {
statements;
}
If your if statement has only one command statement within its body, you may omit the brackets:
if (1) printf("if with no brackets\n");
However, you cant write more than one statement in C ifs, unless you add brackets:
int x = 1;
if (1) {
x++;
printf("%d", x);
}
If statements can be declared one inside the other, nesting the structure:
int x = 1;
int y = 8;
if (x < 10) {
printf("x: %d\n", x);
if (y > 7) {
printf("y: %d\n", y);
printf("x is less than 10 and y is greatar than 7\n");
}
}
You would very well be able to execute whats inside the second if with a logical operator &&:
if ((x < 10) && (y > 7)) {
printf("y: %d\n", y);
printf("x is less than 10 and y is greatar than 7\n");
}
But there may have instructions which you only want to execute when x < 10, situation where you will pick a nested if, and instructions whichi you may wish to execute if both conditions apply. In this case any option is ok, use a logical operation or a nested if, altough a logical operator will be way more clear.
Else and else ifs
else, and else if statements are optional part of if statements, and may be omitted.
Else if statement will be evaluated when your if statement is evaluated to false, then the same expression will be avaluated for another value. You may include as many else if statements as you like:
int x = 3;
if (x == 0)
printf("x is 0\n");
else if (x == 1)
printf("x is 1\n");
else if (x == 2)
printf("x is 2\n");
else if (x == 3)
printf("x is 3\n");
Else statements are also optional after the if, and may appear either else if has been used or not. However they must be final, after the if statement, and also the else if, if you decided to use it.
unsigned int x = 5;
if (x == 0)
printf("x is 0\n");
else if (x == 1)
printf("x is 1\n");
else if (x == 2)
printf("x is 2\n");
else if (x == 3)
printf("x is 3\n");
else
printf("x is a number bigger than 3. I cant even count it!\n");
Like the if statement, else if and else can execute more than one instruction, all you have to do is put your instructions inside the brackets then.
else (expression) {
statement1;
statement2;
}
short if
In your journeys, you may encounter with this strange type of structure:
int x = 1;
x > 0 ? printf("x is true\n") : ("x is false \n");
This is nothing more than a short if. Short ifs have only one statement if the expression is evaluated to true, and one if it is evaluated to false. It could look a bit weird at first sight, specially since it omits the if statement, but with just a little practice you will feel very confortable with it. This is nothing more than writing if like this:
int x = 1;
if (x > 0)
printf("x is true\n");
else
("x is false \n");
Already easy on your eyes? Lets walk trough it then. Before the question mark, we have the expression to be evaluated, in case x > 0. If its evaluated to true, it executes the first (left) instruction. If evaluated to false(0) it will ignore the left estatement and execute the second(right) statement. Easy now isnt?
While
While statement will perform your commands, as long as the condition contained in the while expression is true. You may call it a loop.
int x = 10;
while (x > 1) {
x--;
printf("x: %d \n", x);
}
Important is to note 2 elements in this loop, the test expression, and the body of the loop. Here is our test expression:
while (x > 1)
And here is the body of our loop contained within the brackets:
x--;
printf("x: %d \n", x);
The body is the structure which will be repetead until your test expression becomes false, but pay atention, if your test expression already starts evaluated as false, the body will not be performed not once.
Break
The break statement can be used to terminate a loop. Lets take a look how it works at the while loop which we already learned:
int x = 0;
while (1) {
printf("x: %d \n", x);
x++;
if (x > 8) {
break;
}
}
Since any number greater equal than 1 is considered to be true, this loop would continue forever and ever, until you close manually your console application. But since we have inserted an if statement, telling to break, or terminate a loop, when x is greater than 8, our loop will only print on the screen the values 0 to 8.
Continue
The continue statement will terminate the current iteration which is being evaluated at the moment:
int x = 0;
while (x < 10) {
x++;
if (x == 8) {
continue;
}
printf("x: %d \n", x);
}
What would happen if we had written the following code instead:
int x = 0;
while (x < 10) {
printf("x: %d \n", x);
x++;
if (x == 8) {
continue;
}
}
Since our printf() command is declared before the conditional if, it seams the continue instruction was not executed terminating current iteration and jumping to next. The case is, it did in reality, however, since there are no instructions after the continue statement, theres nothing to jump, even if current iteration was terminated at this current point. If there was any instruction beyond this point and inside the conditional if, then it would be ignored.
Do ... While
The do...while loop looks a lot like the while loop. The difference is the statemets to be executed come first in the do command, then the expression is tested in the while:
do {
printf("to be executed at least once \n");
} while (0);
a while(0) already evaluates the expression as false. if had we used in a while loop, it would not print anything, since it evaluates the expression before executing the body of the loop. So the following code would never print anything:
while(0) {
printf("to be executed at least once \n");
}
But since we are working with a do command to be executed before the expression being tested, any code inside the do body will execute at least once, even if our test expression already starts as false at the moment its being checked.
Switch
Switch statemets work to evaluate various conditions which the same expression may assume or take form, in a cleaner way than a bunch of if statements.
The smallest switch statement we could have would be something like that:
switch (expression) {
printf("inside switch statement\n");
}
In this case, switch will test for the expression value. However, altough such declaration is legal in C, it would be pratically useless, since we dont have any parameter to evaluate or compare it. Thats where the "case" keyword enters the stage. Case will contain the value your expression is compared with, and if they match, the contained code whithin case is executed:
int x = 5;
switch (x) {
case(3):
printf("x is 3\n");
case(4):
printf("x is 4\n");
case(5):
printf("x is 5\n");
}
Each case statement must have a unique value and must compare the expression for an integer value. So the following code will not compile:
char s = "s";
switch (s) {
case "s":
printf("we got s\n");
}
Now lets say our test matched in the first case statement. Look what happened:
int x = 3;
switch (x) {
case(3):
printf("x is 3\n");
case(4):
printf("x is 4\n");
case(5):
printf("x is 5\n");
}
Wow!?! if x is 3, then why it printed 4 and 5 also at the screen? In this case what hapenned is that our code will evaluate the case comparation to true, then fall trough the code below it. To not keep executing statements, all we have to do is place our old friend break statement at the end of every case statement, so we can exit the our switch gracefully and not execute the rest of the code:
switch (x) {
case(3):
printf("x is 3\n");
break;
case(4):
printf("x is 4\n");
break;
case(5):
printf("x is 5\n");
break;
}
You may have seen the last break is actually optional, but i would strongly recommend you still place it for matters of future maintenance.
Now, what would happen if the value of the expression does not match any of the case labels? Our swich statement would just reach the final bracket, no questions asked, no action taken. Lets introduce our next statement for the switch then. The default statement will enter the action as a last resort, in the situation where none of your case statements match the expression tested:
int x = 6;
switch (x) {
case(3):
printf("x is 3\n");
break;
case(4):
printf("x is 4\n");
break;
case(5):
printf("x is 5\n");
break;
default:
printf("x is not what we are looking for.\n");
}
Great. Aint it more clear and organized than using a bunch of arbitrary if statements, followed by else ifs? And you also take as a bonus a default statement, what could be better than this except some hot chocolate? Imagine what happens with millions of ifs in your code, testing your same expression a million times. When you exchange it for switch, your expression will be
tested only once!
switch (x)
Internally, the compiler will optimize your switch statement by creating one label for each case(or default statement if present), then all it is left is to get the test expression, perform an arithmetic and then a few jumps to the selected label deppending on the result of the arithmetic.
For
The for statement is a loop, much like the while loop, and has some components you would like to take note:
for (exp1; exp2; exp3) {
statements
}
exp1, 2 and 3 are expressions, separated by semicolons. In general, exp 1 is the declaration of the variable to be tested. As a declaration, you have to declare properly, event declaring its type:
int x = 0;
exp2 is the test expression of our variable declared in exp1:
x < 10;
exp3 will update your declared variable, by modifying and updating it, with operations for example, lile increment, decrement, multiply and divide:
x++
Then we may end with an initial structure like this:
for (int x = 0; x < 10; x = x + 2)
Note we dont add a semicolon to the last expression of the for loop.
Also take note i wrote this as a general rule. In fact you have have a good degree of flexibility in the for loop, like being free to omit an expression, like this:
for (int x = 0; ; x++ )
In this case omiting expression 2, however i would strongly suggested to review what kind of code structure you really desire, or implement the missing expression in the body of the loop itself:
for (int x = 0; ; x++ ) {
printf("x: %d \n", x);
if (x >= 8) {
break;
}
}
Since you may end up within an unpleaseant situtation, like a infinite loop.
The next component to check, is the statements components, within the brackets, or the body of the for loop. Those will be executed, like in the while loop, every time until the tested condition is evaluated to false. As you have seen, you can mess with the components of the for loop, exp1, 2 and 3 inside the body of the loop as you like.
Why would you use the for loop, instead of the while loop then? That depends of the programmer. To me, i think it makes the code more clear, instead of using variables which may be declared at one part of your code completely different or far from your loop, may be a bad idea. Also note, you dont have to worry about declaring variables which would only exist to be part of your loop, neither about the memory space they take. Look:
for (int x = 2; x < 8 ; x++ ) {
printf("x: %d \n", x);
}
in this case x, declared within our for loop was created and erased as soon our loop ends. If you try to acess x after the loop, you would get an error, since x only lives within the local scope of the for loop. with the while loop, you would have a structure like this:
int x = 2;
while (x < 8) {
printf("x: %d \n", x);
x++;
}
With our variable x declared outside the body of the while loop, you would be able to acess x after the loop, it would still ocuppy memory in your app, and any change you made inside the loop, would still be active in the x even after the loop. With for loop, you dont have to worry about it!
Return
The return command is our old friend, as we have seen it briefly before. Return has the property to end the function at the moment it is called, and also return values to whoever calls it, returning the execution flow to any function point or piece of code it was called. You may have code or functions which have no return statement to end the it. In this case the function will end at the closing bracket, and it will only execute your statements, not returning anything to anyone. However the signature of your function must be compatible with your return:
void function f() {
printf("a function with no return");
}
A function with no return, must be declared as void. A function declared with this void modifier may have a return statement, and it will end the function before reaching the final bracket, however, it wont really return anything, so trying to do something like this will be invalid:
void f();
int main()
{
int x = f();
return 0;
}
void f() {
printf("code inside function\n");
return 7;
printf("this code is not executed since return was called in previous statement \n");
}
In this case we were trying to return the value 7 to the int x, but since it was a void function, it was incompatible with the datatype we were trying to atribute a value. The right way to call this function is like that:
void f();
int main()
{
f();
return 0;
}
void f() {
printf("code inside function\n");
return 7;
printf("this code is not executed since return was called in previous statement \n");
}
Now your code will work. Note it will only print the first printf() command. Then we return within a void function, and no code after this command will be executed in the fucntion anymore, so our secondf printf() will be ignored.
Functions declarations should have a compatible return. You must be careful for example, declaring a function with one datatype and returning another. Altough it is possible in some situations, you may end up with errors difficult to find, like returning a value higher than the function declaration. Look at this example:
char f();
int main()
{
int c = f();
printf("%d \n", c);
return 0;
}
char f() {
printf("code inside function\n");
return 10256;
printf("this code is not executed since return was called in previous statement \n");
}
In this case, the compiler will not complain of our function f(), however it will return a truncated value, too large to be stored in a char datatype. It will return our value of 10256 and attempt to store this value into the variable int c. However, even c being declared as int, and not having any problem at all storing a value size of 10256, the function will return a char, which cant store a value of this size, so it will truncate the value then return, not making any difference if you declare the variable c as a int or char, the value stored in it will be a truncated version of 10256, ending up with 16. This kind of error will not raise any flags on the compiler, thats why they are difficult to spot. Imagine now a huge code with hundreds or maybe even million of lines returning a wrong value everytime, and you cant count on the compiler to help you find where the error is being generated!
Goto
The goto statement may be used to replace ifs and loops. It can be used by creating a series of labels. Each label works similar as the case statement we have seen in the switch statement section. After creating that label containing ode to be executed, you can use the goto statement to move your execution to tht specific label:
label1:
printf("we are at label 1\n");
goto label3;
label2:
printf("we are at label 2\n");
goto label4;
label3:
printf("we are at label 3\n");
goto label2;
label4:
printf("we are at label 4\n");
You are able to declare labels by the same rule you declare variables. Just no try something too fancy. You must be careful with labels as your code lose some clarity, and you may end up also in undesirable situation like in an infinite loop:
label1:
printf("we are at label 1\n");
goto label3;
label2:
printf("we are at label 2\n");
goto label1;
label3:
printf("we are at label 3\n");
goto label2;
Many programmers really hate this statement. I myself, since I'm here to place my toughts about programming, dont think the statement is the evil incarnated. In fact I have used to create cheap and fast code, to bigger code, as long as you dont fill your program with loads of it making it difficult to follow the flow, and with good documentation, i dont see big problems, and can even take you out of situations like those of nested loops, where break statement only terminates the inner loop where it was declared:
int x = 10;
int y = 9;
int z = 8;
while (x > 0) {
x--;
printf("x: %d \n", x);
while (y > 0) {
y--;
printf("y: %d \n", y);
while (z > 0) {
z--;
printf("z: %d \n", z);
if (z < 3) {
goto end;
}
}
}
}
end:
printf("terminating program\n");
return;
In this hypothetical example, z was being decreased at each iteration, and since it reached its desired value, we can just jump out of the loop and terminate the program. Otherwise, had we used the break statement instead of goto, the program would just jump to the loop above decreasing y at each iteration, then finally decreasing x, and only then it would end the loop.
If you remember what i told about the switch statement, and about compilers creating labels for each case, then performing jumps to these labels after an arithmetic, well, very good. In the goto statement something very similar takes place internaly. In fact its so similar, that many programmers use the goto statement to visualize better what happens internally. The labels are the very same logic internally or in C programming language, then your code will use goto statement to jump to that label. The only difference here is, internally, the compiler will use a statement called jmp (or perhaps one of its variations like je, ja etc) instead of goto.
Hunf! So tired. Hope to come back later to finish formating this post :)