函数模板和类模板
前言
C++提供了函数模板(functiontemplate)。所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。
1)C++提供两种模板机制:函数模板、类模板
2)类属—— 类型参数化,又称参数模板
使得程序(算法)可以从逻辑功能上抽象,把被处理的对象(数据)类型作为参数传递。
总结:
Ø 模板把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。
Ø 模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。
6.1函数模板
6.1.1为什么要有函数模板
#include
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
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(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(a, b); //1 函数模板 显示类型 调用
printf("a:%c b:%c \n", a, b);
}
6.1.2函数模板语法
函数模板定义形式
template <类型形式参数表>
类型形式参数的形式为:
typenameT1, typename T2 ,…… , typename Tn
或 class T1 , class T2 , …… , classTn
函数模板调用
myswap(a,b); //显示类型调用
myswap(a, b); //自动数据类型推导
6.1.3函数模板和模板函数
6.1.4函数模板做函数参数
#include
using namespace std;
template
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
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(myarray, size); //显示类型调用
printf("排序之后\n");
myPrint(myarray, size);
system("pause");
return 0;
}
6.1.5函数模板遇上函数重载
函数模板和普通函数区别结论:
/*
函数模板不允许自动类型转化
普通函数能够进行自动类型转换
*/
函数模板和普通函数在一起,调用规则:
/*
1函数模板可以像普通函数一样被重载
2C++编译器优先考虑普通函数
3如果函数模板可以产生一个更好的匹配,那么选择模板
4可以通过空模板实参列表的语法限定编译器只通过模板匹配
*/
#include
using namespace std;
//让 类型参数化 ===, 方便程序员进行编码
// 泛型编程
//template 告诉C++编译器 我要开始泛型编程了 .看到T, 不要随便报错
template
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()"<void say(){ cout<<"Base say()"<public:
void exec(){ display(); say(); }
void f1(string a) { cout<<"Base f1(string)"<void f1(int a) { cout<<"Base f1(int)"<//overload,两个f1函数在Base类的内部被重载
};
class DeriveA:public Base{
public:
void display() { cout<<"DeriveA display()"<//override,基类中display为虚函数,故此处为重写
void f1(int a,int b) { cout<<"DeriveA f1(int,int)"<//redefining,f1函数在Base类中不为虚函数,故此处为重定义
void say() { cout<<"DeriveA say()"<//redefining,同上
};
class DeriveB:public Base
{
public:
void f1(int a) { cout<<"DeriveB f1(int)"<//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
}
执行结果:
综上所述,总结如下:
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
T Max(T a, T b)
{
cout << "T Max(T a, T b)" << endl;
return a > b ? a : b;
}
template
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 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 // class B : public A { public: B(int i) : A(i) { } void printB() { cout<<"A:"< } protected: private: }; //模板与上继承 //怎么样从基类继承 //若基类只有一个带参数的构造函数,子类是如何启动父类的构造函数 void pintBB(B &b) { b.printB(); } void printAA(A&a)//类模板做函数参数 { // a.getT(); } void main() { A a(100); //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则 a.getT(); printAA(a); B b(10); b.printB(); cout<<"hello..."< system("pause"); return ; } |
#include
using namespace std;
//A编程模板 类
//模板类 类型参数化
//类模板的定义
//类模板的使用
//类模板 做函数参数
//模板类
template
class A
{
public:
A(T a)
{
this->a = a;
}
public:
void printA()
{
cout << "a: " << a << endl;
}
protected:
T a;
};
//从模板类 派生了 普通类
// 模板类派生时, 需要具体化模板类. 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;
};
//从模板类 派生 模板类
template
class C:public A
{
public:
C(T c, T a) :A(a)
{
this->c = c;
}
void printC()
{
cout << "c:" << c << endl;
}
private:
T c;
};
//类模板 做函数参数
//参数 ,C++编译器 要求具体的类 所以所 要 A &a
void UseA(A &a)
{
a.printA();
}
void main()
{
B b1(1, 2);
b1.printB();
C c1(1, 2);
c1.printC();
//模板类(本身就是类型化的)====具体的类=====>定义具体的变量
A a1(11), a2(20), a3(30); //模板类是抽象的 ====>需要进行 类型具体
a1.printA();
UseA(a1);
UseA(a2);
UseA(a3);
system("pause");
}
6.2.4类模板语法知识体系梳理
6.2.4.1所有的类模板函数写在类的内部
//重载+ <<运算符
#include
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
using namespace std;
template
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 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);
//运算符重载的正规写法
// 重载 << >> 只能用友元函数 ,其他运算符重载 都要写成成员函数 , 不要滥用友元函数
{
Complex c4 = MySub(c1, c2);
cout << c4 << endl;
}
system("pause");
}
6.2.4.2所有的类模板函数写在类的外部,在一个cpp中
#include
using namespace std;
//1)需要在类前增加类的前置声明函数的前置声明
template
class Complex; //类的前置声明
template
Complex MySub(Complex &obj1, Complex &obj2);
template
class Complex
{
//重载<< 运算符
//友元函数:用友元函数重载<<>>
//friend ostream& operator<< (ostream &out, Complex&c3) ;
friend ostream &operator<< (ostream &out, Complex &c3);
//2)类的内部声明必须写成:
friend Complex MySub(Complex &obj1, Complex &obj2);
public:
Complex(T a, T b);
void printCom();
Complex operator+ (Complex &c2);
private:
T a;
T b;
};
//构造函数的实现写在了类的外部
template
Complex::Complex(T a, T b)
{
this->a = a;
this->b = b;
}
template
void Complex::printCom()
{
cout << "a:" << a << "b:" << b << endl;
}
//重载+ 运算符
//1.参数 2.函数名 3.返回值
template
Complex Complex::operator+(Complex &c2)
{
Complex tmp(a + c2.a, b + c2.b);
return tmp;
}
//友元函数 实现 << 运算符重载
template
ostream & operator<<(ostream &out, Complex &c3)
{
out << c3.a << " + " << c3.b << "i" << endl;
return out;
}
//滥用 友元函数
//3)友元函数实现必须写成:
template
Complex MySub(Complex &obj1, Complex &obj2)
{
//Complex 这个不能少
Complex tmp(obj1.a - obj2.a, obj1.b - obj2.b);
return tmp;
}
void main()
{
//需要把模板类 进行具体化以后 才能定义对象 C++编译器要分配内存
Complex c1(1, 2);
Complex c2(3, 4);
Complex c3 = c1 + c2;
//c3.printCom();
cout << c3 << endl;
//滥用友元函数
{
//4)友元函数调用必须写成
Complex c4 = MySub(c1, c2);
cout << c4 << endl;
}
cout << "hello..." << endl;
system("pause");
return;
}
//构造函数没有问题
//普通函数没有问题
//友元函数:用友元函数重载<<>>
// friend ostream& operator<<(ostream &out, Complex&c3) ;
//友元函数:友元函数不是实现函数重载(非<<>>)滥用友元函数
//1)需要在类前增加类的前置声明函数的前置声明
template<typename T>
class Complex;
template<typenameT>
Complex mySub(Complex&c1, Complex&c2);
//2)类的内部声明必须写成:
friend ComplexmySub (Complex&c1, Complex&c2);
//3)友元函数实现必须写成:
template<typenameT>
ComplexmySub(Complex&c1, Complex&c2)
{
Complex tmp(c1.a - c2.a,c1.b-c2.b);
returntmp;
}
//4)友元函数调用必须写成
Complex<int> c4 = mySub(c1,c2);
cout<
结论:友元函数只用来进行左移友移操作符重载。
6.2.4.3所有的类模板函数写在类的外部,在不同的.h和.cpp中,
也就是类模板函数说明和类模板实现分开
//类模板函数
构造函数
普通成员函数
友元函数
用友元函数重载<<>>;
用友元函数重载非<<>>
//要包含.cpp
6.2.4.4总结
归纳以上的介绍,可以这样声明和使用类模板:
1) 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
2) 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。
3) 在类声明前面加入一行,格式为:
template
如:
template //注意本行末尾无分号
class Compare
{…}; //类体
4) 用类模板定义对象时用以下形式:
类模板名<实际类型名>对象名;
类模板名<实际类型名>对象名(实参表列);
如:
Compare cmp;
Compare cmp(3,7);
5) 如果在类模板外定义成员函数,应写成类模板形式:
template
函数类型类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
关于类模板的几点说明:
1) 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:
template
class someclass
{…};
在定义对象时分别代入实际的类型名,如:
someclass obj;
2) 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。
3) 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。
结论:友元函数只用来进行左移友移操作符重载。
6.2.5类模板中的static关键字
/*
dm10_类模板中的static关键字
编译器并不是把函数模板处理成能够处理任意类的函数
编译器从函数模板通过具体类型产生不同的函数
编译器会对函数模板进行两次编译
在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。
*/
#include
using namespace std;
template
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
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;
AA b1, b2, b3;
b1.m_a = 'a';
b2.m_a++;
b2.m_a++;
cout << AA::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
using namespace std;
template
class MyVector
{
//重载<< 小心
friend ostream& operator << (ostream &out, MyVector &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
using namespace std;
//重载<< 运算符
template
ostream& operator << (ostream &out, MyVector &obj)
{
for (int i = 0; i < obj.m_len; i++)
{
out << obj.m_space[i] << " ";
//out<
//当MyVector 或者MyVector
//obj.m_space[i]可以直接就是char类型或者int类型 这个是能直接打印出来的
//而out<t1对象 而t1对象是无法打印出来的,所以需要Teacher封装的函数有重载<<运算符函数
}
out << endl;
return out;
}
//有参构造函数
//MyVector myv1(10);
template
MyVector::MyVector(int size)
{
m_space = new T[size];
m_len = size;
}
//拷贝构造函数
template
MyVector::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
MyVector::~MyVector()
{
if (m_space !=NULL)
{
delete[] m_space;
m_space = NULL;
m_len = 0;
}
}
//重载[]运算符
//5) 如果在类模板外定义成员函数,应写成类模板形式:
//template
//函数类型类模板名<虚拟类型参数>::成员函数名(函数形参表列) { … }
template
T& MyVector::operator[] (int index)
{
return m_space[index];
}
//重载=运算符
// a3 = a2 = a1;
template
MyVector& MyVector::operator=(const MyVector &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
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 :" < myv1(10);
for (int i = 0; i < 10; i++)
{
myv1[i] = i + 1;
cout << myv1[i] << " ";
}
cout << endl;
MyVector myv2 = myv1;
for (int i = 0; i < 10; i++)
{
cout << myv2[i] << " ";
}
cout << endl;
cout << myv2 << endl;
//重载<<
//ostream& operator << (ostream &out, MyVector &obj)
cout << "hello..." << endl;
system("pause");
return;
}
//数组模板类(MyVector)完成对char类型元素的管理
void main02()
{
MyVector 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 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 { public: MyArray01(int len) : MyVector(len) { ; } protected: private: }; //演示从模板类派生模板类 //BoundArray template class MyArray02 : public MyVector { public: MyArray02(int len) : MyVector(len) { ; } protected: private: }; |
测试案例: //演示从模板类继承模板类 void main() { MyArray02 dArray2(10); dArray2[1] = 3.15; } //演示从模板类继承一般类 void main11() { MyArray01 d_array(10); for (int i=0; i { d_array[i] = 3.15; } for (int i=0; i { cout << d_array[i] << " "; } cout<<"hello..."< 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
using namespace std;
template
class MyVector
{
//重载<< 小心
friend ostream& operator << (ostream &out, MyVector &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
using namespace std;
//重载<< 运算符
template
ostream& operator << (ostream &out, MyVector &obj)
{
for (int i = 0; i < obj.m_len; i++)
{
out << obj.m_space[i] << " ";
//当MyVector 或者MyVector
//obj.m_space[i]可以直接就是char类型或者int类型 这个是能直接打印出来的
//而out<t1对象 而t1对象是无法打印出来的,所以需要Teacher封装的函数有重载<<运算符函数
}
out << endl;
return out;
}
//有参构造函数
//MyVector myv1(10);
template
MyVector::MyVector(int size)
{
m_space = new T[size];
m_len = size;
}
//拷贝构造函数
template
MyVector::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
MyVector::~MyVector()
{
if (m_space !=NULL)
{
delete[] m_space;
m_space = NULL;
m_len = 0;
}
}
//重载[]运算符
//5) 如果在类模板外定义成员函数,应写成类模板形式:
//template
//函数类型类模板名<虚拟类型参数>::成员函数名(函数形参表列) { … }
template
T& MyVector::operator[] (int index)
{
return m_space[index];
}
//重载=运算符
// a3 = a2 = a1;
template
MyVector& MyVector::operator=(const MyVector &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
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 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 myv1(10);
for (int i = 0; i < 10; i++)
{
myv1[i] = i + 1;
cout << myv1[i] << " ";
}
cout << endl;
MyVector myv2 = myv1;
for (int i = 0; i < 10; i++)
{
cout << myv2[i] << " ";
}
cout << endl;
cout << myv2 << endl;
//重载<<
//ostream& operator << (ostream &out, MyVector &obj)
cout << "hello..." << endl;
system("pause");
return;
}
//数组模板类(MyVector)完成对char类型元素的管理
void main02()
{
MyVector 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 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");
}
/////////////////////////////////