6、 函数模板和类模板

函数模板和类模板

前言

C++提供了函数模板(functiontemplate)。所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。

1)C++提供两种模板机制:函数模板、类模板

2)类属—— 类型参数化,又称参数模板

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

总结:

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

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

6.1函数模板

6.1.1为什么要有函数模板

#include <iostream>
using namespace std;


//函数的业务逻辑一样 
//函数的参数类型 不一样
void mySwap(int &a, int &b)
{
	int c = a;
	a = b;
	b = c;
}
void mySwap01(float &a, float &b)
{
	float c = a;
	a = b;
	b = c;
}

//让 类型参数化 ===, 方便程序员进行编码
// 泛型编程 
//template 告诉C++编译器 我要开始泛型编程了 .看到T, 不要随便报错
template <typename T>
void myswap(T &a, T &b)
{
	T c = a;
	a = b;
	b = c;
	cout << "hello ....我是模板函数 欢迎 calll 我" << endl;
}
//函数模板的调用
// 显示类型 调用
// 自动类型 推导
void main()
{
	int x = 10;
	int y = 20;

	float x1 = 10.1;
	float y1 = 1.1;
	mySwap01(x1,y1);

	myswap<int>(x, y);//1 函数模板 显示类型 调用
	printf("x:%f y:%f \n", x1, y1);

	mySwap(x,y);
	myswap(x,y);
	printf("x:%d y:%d \n", x, y);

	char a = 'a';
	char b = 'b';
	myswap<char>(a, b); //1 函数模板 显示类型 调用
	printf("a:%c b:%c \n", a, b);
}

6.1.2函数模板语法

函数模板定义形式

template    <类型形式参数表>

类型形式参数的形式为:

                     typenameT1typename T2 ,…… , typename Tn

或     class T1 class T2 , …… , classT


函数模板调用

                   myswap<float>(a,b);       //显示类型调用

                  myswap(a, b);                   //自动数据类型推导

6.1.3函数模板和模板函数


6.1.4函数模板做函数参数

#include <iostream>
using namespace std;

template <typename T>
int mySort(T *array, T size)
{
	T i, j;
	T tmp;
	if (NULL == array)
	{
		return -1;
	}
	//冒泡排序
	for ( i = 0; i < size; i++)
	{
		for (j = i + 1; j < size; j++)
		{
			if (array[i] < array[j])//从大到小
			{
				tmp = array[i];
				array[i] = array[j];
				array[j] = tmp;
			}
		}
	}
	return 0;
}

template <typename T>
int myPrint(T *array, T size)
{
	T i = 0;
	for ( i = 0; i < size; i++)
	{
		cout << array[i] << " ";
	}
	return 0;
}
int main()
{
	//int 类型
	int myarray[] = { 11, 33, 44, 33, 22, 2, 3, 6, 9 };

	int size = sizeof(myarray) / sizeof(*myarray);

	mySort<int>(myarray, size); //显示类型调用
	 
	printf("排序之后\n");

	myPrint<int>(myarray, size);

	system("pause");
	return 0;
}

6.1.5函数模板遇上函数重载

函数模板和普通函数区别结论:

/*

函数模板不允许自动类型转化

普通函数能够进行自动类型转换

*/

函数模板和普通函数在一起,调用规则:

/*

         1函数模板可以像普通函数一样被重载

         2C++编译器优先考虑普通函数

         3如果函数模板可以产生一个更好的匹配,那么选择模板

         4可以通过空模板实参列表的语法限定编译器只通过模板匹配

*/

#include <iostream>
using namespace std; 

//让 类型参数化 ===, 方便程序员进行编码
// 泛型编程 
//template 告诉C++编译器 我要开始泛型编程了 .看到T, 不要随便报错
template <typename T>
void myswap(T &a, T &b)//函数要求两个形参的类型 严格的要求类型匹配
{
	T c = a;
	a = b;
	b = c;
	cout << "hello ....我是模板函数 欢迎 calll 我" << endl;
}

void myswap(int a, char c)
{
	cout << "a:" << a << "c:" << c << endl;
	cout << "我是普通函数 欢迎来访" << endl;
}
//函数模板和普通函数区别结论:

//函数模板 不允许自动类型转化
//普通函数 能够进行自动类型转换

//函数模板和普通函数在一起,调用规则:

//1 函数模板可以像普通函数一样被重载
//2 C++编译器优先考虑普通函数
//3 如果函数模板可以产生一个更好的匹配,那么选择模板
//4 可以通过空模板实参列表的语法限定编译器只通过模板匹配


int main()
{
	int a = 10;
	char c = 'c';
	myswap(a, c); // 普通函数的调用:  可以进行隐式的类型转换 
	myswap(c, a); //普通函数的调用:  可以进行隐式的类型转换 

	myswap(a, a); // 函数模板函数的调用(本质:类型参数化): 将严格的按照类型进行匹配,不会进行自动类型转换


	system("pause");
	return 0;
}

C++继承中重载、重写、重定义的区别:

重载overload:是函数名相同,参数列表不同 重载只是在类的内部存在。但是不能靠返回类型来判断。
重写override:也叫做覆盖。 子类重新定义父类中有相同名称和参数的虚函数。函数特征相同。但是具体实现不同,主要是在继承关系中出现的 。
重写需要注意:
1 被重写的函数 不能是static的必须是virtual的
2 重写函数必须有相同的类型,名称和参数列表
3 重写函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写改写为public,protected也是可以的
 

重定义 (redefining)也叫做隐藏:

子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。

如果一个类,存在和父类相同的函数,那么,这个类将会覆盖其父类的方法,除非你在调用的时候,强制转换为父类类型,否则试图对子类和父类做类似重载的调用是不能成功的。 

class Base {
private:
virtual void display() { cout<<"Base display()"<<endl; }
void say(){ cout<<"Base say()"<<endl; }
public:
void exec(){ display(); say(); }
void f1(string a) { cout<<"Base f1(string)"<<endl; }
void f1(int a) { cout<<"Base f1(int)"<<endl; } //overload,两个f1函数在Base类的内部被重载
};

class DeriveA:public Base{
public:
void display() { cout<<"DeriveA display()"<<endl; } //override,基类中display为虚函数,故此处为重写
void f1(int a,int b) { cout<<"DeriveA f1(int,int)"<<endl; } //redefining,f1函数在Base类中不为虚函数,故此处为重定义
void say() { cout<<"DeriveA say()"<<endl; } //redefining,同上
};


class DeriveB:public Base
{
public:
void f1(int a) { cout<<"DeriveB f1(int)"<<endl; } //redefining,重定义
};


int main(){
DeriveA a;
Base *b=&a;
b->exec(); //display():version of DeriveA call(polymorphism) //say():version of Base called(allways )

b里边的函数display被A类覆盖,但是say还是自己的。


a.exec(); //same result as last statement   
a.say();
DeriveB c;
c.f1(1); //version of DeriveB called
}

执行结果:

C++ 重写重载重定义区别 - 漩涡鸣人 - 好记性不如烂笔头---漩涡鸣人
 
 综上所述,总结如下:
1 成员函数重载特征:
   a 相同的范围(在同一个类中)
   b 函数名字相同
   c 参数不同
   d virtual关键字可有可无
2 重写(覆盖)是指派生类函数覆盖基类函数,特征是:
   a 不同的范围,分别位于基类和派生类中
   b 函数的名字相同
   c 参数相同
   d 基类函数必须有virtual关键字
3 重定义(隐藏)是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
   a 如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。
   b 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏。
 
注意区分虚函数中的重载和重写:
class A{
public:
      virtual int fun(){}
};

class B:public A{
       int fun(int a){}  //这是重载而不是重写:
}

int mian()
{

}


class B:public A{
       int fun() // 从A继承来的 fun, 编译器会自己偷偷帮你加上
       int fun(int a){} // 新的fun, 和前面的只是名字一样的重载函数, 不是虚函数
}
/*
函数模板不允许自动类型转化
普通函数能够进行自动类型转换
*/

/*
1 函数模板可以像普通函数一样被重载
2 C++编译器优先考虑普通函数
3 如果函数模板可以产生一个更好的匹配,那么选择模板
4 可以通过空模板实参列表的语法限定编译器只通过模板匹配
*/


#include "iostream"
using namespace std;


int Max(int a, int b)
{
	cout << "int Max(int a, int b)" << endl;
	return a > b ? a : b;
}

template<typename T>
T Max(T a, T b)
{
	cout << "T Max(T a, T b)" << endl;
	return a > b ? a : b;
}

template<typename T>
T Max(T a, T b, T c)
{
	cout << "T Max(T a, T b, T c)" << endl;
	return Max(Max(a, b), c);
}


void main()
{
	int a = 1;
	int b = 2;

	cout << Max(a, b) << endl; //当函数模板和普通函数都符合调用时,优先选择普通函数
	cout << Max<>(a, b) << endl; //若显示使用函数模板,则使用<> 类型列表

	cout << Max(3.0, 4.0) << endl; //如果 函数模板产生更好的匹配 使用函数模板

	cout << Max(5.0, 6.0, 7.0) << endl; //重载

	cout << Max('a', 100) << endl;  //调用普通函数 可以隐式类型转换 
	system("pause");
	return;
}

6.1.6 C++编译器模板机制剖析

思考:为什么函数模板可以和函数重载放在一块。C++编译器是如何提供函数模板机制的?

编译器编译原理

什么是gcc

gcc(GNU C Compiler)编译器的作者是Richard Stallman,也是GNU项目的奠基者。

什么是gcc:gcc是GNU Compiler Collection的缩写。最初是作为C语言的编译器(GNU C Compiler),现在已经支持多种语言了,如C、C++、Java、Pascal、Ada、COBOL语言等

gcc支持多种硬件平台,甚至对Don Knuth 设计的 MMIX 这类不常见的计算机都提供了完善的支持

gcc主要特征

1)gcc是一个可移植的编译器,支持多种硬件平台

2)gcc不仅仅是个本地编译器,它还能跨平台交叉编译。

3)gcc有多种语言前端,用于解析不同的语言。

4)gcc是按模块化设计的,可以加入新语言和新CPU架构的支持

5)gcc是自由软件

gcc编译过程

预处理(Pre-Processing)

编译(Compiling)

汇编(Assembling)

链接(Linking)

Gcc *.c –o 1exe (总的编译步骤)

Gcc –E 1.c –o 1.i  //宏定义 宏展开

Gcc –S 1.i –o 1.s

Gcc –c 1.s –o 1.o

Gcc 1.o –o 1exe

结论:gcc编译工具是一个工具链。。。。

hello程序是一个高级C语言程序,这种形式容易被人读懂。为了在系统上运行hello.c程序,每条C语句都必须转化为低级机器指令。然后将这些指令打包成可执行目标文件格式,并以二进制形式存储器于磁盘中。

gcc常用编译选项

选项

作用

-o

产生目标(.i、.s、.o、可执行文件等)

-c

通知gcc取消链接步骤,即编译源码并在最后生成目标文件

-E

只运行C预编译器

-S

告诉编译器产生汇编语言文件后停止编译,产生的汇编语言文件扩展名为.s

-Wall

使gcc对源文件的代码有问题的地方发出警告

-Idir

将dir目录加入搜索头文件的目录路径

-Ldir

将dir目录加入搜索库的目录路径

-llib

链接lib

-g

在目标文件中嵌入调试信息,以便gdb之类的调试程序调试

练习

gcc -E hello.c -o hello.i(预处理)

gcc -S hello.i -o hello.s(编译)

gcc -c hello.s -o hello.o(汇编)

gcc hello.o -o hello(链接)

以上四个步骤,可合成一个步骤

gcc hello.c -o hello(直接编译链接成可执行目标文件)

gcc -c hello.c或gcc -c hello.c -o hello.o(编译生成可重定位目标文件)

建议初学都加这个选项。下面这个例子如果不加-Wall选项编译器不报任何错误,但是得到的结果却不是预期的。

#include <stdio.h>

int main(void)

{

        printf("2+1is %f", 3);

        return 0;

}

Gcc编译多个.c

hello_1.h

hello_1.c

main.c

一次性编译

gcc  hello_1.c main.c –o newhello

独立编译

gcc -Wall -c main.c -o main.o

gcc -Wall -c hello_1.c -o hello_fn.o

gcc -Wall main.o hello_1.o -o newhello

函数模板机制结论

编译器并不是把函数模板处理成能够处理任意类的函数

编译器从函数模板通过具体类型产生不同的函数

编译器会对函数模板进行两次编译

第一次编译:在声明的地方对模板代码本身进行编译;

第二次编译:在调用的地方对参数替换后的代码进行编译。(这样就不会把函数模板处理成能够处理任意类的函数)

6.2类模板 

6.2.1为什么需要类模板

类模板与函数模板的定义和使用类似,我们已经进行了介绍。有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:

Ø  类模板用于实现类所需数据的类型参数化

Ø  类模板在表示如数组、表、图等数据结构显得特别重要,

这些数据结构的表示和算法不受所包含的元素类型的影响

6.2.2单个类模板语法

6.2.3继承中的类模板语法


//结论: 子类从模板类继承的时候,需要让编译器知道父类的数据类型具体是什么(数据类型的本质:固定大小内存块的别名)A<int>

//

class B : public A<int>

{

public:

         B(int i) : A<int>(i)

         {

         }

         void printB()

         {

                   cout<<"A:"<<t<<endl;

         }

protected:

private:

};

//模板与上继承

//怎么样从基类继承

//若基类只有一个带参数的构造函数,子类是如何启动父类的构造函数

void pintBB(B &b)

{

         b.printB();

}

void printAA(A<int>&a)//类模板做函数参数

{

          //

         a.getT();

}

void main()

{

         A<int>  a(100); //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则

         a.getT();

         printAA(a);

         B b(10);

         b.printB();

         cout<<"hello..."<<endl;

         system("pause");

         return ;

}

#include <iostream>
using namespace std;
//A编程模板 类 
//模板类  类型参数化


//类模板的定义
//类模板的使用
//类模板 做函数参数

//模板类
template <typename T>
class A
{
public:
	A(T a)
	{
		this->a = a;
	}
public:
	void printA()
	{
		cout << "a: " << a << endl;
	}

protected:
	T a;
};

//从模板类 派生了 普通类
// 模板类派生时, 需要具体化模板类. 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;
};


//从模板类 派生 模板类
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 << endl;
	}

private:
	T c;
};
//类模板 做函数参数

//参数 ,C++编译器 要求具体的类 所以所 要 A<int> &a 
void UseA(A<int> &a)
{
	a.printA();
}

void main()
{
	B  b1(1, 2);
	b1.printB();

	C<int> c1(1, 2);
	c1.printC();

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

	UseA(a1);
	UseA(a2);
	UseA(a3);

	system("pause");
}

6.2.4类模板语法知识体系梳理

6.2.4.1所有的类模板函数写在类的内部

//重载+ <<运算符
#include <iostream>
using namespace std;

class Complex
{
	friend ostream& operator << (ostream &out, Complex &obj);
public:
	Complex(int a, int b)
	{
		this->a = a;
		this->b = b;
	}
	//重载+运算符
	Complex operator+(Complex& c2)
	{
		Complex tmp(a+c2.a,b+c2.b);
		return tmp;
	}

	void printCom()
	{
		cout << "a:" << a << " b: " << b << endl;
	}
private:
	int a;
	int b;
};
ostream& operator << (ostream &out, Complex &obj)
{
	out << "obj.a:" << obj.a << "obj.b:" << obj.b << endl;
	return out;
}
void main()
{
	Complex c1(1,2);
	Complex c2(2,4);

	Complex c3 = c1 + c2;
	//c1.operator+(c2);//成员函数
	//Complex operator+(Complex& c2);
	//c3.printCom();
	cout <<  c3 << endl;
	//友元函数 重载<<
	//ostream& operator << (ostream &out, Complex &obj);
	//成员函数
	//这样的话必须拿到ostream类的源码 这样好在这个类里面写一个成员函数 但是实际上拿不到
	//故友元函数用处之一就在此
	//out.operator<<(c3);

	
	system("pause");
}


//重载+ <<运算符
//改成类模板
#include <iostream>
using namespace std;

template <typename T>
class Complex
{
	friend Complex MySub(Complex &obj1, Complex &obj2)
	{
		Complex tmp(obj1.a-obj2.a,obj1.b-obj2.b);
		return tmp;
	}

	friend ostream& operator << (ostream &out, Complex &obj)
	{
		out << "obj.a:" << obj.a << "obj.b:" << obj.b << endl;
		return out;
	}
public:
	Complex(T a, T b)
	{
		this->a = a;
		this->b = b;
	}
	//重载+运算符
	Complex operator+(Complex& c2)
	{
		Complex tmp(a+c2.a,b+c2.b);
		return tmp;
	}

	void printCom()
	{
		cout << "a:" << a << " b: " << b << endl;
	}
private:
	T a;
	T b;
};

//友元函数实现写在类的外部 报错
//	缺少 类模板 "Complex" 的参数列表

//ostream& operator << (ostream &out, Complex &obj)
//{
//	out << "obj.a:" << obj.a << "obj.b:" << obj.b << endl;
//	return out;
//}

void main()
{
	Complex<int> c1(1,2);
	Complex<int>  c2(2, 4);

	Complex<int>  c3 = c1 + c2;
	//c1.operator+(c2);//成员函数
	//Complex operator+(Complex& c2);
	//c3.printCom();
	cout <<  c3 << endl;
	//友元函数 重载<<
	//ostream& operator << (ostream &out, Complex &obj);
	//成员函数
	//这样的话必须拿到ostream类的源码 这样好在这个类里面写一个成员函数 但是实际上拿不到
	//故友元函数用处之一就在此
	//out.operator<<(c3);

	//运算符重载的正规写法 
	// 重载 << >> 只能用友元函数  ,其他运算符重载 都要写成成员函数 , 不要滥用友元函数
	{
		Complex<int> c4 = MySub(c1, c2);
		cout << c4 << endl;

	}
	system("pause");
}

6.2.4.2所有的类模板函数写在类的外部,在一个cpp

#include <iostream>
using namespace std;

//1)需要在类前增加类的前置声明函数的前置声明
template <typename T>
class Complex; //类的前置声明

template <typename T>
Complex<T> MySub(Complex<T> &obj1, Complex<T> &obj2);


template <typename T>
class Complex
{
	//重载<< 运算符
	//友元函数:用友元函数重载<<>>
	//friend ostream& operator<<<T> (ostream &out, Complex<T>&c3) ;
	friend ostream &operator<< <T> (ostream &out, Complex &c3);

	//2)类的内部声明必须写成: 
	friend Complex MySub<T>(Complex &obj1, Complex &obj2);

public:
	Complex(T a, T b);
	void printCom();
	Complex operator+ (Complex &c2);
private:
	T	a;
	T	b;
};

//构造函数的实现写在了类的外部
template <typename T>
Complex<T>::Complex(T a, T b)
{
	this->a = a;
	this->b = b;
}

template <typename T>
void Complex<T>::printCom()
{
	cout << "a:" << a << "b:" << b << endl;
}

//重载+ 运算符
//1.参数 2.函数名 3.返回值
template <typename T>
Complex<T> Complex<T>::operator+(Complex<T> &c2)
{
	Complex tmp(a + c2.a, b + c2.b);
	return tmp;
}

//友元函数 实现 << 运算符重载
template <typename T>
ostream & operator<<(ostream &out, Complex<T> &c3)
{
	out << c3.a << " + " << c3.b << "i" << endl;
	return out;
}
//滥用 友元函数
//3)友元函数实现必须写成:
template <typename T>
Complex<T> MySub(Complex<T> &obj1, Complex<T> &obj2)
{
	//Complex<T> 这个<T>不能少
	Complex<T>  tmp(obj1.a - obj2.a, obj1.b - obj2.b);
	return tmp;
}
void main()
{
	//需要把模板类 进行具体化以后  才能定义对象  C++编译器要分配内存
	Complex<int>	c1(1, 2);
	Complex<int>	c2(3, 4);

	Complex<int> c3 = c1 + c2;
	//c3.printCom();
	cout << c3 << endl;

	//滥用友元函数
	{
		//4)友元函数调用必须写成
		Complex<int> c4 = MySub<int>(c1, c2);
		cout << c4 << endl;

	}



	cout << "hello..." << endl;
	system("pause");
	return;
}

 //构造函数没有问题

 //普通函数没有问题

 //友元函数:用友元函数重载<<>>

//      friend ostream& operator<<<T>(ostream &out, Complex<T>&c3) ;

 //友元函数:友元函数不是实现函数重载(非<<>>)滥用友元函数

 //1)需要在类前增加类的前置声明函数的前置声明

template<typename T>

class Complex; 

template<typenameT>

Complex<T> mySub(Complex<T>&c1, Complex<T>&c2);

    //2)类的内部声明必须写成:

friend Complex<T>mySub <T>(Complex<T>&c1, Complex<T>&c2);

  //3)友元函数实现必须写成:

   template<typenameT>

 Complex<T>mySub(Complex<T>&c1, Complex<T>&c2)

{

                  Complex<T> tmp(c1.a - c2.a,c1.b-c2.b);

                  returntmp;

}

  //4)友元函数调用必须写成

  Complex<int> c4 = mySub<int>(c1,c2);

cout<<c4;

结论:友元函数只用来进行左移友移操作符重载。

6.2.4.3所有的类模板函数写在类的外部,在不同的.h.cpp,

也就是类模板函数说明和类模板实现分开

//类模板函数

构造函数

普通成员函数

友元函数

用友元函数重载<<>>;

用友元函数重载非<<>>

//要包含.cpp

6.2.4.4总结

归纳以上的介绍,可以这样声明和使用类模板:

1) 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。

2) 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。

3) 在类声明前面加入一行,格式为:

   template <class 虚拟类型参数>

如:

   template <class numtype> //注意本行末尾无分号

   class Compare

    {…}; //类体

4) 用类模板定义对象时用以下形式:

类模板名<实际类型名>对象名;

类模板名<实际类型名>对象名(实参表列);

如:

   Compare<int> cmp;

   Compare<int> cmp(3,7);

5) 如果在类模板外定义成员函数,应写成类模板形式:

  template <class 虚拟类型参数>

函数类型类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}

关于类模板的几点说明:

1) 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:

   template <class T1,class T2>

   class someclass

   {…};

在定义对象时分别代入实际的类型名,如:

   someclass<int,double> obj;

2) 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。

3) 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。

结论:友元函数只用来进行左移友移操作符重载。

6.2.5类模板中的static关键字

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

#include <iostream>
using namespace std;

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

private:

};


/*
类模板相当于第一次编译留下编译头,第二次遇到相应类型,在编译所需要类
class AA1
{
public:
static int m_a;
protected:
private:
};
int AA1::m_a  = 0;


class AA2
{
public:
static char m_a;
protected:
private:
};
char AA2::m_a  = 0;
*/
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;

	AA<char> b1, b2, b3;
	b1.m_a = 'a';
	b2.m_a++;
	b2.m_a++;

	cout << AA<char>::m_a << endl;

	cout << "hello..." << endl;
	system("pause");
	return;
}

Ø  从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员

Ø  和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化

Ø  每个模板类有自己的类模板的static数据成员副本




原理图:

6.3类模板在项目开发中的应用

小结

Ø  模板是C++类型参数化的多态工具。C++提供函数模板和类模板。

Ø  模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。

Ø  同一个类属参数可以用于多个模板。

Ø  类属参数可用于函数的参数类型、返回类型和声明函数中的变量。

Ø  模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。

模板称为模板函数;实例化的类模板称为模板类。

Ø  函数模板可以用多种方式重载。

Ø  类模板可以在类层次中使用。

训练题

1)  请设计一个数组模板类(MyVector),完成对int、char、Teacher类型元素的管理。
   需求

设计:

类模板构造函数拷贝构造函数<<[]  重载=操作符  

 a2=a1

                            实现

 

2)  请仔细思考:

a)        如果数组模板类中的元素是Teacher元素时,需要Teacher类做什么工作

b)        如果数组模板类中的元素是Teacher元素时,Teacher类含有指针属性哪?

十分重要的一个例子:

//MyVector.h
#pragma once
#include <iostream>
using namespace std;

template <typename T>
class MyVector
{
	//重载<< 小心<T>
	friend ostream& operator <<<T> (ostream &out, MyVector<T> &obj);
public:
	MyVector(int size);//有参构造函数

	MyVector(const MyVector &obj);//拷贝构造函数

	~MyVector();//析构函数
public:
	//重载[]运算符
	T& operator[](int index);

	//重载=运算符
	MyVector& operator=(const MyVector &obj);
public:
	int getLen()
	{
		return m_len;
	}

protected:
	T *m_space;
	int m_len;
};

////////////////////////////////////////
//MyVector.hpp
#include "MyVector.h"
#include <iostream>
using namespace std;

//重载<< 运算符
template <typename T>
ostream& operator << (ostream &out, MyVector<T> &obj)
{
	for (int i = 0; i < obj.m_len; i++)
	{
		out << obj.m_space[i] << " ";
		//out<<t1 t1是Teacher类对象
		//当MyVector<char> 或者MyVector<int>
		//obj.m_space[i]可以直接就是char类型或者int类型 这个是能直接打印出来的
		//而out<<t1 t1是Teacher类对象  obj.m_space[]==>t1对象 而t1对象是无法打印出来的,所以需要Teacher封装的函数有重载<<运算符函数
	}
	out << endl;
	return out;
}

//有参构造函数
//MyVector<int> myv1(10);
template <typename T>
MyVector<T>::MyVector(int size)
{
	m_space = new T[size];
	m_len = size;
}

//拷贝构造函数
template <typename T>
MyVector<T>::MyVector(const MyVector &obj)
{
	//1 根据对象的大小分配内存
	m_len = obj.m_len;
	m_space = new T[m_len];

	//copy数据
	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;
	}
}


//重载[]运算符
//5) 如果在类模板外定义成员函数,应写成类模板形式:
//template <class 虚拟类型参数>
//函数类型类模板名<虚拟类型参数>::成员函数名(函数形参表列) { … }

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<T> &obj)
{
	//1 释放旧的内存
	if (m_space != NULL)
	{
		delete[] m_space;
		m_space = NULL;
		m_len = 0;
	}
	//2 根据obj分配内存
	m_len = obj.m_len;
	m_space = new T[m_len];

	//3 拷贝数据
	for (int i = 0; i < m_len; i++)
	{
		m_space[i] = obj[i];
	}

	return *this;//返回本身
}
///////////////////////////////////////
//main.cpp
#define _CRT_SECURE_NO_WARNINGS
#include "MyVector.hpp"//类模板会二次编译  可写成hpp
#include <iostream>
using namespace std;

//1  优化Teacher类, 属性变成 char *panme, 内置函数里面 分配内存
//2  优化Teacher类,析构函数 释放panme指向的内存空间
//3  优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数 
//4  优化Teacher类,在Teacher增加 << 
//5  在模板数组类中,存int char Teacher Teacher*(指针类型)


class Teacher
{
	friend ostream &operator<<(ostream &out, const Teacher &obj);
public:
	Teacher()
	{
		age = 33;
		strcpy(name,"");
	}

	Teacher(char *name, int age)
	{
		this->age = age;
		strcpy(this->name,name);
	}
	void printT()
	{
		cout << name << ", " << age << endl;
	}
public:
	int age;
	char name[32];

};
ostream &operator<<(ostream &out, const Teacher &obj)
{
	out << "obj.name:" << obj.name << "obj.age :" <<obj.age << endl;
	return out;
}
//数组模板类(MyVector)完成对int类型元素的管理
void main01()
{
	MyVector<int> myv1(10);

	for (int i = 0; i < 10; i++)
	{
		myv1[i] = i + 1;
		cout << myv1[i] << " ";
	}
	cout << endl;


	MyVector<int> myv2 = myv1;
	for (int i = 0; i < 10; i++)
	{
		cout << myv2[i] << " ";
	}
	cout << endl;
	cout << myv2 << endl;
	//重载<<
	//ostream& operator << (ostream &out, MyVector<T> &obj)


	cout << "hello..." << endl;
	system("pause");
	return;
}
//数组模板类(MyVector)完成对char类型元素的管理
void main02()
{
	MyVector<char> myv1(10);
	myv1[0] = 'a';
	myv1[1] = 'b';
	myv1[2] = 'c';
	myv1[3] = 'd';

	cout << myv1;

	system("pause");
}

//数组模板类(MyVector)完成对Teacher类型元素的管理
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.printT();
	}
	//结论1:如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现//深拷贝和浅拷贝的问题。
	//	结论2:需要Teacher封装的函数有:
	//	1)	重写拷贝构造函数
	//	2)	重载等号操作符
	//	3)	重载左移操作符。

	cout << tArray;



	system("pause");
}
////////////////////////////////////

class Teacher

{

    friend ostream &operator<<(ostream &out, const Teacher &obj);

public:

    Teacher(char *name, int age)

    {

       this->age = age;

       strcpy(this->name, name);

    }

 

    Teacher()

    {

       this->age = 0;

       strcpy(this->name, "");

    }

   

private:

    int age;

    char name[32];

};

class Teacher

{

    friend ostream &operator<<(ostream &out, const Teacher &obj);

public:

    Teacher(char *name, int age)

    {

       this->age = age;

       strcpy(this->name, name);

    }

 

    Teacher()

    {

       this->age = 0;

       strcpy(this->name, "");

    }

   

private:

    int age;

    char*pname;

};


         结论1:如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现深拷贝和浅拷贝的问题。

         结论2:需要Teacher封装的函数有:

1)  重写拷贝构造函数

2)  重载等号操作符

3)  重载左移操作符。

理论提高:所有容器提供的都是值(value)语意,而非引用(reference)语意。容器执行插入元素的操作时,内部实施拷贝动作。所以STL容器内存储的元素必须能够被拷贝(必须提供拷贝构造函数)。

3)  请从数组模板中进行派生      

//演示从模板类派生一般类

#include "MyVector.cpp"

class MyArray01 : public MyVector<double>

{

public:

         MyArray01(int len) : MyVector<double>(len)

         {

                   ;

         }

protected:

private:

};

//演示从模板类派生模板类 //BoundArray

template <typename T>

class MyArray02 : public MyVector<T>

{

public:

         MyArray02(int len) : MyVector<double>(len)

         {

                   ;

         }

protected:

private:

};

测试案例:

//演示从模板类继承模板类

void main()

{

         MyArray02<double> dArray2(10);

         dArray2[1] = 3.15;

}

//演示从模板类继承一般类

void main11()

{

         MyArray01 d_array(10);

         for (int i=0; i<d_array.getLen(); i++)

         {

                   d_array[i] = 3.15;

         }

         for (int i=0; i<d_array.getLen(); i++)

         {

                   cout << d_array[i] << " ";

         }

         cout<<"hello..."<<endl;

         system("pause");

         return ;

}

6.4作业

封装你自己的数组类;设计被存储的元素为类对象;

思考:类对象的类,应该实现的功能。

//1  优化Teacher类, 属性变成 char*panme, 构造函数里面分配内存

//2  优化Teacher类,析构函数释放panme指向的内存空间

//3  优化Teacher类,避免浅拷贝重载= 重写拷贝构造函数

//4  优化Teacher类,在Teacher增加<<

//5  在模板数组类中,存int charTeacher Teacher*(指针类型)

//=====>stl 容器的概念

//MyVector.h
#pragma once
#include <iostream>
using namespace std;

template <typename T>
class MyVector
{
	//重载<< 小心<T>
	friend ostream& operator <<<T> (ostream &out, MyVector<T> &obj);
public:
	MyVector(int size);//有参构造函数

	MyVector(const MyVector &obj);//拷贝构造函数

	~MyVector();//析构函数
public:
	//重载[]运算符
	T& operator[](int index);

	//重载=运算符
	MyVector& operator=(const MyVector &obj);
public:
	int getLen()
	{
		return m_len;
	}

protected:
	T *m_space;
	int m_len;
};

///////////////////////////////////////
//MyVector.hpp
#include "MyVector.h"
#include <iostream>
using namespace std;

//重载<< 运算符
template <typename T>
ostream& operator << (ostream &out, MyVector<T> &obj)
{
	for (int i = 0; i < obj.m_len; i++)
	{
		out << obj.m_space[i] << " ";
		//当MyVector<char> 或者MyVector<int>
		//obj.m_space[i]可以直接就是char类型或者int类型 这个是能直接打印出来的
		//而out<<t1 t1是Teacher类对象  obj.m_space[]==>t1对象 而t1对象是无法打印出来的,所以需要Teacher封装的函数有重载<<运算符函数
	}
	out << endl;
	return out;
}

//有参构造函数
//MyVector<int> myv1(10);
template <typename T>
MyVector<T>::MyVector(int size)
{
	m_space = new T[size];
	m_len = size;
}

//拷贝构造函数
template <typename T>
MyVector<T>::MyVector(const MyVector &obj)
{
	//1 根据对象的大小分配内存
	m_len = obj.m_len;
	m_space = new T[m_len];

	//copy数据
	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;
	}
}


//重载[]运算符
//5) 如果在类模板外定义成员函数,应写成类模板形式:
//template <class 虚拟类型参数>
//函数类型类模板名<虚拟类型参数>::成员函数名(函数形参表列) { … }

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<T> &obj)
{
	//1 释放旧的内存
	if (m_space != NULL)
	{
		delete[] m_space;
		m_space = NULL;
		m_len = 0;
	}
	//2 根据obj分配内存
	m_len = obj.m_len;
	m_space = new T[m_len];

	//3 拷贝数据
	for (int i = 0; i < m_len; i++)
	{
		m_space[i] = obj[i];
	}

	return *this;//返回本身
}
//////////////////////////////////////
//main.cpp
#define _CRT_SECURE_NO_WARNINGS
#include "MyVector.hpp"//类模板会二次编译  可写成hpp
#include <iostream>
using namespace std;

//1  优化Teacher类, 属性变成 char *panme, 内置函数里面 分配内存
//2  优化Teacher类,析构函数 释放panme指向的内存空间
//3  优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数 
//4  优化Teacher类,在Teacher增加 << 
//5  在模板数组类中,存int char Teacher Teacher*(指针类型)

//结论1:如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现//深拷贝和浅拷贝的问题。
//	结论2:需要Teacher封装的函数有:
//	1)	重写拷贝构造函数
//	2)	重载等号操作符
//	3)	重载左移操作符。
class Teacher
{
	//	3)	重载左移操作符
	friend ostream &operator<<(ostream &out, const Teacher &obj)
	{
		out << "obj.m_pName:" << obj.m_pName << "obj.age :" << obj.age << endl;
		return out;
	}
public:
	Teacher()
	{
		age = 33;
		m_pName = new char[1];
		strcpy(m_pName, "");
	}

	Teacher(char *name, int age)
	{
		this->age = age;
		//根据name分配内存大小
		m_pName = new char[strlen(name) + 1];
		strcpy(m_pName, name);
	}

	~Teacher()
	{
		if (m_pName != NULL)
		{
			delete[] m_pName;
			m_pName = NULL;
			age = 0;
		}
	}
	void printT()
	{
		cout << m_pName << ", " << age << endl;
	}
public:
	//	1)	重写拷贝构造函数
	Teacher(const Teacher &obj)
	{
		m_pName = new char[strlen(obj.m_pName) + 1];
		strcpy(m_pName, obj.m_pName);
	}
	//	2)	重载等号操作符
	//t1=t2=t3
	Teacher& operator = (const Teacher &obj)
	{
		//1 释放旧的内存空间
		if (m_pName != NULL)
		{
			delete[] m_pName;
			m_pName = NULL;
			age = 0;
		}
		//2 根据obj分配内存大小
		m_pName = new char[strlen(obj.m_pName) + 1];

		//3 进行copy
		strcpy(m_pName,obj.m_pName);
		age = obj.age;

		return *this;
	}

public:
	int age;
	//char name[32];
	char *m_pName;
};

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->printT();
	}
	//cout << tArray; 这里打印出来是存储Teacher类型的地址 不知道如何让他直接打印出来
	//感觉这里的Teacher重载<<根本没用上啊
	cout << "hello..." << endl;
	system("pause");
	return;
}
//数组模板类(MyVector)完成对int类型元素的管理
void main01()
{
	MyVector<int> myv1(10);

	for (int i = 0; i < 10; i++)
	{
		myv1[i] = i + 1;
		cout << myv1[i] << " ";
	}
	cout << endl;


	MyVector<int> myv2 = myv1;
	for (int i = 0; i < 10; i++)
	{
		cout << myv2[i] << " ";
	}
	cout << endl;
	cout << myv2 << endl;
	//重载<<
	//ostream& operator << (ostream &out, MyVector<T> &obj)


	cout << "hello..." << endl;
	system("pause");
	return;
}
//数组模板类(MyVector)完成对char类型元素的管理
void main02()
{
	MyVector<char> myv1(10);
	myv1[0] = 'a';
	myv1[1] = 'b';
	myv1[2] = 'c';
	myv1[3] = 'd';

	cout << myv1;

	system("pause");
}

//数组模板类(MyVector)完成对Teacher类型元素的管理
void main03()
{
	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.printT();
	}
	//结论1:如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现//深拷贝和浅拷贝的问题。
	//	结论2:需要Teacher封装的函数有:
	//	1)	重写拷贝构造函数
	//	2)	重载等号操作符
	//	3)	重载左移操作符。

	cout << tArray;



	system("pause");
}
/////////////////////////////////


猜你喜欢

转载自blog.csdn.net/weixin_40807247/article/details/80992338