06-C++ 模板

模板、类型转换

模板

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 步骤

  1. 声明
  2. 使用

2.2 声明语法

//class 和 typename 都是一样的,用哪个都可以
template<class 模板名,class 模板名,...>
//或
template<typename 模板名,typename 模板名,...>

注意:

位置:函数上

2.3 使用语法

在该函数 任何一处 使用 数据类型 的地方都可以使 用模板名替换

2.4 注意

  1. 函数模板 可以 自动推导参数的类型,但是 不会进行类型转换
  2. 函数模板 可以自动类型推导,也可以显式指定类型
  3. 只能在 声明的 所在函数中使用

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=30
    add(10.2, 20.3); //a+b=30.5
    add('a', 'b'); //a+b=195
    //模板可以推导其对应的数据类型,但是无法进行自动转换
    //add(1, 'a'); // 报错

    //普通函数可以进行自动类型转换
    add02(1, 'a'); //a+b=98

    //模板可以显示替换
    add<int>(1,2); //a+b=3
    add<int>(1,'a'); //a+b=3
    return 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    a

    Data<int,double> d02(100,1.0);
    showData(d02); //100    1
    return 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);
  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值