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$.
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:
we refer to each one by a name,
we talk about each one in terms of a type (integer vs real vs …),
each has a specific size in memory (which depends on the type) and
each has a defined place in memory.
We will talk about these characteristics of variables in terms of both syntax and semantics.
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:
the variable’s type (int
, double
, bool
, etc.),
the variable’s name (which must be a valid identifier) and
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
."
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} $).
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).
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.
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.
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!
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.
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.
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:
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.
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…).
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;
}
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.
Which variables have been initialized and which have not?
`i, j, x` and `character` have been initialized. `k` and `y` have not been initialized.
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.
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>
Write a function that prints out how many times it has been called. Why is it necessary to use a global variable?
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);
Write a mathematical expression for the value this function should return
in terms of a
, b
and c
.
What preconditions should be applied to the parameters of the function?
Write the corresponding function definition in a separate source file.
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$.
(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.