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?

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
  • 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
Made with Slides.com