C++ Primer 学习笔记_75_模板与泛型编程 --模板定义

模板与泛型编程

--模板定义

引言:

所谓泛型程序就是以独立于不论什么特定类型的方式编写代码。使用泛型程序时,我们须要提供详细程序实例所操作的类型或值。

模板是泛型编程的基础。使用模板时能够无须了解模板的定义

泛型编程与面向对象编程一样,都依赖于某种形式的多态性。面向对象编程中的多态性在执行时应用于存在继承关系的类。我们能够编写使用这些类的代码,忽略基类与派生类之间类型上的差异。仅仅要使用基类的引用或指针,基类类型或派生类类型的对象就能够使用同样的代码。

在泛型编程中,我们所编写的类和函数能够多态地用于跨越编译时不相关的类型。一个类或一个函数能够用来操纵多种类型对象。标准库中的容器、迭代器和算法是非常好的泛型编程的样例。标准库用独立于类型的方式定义每一个容器、迭代器和算法,因此差点儿能够在随意类型上使用标准库的类和函数

在C++中,模板是泛型编程的基础。模板是创建类或函数的蓝图或公式。

编写重载函数:

int compare(const string &v1,const string &v2)
{
    if (v1 < v2)
    {
        return -1;
    }
    else if (v1 > v2)
    {
        return 1;
    }

    return 0;
}

int compare(const double &v1,const double &v2)
{
    if (v1 < v2)
    {
        return -1;
    }
    else if (v1 > v2)
    {
        return 1;
    }

    return 0;
}

这些函数差点儿同样,它们之间唯一的差别是形參的类型,每一个函数的函数体是同样的

每一个要比較的类型都须要反复函数的函数体,不仅麻烦并且easy出错。更重要的是,须要事先知道空间可能会比較哪些类型。假设希望将函数用于未知类型,这样的策略就不起作用了

一、定义函数模板

我们能够不用为每一个类型定义一个新函数,而是定义一个函数模板。函数模板是一个独立于类型的函数,能够作为一种方式,产生函数的特定类型版本号。

template<typename T>
int compare(const T &v1,const T &v2)
{
    if (v1 < v2)
    {
        return -1;
    }
    else if (v1 > v2)
    {
        return 1;
    }

    return 0;
}

模板定义以keywordtemplate開始,后接模板形參表,模板形參表是用尖括号括住的一个或多个模板形參的列表,形參之间以逗号分隔。并且模板形參表不能为空。

1、模板形參表

模板形參表类似于函数形參表,表示能够在类或函数的定义中使用的类型或值。比如,compare 函数声明一个名为T的类型形參。在compare内部,能够使用名字T引用一个类型,T表示哪个实际类型由编译器依据所用的函数而确定

模板形參能够是表示类型的类型形參,或者是表示常量表达式的非类型形參。类型形參跟在keywordclasstypename之后定义。

2、使用函数模板

使用函数模板时,编译器会判断哪个(或哪些)模板实參绑定到模板形參。一旦编译器确定了实际的模板实參,就称它实例化了函数模板的一个实例。

推导出实际模板实參后,编译器使用实參取代相应的模板形參产生编译该版本号的函数。

int main ()
{
    // 绑定到compare(const int&, const int&)
    cout << compare(1, 0) << endl;

    // 绑定到compare(const string&, const string&)
    string s1 = "hi", s2 = "world";
    cout << compare(s1, s2) << endl;
    return 0;
}

3、inline函数模板

inline说明符放在模板形參表之后、返回类型之前,不能放在keywordtemplate之前。

template<typename T> inline
int compare(const T &v1,const T &v2);	//OK

inline template<typename T>
int compare(const T &v1,const T &v2);	//Error

//P528 习题16.1
template<typename T> inline
T abs(T val)
{
    return val > 0 ? val : -val;
}

int main ()
{
    cout << abs(-1) << endl;
    cout << abs(-0.98) << endl;
    cout << abs(short(3.4)) << endl;
    return 0;
}

//习题16.2
template<typename T>
ostream &write(ostream &os,T val)
{
    return os << val << endl;
}

int main()
{
    write(cout,1);
    write(cout,12.3);
    write(cout,"Hello World");

    ofstream outFile("output");
    write(outFile,"Hello");
    write(outFile,123);

    string temp;
    ostringstream strStream(temp);
    write(strStream,"Hello_World");

    cout << strStream.str() << endl;
}

二、定义类模板

为了举例说明类模板,我们将为标准库queue类实现一个自己的版本号。

我们自己定义的Queue类必须能够支持不同类型的对象,所以将它定义为类模板。Queue所能支持的操作:

1)push:在队尾加入一项

2)pop:从队头删除一项

3)front:返回队头的引用

4)empty:指出队列是否为空

template<typename Type> class Queue
{
public:
    Type &front();
    const Type &front() const;

    void push(const Type &);
    void pop();
    bool empty() const;

private:
    //...
};

类模板也是模板,因此必须以keywordtemplate开头,后接模板形參表。

除了模板形參表外,类模板的定义看起来与随意其它类类似。在类和类成员的定义中,能够使用模板形參作为类型或值的占位符,在使用类时再提供那些类型或值。

使用类模板

与调用函数模板形成对照,使用类模板时,必须为模板形參显式指定实參

    Queue<int> qi;
    Queue< vector<double> > qc;
    Queue<string> qs;

编译器使用实參来实例化这个类的特定类型版本号。实质上,编译器用用户提供的实际特定类型取代Type,又一次编写Queue。在这个样例中,编译器将实例化三个Queue:第一个用int取代 Type,第二个用vector<double>取代 Type,第三个用string取代 Type。

//P529 习题16.5
template<typename T>
T getBigger(const T &val1,const T &val2)
{
    return val1 > val2 ? val1 : val2;
}

//习题16.6
template<typename Type> class List
{
public:
    List();

    void push_back(const Type &);
    void push_front(const Type &);

    std::size_t size() const;

    void insert(Type *ptr,const Type &val);

    bool empty();

private:
    //...
};

三、模板形參

像函数形參一样,为模板形參选择的名字没有本质含义:

template<typename Glorp>
int compare(const Glorp &v1,const Glorp &v2)
{
    if (v1 < v2)
    {
        return -1;
    }
    else if (v1 > v2)
    {
        return 1;
    }

    return 0;
}

该代码与前面定义的compare模板一样。

能够给模板形參授予的唯一含义是差别是类型形參还是非类型形參。假设是类型形參,我们就知道该形參表示未知类型,假设是非类型形參,我们就知道它是一个未知值。

假设希望使用模板形參所表示的类型或值,能够使用与相应模板形參同样的名字。比如,compare函数中全部的Glorp引用将在该函数被实例化时确定为同一类型。

1、模板形參作用域

模板形參的名字能够在声明为模板形參之后直到模板声明定义的末尾处使用。

模板形參遵循常规名字屏蔽规则:

typedef double T;
template <class T>
T calc(const T &a,const T &b)
{
    //此处T为template形參表中的T,全局名字被屏蔽
    T tmp = a;
    //...
    return tmp;
}

2、使用模板形參名字的限制

用作模板形參的名字不能在模板内部重用:

template <class T>
T calc(const T &a,const T &b)
{
    typedef double T;   //Error

    T tmp = a;
    //...
    return tmp;
}

这一限制还意味着模板形參的名字仅仅能在同一模板形參表中使用一次:

template <class T,class T> T calc(const T &a,const T &b);	//Error

正如能够重用函数形參名字一样,模板形參的名字也能在不同模板中重用:

template <class T> T calc(const T &a,const T &b);

template <class T> int compare(const T &,const T&);	//OK

3、模板声明

像其它随意函数或类一样,对于模板能够仅仅声明而不定义声明必须指出函数或类是一个模板:

template <class T>
int compare(const T &,const T&);

同一模板的声明和定义中,模板形參的名字不必同样:

template <class T>
T calc(const T &,const T &);

template <typename U>
U calc(const U&,const U&);

template <class Type>
Type calc(const Type &,const Type &);

每一个模板类型形參前面必须带上keywordclass或typename,每一个非类型形參前面必须带上类型名字,省略keyword或类型说明符是错误的:

template<typename T,U>
T calc(const T &,const U &);	//Error

template<typename T,class U>
T calc(const T &,const U &);	//OK

//P531 习题16.9
template <typename Type,typename T>
Type find(Type begin,Type end,const T &val)
{
    while (begin != end)
    {
        if (*begin == val)
        {
            return begin;
        }
        ++ begin;
    }

    return end;
}

int main()
{
    int ia[] = {01,1,1,999,2,3,2,34,4,3,4};

    int *p;
    if ((p = find(ia,ia+sizeof(ia)/sizeof(*ia),999)) != ia + sizeof(ia)/sizeof(*ia))
    {
        cout << *p << endl;
    }
    else
    {
        cout << "Not Found!" << endl;
    }

    vector<int> iVec(ia,ia + sizeof(ia)/sizeof(*ia));
    vector<int>::iterator iter;
    if ((iter = find(iVec.begin(),iVec.end(),888)) != iVec.end())
    {
        cout << *iter << endl;
    }
    else
    {
        cout << "Not Found!" << endl;
    }

    ifstream inFile("input");
    vector<string> strVec;
    string val;

    while (inFile >> val)
    {
        strVec.push_back(val);
    }

    vector<string>::iterator it;
    if ((it = find(strVec.begin(),strVec.end(),"hello")) != strVec.end())
    {
        cout << *it << endl;
    }
    else
    {
        cout << "Not Found!" << endl;
    }
}

C++ Primer 学习笔记_75_模板与泛型编程 --模板定义,布布扣,bubuko.com

时间: 2024-10-26 11:24:44

C++ Primer 学习笔记_75_模板与泛型编程 --模板定义的相关文章

C++ Primer 学习笔记_65_面向对象编程 --概述、定义基类和派生类

面向对象编程 --概述.定义基类和派生类 引言: 面向对象编程基于的三个基本概念:数据抽象.继承和动态绑定. 在C++中,用类进行数据抽象,用类派生从一个类继承另一个:派生类继承基类的成员.动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数. 继承和动态绑定在两个方面简化了我们的程序:[继承]能够容易地定义与其他类相似但又不相同的新类,[派生]能够更容易地编写忽略这些相似类型之间区别的程序. 面向对象编程:概述 面向对象编程的关键思想是多态性(polymorphism)

C++ Primer 学习笔记_65_面向对象编程 -概述、定义基类跟派生类

面向对象编程 --概述.定义基类和派生类 引言: 面向对象编程基于的三个基本概念:数据抽象.继承和动态绑定. 在C++中,用类进行数据抽象,用类派生从一个类继承另一个:派生类继承基类的成员.动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数. 继承和动态绑定在两个方面简化了我们的程序:[继承]能够容易地定义与其他类相似但又不相同的新类,[派生]能够更容易地编写忽略这些相似类型之间区别的程序. 面向对象编程:概述 面向对象编程的关键思想是多态性(polymorphism)

C++ Primer 学习笔记_77_模板与泛型编程 --实例化

模板与泛型编程 --实例化 引言: 模板是一个蓝图,它本身不是类或函数.编译器使用模板产生指定的类或函数的特定版本号.产生模板的特定类型实例的过程称为实例化. 模板在使用时将进行实例化,类模板在引用实际模板类型时实例化,函数模板在调用它或用它对函数指针进行初始化或赋值时实例化. 1.类的实例化 当编写Queue<int>qi时,编译器自己主动创建名为Queue<int>的类.实际上,编译器通过又一次编写Queue模板,用类型int取代模板形參的每次出现而创建Queue<int

C++ Primer 学习笔记_81_模板与泛型编程 --类模板成员[续1]

模板与泛型编程 --类模板成员[续1] 二.非类型形参的模板实参 template <int hi,int wid> class Screen { public: Screen():screen(hi * wid,'#'), cursor(hi * wid),height(hi),width(wid) {} //.. private: std::string screen; std::string::size_type cursor; std::string::size_type height

C++ Primer 学习笔记_82_模板与泛型编程 --类模板成员[续2]

模板与泛型编程 --类模板成员[续2] 六.完整的Queue类 Queue的完整定义: template <typename Type> class Queue; template <typename Type> ostream &operator<<(ostream &,const Queue<Type> &); template <typename Type> class QueueItem { friend clas

C++ Primer 学习笔记_84_模板与泛型编程 --模板特化

模板与泛型编程 --模板特化 引言: 我们并不总是能够写出对全部可能被实例化的类型都最合适的模板.某些情况下,通用模板定义对于某个类型可能是全然错误的,通用模板定义或许不能编译或者做错误的事情;另外一些情况下,能够利用关于类型的一些特殊知识,编写比从模板实例化来的函数更有效率的函数. compare函数和 Queue类都是这一问题的好样例:与C风格字符串一起使用进,它们都不能正确工作. compare函数模板: template <typename Type> int compare(cons

C++ Primer 学习笔记_83_模板与泛型编程 --一个泛型句柄类

模板与泛型编程 --一个泛型句柄类 引言: [小心地雷] 这个例子体现了C++相当复杂的语言应用,理解它需要很好地理解继承和模板.在熟悉了这些特性之后再研究这个例子也许会帮助.另一方面,这个例子还能很好地测试你对这些特性的理解程度. 前面示例的Sales_item和Query两个类的使用计数的实现是相同的.这类问题非常适合于泛型编程:可以定义类模板管理指针和进行使用计数.原本不相关的Sales_item类型和 Query类型,可通过使用该模板进行公共的使用计数工作而得以简化.至于是公开还是隐藏下

C++ Primer 学习笔记_85_模板与泛型编程 --模板特化[续]

模板与泛型编程 --模板特化[续] 三.特化成员而不特化类 除了特化整个模板之外,还可以只特化push和pop成员.我们将特化push成员以复制字符数组,并且特化pop成员以释放该副本使用的内存: template<> void Queue<const char *>::push(const char *const &val) { char *new_item = new char[sizeof(val) + 1]; strncpy(new_item,val,sizeof(

C++ Primer 学习笔记_79_模板与泛型编程 --模板编译模型

模板与泛型编程 --模板编译模型 引言: 当编译器看到模板定义的时候,它不立即产生代码.只有在用到模板时,如果调用了函数模板或定义了模板的对象的时候,编译器才产生特定类型的模板实例. 一般而言,当调用函数时[不是模板],编译器只需看到函数的声明.类似的,定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的.因此,应该将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中. 模板则不同:要进行实例化,编译器必须能够访问定义模板的源代码.当调用函数模板或类模板的成员函