Module 3: Variables

  • what is a variable?

  • how do we declare variables?

  • what is a variable’s type?

  • how do we use variables?

You should already have some familiarity with the concept of variables from mathematics. In math, we describe variables using "let" statements, e.g., $\textrm{let } x = \frac{2 \pi}{3}$. In this usage, the name $x$ is a placeholder for the value $\frac{2\pi}{3}$, i.e., wherever you see the variable $x$ you can substitute in the value $\frac{2\pi}{3}$ instead. If you wanted to express a changing value of $x$, you might use names like $x\prime$ or $x\prime\prime$, which are clearly related but are in fact different names: $x$ is not the same variable as $x \prime$.

variables

In computer programming, variables are a related but slightly different concept. Variables in programming languages, like in mathematics are names that can be used to refer to values. Unlike mathematics, however, programming variables do not refer directly to values but rather than places in memory that hold values. For example, the image to the right depicts three integer values (42, 17 and 54) being held at three different locations in memory. It is worth noting a couple of things about these variables:

  1. we refer to each one by a name,

  2. we talk about each one in terms of a type (integer vs real vs …​),

  3. each has a specific size in memory (which depends on the type) and

  4. each has a defined place in memory.

We will talk about these characteristics of variables in terms of both syntax and semantics.

Syntax

The way that we create a variable is to declare it. A variable declaration in C++ (or many other languages with static type checking) looks like one of these two lines of code:

double x;
int bestNumber = 42;

These variable declarations have two mandatory parts and one optional part:

  1. the variable’s type (int, double, bool, etc.),

  2. the variable’s name (which must be a valid identifier) and

  3. an (optional) initializer to set the variable’s value.

Once we’ve declared a variable, we can use it in later expressions, e.g.:

#include <cmath>

/* ... */

double x = M_PI;
double y = x / 2;

We will talk about the meaning of this in the semantics section below, but for now, note that variables must be declared before they can be used. It is an error to refer to a variable before it has been declared. If you try to reference a variable called angle before you declare it, the compiler will fail with a message saying something like, " angle? what’s angle? I’ve never heard of an angle."

Semantics

When we declare a variable, we set aside some memory to store a value. Like a mathematical variable, our variables will have names and types: int x means something similar to $ x | x \in \mathbb{Z} $).

Types

Some of the most common and fundamental types that we will use in the course are:

int

An integer value is an integer (in the mathematical sense) between INT_MIN and INT_MAX, which are defined in the climits header file and vary from system to system. For example, a modern notebook computer will have a range of possible integers that is two billion numbers wide, whereas on the $3\pi$ robot it is only $\approx$ 64k!

bool

A true or false value is known as a Boolean value, named after George Boole, a 19th-century philosopher who wrote The Laws of Thought. This book gave us a Boolean algebra that was foundational for computing and which we still use today!

char

Characters are numbers between 0 and 255, where each number is a code for a letter, number or symbol. These include:

  • all the upper and lower-case letters (A-Z and a-z)

  • the digits (0-9)

  • standard punction characters (.,<,>,?,",;,:,{,}, etc.)

  • some special characters (\n: newline, \t: tab, etc.)

float, double

float stands for floating point number, which means real numbers. double stands for double-precision floating point number. This is a more precise way of describing real numbers, and these days it’s the type used in practice. The cost of extra precision used to provoke questions about "is it worth it", but these days computers are built to handle double values by default.

The idea for variables in computing comes from mathematical variables, but there is also a crucial difference. Our C++ variable x represents a place in memory that stores an int. This chunk of memory will have a size and a location. The size is determined by the type: we need more space to store an integer (which could have a range of four billion values) than a character (which only has a range of 256 values).

Location

The location of a variable is a number (called an address) that identifies the exact place in memory where the variable is stored. Programmers use names to refer to variables, but once our source code has been translated into machine code, the computer will actually use the address to interact with the variable. Variable names are for the benefit of programmers, not the computer.

Value

A critical distinction between our concept of variables and that of mathematics is that we can change the value that’s stored in a variable. We can set the initial value of a variable using the initializer that we saw in "syntax" above. For example, the variable declaration int bestNumber = 42; declares that we are setting aside enough memory to store an int, that we are going to refer to that memory using the name bestNumber and that its initial value should be 42. If we don’t initialize a variable, its value is undefined. This means that, after a declaration like int foo; with no initializer, we have no idea what value foo contains. Undefined behaviour makes programs harder to understand and test, so it’s a good idea to initialize variables.

After a variable has been declared, we can also change the value that’s stored in it with an assignment statement. An assignment statement uses a single equals sign to change the value of the variable on the left, assigning the value of the expression on the right. For example, the following assignment statements change the value of a variable called x:

x = 17.0;
x = y / 2.0 + 9.1;

Note that this is different from mathematical equality. We are not stating that two things are equal, such that we can solve for one or the other variable. Rather, it may be helpful to think of a single equals sign as an arrow pointing left: the expression on the right is evaluated and then that value is assigned to the variable on the right. In particular, that means it’s perfectly legal to write the following assignment statement:

x = x + 1;

This is not a paradox! Since this is an assignment statement, we evaluate the expression on the right (x + 1) and then assign that value to the variable on the left (x). The net result of this assignment statement is to increase the value of x by one.

Referencing variables

Using variables in expressions is so simple that we’ve actually already done it without discussion: we simply write the variable’s name. An expression like x + 1 tells the computer, "go find the current value of x and then add 1 to it". So, if x is currently 42, then x + 1 will be 43. Simples!

Variable scope

We can create variables in two places: either inside functions or outside them. When we declare a variable outside of any function (called file scope), the result is a global variable. A global variable can be accessed by any C++ code in the rest of the file, following the rule that variables must be declared before they can be used. When we run a program, there is only one instance of every global variable: the global variable is shared among all of the functions that use it.

The more typical way to create variables, however, is within a function. Variables declared in this way are called local variables. These variables come into being every time a function is called and disappear when the function finishes its execution. Every time we call the function we get a new set of local variables. This allows us to have "scratch space" for the function to use in its work that is separate from all other functions (even different calls to the same function) and to know that no other code is interfering with our variables. This helps us to reason about a function’s behaviour: we don’t need to understand what every other function in the program does with global variables, we only need to understand how this function works with its local variables. For this reason, we strongly prefer local variables over global variables. Global variables do occasionally have their place, but you need a good reason to use a global variable whereas creating a new local variable is almost always a sensible thing to do.

Try stepping through the local-global.cpp example with The Teaching Machine. Notice how both aGlobalVariable and myLocalVariable are initialized to zero and incremented, yet their behaviour is different: myLocalVariable only lives as long as the execution of printThings(), so a new version of myLocalVariable is created every time we call it. aGlobalVariable, on the other hand, exists for the lifetime of the program.

We’ll come back to the concept of scope in a future lecture, once we’ve constructed some more interesting (nested) scopes.

Parameters revisited

Now that we’ve seen how variables work, we can understand function parameters a bit more precisely. In fact, parameters are just local variables, but what makes them special is that they are initialized by arguments in a function call rather than an explicit initializer like = 0. We can even modify them within a function, as shown in the code example speed.cpp. Try running this, or even better, exploring it with The Teaching Machine. Notice how, when we enter the speed function, the parameters distance and time are actually local variables that we can modify however we want to.

Constants

Sometimes we want to define a new value with a type and a name, but we don’t ever want it to change: we don’t want it to vary. In that case, we can use a particular kind of variable: a constant. Syntactically, we can turn a variable into a constant by adding the keyword const before it, as in:

const double FEET_PER_METRE = 3.28084;
const double MOLS = 6.0221409e+23;

Note that our convention is to spell constants with all capital letters to distinguish them from variables that can vary. This is a fairly standard practice, although some software projects prefer other naming conventions like FeetPerMetre or kFeetPerMetre.

It is illegal to modify the value of a constant: the compiler will stop you from doing this with a compile-time error. For example, trying to assign a new value to the constant FEET_PER_METRE makes no more sense than trying to assign a new value to the number 3.28084. There are a couple of reaons to prefer named constants to literal values:

Clarity

The expression 2 * x / FEET_PER_METRE is much clearer than x / 1.64042. Whenever our code contains hardcoded literal values that aren’t clear about their origins, it’s worth thinking about whether a named constant might be a better choice.

Maintenance

If we needed to change something in a large piece of software, it’s easier to change, e.g., one FEET_PER_METRE declaration than all of the places in code that use the literal value 3.28084 (or 1.64042, or…​).

Exercises

Variable inspection

Run the following example in the Teaching Machine and use it to answer the questions underneath.

#include <iostream>
using namespace std;

int main()
{
    int i = 44;
    long j = 98765432199;
    int k;
    double y;
    float x = 7.3;
    char character = 'N';

    character = 'n';
    k = j;
    y = i;
    x = 2*y;
    y = 1.60217646e-19;

    return 0;
}
  1. Step through the variable declarations. To what memory location is each variable assigned and how many bytes long is it?

    `i`: at 8192, 4 bytes.
    `j`: at 8196, 8 bytes.
    `k`: at 8204, 4 bytes.
    `y`: at 8208, 8 bytes.
    `x`: at 8216, 4 bytes.
    `character`: at 8220, 1 byte.

  2. Which variables have been initialized and which have not?

    `i, &nbsp;j, &nbsp;x` and `character` have been initialized. `k` and `y` have not been initialized.

  3. Step through the first two assignments. Can you explain why k is not equal to j?

    `j` is a `long` integer with a very large value of 98,765,432,199. This is bigger than the `MAXINT`, the largest possible `int` value (which is <span class="codeConstant">2,147,483,648</span> for the Teaching Machine). If you switch to binary view you will see that `k` (which is an `int`) was created from `j` simply by copying the bit pattern for the first 4 bytes (or first 32 bits). The very highest bit is the sign bit. Since it was a 1, the value for k is interpreted as being negative. Converting a `long` to an `int` is always dangerous.

  4. Which variables have valid values replaced by assignment?

    `x` is changed from <span class="codeConstant">7.3</span> to <span class="codeConstant">88.0</span> and `y` is changed from <span class="codeConstant">44.0</span> to <span class="codeConstant">1.60217646e-19</span> </p>

Global variables

Write a function that prints out how many times it has been called. Why is it necessary to use a global variable?

More quadratic play

Inspect the following function declaration as held in a header file:

/*!
 * @file      quadratic.h
 * @brief     A declaration of a function for finding quadratic roots.
 *
 * @author    Jonathan Anderson <jonathan.anderson@mun.ca>
 * @copyright (c) 2017 Jonathan Anderson. All rights reserved.
 * @license   Apache License, Version 2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

/**
 * Find the larger root of a quadratic equation of the form ax^2 + bx + c = 0.
 *
 * @param   a       the quadratic term @pre ???
 * @param   b       the linear term @pre ???
 * @param   c       the constant term
 *
 * @returns the larger root of the quadratic equation
 */
double quadraticRoot(double a, double b, double c);
  1. Write a mathematical expression for the value this function should return in terms of a, b and c.

  2. What preconditions should be applied to the parameters of the function?

  3. Write the corresponding function definition in a separate source file.

Amplifier gain

The gain of an a certain class of amplifier (i.e., how much the amplifier amplifies a signal) is given by the following expression:

\[ \frac{(\beta + 1)(R_L \|\| r_o)}{R_s + (\beta + 1)(r_e + (R_L \|\| r_o)} \]

where $\beta$ is a design’s "feedback factor" and $R_L$, $R_s$, $r_e$ and $r_o$ are resistances. Given that the resistance of two parallel resistors can be calculated as:

\[ R_1 || R_2 = \frac{R_1 R_2}{R_1 + R_2} \]

write a function ampGain that calculates the gain of an amplifier when given values for $\beta$, $R_L$, $R_s$, $r_e$ and $r_o$.

License: CC BY-NC-SA

(c) 2009–2018 Michael Bruce-Lockhart, Theo Norvell, Dennis Peters and Jonathan Anderson. Licensed under a Creative Commons Attribution–Noncommercial–Share-Alike 2.5 Canada License. Permissions beyond the scope of this license may be available at theteachingmachine.org.