C Basics Functions and Files
C Basics Functions and Files
Tags
Introduction to functions
Why function?
You already know that every executable program must have a function
named main (which is where the program starts execution when it is run).
However, as programs start to get longer and longer, putting all the code
inside the main function becomes increasingly hard to manage.
Functions provide a way for us to split our programs into small, modular
chunks that are easier to organize, test, and use. Most programs use many
functions. The C++ standard library comes with plenty of already-written
functions for you to use -- however, it’s just as common to write your own.
Functions that you write yourself are called user-defined functions.
Consider a case that might occur in real life: you’re reading a book, when
you remember you need to make a phone call. You put a bookmark in your
book, make the phone call, and when you are done with the phone call,
you return to the place you bookmarked and continue your book precisely
where you left off.
C++ programs can work the same way. A program will be executing
statements sequentially inside one function when it encounters a function
call. A function call is an expression that tells the CPU to interrupt the
current function and execute another function. The CPU “puts a
bookmark” at the current point of execution, and then calls (executes) the
function named in the function call. When the called function ends, the
CPU returns back to the point it bookmarked, and resumes execution.
The function initiating the function call is the caller, and the function being
called is the callee or called function.
The first line is informally called the function header, and it tells the
compiler about the existence of a function, the function’s name (identifier),
and some other information that we’ll cover in future lessons (like the
return type and parameter types).
The curly braces and statements in-between are called the function body.
This is where the statements that determine what your function does will
go.
Here is a sample program that shows how a new function is defined and
called:
#include <iostream>
// for std::cout
return 0;
}
Starting main()
In doPrint()
Ending main()
void doPrint()
{
std::cout << "In doPrint()\n";
}
return 0;
}
Since doPrint gets called twice by main, doPrint executes twice, and In
doPrint() gets printed twice (once for each call).
void doB()
{
std::cout << "In doB()\n";
}
void doA()
{
std::cout << "Starting doA()\n";
doB();
doA();
return 0;
}
Starting main()
Starting doA()
In doB()
Ending doA()
Ending main()
#include <iostream>
int main()
{
void foo() // Illegal: this function is nested insi
de function main()
{
std::cout << "foo!\n";
}
#include <iostream>
int main()
{
foo();
return 0;
}
Scenario
Consider the following program:
#include <iostream>
int main()
{
// get a value from the user
std::cout << "Enter an integer: ";
int num{};
std::cin >> num;
return 0;
}
void getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
}
int main()
{
getValueFromUser(); // Ask user for input
std::cout << num << " doubled is: " << num * 2 <<
'\n';
return 0;
}
Return values
First, your function has to indicate what type of value will be returned.
This is done by setting the function’s return type, which is the type
that is defined before the function’s name. In the example above,
function getValueFromUser has a return type of void (meaning no value
will be returned to the caller), and function main has a return type
of int (meaning a value of type int will be returned to the caller).
Note that this doesn’t determine what specific value is returned -- it
only determines what type of value will be returned.
Second, inside the function that will return a value, we use a return
statement to indicate the specific value being returned to the caller.
The specific value returned from a function is called the return value.
When the return statement is executed, the function exits immediately,
and the return value is copied from the function back to the caller. This
process is called return by value.
Let’s take a look at a simple function that returns an integer value, and a
sample program that calls it:
int main()
{
std::cout << returnFive() << '\n'; // prints 5
std::cout << returnFive() + 2 << '\n'; // prints 7
return 0;
}
5
7
Return values will not be printed unless the caller sends them to the
console via std::cout . In the last case above, the return value is not sent
to std::cout , so nothing is printed.
With this in mind, we can fix the program we presented at the top of the
lesson:
#include <iostream>
int main()
{
int num { getValueFromUser() }; // initialize num w
ith the return value of getValueFromUser()
std::cout << num << " doubled is: " << num * 2 <<
'\n';
return 0;
}
Revisiting main()
You may be wondering why we return 0 from main() , and when we might
return something else.
The return value from main() is sometimes called a status code (or less
commonly, an exit code, or rarely a return code). The status code is used
to signal whether your program was successful or not.
The C++ standard only defines the meaning of 3 status
codes: 0 , EXIT_SUCCESS , and EXIT_FAILURE . 0 and EXIT_SUCCESS both mean the
program executed successfully. EXIT_FAILURE means the program did not
execute successfully.
<cstdlib> header:
#include <iostream>
int main()
{
int num { getValueFromUserUB() }; // initialize num
with the return value of getValueFromUserUB()
std::cout << num << " doubled is: " << num * 2 <<
'\n';
return 0;
}
💡 Best practice
The function author can decide what the return value means
#include <iostream>
int main()
{
int x{};
std::cout << "Enter an integer: ";
std::cin >> x;
int y{};
std::cout << "Enter an integer: ";
std::cin >> y;
return 0;
}
While this program works, it’s a little redundant. In fact, this program
violates one of the central tenets of good programming: Don’t Repeat
Yourself (often abbreviated DRY).
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input;
}
int main()
{
int x{ getValueFromUser() }; // first call to getVa
lueFromUser
int y{ getValueFromUser() }; // second call to getV
alueFromUser
return 0;
}
Enter an integer: 5
Enter an integer: 7
5 + 7 = 12
💡 Best practice
Follow DRY: “don’t repeat yourself”. If you need to do something
more than once, consider how to modify your code to remove as
much redundancy as possible.
#include <iostream>
int main()
{
printHi(); // okay: function printHi() is called, n
o value is returned
return 0;
}
In the above example, the printHi function has a useful behavior (it prints
“Hi”) but it doesn’t need to return anything back to the caller.
Therefore, printHi is given a void return type.
When main calls printHi , the code in printHi executes, and “Hi” is printed.
At the end of printHi , control returns to main and the program proceeds.
A function that does not return a value is called a non-value returning
function (or a void function).
A void function will automatically return to the caller at the end of the
function. No return statement is required.
int main()
{
printHi();
return 0;
}
int main()
{
printHi(); // okay: function printHi() is called, n
o value is returned
The first call to printHi() is called in a context that does not require a
value. Since the function doesn’t return a value, this is fine.
Scenario
Create a modular getValueFromUser function in this program:
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input;
}
std::cout << num << " doubled is: " << num * 2 <<
'\n';
return 0;
}
However, what if we wanted to put the output line into its own function as
well? You might try something like this:
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input;
}
int main()
{
int num { getValueFromUser() };
return 0;
}
void printDouble()
{
int num{}; // we added this line
std::cout << num << " doubled is: " << num * 2 <<
'\n';
}
While this addresses the compiler error and makes the program compile-
able, the program still doesn’t work correctly (it always prints “0 doubled
is: 0”). The core of the problem here is that function printDouble doesn’t
have a way to access the value the user entered.
We need some way to pass the value of variable num to
function printDouble so that printDouble can use that value in the function
body.
An argument is a value that is passed from the caller to the function when
a function call is made:
#include <iostream>
int main()
{
printValues(6, 7); // This function call has two ar
guments, 6 and 7
return 0;
}
6
7
Note that the number of arguments must generally match the number of
function parameters, or the compiler will throw an error. The argument
passed to a function can be any valid expression (as the argument is
essentially just an initializer for the parameter, and initializers can be any
valid expression).
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input;
}
int main()
{
int num { getValueFromUser() };
printDouble(num);
return 0;
}
In this program, variable num is first initialized with the value entered by
the user. Then, function printDouble is called, and the value of
argument num is copied into the value parameter of function printDouble.
Function printDouble then uses the value of parameter value.
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
return input;
}
int main()
{
printDouble(getValueFromUser());
return 0;
}
#include <iostream>
In pictorial format:
More examples
Let’s take a look at some more function calls:
#include <iostream>
int main()
{
std::cout << add(4, 5) << '\n'; // within add() x=4, y
std::cout << add(1 + 2, 3 * 4) << '\n'; // within add(
return 0;
}
9
15
10
7
6
Unreferenced parameters
In certain cases, you will encounter functions that have parameters that
are not used in the body of the function. These are called unreferenced
parameters.
As a trivial example:
int main()
{
doSomething(4);
You’re probably wondering why we’d write a function that has a parameter
whose value isn’t used. This happens most often in cases similar to the
following:
Let’s say we have a function with a single parameter. Later, the function is
updated in some way, and the value of the parameter is no longer needed.
If the now-unused function parameter were simply removed, then every
existing call to the function would break (because the function call would
be supplying more arguments than the function could accept). This would
require us to find every call to the function and remove the unneeded
argument. This might be a lot of work (and require a lot of retesting). It also
might not even be possible (in cases where we did not control all of the
code calling the function). So instead, we might leave the parameter as it
is, and just have it do nothing.
Local variables
return z;
}
return z;
}
return z;
}
return z;
} // z, y, and x destroyed here
Note that variable creation and destruction happen when the program is
running (called runtime), not at compile time. Therefore, lifetime is a
runtime property.
The above rules around creation, initialization, and destruction are
guarantees. That is, objects must be created and initialized no later than
the point of definition, and destroyed no earlier than the end of the set of
the curly braces in which they are defined (or, for function parameters, at
the end of the function).
In actuality, the C++ specification gives compilers a lot of flexibility to
determine when local variables are created and destroyed. Objects may be
created earlier, or destroyed later for optimization purposes. Most often,
local variables are created when the function is entered, and destroyed in
#include <iostream>
void doSomething()
{
std::cout << "Hello!\n";
}
int main()
{
int x{ 0 }; // x's lifetime begins here
return 0;
} // x's lifetime ends here
In the above program, the lifetime of x runs from the point of definition to
the end of function main . This includes the time spent during the execution
of function doSomething .
When an identifier can not be seen, we can not use it, and we say it
is out of scope.
#include <iostream>
int main()
{
// x can not be used here because it's not in scope
yet
doSomething();
return 0;
} // x goes out of scope here and can no longer be used
Another example
Here’s a slightly more complex example. Remember, lifetime is a runtime
property, and scope is a compile-time property, so although we are talking
about both in the same program, they are enforced at different points.
#include <iostream>
int main()
{
int a{ 5 }; // a is created, initialized, and enters s
int b{ 6 }; // b is created, initialized, and enters s
Parameters x and y are created when the add function is called, can only
be seen/used within function add , and are destroyed at the end of add .
Variables a and b are created within function main , can only be
seen/used within function main , and are destroyed at the end of main .
To enhance your understanding of how all this fits together, let’s trace
through this program in a little more detail. The following happens, in order:
Functional separation
In the above example, it’s easy to see that variables a and b are different
variables from x and y .
#include <iostream>
int main()
{
int x{ 5 }; // main's x is created, initialized, an
d enters scope here
int y{ 6 }; // main's y is created, initialized, an
d enters scope here
return 0;
} // main's y and x go out of scope and are destroyed h
ere
#include <iostream>
int main()
{
std::cout << "Enter an integer: ";
int x{}; // x defined here
std::cin >> x; // and used here
return 0;
}
In the above example, each variable is defined just before it is first used.
There’s no need to be strict about this -- if you prefer to swap lines 5 and
6, that’s fine.
As an aside…
Due to the limitations of older, more primitive compilers, the C language
used to require all local variables be defined at the top of the function. The
equivalent C++ program using that style would look like this:
sum = x + y;
std::cout << "The sum is: " << sum << '\n';
return 0;
}
The intended initialization value may not be available at the top of the
function (e.g. we can’t initialize sum to its intended value because we
don’t know the value of x and y yet).
#include <iostream>
int main()
{
int x {};
int num { getValueFromUser(x) }; // main must pass
x as an argument
return 0;
}
#include <iostream>
int getValueFromUser()
{
int val {}; // val is a local variable
std::cout << "Enter a value: ";
std::cin >> val;
return val;
}
int main()
{
int num { getValueFromUser() }; // main does not ne
ed to pass anything
return 0;
}
In this example, val is now a local variable. main() is now simpler because
it does not need to define or pass a variable to call getValue() .
💡 Best practice
#include <iostream>
int getValueFromUser()
{
std::cout << "Enter an integer: ";
int input{};
std::cin >> input;
int main()
{
std::cout << getValueFromUser() << '\n'; // where d
oes the returned value get stored?
return 0;
}
'\n' executes.
In modern C++ (especially since C++17), the compiler has many tricks to
avoid generating temporaries where previously it would have needed to.
For example, when we use a return value to initialize a variable, this would
normally result in the creation of a temporary holding the return value, and
then using the temporary to initialize the variable. However, in modern
C++, the compiler will often skip creating the temporary and just initialize
the variable directly with the return value.
Similarly, in the above example, since the return value
of getValueFromUser() is immediately output, the compiler can skip creation
and destruction of the temporary in main() , and use the return value
of getValueFromUser() to directly initialize the parameter of operator<< .
Benefits
Functions provide a number of benefits that make them extremely useful in
programs of non-trivial length or complexity:
Typically, when learning C++, you will write a lot of programs that involve 3
subtasks:
For trivial programs (e.g. less than 20 lines of code), some or all of these
can be done in function main. However, for longer programs (or just for
practice) each of these is a good candidate for an individual function.
New programmers often combine calculating a value and printing the
calculated value into a single function. However, this violates the “one
task” rule of thumb for functions. A function that calculates a value should
return the value to the caller and let the caller decide what to do with the
calculated value (such as call another function to print the value).
#include <iostream>
int main()
💡 Best practice
When addressing compilation errors or warnings in your programs,
resolve the first issue listed and then compile again.
To fix this problem, we need to address the fact that the compiler doesn’t
know what add is. There are two common ways to address the issue.
int main()
{
std::cout << "The sum of 3 and 4 is: " << add(3, 4)
<< '\n';
return 0;
}
That way, by the time main calls add, the compiler will already know
what add is. Because this is such a simple program, this change is
relatively easy to do. However, in a larger program, it can be tedious trying
to figure out which functions call which other functions (and in what order)
so they can be declared sequentially.
Furthermore, this option is not always possible. Let’s say we’re writing a
program that has two functions A and B. If function A calls function B, and
function B calls function A, then there’s no way to order the functions in a
way that will make the compiler happy. If you define A first, the compiler
will complain it doesn’t know what B is. If you define B first, the compiler
will complain that it doesn’t know what A is.
Now, here’s our original program that didn’t compile, using a function
declaration as a forward declaration for function add:
#include <iostream>
int main()
{
std::cout << "The sum of 3 and 4 is: " << add(3, 4)
<< '\n'; // this works because we forward declared add
() above
return 0;
}
Now when the compiler reaches the call to add in main, it will know
what add looks like (a function that takes two integer parameters and
However, we prefer to name our parameters (using the same names as the
actual function). This allows you to understand what the function
parameters are just by looking at the declaration.
💡 Best practice
Keep the parameter names in your function declarations.
#include <iostream>
int main()
{
std::cout << "The sum of 3 and 4 is: " << add(3, 4)
<< '\n';
In this program, we forward declare add, and we call add, but we never
define add anywhere. When we try and compile this program, the program
compiled okay, but it failed at the link stage because int add(int, int) was
never defined.
return z;
}
int x; // variable
definition
void foo() { } //
function definition (has
Implements a function or
body)
Definition instantiates a variable.Definitions
are also declarations.
int x; // variable
definition
int main()
{
int x{};
int x{ 5 }; // violation of ODR, we've already defi
ned x
}
In this example, function add(int, int) is defined twice (in the global
scope), and local variable int x is defined twice (in the scope of main() ).
The compiler thus issues a compile errors:
However, it is not a violation of ODR part 1 for main() to have a local
variable defined as int x and add() to also have a function parameter
defined as int x . These definitions occur in different scopes (in the scope
of each respective function), so they are considered to be separate
definitions for two distinct objects, not a definition and redefinition of the
same object.
When you compile your program, you’ll need to include all of the relevant
code files on the compile line.
For example: g++ main.cpp add.cpp -o main , where main.cpp and add.cpp are
the names of your code files, and main is the name of the output file.
`"main.cpp",`
`"add.cpp",`
Or you can have VS Code automatically compile all .cpp files in the
directory by replacing "${file}", with "${fileDirname}/**.cpp" (works on
Unix).
#include <iostream>
int main()
{
std::cout << "The sum of 3 and 4 is: " << add(3, 4) <<
'\n';
return 0;
}
When the compiler reaches the function call to add on line 5 of main, it doesn’t
know what add is, because we haven’t defined add until line 9! Our solution to
this was to either reorder the functions (placing add first) or use a forward
declaration for add.
add.cpp :
main.cpp :
#include <iostream>
int main()
{
std::cout << "The sum of 3 and 4 is: " << add(3, 4) <<
'\n'; // compile error
return 0;
}
#include <iostream>
int main()
{
std::cout << "The sum of 3 and 4 is: " << add(3, 4) <<
'\n';
return 0;
}
method, we can give files access to functions that live in another file.
Naming collision
If the colliding identifiers are introduced into the same file, the result will be
a compiler error. If the colliding identifiers are introduced into separate
files belonging to the same program, the result will be a linker error.
a.cpp :
#include <iostream>
void myFcn(int x)
{
main.cpp :
#include <iostream>
void myFcn(int x)
{
std::cout << 2 * x;
}
int main()
{
return 0;
}
1. Two (or more) identically named functions (or global variables) are
introduced into separate files belonging to the same program. This will
result in a linker error, as shown above.
2. Two (or more) identically named functions (or global variables) are
introduced into the same file. This will result in a compiler error.
As programs get larger and use more identifiers, the odds of a naming
collision being introduced increases significantly. The good news is that
C++ provides plenty of mechanisms for avoiding naming collisions. Local
scope, which keeps local variables defined inside functions from
Scope regions
A scope region is an area of source code where all declared identifiers are
considered distinct from names declared in other scopes. Two identifiers
with the same name can be declared in separate scope regions without
causing a naming conflict. However, within a given scope region, all
identifiers must be unique, otherwise a naming collision will result.
Identifiers declared inside the global scope are in scope from the point
of declaration to the end of the file.
For example:
#include <iostream>
// imports the declaration of std::cout into the global
scope
// All of the following statements are part of the glob
al namespace
When C++ was originally designed, all of the identifiers in the C++
standard library (including std::cin and std::cout ) were available to be
used without the std:: prefix (they were part of the global namespace).
However, this meant that any identifier in the standard library could
potentially conflict with any name you picked for your own identifiers (also
defined in the global namespace). So C++ moved all of the functionality in
the standard library into a namespace named std (short for “standard”).
It turns out that std::cout ‘s name isn’t really std::cout . It’s actually
just cout , and std is the name of the namespace that identifier cout is part
of. Because cout is defined in the std namespace, the name cout won’t
conflict with any objects or functions named cout that we create outside of
the std namespace (such as in the global namespace).
When you use an identifier that is defined inside a namespace (such as the
std namespace), you have to tell the compiler that the identifier lives
#include <iostream>
int main()
{
std::cout << "Hello world!"; // when we say cou
t, we mean the cout defined in the std namespace
return 0;
}
This is the safest way to use cout , because there’s no ambiguity about
which cout we’re referencing (the one in the std namespace).
💡 Best practice
Use explicit namespace prefixes to access identifiers defined
in a namespace.
#include <iostream>
int main()
{
cout << "Hello world!";
return 0;
}
#include <iostream>
// imports the declaration of std::cout into the glo
bal scopeusing namespace std;
// makes std::cout accessible as "cout"
int main()
{
cout << "Hello, world!"; // Compile error! Whic
h cout do we want here? The one in the std namespac
e or the one we defined above?
return 0;
}
The above program doesn’t compile, because the compiler now can’t
tell whether we want the cout function that we defined, or std::cout
In C++, curly braces are often used to delineate a scope region that is
nested within another scope region (braces are also used for some non-
scope-related purposes, such as list initialization). For example, a function
defined inside the global scope region uses curly braces to separate the
scope region of the function from the global scope.
In certain cases, identifiers defined outside the curly braces may still be
part of the scope defined by the curly braces rather than the surrounding
scope -- function parameters are a good example of this.
#include <iostream>
int main()
{ // braces used to delineate nested scope region for func
foo(5);
return 0;
} // x goes out of scope here
// foo and main (and std::cout) go out of scope here (the
The #include and function definitions for foo() and main() exist in the
global scope region, so they are not indented. The statements inside each
function exist inside the nested scope region of the function, so they are
indented one level.
The preprocessor does not actually modify the original code files in
any way -- rather, all changes made by the preprocessor happen
either temporarily in-memory or using temporary files.
For example, it strips out comments, and ensures each code file
ends in a newline. However, the preprocessor does have one very
important role: it is what processes #include directives.
Preprocessor directives
When the preprocessor runs, it scans through the code file (from top to
bottom), looking for preprocessor directives.
Preprocessor directives (often just called directives) are instructions that
start with a # symbol and end with a newline (NOT a semicolon).
These directives tell the preprocessor to perform certain text manipulation
tasks. Note that the preprocessor does not understand C++ syntax --
instead, the directives have their own syntax (which in some cases
resembles C++ syntax, and in other cases, not so much).
#include
#include <iostream>
There are two basic types of macros: object-like macros, and function-like
macros.
The top definition has no substitution text, whereas the bottom one
does. Because these are preprocessor directives (not statements),
note that neither form ends with a semicolon.
#include <iostream>
#define MY_NAME "Alex"
int main()
{
std::cout << "My name is: " << MY_NAME <<
'\n';
return 0;
}
int main()
{
std::cout << "My name is: " << "Alex" <<
'\n';
return 0;
}
#define USE_YEN
Macros of this form work like you might expect: most further
occurrences of the identifier is removed and replaced by nothing!
Unlike object-like macros with substitution text, macros of this form
are generally considered acceptable to use.
Conditional compilation
#include <iostream>
#define PRINT_JOE
int main()
{
#ifdef PRINT_BOB
std::cout << "Bob\n"; // will be excluded since PRI
NT_BOB is not defined
#endif
return 0;
}
Because PRINT_JOE has been #defined, the line std::cout << "Joe\n" will be
compiled. Because PRINT_BOB has not been #defined, the line std::cout <<
#include <iostream>
int main()
{
#ifndef PRINT_BOB
std::cout << "Bob\n";
#endif
return 0;
}
#include <iostream>
int main()
{
std::cout << "Joe\n";
The above code only prints “Joe”, because “Bob” and “Steve” are excluded
from compilation by the #if 0 preprocessor directive.
This provides a convenient way to “comment out” code that contains
multi-line comments (which can’t be commented out using another multi-
line comment due to multi-line comments being non-nestable):
#include <iostream>
int main()
{
std::cout << "Joe\n";
To temporarily re-enable code that has been wrapped in an #if 0 , you can
change the #if 0 to #if 1 :
#include <iostream>
int main()
{
std::cout << "Joe\n";
#include <iostream>
void foo()
{
#define MY_NAME "Alex"
}
return 0;
}
For example, the following also behaves identically to the prior examples:
Alex.h :
main.cpp :
int main()
{
std::cout << "My name is: " << MY_NAME << '\n'; //
preprocessor replaces MY_NAME with "Alex"
During the translation process, once the preprocessor has finished, all
defined identifiers from that file are discarded. This means that directives
are only valid from the point of definition to the end of the file in which
they are defined. Directives defined in one file do not have any impact on
other files (unless they are #include d into another file). For example:
function.cpp :
#include <iostream>
void doSomething()
{
#ifdef PRINT
std::cout << "Printing!\n";
#endif
#ifndef PRINT
std::cout << "Not printing!\n";
#endif
}
main.cpp :
#define PRINT
int main()
{
doSomething();
return 0;
}
Not printing!
Even though PRINT was defined in main.cpp, that doesn’t have any impact
on any of the code in function.cpp (PRINT is only #defined from the point
of definition to the end of main.cpp).
Header files
When programs contain only a few small files, manually adding a few forward
declarations to the top of each file isn’t too bad. However, as programs grow
larger (and make use of more files and functions), having to manually add a
large number of (possibly different) forward declarations to the top of each file
becomes extremely tedious.
Header files allow us to put declarations in one place and then import them
wherever we need them. This can save a lot of typing in multi-file programs.
#include <iostream>
int main()
{
std::cout << "Hello, world!";
return 0;
}
main.cpp :
#include <iostream>
int main()
{
std::cout << "The sum of 3 and 4 is " << add(3, 4)
<< '\n';
return 0;
}
2. The actual content of the header file, which should be the forward
declarations for all of the identifiers we want other files to be able to
see.
💡 Best practice
Header files are often paired with code files, with the header file providing
forward declarations for the corresponding code file. Since our header file
will contain a forward declaration for functions defined in add.cpp, we’ll
call our new header file add.h.
💡 Best practice
If a header file is paired with a code file (e.g. add.h with add.cpp ),
they should both have the same base name ( add ).
main.cpp :
#include "add.h"
// Insert contents of add.h at this point. Note use of
double quotes here.
#include <iostream>
int main()
{
std::cout << "The sum of 3 and 4 is " << add(3, 4)
<< '\n';
return 0;
}
add.cpp :
#include "add.h"
// Insert contents of add.h at this point. Note use of
double quotes here.
When the preprocessor processes the #include "add.h" line, it copies the
contents of add.h into the current file at that point. Because
our add.h contains a forward declaration for function add(), that forward
declaration will be copied into main.cpp. The end result is a program that
is functionally the same as the one where we manually added the forward
declaration at the top of main.cpp.
add.h :
main.cpp :
int main()
{
std::cout << "The sum of 3 and 4 is " << add(3, 4)
<< '\n';
return 0;
}
add.cpp :
When main.cpp is compiled, the #include "add.h" will be replaced with the
contents of add.h and then compiled. Therefore, the compiler will compile
something that looks like this:
// from add.h:
int add(int x, int y)
{
return x + y;
}
int main()
{
return 0;
}
When the compiler compiles add.cpp , the #include "add.h" will be replaced
with the contents of add.h and then compiled. Therefore, the compiler will
compile something like this:
💡 Best practice
Do not put function and variable definitions in your header files
(for now).
This allows the compiler to catch certain kinds of errors at compile time
instead of link time. For example:
something.h :
something.cpp :
#include "something.h"
💡 Best practice
Source files should #include their paired header file (if one
exists).
Any change to such a .cpp file will cause both the .cpp file and any
other .cpp file that includes it to recompile, which can take a long time.
Headers tend to change less often than source files.
It is non-conventional to do so.
💡 Best practice
When we use angled brackets, we’re telling the preprocessor that this
is a header file we didn’t write ourselves. The preprocessor will search
for the header only in the directories specified by the include
directories . The include directories are configured as part of your
The original versions of cout and cin were declared in iostream.h in the
global namespace. Life was consistent, and it was good.
To work around this issue, C++ introduced new header files that lack
the .h extension. These new header files declare all names inside
the std namespace. This way, older programs that include #include
<iostream> .
C++ specific
<xxx> iostream std namespace
(new)
std namespace
C compatibility
<cxxx> cstddef (required)global namespace
(new)
(optional)
C++ specific
<xxx.h> iostream.h Global namespace
(old)
💡 Best practice
Use the standard library header files without the .h extension.
User-defined headers should still use a .h extension.
Another common question involves how to include header files from other
directories.
One (bad) way to do this is to include a relative path to the header file you
want to include as part of the #include line. For example:
#include "headers/myHeader.h"
#include "../moreHeaders/myOtherHeader.h"
While this will compile (assuming the files exist in those relative
directories), the downside of this approach is that it requires you to reflect
your directory structure in your code. If you ever update your directory
structure, your code won’t work anymore.
A better method is to tell your compiler or IDE that you have a bunch of
header files in some other location, so that it will look there when it can’t
find them in the current directory. This can generally be done by setting
an include path or search directory in your IDE project settings.
directory):
g++ -o main -I/source/includes main.cpp
The nice thing about this approach is that if you ever change your
directory structure, you only have to change a single compiler or IDE
setting instead of every code file.
It’s common that a header file will need a declaration or definition that lives
in a different header file. Because of this, header files will often #include
other header files.
When your code file #includes the first header file, you’ll also get any other
header files that the first header file includes (and any header files those
include, and so on). These additional header files are sometimes
called transitive includes, as they’re included implicitly rather than
explicitly.
The content of these transitive includes are available for use in your code
file. However, you generally should not rely on the content of headers that
are included transitively (unless reference documentation indicates that
those transitive includes are required). The implementation of header files
may change over time, or be different across different systems. If that
happens, your code may only compile on certain systems, or may compile
now but not in the future. This is easily avoided by explicitly including all of
the header files the content of your code file requires.
💡 Best practice
Each file should explicitly #include all of the header files it needs
to compile. Do not rely on headers included transitively from
other headers.
Give a header file the same name as the source file it’s associated with
(e.g. grades.h is paired with grades.cpp ).
Only #include what you need (don’t include everything just because
you can).
Header guards
We noted that a variable or function identifier can only have one definition
(the one definition rule). Thus, a program that defines a variable identifier
more than once will cause a compile error:
int main()
{
int x; // this is a definition for variable x
int x; // compile error: duplicate definition
return 0;
}
Similarly, programs that define a function more than once will also cause a
compile error:
#include <iostream>
int main()
{
std::cout << foo();
return 0;
}
While these programs are easy to fix (remove the duplicate definition),
with header files, it’s quite easy to end up in a situation where a definition
in a header file gets included more than once. This can happen when a
header file #includes another header file (which is common).
Author’s note
In upcoming examples, we’ll define some functions inside header files. You
generally shouldn’t do this. We are doing so here because it is the most
effective way to demonstrate some concepts using functionality we’ve
already covered.
square.h :
int getSquareSides()
{
return 4;
}
wave.h :
#include "square.h"
main.cpp :
int main()
{
return 0;
}
Thus, after resolving all of the #includes, main.cpp ends up looking like this:
int main()
{
return 0;
}
Header guards
The good news is that we can avoid the above problem via a mechanism
called a header guard (also called an include guard). Header guards are
conditional compilation directives that take the following form:
#ifndef SOME_UNIQUE_NAME_HERE
#define SOME_UNIQUE_NAME_HERE
// your declarations (and certain types of definitions)
here
#endif
int getSquareSides()
{
return 4;
}
#endif
Even the standard library headers use header guards. If you were to take a
look at the iostream header file from Visual Studio, you would see:
#ifndef _IOSTREAM_
#define _IOSTREAM_
// content here
#endif
In large programs, it’s possible to have two separate header files (included
from different directories) that end up having the same filename (e.g.
directoryA\config.h and directoryB\config.h ). If only the filename is used for
the include guard (e.g. CONFIG_H), these two files may end up using the
same guard name. If that happens, any file that includes (directly or
indirectly) both config.h files will not receive the contents of the include
file to be included second. This will probably cause a compilation error.
Because of this possibility for guard name conflicts, many developers
recommend using a more complex/unique name in your header guards.
Some good suggestions are a naming convention of
PROJECT_PATH_FILE_H, FILE_LARGE-RANDOM-NUMBER_H, or
FILE_CREATION-DATE_H.
#ifndef SQUARE_H
#define SQUARE_H
int getSquareSides()
{
return 4;
}
#endif
wave.h
#ifndef WAVE_H
#define WAVE_H
#include "square.h"
#endif
main.cpp :
#include "square.h"
#include "wave.h"
int main()
{
return 0;
}
After the preprocessor resolves all of the #include directives, this program
looks like this:
#endif // SQUARE_H
int getSquareSides()
{
return 4;
}
#endif // SQUARE_H
#endif // WAVE_H
int main()
{
return 0;
}
Header guards do not prevent a header from being included once into
different code files
square.h :
#ifndef SQUARE_H
#define SQUARE_H
int getSquareSides()
{
return 4;
}
#endif
square.cpp :
main.cpp :
int main()
return 0;
}
Note that square.h is included from both main.cpp and square.cpp. This
means the contents of square.h will be included once into square.cpp and
once into main.cpp.
Let’s examine why this happens in more detail. When square.h is included
from square.cpp, SQUARE_H is defined until the end of square.cpp. This
define prevents square.h from being included into square.cpp a second
time (which is the point of header guards). However, once square.cpp is
finished, SQUARE_H is no longer considered defined. This means that
when the preprocessor runs on main.cpp, SQUARE_H is not initially
defined in main.cpp.
The end result is that both square.cpp and main.cpp get a copy of the
definition of getSquareSides. This program will compile, but the linker will
complain about your program having multiple definitions for
identifier getSquareSides!
The best way to work around this issue is simply to put the function
definition in one of the .cpp files so that the header just contains a forward
declaration:
square.h :
#ifndef SQUARE_H
#define SQUARE_H
#endif
square.cpp :
#include "square.h"
main.cpp :
int main()
{
std::cout << "a square has " << getSquareSides() <<
" sides\n";
std::cout << "a square of length 5 has perimeter le
ngth " << getSquarePerimeter(5) << '\n';
return 0;
}
#pragma once
#pragma once
#pragma once serves the same purpose as header guards: to avoid a header
file from being included multiple times. With traditional header guards, the
developer is responsible for guarding the header (by using preprocessor
directives #ifndef , #define , and #endif ). With #pragma once , we’re requesting
that the compiler guard the header. How exactly it does this is an
implementation-specific detail.
For advanced readers
There is one known case where #pragma once will typically fail. If a header
file is copied so that it exists in multiple places on the file system, if
somehow both copies of the header get included, header guards will
successfully de-dupe the identical headers, but #pragma once won’t
(because the compiler won’t realize they are actually identical content).
For most projects, #pragma once works fine, and many developers now
prefer it because it is easier and less error-prone. Many IDEs will also
auto-include #pragma once at the top of a new header file generated through
the IDE.
Warning
The #pragma directive was designed for compiler implementers to use for
whatever purposes they desire. As such, which pragmas are supported
The most important thing to remember (and hardest thing to do) is to design
your program before you start coding. In many regards, programming is like
architecture. If you try to program before you have a good game-plan moving
forward, you’ll likely find that your code has a lot of problems, and you’ll have
to spend a lot of time fixing problems that could have been avoided altogether
with a little thinking ahead.
A little up-front planning will save you both time and frustration in the long run.
Model how long it takes for a ball dropped off a tower to hit the
ground.
While defining your problem helps you determine what outcome you
want, it’s still vague. The next step is to think about requirements.
For example:
A single problem may yield many requirements, and the solution isn’t
“done” until it satisfies all of them.
Get dressed
Eat breakfast
Travel to work
Prepare breakfast
Take a shower
Bedroom things
Bathroom things
Take a shower
Get dressed
Breakfast things
Eat cereal
Transportation things
Travel to work
Bedroom things
Bathroom things
Breakfast things
Transportation things
Calculate result
Print result
int main()
{
// doBedroomThings();
// doBathroomThings();
// doBreakfastThings();
// doTransportationThings();
return 0;
}
int main()
{
// Get first number from user
// getUserInput();
// Calculate result
// calculateResult();
// Print result
// printResult();
return 0;
}
#include <iostream>
return input;
}
int main()
{
// Get first number from user
int value{ getUserInput() }; // Note we've included
std::cout << value << '\n'; // debug code to ensure
// Calculate result
// calculateResult();
// Print result
// printResult();
return 0;
}
Once your program is “finished”, the last step is to test the whole
program and ensure it works as intended. If it doesn’t work, fix it.
Add features over time. Once you have your simple program
working and working well, then you can add features to it. For
Test each piece of code as you go. New programmers will often
write the entire program in one pass. Then when they compile it for
the first time, the compiler reports hundreds of errors. This can not
only be intimidating, if your code doesn’t work, it may be hard to
figure out why.
Instead, write a piece of code, and then compile and test it
immediately. If it doesn’t work, you’ll know exactly where the
problem is, and it will be easy to fix.
Once you are sure that the code works, move to the next piece and
repeat. It may take longer to finish writing your code, but when you
are done the whole thing should work, and you won’t have to
spend twice as long trying to figure out why it doesn’t.
A function call is an expression that tells the CPU to execute a function. The
function initiating the function call is the caller, and the function being called is
the callee or called function. Do not forget to include parenthesis when
making a function call.
The return value from function main is called a status code, and it tells the
operating system (and any other programs that called yours) whether your
program executed successfully or not. By consensus a return value of 0
means success, and a non-zero return value means failure.
Functions with a return type of void do not return a value to the caller. A
function that does not return a value is called a void function or non-value
returning function. Void functions can’t be called where a value is required.
A return statement that is not the last statement in a function is called an early
return. Such a statement causes the function to return to the caller
immediately.
When two identifiers are introduced into the same program in a way that the
compiler or linker can’t tell them apart, the compiler or linker will error due to
a naming collision. A namespace guarantees that all identifiers within the
namespace are unique. The std namespace is one such namespace.
Header files are files designed to propagate declarations to code files. When
using the #include directive, the #include directive is replaced by the contents
of the included file. When including headers, use angled brackets when
including system headers (e.g. those in the C++ standard library), and use
double quotes when including user-defined headers (the ones you write).
When including system headers, include the versions with no .h extension if
they exist.
Header guards prevent the contents of a header from being included more
than once into a given code file. They do not prevent the contents of a header
from being included into multiple different code files.
Quiz
Functions
Q1
In a function definition, what are the curly braces and statements in-
between called?
Solution
What does the following program print? Do not compile this program,
just trace the code yourself.
Solution
void doB()
{
std::cout << "In doB()\n";
}
void doA()
{
std::cout << "In doA()\n";
doB();
}
doA();
doB();
return 0;
}
Value-returning functions
Q1
a)
#include <iostream>
int return7()
{
return 7;
}
int return9()
{
return 9;
}
int main()
{
std::cout << return7() + return9() << '\n';
return 0;
}
b)
#include <iostream>
int return7()
{
return 7;
int return9()
{
int main()
{
std::cout << return7() + return9() << '\n';
return 0;
}
c)
#include <iostream>
int return7()
{
return 7;
}
int return9()
{
return 9;
}
int main()
{
return7();
return9();
return 0;
}
d)
int getNumbers()
{
return 5;
return 7;
}
int main()
{
std::cout << getNumbers() << '\n';
std::cout << getNumbers() << '\n';
return 0;
}
e)
#include <iostream>
int main()
{
std::cout << return 5() << '\n';
return 0;
}
f)
int returnFive()
{
return 5;
}
int main()
{
std::cout << returnFive << '\n';
return 0;
}
Q2
What does “DRY” stand for, and why is it a useful practice to follow?
Solution
DRY stands for “Don’t Repeat Yourself”. It is a practice that involves
writing your code in such a way so as to minimize redundancy.
This makes your programs more concise, less error prone, and
more maintainable.
Void functions
Inspect the following programs and state what they output, or whether
they will not compile.
a)
#include <iostream>
void printA()
{
std::cout << "A\n";
}
int main()
{
printA();
printB();
return 0;
}
Solution
b)
#include <iostream>
void printA()
{
std::cout << "A\n";
}
int main()
{
std::cout << printA() << '\n';
return 0;
}
Solution
Q1
#include <iostream>
int main()
{
std::cout << multiply(4, 5) << '\n';
return 0;
}
Solution
Q2
#include <iostream>
return 0;
}
Solution
Q3
What value does the following program print?
#include <iostream>
int main()
{
std::cout << multiply(add(1, 2, 3), 4) << '\n';
return 0;
}
Q4
Write a function called doubleNumber() that takes one integer parameter.
The function should return double the value of the parameter.
Solution
int doubleNumber(int x)
{
return 2 * x;
}
Q5
Write a complete program that reads an integer from the user, doubles
it using the doubleNumber() function you wrote in the previous quiz
question, and then prints the doubled value out to the console.
Solution
#include <iostream>
int doubleNumber(int x)
{
return 2 * x;
}
int main()
{
int x{};
std::cin >> x;
std::cout << doubleNumber(x) << '\n';
Local scope
#include <iostream>
void doIt(int x)
{
int y{ 4 };
std::cout << "doIt: x = " << x << " y = " << y << '\n'
x = 3;
std::cout << "doIt: x = " << x << " y = " << y << '\n'
}
int main()
{
int x{ 1 };
int y{ 2 };
std::cout << "main: x = " << x << " y = " << y << '\n'
doIt(x);
std::cout << "main: x = " << x << " y = " << y << '\n'
return 0;
}
Solution
main: x = 1 y = 2
doIt: x = 1 y = 4
Note that even though doIt ‘s variables x and y had their values
initialized or assigned to something different
than main ‘s, main ‘s x and y were unaffected because they are
different variables.
Q1
What is a function prototype?
Solution
Q2
Solution
Q3
How do we declare a forward declaration for functions?
Solution
For functions, a function declaration/prototype serves as a forward
declaration.
Q4
Write the function declaration for this function (use the preferred form
with names):
Solution
Q5
a)
#include <iostream>
int main()
{
std::cout << "3 + 4 + 5 = " << add(3, 4, 5) <
< '\n';
return 0;
}
Solution
b)
#include <iostream>
int main()
{
std::cout << "3 + 4 + 5 = " << add(3, 4, 5) <
< '\n';
Solution
c)
#include <iostream>
int main()
{
std::cout << "3 + 4 = " << add(3, 4) << '\n';
return 0;
}
Solution
d)
#include <iostream>
int main()
{
std::cout << "3 + 4 + 5 = " << add(3, 4, 5) <
< '\n';
return 0;
}
Solution
e)
#include <iostream>
Solution
Compiles and links. This is the same as the prior case. Function
declarations do not need to specify the names of the
parameters (even though we generally prefer to include them).
Split the following program into two files ( main.cpp , and input.cpp ). main.cpp
should have the main function, and input.cpp should have the getInteger
function.
#include <iostream>
int getInteger()
{
std::cout << "Enter an integer: ";
int x{};
std::cin >> x;
return x;
}
int main()
{
std::cout << x << " + " << y << " is " << x + y << '\n
return 0;
}
Solution
input.cpp
int getInteger()
{
std::cout << "Enter an integer: ";
int x{};
std::cin >> x;
return x;
}
main.cpp
int main()
{
int x{ getInteger() };
int y{ getInteger() };
std::cout << x << " + " << y << " is " << x + y <<
return 0;
}
Header guards
Solution
#ifndef ADD_H
#define ADD_H
#endif
Summary
Q1
Expected behavior:
Solution
int readNumber()
{
std::cout << "Enter a number to add: ";
int x {};
std::cin >> x;
return x;
}
void writeAnswer(int x)
{
std::cout << "The answer is " << x << '\n';
}
int main()
{
int x { readNumber() };
int y { readNumber() };
writeAnswer(x + y); // using operator+ to pass t
return 0;
}
Q2
Solution
io.cpp
#include <iostream>
int readNumber()
{
void writeAnswer(int x)
{
std::cout << "The answer is " << x << '\n';
}
main.cpp
int main()
{
int x { readNumber() };
int y { readNumber() };
writeAnswer(x+y);
return 0;
}
Q3
Solution
io.h
int readNumber();
void writeAnswer(int x);
#endif
io.cpp
#include "io.h"
#include <iostream>
int readNumber()
{
std::cout << "Enter a number to add: ";
int x {};
std::cin >> x;
return x;
}
void writeAnswer(int x)
{
std::cout << "The answer is " << x << '\n';
}
main.cpp
#include "io.h"
int main()
{
int x { readNumber() };
int y { readNumber() };
writeAnswer(x+y);