C++ Boot Camp - Basic Syntax

Written By: Milad Fatenejad

C++ is an extremely versatile language. It can be used to program extremely complicated high-level programs, like office suites, and very low-level programs. Properly written C++ programs can run extremely quickly and can be written relatively painlessly. The goal of this section is to teach you some of the basic syntax of C++. This will include examples which demonstrate the use of variable declarations, loops, conditionals, and functions. This section ends with a description of how to create multi-file C++ programs.

Hello, World!

The most basic C++ program is shown below. It simply prints "Hello, World!" to the console and exits. You must first compile this program to run it. For this boot camp, we will use the GNU C++ compiler, g++. To compile the program, simply enter:

g++ helloworld.cpp -o helloworld

This will make an executable called "helloworld", which you can now run.

helloworld.cpp

Line 
1// Prints "Hello, World!" to the console.
2#include <iostream>
3
4int main()
5{
6  std::cout << "Hello, World!\n";
7  return 0;
8}

While this may seem like a trivial example, it can actually offer a lot of insight. Line 1 contains a comment. Anything following two forward-slashes, //, is ignored by the compiler. Also, anything between the symbols /* and */ is ignored by the compiler. Line 4 begins the definition of a function called main. Every program needs to define this function, because this tells the computer where to start executing the program. All of the code that is associated with the function main is held between the curly braces on lines 5 and 8. So the function main contains two lines of code shown on lines 6 and 7.

The code on line 6 tells the computer to print the string "Hello, World!\n" to the console. The "\n" at the end of the string represents a new line. Try removing the "\n" and rerunning to the program. Notice that line 6 ends with a semi-colon. Almost every line of code in C++ must end in a semi-colon. This tells the compiler where one command ends and the next one begins. C++ ignores white space for the most part. For example, you can compress all of lines 4 through 8 into one line, and the code will still compile. But how does the code on line 6 work? What is that funny std::cout thing? It is an output stream that represents the standard output. Any data you send to std::cout is automatically displayed on the console. In this case we've chosen to output one piece of data, the string "Hello, World!\n". We tell the compiler to send the data to std::cout using the insertion stream operator, <<. If we want to send more data to the console, we can string together data. For example, to print "Hello, World!\n", then the number 5, then the string "blue".

std::cout << "Hello, World!\n" << 5 << "blue";

Line 7 exits the function main. The number 0 is the return value of this function. The word int on line 4 tells the compiler that main is a function that returns an integer. The integer 0 is returned on line 7. The program ends when main is exited. The return value of main represents the return value of the entire program and can be used to indicate whether the program is ending normally or not. When a program returns a value other than zero, it represents an abnormal program exit.

Aside: Return Values in the Shell
You can determine the return value of a program you run on the command line using the special shell variable $?. This variable stores the return value of the last program to execute. For example, if we ran our program and printed $?, we would see the following:

tux-111:~/cpp-bootcamp/basic-syntax% ./helloworld 
Hello, World!
tux-111:~/cpp-bootcamp/basic-syntax% echo $?
0

What does line 2 do? Try commenting it out and see what happens. If you recompile this program without line 2, you should see an error like the one below:

main.cpp: In function 'int main()':
main.cpp:6: error: 'cout' is not a member of 'std'

The compiler is trying to tell you that it doesn't know what cout is. The C++ compiler on its own understands very few words. If you go here you can see a list of every word the compiler understands. Every other word has to be a name that you declare. For example, the compiler knows that main is a function that returns an integer because you define it on line 4. The file iostream contains a declaration of the symbol cout. Line 2 contains a directive which tells the compiler to take the contents of the file iostream and literally insert it at line 2 of helloworld.cpp. When we removed line 2, we removed the declaration of the symbol cout and the compilation failed, because the compiler did not know what cout was. You can look at the file iostream, it is located in the directory: /usr/include/c++/4.1/.

The file, iostream, is part of something called the standard library. The standard library is a library which every C++ program is automatically linked to. It contains code for doing things like printing to the screen, doing math (sqrt, cos, etc. are all defined in the standard library) - essentially, it contains the minimum features necessary to write a useful program. As we learn more and more throughout the boot camp, we will introduce more features from the standard library.

Any line that contains a command that begins with a pound sign is a command that is sent to something called the preprocessor. These commands like #include (or #define, #if - although we haven't seen these yet) are called preprocessor directives because they are sent to a program called the "preprocessor". Before the compiler actually goes through and compiles each line of C++, it runs the source code through the preprocessor which handles all of the preprocessor directives. For example, when the following command is run:

g++ helloworld.cpp -o helloworld

Two processes are automatically performed by the compiler, g++:

  1. helloworld.cpp is run through the preprocessor. The file, iostream, is inserted at line two.
  2. The preprocessed source code (with iostream inserted) is compiled to produce the executable file, helloworld.

Aside: Viewing Preprocessed Output
The two step process described above is literally what the compiler does. You can make the compiler perform just the preprocessing step using the -E compiler option. For example:

g++ -E helloworld.cpp > helloworld.i

The file helloworld.i contains the preprocessed output. Notice that it is almost half a megabyte in size! That is because the file iostream includes other files, which include other files, etc... You can then perform step two manually by issuing the following command:

g++ helloworld.i -o helloworld

The preprocessor can be invoked completely separately from g++ using the program cpp. Check out the man page for cpp for more information.

Variables

In C++, every variable needs to be explicitly defined. For example, you can define an integer, i, by writing the following:

integer i;

You can define multiple variables at once and assign them initial values when you define them. For example, the statement:

double a, b = 4.1, c;

defines 3 double precision variables, a, b, and c. A double precision variable is essentially a real number. The variable b is given the initial value 4.1. If a variable is not given an initial value, it is not initialized to zero, it might contain anything. So, to create a variable, you write the variable's type (int, double, etc...) followed by the variable's name. C++ provides several built-in types, such as int (integer), double (real number) and char (character). Built-in types can be used at any time and are automatically understood by the compiler. Later, we will see that C++ allows you define your own types, and other types are defined in the standard library. These can be used if the right header file is included in your program (using a #include preprocessor directive). The program defined in vardef.cpp demonstrates how to define and use some variables. This file is shown below.

vardef.cpp

Line 
1/* Simple program to demonstrate the creation and use of variables. */
2#include <iostream>
3#include <string>
4
5int main()
6{
7  int i1 = 3, i2(5), i3; // Create three integers, i1, i2, and i3
8                         // i2(5) is identical to i2 = 5
9
10  i3 = i1 + i2;
11
12  unsigned int i4 = 3; // Create a non-negative integer
13 
14  // Create a non-negative integer and mistakenly assign it a negative
15  // value. The compiler won't complain about this, but it is not
16  // good. Run this program to see to see what value i5 actually has:
17  unsigned int i5 = -1; 
18
19  // Print all of the variables. std::endl is a variable defined in
20  // iostream which represents a newline.
21  std::cout << i1 << "  " << i2 << "  " << i3 << "  " << i4 << "  " 
22            << i5 << std::endl
23            << "sizeof(int) = " << sizeof(int) << std::endl;
24 
25 
26  // Create a string, which is of type std::string. It is not a
27  // builtin type. The string type is defined in the file string,
28  // which is included above.
29  std::string str1 = "Hello", str2(", World!");
30
31  // Print the two strings:
32  std::cout << str1 << str2 << std::endl
33            << "sizeof(std::string) = " << sizeof(str1) << std::endl;
34}

This program can be compiled using the command:

std::cout << "Hello, World!\n" << 5 << "blue";

There are only a few items to point out. On lines 12 and 17, an unsigned (non-negative) integer is created using the unsigned keyword. In a similar way you can create an unsigned double. Notice that on line 17, the unsigned integer is given a negative value! The compiler will not complain about this. Instead the variable will be given a nonsensical value. Run the program to see what value i5 assigned. On line 23, the sizeof built-in function is used. Every time a variable is created, some amount of memory is used by the program. For example, each integer that is created consumes four bytes of memory. Each double precision variable consumes eight bytes of memory. You can use the built-in sizeof function to see how much memory a variable of a given type requires. This is done on lines 23 and 33. Finally, line 29 demonstrates the use of a non-built-in (user defined) type - std::string. A variable of type std::string represents a string. To use strings, you have to include the file string, as is done on line 3.

Reading Data and Using Files

So far we've seen one technique for performing IO (input/output), namely writing data to the console. But how to we read data from the console (i.e. how do we ask the user some questions?), or how do we read to/write from files? To answer these questions, lets analyze the following line of code in detail:

std::cout << "Hello, World!\n";

When the compiler gets to this line, the first thing it does is ask itself, "What is std::cout?". As we discussed earlier, std::cout is defined in the header file iostream which we presumably inserted earlier using the preprocessor directive: #include <iostream>.std::cout is an output stream which means it is something we can send data to so that it can be outputted somewhere. In the case of std::cout, and data that we send is outputted on the console. We send data to an output stream using the insertion stream operator, <<. In the example above, the insertion stream operator sends the data "Hello, World!\n" to the output stream std::cout.

So, logically, to read data from the console you should expect there to be an input stream - opposite to std::cout - which is used to read in data. Such a stream exists, and is naturally called std::cin. To get data from the input stream std::cin, we use the extraction operator >>, as shown in the following example:

int a, b;
std::cin >> a >> b;

When the program executes the commands above, it will wait for the user to type in two integers, separated by spaces, then hit enter. It will then set a and b based on the input. We can see from this example that reading input is similar to writing output except for two important differences:

  1. An input stream (e.g. std::cin) is used instead of an output stream (e.g. std::cout)
  2. The extraction stream operator, >>, is used instead of the insertion stream operator

Earlier, it was explained that an output stream is something we can send data to so that it can be outputted somewhere. In the example above, that somewhere is the console. To write to a file, we will create an output stream that dumps data to a file. We do so by creating a variable of type std::ofstream which is defined in the header file fstream. The o in std::ofstream stands for output, and the f stands for file. We can use variables of type std::ofstream just like we use std::cout. The following example demonstrates the use of output file streams.

writefile.cpp

Line 
1// Ask the user for some data and write the data to a file
2#include <iostream>
3#include <string>
4#include <fstream>
5
6int main()
7{
8  int age;
9  std::string first_name, last_name;
10
11  // Get the data from the user:
12  std::cout << "What is your first name, last name, and age?\n";
13  std::cin >> first_name >> last_name >> age;
14
15  // Create a file and write the data to the file:
16  std::ofstream output_file("out.txt");
17
18  output_file << first_name << "\n" << last_name << "\n"
19              << age << std::endl;
20
21  return 0;
22}

First, notice that we had to include the header file fstream on line 4 to use file streams. This program asks the user to enter 3 pieces of data - their first name, last name, and age. An output file, "out.txt", is then created and the data is written to that file. The output file stream, output_file is created on line 16. Notice that this variable is initialized using the string "out.txt". This results in the creation of the file "out.txt". Data is written to the file on lines 18 and 19. Notice that if we replaced output_file on line 18 with std::cout, the data would be written to the screen. Instead, it is written to the file "out.txt" because we are using an output file stream. To read data from a preexisting file, simply create an std::ifstream variable, and initialize it using the file name. Then, just use the extraction stream operator, >>, instead of the insertion stream operator. As an example, the following short program opens the file "out.txt" that we just generated, reads the data, and writes it to the console.

readfile.cpp

Line 
1// Read data from out.txt and write it to the screen
2#include <iostream>
3#include <string>
4#include <fstream>
5
6int main()
7{
8  int age;
9  std::string first_name, last_name;
10
11  // Open the file "out.txt" and read the data:
12  std::ifstream input_file("out.txt");
13  input_file >> first_name >> last_name >> age;
14
15  // Write some information to the screen:
16  std::cout << "Hello " << first_name << " " << last_name << "\n"
17            << "How does it feel to be " << age << "?\n";
18
19  return 0;
20}

Aside: Streaming Data to/from Strings
We saw above that the language of streams is extremely powerful. It allows us to write data to and read data from any source using a common syntax. We've seen that switching from writing to a file and writing to the console is trivial. What about writing data to a string? As you might expect, the standard library defines a type called std::stringstream in the header file sstream. For example, the following code:

std::stringstream ss;
ss << 5 << "  " << 6;
std::string s = ss.str();

creates a string stream (that can be used for output OR input) called ss and sends some data to it. The final line creates a string, s, and sets it to the string "5 6". We haven't learned everything we need to take full advantage of string streams, but you should learn to use them down the road because they give you an extremely powerful way to format strings.

Conditionals and Loops

We will now quickly introduce loops and conditionals (if, else). C++ handles loops and conditionals like many other languages but there are a few quirks worth pointing out. Lets start with conditionals - the following program demonstrates the use of conditionals in C++.

conditional.cpp

Line 
1// Simple program to demonstrate the use of conditionals in C++
2#include <iostream>
3#include <string>
4
5int main()
6{
7  int num;
8  std::string str1 = "Hello, World!\n";
9  std::string str2 = "Goodbye, World!\n";
10
11  std::cout << "Enter either 1 or 2:\n";
12  std::cin >> num;
13
14  // Use == to test for equality (2 EQUALS NOT 1!):
15  if(num == 1)
16    std::cout << str1;
17  else if(num == 2)
18    std::cout << str2;
19  else
20    std::cout << "You didn't enter 1 or 2\n";
21
22  std::cout << "Do you want me to print both strings?\n"
23            << "(0: no, any other integer: yes)\n";
24  std::cin >> num;
25
26  // In C++, the integer zero is false, and any other integer is true.
27  if(num) {
28    std::cout << str1;
29    std::cout << str2;
30  } else {
31    std::cout << "I won't print ";
32    std::cout << "either string.\n";
33  }
34
35  return 0;
36}

Lines 15-20 demonstrate the form of an if statement in C++. The form is:

if(<condition>)
  <statement to execute if <condition> is true>;
else
  <statement to execute if <condition> is true>;

If the condition evaluates to true, the statement immediately following the if statement is executed. Conversely, if the condition is false, the statement immediately after the else is executed. One common mistake that people new to C++ make is demonstrated in the example below.

int n = 5;
if(n == 4)
  std::cout << "Hello\n";
  std::cout << "World!\n";

You might expect that nothing is printed in this example. However, the string "World!\n" is printed, because only the 1 statement after the if is considered part of the if statement. However, by using some curly braces, as shown below and in lines 27-33 above, we can produce the correct behavior:

int n = 5;
if(n == 4) {
  std::cout << "Hello\n";
  std::cout << "World!\n";
}

Now, everything within the curly braces is considered part of the if statement, and nothing is printed. The following examples demonstrate how build the conditions for if statements:

if(a == b) {...}  // Tests whether a and b are equal
if(a != b) {...}  // Tests whether a and b are not equal
if(a <  b) {...}  // Tests whether a is less than b
if(a <= b) {...}  // Tests whether a is less than or equal to b
if(a >  b) {...}  // Tests whether a is greater than b
if(a >= b) {...}  // Tests whether a is greater than or equal to b
int a = 5; if(a) {...} // True if a is non-zero
if( (a == b) && (a == c) ) {...} // Tests whether a == b AND a == c
if( (a == b) || (a == c) ) {...} // Tests whether a == b OR  a == c
if( !(a == b && a == c) ) {...} // Tests whether a == b AND a == c is NOT true (The exclamation means NOT)

Aside: What does if(a = b) do?
If you want to compare to variables to see if they are equal you must use the comparison operator, "==", not the assignment operator "=". As you might imagine, it is really easy to accidentally write one equal sign instead of two. Unfortunately, if(a = b) is valid C++ and will compile. Consider the example below:

int a = 4, b = 5;
if(a = b) {
  std::cout << "Hello, World!\n";
}

When the compiler gets to the if statement it does two things:

  1. Sets a equal to b
  2. Checks to see if a is non-zero (which is it; in this case a has been assigned the value 5)
  3. Since a is non-zero, "Hello, World!\n" is printed. This is not the desired behavior.

If you are running with all compiler warnings active, using the -Wall compiler flag, then g++ will complain about the above code. This demonstrates why it is always a good idea to compile with -Wall.

The next example program demonstrates how loops function in C++.

loops.cpp

Line 
1// A short program to demonstrate the use of for and while loops in
2// C++. Every loop in this program displays the integers 0 through 9
3#include <iostream>
4
5int main()
6{
7  int i = 0;
8  bool should_loop = true;
9
10  // A while loop in C++ is similar to many other languages.
11  i = 0;
12  while(should_loop == true) {
13    std::cout << i << "  ";
14    i = i+1;
15    if(i >= 10) should_loop = false;
16  }
17  std::cout << std::endl;
18
19  // A for loop allows you to iterate a certain number of times:
20  for(i = 0; i < 10; i = i+1) {
21    std::cout << i << "  ";
22  }
23  std::cout << std::endl;
24
25  // Many times you will see loops like the one below:
26  for(int j = 0; j < 10; j++) {
27    std::cout << j << "  ";
28  }
29  std::cout << std::endl;
30
31  return 0;
32}

The above program contains three loops, each of which print the integers 0 through 9. First, a variable of type bool called should_loop is created on line 2. A variable of type bool can have one of two values: true and false. Naturally, a statement like if(should_loop) is the same as if(should_loop == true). This variable is used on line 12 as the condition for a while loop. The while loop in C++ operates much like while loops in other languages, like MatLab or FORTRAN. As long as the condition in parentheses is true, the loop will continue to execute. The body of the loop is contained within the curly braces following the while statement. Loops are similar to if statements, in that the curly braces can be omitted. If there is no curly brace than the one statement following the loops is considered the loop body.

If we want to print the integers between zero and nine, then it makes a lot more sense to use a for loop. The for loop is extremely similar to the for loop in other languages, or the do loop in FORTRAN. The code on line 20 demonstrates the use of a for loop in C++. The loop counter is the integer i. Within the parentheses on line 20, there are three statements, separated by semi-colons. The first statement initializes the loop counter to zero. The second statement sets the condition to end the loop. In this case the loop runs as long as i < 10. The final statement tells the compiler how to increment the loop counter. In this case, it says to increment i by 1 on each iteration. If instead, we wanted to loop from 2 to 21 printing only even numbers, we might write:

for(i = 2; i < 21; i = i + 2) {...}

When you encounter loops in C++, the form shown on line 26 is much more common. In this case, the loop counter, j is created in the for loop statement. This is really useful when you want to create a variable simply to be a counter in a loop. The condition to increment the loop counter also looks strange. The statement j++ is identical to j = j + 1. The ++ is called the increment operator and can be used to add one to an integer. This is a good time to introduce a few other mathematical operators that you may not have seen before, but are commonly used:

// Note: All of these statements also work with doubles!
int a = 4;
a++;     // Add 1 to a. Identical to a = a + 1. (You might also see ++a, which is similar)
a--;     // Subtract 1 from a. Identical to a = a - 1 (You might also see --a, which is similar)
a += 3;  // Add 3 to a. Identical to a = a + 3
a -= 3;  // Subtract 3 from a. Identical to a = a - 3
a /= 3;  // Divide a by 3. Identical to a = a / 3;
a *= 3;  // Multiply a by 3. Identical to a = a * 3;

Aside: The difference between ++C and C++
The statements ++a and a++ are similar, but do not do exactly the same thing. Try the following example:

int a = 5; b = 5;
std::cout << a++ << "  " << ++b << std::endl;
std::cout << a << "  " << b << std::endl;

This results in the following being printed:

5  6
6  6

Writing ++a and a++ has the same effect - namely that the value of a is increased by 1. However, a++ returns the old value of a - the value of a before incrementing. On the other hand, ++a returns the new value. This is usually not important, but is something to be aware of.

This has been a fairly brief introduction to loops and conditionals, and a few points have been left out in the interest of brevity. However, since all of you have had some experience programming in another language, I hope that the above explanation was adequate.

Functions

So far, every program we have written has involved a single function - main, which is required in any C++ program. The function main returns an integer, which is used as the program's return value. Any C++ program of interest will involve the definition of many functions. Lets start by creating a new Hello, World! program that uses a second function (one function in addition to main).

helloworldfunction.cpp

Line 
1// Prints "Hello, World!\n", but demonstrates the use of a function.
2#include <iostream>
3
4// Create a function called print:
5void print()
6{
7  std::cout << "Hello, World!\n";
8}
9
10int main()
11{
12  print(); // Call the print function
13  return 0;
14}

When the program is executed, the computer begins by looking for the function main, which every C++ program must have. The program execution begins on line 12, because that is the first line in main. The code on line 12 instructs the computer to call the function print which takes no arguments. This function begins on line 7, so the execution jumps to that point. Line 7 instructs the computer to print "Hello, World!\n". After this, the function print() is done, and execution continues on line 13. Lets examine what is involved in defining a function. When you create a variable in C++, you must tell the compiler the type of the variable - e.i. you must tell the compiler whether the variable is an int, double, etc... When you define a function you must tell the compiler three things:

  1. What type of data the function returns
  2. The number an type of arguments
  3. What the function actually does

In this example, we define a function called print on line 5. This function return type of the function is specified before the name. In this case the function returns void - meaning nothing. The function name is followed by (), because the print does not take any arguments. We tell the compiler what print does on line 7 - between the curly brackets. This is a fairly trivial example. Lets look at something a little more interesting. The following example defines a function power which performs exponentiation.

power.cpp

Line 
1// Ask the user for two integers, x and n and print x^n.
2#include <iostream>
3
4// The function power takes two integer arguments and returns x^n.
5int power(int x, int n)
6{
7  if(n == 0) return 1;
8
9  int answer = 1;
10  for(int i = 0; i < n; i++) {
11    answer *= x;
12  }
13
14  return answer;
15}
16
17int main()
18{
19  int x, n;
20
21  // Get the numbers:
22  std::cout << "Enter two integers:\n";
23  std::cin >> x >> n;
24
25  // Print the answer:
26  std::cout << x << "^" << n << " = " << power(x, n) << std::endl;
27  return 0;
28}

The function power is defined on lines 5 through 15. Line 5 tells the compiler that we are about to define a function called power that will return an integer and will require two arguments. The arguments are both integers. The first argument is given the name x, and the second is given the name n. Note that there is are variables called x and n defined in main. These variables have absolutely nothing to do with the function arguments x and n to power - they are completely different variables that just happen to have the same name. When we call power we must specify two arguments.

Now, lets look at line 26, which prints the results of our exponentiation calculation. We call the function power in this line. For arguments, we supply the integers x and n. The value that is printed to the console, is the return value of power. One thing to note is that when power is called the variables x and n are COPIED. These copies are then used as the function arguments in lines 7 - 14. In other words, if we were to change the value of x in power, the value of x in main would not change. This behavior is different from many other programming languages like FORTRAN.

Scope

We've seen in the previous example that variables defined in different parts of the program can share the same name, even though they are completely unrelated. We saw this in the example above. In that case there were two variables called x and two variables called n. Variables in C++ exist only in the scope in which they were defined. The scope of the variable can be determined by looking at the curly brackets which surround it. This is best illustrated by a short example:

Line 
1#include <iostream>
2int c;
3int main() 
4{
5  int a = 5;
6  if(a == 5)
7  {
8    int a; // ERROR: Can't have two variables called a!
9
10    int b = 6;
11    std::cout << b << std::endl; 
12  }
13
14  std::cout << b << std::endl; // ERROR: b no longer exists!
15}

On line 5 we create an integer, a. We can use this variable anywhere within it's scope. The scope of a is defined by the curly brackets on lines 4 and 15. So a exists between these lines. On line 8 we try creating another variable, a. This compiler will generate an error when it gets to this line, because the a defined on line 5 still exists. On line 10, we create a new variable b. This variables scope is defined by the curly brackets on lines 7 and 12. After the program execution exits the body of the if statement, the variable b is destroyed - in no longer exists and cannot be used. For this reason the code on line 14 generates an error. What about the variable c defined on line 2? This variable is not bound by any curly brackets. Variables like c are global variables. They are created when the program starts and are destroyed when the program ends. They can be used at any time during the execution of the program. By contrast, the variables a and b are local variables.

Overloading Functions

A function is uniquely identified by its name AND its arguments. Thus in C++, a multiple functions can have the same name and different arguments. Such functions are "overloaded". See the short example below.

int myfunction(int i) {...}
int myfunction(std::string i) {...}
std::string myfunction(int i) {...} // ERROR!

The first two lines are acceptable. We are defining two functions that happen to have the same name - as far as C++ is concerned, they have nothing to do with one another. However, the final line generates an error, because we have already defined a function called myfunction which takes an integer.

Aside: Overloading is Happening All Around You
Many functions in the standard library are overloaded. For example, if you include the cmath header file, you can access many useful math functions like sqrt (square root) and pow (exponentiation). These functions are overloaded to take arguments that are both int and double to maximize their efficiency. But beyond simple functions like these function overloading is happening all around you, but you may not realize it. In C++, operators like +, -, / and * are just functions which you can define, although you haven't learned exactly how to do this yet. When you add to integers, add an int to a double, or add two doubles you are actually invoking three different plus functions which take different arguments: 2 int's, an int and a double, and 2 doubles. There is even a plus function defined for std::string - so we can add two strings together. Overloading gives C++ a really natural way for dealing with operators.

Declaration vs. Definition

Lets say that you want to define two functions, func1 and func2. Suppose func1 is defined as:

int func1(int arg)
{
  if(arg == 1) {
    return func2(2);
  }
  return 1;
}

and func2 is defined as:

int func2(int arg)
{
  if(arg == 2){
    return func1(3);
  }
  return 2;
}

Don't worry about what these functions do - just notice that they call one another. Using what we now know, there is no way to use these functions. When compiling a source file, the compiler begins at the top of the file and starts working its way down. Whenever it encounters a word it does not understand it checks to see what we have defined that word to mean. Lets say that in our program we defined func1 first. The compiler would get to the line return func2(2) and would generate an error, because it doesn't understand what func2 is. If we were to define func2 first, we would have a similar problem. So how do we get around this issue? While this may be a contrived example, there are many occasions where things depend on one another...

Lets say we are going to define func1 first. The key is to recognize that the compiler doesn't need to know what func2 does - it just needs to know that somewhere out there, there is a function called func2 that takes one int argument. In other words we need to declare that such a function exists and thats exactly what function declarations do! Take a look at the following example:

twofuncs.cpp

Line 
1// Very contrived example to demonstrate the use of function
2// declarations.
3#include <iostream>
4
5int func1(int arg); // Declaration of func1
6int func2(int arg); // Declaration of func2
7
8// Now that we have declared func1 and func2 we can define them in any
9// order.
10
11// This is the definition for func1:
12int func1(int arg)
13{
14  if(arg == 1) {
15    return func2(2);
16  }
17  return 1;
18}
19
20// This is the definition for func2:
21int func2(int arg)
22{
23  if(arg == 2){
24    return func1(3);
25  }
26  return 2;
27}
28
29int main()
30{
31  // Call both functions, just to convince yourself the code works:
32  std::cout << func1(1) << std::endl
33            << func2(2) << std::endl;
34
35  return 0;
36}

There are two function declarations in the example above on lines 5 and 6. A function declaration is exactly like a function definition except that instead of curly braces, we put a semicolon and move on. Once the functions have been declared, we can define them in any order we want. Just to make sure this is clear I will list the various components of func1 and func2.

  • Line 5 contains a declaration for func1: Once the compiler processes this line, it knows that a function called func1 that takes an integer exists. This allows us to call func1 anywhere below this declaration
  • Line 6 contains a declaration for func2: Once the compiler processes this line, it knows that a function called func2 that takes an integer exists. This allows us to call func2 anywhere below this declaration
  • Lines 12-18 contain the definition of func1: To finish compiling the program, the compiler eventually needs to be told what func1 does. This happens in the definition. We can use func2 in this definition because it has already been declared above.
  • Lines 21-27 contain the definition of func2: To finish compiling the program, the compiler eventually needs to be told what func2 does. We can use func2 in this definition because it has already been defined.

You think that line 5 is unnecessary. This is true - technically we could remove line 5 and everything would work perfectly. We define func1 before func2 so when the compiler gets to line 24 it already knows what func1 is from its definition on lines 12-18. So why does the declaration of func1 on line 5 appear? Lets say we removed line 5 and a few weeks from now, someone else decides to modify the program, and in doing so, inadvertently flips the definitions of func1 and func2. When they tried to compile the program, they would get an error message like the one below:

twofuncs.cpp: In function ‘int func2(int)’:
twofuncs.cpp:15: error: ‘func1’ was not declared in this scope

and wouldn't know what was going on. Putting both declarations costs nothing and helps prevent problems down the road. Now imagine you have a larger program that defines 50 functions that all depend on one another in various ways. You don't want to have to worry about the order in which you define things. You just want to write your program and have things work. If at the top of your program you put a declaration for each function, then you don't have to worry about the order things are defined in. Actually, the header files that we have been including, like iostream and string, are simply files that contain declarations for a bunch of things so that we can use them whenever and where ever we want.

Aside: Declaring but not Defining What happens if you were to remove the definition of func2 by commenting out lines 21 through 27? If you tried to compile the program by issuing the following command:

g++ twofuncs.cpp -o twofuncs

You would get an error like the one below:

/tmp/ccVMi8Aj.o: In function `func1(int)':
twofuncs.cpp:(.text+0x69): undefined reference to `func2(int)'
/tmp/ccVMi8Aj.o: In function `main':
twofuncs.cpp:(.text+0x8d): undefined reference to `func2(int)'
collect2: ld returned 1 exit status

Part of getting proficiency with C++ involves understanding error messages. The compilation process that we have been talking about is actually a two step process where the steps are called: compiling, and linking. When you ask g++ to compile something, it first runs through and turns all of the functions into machine code (ones and zeros for the computer), but it essentially leaves the function names untouched. Then during a separate linking step, it glues all of the functions together. The program that performs the linking is called ld. What it is telling you here is that it successfully compiled the code, but there was a problem during the linking step. Specifically the linker was unable to find the definition of func2. We'll learn more about this in the next section.

Member functions (Methods)

The last basic syntax topic covers something called member functions. When someone makes a type in C++ (like std::string) then they have the ability to create special functions that are associated with that type. For example, if you make an std::string, then you could imagine making a function called size that returns the number of characters in the string. Such a function exists as part of the std::string type. To call it, you would write the following:

std::string str = "Hello, World!";
int n = str.size()

Notice that the integer, n, is initialized to the length of the string str using the size() member function. We call member functions by writing the variable name, followed by a . followed by the call to the member function. These member functions perform actions that are relevant to a specific type, like returning the length of a string. Member functions are also sometimes called "methods". We will learn a lot more about them when we learn to create our own types later on.