All the projects for this class will primarily be in C++ (language version 11). While C++ is derived from C, which most of you have some experience with, it adds support for object oriented code like Java as well as many other features. These include thing you may be familiar with such as inheritance, child classes, and overriding parent functions. This guide will focus on the features most relevant to the class.
For questions not answered by this guide, you can ask a TA, post on Piazza, or visit this link containing a more detailed look at C++ basics.
Header Files
Though C and C++ may look similar, to use the full potential of C++ you may want to include C++ library headers. Though there isn't an exhaustive table of one-to-one mapping of a C header file to its C++ version, below are a few common examples:
#include <iostream> // C++ #include <stdio.h> // C
#include <string> // C++ #include <string.h> // C
Beware that though these names may look similar, they could differ by a lot – C++ strings offer a good number of utility methods, compared to the C string (char *).
Passing Arguments
Like C, we can pass function parameters by value or by pointer. However, C++ also allows pass by reference, letting you directly modify the original variable without dereferencing a pointer.
For example we could write a basic squaring function in two ways:
void square_pointer(int *a)
{
*a = (*a) * (*a);
}
void square_reference(int &a)
{
a = a * a;
}
int main()
{
int x = 2;
square_pointer(&x); // After this line, x = 4
square_reference(x); // After this line, x = 16
}
Namespaces
To avoid problems with repeat names, C++ includes a feature called namespaces. They allow us to provide additional scope for variables & functions.
namespace mynamespace
{
int x = 5; // variable inside namespace
}
int x = 34; // global variable
int main()
{
int x = 213; // local variable
std::cout << x << ‘\n’; // prints “213”
std::cout << mynamespace::x << ‘\n’; // prints “5”
}
Sometimes, you might see the usage of using namespace name;
in a file - this indicates that variables & functions defined in that namespace can be used without specifying the namespace. For example, if we preface the above code with using namespace std;
we’d no longer need to preface the cout‘s with std::,
which would look like this:
using namespace std;
int main()
{
int x = 213;
cout << x << ‘\n’; // No std:: needed anymore in front of the cout
cout << mySpace::x << ‘\n’;
}
However, be wary of the potential dangers of using
statements, as multiple “used” namespaces might share variable/function names, possibly leading to, for example, the unintended usage of the wrong function of the same name.
Classes
We declare C++ class with the following syntax.
class Vector3D
{
public: // Note the colon (:)
float x; // Semi-colon after the var name
float y;
float z;
float getMagnitudeSquared(); // Semi-colon after the method
}; // Semi-colon after the braces (})
There are three visibility levels in C++: public, protected (visible to inherited class) and private (visible to class itself). The class members are declared private by default. The "public:" syntax tells that the following entries will be declared public, without having to put "public" in front of every entry like public float x
.
Similar to how we have header and implementation files for C programs, often times we declare the classes & member functions (aka. methods) in header files (often in ".h" extension) and define/implement the member functions in implementation files (often in ".cpp" extension). For the completeness of this topic, there are also cases where you may define the member functions inline.
You may ask: If I declare a function as int getSomething();
in a header file, I can define/implement it with int getSomething() { … }
in an implementation file.
What's the equivalent for C++ class member functions?
With the example of float getMagnitudeSquared();
above for the class Vector3D
, we can implement it with the following:
Vector3D::getMagnitudeSquared() // Note the double-colon (::)
{
return x * x + y * y + z * z; // May use "this->x"
}
Structs
C++ structs (different from C) are essentially classes yet with all visibilities public by default.
Memory Allocation
Dynamic Allocation
In C, malloc and free are commonly used to allocate persistent memory for various data structures. While both of these functions retain their previous functionality, C++ also the keywords new and delete which also allow you to allocate objects on the heap. These functions allow for dynamic allocation, without explicitly specifying the number of bytes to be allocated, and also call the constructor and destructor respectively.
Here's an example for allocating some memory for the Vector3D
(declared above):
Vector3D *vec = new Vector3D; // To allocate memory for a vector &
// init with implicitly-defined
// default constructor
std::cout << vec->x << std::endl; // Use the arrow (->) operator
delete vec; // Deallocate the memory
Stack Allocation
A great feature (similar to what we have in C) is that we can allocate the objects on stack. So rather than allocating memory for new objects every time and having to count on yourself to remember to deallocate the space taken by that object, we may do the following:
Vector3D vec = Vector3D(); // We construct Vector3D
// directly into vec
std::cout << vec.x << std::endl;
// No need to delete!
Precaution: You wouldn't want to return the reference to a stack-allocated object in a function; by the time the function caller gets the pointer value, the referenced object may have already been deallocated – more potential seg faults.
For those who are curious…
In C++ 11, smart pointers are introduced into the language standard so there's less chance of you stepping on zombie/bad objects. This could be something you may be interested in knowing, but we would only be dealing with plain pointers in this course.
I/O
In C, often we use stdin and stdout:
#include <stdio.h>
int main()
{
char c = fgetc(stdin); // Get some input
fprintf(stdout, "Input: %c\n", c); // And print it out
// Equivalent: printf("Input: %c\n", c);
}
In C++, equivalent to stdin and stdout we have std::cin and std::cout:
#include <iostream>
int main()
{
char c;
std::cin >> c;
std::cout << "Input: " << c << std::endl;
}
It's probably good to mention that you can use std::endl
for \n
.
To input/output to the standard input/output we can use the extraction (>>) and insertion (<<) operators. Think of them as taking something out from the input stream and inserting something into the output stream respectively.
File I/O
Free free to look up how file I/O are improved in C++! You may find them in some starter code but it won't be a major focus in the assignments.
Standard Template Library
For the completeness of this topic area, C++ comes with STL that can be broken down into the following 3 general components:
- Containers
- Algorithms
- Iterators
Vectors
As a container type, std::vector
can be found throughout the codebase. Think of it as an off-the-shelf resizable array, similar to an ArrayList
you probably implemented in Java.
Below is an example of basic interaction with a vector object:
#include <iostream>
#include <vector>
int main()
{
// We initialize a vector with an initializer list
std::vector<int> nums = {2, 4, 6, 0, 1};
// Access by index
std::cout << "nums[2] is " << nums[2] << std::endl;
// Index-based iteration
for (int i = 0; i < nums.size(); i++)
{
std::cout << nums[i] << " ";
}
std::cout << std::endl;
// Iteration-based iteration
// std::vector<int>::begin() returns an iterator type we can use to
// access the items and std::vector<int>::end() tells the end
// Since std::vector<int>::iterator is a wrapped "pointer", we'll
// need to dereference it to get the item
for (std::vector<int>::iterator it = nums.begin();
it != nums.end(); it++)
{
std::cout << *it << " ";
}
std::cout << std::endl;
// You may also use "auto" for the compiler to infer the type :)
for (auto it = nums.begin(); it != nums.end(); it++)
{
std::cout << *it << " ";
}
std::cout << std::endl;
// Range for loop
for (int num : nums)
{
std::cout << num << " ";
}
std::cout << std::endl;
}
Recall object stack allocation, when we define the variable nums
, it's essentially living for the duration on the call frame – you wouldn't want to return a pointer to a stack-allocated vector from a function, because by the time you receive the pointer in the caller function, the vector could already be released.
For the range based for-loops example above, please note that each vector item nums[i]
(suppose i
is the index) is copied to num
. This may look fine with primitive variables but C++ objects undermine issues specific to C++. Consider the following example:
#include <iostream>
#include <string>
#include <vector>
class Image
{
public:
Image()
{
std::cout << "Image constructed" << std::endl;
}
Image(Image const &other)
{
std::cout << "Image constructed (from existing image)"
<< std::endl;
}
};
int main()
{
std::vector<Image> images = std::vector<Image>(5);
// Should see 5 images constructed
for (Image image : images)
{
// Noop
}
// Ouch. And we see 5 images constructed from existing ones
}
You probably have guessed it. What's bad about the range for loop above is that each image is copied per loop, and we do not want this if perhaps each image takes 10MB in memory (and we're making room for each copy of the image and mem-copying the image data).
This leads to a solution using references. A not-so-beautiful way to get around image copying can be to dynamically allocate these images on heap and put these memory addresses into the vector by ourselves. Also we'll need to go into the vector and manually free the memory taken by each image.
A "good" solution is that we use a reference to each image while looping over the vector. With the same "Image" class as defined above:
int main()
{
std::vector<Image> images = std::vector<Image>(5);
// Should see 5 images constructed
for (Image &image : images)
{
// Noop
}
// Nice! No image is copied :D
}
The ampersand (&) makes all the difference. To put this even beyond using references, we can use const references if the image won't be modified during the for loop:
int main()
{
std::vector<Image> images = std::vector<Image>(5);
// Should see 5 images constructed
for (Image const &image : images)
{
// Noop
}
// Nice! No image is copied :D
}
For a more conceptual view on how we will use vectors in this class to represent images, please see the “Images as Data” guide.