Skip to content

Instantly share code, notes, and snippets.

@JKomskis
Last active August 28, 2024 16:28
Show Gist options
  • Save JKomskis/4a1451ef34392451fd4fc5d665be48fc to your computer and use it in GitHub Desktop.
Save JKomskis/4a1451ef34392451fd4fc5d665be48fc to your computer and use it in GitHub Desktop.

What to Install

C++ Compiler

Windows

MinGW - http://mingw.org/

or

WSL - https://docs.microsoft.com/en-us/windows/wsl/install-win10 (win 10 only)

MacOSX

Homebrew (g++) - https://brew.sh

Valgrind

WSL/Linux: sudo apt install valgrind

Brew: brew install valgrind

Editors

Notepad++ (Win) - https://notepad-plus-plus.org/

Visual Studio Code (Win/Mac/Linux) - https://code.visualstudio.com/

Stepik Walkthrough

https://stepik.org

C++ Refresher

Preprocessor Directives

include

#include <header>

#include "file"

Replaces the directive with the content of the specified header or file.

#include <iostream>

int main() {
  std::cout << "Hello World" << std::endl;
}

define

#define identifier replacement

This preprocessor direct replaces all instances of identifier with replacement.

#define NUM_ITEMS 500

int main() {
  int items[NUM_ITEMS];
}
#define get_max(a,b) a>b?a:b

int main() {
    std::cout << get_max(5,7) << std::endl; //outputs 7
}

ifdef/ifndef

#ifdef identifier

#ifndef identifier

Allows a section of the program to be compiled if identifier is/isn't defined.

Aside: Header Guards

When writing header files, it is good practice to use header guards. Since header files may be included multiple times by several files, header guards ensure the code in the header file will only be included once.

// node.h
#ifndef NODE_H
#define NODE_H

//node header code here

#endif

if/else/elif

#if condition

#else condition

#elif condition

#define NUM_ITEMS 100

#if NUM_ITEMS>99
std::cout << "NUM_ITEMS is greater than 99" << std::endl;
#elif NUM_ITEMS<50
std::cout << "NUM_ITEMS is less than 50" << std::endl;
#else
std::cout << "NUM_ITEMS is between 50 and 99" << std::endl;
#endif

Data Types

C++ supports all the data types you're used to in Java, and some you are probably not as familiar with:

  • char
  • int
  • unsigned int
  • long
  • unsigned long
  • double
  • bool
  • void
  • size_t //An alias of an unsigned integer type. Guarateed to be big enough for any array index.
  • std::string

auto and decltype

The auto and decltype keywords can be used to derive the type of a variable on the fly to improve readability.

int x = 5;
auto y = x; // y will be of type int
int x = 5;
decltype(x) y = x; //y will be of type int

strings

C++ has 2 ways of using strings, C-style strings and std::string.

A C-style string is nothing more than a char*. The pointer in this case points to an array of characters, ended with the null terminator (\0).

std::string is a class from the <string> header, which is the STL equivalent of C-style strings.

#include <string>

int main() {
  std::string str = "Hello World";
  std::cout << str << std::endl;
}

Control Flow

if/else

if and else work the same way in C++ as they do in Java.

int main() {
  int x = 0;
  if(x > 0) {
    std::cout << "x is positive." << std::endl;
  } else if(x < 0) {
    std::cout << "x is negative." << std::endl;
  } else {
    std::cout << "x is 0." << std::endl;
  }
}

while

while works the same way in C++ as it does in Java.

int main() {
  int n = 10;
  while(n >= 0) {
    std::cout << n << std::endl;
  }
}

Note: do-while loops also work the same as they do in Java.

for

for works the same way in C++ as it does in Java.

int main() {
  for(int i = 0; i < 100; ++i) {
    std::cout << i << std::endl;
  }
}

Note: for-each loops also work the same way as they do in Java.

switch-case

switch and case statements work the same way in C++ as they do in Java.

int main() {
  std::string str = "blue";
  switch(str) {
    case "red":
      //do if str is red
      break;
    case "blue":
      //do if str is blue
      break;
    default:
      //do if none of the previous cases were true
  }
}

Pointers

A Pointer is a variable whose value is the memory address of another variable.

Declaring Pointers

Normally, variables are declared with the format <datatype> <name>. A pointer of any type (including other pointers) can be declared with the format <datatype> * <name>. The whitespace does not matter, but usually I write it as <datatype> *<name>.

Address-of Operator (&)

In order to get the memory address of a variable, one can use the & operator in front of the variable.

  int x = 5;
  std::cout << &x << std:endl; //prints the address where x is stored

Dereference Operator (*)

In order to get the value to which a pointer points, the * operator can be used in front of the variable.

 int num = 5;
 int *x = &num;
 std::cout << x << std::endl; //prints the address of num
 std::cout << *x << std::endl; //prints the value to which x points (5)

Pointers and Arrays

Arrays and pointers are closely related. Arrays can always be converted to pointers of the same type.

int numbers[5];
int *num_ptr = numbers;

std::cout << numbers[2] << std::endl; //acesses the third element of the array;
std:cout << *(num_ptr + 2) << std::endl; //accesses the third element of the array;

Pointer Arithmetic

Pointers support addition and subtraction arithmetic. With fundamental datatypes, adding or subtracting a number to the datatype with increase or decrease it by that amount. With pointers, however, adding or subtracting a number increases or decreases the pointer by that number times the size of the type to which the pointer points. Suppose an int is 4 bytes. If we have an int pointer pointing to byte address 1000, then adding one to the pointer would result in it pointing to address 1004.

Two pointer of the same type can also be subtracted from each other to find the number of pointed to types that fit between the pointers.

We already saw pointer arithmetic in the array example above.

char cstr[5];
for(char *curr = &cstr; curr != '\0'; ++curr) {
  std::cout << *curr;
}

Arrays

Initialization

Arrays can be declared with <datatype> <name>[<size>].

To intialize an array with values with the syntax <datatype> <name>[<size>] = {<value1>, <value2>, ... }

Accessing

Array elements are accessed using [], just like in Java.

Multidimensional Arrays

Multidimensional Arrays are simply arrays of arrays. They are declared with additional sets of []

int grid[5][5]; //create an array of 5 elements, each of which has 5 elements

Arrays as Arguments

When passing arrays to functions, it would be inefficient to copy the entire array. Instead, we pass the address of the start of the array.

void print(int arr[], int len) {
  for(int i = 0; i < len; ++i) {
    std::cout << arr[i] << std::endl;
  }
}

int main() {
  int arr1[] = {1, 2, 3, 4, 5};
  print(arr1, 5);
}

This also works for multidimensional arrays, but the length of every dimension except the first must be specified.

void print(int arr[][3], int len) {
  for(int i = 0; i < len; ++i) {
    std::cout << arr[i] << std::endl;
  }
}

int main() {
  int arr1[][3] = {{1, 2, 3}, {4, 5, 6}};
  print(arr1, 5);
}

Dynamic Memory

So far, all the variables we've been using are allocated to the stack. This means the memory usage of our program is known at compile time. There are some cases though where we may not know our memory needs until runtime. In these scenarios, we need to allocate memory to the program from the heap using the new operator.

The new operator can be used with any datatype, and returns a pointer to that datatype.

int *int_ptr = new int(5);

The new[] operator is used to allocate memory on the heap for an array.

int *arr = new int[5]; //arr points to an array of 5 ints

By dynamically allocating memory, we can make the array any size we want during runtime.

delete and delete[]

Java uses a garbage collector to free up any unused memory. C++ has no such feature, which means if you remove all references the dynamically allocated memory, the data will stay in memory until the programs finishes execution and the OS cleans up after the program. This is called a memory leak. In long running applications, this can cause all of the free memory in a computer to be taken, potentially crashing the system.

Generally, whenever new or new[] is used, delete or delete[] must be used by the last reference to the memory goes out of scope to avoid leaking memory.

delete pointer

delete[] pointer

Note that in multidimensional arrays, delete[] must be called on each inner array before calling delete[] on the outer array, or all the data in the inner arrays will be leaked.

Functions

Functions in C++ work similarly to functions in Java. Unlike in Java where fundamental types are passed by value and everything else is passed by reference, in C++ everything is passed by value.

int increase(int a) {
  ++a;
}

int main() {
  int a = 4;
  increase(a);
  std::cout << a << std::endl; //outputs 4
}

In order to pass a parameter by reference, the & can be used between the datatype and name of the parameter.

int increase(int& a) {
  ++a;
}

int main() {
  int a = 4;
  increase(a);
  std::cout << a << std::endl; //outputs 5
}

For complex objects, passing by reference can be far more efficient than passing by value.

Templates

Functions can be templated to avoid having to rewrite functions for different types.

template <typename T>
T add(T a, T b) {
  return a+b;
}

int main() {
  int x = add<int>(10, 20); //30
  double y = add<double>(10.5, 19.5); //30.0
}

Templates can take things other than typenames. They can take fundamental types, custom objects, function pointers, etc.

template<typename T, int N>
int increase_by(T a){
  return a+N;
}

int main() {
  int x = increase_by<int, 5>(3); //8
  double y = increase_by<double, 2>(0); //2.0
}

Structs & Classes

structs and classes are both ways of creating data structures. The only difference is the members of a struct are public by default, and the members of a class are private by default. The members of a class/struct can be either variables or methods.

struct rectangle {
  int length;
  int width;
};

class animal {
  std::string name;
  std::string species;
  
  public:
    std::string get_name() { return name; }
};

The . (dot) operator is used to access a member variable or a member function.

r.length

dog.get_name()

Constructors and Deconstructors

Constructors are used to create objects.

class rectangle {
  int length;
  int width;
  
  public:
    rectangle(int length, int width) : length(length), width(width){}
    int get_length() { return length; }
    int get_width() {return width; }
};

In more complex objects where memory is dynamically allocated, a desconstructor should be used to ensure all the memory used by an object is freed when the object goes out of scope.

class animal {
  Person *owner;
  
  public:
    animal() {
      owner = new Person();
    }
    ~animal() {
      delete owner;
    }
};

An exercise for the motivated student: research copy constructors, copy assignment operators, move constructors, and move assignment operators.

Templates

Classes can also be templated, which is useful making objects that support different types.

template<typename T>
class MyList {
  T *arr;
  
  MyList(size_t size) {
    arr = new T[size];
  }
  ~MyList() {
    delete[] arr;
  }
};

MyList<double> double_list;

Inheritance

To make one class inherit fro another, the following syntax can be used:

class <child class name> : <access modifier> <parent class>

In order to call the parent class's constructor with certain parameters, you must use the child class's initializer list.

template<typename T>
class List {
  int max_size;
  public:
    List(int max_size) : max_size(max_size) {}
    virtual T get(size_t index) = 0;
};

template<typename T>
class LinkedList : public List {
  Node *head;
  
  public:
    public LinkedList(int max_size) : List(max_size) {
      //...
    }
    virtual T get(size_t index) {
      //...
    }
};

Note: C++ does not have interfaces, but we can make one by creating a class where all the member functions are abstract virtual functions. Another Note: C++ supports multiple inheritance, so any class can inherit from multiple classes. To use multiple inheritance, just replace comma separate the list of parent classes after the colon in the child class declaration.

Aside: The virtual keyword

In C++, by default, when the function of a parent class is overriden by a child class and a variable of the parent type calls the function, it will call the definition provided in the parent. If you want the definition provided in the child to be called when possible, you must use the keyword virtual at the beginning of the function.

Const

The const keyword can be used to indicate that a variable is not allowed to change.

const datatype name = value

or

datatype const name = value

When dealing with pointers, const gets a little trickier.

  • pointer to an int: int * x
  • const pointer to a mutable int: int * const x
  • pointer to a const int: const int * x or int const * x
  • const pointer to a const int: const int * const x

Const Objects and Functions

const can also be used at the end of a class's function declaration. This will cause the compiler to produce an error if the function tries to mutate the state of the object.

When declaring an object const, the object will only be able to call const functions.

class rectangle {
  unsigned int length;
  unsigned int width;
  
  public:
    rectangle(unsigned int l, unsigned int w) {
      length = l;
      width = w;
    }
    int get_length() const {
      return length;
    }
    void set_length(unsigned int new_length) {
      length = new_length;
    }
};

rectangle r1(1, 2);
const rectangle r2(2, 3);

r1.get_length(); //returns 1
r1.set_length(5); //sets the length to 5

r2.get_length(); //returns 2
//r2.set_length(5); //error

Const member variables

Members of a class can be declared const. When a class member is const, it must be initialized through an initializer list.

class const_rectangle {
  const unsigned int length;
  const unsigned int width;
  
  public:
    rectangle(unsigned int l, unsigned int w) : length(l), width(w){}
    int get_length() const {
      return length;
    }
    void set_length(unsigned int new_length) {
      length = new_length;
    }
};

Misc. Tips

Always initialize your variables when you declare them, just to be safe. It can be a dummy value or anything else, just specify something.

Whenever possible, pass variables by const reference rather than by value. This will make your program more efficient and prevent you from inadvertantly modifying variables.

print_list(Node head) -> print_list(const Node &head)

For this class, don't use an IDE! You don't need it. Debuggers and all that are great, but mentally walking yourself through your code is a valuable exercise. If you use C++ in the real world on a substantial project, then I would use an IDE so you can focus more on your product. Part of this class, however, is able learning to think like a programmer. Mentally execute your code and try to find as many errors as possible before compiling/executing your code. You'll be a better programmer in the long run because of it.

Additional resources

http://www.cplusplus.com/doc/tutorial/

https://www.tutorialspoint.com/cplusplus/index.htm

https://learnxinyminutes.com/docs/c++/

@kapooramanpreet
Copy link

Hi Joe,

Hope you are doing well. Can you please update the example under "Pointers and Arrays":

int numbers[5];
int num_ptr = numbers; 

I think int num_ptr statement should be a pointer declaration, int*

@JKomskis
Copy link
Author

Hey Aman! Sure thing, thanks for the fix! I hope you're doing well too.

@kapooramanpreet
Copy link

kapooramanpreet commented Aug 29, 2021 via email

@JKomskis
Copy link
Author

Things are good here! One more year left of grad school, and then it's off to working full time.

@kapooramanpreet
Copy link

Awesome, glad it's going well for you. One more quick update to this,

Under the Templates section and the second example, the function states

int main() {
  int x - increase_by<int, 5>(3); //8
  double y = increase_by<double, 2>(0); //2.0
}

but it should be equals instead of a dash after the int x.

Thanks for your help, Joe! :)

@JKomskis
Copy link
Author

Thanks for finding the typo Aman! I've updated the gist.

@MaksimArtemev
Copy link

Just found a small typo under "Misc. Tips". Second to last sentence says "comiling/executing your code" instead of "compiling".

@JKomskis
Copy link
Author

JKomskis commented Sep 3, 2023

Just found a small typo under "Misc. Tips". Second to last sentence says "comiling/executing your code" instead of "compiling".

Thanks for the catch, updated!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment