Differences Between C and C++¶
For most use cases, you can think of C++ as a superset of C. While this is not technically true, more often than not you are able to write standard C code for a C++ program without issues. However, doing so ignores a lot of the benefits and reasons to use C++.
Classes and Structs¶
In C structs can only contain member variables, but in C++ structs are basically classes but with a default member visibility of public instead of private.
Example
The following code blocks are equivalent.
Namespaces¶
One problem that is prevalent in C concerns the scoping of names. For example, let there be two files A.h
and B.h
and a program ighxy.c
, and let them both contain a float x
and int bar(void)
.
Our program cannot compile because the linker cannot distinguish which bar()
function we want to use! One way to fix
this in a C program would be to rename them a_bar()
and b_bar()
. Although this fix seems trivial for this example,
applying it to a file that has potentially 100 functions can be a lot more difficult, especially if two files just
happen to share the same prefix for their functions!
C++ introduces namespaces to tackle this problem. With namespaces, we can deal with naming conflicts much more easily. Though be aware that namespaces are not necessary everywhere. See the following code snippet to see how they work.
Example
Warning
You may come across something like:
using namespace std;
namespace io = std::filesystem;
int main(int argc, char* argv[]) {
bool isDirectory = io::is_directory(argv[1]); // Equivalent to std::filesystem::is_directory(argv[1])
cout << isDirectory << endl;
return 0;
}
There are two things going on here.
First, using namespace std
makes all functions and types defined within the standard namespace and included via
#include
directives visible to example.cpp
. If you are familiar with Python, the Python equivalent of this would
be import std as *
. However, it is considered bad practice to do this as it eliminates the point of using
namespaces.
Secondly, namespace io = std::filesystem
is basically an alias for the std::filesystem
namespace. This practice
is acceptable for long namespace identifiers, but be careful as it can still run into namespace conflicts if your
alias is the same as another namespace or alias.
Constant Expressions¶
In C, if we want to declare a constant or a function/expression that we want to be evaluated at compile time, we need
to use #define
statements. One of the problems with #define
statements is that they perform a simple copy paste
wherever they're used. For example:
Note
AREA_OF_CIRCLE
is a macro with arguments. If you are confused by it, this resource
has a detailed explanation on how they work.
Because of this copy-pasting, you need to be very careful with syntax, sometimes necessitating an ugly
do {} while(0)
wrapper.
Moreover, symbols declared with #define
are always globally visible, ignoring namespaces!
In C++, the use of constant expressions are preferred.
constexpr float pi = 3.14F;
constexpr float area_of_circle(float radius) {
return pi * radius * radius;
}
Constant expressions do not get copy pasted, and are instead placed in program memory just like a normal variable or function. They also respect namespaces and function scopes, meaning the following code compiles.
void foo(void) {
constexpr float rand = 123.456;
...
}
void bar (void) {
constexpr float rand = 789.123;
...
}
Lambdas¶
Lambdas are primarily useful when you need to register a callback function one time and don't feel it's necessary to write out a full function. They are in no way required though, so do not worry about learning them. However, it's necessary to know that they exist such that you don't get confused when reading code. For more information, go here for Microsoft's explanation.
Misc¶
Arrays¶
Using the C++ implementation of arrays is preferred over C arrays. It is simply easier and safer to work with than a standard C array without any performance costs.
Example
Passing an array to a function an iterating over it
#include "stdio.h"
void print_contents(int *arr, int size) {
for (int i = 0; i < size; i++) {
printf("%d\n", *arr);
}
}
int main(void) {
int arr[5] = {0, 1, 2, 3, 4};
foo(arr, 5);
return 0;
}
arr
is an array!
C++ 20 makes passing arrays around a lot simpler. Do not worry about understanding the code shown below. It uses some fairly advanced concepts and exists to illustrate how different such a simple operation can be.
#include <iostream>
#include <array>
#include <span>
void print_contents(std::span<int> container) {
for (const auto &e : container) {
std::cout << e << std::endl;
}
}
int main(void) {
std::array<int, 5> arr = {0, 1, 2, 3, 4};
foo(arr);
return 0;
}
The advantages of the C++ version are:
- Size is implicitly part of the object
- We guarantee that foo takes a container, but it does not care if it's an array or, say, a vector, which is preferable in this scenario where we simply iterate through the container's existing elements