User Notes for C++

by Bob Gaebler

This is a work in progress. Much of the substance of the document is still TBD. The concept is to compile a list of simple usage notes and idiom examples to help an experienced programmer who has access to a reasonable C++ language reference manual, or an occasional C++ programmer who most of the time programs in another language, but occasionally comes back to it and wants a few reminders, to get a quick start or a quick picker-upper in some of the more essential idioms and elements of expression in the language.

Note: This document is prepared using CSS2 style elements, and is best viewed using the Mozilla Firefox browser. (Possibly the Netscape 6.x browser may also render it well.) If you don’t have the new Mozilla browser, you should.

Index


Introduction:

This User Notes document is a quick reference guide and cook book, designed to present the essentials and basic survival skills in the programming language C++, conveyed by a collection of simple examples.

Although it is preferable to program C++ in a platform-transportable way, and particularly making use of STL, this version of User Notes also includes notes on Visual C++ 6.0. Some of the examples pertain to survival skills in using the VC++ IDE to compile and manage C++ projects.

I shall try to keep the different kind of examples distinct.

It is assumed that the User is an experienced programmer, with several years of maturity in programming, and several programming languages under his or her belt, but lacking specific C++ experience. These notes are designed to help such a programming cognoscenti get a quick start in using the C++ language. The same can be said about the VC++ IDE.

Note: In these notes, special highlights are used to mark up sections of the text.

VC++
items marked thus flag sections of the notes that pertain to the Visual C++ 6.0 IDE.
tbd
items are marked thus in these notes as a working document to indicate sections that need further work or verification.
Workspace
items marked like this denote menu selections in the VC++ IDE.
int main(...)
Items marked this way are used to depict example elements from program code. short segments of code are
also marked this way
OK
Items marked this way designate a control on a dialog box.




C++ Programming Notes


Command Line Parameters

For console programs, the traditional definition of the C function main works fine for C++:

int main(int argc, char* argv[]) { for (int i=0;i<argc;++i) { <do something with parameter argv[i]> } ... }

However, for console programs with MFC support, it appears slightly different:

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { ... }

In C++ one can take advantage of the builtin string class member functions and operators to parse individual parameters, so it may be advantageous to copy the parameter list into a vector of strings:

#include <string> #include <vector> int main(int argc, char* arg[]) { vector<string> argv; for (int i=0;i<argc;++i) argv.push_back(string(arg[i])); for (int i=0;i<argc;++i) { <do something with parameter argv[i], such as:> string nextFileName = argv[i] + ".txt"; char sepChar = a[i][3]; int parenLoc = a[i].find_first_of("[,("); ... } ... }

The following command line args idiom illustrates two things: the recommended use of enum for defining symbolic numeric constants, and regularizing the treatment of optional parameters by appending dummy parameters to the end of the arg list:

#include <string> #include <vector> int main(int argc, char* arg[]) { vector<string> argv; enum { minArgs=2, maxArgs=4 }; for (int i=0;i<argc;++i) argv.push_back(string(arg[i])); // (supply trailing psuedo args for omitted optional flags) for (int j=argc; j<maxArgs; ++j) argv.push_back("-"); ... <no risk of argv[2] or argv[3] being undefined:> if ((argv[2]=="-v") || (argv[3]=="-v")) { // handle verbose setting ... } ... }

Standard Input and Output streams

Declare and use the standard input and output streams thus:

#include <iostream> // be sure to include these two using namespace std; // .. items for stream I/O int main(int argc, char* argv[]) { cout << "Hello world"; cout << ", this is the "; int n = 12; cout << n << "th time I've said this:" << endl; << " Remember to use 'endl' to end an output line!" << endl; ... cin >> n; int m,p; cin >> m >> p; cout << "we read " << n << m << p << endl; cout << "but wrote them out all jammed together" << endl; ... return 0; }

Environment Pointers

In addition to the command line parameters, a program can also obtain the environmental variables in effect at the time of invocation:

#include <iostream> using namespace std; int main(int argc, char* argv[], char* envp[]) { // note extra parameter cout << "--environment:" << endl; if (envp) { for (int i=0; envp[i]; ++i) { cout << "envp[" << i << "]: " << envp[i] << endl; } } ... }

Binary File I/O – byte stream

The ifstream and ofstream classes are useful for doing simple sequential file I/O. For binary I/O, the iterators istreambuf_iterator and ostreambuf_iterator are convenient iterators when accessing one byte at a time, and are more efficient than their counterparts, istream_iterator and ostream_iterator which also incur overhead from the code needed to process all the formatted stream insertion and extraction operators (“<<” and “>>”).

// read a file, write it out with pairs of bytes swapped. #include <iostream> #include <string> #include <fstream> #include <iterator> using namespace std; int main(int argc, char* argv[]) { if (argc < 3) { cout << endl << "** incorrect number of arguments **" << endl << endl; cout << argv[0] << " <infile> <outfile>" << endl << endl; } else { // declare input and output files, link to specified file names ifstream inputFile(argv[1],ios::binary); ofstream outputFile(argv[2],ios::binary); // associate iterators with the open files ostreambuf_iterator<char> os(outputFile); istreambuf_iterator<char> is(inputFile); istreambuf_iterator<char> iend; // dummy: always gives eof char c1,c2; while (is!=iend) { // get first character of pair c1 = *is++; if (is!=iend) { // get second character if it exists c2 = *is++; *os++ = c2; // write it to file first } *os++ = c1; // then write first (or only) character } } return 0; } // main

Binary File I/O – structures

The ifstream and ofstream classes can also be used for sequential file I/O between data structures in memory and binary files.

Here is an example of reading the header data structures of a .bmp image file. The .bmp file has at the beginning two data structures: to identify the file, and to describe the layout of the image and the representation employed.

Defining data structures for file I/O.

In defining our struct to represent binary information in a file, care must be taken to ensure that the compiler’s propensity to align different members of a struct on appropriate 2-byte, 4-byte, or 8-byte boundaries is overridden, or carefully controlled. Note also that in a .bmp file, numbers are stored least significant byte first (“little endian”). No consideration is given in the following code for converting or handling these headers on a “big endian” computer.

// .bmp file structures // NOTE: in a .bmp file, numbers are stored in "little endian" byte order #pragma pack(1) // prevent compiler injecting alignment slack bytes struct Bmp_file_header { // identification header at start of .bmp file unsigned char fileType[2]; // file type stamp: "BM" unsigned int size; // of entire file, in bytes unsigned short int reserved1; unsigned short int reserved2; unsigned int offset; // from start of file to image data, bytes }; // Bmp_file_header struct Bmp_info_header { // image layout descriptor unsigned int size; // of this info header, in bytes int width; // of image, in pixels int height; // of image, in pixels unsigned short int planes; // number of color planes unsigned short int bitsPerPixel; // most common are 4, 8, 24 unsigned int compression; // type of compression; 0=uncompr. unsigned int imageSize; // total image size, in bytes int xResolution; // pixels per meter int yResolution; // pixels per meter unsigned int nColors; // number of colors unsigned int importantColors; // number of important colors }; // Bmp_info_header // a color table follows the headers if bitsPerPixel <= 8 // color table is an array of: struct Bmp_pixel_color { // color table element, one per color unsigned int r : 8; // Red intensity (0..255) unsigned int g : 8; // Green intensity unsigned int b : 8; // Blue intensity unsigned int spare : 8; }; // Bmp_pixel_color

Reading binary data structures.

In order to read the .bmp file header, we first attach the file to a stream in binary mode, then check that it is successfully opened. The ifstream is defined as a stream of char. Therefore, the read(..) function will expect a char* address, so we cast the address of the header buffer struct accordingly. Premature end-of-file and other error conditions are detected with the stream’s fail() function.

#include <iostream> #include <fstream> #include "bmpdefs.h" using namespace std; int main(int argc, char* argv[]) { enum { minArgs=2 }; if (argc != minArgs) { cout << endl << "** wrong number of args: " << argc-1 << endl; cout << " " << argv[0] << " <filename>" << endl; exit(-1); } // Attach input file to stream in binary mode ifstream inputFile(argv[1],ios::binary); if (!inputFile.is_open()) { cout << endl << "** file not found: " << argv[1] << endl; exit(-1); } // Read the file header void* bp = new Bmp_file_header; inputFile.read(static_cast<char*>(bp),sizeof(bmp_header)); if (inputFile.fail()) { cout << "** read error... abandoning" << endl; exit(-1); } ...

Determining file length, streampos.

The template fstream provides a class, streampos, that can be used to represent a position within a file, either read or write. A file’s read and write positions may be queried and stored in a streampos, and may also be set from the value of a streampos. The implementation of streampos varies from compiler to compiler, and often is a struct, so we cannot count on, say, comparing a streampos itself directly to an expected file size.

However, the “-” operator is overloaded for streampos, so that two streampos values may be subtracted to yield a stream offset. This gives us one way to determine the actual size of the file: subtract the position of the beginning of the file from the position of the end of the file.

We can set the file position to any position in an input file with the .seekg(..) function. We query the position in the file with the .tellg() function. Note that we save the file’s current position before we proceed to alter the position in calculating the length.

... // Check for valid .bmp file format: // save current file read position streampos fCurrentPos = inputFile.tellg(); // determine actual file length. <Note beg and end predefined positions> long fLength = inputFile.seekg(0,ios::end).tellg() - inputFile.seekg(0,ios::beg).tellg(); // the "size" field in header must match the actual file size if ( !((fLength == bmp_header.size) && (bmp_header.fileType[0]=='B') && (bmp_header.fileType[1]=='M')) ) { cout << endl << "This does NOT look like a bmp file!" << endl; cout << " (actual file length is " << fLength << "):" << endl; exit(-1); } inputFile.seekg(fCurrentPos); // restore read position ... } // main

Block Guard or “definition-only” Class Idiom

The above example using streampos to preserve and restore the input file’s position lends an opportunity to illustrate the Block Guard Class idiom. A class’s constructor is called at the point of definition, and it’s destructor is called upon exit from the enclosing block. This structure gives a handy way of encapsulating actions that must be done and undone upon entry to and exit exit from sections of code. Examples include critical sections, semaphores, resource locks, and such.

Here, we can use this idiom to guard the scope of a block within which we are free to alter the input file’s read position, and exit from which will cause the automatic restoration of the original read position. Note that the class Bookmark has no member functions defined. It’s sole reason for existence is to have it’s constructor and destructor automatically called at block entry and block exit.

class Bookmark { // save/restore input position in file ifstream& inputFile; streampos currentPos; public: Bookmark(ifstream& _inputFile) : inputFile(_inputFile), currentPos(_inputFile.tellg()) {}; <save> ~Bookmark() {inputFile.seekg(currentPos);}; <restore> }; // Bookmark int main(int argc, char* argp[]) { ... <file position saved by current’s constructor> {Bookmark current(inputFile); // determine actual file length fileLength = inputFile.seekg(0,ios::end).tellg() - inputFile.seekg(0,ios::beg).tellg(); } <file position restored by current’s destructor upon block exit> ... }  


VC++ Notes


Getting Started – Workspaces and Projects: (VC++)

Each application build is contained within a project The project structure encompasses all the source, header, and resource files necessary to build the application. It may import source or header files from other shared projects in the same workspace.

The workspace serves as a container for user preferences and other operational (session) settings that a user likes to keep common among similar projects. Also, it is possible that several application builds may share classes, resources, or other objects in common. To support this sharing, workspaces are defined so as to permit the inclusion of multiple projects.

When a workspace contains multiple projects, one of them is designated the current project. All Build, Debug, and other commands pertain to the current project.

Remember, workspaces contain projects, not the other way around.

Getting Started – new Workspace: (VC++)

When creating a new workspace, the default action for VC++ is to create a new subdirectory to contain the workspace that you are creating. It will have the same name as the name you give the workspace. You have the opportunity to override this by modifying the proposed pathname of the Location before completing the creation.

Default: new subdirectory for new workspace

In the directory where you want the new workspace to be placed, create the workspace as follows:

In the Location field of the dialog, type in the name of the parent directory, or navigate to it. Fill in the Workspace name field with the name of your choice. The Location field will automatically be updated to append this name to the current directory path as you type. Click OK.

Option: new workspace in existing directory

Create the workspace in an existing directory as follows:

Fill in the Workspace name field with the name of your choice. The Location field will automatically be updated to append this name to the current directory path as you type. Override this by entering the full path name of, or navigating to, the directory in which the workspace is to be formed. Click OK.

Note: Avoid putting more than one workspace in any directory. Control files which manage the workspace and its properties are placed in the directory, and this may cause confusion of option settings between several workspaces. It may also lead to name conflicts that could confound the IDE.

Getting Started – new Project in an existing Workspace: (VC++)

A simple, bare-bones application skeleton is automatically generated by the New Project function. This gives the user a quick start on getting together the minimal code and headers to get started with minimal functionality. The dummy code will compile to a small, trivial application. All the user has to do is add the code to make it do something useful. The kind of application generated is selectable, anything from a simple hello world console (text only) application to a small fully-windows-based dialog.

New Project types:

When a New Project is created, the appropriate library paths, and compile and link options are automatically set up according to the type of project selected. For example, a Win32 Console Application - An application that supports MFC type of project has the libraries for MFC included in the project settings, and appropriate headers are #defined in the skeleton code.

A variety of application skeletons can be created:

Example Hello World Console Application:

As an example of how to create a project for your program, suppose you already have a VC++ Workspace created. Open this workspace. Then, starting from File menu, select:

Type the project name (this will become the name of the executable, and of the main .cpp file) in the indicated field, select Add to current workspace and then OK. On the next screen, select A "Hello World" application., then Finish, and then OK.

Other project types are created in similar fashion.

Getting Started – new Project and Workspace: (VC++)

It is possible to create a project and a workspace all with a single action. In this case, the project files and the workspace files all appear in the same directory, rather than the project being stored in a subdirectory of the workspace. This is handy for a quick start, particularly if you are only going to do a single program, or know that you will want to have different user settings for this one program. But generally, this approach is best avoided in favor of setting up (at least) one workspace to contain all the favored user settings, and creating all projects subsequently under that.

First, make sure that no workspace is open. To do this, start at the File menu:

(In this example, suppose a Win32 console application is being created.) Starting from the File menu, select:

Type the project name in the indicated field. Make sure the Location: field has the path name of the desired parent directory. The button Create new workspace should be selected. Select OK. On the next screen, select A "Hello World" application., then Finish, and then OK.

This puts both the workspace and the project in the same subdirectory, which is created with the same name as the project, and which is placed under the directory identified in the Location: field of the Projects tab.

Settings (VC++)

There are two main types of settings that a user may be concerned with, those which govern the building of the application code, and those which affect the IDE environment in which the user works.

Compile, Link, Libraries, Build Settings:

To change settings for compile options, link options, libraries and such, use:

Some useful options, and the tabs on which they appear are:

  • Microsoft Foundation Classes:
    • Not using MFC
    • Use MFC in Static Library.
    • Use MFC in Shared DLL.
  • Working directory:
  • Program arguments:
  • Category: General
    • Generate browse info
      This box must be checked so that browse information for each specific compiled file is generated. The check box on the Browse Info tab must also be checked in order to use the browse information.
    • Optimizations:
    • Warning level:.
    • Preprocessor definitions:
      Insert symbols that need to be defined for the preprocesor here.
    • Project Options:
      Insert any additional compiler switches needed here.
  • Category: Precompiled Headers
    • Not using precompiled headers
      This is often the best choice. All headers are included and compiled each time a code file that #includes it is compiled. (This eliminates the fatal error C1010: unexpected end of file while looking for precompiled header directive error message.)
    • Use precompiled header file (.pch) (the default setting)
      Specify the name of the file. The default filename that VC++ sets up when you create a new project is stdafx.h. If you have headers that are very seldom changed, and are commonly used throughout many source files, it might be advantageous to use precompiled headers. This way, the header is compiled once, and the compiler’s internal representation of that information is stored in a file, and simply reread when the #include is encountered, instead of recompiling the header. For large projects, this can save quite a bit of compile time.
  • Output file name:
  • Object/library modules:
    Name additional object files and library files needed here.
  • Generate debug info
  • Link incrementally
  • Project Options:
    Any additional link option switches may be added here.
When browse info is built along with the program, editing and browsing of the source files is greatly simplified because the user can follow hot links from any object in the source file to its definition.
  • Build browse info file
    This directs that a master browse file be created. This is necessary before any individual browse information files are usable for browsing.
  • Suppress startup banner.

IDE environment settings

To change settings for editor, debugger, workspace and such, use:

Particularly helpful options, and the tabs on which they appear are:

  • Reload last workspace at startup
  • Reload documents when opening workspace.
  • Tab size
  • Indent size
  • Insert spaces / Keep tabs.
  • Auto indent.
This governs the appearance of the various types of window displays presented to the user: Source Windows, Debugger Windows, Watch Windows, etc.

  • Category (type of window)
  • Font
  • Size.
  • Colors. Foreground and background colors are selectable for each of various types of text.

    • Text
    • Text Selection
    • Bookmark
    • Current Statement
    • Keyword
    • ...many others

Quick Start notes on MFC (VC++)

This section is a rough "quick notes" area for jotting down steps in getting a quick start with MFC.

Dialog application

This is for a simple application with dialog box, having buttons and controls and fields, but without any document associated with it.

Start in an open workspace and create a project

Enter the project name, select Add to current workspace and then OK. On the next screen, select Dialog based, then Next. Select check boxes as appropriate. Defaults About box, 3D Controls, and ActiveX Controls are usually sufficient. Perhaps add Context-sensitive Help. Click Next.

Just click Next on the next screen. The defaults, Yes, please, for source file comments, and As a shared DLL for MFC library usage are usually what you want. The final screen gives you a chance to rename the application and dialog class the wizard creates you you. The names are derived from the Project name. You may rename the source files for the dialog class as well. Click Finish.

Remember (Alt-F7) for the Project Settings, and set the Generate browse info box in C/C++ tab, and Build browse info file in the Browse Info.

Adding Controls to Dialog

When you first start up a new MFC project, VC++ puts you into the resource editor with a new, blank, dialog panel, populated with the default OK / Cancel / Help buttons. You then add additional controls to this panel to create the dialog box.

Placing Controls

Click first on the Controls toolbar, selecting the appropriate icon to identify what type of control is to be added to the dialog. The cursor changes to a cross-hair over the dialog panel. Click and drag to define the outline of the new control.

Positioning Controls

Controls may be resized and repositioned by selecting the control, then dragging the border of the control, or dragging one of the resize tabs in the centers and corners of the borders. The control may also be positioned coarsly by using the arrow keys.

Some degree of control over the positioning and sizing of controls is provided on the Layout menu. Multiple controls may be selected and then adjusted to have same alignment on a selected border, or same size, or various other properties in common. The most handy shortcuts are the ctrl-arrow keys, which adjusts the group of selected controls so that the borders indicated by the arrow key are all aligned with the most extreme border in the group.

Control Properties

Pressing alt-Enter brings up the Properties dialog, in which on may set various properties of the control, like Visible, Disabled, Tab stop, and the like.

Associating Variables with Controls

Some controls have variables associated with them. For example, the Edit Box control provides an input field in which one line of text may be entered. A string variable may be associated with it such that changes to the field in the dialog appear in the variable, and changes in the variable appear in the field.

To associate a variable, referred to as a “Member Variable”, with the control, secondary click on the control and select Class Wizard from the popup. In the Class Wizard, select the Member Variable tab. In the Control IDs panel, select the name of the control. (Name may be found by pulling up the Properties sheet.) Click the Add Variable button, and complete the name of the variable. This will become the C++ name of the variable.  


Continuing ... TBD...

Tbd tbd.