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 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:
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
|
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.
|
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 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 valuestrue
(1) andfalse
(2).
Note
|
You may be more familiar with the The |
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 |
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 ofa
to the value ofb
(whetherb
is a constant or variable), and returns the new value ofa
. You can assign smaller types to larger types, like ifa
was anint
andb
was achar
. -
addition:
a + b
returns the sum ofa
andb
; the return type is the largest type in the operation (in the order ofdouble
,int
,char
).int
s andchar
s 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 ofb
froma
; the return type and overflow behaviour is the same as with addition. -
multiplication:
a * b
retursn the product ofa
andb
. The return type and overflow behaviour is the same as with addition. -
division:
a / b
returns the quotient ofa
divided byb
. If botha
andb
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, ofa
divided byb
. 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:
Since operations can both assign and return a value, you can do things like this:
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 will always write |
To use other primitive types with if
statements (or more generally, conditional statements), you can use various boolean operators:
-
equivalence:
a == b
returnstrue
ifa
andb
have exactly the same value, bit-by-bit (with the exception that0.0
and-0.0
are equivalent). Otherwise, it returnsfalse
. -
not equal to:
a != b
returnstrue
ifa
is not equivalent tob
. Otherwise, it returnsfalse
. Commonly, this is called the "bang equals". This operator is also equivalent to an exclusive or on boolean values. -
strict less than:
a < b
returnstrue
ifa
is less than, and not equal to,b
. Otherwise, it returnsfalse
. -
strict greater than:
a > b
returnstrue
ifa
is greater than, and not equal to,b
. Otherwise, it returnsfalse
. -
less than or equal to:
a <= b
returnstrue
ifa
is less than or equal tob
. Otherwise, it returnsfalse
. -
greater than or equal to:
a >= b
returnstrue
ifa
is greater than or equal tob
. Otherwise, it returnsfalse
. -
logical negation:
!a
returnstrue
ifa
isfalse
and vice-versa. This operator is commonly called the "bang," and is also known as the "logical NOT." -
logical AND:
a && b
returnstrue
if botha
andb
are true, and otherwisefalse
. Evaluation is always left-to-right; ifa
isfalse
, thenb
will not be evaluated. -
logical OR:
a || b
returnstrue
if eithera
orb
are true, and otherwisefalse
. Evaluation is always left-to-right; ifa
istrue
, thenb
will not be evaluated. -
ternary operator:
a ? b : c
returnsb
ifa
istrue
, andc
ifa
isfalse
. 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 toif
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!
|
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. However, they can also be useful when checking things that may explode in your face without extra checks:
|
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 int
s, arr
.
Both are filled with garbage.
Line 2 declares an array of six double
s, initialized to pi at various precisions.
Line 3 declares an array of a hundred int
s, 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::vector
s 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 |
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 afor
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;
}
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;
}
struct
s
In C++ there are class
es you can use, but they’re designed specifically for OOP and are sometimes rather finicky to work with.
struct
s 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();
}