模板

主要针对C++泛型编程和STL技术做详细解读,探讨C++更深层的使用

模板的概念

模板就是建立通用的模具,提高复用性
可以用PPT模板来思考模板的概念
模板的特点:

  • 模板不可以直接使用,他只是一个框架
  • 模板的同意并不是万能的

    函数模板

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

    函数模板语法

    函数模板作用:
    建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟类型来代表

大致解释虚拟类型:
C  提高编程 - 图1
语法:

  1. template<typename T>
  2. 函数声明或定义

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

  1. #include<iostream>
  2. using namespace std;
  3. //普通函数
  4. //两个整型交换的函数
  5. void swapInt(int& a, int& b)
  6. {
  7. int temp = a;
  8. a = b;
  9. b = temp;
  10. }
  11. //交换两个浮点型的函数
  12. void swapDouble(double& a, double& b)
  13. {
  14. double temp = a;
  15. a = b;
  16. b = temp;
  17. }
  18. //函数模板
  19. //也可以不是T 约定俗成一般就用T
  20. template<typename T>//声明一个模板,告诉编译器告诉编译器,后面代码中紧跟着的T不要报错,T是一个通用数据类型
  21. void mySwap(T& a, T& b)
  22. {
  23. T temp = a;
  24. a = b;
  25. b = temp;
  26. }
  27. void test01()
  28. {
  29. int a = 10;
  30. int b = 20;
  31. cout << "函数模板" << endl;
  32. //利用函数模板来交换
  33. //两种方式使用函数模板
  34. //1、自动类型推导 编译器自己猜测数据类型
  35. mySwap(a, b);
  36. cout << "自动类型推导" << endl;
  37. cout << "a = " << a << endl;
  38. cout << "b = " << b << endl;
  39. cout << endl;
  40. //显示指定类型
  41. int e = 100;
  42. int f = 200;
  43. mySwap<int>(e, f);//<int>直接告诉函数要调用什么类型
  44. cout << "显示指定类型" << endl;
  45. cout << "e = " << e << endl;
  46. cout << "f = " << f << endl;
  47. cout << endl;
  48. cout << "普通函数" << endl;
  49. int g = 1;
  50. int h = 2;
  51. swapInt(g, h);
  52. cout << "g = " << g << endl;
  53. cout << "h = " << h << endl;
  54. cout << endl;
  55. double c = 1.1;
  56. double d = 2.2;
  57. swapDouble(c, d);
  58. cout << "c = " << c << endl;
  59. cout << "d = " << d << endl;
  60. }
  61. int main()
  62. {
  63. test01();
  64. system("pause");
  65. return 0;
  66. }

总结:

  • 函数模板利用关键字template
  • 使用函数模板有两种方式:自动类型推导、显示指定类型
  • 模板的目的是为了提高复用性,将类型参数化

函数模板注意事项

注意事项:

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

示例:

  1. #include<iostream>
  2. using namespace std;
  3. //函数模板的注意事项
  4. //typname可以替换成class
  5. //typename是新定标准,class是旧标准;一般最好用新标准而且可读性强
  6. template<class T>//函数模板和类模板都可以用typename 或 class,看个人喜好
  7. void mySwap(T& a, T& b)
  8. {
  9. T temp = a;
  10. a = b;
  11. b = temp;
  12. }
  13. //1、自动类型推导,必须推导出一致的数据类型T才可以使用
  14. void test01()
  15. {
  16. int a = 10;
  17. int b = 20;
  18. char c = 'c';
  19. //mySwap(a, b);//正确,因为a,b的类型相同
  20. //mySwap(a, c);//报错,因为两个类型不同,推导不出一致的T类型
  21. cout << "自动类型推导" << endl;
  22. cout << "a = " << a << endl;
  23. cout << "b = " << b << endl;
  24. cout << endl;
  25. }
  26. //2、模板必须要确定出T的数据类型,才可以使用
  27. template<typename T>
  28. void func()
  29. {
  30. cout << "func调用" << endl;
  31. }
  32. void test02()
  33. {
  34. //func();//报错,因为没有指定类型,即使函数不需要指定类型
  35. func<int>();
  36. }
  37. int main()
  38. {
  39. test01();
  40. test02();
  41. system("pause");
  42. return 0;
  43. }

总结:

  • 使用模板必须确定出通用数据类型,并且能够推导出一致的类型

函数模板案例

案例描述:

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
  • 排序规则从大到小,排序算法为选择排序
  • 分别利用char数组和int数组进行测试

示例:

  1. #include<iostream>
  2. using namespace std;
  3. //函数模板的注意事项
  4. template<typename T>//函数模板和类模板都可以用typename 或 class,看个人喜好
  5. void mySwap(T& a, T& b)
  6. {
  7. T temp = a;
  8. a = b;
  9. b = temp;
  10. }
  11. //排序算法
  12. template<typename T>
  13. void mySort(T arr[],int len)
  14. {
  15. for (int i = 0; i < len; i++)
  16. {
  17. int max = i;//认定最大值下标
  18. for (int j = i + 1; j < len; j++)
  19. {
  20. //认定最大值 比遍历出的数值要小说明j下标的元素才是最大值
  21. if (arr[max] < arr[j])
  22. {
  23. max = j;//更新最大值下标
  24. }
  25. if (max != i)
  26. {
  27. //交换max和i下标的元素
  28. mySwap(arr[max], arr[i]);
  29. }
  30. }
  31. }
  32. }
  33. //提供打印数组的模板
  34. template<typename T>
  35. void printArray(T arr[], int len)
  36. {
  37. for (int i = 0; i < len; i++)
  38. {
  39. cout << arr[i] << " ";
  40. }
  41. cout << endl;
  42. }
  43. void test01()
  44. {
  45. //测试char数组
  46. char charArr[] = "badcfe";//无序的字符数组
  47. int num = sizeof(charArr) / sizeof(char);
  48. mySort(charArr, num);
  49. printArray(charArr,num);
  50. }
  51. void test02()
  52. {
  53. //测试int数组
  54. int intArr[] = { 7,5,1,3,9,2,4,6,8 };
  55. int num = sizeof(intArr) / sizeof(int);
  56. mySort(intArr, num);
  57. printArray(intArr, num);
  58. }
  59. int main()
  60. {
  61. test01();
  62. test02();
  63. system("pause");
  64. return 0;
  65. }

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

普通函数和函数模板的区别:

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

示例:

#include<iostream>
using namespace std;

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

//1、普通函数调用时可以发生隐式类型转换
//2、函数模板 用自动类型推导,不可以发生隐式类型转换
//3、函数模板 用显示指定类型,可以发生隐式类型转换

//普通函数
int myAdd01(int a, int b)
{
    return a + b;
}

//函数模板
template<typename T>
T myAdd02(T a, T b)
{
    return a + b;
}

void test01()
{
    int a = 10;
    int b = 20;
    char c = 'c';//隐式类型转换把字符型变量转成整型变量,读取对应的ASCII码

    //普通函数:可以发生隐式类型转换
    cout << "普通函数:myAdd01(a, c)" << myAdd01(a, c) << endl;
    cout << endl;

    //自动类型推导:不会发生隐式类型转换
    cout << "普通函数:myAdd02(a, c) 无法推导类型不一致的值,该段报错" << endl;
    //cout << myAdd02(a, c) << endl;//无法推导出不一致的类型
    cout << endl;

    //显示指定类型:可以发生隐式类型转换
    cout << "函数模板:myAdd02<int>(a, c) = " << myAdd02<int>(a, c) << endl;
}

int main()
{
    test01();

    system("pause");
    return 0;
}

总结:建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T

普通函数与函数模板的调用规则

调用规则如下:

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

示例:
如果函数模板和普通函数都可以实现,优先调用普通函数

#include<iostream>
using namespace std;

void my_Print(int a, int b)
{
    cout << "调用的是普通函数" << endl;
}

template<typename T>
void my_Print(T a, T b)//函数模板和普通函数可以写的一样,相当于重载
{
    cout << "调用的是函数模板" << endl;
}

void test01()
{
    int a = 10;
    int b = 20;

    my_Print(a, b);//这里满足的是规则1,优先调用普通函数
}

int main()
{
    test01();

    system("pause");
    return 0;
}

可以通过空模板参数列表,来强制调用函数模板

#include<iostream>
using namespace std;

void my_Print(int a, int b)
{
    cout << "调用的是普通函数" << endl;
}

template<typename T>
void my_Print(T a, T b)
{
    cout << "调用的是函数模板" << endl;
}

void test01()
{
    int a = 10;
    int b = 20;

    //通过空模板的参数列表,强制调用函数模板
    my_Print<>(a, b);//尖括号就是空模板列表
}

int main()
{
    test01();

    system("pause");
    return 0;
}

函数模板可以发生重载

#include<iostream>
using namespace std;

template<typename T>
void my_Print(T a, T b)
{
    cout << "调用的是函数模板" << endl;
}

template<typename T>
void my_Print(T a, T b, T c)
{
    cout << "调用的是重载的函数模板" << endl;
}

void test01()
{
    int a = 10;
    int b = 20;

    my_Print(a, b, 100);//调用的是函数模板重载
}

int main()
{
    test01();

    system("pause");
    return 0;
}

如果函数模板可以产生更好的匹配,优先调用函数模板

#include<iostream>
using namespace std;

void my_Print(int a, int b)
{
    cout << "调用的是普通函数" << endl;
}

template<typename T>
void my_Print(T a, T b)
{
    cout << "调用的是函数模板" << endl;
}

void test01()
{
    char c1 = 'a';
    char c2 = 'b';

    my_Print(c1, c2);//调用的是函数模板
}

int main()
{
    test01();

    system("pause");
    return 0;
}

总结:既然提供了函数模板,最好不要提供普通函数,否则容易出现二义性

模板的局限性

局限性:

  • 模板的通用性并不是万能的

例如:

template<typename T>
void f(T a, T b)
{
    a = b;
}

上述的代码中提供的是赋值操作,如果传入的a和b是一个数组,就无法实现了;再如下

template<typename T>
void f(T a, T b)
{
    if(a > b){ ... }
}

上述代码中,如过T的数据类型传入的是像Person这样的自定义数据类型,也是无法正常运作

因此C++为了解决这种问题,提供模板的重载,可以为这些 特定的类型 提供 具体化的模板

示例:

#include<iostream>
using namespace std;

//模板局限性
class Person
{
public: 
    Person(string name, int age)
    {
        this->m_Name = name;
        this->m_Age = age;
    }

    //姓名
    string m_Name;

    //年龄
    int m_Age;
};

//对比两个数据是否相等的函数
template<typename 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_Name == p2.m_Name && p1.m_Age == p2.m_Age)
    {
        return true;
    }
    else
    {
        return false;
    }
}

void test01()
{
    int a = 10;
    int b = 20;
    bool ret = myCompare(a, b);
    cout << "函数模板对普通类型的使用" << endl;

    if (ret)
    {
        cout << "a == b" << endl;
    }
    else
    {
        cout << "a != b" << endl;
    }
}

void test02()
{
    Person p1("Tom", 10);
    Person p2("Tom", 10);
    bool ret = myCompare(p1, p2);
    cout << "函数模板对普通类型的使用" << endl;

    if (ret)
    {
        cout << "p1 == p2" << endl;
    }
    else
    {
        cout << "p1 != p2" << endl;
    }
}

int main()
{
    test01();
    cout << endl;

    test02();

    system("pause");
    return 0;
}

总结:

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

    类模板

    类模板语法

    类模板作用:

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

语法:

template<typename T>
类

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

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

//类模板
template<typename NameType,typename AgeType>//因为下面两个类型是不一样的,所以要指定两个T
class Person
{
public: 
    Person(NameType name, AgeType age)
    {
        this->m_Name = name;
        this->m_Age = age;
    }

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

    //姓名
    NameType m_Name;
    //年龄
    AgeType m_Age;
};

void test01()
{
    Person<string, int> p1("孙悟空", 999);//<>指定类型,然后再赋值
    p1.showPerson();
}

int main()
{
    test01();

    system("pause");
    return 0;
}

总结:类模板和函数模板语法相似,在声明模板template后面加类,此类称为类模板

类模板与函数模板区别

类模板与函数模板主要有两点区别:

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

示例:

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

//类模板
template<typename NameType,typename AgeType = int>//<>参数列表可以先指定类型,也就是默认参数(函数模板不可以这样使用)
class Person
{
public: 
    Person(NameType name, AgeType age)
    {
        this->m_Name = name;
        this->m_Age = age;
    }

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

    //姓名
    NameType m_Name;
    //年龄
    AgeType m_Age;
};

//1、类模板没有自动类型推导使用方式(函数模板不能这样使用,只有类模板才可以)
void test01()
{
    //Person p1("孙悟空", 999);//没有<>指定类型,会报错,因为类模板没有自动类型推导
    Person<string, int> p1("孙悟空", 999);//这是正确的,只能用显示指定类型
    p1.showPerson();
}
//2、类模板在模板参数列表可以有默认参数
void test02()
{
    Person<string> p1("猪八戒", 1000);//因为已经有默认参数了所以不写int也可以使用
    p1.showPerson();
}

int main()
{
    test01();
    test02();

    system("pause");
    return 0;
}

总结:

  • 类模板使用只能用显示指定类型方式
  • 类模板中的模板参数可以有默认参数

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

    类模板中成员函数和普通类中成员函数创建时机是由去别的

  • 普通类中的成员函数一开始就可以创建

  • 类模板中的成员函数在调用时才创建

示例:

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

//类模板中成员函数的创建时机
class Person1
{
public: 
    void showPerson()
    {
        cout << "Person1 show" << endl;
    }
};
class Person2
{
public: 
    void showPerson()
    {
        cout << "Person2 show" << endl;
    }
};

template<typename T>
class MyClass
{
public:
    T obj;

    //类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成
    void func1()
    {
        obj.showPerson1();
    }
    void func1()
    {
        obj.showPerson2();
    }
};
void test01()
{
    MyClas<Person1> m;
    m.func1();
    //m.func2();//编译会出错,说明函数调用才会去创建成员函数
}

int main()
{
    test01();

    system("pause");
    return 0;
}

类模板对象做函数参数

  • 类模板实例化出的对象,向函数传参的方式

一共有三种传入方式:

  1. 指定传入类型 ——直接显示对象的数据类型
  2. 参数模板化 ——将对象中的参数变为模板进行传递
  3. 整个类模板化 ——将这个对象类型模板化进行传递

示例:

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

//类模板对象做函数参数
template<typename T1, typename T2>
class Person
{
public:
    Person(T1 name, T2 age)
    {
        this->m_Name = name;
        this->m_Age = age;
    }

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

    T1 m_Name;
    T2 m_Age;
};
//1、指定传入类型:最常用
void printPerson1(Person<string, int>& p)
{
    p.showPerson();
}
void test01()
{
    Person<string, int> p("孙悟空", 100);
    printPerson1(p);
}

//2、参数模板化
template<typename T1, typename T2>
void printPerson2(Person<T1, T2>& p)
{
    p.showPerson();
    //查看编译器推导出来的类型
    cout << "T1的类型:" << typeid(T1).name() << endl;
    cout << "T2的类型:" << typeid(T2).name() << endl;
}
void test02()
{
    Person<string, int> p("猪八戒", 90);
    printPerson2(p);
}

//3、整个类模板化
template<typename T>//这个就是函数模板
void printPerson3(T& p)
{
    p.showPerson();
}
void test03()
{
    Person<string, int> p("唐僧", 30);
    printPerson3(p);
}

int main()
{
    test01();
    test02();
    test03();

    system("pause");
    return 0;
}

总结:

  • 通过类模板创建的对象,可以有三种方式向函数中进行传参
  • 使用比较广泛时第一种:指定传入类型

    类模板与继承

    当模板碰到阶乘时,需要注意以下几点:

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型

  • 如果不指定,编译器无法给子类分配内存
  • 如果向灵活指定父类中T的类型,子类也需要变为类模板

示例:

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

//类模板与继承
template<typename T>
class Base
{
    T m;
};
//class Son :public Base//错误,必须知道父类中的T类型,才能继承给子类
class Son :public Base<int>
{};
void test01()
{
    Son s1;
}

//如果想灵活的指定父类中T的类型,子类也需要变为模板
template<typename T1, typename T2>
class Son2 :public Base<T2>
{
public:
    Son2()
    {
        cout << "T1的类型为:" << typeid(T1).name() << endl;
        cout << "T2的类型为:" << typeid(T2).name() << endl;
    }
    T1 obj;
};
void test02()
{
    Son2<int, char> s2;//把类型当成参数一样传递给T1和T2
}

int main()
{
    test01();
    test02();

    system("pause");
    return 0;
}

总结:如果父类是个类模板,子类需要指定出父类中T的数据类型

类模板成员函数类外实现

示例:

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

//类模板中成员函数类外实现
template<typename T1, typename 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;
   }*/

public:
    T1 m_Name;
    T2 m_Age;
};
//构造函数 类外实现
template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age)//<T1, T2>体现构造函数这是类模板的类外实现
{
    this->m_Name = name;
    this->m_Age = age;
}
//成员函数的类外实现
template<typename T1, typename T2>
void Person<T1, T2>::showPerson()//一定要写模板参数列表<T1, T2>,才能体现类模板
{
    cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
}

void test01()
{
    Person<string, int> P("Tom", 20);//把类型当成参数一样传递给T1和T2
    P.showPerson();
}

int main()
{
    test01();

    system("pause");
    return 0;
}

总结:类模板中的成员函数类外实现时,需要加上模板参数列表

类模板分文件编写

问题:

  • 类模板中成岩函数创建时机是在调用阶段,导致份文件编写时链接不到

解决:

  • 方法1:直接包含.cpp源文件
  • 方法2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制

示例:
第一种解决办法:调用.cpp文件而非.h文件
person.h中代码

#pragma once
#include<iostream>
#include<string>
using namespace std;

//类的声明
template<typename T, typename T2>
class Person
{
public:
    //成员函数声明
    Person(T1 name, T2 age);
    void showPerson();

public:
    T1 m_Name;
    T2 m_Age;
};

person.cpp中代码

#include"person.h"

//类的实现
//构造函数 类外实现
template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age)//<T1, T2>体现构造函数这是类模板的类外实现
{
    this->m_Name = name;
    this->m_Age = age;
}
//成员函数的类外实现
template<typename T1, typename T2>
void Person<T1, T2>::showPerson()//一定要写模板参数列表<T1, T2>,才能体现类模板
{
    cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
}
#include<iostream>
//第一种解决办法:直接包含源文件
#include"person.cpp"//person.h会导致无法创建函数,。cpp表示直接调用类外成员函数,而且person.cpp已经包含person.h,编译器也看得
using namespace std;

void test01()
{
    Person<string, int> P("Jerry", 18);
    P.showPerson();
}

int main()
{
    test01();

    system("pause");
    return 0;
}

第二种解决办法:把实现和声明写在一个头文件里后缀为.hpp的类模板(一般用第二种方法)
person.hpp中代码

#pragma once
#include<iostream>
#include<string>
using namespace std;

//声明和实现都在一个文件里
template<typename T, typename T2>
class Person
{
public:
    //成员函数声明
    Person(T1 name, T2 age);
    void showPerson();

public:
    T1 m_Name;
    T2 m_Age;
};

//构造函数 类外实现
template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age)//<T1, T2>体现构造函数这是类模板的类外实现
{
    this->m_Name = name;
    this->m_Age = age;
}
//成员函数的类外实现
template<typename T1, typename T2>
void Person<T1, T2>::showPerson()//一定要写模板参数列表<T1, T2>,才能体现类模板
{
    cout << "姓名:" << this->m_Name << "年龄:" << this->m_Age << endl;
}
#include<iostream>
//第二种解决方法:将.h和.cpp中的内容写到一起,将后缀名改为.hpp文件,一般这个后缀局势类模板
#include"person.hpp"
using namespace std;

void test01()
{
    Person<string, int> P("Jerry", 18);
    P.showPerson();
}

int main()
{
    test01();

    system("pause");
    return 0;
}

总结:主流的解决方式是第二种,将类模板成员函数写到一起,并将后缀名改为.hpp

类模板与友元

掌握类模板配合友元函数的类内和类外实现

全局函数类内实现 - 直接再类内声明友元即可
全局函数类外实现 - 需要提前让编译器知道全局函数的存在

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

//提前让编译器知道Person类存在
template<typename T1, typename T2>//Person是类模板所以这个也要加上
class Person;
//类外实现,先让编译器看到,要卸载类之前才能成功
template<typename T1, typename T2>
void printPerson2(Person<T1, T2> p)
{
    cout << "类外实现 — 姓名:" << p.m_Name << "年龄:" << p.m_Age << endl;
}

//通过全局函数打印Person信息
template<typename T1, typename T2>
class Person
{
    //全局函数 类内实现
    friend void printPerson(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;
        this->m_Age;
    }

private:
    T1 m_Name;
    T2 m_Age;
};


//1、全局函数在类内实现
void test01()
{
    Person<string, int> p("Tom", 20);
    printPerson(p);
}

//2、全局函数在类外实现
void test02()
{
    Person<string, int> p("Jerry", 10);
    printPerson2(p);
}

int main()
{
    test01();
    test02();

    system("pause");
    return 0;
}

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

类模板案例

案例描述:实现一个通用的数组类,要求如下

  • 可以多内置数据类型已经自定义数据类型的数据进行存储
  • 将数组种的数据存储到堆区
  • 构造函数种可以传入数组的容量
  • 提供对应的拷贝构造函数以及operator= 防止浅拷贝问题
  • 提供尾插法和未删法对数组中的数据进行增加和删除
  • 可以通过下标的方式访问数组中的元素
  • 可以获取数组中当前袁术的个数和数组的容量

示例:
myArray.hpp文件

//自己通用的数组类
#pragma once
#include<iostream>
using namespace std;

template<typename T>
class MyArray
{
public:
    //有参构造 参数 容量
    MyArray(int capacity)
    {
        //cout << "MyArray有参构造调用" << endl;
        this->m_Capacity = capacity;
        this->m_Size = 0;
        this->pAddress = new T[this->m_Capacity];
    }
    //拷贝构造
    MyArray(const MyArray& arr)
    {
        //cout << "MyArray拷贝构造调用" << endl;
        this->m_Capacity = arr.m_Capacity;
        this->m_Size = arr.m_Size;
        //this->pAddress = arr.pAddress;默认浅拷贝

        //深拷贝
        this->pAddress = new T[arr.m_Capacity];

        //将arr中的数据都拷贝过来
        for (int i = 0; i < this->m_Size; i++)
        {
            this->pAddress[i] = arr.pAddress[i];
        }
    }
    //operator= 防止浅拷贝问题
    MyArray& operator=(const MyArray& arr)
    {
        cout << "MyArray 的 operator= 调用" << endl;
        //先判断原来堆区是否有数据,如果有先释放
        if (this->pAddress != NULL)
        {
            delete[]this->pAddress;
            this->pAddress = NULL;
            this->m_Capacity = 0;
            this->m_Size = 0;
        }

        //深拷贝
        this->m_Capacity = arr.m_Capacity;
        this->m_Size = arr.m_Size;
        this->pAddress = new T[arr.m_Capacity];
        for (int i = 0; i < this->m_Size; i++)
        {
            this->pAddress[i] = arr.pAddress[i];
        }
        return*this;
    }

    //尾插法
    void Push_Back(const T& val)
    {
        //判断容量是否等大小
        if (this->m_Capacity == this->m_Size)
        {
            return;
        }
        this->pAddress[this->m_Size] = val;//在数组末尾插入数据
        this->m_Size++;//更新数组大小
    }

    //尾删法
    void Pop_Back()
    {
        //让用户访问不到最后一个元素,即为尾删,逻辑删除
        if (this->m_Size == 0)
        {
            return;
        }
        this->m_Size--;
    }

    //通过下标的方式访问数组中的元素 arr[0] = 100
    T& operator[](int index)
    {
        return this->pAddress[index];
    }

    //返沪数组容量
    int getCapacity()
    {
        return this->m_Capacity;
    }

    //返回数组大小
    int getSize()
    {
        return this->m_Size;
    }

    //析构函数
    ~MyArray()
    {
        if (this->pAddress != NULL)
        {
            //cout << "MyArray析构函数调用" << endl;
            delete[]this->pAddress;
            this->pAddress = NULL;
        }
    }

private:
    T* pAddress;//指针指向堆区的真实数组
    int m_Capacity;//数组容量
    int m_Size;//数组大小
};
#include<iostream>
#include<string>
#include"MyArray.hpp"
using namespace std;

//打印
void printIntArray(MyArray<int>& arr)
{
    for (int i = 0; i < arr.getSize(); i++)
    {
        cout << arr[i];
    }
    cout << endl;
}

//测试自定义数据类型
class Person
{
public:
    Person() {}
    Person(string name, int age)
    {
        this->m_Name = name;
        this->m_Age = age;
    }

    string m_Name;
    int m_Age;
};

void test01()
{
    MyArray<int> arr1(5);

    for (int i = 0; i < 5; i++)
    {
        //利用尾插法向数组插入数据
        arr1.Push_Back(i);
    }
    cout << "arr1的打印输出为:";
    printIntArray(arr1);
    cout << "arr1的容量为:" << arr1.getCapacity() << endl;
    cout << "arr1的大小为:" << arr1.getSize() << endl;
    cout << endl;

    MyArray<int> arr2(arr1);
    cout << "arr2的打印输出为:";
    printIntArray(arr2);
    cout << "arr2尾删后:" << endl;
    arr2.Pop_Back();
    cout << "arr2的容量为:" << arr2.getCapacity() << endl;
    cout << "arr2的大小为:" << arr2.getSize() << endl;
    cout << endl;

    /*测试 拷贝 构造 析构 运算符重载是否正常调用
    MyArray<int> arr2(arr1);
    MyArray<int> arr3(10);
    arr3 = arr1;*/
}

void printPersonArry(MyArray<Person>& arr)
{
    for (int i = 0; i < arr.getSize(); i++)
    {
        cout << "姓名:" << arr[i].m_Name << " " << "年龄:" << arr[i].m_Age << endl;
    }
}

void test02()
{
    MyArray<Person> arr(10);
    Person p1("孙悟空", 999);
    Person p2("韩信", 30);
    Person p3("妲己", 20);
    Person p4("赵云", 25);
    Person p5("安其拉", 27);

    //将数据插入到数组中
    arr.Push_Back(p1);
    arr.Push_Back(p2);
    arr.Push_Back(p3);
    arr.Push_Back(p4);
    arr.Push_Back(p5);

    //打印数组
    printPersonArry(arr);

    //出书容量
    cout << "arr的容量为:" << arr.getCapacity() << endl;

    //输出大小
    cout << "arr的大小为:" << arr.getSize() << endl;
}

int main()
{
    test01();
    test02();

    system("pause");
    return 0;
}

STL初识

STL诞生

  • 长久以来,软件界一直希望建立一种可重复利用的东西
  • C++的面向对象泛型编程思想,目的就是复用性的提升
  • 大多数情况下,数据结构和算法都未能有一套标准,导致被迫从事大量重复工作
  • 为了建立数据结构和算法的一套标准,诞生了STL

    STL基本概念

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

  • STL从广义上分为:容器(container)算法(algorithm)迭代器(iterator)
  • 容器算法之间通过迭代器进行无缝链接
  • STL几乎所有的代码都采用了模板类或这模板函数

    STL六大组件

    STL大体分为六大组件,分别是:容器、算法、迭代器、仿函数、适配器(配接器)、空间配置器
  1. 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据
  2. 算法:各种常用的算法,如sort、find、copy、for_each等
  3. 迭代器:扮演了容器与蒜贩之间的胶合剂
  4. 仿函数:行为类似函数,可做为算法的某种策略
  5. 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西
  6. 空间配置器:负责空间的配置与管理

    STL中容器、算法、迭代器

    容器:置物之所(存放数据)
    STL容器就是将御用最广泛的一些数据结构实现出来
    常用数据结构:数组,链表,树,栈,队列,集合,映射表等
    这些容器分为序列式容器关联式容器两种:
  • 序列式容器:强调值得排序,序列是容器中的每个元素均有固定的位置
  • 关联式容器:二叉树结构,各元素之间没有严格的物理上的循序关系

算法:问题之解法()
有限步骤,解决逻辑或数学上的问题,这门学科叫做算法(Algorithm,算法头文件用的就是这个英文单词)
算法分为:质变算法非质变算法

  • 质变算法:是指运算过程中会更改区间内的元素的内容,例如拷贝,替换,删除等等
  • 非质变算法:是指运算过程中不会更改区间内元素的内容,例如查找,计算,遍历,虚招极值等等

迭代器:容器和算法之间的粘合剂
提供一种方法,是值能够依序需方每个容器所含的各个元素,二有无需暴露该容器的内部表示方式
每个容器都有自己的专属的迭代器
迭代器使用非常类似与指针,初学阶段可以先理解迭代器为指针

迭代器种类:

种类 功能 支持运算
输入迭代器 对数局的只读访问 只读,支持++、==、!=
输出迭代器 对数据的只写访问 只写,支持++
前向迭代器 读写操作,并能向前推进迭代器 读写,支持++、==、!=
双向迭代器 读写操作,并能向前和向后操作 读写,支持++、—
随机访问迭代器 读写操作,可以跳跃的方式访问任何数据,最强的迭代器 读写,支持++、—、[n]、-n、<、<=、>、>=

常用的容器中迭代器种类为双向迭代器 和 随机访问迭代器

容器算法迭代器初识

STL中最常用的容器为Vector,可以理解为数组

vector存放内置数据类型

容器:vector
算法:for_each(遍历算法)
迭代器:vector<int>::iterator
示例:

#include <iostream>
#include<vector>
#include<algorithm>
using namespace std;

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

//vector容器存放内置数据类型
void test01()
{
    //创建一个vector容器,数组
    vector<int> v;

    //向容器中插入数据,push_back()就是尾插
    v.push_back(10);
    v.push_back(20);
    v.push_back(30);
    v.push_back(40);

    //通过迭代器访问容器中的数据
    vector<int>::iterator itBegin = v.begin();//起始迭代器 指向容器中第一个元素
    vector<int>::iterator itEnd = v.end();//结束迭代器,指向最后一个元素的下一个位置

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

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

    //第三种遍历方式 利用STL中的遍历算法
    cout << "第三种方法:";
    for_each(v.begin(),v.end(), myPrint);//用了回调函数的技术,本质就是for循环
}

int main()
{
    test01();

    return 0;
}