C++学习 第十四章
valarray类简介
该数值处理类被定义为一个功能模块,能够执行一系列运算操作,包括对数组中的所有元素求和以及找出数组中的最大值和最小值的操作.其设计基于模板技术,从而能够灵活处理不同数据类型.本指南将详细介绍其使用方法与应用场景.
//valarray类声明对象的方式
#include<valarray>
valarray<int> q_values;
valarray<double> weights;
//valarray构造函数的用法
int gba[5] = {5,4,3,2,1};
valarray<int> i_value; //定义了一个int型数组,size为0
valarray<int> i_value2(8); //定义一个有8个元素的数组
valarray<int> i_value3(10,8); //定义一个有8个元素的数组,每个元素的值都被初始化为10
valarray<int> i_value4(gba,4); //定义一个有4个元素的数组,元素被初始化为gba数组的前四个元素
//valarray的方法
operator[]() //访问各个元素
size() //返回包含的元素数
sum() //返回所有元素的总和
max() //返回最大的元素
min() //返回最小的元素
在C++程序设计中进行代码重用时应着重考虑如何实现现有代码资源的有效复用以降低开发成本同时提高程序质量为此我们需要设计一个名为student的学生类该类将包括学生的基本信息如姓名与学习成绩其中学习成績则采用我们之前介绍过的valarray数据结构在设计过程中我们首先要确定该类应包含哪些必要的成员变量以及这些成员变量之间的相互关系随后还需解决如何实现这些成员变量之间的数据交换与操作由于student类的设计目标是实现name与score两大核心属性因此在继承机制的选择上我们需做仔细权衡最终确定采用基于valarray对象的组合方式以确保各属性之间既独立又相互关联从而达到预期的设计目标
class Student
{
private:
string name;
valarray<double> scores;
...
};
请勿直接复制
//利用组合的方式设计的students类
//student.h
#ifndef STUDENT_H_
#define STUDENT_H_
#include<string>
#include<valarray>
//用于简化代码
typedef valarray<double> ArrayD
class Student
{
private:
string name;
ArrayD scores;
//由于valarray类中并没有重载<<运算符,所以定义私有方法
//以保证友元函数中重载的<<运算符函数可以看起来更加简洁
std::ostream & arr_out(std::ostream & os) const;
public:
Student() : name("Null Student"),score() {}
//explicit关键字防止了隐式的转换
explicit Student(const std::string & s) : name(s),scores() {}
explicit Student(int n) : name("Nully"),socres(n) {}
Student(const std::string & s,int n) : name(s),scores(n){}
Student(const std::string & s,const ArrayD & a) : name(s),scores(a){}
Student(const char * str,const double * d, int n): name(str),scores(d,n){}
~Student(){}
double Average() const;
const std::string & Name() const;
//下述是对于[]运算符的重载,[]运算符的重载必须是在类成员中重载
//不能使用友元函数,之所以使用重载
//是因为一个是用于const类型,而另一个使用的是引用类型
//如果只有第一种的话,没有办法给const类型进行[]运算,因为系统并不能保证对象是否被修改
//如果只有第二种的话,则没有办法对对象进行[]之后修改,成为了只读的方式
double & operator[](int i);
double operator[](int i) const;
friend std::istream & operator>>(std::istream & is,Student & s);
friend std::istream & getline(std::istream & is,Student & s);
friend std::ostream & operator<<(std::ostream & os,const Student & s);
};
3.私有继承
私有继承的特点:基类的公有成员和保护成员豆浆称为派生类的私有成员。也就是基类方法不会称为派生对象公有接口的一部分,但可以在派生类的成员函数中使用他们。
私有继承将对象作为一个未被命名的继承对象添加到类中,我们用子对象来表示通过继承或者包含添加的对象。
私有继承提供的特性与包含相同:获得实现,但是不获得接口。
4.使用私有继承来实现Student类
//如何声明一个私有继承----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),std::valarray<double>(pd,n){}
//利用组合的方式设计的students类
//student.h
#ifndef STUDENT_H_
#define STUDENT_H_
#include<string>
#include<valarray>
//用于简化代码
class Student : private std::string, private std::valarray<double>
{
private:
typedef valarray<double> ArrayD
//由于valarray类中并没有重载<<运算符,所以定义私有方法
//以保证友元函数中重载的<<运算符函数可以看起来更加简洁
std::ostream & arr_out(std::ostream & os) const;
public:
Student() : std::string("Null Student"),score() {}
//explicit关键字防止了隐式的转换
explicit Student(const std::string & s) : std::string(s),ArrayD() {}
explicit Student(int n) : std::string("Nully"),ArrayD(n) {}
Student(const std::string & s,int n) : std::string(s),ArrayD(n){}
Student(const std::string & s,const ArrayD & a) : std::string(s),ArrayD(a){}
Student(const char * str,const double * d, int n): std::string(str),ArrayD(d,n){}
~Student(){}
double Average() const;
const std::string & Name() const;
//下述是对于[]运算符的重载,[]运算符的重载必须是在类成员中重载
//不能使用友元函数,之所以使用重载
//是因为一个是用于const类型,而另一个使用的是引用类型
//如果只有第一种的话,没有办法给const类型进行[]运算,因为系统并不能保证对象是否被修改
//如果只有第二种的话,则没有办法对对象进行[]之后修改,成为了只读的方式
double & operator[](int i);
double operator[](int i) const;
friend std::istream & operator>>(std::istream & is,Student & s);
friend std::istream & getline(std::istream & is,Student & s);
friend std::ostream & operator<<(std::ostream & os,const Student & s);
};
//访问基类方法
//包含使用对象名来调用方法
double Student::Average() const
{
if(scores.size() > 0)
return scores.sum() / scores.size();
else
return 0;
}
//私有继承使得能够使用类名和作用域解析运算符来调用基类的方法
double Student::Average() const
{
if(ArrayD::size() > 0)
return ArrayD::sum() / ArrayD::size();
else
return 0;
}
//访问基类对象
//对于包含来说,返回对象即可,因为对象就是类中的成员
const string & Student::Name() const
{
return name;
}
//对于私有继承来说,由于没有成员,所以需要进行强制类型转换
const string & Student::Name() const
{
return (const string &) *this;
}
//访问基类的友元函数
//使用强制类型转换的方式来使用基类的友元函数
ostream & operator<<(ostream & os, cosnt Student & stu)
{
os << "Scores for" << (const String &) stu << ":\n";
...
}
当一个程序既可以选择通过显式包含来实现类的关系(如命名空间中的对象引用),又可以选择通过继承的方式时
-
保护继承
它是私有继發的一种形式。
通过关键字protected即可實現这种繼承方式。
當采用private方式時,在后续继起生成的新類型中無法訪問父類型的方法。
而如果采用protected的方式,则可以在后续新類型中繼續訪問父類型的接口。
這是因为private方式會將所有公共和保護性的方法轉為private;相較之下,
保護性的繼承則保住了部分可訪性 -
保護繼承
它是-private繼承的一種變體,
通過關鍵字protected實現此种继承方式。
當從一個保護性继承的对象生成新的子類時,
其 interface 只能被限制於当前层次;
而假如我們采用 private 接口,
則無法在下一層次存取父 class 的 interface 方法,
因為 private 接口會將所有的公共和保護性 method
轉化為 private 方式處理;
相較之下,
保護性的 interface 則能更好地維護一些可存取性
| 特征 | 公有继承 | 保护继承 | 私有继承 |
|---|---|---|---|
| 公有成员变成 | 派生类公有 | 派生类保护 | 派生类私有 |
| 保护成员变成 | 派生类保护 | 派生类保护 | 派生类私有 |
| 私有成员变成 | 只能通过基类接口访问 | 只能通过基类接口访问 | 只能通过基类接口访问 |
| 能否隐式向上转换 | 是 | 是(只能在派生类中) | 否 |
通过引入using关键字来明确访问权限
double Student::Sum() const
{
return std::valarray<double>::sum();
}
另外一种方法是将函数嵌套在一个内部函数中,并且可以在其中声明为using关键字以指定允许继承的方法或属性。无论是否采用隐式或显式的继承,请注意这种方法仍然有效。例如:
class Student : private std::string, private std::valarray<double>
{
public:
using std::valarray<double>::min;
using std::valarray<double>::max;
...
};
上述using声明使得std::valarray
就犹如它们是Student类中的公共成员函数一样。
值得注意的是,
在该处的using声明中未包含函数名称或参数列表(即未使用括号),
而是直接指定了属性名称。
- 多重继承
Multiple Inheritance(MI)描述了具有多个直接基类的对象,在某些方面与单继承的方式相似。值得注意的是,在实现is-a关系的对象中使用公共继承(public)修饰符是必要的。例如:
class SingerWaiter : public Waiter,public Singer {...}
请注意:所有被继承的基类都需要指定public作为限定符以实现继承;如果不指定该限定符,默认情况下系统将采用关键字private。
9.共用成员接口(Common Member Interface)以及常见问题点的解决途径
设想一下我们有一个抽象基类Worker,默认情况下它包含了一些基础功能。然而这个抽象基类有两个独立的子类Singer和Waiter各自拥有不同的功能模块。为了满足实际需求我们的目标是通过这两个基类来创建一个新的子类SingerWaiter也就是利用共用成员接口来整合并继承两个子类的功能特性。然而这样做会带来一些问题
- 工作单元的数量是多少?
由于Singer和Wavelet各自都继承了一个Combined workflow component。
因此,Integrated workflows整合后包含有两个Integrated workflows components。
这将导致将一个Integrated workflow (如 SingerPlus-Flow)赋值给Flow pointer时产生歧义。
即系统无法明确区分它是属于哪个工作流类型。
SingerWaiter ed;
Worker * pt = & ed; //出现二义性
//解决方法 进行强制类型转化
Worker * pt = (Waiter *)& ed;
Worker * pt = (Singer *)& ed;
包含两个Worker的对象也会出现其他问题;其根本原因在于:为什么需要为每个工人复制两次?这种做法会导致每个SingerWorker拥有两个ID以及两个名称;实际上每个工人只需要一个独特的名称以及一个唯一的标识符;在C++中,默认情况下支持多重继承的同时还引入了一种新的技术手段;这种技术被称为虚基类;它使得MI成为可能
允许从多个不同的基类中继承属性和行为的虚拟基类是一种编程概念,在C++中表示为virtual base class。该方法是通过在定义派生类时使用virtual关键字来实现的
//两种定义虚基类的方法
class Singer : public virtual Worker {...};
class Waiter: virtual public Worker {...};
class SingerWaiter: public Singer, public Waiter{...};
//这样定义,SingerWaiter对象就质保函Worker对象的一个副本
//从本质来说,继承的Singer和Waiter对象共享一个Worker对象
//而不是各自引入Worker对象的副本
//因此SingerWaiter对象只包含一个子对象,可以使用多态
对于普通的继承关系来说,在构造函数中会有自动生成的消息传递机制:如果B是A的一个继承类而C是B的一个继承类,在C的构造函数中会主动调用成员初始化列表并显式地触发B的构造函数执行(无需显式调用A),这是因为B会自动调用其父级对象A的构造函数以完成初始化工作;但对于虚基类而言这一机制将无法正常运行:因为当自动生成消息传递时会通过两条不同的路径将数据传至基对象中可能导致数据冲突因此为了避免这种潜在问题规定了不允许中间层对象自动将消息传递给其基对象;也就是说我们需要在必要时手动触发所需基类的构造函数如下进行处理:
SingerWaiter(const Worker & wk, int p = 0, int v = Singer::other):Worker(wk),Singer(wk,v),Waiter(wk,p);
请特别注意,在使用虚拟基本类时,请确保明确引用了显式的基类构造函数;而针对传统的继承而言,在未进行此类引用的情况下则被视为非法操作。
12.虚基类中使用方法
//二义性问题
//由于Singer和Waiter类中都包含show(),所以对于SingerWaiter来说,存在使用的二义性
//也就是说下面是写法是不对的 存在二义性
SingerWaiter newhire("Elise Hawks", 2005,6,soprano);
newhire.show();
//解决方法一:使用作用域解析运算符
newhire.Singer::show();
//解决方法二:定义SingerWaiter类的show()
void SingerWaiter::show()
{
Singer::show();
}
//如果要显示全部信息
//同时调用两个show()方法显然是行不通的
//原因就是包含了两个worker对象,会显示两次ID和姓名
void SingerWaiter::show()
{
Singer::show();
Waiter::show();
}
//解决方法一
//将每个基类的内容都通过data包含,将data定义成虚函数
//定义Worker,Singer,Worker的data,内容是他们独立的成员
//data()应该为保护类型,如此一来,类内可以正常使用,外部不能使用
//然后通过组件的方式组合在SingerWaiter中
void SingerWaiter::data()
{
Singer::data();
Waiter::data();
}
void SingerWaiter::show()
{
Worker::data();
data();
}
//解决方法二
//将数据组件设置为保护类型,使用方法get()来取得数据,需要注意的是只请求Worker一次
//然后将get()的内容整合在一起,通过set()函数集合起来。
13.MI的其他问题
在虚拟基本类型与非虚拟基本类型之间进行结合应用的情况下,则会产生不同的效果表现。具体而言,在这种情况下派生体的表现形式也会因此而有所不同:当基本类型为虚拟类型时,则派生体将承载相应的一个实例;而若基本类型不具备虚拟特性,则派生体则会承载多个实例。这种情况下系统会按照各自的基本原则进行处理,并举例说明其中的具体逻辑关系:例如变量B既充当了分类体系中的核心基础框架(即同时充当了类别C和D的虚拟基础模型),同时也被用作其他分类体系中的常规基础框架(即用于构建X和Y两类标准型别)。在这种复合型结构下,在Q中分别继承了四个基本体时(即C、D、X、Y四者均被Q所继承),其中每个具备虚拟特性的模型都会生成对应的基础实体(即每个具备属性可变化的模型都会生成一个独立的基础实例),这样就会导致最终结果出现三个独立的基础实体被整合到单一的对象结构之中)。
- 虚基类和支配
对于非虚基类来说,在处理成员引用时非常直接:当一个类型自多个类型继承了具有相同名称的成员时,在引用该成员名称时(无需通过作用域解析运算符限定其所属类型),可能会导致识别上的歧义。然而,在基于虚类型的继承中,则通常不会出现这种歧义(这是因为基于虚类型的继承具有优先级机制,在这种情况下即使不指定作用域解析运算符也会避免歧义的发生)。而如果是基于虚类型的继承,则通常不会出现这种歧义。
class B
{
public:
short q();
};
class C : virtual public B
{
public:
long q();
int omg();
};
class D : public c
{
...
};
class E : virtual public B
{
private:
int omg();
...
};
class F : public D, public E
{
...
};
对于给定的代码而言,在类F中调用q()方法时不会产生歧义性问题。这是因为,在这种情况下类C中的定义会优先于类B中的定义(由于class C是class B的继承体)。因此,在类F中可以直接引用q()方法(即直接引用C::q())。另一方面,则对于omg()方法而言并不存在这种继承关系(即E与C之间并不存在层次上的依赖),因此在调用omg()时会出现歧义性问题。需要注意的是:虚数继承的规则并不涉及访问权限问题(即使class C中的q()被声明为私有成员),因此在这种情况下面向量继承依然会调用未授权的C::q()方法。需要注意的是:这种优先顺序仅存在于虚拟基继承之中(即虚数继承的情形)。而在普通的基于继承的关系中则不会出现此情况。
14.定义类模板
之前我们介绍过栈的实现,当时我们的数据类型是unsigned long类型的数据,实际上,我们也可以建造专门存储double,int或者是其他数据类型的栈,实现的代码除了保存的数据类型不同外,其他没有任何区别,这样与其编写新的声明,不如编写一个泛型栈,然后将具体的类型作为参数传递给这个类,我们可以使用类模板来进行这种操作:
有几点需要注意的事项:
- 成员函数模板不构成独立的功能单位,在C++中无法单独编译;这与传统意义上的类及其实现分开成两个文件的做法不同。在模版设计中,声明与实现必须在同一份源代码文件中完成。
- 每个成员函数的头部都应具有相同的模版声明前缀。
- 此外还应将类限定符的表示方式调整为Stack
::的形式。
//定义一个类模板
//stacktp.h
#ifndef STACKTP_H_
#define STACKTP_H_
template<class Type>
//template <typename T> 也可以
class Stack
{
private:
enum{MAX = 10};
Type items[MAX];
int top;
public:
Stack();
bool isempty();
bool isfull();
bool push(const Type & item);
bool pop(Type & item);
};
template<class Type>
Stack<Type>::Stack()
{
top = 0;
}
template<class Type>
bool Stack<Type>::isempty()
{
return top == 0;
}
template<class Type>
bool Stack<Type>::isfull()
{
return top == MAX;
}
template<class Type>
bool Stack<Type>::push(const Type & item)
{
if(top<MAX)
{
items[top++] = item;
return true;
}
else
return false;
}
template<class Type>
bool Stack<Type>::pop(Type & item)
{
if(top > 0)
{
item = items[--top];
return true;
}
else
return false;
}
#endif
只有在程序中明确包含具体的模板时才能创建相应的模板类。
因此必须通过实例化操作来创建相应的对象。
为此需要声明一个类型T的对象。
其中的方法是使用所需的具体类型来代替泛型参数。
Stack<int> kernels;
Stack<double> colonels;
注意:类模板与函数模板之间的区别在于:类模板需要明确指定所需的数据类型(如int、string等),而函数模板则可以根据输入参数自动生成相应的类型信息。
template<typename T>
void simple<T t> {cout << t << "\n"};
...
simple(2); //simple(int);
simple("LOL"); //simple(const char *)
16.指针类型的类模板
基于一个完全正确的类模板的情况,请介绍几种在不当指针栈使用中的常见错误情况:
Stack<char *> st;
//版本1
string po;
//替换为
char * po;
//这就是相当于使用char指针而不是string对象来接受键盘输入
//不过这种方法是不正确的,因为仅仅创建指针没有创建用于保存输入字符串的空间
//程序可以通过编译,但是会在cin将输入保存在某些不合适的内存单元时崩溃。
//版本2
string po;
//替换为
char po[40];
//这为输入的字符串分配了空间,另外po的类型为char*,因此可以被放入栈中。
//但数组完全与pop()的方法的假设冲突
template<class Type>
bool Stack<Type>::pop(Type & item)
{
if(top > 0)
{
item = items[--top];
return true;
}
else
return false;
}
//首先pop()中,引用变量item,必须引用某种类型的左值,而不是数组名
//其次更不能给数组名赋值,所以这种方法也不行
//版本3
string po;
//替换为
char * po = new char[40];
//这位字符串添加了空间,而且po是变量,能够与源代码兼容
//不过问题在于,只有一个pop变量,该变量总是指向同一个内存单元
//确实每次读取新字符串的时候,内存的内容会发生变化
//但是每次执行压入操作的时候,加入到栈中的地址都相同
//每次执行弹出的操作时,得到的地址也都是相同的
//他总是指向读入的最后一个字符串
恰当的使用指针栈的一种方法是让调用程序传递一个指向字符串的数组,在这基础上将这些字符串内容存储在栈中才是有意义的操作
两种用于确定数组元素数量的方法:
第一种方法采用动态数组结合构造函数参数的方式确定元素数量。具体而言,则是根据提供的参数调用new关键字以开辟一块指定大小的内存区域,并特别提醒在释放这些内存时必须显式地定义并调用析构函数以完成释放操作。
第二种方法则利用模板机制设定常规数组的尺寸。值得注意的是,在这种情况下C++11引入了新的array模板实现这一功能因此在没有使用new运算符的情况下直接在栈内分配了相应的内存空间从而避免了传统动态数组管理的一些潜在问题。
//arraytp.h
#ifndef ARRAYTP_H_
#define ARRAYYP_H_
#include<iostream>
#include<cstdlib>
template <class T , int n>
class ArrayTP
{
private:
T ar[n];
public:
ArrayTP(){};
explicit ArrayTP(const T & v);
virtual T & operator[](int i);
virtual T operator[](int i) const;
};
template<class T, int n>
ArrayTP<T,n>::ArrayTP(const T & v)
{
for(int i = 0; i < n; i++)
ar[i] = v;
}
template<class T, int n>
T & ArrayTP<T,n>::operator[](int i)
{
if(i < 0 || i >= n)
{
std::cerr << "Error in array limits: " << i << " is out of range\n";
std::exit(EXIT_FAILURE);
}
return ar[i];
}
template<class T, int n>
T ArrayTP<T,n>::operator[](int i) const
{
if(i < 0 || i >= n)
{
std::cerr << "Error in array limits: " << i << " is out of range\n";
std::exit(EXIT_FAILURE);
}
return ar[i];
}
该模板头template
//假设有如下声明
ArrayTP<double,12> egg;
//这将导致编译器定义名为ArrayTP<double,12>的类,并创建一个类型为ArrayTP<double,12>类型的对象egg
//定义类时,使用double来代替T,使用12来代替n
注意:表达式的参数类型可以包含整型、枚举、引用或指针类型。此外,在模板代码中不可以修改这些参数的具体值。换句话说,在模板代码中对变量进行后缀操作(如++)或者直接引用其地址(如&)也是不允许的。
- 两种声明方法的主要区别在于它们对内存管理的方式不同。
- 构造函数声明方式使用的是通过new和delete操作管理堆内存区域;而基于表达式参数的方法则采用由变量直接管理的内存栈空间。
- 每个基于表达式参数的方法对于特定尺寸的数组都会独立生成对应的模板类;然而,在构造函数声明方式下,则会根据不同的数组尺寸生成多个通用模板。
- 构造函数声明方式更具灵活性:由于它将数组尺寸作为类成员属性存储在定义之中;因此不仅能够将一种尺寸的数组赋值给另一种尺寸的数组对象;还能够支持创建具有可变尺寸的数组类型。
18.模板的多功能性
- 模板用于继承和包含
template<class T>
class Array
{
private:
T entry;
...
};
template <typename Type>
class GrowArray: public Array<Type> {..}; //继承
template <typename Tp>
class Stack //包含
{
Array<Tp> ar;
...
};
...
Array<Stack<int>> asi; //一个Array类 存储的内容是int类型的Stack
- 反复使用模板
举例来说, 可以反复使用模板.
注意: 在模版语法中, 维数的顺序与等效于二维数组倒置.
ArrayTP<ArrayTP<int,5>,10> twodee;
//相当于声明int twodee[10][5];
- 应用多种类型的参数
模板能够容纳多种类型的参数。例如,如果希望类能存储两种不同的值,则可以通过创建并使用Pair模板来分别存储这两个独特的值。
#include<iostream>
#include<string>
template<class T1, class T2>
class Pair
{
private:
T1 a;
T2 b;
public:
T1 & first();
T2 & second();
T1 first() const {return a;}
T2 second() const {return b;}
Pair(const T1 & aval, const T2 & bval) : a(aval),b(aval) {}
Pair() {}
};
template<class T1,class T2>
T1 & Pair<T1,T2>::first()
{
return a;
}
template<class T1,class T2>
T1 & Pair<T1,T2>::second()
{
return b;
}
int main()
{
using std::cout;
using std::endl;
using std::string;
Pair<string,int> rating[2] =
{
Pair<string int>("TGSS",5),
Pair<string int>("TGSSAS",6)
};
}
请确保代码块正确无误
类模板的一个特点是能够为类型参数设定默认值
类模板的一个特点是能够为类型参数设定默认值
template <class T1, class T2 = int> class Topo {...};
19.模板的具体化
- 隐式实例化
Currently, the methods in use are implicit instantiation. That is, they declare one or more objects and specify the required types. The compiler employs a template-based approach to generate specific class definitions.
ArrayTP<int,100> stuff;
//隐式实例化在编译器需要对象之前,不会生成类
ArrayTP<int,100> *pt;
pt = new ArrayTP<double,30>;
//第二条语句导致编译器生成类定义,并根据该定义创建一个对象
当通过关键字template指定所需类型来声明类时,在不创建或者提及具体实例的情况下(即进行显式实例化),编译器会生成该类型的显式实例化声明,并且必须位于模板定义所在的名称空间中)。在这种情况下,在不创建或提及具体实例的情况下(即进行显式实例化),编译器也会生成相应的类型声明。
template class ArratTP<string,100>;
特定类型的显式具体化通常指的是在为特殊类型实例化时对模板进行修改以实现其独特功能或行为的过程。在这种情况下建议创建相应的显式具体化以满足需求
//例如已经对该SortArray类创建了operator>()方法
//对于数字以及定义了operator>()的类来说该方法没问题
//但是对于const char * 来说就是有问题的
//这要求类使用strcmp()来进行比较
//在这种情况下可以提供一个显示模板具体化,这将采用为具体类型定义的模板
//当具体化模板和通用模板都匹配的时候,会先使用具体化版本
template<class T>
class SortArray
{
...
};
//具体化模板的定义格式如下
template<> class Classname<specialized-type-name> {...};
//提供一个专供const char * 使用的模板
template<> class SortArray<const char *>
{
...
};
- 部分具体化
部分具体化,部分限制模板的通用性。
//部分具体化
template<class T1> class Pait<T1,int> {...};
//也就是将T2具体化为int,而T1保持不变,为泛型
//如果<>为所有参数,则等同于普通的模板类
//如果<>没有参数,则等同于显式具体化
需要注意的是,在选择可选模板时,默认情况下编译器会选择最具体的那个模板。具体来说,在某些情况下如需实现类似的功能时,请通过为特定的指针部分提供特殊的实现来进一步细化现有模板的应用场景。换句话说,在指定类型并非指向其他对象的情况下,则应采用非指向对象类型的默认处理方式;而当指定类型为指向其他对象(如int*)时,则应采用带有指针对象特性的专用实现。
- 成员模板
模板可作为结构,并既可以表示一个完整的类本身又可以表示其内部成员;同时,在外部环境中进行定义时需要注意处理嵌套类型的模板参数,并通过作用域解析运算符明确指定其所属类型以避免混淆和错误引用的问题。
//tempmemb.cpp
#include<iostream>
using std::cout;
using std::endl;
template <typename T>
class bata
{
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 blab(U u, T t) {return (n.Value() + q.Value()) * u / t;}
void show() const {q.show(); n.show();}
};
int main()
{
beta<double> guy(3.5,3);
guy.Show();
cout << guy.blab(10,2.3) << endl;
cout << guy.blab(10.0,2.3)<<endl;
return 0;
}
//在类外定义成员模板
#include<iostream>
using std::cout;
using std::endl;
template <typename T>
class bata
{
private:
template <typename V>
class hold;
hold<T> q;
hold<int> n;
public:
beta(T t, int i):q(t) n(i) {}
template<typename U>
U blab(U u, T t);
void show() const {q.show(); n.show();}
};
template <typename T>
template <typename V>
class beta<T>::hold
{
private:
V val;
public:
hold(V v = 0) :val(v){}
void show() const {cout << val << endl;}
V Value() const {return val;}
};
template <typename T>
template <typename U>
U beta<T>::blab(U u, T t)
{
return (n.Value() + q.Value()) * u / t;
}
int main()
{
beta<double> guy(3.5,3);
guy.Show();
cout << guy.blab(10,2.3) << endl;
cout << guy.blab(10.0,2.3)<<endl;
return 0;
}
作为参数使用时, 模板不仅包括类型变量(如typename T), 还可以包含普通数值(如int n). 此外, 在递归调用中使用时, 则具备新增的模版特性和优势.
template<template<typename T> class Thing>
class Crab;
该模板参数为template
//假设有如下声明
//King是满足上述模板参数的类模板
Crab<King> legs;
//King的书写方式
template<typename T>
class King {...};
//Crab类中声明如下两个对象
Thing<int> s1;
Thing<double> s2;
//则Thing<int> 会被替换为King<int>
//则Thing<double> 会被替换为King<double>
可以混合使用模板参数和非模板参数
template<template<typename T> class Thing, typename U, typename V>
class Crab
{
private:
Thing<U> s1;
Thing<V> s2;
...
};
//这样s1,s2可存储的数据类型为泛型,而非硬编码指定的类型
//类声明也修改为如下格式
Crab<Stack,int,double> nebula;
22.模板类和友元
模板的友元分三类:
- 非模版共生
- 受限模块共生(即该模块的具体化仅在被实例化时根据其类型而变化)
- 非受限模块共生(即该模块的所有具体化都是对应类每一个具体化的独立模块)
①模板类的非模板友元函数
在模板类中将一个常规函数声明为友元:
template<class T>
class HasFriend
{
public:
friend void counts();
};
请告知您希望我以哪种语言进行改写
为友元函数提供模板类参数:
我们无法直接使用以下方式声明该类型变量的原因是没有类似于HasFriend的对象。
friend void report(HasFriend &);
正确的做法是涉及一种详尽说明的具体表达方式;为了使A/B模板参数得以应用,请确保明确指定具体细节。
template<class T>
class HasFriend
{
friend void report(HasFriend<T> &);
...
};
其中具有该特征(即带有HasFriend
void report(HasFriend<int> &) {...};
void report(HasFriend<double> &) {...};
②模板类的约束模板友元函数
在类定义之前声明每个模板函数;然后,在函数体内再次将其指定为friend类型;最后为其提供相应的模板实现。
template <typename T> void counts();
template <typename T> void report(T &);
template <typename TT>
class HasFriendT
{
...
//由于count没有参数,因此必须使用模板参数语法来指明其初始化。
friend void counts<TT>();
friend void report<>(HasFriend<TT> &);
};
③模板类的非约束模板友元函数
约束模板下的 Friend 函数是通过在外部分析器中对 Template 进行具体化的实现。类似的, int 类的具体化会得到 int 函数的具体化,以此类推。通过在 class 内部声明 template 的方式,则可以创建非 constraint 类型的 Friend 函数,也就是每个 function 的 specific 实现都是对应 class specific 实例的良好伙伴关系建立者。值得注意的是,在这种情况下(即 non-constraint 类型),friend template 的 type parameter 和 template class 的 type parameter 之间将产生不同的关系变化
template<typename T>
class ManyFriend
{
...
template <typename C, typename D> friend void show2(c &, D &);
};
注意:由于它是所有ManyFriend具体化的友元,并且具备访问所有具体化item成员的能力
void show2<ManyFriend<int> &, ManyFriend<double> &>(ManyFriend<int> & C, ManyFriend<double> & D);
上述代码主要用于获取或处理
- 模板别名
在C++11中, 允许使用语法using =来定义非模板或模板类型. 其作用等同于....
template<typename T> using arrtype = std::array<T,12>;
//可以使用arrtype<T> 代替 std::array<T,12>
using pc = const char *;
//等价于
typedef const char * pc;
