本文共 9171 字,大约阅读时间需要 30 分钟。
模板是C++类型参数化的多态工具。C++提供函数模板和类模板。模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。同一个类属参数可以用于多个模板。类属参数可用于函数的参数类型、返回类型和声明函数中的变量。模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。模板称为模板函数;实例化的类模板称为模板类。函数模板可以用多种方式重载。类模板可以在类层次中使用 。
1)使得程序(算法)可以从逻辑功能上抽象,把被处理的对象(数据)类型作为参数传递。 总结: 模板把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。 模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
// 函数的业务逻辑 一样 // 函数的参数类型 不一样// 让 类型参数化 ===, 方便程序员进行编码// 泛型编程 // template 告诉C++编译器 我要开始泛型编程了。看到T, 不要随便报错templatevoid myswap(T &a, T &b) //标准库为我们定义了一个模板化的swap函数,可以用std::swap使用。我们自定义的,就不要再使用swap{ T c = 0; c = a; a = b; b = c; cout << "hello ....我是模板函数 欢迎 calll 我" << endl;}// 函数模板的调用// 显示类型 调用// 自动类型 推导void main(){ int x = 10; int y = 20; //myswap (x, y); //1 函数模板 显示类型 调用 myswap(x, y); //2 自动类型 推导 printf("x:%d y:%d \n", x, y);}
注意:普通函数可以进行隐式的类型转化,而函数模板将严格的按照类型惊醒匹配,不会进行任何自动类型转换。当函数模板和普通函数都符合调用时,优先选择普通函数;如果 函数模板产生更好的匹配 使用函数模板。
2)C++编译器模板机制剖析 编译器并不是把函数模板处理成能够处理任意类的函数;编译器从函数模板通过具体类型产生不同的函数;编译器会对函数模板进行两次编译;在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。
类模板在表示如数组、表、图等数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响。
编写类模板代码,在编写成员函数时和以往稍有不同,注意几点:
A. 如果类模板的成员函数较简单,直接在类里面实现;(原因往后看)
B. 如果成员函数的实现写在类外(写在同一.h文件中),要注意友元函数的编写operator<< <T>,你可能不清楚怎么回事,请去看该文章最后的代码MyVector中<<运算符的重载,此外还有:
BA. 模板头:template <typename T> ——每个成员函数必须全部单独
BB. 成员函数 的 参数列表 类型具体化——XXXXX<T>:: ;
BC. 成员函数 的 返回值类型 类型具体化——XXXXX<T>:: ;
BD. 成员函数 的 类作用域具体化——XXXXX<T>:: 。
C. 为什么我前面建议在编写类模板代码时那么建议?——简单不容易出错!
如果把成员函数的实现写在类外,且另外单独实现在.cpp,那么在main主函数中要引入的是——#include "XXXTemplate.cpp",不是#include "XXXTemplate.h"。
因为C++类模板机制是“两次编译”,第一次在.h文件生成的函数头,第二次在.cpp文件又生成。
上面提到的几个问题,在下文都会有重点说明!
1) 单个类模板,类模板做参数,继承中的类模板,从模板类 派生 模板类,#includeusing namespace std;//模板类 template class A{public: A(T a) { this->a = a; }public: void printA() { cout << "a: " << a << endl; }protected: T a;};//4、从模板类 派生 普通类//模板类派生时, 需要 具体化模板类。C++编译器需要知道 父类的数据类型具体是什么样子的//要知道父类所占的内存大小是多少,只有数据类型固定下来,才知道如何分配内存 class B : public A {public: B(int a = 10, int b = 20) : A (a) //对象初始化列表——父类提供了有参构造函数(且只有这么一个) { this->b = b; } void printB() { cout << "a:" << a << " b:" << b << endl; }private: int b;};//5、从模板类 派生 模板类template class C : public A {public: C(T c, T a) : A (a) { this->c = c; } void printC() { cout << "c:" << c << " a:" << a << endl; }protected: T c;};//类模板 做函数参数//3、参数 ,C++编译器要求具体的类,所以 要写成:A &a void UseA(A &a){ a.printA();}void main(){ //2、模板类(本身就是类型化的)====具体的类=====>定义具体的变量 A a1(11), a2(20), a3(30); //1、模板类是抽象的 ====>需要进行 类型具体 //a1.printA(); UseA(a1); UseA(a2); UseA(a3); //4、从模板类 派生 普通类 B b1(1, 2); b1.printB(); //5、从模板类 派生 模板类 C c1(1, 2); c1.printC();}
注意:当把友元函数的实现写在外部时,容易出错。模板是两次编译生成的,第一次编译生成的函数头和第二次编译生成的函数头不一样。
友元函数的实现写在类外部,但是同在一个CPP文件。正确的语法是://友元声明friend ostream & operator<<另外,不要滥用友元!特别是和模板在一起的时候!(ostream &out, Complex &c3); //注意在友元声明时, 出现的位置//友元实现template ostream & operator<<(ostream &out, Complex &c3) //不是成员函数,所以不用加作用域说明符{ out << c3.a << " + " << c3.b << "i" << endl; return out;}
如果把.h和.cpp文件分开,在test主函数中要引入的是——#include "XXXTemplate.cpp",不是#include "XXXTemplate.h"。
2)类模板与static关键字——每个模板类有自己的类模板的static数据成员副本
#includeusing namespace std;//结论:int模板的类和double模板的类,各自拥有各自的statictemplate class AA{public: static T m_a;};template T AA ::m_a = 0;void main(){ AA a1, a2, a3; a1.m_a = 10; a2.m_a++; a3.m_a++; cout << AA ::m_a << endl; //12 AA b1, b2, b3; b1.m_a = 'a'; b2.m_a++; b2.m_a++; cout << AA ::m_a << endl; //c //m_a 应该是 每一种类型的类 使用自己的m_a}
3)设计一个数组模板类( MyVector ),完成对int、char、Teacher类型元素的管理。需求类模板,构造函数、拷贝构造函数、<<、[ ]、=
MyVector.h
#pragma once#includeMyVector.cppusing namespace std;template class MyVector{ friend ostream& operator<< (ostream&out, const MyVector& v);//千万记住 友元与模板 结合时,且函数实现在类外部,此处的特殊标记, public: MyVector(int size =0); MyVector(const MyVector& obj); ~MyVector(); T& operator[](int index); MyVector& operator=(const MyVector& obj); int getlen() { return m_len; }private: T *m_space; int m_len;};
#includeMyVector_Test.cpp#include"MyVector.h"using namespace std;template ostream & operator<< (ostream& out, const MyVector & v) //此处加 去具体化MyVector{ for (int i = 0; i < v.m_len; i++) { out << v.m_space[i] << " "; } out << endl; return out;}//MyVector myv1(10);template MyVector ::MyVector(int size){ m_len = size; m_space = new T[m_len];}//MyVector myv2 = myv1;template MyVector ::MyVector(const MyVector& obj){ m_len = obj.m_len; m_space = new T[m_len]; //复杂数据类型,不能再使用memcpy for (int i = 0; i < m_len; i++) { m_space[i] = obj.m_space[i]; }}template MyVector ::~MyVector(){ if (m_space != NULL) { delete[] m_space; m_space = NULL; m_len = 0; }}template T& MyVector ::operator[](int index){ return m_space[index];}//a3 = a2 = a1template MyVector & MyVector ::operator=(const MyVector& obj) //注意返回值的{ if (m_space != NULL) { delete[] m_space; m_len = 0; } m_len = obj.m_len; m_space = new T[m_len]; //复杂数据类型,不能再使用memcpy for (int i = 0; i < m_len; i++) { m_space[i] = obj[i]; } return *this;}
#define _CRT_SECURE_NO_WARNINGS#include#include"MyVector.cpp" //注意:包含的是.cpp,不是.h,否则将不认识类的成员函数using namespace std;void main01(){ MyVector myv1(10); for (int i = 0; i < myv1.getlen(); i++) { myv1[i] = i+1; cout << myv1[i] << " "; } cout << endl; MyVector myv2 = myv1; for (int i = 0; i < myv2.getlen(); i++) { cout << myv2[i] << " "; } cout << endl << myv2 << endl;}void main02(){ MyVector myv1(10); myv1[0] = 'a'; myv1[1] = 'b'; myv1[2] = 'c'; myv1[3] = 'd'; cout << myv1;}//把Teacher放入到MyVector数组中//结论1:如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现深拷贝和浅拷贝的问题。//结论2:需要Teacher封装的函数有:// 1)重写拷贝构造函数(Teacher类的属性含有指针)// 2)重载等号=操作符.// 3)重载左移< <操作符. 解释3):如果不去重载左移操作符,那么在myvector的重载<<中out << v.m_space[i] " "相当于out.t1,显然不合理 *未优化class teacher{public: teacher() { age="33;" strcpy(name, ""); } teacher(char *name, int age) this-> age = age; strcpy(this->name, name); } void print() { cout << "name:" << name << " age:" << age << endl; }private: int age; char name[32]; //在此处已经分配了内存};void main(){ Teacher t1("t1", 31), t2("t2", 32), t3("t3", 33), t4("t4", 34); MyVector tArray(4); //存 tArray[0] = t1; tArray[1] = t2; tArray[2] = t3; tArray[3] = t4; //取 for (int i = 0; i<4; i++) { Teacher tmp = tArray[i]; tmp.print(); }}*///1 优化Teacher类,属性变成 char *pname, 购置函数里面 分配内存//2 优化Teacher类,析构函数 释放panme指向的内存空间//3 优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数 //4 优化Teacher类,在Teacher增加 << //5 在模板数组类中,存int char Teacher Teacher*(指针类型)//优化:class Teacher{public: Teacher() { age = 33; m_p = new char[1]; strcpy(m_p, ""); } Teacher(char *name, int age) { this->age = age; m_p = new char[strlen(name) + 1]; strcpy(m_p, name); } Teacher(const Teacher& obj)//拷贝构造函数 { m_p = new char[strlen(obj.m_p) + 1]; strcpy(m_p, obj.m_p); age = obj.age; } ~Teacher() { if (m_p != NULL) { delete[] m_p; //delete和delete[]的区别:http://www.cnblogs.com/charley_yang/archive/2010/12/08/1899982.html m_p = NULL; } } void print() { cout << "name:" << m_p << " , age:" << age << endl; }public: friend ostream& operator<< (ostream& out, Teacher& t); Teacher& operator=(const Teacher& obj) { if (m_p != NULL) { delete[] m_p; m_p = NULL; age = 33; } m_p = new char[strlen(obj.m_p) + 1]; age = obj.age; strcpy(m_p, obj.m_p); return *this; }private: int age; //char name[32]; //在此处已经分配了内存 char *m_p;};ostream& operator<< (ostream& out, Teacher& t){ out << t.m_p << " , " << t.age << endl; return out;}void main(){ Teacher t1("t1", 31), t2("t2", 32), t3("t3", 33), t4("t4", 34); MyVector 操作符.>tArray(4); //存指针 //存 tArray[0] = &t1; tArray[1] = &t2; tArray[2] = &t3; tArray[3] = &t4; /*MyVector tArray(4); cout << " "; cout << tArray;*/ //取 for (int i = 0; i<4; i++) { Teacher *tmp = tArray[i]; tmp->print(); } cout << endl << tArray ; //MyVector里面的out << v.m_space[i] << " "; 结合 Teacher里面的out << t.pName << ", , :" << t.age << endl;}
所有容器提供的都是值(value)语意,而非引用(reference)语意。容器执行插入元素的操作时,内部实施拷贝动作。所以STL容器内存储的元素必须能够被拷贝(必须提供拷贝构造函数),能够进行赋值操作(重载=)。
比如上面优化后的Teacher类,里面改成了*pName,而MyVector类里面的拷贝、赋值将对指针*pName进行浅拷贝,顺其自然,在MyVector析构时,程序宕机!所以你才看到了优化后的Teacher类自己提供了拷贝构造函数和=操作符重载。如果你还没有意识到在MyVector里面拷贝构造函数和=操作符重载中的
templatem_space[i] = v.m_space[i];——它对你的*pName执行了浅拷贝(如果不在Teacher类中实现 拷贝构造函数和=操作符重载),兄弟!MyVector ::MyVector(const MyVector& v){ this->m_len = v.m_len; this->m_space = new T[m_len]; for (int i = 0; i < v.m_len; i++) { m_space[i] = v.m_space[i]; }}template MyVector & MyVector ::operator = (const MyVector& v){ if (m_space != NULL) { delete[] m_space; m_space = NULL; m_len = 0; } m_len = v.m_len; m_space = new T[m_len]; for (int i = 0; i < m_len; i++) { m_space[i] = v.m_space[i]; } return *this;}
如果你还在纳闷,仔细敲敲上面的代码,看一看这篇文章:http://blog.csdn.net/songshimvp1/article/details/48244599。
全部拿走!不用谢我!