Programming in C++

User-Defined Types and Header Files / Graphics


A structure in C++ is similar to a record in Pascal, QBasic, and some other programming languages. A structure, like an array, is a collection of values but structures are different from arrays in that the values stored in an structure may be of different types, and a structure's stored values, called members, are individually named and typed, just like "ordinary" variables. In other words, a structure is a collection of related elements, possibly of different types, having a single referencing name. Each element (member) in a structure is called a field of the structure. All of the elements or data in a structure should be related to one object. Each element in a structure can be individually accessed by using the "dot" ( . ) operator as you will see below.

The form for declaring a structure type is:

   struct type-id
       type1 member1-name;
       type2 member2-name;
       type3 member3-name;
       typeN memberN-name;

NOTE: The semi-colon placed after the closing bracket is required. The proper placement of a structure type is at the beginning of your program in the global area before the main ( ) function.

The above structure would create a type, not a variable. To declare of variable of this type, simply declare it as follows:

   type-id var-id;

Suppose we want to store the following data for an item in an auto parts store:

   supplier #
   part #
   retail price
   wholesale price

We could create a structure type to handle this situation as follows:

   struct tPart
       int supplierNumber;
       int partNumber;
       float retailPrice;
       float wholesalePrice;

To declare a couple of variables of this type, simply do as follows:

   tPart aPart, anotherPart;

We can give values to each individual member as follows:

   aPart.supplierNumber = 223;
   aPart.partNumber = 21;
   aPart.retailPrice = 117.38;
   aPart.wholesalePrice = 54.34;

You are allowed, but not advised, to combine a structure specifier with the declaration of a variable of that structure type. For example,

       int supplierNumber;
       int partNumber;
       float retailPrice;
       float wholesalePrice;
   } somePart;

can be used instead of creating a structure type and then declaring a variable; in this case, somePart would be immediately associated with the structure type. Obviously, this is not advised because there would be no way to declare multiple variables of that particular structure type. You can also initialize structures at compile time just like you can arrays. For example, with the earlier tPart structure type, we could use:

   tPart somePart = { 223, 21, 117.38, 54.34 };

Unlike arrays, you can directly assign one structure variable the value of another structure that is of the same type. For example, if we had another variable declared tPart newPart, we could simply use the assignment operator to assign newPart the value of somePart:

   newPart = somePart;

You can also declare an array of structure types, and you are also allowed to give structure types arrays as individual members. For example:

   struct STUDENT
       int idNum;
       int testScores[10];
       int finalExam;

   STUDENT classStu[50];

In the above structure type, testScores[] is an array that holds ten values. classStu[] is an array that holds 50 STUDENT structure types. We can access and manipulate the contents of each element in the classStu[] array much like we do ordinary arrays. For example,

   classStu[5].testScores[1] = 88;

would give the second test score of the sixth element in classStu[] a value of 88.

Structure Example:
The following is a program that acts as a computerized ordering program for a auto part store (code was written with DEV C++ copiler):

#include <iostream.h>
#include <stdlib.h>
#include <iomanip.h>

struct tAutoPart
     int partNumber;
     int idNumber;
     double retailPrice;
     double wholesalePrice;

int main()
     tAutoPart partOrder[MAXORDERSIZE];

     cout << "Enter how many parts are to be ordered: ";
     cin >> MAXORDERSIZE;
     cout << endl;

     for (int k = 0; k < MAXORDERSIZE; k++)
           cout << "Enter the part number for order " << k+1 << ": ";
           cin >> partOrder[k].partNumber;
           cout << "Enter the identification number for order " << k+1 << ": ";
           cin >> partOrder[k].idNumber;
           cout << "Enter the retail price for order " << k+1 << ": ";
           cin >> partOrder[k].retailPrice;
           cout << "Enter the wholesale price for order " << k+1 << ": ";
           cin >> partOrder[k].wholesalePrice;
           cout << endl;

     cout << endl << endl;
     cout << "COMPLETE ORDER" << endl;
     cout << setfill('-') << setw(60) << "-" << setfill(' ') << endl;
     for (int j = 0; j < MAXORDERSIZE; j++)
           cout << "Order " << j+1 << " --> ";
           cout << "Part Number: " << partOrder[j].partNumber << endl;
           cout << setw(12) << " " << "ID Number: " << partOrder[j].idNumber << endl;
           cout << setw(12) << " " << "Retail Price: $" << partOrder[j].retailPrice << endl;
           cout << setw(12) << " " << "Wholesale Price: $" << partOrder[j].wholesalePrice << endl << endl;
           cout << setfill('-') << setw(60) << "-" << setfill(' ') << endl << endl;

     cout << endl << endl << endl;

     return 0;

We can now move on to our next topic: enumerated data types, which simply allow you to give descriptive names to integers. Read on for more...

Enumerated Data Types

Enum is derived from the integer type and therefore enumerated data types are integral in nature. Each integer value is given an identifier called an enumeration constant. In other words, it allows you to give names (labels) to some list of integers. The purpose is to assign names to integers, which in turn makes more readable code. There are two forms: the enumerated constant and the enumerated type. The form of a enumerated constant is as follows:

   enum { enumeration constants };

The form of a enumeration type is as follows:

   enum type_name { enumeration constants };
   type_name var_name;

The list of constants is usually given a number of 0, 1, 2, 3, etc. By default, C++ assigns a 0 to the first constant and then equates each enumerated constant to the next higher integral number, which means you can initialize a constant to whatever integer value you choose. For example,

   enum tWeekDays { sunday, monday, tuesday, wednesday, thursday, friday, saturday };

would assign sunday = 0, monday = 1, tuesday = 2, and so on. However, you could also do something like this:

   enum tFruits { lemon, orange = 5, grape, apple = 4, pear, peach};

This would assign lemon = 0, orange = 5, grape = 6, apple = 4, pear = 5, and peach = 6. Consider the output from the following section of code:

   tFruits fruit;
   fruit = orange;
   cout << fruit << endl;
   fruit = pear;
   cout << fruit << endl;
   cout << ++fruit << endl;

This would produce:


Enumerated data types are commonly used with switch statements. Since switch statements are also integral in nature, you could use the enumerated constant of a enum type as a switch "case" or "cases" in a switch statment. If you are not familiar with switch statements, [see Switch Statements (section 4)]. For example, consider the following switch statement:

   enum tWeekDays { sunday, monday, tuesday, wednesday, thursday, friday, saturday };

   tWeekDays dayOfWeek;

   switch (dayOfWeek)
       case sunday:
           cout << "Today is Sunday." << endl;
       case monday:
           cout << "Today is Monday." << endl;
       case tuesday:
           cout << "Today is Tuesday." << endl;
       case wednesday:
           cout << "Today is Wednesday." << endl;
       case thursday:
           cout << "Today is Thursday." << endl;
       case friday:
           cout << "Today is Friday." << endl;
       case saturday:
           cout << "Today is Saturday." << endl;

The next section is an introduction to graphics. Read on for more...

Intro To Graphics

Graphics is obviously a very important aspect of programming. In this tutorial, I will only brush on a few topics. It is important to note that everything I explain is geared toward Dev-C++'s Version 4.0 compiler. I've found from my experiences that compilers are particularly different on how they set up their graphics functions so if you are using a different compiler, don't be surprised if these functions don't work for you. To make the DEV compiler "recognize" the graphics function, you must first go to the options menu, choose "compiler options", and check the box which says "Add the following commands when calling compiler". Place the following in the input box:

   -lbgi -lgdi32

The bgi stands for Borland Graphics Interface and gdi stands for graphical device interface (32 bit). The -l preceding the two commands most likely stands for link which tells the compiler to link or load these functions during compilation of programs. Why are we using Borland Graphics Interface? Well, in order to let the DEV compiler incorporate graphics, a programmer a few years back created code based on Borland's graphical interface which would be compatitable with DEV's compiler. Very slick programmer.

The Borland Graphics Interface (BGI) library of functions use the following coordinate system:

   Top-Left of window - (0, 0)
   Top-Right of window - (maxX, 0)
   Bottom-Left of window - (0, maxY)
   Bottom-Right of window - (maxX, maxY)

In "normal" VGA mode, the maxX is 639 and the maxY is 479 which provides a (640 X 480) screen resolution. You must also add the following #include statements when using graphics in your programs:

   #include <iostream.h>
   #include <stdlib.h>
   #include <winbgim.h>

The following is code which can be used as a setup graphics program for you to open when you want to begin coding a new graphics program. The purpose of the code is to initialize the graphics functions and make sure no errors were encountered during initialization. Instead of having to type the code in everytime you want to create a graphics program, you can save this and keep it as a setup graphics program:


#include <iostream.h>
#include <stdlib.h>
#include <winbgim.h>

int main()
     int graphDriver, graphMode, errorCode;

     // initialize graphic functions
     graphDriver = DETECT; // detects highest resolution possible (640 x 480)
     initgraph(&graphDriver, &graphMode, "");
     errorCode = graphresult();
     if (errorCode != grOk)
           cout << "Error initializing graphics." << endl
                << grapherrormsg(errorCode) << endl;
           // begin creating graphics



Let's start to talk about certain BGI functions that you can use in your programs.

   getmaxx( ) and getmaxy( )

These functions return the maximum pixel coordinates in the horizontal and vertical directions, respectively. So, (getmaxx(), getmaxy()) refers to the lower right corner of the graphics window.

   getmaxcolor( )

This function returns the largest available color number. For example, consider the following segment of code which displays 3500 "random" pixels in the graphics window:

// initialize graphics first

int xMax = getmaxx();
int yMax = getmaxy();
int maxColor = getmaxcolor();
int pixel, x, y;


for (pixel = 0; pixel < 3500; pixel++)
    x = rand() % (xMax + 1);
    y = rand() % (yMax + 1);
    color = rand() % (maxColor + 1);


The function     setviewport()     allows you to limit the drawing that is done to a specified rectangular portion of the graphics window. For example, consider the following code:

   setviewport(50, 100, 350, 300, 1);
   rectangle(0, 0, 299, 199);
   line(25, 50, 425, 50);

This would draw a white rectangle around the viewport coordinates and a red line from (25,50) to (299,50) in viewport coordinates or from (75,150) to (350,150) in absolute coordinates. After a viewport is defined, all coordinates used will be relative to the viewport settings. The following describes the functions we used in the above code:

setviewport(x1,y1,x2,y2,1)     --> (x1,y1) = top left coordinate of viewport; (x2,y2) = bottom right coordinate of viewport; 1 = clipping is turned on (could also be 0 = clipping turned off)

setcolor(color)     --> where color is a word or number describing the color of choice. I'm not sure but I believe there are 16 possible colors.

rectangle(x1,y1,x2,y2)     --> (x1,y1) = top left coordinate of rectangle; (x2,y2) = bottom right coordinate of rectangle;

line(x1,y1,x2,y2)     --> (x1,y1) = starting coordinate of line; (x2,y2) = ending coordinate of line;

In order to clear all or part of the graphics window (display), you can use the following functions:

cleardevice( )     --> clears the entire graphics window
clearviewport( )     --> clears the currently active viewport

The following may be used to draw lines:

line(x1, y1, x2, y2)     --> draws a line from (x1, y1) to (x2, y2)

moveto(x,y)     --> changes the current position to (x, y)

lineto(x,y)     --> draws a line in the current style and color from the current position to the given position (x,y) and also sets the current position to (x,y)

linerel(dX, dY)     --> draws a line from the current position (CP) to (CP + dX, CP + dY)

moverel(dX, dY)     --> changes the current position (CP) to (CP + dX, CP + dY)

Customizing Lines
By default, all lines are drawn in a solid line style, one pixel thick. You can change these settings by using the     setlinestyle( )     function, which has three parameters:

   1) int linestyle, which can have one of the following values [ SOLID_LINE (0), DOTTED_LINE (1), CENTER_LINE (2), DASHED_LINE (3), USERBIT_LINE (4) ] (there are more than 5 linestyles; check your compiler).

   2) unsigned upattern, which allows a 16-bit user defined pattern; this value will be ignored if linestyle is not equal to 4.

   3) int thickness, which can have one of the following values [ NORM_WIDTH (1) or THICK_WIDTH (3) ]

NOTE: When creating USERBIT_LINES, consider the following:

   if upattern = 0xAAAA, then every other pixel is "on"
   if upattern = 0xCCCC, then 2 on, 2 off, 2 on, 2 off, etc.

Remember that upattern must be created using a 16-bit pattern, which is basically evaulated at the binary level. 'A' in binary = 1010, 'C' in binary = 1100. NOTE: 1 means "turned on"; 0 means "turned off"

For example, to set the linestyle to a dotted line format with thick width, use the following:

   setlinestyle(DASHED_LINE, 0, THICK_WIDTH);

The rectangle( ) function accepts four parameters: left, top, right, bottom. These parameters specify the upper left and bottom right corners of the rectangle to be drawn.

   rectangle(0, 0, 100, 50);

The circle( ) function accepts three parameters: xCenter, yCenter, radius. This would draw a circle with the center point being (xCenter, yCenter) and radius of radius.

   circle(50, 50, 10);

The rectangle( ) and circle( ) functions draw only the perimeter (outline) of the specified objects which means the objects are not filled in with color. One way to fill in an object or any closed area is to use the following function:

   floodfill(x, y, borderColor)

The floodfill( ) function has three parameters: the point (x, y), called the seed point, can be any point that is located inside the region to be filled; the borderColor value specifies the color of the border of the region to be filled --> filling will stop when borderColor is reached. The floodfill( ) function will "leak" or "spill out" outside the region, perhaps filling the entire graphics window, if the (x, y) point selected is inside a non-closed region.

There are also two other "fill" functions, which draw and fill an object:

   fillellipse(xCenter, yCenter, xRadius, yRadius);

The fillellipse( ) function has four parameters: (xCenter, yCenter) specifies the center point of the ellipse; xRadius specifes the horizontal radius; yRadius specifies the vertical radius.

   fillpoly(numPoints, polyPoints);

The fillpoly( ) function and corresponding drawpoly( ) function has two parameters: polyPoints should be an array containing the points of the polygon (must pass the first point in again as the last point in the polygon); numPoints specifies how many actual points are in polyPoints. The following shows an example:

   int polyPoints[] = { 3, 5, 25, 33, 250, 76, 123, 123, 3, 5 };
   drawpoly(5, polyPoints);

Drawing ellipses involves using the following function:

   ellipse(x, y, startAngle, endAngle, xRadius, yRadius);

The ellipse( ) function has six paramters: the point (x, y) is the center point of the ellipse; using a startAngle and endAngle other than 0 and 360 allows you to draw a certain sector or portion of an ellipse; xRadius and yRadius are self-explanatory.

Specifying Colors
If you want to change or specify the color and/or pattern used for filling objects and regions, you can use the following function:

   setfillstyle(int pattern, int color);

NOTE: setcolor( ) will specify the color used for the border of objects and normal drawing functions; setfillstyle( ) specifies the interior of objects.

There are 13 different patterns defined for the pattern of filling, including:


The following section of code will draw rectangle with a border of white and interior of red:

setfillstyle(SOLID_FILL, RED);

Displaying Graphical Text
Text can be displayed on the graphics window using the following functions:

   outtextxy(x, y, string);

The outtext( ) function displays the given string using the font, direction, and the size set by the function     settextstyle( )    , which has three paramters; int font, int direction, and int size (1 - 10);

   settextstyle(font, direction, size);
   DEFAULT: settextstyle(0, 0, 1);

Direction may be one of the following: HORIZ_DIR (0) or VERT_DIR (1)

You can "stick" with the default, but if you do, your text will be practically unreadable. You should experiment with various settings to see which values you prefer.

By default, outtext( ) positions the text such that the upper left corner of the first character is in the current position. outtextxy( ) is similar to outtext( ) except the upper left corner of the fisrt character is in (x, y).

You can use     settextjustify()     to control the horizontal and vertical position of the text relative to the current position or (x, y). It requires two parameters: horizontal and vertical. Horizontal may be:

   LEFT_TEXT (0)

The horizontal parameter determines whether the text starts at the current position, is centered at the current position, or ends at the current position.

The vertical parameter may be:

   TOP_TEXT (2)

The vertical parameter determines whether the text is displayed above the current positoin, centered at the current position, or below the current position. The default values of text justification are LEFT_TEXT and TOP_TEXT.

This wraps up all I want to say concerning graphics. The next section shows how to create header files. Read on for more...

Creating User-Defined Header Files

It is important to note that I am using Dev-C++ V.4 as the compiler for all information depicted in this tutorial. Let me start this topic off by first explaining what a header file actually is. Everytime you write a program you have to specify certain #include statements at the beginning of your program. Why is this? Basically, the #include statement is actually a link to a header file containing pre-defined elements that you will need to use for successful execution of your program. Header files are created so you can use the code contained in them in multiple programs without having to write the code everytime you want to use it in a program. It is all pre-defined in a seperate header file so all you have to do is "include" it in your program.

Take the iostream.h header file for example. Load your compiler and open iostream.h . Peek at what it contains. Do you understand it? Can you figure out what some of it is doing? Probably not at this point, but you should be able to recognize certain function calls and declarations. What you see is actually code used to define the standard input/output streams. By using this header file in your programs, you have access to the input/output streams. Without this convenient header file, if you wanted to have access to the input/output streams, you would first have to define them. Very tedious and time consuming not to mention the extra effort of learning how to define them.

A user-defined header file is a header file that you, as the programmer, create. --> iostream.h and all other header files which are "included" using angle brackets are pre-defined and are provided for by the makers of the Dev-C++ compiler. For example, #include <time.h> and #include <winbgim.h> (notice the brackets). When you create a header file, which I describe below, and "include" it in your program, you will use quotes instead of brackets. For example, suppose guess.h is a header file that you created for a program. You can include this header file in your program by using: #include "guess.h".

For the most part, user-defined header files are used to define constants, user-defined types, and to list function prototypes to be used in a main file. NOTE: header files do not include function definitions --> they will go in a seperate "auxillary" source file that will have a (.cpp) extension. Also note that you shouldn't compile header files individually. There is no need to do this. The header files will be compiled when you compile the actual program which tries to link to it. Also, when you compile the actual program, the only elements in the header file which will be compiled are the elements in which the program will need to use.

The following explains what is required to create a header file using Dev-C++ V.4 compiler:

First, open a new document and clear it. Next, enter the following information (I describe the elements further below):

   #ifndef __NAMEOFHEADER_H
      #define __NAMEOFHEADER_H

   // place constants here

   // place user-defined types here

   // place function prototypes here


Notice the (#) symbol preceding commands in the above file. Anytime the (#) is used, it is associated with the compiler directories. It tells the compiler to do something as in turning on/off some compiler setting. It is not an executable statement. It only functions during compilation. NOTE: in the above code, NAMEOFHEADER is the name of the header file; this name can be anything you want it to be, but it should be descriptive and reflect what it actually functions to do.

The     #ifndef     command can be read as "if not defined" which means it checks to see if a given symbol is defined or not. If the symbol is not defined, compilation will move into the body of the header file and do necessary definitions. If the symbol is defined, the header fill will not need to perform definitions. The     #define     is only performed if     #ifndef     returns a value of true, which means the symbol is not defined.     #define     literally does what it reads: it defines the elements contained in the header file for use in the program which called upon it. Basically, these two commands, especially     #ifndef    , gaurd against re-declaration errors if the header file is included in the calling program more than once. In a single application, you may be using multiple source files, and it is possible that more than one of the source files may try to #include the same header file.

Generally speaking, when you create a header file that contains the constants and prototypes for a program, you will also want to create an auxillary file (will have a .cpp extension) that holds all of the function definitions to be used in the program. This means that the only function in the main program will be the main() function. The question may arise as to how the compiler will know how to link the auxillary file to the actual main program file. This can be done through the use of a project file. It is important to note that the main program file, which contains the main() function, and the auxillary file, which contains the function definitions, both have to include the user-defined header file and also any other compiler directives they use. When you include a user-defined header file, you have to use double quotes instead of using brackets. The brackets tell the compiler that it is dealing with pre-defined include files and the quotes tell the compiler it is dealing with a user-defined header file. For example, if I create a header file called myHeader.h, I can include it by using #include "myHeader.h".

Creating Project Files
In order for you to successfully link all of your source files (main file and auxillary file), you will have to create a project and place the files in it. Project files are used to "tie together" mulitple source files into a single executable application or program. Creating a project is extremely easy. The following explains how to create a project using Dev-C++ V. 4 compiler:

If the individual source files already exist, which means you have already created them, click the file menu and select new project. For dos level programs (probably what you want), at the next prompt select console application and make sure C++ project is selected. At the dialog box, enter a descriptive name for the project, which will be given an extension of .dev. It is important to note that none of the source files can have the same base name as the project file. The base name of project will be used as the name of the executable file that is created after successful compilation. Next goto the project menu and select add to project. At the open prompt, select all of the source files to be included in the program (NOTE: do not include the header file itself). Now, you should see an "outline" form of the project on the left side pane. Now, you need to go to the options menu and select Compiler Options. Select directories and place either an "x" or mark the "Add the directory below to be searched for include files..." and enter the full path to the user-defined header file that you created in the text box. This only needs to be done if you didn't place your user-defined header file in the default "include" directory under the Dev-C++ directories. Then, goto the execute menu and select rebuild all. Assuming there are no errors, the executable file should be created (.exe extension with the base project name).

If you have not created any project files, simple choose to create a "New Unit for Project..." instead of "Add unit to project...".

The following are two source files and one user-defined header file that you can use as an example of setting up a project. The program is written for Dev-C++ V.4 compiler.

/////////////////////////////////////////    user-defined header file    /////////////////////////////////////

#ifndef __STUGRADES_H
  #define __STUGRADES_H

// initialize constants
const int possiblePoints = 300;

// function prototypes
void getStudentInfo(char name[25], int& points);
char calculateGrade(int points);
void displayResults(char name[25], char grade);


\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\   end of user-defined header file    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

/////////////////////////////////////////        main source file        /////////////////////////////////////

// PURPOSE: Uses a user-defined header file to calculate a student's letter grade
//          based on three test scores. POSSIBLE POINTS = 300

// PROGRAMMER: Mike Ware

#include <iostream.h>
#include <stdlib.h>
#include <iomanip.h>
#include "STUGRADES.H"

int main()
     char stuName[25], grade;
     int earnedPoints = 0;

     getStudentInfo(stuName, earnedPoints);
     grade = calculateGrade(earnedPoints);
     displayResults(stuName, grade);

     return 0;

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\    end of main source file     \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

/////////////////////////////////////////     auxillary source file      /////////////////////////////////////

// auxilary file for main file --> contains the function definitions

#include <iostream.h>
#include <stdlib.h>
#include <iomanip.h>
#include "STUGRADES.H"

// get student name and total points earned for student
void getStudentInfo(char name[25], int& points)
     int test1, test2, test3;

     cout << "Enter the name of the student: ";
     cin.getline(name, 25);

     cout << "Enter the student's earned points for test 1: ";
     cin >> test1;
     cout << "Enter the student's earned points for test 2: ";
     cin >> test2;
     cout << "Enter the student's earned points for test 3: ";
     cin >> test3;

     points = test1 + test2 + test3;
}// end getStudentInfo()

// calculate the letter grade for student based on numberical earned points
char calculateGrade(int points)
     char letterGrade;
     int percentage = (points / float(possiblePoints)) * 100;

     if (percentage >= 90)
           letterGrade = 'A';
     else if (percentage >= 80)
           letterGrade = 'B';
     else if (percentage >= 70)
           letterGrade = 'C';
     else if (percentage >= 60)
           letterGrade = 'D';
           letterGrade = 'F';

     return letterGrade;
}// end calculateGrade()

// display student name and letter grade
void displayResults(char name[25], char grade)
     cout << endl << endl;
     cout << "STUDENT NAME: " << name << endl;
     cout << setw(45) << setfill('-') << "-" << setfill(' ') << endl;
     cout << setw(14) << "GRADE: " << grade << endl << endl;
}// end displayResults()

\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\  end of auxillary source file  \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

In the next section, you will be introduced to object oriented programming by using classes. Read on for more...

You might also like...



Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“There are only two kinds of languages: the ones people complain about and the ones nobody uses” - Bjarne Stroustrup