• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
LOFLY
终其一生,编织快乐
博客园    首页    新随笔    联系   管理    订阅  订阅

C++中的代码重用

has-a关系 模板类valarry

C++中的代码重用

valarray类简介

头文件valarray
与vector和array比起来, valarray的功能更加强大。valarray的算术支持更多。

valarray <int> v1 ;  // int类型的数组,大小为0
valarray <int> v2(8); // 8个Int元素的数组
valarray <double> v3(10,8); // 8个10 


double gra[5] = {1.6,12.6,.9,.09,9.99};
valarray<double> v4(gra, 4); // 4个元素的数组, 取gra数组的前4个元素

C++ 11中也可以使用初始化列表
valarry<int> v5 = {1,2,3};
valarry<int> v5 {1,2,3};

valarray类的一些方法

  • operator: 让您能够访问各个元素
  • size():返回包含的元素数
  • min():返回最小的元素数
  • max(): 返回最大的元素数
  • sum(): 返回所有的元素之和

has-a的关系表示组合

示例:
Student.h文件

#ifndef STUDENT_H_
#define STUDENT_H_

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

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(){}

    explicit Student(int n):name{"Nully"},scores(n){}
    Student(const std::string &s , const ArrayDb & a):name(s),scores(a) {};
    Student(const char * str, const 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;

    // 友元函数
    // 输入
    friend std::istream & operator>> (std::istream &is, Student & stu); // 1 word
    friend std::istream & getline(std::istream & is, Student & stu); // 1 line

    // 输出
    friend std::ostream &operator <<(std::ostream & os, const Student & stu);

};

#endif

说明: typedef std::valarray<double> ArrayDb;放在类内的私有部分,表示该可以在Student类的实现中使用它,而不能在Student类外使用。

关于隐式类型转换的1点说明:

#include <iostream>
using std::cout;
using std::endl;

class Test
{
private:
    int int_num;
public:
    Test():int_num(0) 
    {
        cout << "default constructor" << endl;
    };
    Test(int n):int_num(n)
    {
        cout << "1 param constructor" << endl;
    }
    ~Test()
    {
        cout << "destroy test obj..." << endl;
    };

    Test(const Test &t)
    {
        cout << "copy constructor" << endl;
        int_num = t.int_num;
    }


    int getNum() const
    {
        return int_num ;
    }
};


int main(int argc, char const *argv[])
{
    Test test(10);
    cout<< "Output int_num value:" << test.getNum() << endl;


    test = 11; //  调用构造函数Test(11)生成零时的Test对象, 使用临时对象来替换原来的test
    return 0;
}

一个参数的构造函数,没有使用explicit修饰

 Test(int n):int_num(n)
    {
        cout << "1 param constructor" << endl;
    }

test中的值变成临时对象中的值,test地址没变(实验论证,并没有调用拷贝构造函数)

Test test(10);
cout<< "Output int_num value:" << test.getNum() << endl;

test = 11; //  调用构造函数Test(11)生成零时的Test对象, 使用临时对象来替换原来的test
对于继承的对象,构造函数在成员初始化列表中使用类名来调用特定的基类构造函数。 对于成员对象,构造函数则使用成员名。
Student(const char * str, const double * pd, int n):name(str), scores(pd,n){};
初始化列表中每一项都调用与之匹配的构造函数。即name(str)调用构造函数string(const char *), scores(pd,n)调用构造函数ArrayDb(const double * , int);

C++ 要求在构建对象其他部分之前, 先构建继承对象的所有成员对象, 如果省略初始化列表, C++将使用成员对象所属类的默认构造函数。

初始化顺序:初始化列表中包含多个项目时吗, 初始化顺序为成员被声明的顺序。而不是在初始化列表中的顺序。

Student.cpp文件

#include "student.h"
using std::endl;
using std::istream;
using std::ostream;
using std::string;

// 

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

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


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

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

// 私有方法
std::ostream & Student::arr_out(std::ostream & os) const
{
    int i ;
    int lim = scores.size();

    if(lim > 0)
    {
        for(i= 0; i< lim ; i++)
        {
            os << scores[i] << " " ;
            if(i % 5 != 0)
            {
                os << endl;
            }
        }
    }
    else
    {
        os << " empty array" ;
    }
}

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

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

std::ostream &operator <<(std::ostream & os, const Student & stu)
{
    os <<"Scores for " << stu.name << endl;
    stu.arr_out(os);
    return os;
}

use_stu.cpp文件

#include "student.h"
// #include <iostream>
using std::cout;
using std::endl;
using std::cin;

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

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

    Student ada[pupils] = {Student(quizzes),Student(quizzes),Student(quizzes)};
    int i ;
    for(i = 0; i< pupils; i++)
    {
        set(ada[i], quizzes);
    }

    cout << "\n Students List: \n";
    for(i = 0; i < pupils; i++)
    {
        cout << endl << ada[i];
        cout << "average: " << ada[i].average() << endl;
    }

    cout << "Done!" ;

    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];
    }

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

}

私有继承

包含(组合)是将对象作为一个命名的成员对象添加到类中,而私有继承(private inheritance)将对象作为一个未命名的继承对象添加到类中。

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

}// 私有继承提供了两个无名称的子对象成员

初始化问题:
对于继承类, 新版本的构造函数将使用成员初始化列表语法, 它使用类名而不是成员名来标识构造函数:

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

student1.h头文件

#ifndef STUDENT1_H_
#define STUDENT1_H_

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

class Student : private std::string, private std::valarray<double>
{
private:
    typedef std::valarray<double> ArrayDb;
    // 输出成绩的私有方法
    std::ostream &arr_out(std::ostream &os) const;

public:
    Student() : std::string("Null Student"), ArrayDb(){};
    explicit Student(const std::string &s) : std::string(s), ArrayDb(){};
    explicit Student(int n) : std::string("Nully"), ArrayDb(n) {}
    Student(const std::string & s, int n): std::string(s), ArrayDb(n) {}
    Student(const std::string & s, const double *pd,int n): std::string(s), ArrayDb(pd,n) {}

    ~Student(){};
    double average() const;
    double & operator[](int i);
    double operator[](int i) const;
    const std::string & name() const;


    // 友元函数
    friend std::istream & operator>> (std::istream &is, Student & stu); // 1 word
    friend std::istream & getline (std::istream &is, Student & stu); // 1 line
    // 输出
    friend std::ostream & operator<< (std::ostream & os, const Student & stu);
};

#endif

访问基类的方法:
使用私有继承时,只能在子类的方法中使用父类的方法。
使用组合时将使用对象名来调用方法, 而使用私有继承时将使用类名和作用域解析运算符来调用方法。

使用私有继承时,子类没有父类对象的名称, 那么如何访问父类对象呢?
答: 进行强制类型转换。

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

访问基类的友元函数
友元函数不属于类成员。 然而,可以通过显式地转换为基类来调用正确的函数。

  ostream & operator<<(ostream & os , Student & stu)
  {
  	os << "Scores for " << (const string &)stu << ": \n" ;
  }

在私有继承中,未进行显式类型转换的派生类引用或者指针, 是无法赋值给基类的引用或指针的。原因1: 如果不转换, 将进入无限的递归调用; 原因2,如果不转换, 因为类是调用的多重继承, 无法指定调用哪个基类的方法。

私有继承中必须使用基类的类名作为作用域来访问基类中的方法 base::method();

student1.cpp

#include "student1.h"
using std::endl;
using std::istream;
using std::ostream;
using std::string;

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

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

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

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

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

    return os;
}

std::istream &operator>>(std::istream &is, Student &stu)
{
    is >> (string &)stu;
    return is;
}

std::ostream &operator<<(std::ostream &os, const Student &stu)
{
    os << "Scores for" << (const string &)stu;
    stu.arr_out(os);
    return os;
}

std::istream & getline (std::istream &is, Student & stu)
{
    getline(is,(string &)stu);
    return is;
}

main文件:

#include "student1.h"
// #include <iostream>
using std::cout;
using std::endl;
using std::cin;

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

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

    Student ada[pupils] = {Student(quizzes),Student(quizzes),Student(quizzes)};
    int i ;
    for(i = 0; i< pupils; i++)
    {
        set(ada[i], quizzes);
    }

    cout << "\n Students List: \n";
    for(i = 0; i< pupils ;i++)
    {
        cout << ada[i].name() << endl;
    }


    for(i = 0; i < pupils; i++)
    {
        cout << endl<< ada[i];
        cout << "average: " << ada[i].average() << endl;
    }

    cout << "Done!" ;

    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];
    }

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

上述代码的一点说明:

    double & operator[](int i); // 1
    double operator[](int i) const; // 2

这两个语句 咋一看是不能重载的, 而在实际操作过程中是可以发生重载的。比如说,

Student s1(3);
s1[0]; // 调用 第1条语句

const Student s2(3);
s2[0]; // 调用语句 2
说明const 也能作为函数重载的调用规则。

组合和私有继承的区别:

  1. 是否显式使用成员
  2. 列表初始化
  3. 调用方法时是否添加作用域

使用包含还是继承?
优先考虑组合。

使用私有继承的情况:

  1. 相应访问 某个类的protected成员。 使用组合时访问不了的, 但使用私有继承可以直接访问。
  2. 重新定义虚函数。 派生类可以重新定义虚函数。 使用私有继承的话, 重新定义的虚函数只能在类中使用, 而不是公有的。

总结: 通常,应该使用组合来建立has-a关系, 如果新类需要访问原有类的保护成员,或者需要重新定义虚函数,则应该使用私有继承。

使用保护继承

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

}

使用保护继承时,父类的公有成员和保护成员都会成为子类的保护成员。
隐式向上转换(implicit upcasting)意味着无需进行显式类型转换, 就可以将基类指针或引用指向派生类对象。
各种继承方式

特征 公有(public)继承 保护(protected)继承 私有(private)继承
公有成员变成 派生类的公有成员 派生类的保护成员 派生类的私有成员
保护成员变成 派生类的保护成员 派生类的保护成员 派生类的私有成员
私有成员变成 只能通过基类接口访问 只能通过基类的接口访问 只能通过基类的接口访问
能否向上隐式转换 是 是(但只能在派生类中) 否

使用using重新定义访问权限
方法1:定义一个公有函数,包含一个私有函数。

方法2:在子类的公有部分使用using 声明,声明要使用的父类成员。

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

public:
	using std::valarray<double>::min;
	using std::valarray<double>::max;
}

上述声明使得valarray::min()和valarray::max()可用, 就像它们是Student的公有方法一样。
using声明只适用于继承,而不适用于包含。

posted @ 2022-09-18 16:30  编织快乐  阅读(58)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3