您的位置:首页 > 编程语言 > C语言/C++

C++技术点积累(5)——泛型编程(函数模板、类模板)

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

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

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

      总结:

             模板把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。

             模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。

// 函数的业务逻辑 一样
// 函数的参数类型 不一样
// 让 类型参数化 ===, 方便程序员进行编码
// 泛型编程
// template 告诉C++编译器 我要开始泛型编程了。看到T, 不要随便报错
template <typename T>
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<int>(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 <iostream>
using namespace std;

//模板类
template <class T>
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<int>
{
public:
B(int a = 10, int b = 20) : A<int>(a)  //对象初始化列表——父类提供了有参构造函数(且只有这么一个)
{
this->b = b;
}
void printB()
{
cout << "a:" << a << " b:" << b << endl;
}
private:
int b;
};

//5、从模板类 派生 模板类
template <typename T>
class C : public A<T>
{
public:
C(T c, T a) : A<T>(a)
{
this->c = c;
}
void printC()
{
cout << "c:" << c << " a:" << a << endl;
}
protected:
T c;
};

//类模板 做函数参数
//3、参数 ,C++编译器要求具体的类,所以 要写成:A<int> &a
void UseA(A<int> &a)
{
a.printA();
}

void main()
{
//2、模板类(本身就是类型化的)====具体的类=====>定义具体的变量
A<int> a1(11), a2(20), a3(30); //1、模板类是抽象的  ====>需要进行 类型具体
//a1.printA();
UseA(a1);
UseA(a2);
UseA(a3);

//4、从模板类 派生 普通类
B  b1(1, 2);
b1.printB();

//5、从模板类 派生 模板类
C<int> c1(1, 2);
c1.printC();
}


        注意:当把友元函数的实现写在外部时,容易出错。模板是两次编译生成的,第一次编译生成的函数头和第二次编译生成的函数头不一样。

[b]       友元函数的实现写在类外部,但是同在一个CPP文件。正确的语法是:[/b]
//友元声明
friend ostream & operator<<  <T>  (ostream &out, Complex &c3);  //注意在友元声明时,<T>出现的位置

//友元实现
template <typename T>
ostream & operator<<(ostream &out, Complex<T> &c3)  //不是成员函数,所以不用加作用域说明符
{
<span style="white-space:pre">	</span>out <<  c3.a << " + " << c3.b <<  "i" << endl;
<span style="white-space:pre">	</span>return out;
}
       另外,不要滥用友元!特别是和模板在一起的时候!

       如果把.h和.cpp文件分开,在test主函数中要引入的是——#include "XXXTemplate.cpp",不是#include "XXXTemplate.h"。



2)类模板与static关键字——每个模板类有自己的类模板的static数据成员副本

#include<iostream>
using namespace std;

//结论:int模板的类和double模板的类,各自拥有各自的static

template <typename T>
class AA
{
public:
static T m_a;
};
template <typename T>
T AA<T>::m_a = 0;

void main()
{
AA<int> a1, a2, a3;
a1.m_a = 10;
a2.m_a++;
a3.m_a++;
cout << AA<int>::m_a << endl;   //12

AA<char> b1, b2, b3;
b1.m_a = 'a';
b2.m_a++;
b2.m_a++;
cout << AA<char>::m_a << endl;   //c

//m_a 应该是 每一种类型的类 使用自己的m_a
}



3)设计一个数组模板类( MyVector ),完成对int、char、Teacher类型元素的管理。需求类模板,构造函数、拷贝构造函数、<<、[ ]、=

MyVector.h

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

template <typename T>
class MyVector
{
friend ostream& operator<< <T>(ostream&out, const MyVector& v);//千万记住 友元与模板 结合时,且函数实现在类外部,此处的特殊标记,<T>
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<iostream>
#include"MyVector.h"
using namespace std;

template <typename T>
ostream & operator<< (ostream& out, const MyVector<T>& v)   //此处加<T>去具体化MyVector
{
for (int i = 0; i < v.m_len; i++)
{
out << v.m_space[i] << " ";
}
out << endl;
return out;
}

//MyVector<int> myv1(10);
template <typename T>
MyVector<T>::MyVector(int size)
{
m_len = size;
m_space = new T[m_len];
}

//MyVector<int> myv2 = myv1;
template <typename T>
MyVector<T>::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 <typename T>
MyVector<T>::~MyVector()
{
if (m_space != NULL)
{
delete[] m_space;
m_space = NULL;
m_len = 0;
}
}

template <typename T>
T& MyVector<T>::operator[](int index)
{
return m_space[index];
}

//a3 = a2 = a1
template <typename T>
MyVector<T>& MyVector<T>::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<iostream>
#include"MyVector.cpp" //注意:包含的是.cpp,不是.h,否则将不认识类的成员函数
using namespace std;

void main01()
{
MyVector<int> myv1(10);
for (int i = 0; i < myv1.getlen(); i++)
{
myv1[i] = i+1;
cout << myv1[i] << " ";
}
cout << endl;

MyVector<int> myv2 = myv1;
for (int i = 0; i < myv2.getlen(); i++)
{
cout << myv2[i] << " ";
}

cout << endl << myv2 << endl;
}

void main02()
{
MyVector<char> 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<Teacher> 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<Teacher*> tArray(4);  //存指针
//存
tArray[0] = &t1;
tArray[1] = &t2;
tArray[2] = &t3;
tArray[3] = &t4;

/*MyVector<Teacher> 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析构时,程序宕机!所以你才看到了优化后的[b]Teacher类自己提供了拷贝构造函数和=操作符重载。如果你还没有意识到在MyVector里面[b]拷贝构造函数和=操作符重载中的[/b][/b]

template <typename T>
MyVector<T>::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 <typename T>
MyVector<T>& MyVector<T>::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类中实现拷贝构造函数和=操作符重载),兄弟!

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



[b][b]全部拿走!不用谢我![/b][/b]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息