C++中的has-a关系

C++中的has-a关系C 的一个主要目标是促进代码重用 公有继承是实现这种目标的机制之一 但并不是唯一的机制 还有其他的方法 其中之一是使用这样的类成员 本身是另一个类的对象 这种方法称为包含 containment 组合 composition 或层次化 layering

大家好,我是讯享网,很高兴认识大家。

        C++的一个主要目标是促进代码重用。公有继承是实现这种目标的机制之一,但并不是唯一的机制,还有其他的方法。其中之一是使用这样的类成员:本身是另一个类的对象。这种方法称为包含(containment)、组合(composition)或层次化(layering)。另一种方法是使用私有或保护继承。通常,包含、私有继承和保护继承用于实现has-a关系,即新的类将包含另一个类的对象。多重继承使得能够使用两个或更多的基类派生出新的类,将基类的功能组合在一起。 

        包含将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未命名的继承对象添加到类中。

包含对象成员的类

class Student { private: typedef std::valarray<double>ArrayDb; std::string name; ArrayDb scores; std::ostream & arr_out(std::ostream & os)const; public: Student() :name("Null Student"), scores() {} Student(const char * str, const double * pd, int n) :name(str), scores(pd, n) {} ~Student() {} double Average()const; const std::string & Name()const; friend std::ostream & operator<<(std::ostream & os, const Student & stu); //const确保方法不修改参数 };

讯享网

        上面的Student类包含string类的对象name以及valarray<double>类的scores对象。上述类将数据成员声明为私有的,这意味着Student类的成员函数可以使用string和valarray<double>类的公有接口来访问和修改name和scores对象。但在类的外面不能这样做,而只能通过Student类的公有接口访问name和scores。对于这种情况,通常被描述为Student类获得了其对象成员的实现,但没有继承接口。

接口和实现

        使用公有继承时,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现)。获得接口关系式is-a关系的组成部分。而使用组合,类可以获得实现,但不能获得接口。不继承接口是has-a关系的组成部分。

私有继承

        另一种实现has-a关系的途径——私有继承。使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这意味着基类方法将不会成为派生对象公有接口的一部分,但可以在派生类的成员函数中使用它们。

        使用公有继承,基类的公有方法将成为派生类的公有方法。总之,派生类将继承基类的接口;这是is-a关系的一部分。使用私有继承,类将继承实现,基类的公有方法将成为派生类的私有方法。总之,派生类不继承基类的接口,这种不完全继承是has-a关系的一部分。因此私有继承提供的特性与包含相同:获得实现,但不获得接口。

讯享网class Student :private std::string, private std::valarray<double> { private: typedef std::valarray<double>ArrayDb; std::ostream & arr_out(std::ostream & os)const; //private method for scores output public: Student() :std::string("Null Student"), ArrayDb() {} Student(const char * str, const double * pd, int n) :std::string(str), ArrayDb(pd, n) {} ~Student() {} double Average()const; const std::string & Name()const; friend std::ostream & operator<<(std::ostream & os, const Student & stu); //const确保方法不修改参数 };

        这个版本的Student类不需要私有数据,因为两个基类已经提供了所需的所有数据成员。包含版本提供了两个被显式命名的对象成员,而私有继承提供了两个无名称的子对象成员。另外,在构造函数中的成员初始化列表里,私有继承使用类名而不是成员名来标识构造函数,这是私有继承与包含之间的第二个主要区别。

        使用私有继承时,只能在派生类的方法中使用基类的方法。但有时候希望基类工具是公有的。例如下面包含中的double Student::Average() const函数使用对象来调用方法。然而,私有继承使得能够使用类名和作用域解析运算符来调用基类方法,如上面的Average()函数使用ArrayDd::sum()和ArrayDb::size()方法。

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

        访问基类对象时,包含版本中的Name()方法返回string成员name。使用私有继承时,Student类的代码将使用强制类型转换来访问内部的string对象。由于Student类是从string类派生而来的,因此可以通过强制类型转换将Student对象转换为string对象,结果为继承而来的string对象。此方法返回一个引用,该引用指向调用该方法的Student对象继承而来的string对象。


讯享网

讯享网//use containment const string & Student::Name()const //第一个const确保引用或指针返回的值不能用于修改对象中的数据 { //第二个const确保方法不修改调用它的对象 return name; } //use private inheritance const string & Student::Name()const { return (const string &) *this; }

        访问基类的友元函数时,私有继承用类名显式地限定函数名不适合于友元函数,因为友元不属于类。然而可以通过显式地转换为基类来调用正确的函数。而包含直接使用成员名。

        另外,私有继承中,os << "Scores for " << (const string &)stu << ":\n";显式地将stu转换为string对象引用,进而调用函数operator<<(ostream&, const string &)。引用stu,即const string & stu不会自动转换为string引用。因为在私有继承中,未进行显式类型转换的派生类引用或指针,无法赋给基类引用或指针。而且,由于使用的是多重继承,编译器将无法确定应转换为哪个基类,如果两个基类都提供了函数operator<<()。这个例子中,即使使用公有继承,也必须进行显式类型转换。如果不使用类型转换,os<<stu将与友元函数原型匹配,从而导致递归调用。

//use containment ostream & operator<<(ostream & os, const Student & stu) { os << "Scores for " << stu.name << ":\n"; stu.arr_out(os); return os; } //use private inhertance ostream & operator<<(ostream & os, const Student & stu) { os << "Scores for " << (const string &)stu << ":\n"; stu.arr_out(os); return os; }

使用包含还是私有继承

        通常应使用包含来建立has-a关系;如果新嘞需要访问原有类的保护成员,或需要重新定义虚函数,则应使用私有继承。

保护继承

        保护继承是私有继承的辩题。保护继承在列出基类时使用关键字protected:

讯享网class Student :protected std::string, protected std::valarray<double> { ... }; 

        使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员。和私有继承一样,基类的接口在派生类中也是可用的,但在继承层次结构之外是不可用的。当从派生类派生出另一个类时,私有继承和保护继承之间的主要区别就出来了。使用私有继承时,第三代类将不能使用基类的接口,因为基类的公有方法在派生类中将变成私有方法;使用保护继承时,基类的公有方法在第二代中将变成受保护的,因此第三代派生类可以使用它们。

使用using重新定义访问权限

        使用保护派生或私有派生时,基类的公有成员将成为保护成员或私有成员。假设要让基类的方法在派生类外面可用,方法之一是定义一个使用该基类的派生类方法。如下,这样Student对象便能够调用Student::sum(),sum()进而将valarray<double>::sum()方法应用于被包含的valarray对象。

double Student::sum()const { return std::valarray<double>sum(); //use privately-inherited method }

        另一种方法就是将函数调用包装在另一个函数调用中,即使用一个using声明来指出派生类可以使用特定的基类成员,即使使用的是私有派生。注意using声明只适用于继承,不适用于包含。下述using声明使得valarray<double>::min可用,就像它们是Student的公有方法一样。其中ada[i]是Student类的对象。

讯享网class Student :private std::string, private std::valarray<double> { public: using std::valarray<double>::min; using std::valarray<double>::max; ... }; cout << "high score: " << ada[i].max() << end;
小讯
上一篇 2025-04-09 15:46
下一篇 2025-03-20 19:28

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/14819.html