Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

lafore_robert_objectoriented_programming_in_c

.pdf
Скачиваний:
51
Добавлен:
27.03.2023
Размер:
8.94 Mб
Скачать

Multifile Programs

635

We’ll see an important example of a class library in Chapter 15, “The Standard Template Library.”

A class library usually includes two components: the interface and the implementation. Let’s see what the difference is.

Interface

Let’s say that the person who wrote a class library is called the class developer, and the person who uses the library is called the programmer.

To use a class library, the programmer needs to access various declarations, including class declarations. These declarations can be thought of as the public part of the library and are usually furnished in source-code form as a header file, with the .H extension. This file is typically combined with the client’s source code using an #include statement.

The declarations in such a header file need to be public for several reasons. First, it’s a convenience to the client to see the actual class definitions rather than to have to read a description of them. More importantly, the programmer will need to declare objects based on these classes and call on member functions from these objects. Only by declaring the classes in the source file is this possible.

These declarations are called the interface because that’s what a user of the class (the programmer) sees and interacts with. The programmer need not be concerned with the other part of the library, the implementation.

Implementation

On the other hand, the inner workings of the member functions of the various classes don’t need to be known by the programmer. The class developers, like any other software developers, don’t want to release source code if they can help it, since it might be illegally modified or pirated. Member functions—except for short inline functions—are therefore often distributed in object form, as .OBJ files or as library (.LIB) files.

Figure 13.1 shows how the various files are related in a multifile system.

Organization and Conceptualization

Programs may be broken down into multiple files for reasons other than the accommodation of class libraries. As in other programming languages, a common situation involves a project with several programmers (or teams of programmers). Confining each programmer’s responsibility to a separate file helps organize the project and define more cleanly the interface among different parts of the program.

13

PROGRAMS MULTIFILE

Chapter 13

636

FIGURE 13.1

Files in a multifile application.

It is also often the case that a program is divided into separate files according to functionality: One file can handle the code involved in a graphics display, for example, while another file handles mathematical analysis, and a third handles disk I/O. In large programs, a single file may simply become too large to handle conveniently.

The techniques used for working with multifile programs are similar, whatever the reasons for dividing the program.

Multifile Programs

637

Creating a Multifile Program

Suppose that you have purchased a prewritten class file called THEIRS.OBJ. (A library file with the .LIB extension is dealt with in much the same way.) It probably comes with a header file, say THEIRS.H. You have also written your own program to use the classes in the library; your source file is called MINE.CPP. Now you want to combine these component files—THEIRS.OBJ, THEIRS.H, and MINE.CPP—into a single executable program.

Header Files

The header file THEIRS.H is easily incorporated into your own source file, MINE.CPP, with an #include statement:

#include “theirs.h”

Quotes rather than angle brackets around the filename tell the compiler to look for the file in the current directory, rather than in the default include directory.

Directory

Make sure that all the component files, THEIRS.OBJ, THEIRS.H, and MINE.CPP, are in the same directory. In fact, you will probably want to create a separate directory for each project, to avoid confusion. (This isn’t strictly necessary, but it’s the simplest approach.)

Each compiler keeps its own library files (such as IOSTREAM and CONIO.H) in a particular directory, often called INCLUDE, and usually buried many levels down in the compiler’s directory structure. The compiler already knows where this directory is.

You can also tell the compiler about other include directories that you create yourself. You may want to keep some of your header files in such a directory, where they will be available for several projects. In Appendix C, “Microsoft Visual C++,” and Appendix D, “Borland C++Builder,” we explain how to tell the compiler where such a directory is located.

Projects

Most compilers manage multiple files using a project metaphor. A project contains all the files necessary for the application. It also contains instructions for combining these files, often in a special file called a project file. The extension for this file varies with the compiler vendor. It’s

.BPR for Borland, and .DSP for Microsoft. Modern compilers construct and maintain this file automatically, so you don’t need to worry about it. In general you must tell the compiler about all the source (.CPP) files you plan to use so they can be added to the project. You can add .OBJ and .LIB files in a similar way. Appendixes C and D provide details on creating multifile programs for specific compilers.

13

PROGRAMS MULTIFILE

Chapter 13

638

Only a single command needs to be given to the compiler to compile all the source (.CPP and

.H) files and link the resulting .OBJ files (and any other .OBJ or .LIB files) into a final .EXE file. This is called the build process. Often the .EXE file can be executed as well. (In Windows and other advanced programming there are many more types of files.)

One of the nice things about a project is that it keeps track of the dates when you compiled each source file. Only those source files that have been modified since the last build are recompiled; this can save considerable time, especially on large projects. Some compilers distinguish between a Make command and a Build command. Make compiles only those source files that have changed since the last build, whereas Build compiles all files regardless of date.

Inter-File Communication

In a multifile program, program elements in different files need to communicate with each other. In this section we’ll discuss how to make this possible. We’ll first discuss how communication is handled between separately-compiled source (.CPP) files that are linked together. Then we’ll see how header (.H) files that are included in source files fit into the picture.

Communication Among Source Files

This section explores how elements of separate source files communicate. We’ll examine three kinds of programming elements: variables, functions, and classes. Each has its own rules for inter-file use.

The idea of scope will be important here, so you may want to refer back to our discussion of scope and storage class in Chapter 5. Scope is the region of a program where a variable or other program element can be accessed. Elements declared within a function have local scope; that is, they are visible only within the function body. Similarly, class members are only visible within the class (unless the scope resolution operator is used).

Program elements declared outside any function or class have global scope: they can be used throughout an entire file, following the point where they are defined. As we’ll see, they are visible in other files as well.

Inter-File Variables

We’ll start with simple variables. Recall the distinction between declaration and definition. We declare a simple variable by giving it a name and a type. This does not necessarily provide a physical location in memory for the variable; it only tells the compiler that a variable with this name and type may exist somewhere. A variable is defined when it is given a place in memory that can hold the variable’s value. The definition creates the “real” variable.

Multifile Programs

Most declarations are also definitions. Actually, the only declaration of a simple variable that is not a definition uses the keyword extern (with no initializer).

int someVar;

//declaration

and also definition

extern int someVar;

//declaration

only

As you might expect, a global variable can be defined in only one place in a program.

//file A

int globalVar; //definition in file A

//file B

int globalVar; //illegal: same definition in file B

Of course, this discussion applies only to global variables. You can define as many variables with the same name and type as you like, provided they are all local to different functions or classes.

How do you access a global variable in one file from a different file? The fact that the linker will object to defining the same global variable in more than one file does not mean that a variable in one file is automatically visible to all code in other files. You must declare the variable in every file that uses it. If you say

//file A

int globalVar; //defined

//file B

globalVar = 3; //illegal, globalVar is unknown here

the compiler will tell you that globalVar is an unidentified identifier.

To allow a variable to be accessed in files other than the one where it’s defined, you must declare it in the other files using the keyword extern.

//file A

 

int globalVar;

//definition

//file B

 

extern int globalVar;

//declaration

globalVar = 3;

//now this is OK

The declaration causes globalVar in file A to be visible in file B. The extern keyword signals that the declaration is only a declaration, not a definition. It tells the compiler (which can see only one file at a time) not to worry that the globalVar variable in file B is undefined there. The linker (which sees all the files) will take care of connecting a reference to a variable in one file with its definition in another.

639

13

PROGRAMS MULTIFILE

Chapter 13

640

You should note a possibly surprising restriction: you can’t initialize a variable in an extern declaration. The statement

extern int globalVar = 27; //not what you might think

will cause the compiler to assume that you meant to define globalVar, not just declare it. It will simply ignore the extern keyword and create a definition. If the variable is defined in another file, you’ll get the “already defined” error from the linker.

What if you actually want to use global variables with the same name in different files? In that case you can define them using the static keyword. This restricts a variable’s visibility to the file where it’s defined. Other variables with the same name can be used in other files.

//file A

 

 

 

 

 

 

static int globalVar;

//definition;

visible

only

in

file

A

//file

B

 

 

 

 

 

 

static

int globalVar;

//definition;

visible

only

in

file

B

Although two variables with the same name are defined here, there is no conflict. Code in file A that refers to globalVar will access the variable in its file, and code in file B behaves likewise. Static variables are said to have internal linkage, while non-static global variables have external linkage. (As we’ll see later in this section, you can also use namespaces to restrict a variable’s scope to a single file.)

In a multifile program it’s a good idea to make global variables static whenever they are not accessed in other files. This prevents problems when the same name is used by mistake in another file. It also makes it clearer to someone looking at the listing that they don’t need to worry about the variable being accessed elsewhere.

Notice that the keyword static has several meanings, depending on whether it’s applied to a local or a global variable. We saw in Chapter 5, “Functions,” that when static modifies a local variable (one defined inside a function) it changes the variable’s lifetime from that of the function to that of the program but keeps its visibility restricted to the function. As we discussed in Chapter 6, “Objects and Classes,” a static class data member has the same value for all objects rather than a separate value for each object. However, for a global variable, static simply restricts its visibility to its own file.

A const variable that is defined in one file is normally not visible in other files. In this regard it’s like a static variable. However, you can cause a const variable to be visible in another file by using the extern keyword with both the definition and the declaration:

//file A

 

extern const int conVar2 = 99;

//definition

//file

B

 

extern

const int conVar2;

//declaration

Multifile Programs

641

Here, file B has access to the const variable in file A. The compiler can tell the difference between a const definition and a declaration by seeing where the variable is initialized.

Inter-File Functions

Remember that a function declaration specifies the name of the function, its return type, and the type of any arguments. A function definition is a declaration that includes a function body. (The body is the code within braces.)

When the compiler generates a call to a function, it doesn’t need to know how the function works. All it needs to know is the function name, its return type, and the types of its arguments. This is exactly what the declaration specifies. It is therefore easy to define a function in one file and make calls to it from a second file. No extra keywords (like extern) are needed. All that’s necessary is to declare the function in the second file before making calls to it.

//file A

int add(int a, int b) //function definition

{ return a+b; } //(includes function body)

//file B

int add(int, int); //function declaration (no body)

. . .

int answer = add(3, 2); //call to function

You don’t need to use the keyword extern with functions because the compiler can tell the difference between a function’s declaration and definition: the declaration has no body.

Incidentally, you can declare (not define) a function or any other program element as many times as you want. The compiler won’t object, unless the declarations disagree.

//file A

int add(int, int); //declaration

int add(int, int); //another declaration is OK

Like variables, functions can be made invisible to other files by declaring them static.

//file A

static int add(int a, int b) //function definition { return a+b; }

//file B

static int add(int a, int b) //different function { return a+b; }

13

PROGRAMS MULTIFILE

This code creates two distinct functions. Neither is visible in the other file.

Chapter 13

642

Inter-File Classes

Classes are unlike simple variables in that the definition of a class does not set aside any memory. It merely informs the compiler what members constitute the class. It’s a little like specifying how many bytes will be used for type int, except that the compiler already knows the makeup of type int, but it doesn’t know about type someClass until you define it.

A class definition contains declarations or definitions for all its members:

class

someClass

//class definition

{

 

 

private:

 

 

int memVar;

//member data definition

public:

 

 

int memFunc(int, int);

//member function declaration

};

 

 

Members must be declared but don’t need to be defined in the class definition. As we’ve seen, member function definitions are routinely placed outside the class and identified with the scope resolution operator.

A class declaration is simply a statement that a certain name applies to a class. It conveys no information to the compiler about the members of the class.

class someClass;

//class declaration

Don’t confuse a class definition with the definition (creation) of an object of that class:

someClass anObj;

Unlike a class definition, the definition of an object sets aside space in memory for the object.

Classes behave differently from variables and functions in inter-file communication. To access a class across multiple source files it’s necessary to define the class (not just declare it) in every file in which its objects will be used. The fact that a class is defined in file A and declared in File B does not mean that the compiler can create objects of that class in file B.

Why does a class need to be defined in every file where it’s used? The compiler needs to know the data type of everything it’s compiling. A declaration is all it needs for simple variables because the declaration specifies a type already known to the compiler.

//declaration

 

extern int someVar;

//if it sees this, the compiler

someVar = 3;

//can generate this

Similarly, the declaration of a function reveals the data types of everything needed for a function call.

Multifile Programs

643

//declaration

 

int someFunc(int, int);

//if it sees this, the compiler

var1 = someFunc(var2, var3);

//can generate this

However, for a class, the entire definition is necessary to specify the types of its member data and functions.

//definition

 

class someClass

//if it sees this, the compiler

{

 

private:

 

int memVar;

 

public:

 

int memFunc(int, int);

 

};

 

someClass someObj;

//can generate this

v1 = someObj.memFunc(v2, v3);

//and this

A mere declaration is insufficient for the compiler to generate code to deal with class objects (except for pointers and references to objects).

You can’t define a class more than once in a source (.CPP) file, but every source file in a program can have its own definition of the same class. Indeed, it must have such a definition if it is to work with objects of that class. In the next section we’ll show how to use a header file to supply a class definition to many files.

Header Files

As we noted in Chapter 2, the #include preprocessor directive acts like the paste function in a word processor, causing the text of one file to be inserted in another. We’ve seen many examples of library files such as IOSTREAM being included in our source files.

We can also write our own header (usually .H) files and include them in our source files.

Common Information

One reason to use a header file is to supply two or more source files with the same information. The header file holds variable or function declarations, and is included in the source files. In this way the variables or functions can be accessed from many files.

Of course, each program element must also be defined somewhere. Here, a variable and a function are declared in FILEH.H and defined in FILEA.CPP. Code in FILEB.CPP can then use these elements without any additional declarations of its own.

//fileH.h

 

 

extern int gloVar;

//variable

declaration

int gloFunc(int);

//function

declaration

13

PROGRAMS MULTIFILE

644

Chapter 13

 

//fileA.cpp

 

 

int gloVar;

 

//variable definition

int GloFunc(int n)

//function definition

{ return

n; }

 

//fileB.cpp

 

 

#include “fileH.h”

 

. . .

 

 

gloVar = 5;

 

//work with variable

int gloVarB

= gloFunc(gloVar);

//work with function

Beware: you can put declarations in a header file, but you can’t put variable or function definitions in a header file that will be shared by multiple source files (unless they’re static or const). If you do, the same definitions will then end up in two different source files and the linker will issue “multiply defined” errors.

A very common and indeed almost essential technique is to put a class definition in a header file that is included in every source file that needs it. This doesn’t cause the multiply-defined problem because a class definition does not set aside any memory; it’s only a specification.

//fileH.h

 

class someClass

//class definition

{

 

private:

 

int memVar;

 

public:

 

int memFunc(int, int);

 

};

 

//fileA.cpp

 

#include “fileH.h”

 

int main()

 

{

 

someClass obj1;

//create an object

int var1 = obj1.memFunc(2, 3);

//work with object

}

 

//fileB.cpp

 

#include “fileH.h”

 

int func()

 

{

 

someClass obj2;

//create an object

int var2 = obj2.memFunc(4, 5);

//work with object

}