This lesson is a quick introductory tour of functions within C++. It is not intended for those who are entirely new to programming. Rather, the people who may find it useful include:
It summarises several lessons from our introductory course. Those looking for more thorough explanations or additional context should consider completing Chapters 7 and 8 of that course.
#include
DirectiveThe most typical way we import code defined in another file is through an #include
directive:
// Same Directory
#include "math.cpp"
// Child Directory
#include "some-subfolder/math.cpp"
// Parent Directory
#include "../math.cpp"
A directive is something we include in our code that will be detected and acted upon by the C++ preprocessor.
The preprocessor is a tool that sits between our code and the compiler. Based on any directives we have, the preprocessor modifies our source code before it is received by the compiler. We cover the preprocessor in more detail later.
The include directive performs a crude copy-and-paste operation on our code before it is compiled. Because of this, we can just use included code as if it were in the same file we’re working on:
// math.cpp
int add(int x, int y) {
return x + y;
}
// main.cpp
#include "math.cpp"
int main() {
Add(x+y);
}
In a project with a more complicated web of include directives, this recursive copy-and-paste process is liable to attempt to include the same file twice:
// geometry.cpp
float Pi{3.14f};
// math.cpp
#include "geometry.cpp"
// main.cpp
#include "geometry.cpp"
#include "math.cpp"
After resolving all the #include
directives, main.cpp
is including geometry.cpp
and then indirectly including it again through math.cpp
This will result in a compilation error because we’re now defining a variable called Pi
twice. To solve this, for any file that is intended to be included in other files, we should add the #pragma once
directive to ensure it never gets included more than once:
// geometry.cpp
#pragma once
float Pi{3.14f};
// math.cpp
#pragma once
#include "geometry.cpp"
Namespaces are the primary way to organize code in large applications. We give our namespace
a meaningful name, and then open a set of braces, thereby creating a new scope.
namespace Math {
// new scope
}
Within these braces, we can create variables, functions, and other constructs in the usual way:
namespace Math {
int Add(int x, int y) {
return x + y;
}
// Namespaces can be nested
namespace Geometry {
float Pi{3.14f};
}
}
::
Access to namespaces, and different scopes in general, can be done using the scope resolution operator ::
For example, to access a variable or function within the scope we called Math
, we would prepend the identifier with Math::
Math::Add(1, 2);
Our Math
namespace has a nested Geometry
namespace, which we can access using multiple scope resolution operators:
Math::Geometry::Pi / 2;
Multiple variables with the same name can be accessible within the same scope. In the following example, within the Math
namespace, two variables identified by x
are available:
int x{1};
namespace Math{
int x{2};
}
We have the x
defined within the global namespace, which has a value of 1
, and the x
defined within the local namespace, which has a value of 2
This is sometimes referred to as shadowed variables or shadowed identifiers. It is something we should try to avoid, as it can be confusing and cause bugs.
When resolving an identifier, the compiler will use the one in the scope that is most local to the expression that is using the identifier. As such, in the following example, Math::Value
will be 2
:
int x{1};
namespace Math{
int x{2};
int Value{x}; // 2
}
Access to the global scope is available using the unary scope resolution operator. That is, ::
without a left operand. In the following example, Math::Value
will now be 1
:
int x{1};
namespace Math{
int x{2};
int Value{::x}; // 1
}
C++ comes with a large standard library, which contains a large number of generally useful code. Standard library functionality is typically included using <
and >
as part of an #include
directive.
One of the most common standard library features we will want is the ability to create strings:
// The standard library's string implementation
#include <string>
Strings, and most standard library types and functionality are available within the std
namespace:
#include <string>
int main() {
std::string Greeting{"Hello"};
}
std::cout
By including <iostream>
we get access to some useful input and output functionality.
// Ways to interact with input and output
#include <iostream>
The std::cout
object allows us to stream content to our terminal using the <<
operator, thereby letting us see program output:
#include <string>
#include <iostream>
int main() {
std::string Greeting{"Hello"};
std::cout << Greeting;
}
This program yields the following output:
Hello
The <<
operator returns the reference to std::cout
, so we can keep streaming content to it as a larger expression:
#include <string>
#include <iostream>
int main() {
std::string Greeting{"Hello"};
// The << operator can be chained
std::cout << Greeting << " World";
}
Hello World
We can insert line breaks into our output using the \n
sequence, or by streaming a std::endl
token:
#include <iostream>
int main() {
std::cout << "After this, there is a break\n";
std::cout << "after this too!" << std::endl;
std::cout << "This will\nspan two lines";
}
After this, there is a break
after this too!
This will
span two lines
using
StatementsWhen we’re repeatedly going to be using a variable or function from a namespace, it can be helpful to add a using
statement. This can be within the global scope, where it will apply to an entire file, or just within the scope of a specific block:
void MyFunction() {
using std::cout, std::endl;
// In the rest of this function, we can now
// use cout and endl without needing to
// specify the std:: prefix
cout << "This works!" << endl;
}
If we want to refer to a whole namespace without qualification, we can have a using namespace
statement:
void MyFunction() {
using namespace std;
// Now, anything in the std namespace can be
// used, without needing prefixed with std::
cout << "This works!" << endl;
}
The using namespace
statement should generally be restricted, as it bypasses the purpose of having a namespace in the first place.
When files get more complex, it can be difficult to determine where functions and variables are coming from if we omit namespace qualifiers.
If we do want to have using namespace
statements, we should prefer using them just within the specific function we need them, rather than across an entire file.
This is particularly true when we’re writing code that we are going to #include
in other files, because then our global using
statement will affect those files, too.
Another scenario where we may want to use a using namespace
statement is to gain access to more literals.
Under the hood, literals are simply functions, and are typically defined within namespaces. We’ll see examples of this later in the course, when we learn to create literals for our own custom types.
For now, we can see an example of this using a literal for creating std::string
, which is available within the std
namespace.
We gain access to it through an appropriate using namespace
statement, such as:
using namespace std
using namespace std::literals
using namespace std::string_literals
In any scope where such a statement is in effect, we can use double-quotes with an s
suffix, such as "Hello"s
to create a std::string
:
#include <string>
int main(){
using namespace std::string_literals;
// Creates a std::string
auto MyString{"Hello World"s};
}
''
, ""
, and ""s
When it comes to strings and characters, there are three main literals we’ll use in this course:
'H'
create a char
- a built-in type that represents a single character"Hello"
is an array of char
objects. This is a simple way of representing a string.s
, such as "Hello"s
create a std::string
. This is a modern string type with many more capabilities than a simple character array.The std::string
type can be created from character arrays, so the simple ""
literal is often used even when the result is a std::string
:
// Creating a std::string from a char array
std::string MyString{"Hello"};
We’ll explore characters and strings in much more detail throughout this course.
In this lesson, we explored the basics of organizing code using namespaces. We also discovered how to use #include
directives to use code from other files and libraries, and how to use the standard library to access powerful pre-built functionality. Key takeaways:
#include
directive allows reusing code from other files and libraries.::
is used to access elements in a namespace.std::cout
and std::endl
are used for console output.using
statements allow using names from a namespace without explicit qualification.std::string
are created using the s
suffix - for example: "Hello World"s
.A quick introduction to namespaces in C++, alongside the standard library and how we can access it
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games