From Pascal & C to C++: A Critical Review, a New System Design Paradigm, and Case Studies

Uloženo v:
Podrobná bibliografie
Název: From Pascal & C to C++: A Critical Review, a New System Design Paradigm, and Case Studies
Autoři: Christiansen, M. G., Tanik, Murat
Informace o vydavateli: University of Alabama at Birmingham. Department of Electrical and Computer Engineering
Rok vydání: 2018
Sbírka: University of Alabama at Birmingham: UAB Digital Collections
Témata: C++ (Computer program language), Pascal (Computer program language), technical reports
Druh dokumentu: text
Popis souboru: application/pdf
Jazyk: English
Relation: Technical report (University of Alabama at Birmingham. Department of Electrical and Computer Engineering); 2018-07-ECE-006; Technical report (Southern Methodist University. Department of Computer Science and Engineering); 87-CSE-12; Technical Report 2018-07-ECE-006 Technical Report 87-CSE-12 From Pascal & C to C++: A Critical Review, a New System Design Paradigm, and Case Studies M. G. Christiansen Murat M. Tanik This technical report is a reissue of a technical rep011 issued May 1987 Department of Electrical and Computer Engineering University of Alabama at Birmingham July 2018 Technical Report 87-CSE-12 FROM PASCAL & C TO C++: A CRITICAL REVIEW, A NEW SYSTEM DESIGN PARADIGM, AHD CASE STUDIES M. G. Christiansen M. M. Tanik Department of Computer Science and Engineering Southern Methodist University Dallas, Texas 75275 May 1987 l . Introduction From Pascal & C to C++: A Critical Review, A New System Design Paradigm, and Case Studies M.G.Christiansen M.M.Tanik Southern Methodist University Dallas, Texas 75275 This report wi 11 introduce the reader to C++, a programming language. Since C++ is an extension or C, we include a review or C berore introducing C++ . . For the sake or completeness, a concise comparison or Pascal and C is presented. It is hoped that this wi 11 provide a uniformity for readers familiar with either Pascal or C. We begin with the C/Pascal comparison, followed by a critical review or C++. We then present a discussion or a new object centered systems design paradigm. Finally we present two case studies that demonstrate the pertinent C++ features in greater detail. 2. Pascal and C : A Correspondence An exhaustive and pragmatic comparison or Pascal with C must take into account implementation issues concerning the compilers being considered and the operating systems in which these compilers are implemented. But it is possible to discuss the linguistic aspects or these languages without being concerned with implementation aspects. This kind or summary is useful to Pascal or C programmers to quickly grasp the corresponding concepts in either language. 2.1. C Programming It can be said that C and UNIX are very popular and many professional programmers have recently discovered C. C and UNIX are now well known words outside the university computer science departments. AT&T is supporting, promoting and improving (revising) C & UNIX, while ANSI is developing a C standard. But C was developed as, and still is, a machine-oriented high level language provides some of these facilities: It encourages the use of pointer arithmetic by the inclusion of the pointer operator *, and the reference operator &. - It only enforces weak-type checking. It provides the ability to cast a variable of one type into another type with the cast operator. Only function declaration is provided, no procedures. A default int function return type (void declaration solved some problems associated with this). All variable arguments are passed by value, although a pointer can be used to perform an explicit operand reference. - It provides a rich set of operators including machine oriented shifts >>, < I* include 110 declarations *I I* include local files *I I* define constants *I ; I* declare global vars *I ( ) /* define functions *I main() { ; ; } -Preprocessor capabilities Commands starting with the # sign are in reality C preprocessor commands. A 1 l C environments contain a preprocessor which is an integral part of the UNIX environment. Preprocessor provides three fundamental functions which gives the C some of its power over Pascal. These are: l. ";#include filename" into your program. A standard 110 library specified between the < inserts separately prepared text files typical example is the inclusion of from a specified directory that must be name > symbols. 2. ";#define name value" defines a macro substitution. This statement can be used to define one line functions which are evaluated by the preprocessor. A typical use is for constant variable declarations. 3. #if condition, #else, #endif for conditional compilation of source code between the statements. Naturally, this capability is very useful in handling machine- dependent portions of the app 1 i cat ion. 5 ln addition, there are #undef for forgetting identifier definition, #line for error diagnostic, #ifdef to check if identifier is defined, and #ifndef for checking if identifier is undefined. Some special preprocessors also have more exotic controls. As we shall see preprocessor such as C++ also extends the limited typing capabilities of C to imitate SIMULA object classes. - Keywords These are standard C keywords. There are implementations in which this list is extended. int extern else void char register for enum float typedef do double static whi 1 e struct go to switch union return case long sizeof default short break entry unsigned continue auto if Reserve words in Standard Pascal and end n i 1 set array file not then begin for of to case function or type const go to packed unt i 1 div if procedure var do in program wh i 1 e down to label record with else mode repeat - Type Definitions In C a user define type is specified with: typedef This declaration does not provide a different type in the sense of Pascal user defined type declarations. It is simply a type aliasing mechanism so that the can be used In the program. An example is 6 typedef int Meters; A symbolic synonym for type int is created. - Variables A list of one or more variables is declared with: int numl, num2; I* num1 and num2 declared integer *I char char!; I* charl is declared character *I int arrayl[7] I* array! is an array of 7 integer elements *I int array2[3][2] I* array2 is a two dimensional array *I char strl[S] I* atrl is declared as a string of length 5 * I ' short a I* 16 bits int in PDP11 *I long b I* 23 bits int in PDP11 *I float c I* 32 bits real in PDP11 *I double d I* 64 bits real in PDP 11 *I Al 1 arrays in C begins with the index value of 0. You do not have the flexibility of controlling beginning and ending index values. A convenience of Cis the capability of initializing declarations as shown below: int arrayl [3] = {7 , 1, 6) In function declarations array dimension is not declared and the dimension value is determined at the time of the function cal 1 as shown below: FuncExample(arrayl) int array![]; C does not implement run time array bounds checking, nor does it provide pointer reference verification. These problems are often the cause of errors in new and existing code. Remember that the Jensen and provide external declarations statement for that purpose. Wirth standard Pascal does not of variables. C has extern Technically, in C the interpretation of an identifier is based on not only type but also a storage class specifier. Storage class specifier determines the location and persistence of identifier. In Pascal, storage class is implicit and corresponds to the automatic storage class of C. C reference manual defines five storage classes, typedef, extern, register, 7 l auto, and static. Among these, typedef does not reserve storage as discussed above. Extern is used for external declaration of Identifiers provided that there is an external definition for these identifiers. Register specifier operates I ike auto, but indicates to the compiler that the declared identifiers are heavily used and are candidates for storing in machine registers. In auto the persistence is limited to the block, that is variables are local and discarded upon exit from block. Static variables which are local to the block maintain their values after exiting the block. If a global variable is declared static, it can only be accessed by functions located in the same file. That is, in effect, the scope created by static external variables is local to the file which means somewhere in between global and local in Pascal sense. - Type Conversion In the jargon of C this operation is cal led CASTing. The statement y = (float)x stores in y the value of x in type float and the value or type of x (int) stays same. In addition, there are automatic conversion such as: if type char is mixed with type int the result is int. These facil !ties can be very useful in real-life applications, in which the strong typing of Pascal creates isolated type conversion problems. A general use of this facility is to treat pointer expressions as if they have a different type. - Arithmetic and Logical Operations: The following is a comparison between the operators offered in and how these operations relate to Pascal where applicable. - Arithmetic Operators operation c Pascal Addition + + Subtraction Multiplication * * Division I I Modula 1. mod integer div div 8 C - Logical Operators operation negation And Or c && I I I I - Bitwise Operators operation and or exclusive or c & shift-right >> shift-left << one's complement rv Pascal not and or Pascal Many pascal implementations function forms or otherwise. provides bitwise operations - Relational Operators operation greater than greater than or equal to Less than Less than or equal to inequa1ity equality - Set operations operation Union difference conjunction equality c > >= < >= < = Pascal + and = 9 in inequality inclusion membership - Condensing operations <> in C provides increment and decrement operations as Follows: Increment operator: ++ or ++ - decrement operator: -- or -- Usually these operators increment the operand by a value of one, but if the ++ or -- operators are used on a pointer variable the reference is inc~emented a value equal to the size of the data structure being referenced. This creates the effect of moving the reference to the next item in a linear 1 ist, for example an array. If an assignment statement can be reduced to the rorm: = ; then this statement can be written as: = ; i.e varl = varl * var2; can be written as varl*=var2; Pascal does not provide this kind of condensing but SUCC and PRED Functions are provided. - Input/Output In general Pascal's Write corresponds C's Printr, and Pascal's Read corresponds C'c Scanf. More primitive operations sometimes used in C are getchar, getc, putchar and putc. These operations are not similar to get and put in Pascal which provide control over a buffer variable. - Conditional statements One syntactic difference to be noted between the Ir statements or Pascal and C is in the use of the semicolon. In C the semicolon is a statement terminator thereFore it fs required at the end of each statement. In Pascal ";ir then •. else" construct itself is a complete statement. Thus, the use of a semicolon before else is syntactically incorrect. 10 C has a Switch statement which can be used in combination of break statement to produce the same effect of Pascal's case statement. The break statement of C is a limited goto statement which serves a useful function of planned exit from loops. Switch { } case; case; default: In recent releases of C the restriction that should be of type int has been removed and made this statement more compatible with Pascal's case statement. The differences are that standard Pascal does not have default option, and automatically exits from the body of the case statement after a single selection. Looping constructs are fundamentally same in Pascal and C. - Condition testing at the top of the loop: For in C: for (expl;exp2;exp3) { } where expl is initialization, exp2 is condition, exp3 is increment control. For statement in Pascal for := to do begin end; While inC: while (condition) { }; While statement in Pascal: while (condition) do begin end; l l l - Condition testing at the bottom or the loop: In C: do { ;} while ; In Pascal: repeat; unti 1 ; Records(structures) ln C: struct { ; ; ; }[); In Pascal: = record end; 3. Introduction to C++ In this section we present a description or C++. This programming language is based on the C language and contains several extensions that ease the burden or building and maintaining sortware systems. [1,11] While it is true that C++ has many or the characteristics or object oriented programming, it is not an object oriented 12 language like 5mal ltalk-80. In Smalltalk everything is considered an object and these objects pass messages between each other requesting services be perrormed and/or results returned. As a result of this all type checking is delayed til 1 run time. These systems are usually interpreted which require special architectures to run at acceptable speeds. While C++ does have a 1 imited capability to delay type checking until runtime in the form or virtual runctions, type checking is performed at compile time and a program written in C++ is as efricient, ir not more so, than an equivalent program written in c. A reature that C++ shares wtth object oriented languages is the ability to define structures that allow the encapsulation or concepts in the form of data structures and or functions specirical ly defined to operate on the data structures. This encapsulation is provided by the class construct and it's member runctions. Instances of these classes are orten rererred to as objects because they orten represent some object being modeled in the application. The abi 1 ity to program using classes is perhaps the most important new feature provided by C++. In later sections we will see examples of how this can be used to generate sortware systems using a hierarchical approach. This will enhance reusability, limit misuse which leads to bugs, allow modirication of existing code with less chance or side errects, and in general ease the production or highly structured, easy to understand code. Some of the other key reatures that can be round in C++ are: Compatibility between C++ and C. C++ is a super-set or C and all or the reatures or C can still be round in C++. But smal 1 improvements to the general C syntax make it more pleasant to work with while remaining compatible with C. These changes include new comment speciriers, variable declaration, rererences, and others. All or these are optional and are discussed in more detail in the next section. Strengthen the type checking perrormed, especially the type checking perrormed ror runction return values, runction arguments, and pointer operations. Update the runction mechanism. Features have been added to the runction derinition and calling mechanisms such as derault arguments, optional arguments, improved syntax, and runction overloading. 13 Operator overloading has redefinition of C operators user defined types. been included which allows the to perform special operations on -A new l/0 mechanism called the stream better supports the conceptualization bytes moving from source to sink. has been added that of l/0 as a stream of - As mentioned, perhaps the most important changes have come with the addition of support for data abstraction and object. definition. A new construct cal led the class has been added to this end. In the next section we will provide a overview of C++. In latter sections we wil 1 provide more insight into the class constructs and how it can be used to support data abstraction and object definition. Last we wil 1 discuss a case study included to demonstrate the class partitioning of an application. 4. The C++ Language In a previous section we gave a brief overview of the C language. ln doing this we tried to mention some of the perceived shortcomings of the language. Pascal was used as a comparison to C to bo.th exp 1 a in the features of C, and to provide a contrasting example of how a language might be implemented. In tt1is section we will describe the C++ language. This discussion wi 11 center around the differences that exist between C and C++. These differences primarily concern the improved facility for data abstraction that C++ provides. Other differences concern the improved syntax and declaration capabilities. C++ is a super-set of C. A syntactically correct C program wil 1 compile using C++. In fact C++ is implemented as a translator that transforms the C++ code into a C source file which is then compiled and linked as a C program. This is why it is often referred to as a preprocessor rather than a compiler. 4.1. Control and Assignme~t Statements C++ uses all of the control statements provided by C without any major extensions. Basic program structure is identical so that the C programmer would have no difficulty with the syntax. One change that should be noticed in all of the following 14 examples is a new form of comments. Along with the I* *I comments provided by C we have an new comment marker that specifies that everything between it and the end of line be ignored. This marker is in the form of a double slash ";II". The C operators have not been changed. The full range of the assignment operators are provided e.g. a += b. Also the increment(++) and decrement(--) operators, logical, shift, bitwise logical, sizeof, arithmetic, and conditional operators are al 1 present. Nested blocks can be defined that hide variable usages. For example the variable aaa is declared twice below, but bbb only once: int a a a= 1 ' bbb; { int aaa= 2 bbb= aaa; II bbb = 2 } bbb= aaa; II bbb = In this example the two braces are not associated with any control statement but could be. The name aaa is declared twice and refers to separate instances of an fnteger variable. - Variable and Type Declaration C++ has the fundamental types provided in C. This includes: char short int long float double Unsigned type modifier, storage class, character arrays are provided. auto, extern, static, and register constants, string declarations, and An extension to variable declaration allows the declaration of constant values that cannot be changed. This is provided by using the type modifier const fn the variable declaration. Some examples are: const float pi= 3.14159; const char nl= '\n'; Related to constants define enumerations is and another new enumerated 15 feature, the ability to types. As an example of an enumeration: enum { CLUB, SPADE, HEART, DIAMOND }; is equivalent to declaring the constants: const int CLUB= 0; const int SPADE= 1; const int HEART= 2; const int DIAMOND= 3; Enumerations always are assigned integer values that start with zero. An enumeration can be used to derine a new type. Note the lack or the typeder keyword. enum suit {CLUB, SPADE, HEART, DIAMOND}; Another extension to C is the ability anywhere in a program. This provides short 1 ived variables near their use ror clarity. An example or this is: ror (int i= 0; i < 1 00; i ++) { • • • • • ) to declare variables the ability to declare the sake or program Note that i was declared an int in the inftialization section or the ror statement. Pointer(*), vector([]), and reFerence(&) operations are provided. The reFerence operator has been extended to allow variables to be declared as reFerences to variables and structures. This provides the capability to derine explicit pointers to items. An operation on an reFerenced item produces results identical to the operation being perFormed on the item reFerenced. For example: int XXX= 1; int& yyy = xxx; int zzz = yyy; yyy= 2; II xxx and yyy share same location II zzz = 1 II XXX= 2 This is identical to using a pointer except the syntax is easier to understand. The reference operation is useful in Function arguments as demonstrated in the next section. 16 4.2. Function Declaration The function declaration syntax has many new features. One change is that all function declarations and definitions must indicate a return type. In C a function defaults to returning a type int if none is specified. In C++ not specifying a return type will result tn an error. If no return value is desired the type void is provided to make that specification. One ramification of this is that functions must be defined before their use. These definitions must include the argument types and can include default values for those arguments. In this example the function foo is defined in the first line as returning void and using two arguments of type int. Function declarations now specify the type of the arguments as wel 1 as it's name in the parameter list. Note how a reference is used in the declaration of bbb. void foo(int, int&); II Definition of foo foo ( x, y); II Use of foo void foo(int aaa; int& bbb) { aaa++; bbb++; } II Declaration of foo This example demonstrates that the function increments a local copy of the value of x, but it increments the actual y when foo(x, y) is cal led. This provides a cal !-by-reference capability to the language. The use of references and the void return type combine to give a function the capability to behave like a procedure as defined in Pascal. Another feature provided to the function is the use of default parameters if not provided in the function call. The specification of the default parameters are made in the function definition before the function is used. This example definition replaces the one used in the example above: 1 7 void foo(int = 1, int = 2); //Another definition of foo The following results are obtained: foo(); II aaa = 1 and bbb = 2 by default foo(3); II aaa = 3 by definition and bbb = 2 by default for(3, 4) II aaa = 3 and bbb = 4 by definftion C++ also provides the capability to overload function declarations. This allows different functions to share the same name but each must differ in the parameters they accept. This allows functions that share some conceptual operation in the application to share the same name. For example if our function foo was also required to accept two floats as arguments we could define a second function: foo(float aaa, float& bbb) { aaa++; bbb++; } This declaration uses the same name but different type arguments. When the program that uses these two definitions of foo is compiled the type of the parameters determine which of the two is activated. A final new feature of function usage is specifier. This declares that the source of definition includes the inl ine specifier place of the use of the function. For example: inline float length (float a. float b) { return( (a*a + b*b) I 2 ); } float veclen= length(3, 4); the inline function a function whose will be inserted in The C++ translator wil 1 expand length() in the assignment of the variable veclen making the substitutions of 3 for a, and 4 for b. This replaces the macro capabilities of the #define preprocessor statement provided in C. The use of this feature should be limited to short, simple functions such as the one above. Overuse or use with large functions will generate large C programs by the C++ translator. 18 4.3. Structures In C++ structures can be replaced by the class construct but are still provided to provide consistency with C. Classes wil 1 be described in greater detail in the next section, but first we wil 1 discuss how structures have been effected. The structure construct has had several important features added. These are member functions, constructor and destructor functions, and the new and delete operators. Member functions are functions that are associated with the structure and allow the definition of functions that operate only on instances of the structure. Every structure definition allows two special member functions that are envoked when an instance of the structure is created and destroyed. These are the creator and destructor functions. Finally two new operators are defined that manage free store, the new and delete operators. These two can be used to create and delete instances of structures. These features are covered in greater detail in the following paragraphs. A member function is a method of defining functions that perform operations on the instances of structures. This provides a close association of these operations and the structures they are designed to support. As an example consider a structure that represents a date and member functions intended to operate on a date: struct date { ); int month, day, year; void set( int, int, int); void next(); void print(); This structure definition has three integer values associated with it and three member function that operate on it. To declare and set a date we can write: 19 rna in () { struct date datel, date2; } datel.set(7, 22, 56); date2.set(4, 2, 87); date2.next(); datel.print(); Note that the structure names are associated with the member functions that are to operate on them. This avoids the cumbersome need to pass the address of the structure to the function. For an example of defining a member function consider set() and print(): void date::set(int m, int d, int y) { } month= m; day= d; year= y; void date::print() { printf('"7.d- ";/.d- ";/.d\n", month, day, year); } Note the use of the :: operator. This is cal led the scope resolution operator. It specifies what structure or class the function is to be associated with. It's general syntax is: namel::name2 Where the variable or function name specified by name2 is referenced by the structure or class specified by namel. The . operator can be used to refer to member functions of other structures defined in the program. In a member function the names of the structure variables are used without reference to the structure. In the example above the variables month, day, and year implicitly refer to the instance of the date object. (A special pointer: this is provided to explicitly declare the reference.) The example set() could be modified to provide more clarity: 20 void date::set(int month, int day, int year) { } this->month= month; this->day= day; this->year= year; Another problem with the function date::set() is that it needs to be called explicitly for each new instance of date that is used. An alternative to this would involve writing a creator method for the structure date. The structure declaration would now contain the new member function: struct date { date ( i nt, i nt, i nt); }; And the actual creator function would be defined as: date::date(int month, int day, int year) { } this->month= month; th i s-·>day= day; this->year= year; Now an instance of date can be created and initialized when it is declared: struct date today(6, 20, 87); Member function should provide an interface between the users of the structures and the structure's data. This provides protection against incorrect or illegal access to the data structure. Unfortunately there is no way to provide protection of the data from external uses. The ability to hide data is one of the key features of the class construct and fs covered in the next section. Note that we can include the use of function overloading and define several versions of date creator function that operates upon various methods of initialization. Similarly a destructor function can be written that is called when an instance of a structure is destroyed. This loss of the instance can either be implicate when the scope of the 2 1 declaration is e x ited, or explicate when the instance is removed with the delete operator. Although these new features of the structure construct are quite powerful, use of structures is frowned upon in C++. This is because the language provides the class construct which offers all of these features as wel 1 as some important improvements. 4.4. Classes The class construct has many ·reatures that make it superior to the struct. It has the capability to define, combine, and share the definition of classes among the application. It is also possible to hi9e data in the definition of the class so that only the member function have access to it. These features are important in writing robust and reusable software. As an example of a class lets look at a structure that represents an employee in some organization: struct employee { }; char empN9me[NAME_LEN]; date startingDate; date birthDate; II Member functions char* ~etNa~e(); II Returns a pointer to a copy of the name char* GetbirthDate(); II Returns an ASCII encoding of the date Notice that any user of an instance of employee has access to the name and date fields. This allows direct and perhaps incorrect modification of the information stored in these fields. Also if we later wish to change the format of the structure date, or represent the employee name with a more sophisticated structure we must consider how these changes effect existing code that uses the current structure. The class construct allows the definition or data structures that are only accessible by the member functions defined for the class. As an example consider the employee information again: 22 class employee { }; II Private members char empName[NAME_LEN); date startingOate; date birthDate; public: II Public members char* GetName(); II Returns a pointer to a copy or the name char* GetbirthDate(); II Returns an ASCII encoding or the date This syntax is similar to the struct except ror the class keyword and the addition or the public label. The public label derines that only the variables and runctions derined arter it are accessible by users or the class. That is to' say, the runctions and variables derined between the beginning or the class derinition and the public label are only accessed by the member functions. This allows the hiding or data declared before the public label. All members defined arter the public label are rererred to as public members. All other members are called private members. The member runctions declared in the public section of a class construct provide an interface between an instance or the class and the program using it. Any changes to the private section should only concern the member functions accessing it, limiting the impact of those changes. Restricting access also ensures that the data structures will be properly used by the programmers eliminating a source or potential errors. Another benefit of the class construct is the encapsulation and structuring of classes into hierarchies that represent a natural partition or the application. This is accomplished by defining a new class that contain the features or some previously defined class, and adds new features that are unique to it. This is referred to as a derived class. The class whose features are used is referred to as the base class. As an example or a derived class lets consider a programmer class that uses the employee class defined earlier as it's base class: 23 class programmer :public employee { project currentProject; station stationAddress; public: char GetProject(); ); By declaring employee as the base class of programmer we allow the user of the new class access to all of the public members of employee. If on the other hand we only wanted the member functions of programmer to access the public members of employee but restrict the access of users to only the public members of programmer we would declare the programmer class as: class programmer : employee { ); Notice that declaration. the public label has been omitted in the Users of programmer have access to only the public members of programmer. A structure can utilize the inheritance between a base and derived structure. Referring to the employee structure defined at the beginning of this section we can define the structure programmer as: struct programmer : employee { II All members are public ); This definition of programmer is equivalent to: class programmer :public employee { public: II Class members here . ); Other features supported by the class construct are function overloading and default arguments as described in a previous section. A class can grant access of selected private members to derived classes using the friend construct. A public or private member can be declared as static and thus shared among all 24 instances of the class. Like structures, to through the use of pointers and function of a class can include a functions which are cal led when an created and destroyed. classes can be referred the -> operator. Member creator and destructor instance of the class is An important feature of member functions fs the use of the virtual keyword in the specification of the function in the base class. This defines what is called a virtual function. When a virtual function is defined in a base class, it can be redefined in any other class derived from the base. In essence a virtual function allows the runtime evaluation of a pointer to determine the class to which the pointer references. When a pointer variable is used to invoke a virtual function, a runtime type check is inserted to determine which of the functions defined in the derived classes to evoke according to the class being referred to. Another important feature of C++ is it's ability to overload or redefine the meanings of operators. It is possible to overload any of the C++ operators such as * +, (), This is accomplished by specifying the operator and the type of the operands to be used in a declaration similar to a function's. Often the types of the operand involved in an operator overloading will be user defined types, and the operand represents some operation to be performed. The ability to define operations on user defined types produces clear and concise representation of possible complex operations. 4.5. Object Centered Design As we stated C++ is not considered as an object oriented language in the sense of Smalltalk-80. A programmer who wishes to use C++ should consider the issues raised by an object oriented approach to programming. So far little has been mentioned on how to properly define a class. What features make one design better than another. An object oriented approach calls for the definition of a set of objects that are important or are contained in the application. Each of these objects is constructed from the classes that make up the software system. In the following section we will discuss how to define and combine sets of classes into objects with reusability and robustness in mind. 25 5. Boundary Detection and Formation: A new paradigm in system design. The success of an object centered approach in software engineering depends on how wel 1 the objects are designed. In this section we present some criteria to consider in system design using an object centered approach. Much of this depends on the functional boundaries identified between objects in the application. It is critical that the important functional boundaries be discovered early in the development of the application. This criteria also involves the design of the interface that the objects furnish to the user of the objects. The interface should be efficient and easily understood while providing the needed data hiding and protection from improper use. In C++ the interface is provided by the public member functions defined for the object's class. The definition of an object class should represent some fundamental and generic concept being applied in the application. It's definition should contain only features that are contained in the concept being modeled. It should not be forced to contain features that are of use in only certain applications of the object. Instead these features should be broken into separate object classes creating two objects each with the needed features. A properly defined set of related classes should represent a hierarchy of concepts, which in turn wil I be represented mathematically as a partial order. The root or base classes should contain the fundamental features of the application. There should be little connection, if any, to the other base classes. But it is quite unlikely that a properly defined base definition will contain all of the features needed by some object in the application. Often several of the needed object classes will share the features defined in a base class. The needed object classes will be defined as derived classes of the base class. There should be an attempt to place similar features into the base classes and to reuse those classes in many derived classes rather than redefine those features in new base classes. The problem with redefining features in separate base classes is that it will lead to redundant code being generated for the member functions of each class. On the other hand features that are unique to a specific object 26 should be placed in the object class that represents it. These features will often rely on features supplied by the base classes. In some cases a feature will be shared among a few objects, but rely on features provided by base classes. These are intermediate classes defined as the children of base classes and the parents of object classes. Most object oriented languages provide the ability to define new classes that combine several base classes such that the needed features of each base class can be utilized by a single new derived class. Unfortunately C++ is not one of them and this is perhaps the greatest failing of the language. We are only able to use a single base class in defining a new derived class. But · through the use of the friend specifier, we are able to grant a .class access to the private or public members of a base class. A closer study of this option reveals that this method requires modification of the base class's definition when adding new derived classes. This method is unacceptable and defeats the isolation of the base class. The most important consideration to be made early in the design of an application's objects is the interface that will be provided by each class. An object's interface is provided by the public members of the object's class definition. These public members are often member functions that are designed to provide access to, and protection for the private members of the class. By limiting access to objects through the public members we are able to hide the implementation details and provide protection from errors created by modification of the private members. We can also provide a framework through which a programmer can more easily understand the implementation. This leads to the development of modules that are robust and more likely to be reused. The interface provides access to not only the user of the object, but to derived classes that makes use of a base class's public members. A base class provides a service to the deri ved classes and the interface determines how efficient that service is. The interface should encapsulate the services required by the object being modeled. In the case of base classes an effort should be made to determine all the services that might ever be needed from the object, not just the ones needed in the application. This will result in an object that is more likely to be reused at a later date, and perhaps bring some unimagined aspects of the current application to light. 27 J 6. Case Studies In this section we present two example applications whose listings are included in the appendix. 6.1. Game of Life This is a popular simulation program [8,9]. It is a simulation on a two dime~sional array, where each index contains a live or dead cell. Our implementation uses a two dimensional array object of pointers to cell objects that are in a live or dead state. For each tick of the clock the state of the world (the array) is updated according to the rules [6,7) given below, and the results from each tick are drawn on the screen. The onl y graphics capability needed is to be able to position the cur sor and to clear the screen. The game takes place on an unbounded grid where each eel 1 can either be occupied by an organism or not. Occupied cells are called alive, unoccupied eel ls are called dead. Which cells are alive changes from generation to generation according to the number of neighboring cells that are alive according to the following rules: [10] ' 1. The neighbors of a eel 1 are the eel ls that touch it vertically, horizontally, or diagonally. 2. If a cell is alive but either has no neighboring cells alive or only one alive, then in the next generation the eel 1 dies of loneliness. 3. lf a cell is also alive, then overcrowding. alive and has four or more neighboring eel ls in the next generation the cell dies of 4. A living eel 1 that has either two or three living neighbors remains alive in the next generation. 5. If a cell is dead, then in the next generation ft will become alive if it has exactly three neighboring cells that are already alive. All other cells remain dead into the next generation. 6. All births and deaths take place at exactly the same time, so that dying cells can help give birth to another, but cannot prevent the death of others by reducing overcrowding, nor can cells being born either preserve or kill cells living in the previous generation. 28 The appendix contains the source code for the program. The file life.h contains the structure and class definitions. The file life.cpp contains the main() and a function that uses a data file to initialize the world before the simulation begins. The file world.cpp contains the member functions that support the interaction of eel ls. The file cell.cpp contains the member functions needed to simulate the cells. 6.2. Universe Simulation Our second application is a simple simulation of a two dimensional universe that contains a number of independent bodies. These bodies move through the universe and their position is drawn on the screen. The only graphics capability required is the ability to position the cursor and cilear the screen. In order to keep the code size to a minimum, we provided only the minimum capabilities to our simulation while including as many of the features of C++ as possible. The universe and the bodies in it are defined as -C++ classes. The universe is composed of a two dimensional array of 1 ists (see sl ist.h) which contain pointers to the objects whose position in the universe correspond to the i,j index of the array.(see universe and universe.cpp) The bodies in our universe are derived from the class base.(see base.h and base.cpp) Three derived classes are included which represent bodies each defined with increasing levels of interaction with the universe. (see obj.h, and obj.cpp) The main() body of the program drives the simulation.(see main.cpp) The universe is interfaced through the member function universe::tick(). Every call of tick() causes the universe to update it's state. Universe::tick() tests each cell in universe Array for an object. If one is found, that object's member function ::tick() is invoked which updates the state of that object. The objects found in a list will be of class objl, obj2, or obj3. Depending on the value returned from obj*::tick(), the object is inserted back into the universe or deleted, and this insertion might be to a new location. Once an object is reinserted, it is also drawn at it's location on the screen. Each cell in universe Array contains two linked lists. During each iteration one list is deleted from and the other inserted into. At the next iteration this is reversed. Note that the member function base::tick()(in base.h) is defined 29 as virtual function and is redefined for each of the derived classes in obj.h. These functions are responsible for updating the state of an object in the universe. The use of virtual functions allows the use of pointers to class base in the linked lists while objects of a class derived from base are referenced. A runtime check is used to determine the exact class of an object whose tick() member function is to be evoked. 7. Conclusions In this report we have introduced and reviewed C++, a language that supports data abstraction in the form of the class construct. We described the properties of objects in our discussion of a new paradigm for system design. Finally we presented two case studies whose characteristics are well suited for an object centered implementation. 30 Re-ferences 1. Stroustrup,B. The C++ Programming Language. Addison Wesley, New York 1986. 2. Kernighan,B.W. and Ritchie,O.M. The C Programming Language. Prentice-Hal 1, Englewood Cli'f'fs, New Jersey. 1978. 3. Cox,B.J. Reading, Me. , Object 1986. Oriented Programming. Addison Wesley, 4. Goldberg,A. and Robson,D. SMALLTALK-80, The language and its implementation. Addison Wesley, Reading, Ma. 1983. 5. Jensen,K. and Wirth,N. Pascal user manual and report. Springer-Verlag, N.Y., N.Y. 1978 (second edition). The third edition or this report. incorporates ISO standards. 6. International Organization for Standards, Specification 'for Computer Language Pascal. ISO 7185-1982, 1982. 7. ANSI/IEEE, An American National Standard IEEE Standard Pascal Computer Programming Language. ANSI/IEEE 770 X3.97-1983. 8. Gardner,M. Mathematical Games, Scientific American 223, No.4, (oct.1970), 120-123. 9. Gardner,M. Wheels, Life and other Mathematical Amusements. W.H.Freeman, N.Y., 1983, 214-257. 10. Kruse,R.L. Data Structures and Program Design.(2nd edition) Prent i ce-Ha 1 1 , N.J. , 1987. 11. Stroustrup,B. An Overview of C++. Sigplan Notices, Oct. 86. 31 APPENDIX - The Game of Life •••• 1 ife.h I* *I Life Game Example. This file contains the class and structure definitions used by the remaining modules in the program. II Represents an index into a two dimensional array typedef struct index { i nt i, j; i ndex ( ) { i = 0; j = 0; } index(int ii, int jj) { i= ii; j= jj; } }; typedef long time; const int LIVE= 1; const int DEAD= 0; II A eel 1 in the world class cell { void •w; 11 Pointer to cell's world i nt a 1 i ve; 11 F 1 ag: 0 = dead, 1 = 1 i ve int neighborCount; II Number of live neighbors index position; II Position in worldArray and on screen pub 1 i c: }; cell(index, void*); void draw(); II Draw a specific cell void initLife(); II Set a cell as alive void touch(); II Update the count of live neighbors void tick(time); II Advance the simulation const int iSize= 24; const int jSize= 60; II World in which cells live class world { cell *worldArray [iSize][jSize]; II Array of pointers to Cells time worldTime; pub 1 i c: world(); II Inlt the worldArray void draw(); II Draw the living cells as'*' on screen 32 ••• I* *I void tick(); //Advance the simulation void updateNeighbors(index); //Mark neighbors of live cells void initlife(index); //Place 1 ive cells into worldArray@ index life.cpp This file contains the main routine for the program and a function that initializes the world before the simulation starts. #include #include ";life.h" rna in () { } I* *I void initWorld(world*); world wld; II Declare a world initWorld(&wld); II Inlt the world clear(); II Erase the screen wld.draw(); II Draw the initial configuration while(l) wld.tick(); II Step through the sequence This function will initialize worldArray[][] by placing cells into positions that are read from a file. void initWorld(world* w) { FILE *fp; char buf[lOO], *fName= ";life.dat"; i nt i , j; if ((fp= fopen(fName, ";r"}} ==NULL) { } printf(";Error opening: 1.s\n", fName); exit ( -1 ); while (fgets(buf, sizeor(bur), fp) !=NULL) { puts(buf); switch (buf[O]) { 33 } *** /* *I case '#': II Comments break; case '1': II An absolute position for a eel 1. { int nc= sscanf(buf,";1.*d '7.d '7.d", &i, &j}; printf(";";'.d conversions ";'.d ";'.d\n", nc, i, j); w- > i n i tl i f e ( i ndex ( i , j ) ); break; } case '2' II Assign some number of cells to random positions. { int nc= sscanf(buf,";";/.*d ";'.d", &i); //Read# cells printf(";";/.d conversions ";/.d\n", nc, i); for ( i nt i i = 0; i i < i; i i ++) w->initlife(index(rand()%iSize, rand(1%jSize)); break; } default: pr i ntf (";Unknown: ";/.s: \n", buf); break; } II end case } I I end wh i I e world.cpp World member functions #include #include ";life.h" world::world () II Allocate new cells { } worldTime= 0; for (int i= 0; i < iSize; f++) for (int j= 0; j < jSize; j++) worldArray[i][J]= new ce11(1ndex(f,j), (void*)this); void world::updateNeighbors(index ff) { if (ii.i != 0 && ii.j != 0) worldArray[ i i. i-1 ][ i i .j-1 ]->touch(); i'f (ii.i != 0) 34 } worldArray[ i i. i-1 ][ i i .j)->touch(); if (ii.i != 0 && ii .j < jSize-1) worldArray[ i i. i -1) [ i i. j+l )->touch(); if (I i .j < jSize-1) wor 1 dArray[ i i. i) [ i i. j+ 1 ]->touch (); iF (ii.i < iSize-1 && ii.j < jSize-1) worldArray[ i i. i+ 1) [ i i. j+l )->touch(); if (ii.i < iSize- 1) worldArray[ii.i+1)[ii.j)->touch(); iF (ii .i < iSize-1 && ii.j != 0) worldArray[ii.i+1)[ii.j-1]->touch(); iF (ii.j != 0) worldArray[ii.i][ii.j-1]->touch(); void world::initlife(index i i) ( worldArray[ i i. i ][ i i. j]-> initl iFe(); } void world::tick() ( } worldTime++; cursor(O,jSize+3); printFtick(worldTime); void world::draw() ( } I* *I for ( i nt i = 0; i < iS 1 ze; i ++) For (int j= 0; j < jSize; j++) worldArray[i][j]->draw(); Member functions for class cell #include #include ";life.h" cell: :cell (index i i, void •ww) 35 { alive= DEAD; w= (world*)ww; neighborCount= 0; position= i i; ) void cell: :touch() { neighborCount++; ) void eel 1 ::initLife() { alive= LIVE; ) void c e 1 1 : : t i c k ( t i me t) { } switch ((int)t%2) { II Odd numbered ticks case 1: II Mark neighbor if alive if (alive) ((world*)w)->updateNeighbors(position); break; II Even numbered ticks case 0: if (alive== LIVE) { } II Die if conditions are not met if ((neighborCount != 2) && (neighborCount != 3)) { a 1 i ve= 0; draw (); } else { } II Live if conditions are met if (neighborCount == 3) { a 1 i ve= 1; draw(); } neighborCount= 0; } I I case t"/.2 II Draw cell at position on screen 1f alive void cell::draw() { 36 } cursor(position.i+l, position.j+l); if (a 1 i ve) (void)putchar('*'); else (void)putchar(' '); II End Of Life Game 37 - Universe Simulation *** main.cpp I* Sets up and runs the universe *I #include #include ";base.h" II Contains the base object class #include ";sl ist.h" II Contains the 1 inked 1 ist class #include ";universe.h" II Contains the universe class #include ";obj.h" II Contains the derived body classes main() { } void initObjs(universe* ); int attraction= .5; universe u(attraction); initObjs(u); wh i 1 e ( 1 ) u · > t i c k ( ); II This value determines the universe 's II center attration II Init the universe II Init the objects and insert into II universe II Update the universe ' s state void initObjs(unfverse *u) { } This function will use data in a rile to initialize a number or body objects, and place them into the universe. This initialization information includes: · · The body' s i n i t i a 1 pos i t ion. - The body's Initial motion vector. -· The character used to display it's position on the screen. An attribute assigned to each object. The code was not included to save space in the report. Rerer to base.h & obj.h ror inrormation. *** universe.h I* Class ders ror the universe class *I II Universe sizes 38 1 const int univSizei= 24; const int univSizeJ= 60; const int univCenterl = univSizell2; const int univCenterJ = univSizeJI2; struct doublelist { II structure used in universe array s 1 i st a; slist b; }; class universe { doublelist univArray [univSizel] [univSizeJ]; time univTime; II current time fn universe double attraction; II attraction to center base *get(index i); II delete and return next object at index pub 1 i c: ); *** I* *I universe(double attraction); 11 Initialize a universe object void insert (base *r); II insert r into universe using its position void tick (); II Advance time (Perform next operations) II Returns size, center & centerAttract of universe void getSize(index &s, index &c, double &a); void draw(); II Draw field on screen unlverse.cpp Member functions for the universe class #include #include ";sl ist.h" #include ";base.h" #include ";universe.h" universe::universe(double get) II Creator member function { } univTime= 0; attraction= get; II Insert object s into universe using current time to determine II which list to insert it into. void universe::insert(base *s) { vector position= s->getPosition(); II Determine index from position int i= (int)position.x; int j= (int)position.y; 39 } if (univTime ~ 2) II odd or even univArray[i][j].b.insert((ent*)s); else unfvArray[i][j).a.insert((ent*)s); II Remove and return next object at Index if using current II time to determine which list to delete from. base *universe::get(index ii) { } return (univTime ~ 2) ? (base*)untvArray[ii.f][ii.j].a.get() II o dd (base*)univArray[li.i][ii.j].b.get(); / / e ven II Return information about universe. Note arguments are passed by II reference. void universe::getSize(index &size, index ¢er, double ¢erAttract i { } size.i= univSizei; size.j= univSizeJ; center.i= univCenteri; center.j= univCenterJ; centerAttract= attraction; void universe::tick () { } base *11; univTime++; II Advace time escreen(2); //Clear screen II For each index in the universe for (int i= 0; i < univSizel; i++ ) for (int j= 0; j < univSizeJ; j++ ) { while ((ll=get(index(i,j))) != 0) II Whfle position not empty if(ll->tick(univTime)) {II Advance the object } vector position= 11->getPositfon(); II Get object's index cursor((int)positfon.x, (int)position.y); 11->putSymbol (); lnsert(ll); II Insert into universe else delete 11; II Return to free store } II for *** slist.h I* Contains a single linked list class from C++ reference pg 203. Member function will not be include here to save space. 40 .I typedef void* ent; class slink { friend class slist; slink* next; ent e; slink (ent a, slink* p) { e= a; next= p; } }; class slist { slink* last; pub 1 i c: }; s 1 i st (); void ent insert (ent a); get (); *** base.h I* II add at head of list II remove and return from head of list Definitions for the base class from which body objects are derived. *I II These are various typedefs and structures used in the application. typedef char symbol; typedef long time; typedef struct vector { double x, y; vector () { X= 0.0; Y= 0.0~ } vector (double i, double j) { x= i; y= j; } }; typedef struct index { i nt i , j; i ndex ( ) { i = j = 0; } index (int ii, int jj) { i= ii; J= jj; } }; II Structure used to contain initialization values for the base II creator member function. typedef struct baselnit { }; index position; vector movement; symbol display; 41 class base { symbol displaySymbol;ll Display symbol for universe display pub 1 i c: vector position; II Absolute position vector movVector; II Absolute movement vector base (baselnit); II init virtual int tick (timet); II Action to take each tick void putSymbol (); II Display the symbol }; **** base.cpp I* Member function decl for class base Note most functions are very sparse and are entended to be redefined in the derived classes. *I #include #include ";base.h" base:: base (baselnit s) { } position.x= s.position.i; position.y= s.position.j; movVector= s.movement; displaySymbol= s.display; int base ::tick (timet) { return 1; } void base :: putSymbol () { putchar(displaySymbol); } *** body.h I* class definitions for derived body objects *I II object 1 II Initialization structure 42 typeder struct objllnit baselnit { universe •univ; ); class objl : public base { universe *univ; index uSize; index uCenter; double centerAttract; pub 1 i c: }; objl (objllnit); II init i nt tick (time); Ill ohject 2 typeder struct obj2lnit baselnit { universe *univ; }; class obj2 : public base { universe •univ; index uSize; index uCenter; double centerAttract; pub 1 i c: }; obj2 (obj2lnit); II init i nt tick (time); Ill ohject 3 typeder struct obj3Init baselnit { universe •univ; }; class obj3 : public base { universe •univ; index uSize; index uCenter; double centerAttract; vector negMoveVec; pub 1 i c: }; ••• I* obj3 (obj3lnft); II inft int tick (time); body.cpp 43 Body object derived from class base *I #include #include ";base.h" #include ";sl ist.h" #include ";universe.h" #include ";body.h" II Body object class one. II This is the simplest of the objects. It body uses its moveVector t o II update it's postion at each tick. When it reaches the edge of the II universe it is deleted. objl::objl (objllnit o) : ((baselnit&)o) { } univ= o.univ; univ->get5ize(uSize, centerAttract); uCenter.i= uSize.il2; uCenter.j= uSize.jl2; int objl::tick(time t) { } position.x+= movVector.x; position.y+= movVector.y; if (position.x >= uSize.i) return 0; if (position.x < 0) return 0; if (position.y >= uSize.j) return 0; if (position.y < 0) return 0; return 1; II Body object class two. At each tick it calculates it's next postion II in the universe. When it reaches the edge of the universe it wraps II around to the other side obj2::obj2 (obj2lnit o) : ((baselnit&)o) { } univ= o.univ; univ->getSize(uSize, centerAttract); uCenter.i= uSize.i/2; uCenter.J= uSize.j/2; int obj2::tick(time t} { position.x+= movVector.x; position.y+= movVector.y; if (p~sition.x >= uSize.i) pos1tion.x-= uSize.1; if (position.x < 0) position.x+= uSize.f; 44 ir (positfon.y >= uSize.j) position.y-= uSize.j; ir (position.y < 0) position.y+= uSize.j; return 1; II Body object class three. II A more complex example. The object Is attracted to the center or II the universe. At each tick the attraction is used to adjust the speed II and direction or the object. Objects that rall back into the center II are removed. obj3::obj3 (obj31nit o) : ((baselnft&)o) { } unfv= o.univ; univ->getSize(uSize. centerAttract); uCenter.i= uSize.il2; uCenter.j= uSize.jl2; negMoveVec.x= -(movVector.x); negMoveVec.y= -(movVector.y); int obj3::tick(time t) { } posit. ion. x+= movVector. x; position.y+= movVector.y; ir (position.x >= uSize.i) return O; ir {position.x < 0) return O; ir (position.y >= uSize.j) return O; ir (position.y < 0) return 0; II double deltaX= position.x-uCenter.i; double deltaY= position.y-uCenter.j; double distance= sqrt(deltaX*deltaX + deltaY*deltaY); ir (distance< 1) return 0; double deltaVec= sqrt(distance) * centerAttract; movVector.x= movVector.x + deltaVec * negMoveVec.x; movVector.y= movVector.y + deltaVec * negMoveVec.y; return 1; 45; http://uab.contentdm.oclc.org/cdm/ref/collection/uab_ece/id/30
Dostupnost: http://uab.contentdm.oclc.org/cdm/ref/collection/uab_ece/id/30
Přístupové číslo: edsbas.6F0D73F8
Databáze: BASE