C++类模板
1 类模板
类模板和函数模板的定义和使用类似,我们已经进行了介绍。有时,有两个或多个类,
其功能是相同的,仅仅是数据类型不同。
1.类模板用于实现类所需数据的类型参数化
2.类模板在表示如数组、表、图等数据结构显得特别重要,这些数据结构的表示和算
法不受所包含的数据类型的影响。
/**
*Copyright (c) 2018 Young Fan.All Right Reserved.
*Filename: 4 类模板
*Author: Young Fan
*Date: 2018.4.30
*IDE: Visual Studio 2017
*Description: 类模板
*/
#include<iostream >
using namespace std;
template<class T>
class Person
{
public:
Person(T id,T age)
{
this->mAge = age;
this->mId = id;
}
void Show()
{
cout << "ID:" << mId << " Age:" << mAge << endl;
}
public:
T mId;
T mAge;
};
void test1()
{
//函数模板在在调用的时候,可以自动类型推导
//但,类模板必须显式指定类型
Person<int> p(10, 20); //必须显式指定类型。因为类定义对象,对象要需要编译器分配内存
p.Show();
}
int main()
{
test1();
return 0;
}
总结:
函数模板在在调用的时候,可以自动类型推导,但,类模板必须显式指定类型如:Person<int> p(10, 20); 必须显式指定类型。因为类定义对象,对象要需要编译器分配内存
1.1 类模板派生普通类
/**
*Copyright (c) 2018 Young Fan.All Right Reserved.
*Filename: 5 类模板派生普通类
*Author: Young Fan
*Date: 2018.5.2
*IDE: Visual Studio 2017
*Description: 类模板派生普通类
*/
#include<iostream>
using namespace std;
template<class T>
class Person
{
public:
Person()
{
mAge = 0;
}
public:
T mAge;
};
//类定义对象,对象要需要编译器分配内存
//所以必须显式指定类型
class subPerson:public Person<int> //必须显式指定类型。因为类定义对象,对象要需要编译器分配内存
{
};
int main()
{
return 0;
}
总结:
函数模板在在调用的时候,可以自动类型推导但,类模板必须显式指定类型,必须显式指定类型。因为类定义对象,对象要需要编译器分配内存。故在继承(派生)类的时候要指定类型。
1.2 类模板派生类模板
/**
*Copyright (c) 2018 Young Fan.All Right Reserved.
*Filename: 6 类模板派生类模板
*Author: Young Fan
*Date: 2018.5.2
*IDE: Visual Studio 2017
*Description: 类模板派生类模板
*/
#include<iostream>
using namespace std;
template<class T>
class Animal
{
public:
void Jiao()
{
cout << mAge << "岁动物在叫!" << endl;
}
public:
T mAge;
};
template<class T>
class Cat : public Animal<T>
{
};
int main()
{
Cat<int> cat;
cat.mAge = 2;
cat.Jiao();
return 0;
}
1.3 类的h与cpp分离编写
Person.h
//C语言中防止头文件被重复包含,如下方式
#ifndef PERSON_H //防止头文件被重复包含,因为包含一次就行
//Ctrl + shif + u,可以将选中英文字符内容变大写,如这里讲person_h变为PERSON_H
#define PERSON_H
//..................
#endif
//C++中防止头文件被重复包含用#pragma once
#pragma once
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person(string name, int age); //构造函数声明
void Show(); //方法声明
public:
string mName;
int mAge;
int mID;
};
//.h里只做声明,不做定义(实现),.cpp里做定义(实现)
Person.cpp
/**
*Copyright (c) 2018 Young Fan.All Right Reserved.
*Author: Young Fan
*Date: 2018.6.22
*IDE: Visual Studio 2017
*Description:
*/
#include "Person.h"
Person::Person(string name, int age) //构造函数定义(实现),要加类的作用域
{
this->mName = name;
this->mAge = age;
}
void Person::Show() //方法的定义(实现)
{
cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}
main.cpp
/**
*Copyright (c) 2018 Young Fan.All Right Reserved.
*Author: Young Fan
*Date: 2018.6.22
*IDE: Visual Studio 2017
*Description:
*/
#include<iostream>
#include "Person.h"
using namespace std;
int main()
{
Person p("YoungFan", 25);
p.Show();
return 0;
}
1.4 类模板实现-在类内实现
/**
*Copyright (c) 2018 Young Fan.All Right Reserved.
*Author: Young Fan
*Date: 2018.6.22
*IDE: Visual Studio 2017
*Description:
*/
#include <iostream>
#include <string> //用string要加头文件
using namespace std;
template<class T1,class T2>
class Person
{
public: //构造函数这里也得加权限,不加默认为private
Person(T1 name, T2 age)
{
this->mName = name;
this->mAge = age;
}
void Show()
{
cout << "Name:" << this->mName << " Age:" << this->mAge << endl;
}
public:
T1 mName;
T2 mAge;
};
void test()
{
Person<string, int> p("YoungFan",25);
p.Show();
}
int main()
{
test();
return 0;
}
构造函数也得加权限,不加默认为private
1.5 类模板的外部实现
/**
*Copyright (c) 2018 Young Fan.All Right Reserved.
*Author: Young Fan
*Date: 2018.6.22
*IDE: Visual Studio 2017
*Description:
*/
#include <iostream>
using namespace std;
//普通友元的类外声明,很麻烦,所以不要滥用友元
template<class T> class Person; //类声明
template<class T> void PrintPerson(Person<T>& p); //函数声明
template<class T>
class Person
{
public:
Person(T age, T id); //声明。编译器碰到声明不编译,会生成一个函数符号。在连接的时候
//,根据这个符号去找函数定义(实现),找到就连上,找不到就连接失败
//,连接失败的话就会报无法解析的外部符号
void Show();
//重载左移操作符,这里使用友元
//方法一:加template<class T>,这种方法在vs下编译可以通过并执行,但在其他编译器如Linux不能编译通过
//代码如下所示
//template<class T>
//friend ostream& operator<<(ostream& os, Person<T>& p);
//方法二:不加template<class T>,加<T>
friend ostream& operator<<<T>(ostream& os, Person<T>& p);
//普通友元函数
//方法一:加template<class T>,这种方法在vs下编译可以通过并执行,但在其他编译器如Linux不能编译通过
//代码如下所示
//template<class T>
//friend void PrintPerson(Person<T>& p);
//方法二:不加template<class T>,加<T>,还要写类外声明
friend void PrintPerson<T>(Person<T>& p);
private:
T mID;
T mAge;
};
//类外定义
template<class T>
Person<T>::Person(T age, T id) //使用类模板需要模板参数列表,即Person<T>::
{
this->mID = id;
this->mAge = age;
}
template<class T>
void Person<T>::Show() //使用类模板需要模板参数列表,即Person<T>::
{
cout << "ID:" << this->mID << " Age:" << this->mAge << endl;
}
//重载左移运算操作符
template<class T>
ostream& operator<<(ostream& os, Person<T>& p)
{
os << "Age:" << p.mAge << " ID:" << p.mID << endl; //因为使用的友元,故能直接获取私有成员
return os;
}
template<class T>
void PrintPerson(Person<T>& p)
{
cout << "Age:" << p.mAge << " ID:" << p.mID << endl;
}
void test()
{
Person<int> p(10, 20);
p.Show();
cout << p; //使用重载的操作符“<<”
PrintPerson(p);
}
int main()
{
test();
return 0;
}
1.6 类模板h与cpp分离编写
Person.h
//这里使用C语言的防止头文件被重复包含
//C++中防止头文件被重复包含用#pragma once
#ifndef PERSON_H
#define PERSON_H
#include <iostream>
using namespace std;
template<class T>
class Person
{
public:
Person(T age);
void Show();
private:
T age;
};
#endif
Person.hpp
/**
*Copyright (c) 2018 Young Fan.All Right Reserved.
*Author: Young Fan
*Date: 2018.7.8
*IDE: Visual Studio 2017
*Description:
*/
#include "Person.h"
//函数模板 ,它经过两次编译
//并没有生成独立的具体模板函数(因为是独立编译的,没有调用,即没有调用设置类型)
template<class T>
Person<T>::Person(T age)
{
this->age = age;
}
template<class T>
void Person<T>::Show()
{
cout << "Age: " << age << endl;
}
main.cpp
/**
*Copyright (c) 2018 Young Fan.All Right Reserved.
*Author: Young Fan
*Date: 2018.7.8
*IDE: Visual Studio 2017
*Description:
*/
//#include "Person.h" //不能包含.h
//#include "Person.cpp" //要包含cpp,相当于把该cpp拷进来了
#include "Person.hpp" //把头文件的那个cpp文件后缀改为hpp(相当于头文件和cpp文件的合体)
//然后,在这里包含hpp文件,一般有类模板分文件的时候都这么写
//分离写法的时候不要涉及友元,会不通过,具体不知原因
int main()
{
Person<int> p(10);//构造函数的定义在当前文件没有找到,编译器认为这个函数在其他文件中定义
//让链接器在连接的时候,去找这个函数的具体位置
p.Show(); //Show函数的定义在当前文件没有找到,编译器认为这个函数在其他文件中定义
//让链接器在连接的时候,去找这个函数的具体位置
return 0;
}
//在写类模板的时候,最好写在一个文件里
//但如果要分开为多文件,就把头文件的那个cpp文件后缀改为hpp(相当于头文件和cpp文件的合体)
//所以在写类模板的时候写为hpp,别人一看见就知道里面有类模板
总结:
#include "Person.hpp" 把头文件的那个cpp文件后缀改为hpp(相当于头文件和cpp文件的合体)
然后,在这里包含hpp文件,一般有类模板分文件的时候都这写
分离写法的时候不要涉及友元,会不通过,具体不知原因
Person<int> p(10);//构造函数的定义在当前文件没有找到,编译器认为这个函数在其他文件中定义,让链接器在连接的时候,去找这个函数的具体位置
p.Show(); //Show函数的定义在当前文件没有找到,编译器认为这个函数在其他文件中定义,让链接器在连接的时候,去找这个函数的具体位置
在写类模板的时候,最好写在一个文件里,但如果要分开为多文件,就把头文件的那个cpp文件后缀改为hpp(相当于头文件和cpp文件的合体),所以在写类模板的时候写为hpp,别人一看见就知道里面有类模板
1.7 类模板碰到static成员
从类模板实例化的每一个模板类有自己的类模板数据成员,该模板的所有对象共享一个static 数据成员。
和非模板类的static 数据成员一样,模板类的static 数据成员也应该在文件范围定义和初始化。
每个模板类有自己类模板的static 数据成员的副本。
/**
*Copyright (c) 2018 Young Fan.All Right Reserved.
*Author: Young Fan
*Date: 2018.7.8
*IDE: Visual Studio 2017
*Description:
*/
#include <iostream>
using namespace std;
template<class T>
class Person
{
public:
static int a;
};
//类外初始化
template<class T> int Person<T>::a = 0;
int main()
{
Person<int> p1, p2, p3;
Person<char> pp1, pp2, pp3;
p1.a = 10;
pp1.a = 100;
cout << p1.a << " " << p2.a << " " << p3.a << endl; //打印:10 10 10
cout << pp1.a << " " << pp2.a << " " << pp3.a << endl;//打印: 100 100 100
//1.从类模板实例化的每一个模板类有自己的类模板数据成员,该模板的所有对象共享一个static 数据成员
//2.故每个模板类有自己类模板的static数据成员的副本,即具有独有的static成员
return 0;
}
总结:
1.从类模板实例化的每一个模板类有自己的类模板数据成员,该模板的所有对象共享一个static 数据成员
2.故每个模板类有自己类模板的static数据成员的副本,即具有独有的static成员