C++的学习之旅——C++中的代码重用

目录

一、包含对象成员的类

1、valarray类简介

2、Student类设计

3、Student类实例

二、私有继承

三、保护继承

四、多重继承

五、类模板

1、定义类模板

2、类模板与函数模板区别

3、类模板对象做函数参数

4、类模板与继承

5、类模板成员函数类外实现

6、模板的具体化

(1)隐式具体化

(2)显示实例化

(3)显示具体化

(4)部分具体化

7、类模板分文件编写

8、类模板与友元

(1)模板类的非模板友元函数

(2)模板类的约束模板友元函数

(3)模板类的非约束模板友元函数 


一、包含对象成员的类

可以使用由他人开发好的类的对象来表示。例如开发一个Student的类,里面包含学生的姓名,这样可以通过使用String类来表示姓名。使用String类需要包含文件string1.cpp。

1、valarray类简介

valarray类是由头文件valarray支持的。用于处理数据。他是一个模板类,能够处理不同的数据类型。声明一个对象时,需要在标识符valarray后面添加一对尖括号。在其中包含所需要的数据类型。

vallarray<int> q_values;
vallarray<double> weights;

其构造函数使用例子:

double gpa[5]={3.1,3.5,3.8,2.9,3.3}
valarray<double> V1;
valarray<int> V2(8);
valarray<int> V3(10,8);
valarray<double> v4(gpa,4);

类方法

operator[]();//访问各个元素
size();//包含的元素数
sum();//元素总和
max();//返回最大元素
min(); //返回最小元素

2、Student类设计

学生与姓名,分数不是前面说的is-a的关系,而是has-a的关系,在Student类中定义string对象valarray对象,Student类成员函数可以使用两个类提供的公有接口来访问和修改name和scores对象,但是在类的外面不能这么做,只能通过Student类的公有接口访问和修改name和scores。

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

3、Student类实例

Student类的头文件如下,其中使用了构造函数初始化列表,以及引入了一些用于输入输出的友元函数。

#ifndef STUDENTC_H_
#define STUDENTC_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 (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,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);
		friend std::istream & getline(std::istream & is,Student & stu);
		friend std::ostream & operator<<(std::istream & os,const Student & stu);
		
}

#endif

二、私有继承

使用私有继承,基类的公有成员和保护成员都将称为派生类的私有成员,意味着基类方法不会称为派生类的公有接口的一部分,但可以在派生类的成员函数中使用它们(不过可以在派生类中定义公有成员函数,在函数中直接调用基类方法,简间接使用基类方法)。

包含将对象作为一个命名的成员对象添加到类中,而私有继承将对象作为一个未被命名的继承对象添加到类中。使用“子对象”来表示通过继承或者包含添加的对象。

私有继承的格式和公有继承格式类似,不过需要将public改成private(由于private是默认值,即使省略,也将导致私有继承)

另外,还使用了多个基类,这种称为多重继承

class Student:private std::String,private std::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):std::string(str),ArrayDb(pd,n){}
//私有继承使用的构造函数初始化列表语法

由于使用的是私有继承,在派生类中并没有基类的对象,只能通过强制类型转换,比如将Student类强制转换成string对象。

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

}

 可以通过显示的转换为基类对象来调用正确的函数。

ostream & operator<<(ostream & os,const Student & stu)
{
        os<<"Scores for:"<<(const String &)stu<<":\n";
        //若将上述语句换成:os<<stu,将导致与原型友元函数匹配,从而导致递归调用
        //此外,使用显示转换还可以避免由于多重继承,编译器不知道使用哪个基类方法的问题
    ...
}

包含与私用继承的选择

①一般使用包含,可以定义多个同类对象,例如可以定义3个string对象,不过由于不是派生类,因而不能使用包含对象中的保护成员

②私有继承,有时候会因多重继承出现问题。不过私有继承可以访问保护成员

三、保护继承

 保护继承是私有继承的变体,在列出基类的时候使用关键字protected

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

 使用保护继承时,基类的公有成员和保护成员都会变成派生类的保护成员。和私有继承一样,基类的公有成员在只能在派生类中使用。而当派生类又派生出一个类时,保护继承和私有继承就有差别了:私有继承的第三代类将不能使用基类的公有方法,因为基类的公有方法在派生类中将变成私有方法,而使用保护继承时,基类的公有方法在第二代中将变成受保护的,因而第三代类可以访问和使用它们。

不过,可以使用using重新定义访问权限,如下所示,这样子Student对象就能够调用min()和max()函数,using声明没有圆括号,函数特征标和返回类型。他只能用于私有继承和保护继承,而不能用于包含。

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

各种继承方式的区别: 

四、多重继承

多重继承即继承多个基类,不过这样会引起一些问题,例如SingingWaiter继承了两个基类,而这两个基类继承了Worker类。这样SingingWaiter将包含两个Worker组件。而通常能将派生类对象的地址赋给基类指针,现在将出现二义性(有两个Worker对象)。不过可以使用类型转换来指定对象:

SingingWaiter ed;
Worker *pw=&ed;//出现二义性

Worker *pw1=(Waiter *)&ed;
Worker *pw2=(Singer *)&ed;

不过有时候并不需要两个类,比如既是唱歌的侍者,实际上是同一个人,只需要使用一个Worker(姓名+ID),这时,可以使用虚基类,这将使得派生出来的对象只继承一个基类。虚基类在类声明中使用virtual(virtual和public顺序无关)

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

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

使用虚基类还需要修改构造函数,非虚基类,第三代类只能调用第二代类,第二代类调用第一代类。而在虚基类中,这种方式将导致通过两个途径(Waiter和Singer)将wk传递给Worker对象。因而为了避免冲突,当基类是虚的时候,禁止通过中间类自动传递给基类。因而不会将wk信息传递给Worker,需要显示地调用所需地基类构造函数

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

 此外,若两个基类都定义了同名函数,需要使用作用域解析运算符来表达意思,也可以通过在SingingWaiter中定义新的同名函数,在函数中指定使用哪一个基类的函数。

SingingWaiter newhire;
newhire.Singer::Show();
//or
void SingingWaiter::Show()
{
    Singer::Show();
}
//不过这将导致只显示Singer而忽略了Waiter
void SingingWaiter::Show()
{
    Singer::Show();
    Waiter。Show();
}//这样又重复显示了两个姓名ID
不过可以通过模块化的方式解决,在Singer和Waiter中不显示Worker,而把Worker当成一个组件。
void SingerWaiter::show()
{
    Singer::Show();
    Waiter::Show();
    worker::show();
}

五、类模板

1、定义类模板

作用:建立一个通用的类,与函数模板类似,使用关键字template声明创建模板 ,typename数据类型,可以用class代替,T为通用的数据类型,名称可以替换,通常为大写字母

其通用格式如下

template<typename T>

template<class T,int n>//int n指定特殊类型而不是泛型名,这种称为非类型或者表达式参数
class Array
{
    。。。
}

Array<double,12> //编译器将使用double替换T,12替换n

#include <iostream>
#include <string>
using namespace std; 
template<class NameType, class AgeType>//两个通用数据类型
class Person
{
    public:
        Person(NameType name, AgeType age)
            {
                this->mName = name;
                this->mAge = age;
            }
        void showPerson()
            {
                cout << "name: " << this->mName << " age: " << this->mAge << endl;
            }
    public:
        NameType mName;
        AgeType mAge;
};
void test01()
{
// 指定NameType 为string类型,AgeType 为 int类型
    Person<string, int>P1("孙悟空", 999);//注意使用格式
    P1.showPerson();
}
int main() {
    test01();
    system("pause");
    return 0;
}

2、类模板与函数模板区别

区别:

类模板 没有自动类型推导 的使用方式
类模板在 模板参数列表中可以有默认参数
template<class NameType, class AgeType = int>
class Person
{
    public:
            ......
}

// Person p("孙悟空", 1000); // 错误 类模板使用时候,不可以用自动类型推导
Person <string ,int>p("孙悟空", 1000); //必须使用显示指定类型的方式,使用类模板
Person <string> p("猪八戒", 999); //类模板中的模板参数列表 可以指定默认参数

3、类模板对象做函数参数

类模板对象有三种传入方式:

入指定传 的类型,直接显示对象的数据类型
参数模板化 将对象中的参数变为模板进行传递
整个类模板化 将这个对象类型 模板化进行传递
#include <string>
#include <iostream>
#include <typeinfo>
using namespace std;
//类模板
template<class NameType, class AgeType = int>
class Person
{
	public:
		Person(NameType name, AgeType age)
			{
				this->mName = name;
				this->mAge = age;
			}
			void showPerson()
			{
				cout << "name: " << this->mName << " age: " << this->mAge << endl;
			}
	public:
		NameType mName	;
		AgeType mAge;
};
//1、指定传入的类型
void printPerson1(Person<string, int> &p)
{
	p.showPerson();
}
void test01()
{
	Person <string, int >p("孙悟空", 100);
	printPerson1(p);
}
//2、参数模板化
template <class T1, class T2>
void printPerson2(Person<T1, T2>&p)
{
	p.showPerson();
	cout << "T1的类型为: " << typeid(T1).name() << endl;
	cout << "T2的类型为: " << typeid(T2).name() << endl;
}
void test02()
{
	Person <string, int >p("猪八戒", 90);
	printPerson2(p);
}
//3、整个类模板化
template<class T>
void printPerson3(T & p)
{
	cout << "T的类型为: " << typeid(T).name() << endl;
	p.showPerson();
}
void test03()
{
	Person <string, int >p("唐僧", 30);
	printPerson3(p);
}
int main() {
	test01();
	test02();
	test03();
	system("pause");
	return 0;
}

4、类模板与继承

使用类模板用于继承需要注意:

①当子类继承的父类是一个类模板时,子类在声明的时候,要 指定出父类中T的类型
②如果不指定,编译器无法给子类分配内存
③如果 想灵活指定出父类中T的类型,子类也需变为类模板
template<class T>
class Base
{
    T m;
};
//class Son:public Base //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
class Son :public Base<int> //必须指定一个类型
{
};
void test01()
{
    Son c;
}
//类模板继承类模板 ,可以用T2指定父类中的T类型
template<class T1, class T2>
class Son2 :public Base<T2>
{
    public:
    Son2()
        {
            cout << typeid(T1).name() << endl;
            cout << typeid(T2).name() << endl;
        }
};
void test02()
{
    Son2<int, char> child1;
}
int main() {
    test01();
    test02();
    system("pause");
    return 0;
}

5、类模板成员函数类外实现

在类外编写实现时,需要加上模板参数列表

#include <string>
#include <iostream>
using namespace std;
//类模板中成员函数类外实现
template<class T1, class T2>
class Person {
	public:
	//成员函数类内声明
		Person(T1 name, T2 age);
		void showPerson();
	public:
		T1 m_Name;
		T2 m_Age;
};
//构造函数 类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
	...
}
//成员函数 类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson() {
	...
}

int main() {
	Person<string, int> p("Tom", 20);
	p.showPerson();
	return 0;
}

6、模板的具体化

 模板具体化与函数模板类似,也包含了隐式实例化,显式实例化和显式具体化,统称为具体化。

(1)隐式具体化

前面使用的均是隐式具体化,即声明一个或者多个对象,指出所需类型,编译器再通过模板生成具体的类定义。 

ArrayTP<int,100>stuff;

(2)显式实例化

使用template直接指出所需类型来声明类,编译器将生成类声明(包括方法定义)。声明必须位于模板定义所在的名称空间中。

template class ArrayTP<string,100>;

(3)显式具体化

显式实例化是特定类型的定义,例如一个用来处理整型或者浮点型加减的类模板,不适用于处理结构体成员加减,这时可以使用显式具体化,对模板进行修改,使其行为不同,适合于处理特殊问题

template<typename T>
class SortedArray
{
    ....
};

template <>class SortedArray<const char *>
{
    ......
};


SortedArray<int>scores;//使用原先类模板
SortedArray<const char*>dates;//使用特殊的版本类

(4)部分具体化

C++还允许部分具体化,限制模板类的部分通用性,部分具体化可以给类型参数之一指定具体的类型。

template<class T1,class T2>class Pair{....}

template<class T1>class Pair<T1,int>//T2具体化为int,T1仍为类型参数。若第一个<>为空,第二个<>包含两个具体数据类型,则变成显式具体化。

7、类模板分文件编写

①包含源文件.cpp
②将声明和实现写到同一个文件中,并更改后缀名为 .hpp hpp 是约定的名称,并不是强制

8、类模板与友元

模板类声明也可以有模板,其分为三类:
①非模板友元
②约束模板友元,友元的类型取决于类被实例化时的类型
③非约束模板友元,友元的所有具体化是类的每一个具体化的友元

(1)模板类的非模板友元函数

template <class T>
class HasFriend
{
    friend void report(HasFriend<T>&);//不可以使用friend void report(HasFriend&),HasFriend不是类,只是一个类模板,需要特定的具体化。
}

由于report本身不是模板函数,只是使用一个模板作参数,这意味着需要将友元定义显示具体化,可以使用静态成员来统计两个具体化的创建个数。

void report(HasFriend<short>&){...}
void report(HasFriend<int>&){...}

(2)模板类的约束模板友元函数

也可以使友元函数本身成为模板,使类的每一个具体化都获得与友元匹配的具体化。

//需要在类定义前声明每个模板函数 
template <typename T> void counts();
template <typename T> void report(T &);

class HasFriendT
{
	friend void counts<TT>();
	friend void report<>(HasFriendT<TT> &);//这里的<>内可以为空,因为函数参数已经列出 
}

(3)模板类的非约束模板友元函数 

通过在类内部声明模板,可以创建非约束友元函数,每个函数具体化都是每个类具体化的友元,这样,show可以访问所有具体化的成员。

template <typename T>
class ManyFriend
{
	template<typename C,ytpemane D> friend void show(C &,D &);
 } 

C++的学习笔记持续更新中~

要是文章有帮助的话

就点赞收藏关注一下啦!

感谢大家的观看

欢迎大家提出问题并指正~

猜你喜欢

转载自blog.csdn.net/qq_47134270/article/details/129078462