MinGW - http://mingw.org/
or
WSL - https://docs.microsoft.com/en-us/windows/wsl/install-win10 (win 10 only)
Homebrew (g++) - https://brew.sh
WSL/Linux: sudo apt install valgrind
Brew: brew install valgrind
Notepad++ (Win) - https://notepad-plus-plus.org/
Visual Studio Code (Win/Mac/Linux) - https://code.visualstudio.com/
#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 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 identifier
#ifndef identifier
Allows a section of the program to be compiled if identifier
is/isn't defined.
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 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
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
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
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;
}
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
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
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
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
}
}
A Pointer is a variable whose value is the memory address of another variable.
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>
.
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
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 = #
std::cout << x << std::endl; //prints the address of num
std::cout << *x << std::endl; //prints the value to which x points (5)
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;
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 can be declared with <datatype> <name>[<size>]
.
To intialize an array with values with the syntax <datatype> <name>[<size>] = {<value1>, <value2>, ... }
Array elements are accessed using []
, just like in Java.
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
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);
}
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.
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 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.
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 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 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.
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;
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.
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.
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
orint const * x
- const pointer to a const int:
const int * const x
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
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;
}
};
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.
http://www.cplusplus.com/doc/tutorial/
Hi Joe,
Hope you are doing well. Can you please update the example under "Pointers and Arrays":
I think int num_ptr statement should be a pointer declaration, int*