This module covers some miscellaneous things that I couldn't fit nicely into other modules (smile). That being said, they are absolutely important and worthy of review - your C++ programming won't be complete without them! I won't include any formal examples, as they're each fairly small, but feel free to come up with your own, compiling them as we've done in the previous modules.

Dynamic Allocation

In C, we used malloc to allocate space on the heap, and free to deallocate it. These weren't part of the language itself, but had to instead be included from a library. In C++, while these still work, we can get much cleaner syntax with the new and delete operators. new is used to dynamically allocate memory, and delete is used to deallocate it. These operators don't require a function call or any file inclusion, as they are part of the language. Moreover, they only take in the desired type (and return a pointer to that type), removing any need for sizeof and implicit pointer type casting:

int* ptr1 = new int; // Allocate space for an int on the heap

// Do something with our memory

delete ptr1; // Deallocate the space

Since arrays are similar, they use similar syntax, but we also need to tell new how large the array is, as well as tell delete that it needs to deallocate an array, not just one instance of the type.

int* arr1 = new int[4]; // Allocate space for an array of 4 ints on the heap

// Do something with our memory

delete[] arr1; // Deallocate the space

nullptr

In C, to indicate a null pointer (one without a defined value), we used NULL, defined in the C Standard Library. However, in C++, we instead use the nullptr literal, which is part of the language (no need for external file inclusions)

Struct and Enum Declarations

In C, we had to use typedef to avoid having to specify struct and enum in the types of the relevant variables every time we wanted to use them. However, in C++, these keywords (struct and enum) are used when declaring the type, but not when using it. Therefore, after declaration, we don't need to specify them, leading to cleaner usage:

struct Vector
{
	int x;
	int y;
};

// We can now use the type without needing the struct keyword
Vector vector1;
vector1.x = 2;
vector1.y = 3;

Type Inference on Declaration

In C, we always had to declare all of the types of our variables. In C++, we take our type inference one step further by being able to infer the types of variables! By specifying the type as auto, at compile-time, our compiler can attempt to figure out the type of the given variable from what it's initialized to (it can't figure it out if it's declared separate from definition):

int test1 = 4;
int test2 = 3;

auto test3 = test1 + test2; // Compiler infers that test3 is of type int

It is very easy to fall into the trap of over-using auto and forgetting to specify types. However, this can lead to many subtle bugs. auto should only ever be used for complicated types. In the training, however, I will never use it - one can live a long, happy, and successful life without ever using auto. It is simply included for completeness (smile)

void* casting

When using malloc in C, our returned void* were implicitly cast to the needed type. This is no longer true in C++; we must explicitly cast them to the appropriate type when used (However, we no longer need malloc due to the dynamic allocation points from above (wink))

Booleans

In C, we had no natural support for Boolean values (aside from that included in library files). However, in C++, we get support for a new Boolean type, bool, along with two new literals for the values it can take, true and false:

// src1 and src2 are ints, already defined

bool test4 = false;
bool test5 = ( src1 == src2 );

Whenever a bool is expected, an int can also be taken (0 is false, nonzero is true). The converse is also true - true is evaluated as 1, false is 0 (this occurs naturally when printing, as bools have no format specifier)

Looping over Arrays

Often times, we wish to loop over all values in an array, as shown below, where we sum all values in an array:

int arr2[] = { 10, 11, 12, 13 };
int sum = 0;

for( int i = 0; i < 4; i++ )
{
	sum += arr2[i];
}

This leaves room for error in making sure that we index correctly in the array, pass in the size correctly, and whatnot. Instead, C++ introduces cleaner syntax - the below is functionally the same as the above:

int arr2[] = { 10, 11, 12, 13 };
int sum = 0;

for( int v : arr2 )
{
	sum += v;
}

This is known as a range-based for loop. For each iteration of the loop, v takes on the next value in the array arr2. However, the machine code still needs to know how many times to iterate. Therefore, this only works if the compiler can figure out how large the array will be (i.e. this will not work if the desired array is passed in via a function call).

  • No labels