Namespaces, Includes, and the Standard Library
A quick introduction to namespaces in C++, alongside the standard library and how we can access it
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:
- those who have completed our introductory course, but want a quick review
- those who are already familiar with programming in another language, but are new to C++
- those who have used C++ in the past, but would benefit from a refresher
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.
Intro to C++ Programming
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way
The #include
Directive
The 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"
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
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};
}
}
Scope Resolution Operator ::
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;
Shadowed Variables
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
}
The Standard Library
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"};
}
Streaming Output using 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
Reducing Syntax with using
Statements
When 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.
Literals in Namespaces
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
- or
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};
}
Summary
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:
- Namespaces help avoid naming conflicts and organize code logically.
- The
#include
directive allows reusing code from other files and libraries. - The scope resolution operator
::
is used to access elements in a namespace. - Shadowed variables occur when an identifier in an inner scope has the same name as an identifier in an outer scope.
- The standard library provides a rich set of pre-built functionality.
std::cout
andstd::endl
are used for console output.using
statements allow using names from a namespace without explicit qualification.- Literals for
std::string
are created using thes
suffix - for example:"Hello World"s
.
Conditionals and Loops
Learn the fundamentals of controlling program flow in C++ using if statements, for loops, while loops, continue, and break