COMP6771 Week 1
Intro & types
A simple look at C++
Let's break it down:
- Comment
- #include directive
- Standard library elements
- The output stream, cout
- stream insertion operator
- std namespace
- stream manipulator endl
- Return statements
- Semicolons, braces, string literals
// helloworld.cpp
#include <iostream>
int main() {
std::cout << "Hello, world!" << std::endl;
return 0;
}
Basic compilation of C++
- g++ -std=c++17 -o helloworld helloworld.cpp
- clang++ -std=c++17 -o helloworld helloworld.cpp
// helloworld.cpp
#include <iostream>
int main() {
std::cout << "Hello, world!" << std::endl;
return 0;
}
Basic Types
There are other basic types, but you will not need them for this course, and will rarely need them in industry.
Type | What it stores |
---|---|
bool | True or false |
int | Whole numbers |
double | Real numbers |
char | A single character |
string | Text |
enum | A single option from a finite, constant set |
T* | Raw pointers. DO NOT USE until we explain when, and when not, to use them |
Basic Types
Warning: most of these types do not have an exact size (eg. int may be 16 bits, 32 bits, or another size)
Use the limits library to determine the actual size
If int may not work for you, use fixed width types
This reduction in portability is to improve performance
#include <cstdint>
#include <limits>
std::cout << std::numeric_limits<int>::max() << '\n';
std::cout << std::numeric_limits<int>::min() << '\n';
std::cout << std::numeric_limits<double>::max() << '\n';
std::cout << std::numeric_limits<double>::min() << '\n';
//
int64_t i;
Integer types
Integer types also have your various type specifiers made up of:
- short
- long
- long long
- signed
- unsigned
https://en.cppreference.com/w/cpp/language/types
Type conversion
We will cover this much later in the course.
In the meantime just know that implicit type conversion may happen and not cause any runtime errors
bool b1 = 10; // b1 becomes true
bool b2 = 0.0; // b2 becomes false
int i1 = true; // i1 becomes 1
int i2 = false; // i2 becomes 0
C++ Operators
All basic operators on these types are similar to your knowledge of C
E.G.
- x.y, x->y, x[y], x++
- *++x, x++
- x && y, x || y
- And many, many more
Program errors
During the course we will talk about errors in terms of different times:
- Compile time
- Runtime
- Logic
int main(void) {
a = 5;
}
int main(void) {
int a = 0;
int b = 5 / a;
}
int main(void) {
int a = 3;
int b = 4;
int c = 5;
int average = a + b + c / 3;
}
Syntax
Runtime
Logic
Literals
- Some literals are embedded directly into machine code instructions
- Others are stored in read-only data as part of the compiled code
Type of literals | Examples |
---|---|
Boolean | { true, false } |
Character | { 'a', '\n' } |
Integer | { 20, 0x14, 20UL } |
Floating-point | 12.3, 1.23e4L, |
String | { "Healthy Harold", "a" } |
Declarations vs Definitions
- A declaration makes known the type and the name of a variable
- A definition is a declaration, but also does extra things
- A variable definition allocates storage for, and constructs a variable
- A class definition allows you to create variables of the class' type
- You can call functions with only a declaration, but must provide a definition later
- Everything must have precisely one definition
void declaredFn(int arg);
class DeclaredClass;
// This class is defined, but not all the methods are.
class A {
int declaredMethod(double);
int definedMethod(int arg) { return arg; }
}
// These are all defined.
int definedFn() { return 1; }
int i;
int j = 1;
std::vector<double> vd;
Const
- The value cannot be modified
- Make everything const unless you know it will be modified
#include <iostream>
#include <vector>
int main() {
const auto i = 0; // i is an int
i++; // not allowed
std::cout << i << '\n'; // allowed
const std::vector<int> vec;
vec[0]; // allowed
vec[0]++; // not allowed
vec.push_back(0); // not allowed
}
References
- We can use pointers in C++ just like C, but generally we don't want to
- A reference is an alias for another object
- You can use it as you would the original object
- Similar to a pointer, but:
- Don't need to use -> to access elements
- Can't be null
- You can't change what they point to once set
auto i = 1;
auto j = 2;
auto& k = i;
k = j; // This does not make k reference j instead of i. It just changes the value.
std::cout << "i = " << i << ", j = " << j << ", k = " << k << '\n';
Const references
- A const reference means you can't modify the object using the reference
- Object can still be modified through the original
int i = 1;
const int& ref = i;
std::cout << ref << '\n';
i++; // This is fine
std::cout << ref << '\n';
ref++; // This is not
const int j = 1;
const int& jref = j; // this is allowed
int& ref = j; // not allowed
Type templates
- Later on, we will introduce a few other types
- There are other types you could use instead of std::vector and std::unordered_map, but these are good defaults
- There are container types for linked lists, for example (but linked lists are terrible and should rarely be used)
Type | What it stores | Common usages |
---|---|---|
std::optional<T> | 0 or 1 T's | A function that may fail |
std::vector<T> | Any number of T's | Standard "list" type |
std::unordered_map<KeyT, ValueT> | Many Key / Value pairs | Standard "hash table" / "map" / "dictionary" type |
How to use type templates
- These are NOT the same as Java's generics, even though they are similar syntax to use
- std::vector<int> and std::vector<string> are 2 different types (unlike Java, if you're familiar with it)
- We will discuss how this works when we discuss templates in later weeks
#include <unordered_map>
#include <vector>
// Not allowed - type templates are not types
std::vector getVector();
// std::vector<int> and std::vector<double> are valid types.
std::vector<int> getIntVector();
std::vector<double> getDoubleVector();
// So is combining types
std::vector<std::unordered_map<int, std::string>> getVectorOfMaps();
Auto
- Let the compiler determine the type for you
- Auto deduction: Take exactly the type on the right-hand side but strip off the top-level const and &.
auto i = 0; // i is an int
std::vector<int> f();
auto j = f(); // j is std::vector<int>
// Pointers
int i;
const int *const p = i;
auto q = p; // const int*
auto const q = p;
// References
const int &i = 1; // int
auto j = i; // int
const auto k = i; // const int
auto &r = i; // const int&
Constexpr
- Either:
- A variable that can be calculated at compile time
- A function that, if its inputs are known at compile time, can be run at compile time
// Beats a #define any day.
constexpr int max_n = 10;
// This can be called at compile time, or at runtime
constexpr int constexprfactorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr int tenfactorial = constexprfactorial(10);
// This may not be called at compile time
int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
// This will fail to compile
constexpr int ninefactorial = factorial(9);
Functions
- Breaking code into self-contained functions that perform a single logical operation is one of the backbones of good programming style
- We will cover:
- The anatomy of a function
- Default argument values
- Call by value
- Call by reference
Functions: Default Values
- Functions can use default arguments, which is used if an actual argument is not specified when a function is called
- Default values are used for the trailing parameters of a function call - this means that ordering is important
string rgb(short r = 0, short g = 0, short b = 0);
rgb();// rgb(0, 0, 0);
rgb(100);// rgb(100, 0, 0);
rgb(100, 200); // rgb(100, 200, 0)
rgb(100, , 200); // error
Functions
- Formal parameters: Those that appear in function definition
- Actual parameters (arguments): Those that appear when calling the function
- Function must specify a return type, which may be void
foo(int etc); // not ok
int foo(); // OK
void bar(void); // OK
Functions: Call by value
- The actual argument is copied into the memory being used to hold the formal parameters value during the function call/execution
#include <iostream>
void swap(int x, int y) {
int tmp;
tmp = x;
x = y;
y = tmp;
}
int main() {
int i = 1, j = 2;
std::cout << i << " " << j << std::endl;
swap(i, j);
std::cout << i << " " << j << std::endl;
}
#include <iostream>
void swap(int *x, int *y) {
int tmp;
tmp = x;
x = y;
y = tmp;
}
int main() {
int i = 1, j = 2;
std::cout << i << " " << j << std::endl;
swap(&i, &j);
std::cout << i << " " << j << std::endl;
}
Functions: Call by reference
- The formal parameter merely acts asn alias for the actual parameter.
- Anytime the method/function uses the formal parameter (for reading or writing), it is actually using the actual parameter
- Call by reference is useful when:
- The argument has no copy operation
- The argument is large
#include <iostream>
void swap(int &x, int &y) {
int tmp;
tmp = x;
x = y;
y = tmp;
}
int main() {
int i = 1, j = 2;
std::cout << i << " " << j << std::endl;
swap(i, j);
std::cout << i << " " << j << std::endl;
}
void swap(int i, int j); // 1st-year style
void swap(int *i, int *j); // C style
void swap(int &i, int &j); // C++ style
Lvalue and Rvalue
- Understanding whether an item is an lvalues or an rvalues can help you better understand why particular code expressions do or do not work.
- We will cover examples of what lvalues or rvalues are
Lvalue and Rvalue
- Add the ravlue 5 and 1 and store 6 into lvalue 0x200
- Simplified thinking:
- rvalues may only appear on the RHS of an assignment
- lvalues may appear on both the LHS and RHS
int i = 5;
i = i + 1;
5
i 0x200
Lvalue and Rvalue
- Call by value:
- The rvalue of an actual argument is passed
- Cannot access/modify the actual argument in the callee
- Call by reference:
- The lvalue of an actual argument is passed
- May access/modify directly the actual argument
- Eliminates the overhead of passing a large object
Function Overloading
- Function overloading refers to a family of functions in the same scope that have the same name but different formal parameters.
- This can make code easier to write and understand
void print(double d); // (1)
int print(string s); // (2)
void print(char c); // (3)
print(3.14); // call (1)
print("hello World!"); // call (2)
print('A'); // call (3)
Overload Resolution
- This is the process of "function matching"
- Step 1: Find candidate functions: Same name
- Step 2: Select viable ones: Same number arguments + each argument convertible
- Step 3: Find a best-match: Type much better in at least one argument
Errors in function matching are found during compile time
void g();
void f(int);
void f(int, int);
void f(double, double = 3.14);
f(5.6); // calls f(double, double)
Function overloading and const
When doing call by value, top-level const has no effect on the objects passed ot the function. A parameter that has a top-level const is indistinguishable from the one without
// Top-level const ignored
Record lookup(Phone p);
Record lookup(const Phone p); // redefinition
// Low-level const not ignored
Record lookup(Phone &p); (1)
Record lookup(const Phone &p); (2)
Phone p;
const Phone q;
lookup(p); // (1)
lookup(q); // (2)
Matt: Boring legal stuff
- I'm required to disclose that I work at Google
- Nothing I say during this course represents Google
- I am working as a lecturer independently of my work at Google
- We are using Google products throughout this course, but they are publicly available
Header files
- Place declarations in header files, and definitions in cpp files
- But we never mentioned hello_world.cpp. How does the compiler know that we meant that one?
// path/to/hello_world.h
#ifndef HELLO_WORLD_H
#define HELLO_WORLD_H
void helloWorld();
#endif // HELLO_WORLD_H
// path/to/hello_world.cpp
#include "path/to/hello_world.h"
#include <iostream>
void helloWorld() {
std::cout << "Hello world\n";
}
// main.cpp
#include "path/to/hello_world.h"
int main() {
helloWorld();
}
The linker
g++ -c printer.cpp -Wall -Werror -std=c++17 -<more args>
g++ -c hello_world.cpp -Wall -Werror -std=c++17 -<more args>
g++ main.cpp hello_world.o printer.o -Wall -Werror -std=c++17 -<more args>
// path/to/hello_world.h
#ifndef HELLO_WORLD_H
#define HELLO_WORLD_H
void helloWorld();
#endif // HELLO_WORLD_H
// path/to/hello_world.cpp
#include "path/to/hello_world.h"
#include "path/to/printer.h"
void helloWorld() {
print("Hello world");
}
// path/to/binary/main.cpp
#include "path/to/hello_world.h"
int main() {
helloWorld();
}
// path/to/printer.h
#ifndef PRINTER_H
#define PRINTER_H
#include <string>
void print(std::string);
#endif // PRINTER_H
// path/to/printer.cpp
#include "path/to/printer.h"
#include <iostream>
#include <string>
void print(std::string s) {
std::cout << s << '\n';
}
The problem
- Imagine having thousands of header and cpp files?
- You have a few options
- Manually create each library and make sure you link all the dependencies
- You would have to make sure you linked them all in the right order
- Create one massive binary and give it all the headers and cpp files
- Extremely slow
- Hard to build just parts of the code (eg. To run tests on one file)
- Makefiles
- Unwieldy at large scale (hard to read and hard to write)
- Any better options?
- Manually create each library and make sure you link all the dependencies
The solution - build systems
- We will be using bazel
- Works out what compilation commands to run for you
- Run using "bazel run //path/to/binary:main"
- Compile a single component using "bazel build //path/to:hello_world"
- We will show you how to do testing next week - it's really easy
// path/to/BUILD
cc_library(
name = "hello_world",
srcs = ["hello_world.cpp"],
hdrs = ["hello_world.h"],
deps = []
)
cc_library(
name = "printer",
srcs = ["printer.cpp"]
hdrs = ["printer.h"],
deps = [
# If it's declared within the same build
# file, we can skip the path
":hello_world"
]
)
// path/to/binary/BUILD
cc_binary(
name = "main"
srcs = ["main.cpp"],
deps = [
"//path/to:hello_world"
]
)
Setting up your computer
- The officially supported way will be to use the VM provided
- Install virtualbox and download the VM at http://tiny.cc/cs6771vm
- File > import appliance > 6771.ova (the downloaded file)
- Create a jetbrains account with your student email address (https://www.jetbrains.com/shop/eform/students)
- When you start up the VM, start up clion, and log in with that account
- The course repository is at https://github.com/matts1/comp6771
- Contains the lecture and tutorial code
- Will get updated as the course progresses
- Feel free to try and set it up on your own computer without the VM according to the README in the course repository, but we will not help you (I couldn't get it to work on windows, and don't have a mac to test on)
- If you get it to work, please send me any changes to the README
COMP6771 week 1 2019
By Matt Stark
COMP6771 week 1 2019
- 431