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

          模板是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();
}


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

       友元函数的实现写在类外部,但是同在一个CPP文件。正确的语法是:
//友元声明
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析构时,程序宕机!所以你才看到了优化后的Teacher类自己提供了拷贝构造函数和=操作符重载。如果你还没有意识到在MyVector里面拷贝构造函数和=操作符重载中的

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

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


全部拿走!不用谢我!

参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

松狮MVP

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值