Variables

Variables

variables provide named storage

every C++ variable has a type

a variable’s type determines its size and memory layout

Some Variable Definitions

bool flag = true;

int x = 3 + 4;
int y = 2 * x;

int z;  // bad: the initial value of z is unknown

const double avg_temp = -22.1; // avg_temp can't be modified

double x;  // compiler error: can't have two variables
           // with the same name in the same scope

double char = 104.5;  // compiler error: variables can't
                      // use reserved C++ words

char 4a = 'x'; // compiler error: variables can't start
               // with a number

Different Ways to Initialize a Variable

int a = 5;

int b(5); // notice this looks like a function call

int c{5};

int d = {5};

Initialization with {} catches more errors

long double ld = 3.1415926536;

int a{ld}, b = {ld}; // error: narrowing conversion required

int c(ld), d = ld;   // ok: but value will be truncated

Declarations vs. Definitions

not just variables have names: so do functions, classes, namespaces

there is an important difference between declaring a name and defining a name

Declarations vs. Definitions

a declaration makes a name known to a program

a definition creates the name’s associated entity

Declarations vs. Definitions

a name can be declared any number of times

but a name can only be defined once

Declarations vs. Definitions

int x = 5;  // variable definition
            // and also a variable declaration

this declares the name x, allocates memory to hold 1 int, and initializes it to 5

Declarations vs. Definitions

you can declare a variable without defining it using extern and not providing an initializer

extern int x; // x is declared, but not defined
x = 5;  // compiler error: x is undefined

extern int y = 5; // y is defined and declared

this declares the name x, but does not allocate memory to hold an int

that will be done somewhere else in the program

Declarations vs. Definitions

extern is usually used in programs with multiple compilation units (i.e. multiple .cpp files)

it lets you share variables between compilation units

we won’t be using it in this course

Scope

C++ is lexically scoped

that means you can tell where a variable is usable by looking at { and } tokens in the source code

(it wasn’t until the late 1970s that this became the dominate scoping rule for almost all programming languages)

Scope

int main()
{
   int x = 2;
   cout << x << endl;  // 2

   // braces indicate a scope
   {
        int y = 3;
        cout << y << endl;  // 3
   }
   cout << x << endl;  // 2

   cout << y << endl;  // compiler error: y is out of scope
}

Scope

int main()
{
   int x = 2;
   cout << x << endl;  // 2

   // braces indicate a scope
   {
        int x = 3;  // legal but a bad idea
        cout << x << endl;  // 3
   }
}

avoid declaring a variable in a nested scope with the same name as a variable in an outer scope

References

references let you give a value multiple names

int a = 7; // a refers to an int with the value 7

int b = a; // this makes a copy of a
           // so b refers to a different int with the value 7

cout << a << ' ' << b << endl; // 7 7
a++;
cout << a << ' ' << b << endl; // 8 7
b++;
cout << a << ' ' << b << endl; // 8 8

References

references let you give a value multiple names

int a = 7;

int& b = a; // this makes b refer to the same memory that
            // a refers to; so that memory location
            // now has two names

cout << a << ' ' << b << endl; // 7 7
a++;
cout << a << ' ' << b << endl; // 8 8
b++;
cout << a << ' ' << b << endl; // 9 9

Pointers

pointers store addresses

double s = 6.42;
double* p = &s; // & is the address-of operator
cout << "s is stored at address " << p
     << " and has the value "     << *p
     << endl;

in general, the address of a variable changes from run to run, so its exact value is rarely useful

Pointers

if p has type double*, we say it is a double pointer, or a pointer to a double

if p is a double pointer, then *p is the value of the memory it points to

*p is referred to as de-referencing a pointer

Pointers

generally, for any C++ type T, the type of a pointer to a T is T*

T can be any type, even another pointer

so you can get stuff like int**, which is a pointer to an int pointer

Pointers

double s = 6.42;

double* p = &s; // & is the address-of operator

double* q = p;  // p and q point to the same memory

cout << *p << ' ' << *q << endl; // 6.42 6.42

(*p)++;
(*p)++;

cout << *p << ' ' << *q << endl; // 8.42 8.42

Pointers

any pointer can be assigned the value nullptr

nullptr means the pointer is not pointing to any address

char* p = nullptr;
int* q = nullptr;
double* r = nullptr;

don’t use 0 or NULL for a null pointer!

Pointers

de-referencing nullptr causes a run-time error

char* p = nullptr;

cout << *cp; // Segmentation fault (core dumped)

Pointer Arithmetic

you can increment and decrement pointers

char a = 'a';
char* cp = &a;  // cp points to the memory holding 'a'

++cp;  // now cp points 1 byte past the memory holding 'a'

int i = 6;
int* ip = &i;  // ip points to the memory holding 6

++ip; // now ip points sizeof(int) bytes past the memory holding 6

Pointers

support for pointers is perhaps the distinguishing feature of C

they are surprisingly tricky to use correctly (even after you completely understand them); tools like valgrind are practically essential in C programming

most other languages put more restrictions on pointers than C/C++

C++ generalizes pointers to iterators in its standard template library (STL)

Pointers

pointers are like chainsaws: powerful but dangerous

Pointers and References

similar ideas

it’s easy to make complicated examples mixing pointers and references (among other things!)

so lets avoid that

we’ll almost always use references only for pass-by-reference parameter passing

const and constexpr

a constant expression is an expression whose value cannot change and that can be evaluated at compile time

const double PI = 3.145926;

cout << PI << endl; // 3.145926
PI = 3.14; // compiler error: can't change PI's value

const int max_files = 20;        // max_files is a constant expression
const int limit = max_files + 1; // limit is a constant expression
int staff_size = 27;             // staff_size is not a constant expression
const int sz = get_size();       // sz is not a constant expression

constexpr int mf = 20;        // 20 is a constant expression
constexpr int limit = mf + 1; // mf + 1 is a constant expression
constexpr int sz = size();    // ok only if size is a constexpr function

typedef

typedef makes an alias (synonym) for a type

typedef double real;
typedef real* real_ptr;

int main() {
    real r = 5.5;
    cout << r << endl; // 5.5
}

real and real_ptr are not new types

they are just different names for double and double*

using

using is another (more recent) way to make a type alias

using real = double;
using real_ptr = real*;

int main() {
    real r = 5.5;
    cout << r << endl; // 5.5
}

real and real_ptr are not new types

they are just different names for double and double*

auto

auto can infer the type of a variable from the type of its initializer

auto a = 5;    // a is an int because 5 is an int literal
auto b = '5';  // b is a char
auto c = 5;    // c is a double
cout << a << ' ' << sizeof(a) << endl  // 5
     << b << ' ' << sizeof(b) << endl  // 5
     << c << ' ' << sizeof(c) << endl; // 5
}

this is most useful when dealing with complex type names

struct

struct (and class) lets you create your own data types

struct point2d {
    double x;
    double y;
};

int main() {
    point2d p = {-2.3, 4.4};
    cout << p.x << ' ' << p.y << endl; // -2.3 4.4

    p.x = 2;
    p.y = 15.001;
    cout << p.x << ' ' << p.y << endl; // 2 15.001
}

struct

you can provide initial values for members of a struct

struct point2d {
    double x = 1.0;
    double y = 2.0;
};

int main() {
    point2d origin;
    cout << p.x << ' ' << p.y << endl; // 1.0 2.0
}

we’ll talk more about creating types when we discuss object-oriented programming (OOP)