C++进阶(unordered_set+unordered_map模拟实现)

unordered_set

  • unordered_set是以无特定顺序存储唯一元素的容器,并且允许根据它们的值快速检索单个元素,是一种K模型
  • 在unordered_set中,元素的值同时是它的key,它唯一地标识它。键值是不可变的,因unordered_set中的元素不能在容器中修改一次 ,但是可以插入和删除它们。
  • 在内部,unordered_set中的元素不是按任何特定顺序排序的(无序),而是根据它们的哈希值组织成桶,以允许直接通过它们的值快速访问单个元素,时间复杂度可以达到O(1)。
  • unordered_set容器比set容器更快地通过它们的key访问单个元素,尽管它们通常对于通过其元素的子集进行范围迭代的效率较低。
  • 容器中的迭代器至少是单向迭代器。

使用

构造函数

unordered_set uset;//构造函数
unordered_set uset2(++uset.begin(),uset.end());//,如果不想全部拷贝,可以使用 unordered_set 类模板提供的迭代器,在现有 unordered_set 容器中选择部分区域内的元素,为新建 unordered_set 容器初始化
unordered_set ( const unordered_set& ust );//拷贝构造

容量和大小

empty();//若容器为空,则返回 true;否则 false
size();//返回当前容器中存有元素的个数

迭代器

begin();//返回指向容器中第一个元素的正向迭代器
end();//返回指向容器中最后一个元素之后位置的正向迭代器
cbegin();//和begin()功能相同,只不过其返回的是const类型的正向迭代器
cend();//和end()功能相同,只不过其返回的是const类型的正向迭代器

元素的访问和查找

find(key);//查找以值为 key 的元素,如果找到,则返回一个指向该元素的正向迭代器;反之,则返回一个指向容器中最后一个元素之后位置的迭代器(如果end()方法返回的迭代器)
count(key);//在容器中查找值为 key 的元素的个数
equal_range(key);//返回一个pair对象,其包含2个迭代器,用于表明当前容器中值为key的元素所在的范围

元素的插入和删除

emplace();//向容器中添加新元素,效率比insert()方法高
emplace_hint();//向容器中添加新元素,效率比 nsert()方法高
insert();//向容器中添加新元素
erase();//删除指定元素
clear();//清空容器,即删除容器中存储的所有元素
swap();//交换2个 unordered_set 容器存储的元素,前提是必须保证这 2 个容器的类型完全相等

实例演示:

void test_unordered_set()
{
    unordered_set us;
    set s;

    int arr[] = { 4,2,3,1,6,8,9,3 };

    for (auto e : arr)
    {
        us.insert(e);
        s.insert(e);
    }

    unordered_set::iterator usit = us.begin();
    set::iterator sit = s.begin();

    cout 

unordered_map

介绍

  • unordered_map是存储键值对(KV模型)的关联式容器,其允许通过keys快速的索引到与其对应的value。
  • 在unordered_map中,键值通常用于唯一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
  • 在内部,unordered_map没有对按照任何特定的顺序排序(无序), 为了能在常数范围内找到key所对应的value,unordered_map将相同哈希值的键值对放在相同的桶中
  • unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
  • unordered_map实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
  • 它的迭代器是一个单向迭代器。

使用

构造函数

unordered_map umap//构造函数: 可以不初始化地构造,也可以用一个容器的迭代器去构造
unordered_map ( const unordered_map& ump );//拷贝构造

容量和大小

empty();//判断容器是否为空,,若容器为空,则返回 true;否则 false
size();//返回容器中的元素个数

迭代器

begin();//返回指向容器中第一个键值对的正向迭代器
end();//返回指向容器中最后一个键值对之后位置的正向迭代器
cbegin();//和 begin() 功能相同,只不过在其基础上增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对
cend();//和 end() 功能相同,只不过在其基础上,增加了 const 属性,即该方法返回的迭代器不能用于修改容器内存储的键值对

元素的访问和查找

operator[key];//该模板类中重载了 [] 运算符,其功能是可以向访问数组中元素那样,只要给定某个键值对的键 key,就可以获取该键对应的值。注意,如果当前容器中没有以 key 为键的键值对,则其会使用该键向当前容器中插入一个新键值对
at(key);//返回容器中存储的键 key 对应的值,如果key不存在,则会抛出 out_of_range 异常
find(key);//查找以key为键的键值对,如果找到,则返回一个指向该键值对的正向迭代器;反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(如果end()方法返回的迭代器)

元素的插入和删除

emplace();//向容器中添加新键值对,效率比 insert()方法高
emplace_hint();//向容器中添加新键值对,效率比insert()方法高
insert();//向容器中添加新键值对
erase();//删除指定键值对
clear();//清空容器,即删除容器中存储的所有键值对
swap();//交换2个unordered_map容器存储的键值对,前提是必须保证这2个容器的类型完全相等
void test_unordered_map()
{
    unordered_map um;
    map m;

    int arr[] = { 4,2,3,1,6,8,9,3 };

    for (auto e : arr)
    {
        um.insert(make_pair(e, e));
        m.insert(make_pair(e, e));
    }

    unordered_map::iterator umit = um.begin();
    map::iterator mit = m.begin();

    cout first second first second 

unordered_map和unordered_set的实现

整体概述

这里我们用上一篇的博客中的哈希桶来封装出unordered_map和unordered_set两个容器

哈希桶代码:HashTable.h文件

#pragma once
#include
#include
using namespace std;
const int PRIMECOUNT = 28;
const size_t primeList[PRIMECOUNT] =
{
    53ul, 97ul, 193ul, 389ul, 769ul,
    1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
    49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
    1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
    50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
    1610612741ul, 3221225473ul, 4294967291ul
};
//仿函数,获取key值
template
struct KeyOfValue
{
    const K& operator()(const K& key)
    {
        return key;
    }
    const K& operator()(const pair& kv)
    {
        return kv.first;
    }
};
namespace CLOSE_HASH
{
    //闭散列,需要三种状态
    enum State
    {
        EMPTY,
        EXITS,
        DELETE
    };
    //定义节点
    template 
    struct HashData
    {
        //这里的data对象是匿名对象转化的,被const修饰不能修改,就是传参的关系,你传入了一个对象
        //被const修饰,就代表传入的那个对象不能被修改,你自己的内容还是可以修改的
        HashData(const T& data = T(), const State& state = EMPTY)
            : data(data)
            , state(state){}
        T data;
        State state;
    };
    //哈希表
    template
    class HashTable
    {
        typedef HashData HashData;
    public:
        HashTable(size_t capacity = 10)
            : table(capacity)
            , size(0)
        {}
        size_t getNextPrime(size_t num)
        {
            size_t i = 0;

            for (i = 0; i  num)
                {
                    return primeList[i];
                }
            }
            //如果比所有都大,还是返回最后一个,因为最后一个已经是32位最大容量
            return primeList[PRIMECOUNT - 1];
        }
        //除留余数法
        size_t HashFunc(const K& key)
        {
            return key % table.size();
        }
        //插入元素
        bool Insert(const T& data)
        {
            /*
            1.首先要判断是否需要增容,当装填因子>0.7的时候增容(装填因子 = 数据个数/哈希表大小)
            2.创建一个新表,把旧表的元素重新放到新表当中,因为表的大小发生变化,所以数据在旧表中的位置和新表的位置不一样,需要重新调整
            3.利用swap将两个表进行交换,函数结束的时候,旧表被自动析构
            4.增容之后,插入元素,采用线性探测,插入元素
            */
            KOFV kofv;
            if (size * 10 / table.size() >= 7)//增容
            {
                //增容的大小按照别人算好的近似两倍的素数来增,这样效率更高,也可以直接2倍或者1.5倍。
                //使用了vector默认的有参构造函数vector(size_type n, const value_type& val = value_type())//有参构造用n个val构造并初始化容器
                //const value_type& val = value_type()这段代码是匿名对象类型转换
                vector newTable(getNextPrime(size));
                for (size_t i = 0; i state = DELETE;
                size--;
                return true;
            }
            else
            {
                //没找到
                return false;
            }
        }
    private:
        vector table;
        int size = 0;
    };
    void TestHashTable1()
    {
        HashTable> ht;
        // HashTable, KeyOfValue> ht;

        int arr[] = { 10,20,14,57,26,30,49,72,43,55,82 };
        for (auto e : arr)
        {
            if (e == 72)
            {
                int a = 0;
            }
            ht.Insert(e);
        }

        for (auto e : arr)
        {
            ht.Erase(e);
        }
    }

    void TestHashTable2()
    {
        HashTable, KeyOfValue> ht;

        int arr[] = { 15,23,57,42,82,26,30,49,72,43,55 };
        for (auto e : arr)
        {
            ht.Insert(make_pair(e, e));
        }

        /*for (auto e : arr)
        {
            ht.Erase(e);
        }*/
    }

}
namespace OPEN_HASH
{
    template
    struct HashNode
    {
        HashNode(const T&data):data(data),next(nullptr){}
        T data;
        HashNode* next;
    };
    template
    struct _Hash
    {
        // 大多树的类型就是是什么类型就返回什么类型
        const K& operator()(const K& key)
        {
            return key;
        }
    };

    // 特化string
    template
    struct _Hash
    {
        size_t operator()(const string& key)
        {
            size_t hash = 0;
            // 把字符串的所有字母加起来   hash = hash*131 + key[i]
            for (size_t i = 0; i >
    class HashBucket;
    //迭代器的实现
    template
    struct HashBucket_Iterator
    {
        typedef HashBucket_Iterator Self;
        typedef HashNode Node;
        typedef HashBucket HashBucket;

        Node* node;
        HashBucket* _phb;
        HashBucket_Iterator(Node *node, HashBucket* phb):node(node),_phb(phb){}
        //操作符重载
        Ref operator*()
        {
            return node->data;
        }
        Ptr operator->()
        {
            return &node->data;
        }
        Self& operator++()
        {
            if (node->next)
            {
                node = node->next;
                return *this;
            }
            else
            {
                KOFV kofv;
                size_t index = _phb->HashFunc(kofv(node->data));

                for (size_t i = index + 1; i tables.size(); ++i)
                {
                    if (_phb->tables[i])
                    {
                        node = _phb->tables[i];
                        return *this;
                    }
                }
                node = nullptr;
                return *this;
            }
        }

        bool operator==(const Self& self) const
        {
            return node == self.node
                && _phb == self._phb;
        }

        bool operator!=(const Self& self) const
        {
            return !this->operator==(self);
        }
    };
    template
    class HashBucket
    {
        typedef HashNode Node;
        friend struct HashBucket_Iterator;
    public:
        typedef HashBucket_Iterator iterator;
        size_t getNextPrime(size_t num)
        {
            size_t i = 0;

            for (i = 0; i  num)
                {
                    return primeList[i];
                }
            }
            //如果比所有都大,还是返回最后一个,因为最后一个已经是32位最大容量
            return primeList[PRIMECOUNT - 1];
        }
        //除留余数法
        size_t HashFunc(const K& key)
        {
            Hash hash;
            return hash(key) % tables.size();
        }
        iterator begin()
        {
            for (size_t i = 0; i next;
                    delete cur;
                    cur = next;
                }
            }
        }
        pair Insert(const T& data)
        {
            KOFV kofv;
            //负载因子为1就增容
            if (size == tables.size())
            {
                vector newTable(getNextPrime(size));
                for (size_t i = 0; i next;
                        size_t index = HashFunc(kofv(cur->data));
                        //把cur连接到新表上
                        cur->next = newTable[index];
                        newTable[index] = cur;
                        //cur会发生变化,需要提前记录next
                        cur = next;
                    }
                }
                tables.swap(newTable);
            }
            size_t index = HashFunc(kofv(data));
            // 先查找该条链表上是否有要插入的元素
            Node* cur = tables[index];
            while (cur)
            {
                if (kofv(cur->data) == kofv(data))
                    return make_pair(iterator(cur, this), false);
                cur = cur->next;
            }
            // 插入数据,选择头插(也可以尾插)
            Node* newnode = new Node(data);
            newnode->next = tables[index];
            tables[index] = newnode;
            ++size;
            return make_pair(iterator(newnode, this), true);
        }
        iterator Find(const K& key)
        {
            KOFV kofv;
            int index = HashFunc(key) ;
            Node* cur = tables[index];

            while (cur)
            {
                if (key == kofv(cur->data))
                {
                    return iterator(cur, this);
                }
                cur = cur->next;
            }
            return iterator(nullptr);
        }

        bool Erase(const K& key)
        {
            KOFV kofv;
            int index = HashFunc(key) ;

            Node* prev = nullptr;
            Node* cur = tables[index];

            while (cur)
            {
                if (key == kofv(cur->data))
                {
                    // 删第一个节点时
                    if (prev == nullptr)
                    {
                        tables[index] = cur->next;
                    }
                    else
                    {
                        prev->next = cur->next;
                    }

                    size--;
                    delete cur;
                    return true;
                }
                prev = cur;
                cur = cur->next;
            }
            return false;
        }
    private:
        vector tables;
        int size = 0;
    };
    void TestHashBucket2()
    {
        HashBucket> ht;
        int arr[] = { 15,23,57,42,82,26,30,49,72,43,55 };
        for (auto e : arr)
        {
            ht.Insert(e);
        }

        for (auto e : arr)
        {
            HashBucket>::iterator it = ht.begin();

            while (it != ht.end())
            {
                cout > ht;

        ht.Insert("solleHas");
        ht.Insert("apple");
        ht.Insert("sort");
        ht.Insert("pass");
        ht.Insert("cet6");
        HashBucket>::iterator it = ht.begin();
        while (it != ht.end())
        {
            cout 

改造哈希表

整体框架

这里用的是哈希桶来封装unordered_map和unordered_set两个容器

const int PRIMECOUNT = 28;
const size_t primeList[PRIMECOUNT] =
{
    53ul, 97ul, 193ul, 389ul, 769ul,
    1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
    49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
    1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
    50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
    1610612741ul, 3221225473ul, 4294967291ul
};
template
struct HashNode
{
    HashNode(const T&data):data(data),next(nullptr){}
    T data;
    HashNode* next;
};
template
struct _Hash
{
    // 大多树的类型就是是什么类型就返回什么类型
    const K& operator()(const K& key)
    {
        return key;
    }
};
// 特化string
template
struct _Hash
{
    size_t operator()(const string& key)
    {
        size_t hash = 0;
        // 把字符串的所有字母加起来   hash = hash*131 + key[i]
        for (size_t i = 0; i 
class HashBucket
{
private:
        vector tables;
        int size = 0;// 记录表中的数据个数
};

为了让哈希表能够跑起来,我们这里实现一个迭代器的操作
迭代器的框架: 这里有两个成员,一个是节点指针,还有一个是哈希表的指针,只要就是为了方便实现迭代器++遍历哈希表的操作。模板参数列表的前四个主要是为了实现普通迭代器和const迭代器,第五个参数就是为了获得T中的key值,是一个仿函数(前几篇博客都有提到过),最后一个模板参数是哈希函数,为了构造出哈希表指针而存在

template
struct HashBucket_Iterator
{
    typedef HashBucket_Iterator Self;
    typedef HashNode Node;
    typedef HashBucket HashBucket;

    Node* node;
    HashBucket* _phb;
    HashBucket_Iterator(Node *node, HashBucket* phb):node(node),_phb(phb){}
}   

迭代器基本操作的实现:

//迭代器的实现
    template
    struct HashBucket_Iterator
    {
        typedef HashBucket_Iterator Self;
        typedef HashNode Node;
        typedef HashBucket HashBucket;

        Node* node;
        HashBucket* _phb;
        HashBucket_Iterator(Node *node, HashBucket* phb):node(node),_phb(phb){}
        //操作符重载
        Ref operator*()
        {
            return node->data;
        }
        Ptr operator->()
        {
            return &node->data;
        }
        Self& operator++()
        {
            if (node->next)
            {
                node = node->next;
                return *this;
            }
            else
            {
                KOFV kofv;
                size_t index = _phb->HashFunc(kofv(node->data));

                for (size_t i = index + 1; i tables.size(); ++i)
                {
                    if (_phb->tables[i])
                    {
                        node = _phb->tables[i];
                        return *this;
                    }
                }
                node = nullptr;
                return *this;
            }
        }

        bool operator==(const Self& self) const
        {
            return node == self.node
                && _phb == self._phb;
        }

        bool operator!=(const Self& self) const
        {
            return !this->operator==(self);
        }
    };

哈希表内部改造:

template
    class HashBucket
    {
        typedef HashNode Node;
        friend struct HashBucket_Iterator;
    public:
        typedef HashBucket_Iterator iterator;
        size_t getNextPrime(size_t num)
        {
            size_t i = 0;

            for (i = 0; i  num)
                {
                    return primeList[i];
                }
            }
            //如果比所有都大,还是返回最后一个,因为最后一个已经是32位最大容量
            return primeList[PRIMECOUNT - 1];
        }
        //除留余数法
        size_t HashFunc(const K& key)
        {
            Hash hash;
            return hash(key) % tables.size();
        }
        iterator begin()
        {
            for (size_t i = 0; i next;
                    delete cur;
                    cur = next;
                }
            }
        }
    };

封装unordered_map和unordered_set

unordered_set.h头文件

#pragma once
#include"HashTable.h"
using namespace OPEN_HASH;
namespace Simulation
{
template>
class unordered_set
{
    struct SetKeyOfValue
    {
        const K& operator()(const K& key)
        {
            return key;
        }
    };
    // 告诉编译器这只是一个类型
    typedef HashBucket HashBucket;
public:
    // 告诉编译器这只是一个类型
    typedef typename HashBucket::iterator iterator;

    iterator begin()
    {
        return _ht.begin();
    }
    iterator end()
    {
        return _ht.end();
    }

    pair insert(const K& kv)
    {
        return _ht.Insert(kv);
    }
    bool erase(const K& key)
    {
        return _ht.Erase(key);
    }
    iterator find(const K& key)
    {
        return _ht.Find(key);
    }
private:
    HashBucket _ht;
};
void test_unordered_set1()
{
    unordered_set s;

    s.insert(3);
    s.insert(2);
    s.insert(1);
    s.insert(2);
    s.insert(4);

    s.erase(3);
    for (auto e : s)
    {
        cout  s;

    s.insert("sort");
    s.insert("pass");
    s.insert("cet6");
    s.insert("pass");
    s.insert("cet6");

    s.erase("sort");

    for (auto& e : s)
    {
        cout 

unordered_map.h头文件

#pragma once
#include"HashTable.h"
using namespace OPEN_HASH;
namespace Simulation
{
    template>
    class unordered_map
    {
        struct MapKeyOfValue
        {
            const K& operator()(const pair& kv)
            {
                return kv.first;
            }
        };

        typedef HashBucket, MapKeyOfValue, Hash> HashBucket;
    public:
        // 告诉编译器这只是一个类型
        typedef typename HashBucket::iterator iterator;

        iterator begin()
        {
            return _ht.begin();
        }
        iterator end()
        {
            return _ht.end();
        }

        pair insert(const pair& kv)
        {
            return _ht.Insert(kv);
        }
        bool erase(const K& key)
        {
            return _ht.Erase(key);
        }
        iterator find(const K& key)
        {
            return _ht.Find(key);
        }
        V& operator[](const K& key)
        {
            pair ret = insert(make_pair(key, V()));
            return ret.first->second;
        }
    private:
        HashBucket _ht;
    };
    void test_unordered_map1()
    {
        unordered_map um;

        um.insert(make_pair(1, 1));
        um.insert(make_pair(3, 3));
        um.insert(make_pair(2, 2));
        um.insert(make_pair(4, 4));

        for (auto& e : um)
        {
            cout ::iterator it = um.begin();
        ++it;
        cout first  um;

        um.insert(make_pair("string", "字符串"));
        um.insert(make_pair("sort", "排序"));
        um.insert(make_pair("pass", "通过"));
        um.insert(make_pair("program", "程序"));

        for (auto& e : um)
        {
            cout ::iterator it = um.begin();
        ++it;
        cout first  countMap;

        string strArr[] = { "香蕉","香蕉" ,"水蜜桃","西瓜","苹果","西瓜","香蕉" ,"苹果","西瓜","苹果","苹果","香蕉" ,"水蜜桃" };

        for (auto& e : strArr)
        {
            countMap[e]++;
        }

        countMap["芒果"] = 10;

        for (auto& e : countMap)
        {
            cout 

typedef详解

搞懂了c++创始人写的中的下面这个例子, 有助于你理解typdef:

typedef int P();
typedef int Q();
class X {
    static P(Q); // 等价于static int Q(), Q在此作用域中不再是一个类型
    static Q(P); // 等价于static int Q(int ()), 定义了一个名为Q的function
};

隐藏技能:typedef 定义的新类型, 使用时可以省略括号

typedef int NUM;
NUM a = 10; // 也可写成NUM(a) = 10;
NUM(b) = 12; // 也可写成NUM b = 12;

官方定义
初次接触此类typedef用法的程序员直观上理解这个例子比较困难, 我们来看一下typedef的官方定义:

Typedef does not work like typedef [type] [new name]. The [new name] part does not always come at the end.

You should look at it this way: if [some declaration] declares a variable, typedef [same declaration] would define a type

看我标黑的这句话, 总结一下就是: 任何声明变量的语句前面加上typedef之后,原来是变量的都变成一种类型不管这个声明中的标识符号出现在中间还是最后

举个例子:

初级:

typedef int x; // 定义了一个名为x的int类型
typedef struct { char c; } s; // 定义名为s的struct类型
typedef int *p; //定义了一个名为p的指针类型, 它指向int (中文描述指针好累)

高级:(注意标识符不一定在最后)

typedef int A[];  // 定义一个名为A的int数组的类型
typedef int f(); // 定义一个名为f, 参数为空, 返回值为int的函数类型
typedef int g(int); // 定义一个名为g, 含一个int参数, 返回值为int的函数类型

这时候我们回头看一开始的那个例子:

typedef int P();
static P(Q); 

这样就比较好理解了吧,typedef定义了一个名叫P,参数为空,返回值是int的函数类型,根据我上面介绍的隐藏技能,P(Q)就等价于P Q,声明Q是一个返回值为int

这玩意有什么用呢?
我们都知道C++语言里, 函数都是先声明后使用的(除非在使用之前定义), 看以下例子:

#include 
#include 
#include 

typedef int P(); // 简单的
typedef void Q(int *p, const std::string& s1, const std::string& s2, size_t size, bool is_true); // 复杂的
class X {
public:
    P(eat_shit); // 等价于声明int eat_shit();
    Q(bullshit); // 等价于声明void bullshit(int *p, const string& s1, const string& s2, size_t size, bool is_true);
};

int main() {
    X *xx;
    printf("shit ret: %dn", xx->eat_shit());
    int a[] = {1, 3, 4, 5, 7};
    xx->bullshit(a, "foo", "bar", sizeof(a)/sizeof(int), true);
}

int X::eat_shit() {
    return 888;
}

void X::bullshit(int *p, const std::string& s1, const std::string& s2, size_t size, bool is_true) {
    std::cout 

总结:

  • type (var)(...); // 变量名var与结合,被圆括号括起来,右边是参数列表。表明这是函数指针
  • type (var)[]; //变量名var与结合,被圆括号括起来,右边是[]运算符。表示这是数组指针
  • type (*var[])...; // 变量名var先与[]结合,说明这是一个数组(至于数组包含的是什么,由旁边的修饰决定)

typename详解

"typename"是一个C++程序设计语言中的关键字。当用于泛型编程时是另一术语"class"的同义词。这个关键字用于指出模板声明(或定义)中的非独立名称(dependent names)是类型名,而非变量名

typename 的作用就是告诉 c++ 编译器,typename 后面的字符串为一个类型名称,而不是成员函数或者成员变量,这个时候如果前面没有 typename,编译器没有任何办法知道 T::LengthType 是一个类型还是一个成员名称(静态数据成员或者静态函数),所以编译不能够通过。
举个例子:假设你现在要针对某一种容器设定一个操作函数

template 
void func (){
    T::iteartor * testpt;
}

看到这段代码的时候我们大多数情况下都是可以看出来,这一段代码中的操作是定义了一个容器的迭代器指针类型的变量。但是模版是在编译期间展开的,只有在模版实例化的时候编译器才可以推导出其类型。这段代码对于编译器来说很有可能产生错误的理解,因为我们能快速的根据iteartor是一个迭代器想到这是定义了一个变量,但是对于编译器来说,它怎么会知道一定知道T::iteartor一定是一个迭代器类型,或者一定知道这是一个类型?因为能表示成这样形式的代码有三种情况:

  • 在T作用域中存在一个iteartor的静态变量
  • 在T作用域中存在一个iteartor的静态成员函数
  • 是T类型的成员变量

以上三种含义均可以表示成例子中的样子,编译器怎么知道这是哪一种。在实践过程中,编译器会直接对testpt报错,而且在模板实例化之前,完全没有办法来区分它们,这绝对是滋生各种bug的温床。这时C++标准委员会再也忍不住了,与其到实例化时才能知道到底选择哪种方式来解释以上代码,委员会决定引入一个新的关键字,这就是typename

typename真正的用途

编译期间模版的推导有一个这样的规则:如果解析器在template推导期间遇到了嵌套从属名称,那么不指定他为一个类型,解析器就一定不会把它当成一个类型。

什么是嵌套从属类型?

事实上类型T::const_iterator依赖于模板参数T, 模板中依赖于模板参数的名称称为从属名称(dependent name), 当一个从属名称嵌套在一个类里面时,称为嵌套从属名称(nested dependent name)。 其实T::const_iterator还是一个嵌套从属类型名称(nested dependent type name)。嵌套从属名称是需要用typename声明的,其他的名称是不可以用typename声明的。

总结:嵌套从属名称是需要用typename声明的,其他的名称是不可以用typename声明的

T::iteartor这种,这也就是为什么编译器会对testpt报错的原因。那要怎样指定testpt为一个类型,这就回到了开头的那个问题,我们可以这样解决

template 
void func (){
    typename T::iteartor * testpt;
}

加上了typename之后我们就可以知道T::iteartor是一个类型,编译器也可以根据这个进行类型推导了

文章来源于互联网:C++进阶(unordered_set+unordered_map模拟实现)

THE END
分享
二维码