Cpp Chapter 14: Reusing code in C++ Part1

14.1 Classes with object members

) The valarray class
To declare an object, you follow the identifier valuearray with angle brackets that contain the desired type:

valarray<int> q_values;
valarray<double> weights;

Several examples that use the constructors:

double gpa[5] = {3.1, 3.5, 3.8, 2.9, 3.3};
valarray<double> v1; // an array of double, size 0
valarray<int> v2(8); // an array of 8 int elements
valarray<int> v3(10,8); // an array of 8 int elements, each set to 10
valarray<double> v4(gpa,4); // an array of 4 double elements, set to the first 4 elements of array gpa

After creating such array, you could use initializer list:

valarray<int> v5 = {20, 32, 17, 9};

) Design of a student class
Student has a name and a set of scores, which marks a has-a relationship:

class Student
{
private:
    string name;
    valarray<double> scores;
    ...
};

) public inheritance inherits an interface and implementation, while a has-a relationship doesn‘t. In this case, the string class overloads the operator+() for string concatenation, but the Student class couldn‘t be "concatenated" through + directly. But in some the contained class‘s interface makes sense in the containing class, thus you could define a interface for containing class that actually calls the interface of the contained class for implementation
Next comes the Student class definition:

// studentc.h -- defining a student class using containment
#ifndef STUDENTC_H_INCLUDED
#define STUDENTC_H_INCLUDED

#include <iostream>
#include <string>
#include <valarray>

class Student
{
private:
    typedef std::valarray<double> ArrayDb;
    std::string name;
    ArrayDb scores;
    std::ostream & arr_cout(std::ostream & os) const;
public:
    Student() : name("Null Student"), scores() {} // member initializer list
    explicit Student(const std::string & s) : name(s), scores() {}
    explicit Student(int n) : name("Nully"), scores(n) {}
    Student (const std::string & s, int n) : name(s), scores(n) {}
    Student (const std::string & s, const ArrayDb & a) : name(s), scores(a) {}
    Student (const char * str, double * pd, int n) : name(str), scores(pd, n) {}
    ~Student() {}
    double Average() const;
    const std::string & Name() const;
    double & operator[] (int i);
    double operator[] (int i) const; // two functions above guarantees both const and non-const cases

    friend std::istream & operator>>(std::istream & is, Student & stu);
    friend std::istream & getline(std::istream & is, Student & stu);
    friend std::ostream & operator<<(std::ostream & os, const Student & stu);
};

#endif // STUDENTC_H_INCLUDED

Noteworthy:
1 note this code:

typedef std::valarray<double> ArrayDb;

This makes ArrayDb equivalent to valarray. Placing it in the private sector means that methods and friends could access it while the outside could not.
2 recall explicit turns of implicit conversion while constructor only takes one argument thus it could be used as conversion functions.
3 Overloading operator[] enables one to access the scores with the object name, view this:

Student a;
cout << a[0] << endl;

As the [] operator is overloaded, this will output the first element of the scores valarray which is the member of the object
4 initializing member objects
Recall that for inherited objects, constructors use the class name in member initializer list to invoke base-class constructor:

hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) {...}

For member objects, constructors use the member name:

Student(const char * str, const double * pd, int n) : name(str), scores(pd, n) {}

C++ requires all member objects to be constructed before the rest of an object is constructed. So if you omit the member initializer list, C++ uses default constructor for those classes.
Next comes the code for class methods and use the Student class:

// studentc.cpp -- Student class using containment
#include "studentc.h"
using std::ostream;
using std::endl;
using std::istream;
using std::string;

double Student::Average() const
{
    if (scores.size() > 0)
        return scores.sum() / scores.size();
    else
        return 0;
}

const string & Student::Name() const
{
    return name;
}

double Student::operator[](int i) const
{
    return scores[i];
}

double & Student::operator[](int i)
{
    return scores[i];
}

ostream & Student::arr_out(ostream & os) const
{
    int i;
    int lim = scores.size();
    if (lim > 0)
    {
        for (i = 0; i < lim; i++)
        {
            os << scores[i] << " ";
            if (i % 5 == 4)
                os << endl;
        }
        if (i % 5 != 0)
            os << endl;
    }
    else
        os << " empty array ";
    return os;
}

istream & operator>>(istream & is, Student & stu)
{
    is >> stu.name;
    return is;
}

istream & getline(istream & is, Student & stu)
{
    getline(is, stu.name);
    return is;
}

ostream & operator<<(ostream & os, const Student & stu)
{
    os << "Scores for " << stu.name << ":\n";
    stu.arr_out(os);
    return os;
}
// use_stuc.cpp -- using a composite class
// compile with studentc.cpp
#include <iostream>
#include "studentc.h"
using std::cin;
using std::cout;
using std::endl;

void set(Student & sa, int n);
const int pupils = 3;
const int quizzes = 5;

int main()
{
    Student ada[pupils] = {Student(quizzes), Student(quizzes), Student(quizzes)};
    int i;
    for (int i = 0; i < pupils; i++)
    {
        set(ada[i], quizzes);
    }
    cout << "\nStudent List:\n";
    for (int i = 0; i < pupils; i++)
    {
        cout << ada[i].Name() << endl;
    }
    cout << "\nResults:";
    for (i = 0; i < pupils; i++)
    {
        cout << endl << ada[i];
        cout << "average: " << ada[i].Average() << endl;
    }
    cout << "Done.\n";
    return 0;
}

void set(Student & sa, int n)
{
    cout << "Please enter the student's name: ";
    getline(cin, sa);
    cout << "Please enter " << n << " quiz scores:\n";
    for (int i = 0; i < n; i++)
        cin >> sa[i]; // b.c. operator[] for Student is defined as returning a reference
    while (cin.get() != '\n')
    {
        continue;
    }
}

14.2 Private Inheritance

) Private inheritance: With private inheritance, public and protected members of the base class become private members of the derived class, which marks a has-a relationship.
) Containment adds a named member object to the class, whereas private inheritance adds unnamed inherited object to a class. subobject

class Student : private std::string, private std::valarray<double>
{
public:
    ...
}

Simply use keyword private. Except from that, the class need no private data members because the two privately inherited classes already provides unnamed versions of them.
) In member initializer list, you use class name for private inheritance

Student(const char * str, const double * pd, int n) : std::string(str), ArrayDb(pd, n) {}

) Differences for using private inheritance than using containment
1 To invoke base-class methods, you use class name and scope resolution operator:

double Student::Average() const
{
    if (ArrayDb::size() > 0)
        return ArrayDb::sum() / ArrayDb::size();
    else
        return 0;
}

2 To access base-class objects, you use explicit type cast

const string & Student::Name() const
{
    return (const string &) *this;
}

3 To access base-class friends, you use explicit type cast

os << "Scores for " << (const String &) stu << ":\n";

) Containment or private inheritance
Containment is explicit, clear and preferable, but there are situations using inheritance:
1 access protected members of the "base class"
2 redefine virtual methods of "base class"
under such circumstances, private inheritance is used

) protected inheritance

class Student : protected std::string, protected std::valarray<double>
{
    ...
}

With protected inheritance, public and protected members of the base class become protected members of the derived class. This is especially efficient if you derive a new class from the derived class, then the protected members could be passed to the newly-derived class.

) redefining access with using
You could use using declaration to make base-class methods as public members of the derived class speaking of private inheritance

class Student : private std::string, private std::valarray<double>
{
public:
    using std::valarray<double>::min;
    using std::valarray<double>::max;
    ...
};

By this way, you make min() and max() ,which could be called by using ArrayDb::min() and ArrayDb::max(), as accessible as public methods of the derived class


14.3 Multiple Inheritance

) MI describes a class that has more than one immediate base class, MI uses public inheritance and should mark a is-a relationship.
Now lets create an abstract class Worker, and derive class Singer and class Waiter from it, and eventually derive SingingWaiter from the two classes, which marks an example of MI:
(recall that letting a virtual function prototype = 0 creates a pure virtual function, which could not be implemented which marks the use of abstract base class)

// worker0.h -- working classes
#ifndef WORKER0_H_INCLUDED
#define WORKER0_H_INCLUDED

#include <string>

class Worker
{
private:
    std::string fullname;
    long id;
public:
    Worker() : fullname("no one"), id(0L) {}
    Worker(const std::string & s, long n) : fullname(s), id(n) {}
    virtual ~Worker() = 0; // pure virtual function
    virtual void Set();
    virtual void Show() const;
};

class Waiter : public Worker
{
private:
    int panache;
public:
    Waiter() : Worker(), panache(0) {}
    Waiter(const std::string & s, long n, int p = 0) : Worker(s, n), panache(p) {}
    Waiter(const Worker & wk, int p = 0) : Worker(wk), panache(p) {}
    void Set();
    void Show() const;
};

class Singer : public Worker
{
protected:
    enum {other, alto, contralto, soprano, bass, baritone, tenor};
    enum {Vtypes = 7};
private:
    static char *pv[Vtypes];
    int voice;
public:
    Singer() : Worker(), voice(other) {}
    Singer(const std::string & s, long n, int v = other) : Worker(s, n), voice(v) {}
    Singer(const Worker & wk, int v = other) : Worker(wk), voice(v) {}
    void Set();
    void Show() const;
};

#endif // WORKER0_H_INCLUDED
// worker0.cpp -- working class methods
#include "worker0.h"
#include <iostream>
using std::cout;
using std::cin;
using std::endl;

// Worker methods
Worker::~Worker() {}

void Worker::Set()
{
    cout << "Enter worker's name: ";
    getline(cin, fullname);
    cout << "Enter worker's id: ";
    cin >> id;
    while (cin.get() != '\n')
        continue;
}

void Worker::Show() const
{
    cout << "Name: " << fullname << "\n";
    cout << "Employee ID: " << id << "\n";
}

// Waiter methods
void Waiter::Set()
{
    Worker::Set();
    cout <<"Enter waiter's panache rating: ";
    cin >> panache;
    while (cin.get() != '\n')
        continue;
}

void Waiter::Show() const
{
    cout << "Category: waiter\n";
    Worker::Show();
    cout << "Panache rating: " << panache << "\n";
}

char * Singer::pv[] = {"other", "alto", "contralto", "soprano", "bass", "baritone", "tenor"};

void Singer::Set()
{
    Worker::Set();
    cout << "Enter number for singer's vocal range:\n";
    int i;
    for (i = 0; i < Vtypes; i++)
    {
        cout << i << ": " << pv[i] << "  ";
        if (i % 4 == 3)
            cout << endl;
    }
    if (i % 4 != 0)
        cout << endl;
    while (cin >> voice && (voice < 0 || voice >= Vtypes))
        cout << "Please enter a value >= 0 and < " << Vtypes << endl;

    while (cin.get() != '\n')
        continue;
}

void Singer::Show() const
{
    cout << "Category: singer\n";
    Worker::Show();
    cout << "Vocal range: " << pv[voice] << endl;
}
// worktest.cpp -- test worker class hierarchy
#include <iostream>
#include "worker0.h"
const int LIM = 4;
int main()
{
    Waiter bob("Bob Apple", 314L, 5);
    Singer bev("Beverly Hills", 522L, 3);
    Waiter w_temp;
    Singer s_temp;

    Worker * pw[LIM] = {&bob, &bev, &w_temp, &s_temp};

    int i;
    for (i = 2; i < LIM; i++)
        pw[i]->Set();
    for (i = 0; i < LIM; i++)
    {
        pw[i]->Show();
        std::cout << std::endl;
    }

    return 0;
}

) Now let‘s come to the SingingWaiter class. Suppose you do this:

class SingingWaiter : public Singer, public Waiter {...};

In this way you will have two copies of the Worker object within a SingingWorker object(because Singer and Waiter all inherited from Worker, which also inherits data members)
To avoid this ambiguous thing, declare Singer and Waiter inherits from a virtual base class Worker:

class Singer : virtual public Worker {...};
class Waiter : virtual public Worker {...};

In this way when you do class SingingWatier : public Singer, public Waiter {...}; again, you will only have one Worker object within the SingingWaiter object

) new constructor rules

Suppose you have the constructor for SingingWaiter class like this:

SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other) : Waiter(wk,p), Singer(wk,v) {}

This makes wk to be passed to the very base class Worker by two routes. So if you declare Worker class as virtual base class by using the way described above, C++ bans this passing to virtual base class. Thus you need to explicitly call the virtual base class constructor:

SingingWaiter(const Worker & wk, int p = 0, int v = Singer::other) : Worker(wk), Waiter(wk,p), Singer(wk,v) {}

If not explicitly calling the virtual base class constructors, a default constructor would be used.

) Which method?
Suppose you have code like this:

SingingWaiter newhire("Elise Hawks", 2005, 6, soprano);
newhire.Show();

The function call is ambiguous because the both direct ancestral classes Singer and Waiter both defined a Show() method.
You could specify by using scope resolution operator:

newhire.Singer::Show();

You could also redefine a Show() method for SingingWaiter class, but it comes to more problem:

void SingingWaiter::Show()
{
    Singer::Show();
    Waiter::Show();
}

The common member(id and name) would be printed twice, which is not desirable.
One solution is to redefine the methods, taking their functions apart by only showing Worker contents, only showing Singer contents and only showing Waiter contents, eventually SIngingWaiter could call the combination of these methods:

void Worker::Data() const
{
    cout << "Name: " << fullname << "\n";
    cout << "Employee ID: " << id << "\n";
}

void Waiter::Data() const
{
    cout << "Panache rating: " << panache << "\n";
}

void Singer::Data() const
{
    cout << "Vocal range: " << pv[voice] << "\n";
}

void SingingWaiter::Show() const
{
    cout << "Category: singing waiter\n";
    Worker::Data();
    Data();
}

Here comes the code:

// workermi.h -- working classes with MI
#ifndef WORKERMI_H_INCLUDED
#define WORKERMI_H_INCLUDED

#include <string>

class Worker
{
private:
    std::string fullname;
    long id;
protected:
    virtual void Data() const;
    virtual void Get();
public:
    Worker() : fullname("no one"), id(0L) {}
    Worker(const std::string & s, long n) : fullname(s), id(n) {}
    virtual ~Worker() = 0; // pure virtual function, pure for inheritance, no need to be implemented
    virtual void Set() = 0;
    virtual void Show() const = 0;
};

class Waiter : virtual public Worker
{
private:
    int panache;
protected:
    void Data() const;
    void Get();
public:
    Waiter() : Worker(), panache(0) {}
    Waiter(const std::string & s, long n, int p = 0) : Worker(s, n), panache(p) {}
    Waiter(const Worker & wk, int p = 0) : Worker(wk), panache(p) {}
    void Set();
    void Show() const;
};

class Singer : virtual public Worker
{
protected:
    enum {other, alto, contralto, soprano, bass, baritone, tenor};
    enum {Vtypes = 7};
    void Data() const;
    void Get();
private:
    static char *pv[Vtypes];
    int voice;
public:
    Singer() : Worker(), voice(other) {}
    Singer(const std::string & s, long n, int v = other) : Worker(s, n), voice(v) {}
    Singer(const Worker & wk, int v = other) : Worker(wk), voice(v) {}
    void Set();
    void Show() const;
};

class SingingWaiter : public Singer, public Waiter
{
protected:
    void Data() const;
    void Get();
public:
    SingingWaiter() {}
    SingingWaiter(const std::string & s, long n, int p = 0, int v = other) : Worker(s,n), Waiter(s,n,p),Singer(s,n,v) {}
    SingingWaiter(const Waiter & wt, int v = other) : Worker(wt), Waiter(wt), Singer(wt,v) {}
    SingingWaiter(const Singer & wt, int p = 0) : Worker(wt), Waiter(wt,p), Singer(wt) {}
    void Set();
    void Show() const;
};

#endif // WORKERMI_H_INCLUDED
// workermi.cpp -- working class methods with MI
#include "workermi.h"
#include <iostream>
using std::cout;
using std::cin;
using std::endl;

// Worker methods
Worker::~Worker() {}

void Worker::Data() const
{
    cout << "Name: " << fullname << endl;
    cout << "Employee ID: " << id << endl;
}

void Worker::Get()
{
    getline(cin, fullname);
    cout << "Enter worker's ID: ";
    cin >> id;
    while (cin.get() != '\n')
        continue;
}

// Waiter methods
void Waiter::Set()
{
    cout << "Enter waiter's name: ";
    Worker::Get();
    Get();
}

void Waiter::Show() const
{
    cout << "Category: waiter\n";
    Worker::Data();
    Data();
}

void Waiter::Data() const
{
    cout << "Panache rating: " << panache << endl;
}

void Waiter::Get()
{
    cout << "Enter waiter's panache rating: ";
    cin >> panache;
    while (cin.get() != '\n')
        continue;
}

char * Singer::pv[] = {"other", "alto", "contralto", "soprano", "bass", "baritone", "tenor"};

void Singer::Get()
{
    cout << "Enter number for singer's vocal range:\n";
    int i;
    for (i = 0; i < Vtypes; i++)
    {
        cout << i << ": " << pv[i] << "  ";
        if (i % 4 == 3)
            cout << endl;
    }
    if (i % 4 != 0)
        cout << endl;
    cin >> voice;

    while (cin.get() != '\n')
        continue;
}

void Singer::Data() const
{
    cout << "Vocal range: " << pv[voice] << endl;
}

void Singer::Show() const
{
    cout << "Category: singer\n";
    Worker::Data();
    Data();
}

void Singer::Set()
{
    cout << "Enter singer's name: ";
    Worker::Get();
    Get();
}

void SingingWaiter::Data() const
{
    Singer::Data();
    Waiter::Data();
}

void SingingWaiter::Get()
{
    Waiter::Get();
    Singer::Get();
}

void SingingWaiter::Set()
{
    cout << "Enter singing waiter's name: ";
    Worker::Get();
    Get();
}

void SingingWaiter::Show() const
{
    cout << "Category: singing waiter\n";
    Worker::Data();
    Data();
}
// workmi.cpp -- multiple inheritance
// compile with workermi.cpp
#include <iostream>
#include <cstring>
#include "workermi.h"
const int SIZE = 5;

int main()
{
    using std::cin;
    using std::cout;
    using std::endl;
    using std::strchr;

    Worker * lolas[SIZE];

    int ct;
    for (ct = 0; ct < SIZE; ct++)
    {
        char choice;
        cout << "Enter the employee category:\n";
        cout << "w: waiter s: singer  t: singing waiter q: quit\n";
        cin >> choice;
        while (strchr("wstq", choice) == NULL)
        {
            cout << "Please enter a w, s, t, or q: ";
            cin >> choice;
        }
        if (choice == 'q')
            break;
        switch(choice)
        {
            case 'w' : lolas[ct] = new Waiter;
                        break;
            case 's' : lolas[ct] = new Singer;
                        break;
            case 't' : lolas[ct] = new SingingWaiter;
                        break;
        }
        cin.get();
        lolas[ct]->Set();
    }

    cout << "\nHere is your staff:\n";
    int i;
    for (i = 0; i < ct; i++)
    {
        cout << endl;
        lolas[i]->Show();
    }
    for (i = 0; i < ct; i++)
        delete lolas[i];
    cout << "Bye.\n";
    return 0;
}

原文地址:https://www.cnblogs.com/fsbblogs/p/9848793.html

时间: 2024-10-18 14:49:37

Cpp Chapter 14: Reusing code in C++ Part1的相关文章

Cpp Chapter 9: Memory Models and Namespaces Part1

9.1 Separate compilation ) C++ could compile multiple files separately and link them into the final executable program ) You can divide original program into three parts(three files): 1. A header file that contains structure declarations and prototyp

Cpp Chapter 12: Classes and Dynamic Memory Allocation Part1

12.1 Dynamic memory and classes 12.1.1 A review example and static class members Now try implement a String class(a flawed one): // strngbad.h -- flawed string class definition #include <iostream> #ifndef STRNGBAD_H_INCLUDED #define STRNGBAD_H_INCLU

Cpp Chapter 11: Working with Classes Part2

11.5.3 An implementation comment ) The separation of interface from implementation is one of the goals of OOP. ) Example of the implementation of the class Vector in a rand walk simulation: // randwalk.cpp -- using the Vector class // compile with th

Cpp Chapter 9: Memory Models and Namespaces Part2

9.2.4 Static duration, external linkage ) External variables External variables are defined outside, thus external to any function.It is also termed global variables, which could be accessed in any function that follow's the external variable's defin

Cpp Chapter 10: Objects and Classes Part2

10.2.4 Using classes Following exapmle uses the class definition and implementation written in previous files: // usestok0.cpp -- the client program // compiler with stock00.cpp #include <iostream> #include "stock00.h" int main() { Stock s

Cpp Chapter 12: Classes and Dynamic Memory Allocation Part2

12.3 Things to remember when using new in constructors ) If you use new in constructors, use delete in destructor. Their use should be compatible, pair new with delete and new [] with delete [] ) Multiple constructors should share the same way of new

零元学Expression Blend 4 - Chapter 14 用实例了解布局容器系列-「Pathlistbox」II

原文:零元学Expression Blend 4 - Chapter 14 用实例了解布局容器系列-「Pathlistbox」II 本章将延续上一章的范例,步骤解析. 本章将延续上一章的范例,步骤解析. 若是对Pathlistbox基本属性还不认识的朋友,请返回上一章,小猴子良心建议:先求精.再求广! 01 开启一个新专案後,在主要工作区放入一个Ellipse,并调整到适当的位置 接着,建立我们的文字「I Love Blend 4」 这边要注意的一点: 若要做出范例的排列方式,每个字元必独立在自

Cpp Chapter 13: Class Inheritance Part1

class inheritance lets you derive new classes from old ones, inheriting its properties of the old class, called the base class With inheritance, you can: 1 add functionality to existing classes 2 add the data a class represents 3 modify how a class m

TIJ英文原版书籍阅读之旅——Chapter Seven:Reusing Classes

Reusing Classes 有两种常用方式实现类的重用,组件(在新类中创建存在类的对象)和继承. Composition syntax Every non-primitive object has a toString() method, and it’s called in special situations when the compiler wants a String but it has an object. Inheritance syntax You’re always do