Conditionals in C, C++ and Objective-C

Guess program in C. Conditionals, if, switch and constants
PROGRAMMING C GUESS C++ Objecticve-C
#include <stdio.h>

int main () {
  int n = 42;
  int h = 0, v = 0;

  printf ("Guess a number ? ");
  scanf ("%d", &v);

  if (v < 0) {
    fprintf (stderr, "Only positive numbers\n");
    return -1;
  }
  
  if (v == n) printf ("Good job!\n");
  else {
    printf ("Keep trying!");
    printf ("Number is %s\n", v < n ? "HIGHER" : "LOWER");
    
    /* On fail show a hint */
    printf ("Hint : ");
    h = (v > n) ? v - n : n - v;
    
    if (h < 5) printf (" TOO HOT!!!\n");
    else if (h < 10) printf (" HOT\n");
    else if (h < 15) printf (" WARM\n");
    else printf (" COLD\n");
  }
  return 0;
}

In C and C++, the if-then-else construct does not require us to write the then, which is quite common (Bash and Lua are exceptions). As we can see in the example, when each part of the if-else block follows a single statement, the curly braces {} are not necessary. In fact, in C and C++ several statements grouped with {} are syntactically a single statement.

The condition of the if statement must always be enclosed in parentheses and, as in most other languages, the else block is optional. It is possible, as can be seen in the program, to add a new if block after the else clause to further refine the program flow.

C allows defining if-then-else blocks in an alternative way, although beyond curiosity, its interest is limited. We will see this construction in other languages like Python or Bash, and it allows us to rewrite the block of code that generates the hint as follows:

  (h < 5)  && puts ("TOO HOT") ||
  (h < 10) && puts ("HOT")   ||
  (h < 15) && puts ("WARM")   ||
  puts ("COLD");

This construction takes advantage of the way the && and || operators are evaluated. Since a logical AND requires both operators to be true, the second expression (in this case, the puts statement) must be evaluated if the first is true. If the first is false, there is no point in evaluating the second since the result of the AND will be 0, regardless of the value of the second expression.

These expressions can be chained with the || operator which performs a logical OR and follows a similar logic, but taking into account that an OR is only false if both operators are false. In the case that the first operator is true, it is not necessary to evaluate the second (which would be equivalent to checking the next condition), but in case it is false, it is necessary to check the second value to obtain the result.

The ternary operator is ?: and is used as follows:

(condition) ? then-do-this : else-do-this

DID YOU KNOW

The sequence of ifs in our example could have been written using the ternary operator. It would be something like this:

    (h < 5) ? printf (" TOO HOT!!!\n")
        : (h < 10) ? printf (" HOT\n")
        : (h < 15) ? printf (" WARM\n")
        : printf (" COLD\n");

As you can see, the structure is very similar to the use of cond && inst1 || inst2, the difference is that the ternary operator is an operator and therefore allows us to build expressions, while the use of logical operators does not. In other words, the ternary operator allows us to return an arbitrary value, while the logical operation will only return zero or 1.

I’ll tell you in advance that languages that provide this operator do so using this same syntax (Python is the exception). The last line where we calculate the value of h to generate the hint, could be rewritten as follows:

h = abs (v - n);

Where abs is the function that calculates the absolute value of an integer and is defined in stdlib.h.

NOTE

The scanf (Scan with Format) function is the complement to printf and allows us to read values from standard input. The string passed as the first parameter is called a format string (format string) and allows us to specify the types of the data we want to read into the variables passed as subsequent parameters.

In this case %d means Decimal and is used to read a number in decimal format.

Other languages provide similar solutions and often use the same (or very similar) set of characters for format strings.

To finish, conditions, both the if statement and the ternary operator ?:, do not have to be boolean operations in C, but it is recommended that they are. In the case that they are not, a value 0 or NULL is equivalent to the logical value FALSE and anything else is equivalent to TRUE.

DID YOU KNOW

The C language introduced the bool type to represent boolean values in the C99 standard. In C++, the bool type has been supported since always and in Objective-C, although we can use bool the same as in C99, a large amount of code uses the type redefinition BOOL which is nothing more than an unsigned char. Objective-C BOOL takes values YES and NO instead of the true or false of the standard bool type.

C (as well as C++ and Objective C) also offers the switch command, which can be seen as a sequence of ifs. Its syntax is as follows:

switch (variable) {
        case CONSTANT1: //code
        break;
        case CONSTANT2: //code
        break;
        default: //Code
}

This code will evaluate the variable and jump to the corresponding case or to the default label in case the value of the variable is not listed in any of the cases. switch works literally as we have described. The program flow is diverted to the corresponding case and therefore we must end the block of each case with a break statement or otherwise the program will continue executing sequentially passing through all the remaining cases.

I’M CURIOUS

Look up Duff’s Device (Duff’s Device) to see a curious use of switch

C++ defines the attribute [[fallthrough]] to indicate the situation in which the break is not included on purpose and it is expected that the following code will be executed. For generated code purposes the attribute has no effect and is only used by compilers or utilities that perform static analysis of the code reporting things like forgetting the break in a switch.

This program uses three integer variables: v, n and h. In C it is necessary to declare the variables (in reality this applies to all) before being able to use them, that is, give them a name (v, n or h in this case) and a type that in the previous example is the integer type int.

I’M A BEGINNER

Variables allow us to store values and modify them as needed in our programs. In this case, the variable v is used to read the value entered by the user. The variable h is used to store an intermediate result. We could reuse the variable v since we don’t use it again in the rest of the program, but in general, that is not a very good idea unless we want to look cool (or have serious memory restrictions), since it makes the program harder to follow.

It’s good practice to give variables names with meaning, especially nowadays that we don’t have memory restrictions and development environments will write most of the names for us. However, in simple programs or functions, it’s sometimes practical to use short names, especially if there are very long operations or conditions that become difficult to read. In the end, use common sense. In this example, the program is so simple that there’s really no difference between one approach or the other. But maybe the code would be a little easier to read declaring the variables as:

int  number;
int  hit, value;

The variable n… well, it doesn’t change value, so it could be a constant… In C/C++ we can do this in several ways:

#define N 42

const int n = 42;

Traditionally C uses the preprocessor directive #define. In this case, what the pre-processor does is literally replace the value of H by 42 throughout the code before compiling it… Just like a text processor would. In general, this works perfectly, but it’s important to know what we’re doing to avoid unwanted effects. #define is really a substitution macro rather than a formal constant.

In the second case, the keyword const tells the compiler that this variable can only be initialized once, after which its value cannot be modified. While this is not entirely true, in programs that don’t do tricks with pointers the declaration will function as a constant for compilation purposes.

The main advantage of the second method is that we have type information. If our string is an integer, our constant also is. Once again, this is not a big problem if you know what you’re doing, but otherwise, C/C++’s automatic type promotions can play tricks on you.

Summary

  • Supports if ... else [if] ... and (condition) && ... || ...
  • Supports ternary operator (cond) ? true_value : false_value
  • Versions prior to C99 do not support booleans: ==0 -> FALSE and !=0 TRUE
  • Supports switch (variable) { case VALUES: break; default:}
  • Constants with preprocessor: #define H 42 (just a sequence of characters)
  • Constants with modifier: const int h = 42 (associated type)

Related Posts

GNU/Linux Programmer's Solver - Introduction

Programming exercises in 18 Programming Languages

Read

linuxprogramming

GNU/Linux Programmer's Solver
Chapter 1 - Hello World

Hello World is the first program you write when learning a new language and there are good reasons for that

Read

PROGRAMMING HELLO WORLD

Return to Home Page