模板偏特化翻译 为什么基类的成员在父类中调用必须指明作用域或者使用this->?
日期:2023-03-11 12:39:36 / 人气: 430 / 发布者:成都翻译公司
在obj类模板调用含有模板参数的类成员函数之前,其实就是在”成员访问运算符(.我们看到,这个程序中Father类对象Obj1的成员函数Foo并没有调用从Base类中继承的exit函数,我们应该这样改正这个程序(明确exit函数的归属位置,即我们到底要调用那里的exit函数)。当模板实例化时,模板的全特化,模板的偏特化都适用时的调用顺序:C++ 模板的技术基础
内容
关键字 typename 的使用
#include
using namespace std;
#include
template
void PrintElement(T const& obj)
{
typename T::const_iterator cpos;
typename T::const_iterator end = obj.cend();
for (cpos = obj.cbegin(); cpos != end; cpos++)
{
cout << *cpos << " ";
}
cout << endl;
}
int main()
{
vector Obj{ 1,2,3,4,5,6,7,8,9,10 };
PrintElement(Obj);
}
有时T是一个类,其他类嵌套在该类中。例如,在STL标准模板库中的vector类中,就有“const_iterator只读迭代器”、“迭代器可读可写迭代器”等常用的嵌套类类型。.
当我们构建模板时,我们会发现编译器不会给我们任何提示信息。这是因为在实例化模板之前,编译器不知道参数 T 是什么类型。你在编译器不知道时使用 T : :const_iterator,其他编译器不知道你是什么。您可以使用 :: 访问很多东西。类类型中的静态静态成员变量也可以通过类类型 T 加 :: 作用域访问器来访问。所以我们要让编译器知道 T::const_iterator 是 T 类型的嵌套子类类型模板偏特化翻译,我们必须用 typename 声明它。
例如:(解释typename关键字的作用)
在上面的程序中,因为有typename,编译器认为ptr是一个T::SubType类型的指针,但是如果没有typename呢?这时候编译器会把T::SubType当作T类型的静态成员变量,那么T::SubType * ptr被认为是两个变量的相乘(编译器认为*是乘号✖),如果你编译如果你这样操作设备,你的程序就会报错,然后就凉了。
.模板构造
#include
#include
using namespace std;
template
void PrintBitset(bitset const& obj)
{
cout << obj.to_string, allocator >() << endl;
}
int main()
{
bitset<4> Obj;
PrintBitset(Obj);
}
看上面的例子,我们会觉得有些不对劲,为什么要加一个“.template”呢?我们先来分析一下这个例子的特点:
① obj 是一个包含模板参数的类对象;
② to_string 是包含模板参数的类成员函数;
在这种情况下,为了防止编译器:
把它想象成“两个成员正在比较大小”:
我们这样做:
#include
#include
using namespace std;
template
void PrintBitset(bitset const& obj)
{
cout << obj.template to_string, allocator >() << endl;
}
int main()
{
bitset<4> Obj;
PrintBitset(Obj);
}
obj 类模板在调用包含模板参数的类成员函数之前,实际上是在“成员访问运算符(.)”之后插入了模板关键字。其实在VS2017中试过很多次模板偏特化翻译,感觉.template关键字加不加。没有区别。我觉得《C++模板》一书中提到的这部分注意事项应该是针对某些编译器的。不同的编译器的性能通常略有不同,可能会出现在这里。错误,我觉得*好加上template关键字,这样可以保证你的程序在任何编译器中编译都不会出错。
实践小项目
要求:我们有N个人的团队,每个团队的每个工人都有“姓名,每周工作时间(h)和每周工资(100元)”,我们要问这个团队的平均小时平均工资工资*高的人以及相应工作人员的姓名。
代码示例:
团队.hpp
#include
#include
#include
using namespace std;
template
class Team;
class Worker
{
template
friend class Team;
private:
string name;
int NowSalary;
int WorkTime;
public:
Worker() {};
Worker(const Worker& Obj)
{
this->name = Obj.name;
this->NowSalary = Obj.NowSalary;
this->WorkTime = Obj.WorkTime;
}
Worker(string name, int NowSalary, int WorkTime)
{
this->name = name;
this->NowSalary = NowSalary;
this->WorkTime = WorkTime;
}
Worker& operator = (const Worker& Obj)
{
this->name = Obj.name;
this->NowSalary = Obj.NowSalary;
this->WorkTime = Obj.WorkTime;
return *this;
}
Worker& operator = (const Worker&& Obj)
{
this->name = Obj.name;
this->NowSalary = Obj.NowSalary;
this->WorkTime = Obj.WorkTime;
return *this;
}
bool operator > (const Worker& obj) const
{
return this->NowSalary / this->WorkTime > obj.NowSalary / obj.WorkTime;
}
bool operator < (const Worker& obj) const
{
return this->NowSalary / this->WorkTime < obj.NowSalary / obj.WorkTime;
}
};
template
class Team
{
private:
Worker* workers;
int Pos;
int MaxSalaryPerHour;
string name;
public:
Team()
{
workers = new Worker[N];
Pos = 0;
MaxSalaryPerHour = 0;
name = "无";
}
void Push(const Worker& Obj)
{
if (Pos >= N)
{
throw out_of_range("Over Max Range!");
}
workers[Pos] = Obj;
Pos++;
}
void Pop()
{
if (Pos == 0)
{
throw out_of_range("Empty!");
}
Worker* NewWorkers = new Worker[--Pos];
for (int i = 0; i < Pos; i++)
{
NewWorkers[i] = workers[i];
}
delete[] workers;
workers = NewWorkers;
}
template
void SearchMax()
{
Worker TheWorker;
for (int i = 0; i < Pos - 1; i++)
{
TheWorker = workers[i] > workers[i + 1] ? workers[i] : workers[i + 1];
}
this->MaxSalaryPerHour = TheWorker.NowSalary / TheWorker.WorkTime;
this->name = TheWorker.name;
cout << "Max Salary Per Hour:" << this->MaxSalaryPerHour << ",The Worker's Name:" << this->name << endl;
}
};
主程序
#include
using namespace std;
#include "Worker.hpp"
#include
Worker Max(const Worker& var1, const Worker& var2)
{
return var1 > var2 ? var1 : var2;
}
int main()
{
Team<4> Obj1;
try
{
Obj1.Push(Worker("张三", 19, 10));
Obj1.Push(Worker("李四", 17, 12));
Obj1.Push(Worker("王五", 10, 2));
Obj1.Push(Worker("赵六", 19, 14));
Obj1.template SearchMax();
}
catch (const out_of_range& exp)
{
cout << exp.what() << endl;
}
}
为什么必须在父类中调用基类的成员来指定作用域或者使用this->?
代码示例:
#include
using namespace std;
template
class Base
{
public:
void exit()
{
cout << typeid(T).name() << endl;
};
};
template
class Father: Base
{
public:
void Foo()
{
exit();
}
};
int main()
{
Father Obj1;
Obj1.Foo();
}
操作结果:
我们看到这个程序中Father类对象Obj1的成员函数Foo并没有调用继承自Base类的exit函数。我们应该像这样更正这个程序(明确exit函数属于哪里,也就是我们要调用exit函数的地方)。
代码示例:
#include
using namespace std;
template
class Base
{
public:
void exit()
{
cout << typeid(T).name() << endl;
};
};
template
class Father: Base
{
public:
void Foo()
{
this->exit(); // Base::exit()
}
};
int main()
{
Father Obj1;
Obj1.Foo();
}
与之前的程序相比,我们在退出函数中添加了 this->pointer/Base::scope 运算符。使用其中之一(this->pointer 或 Base::scope 运算符),编译器可以确保退出函数不是从外部传入,而是父类本身(this->pointer)/从基类继承基础(基础::范围运算符)。
操作结果:
在《C++模板》一书中,有一段话讲述了我上面所说的:
会员模板的优点
我们有时会因为“不同类型的自定义 Stacks 之间”无法相互赋值而感到恼火。为什么Stack类型对象不能调用Stack模板类中的overload=assignment运算符给Stack类型对象赋值?
其实我们重载的=赋值运算符是在模板类实例化(Stack)的时候使用的,确定了可以赋值的两个对象的类类型:
主程序
#include "Stack.hpp"
#include
using namespace std;
int main()
{
Stack Stack_Obj1(90);
Stack Stack_Obj2;
Stack_Obj2 = Stack_Obj1;
}
堆栈文件
#include
using namespace std;
#include
template >
class Stack
{
private:
CONT element;
public:
Stack() = default;
Stack(const T& obj);
Stack(const Stack& obj);
Stack& operator = (const Stack& obj);
T& operator [] (const int& order);
};
template */>
T& Stack::operator[](const int& order)
{
return this->element.at(order);
}
template */>
Stack& Stack::operator=(const Stack& obj)
{
this->element.clear();
this->element.assign(obj.element.begin(), obj.element.end());
}
template */>
Stack::Stack(const Stack& obj)
{
this->element.clear();
this->element.assign(obj.begin(), obj.end());
}
template */>
Stack::Stack(const T& obj)
{
this->element.clear();
this->element.push_front(obj);
}
操作结果:
为什么会发生这种情况,请让我详细说明:
① 首先,我们使用 Stack 获取实例化的模板。每个模板的赋值运算符两端的操作变量必须是变量类型;
②我们用“Stack_Obj1 = Stack_Obj2”来说明=赋值运算符两端的操作变量必须与Stack_Obj1变量的类型相同,即Stack类型;
③我们右边的操作变量Stack_Obj2的操作类型是Stack类型的。编译器一下子就糊涂了。我没有=赋值运算符可以同时操作两种不同类型的变量相互赋值?那我就报错!
如何改进它?这时,成员模板的优点就出来了:成员模板的主要优点是“可以自由定义重载运算符的操作对象,使一个运算符可以同时操作两种不同类型的变量。 ”
改进的代码(Stack.hpp):
#include
using namespace std;
#include
template >
class Stack
{
private:
CONT element;
public:
Stack() = default;
Stack(const T& obj);
Stack(const Stack& obj);
template
Stack& operator = (const Stack& obj);
T& operator [] (const int& order);
T& Top();
void Push(const T& obj);
void Pop();
bool empty();
};
template */>
bool Stack::empty()
{
return this->element.empty();
}
template */>
void Stack::Pop()
{
if (this->element.empty())
{
throw out_of_range("Empty!");
}
this->element.pop_front();
}
template */>
void Stack::Push(const T& obj)
{
this->element.push_front(obj);
}
template */>
T& Stack::Top()
{
if (this->element.empty())
{
throw out_of_range("Empty!");
}
return *(this->element.end() - 1);
}
template
template
Stack& Stack::operator=(const Stack& obj)
{
if ((void*)this != (void*)&obj)
{
return *this;
}
Stack TempStack(obj);
this->element.clear();
while (!TempStack.empty())
{
this->element.push_front(TempStack.Top());
TempStack.Pop();
}
}
template */>
T& Stack::operator[](const int& order)
{
return this->element.at(order);
}
template */>
Stack::Stack(const Stack& obj)
{
Stack TempStack(obj);
this->element.clear();
while (!TempStack.empty())
{
this->element.push_front(TempStack.Top());
TempStack.Pop();
}
}
template */>
Stack::Stack(const T& obj)
{
this->element.clear();
this->element.push_front(obj);
}
你可能会糊涂,没关系,下面是Stack类的成员函数列表:
class Stack
{
private:
CONT element;
public:
Stack() = default;
Stack(const T& obj);
Stack(const Stack& obj);
template
Stack& operator = (const Stack& obj);
T& operator [] (const int& order);
T& Top();
void Push(const T& obj);
void Pop();
bool empty();
};
我们知道,当我们在成员函数中传入的参数是类对象时,是不能通过类对象直接访问该对象的私有成员数据的。我们只能通过类类型提供的外设接口以某种方式访问对象的数据。,为了不改变传入的类对象的原有属性,将传入的类对象完全复制到我们要操作的类对象中,我们进行如下操作:
① 清除我们要操作的类对象中的数据;
②创建与传入对象类型相同的临时变量,在定义时调用复制构造函数将参数数据复制到临时对象中;
③看到我提供给大家的成员函数列表中,*显眼的有两个函数Top和Pop。我们可以使用while循环先访问临时对象的顶部元素,将元素push_front移入容器,然后Pop释放它。丢弃栈顶元素,迭代直到临时变量中的元素为空;
④ 当谈到“如何判断临时对象中的元素是否为空?”时,Stack类中的empty成员函数可以帮到我们。
说实话,当我第一次遇到Stack自定义类的时候,我也纳闷为什么不直接操作Stack的成员数据呢?Stack自定义类类型的成员数据的访问权限设置为public不好吗?事实上,这是反过来的。我们希望该程序仅以我们想要的方式运行。我们可以对STL容器进行处理,利用精华构造出我们想要的“新容器”,但我们*关注的是“安全、安全、安全!” 只有经过封装,我们要提供的外设接口才暴露给操作者,才能保证程序的绝对安全!
模板专业化
其实模板特化就是我们常说的重载,但是我们这里的重载只是模板的类型参数列表不同,*重要的一点是“模板特化是针对类类型的”,函数模板没有这个特征:
应用于函数模板时,编译器报错!
模板全专业化
一个模板称为全特化条件:1.必须有一个主模板类2.模板类型是完全指定的。
代码示例:
#include
using namespace std;
template
class Base
{
public:
void ShowInf()
{
cout << "实例化T的类型为" << typeid(T).name() << endl;
}
};
template <>
class Base
{
public:
void ShowInf()
{
cout << "全特化模板,实例化T的类型为int" << endl;
}
};
int main()
{
Base Obj;
Obj.ShowInf();
}
操作结果:
模板偏特化
偏特化是介于两者之间的模板。它的模板名称与主版本模板的名称相同。但是,在其模板类型中,有明确的部分和未明确的部分。
注意:部分特化的条件:1.必须有主模板,2.模板类型部分明确。
类型参数为指针时模板的部分特化
代码示例:
#include
using namespace std;
template
class Base
{
public:
void ShowInf()
{
cout << "实例化T的类型为" << typeid(T).name() << endl;
}
};
template // 如果模板实例化参数T为指针类型,则调用该模板进行实例化操作
class Base
{
public:
void ShowInf()
{
cout << "偏特化模板,其模板参数为" << typeid(T).name() << endl;
}
};
int main()
{
Base Obj;
Obj.ShowInf();
}
操作结果:
类型参数部分实例化期间的部分模板特化
代码示例:
#include
using namespace std;
template
class Base
{
public:
void ShowInf()
{
cout << "普通模板的实例化" << endl;
}
};
template
class Base
{
public:
void ShowInf()
{
cout << "偏特化模板" << endl;
}
};
int main()
{
Base Obj;
Obj.ShowInf();
}
操作结果:
当两个类型参数 T1 和 T2 相同时模板的部分特化
代码示例:
#include
using namespace std;
template
class Base
{
public:
void ShowInf()
{
cout << "普通模板的实例化" << endl;
}
};
template
class Base
{
public:
void ShowInf()
{
cout << "偏特化模板" << endl;
}
};
int main()
{
Base Obj;
Obj.ShowInf();
}
操作结果:
模板实例化时,模板的全特化和模板的部分特化适用于调用序列:
注意:类类型一旦声明为模板,就不能有同名的普通类的版本,即类模板和同名的普通类不能共存!
优先级:全专班>半专班>大师版模板班
代码示例:
#include
using namespace std;
template
class Base
{
public:
void ShowInf()
{
cout << "普通模板的实例化" << endl;
}
};
template<>
class Base
{
public:
void ShowInf()
{
cout << "全特化模板" << endl;
}
};
template
class Base
{
public:
void ShowInf()
{
cout << "偏特化模板" << endl;
}
};
int main()
{
Base Obj;
Obj.ShowInf();
}
操作结果:
函数模板的重载(类似于类模板的特化)
代码示例:
#include
using namespace std;
template
void ShowInf(T obj)
{
cout << "调用普通模板" << endl;
}
template <>
void ShowInf(int obj)
{
cout << "调用特定类型的函数模板" << endl;
}
void ShowInf(int obj)
{
cout << "调用普通版本的函数版本" << endl;
}
int main()
{
int obj = 10;
ShowInf(obj);
}
操作结果:
从这个例子可以看出,当函数的普通版本和函数模板的专用版本都满足要求时,调用顺序如下:
优先顺序:普通功能版>功能模板专业版>功能模板主版
模板“模板参数”
为什么需要模板的“模板参数”?
*重要的原因是“方便”!怎么方便?我们以 Stack 为例。如果我们想允许容器存储指定的 Stack 元素,我们这样做:
template >
class Stack {
private:
Cont elems; // elements
......
};
当我们实例化一个 Stack 类的对象时,我们通常会这样做:
Stack> dblStack;
但是这样做的缺点是需要指定两次元素类型,但是这两种类型是一样的。
使用模板模板参数允许我们在声明 Stack 类模板时只指定容器的类型,而不指定容器中元素的类型。例如:
template class Cont = std::deque>
class Stack {
private:
Cont elems; // elements
public:
void push(T const &); // push element
void pop(); // pop element
T const &top() const; // return top element
bool empty() const { // return whether the stack is empty
return elems.empty();
}
...
};
此时,当我们实例化 Stack 模板时,我们执行以下操作:
Stack vStack; // integer stack that uses a vector
与第一种方法的区别在于:第二个模板参数是一个类模板。
在这一点上,一切看起来都那么顺利,真的吗?
例如:
#include
using namespace std;
#include
template class CONT >
class Base
{
};
int main()
{
Base obj;
}
操作结果:
错误原因:
其实参数不匹配是有原因的,我们传入的参数是错误的,为什么?让我们回想一下:STL 标准容器类型实际上是一种模板类型。实例化一个STL容器的时候,通常是一个vector,但是vector容器里面其实有两个参数。第二个参数是“容器空间配置器分配器”。为了完全匹配vector模板类的类型,我们做如下调整:
#include
using namespace std;
#include
template > class CONT >
class Base
{
};
int main()
{
Base obj;
}
在这种情况下,程序运行完全正常。
将字符串传递给模板的注意事项
有人问为什么错了?我们知道,适用字符数组的大小比较应该遵循“字符数组中元素个数相同”的原则。这里的“apple”参数推导出的T是const char[6]类型(隐含' '字符),“tomato”参数推导出的T是const char[7]类型。显然,两个字符数组的元素个数是不一样的。在C语言风格的字符数组比较大小规则中,这显然是不允许的。
上面的例子可以更正。在C++风格的字符数组比较大小中,可以实现不同元素个数的字符数组的大小比较。这是由于“当没有引用时,实参推导,即自动类型推断,会丢失所传递的实参的一些属性,例如:当没有引用时,你传入一个字符数组,应用数组之间的比较,实现了C语言风格比较字符数组大小的原理,但是当失去了“字符数组特性”时,也就是你传入的参数被编译器为“简单字符常量指针 const char*”,
为了改善这个毛病,我们可以做以下优化:
① 重载*大比较大小的模板函数,实现参数为const char[]类型的函数模板的特殊实例化实现:
#include
using namespace std;
template
T const* max(T const a[N], T const b[M])
{
return a > b ? a : b;
}
int main()
{
const char ch1[] = "apple", ch2[] = "NewYear";
cout << max(ch1, ch2) << endl;
}
输出结果:
② 使用上面的解决方法:使用非引用参数推导代替引用参数推导:
这样做可能会导致衰变类型退化,失去一些重要的特性,并导致一些无用的副本(这些无用的副本是类型特性丢失的主要原因),
③ 将输入参数的数据类型改为const T*。当我们将 T 声明为 char 数据类型时,const T* 就是 const char* 类型。这样就可以将ch1和ch2强制为指针类型,去掉“字符数组”Features”,类似于实参推导中的衰减退化操作:
#include
using namespace std;
template
T const* max(const T *a, const T *b)
{
return a > b ? a : b;
}
int main()
{
const char ch1[] = "apple", ch2[] = "NewYear";
cout << max(ch1, ch2) << endl;
}
相关阅读Relate
热门文章 Recent
- 德语翻译大学生个人简历模板 德语专业简历模板赏析2023-03-11
- 外文翻译格式模板节选 英文参考文献标准格式2023-03-11
- 初中英语作文万能模板及翻译 初中英语作文万能套用高分模板2023-03-11
- 学位证翻译英文模板 毕业证学位证英文版盖章2023-03-11
- 英语投诉信模板加翻译 考研英语写作范文100篇之七 申请信&投诉信2023-03-11
- 翻译招聘公告模板 广西壮族自治区外事办公室翻译室2020年度公开招聘工作人员公告2023-03-11
- *正规的法国护照翻译模板 法国留学生学历认证需要翻译哪些材料?2023-03-11
- 微软件英语应聘信模板及翻译-应聘信模板2023-03-11
- 病毒rna可直接作为翻译的模板 基因转录与翻译知识点.doc2023-03-11
- 签证深圳居住证翻译模板 零件提交保证书(PSW)2023-03-11