Introduction to Expressions in C

One of the fundamental features of the C language is the emphasis on expressions rather than instructions.

An expression is a formula that combines variables, constants, and operators to produce a value. For example, the expression a + b combines the variables a and b with the + operator to produce a value.

The simplest expressions consist of single variables or single constants. More complex expressions apply operators to operands to produce more complex values. In turn, operands can be other expressions.

Operators, therefore, are the basic tools for constructing expressions, and the C language provides a considerable number of them. At its core, the language includes three types of operators that are common to all programming languages:

  • Arithmetic Operators: used to perform mathematical operations such as addition, subtraction, multiplication, and division.
  • Relational Operators: used to compare values and produce boolean results (true or false).
  • Logical Operators: used to combine boolean expressions and build logical conditions.

But C is not limited to just these three types. We will introduce the various types of operators gradually throughout this guide.

In this lesson, we will start with the basics of expressions and study arithmetic operators, which are the most common and widely used in C. We will see how to use them to perform mathematical operations and how to combine multiple operators in a single expression.

Expressions and Operators

As we saw in the previous chapter, a program written in C typically follows this structure:

directives

int main() {
    declarations
    instructions
}

The instructions are the actions the program must perform. These can vary in type. They can instruct the program to carry out a task such as opening a file, reading input from the keyboard, displaying data on the screen, and so on. But they can also be expressions.

An expression is an instruction that returns a result. So we can consider an expression as a category of instruction.

In C, with few exceptions, nearly all instructions are expressions. This means that almost all instructions return a value. Technically, we say that an expression is evaluated.

Definition

Expressions

In the C language, an Expression is a type of instruction that returns a value.

When an expression is executed, it is said to be evaluated. The evaluation of an expression consists of calculating the value it returns.

The simplest expressions are those that contain only a variable or a constant. For example, the expression a returns the value of the variable a, while the expression 5 returns the constant value 5:

/* Examples of simple expressions */
a; // returns the value of the variable a
5; // returns the constant value 5
Definition

Simple Expressions

In the C language, a Simple Expression is a type of expression composed exclusively of a variable or a constant.

Evaluating a simple expression means returning the value of the variable or the constant.

To create more complex expressions, we need to use operators. Operators are special symbols that instruct the compiler to perform a specific operation. For example, the + operator instructs the compiler to perform an addition between two operands.

/* Example of a complex expression */
a + b; // returns the sum of variables a and b

The important thing to understand is that operands are always evaluated before the operators. So, in the expression a + b, the variables a and b are evaluated before performing the addition operation.

Furthermore, when combining multiple expressions containing operands and operators, it is important to consider the precedence and associativity of the operators. These two concepts determine the order in which operators are evaluated. We will study this concept in the following sections.

Definition

Operators

In the C language, an Operator is a special symbol that applies an operation to one or more operands.

An operand is itself an expression that returns a value.

In C, operators are evaluated based on their precedence and associativity.

In C, operators can also be classified based on the number of operands they accept:

Definition

Operator Classification by Number of Operands

  • Unary Operator: accepts a single operand. For example, the negation operator - changes the sign of a number;
  • Binary Operator: accepts two operands. For example, the addition operator + adds two numbers;
  • Ternary Operator: accepts three operands.

Additionally, unary operators are divided into two types:

Definition

Classification of Unary Operators

  • Prefix Operator: the operator is placed before the operand. For example, the negation operator - is placed before the number to be negated;
  • Postfix Operator: the operator is placed after the operand.

Arithmetic Operators

Let's begin studying the C operators starting with the simplest and most common ones: the arithmetic operators.

They are used to perform mathematical operations such as addition, subtraction, multiplication, and division, and represent the foundation of most programming languages. After all, computers were originally designed to perform mathematical operations!

Here is a list of the arithmetic operators in the C language:

Operator Type Description
+ Unary Prefix Identity operator
- Unary Prefix Negation operator
+ Binary Addition operator
- Binary Subtraction operator
* Binary Multiplication operator
/ Binary Division operator
% Binary Remainder or modulo operator
Table 1: Arithmetic Operators in C

Let's examine these operators in detail.

The first thing to note is that there are two versions of the + and - operators. This is because these operators can be used in both unary and binary form.

  • The unary + operator is called the identity operator. In fact, it does not perform any operation on its operand but simply explicitly indicates that the operand is positive. It is mostly used for readability.

    The following two expressions are, in fact, equivalent:

    int x = 5;
    int y = +5;
    

    This operator is rarely used in practice. In fact, it wasn't even present in the original versions of C.

  • The unary - operator is called the negation operator. It changes the sign of the operand. For example, the expression -5 returns the value -5, which is the opposite of 5.

    int x = 5;
    int y = -5;
    
  • The binary + and - operators are the addition and subtraction operators, and their behavior is straightforward.

    int x = 5 + 3; // x = 8
    int y = 5 - 3; // y = 2
    
  • Similarly, the * and / operators are the multiplication and division operators. They perform multiplication and division between two operands.

    int x = 5 * 3; // x = 15
    int y = 5 / 3; // y = 1
    
  • Finally, the % operator is the remainder or modulo operator. This one is less intuitive. Its purpose is to return the remainder of the division between two integers. For example, the expression 5 % 3 returns 2, since the remainder of dividing 5 by 3 is 2.

    int x = 5 % 3; // x = 2
    

Arithmetic operators can be used with both integer types, such as int, and floating-point variables of type double.

Furthermore, it is possible to use mixed-type variables as operands. For instance, you can add, subtract, multiply, and divide an integer with a floating-point number. In such cases, the result will always be a floating-point number. This behavior is known as coercion.

int x = 5;
double y = 3.5;
double z = x + y; // z = 8.5
Definition

Coercion of Arithmetic Operators

In the C language, coercion is the process of automatically converting a data type into another compatible data type.

Arithmetic operators in C perform coercion on data types to produce a consistent result.

If an arithmetic operator is applied to mixed-type operands, one integer and one floating-point, the result will always be floating-point.

Coercion is a very important topic that we will study in detail in future lessons.

The remainder or modulo operator, on the other hand, can only be used with integer operands. If you attempt to use it with floating-point operands, the compiler will return an error.

double x = 5.5;
double y = 3.5;
double z = x % y; // Error!
Note

Coercion Does Not Apply to the Modulo Operator

Coercion does not apply to the modulo operator %. It can only be used with integer operands.

Details on the Division and Modulo Operators

The division / and modulo % operators have some specific behaviors that are important to understand.

  • When using the division operator with integers, we might get unexpected results. In particular, when dividing two integers that do not divide evenly, the result is truncated, discarding the decimal part. For example, the expression 5/2 returns 2, not 2.5.

    int x = 5 / 2; // x = 2
    

    To obtain a floating-point result, at least one of the two operands must be of type double.

    double y = 5 / 2.0; // y = 2.5
    
  • If the second operand of a division or modulo operation is 0, the result is undefined. This is because division by zero is an undefined operation in mathematics.

    int x = 5 / 0; // Undefined result!
    

    The issue is that the program’s behavior in this case depends on various factors. Two of the most significant are the processor architecture and the operating system. Generally, division by zero causes a division by zero error that interrupts the program execution and is reported as an error.

  • When one or both operands are negative, the result of the division operation might differ from what one would expect.

    In the C89 standard, it is not explicitly stated whether the result of an integer division should be rounded up or down. Because of this, the result of 5/-2 could be either -2 or -3, depending on the compiler.

    Starting from the C99 standard, however, it was established that the result of integer division should be rounded toward zero. This means that the result of 5/-2 will always be -2.

    int x = 5;
    int y = -2;
    
    /* C89: result could be -2 or -3 */
    int z = x / y;
    
    /* C99: result is always -2 */
    int w = x / y;
    
  • The same applies to the modulo operator. Here too, the result of the division between two integers may differ depending on the compiler.

    If the compiler follows the C89 standard, the result of -5 % 2 could be -1 or 1. If it follows the C99 standard, the result will always be -1, meaning it follows the sign of the first operand.

    int x = -5;
    int y = 2;
    
    /* C89: result could be -1 or 1 */
    int z = x % y;
    
    /* C99: result is always -1 */
    int w = x % y;
    

Operator Precedence and Associativity

When an expression contains more than one operator, its meaning may be ambiguous.

Take, for example, the following expression:

int x = 5 + 3 * 2;

What does this expression represent? Should we add 5 and 3 first and then multiply the result by 2, or should we multiply 3 by 2 first and then add 5?

In mathematics, a convention called PEMDAS is used to resolve this kind of ambiguity. PEMDAS is an acronym that stands for:

  • Parentheses
  • Exponents
  • Multiplication and Division
  • Addition and Subtraction

According to this convention, multiplication and division operators have precedence over addition and subtraction. So, the expression 5 + 3 * 2 should be interpreted as 5 + (3 * 2), which is 5 + 6, resulting in 11.

Likewise, the C language uses the same convention and applies operator precedence rules to resolve ambiguities.

These rules define the order in which operators are evaluated:

Priority Operator
Highest Unary + and -
Medium *, /, %
Lowest Binary + and -
Table 2: Operator Precedence Rules in C

The precedence rules state that the unary + and - operators have the highest precedence. This means they are evaluated before all other operators.

The multiplication *, division /, and modulo % operators take precedence over the addition + and subtraction - operators. This means they are evaluated before the latter.

Finally, the addition + and subtraction - operators have the lowest precedence, meaning they are evaluated last.

Here are some example expressions showing how operators are evaluated based on their precedence:

Expression Result
5 + 3 * 2 5 + (3 \cdot 2) = 5 + 6 = 11
5 * 3 + 2 (5 \cdot 3) + 2 = 15 + 2 = 17
5 + 3 / 2 5 + (3 / 2) = 5 + 1 = 6
5 / 3 + 2 (5 / 3) + 2 = 1 + 2 = 3
5 / -2 + 3 (5 / -2) + 3 = -2 + 3 = 1
Table 3: Examples of Expressions with Arithmetic Operators

When operators have the same precedence, their associativity determines the order in which they are evaluated. Associativity can be left-to-right or right-to-left.

For arithmetic operators in C, the associativity is shown in the following table:

Operator Associativity
Unary +, - Right
Binary +, - Left
*, /, % Left
Table 4: Associativity of Arithmetic Operators in C

So, apart from the unary operators, all arithmetic operators in C associate from left to right. This means that, in cases where operators share the same precedence, they are evaluated from left to right.

For example, take the following expression:

10 * 4 / 2 * 3

Based on the rules of precedence and associativity, the expression is evaluated as follows:

\left( \left( \left( 10 \cdot 4 \right) / 2 \right) \cdot 3 \right) = 60

In this case, the multiplication * and division / operators have the same precedence but are evaluated from left to right.

On the other hand, unary operators + and - associate from right to left. This means that, in the case of multiple consecutive unary operators, they are evaluated from right to left.

Take, for example, the expression:

- + - 5

According to the precedence and associativity rules, the expression is evaluated as follows:

- \left( + \left( -5 \right) \right) = 5

Parentheses

The rules of precedence and associativity are fundamental in C, just as in all programming languages.

However, it's not necessary to memorize them, because in C you can use parentheses to force the order in which operators are evaluated.

Parentheses have the highest precedence and are evaluated before all other operators. This means that anything enclosed in parentheses is evaluated first.

The operator precedence table is thus modified as follows:

Priority Operator
Highest ()
High Unary + and -
Medium *, /, %
Lowest Binary + and -
Table 5: Operator Precedence Rules with Parentheses in C

Using parentheses, we can invert the precedence of operators and force the evaluation order. For example, if we want to perform an addition before a multiplication, we can write:

int x = (5 + 3) * 2; // x = 16

In this case, the addition is executed before the multiplication.

Parentheses can also be nested to create more complex expressions. In such cases, the innermost parentheses are evaluated first. For example, the expression ((5 + 3) * 2) + 1 is evaluated as 17.

Definition

Parentheses

In the C language, Parentheses are used to force the order of operator evaluation.

Parentheses have the highest precedence and are evaluated before all other operators.

Parentheses can be nested, and in that case, the innermost ones are evaluated first.

Hint

Use Parentheses Even When Not Strictly Necessary

In certain expressions, even if parentheses are not strictly necessary, it is always better to use them to increase code readability.

For example, consider the following code:

int x = 5 + 3 * 2;

Even though we know that multiplication has higher precedence than addition, it's always better to write:

int x = 5 + (3 * 2);

This makes the code clearer and easier to read.

In Summary

This lesson introduced the concept of expressions and operators in the C language.

  • An expression is a particular type of instruction that returns a value.
  • An expression can consist of variables, constants, and operators.
  • Operators are special symbols that apply an operation to one or more operands.
  • C provides a set of arithmetic operators to perform mathematical operations.
  • Arithmetic operators have precedence and associativity rules that determine the evaluation order.
  • Parentheses can be used to force the order in which operators are evaluated.

In the next lesson, we will study another very important type of operators in C: assignment operators.