从小白开始学C++ 类与对象八(代码重用相关)

最最后一遍关于C++类与对象的文章
类与对象的内容实在是太多了,所以这里还得新开一篇文章来讲

包含对象成员的类

我们可以在我们想要创建的类中使用其他类作为数据成员

比如我们要创建一个Student类,我们使用string类来保存学生的名字,使用valarray类来动态分配关于学生的成绩的数组

首先是Student类的声明:

class Student
{
private:
    string name;
    typedef valarray<double> Array; //使用ypedef简化下面的初始化
    Array scores; //初始化一个double类型的动态数组
    
    ostream &arr_out(ostream &os)const;
public:
    Student():name("no student"),scores(){}
    Student(const string &s):name(s),scores(){}
    explicit Student(int n):name("nully"), scores(n){} //关闭隐式转换
    Student(const string &s, const Array &a):name(s), scores(a){}
    Student(const char *str, const double *pd, int n):name(str), scores(pd, n){}
    ~Student(){}
    
    double Average() 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, Student &stu);
};

上面的构造函数全部使用初始化成员列表方法

然后是具体方法的编写:

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)
{
    return scores[i];
}

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

ostream & Student::arr_out(ostream&os) const
{
    int i;
    int li = scores.size();
    if(li>0)
    {
        for(i=0;i<li;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, Student &stu)
{
    os<<"Scores for "<<stu.name<<":\n";
    stu.arr_out(os);
    return os;
}

然后是main函数调用测试:

void set(Student &sa, int n);
const int pup = 3;
const int qui = 5;
int main()
{
    Student arr[pup] =
    {Student(qui),Student(qui), Student(qui)};
    int i;
    for(i = 0;i<pup;i++)
        set(arr[i], qui);
    cout << "\nStudent list:\n";
    for(i=0;i<pup;i++)
        cout<<arr[i].Name()<<endl;
    cout << "\nresults:\n";
    for(i=0;i<pup;i++)
    {
        cout <<endl<<arr[i];
        cout <<"average: "<<arr[i].Average()<<endl;
    }
    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;
}

输出结果:

please enter the student's name: Mike Young
please enter 5 quiz scores:
60 60 60 60 60
please enter the student's name: Eddie Smith
please enter 5 quiz scores:
95 95 95 95 95
please enter the student's name: Bob Frank
please enter 5 quiz scores:
100 100 100 100 100

Student list:
Mike Young
Eddie Smith
Bob Frank

results:

Scores for Mike Young:
60 60 60 60 60
average: 60

Scores for Eddie Smith:
95 95 95 95 95
average: 95

Scores for Bob Frank:
100 100 100 100 100
average: 100

里面的函数大家可以自己动手试试哟!


私有继承

使用公有继承时,基类的公有方法将成为派生类的公有方法

使用私有继承时,基类的公有方法将成为派生类的私有方法

初始化成员

我们使用私有继承来实现上面的Student类

Student类从两个类派生而来,所以我们应该这样声明:

class Student : private string,private valarray<double>
{
public:
……
}
//我们不再需要私有成员,两个基类提供了所需的数据成员

这种使用多个基类的继承被称为多重继承

由于没有创建成员名,所以我们使用列表初始化列表句法来初始化成员时,我们应该使用类名而不是成员名:

原来的构造函数:

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

现在的构造函数

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

访问基类的方法

使用私有继承时,只能在派生类的方法中使用基类的方法。

私有继承使得能够使用类名和作用域解析操作符(::)来调用基类的方法

比如这个函数:

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

在私有继承中应该这么写:

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

访问基类对象

我们使用强制类型转换来访问基类对象

比如这个函数:

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

我们需要返回派生类中的string对象,我们做如下强制转换:

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

将Student对象转换为string对象,再使用this指针返回调用方法的对象


保护继承

将声明时的private换成protected便是保护继承

使用保护继承时,基类的公有成员和保护成员都变成派生类的保护成员,基类的接口在派生类中可用


多重继承

也就是从两个或多个基类中派生出一个派生类

这主要会引发两个问题:

1. 从两个不同的基类继承同名方法
2. 从两个或多个相关基类那里继承同一个类的多个实例

我们需要引入虚基类来解决这些问题

我们还需要引入新的构造函数规则

由于难度较大,而且一般可以使用多重继承的例子,我们可以使用包含对象成员的类的方法完成,所以这里不再赘述,感兴趣的朋友可以自行寻找资料学习哟!


类模板

类模板的基本使用方法

我们知道使用函数模板,是可以大大减少我们编写代码的量,那么类是否也有类似的功能呢?这里C++同样提供了类模板,来生成通用的类声明

模板提供参数化类型,能够将类型名作为参数传递来建立类或者函数

基本思想跟函数模板一致,如果对这里的类模板不太理解可以先看看我的另一篇文章:

从小白开始学C++ 默认参数、函数重载、函数模板

同样我们需要一个声明表示该类为类模板:

template <class Type>
// 这里的class并不意味着Type是一个具体的类
// 这里的Type可以换成你自己想用的通用类型,比如T,B

这里同样使用栈的例子来实现类模板,因为栈的类型不同,类中的数组成员会不同,所以我们使用类模板来根据实例来创建不同类型的栈

首先我们看类模板的声明:

template <class Type>
class Stack  //类模板
{
private:
    Type items[10]; //使用Type制造模板
    int top;
public:
    Stack(); //构造函数
    void isempty_full();//判断空满
    bool push(const Type& item);//压栈
    Type pop();//出栈并返回相应类型的值
};

然后和函数模板类似,在每个成员函数的具体实现前加上:

template <class Type>

注意:和你自己在类模板上面定义的声明保持一致!

然后是成员函数的编写:

template<class Type>
Stack<Type>::Stack()
{
    top = 0;
}

template<class Type>
void Stack<Type>::isempty_full()
{
    if(top==0)
        cout << "Stack is empty!\n";
    if(top==10)
        cout << "Stack is full!\n";
    if(top>0&&top<10)
        cout << "Stack is available!\n";
}

template<class Type>
bool Stack<Type>::push(const Type &item)
{
    if(top<10)
    {
        items[top++] = item;
        cout << item << " has been pushed into Stack!\n";
        return true;
    }
    else
        return false;
}

template<class Type>
Type Stack<Type>::pop() //注意这个函数,可以说是嵌套了函数模板
{
    Type item;
    if(top>0)
    {
        item = items[--top];
        cout << item << " has removed from Stack!\n";
        return item;
    }
    else
        return item;
}

基本的栈的操作和初始化定义,所以就没有写详细注释啦!

最后是main函数测试我们编写的类模板:

int main()
{
    Stack<int> sint; //实例化两个类模板
    Stack<char> schar;
    cout << "Int Stack:\n";
    sint.push(2);
    sint.isempty_full();
    int re;
    re = sint.pop();
    cout << "value removed from Stack is : " << re <<endl;
    cout << "\n";
    cout << "Char Stack:\n";
    schar.isempty_full();
    char a = 'A';
    schar.push(a);
    char c;
    c = schar.pop();
    cout << "value removed from Stack is : "<<c<<endl;
    return 0;
}

也是很简单的例子,不多加注释,配合着输出结果大家就可以理解啦!

输出结果:

Int Stack:
2 has been pushed into Stack!
Stack is available!
2 has removed from Stack!
value removed from Stack is : 2

Char Stack:
Stack is empty!
A has been pushed into Stack!
A has removed from Stack!
value removed from Stack is : A

类模板的几个使用技巧

递归使用模板

我们可以这样使用模板:

template <class Type, int n> //允许添加其他附加信息
Type<Type<int, 5>, 10> sint;
//实例化一个 int sint [10][5]
//注意行列的书写顺序

使用多个类型参数

我们可以让一个模板中出现多个类型参数:

template <class Type1, class Type2>

给类型参数提供默认值

template <class Type1, class Type2=int>

成员模板

模板可以作为结构,类或者模板类的成员

我们可以在类模板中嵌套另外的模板类或者模板函数,比如下面这个类模板的声明:

template <typename T>
class beta
{
private:
    template <typename V>
    class hold
    {
    private:
        V val;
    public:
        hold(V v=0):val(v){}
        void show()const{cout << val <<endl;}
        V value()const {return val;}
    };
    hold<T> q;
    hold<int> n;
public:
    beta(T t, int i):q(t),n(i){}
    template<typename U>
    U add(U u,T t){return(n.value()+q.value())*u/t;}
    void display()const {q.show();n.show();}
};

然后我们放上main函数测试该类:

int main()
{
    beta<double> g(3.5, 3);
    g.display();
    cout <<g.add(10, 2.3)<<endl;
    return 0;

}

输出结果:

3.5
3
28

这里可能会稍微比较难理解一点,我们拆分开来看

template <typename T>
class beta

首先我们使用T作为beta的模板标识符,然后我们在它的private中定义了另一个类模板:

template <typename V>
    class hold
    {
    private:
        V val;
    public:
        hold(V v=0):val(v){}
        void show()const{cout << val <<endl;}
        V value()const {return val;}
    };

我们使用V,然后在其private里面定义一个V val,那么在后面实例化的时候,val的具体类型由V来决定

在public中我们使用一个构造函数初始化val的值,show()展示val的值,最后一个value函数返回val的值(前面是val的类型是V,所以函数开头是V

	hold<T> q;
    hold<int> n;

然后在beta 的private声明的最后,隐式的实例化了嵌入beta的类模板hold,使得beta的私有成员多了两个:一个是类型为T的q,一个是类型为int的n

public:
    beta(T t, int i):q(t),n(i){}
    template<typename U>
    U add(U u,T t){return(n.value()+q.value())*u/t;}
    void display()const {q.show();n.show();}

最后我们看到beta的public声明中的三个函数
第一个函数是构造函数,由于上面说了,在private中实例化了两个成员(还有一个是类模板,不需要实例化),所以这里使用初始化列表成员句法将其初始化;

第二个函数是一个函数模板,这里我们使用U来表示该函数模板,这里会传入两个参数:U类型的u, T类型的t,然后返回两个私有成员相加乘以u除以t的值,至于返回类型为什么是U,在main函数实例化的时候可以看到

第三个函数是display,用来展示私有成员的值,调用了类模板中的show方法

最后我们看看main函数都干了什么事情吧!

int main()
{
    beta<double> g(3.5, 3);
    g.display();
    cout <<g.add(10, 2.3)<<endl;
    return 0;
}

首先我们实例化类模板beta,类型定义为double,那么相应的私有成员q的类型就是double了;

然后我们调用display函数展示初始化的值

最后我们再输出add函数的值,这里我们输入了10 和 2.3,那么我们隐式的将U定义为int(10的类型),最后进行运算的时候会发生强制类型转换返回int值,就可以得到我们展示的结果啦!


模板作为参数

模板参数可以和常规参数一起混合使用:

template <template <typename T> class Thing, typename U, typename V>
class King
{
private:
	Thing<U> a1;
	Thing<V> a2;
……
}

友元模板

最后简单提一下友元(类与对象的内容太多了)

模板的友元分为3类:

  • 非模板友元
  • 约束模板友元:友元的类型取决于类被实例化时的类型
  • 非约束模板友元:友元的所有具体化都是类的每一个具体化的友元

具体的内容不再展开,感兴趣的小伙伴可以私信或者在其他CSDN的博客中找到详细解释哟!

同样捞一捞整个系列的博文:

从小白开始学C++ 类与对象七(虚函数,protected成员,抽象基类)

从小白开始学C++类与对象六(类继承)

从小白开始学C++类与对象五(返回类对象的方式,对象指针,嵌套结构,重载<<操作符)

从小白开始学C++ 类与对象四(动态内存分配)

从小白开始学C++ 类与对象三 (操作符重载、友元函数,类的自动转换和强制类型转换)

从小白开始学C++ 类与对象二 (this指针和对象数组)

从小白开始学C++ 类与对象一(创建、定义类、构造函数、析构函数)

感谢您的耐心阅读!努力变成自己想要成为的人吧!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

国家一级假勤奋研究牲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值