文章目录

  • 一 泛型编程的概念
  • 二 模板
    • 1 模板概念
    • 2 模板分类
  • 三 函数模板
    • 1 函数模板的基本语法
    • 2 函数模板的注意事项
    • 3 普通函数与函数模板的区别
    • 4 普通函数与函数模板重载调用规则
    • 5 函数模板的局限性
  • 四 类模板
    • 1 类模板的基本语法
    • 2 类模板与函数模板的区别
    • 3 类模板的成员函数的创建时机
    • 4 类模板对象做函数参数的传参方式
  • 五 类模板与继承之间的联系
  • 六 类模板的成员函数类外实现
  • 七 类模板与友元之间的联系
  • 八 类模板分文件编写带来的问题和解决方案
    • 1.类模板分文件编写带来的问题
    • 2 类模板分文件编写的问题解决方案(1)
    • 3 类模板分文件编写的问题解决方案(2)
  • 九 总结

一 泛型编程的概念

泛型编程,英文为 generic programming; generic 有通用的意思。那通用的是什么呢?

概念编写与类型无关的通用代码,是代码复用的一种编程思想。

二 模板

1 模板概念

模板就是一种通用模具,能提高代码复用率的一种体现。

注意:
模板的通用性强,可是却不是万能的哦。

模板不能直接使用,需要一定的语法规则去使用。

生活中的模板例子:我们经常吃的饺子,有一种工具可以给饺子凹造型,这种工具就是类似一种模板的概念。比如我想吃韭菜猪肉馅的,就给饺子皮包这个馅的,用磨具定型;我想吃玉米猪肉的,就可以用饺子皮包这个馅的,用磨具定型,等等其他的馅,你想吃啥馅就用啥馅,可是用的模具给它定型。这里不同的地方就是馅不同,而饺子的形状都是一样的。这里就是模板的概念了,模板中类型不一样,可是代码形状确实一样的。

2 模板分类

c++提供模板有两种:一种是函数模板;另一种是类模板

三 函数模板

函数模板的作用:
建立一个通用的函数,将函数的返回类型或参数类型通用化;用一个通用的类型去代替。

1 函数模板的基本语法

语法:
template< typename T ,typename T1,...>
函数声明或定义

template 是 c++ 的关键字,申明为模板的作用
typename 定义通用类型,如后面的 T 为通用类型,typename 可以和 class 是可以替换;
T 不一定是 T ,也可以是其他自定义的字母,定义 T 只是大家习惯这么写。
< > 里面可以定义很多通用的类型

例子:
实现一个交换函数,可以 int 之间的互相交换,可以 double 之间的交换,更通用的说可以任意 类型的交换。

/交换整型函数,不使用模板
void swapInt(int& a, int& b) {int temp = a;a = b;b = temp;
}//交换浮点型函数,不使用模板
void swapDouble(double& a, double& b) {double temp = a;a = b;b = temp;
}//利用模板提供通用的交换函数
template<typename T>
void mySwap(T& a, T& b) // 注意类型为通用类型 T
{T temp = a; // 类型为 通用类型 Ta = b;b = temp;
}
// 测试模板交换函数的案例
void test01()
{int a = 10;int b = 20;// 不使用模板的案例//swapInt(a, b); // 交换 int 类型的//swapDouble(a, b); // 交换 double 类型的//利用模板实现交换//1、自动类型推导mySwap(a, b);//2、显示指定类型mySwap<int>(a, b);cout << "a = " << a << endl;cout << "b = " << b << endl;}int main() {test01();system("pause");return 0;
}

函数模板的使用有两种方式:

  1. 自动类型推导:根据被调用函数传进去的实参,进行类型推导模板中的形参类型。
  2. 显示指定类型:直接显示指定模板中的通用类型。

上面的调用方式 mySwap(a,b) 就是自动类型推导,根据 实参,a, b 是 int 类型,推出 T 的类型为 int;
上面的调用方式Swap<int> (a, b) 就是显示指定类型推导,显示指定了 T 的类型为 int 型;

总结:
推荐使用显式指定类型推导的方式去调用函数模板。
模板将类型参数化,使其抽象出来,更加普遍实用。

2 函数模板的注意事项

  1. 自动类型推导,必须推导出一致的数据类型T,才可以使用

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

例子:

//利用模板提供通用的交换函数
template<class T>
void mySwap(T& a, T& b)
{T temp = a;a = b;b = temp;
}// 1、自动类型推导,必须推导出一致的数据类型T,才可以使用
void test01()
{int a = 10;int b = 20;char c = 'c';mySwap(a, b); // 正确,可以推导出一致的TmySwap(a, c); // 错误,推导不出一致的T类型
}
// 2、模板必须要确定出T的数据类型,才可以使用
template<class T>
void func()
{cout << "func 调用" << endl;
}void test02()
{func(); //错误,模板不能独立使用,必须确定出T的类型func<int>(); //利用显示指定类型的方式,给T一个类型,才可以使用该模板
}

假如推导出来的类型不一致,就会发生错误;假如函数模板使用时候没有确定出T的类型的话,也会出错。(注意,这里确定出T的类型不一定是要显示指定出模板的 T 的参数类型,也是可以通过自动类型推导出来 T 的类型的,主要是你要在使用时候一定要明确的确定出 T 的类型。)

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

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

例子:

//普通函数
int myAdd01(int a, int b)
{return a + b;
}//函数模板
template<class T>
T myAdd02(T a, T b)
{return a + b;
}//使用函数模板时,如果用自动类型推导,不会发生自动类型转换,即隐式类型转换
void test01()
{int a = 10;int b = 20;char c = 'c';//普通函数可以发生隐式类型转换cout << myAdd01(a, c) << endl; //正确,结果:109,将char类型的'c'隐式转换为int类型  'c' 对应 ASCII码 99// 自动类型推导出的函数模板不可以发生隐式类型转换myAdd02(a, c); // 报错,使用自动类型推导时,不会发生隐式类型转换// 显式指定类型的函数模板可以隐式类型转换myAdd02<int>(a, c); //正确,结果:109,如果用显示指定类型,可以发生隐式类型转换
}int main() {test01();system("pause");return 0;
}

4 普通函数与函数模板重载调用规则

普通函数可以与函数模板发生重载,最直白的体现就是函数名可以一样了咯。

所以会引发一个问题,假如我用自动类型推导方式去调用函数,是调用没有模板的函数还是调用有模板的函数呢?

调用规则:

  1. 如果函数模板和普通函数都可以实现,优先调用普通函数;
  2. 可以通过空模板参数列表来强制调用函数模板;也可以显示指定类型方式去调用函数模板;
  3. 如果函数模板可以产生更好的匹配,优先调用函数模板;
//普通函数与函数模板调用规则void myPrint(int a, int b)
{cout << "调用的普通函数" << endl;
}template<typename T>
void myPrint(T a, T b)
{ cout << "调用的模板" << endl;
}template<typename T>
void myPrint(T a, T b, T c)
{ cout << "调用重载的模板" << endl;
}void test01()
{//1、如果函数模板和普通函数都可以实现,优先调用普通函数// 注意 如果告诉编译器  普通函数是有的,但只是声明没有实现,或者不在当前文件内实现,就会报错找不到int a = 10;int b = 20;myPrint(a, b); //调用普通函数//2、可以通过空模板参数列表来强制调用函数模板myPrint<>(a, b); //调用函数模板myPrint<int>(a,b); //调用函数模板//3、函数模板也可以发生重载int c = 30;myPrint(a, b, c); //调用重载的函数模板//4、 如果函数模板可以产生更好的匹配,优先调用函数模板char c1 = 'a';char c2 = 'b';myPrint(c1, c2); //调用函数模板
}int main() {test01();system("pause");return 0;
}

总结:
实际开发不建议普通函数和函数模板同时出现,就是普通函数与函数模板不要同名;

5 函数模板的局限性

函数模板不是万能的,我们都知道,那怎么不是万能呢?

假如我们实现一个功能为 :对比两个变量,相等返回 ture,不相等返回 false; 如果我们给函

数模板传的是 int 类型很好比较没问题,那假如传的是数组名字呢? 假如传的是自定义的

类型呢?这就无法有用平时使用模板的方式去使用函数模板了。但是还是有解决办法的。

解决办法:
c++提供 函数模板的重载方式,给一些特定类型提供了具体化的模板

例子:
template < > 重载的函数

// 自定义类型 Person
class Person
{public:Person(string name, int age){this->m_Name = name;this->m_Age = age;}string m_Name;int m_Age;
};//普通函数模板,判断两个变量是否相等
template<class T>
bool myCompare(T& a, T& b)
{if (a == b){return true;}else{return false;}
}//具体化,显示具体化的原型和定意思以template<>开头,并通过名称来指出类型
//具体化优先于常规模板
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);if (ret){cout << "a == b " << endl;}else{cout << "a != b " << endl;}
}void test02()
{Person p1("Tom", 10);Person p2("Tom", 10);//自定义数据类型,不会调用普通的函数模板//可以创建具体化的Person数据类型的模板,用于特殊处理这个类型bool ret = myCompare(p1, p2);if (ret){cout << "p1 == p2 " << endl;}else{cout << "p1 != p2 " << endl;}
}int main() {test01();test02();system("pause");return 0;
}

总结:
其实我们学习模板不是为了用模板去解决什么实际问题,更多的是,能更好的学习STL,利用系统提供的模板,看得懂STL里面的源代码;这是我们的目标之一。

四 类模板

类模板的作用:
建立一个通用化的类,类中的成员的数据类型可以不指定类型,用通用化的类型来替代,也就是类型参数化,使其更加抽象和适用性更强。

1 类模板的基本语法

语法:
template<typename T1, typename T2,…,typename Tn>
类的实现

例子:


//类模板
template<class NameType, class AgeType>
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;}
public:// 将数据类型通用化了NameType m_Name;AgeType m_Age;
};void test01()
{// 指定NameType 为string类型,AgeType 为 int类型//Person p1("孙悟空", 999);//普通类的实例化方式Person<string, int> P1("孙悟空", 999); // 模板类的实例化方式P1.showPerson();
}int main() {test01();system("pause");return 0;
}

2 类模板与函数模板的区别

  1. 类模板没有自动类型推导的方式去使用。只能使用显示指定类型去使用;

  2. 类模板参数列表中可以有默认类型,而函数模板没有默认参数类型;


//类模板 AgeType 可以有默认参数,如 int
template<class NameType, class 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;}
public:NameType m_Name;AgeType m_Age;
};//1、类模板没有自动类型推导的使用方式
void test01()
{// Person p("孙悟空", 1000); // 错误 类模板使用时候,不可以用自动类型推导Person <string ,int>p("孙悟空", 1000); //必须使用显示指定类型的方式,使用类模板p.showPerson();
}//2、类模板在模板参数列表中可以有默认参数
void test02()
{Person <string> p("猪八戒", 999); //类模板中的模板参数列表 //可以指定默认参数,有默认参数就可以不写出来,假如写出来,就根据你写出来的参数类型去指定p.showPerson();
}int main() {test01();test02();system("pause");return 0;
}

3 类模板的成员函数的创建时机

我们知道,普通类的实现,是在实例化一个对象之后会创建成员函数。意思是:你创建对象之后你就可以任意的去调用其中的共有的成员函数
但是再类模板中,实例化对象时候并不会创建成员函数,你不可以去任意的调用里面的成员函数。而是在实例化对象时候调用了该函数才会创建这个函数。为什么呢,因为类模板中的对象是 T 是通用类型的,你在创建对象的时候,指定数据类型并不能保证你里面的成员函数都是需要你指定的数据类型:就比如以下的例子:

class Person1
{public:void showPerson1(){cout << "Person1 show" << endl;}
};class Person2
{public:void showPerson2(){cout << "Person2 show" << endl;}
};template<class T>
class MyClass
{public:T obj;//类模板中的成员函数,并不是一开始就创建的,而是在模板调用时再生成void fun1() { obj.showPerson1();}void fun2() { obj.showPerson2(); }};
void test 02()
{Person1 p1;// 创建对象后,这个对象可以任意的去调用Person1里的成员函数,因为你创建对象之后,里面的成员函数就已经创建好了。
}
void test01()
{MyClass<Person1> m; //创建对象后,这个对象,不可以任意的去调用MyClass里的成员函数,//因为里面的成员函数是没创建好的,只有你调用里面的成员函数才会开始创建m.fun1();m.fun2();//编译会出错,你指定的是Person1,怎么可以调用Persona2里面的成员函数呢?//说明函数调用才会去创建成员函数
}int main() {test01();system("pause");return 0;
}

4 类模板对象做函数参数的传参方式

类模板的对象做函数的实参,如何传递,这是我们研究的问题
一般有三种的传入方式:

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

例子:

/类模板
template<class NameType, class AgeType >
class Person
{public:Person(NameType name, AgeType age){this->mName = name;this->mAge = age;}void showPerson(){cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;}
public:NameType m_Name;AgeType m_Age;
};//1、指定传入的类型
// 直接指定要传参的类型,以模板的方式写
void printPerson1(Person<string, int> &p)
{p.showPerson();
}
void test01()
{Person <string, int > p("孙悟空", 100);printPerson1(p);
}//2、参数模板化
// 给函数的形参列表的类型直接参数化
template <class T1, class 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<class T>
void printPerson3(T & p)
{cout << "T的类型为: " << typeid(T).name() << endl;p.showPerson();}
void test03()
{Person <string, int >p("唐僧", 30);printPerson3(p);
}int main() {test01();test02();test03();system("pause");return 0;
}

总结:
我们更加倾向于第一种传参方式:指定类型传参;因为这种传参方式比较明显;

五 类模板与继承之间的联系

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

  1. 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型;
  2. 如果不指定,编译器无法给子类分配内存;
  3. 如果想灵活指定出父类中T的类型,子类也需变为类模板;

解释:为什么在子类中继承父类是模板的时候需要指定出父类的 T 的类型?
因为当你继承的时候,没有明确父类的 T 类型,子类中是无法计算子类的内存空间的,因为 T 是无法确认的,不知道该如何给子类分配合适的空间,这时候编译器就会给你报错。

子类继承的父类是模板时候的基本语法:
class 子类:继承权限 父类<指定的类型>

例子:

template<class T>
class Base
{T m;
};//class Son:public Base  //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
class Son :public Base<int> //必须指定一个类型
{};
void test01()
{Son c;
}//类模板继承类模板 ,可以用T2指定父类中的T类型
template<class T1, class T2>
class Son2 :public Base<T2>
{public:Son2(){cout << typeid(T1).name() << endl;cout << typeid(T2).name() << endl;}
};void test02()
{Son2<int, char> child1; // 相当于给son2 传递 int,T1 = int ;//给Base传递了 char,T2 = char,T = T2;
}int main() {test01();test02();system("pause");return 0;
}

在子类创建对象的时候,给子类模板实例化对象,上述的例子中,子类创建对象时候·Son2<int, char> child1; 其中 int传给T1char 传给T2,而 T2传给 T ;

六 类模板的成员函数类外实现

和普通的成员函数的类外实现方式差不多,只不过类模板的类外实现需要加多一个模板的参数列表;

例子:

//类模板中成员函数类外实现
template<class T1, class T2>
class Person {public://成员函数类内声明Person(T1 name, T2 age);void showPerson();public:T1 m_Name;T2 m_Age;
};//构造函数 类外实现
template<class T1, class T2> // 这一操作目的是为了让编译器认识下面的 T1 和 T2
Person<T1, T2>::Person(T1 name, T2 age)  //Person<T1,T2> 是类模板的作用域
{this->m_Name = name;this->m_Age = age;
}//成员函数 类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson() {cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
}void test01()
{Person<string, int> p("Tom", 20);p.showPerson();
}int main() {test01();system("pause");return 0;
}

七 类模板与友元之间的联系

友元全局函数在类内实现:在类内申明友元同时实现函数;
友元全局函数在类外实现:在类外实现的时候,需要让编译器提前知道全局函数的存在;

例子:
全局函数在类内实现

template<class T1,class T2>
class Person
{   //  这是全局函数做右元啊,不是成员函数,因为这里函数并没有类的作用域;// 在成员函数做右元的时候里面是有类的作用域的;// 之前学的全局函数做友元是在类内申明类外实现//而现在我们实现的是全局函数类内申明类内实现friend void PrintPerson(Person<T1,T2> &p){cout << p.m_name << endl;cout << p.m_age << endl;}
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("小黄鸡", 100);PrintPerson(p);
}

例子:
全局函数类外实现
先看一段有问题的代码

template<class T1,class T2>
class Person
{   //类内申明全局函数的友元friend void PrintPerson(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 PrintPerson(Person<T1,T2> &p){cout << p.m_name << endl;cout << p.m_age << endl;}void test01()
{Person<string, int> p("小黄鸡", 100);PrintPerson(p);
}

提出几个问题:
问题1:类外实现时候这段代码中 void PrintPerson(Person<T1,T2> &p) 是否需要加作用域呢?即改成 void Person<T1,T2>::PrintPerson(Person<T1,T2> &p);

答:不需要加作用域,因为这是全局函数,不是类内的成员函数。

问题2:类外实现的时候 void PrintPerson(Person<T1,T2> &p),这段代码编译器是无法识别 T1 和 T2 的,请问如何修改让编译器认识 T1 和 T2 ?

答:只需要加上模板参数列表即可,即将void PrintPerson(Person<T1,T2> &p)修改为:
template<class T1,class T2>
void PrintPerson(Person<T1,T2> &p)
这样编译器就认识 T1 和 T2 了。

问题3:请问修改好了问题二代码能否正常调用起来?假如不可以原因是什么?

答:不可以运行起来,并且编译器报错为:无法解析的外部命令。这说明链接阶段出现了问题。
原因:先观察类内全局函数的申明 friend void PrintPerson(Person<T1,T2> &p);这是一个普通的全局函数,而在我们类外的全局函数的实现中:
template<class T1,class T2>
void PrintPerson(Person<T1,T2> &p)
却是模板的全局的函数实现,问题就出现在这里,你的申明和实现并不是同一个东西,一个是普通全局函数,一个是模板的全局函数,所以连接时候会出现问题;那解决该问题的办法是:给全局函数的申明 friend void PrintPerson(Person<T1,T2> &p) 改成 friend void PrintPerson< >(Person<T1,T2> &p),即在全局函数的申明加个空的模板参数列表。这就好起来了,可是你运行的时候,又运行不起来。。
为什么还是运行不起来呢? 因为 编译器不认识类内申明的 friend void PrintPerson< >(Person<T1,T2> &p);不认识什么呢?不认识 PrintPerson 这个函数名,不认识Person<T1,T2>这个模板的类型;如何让编译器认识呢?只要在申明的类外上面实现 PrintPerson 函数就可以,还有申明以下 Person 类就行;

正确代码如下:

//申明 Person 类;
// 为了让类内的全局函数里面的Person<T1,T2>能够被编译器认识;
template<class T1,class T2>
class Person;
// 类外实现全局函数的友元
// 放在类的上面为了让里面的申明全局函数认识PrintPErson;
void PrintPerson(Person<T1,T2> &p){cout << p.m_name << endl;cout << p.m_age << endl;}
template<class T1,class T2>
class Person
{   //类内申明全局函数的友元friend void PrintPerson(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("小黄鸡", 100);PrintPerson(p);
}

总结:
类外实现较为复杂:

  1. 要让编译器知道类内申明是函数模板;
  2. 要让编译器认识函数名;
  3. 要让编译器认识函数里的模板类型;

只要满足以上三点要求就可以类模板的类外外实现友元全局函数全局函数。我们推荐在类模板类内实现有缘全局函数,因为简单,编译器可以直接识别;

八 类模板分文件编写带来的问题和解决方案

我们都知道,当我们的代码量过多时候,经常会进行分文件编写程序。就是把声明和实现分离不同文件的编写,一般我们分为三个文件:

声明文件.h;
实现文件.cpp;
主程序文件.cpp;

1.类模板分文件编写带来的问题

但是如果在类模板中的声明和实现分类开来写,在创建对象调用类模板里面的成员函数时候就会发生链接错误;原因就是类模板里面的成员函数的创建时机是在对象调用时候才会创建。这就造成了,在你创建对象去调用类模板的成员函数时候,你去.h文件里面找类模板的成员函数,里面的成员函数仅仅就是一个声明语句,并没有实现的;导致找不到该函数会发生链接错误;

看看这个错误的案例:

文件:Person.h 里面为类模板的声明

#pragma once
#include <iostream>
using namespace std;
#include <string>
// 类模板的声明
template<class T1, class T2>
class Person {public:Person(T1 name, T2 age); // 声明构造函数,在点cpp文件实现void showPerson();//声明函数,在.cpp文件实现
public:T1 m_Name;T2 m_Age;
};

文件:Person.cpp里面为类模板的成员函数的实现

// 包含文件Person.h 让编译器在此文件中包含Person.h的内容
#include"Person.h"
//构造函数的实现
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 << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
}

文件:主程序文件.cpp

#include<iostream>
using namespace std;
# include "Person.h" //包含Person.h里面的内容,让编译器认识
void test01()
{Person<string, int> p("Tom", 10);//调用构造函数时候会发生链接错误p.showPerson(); // 调用时候就会发生链接错误
}int main() {test01();system("pause");return 0;
}

当你高高兴兴的把上面上个文件写完的时候,当你一点击运行,就给你报错了,报错的理由是有两个无法解析的外部命令,这就是链接才会出现的问题。
Person<string, int> p("Tom", 10);
p.showPerson();
就是在主程序文件.cpp中,这两行代码导致了无法解析的外部命令。

那如何解决这个问题呢?

2 类模板分文件编写的问题解决方案(1)

首先不说解决办法,我们思考一下问题

无法解析的外部命令的问题如何解决呢?

是因为链接时候出现问题,那我们就把链接时候出现问题给解决了,不就解决了无法解析的外部命令了嘛!

那如何解决链接时候出现的问题呢?

我们想到链接时候出问题的原因就是主程序.cpp文件中,类模板对象调用成员函数时候,只在 Person.h
找到了一个成员函数的声明语句,并没有找到它的实现体,因为类模板成员函数是调用时候才创建的,并不像普通模板在创建对像时候就可以创建成员函数了,这就导致了,找不到函数体实现内容导致了链接错误。哦哦,那我们解决的问题矛头就放在这里了,我们只要让主程序.cpp文件中,类模板对象调用成员函数时候,找到成员函数就可以啦。

如何让我们知道找到这个成员函数体呢?

它就在我们的Person.cpp文件中啦,我们在主程序文件包含它就行了。

所以解决办法就是:(并不是完美的解决方案)

把主程序.cpp里面的程序包含Person.cpp文件就行。

#include<iostream>
using namespace std;
# include "Person.cpp //包含Person.cpp里面的内容,让编译器认识
void test01()
{Person<string, int> p("Tom", 10);//调用构造函数时候会发生链接错误p.showPerson(); // 调用时候就会发生链接错误
}int main() {test01();system("pause");return 0;
}

然后你运行起来,发现,哦嚯,可以运行成功了。
其实解决办法不是最重要的,重要的是思考的方式;看我是如何思考这个问题的,大家可以借鉴以下思路;

3 类模板分文件编写的问题解决方案(2)

我们刚刚说了,上面的并不是完美的解决方案,完美的解决办法是我们接下来说的;
把类模板的声明和实现写在一个.hpp文件里面,并且在主程序.cpp里面包含.hpp文件;
此时程序只有两个文件:

  1. 类模板.hpp文件
  2. 主程序.cpp文件

解释:
.hpp文件后缀是自定义的后缀,你可以写任何的后缀,但是我们建议写.hpp,因为在实际开发中,
.hpp文件是大家约定熟成的写法,人家一看就知道你写的是类模板的实现和分离的文件了。

实例代码如下:

类模板.hpp文件

#pragma once
#include <iostream>
using namespace std;
#include <string>template<class T1, class T2>
class Person {public:Person(T1 name, T2 age);void showPerson();
public:T1 m_Name;T2 m_Age;
};//构造函数 类外实现
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 << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
}

主程序.cpp文件

#include<iostream>
using namespace std;
//解决方式2,将声明和实现写到一起,文件后缀名改为.hpp
#include "person.hpp"
void test01()
{Person<string, int> p("Tom", 10);p.showPerson();
}int main() {test01();system("pause");return 0;
}

九 总结

学习模板的目的不是为了写模板,是为了学习掌握c++标准库提供的模板使用,让我们更加熟练的运用。但是模板的编程思维方式是很值得学习的,共勉。

c++基础 STL 第 0 篇:(模板)相关推荐

  1. 深度优先搜索_0基础学算法 搜索篇第一讲 深度优先搜索

    0基础学算法 搜索篇第一讲 深度优先搜索 相信绝大多数人对于深度优先搜索和广度优先搜索是不会特别陌生的,如果我这样说似乎你没听说过,那如果我说dfs和bfs呢?先不说是否学习过它们,至少它们的大名应该 ...

  2. cmd 将文件夹下文件剪切到另外一个文件_总结java中文件拷贝剪切的5种方式-JAVA IO基础总结第五篇...

    本文是Java IO总结系列篇的第5篇,前篇的访问地址如下: 总结java中创建并写文件的5种方式-JAVA IO基础总结第一篇 总结java从文件中读取数据的6种方法-JAVA IO基础总结第二篇 ...

  3. iOS动画系列之五:基础动画之缩放篇旋转篇Swift+OC

    这一篇主要介绍基础动画之缩放和旋转.这些基本操作分享完之后,我想想可以找个稍微复杂一点点的动画做做啦. 这篇继续基础篇,分享一下缩放和旋转.因为整体思路和平移基本上没有变化,加上源代码里面也有OC版本 ...

  4. datagrid出现相同两组数据_stata 数据操作基础知识:以一篇论文数据操作为例

    stata 数据操作基础知识:以一篇论文数据操作为例 上节回顾及问题 统计学学习大图景 数据描述 分位数回归 存在的问题: 1.学了就要多使用,哪怕生搬硬套也要多用 2.时间序列的方法,大家可以操作, ...

  5. vs2019中如何创建qt项目_VS2019创建新项目居然没有.NET Core3.0的模板?

    今天是个值得欢喜的日子,因为VS2019在今天正式发布了.作为微软粉,我已经用了一段时间的VS2019 RC版本了.但是,今天有很多小伙伴在我的<ASP.NET Core 3.0 上的gRPC服 ...

  6. C++语言基础 —— STL —— 容器与迭代器

    [概述] STL 是指 C++ 标准模板库,是 C++ 语言标准中的重要组成部分,其以模板类和模版函数的形式提供了各种数据结构与算法的精巧实现,如果能充分使用 STL,可以在代码空间.执行时间.编码效 ...

  7. 反相畴的基础知识和一篇论文

    校历第十三周计划(11.18-11.24):反相畴的基础知识和一篇论文 上周由于需要尽快和学长交流,因此提前先看了两篇关于反相畴的论文.由于基础知识的匮乏,这周打算补充一些基础知识,主要来源于薄膜生长 ...

  8. STL学习——RB-tree篇

    STL学习--RB-tree篇 简介 RB-tree(红黑树)是一棵平衡二叉搜索树,它需要满足以下规则: 1)每个节点不是红色就是黑色: 2)根节点为黑色: 3)如果节点为红,其子节点必须为黑: 4) ...

  9. linux 脚本编写 -eq,关于shell脚本基础编程第四篇

    shell脚本基础编程第四篇 本章主要内容:函数 函数 function: function 名称 { 命令 ; } 或 name () { 命令 ; } 定义 shell 函数. 创建一个以 NAM ...

  10. Python3学习笔记之-学习基础(第三篇)

    Python3学习笔记之-学习基础(第三篇) 文章目录 目录 Python3学习笔记之-学习基础(第三篇) 文章目录 一.循环 1.for循环 2.while循环 3.break,continue 二 ...

最新文章

  1. 祝全天下的教师,节日快乐!
  2. android drawerlayout 遮罩层白色,DrawerLayout放在Toolbar的下方导致NavigationView出现与状态栏等高的遮...
  3. Servlet跳转到jsp页面的几种方法
  4. STM32F1迷你板外部中断
  5. java file ip_java常用工具类 IP、File文件工具类
  6. 测试Live Writer 发表博客
  7. 使用OpenTelemetry搭配Zipkin构建NetCore分布式链路跟踪 | WebAPI + gRPC
  8. python全栈开发百度云_价值2400 2016年11月全栈开发Flask Python Web 网站编程
  9. pkcs1转pkcs8 php,openssl RSA密钥格式PKCS1和PKCS8相互转换
  10. java socket 包头包体_使用JAVA上抓取Socket服务端和客户端通信TCP数据包
  11. linux嵌入式gdb调试指南,建立嵌入式gdb调试环境
  12. 外卖侠使用教程【干货】
  13. 网站漏洞扫描工具AWVS_v13下载和安装
  14. 关于云和网的未来,这位大佬讲了真话……
  15. 微型计算机蓝屏的处理方法,电脑出现蓝屏如何解决_电脑蓝屏的处理方法
  16. vue注册了却没有使用的错误: component has been registered but not used
  17. CSS3之多列布局columns学习
  18. 扫描线面积并、面积交模板
  19. latex插入图片之后图片后面的文字跑到前面来了怎么办
  20. 解除UVW贴图的疑惑

热门文章

  1. c语言 格式化硬盘,在Windows 7上用c ++格式化硬盘(Formatting a hard disk in c++ on Windows 7)...
  2. 理解设计模式——代理模式
  3. 联想重装系统去掉保护_解决方法:联想硬盘保护系统EDU7.0安装方法
  4. 约瑟夫问题_公式原理详解
  5. 关于html5小游戏的书,HTML5小游戏:書道 - 书法之境 道之升华
  6. 《淘宝技术这十年》简评
  7. 重置uchome密码
  8. 计算机学报论文字数要求,常见EI学报综述类文章分析
  9. Java 强制删除文件或目录
  10. 常见开发工具下载链接