STL学习小记--与C++模板相关的几个特性

简介: 先谈谈模板在我脑子里的典型吧 template const T& GetMax(const T& t1, const T& t2) { return t1>t2?t1:t2; } 如上面的代码,这是一个模板函数(template function)。

先谈谈模板在我脑子里的典型吧

template<class T>
const T& GetMax(const T& t1, const T& t2)
{
    return t1>t2?t1:t2;
}

如上面的代码,这是一个模板函数(template function)。要使用这个函数,传入的参数必须满足>运算符的条件。在C#中有where关键字,很可惜C++的模板没有这种限制。

而据说BS本人也一再声明C++的模板不需要这种限制,作为一个初学者,其中缘由就不得而知了。

template并非一次编译便生成适合所有类型的代码,而是针对被使用的某个(组)类型进行编译。这导致一个很严肃的问题:实际处理template的时候,面对template function,必须先提供一个实际的例子,然后才能调用,这样才能通过编译。所以使用template function的话,需要将整个函数定义在头文件中,在别处使用也需要提供源码。C++标准中似乎有对于使template运行可移植性的方法(编译成dll),但是貌似我使用的编译器都不支持。

模板和宏在功能上相似,这类的问题经常引起大神们的口水战,吾辈且避之。

下面登记下模板中使用的几个特性:

1, Nontyoe Template Parameters(非类型模板参数)

第一次遇到这个是在学习STL的bitset的时候。后来发现很多书在引入这内容的时候都是拿bitset举例。

bitset<32> bs32;

作为初学者的我,刚见到的时候非常疑惑,原来模板还有这种用法,可是这种方式来初始化构造的话,为什么不直接用构造函数呢?就像是建一个32长度的数组一样的方式啊。

可是,32长度的数组不是一个类型,同样,用构造函数创建的32长度容器也不是某一类型,而是某一类型的实例。而bitset<32>则是一个类型,档次果然就不一样了呀。

即是说,如果继续声明一个bitset<32> bs32X;那么bs32X和bs32的类型是一样的,用RTTI来鉴别,typeid就是一样的。这样bs32和bs32X是允许相互赋值的。

而如果声明一个bitset<33> bs33; 那就不是同一类型了。

嗯,模板类是在类级别,而一个是在实例级别,档次呀档次。。。

2,Default Template Parameters(缺省模板参数)

C++允许函数有缺省参数, 很方便地, 也允许模板参数有缺省值

比如下例:

template <class T, class NT= int>
class P
{
public:
    T m_t;
    NT m_nt;
};

这样在使用的时候,即使只是定义

void f()
{
    P<int> p;   
    //...
}

这样也会被默认为P<int, int>的类型。很方便地特性,和函数缺省参数一样,能减少不少代码量。

3, typename

定义一个模板类或是模板函数的时候,需要制定类型的名字,使用T似乎已经是一种潜规则了。

而T前面可以使用typename,也可以使用class来表示后面的标识符是一个类型名。

而关键字typename还可以被用来作为类型前的标识符号。

比如:

template <class T>
class P
{
    typename T::MyType* pT;
};

如果不使用typename关键字,按照C++的一般规则,除了typename修饰之外的任何template中的标识符都被视为一个值而非一个类型,所以编译器就会认为MyType是T中的一个成员,这一行被解释为MyType和pT相乘。调用语句写在类定义中编译器就要报错了。

使用上面的类模板,传入的模板参数必须满足类型中定义了MyType类型,可以使用typedef,可以直接定义class。

4, Member Template(成员模板函数)

全局函数可以是一个template, 同样C++也允许类成员函数是一个template。但是这样的成员函数不能使虚函数,而且也不能有缺省参数。(话外音:那不就是一个带this指针参数的全局函数而已吗?)

比如:

class A
{
public:
    template<class T>
    void f(T t)
    {
           //...
    }
};

然后使用的时候只需要实例化一个A类型的变量就能对各种适用的类型使用f函数了。f函数的具体实现不影响A类型,相当方便。

另外需要提到的是,这个特性常常会被用来作为模板类之间类型转换。

如同在第一个特性中提到的,根据模板参数的不同,类型也会不同,比如MyClass<int>和MyClass<double>属于两个不同的类型,那么两者之间是不能直接转换的,即使int可以转换为double。为了满足这种懒惰成性的需求,就可以使用这个特性。

如下代码:

template <class T>
class A
{
public:
    T m_value;
    
    A():m_value(){}
    A(const T& t){m_value = t;}
    template<class T2>
    void Parse(const A<T2>& t)
    {
        this->m_value = t.m_value;
    }
};

int main()
{
    A<int> a1(102);
    A<double> a2(12.222);
    A<char> a3('a');
    a1.Parse(a2);
    cout<<a1.m_value<<endl;//输出12
    a2.Parse(a3);//字符'a'将转化为ASCII码
    cout<<a2.m_value<<endl;//输出97
    system("pause");
}

其实这里既然是int转double,那么也可以做成直接用a1来初始化a2。也就是在a2的构造函数中使用a1作为参数。(话外: 构造函数也是成员函数,满足非虚函数亦没有缺省参数的条件)

template <class T>
class A
{
public:
    T m_value;
    
    A():m_value(){}
    A(const T& t){m_value = t;}
    template<class T2>
    A(const A<T2>& t){m_value = t.m_value;}
};

int main()
{
    A<int> a1(97);
    A<int> a2(a1);
    A<char> a3(a1);
    cout<<a1.m_value<<endl;//97
    cout<<a2.m_value<<endl;//97
    cout<<a3.m_value<<endl;//a
    system("pause");
}

这里a2构造的时候调用的是默认的拷贝构造函数,而a3构造的时候使用的是模板拷贝构造函数。

5, Nested Template(嵌套模板类)

 类中定义的函数可以是模板函数,类中定义的嵌套类也可以是个模板类。

template <class T>
class MyClass
{
    //...
public:
    template <class T2>
    class NestedClass
    {
        //...
    };
};

另外的注意点

面试题是个很有意思的东西,往往能遇到许多稀奇古怪的状况。以前曾在面试中遇到这样的问题:

int* pI = new int;

int* pI2 = new int();

上面两行代码有什么区别。

我大学汇编是玩NDS上课的,所以无法从那个方向去分析。只是根据实际使用来猜想, 前者分配了空间却没有初始化值,后者分配了空间并初始化值。

这和下面的情况相似(貌似使用自定义类型的时候不一定了):

int i;        //undefined value

int i2 = int();  //initialized with zero

这是基本类型int,那么自定义类型呢?

自定义的class有时候不会显式地定义一个构造函数。

struct XX
{
    //XX():n(){}
    int n;
};

int main()
{
    XX* pX1 = new XX;
    XX* pX2 = new XX();
    cout<<pX1->n<<", "<<pX2->n<<endl;
}

输出的结果是一个未初始化的值和0

那如果自定义的类型已经有一个无参构造函数了呢?那就会调用这个无参构造函数来初始化了。

所以,以后一定要养成定义一个无参构造函数并初始化成员的好习惯啊%>_<%

那么,使用模板的时候呢,使用这个语法可以保证该类型实例能被初始化为一个确切的初值。

摘自:ISO/IEC 14882:2003(E) 5.3.4 - 15
— If the new-initializer is omitted:
      — If T is a (possibly cv-qualified) non-POD class type (or array thereof), the object is default-initialized(8.5). If T is a const-qualified type, the underlying class type shall have a user-declared default constructor.
      — Otherwise, the object created has indeterminate value. If T is a const-qualified type, or a (possibly cv-qualified) POD class type (or array thereof) containing (directly or indirectly) a member of const-qualified type, the program is ill-formed;
— If the new-initializer is of the form (), the item is value-initialized (8.5);

 

 

 

目录
相关文章
|
1月前
|
缓存 算法 程序员
C++STL底层原理:探秘标准模板库的内部机制
🌟蒋星熠Jaxonic带你深入STL底层:从容器内存管理到红黑树、哈希表,剖析迭代器、算法与分配器核心机制,揭秘C++标准库的高效设计哲学与性能优化实践。
C++STL底层原理:探秘标准模板库的内部机制
|
8月前
|
编译器 C++ 容器
【c++丨STL】基于红黑树模拟实现set和map(附源码)
本文基于红黑树的实现,模拟了STL中的`set`和`map`容器。通过封装同一棵红黑树并进行适配修改,实现了两种容器的功能。主要步骤包括:1) 修改红黑树节点结构以支持不同数据类型;2) 使用仿函数适配键值比较逻辑;3) 实现双向迭代器支持遍历操作;4) 封装`insert`、`find`等接口,并为`map`实现`operator[]`。最终,通过测试代码验证了功能的正确性。此实现减少了代码冗余,展示了模板与仿函数的强大灵活性。
241 2
|
8月前
|
存储 算法 C++
【c++丨STL】map/multimap的使用
本文详细介绍了STL关联式容器中的`map`和`multimap`的使用方法。`map`基于红黑树实现,内部元素按键自动升序排列,存储键值对,支持通过键访问或修改值;而`multimap`允许存在重复键。文章从构造函数、迭代器、容量接口、元素访问接口、增删操作到其他操作接口全面解析了`map`的功能,并通过实例演示了如何用`map`统计字符串数组中各元素的出现次数。最后对比了`map`与`set`的区别,强调了`map`在处理键值关系时的优势。
465 73
|
5月前
|
存储 算法 安全
c++模板进阶操作——非类型模板参数、模板的特化以及模板的分离编译
在 C++ 中,仿函数(Functor)是指重载了函数调用运算符()的对象。仿函数可以像普通函数一样被调用,但它们实际上是对象,可以携带状态并具有更多功能。与普通函数相比,仿函数具有更强的灵活性和可扩展性。仿函数通常通过定义一个包含operator()的类来实现。public:// 重载函数调用运算符Add add;// 创建 Add 类的对象// 使用仿函数return 0;
206 0
|
5月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
158 0
|
7月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
293 12
|
9月前
|
存储 缓存 C++
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
C++ 标准模板库(STL)提供了一组功能强大的容器类,用于存储和操作数据集合。不同的容器具有独特的特性和应用场景,因此选择合适的容器对于程序的性能和代码的可读性至关重要。对于刚接触 C++ 的开发者来说,了解这些容器的基础知识以及它们的特点是迈向高效编程的重要一步。本文将详细介绍 C++ 常用的容器,包括序列容器(`std::vector`、`std::array`、`std::list`、`std::deque`)、关联容器(`std::set`、`std::map`)和无序容器(`std::unordered_set`、`std::unordered_map`),全面解析它们的特点、用法
C++ 容器全面剖析:掌握 STL 的奥秘,从入门到高效编程
|
8月前
|
编译器 C++
模板(C++)
本内容主要讲解了C++中的函数模板与类模板。函数模板是一个与类型无关的函数家族,使用时根据实参类型生成特定版本,其定义可用`typename`或`class`作为关键字。函数模板实例化分为隐式和显式,前者由编译器推导类型,后者手动指定类型。同时,非模板函数优先于同名模板函数调用,且模板函数不支持自动类型转换。类模板则通过在类名后加`&lt;&gt;`指定类型实例化,生成具体类。最后,语录鼓励大家继续努力,技术不断进步!
|
8月前
|
存储 算法 C++
【c++丨STL】set/multiset的使用
本文深入解析了STL中的`set`和`multiset`容器,二者均为关联式容器,底层基于红黑树实现。`set`支持唯一性元素存储并自动排序,适用于高效查找场景;`multiset`允许重复元素。两者均具备O(logN)的插入、删除与查找复杂度。文章详细介绍了构造函数、迭代器、容量接口、增删操作(如`insert`、`erase`)、查找统计(如`find`、`count`)及`multiset`特有的区间操作(如`lower_bound`、`upper_bound`、`equal_range`)。最后预告了`map`容器的学习,其作为键值对存储的关联式容器,同样基于红黑树,具有高效操作特性。
369 3
|
9月前
|
存储 算法 C++
【c++丨STL】priority_queue(优先级队列)的使用与模拟实现
本文介绍了STL中的容器适配器`priority_queue`(优先级队列)。`priority_queue`根据严格的弱排序标准设计,确保其第一个元素始终是最大元素。它底层使用堆结构实现,支持大堆和小堆,默认为大堆。常用操作包括构造函数、`empty`、`size`、`top`、`push`、`pop`和`swap`等。我们还模拟实现了`priority_queue`,通过仿函数控制堆的类型,并调用封装容器的接口实现功能。最后,感谢大家的支持与关注。
537 1