C++学习笔记(七)

2021/12/28 C++

# 一、函数模板

模板就是建立通用模具,大大提高复用性

  • C++有一种编程思想为泛型编程,主要利用的技术就是模板
  • C++提供了两种模版机制:函数模板类模板

# 1.1模板语法

  • 作用:建立一个通用的模板,其函数的返回值和形参类型可以不具体制定,用一个虚拟的类型来代表
  • 语法:template<typename T>

解释:

  • template表示声明创建模板
  • typename表示其后面的符号是一种数据类型,可以用class代替
  • T通用的数据类型,名称可以代替,通常是大写字母
#include <iostream>
using namespace std;

// 交换两个整型
void swapInt(int &a, int &b) {
    int temp = a;
    a = b;
    b = temp;
}
// 交换两个浮点
void swapFloat(float &a, float &b) {
    float temp = a;
    a = b;
    b = temp;
}

// 泛型编程(函数模板)
template <typename T>
void mySwap(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}

int main() {
    int a = 10, b = 20;
    swapInt(a, b);
    cout << "swapInt a = " << a << endl;
    cout << "swapInt b = " << b << endl;
    // 利用函数模板进行交换
    // 两种方式使用函数模板
    // 1、自动类型推导
    mySwap(a, b);
    cout << "mySwap a = " << a << endl;
    cout << "mySwap b = " << b << endl;
    // 2、显示指定类型(此时通过尖括号告诉编译器T在这里指的是int)
    mySwap<int>(a, b);
    cout << "mySwap a = " << a << endl;
    cout << "mySwap b = " << b << endl;
}

# 1.2函数模板注意事项

注意事项:

  • 自动类型推倒,必须推导出一致的数据类型T,才可以使用
  • 模板必须要确定T的数据类型,才可以使用

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

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

# 1.4普通函数与函数模板调用规则

  • 如果函数模板和普通函数都可以实现,优先调用普通函数
  • 可以通过空模板参数列表来强制调用函数模板
  • 函数模板也可以发生重载
  • 如果函数模板可以产生更好的匹配,优先调用函数模板

# 1.5模板的局限性

模板并不是万能的,有些特定数据类型,需要具体化方式进行特殊实现

例如:在进行比较大小时,如果传入的数据将无法进行比较操作

#include <iostream>
#include "string"
using namespace std;

class Person {
public:
    string m_Name;
    int m_Age;
    Person(): m_Name("Jack"), m_Age(20) {}
};

// 对比两个数是否相等
template<class T>
bool myCompare(T &a, T &b) {
    if (a == b) {
        return true;
    } else {
        return false;
    }
}

// 利用具体化Person的版本实现代码,具体化优先调用
template<> bool myCompare(Person &p1, Person &p2) {
    if (p1.m_Age == p2.m_Age && p1.m_Name == p2.m_Name) {
        return true;
    } else {
        return false;
    }
}

int main() {
    int a = 100, b = 100;
    bool ret_01 = myCompare(a, b);
    cout << ret_01 << endl;

    Person p1;
    Person p2;
    bool ret_02 = myCompare(p1, p2);
    cout << ret_02 << endl;
    return 0;
}

总结:

  • 利用具体化的模板,可以解决自定义类型的通用化
  • 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板

# 二、类模板

# 2.1类模板语法

类模板的作用是建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表

  • 语法:template<typename T>

解释:template表示声明创建模板,typename表示其后面的符号是一种数据类型,可以用class代替,T表示通用数据类型,名称可以代替,通常使用大写字母

#include <iostream>
#include "string"
using namespace std;

// 类模板
template<class NameType, class AgeType>
class Person {
public:
    NameType m_Name;
    AgeType m_Age;

    Person(NameType name, AgeType age) {
        this->m_Name = name;
        this->m_Age = age;
    }
};

int main() {
    Person<string, int> person("Jack", 18);
    cout << person.m_Name << endl;
    cout << person.m_Age << endl;
    return 0;
}

# 2.2类模板与函数模板

主要区别有两点:

  • 类模板没有自动类型推导的使用方式
  • 类模板在模板参数列表中可以有默认参数

类模板的默认参数:

//
// Created by Jacob Lee on 2021/12/27.
//
#include "iostream"
#include "string"
using namespace std;

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

    void showPerson() {
        cout << "name:" << this->m_Name << endl;
        cout << "age:" << this->m_Age << endl;
    }
};

int main() {
    Person<string, int> person("Jack", 20);
    person.showPerson();
    return 0;
}

# 2.3类模板中成员函数创建时机

类模板中成员函数和普通类中成员函数创建时机是有所区别的

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用时才可以创建

# 2.4类模板对象做函数参数

一共有三种传入方式:

  • 制定传入的类型:直接显示对象的数据类型
  • 参数模板化:将对象中的参数变为模板进行传递
  • 整个类模板化:将这个对象类型模板化进行传递
#include "iostream"
#include "string"
using namespace std;

template<class T1, class T2>
class Person {
public:
    T1 m_Name;
    T2 m_Age;

    Person(T1 name, T2 age) {
        this->m_Name = name;
        this->m_Age = age;
    }

    void showPerson() {
        cout << "name:" << this->m_Name << endl;
        cout << "age:" << this->m_Age << endl;
    }
};

// 1、制定传入类型
void showMethod01(Person<string, int> &person) {
    person.showPerson();
}

// 2、参数模板化
template<class T1, class T2>
void showMethod02(Person<T1, T2> &person) {
    person.showPerson();
}

// 3、对象类型模板化
template<class T>
void showMethod03(T &person) {
    person.showPerson();
}

int main() {
    Person<string, int> p1("Lee", 18);
    showMethod01(p1);
    showMethod02(p1);
    showMethod03(p1);
}

# 2.5类模板与继承

当类模板碰到继承时,需要注意以下几点:

  • 当子类继承的父类是一个类模板时,子类在声明的时候需要指出父类中T的类型
  • 当不指定时,编译器无法给子类分配内存
  • 如果想灵活指定父类中T的类型,子类也需要是一个类模板
#include "iostream"
#include "string"
using namespace std;

template<class T>
class Base {
public:
    T m;
};

class Son: public Base<string> {};

// 如果想要灵活的指定父类中T的类型,子类需要是一个类模板
template<class T1, class T2>
class Another: public Base<T1> {
public:
    T2 n;
};

int main() {
    Son son;
    son.m = "1";
    cout << son.m << endl;

    Another<string, int> another;
    another.m = "hello";
    another.n = 20;
    cout << another.m << endl;
    cout << another.n << endl;
}

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

#include "iostream"
#include "string"
using namespace std;

template<class T1, class T2>
class Person {
public:
    T1 m_Name;
    T2 m_Age;

    Person(T1 name, T2 age);

    void showPerson();
};

// 类模板构造函数类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
    this->m_Name = name;
    this->m_Age = age;
}

// 类模板成员函数实现
template<class T1, class T2>
void Person<T1, T2>::showPerson() {
    cout << "name:" << this->m_Name << endl;
    cout << "age:" << this->m_Age << endl;
}

int main() {
    Person<string, int> person("Jack", 18);
    person.showPerson();
    return 0;
}
  • 总结:类模板中的成员函数类外实现时,需要加上模板的参数列表

# 2.7类模板分文件编写

解决办法:

  • 直接包含.cpp源文件
  • 将声明和实现写到同一个文件中,并更改后缀名为.hpp,其中hpp是约定的名字并不是强制要求

# 2.8类模板与友元

全局函数类内的实现:直接在类内声明友元即可

全局函数类外的实现:需要提前让编译器值到全局函数的存在

建议全局函数做类内实现,用法简单且编译器可以直接识别