博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C++技术点积累(5)——泛型编程(函数模板、类模板)
阅读量:4124 次
发布时间:2019-05-25

本文共 9171 字,大约阅读时间需要 30 分钟。

          模板是C++类型参数化的多态工具。C++提供函数模板和类模板。模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。同一个类属参数可以用于多个模板。类属参数可用于函数的参数类型、返回类型和声明函数中的变量。模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。模板称为模板函数;实例化的类模板称为模板类。函数模板可以用多种方式重载。类模板可以在类层次中使用 。

1、函数模板——本质:类型参数化

  1)使得程序(算法)可以从逻辑功能上抽象,把被处理的对象(数据)类型作为参数传递。

      总结:
             模板把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。
             模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。

// 函数的业务逻辑 一样 // 函数的参数类型 不一样// 让 类型参数化 ===, 方便程序员进行编码// 泛型编程 // template 告诉C++编译器 我要开始泛型编程了。看到T, 不要随便报错template 
void 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++编译器模板机制剖析

      编译器并不是把函数模板处理成能够处理任意类的函数;编译器从函数模板通过具体类型产生不同的函数;编译器会对函数模板进行两次编译;在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译

2、类模板

         类模板在表示如数组、表、图等数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响。

       编写类模板代码,在编写成员函数时和以往稍有不同,注意几点:

           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)
单个类模板,类模板做参数,继承中的类模板,从模板类 派生 模板类,

#include 
using 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数据成员副本

#include
using 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#include
using 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;};
MyVector.cpp
#include
#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;}
MyVector_Test.cpp
#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里面拷贝构造函数和=操作符重载中的

template 
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;}
m_space[i] = v.m_space[i];——它对你的*pName执行了浅拷贝(如果不在Teacher类中实现
拷贝构造函数和=操作符重载),兄弟!

如果你还在纳闷,仔细敲敲上面的代码,看一看这篇文章:http://blog.csdn.net/songshimvp1/article/details/48244599。

全部拿走!不用谢我!

你可能感兴趣的文章
DoesNotExist异常导入路径
查看>>
JS中如何快速将字符串的“true"和"false"转换成Bool类型
查看>>
微信小程序封装wx.require
查看>>
rest_framework中,序列化显示choices字段的value方法
查看>>
xadmin中根据当前用户自动填写默认值
查看>>
js数组与字符串互相转换
查看>>
pandas DataFrame 转换日期比较时报 Can only use .dt accessor with datetimelike values错误
查看>>
Pandas DataFrame求差集
查看>>
rest_framework,发起retrieve请求时参数带小数点报404错误
查看>>
Vue移动端better-scroll组件中的@click事件失效
查看>>
pandas之DataFrame更改数据的列位置
查看>>
DataFrame索引加1
查看>>
Django自定义用户表时Admin后台密码密文的解决办法
查看>>
Vue页面刷新后store数据丢失
查看>>
无法启动uwsgi,显示No such file or directory [core/utils.c line 3654]
查看>>
nginx接受请求报错failed(13:Permission denied)
查看>>
stylus使用伪类和伪元素的方法
查看>>
Centos8安装mysql8.0遇到No match for argument: mysql-community-server错误
查看>>
Centos8远程连接mysql报You are not allowed to create a user with GRANT错误
查看>>
Centos8 重置mysql8的root密码
查看>>