Beginning C++ Programming

4.2 (12 reviews total)
By Richard Grimes
  • Instant online access to over 8,000+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Starting with C++

About this book

C++ has come a long way and is now adopted in several contexts. Its key strengths are its software infrastructure and resource-constrained applications, including desktop applications, servers, and performance-critical applications, not to forget its importance in game programming. Despite its strengths in these areas, beginners usually tend to shy away from learning the language because of its steep learning curve.

The main mission of this book is to make you familiar and comfortable with C++. You will finish the book not only being able to write your own code, but more importantly, you will be able to read other projects. It is only by being able to read others' code that you will progress from a beginner to an advanced programmer. This book is the first step in that progression.

The first task is to familiarize you with the structure of C++ projects so you will know how to start reading a project. Next, you will be able to identify the main structures in the language, functions, and classes, and feel confident being able to identify the execution flow through the code. You will then become aware of the facilities of the standard library and be able to determine whether you need to write a routine yourself, or use an existing routine in the standard library.

Throughout the book, there is a big emphasis on memory and pointers. You will understand memory usage, allocation, and access, and be able to write code that does not leak memory. Finally, you will learn about C++ classes and get an introduction to object orientation and polymorphism.

Publication date:
April 2017
Publisher
Packt
Pages
526
ISBN
9781787124943

 

Chapter 1. Starting with C++

Why C++? There will be as many reasons to use C++ as there will be readers of this book.

You may have chosen C++ because you have to support a C++ project. Over the 30 years of its lifetime there have been millions of lines of C++ written, and most popular applications and operating systems will be mostly written in C++, or will use components and libraries that are. It is nearly impossible to find a computer that does not contain some code that has been written in C++.

Or, you may have chosen C++ to write new code. This may be because your code will use a library written in C++, and there are thousands of libraries available: open source, shareware, and commercial.

Or it may be because you are attracted to the power and flexibility that C++ offers. Modern high-level languages have been designed to make it easy for programmers to perform actions; while C++ has such facilities, it also allows you to get as close to the machine as possible, gives you the (sometimes dangerous) power of direct memory access. Through language features such as classes and overloading, C++ is a flexible language that allows you to extend how the language works and write reusable code.

Whatever your reason for deciding on C++, you have made the right choice, and this book is the right place to start.

 

What will you find in this chapter?


Since this book is a hands-on book, it contains code that you can type, compile, and run. To compile the code, you will need a C++ compiler and linker, and in this book that means Visual Studio 2017 Community Edition, which provides Visual C++. This compiler was chosen because it is a free download, it is compliant  with C++ standards and it has  very wide range of tools to make writing code easier. Visual C++ provides C++11-compliant language features and almost all the language features of C++14 and C++17. Visual C++ is also provided with the C99 runtime library, C++11 standard library, and C++14 standard library. All of this mentions of standard means that the code that you learn to write in this book will compile with all other standard C++ compilers.

This chapter will start with details about how to obtain and install Visual Studio 2017 Community Edition. If you already have a C++ compiler, you can skip this section. Most of this book is vendor-neutral about the compiler and linker tools, but Chapter 10, Diagnostics and Debugging, which covers debugging and diagnostics, will cover some Microsoft-specific features. Visual Studio has a fully featured code editor, so even if you do not use it to manage your projects, you'll find it useful to edit your code.

After we've described the installation, you'll learn the basics of C++: how source files and projects are structured, and how you can manage projects with potentially thousands of files.

Finally, the chapter will finish with a step-by-step structured example. Here you will learn how to write simple functions that use the standard C++ library and one mechanism to manage files in the project.

 

What is C++?


The predecessor of C++ is C, which was designed by Dennis Richie at Bell Labs and first released in 1973. C is a widely used language and was used to write the early versions of Unix and Windows. Indeed, the libraries and software-development libraries of many operating systems are still written to have C interfaces. C is powerful because it can be used to write code that is compiled to a compact form, it uses a static type system (so the compiler does the work of type checking), and the types and structures of the language allow for direct memory access to computer architecture.

C, however, is procedural and based on functions, and although it has record types (struct) to encapsulate data, it does not have object-like behaviors to act on that encapsulated state. Clearly there was a need for the power of C but the flexibility and extensibility of object-oriented classes: a language that was C, with classes. In 1983, Bjarne Stroustrup released C++. The ++ comes from the C increment operator ++.

Note

Strictly, when postfixed to a variable, the ++ operator means increment the variable, but return the variable's value before it was incremented. So the C statements int c = 1; int d = c++; will result in variable d having a value of 1 and variable c having a value of 2. This does not quite express the idea that C++ is an increment on C.

 

Installing Visual C++


Microsoft's Visual Studio Community 2017 contains the Visual C++ compiler, the C++ standard libraries, and a collection of standard tools that you can use to write and maintain C++ projects. This book is not about how to write Windows code; it is about how to write standard C++ and how to use the C++ Standard Library. All the examples in this book will run on the command line. Visual Studio was chosen because it is a free download (although you do have to register an e-mail address with Microsoft), and it is standards-compliant. If you already have a C++ compiler installed, then you can skip this section.

Setting up

Before starting the installation, you should be aware that, as part of the agreement to install Visual Studio as part of Microsoft's community program, you should have a Microsoft account. You are given the option to create a Microsoft account the first time you run Visual Studio and if you skip this stage, you will be given a 30-day evaluation period. Visual Studio will be fully featured during this month, but if you want to use Visual Studio beyond this time you will have to provide a Microsoft account. The Microsoft account does not impose any obligation on you, and when you use Visual C++ after signing in, your code will still remain on your machine with no obligation to pass it to Microsoft.

Of course, if you read this book within one month, you will be able to use Visual Studio without having to sign in using your Microsoft account; you may view this as an incentive to be diligent about finishing the book!

Downloading the installation files

To download the Visual Studio Community 2017 installer go to https://www.visualstudio.com/vs/ community/.

When you click on the Download Community 2017 button, your browser will download a 1 MB file called vs_community__1698485341.1480883588.exe. When you run this application, it will allow you to specify the languages and libraries that you want installed, and then it downloads and installs all the necessary components.

Installing Visual Studio

Visual Studio 2017 treats Visual C++ as an optional component, so you have to explicitly indicate that you want to install it through custom options. When you first execute the installer, you will see the following dialog box:

When you click on the Continue button the application will set up the installer, as shown here:

Along the top are three tabs labeled Workloads, Individual Components and Language Packs. Make sure that you have selected the Workloads tab (as shown in the screenshot) and check the checkbox in the item called Desktop development with C++.

The installer will check that you have enough disk space for the selected option. The maximum amount of space Visual Studio will require is 8 GB, although for Visual C++ you will use a lot less. When you check Desktop development with C++ item, you will see the right side of the dialog change to list the options selected and the disk size required, as follows:

For this book, leave the options selected by the installer and then click the Install button in the bottom right hand corner. The installer will download all the code it needs and it will keep you updated with the progress with the following dialog box:

When the installation is complete the Visual Studio Community 2017 item will change to have two buttons, Modify and Launch, as showing here:

The Modify button allows you to add more components. Click on Launch to run Visual Studio for the first time.

Registering with Microsoft

The first time you run Visual Studio it will ask you to sign in to Microsoft services through the following dialog:

You do not have to register Visual Studio, but if you choose not to, Visual Studio will only work for 30 days. Registering with Microsoft places no obligations on you. If you are happy to register, then you may as well register now. Click on Sign in button provide your Microsoft credentials, or if you do not have an account then click on Sign up to create an account.

Note

When you click on the Launch button a new window will open, but the installer window will remain open. You may find that the installer window hides the Welcome window, so check the Windows task bar to see if another window is open. Once Visual Studio has started you can close the installer window.

You will now be able to use Visual Studio to edit code, and will have the Visual C++ compiler and libraries installed on your machine, so you will be able to compile C++ code in Visual Studio or on the command line.

 

Examining C++ projects


C++ projects can contain thousands of files, and managing these files can be a task. When you build the project, should a file be compiled, and if so, by which tool? In what order should the files be compiled? What output will these compilers produce? How should the compiled files be combined to produce the executable?

Compiler tools will also have a large collection of options, as diverse as debug information, types of optimization, support for different language features, and processor features. Different combinations of compiler options will be used in different circumstances (for example, release builds and debug builds). If you compile from a command line, you have to make sure you choose the right options and apply them consistently across all the source code you compile.

Managing files and compiler options can get very complicated. This is why, for production code, you should use a make tool. Two are installed with Visual Studio: MSBuild and nmake. When you build a Visual C++ project in the Visual Studio environment, MSBuild will be used and the compilation rules will be stored in an XML file. You can also call MSBuild on the command line, passing it the XML project file. The nmake tool is Microsoft's version of the program maintenance utility common across many compilers. In this chapter, you will learn how to write a simple makefile to use with the nmake utility.

Before going through the basics of project management, first we have to examine the files that you will commonly find in a C++ project, and what a compiler will do to those files.

Compilers

C++ is a high-level language, designed to give you a wealth of language facilities and to be readable for you and other developers. The computer's processor executes low-level code, and it is the purpose of the compiler to translate C++ to the processor's machine code. A single compiler may be able to target several types of processor, and if the code is standard C++, it can be compiled with other compilers that support other processors.

However, the compiler does much more than this. As explained in Chapter 4, Working With Memory, Arrays, and Pointers, C++ allows you to split your code into functions, which take parameters and return a value, so the compiler sets up the memory used to pass this data. In addition, functions can declare variables that will only be used within that function (Chapter 5, Using Functions, will give more details), and will only exist while the function is executed. The compiler sets up this memory, called a stack frame. You have compiler options about how stack frames are created; for example, the Microsoft compiler options /Gd, /Gr, and /Gz determine the order in which function arguments are pushed onto the stack and whether the caller function or called function removes the arguments from the stack at the end of the call. These options are important when you write code that will be shared (but for the purpose of this book, the default stack construction should be used). This is just one area, but it should impress upon you that compiler settings give you access to a lot of power and flexibility.

The compiler compiles C++ code, and it will issue a compiler error if it comes across an error in your code. This is syntax checking of your code. It is important to point out that the code you write can be perfect C++ code from a syntax point of view, but it can still be nonsense. The syntax checking of the compiler is an important check of your code, but you should always use other checking. For example, the following code declares an integer variable and assigns it a value:

    int i = 1 / 0;

The compiler will issue an error C2124 : divide or mod by zero. However, the following code will perform the same action using an additional variable, which is logically the same, but the compiler will issue no error:

    int j = 0; 
    int i = 1 / j;

When the compiler issues an error it will stop compiling. This means two things. Firstly, you get no compiled output, so the error will not find its way into an executable. Secondly, it means that, if there are other errors in the source code, you will only find out about it once you have fixed the current error and recompiled. If you want to perform a syntax check and leave compilation to a later time, use the /Zs switch.

The compiler will also generate warning messages. A warning means that the code will compile, but there is, potentially, a problem in the code that will affect how the executable will run. The Microsoft compiler defines four levels of warnings: level 1 is the most severe (and should be addressed) and level 4 is informational.

Warnings are often used to indicate that the language feature being compiled is available, but it needs a specific compiler option that the developer has not used. During development of code, you will often ignore warnings, since you may be testing language features. However, when you get closer to producing production code you should pay more attention to warnings. By default, the Microsoft compiler will display level 1 warnings, and you can use the /W option with a number to indicate the levels that you wish to see (for example, /W2 means you wish to see level 2 warnings as well as level 1 warnings). In production code, you may use the /Wx option, which tells the compiler to treat warnings as errors so that you must fix the issues to be able to compile the code. You can also use the pragmas compiler (pragmas will be explained later) and compiler options to suppress specific warnings.

Linking the code

A compiler will produce an output. For C++ code, this will be object code, but you may have other compiler outputs, such as compiled resource files. On their own, these files cannot be executed; not least because the operating system will require certain structures to be set up. A C++ project will always be two-stage: compile the code into one or more object files and then link the object files into an executable. This means that your C++ compiler will provide another tool, called a linker.

The linker also has options to determine how it will work and specify its outputs and inputs, and it will also issue errors and warnings. Like the compiler, the Microsoft linker has an option, /WX, to treat warnings as errors in release builds.

Source files

At the very basic level, a C++ project will contain just one file: the C++ source file, typically with the extension cpp or cxx.

A simple example

The simplest C++ program is shown here:

    #include <iostream> 

    // The entry point of the program 
    int main() 
    { 
        std::cout << "Hello, world!n"; 
    }

The first point to make is that the line starting with // is a comment. All the text until the end of the line is ignored by the compiler. If you want to have multiline comments, every line must start with //. You can also use C comments. A C comment starts with /* and ends with */ and everything between these two symbols is a comment, including line breaks.

C comments are a quick way to comment out a portion of your code.

The braces, {}, indicates a code block; in this case, the C++ code is for the function main. We know that this is a function because of the basic format: first, there is the type of the return value, then the name of the function with a pair of parentheses, which is used to declare the parameters passed to the function (and their types). In this example, the function is called main and the parentheses are empty, indicating that the function has no parameters. The identifier before the function name (int) says that the function will return an integer.

The convention with C++ is that a function called main is the entry point of the executable, that is, when you call the executable from the command line, this will be the first function in your code that will be called.

Note

This simple example function immediately immerses you into an aspect of C++ that irritates programmers of other languages: the language may have rules, but the rules don't always appear to be followed. In this case, the main function is declared to return an integer, but the code returns no value. The rule in C++ is that, if the function declares that it returns a value, then it must return a value. However, there is a single exception to this rule: if the main function does not return a value, then a value of 0 will be assumed. C++ contains many quirks such as this, but you will soon learn what they are and get used to them.

The main function has just one line of code; this is a single statement starting with std and ending with the semicolon (;). C++ is flexible about the use of whitespace (spaces, tabs, and newlines) as will be explained in the next chapter. However, it is important to note that you have to be careful with literal strings (as used here), and every statement is delimited with a semicolon. Forgetting a required semicolon is a common source of compiler errors. An extra semicolon is simply an empty statement, so for a novice, having too many semicolons can be less fatal to your code than having too few.

The single statement prints the string Hello, world! (and a newline) to the console. You know that this is a string because it is enclosed in double quote marks (″″). The string is put to the stream object std::cout using the operator <<. The std part of the name is a namespace, in effect, a collection of code with a similar purpose, or from a single vendor. In this case, std means that the cout stream object is part of the standard C++ library. The double colon :: is the scope resolution operator, and indicates that you want to access the cout object declared in the std namespace. You can define namespaces of your own, and in a large project you should define your own namespaces, since it will allow you to use names that may have been declared in other namespaces, and this syntax allows you to disambiguate the symbol.

The cout object is an instance of the ostream class and this has already been created for you before the main function is called. The << means that a function called operator << is called and is passed the string (which is an array of char characters). This function prints each character in the string to the console until it reaches a NUL characte. This is an example of the flexibility of C++, a feature called operator overloading. The << operator is usually used with integers, and is used too shift the bits in the integer a specified number of places to the left; x << y will return a value which has every bit in x shifted left by y places, in effect returning a value that has been multiplied by 2y. However, in the preceding code, in place of the integer x there is the stream object std::cout, and in place of the left shift index there is a string. Clearly, this does not make any sense in the C++ definition of the << operator. The C++ standard has effectively redefined what the << operator means when used with an ostream object on the left-hand side. Furthermore, the << operator in this code will print a string to the console, and so it takes a string on the right-hand side. The C++ Standard Library defines other << operators that allow other data types to be printed to the console. They are all called the same way; the compiler determines which function is compiled dependent upon the type of the parameter used. Earlier we said that the std::cout object had already been created as an instance of the ostream class, but gave no indication of how this has occurred. This leads us to the last part of the simple source file not already explained: the first line starting with #include. The # here effectively indicates that a message of some kind will be given to the compiler. There are various types of messages you can send (a few are #define, #ifdef, #pragma, which we will return to elsewhere in this book). In this case, #include tells the compiler to copy the contents of the specified file into the source file at this point, which essentially means the contents of that file will be compiled too. The specified file is called a header file, and is important in file management and the reuse of code through libraries.

The file <iostream> (note, no extension) is part of the Standard Library and can be found in the include directory provided with the C++ compiler. The angle brackets (<>) indicate that the compiler should look in the standard directories used to store header files, but you can provide the absolute location of a header file (or the location relative to the current file) using double quotes (″″). The C++ Standard Library uses the convention of not using file extensions. You should use the extension h (or hpp and, rarely, hxx) when naming your own header files. The C Runtime Library (which is also available to your C++ code) also uses the extension h for its header files.

Creating source files

Start by finding the Visual Studio 2017 folder on the Start Menu and click on the entry for Developer Command Prompt for VS2017. This will start a Windows command prompt and set up the environmental variables to use Visual C++ 2017. However, rather unhelpfully, it will also leave the command line in the Visual Studio folder under the Program Files folder. If you intend to do any development, you will want to move out of this folder to one where creating and deleting files will do no harm. Before you do that, move to the Visual C++ folder and list the files:

C:\Program Files\Microsoft Visual Studio\2017\Community>cd %VCToolsInstallDir%
C:\Program Files\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.0.10.2517>dir

Since the installer will place the C++ files in a folder that includes the current build of the compiler, it is safer to use the environment variable VCToolsInstallDir rather than specifying a specific version so that the latest version is used (in this case 14.0.10.2517). There are a few things to notice. First, the folders bin, include, and lib:

Folder

Description

bin

This contains, indirectly, the executables for Visual C++. The bin folder will contain separate folders for the CPU type you are using, so you will have to navigate below this to get to the actual folder containing the executables. The two main executables are cl.exe, which is the C++ compiler and link.exe, which is the linker.

include

This folder contains the header files for the C Runtime Library and the C++ Standard Library.

lib

This folder contains the static link library files for the C Runtime Library and the C++ Standard Library. Again, there will be separate folders for the CPU type

We will refer back to these folders later in this chapter.

The other thing to point out is the file vcvarsall.bat which is under the VC\Auxillary\Build folder. When you click on Developer Command Prompt for VS2017 on the Start menu, this batch file will be run. If you wish to use an existing command prompt to compile C++ code, you can set that up by running this batch file. The three most important actions of this batch file are to set up the PATH environment variable to contain a path to the bin folder, and to set up the INCLUDE and LIB environment variables to point to the include and lib folders, respectively.

Now navigate to the root directory and create a new folder, Beginning_C++, and move to that directory. Next, create a folder for this chapter called Chapter_01. Now you can switch to Visual Studio; if this is not already running, start it from the Start menu.

In Visual Studio, click the File menu, then New, and then the File... menu item to get the New File dialog, and in the left-hand tree-view, click on the Visual C++ option. In the middle panel you'll see two options: C++ File (.cpp) and Header File (.h), and C++ properties for Open folder, as shown in the following screenshot:

The first two file types are used for C++ projects, the third type creates a JSON file to aid Visual Studio IntelliSence (help as you type) and will not be used in this book. Click on the first of these and then click the Open button. This will create a new empty file called Source1.cpp, so save this to the chapter project folder as simple.cpp by clicking on the File menu, then Save Source1.cpp As, and, navigating to the project folder, change the name in the File name box to simple.cpp before clicking on the Save button.

Now you can type in the code for the simple program, shown as following:

    #include <iostream> 

    int main() 
    { 
        std::cout << "Hello, world!n"; 
    }

When you have finished typing this code, save the file by clicking on the File menu and then the Save simple.cpp option in the menu. You are now ready to compile the code.

Compiling the code

Go to the command prompt and type cl /? command. Since the PATH environment is set up to include the path to the bin folder, you will see the help pages for the compiler. You can scroll through these pages by pressing the Return key until you get back to the command prompt. Most of these options are beyond the scope of this book, but the following table shows some that we will talk about:

Compiler Switch

Description

/c

Compile only, do not link.

/D<symbol>

Defines the constant or macro <symbol>.

/EHsc

Enable C++ exception handling but indicate that exceptions from extern ″C″ functions (typically operating system functions) are not handled.

/Fe:<file>

Provide the name of the executable file to link to.

/Fo:<file>

Provide the name of the object file to compile to.

/I <folder>

Provide the name of a folder to use to search for include files.

/link<linker options>

Pass <linker options> to the linker. This must come after the source file name and any switches intended for the compiler.

/Tp <file>

Compile <file> as a C++ file, even if it does not have .cpp or .cxx for its file extension.

/U<symbol>

Remove the previously defined <symbol> macro or constant.

/Zi

Enable debugging information.

/Zs

Syntax only, do not compile or link.

Note that some options need spaces between the switch and option, some must not have a space, and for others, the space is optional. In general, if you have the name of a file or folder that contains a space, you should enclose the name in double quotes. Before you use a switch, it is best to consult the help files to find out how it uses spaces.

At the command line, type the cl simple.cpp command. You will find that the compiler will issue warnings C4530 and C4577. The reason is that the C++ Standard Library uses exceptions and you have not specified that the compiler should provide the necessary support code for exceptions. It is simple to overcome these warnings by using the /EHsc switch. At the command line, type the cl /EHsc simple.cpp command. If you typed in the code correctly it should compile:

C:\Beginning_C++\Chapter_01>cl /EHsc simple.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.25017 for x86
Copyright (C) Microsoft Corporation.  All rights reserved

simple.cpp

Microsoft (R) Incremental Linker Version 14.10.25017.0
Copyright (C) Microsoft Corporation.  All rights reserved.
/out:simple.exe

simple.obj

By default, the compiler will compile the file to an object file and then pass this file to the linker to link as a command-line executable with the same name as the C++ file, but with the .exe extension. The line that says /out:simple.exe is generated by the linker, and /out is a linker option.

List the contents of the folder. You will find three files: simple.cpp, the source file; simple.obj, the object file which is the output of the compiler; and simple.exe, the output of the linker after it has linked the object file with the appropriate runtime libraries. You may now run the executable by typing simple on the command line:

C:\Beginning_C++\Chapter_01>simple
Hello, World!

Passing parameters between the command-line and an executable

Earlier, you found that the main function returned a value and by default this value is zero. When your application finishes, you can return an error code back to the command line; this is so that you can use the executable in batch files and scripts, and use the value to control the flow within the script. Similarly, when you run an executable, you may pass parameters from the command line, which will affect how the executable will behave.

Run the simple application by typing the simple command on the command line. In Windows, the error code is obtained through the pseudo environment variable ERRORLEVEL, so obtain this value through the ECHO command:

C:\Beginning_C++\Chapter_01>simple
Hello, World!

C:\Beginning_C++\Chapter_01>ECHO %ERRORLEVEL%
0

To show that this value is returned by the application, change the main function to return a value other than 0 (in this case, 99, shown highlighted):

    int main() 
    { 
        std::cout << "Hello, world!n"; 
        return 99;
    }

Compile this code and run it, and then print out the error code as shown previously. You will find that the error code is now given as 99.

This is a very basic mechanism of communication: it only allows you to pass integer values, and the scripts that call your code must know what each value means. You are much more likely to pass parameters to an application, and these will be passed through your code via parameters to the main function. Replace the main function with the following:

        int main(int argc, char *argv[]) 
        { 
            std::cout << "there are " << argc << " parameters" <<  
            std::endl; 
            for (int i = 0; i < argc; ++i) 
            { 
                std::cout << argv[i] << std::endl; 
            } 
        }

When you write the main function to take parameters from the command line, the convention is that it has these two parameters.

The first parameter is conventionally called argc. It is an integer, and indicates how many parameters were passed to the application. This parameter is very important. The reason is that you are about to access memory through an array, and this parameter gives the limit of your access. If you access memory beyond this limit you will have problems: at best you will be accessing uninitialized memory, but at worst you could cause an access violation.

It is important that, whenever you access memory, you understand the amount of memory you are accessing and keep within its limits.

The second parameter is usually called argv and is an array of pointers to C strings in memory. You will learn more about arrays and pointers in Chapter 4, Working With Memory, Arrays, and Pointers, and about strings in Chapter 9, Using Strings, so we will not give a detailed discussion here. The square brackets ([]) indicate that the parameter is an array, and the type of each member of the array is given by the char *. The * means that each item is a pointer to memory. Normally, this would be interpreted as a pointer to a single item of the type given, but strings are different: the char * means that in the memory the pointer points to there will be zero or more characters followed by the NUL character (). The length of the string is the count of characters until the NUL character. The third line shown here prints to the console the number of strings passed to the application. In this example, rather than using the newline escape character (n) to add a newline, we use the stream std::endl. There are several manipulators you can use, which will be discussed in Chapter 6, Classes. The std::endl manipulator will put the newline character into the output stream, and then it will flush the stream. This line shows that C++ allows you to chain the use of the << put operator into a stream. The line also shows you that the << put operator is overloaded, that is, there are different versions of the operator for different parameter types (in this case, three: one that takes an integer, used for argv, one that takes a string parameter, and another that takes manipulator as a parameter), but the syntax for calling these operators is exactly the same.

Finally, there is a code block to print out every string in the argv array, reproduced here:

    for (int i = 0; i < argc; ++i) 
    { 
        std::cout << argv[i] << std::endl; 
    }

The for statement means that the code block will be called until the variable i is less than the value of argc, and after each successful iteration of the loop, the variable i is incremented (using the prefix increment operator ++). The items in the array are accessed through the square bracket syntax ([]). The value passed is an index into the array.

Notice that the variable i has a starting value of 0, so the first item accessed is argv[0], and since the for loop finishes when the variable i has a value of argc, it means that the last item in the array accessed is argv[argc-1]. This is a typical usage of arrays: the first index is zero and, if there are n items in the array, the last item has an index of n-1.

Compile and run this code as you have done before, with no parameters:

C:\Beginning_C++\Chapter_01>simple
there are 1 parameters
simple

Notice that, although you did not give a parameter, the program thinks there is one: the name of the program executable. In fact, this is not just the name, it is the command used to invoke the executable. In this case, you typed the simple command (without the extension) and got the value of the file simple as a parameter printed on the console. Try this again, but this time invoke the program with its full name, simple.exe. Now you will find the first parameter is simple.exe. Try calling the code with some actual parameters. Type the simple test parameters command in the command line:

C:\Beginning_C++\Chapter_01>simple test parameters
there are 3 parameters
simple
testparameters

This time the program says that there are three parameters, and it has delimited them using the space character. If you want to use a space within a single parameter, you should put the entire string in double quotes:

C:\Beginning_C++\Chapter_01>simple ″test parameters″
there are 2 parameters
simple
test parameters

Bear in mind that argv is an array of string pointers, so if you want to pass in a numeric type from the command line and you want to use it as a number in your program, you will have to convert from its string representation accessed through argv.

The preprocessor and symbols

The C++ compiler takes several steps to compile a source file. As the name suggests, the compiler preprocessor is at the beginning of this process. The preprocessor locates the header files and inserts them into the source file. It also substitutes macros and defined constants.

Defining constants

There are two main ways to define a constant via the preprocessor: through a compiler switch and in code. To see how this works, let's change the main function to print out the value of a constant; the two important lines are highlighted:

    #include <iostream>  
    #define NUMBER 4

    int main() 
    { 
        std::cout << NUMBER << std::endl;
    }

The line that starts with #define is an instruction to the preprocessor, and it says that, wherever in the text there is the exact symbol NUMBER, it should be replaced with 4. It is a text search and replace, but it will replace whole symbols only (so if there is a symbol in the file called NUMBER99 the NUMBER part will not be replaced). After the preprocessor has finished its work the compiler will see the following:

    int main() 
    { 
        std::cout << 4 << std::endl;
    }

Compile the original code and run it, and confirm that the program simply prints 4 to the console.

The text search and replace aspect of the preprocessor can cause some odd results, for example, change your main function to declare a variable called NUMBER, as follows:

    int main() 
    { 
        int NUMBER = 99;
        std::cout << NUMBER << std::endl; 
    }

Now compile the code. You will get an error from the compiler:

C:\Beginning_C++\Chapter_01>cl /EHhc simple.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.25017 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

simple.cpp
simple.cpp(7): error C2143: syntax error: missing ';' before 'constant'
simple.cpp(7): error C2106: '=': left operand must be l-value

This indicates that there is an error on line 7, which is the new line declaring the variable. However, because of the search and replace conducted by the preprocessor, what the compiler sees is a line that looks as follows:

    int 4 = 99;

This is not correct C++! In the code that you have typed, it is obvious what is causing the problem because you have a #define directive for the symbol within the same file. In practice, you will include several header files, and these may include files themselves, so the errant #define directive could be in one of many files. Equally, your constant symbols may have the same names as variables in header files included after your #define directive and may be replaced by the preprocessor.

Using #define as a way to define global constants is often not a good idea and there are much better ways in C++, as you'll see in Chapter 3, Exploring C++ Types.

If you have problems you think are coming from the preprocessor replacing symbols, you can investigate this by looking at the source file passed to the compiler after the preprocessor has done its work. To do this, compile with the /EP switch. This will suppress actual compilation and send the output of the preprocessor to stdout (the command line). Be aware that this could produce a lot of text, so it is usually better to direct this output to a file and examine that file with the Visual Studio editor.

Another way to provide the values used by the preprocessor is to pass them via a compiler switch. Edit the code and remove the line starting with #define. Compile this code as normal (cl /EHsc simple.cpp), run it, and confirm that the number printed on the console is 99, the value assigned to the variable. Now compile the code again with the following line:

cl /EHsc simple.cpp /DNUMBER=4

Note that there is no space between the /D switch and the name of the symbol. This tells the preprocessor to replace every NUMBER symbol with the text 4 and this results in the same errors as above, indicating that the preprocessor is attempting to replace the symbol with the value provided.

Tools such as Visual C++ and nmake projects will have a mechanism to define symbols through the C++ compiler. The /D switch is used to define just one symbol, and if you want to define others they will have their own /D switch.

You are now wondering why C++ has such an odd facility that appears only to cause confusing errors. Defining symbols can be very powerful once you understand what the preprocessor is doing.

Using macros

One useful feature of preprocessor symbols is macros. A macro has parameters and the preprocessor will ensure that the search and replace will replace a symbol in the macro with the symbol used as a parameter to the macro.

Edit the main function to look as follows:

    #include <iostream> 

    #define MESSAGE(c, v)  
    for(int i = 1; i < c; ++i) std::cout << v[i] << std::endl; 

    int main(int argc, char *argv[]) 
    { 
        MESSAGE(argc, argv); 
        std::cout << "invoked with " << argv[0] << std::endl; 
    }

The main function calls a macro called MESSAGE and passes the command line parameters to it. The function then prints the first command line parameters (the invocation command) to the console. MESSAGE is not a function, it is a macro, which means that the preprocessor will replace every occurrence of MESSAGE with two parameters with the text defined previously, replacing the c parameter with whatever is passed as the first parameter of the macro, and replacing v with whatever is used as the second parameter. After the preprocessor has finished processing the file, main will look as follows:

    int main(int argc, char *argv[]) 
    { 
        for(int i = 1; i < argc; ++i)  
            std::cout << argv[i] << std::endl; 
        std::cout << "invoked with " << argv[0] << std::endl; 
    }

Note that, in the macro definition, the backslash () is used as a line-continuation character, so you can have multiline macros. Compile and run this code with one or more parameters, and confirm that MESSAGE prints out the command-line parameters.

Using symbols

You can define a symbol without a value and the preprocessor can be told to test for whether a symbol is defined or not. The most obvious situation for this is compiling different code for debug builds than for release builds.

Edit the code to add the lines highlighted here:

    #ifdef DEBUG
    #define MESSAGE(c, v)  
    for(int i = 1; i < c; ++i) std::cout << v[i] << std::endl; 
    #else
    #define MESSAGE
    #endif

The first line tells the preprocessor to look for the DEBUG symbol. If this symbol is defined (regardless of its value), then the first definition of the MESSAGE macro will be used. If the symbol is not defined (a release build) then the MESSAGE symbol is defined, but it does nothing: essentially, occurrences of MESSAGE with two parameters will be removed from the code.

Compile this code and run the program with one or more parameters. For example:

C:\Beginning_C++\Chapter_01>simple test parameters
invoked with simple

This shows that the code has been compiled without DEBUG defined so MESSAGE is defined to do nothing. Now compile this code again, but this time with the /DDEBUG switch to define the DEBUG symbol. Run the program again and you will see that the command-line parameters are printed on the console:

C:\Beginning_C++\Chapter_01>simple test parameters
testparameters
invoked with simple

This code has used a macro, but you can use conditional compilation with symbols anywhere in your C++ code. Symbols used in this way allow you to write flexible code and choose the code to be compiled through a symbol defined on the compiler command line. Furthermore, the compiler will define some symbols itself, for example, __DATE__ will have the current date, __TIME__ will have the current time, and __FILE__ will have the current file name.

Note

Microsoft, and other compiler producers, defines a long list of symbols that you can access, and you are advised to look these up in the manual. A few that you may find useful are as follows: __cplusplus will be defined for C++ source files (but not for C files) so you can identify code that needs a C++ compiler; _DEBUG is set for debug builds (note the preceding underscore), and _MSC_VER has the current version of the Visual C++ compiler, so you can use the same source for various versions of the compiler.

Using pragmas

Associated with symbols and conditional compilation is the compiler directive, #pragma once. Pragmas are directives specific to the compiler, and different compilers will support different pragmas. Visual C++ defines the #pragma once to solve the problem that occurs when you have multiple header files each including similar header files. The problem is that it may result in the same items being defined more than once and the compiler will flag this as an error. There are two ways to do this, and the <iostream> header file that you include next uses both of these techniques. You can find this file in the Visual C++ include folder. At the top of the file you will find the following:

    // ostream standard header 
    #pragma once 
    #ifndef _IOSTREAM_ 
    #define _IOSTREAM_

At the bottom, you will find the following line:

    #endif /* _IOSTREAM_ */

First the conditional compilation: the first time this header is included, the symbol _IOSTREAM_ will not be defined, so the symbol is defined and then the rest of the file will be included until the #endif line.

This illustrates good practice when using conditional compilation. For every #ifndef, there must be a #endif, and often there may be hundreds of lines between them. It is a good idea, when you use #ifdef or #ifundef, to provide a comment with the corresponding #else and #endif, indicating the symbol it refers to.

If the file is included again then the symbol _IOSTREAM_ will be defined, so the code between the #ifndef and #endif will be ignored. However, it is important to point out that, even if the symbol is defined, the header file will still be loaded and processed because the instructions about what to do are contained within the file.

The #pragma once performs the same action as the conditional compilation, but it gets around the problem of using a symbol that could be duplicated. If you add this single line to the top of your header file, you are instructing the preprocessor to load and process this file once. The preprocessor maintains a list of the files that it has processed, and if a subsequent header tries to load a file that has already been processed, that file will not be loaded and will not be processed. This reduces the time it takes for the project to be preprocessed. Before you close the <iostream> file, look at the number of lines in the file. For <iostream> version v6.50:0009 there are 55 lines. This is a small file, but it includes <istream> (1,157 lines), which includes <ostream> (1,036 lines), which includes <ios> (374 lines), which includes <xlocnum> (1,630 lines), and so on. The result of preprocessing could mean many tens of thousands of lines will be included into your source file even for a program that has one line of code!

Dependencies

A C++ project will produce an executable or library, and this will be built by the linker from object files. The executable or library is dependent upon these object files. An object file will be compiled from a C++ source file (and potentially one or more header files). The object file is dependent upon these C++ source and header files. Understanding dependencies is important because it helps you understand the order to compile the files in your project, and it allows you to make your project builds quicker by only compiling those files that have changed.

Libraries

When you include a file within your source file, the code within that header file will be accessible to your code. Your include file may contain whole function or class definitions (these will be covered in later chapters), but this will result in the problem mentioned previously: multiple definitions of a function or class. Instead, you can declare a class or function prototype, which indicates how calling code will call the function without actually defining it. Clearly, the code will have to be defined elsewhere, and this could be a source file or a library, but the compiler will be happy because it only sees one definition.

A library is code that has already been defined; it has been fully debugged and tested, and therefore, users should not need to have access to the source code. The C++ Standard Library is mostly shared via header files, which helps you when you debug your code, but you must resist any temptation to edit these files. Other libraries will be provided as compiled libraries.

There are essentially two types of compiled libraries: static libraries and dynamic link libraries. If you use a static library, then the compiler will copy the compiled code that you use from the static library and place it in your executable. If you use a dynamic link (or shared) library, then the linker will add information used during runtime (it may be when the executable is loaded, or it may even be delayed until the function is called) to load the shared library into memory and access the function.

Note

Windows uses the extension lib for static libraries and dll for dynamic link libraries. GNU gcc uses the extension a for static libraries and so for shared libraries.

If you use library code in a static or dynamic link library, the compiler will need to know that you are calling a function correctly-to make sure your code calls a function with the correct number of parameters and correct types. This is the purpose of a function prototype: it gives the compiler the information it needs to know about calling the function without providing the actual body of the function, the function definition.

This book will not go into the details of how to write libraries, since it is compiler-specific; nor will it go into the details of calling library code, since different operating systems have different ways of sharing code. In general, the C++ Standard Library will be included in your code through the standard header files. The C Runtime Library (which provides some code for the C++ Standard Library) will be static-linked, but if the compiler provides a dynamic-linked version you will have a compiler option to use this.

Pre-compiled headers

When you include a file into your source file, the preprocessor will include the contents of that file (after taking into account any conditional compilation directives) and, recursively, any files included by that file. As illustrated previously, this could result in thousands of lines of code. As you develop your code, you will often compile the project so that you can test the code. Every time you compile your code, the code defined in the header files will also be compiled even though the code in library header files will not have changed. With a large project, this can make the compilation take a long time.

To get around this problem, compilers often offer an option to precompile headers that will not change. Creating and using precompiled headers is compiler-specific. For example, with the GNU C++ compiler, gcc, you compile a header as if it is a C++ source file (with the /x switch), and the compiler creates a file with an extension of gch. When gcc compiles source files that use the header it will search for the gch file, and if it finds the precompiled header, it will use that; otherwise, it will use the header file. In Visual C++ the process is a bit more complicated because you have to specifically tell the compiler to look for a precompiled header when it compiles a source file. The convention in Visual C++ projects is to have a source file called stdafx.cpp, which has a single line that includes the file stdafx.h. You put all your stable header-file includes in stdafx.h. Next, you create a precompiled header by compiling stdafx.cpp using the /Yc compiler option to specify that stdafx.h contains the stable headers to compile. This will create a pch file (typically, Visual C++ will name it after your project) containing the code compiled up to the point of the inclusion of the stdafx.h header file. Your other source files must include the stdafx.h header file as the first header file, but they may also include other files. When you compile your source files, you use the /Yu switch to specify the stable header file (stdafx.h), and the compiler will use the precompiled header pch file instead of the header.

When you examine large projects, you will often find precompiled headers are used; as you can see, it alters the file structure of the project. The example later in this chapter will show how to create and use precompiled headers.

Project structure

It is important to organize your code into modules to enable you to maintain it effectively. Chapter 7, Introduction to Object-Orientated Programming, explains object orientation, which is one way to organize and reuse code. However, even if you are writing C-like procedural code (that is, your code involves calls to functions in a linear way) you will also benefit from organizing it into modules. For example, you may have functions that manipulate strings and other functions that access files, so you may decide to put the definition of the string functions in one source file, string.cpp, and the definition of the file functions in another file, file.cpp. So that other modules in the project can use these files, you must declare the prototypes of the functions in a header file and include that header in the module that uses the functions.

There is no absolute rule in the language about the relationship between the header files and the source files that contain the definition of the functions. You may have a header file called string.h for the functions in string.cpp; and a header file called file.h for the functions in file.cpp. Or you may have just one file called utilities.h that contains the declarations for all the functions in both files. The only rule that you have to abide by is that, at compile time, the compiler must have access to a declaration of the function in the current source file, either through a header file, or the function definition itself. The compiler will not look forward in a source file, so if function A calls another function, B, in the same source file then function B must have already been defined before function A calls it, or there must be a prototype declaration. This leads to a typical convention of having a header file associated with each source file that contains the prototypes of the functions in the source file, and the source file includes this header. This convention becomes more important when you write classes.

Managing dependencies

When a project is built with a building tool, checks are performed to see if the output of the build exists and if not, the appropriate actions to build it are performed. The common terminology is that the output of a build step is called a target and the inputs of the build step (for example, source files) are the dependencies of that target. Each target's dependencies are the files used to make them. The dependencies may themselves be a target of a build action and have their own dependencies.

For example, the following diagram shows the dependencies in a project:

In this project, there are three source files (main.cpp, file1.cpp, and file2.cpp). Each of these includes the same header, utils.h, which is precompiled (hence there is a fourth source file, utils.cpp, that only contains utils.h). All of the source files depend on utils.pch, which in turn depends upon utils.h. The source file main.cpp has the main function and calls functions in the other two source files (file1.cpp and file2.cpp), and accesses the functions through the associated header files, file1.h and file2.h.

On the first compilation, the build tool will see that the executable depends on the four object files, and so it will look for the rule to build each one. In the case of the three C++ source files, this means compiling the cpp files, but since utils.obj is used to support the precompiled header, the build rule will be different to the other files. When the build tool has made these object files, it will then link them together along with any library code (not shown here).

Subsequently, if you change file2.cpp and build the project, the build tool will see that only file2.cpp has changed, and since only file2.obj depends on file2.cpp, all the make tool needs to do is compile file2.cpp and then link the new file2.obj with the existing object files to create the executable. If you change the header file, file2.h, the build tool will see that two files depend on this header file, file2.cpp and main.cpp, and so the build tool will compile these two source files and link the new two object files file2.obj and main.obj with the existing object files to form the executable. If, however, the precompiled header source file, util.h, changes, it means that all of the source files will have to be compiled.

For a small project, dependencies are easy to manage, and as you have seen, for a single source file project you do not even have to worry about calling the linker, because the compiler will do that automatically. As a C++ project gets bigger, managing dependencies gets more complex, and this is where development environments such as Visual C++ become vital.

Makefiles

If you are supporting a C++ project, you are likely to come across a makefile. This is a text file containing the targets, dependencies, and rules for building the targets in the project. The makefile is invoked through the make tool, nmake on Windows and make on Unix-like platforms.

A makefile is a series of rules that look as follows:

    targets : dependents 
        commands 

The targets are one or more files that depend on the dependents (which may be several files), so that if one or more of the dependents is newer than one or more of the targets (and hence has changed since the targets were last built), then the targets need to be built again, which is done by running the commands. There may be more than one command, and each one is on a separate line prefixed with a tab character. A target may have no dependents, in which case the commands will always be called.

For example, using the preceding example, the rule for the executable, test.exe will be as follows:

    test.exe : main.obj file1.obj file2.obj utils.obj 
        link /out:test.exe main.obj file1.obj file2.obj utils.obj

Since the main.obj object file depends on the source file main.cpp, the headers File1.h and File2.h, and the precompiled header utils.pch, the rule for this file will be as follows:

    main.obj : main.cpp file1.h file2.h utils.pch 
        cl /c /Ehsc main.cpp /Yuutils.h

The compiler is called with the /c switch, which indicates that the code is compiled to an object file, but the compiler should not invoke the linker. The compiler is told to use the precompiled header file utils.pch through the header file utils.h with the /Yu switch. The rules for the other two source files will be similar.

The rule to make the precompiled header file is as follows:

    utils.pch : utils.cpp utils.h 
        cl /c /EHsc utils.cpp /Ycutils.h

The /Yc switch tells the compiler to create the precompiled header using the header file, utils.h.

Makefiles are often much more complicated than this. They will contain macros, which group targets, dependents, or command switches. They will contain general rules for target types rather than the specific rules shown here, and they will have conditional tests. If you need to support or write a makefile, then you should look up all of the options in the manual for the tool.

 

Writing a simple project


This project will illustrate the features of C++ and projects that you have learned in this chapter. The project will use several source files so that you can see the effect of dependencies and how the build tool will manage changes to the source files. The project is simple: it will ask you to type your first name, and then it will print your name and the time and date to the command line.

The project structure

The project uses three functions: the main function, which calls two functions print_name and print_time. These are in three separate source files and, since the main function will call the other two functions in other source files, this means the main source file will have to have prototypes of those functions. In this example, that means a header for each of those files. The project will also use a precompiled header, which means a source file and a header file. In total, this means three headers and four source files will be used.

Creating the precompiled header

The code will use the C++ Standard Library to input and output via streams, so it will use the <iostream> header. The code will use the C++ string type to handle input, so it will use the <string> header. Finally, it accesses the C runtime time and date functions, so the code will use the <ctime> header. These are all standard headers that will not change while you develop the project, so they are good candidates for precompiling.

In Visual Studio create a C++ header file and add the following lines:

    #include <iostream> 
    #include <string> 
    #include <ctime>

Save the file as utils.h.

Now create a C++ source file and add a single line to include the header file you just created:

    #include ″utils.h″

Save this as utils.cpp. You will need to create a makefile for the project, so in the New File dialog, select Text File as your file type. Add the following rules for building the precompiled header:

    utils.pch utils.obj :: utils.cpp utils.h 
        cl /EHsc /c utils.cpp /Ycutils.h

Save this file as makefile. with the appended period. Since you created this file as a text file, Visual Studio will normally automatically give it an extension of txt, but since we want no extension, you need to add the period to indicate no extension. The first line says that the two files, utils.pch and utils.obj, depend on the source file and header file being specified. The second line (prefixed with a tab) tells the compiler to compile the C++ file, not to call the linker, and it tells the compiler to save the precompiled code included into utils.h. The command will create utils.pch and utils.obj, the two targets specified.

When the make utility sees that there are two targets, the default action (when a single colon is used between targets and dependencies) is to call the command once for each target (there are macros that you can use to determine which target is being built). This would mean that the same compiler command would be called twice. We do not want this behavior, because both targets are created with a single call to the command. The double colon, ::, is a work around: it tells nmake not to use the behavior of calling the command for each target. The result is that, when the make utility has called the command once, to make utils.pch, it then tries to make utils.obj but sees that it has already been made, and so realizes that it does not need to call the command again.

Now test this out. At the command line, in the folder that contains your project, type nmake.

If you do not give the name of a makefile, the program maintenance tool will automatically use a file called makefile (if you want to use a makefile with another name, use the /f switch to provide the name):

C:\Beginning_C++\Chapter_01\Code>nmake
Microsoft (R) Program Maintenance Utility Version 14.00.24210.0
Copyright (C) Microsoft Corporation.  All rights reserved.

cl /EHsc /c utils.cpp /Ycutils.h
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24210 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

utils.cpp

Do a directory listing to confirm that utils.pch and utils.obj have been made.

Creating the main file

Now create a C++ source file and add the following code:

    #include "utils.h" 
    #include "name.h" 
    #include "time.h" 

    void main() 
    { 
        print_name(); 
        print_time(); 
    }

Save this file as main.cpp.

The first include file is the precompiled header for the Standard Library headers. The other two files provide function prototype declarations for the two functions that are called in the main function.

You now need to add a rule for the main file to the makefile. Add the following highlighted line to the top of the file:

    main.obj : main.cpp name.h time.h utils.pch
        cl /EHsc /c main.cpp /Yuutils.h

    utils.pch utils.obj :: utils.cpp utils.h 
        cl /EHsc /c utils.cpp /Ycutils.h

This new line says that the main.obj target depends on two header files: a source file and the precompiled header file, utils.pch. At this point, the main.cpp file will not compile, because the header files do not exist yet. So that we can test the makefile, create two C++ header files; in the first header file, add the function prototype:

    void print_name();

Save this file as name.h. In the second header file, add the function prototype:

    void print_time();

Save this file as time.h. You can now run the make utility, which will compile only the main.cpp file. Test this out: delete all of the target files by typing del main.obj utils.obj utils.pch on the command line and then run the make utility again. This time, you'll see that the make utility compiles utils.cpp first and then compiles main.cpp. The reason for this order is because the first target is main.obj, but since this depends on utils.pch, the make tool moves to the next rule and uses this to make the precompiled header, before returning to the rule to create main.obj.

Note that you have not defined print_name nor print_time, yet the compiler does not complain. The reason is that the compiler is only creating object files, and it is the responsibility of the linker to resolve the links to the functions. The function prototypes in the header files satisfy the compiler that the function will be defined in another object file.

Using input and output streams

So far, we have seen how to output data to the console via the cout object. The Standard Library also provides a cin stream object to allow you to input values from the command line.

Create a C++ source file and add the following code:

    #include "utils.h" 
    #include "name.h" 

    void print_name() 
    { 
        std::cout << "Your first name? "; 
        std::string name; 
        std::cin >> name; 
        std::cout << name; 
    }

Save this file as name.cpp.

The first include file is the precompiled header, which will include the two Standard Library headers <iostream> and <string>, so you can use types declared in those files. The first line of the function prints the string Your first name? on the console. Note that there is a space after the query, so the cursor will remain on the same line, ready for the input. The next line declares a C++ string object variable. Strings are zero or more characters, and each character will take up memory. The string class does all the work of allocating and freeing the memory that will be used by the string. This class will be described in more detail in Chapter 8, Using the Standard Library Containers. The cin overloads the >> operator to get input from the console. When you press the Enter key the >> operator will return the characters you typed into the name variable (treating the space character as a delimiter). The function then prints out the contents of the name variable to the console without a newline.

Now add a rule for this source file to the makefile; add the following lines to the top of the file:

    name.obj : name.cpp name.h utils.pch 
        cl /EHsc /c name.cpp /Yuutils.h

Save this file and run the make tool to confirm that it will make the name.obj target.

Using time functions

The final source file will obtain the time and print this on the console. Create a C++ source file and add the following lines:

    #include "utils.h" 
    #include "time.h" 

    void print_time() 
    { 
        std::time_t now = std::time(nullptr); 
        std::cout << ", the time and date are " 
                  << std::ctime(&now) << std::endl; 
    }

The two functions, std::time and std::gmtime, are C functions, and std::time_t is a C type; all are available through the C++ Standard Library. The std::time function obtains the time as the number of seconds since midnight on January 1, 1970. The function returns a value of type std::time_t, which is a 64-bit integer. The function can optionally copy this value to another variable if you pass a pointer to where, in memory, the variable is stored. In this example, we do not need this facility, so we pass the C++ nullptr to the function to indicate that a copy should not be performed. Next, we need to convert the number of seconds to a string that has the time and date in a format you can understand. This is the purpose of the std::ctime function, which takes as a parameter a pointer to the variable that holds the number of seconds. The now variable has the number of seconds, and the & operator is used to obtain the address of this variable in memory. Memory and pointers are covered in more detail in Chapter 4, Working With Memory, Arrays, and Pointers. This function returns a string, but you have not allocated any memory for this string, nor should you attempt to free the memory used by this string. The std::ctime function creates a statically allocated memory buffer, which will be used by all the code running on the current execution thread. Every time you call the std::ctime function on the same thread of execution, the memory location used will be the same, although the contents of the memory may change.

This function illustrates how important it is to check the manual to see who has responsibility for allocating and freeing memory. Chapter 4, Working With Memory, Arrays, and Pointers, goes into more detail about memory allocation.

The string returned from std::ctime is printed to the console using several calls to the put << operator to format the output.

Now add a build rule to the makefile. Add the following to the top of the file:

    time.obj : time.cpp time.h utils.pch 
        cl /EHsc /c time.cpp /Yuutils.h

Save this file and run the make tool, and confirm that it builds the time.obj target.

Building the executable

You now have all the object files needed for your project, so the next task is to link them together. To do this, add the following line to the top of the makefile:

    time_test.exe : main.obj name.obj time.obj utils.obj 
        link /out:[email protected] $**

The target here is the executable, and the dependents are the four object files. The command to build the executable calls the link tool and uses a special syntax. The [email protected] symbol is interpreted by the make tool as use the target, and so the /out switch will actually be /out:time_test.out. The $** symbol is interpreted by the make tool as use all the dependencies so that all the dependencies are linked.

Save this file and run the make utility. You should find that only the link tool will be called, and it will link together the object files to create the executable.

Finally, add a rule to clean the project. It is good practice to provide a mechanism to remove all of the files created by the compile process and leave the project clean, with only the source files. After the line to link the object files, add the following lines:

    time_test.exe : main.obj name.obj time.obj utils.obj 
        link /out:[email protected] $** 

    clean : @echo Cleaning the project... 
        del main.obj name.obj time.obj utils.obj utils.pch
        del time_test.exe

The target clean is a pseudo target: no file is actually made, and for this reason, there are no dependencies. This illustrates a feature of the make utility: if you call nmake with the name of a target, the utility will make just that target. If you do not specify a target then the utility will make the first target mentioned in the makefile, in this case time_test.exe.

The clean pseudo target has three commands. The first command prints Cleaning the project... to the console. The @ symbol here tells the make utility to run the command without printing the command to the console. The second and third commands call the command-line tool del to delete the files. Clean the project now by typing nmake clean on the command line, and confirm that the directory has just the header files, source files, and the makefile.

Testing the code

Run the make utility again so that the executable is built. On the command line, run the example by typing the time_test command. You will be asked to type your first name; do this, and press the Enter key. You should find that your name, the time, and date are printed on the console:

C:\Beginning_C++\Chapter_01>time_test
Your first name? Richard
Richard, the time and date are Tue Sep  6 19:32:23 2016

 

Changing the project

Now that you have the basic project structure, with a makefile you can make changes to the files and be reassured that, when the project is rebuilt, only the files that have changed will be compiled. To illustrate this, change the print_name function in name.cpp to ask for your name in a more polite way. Change the first line in the function body as highlighted here:

    void print_name() 
    {
        std::cout << "Please type your first name and press [Enter] ";
        std::string name;

Save the file and then run the make utility. This time, only the name.cpp source file is compiled, and the resulting file, name.obj, is linked with the existing object files.

Now change the name.h header file and add a comment in the file:

    // More polite version
    void print_name();

Make the project. What do you find? This time, two source files are compiled, name.cpp and main.cpp, and they are linked with the existing object files to create the executable. To see why these two files are compiled, take a look at the dependency rules in the makefile. The only file that was changed was name.h, and this file is named in the dependency list of name.obj and main.obj, hence, these two files are rebuilt. Since these two files are in the dependency list of time_test.exe, the executable will be rebuilt, too.

 

Summary


This chapter was a gentle, but thorough, introduction to C++. You learned about the reasons to use the language and how to install the compiler from one vendor. You learned how C++ projects are structured, about source files and header files, and how code is shared through libraries. You also learned how to maintain projects using makefiles, and, through a simple example, you have had hands-on experience of editing and compiling code.

You have a compiler, an editor, and a tool to manage projects, so now you are ready to learn more details about C++, starting in the following chapter with C++ statements and controlling execution flow in an application.

About the Author

  • Richard Grimes

    Richard Grimes has been programming in C++ for 25 years, working on projects as diverse as scientific control and analysis and finance analysis to remote objects for the automotive manufacturing industry. He has spoken at 70 international conferences on Microsoft technologies (including C++ and C#) and has written 8 books, 150 articles for programming journals, and 5 training courses for Microsoft. Richard was awarded Microsoft MVP for 10 years (1998-2007). He has a reputation for his deep understanding of the .NET framework and C++ and the frank way in which he assesses new technology.

    Browse publications by this author

Latest Reviews

(12 reviews total)
I paid EUR 10 but never received either an ebook nor PDF nor hard copy version. I could argue that you people are fraudsters.
Alles erwartungsgemäß verlaufen
It's ok but it can be better, has some grammatical errors, not for a real beginner but somehow experienced on computer language programming

Recommended For You

Book Title
Access this book, plus 8,000 other titles for FREE
Access now