5Data Sharing and Protecting

An important characteristic of C++ is that it is a language in which it is suitable to write large complex programs and enables data sharing and protection mechanisms. In this chapter we will introduce the concept of scope, visibility, and the lifetime of identifiers, as well as the sharing and protection mechanism of class members. At last we will introduce the multifile structure and compilation preprocessing directives, that is, how to use several source files to organize large programs.

5.1Scope and Visibility of Identifiers

Scope concerns the effective range of an identifier, while visibility concerns whether an identifier can be referenced. We know that a variable declared in a function can only be effective in this function, which is due to the restriction of the scope and visibility of variables. Scope and visibility are interrelated but different.

5.1.1Scope

Scope is the region where an identifier is effective in the program body. The scopes of identifiers in C++ include function prototype scope, block scope (also termed local scope), class scope, and file scope.

Function Prototype Scope

Function prototype scope is the smallest scope in a C++ program. In Chapter 3 we introduced how there must be a type specification of formal parameters in the function prototype. Function prototype scope is the effective area of formal parameters during the declaration of function prototype. The following is an example of a declaration of a function.

The effective area of identifier radius is between the two brackets of Area’s parameter list, and you cannot reference this identifier in other places of the program. Therefore, the scope of identifier radius is called the function prototype scope. Since formal parameter type is the only factor that works in the formal parameter list of a function prototype, omitting the identifier will not affect the compiling and running results. Considering the readability of the program, we usually give each formal parameter an identifier while declaring a function prototype.

Block Scope

To understand block scope, let us first look at an example:

Here, variable b is declared inside function fun(), and is initialized by the value of a. Then variable c is declared inside the if statement. Both b and c have block scopes, though they belong to different block scopes. A block is a section of program that is enclosed in a pair of brackets. In this example, the function body itself is a block; the branch body after the if statement is a smaller block – the two blocks have an inclusive relationship. Identifiers declared in a block have a scope starting from the declaration and ending at the end bracket of the block. Therefore, the scope of variable b is from its declaration to the end of the block it is in (i.e., the whole function body), and the scope of variable c is from its declaration to the end of the block it is in (i.e., the branch body). A variable that has a local scope is also called a local variable.

Class Scope

A class can be viewed as a collection of named members, and member M of class X has a class scope. The access method of M is:

If there is no declaration of local scope identifiers of the same name in X’s member functions, then we can access member M inside this function. That is, M works in all these kinds of functions. The use of class encapsulation is to limit the scope of data.

Access M through the expression x.M or x::M, which is the most basic method to access object members in a program.

Access M through the expression prt−>M, where prt is a pointer that points to an object of class X. More details about pointers are in Chapter 6.

Other special access methods and the scope rules of classes and their objects in C++ will be discussed in the following chapters.

File Scope

Declarations that appear in none of the scopes talked above have file scopes. The scope of this kind of identifier starts from the declaration point and ends at the end of the file. The global variables declared in Example 5.1 have file scope, and they are effective in the whole file.

Example 5.1: Examples of scope and visibility.

In this example, variable i declared before the main function has file scope, and its effective area is the whole source file. We initialize i to 5 at the beginning of the main function, then declare a variable of the same name in block 1 and initialize it to 7. The first output result is 7, because the variable with local scope covers the variable with file scope – that is, the variable with file scope becomes invisible (this is the visibility issue, which we will discuss later). When the program finishes executing block 1 and starts the second output, the output is the value of the variable with file scope, which is 5.

A variable with file scope is also called a global variable.

5.1.2Visibility

Now, let us have a look at the effective area of a variable from the perspective of an identifier reference – that is, the visibility of the identifier. When a program executes to one point, the identifier that can be referenced is the identifier that is visible at this point. To understand visibility, we first look at the relationship between different scopes. File scope is the largest scope, followed by class scope and local scope. Figure 5.1 describes the general relationship between scopes. Visibility represents what you can see when you “look” from inner scope to outer scope. Therefore, visibility and scope are closely interrelated.

The general rules of scope visibility are:

You must first declare an identifier before referencing it.

You cannot declare identifiers of the same name in the same scope.

If identifiers of the same name are declared in different scopes that do not include each other, these identifiers will not affect each other.

If identifiers of the same name are declared in two or more scopes with inclusion relations, the outer identifiers are invisible from inner scopes.

Fig. 5.1: Relationships of scopes.

Look at Example 5.1 again. This is an example of file scope including a local scope. You can reference the variable with file scope outside block 1, which means that it is visible. When the program execution enters block 1, it can only reference variables of the same name with local scope, while variables of the same name with file scope are hidden.

Rules of scope and visibility do not only apply to simple variables, but also apply to user-defined data types and objects of classes.

5.2Lifetime of Object

Each object (including simple variable) has a birth time and disappearing time. The period from birth to end is the object’s lifetime. During the lifetime, the object will remain in its state (the value of data member) and the variable will retain its value, until they have been updated. In this section, we will use objects to represent both class objects and general variables. The lifetime of an object can be divided into static lifetime and dynamic lifetime.

5.2.1Static Lifetime

If the lifetime of an object is the same as the running period of the program, then it has a static lifetime. All the objects with file scope have a static lifetime. If we want to declare an object with local scope inside the function to have a static lifetime, we have to use the keyword static. For example, the variable i declared in the following statement is a variable that has a static lifetime, which is also called a static variable:

We will discuss static members in Section 5.3.

5.2.2Dynamic Lifetime

Besides the two situations discussed above, all the other objects have a dynamic lifetime. Objects declared to have a dynamic lifetime in local scope are customarily called local lifetime objects. Dynamic lifetimes start from the declaration point and end at the ending part of the identifier’s scope.

Example 5.2: Lifetime and visibility of variables.

Result:

Example 5.3: A clock program that has static and dynamic lifetime objects.

We still use class Clock as an example here. In this example, we declare objects that have function prototype scope, local scope, class scope, and file scope separately, and discuss their visibility and lifetime.

Result:

This program includes variables and objects of all kinds of scopes: in the definition of class Clock, the three parameters of function member SetTime have function prototype scope; object myClock has local scope; data members and function members of class Clock have class scope; and object globClock has file scope. These variables, objects, and public members are all visible in the main function. As for lifetime, apart from object globClock with file scope having a static lifetime, which is the same as the running period of the program, all the others have a dynamic lifetime.

5.3Static Members of Class

The basic unit of program modules in structured program design is function. Therefore, memory data sharing between modules is achieved through the data sharing between functions, including, parameter passing and global variables.

Object-oriented program design method concerns both data sharing and data protection. It encapsulates data and functions operating the data together, to construct more integrated modules. The data members of a class can be accessed by any function of the same class. Thus, on the one hand data sharing is achieved between functions within a class; on the other hand this kind of data sharing is restricted – we can set appropriate access control attributes to limit the data sharing within the class scope, and class data members are still hidden from the outside of the class, thus to achieve both sharing and hiding.

Yet, this is not all of data sharing. Data sharing is also needed between objects.

Static members are designed to solve the problem of data sharing between different objects of the same class. For example, we can abstract the common attributes of all the employees of a company, and design the following employee class:

If we need to count the total number of employees, where do we store this data? If you use a variable outside the class to store the total number, then you cannot hide this data. If you add a data member in the class to store the total number, then every object should store a copy of this data, which not only is redundant but also may cause data inconsistency. Since this data should be shared among all the objects in the class employee, the best way is to let all the objects of the class have the same data member that stores the total number, which is the static data member we will introduce next.

5.3.1Static Data Member

When we say that “all the objects in a class have the same attribute”, we mean that the number, the name, and the data type of the attribute of each object are the same, while the attribute value of each object can be different. This kind of attribute is called “instance attribute” in the object-oriented method, and is represented by a nonstatic data member of class in C++ programs. For example, the EmpNo, ID, and name of class employee in the example above are all instance attributes represented by nonstatic data members. Nonstatic data members have a copy in every object of the class, by which an object differs from other objects of the same class.

There is a concept called “class attribute” in the object-oriented method. If there is an attribute that belongs to the whole class and does not belong to any specific object, then we can use the keyword static to declare it a static member. A static member has only one copy in a class, and it is protected and used by all the objects of this class, to achieve data sharing among different objects of the same class. Class attribute is a data item that describes the common characteristic of all the objects of one class. Its attribute value is the same to all object instances. In short, if we compare a “class” to a factory, and objects to the products made by the factory, then static members would be stored in the factory and belong to the factory, and they would not belong to each product.

A static data member has a static lifetime. Because static data does not belong to any object, we can only access it through a class name, and the general way is “class name::identifier.” We only do a referential declaration of a static data member in the definition of its class, while the definitional declaration must be done using the class name qualification at someplace in the file scope, and the initialization can also be done during the definitional declaration. UML language underlines the data member to represent a static data member. We can find out the usage of static data members in the following example.

Example 5.4: Class Point with static data members.

This program is modified from the class Point in Chapter 4. It brings in a static data member countP to count the object number of class Point. The UML diagram of class Point that includes static data member countP is as shown in Figure 5.2:

Fig. 5.2: UML diagram of class Point that includes static data member.

In the example above, data member countP in class Point is declared static, and used to count the number of objects in class Point; every time a new object is defined, the value of countP increases by 1. The definition and initialization of static data member countP are done outside the class. Pay attention to the reference method when initializing the static data member. First, it uses the class name to qualify the data member; next, although this static data member is private, it can be initialized directly. Except for this special occasion, in other places, such as in the main function, no direct access is allowed. The value of countP is calculated in the constructor: when object A is created, the constructor with default parameters is called; when object B is created, the copy constructor is called. These two constructors access the same static member countP. Also, the output of calling function GetC through object A and B respectively is the values of the same countP at different times. In this way, we can achieve data sharing between A and B directly.

The result of the program:

Furthermore, we usually access static data members through noninline functions, because the compiler will make sure that it has initialized the static data members before calling any noninline functions. However, when using inline functions to call static members from another compiling unit, it is possible that the static member has not been initialized yet; therefore this kind of calling is unsafe.

5.3.2Static Function Member

In Example 5.3, function GetC() is devoted to output static member countP. We can only output countP by calling function GetC() through one object of class Point. Before declaring all the objects, the value of countP is its initial value 0. How can we output this initial value? It is obvious that we cannot call GetC() through an object since there has been no object declared yet. Because countP belongs to the whole class instead of any object, we hope to access countP without going through objects. Now let us try to rewrite the main function of Example 5.3 as follows:

Unfortunately, there are errors during the compiling: we must call general function members through object names.

Still, there are methods that can help us achieve our expectations above, that is, static member functions. The so-called static member function is a function member declared by the keyword static. Just like a static data member, a static member function belongs to the whole class, is owned by all the objects of the same class, and is shared by all these objects.

As a member function, the access attribute of a static data member is controlled strictly by class. For a public static member function, we can call it through the class name or object name. A general nonstatic member function can only be called through the object name.

Static member functions can access static data and function members of this class directly. To access nonstatic data members, they first have to get the object name through parameter passing, and then access the nonstatic data member through the object name. Let’s have a look at the following program segment:

We can find out that it is inconvenient for static function members to access nonstatic members. Generally, a static function member is primarily used to access static data members in the same class, and to maintain the data shared among objects.

In UML language, a static function member is represented by adding the <<static>> stereotype to function members.

Example 5.5: Class Point with static data and static function members.

In Example 5.4, we use the static private data member countP to count the number of objects in the Point class; now we use a static function member to access countP in this example. The UML diagram of the Point class that has the added static function member is as shown in Figure 5.3:

Fig. 5.3: UML diagram that includes static function member.

Compared with Example 5.4, we only change GetC() to a static member function in the definition of the class. Then, we can use both class name and object name to call GetC() in the main function.

The result of this program is the same as the one in Example 5.4. Compared with Example 5.4, the advantage of a static function member is that it is object-independent, so we can access the static data directly.

Pay attention to that while using a static data member: if you want to access the static data member through a nonstatic function, you should use a noninline function, and the definition of this noninline function should be in the same source file as the initialization of the static member. Why should we do so? It is because that static data member should be initialized before being accessed, and the compiler can only make sure to initialize static data members of class before calling any nonstatic function defined in the same source file. We will introduce a multifile structure in Section 5.6, and we will see that one project can be made up of several source files. The definition of class, the initialization of static data members, and the definition of member functions can be in different source files. In this case, using a nonstatic inline member function to access a static data member will cause unexpected errors.

5.4Friend of Class

The encapsulation of data and the function operating the data to form a class can achieve both data sharing and data hiding, which is obviously a great advantage of object-oriented program design. But encapsulation is not omnipotent. Now, consider a simple example of the Point class that we are familiar with, in which each object represents a “point”. If we want to calculate the distance between two arbitrary points, how do we design this function?

If we design the distance calculator function as a general function outside the class, we cannot reflect the relation between the function and “point”, and functions outside the class cannot refer to the coordinates of the “point” (private member) directly, which makes the calculation inconvenient.

Then what about designing the calculator function as a member function of class Point? It is not hard to implement from a grammatical point of view, but it is hard to understand. This is because that distance is a relation between two points, and it does not belong to any point, nor to the whole class. In other words, to design the function whether as a nonstatic member or as a static member, it will always affect the readability of the program.

We once used a combination of classes in Chapter 4, where two objects of class Point combine to form a class Line (line segment), and class Line has the function of calculating the length of a line segment. However, class Line is an abstract of a line segment. If the problem we are facing is there are lots of points, and the distance between any two arbitrary points needs to be calculated frequently, then it has to create an object of line every time we calculate the distance. This is inconvenient and affects the program’s readability.

Therefore, we need a function outside class Point that has a special relationship with class Point.

Have a look at the following program segment:

This is a case of class combination, where the object of class A is embedded in class B, but the member function of B cannot access A’s private member x directly. From the view of data security, this is the safest option – the embedded components are equal to a black box. But it is inconvenient to use. For example, realizing the member function Set() of B by the following writing will cause compiling errors:

Since the objects of A are embedded in B, can we let B’s functions access the private data of A directly?

C++ provides syntax support for the needs above, in the form of friends.

A friend provides a data sharing mechanism among member functions of different classes or of different objects, and among member functions of a class and general functions. Or, more generally, a friend enables a class to actively declare which classes or functions are its friends, to give them permission to access this class. In other words, a general function or a member function of a class can access data that is encapsulated in another class through the relation of friend. A friend destroys the hiding and encapsulation of data to a certain extent. However, in order to share data and to improve the efficiency and readability of a program, this kind of small damage is necessary in many cases. The key point is to find a balance between sharing and encapsulation.

We can use the keyword friend to declare a function or a class as a friend in a class. If a friend is a general function or a member function of a class, then it is called a friend function; if a friend is a class, then it is called a friend class, and all the member functions of the friend class will become friend functions automatically.

5.4.1Friend Function

Friend function is a nonmember function modified by the keyword friend in the class. A friend function can be a general function or a member function of another class. Although it is not the member function of this class, it can access the private and protected members of the class through object names. A friend function is represented by adding the <<friend>> stereotype before the member function in UML language. Have a look at the following example:

Example 5.6: Use friend function to calculate the distance between two points.

We used class Line composed by the combination of two objects of class Point to calculate the length of the line segment when introducing the class combination. In this example, we will use a friend function to achieve a more general functionality: calculate the distance between two arbitrary points. We still use Point to describe the points on the screen, and use a general function fDist to calculate the distance between two points. During the calculation, this function needs to access the private data members X and Y of class Point, so we declare fDist as a friend function of class Point. The UML diagram of class Point is as shown in Figure 5.4:

Fig. 5.4: UML diagram of class Point that includes a friend function member.

Result:

We only declare the prototype of a friend function in the definition of class Point, while the definition of friend function fDist is outside the class. We can find out that the friend function accesses the private members X and Y of class Point through object names, which is the key point of friend relation. To calculate the distance between two points, we can use a friend to make the program more readable. Of course, if you want to represent a line segment, class Line is a better choice. This means that although from the grammatical prospective there are many methods to solve one problem, we should choose a method that can describe the essentials of the problem more directly, and this kind of program will be more readable.

A friend function can be either a general function or a member function of a class. Using the method of a friend member function is almost the same as that of a friend general function, while we need to access the friend member function through the corresponding class or object name.

5.4.2Friend Class

Like with the friend function, one class can declare another class as its friend class. If class A is the friend class of class B, then all the member functions of class A are friend functions of class B, and they can access class B’s private and protected members. The syntax form of a friend class declaration is:

Declaring a friend class is a method to correlate classes and achieve data sharing among classes. UML language uses the <<friend>> stereotype to represent friendship between two classes. Now, modify the program segment at the beginning of this section into the following form: class B is the friend class of class A, and then the member functions of B can access the private member x of A directly. Figure 5.5 shows how to use UML diagram to describe the friendship between class A and class B:

Fig. 5.5: UML diagram of the friendship between class A and class B.

We will use a friend class to implement the matrix operation in the next chapter.

There are some points you have to pay attention to, regarding friends: firstly, friendship is not transitive. Suppose class B is class A’s friend and class C is class B’s friend, if there is no declaration, then class C and class A do not have any friendship, and they cannot share data. Secondly, a friendship relation is unidirectional. If class B is declared as a friend of class A, then member functions of class B can access the private and protected data of class A, while member functions of class A cannot access the private and protected data of class B. Thirdly, friendship cannot be inherited. If class B is class A’s friend, the derived class of class B will not become class A’s friend automatically. For example, someone may trust you, but he may not trust your children.

5.5Protection of Shared Data

Although hiding data can ensure the safety of data, different kinds of data sharing may damage the safety of data to certain extents. Therefore, for data that needs both sharing and protection from being unchanged, we should declare it as constant. Because a constant is unchangeable during the program execution, the data can be protected effectively. We have introduced the simple data type of constants in Chapter 2. The object can also be modified by const when we declare it, and we call it a constant object. We will introduce the constant reference, constant object, and constant member of an object. Constant arrays and constant pointers will be introduced in the next chapter.

5.5.1Constant Reference

If we use const to modify a reference when we declare it, the declared reference is a constant reference. The object referenced by a constant reference cannot be updated. If we use a constant reference as a formal parameter, changes on the actual parameter will not happen. The declaration form of a constant reference is:

Example 5.7: Constant reference as formal parameter.

5.5.2Constant Object

A constant object is an object that the value of its data member cannot be changed during the whole lifetime of the object. That is to say, constant objects must be initialized, but cannot be updated. The syntax form of declaring a constant object is:

For example:

Similar to the constant of a basic data type, the value of a constant object cannot be changed. C++ provides reliable protection for constants of basic data types. If the following statements appear in a program, there will be errors. That is to say, syntax checking ensures that the constant will not be assigned again.

How can syntax ensure that the value of a constant object will not be changed? There are two ways to change the value of an object data member: one is to access public data members through the object name from outside the class, in which case syntax will restrict any reassignment; the other is to change the value of the data member in the member function of the same class, but you cannot predict and count which member function will change the value, so the syntax can only prescribe that the general member functions cannot be called through constant objects. But then what’s the use of the constant object? It does not have any external interfaces to use. Do not worry. We can use constant member functions specially defined for constant objects, which will be introduced in the next section.

5.5.3Class Members Modified by const

1.Constant Member Function

Functions modified by keyword const are called constant member functions, and their declaration form is as below:

Notes:

a)const is a part of the function type, so the definition of the function should also have the keyword const.

b)A constant member function cannot update the data members of object, and it cannot call member functions without the const modification in this class. (This ensures that the values of data members will not change in the constant member function.)

c)If you declare an object a constant object, then it can only call its constant member function, and cannot call other member functions. (This is the protection mechanism of constant objects from a grammatical point of view in C++, and constant member functions are the only external interfaces of constant objects.)

d)The keyword const can be used to differentiate overloading functions. For example, if you declare like this in the class:

This is an effective overloading of print.

Fig. 5.6: UML diagram of class R that includes constant member function.

Constant member function is represented by adding <<const>> stereotype before member function in UML language.

Example 5.8: Example of constant member function.

The example declares a constant member function in class R, and the UML diagram is as below,

Analysis: Two functions of the same name print are declared in class R, and one of them is a constant function. In the main function, two objects – a and b are declared, and object b is a constant object. Then we call functions that are not modified by const through object a, and call constant functions modified by const through object b.

2.Constant Data Member

Just like general data, data members of class can also be constants or constant references. A data member declared by const is called a constant data member. If a constant data member is declared in a class, then it cannot be assigned values in any function. A constructor can only initialize this kind of data member using the initialization list. A constant data member is represented by adding const type before the data member type in UML language. Please have a look at the following example:

Example 5.9: An example of a constant data member.

In this example, a constant data function is declared in class A, which is presented in the following UML diagram (Figure 5.7):

Fig. 5.7: UML diagram of class A with constant data members.

Result:

5.6Multifile Structure and Compilation Preprocessing Directives

5.6.1General Organization Structure of C++ Program

Up to now, we have studied many complete C++ source program examples and analyzed their structures. These programs are basically made up of three parts: the definition of class, the implementation of class members, and the main function. Since the examples we have studied are relatively small, those three parts are written in the same file. Large projects often need several source program files, where each source program file is called a compiling unit. In this case, C++ syntax requires that the definition of a class must appear in all the compiling units that use this class. A good and usual way is to write the definition of the class in a header file, and each compiling unit that uses this class should include this header file. Usually, a project can be divided into at least three files: the class definition file (*.h file), class implementation file (*.cpp file), and class application file (*.cpp, main function file). For more complex programs, each class has its own definition and implementation files. By using this kind of organization structure, different files can be written and compiled separately, and then be linked together. This kind of structure can also make full use of the benefits of class encapsulation – when debugging and modifying, we only need to modify the definition and implementation of one class and keep other classes unmodified. Now we divide the program in Example 5.5 using the above method, and rewrite the program as below:

Example 5.10: A class Point that has static data and function members and a multifile structure.

Let’s analyze the structure of the whole program. It is made up of three separate source files, whose relationship as well as the process of compiling and linking are illustrated in Figure 5.8.

We can see that the two .cpp files in the multifile structure both have added a new include statement. When we do input/ output operations, we need to use the statement #include <iostream> to include the standard header file iostream provided by the system in the source program. Here, we also need to use the statement #include "point.h" to include the user-defined header file. The #include instruction in C++ is used to embed the specified source file into the current file at the position of the #include instruction; the embedded file can be either an .h file or a .cpp file.

There are two forms for writing an include instruction. The form “#include <file name>” indicates for the compiler to search the file that needs to be embedded in the standard way, and that the file to be embedded is located in the subdirectory include of the C++ system directory. This form is usually used to embed standard files provided by the system, such as standard header file iostream. The other form is “#include "file name"”, which indicates for the compiler to first search the file that needs to be embedded under the current directory; if the file cannot be found, the compiler will use the standard searching method. This form is usually used if the files are written by the user, such as the class definition file point.h in this example.

#include is a compilation preprocessing directive, which will be detailed later.

We can see from Figure 5.8 that compiling the two .cpp files respectively can generate their own object files suffixed with .obj, which are then linked together to generate an executable file suffixed with .exe. If we only modify the implementation part of a class member function, then we only need to recompile the file point.cpp and reapply the linking, while all the other files can remain unchanged. If the program is large and has a lot of statements, then this method can greatly improve the efficiency.

Fig. 5.8: C++ multi-file organization structure.

This kind of multifile organization structure is achieved by different methods in different environments. We usually use a project to manage multiple files under Windows, while we use the make tool to do it under Unix. While developing a program, we also need to study the programming environment

While using a multifile structure, we have to pay attention to the specificity of inline functions. Like the definition of class, an inline function needs to provide a uniform complete implementation in each compiling unit that calls it. A good and usual way is to write the implementation of an inline function in a header file, and include this header file in each compiling unit that calls the inline function.

5.6.2External Variable and External Function

1.External Variable

If we want a variable to be used by other files than the source file that defines it, we can declare it as an external variable by using the keyword extern in its declaration.

An external variable has file scope, and is defined outside any function. When declaring an external variable, we can give its definition, or just refer to an external variable that is declared elsewhere. Let’s see the following example:

Although i is defined in source file 1, because it is declared as an external variable in source file 2, we can still use it. The external variable is a global variable shared by several source files.

The declaration of an external variable can be a definitional declaration, i.e., defining the variable (allocate memory and initialize it) while declaring it, or a referential declaration (referring to a variable defined elsewhere). For example, the source file 1 above defines variable i while declaring it (i.e., a definitional declaration), while the declaration of i in source file 2 is only a referential declaration.

External variables can be declared at many places using extern, while they can be defined and initialized only once.

When declaring a file scope variable, its default state can be shared by different compiling units (source program files), as long as the compiling unit uses extern to declare this variable. However, if we use static to declare a variable in a file scope, then the effective area of this variable is limited to the compiling unit that defines it, and it cannot be accessed by other compiling units.

2.External Function

All of the functions declared outside all the classes (i.e., non-member functions) have file scopes. If there is no special declaration, this kind of functions can be called in different compiling units, as long as you declare the function prototype before calling it. Of course, you can also use extern while declaring a function prototype or defining a function, whichworks the same as the default state without the extern modification.

If you use the keyword static to declare a function prototype or to define a function, it will limit the function scope to the current compiling unit. In this case, the compiler will require the current compiling unit to include the definition of the function, and the definition of this function is only visible in the current compiling unit. Then you cannot call this function in other compiling units.

5.6.3Standard C++ Library and Namespace

In C, system functions and some macrodefinitions are placed inside the runtime library. The C++ library retains most of the C language system functions, and it also adds some predefined templates and classes. A standard C++ class library is a flexible and scalable collection of reusable software modules. Standard C++ classes and components are classified into six types logically:

Input/output class

Container class and ADT (abstract data type)

Storage management class

Algorithm

Error handling

Run-environment supporting

The declaration of predefined elements in the library is stored in different header files. To use these predefined elements, we should include the corresponding header files in our source files. The library and header files are a bit different in different compiling systems. For example, to use an I/O stream class in the VC++6.0 environment, the program should include the header file iostream.

To the readers that use a VC++ environment, in the early versions before VC++4.1, the library was called a runtime library, and header files were all named “*.h”. The standard C++ library came into use since VC++4.2, and it meets the ANSI standard, enabling your program to be portable between different compiling systems and different platforms. New header files do not have “.h” as their name suffix anymore, while the C++ standard library still retains 18 C header files with “.h”-suffixed names.

You can use either header files of the old version (“*.h”) or the new standard C++ library header files (with no filename suffix). The compiling system will determine which library to link automatically according to the name of the header file. But you must pay attention so that the two kinds of header files are not mixed in use. For example, if we have already included the header file iostream, then we cannot include math.h, and we should use the new header file cmath.

You can use predefined elements after including the corresponding header files. However, it is different when using standard C++ library, where you need to add the following statement to add the specified namespace to the current namespace:

If we do not use the statement above, then we should use the namespace name “std::” to qualify the identifiers that belong to std namespace in use.

We will introduce the concept of namespace in Chapter 10.

5.6.4Compilation Preprocessing

The compiler will first use the preprocessing program to preprocess the program text before compiling the source program. A preprocessing program provides a group of compilation preprocessing directives and preprocessing operators. Actually, preprocessing directives are not a part of C++; they are only used to extend the environment of C++ program design. Each preprocessing directive in a program occupies one separate line beginning with “#” and without a semicolon as the ending tag. Preprocessing directives can appear wherever they are needed.

1.#include Directive

The #include directive is also called a file inclusion directive. Its role is to embed another source file into the current source file. We usually use a #include directive to embed header files. There are two forms of file inclusion directives:

a)#include<file name>

Search the file that needs to be included using the standard method, and the file is under the include subdirectory of the C++ system directory.

b)#include"file name"

First search the file that needs to be included under the current directory; if the search fails, search the file using the standard method.

#include directives can be nested for use. Suppose there is a header file myhead.h. This header file can in turn have the following two #include directives:

2.#define and #undef Directives

We use #define to define symbolic constants in C. For example, the following preprocessing directive defines a symbolic constant PI to 3.14:

Although symbolic constants can be defined as above in C++, a better method is to use the const qualification in type declaration statements.

We can also use #define to define macros with parameters in C to achieve simple function calculations, which can improve the execution efficiency. However, this function is now replaced by inline functions in C++.

#undef is used to undefine macros defined by #define.

3.Conditional Compilation Directive

A conditional compilation directive instructs that some parts of a program can be compiled only when some conditions are satisfied. Therefore, we can use conditional compilation directives to make the same source file generate different object codes under different compiling conditions. For example, we can add some debugging statements during the program debugging to achieve tracing, and use conditional compilation directives to restrict the debug statements not to participate in the recompiling after the debugging. There are five forms of frequently used conditional compilation statements:

a) Form 1

b) Form 2

c) Form 3

d) Form 4

If “identifier” is defined by #defined, and has not been undefined by undef, then compile program segment 1; else compile program segment 2. If there is no program segment 2, then omit #else:

e) Form 5

If “identifier” has not been defined, then compile program segment 1; else compile program segment 2. If there is no program segment 2, then omit #else:

4.Operator defined

defined is a preprocessing operator, not a directive. So we should not use it beginning with #. The defined operator is used like this:

If “identifier” has been defined by #define, and has not been undefined by #undef, then the expression above is not 0, else the value of the expression above is 0. The following two expressions are equal in effects:

Since the #include directive can be nested, we should avoid including one header file many times in a program design; otherwise it will cause the redefinitions of variables and classes. For example, suppose a project has the following four source files:

Because of the nested use of the #include directive, header file head.h has been included twice, and then the system will indicate errors during the compiling and redefinition of class Point. How does one avoid this error? We need to use conditional compilation directives in the header files that may have been included multiple times. We can use an exclusive identifier to mark whether a file has been compiled or not: if it has already been compiled, then this program segment should have been included repeatedly, and the compiler will not recompile the repeated part any more. Thus, “head.h”can be rewritten as:

In this header file, the compiler first needs to judge whether the identifier HEAD_H has been defined or not. If it has not been defined, then the header file should not have been compiled yet, and the compiler will macrodefine the identifier HEAD_H and compile the following program segment. The defined identifier marks that the file has been compiled; if the identifier has already been defined, then the file should have been compiled and the compiler will not recompile the following program segment anymore. In this way, we can avoid the redefinition of class Point.

5.7Example – Personnel Information Management Program

We have used a personnel information management program for a small company in Chapter 4 as an example to explain the design and use of classes and member functions. In this section we will do the following improvements for Example 4.5 from the last chapter:

  1. We have introduced static data members in Section 5.2.1 and 5.3.1 In this example we will use the property that a static data member has a static lifetime to process a data member – the number of employees. This new functionality is: add a static data member in class employee to store the currently maximum employee number, and the currently maximum number will increase automatically when adding a new employee. Through this, we can avoid the problem of frequently calling the member function IncreaseEmpNo (int steps).
  2. We have introduced the general structure of C++ programs in Section 5.6.1. Here we will adjust the program structure based on Example 4.5 in the last chapter: we will divide the definition part and implementation part of class employee into two files.

The class design in this example is almost the same as the one in Example 4.5. Expect that we add a static data member static int employeeNo in the base class employee to store the currently maximum employee number; we also delete the member function IncreaseEmpNo (int steps), whose functionality is achieved by the constructor. The UML diagram of this class design is as shown in Figure 5.9.

Fig. 5.9: UML diagram of personnel information management program.

Example 5.11: Personnel information management program.

The whole program is divided into three files: class definition header file employee.h, class implementation file employee.cpp, and main function file 5_11.cpp. We should link 5_11.cpp and employee.cpp together after compiling. If we use the VC++ environment, we should put 5_11.cpp and employee.cpp in the same project.

Result:

We can find out that the result of this example is the same as the result of Example 4.5. However, the use of a static data member in the employee class makes all the objects of the class share this static data. Through this, the employee number can be generated automatically by a constructor every time an object is created.

Also, we have introduced conditional compilation directives in Section 5.6.2. Through the use of conditional compilation directives, we can avoid the compiling error of repeatedly including the employee class. Readers can refer to Section 5.6.2 to add conditional compilation directives into employee.h.

5.8Summary

Data sharing and data protection mechanisms are very important features in C++. They include scope, visibility and lifetime of identifiers, achieving data sharing and operation sharing between different objects of a same class by using static members of the class, and using constant members to set the protection attributes of members.

A multifile structure helps to write several source files to organize a large program. Also, by using preprocessor directives, we can do necessary preprocessing work for source programs, thus avoiding many unnecessary troubles and errors.

Exercises

5.1 What is scope? What kinds of scopes are there in C++?

5.2 What is visibility? What are the general rules of visibility?

5.3 What is the result of running the following program? Run it and see if it is the same as you think.

5.4 Suppose there are two independent classes – Engine and Fuel, how does one make the members of Fuel access the private and protected members of Engine?

5.5 What is a static data member? What are its characteristics?

5.6 What is a static function member? What are its characteristics?

5.7 Define a class Cat: it has static data member HowManyCats that counts the number of cats; static member function GetHowMany() that can access HowManyCats. Design a program to test this class, and learn the use of the static data member and static member function.

5.8 What is a friend function? What is a friend class?

5.9 If class A is a friend of class B, class B is a friend of class C, and class D is a derived class of class A, then is class B a friend of class A? Is class C a friend of class A? Is class D a friend of class B?

5.10 Can static member variables be private? Declare a private static integral member variable.

5.11 Define a global variable n andmain function main() in one file, and then define a function fn1() in another file. Assign n in main(), then call fn1() and reassign n in fn1(). Display the final value of n.

5.12 Define a static variable n in function fn1(), and so that fn1() adds n by 1. Call fn1() 10 times in the main function, and then display n.

5.13 Define classes X, Y, Z, and function h(X*): class X has a private member i; member function g(X*) of Y is a friend function of X, which adds 1 to the member i of X; class Z is a friend class of class X, and its member function f(X*) adds 5 to i; function h(X*) is a friend function of X, which adds 10 to i. Define and implement the three classes in one file, and implement function main() in another file.

5.14 Define class Boat and class Car, both of which have an attribute weight. Define their friend function totalWeight(), and calculate the sum of their weights.

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

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