The Advanced Course begins with the assumption that you are familiar and comfortable with programming so that we can advance to more complex, more difficult, more interesting topics.

Important

This course assumes you are familiar with every concept in this lesson, perhaps with the sole exception of Object Oriented Programming.

If you are unable to solve problems such as CCC16S2: Tandem Bicycle, then it would probably be wise to take the Beginners' course instead.

The purpose of this lesson is to familiarize you with (or to provide you a review of) C++ syntax, which is this course’s language of choice.

Comments

In C++, single-line comments are denoted with //. All text on the line of the double-slash, after the double-slash, is commented and won’t be parsed by the compiler. Code::Blocks will colour them grey by default. In other environments, they are commonly coloured green.

1
2
3
4
5
// This is a single-line comment.

int n; // This is also a single-line comment,
       // but what came before the double-slashes
       // will still be parsed by the compiler.

Sometimes, very long or very short explanations may be warranted. The beginning of a comment block is denoted by /* and the ending by \*/. This allows us to put a comment in the middle of a line — or across multiple lines without needing to double-slash every line.

1
2
3
4
5
6
7
8
9
10
11
12
/* This is the beginning of a comment block.
 * It's standard convention to put an asterick
 * in front of all lines contained in a comment
 * block.
   But it isn't necessary.
 * It's also standard convention to not end the
 * block at the end of a line, but at the beginning
 * of a new line.
 */

int /* We're putting this here as an example;
don't actually do this with simple statements.*/ n;

Comments are used to document code; when we show code to you, you will see many comments explaining exactly what each line does. We would also appreciate it if, when you show us code to debug, you also include comments explaining what you intend each line to do.

Input and Output

C++ is a derivative of C, and both languages have their own library for I/O. Both methods work; the C++ version is simpler (for now), but we’ll introduce the C version later.

The C++ I/O library is in the <iostream> header and resides in the std (Standard Library) namespace.

Output Using std::cout

To write (or print), we use the std::cout "function"with the << operator:

1
2
3
4
5
6
7
8
9
10
#include <iostream>  //for std::cout, std::endl

using namespace std; //so we don't have to write the std::

int main(int argc, const char* argv[])
{
    //print Hello World!, a newline, and flush the stream
    cout << "Hello World!" << endl;
    return 0;
}

In line 8, we print Hello World! with STL’s std::cout. So that the code would execute, we encompassed the code inside the main function (line 5). So that our compiler knows what cout and endl are, we first included <iostream> (line 1) and then told the compiler to allow us to freely use things from the std namespace (line 3).

Note

It’s generally considered bad form to use using namespace std;, as it locks up many symbols (variable, class, function names) from use. As such, these lessons (except for this section) will not use using namespace std;, instead opting to write the full symbols.

You may choose whether or not to use this line; it really doesn’t matter in your own code.

All of that is to say that, for now, we simply need those three lines to run any code. The real meat of the matter here is line 8:

    cout << "Hello World!" << endl;

If you’ve never used C++ before, this line looks pathological. Did I not just say that std::cout is a function? What’s with the double angle brackets?

This is a very interesting peculiarity with C++; for some reason — perhaps to appear more…​ beginner friendly? — the <iostream> library really likes treating everything visually as a stream. The double angle brackets are like the direction of the stream: in this case, towards std::cout (which stands for, by the way, Console Output). This becomes more clear when you begin to look at reading from the console.

Note

std::endl is not just a newline character. To simply print a newline character, you can use '\n'; std::endl generally prints both the newline character and the carriage return character ('\r'), and then flushes the stream.

When printing to the console, what is intended to print is first stored in a buffer. It doesn’t print until it decides it needs to; on a typical console line, that’s during a screen refresh (sixty times a second). However, if you’re interacting with another program (like in practice problems), that’s rather slow; instead, you can flush it to have it printed immediately. std::endl provides this functionality; you can also use std::flush or a program termination to print your output immediately.

And, uhh, no…​ std::cout is not actually a function; it’s an object. We’ll just stop here.

Input Using std::cin

To read (or scan), we use the std::cin "function" and the operator >>.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>   //for std::cin and std::cout

using namespace std; //for dropping the std::

int main(int argc, const char* argv[])
{
    int n;             //declare an integer n
    cin >> n;          //read an integer from the console, putting it in n
    cout << n << endl; //spit n back out, then a newline and flush the stream.
    return 0;
}

Like with the pathological syntax of std::cout, the syntax of std::cin resembles that of a flow equation: something goes from std::cin to n.

Variables and Types

In C++, variables are declared by writing [type] [variable name]. You can initialize them at declaration with [type] [variable name] = [value].

Note

Many languages offer automatic null-initialization; for example, an int would automatically start as 0. C is a low-level language (it’s closer to the hardware), and providing automatic null-initialization would be wasteful, as a variable can be declared and then subsequently initialized. With null-initialization, there would be two writes: one with 0 and the next with the desired value; without, just one.

Since C++ doesn’t provide null initialization, you must remember to initialize variables you intend to use. Variable declaration only allocates memory; initially, its values would simply be whatever was there before. For your purposes, it’s nothing but garbage.

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

int main(int argc, const char* argv[])
{
    int n;                  //declare an int called n
    char c = 'A';           //declare a character called c and assign it the value of 'A'
    double d = 0.0, e;      //declare a double called d and assign it 0.0; then declare a double called e

    //spit out the values of the declared variables
    std::cout << n << std::endl << c << std::endl << d << std::endl << e << std::endl;
    return 0;
}

If you run this code, you should see an output like the following:

<span id="L1" class="line"><span class="mi">50786312</span></span>
<span id="L2" class="line"><span class="n">A</span></span>
<span id="L3" class="line"><span class="mf">0.00000</span></span>
<span id="L4" class="line"><span class="o">-</span><span class="mf">2.46962e-185</span></span>

You may notice that the output differs on lines 1 and 4; that’s because the program was outputting garbage that varies from machine to machine, from program to program, and from time to time.

You can declare two variables on the same statement like in line seven. Each variable is initialized separately.

In C++, there are four primitive types:

  • the int, an integral type which supports integers from -2,147,483,648 to 2,147,483,647,

  • the float, a floating-point type which supports six decimal digits of precision,

  • the char, a single-byte type which supports the default 128 ASCII characters, and

  • the bool, a boolean type which supports the boolean values true (1) and false(2).

Note

You may be more familiar with the double primitive type, which is the double-precision floating-point type; if you ever find yourself really in need of using floating points, the double is most likely a better choice than the float.

The double supports fifteen decimal digits of precision.

Note

In general, try to avoid using floating-point types, especially if your calculations include addition and subtraction. If you must use floating points, try to restrict them to multiplication and division.

You can see why this is the case if you try to test if 0.1 + 0.2 == 0.3, or if 1.0 / 0.0 == 1.0 / -0.0. In the former case, there’s not enough precision and the addition results in an extra little \(4\times10^{-17}\); in the latter, 1.0 / 0.0 should result #INF and 1.0 / -0.0 should result -#INF, even though 0.0 and -0.0 are both mathematically and computationally equivalent.

Operations on Primitives

There are six base operations, five compound operations, and four abbreviated operations that can be applied to primitive types:

  • assignment: a = b sets the value of a to the value of b (whether b is a constant or variable), and returns the new value of a. You can assign smaller types to larger types, like if a was an int and b was a char.

  • addition: a + b returns the sum of a and b; the return type is the largest type in the operation (in the order of double, int, char). ints and chars can overflow — if the result if over the capacity of the type, it will wrap around. Floating-point overflows result #INF.

  • subtraction: a - b returns the difference of b from a; the return type and overflow behaviour is the same as with addition.

  • multiplication: a * b retursn the product of a and b. The return type and overflow behaviour is the same as with addition.

  • division: a / b returns the quotient of a divided by b. If both a and b are integral types, the return type is also integral and the result is truncated (round towards zero). If either is a floating-point type, the result will be floating-point. Integer division by zero will immediately crash a program. Floating-point division by zero results ±INF depending on the signs of the divisor and the zero and #IND if the numerator is also zero.

  • modulus: a % b returns the modulus, or remainder, of a divided by b. The modulus is only valid across integral types, and anything modulo zero will immediately crash the program. Typically, the result of a modulus has the same sign as the divisor.

The five compound operations do both an operation and an assignment:

    n += m; //equivalent to n = n + m;
    n -= m; //equivalent to n = n - m;
    n *= m; //equivalent to n = n * m;
    n /= m; //equivalent to n = n / m;
    n %= m; //equivalent to n = n % m;
    n ++;   //equivalent to n = n + 1;, but returns the original value of n.
    ++ n;   //equivalent to n = n + 1;
    n --;   //equivalent to n = n - 1;, but returns the original value of n.
    -- n;   //equivalent to n = n - 1;

Since operations can both assign and return a value, you can do things like this:

    std::cout << 3 + (n = m + (l *= 2));
    // assigns l to 2l, n to m + 2l, and prints 3 + m + 2l

Boolean Operations and Branching

Branching is an important part of any program. In C++, this is accomplished using the if and switch statements:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    if (n > 10) //for n is some int
    {
        std::cout << "n is greater than 10." << std::endl;
    }
    else if (n < 10)
    {
        std::cout << "n is less than 10." << std::endl;
    }
    else
    {
        std::cout << "n is 10." << std::endl;
    }

    switch (c) //for c is some char
    {
        case 'Z':
            std::cout << "Z, the last letter of the alphabet" << std::endl;
            break; //ends the branch
        case 'Y':
            std::cout << "Y, a consonant, but also ";
            //because there's no break statement, the 'Y' case leaks
            //into the next cases.
        case 'A':
        case 'E': //you can chain cases that should
        case 'I': //result in the same behavior.
        case 'O':
        case 'U':
            std::cout << c << ", a vowel." << std::endl;
            break;
        default:
            std::cout << "fatal error, terminating program" << std::endl;
            c = 1/0;
            break;
    }

While if statements take boolean values, switch statements take a variable and check cases against constant, compiler-time-deterministic values.

Note

Since if statements directly take boolean variables, if you have a boolean variable you wish to check, you can directly put it into the statement:

    bool b = true;
    if (b)
    {
        std::cout << "asdf";
    }

will always write asdf, barring extreme circumstances.

To use other primitive types with if statements (or more generally, conditional statements), you can use various boolean operators:

  • equivalence: a == b returns true if a and b have exactly the same value, bit-by-bit (with the exception that 0.0 and -0.0 are equivalent). Otherwise, it returns false.

  • not equal to: a != b returns true if a is not equivalent to b. Otherwise, it returns false. Commonly, this is called the "bang equals". This operator is also equivalent to an exclusive or on boolean values.

  • strict less than: a < b returns true if a is less than, and not equal to, b. Otherwise, it returns false.

  • strict greater than: a > b returns true if a is greater than, and not equal to, b. Otherwise, it returns false.

  • less than or equal to: a <= b returns true if a is less than or equal to b. Otherwise, it returns false.

  • greater than or equal to: a >= b returns true if a is greater than or equal to b. Otherwise, it returns false.

  • logical negation: !a returns true if a is false and vice-versa. This operator is commonly called the "bang," and is also known as the "logical NOT."

  • logical AND: a && b returns true if both a and b are true, and otherwise false. Evaluation is always left-to-right; if a is false, then b will not be evaluated.

  • logical OR: a || b returns true if either a or b are true, and otherwise false. Evaluation is always left-to-right; if a is true, then b will not be evaluated.

  • ternary operator: a ? b : c returns b if a is true, and c if a is false. This is a powerful operator that allows for interesting shortcuts in code; typically, they make code harder to read, but easier to write. Interestingly, despite being defined similarly, they don’t behave similarly to if statements; they are, on average, 60% faster.

Important

When checking for equivalence, ensure that you use the double equal operator, or you will be assigning values and using the assigned value! if (n = 0) will never fire, and if (n = 1) will always fire because the assigned values will always be used in the check!

Note

Because the logical AND and logical OR will ignore their second operand if the result can be determined directly from the first, compound expressions can be dangerous. true || (++n > 10) will never increment n.

However, they can also be useful when checking things that may explode in your face without extra checks: d != 0 && n / d > 3 will never explode your program due to a divide-by-zero, as if d were zero then n / d would never evaluate.

Arrays

In C++, an array is a modifier on a type. They’re declared by adding [] at the end of variable name, initialized with {}, and accessed with [].

1
2
3
    int n, arr[10];
    double darr[] = {3.0, 3.1, 3.14, 3.141, 3.1415, 3.14159};
    int arr_null_init[100] = {1};

Line 1 declares an int, n, and an array of 10 ints, arr. Both are filled with garbage.

Line 2 declares an array of six doubles, initialized to pi at various precisions. Line 3 declares an array of a hundred ints, all initizalized to zero except position 0, which is initialized to 1.

In C++, arrays are zero-indexed:

1
2
    int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    std::cout << arr[0] << arr[9] << arr[10];

This snippet of code will output 09 and then some garbage (there are no spaces as we didn’t specify any), since the 10th index is after the end of the array.

Variable-size Arrays: std::vector

If you need an array type but don’t know how many elements you need, you can use an std::vector from <vector>. std::vectors begin with zero size and zero capacity, but increase as you add more elements with std::vector<T>::push_back(T), create more elements with std::vector<T>::emplace_back(T()), or reserve space for elements with std::vector<T>::reserve(int). The increase in size and capacity is done automatically when needed.

Elements can also be removed off the back with std::vector<T>::pop_back().

When declaring vectors, a type must be specified in angle brackets. Since they are not primitive types, vectors are automatically initialized.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <vector>
#include <iostream>

int main(int argc, const char* argv[])
{
    std::vector<int> arr;   //no need to set initializations! In fact, they'll give you errors.
    int size, in = 0;

    std::cin >> size;

    arr.reserve(size)

    while(in >= 0)         //leave when you get a negative input
    {
	arr.push_back(in); //will automatically add directly to the end of the vector
        std::cin >> in;
    }

    //spit everything back out
    for (int i = 1; i < arr.size(); ++i)
    {
        std::cout << arr[i] << std::endl;
    }

    return 0;
}

Loops

When dealing with arrays or anything which requires immediate repetition, we use loops. (Of course, you know this already.)

In C++, there are three primitive loops: the for loop, the while loop, and the do .. while loop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int arr_size = 10;

    ...

    //there are three statements in the declaration of a for-loop:
    // - the initializer, which is run as soon as scope enters the loop,
    // - the condition, which is run before each iteration, and
    // - the increment, which is run after each iteration.
    for (int i = 0; i < arr_size; ++i)
    {
        std::cout << arr[i];
    }
    std::cout << std::endl;

    //the while loop is simpler; it's like an if-statement but every
    //time we're about to leave the scope of the loop it goes back to
    //the condition.
    int i = 0;
    while (i < arr_size)
    {
        std::cout << arr[i];
        ++i;
    }

    //the do..while loop is like the while loop, except the first
    //iteration will always run.
    i = 0;
    do
    {
        std::cout << arr[i];
        ++i;
    } while (i < arr_size);
Note

All three loops do not necessarily need to have statements.

The for loop can be written as for(;;), and it will run as an infinite loop. The while and do..while loops can be written as while(1) and they will also run as infinite loops.

Sometimes, you need to be able to control the actions of loops while inside it, usually when checking for edge cases. In these cases, you can use the break and continue keywords:

  • break immediately exits the innermost loop, and

  • continue immediately exits the current iteration of the innermost loop. In a for loop, this counts and the end of an iteration, and the increment and condition statements will be immediately evaluated.

Strings

C is a low-level language, and supports the char* or char[] type as a string. In C, string manipulation is usually done through the <string.h> library. C++ introduces the <string> library to make string manipulation a little less horrible.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <string>
#include <string.h>

int main(int argc, const char* argv[])
{
    char cstr[1024] = "This is a C-style string.";
    strcpy(cstr+8, "C-style string manipulation.");
    strcat(cstr, " This is the most accurate version of string manipulation, ");
    strcat(cstr, "but is a pain to do when you're new to the language.");
    std::cout << cstr << std::endl;

    std::string str("This is a C++ STL string.");
    str = str.substr(0, 8) + "manipulation of" + str.substr(7, std::string::npos);
    str += " It's a lot easier to grasp, but it's certainly not as fast";
    str += " computationally, if you know how to do C-string manipulation.";
    std::cout << str << std::endl;

    return 0;
}  

The reference is very useful when dealing with strings.

Note

C-strings are pointer-based and manipulation is heavy on the use of pointers. If you aren’t comfortable with pointers, you should probably stick to STL strings.

Modularity

Functions

In C/C++, there isn’t really a big difference between methods/procedures and functions: they’re all declared the same way and behave pretty much the same way. Methods/procedures just have return type void; that is to say, it doesn’t return anything and so they can’t be used in expressions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>

//return type, name, params
int add(int a, int b)
{
    //code goes here (in this case, it's unnecessary).
    //if the return type is not void, you must return a value
    //in every possible branch.
    return a + b;
}

//return type, name, params; in this case, this is a method
//so it returns void
void print(int n)
{
    std::cout << n << std::endl;

    //in methods, returning is optional, but you can choose to return
    //down certain branches to terminate early.
    return;
}

//main can also return void in certain environments, and typically the
//parameters are unnecessary. It's good form to include them, though.
int main(int argc, const char* argv[])
{
    print(add(2,3));

    //the main function actually doesn't need to return, but it's good form.
    return 0;
}

structs

In C++ there are classes you can use, but they’re designed specifically for OOP and are sometimes rather finicky to work with. structs were made for C, and so they’re somewhat easier to work with, since they’re just wrappers for groups of data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>

struct whatever
{
    int a, b;     //member variables
    void print(); //member function prototype
}; //this semicolon is important!

// this is how you define a member function outside its parent's declaration:
// you need the scope resolution operator (::). You can also directly define
// it inside its parent, but it's better form to do it outside as there's
// less clutter when you're checking what you can do with a struct.
void whatever::print()
{
    std::cout << a + b << std::endl;
}

int main()
{
    whatever w;
    w.a = 5;
    w.b = 895623;
    w.print();

    return 0;
}

Prototype declarations

Sometimes you will need to reference two functions or types within each other. C++ compilers parse from the top of the document to the bottom, so if a symbol isn’t yet declared it will start screaming at you even if it’s declared later. What you can do is make a prototype declaration to tell the compiler, "Hey, here’s something I’m going to use before I define," and it won’t flip out.

1
2
3
4
5
6
7
8
9
int recursion_a();
int recursion_b()
{
    recursion_a();
}
int recursion_a()
{
    recursion_b();
}