模板、类型转换
模板
1. 简介
一种用于实现 通用编程 的机制。
将 数据类型 可以作为参数进行传递 。
通过使用模板,我们可以编写可复用的代码,可以适用于多种数据类型。
c++模板的语法使用尖括号 < >
来表示泛型类型,并使用关键字 template
来定义和声明模板。
示例:
#include <iostream>using namespace std;
// 定义一个模板函数,此处 typename 可以替换成 class 二者作用相同
template<typename T>
T add(T a, T b)
{return a + b;
}int main(int argc, char *argv[])
{int x = 5, y = 10;float f1 = 2.5, f2 = 3.7;// 调用模板函数std::cout << "Sum of integers: " << add(x, y) << std::endl;std::cout << "Sum of floats: " << add(f1, f2) << std::endl;return 0;
}
1.1 原理
编译器并不是把函数模板处理成能够处理任何类型的函数;
函数模板通过具体类型产生不同的函数
编译器会对函数模板进行
两次编译
:
- 在
声明
的地方对模板代码本身
进行编译;- 在
调用
的地方对参数替换后的代码
进行编译
1.2 分类
函数模板
类模板
2. 函数模板
2.1 步骤
- 声明
- 使用
2.2 声明语法
//class 和 typename 都是一样的,用哪个都可以
template<class 模板名,class 模板名,...>
//或
template<typename 模板名,typename 模板名,...>
注意:
位置:函数上
2.3 使用语法
在该函数
任何一处
使用数据类型
的地方都可以使用模板名替换
。
2.4 注意
- 函数模板 可以
自动推导参数的类型
,但是不会进行类型转换
- 函数模板 可以自动类型推导,
也可以显式指定类型
- 只能在 声明的 所在函数中使用
2.5 示例
#include <iostream>using namespace std;
template<class X>
void add(X a, X b)
{cout << "a+b=" << a + b << endl;
}
void add02(int a, int b)
{cout << "a+b=" << a + b << endl;
}int main(int argc, char *argv[])
{add(10, 20); //a+b=30add(10.2, 20.3); //a+b=30.5add('a', 'b'); //a+b=195//模板可以推导其对应的数据类型,但是无法进行自动转换//add(1, 'a'); // 报错//普通函数可以进行自动类型转换add02(1, 'a'); //a+b=98//模板可以显示替换add<int>(1,2); //a+b=3add<int>(1,'a'); //a+b=3return 0;
}
2.6 普通函数和模板函数的区别
(1)、函数模板不允许自动类型转化,普通函数能够自动进行类型转;
示例:
同上面示例
(2)、函数模板和普通函数同时识别,优先使用普通函数
,加<>强制使用函数模板
示例:
template<typename T>
void func(T a, T b){cout<<"函数模板"<<endl;
}
//普通函数
void func(int a, int b){cout<<"普通函数"<<endl;
}
void test02()
{ //普通函数func(10, 20);//显示调用函数模板func<>(10, 20);
}
(3)、函数模板可以使用<>,普通函数不行
2.7 函数模板的局限性
在函数模板的数据类型 为对象的时候,打印对象会报错,因为 没有 重载<< 运算符
解决方案:
- 重载 << 运算符
- 函数模板具体化
示例:
#include <iostream>
#include <cstring>
using namespace std;template <class X>
void print(X x)
{cout << x << endl;
}class Person{
// friend ostream& operator <<(ostream& out,Person& p);friend void print<Person>(Person p);
private:char name[50];int age;
public:Person(){}Person(char *name,int age){strcpy(this->name,name);this->age = age;}
};//1、重载运算符
//ostream& operator <<(ostream& out,Person& p)
//{
// out << p.name << "\t" << p.age << endl;
// return out;
//}//2、函数模板具体化
template<> void print<Person>(Person p)
{cout << p.name << "\t" << p.age << endl;
}
int main(int argc, char *argv[])
{print(10);Person p("张三",18);print(p);return 0;
}//10
//张三 18
3. 类模板
3.1 步骤、语法
同函数模板
3.2 位置
在类上
3.3 使用
3.3.1 创建对象
用法:
类名<实际数据类型1,实际数据类型2,...> 对象名(实参列表);
注意:
- 类模板
实例化对象
是不能自动推导类型
,需指定
示例:
#include <iostream>
#include <string>
using namespace std;
template<class X,class Y>
class Data{
private:X x;Y y;
public:Data(){}Data(X x,Y y){this->x = x;this->y = y;}void print(){cout << "x = " << x << endl;cout << "y = " << y << endl;}
};int main(int argc, char *argv[])
{//类模板创建对象Data<int,char> d01(10,'a');d01.print();Data<string,int> d02("张三",18);d02.print();
// 类模板创建对象时必须说明其模板的数据类型
// Data d03("张三",18); //报错
// d03.print();return 0;
}//x = 10
//y = a
//x = 张三
//y = 18
3.3.2 类模板的派生
- 类模板派生 普通子类
- 类模板派生 类 模板
示例:
#include <iostream>
#include <string>
using namespace std;
template<class X,class Y>
class Data{
private:X x;Y y;
public:Data(){}Data(X x,Y y){this->x = x;this->y = y;}void print(){cout << "x = " << x << endl;cout << "y = " << y << endl;}
};
//子类继承与类模板,要么说明其模板的数据类型
class Content:public Data<int,int>{
public:Content(){}Content(int x,int y):Data(x,y){}
};
//要么自己也是类模板
template <class X,class Z>
class Context:public Data<X,Z>{
public:Context(){}Context(X x,Z y):Data<X,Z>(x,y){}
};
int main(int argc, char *argv[])
{Content c01(10,20);c01.print();Context<string,string> c02("德玛","男");c02.print();return 0;
}//x = 10
//y = 20
//x = 德玛
//y = 男
3.3.3 类模板成员函数外部实现
注意:外部实现函数时也需定义与类相同的函数模板
示例:
#include <iostream>using namespace std;
template<class X,class Y>
class Data{
private:X x;Y y;
public:Data();Data(X x,Y y);void print();
};
//外部实现,也需要定义类的模板
template<class X,class Y>
Data<X,Y>::Data()
{}
template<class X,class Y>
Data<X,Y>::Data(X x,Y y)
{this->x = x;this->y = y;
}
template<class X,class Y>
void Data<X,Y>::print()
{cout << "x = " << x << endl;cout << "y = " << y << endl;
}int main(int argc, char *argv[])
{Data<int,double> d01(10,20.0);d01.print();return 0;
}//x = 10
//y = 20
3.3.4 类模板的分文件实现
在实例化模板之前,编译器对模板的定义体是不处理的
在实例化模板时编译器必须在上下文中可以查看到其定义实体
因此模板的实例化与定义体必须放到同一文件中。
但是如果文件名为.h皆为是头文件,在头文件中定义函数不符合头文件的将声明与实例分开的规则
顾将头文件名改为hpp,并在前内部进行示例化
data.hpp
头文件
#include <iostream>using namespace std;
template<class X,class Y>
class Data{
private:X x;Y y;
public:Data();Data(X x,Y y);void print();
};
template<class X,class Y>
Data<X,Y>::Data()
{}
template<class X,class Y>
Data<X,Y>::Data(X x,Y y)
{this->x = x;this->y = y;
}
template<class X,class Y>
void Data<X,Y>::print()
{cout << "x = " << x << endl;cout << "y = " << y << endl;
}
main.cpp
#include <iostream>
#include "data.hpp"
using namespace std;int main(int argc, char *argv[])
{Data<int,double> d01(10,20.0);d01.print();return 0;
}//x = 10
//y = 20
3.3.5 类模板作为函数参数
注意:该函数必须是类模板的友元函数
-
类模板的对象作为参数,数据类型写死,就是int,char等,
- 弊端:在调用该函数时,传递的对象的数据类型就只能是写死的类型
-
将该函数也创建成与类模板相同的函数模板,函数的参数是 同类模板的数据类型的类模板对象
示例:
#include <iostream>using namespace std;
template<class X,class Y>
class Data{//
// friend void showData(Data<int,char> & data);template<class Z,class W>friend void showData(Data<Z,W> & data);
private:X x;Y y;
public:Data(){}Data(X x,Y y){this->x = x;this->y = y;}
};//类模板的对象作为参数,数据类型写死,就是int,char,
//弊端:在调用该函数时,传递的对象的数据类型就只能是写死的类型
//void showData(Data<int,char> & data)
//{
// cout << data.x << "\t" << data.y << endl;
//}//将该函数也创建成与类模板相同的函数模板,
//函数的参数是 同类模板的数据类型的类模板对象
template<class X,class Y>
void showData(Data<X,Y> & data)
{cout << data.x << "\t" << data.y << endl;
}int main(int argc, char *argv[])
{Data<int,char> d01(100,'a');showData(d01); //100 aData<int,double> d02(100,1.0);showData(d02); //100 1return 0;
}
4.练习:自定义集合
示例:数组长度可变,可以存储基本类型数据,也可以存储对象
arraylist.hpp
头文件
#include <iostream>
#include <cstring>
#include <cstdlib>using namespace std;
template<class T>
class ArrayList{
private://记录数组的地址T *data;//记录已存数组的个数int size;//记录可容纳数组的个数int count;
public:ArrayList();~ArrayList();//添加数据函数void add(T t);//获取集合函数T& get(int index);//获取集合长度int getSize();
};
//实现无参构造
template<class T>
ArrayList<T>::ArrayList()
{this->count = 2;this->size = 0;this->data = new T[count];
}
//实现析构
template<class T>
ArrayList<T>::~ArrayList()
{if(data != NULL){delete [] data;data = NULL;}
}
//实现添加数据函数
template<class T>
void ArrayList<T>::add(T t)
{if(size == count){//满了,扩容count = count * 2;T *newData = new T[count];memcpy(newData, data, size*sizeof(T));delete [] data;data = newData;}data[size] = t;size++;
}
//实现获取数据函数
template<class T>
T& ArrayList<T>::get(int index)
{return data[index];
}
//实现获取集合长度函数
template<class T>
int ArrayList<T>::getSize()
{return size;
}
main.cpp
#include <iostream>
#include "arraylist.hpp"using namespace std;void fun01()
{ArrayList<int> a1;a1.add(1);a1.add(3);a1.add(5);a1.add(7);a1.add(9);int size = a1.getSize();cout << "长度为:" << size << endl;for(int i = 0; i < size; i++){cout << a1.get(i) << endl;}
}class Person{
private:char *name;int age;
public:Person(){}Person(char *name, int age){//指针直接赋值this->name = name;//字符串指针不可以使用strcpy,字符串指针存储的是字符串的首地址,使用strcpy会出现内存污染
// strcpy(this->name, name);this->age = age;}~Person(){}void print_info(){cout << "姓名: " << name << "\t年龄:" << age << endl;}
};void fun02()
{Person p1("张三", 18);Person p2("李四", 13);Person p3("王五", 13);Person p4("钱⑥", 12);
// p1.print_info();ArrayList<Person> a1;a1.add(p1);a1.add(p2);a1.add(p3);a1.add(p4);int size = a1.getSize();cout << "长度为:" << size << endl;for(int i = 0; i < size; i++){a1.get(i).print_info();}}int main(int argc, char *argv[])
{fun01();fun02();return 0;
}//长度为:5
//1
//3
//5
//7
//9
//长度为:4
//姓名: 张三 年龄:18
//姓名: 李四 年龄:13
//姓名: 王五 年龄:13
//姓名: 钱⑥ 年龄:12
类型转换
1.上行转换、下行转换
-
上行转换
- 父类指针指向子类空间 安全的
-
下行转换
- 子类指针指向父类空间 不安全的
2. 类型转换
2.1 C提供的强制转换
语法: (转换后的类型)要转换的数据或变量
2.2 静态转换
语法:
static_cast<T>(要转换的数据)
示例:
//基本类型转换 支持
int num = static_cast<int>(3.14f);//基本指针类型转换 不支持
float f=0.0f;
//int *p1 = static_cast<int *>(&f);//上行转换 支持(安全)
Base *p2 = static_cast<Base *>(new Son);//下行转换 支持(不安全)
Son *p3 = static_cast<Son *>(new Base);//不相关类型转换 不支持
//Son *p4 = static_cast<Son *>(new Other);
2.3 动态类型转换
语法:
dynamic_cast<T>(要转换的数据)
示例:
//基本类型转换 不支持
//int num = dynamic_cast<int>(3.14f);//基本指针类型转换 不支持
float f=0.0f;
//int *p1 = dynamic_cast<int *>(&f);//上行转换 支持(安全)
Base *p2 = dynamic_cast<Base *>(new Son);//下行转换 不支持(不安全)
//Son *p3 = dynamic_cast<Son *>(new Base);//不相关类型转换 不支持
//Son *p4 = dynamic_cast<Son *>(new Other);
2.4 常量转换
语法:
const_cast<T>
注意:
只能对指针与引用的变量使用
示例:
//将非const 转换成 const
int num = 10;
const int *p1 = const_cast<const int *>(&num);//将const 转换成 非const
const int data=0;
int *p = const_cast<int *>(&data);
2.5 重新解释转换
简介:
这是最不安全的一种转换机制,最有可能出问题。
主要用于将一种数据类型从一种类型转换为另一种类型。它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针.
语法:
reinterpret_cast<T>
示例:
//基本类型转换 不支持
//int num = reinterpret_cast<int>(3.14f);//基本指针类型转换 支持
float f=0.0f;
int *p1 = reinterpret_cast<int *>(&f);//上行转换 支持(安全)
Base *p2 = reinterpret_cast<Base *>(new Son);//下行转换 支持(不安全)
Son *p3 = reinterpret_cast<Son *>(new Base);//不相关类型转换 支持
Son *p4 = reinterpret_cast<Son *>(new Other);