Sirius' blog Sirius' blog
首页
  • 学习笔记

    • 《C++》
    • 《MATLAB》
    • 《Python》
  • 学习笔记

    • 《Git》
    • 《CMake》
  • 技术文档
  • 博客搭建
  • 学习
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Sirius0v0

怕什么真理无穷,进一寸有一寸的欢喜
首页
  • 学习笔记

    • 《C++》
    • 《MATLAB》
    • 《Python》
  • 学习笔记

    • 《Git》
    • 《CMake》
  • 技术文档
  • 博客搭建
  • 学习
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础

  • Cpp小记
  • C++学习笔记-220705-V1
  • 进阶

    • CC++模板
      • 模板基础概念
      • 基础进阶:非类型模板参数推导
      • 函数模板
        • 普通函数与函数模板
        • 具体化模板(特化)
      • 类模板
        • 类模板对象做函数参数
        • 类模板与继承
        • 类模板成员函数类外实现
        • 类模板分文件
        • 类模板与友元
    • CC++STL-常用容器
    • CC++STL-函数对象
    • CC++STL-常用算法
    • 现代C++:常量、变量与类型推导
    • 现代C++:函数式编程(lambda、函数对象包装器)
    • 现代C++:右值引用、移动语义与完美转发
    • 现代C++:智能指针
    • C++11多线程编程
  • 奇思妙用

  • 库的使用

  • 《C++》学习笔记
  • 进阶
Sirius0v0
2023-07-09
目录

CC++模板

# C/C++:模板

# 模板基础概念

C++提供两种模板机制:

  • 函数模板
  • 类模板

语法:

template<typename T>
函数/类声明或定义

typename可用class代替

T 通用数据类型,名称可替换,通常为大写字母

两种方式使用模板:

  • 自动类型推导
  • 显示指定类型

# 基础进阶:非类型模板参数推导

模板参数 typename T 中的T不仅仅可以是某种类型,还有一种常见模板参数形式可以让不同字面量成为模板参数,即非类型模板参数。

模板参数只支持整数类型(包括enum),浮点类型、指针类型、自定义类型等均不能声明为模板参数。

使用非类型模板参数与使用函数参数有何区别?前者是编译期常量,因而可以在编译期对代码进行整体优化。例如对函数进行输出调试:

使用非类型模板参数:

template <bool debug>
int add(int a, int b) {
    // C++17之后:
    // 为确保编译期确定分支,可以使用 if constexpr
    if (debug) {
        std::cout << a << '\t' << b << '\n';
    }
    return a + b;
}

int main() {
    // std::cout << add<1>(3, 5) << '\n';
    std::cout << add<0>(3, 5) << '\n';
    return EXIT_SUCCESS;
}

对于开启和关闭调试的汇编结果如下两图,左图为开启调试模式,右图为关闭调试模式,可见当使用非类型模板参数时,编译期会优化掉输出的代码:


使用函数参数:

int add(int a, int b, bool debug)
{
    if (debug) {
        std::cout << a << '\t' << b << '\n';
    }
    return a + b;
}

int main() {
    std::cout << add(3, 5, 0) << '\n';
    return EXIT_SUCCESS;
}

关闭调试的汇编结果如下图,可见当使用函数参数时,即便关闭调试选项,结果中依然会存在多余的输出代码,综上,合理使用非类型模板参数可以有效提高程序执行效率。

# 函数模板

# 普通函数与函数模板

两者的区别:

  • 普通函数调用可发生隐式类型转换
  • 函数模板
    • 用自动类型推导,不发生隐式类型转换
    • 用显示指定类型,发生隐式类型转换

调用规则:

void myPrint(int a)
{cout << a <<endl;}

template<class T>
void myPrint(T a)
{cout << a <<endl;}
  • 优先调用普通函数

    // 调用
    myPrint(5);
    
  • 可通过空模板参数列表强制调用函数模板

    // 强制调用模板
    myPrint<>(5);
    
  • 若函数模板可产生更好地匹配,优先调用函数模板

    char c = 'a';
    myPrint(c);
    
  • 函数模板也可以发生重载

# 具体化模板(特化)

  • 自定义类型传入时,需要利用具体化模板
class Person
{
    Person(int a, string n):m_Age(a),m_Name(n){};
    int m_Age;
    string m_Name;
}

template<class T>
bool myCompare(T &a, T &b)
{
    if(a==b) return true;
    else return false;
}

// 具体化模板
template<> bool myCompare(Person &p1, Person &p2)
{
    if (p1.m_Name==p2.m_Name && p1.m_Age==p2.m_Age)
        return true;
    else
        return false;
}

# 类模板

template<class NameType, class AgeType>
class Person
{
public:
    Person(NameType name, AgeType age):m_Name(name),m_Age(age){}
    
    NameType m_Name;
    AgeType m_Age;
}

类模板与函数模板的区别主要为:

  1. 类模板没有自动类型推导
  2. 类模板在模板参数列表中可以有默认参数
template<class NameType, class AgeType = int>
// class defination

# 类模板对象做函数参数

三种类模板对象做函数参数的方法:

  • ※指定传入类型
  • 参数模板化
  • 整个类模板化

已知一个类模板如下:

template <typename K, typename W>
class MyDict
{
public:
    MyDict() : m_key("null"), m_word(0) {}
    MyDict(K key, W word) : m_key(key), m_word(word) {}
    ~MyDict() {}
private:
    K m_key;
    W m_word;
public:
    void print()
    {
        std::cout << '{' << this->m_key << " , " << this->m_word << '}' << std::endl;
    }
};

# 指定传入类型

// 直接指定传入类型
void PrintMyDict1(MyDict<int, int> &md)
{md.print();}

# 参数模板化

template<typename T1, typename T2>
void PrintMyDict2(MyDict<T1, T2> &md)
{
    md.print();
}

可以通过typeid(T1).name()查看类型

由于 GCC 编译器的优化,无法通过上述表达式查看类型,可以加上#include <cxxabi.h> 这个头文件,并使用abi::__cxa_demangle(typeid(int).name(),0,0,0 )

#include <cxxabi.h>

template<typename T>
void view_typeid_name()
{
    std::cout << abi::__cxa_demangle(typeid(T).name(),0,0,0) << std::endl;
}

# 整个类模板化

template<typename T1>
void PrintMyDict3(T1 &md)
{
    md.print();
    view_typeid_name<T1>();
}

# 类模板与继承

当类模板被继承时,需要注意:

  • 子类在声明的时候,要指定出父类中T的类型,可将子类也变为类模板
template<typename T>
class Base
{
    T m;
};

class Son:public Base<int>
{};

template<typename T1, typename T2>
class Son2:public Base<T1>
{
    T2 obj;
};

# 类模板成员函数类外实现

// 构造函数类外实现
template<typename T1, typename T2>
Person<T1,T2>::Person(T1 name, T2 age)
{ /* do something */ }

// 成员函数类外实现
template<typename T1, typename T2>
void Person<T1, T2>::showPerson()
{ /* do something */ }

# 类模板分文件

由于类模板中的成员函数创建时机是在调用阶段,导致分文件编写时链接不到。

解决方案:

  • 直接包含.cpp源文件
  • 将声明和实现写到同一个文件中,并更改后缀名为.hpp

# 类模板与友元

配合友元函数的类内和类外实现

  • 类内实现 - 直接在类内声明友元即可
  • 类外实现 - 提前让编译器知道全局函数的存在
// 告诉下方的函数模板printDict_out中的MyDict是一个模板类
template <typename K, typename W>
class MyDict;

// 让编译器提前知道该MyDict的友元模板函数的存在
template <typename K, typename W>
void printDict_out(MyDict<K, W> md)
{
    std::cout << '{' << md.m_key << " , " << md.m_word << '}' << std::endl;
}

template <typename K, typename W>
class MyDict
{
    // 友元全局函数 类内实现
    friend void printDict_in(MyDict<K, W> md)
    {
        std::cout << '{' << md.m_key << " , " << md.m_word << '}' << std::endl;
    }

    // 友元全局函数 类外实现
    // 加空模板参数列表,表示是函数模板的函数声明
    friend void printDict_out<>(MyDict<K, W> md);

public:
    MyDict() : m_key("null"), m_word(0) {}
    MyDict(K key, W word) : m_key(key), m_word(word) {}
    ~MyDict() {}

private:
    K m_key;
    W m_word;
};
编辑 (opens new window)
#Cpp
上次更新: 2023/08/16, 16:01:49
C++学习笔记-220705-V1
CC++STL-常用容器

← C++学习笔记-220705-V1 CC++STL-常用容器→

最近更新
01
ipopt优化库配置及使用
07-21
02
ubuntu离线安装包的方法
07-21
03
其它控件的使用
03-05
更多文章>
Theme by Vdoing | Copyright © 2020-2024 Sirius0v0 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式