c++模板进阶

一、模板初阶总结

​ 学完模板初阶之后,可以总结以下3点:

​ 1.可以通过修改模板参数来控制容器内部的数据类型;

​ 2.可以控制模板参数实现某种设计逻辑,如:适配器模式;

​ 3.可以通过类模板实例化出来类型,然后实例化出来函数对象,在函数体内部使用函数对象实现自主切换比较方式,如仿函数;

二、模板进阶

1.class与typename的区别

template<class Container>
void Print(const Container& con)
{
    //此处必须使用typename修饰
	typename Container::const_iterator it = con.begin();
	while (it != con.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

​ 编译器是从上往下执行,会向上检查各种声明和定义,但是此时的Container是不明确的,没有实例化。编译器知道的是Container是一个类型,类型::这种语法后面的内容可能是静态成员变量,静态成员函数,内嵌类型,内部类,而Container::const_iterator 如果是类型就支持 类型 it = con.begin(),如果不是,如静态成员变量,静态成员函数就会语法出错。使用了typename就告诉编译器是类型,这样语法就合乎逻辑了。

​ 总之,如果不是实例化,类型::内嵌类型就使用typename修饰。

2.非类型模板参数

2.1静态数组

#define N 10;
template<class T>
class A
{
private:
	int arr[N];
};

​ 使用这样的方式,同时生成多个对象的数组大小是相同的,不符合泛型编程。

template<class T, size_t N = 10>
class A
{
private:
	int arr[N];
};

​ 可以使用非类型模板参数,但是非类型模板参数仅仅支持整型家族,不支持浮点型,就和成员常量一样。这里的N是一个常量。

​ 编译器会按需实例化,实例化之后进入检查语法。

​ bitmap(位图)还有,c++11的array就是用了非类型模板参数。

3.array

​ array是一个固定大小的序列容器,数组在栈上,成员变量是一个静态数组和size;

3.1接口函数

在这里插入图片描述

没有插入删除之类的接口,大小是固定的。

3.2array的优势

​ 可能array唯一的优势就是能够很快的检查出来越界。而原生数组不能检查越界读,可以检查部分越界写。但是vector<int> v(10,1);v[10]也可以检查越界,所以array的设计用处不大。

4.如何看待c++发展

​ c++是比较贴近底层的语言,不像Java还需要在jvm上运行。但是c++委员会比较划水,到现在也没有出网络库还得自己写套接字,同时还更新了较为鸡肋的东西,如array容器和原生数组基本相同。

5.模板的特化

​ 模板实例化出来的类都是不一样的,特化出来的类模板可以生成内容与普通类模板完全不同的类。

5.1函数模板的特化

​ 使用场景如:想让指针按照指向的内容进行比较这样需要特殊化处理的场景。编译器会按照更匹配的原则来进行实例化。

template <class T>
bool Less(T a, T b)
{
    return a < b;
}
template <>//标准模板的特化
bool Less<int *>(int *a, int *b)
{
    return *a < *b;
}
//当然最好直接写成函数重载.

5.2类模板的特化

template <class T>
class A
{
public:
    A(){cout<<"class A"<<endl};
};
template <>//类模板就必须按照模板的特化来完成
class A<int>
{
public:
    A(){cout<<"class A<int>"<<endl};
};

​ 对于大部分比较,可以用仿函数进行控制比较,但若让指针按照指向的内容进行比较,原来得专门再写一个仿函数进行运算符重载,现在可以使用模板的特化对特殊类型进行处理,不需要手动传递新的仿函数进行实例化;

5.3模板的全特化和偏特化

template <class T1, class T2>
struct A
{
    A(T1 a, T2 b) { cout << "struct A" << endl; }
};
template <>//全特化
struct A<int, double>
{
    A(int a, double b) { cout << "struct A<int, double>" << endl; }
};
template <class T2>//偏特化1.部分类型的特化成具体类型
struct A<T2, double>
{
    A(int a, double b) { cout << "struct A<T2, double>" << endl; }
};
template <class T1,class T2>//偏特化2.部分类型的进一步限制,缩小范围
struct A<T1, T2*>
{
    A(int a, double b) { cout << "struct A<T1, T2*>" << endl; }
};

​ 总结:如果想要对某一个类型进行特殊处理就是使用全特化,而对一批类型进行特殊处理就使用偏特化。

5.4特化的使用场景

​ 1.迭代器的萃取就是使用了模板的特化;

​ 2.哈希表的k默认不可以使用自定义类型,而string可以就是使用了模板的特化。

6.模板的分离编译

6.1现象

#include <deque>

namespace Stack
{
    template <class T, class Container = std::deque<T>>
    class stack
    {
    public:
        void push(const T &val);
        void pop();
        const T &top();
        size_t size();
        bool empty();

    private:
        Container con_;
    };
}
namespace Stack
{
    template <class T, class Container>
    void stack<T, Container>::push(const T &val)
    {
        con_.push_back(val);
    }
    template <class T, class Container>
    void stack<T, Container>::pop()
    {
        con_.pop_back();
    }
    template <class T, class Container>
    const T &stack<T, Container>::top()
    {
        return con_.back();
    }
    template <class T, class Container>
    size_t stack<T, Container>::size()
    {
        return con_.size();
    }
    template <class T, class Container>
    bool stack<T, Container>::empty()
    {
        return con_.empty();
    }
}

​ 使用模板后进行分离编译,会发生链接错误。因为在stack.cc文件中,模板未实例化出来之前,相当于编译器没有写,所以编译阶段就没有形成符号,进行符号汇总,之后链接阶段无法进行符号表合并,形成有效符号表。而test.cc文件中一般都会实例化类出来,比如stack<int>,会在编译阶段进行符号汇总。

​ 即编译阶段如果进行了模板的实例化,就会生成符号汇总,才可以在汇编阶段生成符号表,如果有定义,生成的就是有效的,没定义就是无效的,最后链接阶段会把所有的符号表合并,存在有效的就链接成功,没有就失败。

6.2解决方式

6.2.1.专用化类
//stack.cc文件最后加如下内容,但是缺点就是使用一个类型就得显式实例化一个类。
//这样做会使得stack.cc文件在编译阶段能够形成符号汇总,有了这个条件就可以在汇编阶段生成符号表,因为此文件中有定义,所以这个符号表就是有效的,最终就会链接成功。
template
class stack<int>;//实例化生成类
template <>//不能这样写,这样就是模板的特化
class Stack::stack<int>;
6.2.2在同一个文件进行分离

​ 对这类型文件有人将其命名为“xxx.hpp”、“xxx.hxx”、“xxx.hh”,但是这只是一种暗示”.h和.cpp“,“.h和.cxx”,“.h和.cc”放到了一起,没有强制规定。

//这个文件中没有进行符号汇总,但是进行了分离且有定义,能保证生成有效的符号表。
//若其他文件使用此文件时,只要在其他文件进行显式实例化,就可以直接保证其他文件汇编生成的符号表是有效的,链接成功。
#include <deque>

namespace Stack
{
    template <class T, class Container = std::deque<T>>
    class stack
    {
    public:
        void push(const T &val);
        void pop();
        const T &top();
        size_t size();
        bool empty();

    private:
        Container con_;
    };
}
namespace Stack
{
    template <class T, class Container>
    void stack<T, Container>::push(const T &val)
    {
        con_.push_back(val);
    }
    template <class T, class Container>
    void stack<T, Container>::pop()
    {
        con_.pop_back();
    }
    template <class T, class Container>
    const T &stack<T, Container>::top()
    {
        return con_.back();
    }
    template <class T, class Container>
    size_t stack<T, Container>::size()
    {
        return con_.size();
    }
    template <class T, class Container>
    bool stack<T, Container>::empty()
    {
        return con_.empty();
    }
}

7.模板总结

​ 模板实例化,对于同种类型只会生成一个类。

优点:

​ 1.模板的出现使得开发效率显著提高,只需要自己写一份模板,编译器就自动生成各种代码,不会让用户手动写冗余的代码,十分便捷;

​ 2.代码更加灵活,适配器,仿函数,可通过改变模板参数实现,冗余部分直接替换;

缺点:

​ 1.会导致编译时间变长,模板实例化的过程是编译器来完成,需要时间;

​ 2.调试时,调试信息难以理解,调试信息过长;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值