你的c++学习路上明灯
这几天会更的比较快,因为要做的事情比较多,我觉得,如果我选择了一些东西我就要为之努力,就像我在CSDN上分享我的学习经历,那么我就要保证自己的学习质量和速度,包括自己学习的量,质量和数量都是要的,如果有什么不足之处,真的希望xdm不吝赐教。
好了,结束了我们c++核心课程,进入到c++的提高课程,模板和STL部分,为什么先讲模板呢?因为我先学的模板,老师先教的模板,哈哈哈哈,废话文学功底见长,其实这本身就是有一个顺序的,哈哈哈。不皮了,我们开始下面的学习。
模板:建立通用的模具,提高复用性
模板的目的主要就是:提高复用性,将类型参数化。
说人话就是,懒得写那么多,一个就够用了。
拿个通用的例子来讲,都写过英语作文吧,都背过作文模板吧,不管要写多少作文,反正能用这个模板就直接套,都不用背其他的模板了;
一,基本介绍
1.特点:
1)不能直接使用(只是一个框架)‘
2)模板的通用不是万能的;
2.c++提供两种模板机制:
1)函数模板:建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来表示。
2)类模板:建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来表示。
二,详细的使用
一,函数模板
1.函数模板的基本语法
1)函数模板利用关键字template
2)使用函数模板有两种方式,1.自动类型推导。
2,显示指定类型·
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; //交换两个整型的函数 void SwapInt(int &a,int &b) { int temp = a; a = b; b = temp; } //交换两个double类型的函数 void SwapDouble(double& a, double& b) { double temp = a; a = b; b = temp; } //用函数模板来写 //提高该段代码的复用性 template<typename T> void MySwap(T& a, T& b) { T temp = a; a = b; b = temp; } void test1() { int a = 10; int b = 20; SwapInt(a, b); cout << "a = " << a << endl << "b = " << b << endl; double c = 3.13; double d = 2.11; SwapDouble(c, d); cout << "c = " << c << endl << "d = " << d << endl; //1.编译器自动类型推导 MySwap(a, b); cout << "a = " << a << endl << "b = " << b << endl; //2.显示指定类型 MySwap<int>(a, b); cout << "a = " << a << endl << "b = " << b << endl; } int main() { test1(); return 0; }
2.函数模板的注意事项:
1)自动类型推导,必须推导出一致的数据类型T,才可以使用。
2)模板必须要确定出T的数据类型,才可以使用。
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; template<class T> void MySwap(T& a, T& b) { T temp = a; a = b; b = temp; } void test1() { int a = 10; int b = 20; double c = 3.24; //只能转换相同数据类型的 //MySwap(a, b);//正确 //MySwap(a, c);//错误 } template<class T> void func() { cout << "func的调用" << endl; } void test2() { //func(); 因为func函数中并没有用到数据,所以无法确定T的数据类型 //故,无法调用func函数 //如果想调用该函数,就必须确定T的数据类型,任何数据类型都行,反正也不用T func<int>(); func<double>(); } int main() { test1(); test2(); return 0; }
3.普通函数与函数模板的区别:
1)普通函数调用时,可以发生自动类型转换(隐式类型转换)
2)函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换。
如果利用显示指定类型的方式,可以发生隐式类型转换。(推荐使用)
4.普通函数和函数模板的调用规则:
1)如果函数模板和普通函数都能实现,优先调用普通函数,
2)可以通过空模板参数列表来强制使用函数模板
3)函数模板也可以发生重载
4)如果函数模板可以产生更好的匹配,优先调用函数模板
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; void MyPrint(int a,int b) { cout << "普通函数" << endl; } template<class T> void MyPrint(T a, T b) { cout << "函数模板" << endl; } //函数模板也可以发生重载 template<class T> void MyPrint(T a, T b,T c) { cout << "函数模板重载" << endl; } void test1() { int a = 10; int b = 20; //如果函数模板和普通函数都能实现,优先调用普通函数 MyPrint(a, b); //空模板参数列表强制调用函数模板 MyPrint<>(a , b); char c = 'c'; char d = 'd'; //如果调用普通函数,则需要隐式转换, //如果函数模板可以产生更好的匹配,优先调用函数模板 MyPrint(c, d); } int main() { test1(); return 0; }
5.模板的局限性:模板的通用性并不是万能的(在某些特定的时候是不合适的)
c++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化的模板
1)利用具体化的模板,可以解决自定义类型的通用化
2)学习模板并不是为了写模板,而是在STL中能够运用系统提供的模板
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; #include<string> class person { public: person(string name,int age) { m_Name = name; m_Age = age; } string m_Name; int m_Age; }; template<class T> bool Compare(T& a, T& b) { if (a == b) return true; return false; } //利用具体化person的版本实现代码,具体化优先调用于普通模板 //具体化:显示具体化的原型。以template<>开头 template<> bool Compare(person& p1, person& p2) { if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) { return true; } else return false; } void test1() { int a = 10; int b = 20; int ret = Compare(a, b); if (ret) { cout << "a和b相等" << endl; } else cout << "a和b不相等" << endl; person p1("Tom",18); person p2("Tom", 18); int r = Compare(p1, p2); if (r) { cout << "p1和p2相等" << endl; } else cout << "p1和p2不相等" << endl; } int main() { test1(); return 0; }
二,类模板
1.类模板和函数模板的区别:
1)类模板没有自动类型推导的使用方式
2)类模板在模板参数列表中可以有默认参数
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; template<class NameType,class AgeType = int> class person { public: NameType m_Name; AgeType m_Age; person(NameType name,AgeType age) { this->m_Age = age; this->m_Name = name; } void show() { cout << "年龄:" << this->m_Age << endl; cout << "名字:" << this->m_Name << endl; } }; void test1() { //1.类模板没有自动类型推导的使用方式 //person p("Tom", 19); 错误的 person<string, int> p("Tom", 19); p.show(); //2.类模板在模板参数列表中可以有默认参数,函数模板不行 person<string>p1("jerry", 10); p1.show(); } int main() { test1(); return 0; }
2.类模板中成员函数创建时机
1)普通类中的成员函数在编译的时候就可以创建
2)类模板中的成员函数在调用的时候才创建
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; class person1 { public: void show1() { cout << "person1" << endl; } }; class person2 { public: void show2() { cout << "person2" << endl; } }; template<class T> class Myclass { public: T obj; void func1() { obj.show1(); } void func2() { obj.show2(); } }; void test1() { //类模板中的成员函数在调用时才创建,所以上方并没有报错,即便他们是互相矛盾的 Myclass<person1>m; m.func1(); //m.func2(); } int main() { test1(); return 0; }
3.类模板对象做函数参数
1)指定传入的类型——直接显示对象的数据类型(常用)
2)参数模板化——将对象中的参数变为模板进行传递
3)整个类模板化——将这个对象类型模板化进行传递
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; //类模板对象做函数参数 template<class T1,class T2> class person { public: T1 m_Name; T2 m_Age; person(T1 name,T2 age) { this->m_Age = age; this->m_Name = name; } void show() { cout << "年龄:" <<this->m_Age << endl; cout << "名字:" << this->m_Name << endl; } }; //1.指定传入的类型 void print1(person<string, int>&p) { p.show(); } void test1() { person<string, int>p("Tom", 10); print1(p); } //2.参数模板化 template<class T1, class T2> void print2(person<T1,T2>& p) { p.show(); //可以借助这个函数检测模板参数的类型 cout << "T1的类型为:" << typeid(T1).name() << endl; cout << "T2的类型为:" << typeid(T2).name() << endl; } void test2() { person<string, int>p("Tom", 10); print2(p); } //3.整个类模板化 template<class T> void print3(T & p) { p.show(); } void test3() { person<string, int>p("Tom", 10); print3(p); } int main() { test1(); test2(); test3(); return 0; } //其实可以理解为将参数的填入变得更加多元化。
4.类模板与继承
1)当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
2)如果不指定,编译器无法给子类分配内存
3)如果想灵活指定出父类中的T的类型,子类也需要变为类模板
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> using namespace std; template<class T> class Base { public: T obj; }; //当父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型 //class Son1 :public Base 错误 class Son1 :public Base<int> { public: }; void test1() { Son1 s1; } //如果想灵活指定父类中T的类型,子类也需变成类模板 template<class T1,class T2> class Son2 :public Base<T2> { public: T1 t; }; void test2() { Son2<int ,char> s2; } int main() { test1(); test2(); return 0; }
5.类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决:1)直接包含.cpp文件
2)将声明和实现写到同一个文件中,并更改后缀名为.hpp
.hpp是约定的名称,并不是强制。
#praga once 防止头文件重复包含
6.类模板成员函数类外实现
#define _CRT_SECURE_NO_WARNINGS 1 #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 func(); }; //构造函数类外实现 template<class T1,class T2> person<T1,T2>::person(T1 name, T2 age) { this->m_Age = age; this->m_Name = name; } //成员函数类外实现 template<class T1, class T2> void person<T1, T2>::func() { cout << "姓名:" << this->m_Name << " 年龄:" << this->m_Age << endl; } void test1() { person<string, int>p("Tom", 20); p.func(); } int main() { test1(); return 0; }
7.类模板与友元
类模板配合友元函数类内和类外实现
#define _CRT_SECURE_NO_WARNINGS 1 #include<string> #include<iostream> using namespace std; //类外实现会比较复杂,所以最好还是类内实现 //要先让编译器知道有person这个类模板,还要让它知道personprint是一个带模板的全局函数 template<class T1,class T2> class person; //这里的却是一个类模板 template<class T1, class T2> void personprint2(person<T1, T2> p) { cout << "年龄:" << p.m_Age << endl << "姓名:" << p.m_Name << endl; } template<class T1,class T2> class person { //1,全局函数类内实现 friend void personprint(person<T1,T2> p) {//因为,person是一个类模板,数据类型是未知的,所以还是要带着模板 cout << "年龄:" << p.m_Age << endl << "姓名:" << p.m_Name << endl; } //2.全局函数类外实现 friend void personprint2<>(person<T1, T2> p);//普通函数 public: T1 m_Age; T2 m_Name; person(T1 age, T2 name) { this->m_Age = age; this->m_Name = name; } }; void test1() { person<string, int>p("Tom", 19); personprint(p); } int main() { test1(); return 0; }