初面网初面网

C++高级知识点

模板

引子: 数学里,就加法而言,有整数加法,有小数加法。如果用程序表达,就需要定义好类型,再去进行操作。但实际上的步骤是:

  1. 加法操作 这种概念
  2. 具体到某个问题,才考虑是整数加法还是小数加法

所以,为了能够将 数据类型的定义 延后到调用的时候,可以先给定一个可以替代任何类型的标志。这种"先利用标志替代数据类型"的思想,就是泛型思想,利用这种思想的编程,就是泛型编程。在C++里,这种技术叫做 模板

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

/*
template         声明创建模板
typename         表明后面的符号是一种数据类型,可用class代替
T                数据通用类型,通常为大写字母,可用其他字母替换


类模板没有自动类型推导
类模板在模板参数列表中可以有默认参数
*/

template<typename T>
void addNum(T a, T b){
    return a + b;
}

int main()
{
    //1、自动类型推导
    //addNum(1, 2);

    //2、显示指定类型
    addNum<float>(1.0f, 2.0f);

    // 省略system和return
}

注意:

1、必须推导出一致的数据类型T才可以使用

addNum(1, 2.0f);

这种就无法推导出一致的数据类型,所以运行时会报错

2、必须确定出T的数据类型才可以使用模板


普通函数与函数模板的区别

  • 普通函数调用时可以发生自动类型推导(隐式类型转换)
  • 函数模板调用时,在自动类型推导则不会发生隐式类型转换
  • 利用显示指定类型的方式,可以发生隐式类型转换

调用规则

  1. 如果,函数模板和普通函数都可以实现,优先普通函数
  2. 如果,函数模板可以产生更好的匹配,优先函数模板
  3. 可以通过 空模板参数列表 来强制调用函数模板
  4. 函数模板也可以发生重载
  5. 类模板中成员函数在调用时才去创建,因为在运行前,无法确定类型

类模板对象做函数参数

  1. 指定传入的类型(常用)
  2. 参数模板化
  3. 整个类模板化
template<class T1,class T2>
class Person {
public:
    Person(T1 name, T2 age) {
        this->m_name = name;
        this->m_age = age;
    }

    void showPerson() {
        cout << this->m_name << this->m_age << endl;
    }

    T1 m_name;
    T2 m_age;
};

void printPerson1(Person<string, int>& p) {
    p.showPerson();
}

void test01(){
    Person<string, int> p("张三", 25);
    printPerson1(p);
}

template<class T1, class T2>
void printPerson2(Person<T1, T2>& p) {
    p.showPerson();

    cout << typeid(T1).name();      // 查看类型
}

void test02() {
    Person<string, int> p("李四", 33);
    printPerson2(p);
}

template<class T>
void printPerson3(T& p) {
    p.showPerson();
}

void test03() {
    Person<string, int> p("王五", 3);
    printPerson3(p);
}

类外实现模板类的函数

template<typename T>
class Person {
public:
    void setA(T);
    T m_a;
};

template<class T>
void Person<T>::setA(T a) {
    m_a = a;

    cout << m_a << endl;
}

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

  1. 直接包含.cpp源文件 解决
  2. 将声明和实现写到同一个文件中,并更改后缀名为.hpp(头文件重命名,.h后面加pp。这是个约定的名称,非强制)

类模板与友元

  • 全局函数类内实现:直接在类内声明友元即可
  • 全局函数类外实现:需要提前让编译器知道全局函数的存在
template<class T1, class T2>
class Person;

template<class T1, class T2>
void printPerson2(Person<T1, T2> p) {
    cout << p.m_name;
}

// 以上代码块用于声明类和实现全局函数的类外实现

template<class T1, class T2>
class Person {

    friend void printPerson1(Person<T1, T2> p) {
        cout << p.m_name << p.m_age << endl;
    }
    friend void printPerson2<>(Person<T1, T2> p);      // 注意这里函数名后的尖括号,它用于保证正常识别类型
public:
    Person(T1 name, T2 age) {
        this->m_name = name;
        this->m_age = age;
    }
private:
    T1 m_name;
    T2 m_age;
};

void test01() {
    Person<string, int>p("Tom", 20);

    printPerson2(p);
}

STL(Standard Template Library, 标准模板库)

STL, 广义上分为:容器(Container) 算法(Algorithm) 迭代器(Iterator)

容器和算法之间,通过迭代器进行无缝连接

STL几乎所有代码都采用了模板类或者模板函数

组件说明
容器各种数据结构,如vector、list、deque、set、map等, 用于存放数据
算法各种常用算法,如sort、find、copy、for_each等
迭代器扮演了容器与算法之间的胶合剂
仿函数行为类似函数,可作为算法的某种策略
适配器用来修饰容器或者仿函数或者迭代器接口的东西
空间配置器负责空间的配置与管理

·容器

可容纳数组、链表、树、栈、队列、集合、映射表等。有两类:

序列式容器:强调值的顺序,元素位置固定

关联式容器:二叉树结构,元素在物理上没有严格的顺序关系

·算法

计算的方法

·迭代器

先简单理解为指针


Vector容器

容器:vector

算法:for_each

迭代器:vector<int>::iterator

// 加上两个头文件
#include <vector>
#include <algorithm>

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

void test01() {
    vector<int> v;

    // 插入数据(尾插)
    v.push_back(10);

    // 迭代器访问容器中的数据
    vector<int>::iterator itBegin = v.begin();
    vector<int>::iterator itEnd = v.end();      // v.end()指向最后一个元素的后一位地址

    // 第一种遍历方式
    /*while (itBegin != itEnd) {
        cout << *itBegin << endl;
        itBegin++;
    }*/

    // 第二种遍历方式
    /*for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
        cout << *it << endl;
    }*/

    // 第三种遍历方式 利用STL提供的遍历方法  (推荐!!)
    for_each(v.begin(), v.end(), myPrint);
}

更新于 2025/2/26