模板偏特化翻译 为什么基类的成员在父类中调用必须指明作用域或者使用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

  • 法国签证营业执照翻译件模板 你与申根签证只有一条推送的距离
  • 江苏省增值税发票翻译模板 江苏税务局出口货物退(免)税申报管理系统软件
  • 肄业证书翻译模板 复旦大学学生学业证明文书管理细则(试行)
  • 四级英语作文模板带翻译 大学英语四级翻译模拟训练及答案
  • 社会某信用代码证翻译模板 js验证某社会信用代码,某社会信用代码 验证js,js+验证+社会信用代码证
  • 美国移民证件翻译模板 日语签证翻译聊聊身份证翻译模板
  • 翻译软件模板 人类史上*实用的的文档快速翻译指南
  • 江苏省增值税发票翻译模板 江苏出口货物退(免)税申报管理服务平台
  • 瑞士签证房产证翻译件模板 瑞士探亲签证—就读子女
  • 日语户口本翻译模板 户口本翻译价格_户口本翻译一般多少钱?
  • 模板偏特化翻译 为什么基类的成员在父类中调用必须指明作用域或者使用this->? www.chinazxzy.com/fymb/5080.html
    
    本站部分内容和图片来源于网络用户和读者投稿,不确定投稿用户享有完全著作权,根据《信息网络传播权保护条例》,如果侵犯了您的权利,请联系:chinazxzy@163.com,及时删除。
    Go To Top 回顶部
    • 扫一扫,微信在线