Cpp Advanced

download Cpp Advanced

of 53

Transcript of Cpp Advanced

  • 8/6/2019 Cpp Advanced

    1/53

    C++ Tutorial Part II - Advanced

    Silan Liu

    1. VPTR AND VTABLE4

    1.1 A Class and its Objects in the Memory4

    1.2 Linking of a Method Call4

    1.3 Memory Footprint of a Non-polymorphic Object4

    1.4 Non-virtual Function Call5

    1.5 Virtual Function Call Using vPtr and vtable5

    1.6 A Language Needs Compiler and Type Information to Utilize vtable6

    1.7 Polymorphic Base Classes are placed at the front6

    1.8 Inheritance of Base-class vPtrs61.9 De-referencing the vPtr7

    2. MULTIPLE INHERITANCE8

    2.1 Multiple Inheritance and Interface8

    2.2 Virtual Inheritance82.3 Method Name Clash and Ambiguity in Multiple Inheritance8

    3. TEMPLATES10

    3.1 Function Template10

    3.2 Class Templates10

    3.3 The Power of Type Extensibility with Class Template11

    3.4 Non-Type Parameters in Class Template11

    3.5 Templates and Friends12

    4. EXCEPTION HANDLING13

    4.1 What is Exception Handling134.2 Do Not Use Exception Handling on Normal Application Code13

    4.3 try Block13

    4.4 throw13

    4.5 Throw Point13

    4.6 catch Block14

    4.7 Rethrow an Exception14

    4.8 Exception Specifications14

    4.9 Stack Unwinding15

    4.10 Exception Thrown from Constructor and Destructor16

    4.11 Resource Leak17

    4.12 Insufficient Memory when Using new174.13 Standard Library Exception Hierarchy17

    4.14 Difference Between C++ and Javas Exception Handling18

    5. DATA STRUCTURES19

    5.1 Data Structure19

    5.2 Linked List19

    5.3 Tree19

    5.4 Struct19

    5.5 Bitwise Operators19

    5.6 Example: Display a Number in Binary Form20

    5.7 Bit Field205.8 Character Handling Library20

  • 8/6/2019 Cpp Advanced

    2/53

    5.9 String Conversion Methods21

    5.10 String Handling Library string.h21

    5.11 Error Message Method22

    6. THE PREPROCESSOR23

    6.1 #include23

    6.2 Conditional Compilation24

    6.3 #define Symbols for Conditional Compilation24

    6.4 #define for Constants vs. C++ Constants25

    6.5 #define for Macros vs. C++ Inline Template Functions25

    6.6 Line Numbers26

    6.7 Predefined Symbolic Constants26

    7. C - LIKE CODE27

    7.1 Redirecting Input & Output on UNIX/DOS Systems27

    7.2 Variable-Length Argument List27

    7.3 Command-Line Arguments28

    7.4 Program Termination with Exit and Atexit28

    7.5 Type Qualifier volatile287.6 Suffixes for Primitives28

    7.7 Signal Handling28

    7.8 C-Style Dynamic Memory Allocation29

    7.9 Unconditional Branch: Goto29

    7.10 Union30

    7.11 Linkage Specification extern "C"30

    7.12 __stdcall31

    7.13 typedef31

    8. CLASS STRING & STRING STREAM PROCESSING32

    8.1 Basics of string328.2 String Assignment32

    8.3 Creating Strings32

    8.4 Appending Strings32

    8.5 Comparing Strings32

    8.6 Sub-string33

    8.7 Swapping string338.8 Characteristics of string33

    8.9 Finding Character in string33

    8.10 Replacing Characters in a string33

    8.11 Inserting Characters into a string33

    8.12 Conversion to C-Style char *348.13 string Iterators34

    8.14 String Stream Processing34

    9. STANDARD TEMPLATE LIBRARY (STL)35

    9.1 Introduction to Container35

    9.2 Iterator35

    9.3 Iterator Types36

    9.4 Iterator Capabilities36

    9.5 Iterator Capabilities Supported by Different Containers36

    9.6 IO Iterators36

    9.7 Operations Supported by Different Iterators379.8 Sequence Containers37

  • 8/6/2019 Cpp Advanced

    3/53

    9.9 vector37

    9.10 Binary Predicate Function39

    9.11 List39

    9.12 Deque40

    9.13 Comparison of vector, list and deque40

    9.14 Associative Containers40

    9.15

    Class pair409.16 multiset41

    9.17 set41

    9.18 multimap41

    9.19 map42

    9.20 Container Adapters42

    9.21 Stack42

    9.22 queue42

    9.23 priority_queue43

    9.24 Introduction to Algorithms43

    9.25 Fill Container with Objects43

    9.26 Compare Elements44

    9.27 Remove Elements44

    9.28 Replace Elements45

    9.29 Mathematical Algorithms45

    9.30 Searching and Sorting Elements45

    9.31 Swap Elements46

    9.32 Copy Elements46

    9.33 Merge Containers46

    9.34 Unify Elements47

    9.35 Reverse Elements47

    9.36 Locate Element in Sorted Container47

    9.37 Heapsort47

    9.38 min & max48

    10. ANSI/ISO C++ STANDARD LANGUAGE ADDITIONS49

    10.1 bool49

    10.2 Four Casts49

    10.3 Namespace50

    10.4 Run-Time Type Information (RTTI)51

    10.5 Operator Keywords52

    10.6 explicit Constructor52

    10.7 mutable Class Member52

    11. SOME INDIVIDUAL TOPICS5311.1 DOS Keyboard Characteristics53

    11.2 Detach Method for Wrapper Classes53

  • 8/6/2019 Cpp Advanced

    4/53

    1. VPTR AND VTABLE

    1.1 A Class and its Objects in the Memory

    When a class is first-time accessed in a program, e.g. an object of this class is created or a static method is called,

    the class definition including one unique copy of its methods is loaded into the memory. Each method has its own

    memory address.

    Then, every time when an object of this class is created, a copy of the data members of this class is created andput in a block of memory. Data members are placed in a fixed order one next to another. The address of an object

    is the address of the first byte of its memory block.

    1.2 Linking of a Method Call

    When compiler sees a method call such as

    Woman * pw = new Woman;

    pw->MakeUp();

    it needs to link the individual data copy of the object to the unique implementation of the method. If the method

    is non-virtual, this task is done at compile time, while if the method is virtual, it is done at run time.

    1.3 Memory Footprint of a Non-polymorphic Object

    Suppose class Derived inherits from Base1, Base2, Base3, and none of them has virtual functions:

    class Base1 {

    public:

    void Hi1()

    { printf("Hi from Base1!\n"); }

    BYTE a1[100];

    };

    class Base2 {

    public:

    void Hi2()

    { printf("Hi from Base2!\n"); }

    BYTE a2[100];

    };

    class Base3 {

    public:

    void Hi3()

    { printf("Hi from Base3!\n"); }

    BYTE a3[100];

    };

    class Derived : public Base1, public Base2, public Base3 {

    public:

    void Hi()

    { printf("Hi from Derived!\n"); }

    BYTE a[100];

    };

    void main()

    {

    Derived *pDerived = new Derived;

    Base1 *pBase1 = (Base1 *)pDerived;

    pBase1->Hi1();

    Base2 *pBase2 = (Base2 *)pDerived;

    pBase2->Hi2();

    Base3 *pBase3 = (Base3 *)pDerived;

    pBase3->Hi3();

    }

    The memory footprint of an object of class Derived and the addresses of the pointers will be like:

    pDerived

    pBase1 pBase2 pBase3

  • 8/6/2019 Cpp Advanced

    5/53

    a1 a2 a3 a

    0 100 200 300 400

    Fig 1. Memory footprint of a non-polymorphic type

    From this footprint you can see some important facts:

    An object of a non-polymorphic class doesnt need to carry any information about addresses of the methods,

    because the linking of non-virtual functions are already done by compiler at compile time. So the object

    contains purely data members. The references such as pBase1, pBase2, pBase3, etc. are only used to access

    the data members, not the methods.

    The memory of base classes is allocated in front of the derived class, which conforms to the sequence of object

    construction.

    The address of the FIRST base class is the same as the derived class.

    When a pointer of the derived class is casted to the base class, it is pointing to the base-class part of the memory.

    1.4 Non-virtual Function Call

    When compiler sees a call to a non-virtual method:

    because only the class which declares the method can implement it, compiler will directly link the call to the

    address of the specific method at compile time.

    1.5 Virtual Function Call Using vPtr and vtable

    When compiler sees a call to a virtual method:

    void Go(Vehicle * pv)

    {

    pv->StartEngine();

    pv->Accelerate();

    }

    it has no idea on the addresses of the virtual methods, because when method Go is called at run time, the

    parameter Vehicle * pv can be passed a pointer to an object of any derived class, such as FamilyCar, 4WD,PickUpTruck, Van, etc., each with its own implementations ofStartEngine and Accelerate at different

    memory locations.

    Therefore, there must be a mechanism for a program to figure out the locations of the virtual methods at run time.

    Now consider the following virtual function call:

    pBase2->Hi2();

    A late-binding process involves the following activities:

    Compiler adds a hidden vPtr member to the class, and generates one unique vtable for the class.

    At compilation time, when compiler sees the definition of a class with virtual methods, it will build a virtual

    table (vtable) for the class, which is an array of function pointers to the implementations of all the virtual

    methods, and add a hidden data member vPtr to the class definition as the FIRST data member.Now suppose the methods of classes in Fig. 1 (Hi, Hi1, Hi2, Hi3) are all virtual functions. The memory

    footprint of an object of class Derived becomes:

    pDerived

    pBase1 pBase2 pBase3

    vPtr a1 vPtr2 a2 vPtr3 a3 a

    0 4 104 108 208 212

    312 412

    a1 are data members of base class 1, a2 are of base class2, , a are of base class 1.

    Fig. 2. Memory footprint of a polymorphic type object

  • 8/6/2019 Cpp Advanced

    6/53

    As you can see in the memory footprint, if you use a Base2 pointer to receive a Derived object, for example,

    this pointer will point to memory offset 104 as pBase2 does.

    Note that each Derive object will have its own memory footprint, with the same structure but in different

    memory locations. However, the vPtrs will all be pointing to the same method implementations, in other

    words, the vPtr2 of two instances will contain the same address.

    The derived-class and the first base class shares the same vPtr, which points to their shared merged vtable

    (see following section Inheritance of Base-class vPtrs for details). The rest of the base classes have their

    own vPtrs.

    Note that no matter how complicated the inheritance hierarchy is, a function pointer in the vtable always

    points to the latest/lowest implementation of the virtual function in the inheritance hierarchy.

    Compiler generates code to do dynamic binding using the vtable.

    At compilation time, when compiler sees a call to a virtual method thourgh a pointer (pBase2->Hi2( )), it

    knows that the address of the function is only known at run time, so it will not try to find the implementation

    of the function. Instead, it knows that the pointer (pBase2) will be pointing to a vPtr at run time. So it

    generates code to go through the vPtr to find the vtable (whose composition is already know from the type

    of the pointer), and go to a certain entry of that vtable, fatch that function pointer, and make the call.

    At run time, when an object is created out of this class definition, its vPtr member will be assigned the address of

    the classs vtable.

    1.6 A Language Needs Compiler and Type Information to Utilize vtable

    To directly link the address of the implementation of a function at compile time (for a non-polymorphic object),

    or to generate code to find it out at run time (for a polymorphic one), the compiler needs to access the class

    definition of the pointer, and the type definition of the pointer is all it wants to know. Because of this, even if

    pBase2 is actually pointing to an object of class Derived, it can not access class Deriveds method Hi, because

    the code generated by the compiler only knows the vtable of class Base2. For the same reason, the following

    function call doesnt work:

    void * pvoid = new Derived;

    pvoid->Hi();

    Scrip languages such as VBScript, JavaScript and early versions of VB do not have a compiler and do not do

    compilation. They explain and run the code line by line at run time. Therefore, they can not generate code before

    run to access the vtable. So they can not utilize vtable and can not directly use polymorphism.

    1.7 Polymorphic Base Classes are placed at the front

    Now suppose only Base2 and Base3 have virtual methods while Base1 doesnt. The memory footprint of Derived

    will become:

    pDerived

    pBase2 pBase3 pBase1

    vPtr a2 vPtr a3 a1 a

    0 4 104 108 208 308 408

    Fig. 3. Memory footprint of a polymorphically mixed type

    You can see that the memory block of polymorphic base classes (Base2 and Base3) are moved to the front, while

    the non-polymorphic base classes (Base1) are moved to the back.

    1.8 Inheritance of Base-class vPtrs

    From the Fig.2 you can see, the vPtr member of the second and third base class was inherited, but the vPtr of

    the first base class is not. Suppose class D inherits only from C, C only from B, and B only from A. When

    compiler construct the vtable for D, it directly merges the vtables of A, B and C into Ds, so that class D has

    only one vPtr pointing to one vtable. The vtable contains function pointers to the lowest implementations of all

    virtual functions across the inheritance hierarchy, no matter where they are defined.

    However, due to multiple inheritance, a class may indirectly inherit from many classes. If we decide to merge thevtables of all the base classes into one, the vtable may become very big. To avoid this, instead of discarding the

  • 8/6/2019 Cpp Advanced

    7/53

    vPtrs and vtables of all base classes and merging all vtables into one, the compiler only does it to all the FIRST

    base classes, and retains the vPtrs and vtables of all the subsequent base classes and their base classes.

    In other words, in an objects memory footprint, you can find the vPtrs of all its base classes all through the

    hierarchy, except for all the first-borns.

    In Fig. 2, virtual function Hi3 is defined in class Base3. When it is called:

    pDerived->Hi3();

    even if it is implemented in class Derived, the program will still go to the vPtr ofBase3 (offset 208 in the

    footprint), then to Base3s vtable, then to Hi3s function pointer, which points back to the implementation in

    class Derived. We can prove it by setting Base3s vPtr in offset 208 to 0. Thus the address ofBase3s vtable is

    lost, and error will cause the program to be shut down:

    pd->Hi3(); // virtual function Hi3 is defined in Base3. Works fine here

    ::memset( (Base3 *)((DWORD)pd + 208), 0, 4);

    pd->Hi3(); // Error happens!

    In a typical case, the derived class inherits from a group of base classes, which can be interfaces (classes with

    only public pure virtual functions) or classes with some implementations. The derived class does not define any

    new virtual function, it only implements the virtual functions defined in the base classes, or simply group the

    services of the base classes together. In such a case, the vtable of the derived class is simply the vtable of the

    first base class. Therefore, the first vPtr in the derived-class object points to the first base classs vtable, the

    second vPtr points to the second base classs vtable, and so on. Everything is clean and neat.

    1.9 De-referencing the vPtr

    Comparing Fig.2 with Fig.1, because the binding of virtual methods are only done at run time, the object contains

    not only data members but also vPtrs telling the addresses of all the virtual functions. When you have a reference

    say pBase2, you know that the first four bytes from the given address is a vPtr ofBase2, followed by the data

    members ofBase2.

    Base2 * pBase2 = new Derived;

    pBase2->Hi2(); // OK!

    cout a2[3]; // OK!

    pBase2->Hi3(); // Compile error: 'Hi2' : is not a member of 'Base3'

    Because of this structure, if you only want to access virtual functions defined in Base2 and its own part of data

    members, not the virtual functions and data members of other classes, what you really need is the address of

    Base2s vPtr.

  • 8/6/2019 Cpp Advanced

    8/53

    2. MULTIPLE INHERITANCE

    2.1 Multiple Inheritance and Interface

    It is commonly believed that multiple inheritance tends to mass things up. That's why Java language which is a

    simplified version of C++ forbids multiple inheritance. However, multiple inheritance from interfaces can be very

    helpful. That's why Java specifically introduced the concept of interface. In C++ interface can be represented by

    an abstract class with all methods being pure virtual.An interface groups a number of methods which are logically closely related. It represents one of possibly many

    aspects of a data type. For example, a Human class may depict many aspects of a person a person has its

    psychological aspect, physical aspect, experience aspect, technical skills aspect, hobbies aspect, family aspect,

    etc. If all methods are grouped in one class, firstable there may be a hundred classes and it may appear to be quite

    dounting, secondly if another program only wants to deal with one aspect ofHuman class say the physical aspect

    such as weight, height, blood pressure, etc., it will have to store a reference to the whole class, and will be able to

    know about all other unneeded aspects of the class such as the person's technical skills. This is typically not

    desireable. We want objects to know as little as possible about each other, so that they are loosely coupled.

    If we have a group of interfaces each representing one aspect ofHuman, and let Human inherit from and

    implement all these interfaces, such as IPsychological, IPhysical, ITechnical, IFamily, then e.g. interface

    IPhysical will only include methods such as GetHeight, GetWeight, GetBloodPressure, etc. It is logically

    more simple, and more importantly, if an other program only wants to deal with the physical aspect ofHuman, it

    can store a IPhysical reference, then it does not know any other aspects of the Human class. Information hiding

    is much better achieved.

    This mechanism is enforced in COM programming. Clients can never get a reference to the whole class. They

    only get references to interfaces.

    2.2 Virtual Inheritance

    When one class inherits from more than one classes, that is called multiple inheritance. Among multiple

    inheritance, there is a special case called diamond inheritance. One example is: class ostream and istream both

    inherits from class ios, and class iostream inherits from both istream and ostream.

    In polymorphism, when we let a base-class pointer point to a derived-class object, that pointer actually points to

    the base-class object within the derived-class object. In diamond inheritance, however, because the derived class

    contains two duplicated base-class objects, compiler doesnt know which part the pointer should point to.Therefore it will prompt an error message.

    Example:

    class Base {};

    class Derived1 :public Base {};

    class Derived2 :public Base {};

    class Multi :public Derived1, public Derived2 {};

    The following statement is illegal:

    Base * ptr = new Multi;

    But the following statements are legal:

    Derived1 * ptr1 = new Multi;Derived2 * ptr2 = new Multi;

    because the compiler can locate the Derived1 or Derived2 part of object in Multi object.

    To solve this problem, use virtual inheritance, which only allows one sub-object:

    class Base {};

    class Derived1 : virtual public Base {};

    class Derived2 : virtual public Base {};

    class Multi : public Derived1, public Derived2 {};

    int main()

    { Base * ptr = new Multi; }

    2.3 Method Name Clash and Ambiguity in Multiple InheritanceSuppose class Derived inherits from two base classes Base1 and Base2, each has an implemented virtual method

  • 8/6/2019 Cpp Advanced

    9/53

    Hi with the same signature. If class Derived provides its own implementation ofHi, then the function pointers of

    method Hi in the two vtables ofBase1 and Base2 will both point to Deriveds implementation. Therefore the

    two different implementations provided by Base1 and Base2 are supressed.

    IfDerived does not provide its own implementation, the two implementations in the base classes are retained.

    However, when you call this method through a reference ofDerived, no one knows which implementation you

    want to call. Compiler will complain over this call. You can only call through a base-class vPtr, so that the

    program knows which implementation you are calling:

    #include "stdafx.h"

    struct Base1 {

    virtual void Hi()

    { printf("Hi from Base1!\n"); }

    };

    struct Base2 {

    virtual void Hi()

    { printf("Hi from Base2!\n"); }

    };

    struct Derived : public Base1, Base2 {};

    int main(int argc, char* argv[])

    {

    Derived * pDerived = new Derived;

    pDerived->Hi(); // Ambiguity!

    ((Base1 *)pDerived)->Hi(); // Base1::Hi will be called

    ((Base2 *)pDerived)->Hi(); // Base2::Hi will be called

    return 0;

    }

  • 8/6/2019 Cpp Advanced

    10/53

    3. TEMPLATES

    3.1 Function Template

    Suppose we want to write a function which receives two numbers and prints them out. It may either receive an int

    or a float or a char. If we use overloaded method, we have to create nine overloaded methods for the

    combination, such as:

    void print(int a, int b)

    { cout

  • 8/6/2019 Cpp Advanced

    11/53

    b = b0; }

    template < class T1, class T2 >

    void Test::print() const

    { cout

  • 8/6/2019 Cpp Advanced

    12/53

    3.5 Templates and Friends

    Inside a class template X that had been declared with

    template < class T>

    class X {

    ...

    };

    There are the following ways of friendship declaration:1. friend void f1( );

    f1( ) is a friend of all template classes.

    2. friend void A::f1( );

    method f1( ) of class A is a friend of all template classes.

    3. friend void f1( X & );

    f1( X & ) will be a friend of class X only, and f1( X & ) will be a friend of class X

    only.

    4. friend void C::f1( X & );

    method C::f1( X & ) will be a friend of class X only. C is the name of another class.

    5. friend class Y;

    every method of class Y is a friend of all template classes.

    6. friend class Y;

    class Y is a friend of class X only.

  • 8/6/2019 Cpp Advanced

    13/53

    4. EXCEPTION HANDLING

    4.1 What is Exception Handling

    First consider a normal way to handle errors:

    const float reciprocal( const float denominator )

    {

    if(denominator == 0)

    {

    cout

  • 8/6/2019 Cpp Advanced

    14/53

    to return to the throw point.

    If the exception is not caught, the method call stack is unwound and control will further more exit to the outer

    scope and continue searching the following catch blocks. If it is already the most outer block such as main,

    method terminate is called, which by default will call method abort.

    4.6 catch Block

    Parameter Name in catch headerA catch keyword specifies in ( ) the type of the object to be caught, and an optional parameter name.

    When exception happens, a temporary copy of the thrown object is created and initialized. If the catch block has

    defined a parameter name of that type, this name will be given to this temporary object and used to access the

    data members and methods of the object. If the catch block does not have an parameter name, then the thrown

    objects internal information can not be accessed, and it is only used to match the catch block. After handler

    finishes, the temporary object is destroyed.

    So if you want to pass addtional information from the thrown point to the catch block, you have to put this

    information inside the thrown object (as its data member) and define an object name in the catch block, so that

    you can access the information through it.

    catch allThe following catch specification

    catch(...)

    will catch all exceptions.

    catch through inheritanceDue to inheritance hierarchies, a derived-class object can be caught by a handler specifying either the derived-

    class type or the base-class type.

    As a general rule, put the specific type handlers in the front, such as derived-class type handlers, and put the

    generic handlers after, such as base-class type handlers, and put catch (... ) at the last. If you reverse the

    sequence, the exceptions will all be caught by generic handlers and will never get to the specific ones.

    A void * handler will catch all exceptions of pointer type.

    Release the Resource in catch BlockWhen an exception happens, the try block terminates and all automatic objects inside the try block are destroyed

    before the handler begins executing. However, it is possible that some allocated resources are not yet released in

    the try block. The catch handler, if possible, should release these resources, e.g., delete the space allocated by

    new, and close any files opened in the try block.

    Copy Constructor of the Thrown ObjectThe handler must have access to the copy constructor of the thrown object. It means that the header file of the

    thrown object should be included.

    4.7 Rethrow an Exception

    A catch handler itself can discover an error and throw an exception. Or it may do some primary handling and then

    throw it again. To rethrow, simply say

    throw;

    A rethrown exception will exit the outer try block and search the following handlers.

    catch (... ) can be used to perform recovery that doesnt depend on exception types, such as releasing common

    resources. Then it may rethrow the exception to alert more specific catch handlers.

    4.8 Exception Specifications

    An exception specification (also called throw list) is throw followed by a list of types enclosed in "( )":

    void print() throw(a, b, c)

    {...}

    It can be placed after any method header, to restrict the kind of exceptions that can be thrown by this method.

    This is used to notice the client who make use of this code the types of possible exceptions your code may throw.

  • 8/6/2019 Cpp Advanced

    15/53

    All derived-class types based on the throw list types can also be thrown.

    If any exception not listed in the throw list is thrown, method unexpected is called, which will call the method

    specified by method set_unexpected, or by default it will call method terminate.

    Method terminate will call the method specified by method set_terminate, or by default it will call method

    abort.

    Header files of these methods

    unexpected, set_unexpected:

    terminate, set_terminate:

    abort:

    Method set_unexpected and set_terminate take method names i.e. function pointer as arguments. The methods

    must be with void return type and no arguments. Method set_unexpected and set_terminate returns a the name

    of the method last called by terminate or unexpected. This enables the programmer to save the name and use it

    later.

    If the use-defined method specified in set_terminate or set_unexpected does not exit the program, method

    abort will be automatically called to end the program execution at last.

    If you include exception "std::bad_exception" (derived from base class "exception" whose prototype is in

    header file ) in the throw list of a method, when unexpected throw happens, method unexpected willthrow "bad_exception" instead of calling terminate by default or user-defined method specified by method

    set_unexpected.

    An empty throw list "throw" means that the method wouldn't throw any exception without alerting method

    unexpected.

    4.9 Stack Unwinding

    When an exception is thrown but not caught in a certain scope, the method call stack is unwound , which means

    that the method which throwns the exception terminates, all local objects destroyed, then control returns to the

    point where this method was called.

    If that calling point is in a try block, control will leave that try block and search the following handlers. If that

    point is not in a try block or the search fails, stack unwinding will happen again -- until it reaches main, and

    method terminate will be called, as said before.

    #include

    #include

    using namespace std;

    void method3() throw(runtime_error)

    {

    throw runtime_error( "Thrown by method 3!" );

    cout

  • 8/6/2019 Cpp Advanced

    16/53

    cin >> a;

    }

    As a general result, if you dont provide any exception handling code and exception happens, the program will be

    terminated. But if you provide a proper exception handling measure, the program can go on working.

    4.10 Exception Thrown from Constructor and Destructor

    When an exception is thrown in a constructor, destructors for all the local objects created as part of the object

    being constructed will be called. If a destructor which is called to unwind the stack throws out an exception,method terminate will be called.

    Normally you should avoid throwing an exception out of a constructor without catching it and doing necessary

    clean up in the constructor first. A simple solution is to use default constructor to create a very "safe" model of

    object, then use another method which is typically called "Init" in many applications to initialize it.

    The following example demonstrates why you should avoid throwing exception out of constructor without

    catching and processing it first. Suppose in the constructor before the throw point you have allocated some

    dynamic memory with operator new to a pointer data member:

    class Employee {

    ...

    Image * const m_pImage;

    AudioClip * const m_pAudio;

    };

    Employee(..., string image, string audio) : ..., m_pImage(NULL), m_pAudio(NULL)

    {

    ...

    m_pImage = new Image(image);

    m_pAudio = new AudioClip(audio); // throw point

    }

    When the exception is thrown by "new AudioClip(audio)", the constructor is exited, the dynamic memory

    become memory leak. The destructor of the class is not called, because a destructor is only called for a fully

    constructed object, and because the constructor is half-way termicated by the throwing of exception, the object is

    not fully constructed.

    The correct way to deal with such situation is to catch possible exception in the constructor, and in the catch

    block, delete all dynamic allocated resources, then rethrow the exception again as a notification:

    Employee(..., string image, string audio) : ..., m_pImage(NULL), m_pAudio(NULL)

    {

    ...

    try {

    m_pImage = new Image(image);

    m_pAudio = new AudioClip(audio);

    }

    catch(...)

    {

    delete m_pImage;

    delete m_pAudio;

    throw;

    }

    }

    This solution works fine except if the class uses constant pointer members:

    class Employee {

    ...

    Image * const m_pImage;

    AudioClip * const m_pAudio;

    };

    Such pointer members must be initialized in the member initializer, where you can not put try and catch blocks.

    The ultimate solution is: use auto_ptr< > class or similar classes to wrap the raw pointers, so that they become

    local objects:

    class Employee {

    ...

    auto_ptr m_image;

  • 8/6/2019 Cpp Advanced

    17/53

    auto_ptr m_audio;

    };

    As we already know, when a constructor is terminated by exception, all local objects are destroyed by calling

    their destructors. The destructor ofauto_ptr< > will delete the object pointed by the wrapped pointer.

    4.11 Resource Leak

    If a resource is allocated before the throw point and supposed to be released after the throw point, when

    exception happens it prevents the resource from being released. This is called resource leak. If the resource ismemory, it is called memory leak.

    One technique to resolve this problem is to create a local object with a pointer which points to this resource, and

    define a proper destructor for this object to delete the resource. Then when exception happens, destructors for this

    object will be called, and the resource will be released.

    C++ has already provided a class template, auto_ptr, which contains a pointer pointing to a given type. The

    destructor of this class destroys the resource to which the pointer points to. Operator "->" and "*" have been

    overloaded, so that auto_ptr objects can be used as normal pointers to access and dereference. For example,

    auto_ptr ptr = new Employee("Frank Liu");

    ptr will be treated as a local object and its destructor will be automatically called when leaving scope, in which

    there is usually the following statement

    delete ptr;

    Header file ofauto_ptr is .

    4.12 Insufficient Memory when Using new

    When new fails to obtain memory, older-version compilers will return 0. The ANSI/ISO C++ draft standard

    version specifies that when new fails, it automatically throws out a "std::bad_alloc" exception defined in header

    file . If you still want new to return 0, you have to put "(nothrow)" after new:

    int * ptr = new (nothrow) int[500];

    If you pass the name of a user-defined function (which has void return type and no arguments) as an argument to

    method "set_new_handler", whose header file is , then new will not throw the "std::bad_alloc"

    exception. Instead it will call that handler repeatedly until it sucessfully allocates memory. You can evenoverload operator new and "set_new_handler" for the class in question. For details, see Ref1.

    4.13 Standard Library Exception Hierarchy

    Experience shows that exceptions fall nicely into a number of categories. The C++ draft standard includes a

    hierarchy of exception classes. The base class is "exception", whose header file is , which offers the

    method "what" to access the appropriate error message.

    Exception class "runtime_error" and "logic_error" are immediately derived from "exception". Their header file

    is . Each of them has several derived classes.

    From "logic_error" there are:

    invalid_argument invalid arguments are passed to a method

    length_error a length larger than the maximum size of the object is used to that object.

    out_of_range a value is out of range, such as a subscript to an array.

    From "runtime_error" there are:

    overflow_error

    underflow_error

    Also derived from class "exception" are the exceptions thrown by C++ language features:

    bad_alloc thrown by new when it fails to obtain memory

    bad_typeid

    bad_cast thrown by dynamic_cast.

  • 8/6/2019 Cpp Advanced

    18/53

    4.14 Difference Between C++ and Javas Exception Handling

    The biggest difference is: Java compiler forces that all exceptions should be caught and handled.

  • 8/6/2019 Cpp Advanced

    19/53

    5. DATA STRUCTURES

    5.1 Data Structure

    A collection of the same type of objects is called a data structure.

    5.2 Linked List

    If you want to maintain a collection of the same objects, you may first think of an array of that type of objects.

    But as we already know, the size of the array have to be decided before compiling. Then we may think of using

    dynamic memory allocation to create the array, so that we can decide the memory size at run time:

    Test * array = new Test [size];

    But still there is limitation: once the array is created, you can not vary the size, unless you delete the whole array

    and create a new one. You can't expand or shrink the list dynamically.

    In order to put a group of objects into a flexible data structure, you can use wrapper objects to wrap around the

    original objects, with some additional pointers pointing to each other. Through these pointers, all the wrapper

    objects can be linked together. Such a pointer-linked collection is called linked list.

    The disadvantages of linked list is that you can not directly access one node with subscript like array. You have to

    find the first one, go through the list through the pointers, until you reach the one you want. The other thing to

    remember is: nodes in linked list are created dynamically. They are not as fast as automatic objects.

    If in a data structure each of the nodes is only connected to one node downstream and one node upstream, such a

    structure is called a "linear linked list".

    There are "open-loop" lists, each has a start node and end node. The pointer of the end node is always set to 0 to

    mark the end. There are also "close-loop" lists, whose start node and end node are connected together forming a

    circular loop of nodes.

    There are Singly-linked lists whose nodes only contain one pointer pointing to the downstream node. You can't

    traverse upstream. There are also doubly-linked lists whose nodes each contains a pointer to both the upstream

    and downstream node. You can traverse on both directions.

    Using pointers to link objects you can create lists as queues, which only removes a node from the front and adds a

    node to the back, or stacks, which only adds and removes nodes to and from the front, or any other types.

    5.3 Tree

    A tree is a structure whose nodes each connected to one upstream node and many down-stream nodes. A binary

    tree is a structure which is connected to one upstream and two downstream nodes. A tree has a root node.

    5.4 Struct

    A structure is a simplified class with all public members:

    struct Card {

    char * face;

    char * suit;

    };

    Structure members are not necessarily stored in consecutive bytes of memory. If the word length of the computer

    is 2 bytes, and one data member is only one-byte long, there will be a one-byte hole between this member and the

    next member. Because the value stored in the hole is undefined, a structure can not be compared.

    A structure is initialized the same way as an array:

    Card c1 = {Three, Hearts};

    If the initialization list contains not enough initializers, the rest will be initialized to 0.

    5.5 Bitwise Operators

    Up to now data is regarded as a whole number without considering its bit status. Internally a number is

    represented by binary bits. C++ provides the following bitwise operators:

    &: bitwise AND. One bit is 1 if the corresponding bits of the both operands are both 1.

    |: bitwise inclusive OR. One bit is 1 if one of both corresponding bits of the two operands is/are 1.

  • 8/6/2019 Cpp Advanced

    20/53

    ^: bitwise exclusive OR. One bit is 1 if the corresponding bits of the two operands are different: one is 1 and one

    is 0

    >= b means a = a >> b.

    5.6 Example: Display a Number in Binary Form

    void BinaryDisplay1(unsigned value)

    {

    mask = 1

  • 8/6/2019 Cpp Advanced

    21/53

    int isxdigit(int c): returns true if c is a hex digit -- '0', '1',..., 'E', 'F'.

    int isalpha(int c): returns true if c is a letter.

    int isalnum(int c): returns true if c is a letter or a digit.

    int islower(int c): returns true if c is a lower case

    int isupper(int c): returns true if c is a upper case

    int tolower(int c): if c is upper case then return c in lower case, otherwise return c unchangedint toupper(int c): if c is lower case then return c in upper case, otherwise return c unchanged.

    int isspace(int c): returns true if c is a whitespace character: newline('\n'), space, form feed('\f'), carriage

    return('\r'), horizontal tab('\t'), or vertical tab('\v').

    int iscntrl(int c): returns true if c is a control character.

    int ispunct(int c): returns true if c is a printing character other than a space, a digit or a letter.

    int isprint(int c): returns true if c is a printing character including space.

    int isgraph(int c): returns true if c is a printing character other than space.

    5.9 String Conversion Methods

    String conversion methods are in .

    double atof(const char *): converts a numbered string to double. E.g.: if ptr = "23.85", double 23.85 will be

    returned.

    int atoi(const char *): converts a numbered string to integer. E.g.: if ptr = "2593", integer 2593 will be returned.

    long atol(const char *): converts a numbered string to long integer.

    double strtod(const char *, char **): converts the double portion of string ptr1 to double, and locate the second

    pointer at the first remaining character in the first string. E.g.: if ptr1 = "1234.2ABCD", double 1234.2 will

    be returned and second pointer will be "ABCD".

    long strtol(const char * ptr1, char ** ptr2, int base): converts the long int portion of string ptr1 to long integer,

    and locate pointer ptr2 at the first remaining character in ptr1. The long integer can be in any base: if base =

    0, then it can be decimal, octal or hexadecimal; if base = 8, it is octal; if base = 10, it is decimal; if base = 16,

    it is hexadecimal. But the base can be any value from 2 to 36, limited only by the number of Latin letters A-

    Z. Using NULL for the second argument will cause the remaining portion be ignored.

    unsigned long strtoul(const char * ptr1, char ** ptr2, int base): only difference with method strtol is the integer

    is unsigned.

    5.10 String Handling Library string.h

    Searching character in stringchar * strchr(const char * s, int c): locate the first c in string s, or return NULL if not found.

    size_t strcspn(const char * s1, const char * s2): return the length of the initial segment of s1 which consists of

    characters not included in s2.

    size_t strspn(const char * s1, const char * s2): return the length of the initial segment of string s1 which consistsonly characters in s2.

    char * strpbrk(const char * s1, const char * s2): locate the first occurrence in s1 of any character contained in s2

    or NULL if not found.

    char * strrchr(const char * s, int c): locate the last occurrence of c in s or NULL if not found.

    char * strstr(const char * s1, const char * s2): locate the first occurrence in s1 of s2 or NULL if not found.

    Memory manipulating methodsThe basic unit of memory is byte, and a byte can be regarded as a character. The following methods manipulate

    memory buffers in characters. The pointer parameters in these methods are declared "void *". Because pointers of

    any type can be directly assigned to a pointer of type "void *", therefore methods can receive pointers of any

    type.

    void * memcpy(void * s1, const void * s2, size_t n)

  • 8/6/2019 Cpp Advanced

    22/53

    Copies n characters from buffer s2 to s1, and return a pointer to the resulting buffer.

    void * memmove(void * s1, const void * s2, size_t n)

    The same as memcpy, but the characters are first copied from s2 into a temporary location, then copied to s1.

    This allows to copy part of one object and overlap with itself:

    int main()

    {

    char x[] = "123 456 789";cout

  • 8/6/2019 Cpp Advanced

    23/53

    6. THE PREPROCESSOR

    6.1 #include

    The #include preprocessor directive causes a copy of a specific file to be pasted in place of the directive before

    compilation. It is used when another file contains the type definition that you want to use. For global variables

    and functions contained in another file, you do not include that file. Instead you inform the compiler with the

    following line before you use it that it is a global variable or function and the compiler should find it out itself:

    extern ULONG m_refCount; // global variable

    extern void Lock(BOOL fLock); // global function

    There are two forms of the #include directive:

    #include

    #include filename

    File included in < > will be searched in the systems pre-defined locations, while the file included by quotes

    will be first searched in the current directory, then the pre-defined locations. < > is used for standard library

    files.

    Most included files are *.h header files. But other files such as *.cpp source files can also be included, as long as

    the definition you want to invoke.

    If header file A makes use of a type defined in file B, normally it should include B before using that type. Such a

    file can be called self-contained. Not all files are self-contained. Some library header files are very

    comprehensive and contains lots of type definitions. Different definitions may use other definitions defined in

    other files. If we want such files to be self contained, we may virtually end up with each header file including all

    other header files. Considering that most clients include such a header file just to use one or two definitions in it,

    it will be a big waste of space. Therefore, such a header may decide not to include some other files. If you want

    to include use type A defined in file X, and type A uses type B which is defined in file Y, it is your duty to insure

    that file Y is included in front of file X:

    // ******* header1.h *********

    #define _STRUCT_A

    structA{

    int a;

    };

    // ******* header2.h *********

    #define _STRUCT_B

    struct B {

    int b;

    };

    // ******* header3.h **********

    #if defined _ STRUCT_A

    struct C {

    int c;

    Aa;

    };

    #endif

    #if defined _ STRUCT_B

    struct D {

    int d;

    B b;

    };

    #endif

    // ******** main ***********

    // header1 must proceed header 3, while header 2 is not necessary

    #include "header1.h"

    #include "header3.h"

    void main(int argc, char* argv[])

    {

    C c;}

  • 8/6/2019 Cpp Advanced

    24/53

    6.2 Conditional Compilation

    #if expression

    ...

    #endif

    Two special expression are:

    #if !defined (SYMBOL)

    #if defined (SYMBOL)

    Their simplified forms are:

    #ifdef

    #ifndef

    Conditional compilation is commonly used as a debugging aid. You can first use

    #define DEBUG 1

    then enclose all debugging codes in

    #if DEBUG

    #endif

    Then after debugging is finished you only need to change 1 to 0 to disable all debugging codes.

    6.3 #define Symbols for Conditional Compilation

    The #define preprocessor directive creates a file-scope symbolic constant. When preprocessor sees

    #define

    it will create a constant named "CONSTANT" in the scope of this file. Then "defined CONSTANT" will return

    true. Therefore, you can use the following predirectives to wrap some code:

    #if !defined _ANY_SYMBOL // #ifndef _ANY_SYMBOL is a simplified version

    #define _ANY_SYMBOL

    ...

    #endif

    The first time preprocessor sees a #include and opens this file, _ANY_SYMBOL is not defined, so thepreprocessor defins _ANY_SYMBOL and goes on. Next time when preprocessor sees another include of this file

    and encounters the #if, it will skip the whole block. This is how you can avoid the enclosed code from being

    included for more than once, which may cause serious problems.

    The constant can be anything. In convention we use the capitialized file name. For example, if the header file is

    "Employee.h", the constant may be "EMPLOYEE_H", "_EMPLOYEE", "_EMPLOYEE_H", etc. Sometimes a

    globally unique ID may be used so that there won't be any confusion.

    A file-scope constant applies only to the code after this. To undefine it, say

    #undef

    A preprocessor constant can also be defined in aglobal scope. In Visual C++, the constants you put in the

    "Project | Settings | C/C++ | Preprocessor directives" field will be defined in global scope. Some commonly used

    constants like "WIN32" and "_DEBUG" are defined in that field. When you choose "Win32 Debug" in "Project |

    Settings", the "_DEBUG" constant will be added automatically. If you choose "Win32 Release", constant

    "NDEBUG" will be added. Then lots of Win32 macros such as assert and your own code may check this

    constants and decide what to behave. For example, if "NDEBUG" is defined, the code within assert will be

    skipped to increase performance:

    void assert(...)

    {

    #if defined NDEBUG

    // assertion work to be done

    #enif

    }

    Look at the following example. Suppose you have two classes defined in two different header files

    class A

    {

  • 8/6/2019 Cpp Advanced

    25/53

    public:

    A()

    {

    #if defined _SAYHELLO

    printf("Hellow from class A!\n");

    #else

    printf("Hi from class A!\n");

    #endif

    }

    };

    class B

    {

    public:

    B()

    {

    #if defined _SAYHELLO

    printf("Hellow from class B!\n");

    #else

    printf("Hi from class B!\n");

    #endif

    }

    };

    If you put "#define _SAYHELLO" in one of the header files, only one constructor will say "Hello" and the otherwill say "Hi". If you put "_SAYHELLO" into the project setting, both constructors will say "Hello".

    6.4 #define for Constants vs. C++ Constants

    #define MAX_POWER 15.3

    #define OVERFLOW_MSG "Data overflow!"

    #define constants such "MAX_POWER" can not be seen by compiler. It is C-style. C++ style constants would be

    using constants. Specifically, if you want to restrain the constant within the scope of a class, use a constant static

    class member:

    private:

    const static int I;

    In the implementation file, you have to initialize it as

    const int A::I = 3;

    However, if you want to use I as array size, compiler has to know its value, and the above way does not work.

    You can use a "enum hack":

    private:

    enum {I = 5};

    float a[I];

    6.5 #define for Macros vs. C++ Inline Template Functions

    Macros are C-style. It has two advantages: it can be applied to different types and it saves a method call. Its

    disadvantage is that there is no type checking. In C++ it can be replaced by template and inline methods which

    has all the benefits of macros and meanwhile performs type checking.

    #define area(r) (3.1416 * (r) * (r))

    ( ) around the argument r and the entire expression forces the proper order of evaluation when the argument is

    an expression.

    Then when preprocessor sees a line like

    area = 3.3 - area(c + 2);

    it will just replace it with

    area = 3.3 - (3.1416 * (c + 2) * (c + 2));

    Such a C-style macro can be replaced by a C++ inline template function with all macro benefits but without

    macro drawback:

    template

  • 8/6/2019 Cpp Advanced

    26/53

    inline const T & Area(const T & r)

    {

    return 3.1416 * r * r;

    }

    If the replacement text in a macro is longer than a line, backslash \ must be put at the end of the line to indicate

    that the replacement text continues in the next line:

    #define check(msg) if(FAILED(hr)) \

    {\::MessageBox(NULL,msg, "Server", MB_OK | MB_TOPMOST); \

    return; \

    }\

    #define macros with stringizing operator #In the macro body, if you put # operator in front of the parameter, the preprocessor will wrap the parameter with

    double quotes. So if a macro defines as

    #define m(msg) ::MessageBox(NULL,msg, "Server", MB_OK | MB_TOPMOST);

    if you say it will become

    m(buf);

    It will become

    ::MessageBox(NULL,buf, "Server", MB_OK | MB_TOPMOST);

    If the macro is

    #define m(msg) ::MessageBox(NULL, #msg, "Server", MB_OK | MB_TOPMOST);

    if will become

    ::MessageBox(NULL, "buf", "Server", MB_OK | MB_TOPMOST);

    #define macros with token pasting operator #### is used to join two tokens together. So if the macro is defined as

    #define m(x) parameter##x

    then if you say

    cout

  • 8/6/2019 Cpp Advanced

    27/53

    7. C - LIKE CODE

    7.1 Redirecting Input & Output on UNIX/DOS Systems

    Normally a programs input comes from keyboard and output goes to screen. In most of the computer systems

    UNIX and DOS systems in particular it is possible to redirect inputs to come from a file, or redirect outputs to a

    file. They can be achieved on the system command line.

    Redirect Input and pipeliningKey in the following on the command line in either UNIX or DOS:

    $ program-name < filename

    it runs the program with its input directed to the file. "" is called theredirect output symbol.

    Using the append output symbol ">>", the program output can be appended to the end of one file:

    $ program-name >> filename

    7.2 Variable-Length Argument List

    This feature is a C feature. In C++ we use method overloading.

    To indicate that a method receives an unspecified number and type of arguments, put "..." in the end of the

    argument list. The header file of the macros and definitions is in .

    int sum(int i, ...)

    {

    int sum = 0;

    va_list a;

    va_start(a, i);

    for (int k = 1; k

  • 8/6/2019 Cpp Advanced

    28/53

    7.3 Command-Line Arguments

    In many systems UNIX and DOS in particular it is possible to pass arguments to main from a command line.

    This is achieved by including two parameters in main's parameter list: integer argc indicates the number of

    passed arguments, and argv is an array of strings to hold the arguments. This method can be used to pass

    information to a program, such as options and filename.

    Command line:

    $ program-name file1 file2

    Program:

    int main(int argc, char * argv[])

    {

    if(argc != 3)

    cout

  • 8/6/2019 Cpp Advanced

    29/53

    signal and the name of a signal handling method. It registers a signal handling method for a specific signal to the

    program, so that when that signal happens, that corresponding method will be called to handle that signal.

    #include

    #include

    void handling(int signal)

    { cout b;

    cout

  • 8/6/2019 Cpp Advanced

    30/53

    }

    Output will be:

    ** Outer loop: i = 0

    Inner loop: j = 0

    Inner loop: j = 1

    Inner loop: j = 2

    Inner loop: j = 3

    ** Outer loop: i = 1

    Inner loop: j = 0

    Inner loop: j = 1

    Inner loop: j = 2

    Inner loop: j = 3

    ** Outer loop: i = 2

    Inner loop: j = 0

    Inner loop: j = 1

    Inner loop: j = 2

    7.10 Union

    A union can have several members of different types including user-defined types. But these different members

    are actually sharing the same block of memory space. At one time a union can only hold one member.

    At different times during a programs execution, not all objects are relevant in the meantime. So a union

    combines a group of objects which are never used at one time to save space. The number of bytes used to store a

    union is at least the size of the largest member.

    A union is declared in the same way as a struct or a class:

    union Number {

    int x;

    float y;

    };

    Number n1;

    n1.x = 33;

    The following operations can be performed on an union:

    Assignment (between same type of unions);

    Take reference (&);

    Access union members through . and ->;

    Unions can not be compared with each other for the same reason as structures.

    An union can also have constructor, destructor or other methods, just like a class. But there is no inheritance or

    virtual issues for union.

    An anonymous union is a union without a type name. Such a union can not be used to define objects later, but

    itself defines an object, whose members can be directly accessed in its scope just like normal variables, without

    using . or -> operators:

    union {

    int x;

    float y;

    };

    x = 33;

    7.11 Linkage Specification extern "C"

    This is to tell the compiler that the following function is not defined in this program, and it is the compiler's job to

    look for the definition. This is also to inform the compiler that the following function was compiled as C code.

    C++ specially encodes method names for type-safe linkage, but C doesnt. So when an attempt is made to link C

    code with C++ code, the method compiled in C will not be directly recognized.

    For a single method:

  • 8/6/2019 Cpp Advanced

    31/53

    extern C method prototype

    For multiple methods:

    extern C

    { method prototypes }

    7.12 __stdcall

    This is to indicate the calling convention which is used to call Win32 API functions. The callee cleans the stack,so the compiler makes vararg functions __cdecl. Functions that use this calling convention require a function

    prototype. The following list shows the implementation of this calling convention:

    Argument-passing order: Right to left.

    Argument-passing convention: By value, unless a pointer or reference type is passed.

    Stack-maintenance responsibility: Called function pops its own arguments from the stack.

    Name-decoration convention: An underscore (_) is prefixed to the name. The name is followed by the at

    sign (@) followed by the number of bytes (in decimal) in the argument list.

    Therefore, the function declared as int func( int a, double b ) is decorated as

    follows: _func@12

    7.13 typedeftypedefis used to assign an alias to an existing data type. Then this synonym can be used in place of the original

    data type:

    typedef Card * PCARD;

    ...

    PCARD pCard;

    typedefcan make a program easier to understand and modify, more portable. It can also be used to represent a

    very long type. In C programming, especially, if you define a struct in the following way

    struct Employee {

    LPSTR name;

    LPSTR dept;

    short age;

    };

    When you create an instance of the struct, you have to say

    struct Employee e1;

    While if you use typedef to define the structure:

    typedef struct {

    LPSTR name;

    LPSTR dept;

    short age;

    } Employee;

    when you create an instance you can say

    Employee e1;

  • 8/6/2019 Cpp Advanced

    32/53

    8. CLASS STRING & STRING STREAM PROCESSING

    8.1 Basics of string

    string s1("Hello!"); // create a string "Hello!"

    string s1 = "Hello!";

    string s1(5, 'X'); // create a string "XXXXX"

    s1 = 'a';

    Constructing a string that is too long will throw a length_error exception.

    Unlike C-style string char *, string is not required to end with NULL. NULL is regarded as a normal character

    in string.

    Stream insertion and extraction operator (>) and method getline have been overloaded for string.

    8.2 String Assignment

    Assignment such as s1 = s2 is not allowed for char * but allowed for string, because string is a class and

    operator = has been properly overloaded. Or you can explicitly call strings assign method

    s1.assign(s2);

    s1.assign(s2, start, number);

    The second call assigns number of characters of s2 to s1, starting from subscript "start":

    A character in a string can be accessed by

    s1[2] = 'a'; // or

    s1.at[2] = 'a';

    [ ] operator doesn't provide range check, while at method does.

    8.3 Creating Strings

    There are two ways to create a string:

    string name(Frank Liu);

    or

    string name = Frank Liu;

    The first one is more efficient than the second, becasue the first one only calls class strings constructor, while the

    second one calls strings default constructor first, then its assignment operator.

    8.4 Appending Strings

    string s1 = Frank;

    string s2 = s1 + Liu;

    s2 will be Frank Liu. Or you can explicitly call strings append method:

    s1.append(" Liu"); // or

    s1.append(s2, start, end);

    The second call appends part of s2 to s1, from subscript start to end.

    8.5 Comparing Strings

    The following operation can be done:

    ==

    !=

    > & >=

    < & s2, zero if s1 == s2, negative if s2 < s2.

  • 8/6/2019 Cpp Advanced

    33/53

    result = s1.compare(start1, length, s2, start2, length); // or

    result = s1.compare(start1, length, s2);

    compare part of s2 with part of s1. s1 starts from "start1", s2 starts from "start2", number of characters is

    "length".

    8.6 Sub-string

    cout

  • 8/6/2019 Cpp Advanced

    34/53

    s1.insert(before, s2);

    inserts s1 into s1 before "before".

    s1.insert(before, s2, start, number);

    insert part of s2 into s1 before "before".

    8.12 Conversion to C-Style char *

    const char * ptr = s1.data();

    returns the content of s1 to ptr, without terminating NULL character. So you must add NULL to the end of string

    ptr.

    s1.copy(ptr, length, 0);

    copy s1 to ptr, without terminating NULL character.

    s1.c_str() returns a NULL-terminated const char *.

    Whenever possible, always use string instead of C-style char *.

    Converting a string containing NULL characters to C-style char * may cause logic errors.

    8.13 string Iterators

    string::const_iterator i1 = s1.begin();

    cout

  • 8/6/2019 Cpp Advanced

    35/53

    9. STANDARD TEMPLATE LIBRARY (STL)

    9.1 Introduction to Container

    Containers are objects that contains objects. In STL, containers only encapsulates some primitive operations. The

    algorithms are independent of the containers. They manipulate containers using iterators.

    Containers CategoriesSequence Containers: organize a collection of objects into a strictly linear arrangement. There are four types of

    sequence containers: normally arrays, vectors, lists and deques.

    Sorted Associative Containers: a collection which is kept sorted with keys. There are four kinds of associative

    containers: sets, multisets, maps and multimaps.

    Adapters: inherit from the two first-class containers and modify their interfaces. There are three major types:

    stack, queue, priority_queue

    Common methods of all containersdefault constructor, copy constructor, destructor;

    operator =, =, ==, !=;

    empty: return true if container has no elementmax_size: maximum number of elements for a container

    size:number of elements currently in the container

    swap:swaps the elements

    Common methods of all containersbegin: returns an iterator or const_iterator to the first element of the container

    end: returns an iterator or const_iterator to the next position after the end of the container

    rbegin: returns an reverse_iterator or const_reverse_iterator to the last element of the container.

    rend: returns an reverse_iterator or const_reverse_iterator to the position before the first element of the

    containererase: erases one or more elements from the container

    clear: erases all elements from the container

    Type of Container ElementContainer element can be of any type, both built-in types or user-defined types. However, user-defined types

    which is to be stored in containers must support a minimum set of methods. When an element is inserted into a

    container, a copy of the element is made. If default memberwise copy can not do the job, then the type must

    provide its own copy constructorand assignment operator. Also, associative containers and many algorithms

    require elements to be compared. So the element type should provide overloaded operator = = and

  • 8/6/2019 Cpp Advanced

    36/53

    use one type of iterator.

    However, C++ did not achieve such a complete abstraction. When you create an iterator, you have to specify the

    type of container it belongs to a vector, a map, a multiset, etc., and the type of the container element - integer,

    string, or other user-defined types.

    We can not create an iterator pointing to a certain element in a container. We can only ask a container to return an

    interator pointing to a certain element.

    Normal arrays are random access containers, pointer to arrays i.e. array names are random access iterators.

    9.3 Iterator Types

    iterator: refer to objects which can be modified

    const_iterator: refer to objects which can not be modified

    reverse_iterator

    const_reverse_iterator

    To create an iterator, Three kinds of information need to be provided: container type, element type and iterator

    type. It is not abstracted enough.

    vector::reverse_iterator p1;

    9.4 Iterator Capabilities

    Input iterator: used to read an element from a container. It can only move in the forward direction one element

    at a time, and only support one-pass algorithms.

    Output iterator: used to write an element to a container. It can only move in the forward direction one element

    at a time, and only support one-pass algorithms.

    Forward iterator: combines the capabilities of the input and output iterator, and retains their position in the

    container as state information.

    bidirectional iterator: add the ability to move in both backward and forward directions.

    Random access iterator: add the ability to directly access any element such as a pointer to an array.

    The capability of an iterator is is decided by the container to which it belongs. Different kinds of containersprovide iterators of different capabilities.

    9.5 Iterator Capabilities Supported by Different Containers

    vector: random access

    list: bidirectional

    deque: random access

    set: bidirectional

    multiset: bidirectional

    map: bidirectional

    multimap: bidirectional

    stack: no iterator supported

    queue: no iterator supported

    priority_queue: no iterator supported

    Different types of STL algorithms requires iterators of different capabilities.

    9.6 IO Iterators

    There are other two types of iterators which do not belong to any container: istream_iterator and

    ostream_iterator. They are used to input and output a certain type of object in a type-safe manner from/to an IO

    object such as cin and cout.

  • 8/6/2019 Cpp Advanced

    37/53

    9.7 Operations Supported by Different Iterators

    All iterators:

    ++p

    p++

    Input iterators:

    *pp1 = p2

    p1 == p2

    p1 != p2

    Output iterators:

    *p

    p1 = p2

    Forward iterators:

    provide all the methodality of both input and output iterators.

    Bidirectional iterators:

    -- p

    p -

    Random access iterators

    p + i

    p i

    p += i

    p -= i

    p[i]

    p1 < p2

    p1 p2

    p1 >= p2

    9.8 Sequence ContainersSequence containers include vector, list and deque. vector stores elements contiguously in memory, list is linked

    with double pointers, and deque combines the advantages of vector and deque.

    They have some common methods:

    front: return an iterator to the first element in the container

    back: return an iterator to the last element in the container

    push_back: insert a new element at the end of the container

    pop_back: remove the last element of the container

    insert:insert one or a range of elements into the container before the indicated location.

    9.9 vector

    vector is the most commonly used container in STL.

    Class vector provides a data structure with contiguous memory locations. This enables efficient, direct access to

    any element via subscript operator [ ] like arrays. So a vector is a more intelligent and complex array.

    A vector returns random access iterators.

    Because only random access iterators support

  • 8/6/2019 Cpp Advanced

    38/53

    Suppose you have a vector of 9 elements, now you delete the 5th. The vector will call elements assignment

    operator to assign the 6th element to the 5th, the 7th to 6th, ... , finally the 9th to the 8th. Then it will delete the

    last element. So you can see, to keep a contiguous memory a great deal of work needs to be done if you delete or

    add from the middle. In contrast, for a linked list, all you need to do is to point the pointer in the 4th element to

    the 6th. Therefore, if you need to frequently insert and delete from the middle, use a linked list.

    When an element is inserted, the compiler will first call copy constructor to create a new element at the end, and

    use assignment operator to assign the second last to the last, and so on, and finally assign the object which is to be

    inserted to the original object which is at the insertion point.vector's elements can be accessed with subscription just like arrays. Operator [ ] does not perform range check,

    but method at does.

    If a new element is added to a full vector, the vector increases its size automatically some would double its

    size, so would increase a certain amount.

    Two more methods of its ownvector has two more methods of its own:

    capacity: vectors capacity is not always its number of elements. When a full vector receives a new element, it

    may double its size. So if a vector has 4 elements and one is added, it will have a size of 5 and capacity of 8.

    resize: if you think the doubled capacity consumes too much memory, you can use it to resize the vector.

    Sample codes and explanations:const int SIZE = 6;

    int a[SIZE] = {1,2,3,4,5,6};

    vector v1; // create an empty vectorvector v2(a, a + size); // create a vector using part of an arrayv1.push_back(11); // insert an element at the back of the vector

    v1.push_back(22);

    vector::iterator p1; // create an iterator

    // traverse a vector with iterator:

    for(p1 = v1.begin(); p1 != v1.end(); p1++)

    cout

  • 8/6/2019 Cpp Advanced

    39/53

    v1.erase(v1.begin(), v1.end()); // remove a range of elements from vector

    v1.clear(); // remove all elements from vector

    9.10 Binary Predicate Function

    A function supplied as an argument to other functions such as container methods and STL algorithms, which

    takes two arguments, performs a comparison, and returns a bool value indicating the result. The algorithms only

    call the passed function to perform the comparison, but how to implement the comparison is customized with this

    function. This technique is also called call back, which is an effort to separate what to do from how to do.

    template < class T >

    bool myCompare(T a, T b)

    { return a < b; }

    9.11 List

    Class list is implemented as a doubly-linked list, so it can not be randomly accessed, and it only supports

    bidirectional iterators. As said before, because the list elements are not stored contiguous and only connected one

    by one through doubly links, it is convenient to insert or delete an element at any location of the list.

    Header file oflists is .

    Methodssplice: remove elements from a list and insert into another

    push_front: insert an element at the front

    pop_front: remove an element at the front

    remove

    unique

    merge

    reverse

    sort

    Sample programlist l1, l2; // create an empty list

    l1.sort();

    Method sort sorts the elements in the list in ascending order by calling element's operator "

  • 8/6/2019 Cpp Advanced

    40/53

    l1.swap(l2); // exchange the contents of l1 with l2l1.assign(l2.begin(), l2.end());

    This is to replace the content of l1 with content of l2 in the specified range.

    v1.remove(44); // remove all elements with value 44

    You can see that list has much more methods than vector. It is because vector can access its elements randomly

    via subscripts, most of these functionality can be done very easily by clients. There is no need to provide such

    methods.

    9.12 Deque

    Class deque is designed to combine the advantages ofvector and list together. Like a vector, a deque can be

    randomly accessed via subscripts, and like a list, elements can be conveniently inserted and deleted at both ends

    of the deque. Because of this combination, a deque iterator must be more intelligent than a vector iterator.

    Insertion and deletion at the middle of a deque is optimized to minimize the number of elements copied.

    Header file ofdeque is .

    deque has two more methods of its own:

    push_front

    pop_front

    9.13 Comparison of vector, list and deque

    Vector has the best random access performance. So a vector is always the first choice if delete and insert only

    happens at the back of the collection. If insertion and deletion frequently happens at both ends, a deque is

    preferred than a list because it is more efficient. If frequent insertion and deletion also happens in the middle of

    the collection, then we should use a list.

    9.14 Associative Containers

    All associative containers store and retrieve elements essentially in pairs: one is the key, one is the value.

    Multisets and sets use their values as keys, while multimaps and maps use a separate key for each of the value.

    In an associative container, the way to quickly search for an element by its key is to put all the keys and the

    addresses of the elements or records in a look-up table arranged with searching algorithms such as binary tree, b

    tree or b+ tree. The size of the look-up table should be minimal to speed up the searching process, so the size of

    the key itself must be minimal. Because sets uses elements themselves as keys, its elements must be of minimal

    size. If the element size is too big to be a key, then you should use a separate key to represent the value, that is to

    say, you have to use maps. The separate key may very probably be a field of the element, such as the employee

    number of class Employee.

    Therefore, the key difference between sequential containers and associative containers is: sequential container

    elements are stored in by themselves, while associative container elements are stored with a look-up table.

    Regardless of the sequence in which the elements are inserted, they are always in sorted order.

    Common Methodsfind

    lower_bound

    upper_bound

    count

    Because associative containers can only be accessed through keys, all their methods are key-related.

    9.15 Class pair

    Class pair has two public data members of any two types:

    template

    class pair {

    public:

    T1 first;

    T2 second;pair() {}

    pair(const T1 & x, const T2 & y)

  • 8/6/2019 Cpp Advanced

    41/53

    : first(x), second(y) {}

    };

    It is used to store a pair of values so that a method can return a pair of values. In .

    9.16 multiset

    The elements themselves are used as keys. The ordering of the elements is decided by a comparator method

    object such as less.

    The type of the key must support appropriate comparison, e.g., keys sorted with less must support

    operator

  • 8/6/2019 Cpp Advanced

    42/53

    typedef multimap< int, double, less > mmid1;

    mmid1 m1;

    m1.insert( mmid1::value_type( 15, 2.73 ) );

    "value_type" is one of those pre-defined types just like iterators, const_iterators. It represents the type used in

    the container.

    for(mmid1::const_iterator i = m1.begin(); i != m1.end(); i++)

    cout first second;

    This is to traverse the multiset with a const_iterator and print out the key and the value of each element.

    Header file of both multimap and map is .

    9.19 map

    Duplicated keys are not allowed in a map, so only a single value can be associated with each key. This is called

    a "one-to-one mapping".

    Because of this "one-to-one mapping", you can specify the key and get back the associated value quickly. A map

    is also called an "associative array", for you can provide the key in subscript operator [] and locate the

    element. Insertion and deletion can be done anywhere efficiently.

    typedef map< int, double, less > mid;

    mid m1;

    m1.insert( mid::value_type(15, 2.73) );m1[13] = 8.93;

    When key 13 is in the map, operator[ ] returns a reference to the element, so that it can be assigned 8.93. If key

    13 is not in the map, operator[ ] inserts the key and returns a reference to the value.

    9.20 Container Adapters

    Container adapters are implemented upon first-class containers, just like shrinking inheritance. Some extra

    implementations can also be added to achieve more specific task, such as the sorting ofpriority_queue. They

    don't support iterators. There are three types of adapters: stack, queue and priority_queue. Their common

    methods are push and pop.

    9.21 Stack

    A stack enables insertion and deletion at the same end of the underlying data structure, commonly referred to as alast-in-first-out data structure. A stack can be implemented with any of the sequence containers: vector, list and

    deque. By default it is deque. For best performance, use vector or deque as the underlying container.

    All stack methods are implemented as inline to avoid an extra method call.

    Header file ofstack is .

    Methodspush: insert an element by calling underlying container method push_back

    pop: remove an element by calling pop_back

    top: return a reference to the top element by calling back

    empty: determine if the stack is empty by calling empty

    size: return the size of the stack by calling stack

    Sample Programstack s1; // using a deque as underlying container

    stack s2; // using a vector as underlying container

    stack s3; // using a list as underlying container

    9.22 queue

    A queue enables insertion at one end of the underlying container, and deletion from the other end. This is

    referred to as FIFO data structure. A queue can be implemented with deque and list. By default it is deque. It

    can perform better than list.

    All queue methods are implemented as inline to avoid an extra method call.

    Header file ofqueue is .

  • 8/6/2019 Cpp Advanced

    43/53

  • 8/6/2019 Cpp Advanced

    44/53

    fill(c1.begin(), c1.end(), A);

    set every element to be A. Takes at least forward iterators.

    fill_n(c1.begin(), 5, A);

    set the first 5 elements to be A. Takes at least output iterators.

    generate(c1.begin(), c1.end(),myFill);

    charmyFill() // generator method

    {

    static char c = A;

    return c++;

    }

    set all elements of the container with objects provided by a generator method. Takes at least forward iterators.

    generate_n(c1.begin(), 5, generate);

    set first 5 elements. Takes at least output iterator.

    9.26 Compare Elements

    bool result = equal( c1.begin(), c1.end(), c2.begin() );

    compare elements of two containers using their operator = =. Takes at least input iterators.

    typedef vector::iterator vv1;

    pair location;

    location =mismatch( c1.begin(), c1.end(), c2.begin() );

    compare elements of two containers using their operator = =, and return a pair of iterators pointing to the

    mismatching element in both containers. If all equal, the pair will be equal to the last iterator. Takes at least

    input iterators.

    char c1[8] = Hello, c2[8] = Good-bye;

    bool result = lexicographical_compare(c1, c1 + 8, c2, c2 + 8);

    return true if first is greater than second.

    if( includes(a1, a1 + SIZE1, a2, a2 + SIZE2) )

    method includes compares the two sets ofsorted values, and returns true if set is include in set 1. Takes at

    least input iterators.

    9.27 Remove Elements

    vector::iterator i = remove( c1.begin(), c1.end(), 10);

    eliminate all elements with value of 10. It doesnt erase the element like erase method, it only move all

    untouched elements forward, leaving all eliminated elements at the back, with undefined values. It returns an

    iterator pointing to the last retained element. So the size of the container is not changed. Takes at least forward

    iterators.

    i = remove_copy(c1.begin(), c1.end(), c2.begin(), 10);

    copy all elements that DO NOT have the value of 10 from container c1 into container c2. It returns an iterator to

    the last copied element in c2. The first two arguments must be at least input iterators, while the third must be at

    least output iterators.

    i = remove_if(c1.begin(), c1.end(),myJudge);

    boolmyJudge(int x)

    { return x > 9; }

    eliminates all elements that the user-defined unary predicate method will return true. It does the same thing on

    eliminated elements like remove. Takes at least forward iterators.

    i = remove_copy_if(c1.begin(), c1.end(), c2.begin(), judge);

    combination ofremove_ifand remove_copy.

  • 8/6/2019 Cpp Advanced

    45/53

  • 8/6/2019 Cpp Advanced

    46/53

    sort(c1.begin(), c1.end());

    sort the elements in ascending order. A second version takes a third argument as a binary predicate method which

    returns a bool type to decide the sorting order.

    bool result =binary_search(c1.begin(), c1.end(), 223);

    use binary search to determine whether 223 is in the specified range.

    9.31 Swap Elements

    swap(a[0], a[1]);

    swap the two arguments.

    iter_swap(v1.begin(), v1.begin + 3);

    exchange the first and fourth element.

    swap_ranges(a, a+5, a+10);

    exchange a range of elements from a to (but not including) a + 5, with a range of elements starting from a + 10.

    Taks three forward iterators.

    9.32 Copy Elementscopy_backward(v1.begin(), v1.end(), v2.end());

    copy a range of elements from one container to another, starting from the element before v1.end to v1.begin,

    returning an iterator at the last elements copied (v1.begin). Takes three bidirectional iterators.

    The main difference between algorithm copy and copy_backward is: copy returns an iterator after the last

    copied element, while copy_backward returns an iterator at the last copied element.

    unique_copy(v1.begin(), v1.end(), back_inserter(v2));

    use back_inserter to insert all unique values into container v2. Takes at least output iterators.

    reverse_copy(v1.begin(), v1.end(), back_inserter(v2));

    make a reversed copy of v1 and insert into v2. First two iterators to be at least bidirectional, and third to beoutput.

    i = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), v3);

    copy the elements in v1 which are not in v2 into v3. Both v1 and v2 must be in ascending order. It returns an

    output iterator at the last copied element in v3. The first four iterators must be at least input iterators, while the

    last output iterator.

    i = set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), v3);

    copy the elements in v1 which are also in v2 into v3. Both v1 and v2 must be in ascending order. It returns an

    output iterator at the last copied element in v3. The first four iterators must be at least input iterators, while the

    last output iterator.

    i = set_symmetric_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), v3);

    copy elements in v1 that are not in v2 and elements in v2 that are not in v1 into v3. Both v1 and v2 must be in

    ascending order. It returns an output iterator at the last copied element in v3. The first four iterators must be at

    least input iterators, while the last output iterator.

    i = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), v3);

    copy elements that are either in one of v1 and v2 or in both of them into v3. Elements that are both in v1 and v2

    are only copied from v1. Both v1 and v2 must be in ascending order. It returns an output iterator at the last copied

    element in v3. The first four iterators must be at least input iterators, while the last output iterator.

    9.33 Merge Containers

    merge(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());

    combine two sorted ascending sequences of values into a third sorted ascending sequence. The first four iterators

    must be at least input iterators, while the last output iterator.

  • 8/6/2019 Cpp Advanced

    47/53

  • 8/6/2019 Cpp Advanced

    48/53

    for ( i = 0; i < v1.size(); ++i)

    pop_heap(v1.begin(), v1.end() i);

    swap the top element with the one before v1.end i. Finally results in a sorted sequence. The method assumes

    that the range of values specified by the two arguments has already been a heap.

    9.38 min & max

    determine the minimum and maximum of two containers.

  • 8/6/2019 Cpp Advanced

    49/53

    10. ANSI/ISO C++ STANDARD LANGUAGE ADDITIONS

    10.1 bool

    In C++ zero represents false and non-zero represents true. But bool type true and false is clearer and is preferred.

    10.2 Four Casts

    The ANSI/ISO C++ draft standard introduces four new cast operators to replace the old-style cast, which do all

    kinds of casting jobs. The new casts are less powerful and more specific, and each of them has separate purposes,

    thus give the program more precise control. Old casting is still legal, but new casts are preferable.

    static_cast and dynamic_caststatic_cast operator and dynamic_cast is mainly used to cast up or dow the inheritance hierarchy to cast base-

    class objects or pointers to derived-class objects or pointers, or vice versa. Consider the following example:

    class Base {

    public:

    Method1() {};

    };

    class Derived : public Base {

    public:

    Method2() {};

    };

    void DoSomething(Base * pBase)

    {

    Derived * pDerived1 = static_cast(pBase);

    pDerived1->Method2();

    Derived * pDerived2 = dynamic_cast(pBase);

    pDerived2->Method2();

    }

    static_cast only perform type checking at compile time. So it is safer than casting with "( )". But it does not

    perform run-time type checking, so it is the programmer's responsibility to make sure that pBase is pointing to a

    Derived object. If not, there will be a run time error.

    In contrast, dynamic_cast performs RTTI, and ifpBase is only pointing to Base object, dynamic_cast will find

    out and return 0. Therefore, you can decide the whether the cast is successful by checking this:

    if(dynamic_cast(ptr) == 0)

    const_castconst_cast casts away const or volatile. It only works on pointers, not objects. You can use it to modify a data

    member in a const method:

    void Test::print() const // Test is a class

    {

    const_cast(this)->member1++; // member1 is a data member of Test

    cout

  • 8/6/2019 Cpp Advanced

    50/53

    ptr = obj. ptr;

    if(obj. owner == true)

    {

    owner = true;

    const_cast< Pointer * >(&obj)->owner = false; }

    else

    owner = false;

    }

    const Pointer & operator=(const Pointer & obj)

    {

    ptr = obj. ptr;

    if(obj. owner == true)

    {

    owner = true;

    const_cast< Pointer * >(&obj)->owner = false;

    }

    else

    owner = false;

    return *this;

    }

    ~ Pointer()

    { if(owner == true) delete ptr; }

    int operator==(const Pointer &other) const { return ptr == other. ptr; }