C++ Primer阅读心得(第十六章)

本文详细探讨了C++中的模板,包括模板实例化、函数模板、类模板、模板实参推断、模板别名、成员模板、模板与继承、默认模板实参、模板显式实例化、模板函数的右值引用参数类型推断规则,以及std::move和std::forward的使用。还介绍了模板重载、特化、变参模板等高级话题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.模板实例化:C++中的模板是一个函数或者类的蓝图,编写了不局限于类型的通用代码。模板定义本身不参与编译,而是编译器根据模板的用户使用模板时提供的类型参数生成代码,再进行编译,这一过程被称为模板实例化。用户提供不同的类型参数,就会实例化出不同的代码。模板与OO的区别在于:OO是通过显式接口(写在.h文件中的基类)和运行期多态(根据运行时的实际类型调用相应的virtual函数)来扩展程序的;而模板是通过隐式接口(类型必须满足模板代码中的相关语句需求,例如:可调用某函数等)和编译期多态(根据使用者提供的不同类型,编译出不同的代码)来扩展程序的。

2.函数模板:编译器可以根据用户使用函数模板时提供的实参,推断出函数模板的类型参数,这被称为模板实参推断。

template<typename T>
int compare(const T& left, const T& right) {
    if (left < right) {
        return -1; 
    }
    if (right < left) {
        return 1; 
    }
    return 0;
}

compare(12, 13); //推断类型为int,实例化int版本
compare(0.1, 0.2); //推断类型为double,实例化double版本

除了类型参数之外,还可以在函数模板中定义非类型参数,我们可以在模板定义中使用一个具体类型来指定它们。

template<unsigned N, unsigned M> //使用unsigned标记非类型参数
int compare(const char (&left) [N], const char (&right) [M]) {
    return compare(left, right);
}

compare("hi", "hello"); //推断出N=3,M=6

一个非类型参数可以是一个整形,或者是指针或左值引用。作为整形推断的结果必须是一个常量表达式,指针和引用必须指向具有静态生存周期的对象(static或者全局)或者是nullptr或0。此外,当用户把函数模板赋值给一个指定类型的函数指针时,编译还可以根据这个指针的类型,对模板参数进行推断。

template<typename T>
int compare(const T&, const T&){...}

int (*pf) (const int&, const int&) = compare; //ok,推断T的类型为int

另外,函数模板可以声明为inline或者constexpr的,将它们放在template之后,返回值之前即可。

3.类模板:与函数模板不同,类模板不能推断实例化,只能在使用时由用户显示制定类型参数来实例化。

template<typename T>
class Foo {
...
};
Foo f1; //错误,必须指定类型
Foo<int> f2; //ok

类模板的成员函数既可以定义在内部,也可以定义在外部。定义在内部的被隐式声明为inline,定义在外部的类名之前必须加上template。

template<typename T>
class Foo{
public:
    void f1() {...} //ok,内部定义
    void f2();
};

template<typename T>
void Foo<T>::f2 {...} //注意外部定义要加上template<>

类模板中既可以把非模板函数、类声明为自己的友元,也可以将模板函数、类声明为自己的友元。如果一个类模板包含一个非模板友元,那么这个友元可以访问此模板的所有实例。如果一个类模板包含一个模板友元,那么根据实现,既可以授权给这个模板友元的一个实例,也可以授权给这个模板友元的所有实例。在C++11中,我们还可以将模板类型参数声明为友元。

class A{...};
template<typename T>
class B{...};
template<typename T>
class C{...};

template<typename T>
class D {
    friend class A; //A是D所有实例的友元
    friend class B<T>; //B<T>是D<T>的友元
    template<typename X> friend class C; //C的所有实例都是D的所有实例的友元
    friend T; //T是D<T>的友元
};

类模板中可以声明static成员,它必须有且仅有一个定义。但是,每个不同的模板实例都会有一个独有的static成员对象。还有一点需要注意的是,类模板的成员函数并非全部实例化的,而是哪个成员函数被使用到了,哪个成员函数才会被实例化,而未被使用到的成员函数是不会被实例化的(静态成员例外,一定会被实例化)。

4.类模板别名:我们可以使用typedef为类模板定义个别名。在C++11中,使用using语句可以固定一个或多个类型参数(partial模板?)。

template <typename T> using WithNum = std::pair<T, int>;
WithNum<std::string> strs; //实际类型,pair<string, int>
WithNum<int> ints; //实际类型,pair<int, int>

5.模板中使用类的类型成员:在C++中,假定通过域操作符访问的名称不是类型(而是static成员、全局变量等)。所以在模板中,如果想使用一个模板类型参数的类型成员,就要通过使用typename关键字显式的通知编译器,域操作符后面的是一个类型。

template <typename containerT, typename elemT>
typename containerT::difference_type count(const containerT& c, const elemT& e) {
    typename containerT::difference_type ret = 0;
    typename containerT::const_iterator iter = c.cbegin();
    while (iter != c.cend()) {
        if (*iter == e) {
            ret++;
        }
        iter++;
    }
    return ret;
}

6. 模板与继承:当从一个模板基类继承时,派生类不能直接使用基类中继承过来的内容,这是因为此时基类还带有参数尚未明确定义,所以编译器无法向上查找。你必须通过this指针或者明确指明内容所属的基类名称(using声明或者前面加上基类名称)来通知编译器这个内容属于谁。

template<typename T>
class TemplateBase {
public:
    void show_base() {
        std::cout << "Show template base" << std::endl;
    }
};

template<typename T>
class TemplateDerived : public TemplateBase<T>{
public:
    using TemplateBase<T>::show_base; //using声明函数在基类中
    void show_derived() {
        std::cout << "Show template derived" << std::endl;
        //show_base();                 //直接调用,error
        this->show_base();             //ok,声明实例化就在对象内部
        TemplateBa
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值