4Class and Object

Everything in the real world we are familiar with is an object. Objects can not only be visible, as houses, cars, airplanes, animals and plants, but also be invisible, as a plan. Objects can be a simple unit, like a person; and they can also be a combination of several objects; for example, there are multiple departments in a company, and each department has several staff members. A class can be formed based on the common attributes abstracted from similar objects. These are the concepts and methods we are familiar with in the real world. The aim of programming is to describe and solve problems in the real world, so the first step is to describe objects and classes faithfully in programs. C++, an object-oriented programming language, supports this kind of abstraction. A class in C++ is formed by the encapsulation of abstracted data and functions.

In this chapter, we will first introduce the main features of object-oriented design: abstraction, encapsulation, inheritance, and polymorphism. We then focus on the core concept of object-oriented design – class, which includes how to define and implement a class, and how to use classes to solve problems.

4.1Basic Features of Object-Oriented Design

4.1.1Abstraction

As a fundamental way to understand problems, abstraction is not new to us. Abstraction in the object-oriented method is a procedure in which specific issues (objects) are summarized and abstracted into the common attributes of a class of objects; it also includes the procedure of describing these attributes. The procedure of abstraction is also a procedure of analyzing and understanding problems. In the development of object-oriented software, we should first know the essence and the description of the problem; and then find the specific procedure to solve it. Generally, the abstraction of a problem should include two aspects: abstraction of data and abstraction of behavior (or abstraction of function, abstraction of code). The former describes the attributes or states of a class of objects, i.e., the characteristics used to distinguish this class of objects from other classes; the latter describes the common behavior or functional characteristics of a class of objects.

Let us see two simple examples: first we implement a simple clock program on a computer. From the analyses of the clock we can see that we need three integers to store time, which represent the hour, minute, and second respectively. This is an abstraction of the data that a clock possesses. In addition, a clock needs some simple functions, such as time display and time setup. This is an abstraction of the behavior of the clock. Using variables and functions in C++, we can describe the abstracted attributes of a clock like this:

Abstraction of data:

Abstraction of function:

The second example concerns the abstraction of human beings. Based on the generalization and abstraction of human beings, we extract the common characteristics and have abstract descriptions as below:

Common attributes: name, sex, and age, which form the data abstraction of human beings. Using C++ variables to represent them, they can be:

Common function: biological behaviors like eating and walking, and social behaviors like working and studying. They make up the function abstraction of human beings. We can also use C++ functions to represent them:

If we need to develop the personnel management software for a company, the common human characteristics mentioned above may not be enough, and we also need to concern wages, working age, department, operational capability, subordination, and so on.

From these two examples we can see that, focusing on different aspects of the same object can lead to different abstraction results. Even for the same problem, different requirements of problem solving may lead to different abstraction results.

4.1.2Encapsulation

Encapsulation means combining abstracted data and behaviors (or functions) together to form an organic whole. In other words, encapsulation means to combine data and the functions operating on the data together to form a “class”, and the data and the functions are both members of the class. For example, based on abstraction, we can encapsulate the data and the functions of a clock to make a class of that clock. According to C++ grammar, the class Clock can be defined as below:

Here we defined a class Clock – its function members and data members describe the abstraction result of a clock. {} limits the boundary of that class. Keywords public and private are used to specify different access permissions of the members, which will be further discussed in 4.2.2. The two functions declared as public are the external interfaces of the class; the outside world can only communicate with the class Clock though these two interfaces. The three integers declared as private are the private data of this class, and they cannot be accessed directly by the outside.

We can see that encapsulation makes some members the external interfaces between the class and the outside, while hiding other members. Through encapsulation we can properly control the access permissions of class members, minimize the interactions between different classes, improve data security, and simplify the coding work.

By encapsulating data and code into a reusable program module, we can use the module efficiently while writing a program. Since we can use an encapsulated module only through its external interfaces and by some specific access rules, we need to not know its implementation details.

4.1.3Inheritance

Knowledge acquisition is a long process, and for some questions there may have already been in-depth studies. How do we use these research results? And, if we get a better understanding of the problem at a later stage of the program design, how do we integrate the new understandings into what we have already done? If we start from scratch over and over again, how can we improve the productivity of the software industry?

Inheritance is designed to solve these problems. Only by using inheritance can we make progress based on existing achievements and avoid duplicating analyses and developments. C++ offers the inheritance mechanism of class, which allows programmers to make more specified, more detailed descriptions for a class while keeping the original attributes of the class. This hierarchy structure can effectively reflect the developing process of human understanding. Chapter 7 will discuss the inheritance of class.

4.1.4Polymorphism

Polymorphism in object-oriented design is a direct simulation of human ways of thinking. Take ‘play ball’ for example: the word ‘play’ is a piece of abstracted information with multiple meanings. We can say play basketball, play volleyball, and play badminton,where ‘play’ indicates participating in some kind of sport, while the rules and actual movements in each case are totally different. In fact, this is an abstraction of multiple sports behaviors. In programming it is the same, and the overloaded function Chapter 3 introduces is a way to achieve polymorphism.

Generally speaking, polymorphism means that a program can deal with multiple types of objects. In C++, this polymorphism falls into forced polymorphism, overloading polymorphism, type parameter polymorphism, and inclusion polymorphism.

Forced polymorphism is realized by converting one type of data into another type of data, a type conversion (explicit or implicit) introduced before. Overloading means to give the same name different meanings. We have already introduced function overloading in Chapter 3, and operator overloading will be covered in Chapter 8 . These two kinds of polymorphism belong to special polymorphisms, which are only superficial polymorphisms.

Inclusion polymorphism and type parameter polymorphism are general polymorphisms,which are real polymorphisms. C++ uses virtual functions to realize inclusion polymorphism. Virtual functions are the essence of polymorphism, and will be discussed in Chapter 8. Templates in C++ are used to achieve type parameter polymorphism, which includes function templates and class templates, and we will discuss them in Chapter 9.

4.2Class and Object

Class is the key point of the object-oriented design method. We can use class to achieve the encapsulation of data.

In procedure-oriented structured programming design, program modules are implemented through functions. Functions encapsulate logically correlated codes and data to achieve a specific usage. In object-oriented programming design, program modules are implemented through classes. Classes encapsulate logically correlated functions and data, and they are the abstract description of the problem. Thus, compared with functions, classes have a higher degree of integration, and are more suitable for developments of large and complex projects.

Section 4.1 introduced the concept of class from the perspectives of abstraction and encapsulation. It is helpful for the beginner to understand class in another simpler way. Let’s first take a look at basic data types, such as int, double, bool, and so on. When defining a variable of a basic data type, what happens exactly? Let’s see the following statements:

Obviously these statements define how variable i is used to store int data and variable b is used to store bool data. But that’s not all that a variable definition indicates. Another important indication may usually be ignored, and that is the restriction of operations on the defined variable. For example, we can do arithmetic and comparison operations on i; while we can do logical and comparison operations on b. This shows that each data type includes not only the data attributes but also the operations on the data.

No matter what kind of program language it is, the amount of its basic data types is limited. The basic data types that C++ provides also cannot describe all the objects in the real world. So C++ supports a user-defined data type, class. In fact, because class is a user-defined data type, we can theoretically define infinite new classes. In this way, we can use the basic data type int to describe integers, and use user-defined class to describe objects like ‘clock,’ ‘car,’ ‘geometric figure,’ and ‘people.’ Just as a basic data type includes data and operation, when defining a class we have to describe its data and operations. This is what was introduced in Section 4.1, that through data abstraction and function abstraction of objects in the real world, we can get the data members and function members of a class.

After defining a class, we can define the variable of the class, and this variable is called the object (or instance) of the class. A definition of a variable of a class is called an instantiation of the class.

4.2.1Definition of Class

Let’s take the clock for example again. Class Clock is defined as below:

Here the data and behavior of the clock are encapsulated as the data member and function member of Class Clock. The grammar of defining a class is as follows:

Public, protected, and private respectively denote different access permissions for different members,whichwill be detailed in Section 4.2.2. Note that we can just define the function prototype inside the class, and the implement of the function (the function body) can be defined outside the class definition:

We can see that unlike common functions, the names of the member functions of a class need to be restricted by the name of the class, such as ‘Clock::ShowTime.’

4.2.2Access Control to Class Members

Class members include data members describing the attributes of the problem and function members describing the behaviors of the problem, which are two indivisible aspects. In order to understand access control to class members, let’s see the clock example again. Any clock records time and has a panel, a knob, or a button. As discussed in Section 4.1, we can abstract all these common attributes of clocks into a Clock class. In normal use, users can only look up time through the panel and adjust time using the knob or button. Amender can take the clock apart, while others had better not try. In this way, the panel, the knob, and the button are the only ways for us to touch and use a clock. Thus we can design them as the external interfaces of the Clock class. The time recorded by the clock is the private member of the class, which users can only access through the external interfaces.

The access control to class members is realized by setting the access control attributes of class members. There are three kinds of access control attributes: public, private, and protected.

Public members define the external interfaces of the class. Public members are declared by the keyword public. We can only access public members outside the class. For the class Clock, we can only use the public function members SetTime() and ShowTime() to adjust or display time.

Fig. 4.1: Access restriction of class members.

Class members defined after the keyword private are private members of the class. If the private members immediately follow the class name, then keyword private can be omitted. Private members can only be accessed by the member functions of the class, and any access to private members from outside of the class is illegal. Thus, private members are completely hidden in the class, which protect the security of data. Hour, Minute, and Second in class Clock are all private members.

Protected members are much like private members; the differences lie in their effects on newly generated classes during the inheritance procedure. Chapter 7 will discussed this issue in detail.

Figure 4.1 describes the access control attributes of class members visually: set the members needed to be hidden to private, make them an inaccessible black box from the outside; set interfaces provided for the outside to public, and then they are transparent to the outside; protected members are like a cage, which offer some special access attributes for derived classes.

Now imagine, if a clock can neither tell the time nor adjust the time, would you like to buy it? You may say: ‘what use is it!’ This is like a class out of any external interface – it cannot be used. So remember, designing a class is for usage, and we must design necessary external interfaces for its use.

In the definition of a class, memberswith different access attributes can appear in any order. The keywords to denote access attributes can also appear at any time. But each member can only have one access attribute. For example, the follow definition of class Clock is also right:

Generally we put public members in the front when writing a class definition. This is easy to read, because public members are what need to be known when the outside needs to access the class. In general, all the data members of a class should be declared as private, and thus the internal data structure won’t affect the outside of the class, and the mutual influences between program modules can be minimized.

4.2.3Member Function of Class

Function members of a class describe the behaviors of the class, such as the function members SetTime() and ShowTime() of class Clock. Member functions implement the program algorithm and are the methods used to process the encapsulated data.

1.Declaration and Implementation of Member Functions

The function prototype should be defined in the class body. A function prototype describes the parameter list and the return type of the function. The implementation of the function is written outside the class body. Different from ordinary functions, a member function should specify its class name when it is implemented. The implementation form is:

2.Member Function with Default Formal Parameters

In Chapter 3 we introduced functions with default parameters. Member functions can also have default formal parameters, and its calling rules are the same as those of ordinary functions. Sometimes default formal parameters can bring great convenience. For example, class Clock’s member function SetTime() can have default parameters as below:

If this member function is called without actual parameters, the clock time will be set to 0 AM according to the default values.

3.Inline Member Function

We know that calling a function costs some memory resources and CPU time to pass parameters, to return value, and to record the calling state in order to correctly return and run after the call finished. If a member function needs to be called frequently, and the function code is simple, such a function can also be defined as an inline function. Like the ordinary inline function introduced in Section 3, the function body of an inline member function will be inserted into wherever it is called at the compiling time. By doing this, the cost of function call is reduced and the execution efficiency is improved, while the code length after the compiling is increased. So we should carefully weigh the pros and cons, and only define fairly simple member functions as inline functions.

Two ways can be used to define an inline function: implicit definition and explicit definition.

Implicit definition puts the function body directly into the class body. For example, to define the member function ShowTime() of class Clock as an inline function,we can write:

To keep the definition simple, we commonly use the keyword inline to define a function explicitly. That is to say, we put the keyword inline before the function’s return type in the function definition, and don’t include the body of ShowTime() in the definition of class:

It is the same as the implicit declaration mentioned above.

4.2.4Object

Class is an abstraction mechanism; it describes the common attributes and behaviors of a type of problem. In C++, an object of a class is a specific entity (or instance) of the class. For instance, take all the employees in a company as a class, and then each employee is a specific instance, i.e., an object.

In Chapter 2 we introduced basic data types and user-defined types. In fact, each data type is an abstraction of a class of data, and every variable defined in a program is an instance of its data type. If we treat class as a user-defined type, then the object of a class can be treated as a variable of this type. For this reason, sometimes both ordinary variables and objects of a class are called objects in this book.

Defining an object is the same as defining an ordinary variable – they both use the following form:

For example:

It defines an object of class Clock, called myClock.

After defining a class and its objects, we can access the public members of the objects. For instance, set and display the time of object myClock. We use operator ‘.’ to achieve this accession, and the general form is:

For example, to access the function member ShowTime() of myClock, which is an object of class Clock, we use:

Only public members of a class can be accessed from outside, while inside the class, every member can be accessed directly by its name. This provides an effective control over the accessing range.

4.2.5Program Instance

Example 4.1: Complete implementation of class clock.

Analysis: This program can be divided into three comparatively independent parts: the first is the definition of class Clock; the second is the implementation of the function members of class Clock; the third is the main function. From earlier discussions we know that defining a class and its function members is only a description of the problem with high abstraction and encapsulation. To solve the problem, we also need messages passing between instances, or objects, of the class. Here the main function is to define the object and pass the messages.

Beware that the member function SetTime is a function with default parameters, and it has three default parameters. The function ShowTime is an explicitly defined inline member function, because it has only a few statements. In the main function, we first declare an object myClock of class Clock, and then use this object to call its member function. The first call sets the time to its default value and outputs it. The second call sets the time to 8:30:30 and outputs it. The running result of the program is:

4.3Constructor and Destructor

The relation between class and object is like the relation between basic types and their variables, i.e., general and special. An object differs from other objects mainly in two ways: the first is its name, which is the external difference; the second is the values of its data attributes, which is the internal difference. Just like how we can initialize a basic-type variable when defining it, we can also initialize the data members of an object when defining the object. Setting values to data members of an object while defining the object is called the initialization of that object. After the use of a specific object is finished, we usually need to do some cleaning. In C++, there are two special function members used to do this work, called constructors and destructors.

4.3.1Class Constructors

To understand the constructor of a class, first we have to understand the procedure of creating an object. To this end let’s see how a variable of a basic type is initialized. When a program is running, every variable of it takes up some memory space. Initializing a variable during its definition means to set an initial value to the memory space when it is allocated. This kind of initialization seems simple in C++ source code, but in fact, to achieve this initialization the compiler needs to generalize some codes automatically according to the variable type.

The procedure of creating an object is similar: during the execution of a program, when it comes to the declaration statement of an object, the program will apply for some memory space from the operating system to store the new object. We hope that the program would fill the initial values for data members of the object when allocating them with memory space, just like what it does to common variables. Unfortunately, compared with common variables, a class object is too complex, and the compiler has no idea how to generalize codes to achieve the initialization. So programmers have to write codes manually to do this initialization. If a programmer hasn’t written his initialization code but rashly gives some initial value to an object during its definition, he won’t have the object initialized and probably will get a compiler syntax error. That’s why none of the objects in the former examples of this book have been initialized.

Despite this, the compiler system of C++ still does lots of work for us on the problemof object initialization. C++ strictly defines the interface form of an initializing program, and has a set of automatic calling mechanisms. Here the initializing program is called a constructor.

A constructor constructs an object with specific values when creating this object, and initializes it to a specific state. The constructor is also a member function of a class, and besides the common characters of member functions, the constructor has some special attributes: the constructor shares the same name with the class and has no return value and the constructor is usually declared as a public function. Once a class has a constructor, the compiler will automatically insert the calling code on the constructor at the place where the new object is defined. So we often say the “constructor is automatically called where the object is defined”.

If a class has no constructor, the compiler will automatically generate a default constructor – with no parameters and doing nothing. If a class has defined a constructor (whether with parameters or not), the compiler will not generate any other constructor.

In the Clock class example, there is no function that has the same name as the class, i.e., there is no constructor. Thus the compiler will generate a default constructor for the class, while the constructor does nothing. So why do we need a constructor that does nothing? That’s because calling the constructor of an object during its creation is a necessary behavior in a C++ program. If the programmer defines a proper constructor, an object of class Clock will receive an initial time value during its creation. Now we modify class Clock as follows:

Implementation of constructor:

Let’s see what the constructor does when creating the object:

When creating object c, the constructor is called implicitly and the actual parameters will be used as the initial values.

Because in class Clock there has already been a constructor, the compiler system won’t generate a default constructor. The user-defined constructor here has formal parameters, so initial values of the parameters should be given, which are used as formal parameters when calling the constructor during the creation of an object. If we define an object in the main function as follows:

There will be a syntax error when compiling the program, because we do not provide the actual parameters.

As a member function of a class, a constructor can access all data members of the class directly; it can be an inline function, have the parameter list and default parameter values, and be overloaded. With these characters, we can choose the proper form of constructor according to the different needs of problems, to initialize an object to a specific state. Let’s see the following example in which an overloaded constructor is called:

The above example shows cases of constructors with and without parameter forms. A constructor without parameters is also called a default form constructor.

Also note that the memory space an object occupies is only used to store member data, and there is no copy of member functions in every object.

4.3.2 The Copy Constructor

Many of us have used a copy machine: when we need a copy of a file, we fetch white paper and use a copy machine to get a copy exactly the same as the original. There are lots of examples of making a copy in our real world. Since the object-oriented programming design aims to faithfully reflect problems in the real world, C++ needs the ability of copying an object.

There are two ways to generate a copy of an object. The first one is to build a new object and set each data member of the new object exactly to the values from the old object. This method works but is a little fussy. Why don’t we make a class that has the ability of automatically copying an object of its own? That’s what a copy constructor does.

A copy constructor is a special constructor. It has all the features of common constructors, while its formal parameter is a reference to an object of the class. It is used to initialize a new object of the same class by the existing object specified by the formal parameter.

Programmers can define a specific copy constructor as needed, to achieve the copying of data members of objects in the same class. If the programmer hasn’t defined a copy constructor for a class, the system will automatically generate a default copy constructer when needed. This default copy constructor copies the value of every data member to the new object, or we can say it clones the old object to get a new one. The new object and the old one have exactly the same data members.

Here is the common way to define and implement a copy constructor:

Let’s see an example of a copy constructor: locate a point on the screen according to the coordinates x and y. Class Point is defined as follows:

Inline constructor and copy constructor are declared inside the class body. The copy constructor is implemented as follows:

Common constructors are called when the objects are created, while copy constructor would be called in any of the following three situations:

1.When using an object to initialize another object of the same class. For example:

2.When calling the function and passing parameters, if the formal parameter of a function is an object of a class. For example:

3.When the function is finished and is going to return to its caller, if the return value of a function is an object of a class. For example:

In the example above, why is the copy constructor called when returning the function value? On the surface, function g seems to return A to the main function. But A is a local object of g() and will disappear when function g terminates, and thus A can’t exist after the process returns to the main function (this will be further discussed in Chapter 5). So in this situation, the compiler will generate a temporary unnamed object in the main function, whose lifetime is only in the statement of the function call, i.e., statement ‘B = g().’ When executing statement ‘return A;,’ actually the copy constructor is called to copy the value of A as a temporary object. After function g terminates, object A disappears while the temporary object exists in statement ‘B = g().’ After computing the statement, this temporary object will also disappear.

Example 4.2: Complete program for point class.

In the main function, there are three sections that demonstrate three situations of calling a copy constructor, respectively.

Running result:

Readers may wonder at how the copy constructor and default copy constructor in the example above do the same job: they both pass values in the old object directly to the new object. Then why do we need to write our own copy constructor in this situation? Yes, if things always go like this, we don’t have to write a copy constructer particularly; just using the default copy constructer is enough. However, remember that when using copy machines we sometimes only want to copy part of a page, in which case we can use white paper to cover the unneeded part before the copying. There are many other situations, such as when we want to copy with zooming in or out, etc. Things are similar in object copying in programs, where we can copy an object according to our needs. Readers can try to modify the above example code to make the copy constructor construct a new point with a certain amount offset from the old point. Besides, when some of the data members are of pointer types, default copy constructer can only do shallow copying, which causes security problems. To achieve the correct copy, i.e., deep copy, we must write our own copy constructor. Details about pointer types and issues of deep copy will be introduced in Chapter 6. Examples in Chapter 9 will show more about deep copy.

4.3.3Class Destructor

Besides a good start, we also need a good finish. Programming also needs to consider the round-off work. In C++, when an object disappears, we often need to take care of the mop-up.

Will an object disappear? Sure! Like every living thing in the world, objects in programs have their own lifetime. We already know that an object is created when it is defined, and when the object will disappear involves the issues of the object’s lifetime, which will be discussed in Chapter 5. Here we only consider one situation: if a local object is defined inside a function, then it will disappear when the function terminates.

What should be done when an object is going to disappear? The most classical situation is: when constructing an object, some resources are allocated by the constructor, such as dynamically allocating some memory space. When the object disappears, these resources have to be released. This is reflected in Examples 7.10 in Chapter 7, in some examples in Chapter 9, and is detailed in Section 7.7.4 “Running Result and Analysis”. Dynamic memory allocation is detailed in Chapter 6. In this chapter, we just make a simple introduction of using destructors to do the round-off work.

Basically, a destructor does almost exactly the opposite of what a constructor does. A destructor does some round-off work before the object disappears. Adestructor is called automatically when the lifetime of the object ends. After calling the destructor, the object disappears and the corresponding memory space is released.

Like a constructor, a destructor is often a public member function. Its name is composed by the class name with the prefix ‘~’. The destructor has no return value. Differently from constructors, destructors don’t accept any parameter, while it can be a virtual function (Chapter 8 will introduce virtual functions). If not declared explicitly, the system will also generate a default destructor, which does nothing.

For example, we add an empty inline destructor to class Clock, which has the same function as the default destructor that the system generates.

Generally speaking, if we want the program to do some work automatically before the object disappears (without manually calling a function), we can write the work into the destructor.

4.3.4Program Instance

Example 4.3: Budget for rebuilding a pool with Circle class.

We need to build a circle aisle and a circle fence around a pool shown in Figure 4.2. Fencing costs 35$/m and an aisle costs 20$/m2. The width of the aisle is 3 meters, and the radius of the pool will be input from the keyboard. Write a program to compute and output the costs of the aisle and the fence.

The pool and the fence can be viewed as two concentric circles. The circumference of the larger circle is the length of the fence, the area of the circular ring is the area of the aisle, and the area of the circular ring is the difference between the larger circle’s area and the smaller circle’s area. We can define a class Circle to describe this problem: the radius of the circle is a private data member, and the function of class Circle is to compute the girth and the area of the circle. We use two objects to represent the fence and pool, and then we can get the area of the aisle and the girth of the fence. Given the unit prices, we can get the budget of the whole project. Let’s see the implementation of the program:

Fig. 4.2: The pool.

Running result:

When running the main function, we first define three variables of type float. After reading the radius of the pool, we create an object Pool using the constructor to initialize its data member with the input value. Then we create the second object PoolRim to represent the fence. The two objects call their function members respectively, and then the problem is solved.

4.4Combination of Classes

Lots of problems in the real world are too complicated to imagine, but complex problems can be divided into simpler subproblems. By gradual division, we can finally describe and solve the problem. Actually this method of division and combination has been widely used in industry for a long time. For example, an important part of television is the kinescope, and many TV factories choose to buy kinescopes from special kinescope producers instead of producing kinescopes themselves. At the same time, a kinescope producermay sell kinescopes tomany TV factories. Through this specialization and cooperation, productivity is largely improved. Now an important way to improve the productivity of software is to achieve the industrial production of software.

In object-oriented programming design, we can divide and abstract a complex object into a combination of simpler objects, and combine it using component objects that are easy to understand and implement.

4.4.1Combination

We have always used the method of combination to build a class, so let’s see the class Circle below:

As we can see, class Circle includes data of type float. We are used to using basic types of C++ as the components of a class. In fact, the data member of a class can be either of basic type or of user-defined type, or a class object. So we can use the method of combination, using existed class objects to build new class. These combined classes, compared with integrated classes, are easier to design and implement.

The combination of classes describes the situations where a class object is embedded in another class as a data member. The relationship between the two classes is that of including and being included. For example, we use a class to describe the computer system. First we can divide it into hardware and software. Hardware includes CPU, memory, and I/O devices; software includes system software and application software. Each of these components can be further divided. Described from the view of class, this is a combination of classes. Figure 4.3 shows their relationships. We can describe a complex problem using the method of combination, which conforms to the logic of stepwise refinement.

When defining an object, if there are embedded object members in this class, then each embedded object is to be created first automatically, because the component object is a part of the complex object. Thus, when creating an object, both the basic type members and the embedded object members should be initialized. It is important to understand the order of calling these objects’ constructors.

The general form of the constructor of a combined class is:

Fig. 4.3: Combination relationships in the class of a computer system.

Here, ‘embedded_object1(parameter_list),embedded_object2(parameter_list),...’ is called the initialization list, which is used to initialize embedded objects.

Data members of basic types can also be initialized like this, and using an initialization list is more efficient than using assignment statements. For example, the constructor of class Circle can also be written like this:

When creating an object of a combined class, not only its own constructor will be called, but also the constructors of its embedded objects will be called. The calling order is:

  1. Call the constructors of the embedded objects according to their definition order in the class.
  2. Call the constructor of the combined class.

When defining an object of a combined class, if there is no initial value given for the object, then the default constructor (with no parameter) will be called, and the default constructors of the embedded objects will be called together. The calling order of destructors is exactly the opposite as that of constructors.

How does one write a copy constructor for a combined class? If the programmer has not written a copy constructor, the compiler system will generate a default copy constructor when needed. If this default copy constructor is called when creating an object, then the compiler will automatically call the copy constructors of the embedded objects.

If we want to write a copy constructor for a combined class, we also need to pass parameters for the copy constructors of the embedded objects. For example, suppose object b of class B is embedded in class C. Then the copy constructor of class C will be like this:

Now let’s see an example.

Example 4.4: Combination of class with Line class.

We design a Line class to represent a line, and use a Point class mentioned in the last section to represent a point. This problem can be solved using combined classes: the Line class includes two objects of the Point class, p1 and p2, as its data members. Class Line has the function of computing its length, which is implemented in its constructor. The source code is as follows:

Running result is:

When calling the main function, two objects of class Point are created first, and then the object line of class Line is created. After that, the second object line2 of class Line is created using a copy constructor. Lastly the distance between the two points is output. During the running of the program, the copy constructor of class Point is called six times, all of which are called before the constructor of class Line is executed. The six calls on the copy constructor of class Point happen respectively when: two objects of class Line are created, embedded objects are initialized, and line2 is created using a copy constructor. The distance between the two points is computed inside the constructor of class Line, and is stored in the private data member len which can only be accessed by the public member function GetLen().

4.4.2Forward Declaration

We know that in C++ a class should be defined before being used. But when we deal with some complex problems, considering the combination of classes, we may come to a situation where two classes reference each other, which is also called circular dependency. For example:

Here the parameter of the public member function f of class A is an object of class B, while the parameter of the public member function g of class B is an object of class A. Because a class must be defined before it is used, no matter the definition of which class is put at the front, it will cause a compiling error. One way to solve this problem is to use forward declaration. Forward declaration means telling the name of an undefined class to the compiler before the class is used, so the compiler knows that it is a class name. When this name is used in the program, the compiler won’t consider it as an error, and the complete definition of the class can be put elsewhere in the program. By adding forward declaration to the program we talked above, the problem will be solved:

Forward declaration can help in some situations, but it is not omnipotent. It is important to know that even if forward declaration is used, we can’t define an object of this class or use an object of this class in an inline function before we provide a complete definition of the class. Let’s see the code below:

For this program, the compiler will report an error. It is because the forward declaration of class Fred only tells that Fred is a name of a class, while there is no complete definition of class Fred. So we can’t define an object of class Fred as a data member of class Barney. So for two classes it is illegal to mutually make an object of one class be a data member of the other.

Let’s see another program:

The compiler will report an error because the inline function in class Barney uses an object of class Fred pointed by x, while class Fred hasn’t been completely defined. To solve this problem, we can change the definition order of the two classes, or change function method() to noinline and provide the definition of the function after the complete definition of class Fred is given.

Remember that when using forward declaration, only the symbol declared can be used, and any detail of the class can’t be used.

4.5UML

In previous sections we have learned the concepts of class and object in OOD in C++. In this section, we will introduce a diagram representation to describe these concepts more visually, to facilitate easier communication among people.

Nowadays there are many widely-used object-oriented modeling languages. In the last two versions of this book, we used Coad/Yourdon notation, and in the third version we use UML (Unified Modeling Language). UML was first adopted and popularized by the OMG (Object Management Group) in 1997. Now it has become the representative standardized language and is widely used. UML includes all the diagram symbols that we need.

UML language is a large and complex system modeling language. The aim of the language is to solve the problem of visual modeling for the whole development process of OO software. Complete descriptions of UML are far beyond the range of this book, and this section only introduces the part of UML language that is directly related to this book. With this study, readers can selectively understand the characteristics of UML language, use simple UML diagrams to describe the key concepts in this book such as class and object in C++, and lay a solid foundation for learning and developing software in the future. Actually the content introduced here is one of the most basic parts of UML. If readers want to know more about UML, please refer to related websites and books.

4.5.1Brief Introduction of UML

UML language is a typical OO modeling language, rather than being a programming language. In UML, concepts are represented by symbols, and relations between concepts are represented by lines.

OO modeling language can be traced back to the 1970s. In the 1980s, lots of OO programming languages came out, marking the maturity of the OO Method. In the mid-1990s, many OO analysis and design methods came out, which introduced various identifiers independent of languages. Among them, the Coad/Yourdon method was one of the earliest OO analysis and design methods. These methods were easy to understand and suited beginners that study OO technique. Booch was the one of the earliest advocates of OO method. He put forward the concept of OO software engineering, which is a good way to design and build systems. Rumbaugh, etc. presented an object-modeling technique (OMT) that uses the OO concept. The OOSE method introduced by Jacobson is suitable for supporting business engineering and demand analysis.

Among various modeling languages, users can hardly distinguish the characteristics of different languages, so they cannot find a language that suits their application characteristics. Besides, although different languages are similar in many aspects, their representation forms vary a lot, which greatly hinder the communications between users. Thus it was objectively necessary to find a unified modeling language. In October 1994, Grady Booch and Jim Rumbaugh began to work on this. They first unified Booch and OMT methods together, and published the public version of this modeling language in October 1995, calling it UM 0.8 (Unified Method). By the end of 1995, the founder of OOSE, Ivar Jacobson, joined them. With the efforts of Booch, Rumbaugh, and Jacobson, the new version was published in June 1996, named UML 0.9, and UM was renamed UML (Unified Modeling Language). On 11/17/1997, OMG accepted UML 1.1 as an OO-based unified modeling language. In June 2003, OMG accepted the 2.0 version of UML.

The important content of UML is different kinds of diagrams, which are used to represent the static structure and dynamic behavior of software models, and the organization and management of modules. This book mainly uses diagrams in UML to represent classes, objects, and their relationships in software, using the basic class diagram that belongs to Static Structure Diagrams.

4.5.2UML Class Diagrams

A class diagram is a diagram that is composed of class and related static relationships. A class diagram shows the static structure of a software model, the inner structure of the class, and the relationships between this class and other classes. UML also defines an object diagram, and a static object diagram is an instance of a specific object diagram, which shows an instance of a specific state of a real software system. Class diagrams can contain objects, and a class diagram containing only objects without any class is an object diagram. We can consider object diagrams as a specific instance of class diagrams. The use of object diagrams is very limited, so in versions above UML1.5 it is explicitly pointed out that tools software is not required to implement this kind of diagram.

Using class diagrams, we can describe all the OO concepts introduced in this book, such as class, template class, and the relationships among them. Class diagram is composed of diagram symbols that describe a class or object, and diagram symbols that describe their relationships. Now we detail diagram symbols.

1.Class and Object

The basic thing to do in a class diagram is to describe class graphically, to represent the name, data member, and function member of a class, and the access control attributes of each class member.

In UML language, we use a rectangle that is divided into three parts vertically to represent a class. The class name is written in the top section, data members (data, called attributes in UML) are in the middle section, and function members (behavior, called operation in UML) are in the bottom section. Surely it can be seen as three rectangles, which represent the name, attributes, and operations of a class respectively, folded vertically. Besides the name section, the other two parts are optional, i.e., attributes and operations of a class can be hidden, and simply a rectangle with a class name in it can represent a class.

Fig. 4.4: Two ways to represent class Clock from Example 4.1; (a) Complete representation of class Clock, (b) Simple representation of class Clock.

Let’s take class Clock in Example 4.1 for example, to see the representation of a class. Readers can refer to the source code in Example 4.1.

Figure 4.4 demonstrates different ways of representing class in UML. Figure 4.4a gives a complete representation of a class with visible explanations of data and behavior. Figure 4.4b is a representation that hides data and behavior. Obviously, Figure 4.4b is simple but has little information. Different representation methods are used in different situations according to the aim of drawing the figure. If we want to describe the members of the class and their access control attributes in detail, we should use the method shown in Figure 4.4a; if we care only about relations among classes and not the things inside the class (for example, in the beginning of designing a program when we need to mark out classes), we should use the method shown in Figure 4.4b.

Now let’s see how to represent data and function members in the complete representation of a class.

Depending on the level of detail of a diagram, each data member can include its access control attribute, name, type, default value, and restrictions. The simplest situation is to only include the member name, while the other parts are all optional. The grammar of representing a data member in UML is:

At least the name of the data member must be given, while others are optional. Here,

Access control attribute: can be public, private, or protected, corresponding to ‘+’, ‘−’, and ‘#’ respectively in UML.

Name: string to identify the data member.

Multiplicity: multiplicity of attributes in the following [].

Type: type of data member. It can be a basic type, such as integer, floating, Boolean, etc., and can also be user-defined type or a class.

Default value: initial value assigned to this member.

Restriction: an explanation of restriction to this data member, such as ‘{read only}’ means that this member is read only.

In Figure 4.4a class Clock, data member Hour is described as:

Access control attribute ‘−’ means that it is a private member, its name is ‘Hour’, its type is ‘int’, and there is no default value or restriction.

In the following example, we represent a public data member called size, with type Area, and a default value of (100,100).

Each function member can include its access control attribute, name, parameter list, return type, and restriction. The simplest situation is to only include its name, since other parts are optional, according to the level of detail of the diagram. The grammar of representing a function member in UML is:

Access control attribute: can be public, private, or protected, corresponding to ‘+’, ‘−’, and ‘#’ respectively in UML.

Name: string to identify the function member.

Parameter list: includes parameters separated by comma. The syntax form to represent the parameter list is ‘[direction] name: type=default value.’ Note that this form is different from that in cpp files. The direction tells whether the parameter is used to represent in, out, or inout.

Return type: type of the return value. It can be basic type, user-defined type, class, or a pointer of the above types.

Restriction: an explanation of restriction to this function member.

In Figure 4.4a class Clock, function member SetTime is described as:

Fig. 4.5: Different representations of the object myClock.

Access control attribute ‘+’ tells that it is a public member. Its name is ‘SetTime,’ its parameter list is in the bracket, the return type is ‘void,’ and there are no restrictions.

In UML language, when using a rectangle to represent an object, the name of the object should be underlined. The whole name of the object is written in the top section of the rectangle, which is composed of the class name and object name that are separated by a colon. The syntax form is ‘object_name:class_name’, while in some situations the object name or the class name can be omitted. Data members and their values are written in the bottom section, and data members are optional.

We take the class Clock as an example:

Figure 4.5 shows different ways to represent an object in UML. The left figure gives a complete representation of an object with data. The right figure only gives the name of the object. The principle of choosing which way to represent an object is the same as that of choosing representations of a class.

2.Diagram Symbols for Relationships

We have introduced the diagram representations of class and object in UML, although with these diagram symbols only, we still can’t describe relationships between class and class, object and object, and class and object in a large system. For example, there can be inheritance relationships or calling relationships between classes, etc.

UML uses lines with specific symbols or dashed lines to represent relationships. Let’s see how to use these symbols to describe the relationships this book has mentioned, such as calling, combination of classes, inheritance, etc.

a)Dependency

Dependency between classes or between objects indicates that changes of one thing may affect another thing that uses it, while it is not true for the opposite. When a class uses another class as a parameter of its function member, we use the dependency relationship. Generally, calling and friends (will be discussed in Chapter 5) between classes, and instantiation of class all belong to this kind of relationship. Simple, original dependency is sufficient in most of the situations when dependency relationship is needed. But to demonstrate the nuance of different dependency meanings, UML also defined some stereotypes for dependency. The most commonly used stereotype is ‘use.’ When we need to represent the uses of relationship between two classes, we use the stereotype <<use>>. Other stereotypes will not be detailed here, and some stereotypes will be used in latter chapters, which will be discussed then.

Fig. 4.6: Dependency.
Fig. 4.7: Representation of association in UML.

Figure 4.6 shows how to represent dependency between classes. UML uses a dashed line pointing to the thing depended on to represent dependency between classes. In Figure 4.6, class A is the source and class B is the destination, meaning that class A uses class B, or class A depends on class B.

b)Relationship – Association

Association is used to represent the interaction relationship between objects from different classes. In UML, a line segment is used to represent association between two classes (or the same class). Generally there are multiplicities at both ends of the line segment. Multiplicity is the most important characteristic of association. Multiplicity in one end of a line segment means: each object on the other end of the line segment should have interaction with a certain number of objects of the class on this end. Figure 4.7 shows how to represent association in UML.

In Figure 4.7, ‘Multiplicity A’ decides how many objects in class A will have interaction with each object in class B, and it is the same meaning for ‘Multiplicity B.’ The symbol forms of multiplicity and their meanings are list in Table 4.1.

c)Inclusion Relation – Aggregation and Composition

Inclusion relation between classes or objects is described by two concepts in UML: aggregation and composition, which belong to a special kind of association. Aggregation in UML means that the relation between classes is that of whole and part, and ‘include,’ ‘compose,’ and ‘divided into ... parts’ are all aggregations. A line segment having two end points is an example of aggregation. Aggregation can be further divided into shared aggregation and composition aggregation (abbreviated as composition). For example, a team has lots of members, while each member can be a member of another team, i.e., a part can join multiple wholes, which is called shared aggregation. Another example is that the whole owns each part, and if the whole disappears, each part disappears too, which is called composition. Composition is a simple form of aggregation, but it has stronger ownership relation: the whole owns every part, and if the whole disappears, each part disappears too. For example, a view window is composed of a title, outline, and display area. In UML, aggregation is represented by a hollow lozenge, and composition is represented by a solid lozenge.

Tab. 4.1: Symbol and Meaning of Multiplicity.

Symbol Meaning
* Any amount of objects (includes 0)
1 One object exactly
n N object exactly
0..1 0 or 1 object (means that association is optional)
n..m N objects at least, m objects at most (n, m are integers)
2, 4 Discrete association (for example 2 or 4)
Fig. 4.8: Aggregation and composition.

Figure 4.8 shows how to represent aggregation and composition between classes.

Example 4.5: Use UML to represent relations between class Line and class Point in Example 4.4.

Data members of class Line include two objects of class Point, p1 and p2, so the multiplicity is 2. The objects of class Point are parts of class Line, so aggregation should be used to describe the relation. Besides, the constructor of class Line uses two objects of class Point, p1 and p2, so we can use dependency to describe this relationship clearly. Figure 4.9 uses UML to represent these relations between class Line and class Point.

d)Inheritance Relation – Generalization

The inheritance relation between classes (which will be discussed in Chapter 7) is called generalization in UML. UML uses a line with a triangle to represent this relation. An angle of the triangle points to the parent class, and the line on the opposite side points to the child class. Figure 4.10 shows this generalization relation. Child class 1 illustrates single inheritance and child class 2 illustrates multiple inheritance.

Fig. 4.9: Relations between class Line and class Point.
Fig. 4.10: Generalization relation.

3.Comments

To represent classes, objects, and relations among them in a more lively fashion, UML uses some auxiliary symbols besides the basic symbols introduced above. Here we introduce comments.

Comments in UML are an important kind of symbol, and can exist independently. Comments are attached on elements or on element sets for description or annotation. By commenting, we can add information like description and comment on the model. In UML, comments are represented as rectangles with angled folds and are linked to other elements of UML using a dashed line.

Example 4.6: Description of class Line and class Point with comments (see Figure 4.11).

Fig. 4.11: Relation between class Line and class Point with comments.

4.6Program Instance – Personnel Information Management Program

In this section, we take the personnel information management of a small company for example, to see how to design a class and its member functions.

4.6.1Design of Class

A small company needs to store the ID, grade, and payment of each employee, and display all the information. According to these needs, we design a class employee. In this class, besides defining a constructor and destructor, we also have to uniformly define operations on personnel information. Datamembers in the class employee include ID, grade, and payment, and function members in class employee include getting ID, computing and getting the grade, and setting and getting payment. The constructor is used to set initial values for ID, grade, and payment. The class diagram is shown in Figure 4.12.

Fig. 4.12: UML diagram for class employee.

4.6.2Source Code and Description

Example 4.7: Personnel information management.

The whole program is divided into two independent documents: employee.h includes declaration and definition of classes, and 4_5.cpp is the main function file.

4.6.3Running Result and Analyses

Running result:

In the above program, we extract common parts of an employee’s information, using class employee to abstract them to data members individualEmpNo, grade, and accumPay, then we write operating functions according to each data member to achieve access to private data members. In function main(), we built four objects of class employee and applied the same operation on them, such as setting ID, grade, and payment, and displaying this information.

4.7Summary

Object-oriented programming uses abstraction, encapsulation, inheritance, and polymorphism to maximize the reusability and scalability of codes, to improve the productivity of software, and to control the cost of development and maintenance of software. Class is the core of OO programming. Using class, we can realize the encapsulation and hiding of data; using the inheritance and the derivation of class, we can make abstract descriptions of problems in depth.

Class is an encapsulation of function and data, which are related logically, and it is an abstract description of a problem. In fact, class belongs to the user-defined type. Different from basic data types, class includes operations on the data.

Access control attributes control access to the class members, by which we achieve data hiding. An object is an instance of class. An object is unique compared with other objects of the same class in that it has its own attributes– data members. Setting values to data members when defining an object is called initializing the object. After the use of an object is finished, some cleaning work must be done. Constructors and destructors in C++ are used to do the initialization and the cleaning work. Copy constructors are a special constructor that use an existing object to initialize a new object.

Exercises

4.1 Explain the use of public and private. What are the differences between public member and private member?

4.2 What is the use of keyword protection?

4.3 What are the uses of constructors and destructors?

4.4 Can data members be public? Can function members be private?

4.5 Class A has a data member int a. If we define two objects of class A, A1, and A2, can the values of a in the two objects be different?

4.6 What is a copy constructor? When is it called?

4.7 What is the difference between a copy constructor and operator =?

4.8 Define a class Dog, which includes attributes like age and weight, and functions to operate on the attributes. Implement and test this class.

4.9 Design and test a class Rectangle whose attributes are coordinates of the left bottom point and the right top point. This class should be able to compute the area of the rectangle.

4.10 Design a class ‘employee’ for personnel management. Considering the generality, we only abstract attributes that everyone has: ID, sex, birth date, and PIN. Here, birth date should be declared as an embedded object of class ‘Date.’ Use member functions to realize input and display data. A constructor, destructor, copy constructor, inline member function, function with default values, and combination of classes must be used.

4.11 Define a class Rectangle with attributes length and width, and use a function member to compute area.

4.12 Define a class data type that can deal with char, integer, and float data types. Give its constructor.

4.13 Define a class Circle with data member radius, and use function member GetArea() to compute area. Create an object of Circle to test the code.

4.14 Define a class tree with data member age, function member grow(int years) which increases ages by years, and age() to display the value of ages.

4.15 Draw an UML diagram of class Circle according to its source code in Example 4.3.

4.16 Draw UML diagrams to represent the inheritance relations among class ZRF, class SSH, and class Person according to the following code:

4.17 In a course selection system of a college, there are two classes: class CourseSchedule and class Course. The relations between the two classes are: the parameters of the member functions of class CourseSchedule, add and remove, are objects of class Course. Please use UML to represent this dependency explicitly.

4.18 For a department personnel information system, we need to build a model for the relation between Department and Teacher. The relation can be described as: each Teacher belongs to 0 or multiple Departments, while each Department includes at least one Teacher. Please draw a UML diagram according to the above relation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset