Classes are there to help you organize your code and to reason about your programs. You could roughly equivalently say that classes are there to help you avoid making mistakes and to help you find bugs after you do make a mistake. In this way, classes significantly helps maintenance.
A class is the representation of an idea, a concept, in the code. An object of a class represents a particular example of the idea in the code. Without classes, a reader of the code would have to guess about the relationships among data items and functions - classes make such relationships explicit and "understood" by compilers. With classes, more of the high-level structure of your program is reflected in the code, not just in the comments.
A well-designed class presents a clean and simple interface to its users, hiding its representation and saving its users from having to know about that representation. If the representation shouldn't be hidden - say, because users should be able to change any data member any way they like - you can think of that class as "just a plain old data structure"; for example:
struct Pair {Note that even data structures can benefit from auxiliary functions, such as constructors.
Pair(const string& n, const string& v) : name(n), value(v) { }
string name, value;
};
When designing a class, it is often useful to consider what's true for every object of the class and at all times. Such a property is called an invariant. For example, the invariant of a vector could be that its representation consists of a pointer to a number of elements and that number of elements is stored in an integer. It is the job of every constructor to establish the class invariant, so that every member function can rely on it. Every member function must leave the invariant valid upon exit. This kind of thinking is particularly useful for classes that manage resource, such as locks, sockets, and files. For example, a file handle class will have the invariant that it holds a pointer to an open file. The file handle constructor opens the file. Destructors free resources acquired by constructors. For example, the destructor for a file handle closes the file opened by the constructor:
class File_handle {If you haven't programmed with classes, you will find parts of this explanation obscure and you'll underestimate the usefulness of classes. Look for examples. Like all good textbooks, TC++PL has lots of examples; for example, see A Tour of the Standard Library. Most modern C++ libraries consist (among other things) of classes and a library tutorial is one of the best places to look for examples of useful classes.
public:
File_handle(const char* n, const char* rw)
{ f = fopen(n,rw); if (f==0) throw Open_failure(n); }
~File_handle() { fclose(f); } // destructor
// ...
private:
FILE* f;
};
There are lots of definitions of "object oriented", "object-oriented programming", and "object-oriented programming languages". For a longish explanation of what I think of as "object oriented", read Why C++ isn't just an object-oriented programming language. That said, object-oriented programming is a style of programming originating with Simula (about 40 years ago!) relying of encapsulation, inheritance, and polymorphism. In the context of C++ (and many other languages with their roots in Simula), it means programming using class hierarchies and virtual functions to allow manipulation of objects of a variety of types through well-defined interfaces and to allow a program to be extended incrementally through derivation.
See What's so great about classes? for an idea about what great about "plain classes". The point about arranging classes into a class hierarchy is to express hierarchical relationships among classes and use those relationships to simplify code.
To really understand OOP, look for some examples. For example, you might have two (or more) device drivers with a common interface:
class Driver { // common driver interfaceThis Driver is simply an interface. It is defined with no data members and a set of pure virtual functions. A Driver can be used through this interface and many different kinds of drivers can implement this interface:
public:
virtual int read(char* p, int n) = 0; // read max n characters from device to p
// return the number of characters read
virtual bool reset() = 0; // reset device
virtual Status check() = 0; // read status
};
class Driver1 : public Driver { // a driverNote that these drivers hold data (state) and objects of them can be created. They implement the functions defined in Driver. We can imagine a driver being used like this:
public:
Driver1(Register); // constructor
int read(char*, int n);
bool reset();
Status check();
private:
// implementation details, incl. representation
};
class Driver2 : public Driver { // another driver
public:
Driver2(Register);
int read(char*, int n);
bool reset();
Status check();
private:
// implementation details, incl., representation
};
void f(Driver& d) // use driverThe key point here is that f() doesn't need to know which kind of driver it uses; all it needs to know is that it is passed a Driver; that is, an interface to many different kinds of drivers. We could invoke f() like this:
{
Status old_status = d.check();
// ...
d.reset();
char buf[512];
int x = d.read(buf,512);
// ...
}
void g()Note that when f() uses a Driver the right kind of operations are implicitly chosen at run time. For example, when f() is passed d1, d.read() uses Driver1::read(), whereas when f() is passed d2, d.read() uses Driver2::read(). This is sometimes called run-time dispatch or dynamic dispatch. In this case there is no way that f() could know the kind of device it is called with because we choose it based on an input.
{
Driver1 d1(Register(0xf00)); // create a Driver1 for device
// with device register at address 0xf00
Driver2 d2(Register(0xa00)); // create a Driver2 for device
// with device register at address 0xa00
// ...
int dev;
cin >> dev;
if (dev==1)
f(d1); // use d1
else
f(d2); // use d2
// ...
}
Please note that object-oriented programming is not a panacea. "OOP" does not simply mean "good" - if there are no inherent hierarchical relationships among the fundamental concepts in your problem then no amount of hierarchy and virtual functions will improve your code. The strength of OOP is that there are many problems that can be usefully expressed using class hierarchies - the main weakness of OOP is that too many people try to force too many problems into a hierarchical mould. Not every program should be object-oriented. As alternatives, consider plain classes, generic programming, and free-standing functions (as in math, C, and Fortran).
Generic programming is programming based on parameterization: You can parameterize a type with another (such as a vector with its element types) and an algorithm with another (such as a sort function with a comparison function). The aim of generic programming is to generalize a useful algorithm or data structure to its most general and useful form. For example, a vector of integers is fine and so is a function that finds the largest value in a vector of integers. However, a generic solution that provides a vector of any type the user cares to use and a function that finds the largest value in any vector is better still:
vector<string>::iterator p = find(vs.begin(), vs.end(), "Grail");These examples are from the STL (the containers and algorithms part of the ISO C++ standard library); for a brief introduction, see A Tour of the Standard Library from TC++PL.
vector<int>::iterator q = find(vi.begin(), vi.end(), 42);
Generic programming is in some ways more flexible than object-oriented programming. In particular, it does not depend on hierarchies. For example, there is no hierarchical relationship between an int and a string. Generic programming is generally more structured than OOP; in fact, a common term used to describe generic programming is "parametric polymorphism", with "ad hoc polymorphism" being the corresponding term for object-oriented programming. In the context of C++, generic programming resolves all names at compile time; it does not involve dynamic (run-time) dispatch. This has led generic programming to become dominant in areas where run-time performance is important.
Please note that generic programming is not a panacea. There are many parts of a program that need no parameterization and many examples where run-time dispatch (OOP) is needed.
文章评论(0条评论)
登录后参与讨论