C++ - Putting Big Projects Back
in the Hands of a Few People.

By STEVE GOSCHNICK

 

[This is the unedited version of a magazine article which appeared as one of three article in: Your Computer mazagine, Aug'91, pp74-81, Australia. It is a tutorial style article aimed at introducing the lay user to the C++ language.]

I have read somewhere that in days gone by at Microsoft, a rule-of-thumb in limiting the number of programmers on any given software development project, was "no more than can share a pizza." While the sentiment here might sound like one of enhancing team spirit (it couldn't be health), the real driving force behind such a rule is much more fundamental to human physiology and the current nature of the task we call computer programming.

The process of programming in so called 3rd Generation Languages (3GL's eg. PASCAL, C, FORTRAN, PL1, etc.), requires programmers to hold much of the program detail in their heads while they are advancing or modifying the code. When they come back to it after a weekend off, it requires reloading the details back into the grey matter, often taking an hour or two just to get up to speed. This process typically goes on for months or even years. With a large complex project, for example the development of a database management system (DBMS), it is tempting to throw a large team of programmers at the task, to hasten its completion. But this is usually a backward step and often leads to both delayed software releases and significant reliability problems.

Just look at the DBASE IV 1.0 saga. When first released, it had a list of bugs as long as your arm. When the stable and clean version DBASE IV 1.1 replaced it, a full three years had transpired since the release of DBASE III Plus. Until version III Plus of DBASE, the original developer, Wayne Ratliff, was in charge. Then he left the company's employ. Such problems are far from limited to DBASE, it is chosen simple as a well known example of a problematic birth. Hands-up the number of people that used the first Version of VENTURA and had it inexplicably hang the computer, from time to time? And how many MAC'ites have suffered the "System Bomb" icon? Or Amiga'ites the "Guru Meditation No.XXX" message? Large teams can exacerbate a situation rather than improve and hasten it. It very often comes down to the left half of the brain not knowing what the right half of someone else's brain, is doing.

While programming teams of no more than 3 or 4 people are desirable, the complexity and scale of most modern applications has placed great pressure to increase team sizes. The initial answer was to organise and foster programming group inter-communication, with tools and naming conventions (eg. Where a programmer has to apply to a co-ordinator to have his/her variable name or subroutine name approved). Another angle was to attempt to modify the attitudes of the programmers, encouraging them to communicate better amongst themselves. With such management solutions applied with rigour, larger teams have been known to increase productivity, rather than decrease it. But even in the perfect management scenario, it very soon becomes a case of diminishing returns. Large teams using C, and the timely release of high quality software, were becoming more and more mutually exclusive events, as the applications got bigger and bigger.

 

US vs. Australian Fix-it.

Looking at the US response and the Australian response to software quality and delays, by the IT powers that be in each country, is educational about the variance in our two societies:

The approach in the US has been to go right to the heart of the problem. They have developed Object Oriented Programming (OOP) languages that greatly alleviate the problems of complex programs seen with 3GL's. OOP puts large sophisticated projects back into the hands of one, two and three person teams. As such it pulls the rug out from under companies with a large-team mentality. The sort of productivity gains currently being attributed to OOP out here in contemporary developers land, is about 4 or 5 times greater than 3GL's (on a task for task basis)! Putting that into perspective of 'programmer team size' is even more spectacular. It means that projects previously carried out by 10 to 20 person teams, can be done with teams of 1 to 3! They recognised that in making such leaps and bounds in programming productivity, they then could not alienate existing programmers by trading off these benefits, against raw machine execution time. C++ is an OOP that makes .EXE files very near in execution speed to those from straight C. Don't be surprised if you start to see products more sophisticated than say, the ORACLE database system, coming out of very small, innovative, outfits that you hadn't previously heard of, using Object Oriented Programming.

In Australia, our guiding light to sunrise industries and the principal purchasing body, the government, is pushing for a bureaucratic solution: heavy lashings of the Australian Software Quality Management Standard AS-3563. Worship of this tome is, we are told, going to catapult us, as a country to becoming a software producing giant. Certain State and the Federal governments are now insisting on program certification to the Standard, before you can sell your product to them. Sounds very reasonable, yet I was told by a senior officer of DITEC recently, that software from big transnational software companies, won't need to be certified. They will be assumed fit. Yet some of the most spectacular bugs listings to date, have come from such sources! The hurdle is there then only for Australian companies it seems. We are leaders in Standards and hurdles. Software quality is of the utmost importance, but so are the languages and tools used. OOP languages and Class libraries, promote quality, from the inside.

What I hope to contribute to, through these articles on C++, even if only in a very small way, is the moving of Australian software developers to OOP. The old developers that won't be moved, will hopefully be replaced with new developers using OOP, maybe even as their first language. It is the developers and the tools they use, that will determine whether we make it or not, as a software producing power by the other end of this decade. Adhering to a Standard should not be the king-pin in a national strategy. Writing COBOL, FORTRAN and C programs that conform to AS-3563 is not the answer.

 

Who's Up to OOP and is it Paying?

So how does the buying public know who is using OOP and who is not? Many developers will start advertising the fact. But you will be able to pick the organisations who have taken their development wholly into Object oriented code, by the rapid rate at which they churn out upgrades. (Nb. The transition from 3GL to OOP is NOT rapid. That can be a long drawn out experience. The rapidity comes after they have moved all to OOP.) Borland for example, have recently released QUATTRO PRO 3, 6 months after version 2! Not surprisingly, they use the OOP languages they sell, inhouse. (Just as new versions can be too far apart, I think 6 months is too early. 9-12 months sounds ideal). And while I don't know for certain, I would lay bets on PC TOOLS is developed in an OOP, given the fast rate of their version upgrades.

 

OOP From a 3GL Programmers Point of View.

Before looking at the code listings, we should first discuss the general variance of OOP from the 3GL.

The different slant on programming taken by OOP can appear to be a subtle one at first, particularly to the experienced programmer. 3GL's like C and PASCAL, are based around the fundamental concept of: DATA + ALGORITHM = PROGRAM. These two aspects of a program are proudly kept as separate as possible in 3GL's. On the other hand, in OOP, you actively combine DATA and ALGORITHMs into sub-components called OBJECTS. This active binding of data plus procedure, we term ENCAPSULATION. Such objects contain both information and wherewithal, much like active objects in real life.

Therefore these Objects can be made to mimic the antics of real world entities, much more easily than with a 3GL. If you want to give some data to the object, to be stored in it, you pass it a MESSAGE containing that data. If you want to retrieve some data from the object, that you know it has stored or can calculate, again you pass it a message, asking for specific the details. If you have asked politely (ie. followed correct protocol), and you have the access rights, you will get what you want. Object intercommunications in OOP, simulates the communication that occurs between entities in the real world.

What the OOP programmer calls "passing a message," the 3GL programmer would argue that it is really just a "subroutine call". The implementation of OOP into pre-existing conventional languages such as C and PASCAL, invites such simplistic assessments. But don't be a hasty judge. OOP changes the emphasis from passing data to procedures, to passing messages to objects. And because OOP changes the very foundation of 3GL's (DATA+ALGORITHM=PROGRAM), the subtle changes in the syntax have a dramatic, positive effect on the art of programming.

 

Let's Fast-track to C++.

When writing about programming, magazine articles based around examples, make the most effective use of the limited slice of space available. If you want the complete "How To..." on C++ or another OOP, the book reins supreme. The two should be used to complement each other. So I am going to dive into an example or two. The first example is code for a CLASS of OBJECTS I have called 'Bevelled_box'. (When you see terms like CLASS and OBJECT appear for the first time in this article, I have put it in capitals. This indicates that the Glossary at the end of the article, explains the jargon's meaning.) The C++ compiler I am using, as recommended in my first C++ article, is Borland's Turbo C++.

 

The Examples.

A Bevelled_box object is a box drawn on the screen in one of the PC's graphic modes, with apparent bevelled edges, giving the box objects a 3-D appearance on the flat screen. We achieve this illusion by rendering each bevelled face with a different colour or shade. Such boxes can be used for the border to a window, a menu, a button, a game tile, etc. (See Fig. 1. The METAGON strategy game - the playing board is on a Bevelled_box.) A Bevelled_box then, is a highly reusable bit of code, which makes it a prime candidate to have a C++ class, defined for it. The reusability of code, is a large part of what OOP is on about.

We will then use this Bevelled_box class to develop our first application, CIRC_ART.CPP. Our application mainline is a mere 20 lines of code (see Listing. 3.) However, it does call on a couple of small functions (see Listing. 3 & 4), but they too are instructive in their own right. Nonetheless, our application is far from useless if you have kids. It produces some random artwork on the screen and frames it, with two bevelled boxes of course (see Fig. 2). It prompts the user for their name, and signs the artwork with it, always placing it in the right spot, regardless of video card, or font used. Judging by the reaction of my children, this program would go down well with Primary schools, particularly those with colour printers for hard-copy. (Although I routinely document my code with my copyright message, you are free to use the code in this article however you like - as long as you make some mention of its origin. Nb. I can also supply the source code and .EXE on a disk for a nominal handling charge of $12 - see details at the end of the article.)

 

Declaration and Definition.

In my next article on C++, we take this base class Bevelled_box and build upon it with an off-spring Class called Button. Button INHERITS all the features of Bevelled_box but adds screen text to the top of it. (The buttons in the top right corner of Fig.1, are an actual use of this Button Class, in a forthcoming commercial game.)

A class in C++ is typically developed in two separate files: the header file; and the implementation file. C programmers will be well used to this separation, but other 3GL programmers may not. C++ clearly separates the Declaration of functions, data structures and classes, from the Definition of them. The declarations are placed in a header file, ending in .HPP file extension. While the definitions of variables and objects are a part of the implementation file, ending in .CPP. Turbo PASCAL programmers also will be familiar with this separation. When programming a UNIT in Turbo PASCAL, a single file is separated into an interface part, and an implementation part.

The reason that both C and C++ has separate files, is to make it easy for a large program to be broken up into many separate chunks. These chunks called MODULES can then be worked upon and compiled separately to .OBJ files, which may later be linked to make one large program, producing a .EXE file. Simply by telling the compiler from within your mainline program, to include a specific header file, says to the compiler: "If you see references in my program, to these functions, structures, and classes, assume they exist elsewhere, to be linked in later. Trust me on this. For goodness sake don't give me a 'Can't find xxxx error message.' " If a compiler sees definition, it allocates a pigeon hole in memory for the code and gives it a name. If it sees declaration, it does no allocation, but simply assumes an allocation will be made elsewhere.

What this means, is that in C and C++ you can define functions and classes all over the place if you want to, and the mainline program can be anywhere within one of the files - although we still have conventions, but that is all they are. Contrast this with standard PASCAL where you must first define a function before you use it and the mainline must therefore always be at the bottom of the file. Borland first alleviated this straight-jacketed PASCAL approach in their Turbo PASCAL, with the Forward declaration command, and later with the program Unit concept. Therefore, giving a little of the flexibility of C and C++, concerning program modulation, to their particular incarnation of PASCAL.

 

LISTING 1 - the Bevelled_box Header File.

  1. // Listing 1.
  2. //-------------------------------------------------------------------
  3. // DOS FILE: BEV_BOX.HPP
  4. // CLASS of Bevelled Boxes. Header File.
  5. //
  6. // A boxes height and width may be defined (in pixels) when
  7. // an OBJECT of type Bevelled_box is defined. The starting
  8. // x and y coords are also set then. As is an aspect ratio
  9. // for the particular graphics card being used, iff you want
  10. // the METHOD's to allow for non-square screen pixels. Else pass 1.0.
  11. //
  12. // Language: C++, Turbo C++ V1 Date: Jan'91.
  13. // Author: Steve Goschnick.
  14. // Copyright: SOLID SOFTWARE.P.O.Box 218,Belgrave,VIC,3160.
  15. //-------------------------------------------------------------------
  16. #include <stdio.h>
  17. #include <alloc.h>
  18. #include <dos.h>
  19. //-------------------------------------------------------------------
  20. class Bevelled_box {
  21. protected:
  22. int box_startx;
  23. int box_starty;
  24. int box_width;
  25. int box_height;
  26. int bevel_width;
  27. float aspect_ratio; //Pixel aspect ratio.
  28. char top_colour;
  29. int box_fill;
  30. char north_colour,east_colour,south_colour,west_colour;
  31. void Draw_one_bevel(int *, int fill_colour, int fill_pattern);
  32. public:
  33. Bevelled_box (int startx, int starty, int width, int height,
  34. int bev_width, float aspect);
  35. ~Bevelled_box (){}
  36. void Set_colours(char top, char north, char west,
  37. char south, char east);
  38. void Display();
  39. void Set_fill(int fill);
  40. };

End of Listing 1

This file contains just one declaration, that of class Bevelled_box. The declarations starts with an open { bracket, in the C tradition, and closes with a } bracket. The semi-colon after } tells the compiler that "the foregoing code is an attempt at declaration, not implementation (in which case nothing would come after the close bracket) so please process it accordingly". Above this declaration is three #include statements referencing three standard C include files (remember C++ is a complete super-set of the C language, and as such uses the runtime libraries of C.) All lines that start with double slash character, are comment lines. Nb. That C and C++ are CASE SENSITIVE, meaning that the two variables, broom_sticks and Broom_sticks, are two different variables.

 

SCOPE and DATA HIDING.

Note that the class definition has two sub-sections: PROTECTED and PUBLIC. There is a third possible sub-section not used here called PRIVATE. Within each of these sub-sections, we can declare variables and/or functions (Nb. Functions declared within a class are called METHODS). Then we just have variables declared in the protected section, starting with the integer box_startx and finishing with west_colour. And also a METHOD (ie. like a function) called Draw_one_bevel.

Private, protected, and public have everything to do with the scope of variables and functions. Public means they are global, which means they can be seen and used by any code, both inside and outside the class. Clearly all the methods of an object that are used to communicate with the outside world, must be declared public. In our case we just have methods in the public section, no variables. We declared them in just the same way as we declare the prototypes of C functions. The function itself is declared to return a char, int, void, or whatever type value. And the number and type of individual parameters are also declared. The parameters also can be named here, but it is not necessary. It is wise to do so though, from a documentation point of view.

Those variables and Methods declared private can only be accessed by the methods inside the object. They are hidden from the outside world. We have no example of this, but protected is very similar.

Those variables and methods declared protected, can only be accessed by the methods within that class of objects, or by classes that INHERIT their attributes. The private and protected Clauses then, are the C++ mechanism for enacting the essential OOP feature called DATA HIDING.

In OOP in general and C++ in particular, we can generate offspring of a class, giving the new class additional features, or overriding specific features with new enhanced ones. This is termed INHERITANCE, and is a central feature of OOP. We do this later in the next article, with a class called Button, which is an offspring of Bevelled_box.

I won't go through the code in the listings blow-by-blow. I have attempted to name variables and methods as sensibly as possible to minimise the documentation necessary. But I will draw on certain bits of the code to emphasis points and explain what's going on. My policy on code documentation is long sensible variable names, with supporting documentation in a separate file of English, rather than broken sentences within Comment lines.

 

CONSTRUCTOR and DESTRUCTOR.

There are two methods in the Class declaration that look remarkable similar, and carry the name of the Class itself. They are Bevelled_box(...) and ~Bevelled_box(). We call the first the CONSTRUCTOR and the second, which must always start with the tilde character, the DESTRUCTOR. When you define an actual object in your application program, the CONSTRUCTOR is automatically executed. It is the ideal place to do variable initialisation (See Listing 3 - CIRC_ART.CPP, which defines two objects of type Bevelled_box, one is called frame and the other called background. Each of these definitions, initiate the Constructor. The parameters passed, go to the Constructor Method).

When your object goes out of scope, the Destructor is automatically executed. (ie. if you define a local object within a subroutine/function, at the point when execution leaves that function, the local variables are longer in scope - ie. they vanish.) As such the Destructor is the ideal place to do your tidying up. For example, if you have to delete previously secured dynamic memory, you do it in the Destructor. Our Bevelled_box Destructor doesn't need to do any tidying up, so it is empty.

Three other Methods are declared in the class. They are: Set_colours; Set_fill; and Display. These Methods along with the Constructor, are defined (implemented) in file BEV_BOX.CPP (See Listing 2.)

 

LISTING 2 - the Bevelled_box Implementation File.

  1. // Listing 2.
  2. //--------------------------------------------------------------------
  3. // DOS FILE: BEV_BOX.CPP
  4. // CLASS of Bevelled Boxes. Implementation File.
  5. //
  6. // This file contains definitions of the Bevelled_box Constructor,
  7. // as well as the following Bevelled_box METHODS:
  8. // Display();
  9. // Set_colours(char top,char north,char west,char south,char east);
  10. // Set_fill(int fill);
  11. // Draw_one_bevel(int *corners, int fill_colour, fill_pattern);
  12. // Language: C++, Turbo C++ V1 Date: Jan'91.
  13. // Author: Steve Goschnick.
  14. // Copyright: SOLID SOFTWARE,P.O.Box 218,Belgrave,VIC,3160.
  15. //--------------------------------------------------------------------
  16. #include "BEV_BOX.HPP"
  17. #include <graphics.h>
  18. //--------------------------------------------------------------------
  19. // The Constructor...
  20. Bevelled_box::Bevelled_box(int startx, int starty, int width,
  21. int height, int bev_width, float aspect)
  22. {
  23. // Initialise all object data.
  24. box_startx = startx;
  25. box_starty = starty;
  26. box_width = width;
  27. box_height = height;
  28. bevel_width = bev_width;
  29. aspect_ratio = aspect;
  30. // Now set default colours for each bevelled side and the top.
  31. // Method "Set_colours" must be called to alter these.
  32. top_colour = EGA_LIGHTGRAY;
  33. north_colour = EGA_WHITE;
  34. south_colour = EGA_DARKGRAY;
  35. east_colour = EGA_LIGHTCYAN;
  36. west_colour = EGA_LIGHTCYAN;
  37. box_fill = SOLID_FILL; //Turbo C++ has 13 patterns.
  38. }
  39. //--------------------------------------------------------------------
  40. // The following METHOD is used to reset the colours
  41. // before a box is displayed to the screen...
  42. void Bevelled_box::Set_colours(char top,char north,char west,
  43. char south,char east)
  44. {
  45. top_colour = top;
  46. north_colour = north;
  47. south_colour = south;
  48. east_colour = east;
  49. west_colour = west;
  50. }
  51. //--------------------------------------------------------------------
  52. void Bevelled_box::Set_fill(int fill)
  53. {
  54. box_fill = fill;
  55. }
  56. //--------------------------------------------------------------------
  57. // "Display" METHOD is called to display a "box" to the screen...
  58. void Bevelled_box::Display()
  59. {
  60. // Display the box on the screen, colouring the top of
  61. // the box and each bevel separately.
  62. int outer_NW_x = box_startx;
  63. int outer_NW_y = box_starty;
  64. int outer_NE_x = box_startx + box_width*aspect_ratio;
  65. int outer_NE_y = box_starty;
  66. int outer_SE_x = outer_NE_x;
  67. int outer_SE_y = box_starty + box_height - 1;
  68. int outer_SW_x = box_startx;
  69. int outer_SW_y = outer_SE_y;
  70. // Calculate the inner 4 corners (of the bevel) - nw is north-west...
  71. int inner_NW_x = outer_NW_x + bevel_width*aspect_ratio;
  72. int inner_NW_y = outer_NW_y + bevel_width;
  73. int inner_NE_x = outer_NE_x - bevel_width*aspect_ratio;
  74. int inner_NE_y = inner_NW_y;
  75. int inner_SE_x = inner_NE_x;
  76. int inner_SE_y = outer_SE_y - bevel_width;
  77. int inner_SW_x = inner_NW_x;
  78. int inner_SW_y = inner_SE_y;
  79. int poly_coords[8];
  80. // Colour the top of the box first...
  81. poly_coords[0] = inner_NW_x;
  82. poly_coords[1] = inner_NW_y;
  83. poly_coords[2] = inner_NE_x;
  84. poly_coords[3] = inner_NE_y;
  85. poly_coords[4] = inner_SE_x;
  86. poly_coords[5] = inner_SE_y;
  87. poly_coords[6] = inner_SW_x;
  88. poly_coords[7] = inner_SW_y;
  89. Draw_one_bevel(poly_coords, top_colour, box_fill);
  90. // Now colour the bevels of the outer 4 edges...
  91. // Do Top edge first.
  92. poly_coords[0] = outer_NW_x;
  93. poly_coords[1] = outer_NW_y;
  94. poly_coords[2] = outer_NE_x;
  95. poly_coords[3] = outer_NE_y;
  96. poly_coords[4] = inner_NE_x;
  97. poly_coords[5] = inner_NE_y;
  98. poly_coords[6] = inner_NW_x;
  99. poly_coords[7] = inner_NW_y;
  100. Draw_one_bevel(poly_coords, north_colour, SOLID_FILL);
  101. // East edge second.
  102. poly_coords[0] = outer_NE_x;
  103. poly_coords[1] = outer_NE_y;
  104. poly_coords[2] = outer_SE_x;
  105. poly_coords[3] = outer_SE_y;
  106. poly_coords[4] = inner_SE_x;
  107. poly_coords[5] = inner_SE_y;
  108. poly_coords[6] = inner_NE_x;
  109. poly_coords[7] = inner_NE_y;
  110. Draw_one_bevel(poly_coords, east_colour, SOLID_FILL);
  111. // South edge third.
  112. poly_coords[0] = outer_SE_x;
  113. poly_coords[1] = outer_SE_y;
  114. poly_coords[2] = outer_SW_x;
  115. poly_coords[3] = outer_SW_y;
  116. poly_coords[4] = inner_SW_x;
  117. poly_coords[5] = inner_SW_y;
  118. poly_coords[6] = inner_SE_x;
  119. poly_coords[7] = inner_SE_y;
  120. Draw_one_bevel(poly_coords, south_colour, SOLID_FILL);
  121. // West edge fourth.
  122. poly_coords[0] = outer_SW_x;
  123. poly_coords[1] = outer_SW_y;
  124. poly_coords[2] = outer_NW_x;
  125. poly_coords[3] = outer_NW_y;
  126. poly_coords[4] = inner_NW_x;
  127. poly_coords[5] = inner_NW_y;
  128. poly_coords[6] = inner_SW_x;
  129. poly_coords[7] = inner_SW_y;
  130. Draw_one_bevel(poly_coords, west_colour, SOLID_FILL);
  131. }
  132.  
  133. //--------------------------------------------------------------------
  134. // "Draw_one_bevel" calls upon
  135. // Turbo C++ graphics routines to fill a four cornered polygon
  136. // with a colour. All four functions referenced here are
  137. // from GRAPHICS.LIB ...
  138. void Bevelled_box::Draw_one_bevel(int *corners,int fill_colour,int fill)
  139. {
  140. setlinestyle(SOLID_LINE,0,NORM_WIDTH);
  141. setcolor(fill_colour);
  142. setfillstyle(fill, fill_colour);
  143. fillpoly(4, corners);
  144. return;
  145. }
  146. // End of Listing 2.

 

Notice that the first command in BEV_BOX.CPP is a pre-processor #include statement that drags in the definitions from Listing 1. The double quotes around it, tell the compiler to look in the local directory, as opposed to <...> on the next line that tells it to look in the system INCLUDE directory, for graphics.h. Now come the actual definitions of the Methods. All are prefixed with Bevelled_box:: to qualify them.

The first is the Constructor. It simply initialises the object's internal data, taking some values from parameters and setting others from default values. Constants like EGA_WHITE are standard Turbo C++ constants coming from the graphics.h include file.

Then follows the Set_colours Method. It is the Method you Message (ie. Call) to change the various bevel colours and that of the top of the box, from the default values. Nothing too difficult in here. Similarly the Set_fill Method is used to alter the fill-pattern from the default, when ever you choose.

Next is the Method called Display. It is responsible for displaying the actual Object on the screen. Using the first five internal data items of the object, namely the boxes starting (x,y) co-ordinate (in pixels), the boxes height and width, and the width of the bevelled edge, it determines the (x,y) co-ordinates of the corners of the inner square and the outer square that appear on screen. ie. 16 values in 8 pairs of (x,y). It combines these 16 values into one set of 4 pairs that is the top square, and 4 sets of 4 pairs that represents the 4 bevel faces. For each set of 4 points, it calls Draw_one_bevel with the appropriate colour and fill-pattern, which in turn draws to the screen using standard Borland graphics functions. Display is a bit long-winded, but I've done this for two reasons: I have given the variables long and meaningful names to provide self-documentation; and I couldn't think of a more elegant algorithm, which if I had, would just be more obtuse to the reader anyway. Lesson: OOP doesn't make algorithm design any easier that is not its real goal.

The BEV_BOX.CPP file can be compiled as is, in Turbo C++ (ALT+F9 keys), to produce a .OBJ file, which in-turn can be linked into any application that needs to use Class type Bevelled_box.

 

LISTING 3 - CIRC_ART Application Program.

  1. // Listing 3.
  2. //--------------------------------------------------------------------
  3. // DOS FILE: CIRC_ART.CPP
  4. // An example application that uses the "Bevelled_box" CLASS,
  5. // to create a picture frame, on the screen.
  6. // Language: C++, Turbo C++ V1 Date: May'91.
  7. // Author: Steve Goschnick.
  8. // Copyright: SOLID SOFTWARE.P.O.Box 218,Belgrave,VIC,3160.
  9. //--------------------------------------------------------------------
  10. #include "BEV_BOX.HPP"
  11. #include <graphics.h>
  12. #include <conio.h>
  13. #include <stdlib.h>
  14. #define ORIGIN_X0 0
  15. #define ORIGIN_Y0 0
  16. #define BORDER 50
  17. // Globals...
  18. float aspect_r;
  19. int screen_width, screen_height;
  20. // Function Prototypes...
  21. void open_graphics();
  22. void sign_it(int, char *);
  23. void paint_it();
  24. //--------------------------------------------------------------------
  25. // MAINLINE.
  26. // Calls these standard Turbo C++ functions: randomize,random,
  27. // setcolor,circle,setfillstyle,floodfill,closegraph.
  28. main()
  29. {
  30. char your_name[40];
  31. printf("Enter the Artists name:\n");
  32. gets(your_name);
  33. open_graphics();
  34. // Create two "Bevelled_box"s, 'background' and 'frame'.
  35. Bevelled_box frame(ORIGIN_X0,ORIGIN_Y0,screen_width,
  36. screen_height,12,1.0);
  37. frame.Set_colours(BROWN,LIGHTGRAY,LIGHTCYAN,DARKGRAY,LIGHTCYAN);
  38. frame.Set_fill(HATCH_FILL);
  39. frame.Display();
  40. Bevelled_box background(ORIGIN_X0+BORDER,ORIGIN_Y0+BORDER,
  41. screen_width-2*BORDER,
  42. screen_height-2*BORDER,6,1.0);
  43. background.Set_colours(LIGHTBLUE,DARKGRAY,LIGHTCYAN,LIGHTGRAY,LIGHTCYAN);
  44. background.Display();
  45. // Now for the art...
  46. paint_it();
  47. //Now sign it...
  48. sign_it(SMALL_FONT,your_name);
  49. char ch = getch(); // Wait for user to strike keyboard.
  50. closegraph();
  51. printf("With Compliments of:\n");
  52. printf("YOUR COMPUTER magazine & SOLID SOFTWARE.\n");
  53. } // END OF MAINLINE.
  54. //--------------------------------------------------------------------
  55. // Draws up to 100 randomly sized & coloured circles...
  56. // Calls these standard Turbo C++ functions: setcolor,circle
  57. // setfillstyle,floodfill,randomise,random.
  58. void paint_it() {
  59. int radius,cc,mid_x,mid_y,current_colour;
  60. randomize();
  61. for (cc=0; cc<100; cc++) {
  62. radius = random(90);
  63. mid_x = random(screen_width) - radius - BORDER - 20;
  64. if (mid_x < (BORDER+radius+20)) continue;
  65. mid_y = random(screen_height) - radius - BORDER - 20;
  66. if (mid_y < (BORDER+radius+20)) continue;
  67. current_colour = random(16);
  68. setcolor(current_colour);
  69. circle(mid_x,mid_y,radius);
  70. setfillstyle(SOLID_FILL,current_colour);
  71. floodfill(mid_x,mid_y,current_colour);
  72. }
  73. }
  74. //--------------------------------------------------------------------
  75. // Function that always puts a signature in the bottom, right corner,
  76. // regardless of FONT, or video card used.
  77. // Calls these standard Turbo C++ functions: setcolor,settextstyle,
  78. // settextjustify,moveto,textwidth,textheight,outtext.
  79. void sign_it(int sign_font, char * code_cutter)
  80. {
  81. settextstyle(sign_font,HORIZ_DIR,4);
  82. setcolor(BLACK);
  83. settextjustify(CENTER_TEXT,CENTER_TEXT);
  84. moveto(screen_width-BORDER-textwidth(code_cutter)/2-6,
  85. screen_height-BORDER-textheight(code_cutter)-6);
  86. outtext(code_cutter);
  87. }
  88. // End of Listing 3.

As mentioned earlier CIRC_ART is our first example application that uses Bevelled_box objects. While the program runs on all graphics cards, it is really geared for EGA or VGA colour. To use these objects we need only reference the include file BEV_BOX.HPP at the top of our program, then link in the pre-compiled BEV_BOX.OBJ using the PROJECT menu within Turbo C++.

I define a few constants and a three global variables at the top of the file. These 3 variables (aspect_r, screen_width, screen_height) are used by a function in Listing 4.

Next is the definition of the mainline routine. The first few lines get a prompt for a person name from the screen. Printf and gets are standard C routines. While C++ has a whole new approach to I/O I have not chosen to use it here. I am more interested in showing you how even a near standard C program can grab and use C++ Class libraries. Next I call a function open_graphics() which is a function in the FUNCS1.CPP file (See Listing 4. We cover this soon).

Then I define two object of Class Bevelled_box: frame and further down background. Note that in C++ you can define variables (and hence objects) at any point within the code, as needed. ie. You don't need to place all your variable definitions at the top of the block of code. The definitions of frame (as in picture-frame) and background (as in background to a painting) each include a string of parameters. Remember object definition automatically fires-up the Constructor, so these parameters are for the Constructor.

The line:

frame.Set_colours(BROWN,LIGHTGRAY,LIGHTCYAN,LIGHTGRAY,LIGHTCYAN);

is the C++ version of "passing a Message" to the object called frame. The Method referenced is Set_colours. C programmers will recognise the dot joiner, as the same one used to reference structure items in C. They might also think of an Object, as a Structure, but with built in procedures. Being a super-set of C, C++ also allows conventional structures (ie. PASCAL programmers, read "Record" for Structure.) However C++ structures can have procedures just like an Object! Such structures are then like an Object that has all its data and procedures declared as PUBLIC.

The fill-pattern for frame, is also altered from the default, by sending a message to frame with the constant HATCH_FILL (from Borland's graphics.h).

The message frame.Display() tells frame to display itself to the screen. Similar messages are sent to object background.

Next, a function called paint_it() is called. This function is defined further on down in the file, below mainline. It is straight C code. It calls on a few of Turbo C++'s standard routines to produce a random number of circles (up to 100 maximum), of a random radius up to 90 pixels, set to one of 16 colours randomly, and placed at a random position within the bevelled picture frame! On VGA and EGA machines, this onslaught of randomness can produce some ascetically pleasing computer generated art.

Then it calls on a little function called sign_it(), which is also defined further down the file. All five statements in this function are themselves calls to standard Turbo C++ graphics functions. It takes two parameter: sign_font which must be one of Borland's 5 screen fonts, in our case we pass it SMALL_FONT (artists are meant to have some degree of modesty when signing their name); and code_cutter which in our case is the name typed in at the keyboard by the programs user, earlier on.

The closegraph() function call puts you video card back into character mode, where DOS expects it to be.

 

Listing 4 - the miscellaneous Function File.

  1. // Listing 4.
  2. //--------------------------------------------------------------------
  3. // DOS FILE: FUNCS1.CPP
  4. // Miscellaneous functions used by applications CIRC_ART and CALCFACE.
  5. // Language: C++, Turbo C++ V1 Date: May'91.
  6. // Author: Steve Goschnick.
  7. // Copyright: SOLID SOFTWARE.P.O.Box 218,Belgrave,VIC,3160.
  8. //--------------------------------------------------------------------
  9. #include <stdio.h>
  10. #include <conio.h>
  11. #include <stdlib.h>
  12. #include <graphics.h>
  13. extern int screen_width, screen_height;
  14. extern float aspect_r;
  15. //--------------------------------------------------------------------
  16. // This is a Function that initialises Turbo C++ graphics-mode,
  17. // auto-detects the current video card, and set global variables
  18. // aspect_r, screen_width, screen_height to the appropriate settings.
  19. // Calls these standard Turbo C++ functions: initgraph,graphresult,
  20. // getmaxx,getmaxy.
  21. void open_graphics()
  22. {
  23. int gdriver = DETECT, imode, errorcode;
  24. // initialize graphics mode
  25. initgraph(&gdriver, &imode,"");
  26. errorcode = graphresult();
  27. if (errorcode != grOk) // an error occurred
  28. {
  29. printf("Graphics error: %s\n", grapherrormsg(errorcode));
  30. printf("Press any key to halt:");
  31. getch();
  32. exit(1); // return with error code
  33. }
  34. switch (gdriver) {
  35. VGA: aspect_r = 1.00; break;
  36. EGA: aspect_r = 1.37; break;
  37. MCGA: aspect_r = 2.40; break;
  38. CGA: aspect_r = 2.40; break;
  39. HERC: aspect_r = 1.43; break;
  40. default: aspect_r = 1.00; break;
  41. }
  42. screen_width = getmaxx();
  43. screen_height= getmaxy();
  44. }
  45. // End of Listing 4.

File FUNCS1.CPP (see Listing 4) was set up to hold miscellaneous functions. At this stage it only has one function called open_graphics(). This function is straight C code. It simply opens up the maximum graphics mode available on your video card and sets the three global variables: aspect_r (pixel aspect ratio), screen_width and screen_height to the appropriate values. On a VGA card 640X480X16 colours is the mode used, and on an EGA card 640X350X16 colours is the mode used. You can easily alter the code in open_graphics() to other graphics resolution modes supported by these cards. If you do, remember also to assign a different pixel aspect ratio. (While aspect_r is not used in CIRC_ART.CPP it is in the next example.)

Note the declaration of the three global variables near the top of the file, with an extern in front of them. This is standard C code. Normally when you define a variable it is declared and defined in the one hit. It is only when we wish to reference the variable across several files as we do here, that we need to separate declaration and definition, much like we do with functions, structures and classes. The extern keyword says to the compiler: "If you see references to this variable, its OK, I have defined it elsewhere. Don't make another pigeon hole in memory for it."

Why haven't I just placed this function in the same file with the mainline as I did with paint_it() and sign_it(...)? Simply because I am also going to call upon the open_graphics() function in my next application program, CALCFACE.CPP. To do so I will just need to link in FUNCS1.OBJ when I make CALCFACE.EXE.

Conclusion.

Our first example covers the basics in Class definition and use. It also should give you some appreciation of the Data Hiding and Encapsulation mechanisms of C++, two central concepts of OOP. The benefits of Encapsulation clearly become apparent, if you consider the Method Draw_one_bevel in Listing 2. For the whole implementation of the Bevelled_box Class, it alone contains the calls to the low level, compiler specific, graphics functions that are necessary to put pixels on to the screen. As such, if we wanted to change our rudimentary graphics routines, to say, a 3rd party Turtle graphics library (eg. Forward(10); TurnRight(90 degrees); etc. ), only this Method is affected. Similarly, if we wanted to change brands of C++ compiler or indeed the operating systems and hardware as well, only this small Method is affected! Thus the literal meaning of encapsulation - to contain within a capsule - is easily and naturally achieved.

The other lynch-pins of OOP are INHERITANCE and POLYMORPHISM, which we will tackle next. In the next article, we code up a new child class of Bevelled_box called Buttons. It Inherits all the attributes of its parent, but we give it some extra internal data and some extra Methods, to place screen-text on top of bevelled-boxes, to make Buttons. We then develop our second application program that uses this Button Class, called CALCFACE.CPP (See Fig. 3). It displays a screen rendition of a simple calculator (without the functionality,) with very little programming effort at all.

Fig. 1 - A beta version of the upcoming computer rendition of the strategy board game METAGON, from Solid Software. Both the Bevelled_box and Button classes, were used to develop it.

Fig. 2 - An example random output screen from CIRC_ART.CPP. The Bevelled_box class is used to display the frame.

Article2Fig2.gif (11240 bytes)

Fig. 3 - The output from program CALCFACE.CPP, which uses the Button class.

Article2Fig3.gif (8566 bytes)

 

GLOSSARY of TERMS:

OOP and C++ TERMS:

Class: A Class is to an Object, what the Human Race is to a Person. A Class name is given to a category of Objects of a common type. Just as a common group of variables names can be of the common type INTEGER.
Constructor: A special Method (ie. Procedure) that is automatically executed when you define a specific occurrence of an Object. The programmer usually uses it to initialise data items.
Encapsulation: Ability to contain and bind together the specific data and procedures within a capsule that we are calling an Object.
Data Hiding: Ability to hide the data within a specific Object from being accessed from other parts of a program, outside the Object, if we choose to.
Destructor: OOPosite to a Constructor. It is automatically executed when a specific Object goes out of Scope (ceases to exist). A programmer usually places tidy-up code in here, such as releasing any dynamically allocated memory.
Object: A specifically defined occurrence of a Class. An Object is given a name by the programmer, much the way any variable is given a name.
OOP: Object Oriented Programming. This type of programming is supported by these languages: C++, SMALLTALK, EIFFEL, TURBO PASCAL V5.5 & V6, Objective C, and many more to follow.
Private: A partition within an object that can contain data and procedures (Methods), which can only be used by the procedures within that object.
Protected: A partition within an object that can contain data and procedures, which can only be used by the procedures within that object, or objects of an offspring class.
Public: A partition within an object that can contain data and procedures, which can be referenced and used both inside and outside the object.
Message: Mechanism by which an instruction is sent to a Method (a procedure) within an object. In C++ its format resembles a procedure call (often with specific parameters), but prefixed with the name of the Object concerned.
Multiple Inheritance: The ability for an object to inherit attributes (data items and Methods) from multiple parent objects. The ability was added to C++ at AT&T V2 Standard. Borland C++ and Turbo C++, both conform to AT&T V2 Standard C++.
Inheritance: The ability to pass on attributes (data items and Methods) to offspring Classes of objects.
Polymorphism: The ability to give a general name to a type of action (eg. Display_it), to a whole range of different objects, each of which enacted the action, via completely different code.
Friend Function: A special function defined outside of objects which the programmer gives access rights to the Private data items and Methods of one or more classes of object. This is a bit of a kludge in C++, for which you won't find nearly as much use, since Multiple Inheritance was introduced into C++ with the AT&T V2 Standard.

 

Other Terms Used:

3GL: Third Generation Language. Namely those general purpose languages that came after Assembler and before OOP. eg. COBOL, FORTRAN, BASIC, FORTH, PL/1, PASCAL, C, ALGOL, MODULA2, ADA.
Algorithm: A formula or recipe for a particular sequence of actions, to achieve a desired result every time.
Dynamic Memory: Standard variables use up Static memory, in the sense that they occupy a fixed amount of memory for the life of the program for global variables, or the life of the procedure in the case of local variables. Most modern languages have system functions or operators that allow storage of variables in Dynamic memory, whereby the memory is grabbed by the program and given back to the operating system, on-the-fly.
#include filename: C and C++ have a pre-processor that runs automatically just prior to compiling a program. Any command that begins with # is a command for this pre-processor. The #include tells it to drag in extra source code from the filename specified, before compilation.
IT: Information Technology.
Module: A self-contained portion of code that can be compiled separately. Reducing big problems to smaller and smaller ones, until they can each be easily coded into separate modules, is the basis of Top-down program design, and a subject of the last great jump in programming methodology.
Scope: Is the range within a program, for which a data item can be seen to exist. For example the scope of private data to an Object is just within that object. A variable local to a procedure, disappears when execution leaves that procedure. It's scope is just within the procedure. In C and C++ scope can be limited down to any block of code, ie. code contained within {...} brackets.
Unit: This is Turbo PASCAL's terminology for separately compiled (.OBJ) modules of code.
Void: Relevant to C and C++. A variable defined as void, is of NO type at all, not integer, not float, nor anything. It can be re-cast as any variable type later by the programmer.

 

This article is Copyright: Steve Goschnick, 1991.