牵着老婆满街逛

严以律己,宽以待人. 三思而后行.
GMail/GTalk: yanglinbo#google.com;
MSN/Email: tx7do#yahoo.com.cn;
QQ: 3 0 3 3 9 6 9 2 0 .

Notes on C++

Notes on C++

 

from Jesse Liberty, “Sams Teach Yourself C++ in 21 Days”, 4th Edition

 

 

Pointers

§         Using the address of Operator (&)

§         The indirection Operator (*)

§         Why would you use Pointers?

§         New and Delete

§         Bugs:

§         const Pointers

§         Do and Don’t

 

References

§         A reference is an alias

§         Do and Don’t

§         What can be referenced?           

§         Null Pointers and Null References

 

Passing Function Arguments by Reference

§         Passing by Reference Using Pointers

§         Passing by Reference Using References

§         Returning Multiple Values

§         Using const reference

§         When to use references and when to use pointers

§         Don’t return a reference to an Object that isn’t in Scope!

§         Pointer, Pointer, Who has the Pointer?

§         Summary

 

Arrays

§         Initializing a simple Array of built-in types.

§         Initializing Multidimensional Arrays

§         Arrays of Pointers

§         Declaring Arrays on the free store (using New keyword)

§         A Pointer to an Array Versus an Array of Pointers

§         Delete Arrays on the Free Store

 

Streams

§         Input using cin

§         Other Member Functions of cin

§         Output with cout

§         FAQ: Can you summarize how I manipulate output?

§         File Input and Output

§         Binary Versus Text Files            

§         Summary

§         Q&A

 

Polymorphism

§         Casting Down

§         Multiple Inheritance

§         Pure Virtual Functions

§         Implementing Pure Virtual Functions

§         Q&A

§         Do & Don’t

 

Special Classes and Functions

§         Static Member Data

§         Static Member Functions

§         Summary

§         Q&A

 

Friend (classes and functions)

§         Friend Classes

§         Friend Functions

 

 

 

 

Pointers

 

  1. Using the address of Operator (&)

Unsigned short int howOld = 50;                      // make a variable

Unsigned short int * pAge = &howOld;         // make pointer to howOld

 

  1. The indirection Operator (*)

v      The indirection operator (*) is also called the dereference operator.  A pointer provides indirect access to the value of the variable whose address it stores.

 

Unsigned short int yourAge;

yourAge = howOld;

// or

yourAge = *pAge;                                                      // get the value stored in the address pointed by pAge

 

v      The asterisk (*) is used in two distinct ways with pointers: as part of the pointer declaration and also as the dereference operator.

When you declare a pointer, the * is part of the declaration and it follows the type of the object pointed to. For example:

// make a pointer to an unsigned short

unsigned short * pAge = 0;

 

v      When the pointer is dereferences, the dereference (or indirection) operator indicates that the value at the memory location stored in the pointer is to be accessed, rather than the address itself.

// assign 5 to the value at pAge

*pAge = 5;

 

  1. Why would you use Pointers?

For three tasks:

v      Managing data on the free store

v      Accessing class member data and functions

v      Passing variables by reference to functions

 

  1. New and Delete

v      New:               allocate memory on the free store for a pointer.

Unsigned short int * pPointer;

pPointer = new Unsigned short int;

//or

unsigned short int * pPointer = new unsigned short int;

*pPointer = 72;                             // Assign the value 72 to the area on the free store to which pPointer points

 

v      Delete:            free the memory

delete pPointer;                            // Return to the free store the memory that this pointer points to, the pointer is still a pointer, can be reassigned.

 

  1. Bugs:

v      Memory leaks:              Is created by reassigning your pointer before deleting the memory to which it points. For every time call new, there should be a call to delete.

v      Stray, wild, or dangling pointers:              A stray pointer (also called a wild or dangling pointer) is created when you call delete on a pointer – thereby freeing the memory that it points to -- and then you don’t set it to null. If you then try to use that pointer again without reassigning it, the result is unpredictable and, if you are lucky, your program will crash.  To be safe, after you delete a pointer, set it to null (0). This disarms the pointer.

 

 

  1. const Pointers

const int * pOne;

int * const pTwo;

const int * const pThree;

 

pOne is a pointer to a constant integer. The value that is pointed to can’t be changed.

pTwo is a constant pointer to an integer.  The integer can be changed, but pTwo can’t point to anything else.

pThree is a constant pointer to a constant integer.  The value that is pointed to can’t be changed, and pThree can’t be changed to point to anything else.

  1. Do and Don’t

Do:

                Do protect objects passed by reference with const if they should not be changed.

                Do pass by reference when the object can be changed.

                Do pass by value when small objects should not be changed.

                                Don’t:

                                                Don’t delete pointers more than once.

 

 

Back to Top

 

 

 

References

 

  1. A reference is an alias

 when you create a reference, you initialize it with the name of another object, the target. From that moment on, the reference acts as an alternative name for the target, and anything you do to the reference is really done to the target.  Reference must be initialized at the time of creation.

 

int &rSomeRef = someInt;  // rSomeRef is a reference to an integer.

 

If you ask a reference for its address, it returns the address of its target.  That is the nature of reference.  They are aliases for the target.

int intOne;

int &rSomeRef = intone;

 

intOne = 5;

cout << “intOne: ” << intOne << endl;

cout << “rSomeRef: ” << rSomeRef << endl;                   // Same result

 

cout << “&intOne: ” << &intOne << endl;

cout << “&rSomeRef: ” << &rSomeRef << endl;            // Same result

 

  1. Do and Don’t

v      Do use references to create an alias to an object

v      Do initialize all references.

v      Don’t try to reassign a reference.

v      Don’t confuse the address of operator with the reference operator.

 

  1. What can be referenced?   Any object can be referenced, including user-defined objects. Note that you create a reference to an object, but not to a class.

 

// Wrong

int & rIntRef = int;

 

// Right

int howBig = 200;

int & rIntRef = howBig;

 

// Wrong

CAT & rCatRef = CAT;

 

// Right

CAT frisky;

CAT & rCatRef = frisky;

 

References to objects are used just like the object itself.

 

  1. Null Pointers and Null References

When pointers are not initialized or when they are deleted, they ought to be assigned to null (0). This is not true for references. In fact, a reference cannot be null, and a program with a reference to a null object is considered an invalid program. When a program is invalid, just about anything can happen. It can appear to work, or it can erase all the files on your disk.

 

Most compilers will support a null object without much complaint, crashing only if you try to use the object in some way. Taking advantage of this, however, is still not a good idea. When you move your program to another machine or compiler, mysterious bugs may develop is you have null objects.

Back to Top

 

 

 

 

Passing Function Arguments by Reference

 

 

In C++, passing by reference is accomplished in two ways: using pointers and using references. Note the difference: you pass by reference using a pointer, or you pass by reference using a reference.

 

The syntax of using a pointer is different from that of using a reference, but the net effect is the same. Rather than a copy being created within the scope of the function, the actual original object is (effectively) passed into the function.

 

Passing an object by reference enables the function to change the object being refereed to.

 

  1. Passing by Reference Using Pointers

int main()

{

        int x = 5; int y = 10;

        swap (&x, &y);

}

void swap(int *px, int * py)

{

        int temp;

        temp = *px;

        *px = *py;

        *py = temp;

}

 

The swap ( ) function is cumbersome in two ways. First, the repeated need to dereference the pointers within the swap( ) function (*px) makes it error-prone and hard to read. Second, the need to pass the address of the variables in the calling function makes the inner working of swap( ) overly apparent to its user.

 

  1. Passing by Reference Using References

int main()

{

        int x = 5; int y = 10;

        swap (x, y);

}

void swap(int &rx, int  &ry)

{

        int temp;

        temp = rx;

        rx = ry;

        ry = temp;

}

 

References provide the convenience and ease of use of normal variables, with the power and pass-by-reference capability of pointers.

 

  1. Returning Multiple Values

Pass objects into the function by reference. The function can fill the objects with the correct values. Because passing be reference allow a function to change the original objects, this effectively enables the function to return the information of the objects.

 

v      Returning values with Pointers

int main()

{

                int x = 0; int y = 0;

                assign (&x, &y);

}

void assign(int *px, int * py)

{

                *px = 10;

                *py = 20;

}// Return new value for x, y of the caller

 

v      Returning values with Reference

int main()

{

                int x = 5; int y = 10;

                assign (x, y);

}

void assign(int &rx, int  &ry)

{

                rx = ry;

                ry = temp;

}  // Return new values for x, y of the caller

 

  1. Using const reference

Pass by reference avoid making a copy of the object, save the memory, but the function can easy to change the object’s value. Put const before the pointers and references prevent the function from changing their value.

 

C++ programmers do not usually differentiate between “constant reference to an object” and “reference to a constant object”.  References themselves can never be reassigned to refer to another object, and so they are always constant. If the keyword const is applied to a reference, it is to make the object referred to constant.

 

  1. When to use references and when to use pointers

C++ programmers strongly prefer references to pointers. References are cleaner and easier to use, and they do a better job of hiding information.

 

References cannot be reassigned, however. If you need to point first to one object and then to another, you must use a pointer. References cannot be null, so if any change exists that the object in question may be null, you must not use a reference. You must use a pointer.

 

v      Do

Do pass parameters by reference whenever possible.

Do return by reference whenever possible.

Do use const to protect references and pointers whenever possible

                               

v      Don’t

Don’t use pointers if reference will work.

 

  1. Don’t return a reference to an Object that isn’t in Scope!

Remember that a reference is always an alias to some other object if you pass a reference into or out of a function, be sure to ask yourself, “What is the object I’m aliasing, and will it still exist every time it’s used?”

 

  1. Pointer, Pointer, Who has the Pointer?

When your program allocates memory on the free store, a pointer is returned. It is imperative that you keep a pointer to that memory because once the pointer is lost,  the memory cannot be deleted and becomes a memory leak.

 

As you pass this block of memory between functions, someone will “own” the pointer. Typically, the value in the block will be passed using references, and the function that created the memory is the one that deletes it. But this is a general rule, not an ironclad one.

 

It is dangerous for one function to create memory and another to free it, however. Ambiguity about who owns the pointer can lead to one of two problems:  forgetting to delete a pointer or deleting it twice. Either one can cause serious problems in your program. It is safer to build your functions so that they delete the memory they create.

 

If you are writing a function that needs to create memory and then pass it back to the calling function, consider changing your interface. Have the calling function allocate the memory then pass it into your function by reference. This moves all memory management out of your program and back to the function that is prepared to delete it.

 

Do pass parameters by value when you must.

Do return by value when you must.

Don’t pass by reference if the item referred to may go out of scope

Don’t use references to null objects.

 

Summary

§         References must be initialized to refer to an existing object and cannot be reassigned to refer to anything else. Any action taken on a reference is in fact taken on the reference’s target object.

§         Passing objects by reference can be more efficient than passing by value. Passing by reference also allow the called function to change the value in the arguments back in the calling function.

§         Arguments to functions and values returned from functions can be passed be references, and that this can be implemented with pointers or with references.

§         Use pointers to constant objects and constant references to pass values between functions safely while achieving the efficiency of passing by reference.

 

Back to Top

 

 

 

Arrays

 

  1. Initializing a simple Array of built-in types.

int intArray[5]  = {10, 20, 30 , 40, 50};

// or

int intArray[] = {10, 20, 30, 40, 50};

 

Do let the compiler set the size of initialized arrays.

 

Size of Array:  const USHORT intArrayLength = sizeof(intArray) / sizeof(intArray[0]);

 

  1. Initializing Multidimensional Arrays

int theArray[5][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};

// or

int theArray[5][3] = {           {1, 2, 3},

                                                {4, 5, 6},

                                                {7, 8, 9},

                                                {10, 11, 12},

                                                {13, 14, 15}            };

 

  1. Arrays of Pointers

CAT * Family[500];

CAT * pCat;

 

For (int i = 0; i < 500; i ++)

{

                pCat = new Cat;

                Family[i] = pCat;

}

 

  1. Declaring Arrays on the free store (using New keyword)

CAT *Family = new CAT[500];

CAT *pCat = Family;                                          // pCat points to Family[0]

pCat->..

pCat++;                                                  // advance to Family[1]

 

  1. A Pointer to an Array Versus an Array of Pointers

Examine the following three declarations:

1: CAT FamilyOne[500];

2: CAT * FamilyTwo[500];

3: CAT * FamilyThree = new CAT[500];

 

FamilyOne is an array of 500 CATs. FamilyTwo is an array of 500 pointers to CATs. FamilyThree is a pointer to an array of 500 CATs.

 

The differences among these three code lines dramatically affect how these arrays operate. What is perhaps even more surprising is that FamilyThree is a variant of FamilyOne, but it is very different from FamilyTwo.

 

This raises the thorny issue of how pointers relate to arrays. In the third case, FamilyThree is a point to an array. That is, the address in FamilyThree is the address of the first item in that array. This is exactly the case of FamilyOne.

 

  1. Delete Arrays on the Free Store

delete [] Family;

 

 

Back to Top

 

 

Streams

 

 

  1. Input using cin

Cin has overloaded the extraction operator for a great variety of parameters, among them int&, short&, long&, double&, float&, char&, char*, and so forth. When you write cin >> someVariable; the type of someVariable is assessed.

 

Example

Using namespace std;

Int main( )

{

                int myInt;

                long myLong;

                double myDouble;

                float myFloat;

                unsigned int myUnsigned;

 

                cout << “Int: ”;

                cin >> myInt;

 

                cout << “Long: ”;

                cin >> myLong;

 

cout << “Double: ”;

                cin >> myDouble;

 

                cout << “Float: ”;

                cin >> myFloat;

 

                cout << “Unsigned: ”;

                cin >> myUnsigned;

               

                // output

                cout << “\n\n Int: \t” << myInt >> endl;

                cout << “Long: \t” << myLong >> endl;

                cout << “Double: \t” << myDouble >> endl;

                cout << “Float: \t” << myFloat >> endl;

                cout << “Unsigned: \t” << myUnsianged >> endl;       

}

  1. Other Member Functions of cin

o        Single Character Input

Operator >> taking a character reference can be used to get a single character from the standard input. The member function get( ) can also be used to obtain a single character, and can do so in two ways: get( ) can be used with no parameters, in which case the return value is used, or it can be used with a reference to a character.

 

o        Using get( ) with no parameters

The first form of get ( ) is without parameters. This returns the value of the character found and will return EOF if the end of the file is reached.  Get( ) with no parameters is not often used. It is not possible to concatenate this use of get ( ) for multiple input because the return value is not an iostream object. Thus, the following won’t work:

Cin.get( ) >> myVarOne >> myVarTwo;                   // illegal

 

The return value of cin.get( ) >> myVarOne is an integer, not an iostream object.

 

A common use of get( ) with no parameters is illustrated below.

 

#include <iostream>

int main( )

{

        char ch;

        while  ( (ch = std::cin.get( )) ! = EOF )

        {

                        std::cout << “ch: ” << ch >> std::endl;

}

std::cout << “\nDone\n”;

return 0;

}

 

o        Using get( ) with a Character Reference Parameter

When a character is passed as input to get( ), that character is filled with the next character in the input stream. The return value is an iostream object, and so this form of get( ) can be concatenated.

 

Char a, b, c;

Std::cin.get(a).get(b).get(c);

 

First cin.get(a) is called. This puts the first letter into a and returns cin so that when it is done, cin.get(b) is called, putting the next letter into b. The end result of this is that cin.get(c) is called and the third letter is put in c.

 

Do use the extraction operator(>>) when you need to skip over whitespace.

Do use get ( ) with a character parameter when you need to examine every character, including whitespace.

 

o        Getting Strings from Standard input

Get( ) takes three parameters to fill a char array. The first is a pointer to a character array, the second parameter is the maximum number of characters to read plus one, and the third parameter is the termination character, which defaults to newline(`\n`)

 

Char stringOne[256];

Char stringTwo[256];

 

Cin.get(stringOne, 256)

Cin >> stringTwo;              //  takes everything up to the first whitespace ,  using getline( ) to solve this problem

 

Cin.getline(stringTwo, 256);

 

o        Using cin.ignore( )

Ignore( ) takes two parameters: the maximum number of characters to ignore and the termination character. For instance, ignore(80, ‘\n’), up to 80 characters will be throw away until a newline character is found. The newline is then thrown away and the ignore( 0 statement ends.

 

Cin.get( stringOne, 255);

Cin.ignore( 255, ‘\n’);

Cin.getline( stringTwo, 255);

 

o        Peek( ) and putback( )

Peek( ), which looks at but does not extract the next character, putback( ) , which insert a character into the input stream.

 

Char ch;

While( cin.get(ch) )

{

        if (ch = = ‘!’)

                        cin.putback(‘$’);

        else

                        cout << ch;

while (cin.peek( ) = = ‘#’)

                cin.ignore(1, ‘#’);

}

 

input: Now!is#the!time

output: Now$isthe$time

 

 peek( ) and putback( ) are typically used for parsing strings and other data such as when writing a compiler.

 

  1. Output with cout

o        Flushing the Output

Cout << flush   // ensure that the output buffer is emptied and that the contents are written to the screen

 

o        Related Functions: put( ), write( )

Put( ) is used to write a single character to the output device.

Write ( ) works the same as the insertion operator (<<), except that it takes a parameter that tells the function the maximum number of characters to write.

 

Std::cout.put(‘H’).put(‘o’).put(‘w’).put(‘\n’);

 

Char one[] = “one if by land”;

int fulllength = strlen(one);

Std::cout.write(one fulllength);

 

o        Using cout.width( )

        The default width of the output will be just enough space to print the number, character, or string in the output buffer. You can change this by using width( ). It only changes the width of the very next output field and then immediately reverts to the default.

       

        Cout << width( 25 );

        Cout << 123456;

       

        Output:                       123456

 

o        Setting the Fill Characters

Normally cout fills the empty field created by a call to width( ) with spaces. At time, you may want to fill the area with other characters, such as asterisks.

Cout.width( 25 );

Cout.fill( ‘*’ );

Cout << 123 << ‘\n’

        Output **********************123

                               

 

o        FAQ: Can you summarize how I manipulate output?

Answer: (with special thanks to Robert Francis) To format output in C++, you use a combination of special characters, output manipulators, and flags.

 

The following special characters are included in an output string being sent to cout using the insection operator:

                        \n – Newline

                        \r – Carriage return

                        \t – Tab

                        \\ - Backslash

                        \ddd (octal number) – ASCII character

                        \a – Alarm (ring bell)

For example

                        Cout << “\aAn error occurred\t”

               

Rings the bell, prints an error message, and moves to the next tab stop. Manipulators are used with the cout operator. Those manipulators that take arguments require that you include iomanip in your file.

 

The following is a list of manipulators that do not require iomanip:

Flush – flushes the output buffer

Endl – Inserts newline and flushes the output buffer

Oct – sets output base to octal

Dec  -  sets output base to decimal

Hex – sets output base on hexadecimal

               

The following is a list of manipulators that do require iomanip:

Setbase ( base ) – sets output base ( 0 = decimal, 8 = octal, 10 = decimal, 16 = hex )

Setw ( width ) – sets minimum output field width

Setfill ( ch ) – Fills character to be used when width is defined

Setprecision (p) – sets precision for floating point numbers.

Setiosflags (f) – sets one or more ios flats

Resetiosflags (f) – resets one or more ios flags

 

For example

        Cout << setw(12) << setfill(‘#’) << hex << x << endl;

 

Set the field width to 12, sets the fill character to ‘#’, specifies hex output, prints the value of ‘x’, puts a newline in the buffer, and flushes the buffer. All the manipulators except flush, endl, and setw remain in effect until changed or until the end of the program. Setw returns to the default after the current cout.

 

The following ios flags can be used with the setiosflags and resetiosflats manipulators:

ios::left – left justifies output in specified width

ios::right – right justifies output in specified width

ios::internal – sign is left justified, value is right justified

ios::dec – decimal output

ios::oct – octal output

ios::hex – hexadecimal output

ios::showbase – adds 0x to hexadecimal numbers,0 to octal number

ios::showpoint – adds trailing zeros as required by precision

ios::uppercase – hex and scientific notation numbers shown in uppercase

ios::showpos - + sign shown for positive numbers

ios::scientific – shows floating point in scientific notation

ios::fixed – shows floating point in decimal notation

 

Additional information can be obtained from file ios and from your compiler’s documentation.

 

  1. File Input and Output
  • Ofstream

Needs fstream.h in the program

 

  • Opening Files for input and output

Ofstream fout( “myfile.cpp”)      // output

 

Ifstream fin(“myfile.cpp”)                           // input

 

Example

Char buffer[255];

 

Ifstream fin(“myInputfile.txt”);

If (fin)                                                             // already exists?

{

        char ch;

        while (fin.get(ch))

                        cout << ch;

}

fin.close ( );

 

ofstream fout(“myOutputfile.txt”);

if (fout)                                                   // is successful to create or open the file

{

        cin.getline(buffer, 255);

        fout << buffer;

        fout.close( );

}

 

Do test each open of a file to ensure that it opened successfully.

Do reuse existing ifstream and ofstream objects.

Do close all fstream objects when you are done using them.

 

Don’t try to close or reassign cin or cout.

 

  1. Binary Versus Text Files

Binary files can store not only integers and strings, but entire data structures. You can write all the data at one time by using the write( ) method of fstream.

 

If you use write( ), you can recover the data using read( ). Each of these functions expects a pointer to character, however, so you must cast the address of your class to be a pointer to character.

 

The second argument to these functions is the number of characters to write, which you can determine using sizeof( ). Note that what is being written is the data, not the methods. What is recovered is only data.

 

Example:

#include <fstream.h>

#include <iostream.h>

 

class Animal

{

public:

                int itsWeight;

                long DaysAlive;

}

 

int main( )

{

                char filename[80];

                cin >> filename;

                ofstream fout (filename, ios::binary);

                Animal Bear(50, 100);

                Fout.write ((char*) &Bear, sizeof Bear);

 

                Fout.close( );

               

                Ifstream fin (filename, ios::binary);

                Animal BearTwo(1, 1);

                Fin.read((char*) &BearTwo, sizeof BearTwo);

               

}

 

       

  1. Summary

 

Four standard stream objects are created in every program: cout, cin, cerr, and clog. Each of these can be “redirected” by many operating systems.

 

The istream object cin is used for input, and its most common use is with the overloaded extraction operator (>>). The ostream object cout is used for output, and its most common use is with the overloaded insertion operator (<<);

 

Each of these objects has a number of other member functions, such as get( ) and put( ). Because the common forms of each of these methods returns a reference to a stream objects, it is easy to concatenate each of these operators and functions.

 

File I/O can be accomplished by using the fstream classes, which derive from the stream classes. In addition to supporting the normal insertion and extraction operators, these objects also support read( ) and write( ) for storing and retrieving large binary objects.

 

  1. Q&A

 

Q: How do you know when to use the insertion and extraction operators and when to use the other member functions of the stream classes?

A: In general, it is easier to use the insertion and extraction operators, and they are preferred when their behaviour is what is needed. In those unusual circumstances when these operators don’t do the job (such as reading in a string of words), the other functions can be used.

 

Q: What is the difference between cerr an clog?

A: cerr is not buffered. Everything written to cerr is immediately written out. This is fine for errors to be written to the screen, but may have too high a performance cost for writing logs to disk. Clog buffers its output, and thus can be more efficient.

 

Q: Why were streams created if printf( ) works well?

A: printf( ) does not support the strong type system of C++, and it does not support user-defined classes.  

 

Q: When would you ever use putback( )?

A: When one read operation is used to determine whether a character is valid, but a different red operation (perhaps by a different object) needs the character to be in the buffer. This is most often used when parsing a file; for example, the C++ compiler might use putback( ).

 

Q: When would you use ignore( )

A: A common use of this is after using get( ). Because get( ) leaves the terminating character in the buffer, it is not uncommon to immediately follow a call to get( ) with a call to ignore(1, ‘\n’); . Again, this is often used in parsing.

 

Back to Top

 

 

 

Polymorphism

 

  1. Casting Down

Run Time Type Identification (RTTI) can tell the pointer what type it is really pointing to in run time.  Beware of using RTTI in your program. Use of it may be an indication of poor design. Consider using virtual functions, templates, or multiple inheritance instead.

 

Casting down is accomplished using dynamic_cast operator

CAT *pCat = dynamic_cast<CAT *> (Family[0]);                                         // Poor design

 

FAQ

When compiling I got a warning from Microsoft Visual C++: warning C4541: ‘dynamic_cast’ used on polymorphic type ‘class CAT’ with /GR-; unpredictable behaviour may result. What should I do?

Answer: This is one of this compiler’s most confusing error messages. To fix it do the following:

o        In your project, choose Project/Settings.

o        Go to the C++ Tab.

o        Change the drop-down to C++ language.

o        Click Enable Runtime Type Information (RTTI).

o        Rebuild your entire project.

 

A program using dynamic_cast effectively undermines the virtual function polymorphism because it depends on casting the object to its real runtime type.

 

·         Do move functionality up the inheritance hierarchy.

·         Do avoid switching on the runtime type of the object – use virtual methods, templates, and multiple inheritance.

·         Don’t move interface up the inheritance hierarchy.

·         Don’t cast pointers to base objects down to derived object.

 

  1. Multiple Inheritance

Classs Horse {…}

Class Bird {…}

Class Pegasus : public Horse, public Bird {…}

 

  1. Pure Virtual Functions

C++ supports the creation of abstract data types with pure virtual functions. A virtual function is made pure by initializing it with zero, as in

 

virtual void Draw() = 0;

 

Any class with one or more pure virtual functions is an abstract data type (ADT), and it is illegal to instantiate an object of a class that is an ADT. Trying to do so will cause a compile-time error. Putting a pure virtual function in your class signals two things to clients of your class:

·         Don’t make an object of this class, derive from it.

·         Make sure you override the pure virtual function.

 

Any class that derives from an ADT inherits the pure virtual function as pure, and so must override every pure virtual function if it wants to instantiate objects.

 

Declare a class to be an abstract data type by including one or more pure virtual functions in the class declaration. Declare a pure virtual function by writing = 0 after the function declaration.

 

Example

                class Shape

                {

                virtual void Draw( )  = 0;                     // pure virtual

};

 

  1. Implementing Pure Virtual Functions

Typically, the pure virtual functions in an abstract base class are never implemented. Because no objects of that type are ever created, no reason exists to provide implementations, and the ADT works purely as the definition of an interface to objects which derive from it.

 

It is possible, however, to provide an implementation to a pure virtual function. The function can then be called by objects derived from the ADT, perhaps to provide common functionality to all the overridden functions.

 

  1. Q&A

 

Q: What does percolating functionality upward mean?

A: This refers to the idea of moving shared functionality upward into a common base class. If more than one class shares a function, it is desirable to find a common base class in which that function can be stored.

               

Q: Is percolating upward always a good thing?

A: Yes, if you are percolating shared functionality upward. No, if all you are moving is interface. That is, if all the derived classes can’t use the method, it is a mistake to more it up into a common base class. If you do, you’ll have to switch on the runtime type of the object before deciding if you can invoke the function.

 

Q: Why is switching on the runtime type of an object bad?

A: With large programs, the switch statements become big and hard to maintain. The point of virtual functions is to let the virtual table, rather than the programmer, determine the runtime type of the object.

 

Q: Why is casting bad?

A: Casting isn’t bad if it is done in a way that is type-safe. If a function is called that knows that the object must be of a particular type, casting to that type is fine. Casting can be used to undermine the strong type checking in C++, and that is what you want to avoid. If you are switching on the runtime type of the object and then casting a pointer, that may be a warning sign that something is wrong with your design.

 

Q: Why not make all functions virtual?

A: Virtual functions are supported by a virtual function table, which incurs runtime overhead, both in the size of the program and in the performance of the program. If you have very small classes that you don’t expect to subclass, you may not want to make any of the function virtual.

 

Q: When should the destructor be made virtual?

A: Any time you think the class will be subclassed, and a pointer to the base class will be used to access an object of the subclass. As a general rule of thumb, if you’ve made any functions in your class virtual, be sure to make the destructor virtual as well.

 

Q: Why bother making an Abstract Data Type – why not just make it non-abstract and avoid creating any objects of that type?

A: The purpose of many of the conventions in C++ is to enlist the compiler in finding bugs, so as to avoid runtime bugs in code that you give your customers. Making a class abstract – that is, giving it pure virtual functions – causes the compiler to flag any objects created of that abstract type as errors.

 

  1. Do & Don’t

Do use abstract data types to provide common functionality for a number of related classes.

Do override all pure virtual functions.

Do make pure virtual any function that must be overridden.

Don’t try to instantiate an object of an abstract data type.

 

Back to Top

 

 

Special Classes and Functions

 

  1. Static Member Data

Static member variables are shared among all instances of a class. They are a compromise between global data, which is available to all parts of your program, and member data, which is usually available only to each object.

 

You can think of a static member as belonging to the class rather than to the object. Normal member data is one per object, but static members are one per class.

class Cat

{

public: Cat (int age) {…};

static int HowManyCats;

}

int Cat::HowManyCats = 0;

 

·         Do use static member variables to share data among all instances of a class

·         Do make static member variable protected or private if you want to restrict access to them.

·         Don’t use static member variables to store data for one object. Static member data is shared among all objects of its class.

 

  1. Static Member Functions

Static member functions are like static member variables: They exist not in an object but in the scope of the class. Thus, they can be called without having an object of that class.

 

class Cat

{

public:

Cat (int age) {…};

static int GetHowMayCats ( ) { return HowManyCats; }

 

private:

static int HowManyCats;

}

int Cat::HowManyCats = 0;

 

                int main( )

{

                Cat * CatHouse[5];

for (int i = 0; i < 5; i ++)

{

                                CatHouse[i] = new Cat(i);

}

                std::count << “There are ” << Cat::GetHowManyCats ( ) << “ cats alive!\n”;

}

 

  1. Summary

You can access static member functions by calling them on an object of the class the same as you do any other member function, or you can call them without an object by fully qualifying the class and object name.

 

Example:

class Cat

{

public:

static int GetHowManyCats ( ) { return HowManyCats; }

private:

static int HowManyCats;

}

int Cat::HowManyCats = 0;

 

                int main( )

{

int howMany;

Cat theCat;                                                                            // define a cat

howMany = theCat::GetHowManyCats( );      // access through an object

howMany = Cat::GetHowMany( );                   // access without an object               

}

 

  1. Q&A

Q: Why use static data when you can use global data?

A: Static data is scoped to the class. In this manner, static data is available only through an object of the class, through an explicit call using the class name if they are public, or by using a static member function. Static data is typed to the class type, however, and the restricted access and strong typing makes static data safer than global data.

 

Q: Why use static member functions when you can use global functions?

A: Static member functions are scoped to the class and can be called only by using an object of the class or an explicit full specification (such as ClassName::FunctionName( )).

 

Q: Is it common to use many pointers to functions and pointers to member functions?

A: No, these have their special uses, but are not common constructs. Many complex and powerful programs have neither.

 

Back to Top

 

 

 

Friend (classes and functions)

 

 

  1. Friend Classes

If you want to expose your private member data or functions to another class, you must declare that class to be a friend. This extends the interface of your class to include the friend class.

 

It is important to note that friendship cannot be transferred, not inherited, not commutative.

 

                Declare one class to be a friend of another by putting the word friend into the class granting the access rights.

                Example

                                class PartNode {

                                public:

                                                friend class PartsList;                          // declares PartsList to be a friend of PartNode

};

 

You will often hear novice C++ programmers complain that friend declarations “undermine” the encapsulation so important to object-oriented programming. This is, frankly, errant nonsense. The friend declaration makes the declared friend part of the class interface and is no more an undermining of encapsulation than is public derivation.

 

  1. Friend Functions

You want to grant this level of access not to an entire class, but only to one or two functions of that class. You can do this by declaring the member functions of the other class to be friends, rather than declaring the entire class to be a friend. In fact, you can declare any function, whether or not it is a member function of another class, to be a friend function.

 

Declare a function to be a friend by using the keyword friend and then the full specification of the function. Declaring a function to be a friend does not give the friend function access to your this pointer, but it does provide full access to all private and protected member data and functions.

 

Example

class PartNode {

                                public:

                                                // …

                                                // make another class’s member function a  _friend

                                                friend void PartsList::Insert(Part *);

// make a global function a friend

friend int SomeFunction( );

// …       

};

 

Back to Top

posted on 2007-04-11 10:18 杨粼波 阅读(492) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理