has-a关系:包含对象成员的类和私有继承
如果一个学生关系中有姓名和分数,输入学生信息和打印学生信息该如何设计呢?
答案是使用包含和私有继承。
1,包含
instance:
《.h文件》
#pragma once
#include <iostream>
#include <string>
#include<valarray>
using namespace std;
class Student
{
private:
typedef valarray<double> Array;
string name;
Array scores;
ostream &arryOut(ostream & os)const;
public:
//多种构造方式
Student() :name("Li dan"), scores() {};//无参构造
explicit Student(const string&str):name(str),scores(){}
explicit Student(int n) :name("Li dan"), scores(n){}
Student(const string&str,int n):name(str),scores(n){}//初始化调用该类成员名而不是类名
Student(const string&str,const Array&arr_):name(str),scores(arr_){}
double Areal() const;
const string & Name() const;
double &operator[](int i);//下标运算符分数
double operator[](int i)const;
```cpp
//输入 输出运算符必须重载为友元函数、
friend istream&operator>>(istream&is, Student&stu);//输入姓名
friend istream& getline(istream&is, Student&stu);
friend ostream&operator<<(ostream&os, const Student&stu);a
~Student();
};
.
#include "pch.h"
#include "Student.h"
ostream & Student::arryOut(ostream & os) const
{
// TODO: 在此处插入 return 语句
int n = this->scores.size();
if (n > 0)
{
for (int i = 0;i < n;i++)
{
os << scores[i] << " ";
if (i % 5 == 4)//满 5就换行
os << endl;
if (i % 5 != 0)
os << endl;
}
}
else
os << "没有分数数据" << endl;
return os;
}
double Student::Areal() const
{
if (this->scores.size()>0)
{
return scores.sum()/scores.size();
}
return 0.0;
}
const string & Student::Name() const
{
// TODO: 在此处插入 return 语句
return name;
}
double & Student::operator[](int i)
{
// TODO: 在此处插入 return 语句
return scores[i];
}
double Student::operator[](int i) const
{
return scores[i];
}
Student::~Student()
{
}
istream & operator>>(istream & is, Student & stu)
{
is >> stu.name;
return is;
}
istream & getline(istream & is, Student & stu)
{
// TODO: 在此处插入 return 语句
getline(is, stu.name);
return is;
}
ostream & operator<<(ostream & os, const Student & stu)
{
// TODO: 在此处插入 return 语句
os << stu.name << "的分数如下: " << endl;
stu.arryOut(os);
return os;
}
// has-a:包含对象成员的类.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
测试文件
#include "pch.h"
#include <iostream>
#include "Student.h"
#include <cassert>
void setStudent(Student&stu, int n);
const int obejctSize = 3;
const int scoreSize = 5;
int main()
{
Student student[obejctSize] = { Student(scoreSize),Student(scoreSize) ,Student(scoreSize) };
for (int i = 0;i < obejctSize;i++)
{
setStudent(student[i], scoreSize);
}
cout << "------------------------------------------------" << endl;
cout << "所有学生名字: " << endl;
for (int i = 0;i < obejctSize;i++)
{
cout << student[i].Name() << endl;
}
//打印对象名字分数
for (int i = 0;i < obejctSize;i++)
{
cout << student[i];
cout << "平均分: " << student[i].Areal();
}
return 0;
}
void setStudent(Student & stu, int n)
{
assert(n >= 0);
cout << "请输入学生名字: " << endl;
getline(cin, stu);//为友元函数,非成员函数,共类外部函数调用
cout << "请输入当前学生的分数" << endl;
for (int i = 0; i < n; i++)
{
cin >> stu[i];//重载了输出运算符,直接打印该同学的姓名和分数
}
while (cin.get() != '\n')
continue;
}
这里需要注意的几个地方:
一,explicit是为了关闭c++的隐式转换功能;
二,c++的约束,对于成员对象初始化列表是使用成员名而非类名,但对于继承是使用类名
比如:成员name
Student(const string&str,int n):name(str),scores(n){}//初始化调用该类成员名而不是类名
三,构造函数的始化顺序是他们声明的顺序而非初始化列表顺序
比如:Student(const charstr,const doublepd,int n):scores(pd,n),name(str){}
是name成员最先被初始化因为他先被声明。
四,输入输出运算符必须重载为友元函数
设计上的难题举例:
输入:我们想直接访问一个学生的分数象直接操作数组一样?
答案: 是重载[]运算符。
double &operator[](int i);//下标运算符分数
double operator[](int i)const;但是两者一个是提供输入,后者是提供输出所以设计为const函数。
输出:我们想使用<<直接输出该对象的分数和姓名
答案:重载<<运算符
ostream & operator<<(ostream & os, const Student & stu)
{
// TODO: 在此处插入 return 语句
os << stu.name << "的分数如下: " << endl;
stu.arryOut(os);
return os;
}
ostream & Student::arryOut(ostream & os) const//该函数声明为私有只需要内部使用
{
// TODO: 在此处插入 return 语句
int n = this->scores.size();
if (n > 0)
{
for (int i = 0;i < n;i++)
{
os << scores[i] << " ";
if (i % 5 == 4)//满 5就换行
os << endl;
if (i % 5 != 0)
os << endl;
}
}
else
os << "没有分数数据" << endl;
return os;
}
返回了输出流就可以直接使用cout输出
2,私有继承
设计上和上面差不多,下面看看区别
#pragma once
#include <iostream>
#include <string>
#include<valarray>
using namespace std;
class Student:private string,private valarray<double>
{
private:
typedef valarray<double> Array;
ostream &arryOut(ostream & os)const;
public:
//多种构造方式
Student() :string("Li dan"), Array() {};//无参构造
explicit Student(const string&str) :string(str), Array() {}
explicit Student(int n) :string("Li dan"), Array(n) {}
Student(const string&str, int n) :string(str), Array(n) {}//初始化调用是类名
Student(const string&str, const Array&arr_) :string(str), Array(arr_) {}
double Areal() const;
const string & Name() const;
double &operator[](int i);//下标运算符分数
double operator[](int i)const;
//输入 输出运算符必须重载为友元函数、
friend istream&operator>>(istream&is, Student&stu);//输入姓名
friend istream& getline(istream&is, Student&stu);
friend ostream&operator<<(ostream&os, const Student&stu);
~Student();
};
注意事项:
一,如何访问基类对象:
答:强制转换为基类:
const string & Student::Name() const
{
// TODO: 在此处插入 return 语句
return (const string&)*this; //使用强制转换
}
二,访问基类的方法:
答:域解析运算符来调用基类的方法```
double Student::Areal() const
{
if (Array::size() > 0)
{
return Array::sum() / Array::size();
}
return 0.0;
}
三,访问基类的友元函数
答:友元函数并非属于类,常规类名限定不可取,则需要显示转换
比如:
ostream & operator<<(ostream & os, const Student & stu)
{
// TODO: 在此处插入 return 语句
os << (const string &)stu << "的分数如下: " << endl;
stu.arryOut(os);
return os;
}
*假如次是调用student对象 st
cout<<st;
此时的代码
os << (const string &)stu << "的分数如下: " << endl;
stu将是指向st的引用,stu将转换为string对象引用,进而调用自己的oprator<<(ostream&,const string&)函数否则将匹配友元函数发生递归调用,如果是多重继承将发生二义性问题,无法确定基类的oprator<<();
*
四,继承特有的using 重定义访问权限
using声明只使用成员名—没有圆括号,函数特征标,以及返回值类型
如:
return using std::valarray::max;
using std:::valarray::oprator[];
3,has-a关系中:如何选择包含还是私有继承
通常使用包含来建立has-a关系它相对于更抽象,如果需要访问原有类的保护成员则需要重新定义虚函数实现重写使用私有继承,