1.模板综述

背景

  • 有时候许多函数或子程序的逻辑结构是一样的,只是要处理的数据类型不一样
  • 有时候多个类具有相同逻辑的成员函数和成员变量,只是成员变量的数据类型以及成员函数的参数类型不一样
  • 模板就是解决数据类型不一致造成代码冗余的一种机制,本质上就是数据类型参数化,用一种逻辑结构抽象出多种数据类型对应的函数或者类

2.函数模板

2.1基础语法

示例代码

#include <iostream>
using namespace std;template <typename T>//模板说明
T myFunc(T size)//函数实现
{cout << "size:" <<size<< endl;return size;
}int main(void)
{myFunc(8);//自动推导调用myFunc<float>((float)12.9);//显示调用cout << "Hello!" << endl;//system("pause");return 0;
}
  • 模板说明里面的类属参数在函数定义里面一定要使用,普通类型可以不使用
  • 可以使用多个类型参数进行模板说明
#include <iostream>
using namespace std;template <typename T1, typename T2>//模板说明,两个类型参数
T2 mySort(T1 * array,T2 length)
{T2 i = 0, j = 0;T1 tmp = array[0];for (i = 0; i < length; i++){for (j = i + 1; j < length; j++){if (array[i] < array[j]){tmp = array[i];array[i] = array[j];array[j] = tmp;}}}return i;
}template <typename T1,typename T2>//模板说明
T2 printArray(T1 *array,T2 length)//函数实现
{T2 i = 0;for (i = 0; i < length; i++){cout << (int)array[i]<<""<< endl;}cout << endl;return i;
}int main(void)
{char myArray[10] = {1,3,87,54,98,37,33,63,89,2};int size = sizeof(myArray) / (*myArray);mySort(myArray, size);printArray(myArray, size);cout << "Hello!" << endl;//system("pause");return 0;
}

2.2函数模板遇上函数重载

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


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

/*
函数模板不允许自动类型转化
普通函数能够进行自动类型转换
*//*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 ;
}

2.3函数模板实现机制

编译器介绍

  • gcc(GNU C Compiler)编译器的作者是RichardStallman,也是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编译工具是一个工具链。。。。

GCC常用编译选项

选项 作用
-o 产生目标(.i、.s、.o、可执行文件等)
-c 通知gcc取消链接步骤,即编译源码并在最后生成目标文件
-E 只运行C预编译器
-S 告诉编译器产生汇编语言文件后停止编译,产生的汇编语言文件扩展名为.s
-Wall 使gcc对源文件的代码有问题的地方发出警告
-Idir 将dir目录加入搜索头文件的目录路径
-Ldir 将dir目录加入搜索库的目录路径
-llib 链接lib库
-g 在目标文件中嵌入调试信息,以便gdb之类的调试程序调试

GCC常用编译步骤

1.gcc -E hello.c -o hello.i(预处理)
2.gcc -S hello.i -o hello.s(编译)
3.gcc -c hello.s -o hello.o(汇编)
4.gcc hello.o -o hello(链接)
以上四个步骤,可合成一个步骤
gcc hello.c -o hello(直接编译链接成可执行目标文件)
gcc -c hello.c或gcc -c hello.c -o hello.o(编译生成可重定位目标文件)

Gcc编译多个文件

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

反汇编观察

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

3.类模板

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

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


类模板在表示如数组、表、图等数据结构显得特别重要,
这些数据结构的表示和算法不受所包含的元素类型的影响


使用类模板声明对象的时候要显示指定形式参数的具体类型,以便C++编译器给对象分配具体的内存

3.1普通类模板的语法

单个类的语法

#include <iostream>
using namespace std;template <typename T>
class A
{
public:A(int a = 0){this->a = a;}void printA(){cout << a << endl;}
protected:
private:T a;
};int main(void)
{A<int> a1;a1.printA();A<int> a2(19);a2.printA();cout<<"Hello!"<<endl;return 0;
}

模板类作函数参数

#include <iostream>
using namespace std;template <typename T>
class A
{
public:A(int a = 0){this->a = a;}void printA(){cout << a << endl;}
protected:
private:T a;
};//类模板 做函数参数//参数 ,C++编译器 要求具体的类 所以所 要 A<int> &a
void UseA(A<int> &a)
{a.printA();
}int main(void)
{A<int> a1;UseA(a1);A<int> a2(19);UseA(a2);cout<<"Hello!"<<endl;return 0;
}

3.2继承中的类模板语法

模板类派生时, 需要具体化模板类. C++编译器需要知道 父类的数据类型具体是什么样子的。要知道父类所占的内存大小是多少。只有数据类型固定下来,才知道如何分配内存。

模板类派生时, 需要具体化模板类. C++编译器需要知道 父类的数据类型具体是什么样子的。要知道父类所占的内存大小是多少。只有数据类型固定下来,才知道如何分配内存。


从模板类派生普通类

#include <iostream>
using namespace std;template <typename T>
class A
{
public:A(int a = 0){this->a = a;}void printA(){cout << a << endl;}
protected:T a;
private:};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;}
protected:private:int b;
};int main(void)
{B  b1(1, 2);b1.printB();cout<<"Hello!"<<endl;return 0;
}

从模板类派生模板类

#include <iostream>
using namespace std;template <typename T>
class A
{
public:A(int a = 0){this->a = a;}void printA(){cout << a << endl;}
protected:T a;
private:};template <typename T>class C :public A<T>
{
public:C(T a, T c) :A<T>(a){this->c = c;}void printC(){cout << "a:" << a << "c:" << c << endl;}
private:T c;
protected:
};int main(void)
{C<int> c1(1, 2);c1.printC();cout<<"Hello!"<<endl;return 0;
}

3.2类模板知识体系梳理

类模板函数全部写在类的内部

#include <iostream>
using namespace std;template <typename T>
class Complex
{friend Complex MySub(Complex c1, Complex c2){Complex tmp(c1.a-c2.a,c1.b-c2.b);return tmp;}friend ostream & operator<<(ostream & out, Complex & c){out << c.a << " + " << c.b << "i" << 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 << " + " << b << "i" << endl;}
protected:
private:T a;T b;
};int main(void)
{//需要把模板类 进行具体化以后  才能定义对象  C++编译器要分配内存Complex<int>    c1(1, 2);Complex<int>    c2(3, 4);Complex<int> c3 = c1 + c2;//c3.printCom();cout << c3 << endl;//滥用友元函数{Complex<int> c4 = MySub(c1, c2);cout << c4 << endl;}cout<<"Hello!"<<endl;return 0;
}

类模板函数全写在类的外部,但在同一个cpp中

如果一个模板类具有友元函数,且该友元函数的形参包含模板类对象,则需要进行类模板和友元函数的前置声明。
需要注意的是:

  1. 模板类和模板类里面的友元函数需要进行前置声明
  2. 友元函数在前置声明的时候,函数名后面不指定具体的数据类型,但是在模板类里面声明的时候,需要在函数名后面紧跟具体的数据类型,比如<T>
  3. 模板类的成员函数在类的外面实现的时候,需要注意参数列表,函数名(类作用域)以及返回类型是否需要强行指定具体的数据类型,以便编译器确定分配内存
  4. 友元函数在类的外部实现的时候,不需要管函数名前面的类作用域以及函数名后面的具体数据类型,只需要注意参数列表以及返回值是否需要指定数据类型即可
  5. 所有函数在类的外部实现的时候,其函数名后面都不用管具体的数据类型。
  6. 友元函数调用的时候,需要在函数名后面紧跟具体数据类型。言外之意,前置声明和外部实现的格式一样,类的内部声明和调用的时候,函数名后面都要具体的数据类型。
#include <iostream>
using namespace std;template <typename T>//类的前置声明
class Complex;template <typename T>//友元函数的前置声明
Complex<T> MySub(Complex<T>& c1, Complex<T>& c2);//友元函数前置声明时不需要在函数名后面指定具体数据类型template <typename T>
ostream& operator<<(ostream& out, Complex<T>& c);//友元函数前置声明时不需要在函数名后面指定具体数据类型template <typename T>
class Complex
{friend Complex<T> MySub<T>(Complex<T>& c1, Complex<T>& c2); //在模板类里面声明友元函数的时候,需要在函数名后面紧跟具体的数据类型,比如<T>friend ostream & operator<<<T>(ostream & out, Complex<T> & c);//在模板类里面声明友元函数的时候,需要在函数名后面紧跟具体的数据类型,比如<T>/*friend ostream & operator<<(ostream & out, Complex<T> & c);如果operator<<后面没有<T>则会报错,所以模板类里面存在友元函数的时候,除了进行友元函数的前置声明意外,还需要在模板类里面进行函数名数据类型具体化,即指定具体的typename参数类型紧跟在函数名后面。*/
public:Complex(T a, T b);Complex operator+(Complex & c2);void printCom();
protected:
private:T a;T b;
};/*
模板类的成员函数在类的外面实现的时候,
需要注意参数列表,函数名(类作用域)以及返回类型是否需要强行指定具体的数据类型,
以便编译器确定分配内存
*/
template <typename T>
Complex<T>::Complex(T a, T b)
{this->a = a;this->b = b;
}template <typename T>
Complex<T> Complex<T>::operator+(Complex<T> & c2)
{Complex<T> tmp(a + c2.a, b + c2.b);return tmp;
}template <typename T>
void Complex<T>::printCom()
{cout << a << " + " << b << "i" << endl;
}/*
友元函数在类的外部实现的时候,
不需要管函数名前面的类作用域以及函数名后面的具体数据类型,
只需要注意参数列表以及返回值是否需要指定数据类型即可
*/
template <typename T>
Complex<T> MySub(Complex<T>& c1, Complex<T>& c2)
{Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);return tmp;
}template <typename T>
ostream & operator<<(ostream & out, Complex<T> & c)
{out << c.a << " + " << c.b << "i" << endl;return out;
}int main(void)
{//需要把模板类 进行具体化以后  才能定义对象  C++编译器要分配内存Complex<int>    c1(1, 2);Complex<int>    c2(3, 4);Complex<int> c3 = c1 + c2;//c3.printCom();cout << c3 << endl;//滥用友元函数{Complex<int> c4 = MySub<int>(c1, c2);//友元函数调用的时候,需要在函数名后面紧跟具体数据类型cout << c4 << endl;}cout<<"Hello!"<<endl;return 0;
}

结论:不要滥用友元函数,一般友元函数只适用于重载<<或者>>操作符。

类模板函数全写在类的外部,但在不同的.h和cpp中

由于模板的实现机制在本质上是两次编译,所以如果只在主程序里面包含头文件(类模板的声明),编译器不会自动寻找cpp文件里面的成员函数和友元函数的函数体。所以会出现找不到某个函数体的错误。只能是包含实现函数体的cpp文件,而cpp文件又包含了h文件,所以实质上是包含了类模板的声明以及类模板函数的实现,故业界都是将这两部分(.h和.cpp)写在同一个文件中,叫做hpp文件,只需要在提供的开源库里面包含该hpp文件,即可使用类模板。

  • 传统类的头文件(类模板声明部分)
#pragma  once#include <iostream>
using namespace std;template <typename T>
class Complex
{friend ostream & operator<< <T> (ostream &out, Complex &c3);public:Complex(T a, T b);void printCom();Complex operator+ (Complex &c2);private:T   a;T   b;
};
  • 传统类的实现部分(具体类模板函数的实现部分)
#include <iostream>
using namespace std;
#include "complex.h"//构造函数的实现 写在了类的外部
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;
}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;
}
  • 测试程序(包含hpp文件)
#include <iostream>
using namespace std;
#include "complex.cpp"void main()
{//需要把模板类 进行具体化以后  才能定义对象  C++编译器要分配内存Complex<int>    c1(1, 2);Complex<int>    c2(3, 4);Complex<int> c3 = c1 + c2;cout << c3 << endl;cout << "hello..." << endl;return;
}

3.3类模板中的static关键字

  • 类模板—>实例化—>模板类
    每一个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员
  • 和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
  • 每个模板类有自己的类模板的static数据成员副本
/*编译器并不是把函数模板处理成能够处理任意类的函数编译器从函数模板通过具体类型产生不同的函数编译器会对函数模板进行两次编译在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。
*/#include <iostream>
using namespace std;template <typename T>
class AA
{
public:static T m_a;
protected:
private:
};template <typename T>
T AA<T>::m_a  = 0;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;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;//m_a 应该是 每一种类型的类 使用自己的m_acout<<"hello..."<<endl;system("pause");return ;
}

3.4类模板小结

类模板的声明

1.先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
2.将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。
3.在类声明前面加入一行,格式为(class和typename作用一样):
template <class虚拟类型参数>
如:

 template <class numtype> //注意本行末尾无分号class Compare{…}; //类体

4.用类模板定义对象时用以下形式:
类模板名<实际类型名> 对象名;
类模板名<实际类型名> 对象名(实参表列);
如:

 Compare<int> cmp;Compare<int> cmp(3,7);

5.如果在类模板外定义成员函数,应写成类模板形式:
template <class 虚拟类型参数>
函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}
6.类模板的类型参数可以有一个或多个,每个类型前面都必须加class或者typename,如:

template <class T1,class T2>
class someclass
{…};

在定义对象时分别代入实际的类型名,如:
someclass<int,double> obj;
7.和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。
8.模板可以有层次,一个类模板可以作为基类,派生出派生模板类。

4.模板在工程中的应用

综述

1.模板是C++中类型参数化的多态工具,提供函数模板和类模板
2.模板定义从模板说明开始,类属参数必须在模板实现中至少使用一次
3.同一个类属参数可以用于多类模板
4.类属参数可用于函数形参,返回类型以及声明函数中的变量
5.模板由编译器根据实际的数据类型实例化,生成实际的可执行代码,从而得到模板函数和模板类
6.函数模板可以进行重载
7.类模板可以进行派生继承


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


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

  • 类模板定义
  • 构造函数
  • 拷贝构造函数
  • 重载操作符<< [] =操作符
  • 从数组模板中进行派生

使用基础数据类型以及一般的自定义类对象作为容器元素

  • 容器/数组类模板头文件
#pragma once        // 保证头文件只被编译一次。#include <iostream>
using namespace std;template <typename T>
class MyVector
{
public:MyVector(int size = 0);//构造函数MyVector(const MyVector & obj);//拷贝构造函数~MyVector();//析构函数T & operator[](int index);MyVector & operator=(MyVector obj);int getLen();friend ostream & operator<< <T>(ostream & out, const MyVector<T> & obj);
protected:
private:int m_len;T *m_space;
};
  • 容器/数组类模板实现文件
#include "myvector.h"template <typename T>
T & MyVector<T>::operator[](int index)
{return m_space[index];
}template <typename T>
MyVector<T>::MyVector(int size = 0)//构造函数
{m_space = new T[size];m_len = size;
}template <typename T>
MyVector<T>::MyVector(const MyVector & obj)//拷贝构造函数
{m_len = obj.m_len;m_space = new T[m_len];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_len = 0;m_space = NULL;}
}template <typename T>
MyVector<T> & MyVector<T>::operator=(MyVector<T> obj)
{/*1.释放旧内存*/if (m_space != NULL){delete[]m_space;m_space = NULL;m_len = 0;}/*2.重新分配内存*/m_space = new T[obj.m_len];m_len = obj.m_len;/*3.拷贝数据*/for (int i = 0; i < m_len; i++){m_space[i] = obj[i];}/*4.返回左值本身*/return  *this;
}template <typename T>
int MyVector<T>::getLen()
{return m_len;
}template <typename T>
ostream & operator<<(ostream & out, const MyVector<T> & obj)
{for (int i = 0; i < obj.m_len; i++){cout << obj.m_space[i] << " ";}cout << endl;return out;
}
  • 容器/数组类测试文件
#define _CRT_SECURE_NO_WARNINGS#include <iostream>
#include "myvector.cpp"//注意包含的是cpp文件
using namespace std;class Teacher
{
public:Teacher(){age = 22;strcpy(name, "Goopher");}Teacher(int age, char *name){this->age = age;strcpy(this->name,name);}void printTeacher(){cout << name << "," << age << endl;}
protected:
private:int age;char name[32];
};class Test1
{
public:
protected:
private:int a;
};class Test2
{
public:
protected:
private:int a;static int b;//static成员存储在全局区,不占类对象的内存模型
};class Test3
{
public:virtual void hello()//虚函数使得类对象具有VPTR指针,多占用一个指针的大小{}
protected:
private:int a;static int b;
};class Test4
{
public:virtual void hello(){}virtual void hello01() = 0;//多个虚函数只对应一个VPTR指针(一个虚函数表)
protected:
private:int a;static int b;
};class Test5
{
public:virtual void hello(){}virtual void hello01() = 0;void printTest()//普通成员函数也不占据类对象的内存模型{}
protected:
private:int a;static int b;
};class Test6
{
public:virtual void hello(){}//virtual void hello01() = 0;void printTest(){}void printTest01()//多个普通成员函数也不占据类对象的内存模型{}
protected:
private:int a;static int b;
};
int main(void)
{   /*初始化v1容器中的没一个对象并在初始化的时候逐个打印*/MyVector<int> v1(10);for (int i = 0; i < v1.getLen(); i++){v1[i] = i + 1;cout << v1[i] << " ";}cout << endl;/*初始化v2容器中的没一个对象并在初始化以后使用重载<<的方式打印*/MyVector<int> v2 = v1;for (int i = 0; i < v2.getLen(); i++){v2[i] = i*2 + 1;}cout <<v2<< endl;/*使用类对象设置容器并打印*/Teacher t1(31, "t1"), t2(32, "t2"), t3(33, "t3"), t4(34, "t4");MyVector<Teacher> v3(4);v3[0] = t1;v3[1] = t2;v3[2] = t3;v3[3] = t4;for (int i = 0; i < v3.getLen(); i++){Teacher tmp = v3[i];tmp.printTeacher();}cout<<"Hello!"<<endl;cout << sizeof(Test1)<<endl;cout << sizeof(Test2) << endl;cout << sizeof(Test3) << endl;cout << sizeof(Test4) << endl;cout << sizeof(Test5) << endl;cout << sizeof(Test6) << endl;Test6 t6;cout << sizeof(t6) << endl;system("pause");return 0;
}

优化后的Teacher类对象作为容器存储元素(类模板头文件和实现文件不变)

#define _CRT_SECURE_NO_WARNINGS#include <iostream>
#include "myvector.cpp"//注意包含的是cpp文件
using namespace std;//1  优化Teacher类, 属性变成 char *panme, 购置函数里面 分配内存
//2  优化Teacher类,析构函数 释放panme指向的内存空间
//3  优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数
//4  优化Teacher类,在Teacher增加 <<
//5  在模板数组类中,存int char Teacher Teacher*(指针类型)//=====>stl 容器的概念
class Teacher
{
public:Teacher(){age = 22;name = new char[1];strcpy(name, "");}Teacher(int age, char *name){this->age = age;this->name = new char[strlen(name) + 1];strcpy(this->name,name);}Teacher(const Teacher &obj){age = obj.age;name = new char[strlen(obj.name) + 1];strcpy(name,obj.name);}~Teacher(){if (name != NULL){delete[]name;name = NULL;age = 22;}}void printTeacher(){cout << name << "," << age << endl;}friend ostream & operator<<(ostream & out, Teacher &obj);Teacher & operator=(const Teacher & obj){if (name != NULL){delete[]name;name = NULL;age = 22;}name = new char[strlen(obj.name) + 1];strcpy(name, obj.name);age = obj.age;return *this;}
protected:
private:int age;char *name;
};ostream & operator<<(ostream & out, Teacher &obj)
{out << obj.name << "," << obj.age << endl;return out;
}
int main(void)
{   /*使用类对象设置容器并打印*/Teacher t1(31, "t1"), t2(32, "t2"), t3(33, "t3"), t4(34, "t4");MyVector<Teacher> v3(4);v3[0] = t1;v3[1] = t2;v3[2] = t3;v3[3] = t4;for (int i = 0; i < v3.getLen(); i++){Teacher tmp = v3[i];tmp.printTeacher();}cout<<"Hello!"<<endl;system("pause");return 0;
}

指针作为容器元素存储

#define _CRT_SECURE_NO_WARNINGS#include <iostream>
#include "myvector.cpp"//注意包含的是cpp文件
using namespace std;//1  优化Teacher类, 属性变成 char *panme, 购置函数里面 分配内存
//2  优化Teacher类,析构函数 释放panme指向的内存空间
//3  优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数
//4  优化Teacher类,在Teacher增加 <<
//5  在模板数组类中,存int char Teacher Teacher*(指针类型)//=====>stl 容器的概念
class Teacher
{
public:Teacher(){age = 22;name = new char[1];strcpy(name, "");}Teacher(int age, char *name){this->age = age;this->name = new char[strlen(name) + 1];strcpy(this->name,name);}Teacher(const Teacher &obj){age = obj.age;name = new char[strlen(obj.name) + 1];strcpy(name,obj.name);}~Teacher(){if (name != NULL){delete[]name;name = NULL;age = 22;}}void printTeacher(){cout << name << "," << age << endl;}friend ostream & operator<<(ostream & out, Teacher &obj);Teacher & operator=(const Teacher & obj){if (name != NULL){delete[]name;name = NULL;age = 22;}name = new char[strlen(obj.name) + 1];strcpy(name, obj.name);age = obj.age;return *this;}
protected:
private:int age;char *name;
};ostream & operator<<(ostream & out, Teacher &obj)
{out << obj.name << "," << obj.age << endl;return out;
}
int main(void)
{   /*使用类对象设置容器并打印*/Teacher t1(31, "t1"), t2(32, "t2"), t3(33, "t3"), t4(34, "t4");MyVector<Teacher*> v3(4);v3[0] = &t1;v3[1] = &t2;v3[2] = &t3;v3[3] = &t4;for (int i = 0; i < v3.getLen(); i++){Teacher *tmp = v3[i];tmp->printTeacher();}cout << t1;cout<<"Hello!"<<endl;system("pause");return 0;
}

总结:类模板实现了数据结构(具体数据类型)和算法的分离,真正的实现了泛型编程。

C++之泛型编程(模板)相关推荐

  1. C++ Primer 学习笔记_75_模板与泛型编程 --模板定义

    模板与泛型编程 --模板定义 引言: 所谓泛型程序就是以独立于不论什么特定类型的方式编写代码.使用泛型程序时,我们须要提供详细程序实例所操作的类型或值. 模板是泛型编程的基础.使用模板时能够无须了解模 ...

  2. C++ 泛型编程/模板 泛函编程/Lambda/λ演算

    1.泛型编程(C++模板) 其中,Ada, Delpha, Java, C#, Swift 称之为 泛型/generics; ML, Scala和 Haskell 称之为 参数多态/parametri ...

  3. C++ 泛型编程模板 之 函数模板初步01

    #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;void mySwapInt(int & ...

  4. C++泛型编程——模板学习

    模板 模板使类和函数可在编译时定义所需处理和返回的数据类型. 一个模板并非一个实实在在的类或函数,仅仅是一个类或函数的描述. 模板一般分为模板函数和类模板,以所处理的数据类型的说明作为参数的类叫类模板 ...

  5. 【C++ 泛型编程 进阶篇】 C++ 泛型编程 模板与异常处理、模板与友元之间的使用

    这里写目录标题 1. 简介 1.1 模板的基础理念 1.2 异常处理的基础理念 2. 模板与异常处理 2.1 异常处理在模板中的基本应用 2.1.1 为模板函数/类添加异常处理 2.1.2 使用noe ...

  6. c++模板---1(模板概念,利用模板实现数组排序,函数模板调用规则)

    什么叫泛型编程?1. 参数类型化. 2. 模板 模板概念 c++提供了函数模板,所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表.这个通用函数就成为函数模 ...

  7. C++11新特性以及std::thread多线程编程

    一 .C++11新特性 1. auto 类型推导 1.1 当=号右边的表达式是一个引用类型时,auto会把引用抛弃,直接推导出原始类型: 1.2 当=号右边的表达式带有const属性时,auto不会使 ...

  8. 《C++ Primer》读书笔记

    第一章 开始 类型:程序所处理的数据都保存在变量中,而每个变量都有自己的类型 内置类型:语言自身定义的类型(而形如string等类型都是标准库定义的) main的返回值:0表示成功,非0指出错误类型 ...

  9. unix编程书籍推荐

    [Unix下C/C++开发] unix编程书籍推荐 LiSteven 发布于 2年前,共有 0 条评论 Unix/Linux/BSD系统 相对于Windows,在UNIX下编程获得相关文档要方便很多. ...

  10. [Unix下C/C++开发] unix编程书籍推荐

    [Unix下C/C++开发] unix编程书籍推荐 发表于1年前(2012-12-20 10:14)   阅读(256) | 评论(0) 6人收藏此文章, 我要收藏 赞0 Unix/Linux/BSD ...

最新文章

  1. 微软警告称Flame病毒利用Windows漏洞
  2. 宏观相似性与惯性质量
  3. android gridview显示本地图片大小,在Android上的GridView中调整图像大小
  4. HaoheDI让ETL变得简单
  5. 设计把所有的奇数移动到所有偶数前面的算法
  6. 不忘本~explicit和implicit修饰符
  7. Android中的音频播放(MediaPlayer和SoundPool)
  8. 将Notepad++配置成Java轻量级的IDE
  9. php 去掉nbsp,php 正则去掉pnbsp;/p 空格 nbsp;
  10. Java注解全面解析
  11. C++从入门到放肆!
  12. 洛谷——P2637 第一次,第二次,成交
  13. 蒋本珊计算机组成原理知识点笔记,计算机组成原理习题答案解析(蒋本珊)
  14. java家谱树_青锋家谱系统-基于springboot+orgtree的青锋家谱树管理系统
  15. mysql数据表备份_MySQL数据库备份之逻辑备份和物理备份概述
  16. editplus使用php,EditPlus如何运行php文件
  17. kali制作钓鱼网站
  18. 二手车电商的大萧条时代
  19. 帝豪gs车机系统wince_用销量告诉你答案 选帝豪GS准没错
  20. Python中的self用法

热门文章

  1. springboot的原生cache_springboot-shiro-redis-session-cache
  2. html text align属性,HTML canvas
  3. python循环post请求_循环post请求太多
  4. 如何将mysql的数据库渲染到页面_vue.js实现数据库的JSON数据输出渲染到html页面功能示例...
  5. mysql简单外连接查询
  6. java 垃圾回收机制_Java的垃圾回收机制
  7. 截取全部数值字符并将其转化为数值类型
  8. leetcode402. 移掉K位数字
  9. C++ 标准库 vector list map使用方法
  10. 为什么你需要设计和维护一套自我移动标准?