6、 函数模板和类模板

﹏ヽ暗。殇╰゛Y 2022-05-20 02:13 366阅读 0赞

函数模板和类模板

前言

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 <类型形式参数表> 类型形式参数的形式为: typenameT1, typename T2 ,…… , typename Tn 或 class T1 , class T2 , …… , classTn

函数模板调用

  1. myswap<float>(a,b); ** //显示类型调用**
  2. myswap(a, b); **//自动数据类型推导**

6.1.3函数模板和模板函数

70

6.1.4函数模板做函数参数

  1. #include <iostream>
  2. using namespace std;
  3. template <typename T>
  4. int mySort(T *array, T size)
  5. {
  6. T i, j;
  7. T tmp;
  8. if (NULL == array)
  9. {
  10. return -1;
  11. }
  12. //冒泡排序
  13. for ( i = 0; i < size; i++)
  14. {
  15. for (j = i + 1; j < size; j++)
  16. {
  17. if (array[i] < array[j])//从大到小
  18. {
  19. tmp = array[i];
  20. array[i] = array[j];
  21. array[j] = tmp;
  22. }
  23. }
  24. }
  25. return 0;
  26. }
  27. template <typename T>
  28. int myPrint(T *array, T size)
  29. {
  30. T i = 0;
  31. for ( i = 0; i < size; i++)
  32. {
  33. cout << array[i] << " ";
  34. }
  35. return 0;
  36. }
  37. int main()
  38. {
  39. //int 类型
  40. int myarray[] = { 11, 33, 44, 33, 22, 2, 3, 6, 9 };
  41. int size = sizeof(myarray) / sizeof(*myarray);
  42. mySort<int>(myarray, size); //显示类型调用
  43. printf("排序之后\n");
  44. myPrint<int>(myarray, size);
  45. system("pause");
  46. return 0;
  47. }

6.1.5函数模板遇上函数重载

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

/*

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

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

*/

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

/*

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

*/

  1. #include <iostream>
  2. using namespace std;
  3. //让 类型参数化 ===, 方便程序员进行编码
  4. // 泛型编程
  5. //template 告诉C++编译器 我要开始泛型编程了 .看到T, 不要随便报错
  6. template <typename T>
  7. void myswap(T &a, T &b)//函数要求两个形参的类型 严格的要求类型匹配
  8. {
  9. T c = a;
  10. a = b;
  11. b = c;
  12. cout << "hello ....我是模板函数 欢迎 calll 我" << endl;
  13. }
  14. void myswap(int a, char c)
  15. {
  16. cout << "a:" << a << "c:" << c << endl;
  17. cout << "我是普通函数 欢迎来访" << endl;
  18. }
  19. //函数模板和普通函数区别结论:
  20. //函数模板 不允许自动类型转化
  21. //普通函数 能够进行自动类型转换
  22. //函数模板和普通函数在一起,调用规则:
  23. //1 函数模板可以像普通函数一样被重载
  24. //2 C++编译器优先考虑普通函数
  25. //3 如果函数模板可以产生一个更好的匹配,那么选择模板
  26. //4 可以通过空模板实参列表的语法限定编译器只通过模板匹配
  27. int main()
  28. {
  29. int a = 10;
  30. char c = 'c';
  31. myswap(a, c); // 普通函数的调用: 可以进行隐式的类型转换
  32. myswap(c, a); //普通函数的调用: 可以进行隐式的类型转换
  33. myswap(a, a); // 函数模板函数的调用(本质:类型参数化): 将严格的按照类型进行匹配,不会进行自动类型转换
  34. system("pause");
  35. return 0;
  36. }

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关键字,此时,基类的函数被隐藏。

注意区分虚函数中的重载和重写:

  1. class A{
  2. public:
  3. virtual int fun(){}
  4. };
  5. class B:public A{
  6. int fun(int a){} //这是重载而不是重写:
  7. }
  8. int mian()
  9. {
  10. }
  11. class B:public A{
  12. int fun() // 从A继承来的 fun, 编译器会自己偷偷帮你加上
  13. int fun(int a){} // 新的fun, 和前面的只是名字一样的重载函数, 不是虚函数
  14. }
  15. /*
  16. 函数模板不允许自动类型转化
  17. 普通函数能够进行自动类型转换
  18. */
  19. /*
  20. 1 函数模板可以像普通函数一样被重载
  21. 2 C++编译器优先考虑普通函数
  22. 3 如果函数模板可以产生一个更好的匹配,那么选择模板
  23. 4 可以通过空模板实参列表的语法限定编译器只通过模板匹配
  24. */
  25. #include "iostream"
  26. using namespace std;
  27. int Max(int a, int b)
  28. {
  29. cout << "int Max(int a, int b)" << endl;
  30. return a > b ? a : b;
  31. }
  32. template<typename T>
  33. T Max(T a, T b)
  34. {
  35. cout << "T Max(T a, T b)" << endl;
  36. return a > b ? a : b;
  37. }
  38. template<typename T>
  39. T Max(T a, T b, T c)
  40. {
  41. cout << "T Max(T a, T b, T c)" << endl;
  42. return Max(Max(a, b), c);
  43. }
  44. void main()
  45. {
  46. int a = 1;
  47. int b = 2;
  48. cout << Max(a, b) << endl; //当函数模板和普通函数都符合调用时,优先选择普通函数
  49. cout << Max<>(a, b) << endl; //若显示使用函数模板,则使用<> 类型列表
  50. cout << Max(3.0, 4.0) << endl; //如果 函数模板产生更好的匹配 使用函数模板
  51. cout << Max(5.0, 6.0, 7.0) << endl; //重载
  52. cout << Max('a', 100) << endl; //调用普通函数 可以隐式类型转换
  53. system("pause");
  54. return;
  55. }

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为什么需要类模板

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

70 1

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

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

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

6.2.2单个类模板语法

6.2.3继承中的类模板语法

" class="reference-link">70 2







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

}

  1. #include <iostream>
  2. using namespace std;
  3. //A编程模板 类
  4. //模板类 类型参数化
  5. //类模板的定义
  6. //类模板的使用
  7. //类模板 做函数参数
  8. //模板类
  9. template <typename T>
  10. class A
  11. {
  12. public:
  13. A(T a)
  14. {
  15. this->a = a;
  16. }
  17. public:
  18. void printA()
  19. {
  20. cout << "a: " << a << endl;
  21. }
  22. protected:
  23. T a;
  24. };
  25. //从模板类 派生了 普通类
  26. // 模板类派生时, 需要具体化模板类. C++编译器需要知道 父类的数据类型具体是什么样子的
  27. //=====> 要知道父类所占的内存大小是多少 只有数据类型固定下来,才知道如何分配内存
  28. class B:public A<int>
  29. {
  30. public:
  31. B(int a = 10, int b = 20) :A<int>(a)
  32. {
  33. this->b = b;
  34. }
  35. void printB()
  36. {
  37. cout << "a:" << a << " b: " << b << endl;
  38. }
  39. private:
  40. int b;
  41. };
  42. //从模板类 派生 模板类
  43. template <typename T>
  44. class C:public A<T>
  45. {
  46. public:
  47. C(T c, T a) :A<T>(a)
  48. {
  49. this->c = c;
  50. }
  51. void printC()
  52. {
  53. cout << "c:" << c << endl;
  54. }
  55. private:
  56. T c;
  57. };
  58. //类模板 做函数参数
  59. //参数 ,C++编译器 要求具体的类 所以所 要 A<int> &a
  60. void UseA(A<int> &a)
  61. {
  62. a.printA();
  63. }
  64. void main()
  65. {
  66. B b1(1, 2);
  67. b1.printB();
  68. C<int> c1(1, 2);
  69. c1.printC();
  70. //模板类(本身就是类型化的)====具体的类=====>定义具体的变量
  71. A<int> a1(11), a2(20), a3(30); //模板类是抽象的 ====>需要进行 类型具体
  72. a1.printA();
  73. UseA(a1);
  74. UseA(a2);
  75. UseA(a3);
  76. system("pause");
  77. }

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

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

  1. //重载+ <<运算符
  2. #include <iostream>
  3. using namespace std;
  4. class Complex
  5. {
  6. friend ostream& operator << (ostream &out, Complex &obj);
  7. public:
  8. Complex(int a, int b)
  9. {
  10. this->a = a;
  11. this->b = b;
  12. }
  13. //重载+运算符
  14. Complex operator+(Complex& c2)
  15. {
  16. Complex tmp(a+c2.a,b+c2.b);
  17. return tmp;
  18. }
  19. void printCom()
  20. {
  21. cout << "a:" << a << " b: " << b << endl;
  22. }
  23. private:
  24. int a;
  25. int b;
  26. };
  27. ostream& operator << (ostream &out, Complex &obj)
  28. {
  29. out << "obj.a:" << obj.a << "obj.b:" << obj.b << endl;
  30. return out;
  31. }
  32. void main()
  33. {
  34. Complex c1(1,2);
  35. Complex c2(2,4);
  36. Complex c3 = c1 + c2;
  37. //c1.operator+(c2);//成员函数
  38. //Complex operator+(Complex& c2);
  39. //c3.printCom();
  40. cout << c3 << endl;
  41. //友元函数 重载<<
  42. //ostream& operator << (ostream &out, Complex &obj);
  43. //成员函数
  44. //这样的话必须拿到ostream类的源码 这样好在这个类里面写一个成员函数 但是实际上拿不到
  45. //故友元函数用处之一就在此
  46. //out.operator<<(c3);
  47. system("pause");
  48. }
  49. //重载+ <<运算符
  50. //改成类模板
  51. #include <iostream>
  52. using namespace std;
  53. template <typename T>
  54. class Complex
  55. {
  56. friend Complex MySub(Complex &obj1, Complex &obj2)
  57. {
  58. Complex tmp(obj1.a-obj2.a,obj1.b-obj2.b);
  59. return tmp;
  60. }
  61. friend ostream& operator << (ostream &out, Complex &obj)
  62. {
  63. out << "obj.a:" << obj.a << "obj.b:" << obj.b << endl;
  64. return out;
  65. }
  66. public:
  67. Complex(T a, T b)
  68. {
  69. this->a = a;
  70. this->b = b;
  71. }
  72. //重载+运算符
  73. Complex operator+(Complex& c2)
  74. {
  75. Complex tmp(a+c2.a,b+c2.b);
  76. return tmp;
  77. }
  78. void printCom()
  79. {
  80. cout << "a:" << a << " b: " << b << endl;
  81. }
  82. private:
  83. T a;
  84. T b;
  85. };
  86. //友元函数实现写在类的外部 报错
  87. // 缺少 类模板 "Complex" 的参数列表
  88. //ostream& operator << (ostream &out, Complex &obj)
  89. //{
  90. // out << "obj.a:" << obj.a << "obj.b:" << obj.b << endl;
  91. // return out;
  92. //}
  93. void main()
  94. {
  95. Complex<int> c1(1,2);
  96. Complex<int> c2(2, 4);
  97. Complex<int> c3 = c1 + c2;
  98. //c1.operator+(c2);//成员函数
  99. //Complex operator+(Complex& c2);
  100. //c3.printCom();
  101. cout << c3 << endl;
  102. //友元函数 重载<<
  103. //ostream& operator << (ostream &out, Complex &obj);
  104. //成员函数
  105. //这样的话必须拿到ostream类的源码 这样好在这个类里面写一个成员函数 但是实际上拿不到
  106. //故友元函数用处之一就在此
  107. //out.operator<<(c3);
  108. //运算符重载的正规写法
  109. // 重载 << >> 只能用友元函数 ,其他运算符重载 都要写成成员函数 , 不要滥用友元函数
  110. {
  111. Complex<int> c4 = MySub(c1, c2);
  112. cout << c4 << endl;
  113. }
  114. system("pause");
  115. }

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

  1. #include <iostream>
  2. using namespace std;
  3. //1)需要在类前增加类的前置声明函数的前置声明
  4. template <typename T>
  5. class Complex; //类的前置声明
  6. template <typename T>
  7. Complex<T> MySub(Complex<T> &obj1, Complex<T> &obj2);
  8. template <typename T>
  9. class Complex
  10. {
  11. //重载<< 运算符
  12. //友元函数:用友元函数重载<<>>
  13. //friend ostream& operator<<<T> (ostream &out, Complex<T>&c3) ;
  14. friend ostream &operator<< <T> (ostream &out, Complex &c3);
  15. //2)类的内部声明必须写成:
  16. friend Complex MySub<T>(Complex &obj1, Complex &obj2);
  17. public:
  18. Complex(T a, T b);
  19. void printCom();
  20. Complex operator+ (Complex &c2);
  21. private:
  22. T a;
  23. T b;
  24. };
  25. //构造函数的实现写在了类的外部
  26. template <typename T>
  27. Complex<T>::Complex(T a, T b)
  28. {
  29. this->a = a;
  30. this->b = b;
  31. }
  32. template <typename T>
  33. void Complex<T>::printCom()
  34. {
  35. cout << "a:" << a << "b:" << b << endl;
  36. }
  37. //重载+ 运算符
  38. //1.参数 2.函数名 3.返回值
  39. template <typename T>
  40. Complex<T> Complex<T>::operator+(Complex<T> &c2)
  41. {
  42. Complex tmp(a + c2.a, b + c2.b);
  43. return tmp;
  44. }
  45. //友元函数 实现 << 运算符重载
  46. template <typename T>
  47. ostream & operator<<(ostream &out, Complex<T> &c3)
  48. {
  49. out << c3.a << " + " << c3.b << "i" << endl;
  50. return out;
  51. }
  52. //滥用 友元函数
  53. //3)友元函数实现必须写成:
  54. template <typename T>
  55. Complex<T> MySub(Complex<T> &obj1, Complex<T> &obj2)
  56. {
  57. //Complex<T> 这个<T>不能少
  58. Complex<T> tmp(obj1.a - obj2.a, obj1.b - obj2.b);
  59. return tmp;
  60. }
  61. void main()
  62. {
  63. //需要把模板类 进行具体化以后 才能定义对象 C++编译器要分配内存
  64. Complex<int> c1(1, 2);
  65. Complex<int> c2(3, 4);
  66. Complex<int> c3 = c1 + c2;
  67. //c3.printCom();
  68. cout << c3 << endl;
  69. //滥用友元函数
  70. {
  71. //4)友元函数调用必须写成
  72. Complex<int> c4 = MySub<int>(c1, c2);
  73. cout << c4 << endl;
  74. }
  75. cout << "hello..." << endl;
  76. system("pause");
  77. return;
  78. }

//构造函数没有问题

//普通函数没有问题

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

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

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

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

template

class Complex;

template

Complex mySub(Complex&c1, Complex&c2);

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

friend ComplexmySub (Complex&c1, Complex&c2);

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

template

ComplexmySub(Complex&c1, Complex&c2)

{

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

}

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

Complex c4 = mySub(c1,c2);

cout<<c4;

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

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

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

//类模板函数

构造函数

普通成员函数

友元函数

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

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

//要包含.cpp

6.2.4.4总结

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

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

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

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

template

如:

template //注意本行末尾无分号

class Compare

  1. \{\}; //类体

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

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

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

如:

Compare cmp;

Compare cmp(3,7);

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

template

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

关于类模板的几点说明:

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

template

class someclass

{…};

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

someclass obj;

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

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

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

6.2.5类模板中的static关键字

  1. /*
  2. dm10_类模板中的static关键字
  3. 编译器并不是把函数模板处理成能够处理任意类的函数
  4. 编译器从函数模板通过具体类型产生不同的函数
  5. 编译器会对函数模板进行两次编译
  6. 在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。
  7. */
  8. #include <iostream>
  9. using namespace std;
  10. template <typename T>
  11. class AA
  12. {
  13. public:
  14. static T m_a;
  15. private:
  16. };
  17. /*
  18. 类模板相当于第一次编译留下编译头,第二次遇到相应类型,在编译所需要类
  19. class AA1
  20. {
  21. public:
  22. static int m_a;
  23. protected:
  24. private:
  25. };
  26. int AA1::m_a = 0;
  27. class AA2
  28. {
  29. public:
  30. static char m_a;
  31. protected:
  32. private:
  33. };
  34. char AA2::m_a = 0;
  35. */
  36. template <typename T>
  37. T AA<T>::m_a = 0;//静态变量的初始化
  38. void main()
  39. {
  40. AA<int> a1, a2, a3;
  41. a1.m_a = 10;
  42. a2.m_a++;
  43. a3.m_a++;
  44. cout << AA<int>::m_a << endl;
  45. AA<char> b1, b2, b3;
  46. b1.m_a = 'a';
  47. b2.m_a++;
  48. b2.m_a++;
  49. cout << AA<char>::m_a << endl;
  50. cout << "hello..." << endl;
  51. system("pause");
  52. return;
  53. }

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

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

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

70 3

70 4 70 5
70 6

原理图:

70 7

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

小结

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

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

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

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

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

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

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

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

训练题

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

设计:

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

a2=a1

  1. 实现

2) 请仔细思考:

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

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

十分重要的一个例子:

  1. //MyVector.h
  2. #pragma once
  3. #include <iostream>
  4. using namespace std;
  5. template <typename T>
  6. class MyVector
  7. {
  8. //重载<< 小心<T>
  9. friend ostream& operator <<<T> (ostream &out, MyVector<T> &obj);
  10. public:
  11. MyVector(int size);//有参构造函数
  12. MyVector(const MyVector &obj);//拷贝构造函数
  13. ~MyVector();//析构函数
  14. public:
  15. //重载[]运算符
  16. T& operator[](int index);
  17. //重载=运算符
  18. MyVector& operator=(const MyVector &obj);
  19. public:
  20. int getLen()
  21. {
  22. return m_len;
  23. }
  24. protected:
  25. T *m_space;
  26. int m_len;
  27. };
  28. //MyVector.hpp
  29. #include "MyVector.h"
  30. #include <iostream>
  31. using namespace std;
  32. //重载<< 运算符
  33. template <typename T>
  34. ostream& operator << (ostream &out, MyVector<T> &obj)
  35. {
  36. for (int i = 0; i < obj.m_len; i++)
  37. {
  38. out << obj.m_space[i] << " ";
  39. //out<<t1 t1是Teacher类对象
  40. //当MyVector<char> 或者MyVector<int>
  41. //obj.m_space[i]可以直接就是char类型或者int类型 这个是能直接打印出来的
  42. //而out<<t1 t1是Teacher类对象 obj.m_space[]==>t1对象 而t1对象是无法打印出来的,所以需要Teacher封装的函数有重载<<运算符函数
  43. }
  44. out << endl;
  45. return out;
  46. }
  47. //有参构造函数
  48. //MyVector<int> myv1(10);
  49. template <typename T>
  50. MyVector<T>::MyVector(int size)
  51. {
  52. m_space = new T[size];
  53. m_len = size;
  54. }
  55. //拷贝构造函数
  56. template <typename T>
  57. MyVector<T>::MyVector(const MyVector &obj)
  58. {
  59. //1 根据对象的大小分配内存
  60. m_len = obj.m_len;
  61. m_space = new T[m_len];
  62. //copy数据
  63. for (int i = 0; i < m_len; i++)
  64. {
  65. m_space[i] = obj.m_space[i];
  66. }
  67. }
  68. //析构函数
  69. template <typename T>
  70. MyVector<T>::~MyVector()
  71. {
  72. if (m_space !=NULL)
  73. {
  74. delete[] m_space;
  75. m_space = NULL;
  76. m_len = 0;
  77. }
  78. }
  79. //重载[]运算符
  80. //5) 如果在类模板外定义成员函数,应写成类模板形式:
  81. //template <class 虚拟类型参数>
  82. //函数类型类模板名<虚拟类型参数>::成员函数名(函数形参表列) { … }
  83. template <typename T>
  84. T& MyVector<T>::operator[] (int index)
  85. {
  86. return m_space[index];
  87. }
  88. //重载=运算符
  89. // a3 = a2 = a1;
  90. template <typename T>
  91. MyVector<T>& MyVector<T>::operator=(const MyVector<T> &obj)
  92. {
  93. //1 释放旧的内存
  94. if (m_space != NULL)
  95. {
  96. delete[] m_space;
  97. m_space = NULL;
  98. m_len = 0;
  99. }
  100. //2 根据obj分配内存
  101. m_len = obj.m_len;
  102. m_space = new T[m_len];
  103. //3 拷贝数据
  104. for (int i = 0; i < m_len; i++)
  105. {
  106. m_space[i] = obj[i];
  107. }
  108. return *this;//返回本身
  109. }
  110. ///
  111. //main.cpp
  112. #define _CRT_SECURE_NO_WARNINGS
  113. #include "MyVector.hpp"//类模板会二次编译 可写成hpp
  114. #include <iostream>
  115. using namespace std;
  116. //1 优化Teacher类, 属性变成 char *panme, 内置函数里面 分配内存
  117. //2 优化Teacher类,析构函数 释放panme指向的内存空间
  118. //3 优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数
  119. //4 优化Teacher类,在Teacher增加 <<
  120. //5 在模板数组类中,存int char Teacher Teacher*(指针类型)
  121. class Teacher
  122. {
  123. friend ostream &operator<<(ostream &out, const Teacher &obj);
  124. public:
  125. Teacher()
  126. {
  127. age = 33;
  128. strcpy(name,"");
  129. }
  130. Teacher(char *name, int age)
  131. {
  132. this->age = age;
  133. strcpy(this->name,name);
  134. }
  135. void printT()
  136. {
  137. cout << name << ", " << age << endl;
  138. }
  139. public:
  140. int age;
  141. char name[32];
  142. };
  143. ostream &operator<<(ostream &out, const Teacher &obj)
  144. {
  145. out << "obj.name:" << obj.name << "obj.age :" <<obj.age << endl;
  146. return out;
  147. }
  148. //数组模板类(MyVector)完成对int类型元素的管理
  149. void main01()
  150. {
  151. MyVector<int> myv1(10);
  152. for (int i = 0; i < 10; i++)
  153. {
  154. myv1[i] = i + 1;
  155. cout << myv1[i] << " ";
  156. }
  157. cout << endl;
  158. MyVector<int> myv2 = myv1;
  159. for (int i = 0; i < 10; i++)
  160. {
  161. cout << myv2[i] << " ";
  162. }
  163. cout << endl;
  164. cout << myv2 << endl;
  165. //重载<<
  166. //ostream& operator << (ostream &out, MyVector<T> &obj)
  167. cout << "hello..." << endl;
  168. system("pause");
  169. return;
  170. }
  171. //数组模板类(MyVector)完成对char类型元素的管理
  172. void main02()
  173. {
  174. MyVector<char> myv1(10);
  175. myv1[0] = 'a';
  176. myv1[1] = 'b';
  177. myv1[2] = 'c';
  178. myv1[3] = 'd';
  179. cout << myv1;
  180. system("pause");
  181. }
  182. //数组模板类(MyVector)完成对Teacher类型元素的管理
  183. void main()
  184. {
  185. Teacher t1("t1", 31), t2("t2", 32), t3("t3", 33), t4("t4", 34);
  186. MyVector<Teacher> tArray(4);
  187. tArray[0] = t1;
  188. tArray[1] = t2;
  189. tArray[2] = t3;
  190. tArray[3] = t4;
  191. for (int i = 0; i<4; i++)
  192. {
  193. Teacher tmp = tArray[i];
  194. tmp.printT();
  195. }
  196. //结论1:如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现//深拷贝和浅拷贝的问题。
  197. // 结论2:需要Teacher封装的函数有:
  198. // 1) 重写拷贝构造函数
  199. // 2) 重载等号操作符
  200. // 3) 重载左移操作符。
  201. cout << tArray;
  202. system("pause");
  203. }









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 容器的概念

  1. //MyVector.h
  2. #pragma once
  3. #include <iostream>
  4. using namespace std;
  5. template <typename T>
  6. class MyVector
  7. {
  8. //重载<< 小心<T>
  9. friend ostream& operator <<<T> (ostream &out, MyVector<T> &obj);
  10. public:
  11. MyVector(int size);//有参构造函数
  12. MyVector(const MyVector &obj);//拷贝构造函数
  13. ~MyVector();//析构函数
  14. public:
  15. //重载[]运算符
  16. T& operator[](int index);
  17. //重载=运算符
  18. MyVector& operator=(const MyVector &obj);
  19. public:
  20. int getLen()
  21. {
  22. return m_len;
  23. }
  24. protected:
  25. T *m_space;
  26. int m_len;
  27. };
  28. ///
  29. //MyVector.hpp
  30. #include "MyVector.h"
  31. #include <iostream>
  32. using namespace std;
  33. //重载<< 运算符
  34. template <typename T>
  35. ostream& operator << (ostream &out, MyVector<T> &obj)
  36. {
  37. for (int i = 0; i < obj.m_len; i++)
  38. {
  39. out << obj.m_space[i] << " ";
  40. //当MyVector<char> 或者MyVector<int>
  41. //obj.m_space[i]可以直接就是char类型或者int类型 这个是能直接打印出来的
  42. //而out<<t1 t1是Teacher类对象 obj.m_space[]==>t1对象 而t1对象是无法打印出来的,所以需要Teacher封装的函数有重载<<运算符函数
  43. }
  44. out << endl;
  45. return out;
  46. }
  47. //有参构造函数
  48. //MyVector<int> myv1(10);
  49. template <typename T>
  50. MyVector<T>::MyVector(int size)
  51. {
  52. m_space = new T[size];
  53. m_len = size;
  54. }
  55. //拷贝构造函数
  56. template <typename T>
  57. MyVector<T>::MyVector(const MyVector &obj)
  58. {
  59. //1 根据对象的大小分配内存
  60. m_len = obj.m_len;
  61. m_space = new T[m_len];
  62. //copy数据
  63. for (int i = 0; i < m_len; i++)
  64. {
  65. m_space[i] = obj.m_space[i];
  66. }
  67. }
  68. //析构函数
  69. template <typename T>
  70. MyVector<T>::~MyVector()
  71. {
  72. if (m_space !=NULL)
  73. {
  74. delete[] m_space;
  75. m_space = NULL;
  76. m_len = 0;
  77. }
  78. }
  79. //重载[]运算符
  80. //5) 如果在类模板外定义成员函数,应写成类模板形式:
  81. //template <class 虚拟类型参数>
  82. //函数类型类模板名<虚拟类型参数>::成员函数名(函数形参表列) { … }
  83. template <typename T>
  84. T& MyVector<T>::operator[] (int index)
  85. {
  86. return m_space[index];
  87. }
  88. //重载=运算符
  89. // a3 = a2 = a1;
  90. template <typename T>
  91. MyVector<T>& MyVector<T>::operator=(const MyVector<T> &obj)
  92. {
  93. //1 释放旧的内存
  94. if (m_space != NULL)
  95. {
  96. delete[] m_space;
  97. m_space = NULL;
  98. m_len = 0;
  99. }
  100. //2 根据obj分配内存
  101. m_len = obj.m_len;
  102. m_space = new T[m_len];
  103. //3 拷贝数据
  104. for (int i = 0; i < m_len; i++)
  105. {
  106. m_space[i] = obj[i];
  107. }
  108. return *this;//返回本身
  109. }
  110. //
  111. //main.cpp
  112. #define _CRT_SECURE_NO_WARNINGS
  113. #include "MyVector.hpp"//类模板会二次编译 可写成hpp
  114. #include <iostream>
  115. using namespace std;
  116. //1 优化Teacher类, 属性变成 char *panme, 内置函数里面 分配内存
  117. //2 优化Teacher类,析构函数 释放panme指向的内存空间
  118. //3 优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数
  119. //4 优化Teacher类,在Teacher增加 <<
  120. //5 在模板数组类中,存int char Teacher Teacher*(指针类型)
  121. //结论1:如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现//深拷贝和浅拷贝的问题。
  122. // 结论2:需要Teacher封装的函数有:
  123. // 1) 重写拷贝构造函数
  124. // 2) 重载等号操作符
  125. // 3) 重载左移操作符。
  126. class Teacher
  127. {
  128. // 3) 重载左移操作符
  129. friend ostream &operator<<(ostream &out, const Teacher &obj)
  130. {
  131. out << "obj.m_pName:" << obj.m_pName << "obj.age :" << obj.age << endl;
  132. return out;
  133. }
  134. public:
  135. Teacher()
  136. {
  137. age = 33;
  138. m_pName = new char[1];
  139. strcpy(m_pName, "");
  140. }
  141. Teacher(char *name, int age)
  142. {
  143. this->age = age;
  144. //根据name分配内存大小
  145. m_pName = new char[strlen(name) + 1];
  146. strcpy(m_pName, name);
  147. }
  148. ~Teacher()
  149. {
  150. if (m_pName != NULL)
  151. {
  152. delete[] m_pName;
  153. m_pName = NULL;
  154. age = 0;
  155. }
  156. }
  157. void printT()
  158. {
  159. cout << m_pName << ", " << age << endl;
  160. }
  161. public:
  162. // 1) 重写拷贝构造函数
  163. Teacher(const Teacher &obj)
  164. {
  165. m_pName = new char[strlen(obj.m_pName) + 1];
  166. strcpy(m_pName, obj.m_pName);
  167. }
  168. // 2) 重载等号操作符
  169. //t1=t2=t3
  170. Teacher& operator = (const Teacher &obj)
  171. {
  172. //1 释放旧的内存空间
  173. if (m_pName != NULL)
  174. {
  175. delete[] m_pName;
  176. m_pName = NULL;
  177. age = 0;
  178. }
  179. //2 根据obj分配内存大小
  180. m_pName = new char[strlen(obj.m_pName) + 1];
  181. //3 进行copy
  182. strcpy(m_pName,obj.m_pName);
  183. age = obj.age;
  184. return *this;
  185. }
  186. public:
  187. int age;
  188. //char name[32];
  189. char *m_pName;
  190. };
  191. void main()
  192. {
  193. Teacher t1("t1", 31), t2("t2", 32), t3("t3", 33), t4("t4", 34);
  194. MyVector<Teacher *> tArray(4);
  195. tArray[0] = &t1;
  196. tArray[1] = &t2;
  197. tArray[2] = &t3;
  198. tArray[3] = &t4;
  199. for (int i = 0; i<4; i++)
  200. {
  201. Teacher *tmp = tArray[i];
  202. tmp->printT();
  203. }
  204. //cout << tArray; 这里打印出来是存储Teacher类型的地址 不知道如何让他直接打印出来
  205. //感觉这里的Teacher重载<<根本没用上啊
  206. cout << "hello..." << endl;
  207. system("pause");
  208. return;
  209. }
  210. //数组模板类(MyVector)完成对int类型元素的管理
  211. void main01()
  212. {
  213. MyVector<int> myv1(10);
  214. for (int i = 0; i < 10; i++)
  215. {
  216. myv1[i] = i + 1;
  217. cout << myv1[i] << " ";
  218. }
  219. cout << endl;
  220. MyVector<int> myv2 = myv1;
  221. for (int i = 0; i < 10; i++)
  222. {
  223. cout << myv2[i] << " ";
  224. }
  225. cout << endl;
  226. cout << myv2 << endl;
  227. //重载<<
  228. //ostream& operator << (ostream &out, MyVector<T> &obj)
  229. cout << "hello..." << endl;
  230. system("pause");
  231. return;
  232. }
  233. //数组模板类(MyVector)完成对char类型元素的管理
  234. void main02()
  235. {
  236. MyVector<char> myv1(10);
  237. myv1[0] = 'a';
  238. myv1[1] = 'b';
  239. myv1[2] = 'c';
  240. myv1[3] = 'd';
  241. cout << myv1;
  242. system("pause");
  243. }
  244. //数组模板类(MyVector)完成对Teacher类型元素的管理
  245. void main03()
  246. {
  247. Teacher t1("t1", 31), t2("t2", 32), t3("t3", 33), t4("t4", 34);
  248. MyVector<Teacher> tArray(4);
  249. tArray[0] = t1;
  250. tArray[1] = t2;
  251. tArray[2] = t3;
  252. tArray[3] = t4;
  253. for (int i = 0; i<4; i++)
  254. {
  255. Teacher tmp = tArray[i];
  256. tmp.printT();
  257. }
  258. //结论1:如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现//深拷贝和浅拷贝的问题。
  259. // 结论2:需要Teacher封装的函数有:
  260. // 1) 重写拷贝构造函数
  261. // 2) 重载等号操作符
  262. // 3) 重载左移操作符。
  263. cout << tArray;
  264. system("pause");
  265. }
  266. /

发表评论

表情:
评论列表 (有 0 条评论,366人围观)

还没有评论,来说两句吧...

相关阅读

    相关 C++函数模板模板

    C++语言全盘继承了C语言的标准库,其中包含非常丰富的系统函数,例如输入/输出函数、数学函数、字符串处理函数和动态内存分配函数等。C++语言另外又增加了一些新的库,我们把C++

    相关 C++ 函数模板&模板详解

    在 C++ 中,模板分为函数模板和类模板两种。函数模板是用于生成函数的,类模板则是用于生成类的。 函数模板&模板函数     类模板&模板类  必须区分概念 函数模板是模板