目录
一、包含对象成员的类
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。
valarray类是由头文件valarray支持的。用于处理数据。他是一个模板类,能够处理不同的数据类型。声明一个对象时,需要在标识符valarray后面添加一对尖括号。在其中包含所需要的数据类型。
vallarray q_values;
vallarray weights;
其构造函数使用例子:
double gpa[5]={3.1,3.5,3.8,2.9,3.3}
valarray V1;
valarray V2(8);
valarray V3(10,8);
valarray v4(gpa,4);
类方法
operator[]();//访问各个元素
size();//包含的元素数
sum();//元素总和
max();//返回最大元素
min(); //返回最小元素
学生与姓名,分数不是前面说的is-a的关系,而是has-a的关系,在Student类中定义string对象valarray对象,Student类成员函数可以使用两个类提供的公有接口来访问和修改name和scores对象,但是在类的外面不能这么做,只能通过Student类的公有接口访问和修改name和scores。
class Student
{
private:
string name;
valarray scores;
......
};
Student类的头文件如下,其中使用了构造函数初始化列表,以及引入了一些用于输入输出的友元函数。
#ifndef STUDENTC_H_
#define STUDENTC_H_
#include
#include
#include
class Student
{
private:
typedef std::valarray 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
{
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<
包含与私用继承的选择
①一般使用包含,可以定义多个同类对象,例如可以定义3个string对象,不过由于不是派生类,因而不能使用包含对象中的保护成员
②私有继承,有时候会因多重继承出现问题。不过私有继承可以访问保护成员
保护继承是私有继承的变体,在列出基类的时候使用关键字protected:
class Student:protected std::string,protected std::valarray
{
....
}
使用保护继承时,基类的公有成员和保护成员都会变成派生类的保护成员。和私有继承一样,基类的公有成员在只能在派生类中使用。而当派生类又派生出一个类时,保护继承和私有继承就有差别了:私有继承的第三代类将不能使用基类的公有方法,因为基类的公有方法在派生类中将变成私有方法,而使用保护继承时,基类的公有方法在第二代中将变成受保护的,因而第三代类可以访问和使用它们。
不过,可以使用using重新定义访问权限,如下所示,这样子Student对象就能够调用min()和max()函数,using声明没有圆括号,函数特征标和返回类型。他只能用于私有继承和保护继承,而不能用于包含。
class Student
{
public:
using std::valarray::min;
using std::valarray::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();
}
作用:建立一个通用的类,与函数模板类似,使用关键字template声明创建模板 ,typename数据类型,可以用class代替,T为通用的数据类型,名称可以替换,通常为大写字母
其通用格式如下
template
template//int n指定特殊类型而不是泛型名,这种称为非类型或者表达式参数
class Array
{
。。。
}
Array //编译器将使用double替换T,12替换n
#include
#include
using namespace std;
template//两个通用数据类型
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类型
PersonP1("孙悟空", 999);//注意使用格式
P1.showPerson();
}
int main() {
test01();
system("pause");
return 0;
}
区别:
① 类模板 没有自动类型推导 的使用方式② 类模板在 模板参数列表中可以有默认参数
template
class Person
{
public:
......
}
// Person p("孙悟空", 1000); // 错误 类模板使用时候,不可以用自动类型推导
Person p("孙悟空", 1000); //必须使用显示指定类型的方式,使用类模板
Person p("猪八戒", 999); //类模板中的模板参数列表 可以指定默认参数
类模板对象有三种传入方式:
① 入指定传 的类型,直接显示对象的数据类型② 参数模板化 , 将对象中的参数变为模板进行传递③ 整个类模板化 , 将这个对象类型 模板化进行传递
#include
#include
#include
using namespace std;
//类模板
template
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 &p)
{
p.showPerson();
}
void test01()
{
Person p("孙悟空", 100);
printPerson1(p);
}
//2、参数模板化
template
void printPerson2(Person&p)
{
p.showPerson();
cout << "T1的类型为: " << typeid(T1).name() << endl;
cout << "T2的类型为: " << typeid(T2).name() << endl;
}
void test02()
{
Person p("猪八戒", 90);
printPerson2(p);
}
//3、整个类模板化
template
void printPerson3(T & p)
{
cout << "T的类型为: " << typeid(T).name() << endl;
p.showPerson();
}
void test03()
{
Person p("唐僧", 30);
printPerson3(p);
}
int main() {
test01();
test02();
test03();
system("pause");
return 0;
}
使用类模板用于继承需要注意:
①当子类继承的父类是一个类模板时,子类在声明的时候,要 指定出父类中T的类型②如果不指定,编译器无法给子类分配内存③如果 想灵活指定出父类中T的类型,子类也需变为类模板
template
class Base
{
T m;
};
//class Son:public Base //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
class Son :public Base //必须指定一个类型
{
};
void test01()
{
Son c;
}
//类模板继承类模板 ,可以用T2指定父类中的T类型
template
class Son2 :public Base
{
public:
Son2()
{
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
}
};
void test02()
{
Son2 child1;
}
int main() {
test01();
test02();
system("pause");
return 0;
}
在类外编写实现时,需要加上模板参数列表
#include
#include
using namespace std;
//类模板中成员函数类外实现
template
class Person {
public:
//成员函数类内声明
Person(T1 name, T2 age);
void showPerson();
public:
T1 m_Name;
T2 m_Age;
};
//构造函数 类外实现
template
Person::Person(T1 name, T2 age) {
...
}
//成员函数 类外实现
template
void Person::showPerson() {
...
}
int main() {
Person p("Tom", 20);
p.showPerson();
return 0;
}
模板具体化与函数模板类似,也包含了隐式实例化,显式实例化和显式具体化,统称为具体化。
前面使用的均是隐式具体化,即声明一个或者多个对象,指出所需类型,编译器再通过模板生成具体的类定义。
ArrayTPstuff;
使用template直接指出所需类型来声明类,编译器将生成类声明(包括方法定义)。声明必须位于模板定义所在的名称空间中。
template class ArrayTP;
显式实例化是特定类型的定义,例如一个用来处理整型或者浮点型加减的类模板,不适用于处理结构体成员加减,这时可以使用显式具体化,对模板进行修改,使其行为不同,适合于处理特殊问题
template
class SortedArray
{
....
};
template <>class SortedArray
{
......
};
SortedArrayscores;//使用原先类模板
SortedArraydates;//使用特殊的版本类
C++还允许部分具体化,限制模板类的部分通用性,部分具体化可以给类型参数之一指定具体的类型。
templateclass Pair{....}
templateclass Pair//T2具体化为int,T1仍为类型参数。若第一个<>为空,第二个<>包含两个具体数据类型,则变成显式具体化。
①包含源文件.cpp②将声明和实现写到同一个文件中,并更改后缀名为 .hpp , hpp 是约定的名称,并不是强制
模板类声明也可以有模板,其分为三类:①非模板友元②约束模板友元,友元的类型取决于类被实例化时的类型③非约束模板友元,友元的所有具体化是类的每一个具体化的友元
template
class HasFriend
{
friend void report(HasFriend&);//不可以使用friend void report(HasFriend&),HasFriend不是类,只是一个类模板,需要特定的具体化。
}
由于report本身不是模板函数,只是使用一个模板作参数,这意味着需要将友元定义显示具体化,可以使用静态成员来统计两个具体化的创建个数。
void report(HasFriend&){...}
void report(HasFriend&){...}
也可以使友元函数本身成为模板,使类的每一个具体化都获得与友元匹配的具体化。
//需要在类定义前声明每个模板函数
template void counts();
template void report(T &);
class HasFriendT
{
friend void counts();
friend void report<>(HasFriendT &);//这里的<>内可以为空,因为函数参数已经列出
}
通过在类内部声明模板,可以创建非约束友元函数,每个函数具体化都是每个类具体化的友元,这样,show可以访问所有具体化的成员。
template
class ManyFriend
{
template friend void show(C &,D &);
}
C++的学习笔记持续更新中~
要是文章有帮助的话
就点赞收藏关注一下啦!
感谢大家的观看
欢迎大家提出问题并指正~