IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c++模板学习笔记 -> 正文阅读

[C++知识库]c++模板学习笔记

前言

因为之前看boost源码在模板上遇到了比较大的问题,所以前段时间抽空读了《c++templates》,而且网上缺少成体系的博客,所以在这里和大家分享下。这算是一个系统性的学习笔记,希望帮助到大家。

多态和泛型

谈到模板大家一定听说过泛型编程或者元编程。那么我们一定会思考什么是泛型,它和多态的区别是什么?

  • 首先多态和泛型都目的都是为了实现多型,只是两种实现的方式不一样。
  • 多态的实现我们在c++中是通过基类指针指向子类对象来实现的,通过virtual关键字将函数推断推迟到运行时,在运行时,通过查找虚函数表来调用不同子类的成员函数来实现多型。
  • 泛型(模板)是编译器通过传入的类型参数帮我们生成对应类型的代码,以达到模板类对不同类型有不同表现的目的。模板根据类型实例化对应类型代码,是编译期以及link时确定的,所以模板成员函数无法用virtual修饰。
  • 模板相对于多态最大的有点就是,模板是编译期确定函数调用,不需要在运行过程中查找虚函数表,运行效率更高。模板缺点是:会增大代码体积,以及模板报错往往发生在链接时,难排查问题。

模板tutorials

步骤是先模板类, 模板函数 。下面命名不规范。

模板类

定义第一个模板类

知识点:

  • 类内定义成员函数
  • 类外定义成员函数

代码

template <typename T>
class MyStack1
{
public:
    // 类内定义
    void Push(const T &element)
    {
        container_.push_back(element);
    };

    T Pop();

private:
    std::vector<T> container_;
};

// 类外定义
template <typename T>
T MyStack1<T>::Pop()
{
    if (container_.size() == 0)
    {
        return T();
    }

    T result = container_.back();
    container_.pop_back();
    return result;
};

使用

在这里插入图片描述

接下来我们把container_ 类型参数化

template <typename T, typename CONT>
class MyStack2
{
public:
    void Push(const T &element)
    {
        container_.push_back(element);
    };

    T Pop()
    {
        if (container_.size() == 0)
        {
            return T();
        }

        T result = container_.back();
        container_.pop_back();
        return result;
    }

private:
    CONT container_;
};

使用

在这里插入图片描述

模板 模板类型参数

有了上面的使用方式,我们会思考能否吧vector< int > 省去。

我们可以使用template template moudule param

  • MyStack3第二个类型参数前加 template是告诉编译器第二个类型参数为模板类
  • typename = std::allocator 等于 typename [类型别名] = std::allocator,如果类型别名用不上,则可以省略不写。
  • template template 模板参数需要和传入模板类的模板参数列表保持严格一致。

代码:

在这里插入图片描述

vector 模板类型参数列表如下:

在这里插入图片描述

使用

在这里插入图片描述

设置默认类型参数

为使用方便我们可以给一个模板参数设置默认类型

代码

template <typename T,
          template <typename Element,
                    typename = std::allocator<Element>>
          typename CONT = std::vector>
class MyStack4
{
public:
    void Push(const T &element)
    {
        container_.push_back(element);
    };

    T Pop()
    {
        if (container_.size() == 0)
        {
            return T();
        }

        T result = container_.back();
        container_.pop_back();
        return result;
    }

private:
    CONT<T> container_;
};

使用

在这里插入图片描述

非类型模板参数?

如果我们想预设一个大小呢?

模板参数列表不仅仅可以传类型,也可以传无类型(non-type)参数如int,枚举(这里最常用的为int 以及enum,double,float不可以,感兴趣可以自行了解下)

修改代码部分如下
在这里插入图片描述
在这里插入图片描述

模板类特化和偏特化

到了这里我们基本上就掌握了类模板的最基本的用法。接下来我们要掌握模板类的特化(包括偏特化)。看这一部分时我们可以把特化版本当作实现多态的子类,把primary模板类当作基类。我们可以通过特化使特化版本生成不同于primary模板类规定的逻辑版本。全特化和偏特化说白了就是本该让编译器生成的代码由程序员完成。

  • 特化T为CLASS1的版本
    在这里插入图片描述
  • 特化T为CLASS1的版本,container_类型为deque的版本
    在这里插入图片描述

使用示例

在这里插入图片描述

运行结果

在这里插入图片描述
根据结果我们还发现了偏特化版本匹配的机制,和重载函数一样优先选择匹配度高的

完整代码

class CLASS1
{
};

template <typename T,
          int SIZE,
          template <typename Element,
                    typename = std::allocator<Element>>
          typename CONT = std::vector>
class MyStack5
{
public:
    MyStack5()
    {
        container_.resize(SIZE);
    };

public:
    void Push(const T &element)
    {
        container_.push_back(element);
    };

    T Pop()
    {
        if (container_.size() == 0)
        {
            return T();
        }

        T result = container_.back();
        container_.pop_back();
        return result;
    }

private:
    CONT<T> container_;
};

// // 偏特化二:当模板参数1为CLASS1
template <int SIZE, template <typename Element,
                              typename = std::allocator<Element>>
                    typename CONT>
class MyStack5<CLASS1, SIZE, CONT>
{
public:
    MyStack5()
    {
        container_.resize(SIZE);
    };

public:
    void Push(const CLASS1 &element)
    {
        std::cout << "class1 Push" << std::endl;
    };

    CLASS1 Pop()
    {
        std::cout << "class1 Pop" << std::endl;
    };

private:
    CONT<CLASS1> container_;
};

// 偏特化二:当模板参数1为CLASS1 ,模板参数二 为 std::deque时走下面逻辑
template <int SIZE>
class MyStack5<CLASS1, SIZE, std::deque>
{
public:
    MyStack5()
    {
        container_.resize(SIZE);
    };

public:
    void Push(const CLASS1 &element)
    {
        std::cout << "class1 Push2" << std::endl;
    };

    CLASS1 Pop()
    {
        std::cout << "class1 Pop2" << std::endl;
    };

private:
    std::deque<CLASS1> container_;
};

模板类继承

最后在这里补充下部分继承相关的问题

模板类 继承 模板类,子类使用父类函数时需要明确该函数是父类的,否则将会在子类的命名空间中查找该函数。模板类继承非模板类则没有上述情况。
完整代码在下面了,建议自行尝试下。

template <typename T>
class Base
{
public:
    void exit()
    {
        std::cout << "base foo" << std::endl;
    }
};

// class Base
// {
// public:
//     void exit()
//     {
//         std::cout << "base foo" << std::endl;
//     }
// };

template <typename T>
class Derive : public Base<T>
{
public:
    void foo1()
    {
        // exit();
        Base<T>::exit();
    }
};

模板函数

如果上面一步步过来,一定可以较为优雅的实现模板类了,下面我们来学习模板函数。
模板函数首要记住的一点就是,模板函数不支持默认类型参数(因为可以用函数重载实现)。

模板函数定义和使用

代码:

template <typename T1, typename T2, typename RT>
RT Myfunc(T1 arg1, T2 arg2){

};

template <typename RT, typename T1, typename T2>
RT Myfunc1(T1 arg1, T2 arg2){

};

使用

在这里插入图片描述

总结

  1. 模板函数的模板参数可以通过传入参数推断;也可以通过手动确定,如(2)手动确认所有类型参数;
  2. RT 类型无法通过传入参数列表推断,所以需要手动确定。
  3. (1)处我们希望int做为RT类型,T1,T2由传入参数推断,但结果编译不通过。原因:因为编译器把int当场了我们为T1手动确认的类型,T2由传入参数2确定为int,这样一来RT就没有被实例化,所以编译报错。
    所以我们可以把RT放在模板类型参数列表的第一个,如(3)这样即可依次确认RT,T1,T2类型,编译通过。

0值问题

有了上面的基础,我们从这里正式开始完善模板函数的学习。

[注]Accumulate 左闭右开求和。

看到下面这段代码,一定会有同学指出 T sum没有赋初值的问题。是的,如果T为拥有默认构造函数的类,编译器会帮助我们调用默认构造函数初始化,但int这种内置类型则无法这样做(为了兼容c标准)。
T sum = 0 这样写明显不正确。所以这时诞生处了零值问题。

未做0值问题处理 代码

template <typename T>
T Accumulate(const T *begin, const T *end)
{
    T sum; //注意这有bug ,当T为int时 sum为垃圾值
    while (begin != end)
    {
        sum += *begin;
        // std::cout << *begin << std::endl;
        ++begin; //++i占用寄存器较少,相比i++更快些
    }

    return sum;
}

0值问题解决

1)改写为 T sum = T() 即可解决。c++中int 会被赋值为0,bool会被赋值为false。

template <typename T>
T Accumulate(const T *begin, const T *end)
{
    T sum = T(); 
    while (begin != end)
    {
        sum += *begin;
        // std::cout << *begin << std::endl;
        ++begin; //++i占用寄存器较少,相比i++更快些
    }

    return sum;
}

2) 上面那种做法需要有默认构造函数,那如果某个类没有默认构造函数呢?

template <typename T>
class AccumulateTrait1
{
};

template <>
class AccumulateTrait1<int>
{
public:
    typedef int ACCT;
    static int zero()
    {
        return 0;
    };
};

template <>
class AccumulateTrait1<float>
{
public:
    typedef double ACCT;
    static double zero()
    {
        return 0.0;
    };
};

template <typename T>
//用typename 告诉编译器AccumulateTrait1<T>::ACCT是类型,而不是静态数据成员或者枚举
typename AccumulateTrait1<T>::ACCT Accumulate1(const T *begin, const T *end)
{
    T sum = AccumulateTrait1<T>::zero();
    while (begin != end)
    {
        sum += *begin;
        // std::cout << *begin << std::endl;
        ++begin; //++i占用寄存器较少,相比i++更快些
    }

    return sum;
};

使用 :

在这里插入图片描述

我们可以借助其他模板类 AccumulateTrait1特化,处理那些没有默认构造函数的类。至此,我们已经完成了一个挺完善的Accumulate1模板函数。

给模板类添加默认类型参数。

上文已经提到模板函数不支持默认参数,但我们可以用模板类+静态函数来为某个模板函数添加默认参数。

如下:我们将吹毛求疵,我们给用户确定让Accumulate返回值类型的权限。

template <typename T,
          typename RT = typename AccumulateTrait1<T>::ACCT>
class ACCMU1
{
public:
    static RT Accumulate1(const T *begin, const T *end)
    {
        RT sum = AccumulateTrait1<T>::zero();
        while (begin != end)
        {
            sum += *begin;
            // std::cout << *begin << std::endl;
            ++begin; //++i占用寄存器较少,相比i++更快些
        }

        return sum;
    };
};

template <typename T>
typename AccumulateTrait1<T>::ACCT Accumulate2(const T *begin, const T *end)
{
    return ACCMU1<T>::Accumulate1(begin, end);
}

template <typename T, typename Trait>
typename Trait::ACCT Accumulate2(const T *begin, const T *end)
{
    return ACCMU1<T, Trait>::Accumulate1(begin, end);
}

使用
在这里插入图片描述

总结,相比上一步而言,这种方法缺点是:没有办法进行模板参数推断了,优点是:可以让客户更灵活的使用。

为了向上一步一样使用,我们只好使用函数再封装一层封装,并且利用函数重载简化使用。

template <typename T>
typename AccumulateTrait1<T>::Acct accumlate1(const T *begin, const T *end)
{
    return Accum<T>::Accumlate1(begin, end);
}
// 可以用用户自定以的type trait
template <typename T, typename Trait>
typename Trait::Acct Accumlate1(const T *begin, const T *end)
{
    return Accum<T, Trait>::Accumlate1(begin, end);
}

策略模式

模板实现策略模式

接下来我们 我们使用模板来实现策略模式,依旧来继续完善我们的Accumlate,让Accumlate可以替换累积策略。

方法一

// 策略1
class SimpleSumPolicy
{
public:
    template <typename T>
    static void Sum(T &total, const T &b)
    {
        total += b;
    };
};

//策略2
class SpecialSumPolicy
{
public:
    template <typename T>
    static void Sum(T &total, const T &b)
    {
        total += 2 * b;
    };
};

template <typename T,
          typename Policy = SimpleSumPolicy,
          typename RT = typename AccumulateTrait1<T>::ACCT>
class ACCMU2
{
public:
    static RT Accumulate3(const T *begin, const T *end)
    {
        RT sum = AccumulateTrait1<T>::zero();
        while (begin != end)
        {
            Policy::Sum(sum, *begin);
            ++begin;
        }

        return sum;
    };
};

方法二

我们可以使用模板类+偏特化实现。该方法优点暂时没想到,所以我个人目前推荐方法一,因为足够简单。希望大家可以指点下,谢谢。

template <typename T1, typename T2>
class SumPolicy2
{
public:
    static void accumulate(T1 &totol, const T2 &value)
    {
        totol += value;
    }
};

template <typename T,
          template <typename, typename> class Policy = SumPolicy2, //这样就强制要求sumpolicy有两个模板参数
          typename Trait = AccumulateTrait<T>>
class Accum2
{
public:
    typedef typename Trait::Acct Acct;
    static Acct Accumlate3(const T *begin, const T *end)
    {
        Acct sum = Trait::Zero();
        while (begin != end)
        {
            Policy<Acct, T>::accumulate(sum, *begin);
            ++begin;
        }
        return sum;
    }
};

模板函数形参类型 根据模板类型参数选择引用传值或者按值传值

传入参数我们能否让它根据不同类型传不同的值,比如:int,bool等按值拷贝,因为内置类型拷贝开销很小,反而寄存器间接寻址开销更大;class 则传引用(const 引用同理)。为了更符合语义,另起一个sum函数做讲解。

为达成目的,我们首先要学会判断是否为class。为了判断是否为class我们需要知道什么是SFINE以及类成员指针
SFINE简单来说,就是当模板函数类型显示确定(手动)时,类型推断失败时,不会生成该版本实例,而不会编译报错。这部分网上文章很多,我也没有完全体会过官网例子,就不误人子弟了。

SFINE官方文档
类成员指针

IsClass类

该类作用判断一个类型是类还是其他。
代码如下

template <typename T>
class IsClass1
{
private:
    typedef char one;
    typedef struct func_template
    {
        char a[2];
    } two;
    template <typename C>
    static one test(int C::*);//传入参数为C的类成员指针

    template <typename C>
    static two test(...);//...标识传入参数为除C的类成员指针以外的其他情况

public:
    enum
    {
        YES = sizeof(IsClass1<T>::test<T>(0)) == sizeof(one)
    };

    enum
    {
        NO = !YES
    };
};

注意点:

  • 枚举是属于类的,不是属于对象的,所以它的初始化不能调用成员函数,所以必须将两个test声明为静态函数。
  • 有人可能会对这IsClass1::test(0)感到疑惑.
    IsClass1::test(0)其实就是调用test,可是明明我们没定义test函数啊?模板函数如果仅声明但没定义,在实例化时依旧会为我们生成默认版本(建议自行声明一个模板,调用下试试)。接下来就是匹配问题了,T用int实例化时,int不属于class的范畴,所以没有类成员指针,static one test(int C:😗)该版本生成失败,所以编译器选择test(…)版本,最终yes = sizeof(two) == sizeof(one)。

下面是完整代码:没有什么新东西看自行看注释

//自定义类型
class TestClass
{
public:
    TestClass() = default;

    TestClass &operator=(const TestClass &other){

    };

    TestClass &operator+(const TestClass &other)
    {
    }
};

//类型trait
template <typename T>
class AccumulateTrait1
{
};

template <>
class AccumulateTrait1<int>
{
public:
    typedef int ACCT;
    static int zero()
    {
        return 0;
    };
};

template <>
class AccumulateTrait1<TestClass>
{
public:
    typedef TestClass ACCT;
    static TestClass zero()
    {
        return TestClass();
    };
};

template <>
class AccumulateTrait1<float>
{
public:
    typedef double ACCT;
    static double zero()
    {
        return 0.0;
    };
};


//两个策略类
class SimpleSumPolicy
{
public:
    template <typename T>
    static void Sum(T &total, const T &b)
    {
        total += b;
    };
};

class SpecialSumPolicy
{
public:
    template <typename T>
    static void Sum(T &total, const T &b)
    {
        total += 2 * b;
    };
};

//用于判断是否为class
template <typename T>
class IsClass1
{
private:
    typedef char one;
    typedef struct func_template
    {
        char a[2];
    } two;
    template <typename C>
    static one test(int C::*);

    template <typename C>
    static two test(...);

public:
    enum
    {
        YES = sizeof(IsClass1<T>::test<T>(0)) == sizeof(one)
    };

    enum
    {
        NO = !YES
    };
};

// 类型选择辅助类 non类型参数为true 选择第一个类型,false选择第二个类型
template <bool BOOL, typename T1, typename T2>
class IfThen1;

template <typename T1, typename T2>
class IfThen1<true, T1, T2>
{
public:
    typedef T1 ResultT;
};

template <typename T1, typename T2>
class IfThen1<false, T1, T2>
{
public:
    typedef T2 ResultT;
};

//萃取提升,利用偏特化根据T提供不同的类型
template <typename T>
class TypeOp1
{
public:
    typedef T Type;
    typedef T &REFT;
    typedef const T CONST;
};

template <typename T>
class TypeOp1<const T>
{
public:
    typedef const T Type;
    typedef const T &REFT;
    typedef const T CONST;
};

template <typename T>
class TypeOp1<T &>
{
public:
    typedef T &Type;
    typedef T &REFT;
    typedef const T CONST;
};
//参数类,根据类型推断对应的传入参数类型
template <typename T>
class Param1
{
public:
    typedef typename IfThen1<IsClass1<T>::YES,
                             typename TypeOp1<T>::REFT,
                             typename TypeOp1<T>::Type>::ResultT Result;
};

//sum函数
template <typename T,
          typename Policy = SimpleSumPolicy,
          typename RT = typename AccumulateTrait1<T>::ACCT>
class ACCMU3
{
public:
    static RT Sum(typename Param1<T>::Result begin, typename Param1<T>::Result end)
    {
        RT sum = begin + end;
        return sum;
    };
};

使用

可以看到当为int时 Sum传值方式为按值传参
在这里插入图片描述

可以看到当为TestClass时 Sum传值方式为按引用传参(当然你也可以选择传const 引用)
在这里插入图片描述

模板高级用法

1)是赋值运算符支持隐式转换。我个人不推荐,因为我觉得明确的参数传递更加严谨,我们不应该为使用简单而增加犯错的风险,下面是代码,感兴趣的可以研究下。

template <typename T>
class MyBaseContainer
{
public:
    //参数列表初始化signature_ 避免int不初始化的问题
    MyBaseContainer() : signature_()
    {
    }

protected:
    void start()
    {
        std::cout << "MyBaseContainer start" << std::endl;
    };

private:
    T signature_;
};

// 模板组织方式选择置入式(即只有头文件,缺点是会导致包含过多文件,导致编译速率的降低)
// class CONT 强调CONT为模板类
template <typename T, int SIZE,
          template <typename ELemt, typename = std::allocator<ELemt>> // template template parameter 需要严格匹配vector的参数列表
          class CONT = std::vector>
class MyContainer : public MyBaseContainer<T>
{
public:
    void operator=(const MyContainer &ops)
    {
        std::cout << "operation = default" << std::endl;
    }
    // void operator=(const MyContainer &ops) = delete;会报错说明 下面的=无法替代默认=运算符
    template <typename T2, int SIZE2,
              template <typename ELemt2, typename = std::allocator<ELemt2>> // template template parameter 需要严格匹配vector的参数列表
              class CONT2 = std::vector>
    MyContainer<T, SIZE, CONT> &operator=(const MyContainer<T2, SIZE2, CONT2> &ops);

public:
    void Start()
    {
        MyBaseContainer<T>::start(); //模板继承体系必须加上域解析符,确定start为基类中的,而不是子中的
    }

    inline bool Empty()
    {
        return contaner_.empty();
    }

    inline void Push(const T &element)
    {
        contaner_.push_back(element);
    }

    inline T Pop()
    {
        assert(!contaner_.empty());
        T result = contaner_.front();
        std::cout << "Pop : " << result << std::endl;
        contaner_.pop_back();
        return result;
    }

    void Traverse() const
    {
        // std::cout << contaner_[0] << std::endl;
        // std::cout << contaner_[1] << std::endl;
        for (int i = 0; i < contaner_.size(); ++i)
        {
            std::cout << contaner_[i] << std::endl;
        }
        // for (T a : contaner_)
        // {
        //     std::cout << a << std::endl;
        // }
        // std::cout << std::endl;
    }

private:
    CONT<T> contaner_;
};

// 支持可以类型隐式转化的进行赋值
template <typename T, int SIZE,
          template <typename, typename> // template template parameter 需要严格匹配vector的参数列表
          class CONT>
template <typename T2, int SIZE2,
          template <typename, typename> // template template parameter 需要严格匹配vector的参数列表
          class CONT2>
MyContainer<T, SIZE, CONT> &
MyContainer<T, SIZE, CONT>::operator=(const MyContainer<T2, SIZE2, CONT2> &ops)
{
    std::cout << "tempalet operation =" << std::endl;
    // 两个指针类型不一致 擦除指针类型
    if ((void *)this == (void *)&ops)
    {
        return *this;
    }

    MyContainer<T2, SIZE2, CONT2> tmp(ops);
    contaner_.clear();
    while (!tmp.Empty())
    {
        std::vector<int> a;
        a.push_back(1.0);
        const T2 element = tmp.Pop();
        std::cout << "operator= : element is : " << element << std::endl;
        contaner_.push_back(element);
        // ops.Pop();
    }
    return *this;
}
  1. 模板类中的模板【template的受控名称】。这一部分,在boost.asio中就有用到,可以注意下下。介于本人对编译器处理模板还不是足够了解,就不做过度解读了,但帮大家测试出了正确写法如下。

在一个模板类中定义一个模板

template <typename T>
class Country
{
public:
    template <typename T2>
    class Province
    {
    public:
        static void func1(){};
        template <typename T3>
        static void func2(T3 arg1){};

    private:
        T a;
    };

    template <typename T3>
    static void countryFunc(T3 a){

    };
    typedef int Type;
};

使用

  • 在普通函数中使用
void testMain()
{
    // typename Country<int>::Province<int> a; //报错,编译器可以推断出Province为模板类,会认为重复定义了
    // Country<int>::Province<int> a; //可见编译器可以知道Province是一个模板类

    // Country<int>::Type b; //报错,编译器认为Type是静态成员
    // typename Country<int>::Type b;

    Country<int>::Province<int>::func2(1); //可见编译器可以知道func2是一个模板函数类
    Country<int>::Province<int>::func2<int>(1); //可见编译器可以知道func2是一个模板函数类
    Country<int>::template Province<int>::func2(1);//可见编译器可以知道func2是一个模板函数类
}

  • 在另一个类中使用
template <typename T1, typename T2 = int>
class WorldMap
{
public:
    static void func(T1 a)
    {
        T2 c;
        // typename Country<T1>::Province<T1> a; //会报错,会把Province当做非模板处理,需要我们用template明确告诉编译器是模板类
        typename Country<T1>::template Province<T1> b; // typename 告诉编译器Province<T1>为一个类型

        // Country<T1>::Province<T1>::func1();  //编译报错,编译器知道后面的<>为模板参数列表,需要我们用template明确告诉编译器是模板类
        // Country<T1>::Province<T1>::func2(a); // 编译报错, 编译器知道后面的<>为模板参数列表,需要我们用template明确告诉编译器是模板类

        Country<T1>::template Province<T1>::func1();
        Country<T1>::template Province<T1>::func2(a);
        Country<T1>::template Province<T2>::func2(c);

        // Country<T1>::template Province<T1>::func2<T1>(a);编译报错,编译器不会解析后面的模板参数列表,需要我们用template明确告诉编译器是模板类
        Country<T1>::template Province<T1>::template func2(a);
        Country<T1>::template Province<T1>::template func2<T1>(a);
    };
};

变长类型包

推荐一个简明扼要的博客。
https://www.cnblogs.com/qicosmos/p/4325949.html

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-25 11:31:44  更:2022-05-25 11:32:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/13 0:38:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码