栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++类模板

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++类模板

目录

类模板语法

类模板与函数模板的区别

类模板中的成员函数创建时机

类模板对象做函数参数

类模板与继承

类模板成员函数类外实现

类模板分文件编写

类模板与友元

类模板数组案例 —— 数组类封装


类模板语法

类模板作用:

  • 建立一个通用类,类中的成员数据类型可以不具体指定,用一个虚拟的类型来代替

语法:

template
// 类

解释:

template  ---  声明创建模板

-

class  ---  表面其后的符号是一种数据类型,可以用 typename 代替

-

T  ---  通用的数据类型,名称可以代替,通常为大写字母

测试代码:

#include
using namespace std;
#include

// 类模板
template
class Person{
public:
    Person(NameType name, AgeType age){
        this->m_Name = name;
        this->m_Age = age;
    }
    void ShowPerson(){
        cout << "name:" << this->m_Name << endl;
        cout << "age: " << this->m_Age  << endl;
    }
    NameType m_Name;
    AgeType m_Age;
};

void test(){
    // 类模板声明带上<类型>
    Personp1("李华", 19);
    p1.ShowPerson();
}

int main(){
    test();
    system("pause");
    return 0;
}

运行结果:


类模板与函数模板的区别

类模板与函数模板区别主要有两点:

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数

区别①测试:(将模板参数列表去掉)缺少 类模板的参数列表

 区别②测试代码:

#include
using namespace std;
#include

// 类模板
template // 默认参数列表
class Person{
public:
    Person(NameType name, AgeType age){
        this->m_Name = name;
        this->m_Age = age;
    }
    void ShowPerson(){
        cout << "name:" << this->m_Name << endl;
        cout << "age: " << this->m_Age  << endl;
    }
    NameType m_Name;
    AgeType m_Age;
};

void test(){
    Person p1("李华", 'a');// 模板的参数列表
    p1.ShowPerson();
}

int main(){
    test();
    system("pause");
    return 0;
}

运行结果:

  •  类模板使用只能用显示指定类型方式
  • 类模板中的模板参数列表可以有默认参数

类模板中的成员函数创建时机

类模板成员函数和普通类成员函数有区别:

  • 普通类中的成员函数一开始就可以创建
  • 类模板中的成员函数在调用时才创建

区别①代码测试:

#include
using namespace std;

class Person1{
public:
    void ShowPerson1(){
        cout << "Person1 show" << endl;
    }
};

class Person2{
public:
    void ShowPerson2(){
        cout << "Person2 show" << endl;
    }
};

template
class myClass{
public:
    T obj;
    // 类模板中的成员函数
    // 没有调用不会创建
    void func1(){
        obj.ShowPerson1();
    }
    void func2(){
        obj.ShowPerson2();
    }
};

void test(){

}

int main(){
    test();
    system("pause");
    return 0;
}

不会报错正常运行

区别②测试代码:

#include
using namespace std;

class Person1{
public:
    void ShowPerson1(){
        cout << "Person1 show" << endl;
    }
};

class Person2{
public:
    void ShowPerson2(){
        cout << "Person2 show" << endl;
    }
};

template
class myClass{
public:
    T obj;
    // 类模板中的成员函数
    // 没有调用不会创建
    void func1(){
        obj.ShowPerson1();
    }
    void func2(){
        obj.ShowPerson2();
    }
};

void test(){
    myClassm1;
    m1.func1();
    //m1.func2(); // 错误,确认为Person1类型,Person1类里没有func2中调用的成员函数

    myClassm2;
    //m2.func1(); // 错误,确认为Person2类型,Person2类里没有func1中调用的成员函数
    m2.func2();
}

int main(){
    test();
    system("pause");
    return 0;
}

运行结果:

类模板刚开始确定不了类型,直到给定类型才能确定 ,所以成员函数并不是一开始就创建的,在调用时才去创建。


类模板对象做函数参数

类模板实例化出的对象,向函数传参的方式

-

一共有三种方式:

  • 指定传入类型  ---  直接显示对象的数据类型
  • 参数模板化      ---  将对象中的参数变为模板进行传递
  • 整个类模板化  ---  将这个对象类型模板化进行传值

传入指定类型

测试代码:

#include
using namespace std;
#include

// 类模板对象做函数参数
template
class Person{
public:
    Person(T1 name, T2 age){
        this->m_Name = name;
        this->m_Age  = age;
    }
    T1 m_Name;
    T2 m_Age;
};

// 指定传入类型
void printPerson1(Person& p){
    cout << p.m_Name << endl;
    cout << p.m_Age  << endl;
}

void test01(){
    Person p1("李华", 18);
    printPerson1(p1);
}

int main(){
    test01();
    system("pause");
    return 0;
}

运行结果:

 参数模板化

传入模板化参数,再在函数上面对参数进行模板化

测试代码:

#include
using namespace std;
#include

// 类模板对象做函数参数
template
class Person{
public:
    Person(T1 name, T2 age){
        this->m_Name = name;
        this->m_Age  = age;
    }
    T1 m_Name;
    T2 m_Age;
};

// 参数模板化
template
void printPerson1(Person& p){
    cout << p.m_Name << endl;
    cout << p.m_Age  << endl;
}

void test01(){
    Person p1("张三", 18);
    printPerson1(p1);
}

int main(){
    test01();
    system("pause");
    return 0;
}

运行结果:

可以用 typeid(T1) 的方式查看推出来的属性

cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;

整个类模板化 

测试代码:

#include
using namespace std;
#include

// 类模板对象做函数参数
template
class Person{
public:
    Person(T1 name, T2 age){
        this->m_Name = name;
        this->m_Age  = age;
    }
    T1 m_Name;
    T2 m_Age;
};

// 整个类模板化
template
void printPerson1(T& p){
    cout << p.m_Name << endl;
    cout << p.m_Age  << endl;
    cout << typeid(T).name() << endl;
}

void test01(){
    Person p1("张三", 18);
    printPerson1(p1);
}

int main(){
    test01();
    system("pause");
    return 0;
}

运行结果:

  •  类模板创建的的对象,可以有三种方式向函数中进行传参
  • 使用比较广泛是第一种:指定传入的类型

类模板与继承

 类模板用到继承时需要注意:

  • 当子类继承的父类是一个类模板时,子类在声明时,要指定父类中 T 的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定父类中 T 的类型,子类也需要变成模板

注意①,父类是类模板,需要指定T

 怎么指定?

在类名后面加

class Son :public Base

测试代码:

#include
using namespace std;
#include
// 类模板与继承
template
class Base{
public:
    T m;
};

// 灵活指定父类中 T 类型,子类也需要变成类模板
template
class Son :public Base{
public:
    Son() {
        cout << typeid(T1).name() << endl;
        cout << typeid(T2).name() << endl;
    }
    T1 obj;
};

void test(){
    Sons2;
}

int main(){
    test();
    system("pause");
    return 0;
}

运行结果:

如果父类是类模板,子类需要指定出父类中 T 的数据类型 


类模板成员函数类外实现
#include
using namespace std;
#include
// 类模板中的成员函数类外实现
template
class Person {
public:
    Person(T1 name, T2 age); 
    void showPerson();
    T1 m_Name;
    T2 m_Age;
};

// 构造函数类外实现,类模板参数列表写在作用域后面
template
Person::Person(T1 name, T2 age) {
    this->m_Name = name;
    this->m_Age  = age;
}

// 成员函数类外实现,类模板参数列表写在作用域后面
template
void Person::showPerson() {
    cout << this->m_Name << endl;
    cout << this->m_Age  << endl;
}

void test() {
    Personp1("李华", 18);
    p1.showPerson();
}

int main(){
    test();
    system("pause");
    return 0;
}

运行结果:

类外实现时,要加上作用域,要加上类模板参数列表写在作用域后面(两个冒号的前面),template告诉编译器这是一个类模板参数列表中的T1和T2是什么。

类外实现类模板的构造函数

// 构造函数类外实现
template
Person::Person(T1 name, T2 age) {
    this->m_Name = name;
    this->m_Age  = age;
}

类外实现类模板的成员函数

// 成员函数类外实现
template
void Person::showPerson() {
    cout << this->m_Name << endl;
    cout << this->m_Age  << endl;
}

类模板中成员函数类外实现时,需要加上类模板参数列表 


类模板分文件编写

问题:类模板中成员函数创建时机时在调用阶段,导致份文件编写时链接不到

-

解决:

  • 方法1:直接包含 .cpp 文件
  • 方法2:将声明和实现写在同一个文件中,并更改后缀名为 .hpp,hpp 是约定的名称,并不是强制

方法1:

直接包含源码

测试代码:

Person.h

#pragma once
#include
#include
using namespace std;

// 类模板分文件编写问题
template
class Person {
public:
    Person(T1 name, T2 age);
    void showPerson();
    T1 m_Name;
    T2 m_Age;
};

Person.cpp

#include"Person.h"

// 构造函数类外实现
template
Person::Person(T1 name, T2 age) {
    this->m_Name = name;
    this->m_Age = age;
}

// 成员函数类外实现
template
void Person::showPerson() {
    cout << this->m_Name << endl;
    cout << this->m_Age << endl;
}

main.cpp

#include"Person.h"

void test() {
    Personp1("李华", 18);
    p1.showPerson();
}

int main(){
    test();
    system("pause");
    return 0;
}

原因:进行分文件,分为 Person.h 和 Person.cpp 两个文件,在 .h 文件里写声明,在 .cpp 文件里写实现,最后在 main.cpp 中去调用,结果会出现无法解析的外部命令,说明是写了函数实现,但没有声明,按道理说 .h 里都给了声明,但编译器为什么认为没有?其实在类模板中成员函数是不会一开始就创建(类模板中的成员函数创建时机是在调用阶段),编译器也就没有发现类模板成员函数的声明了。

解决办法:将 main.cpp 中包含的文件改为 Person.cpp

#include"Person.cpp"

void test() {
    Personp1("李华", 18);
    p1.showPerson();
}

int main(){
    test();
    system("pause");
    return 0;
}

运行结果: 

让编译器去看 Person.cpp 中的内容(直接包含源文件) (用的不多)

方法2:(常用)

将 .h 和 .cpp 文件中的内容写在一起,将文件后缀改为 .hpp

hpp 是约定的,看到这个就可以知道这里面是类模板

-

那这个文件属于源文件还是头文件?

答案是:头文件

测试代码:

Person.hpp

#pragma once
#include
#include
using namespace std;

template
class Person {
public:
    Person(T1 name, T2 age);
    void showPerson();
    T1 m_Name;
    T2 m_Age;
};

// 构造函数类外实现
template
Person::Person(T1 name, T2 age) {
    this->m_Name = name;
    this->m_Age = age;
}

// 成员函数类外实现
template
void Person::showPerson() {
    cout << this->m_Name << endl;
    cout << this->m_Age << endl;
}

 main.cpp

#include"Person.hpp"

void test() {
    Personp1("李华", 18);
    p1.showPerson();
}

int main(){
    test();
    system("pause");
    return 0;
}

 运行结果:

主流解决方法是第二种,将类模板写在一起,修改后缀名为 .hpp。 (便于阅读和理解)


类模板与友元

全局函数类内实现:直接在类内声明友元

全局函数类外实现:需要提前让编译器知道全局函数的存在,不用加作用域

全局函数类内实现

测试代码:

#include
using namespace std;

// 通过全局函数打印类信息

template
class Person {
    // 全局函数 类内实现
    friend void printPerson(Person p) {
        cout << p.m_Name << endl;
        cout << p.m_Age  << endl;
    }

public:
    Person(T1 name, T2 age) {
        this->m_Name = name;
        this->m_Age = age;
    }
private:
    T1 m_Name;
    T2 m_Age;
};

void test() {
    Personp1("李华", 19);
    printPerson(p1);
}

int main(){
    test();
    system("pause");
    return 0;
}

 运行结果:

全局函数类外实现

测试代码:

#include
using namespace std;

// 声明 Person 类,并告诉编译器这个 Person 是一个类模板
template
class Person;
// 让编译器提前知道这个全局函数
template
void printPerson(Person p);

template
class Person {
    // 类内声明
    // 函数名后加空模板参数列表
    friend void printPerson<>(Person p);

public:
    Person(T1 name, T2 age) {
        this->m_Name = name;
        this->m_Age  = age;
    }
private:
    T1 m_Name;
    T2 m_Age;
};

// 类外实现
// 让函数参数内认识 T1 和 T2
template
void printPerson(Person p) {
    cout << p.m_Name << endl;
    cout << p.m_Age  << endl;
}

void test() {
    Personp1("李华", 19);
    printPerson(p1);
}

int main(){
    test();
    system("pause");
    return 0;
}

运行结果:

建议类内实现(简单,编译器可以直接识别)


类模板数组案例 —— 数组类封装

实现通用的数组类的要求:

  • 可以对内置数据类型以及自定义数据类型的数据进行存储
  • 数组数据存储到堆区
  • 构造函数可以传入数组容量
  • 提供对应的拷贝构造函数以及 operator= 防止浅拷贝问题
  • 提供尾插法和尾删法对数组中的数据进行增加和删除
  • 可以通过下标访问数组中的元素
  • 可以获取数组中当前元素个数和数组的容量

测试代码:
.hpp

#include
#include
using namespace std;

template
class MyArray {
public:
    // 有参拷贝,指定数组容量
    MyArray(int capacity) {
        cout << "有参构造" << endl;
        this->m_Capacity = capacity;
        this->m_Size = 0;
        this->pAddress = new T[this->m_Capacity];
    }
    // 释放堆区指针
    ~MyArray() {
        cout << "析构" << endl;
        if (this->pAddress != NULL) {
            delete[] this->pAddress;
            this->pAddress = NULL;
        }
    }
    // 拷贝构造
    MyArray(const MyArray& arr) {
        cout << "拷贝构造" << endl;
        // 更新容量
        this->m_Capacity = arr.m_Capacity;
        // 更新大小
        this->m_Size = arr.m_Size;
        // 深拷贝
        this->pAddress = new T[arr.m_Capacity];
        // 将arr中数据拷贝过来
        for (int i = 0; i < this->m_Size; i++) {
            this->pAddress[i] = arr.pAddress[i];
        }
    }

    // operator= 防止浅拷贝问题
    MyArray& operator=(const MyArray& arr) {
        cout << "运算符重载" << endl;
        // 先判断原来堆区是否有数据,如果有先释放
        if (this->pAddress != NULL) {
            delete[] this->pAddress;
            this->pAddress = NULL;
            this->m_Capacity = 0;
            this->m_Size = 0;
        }
        // 深拷贝
        this->m_Capacity = arr.m_Capacity;
        this->m_Size = arr.m_Size;
        this->pAddress = new T[arr.m_Capacity];

        for (int i = 0; i < this->m_Size; i++) {
            this->pAddress[i] = arr.pAddress[i];
        }
        // 返回自身
        return *this;
    }

private:
    T* pAddress;    // 指向堆区开辟的真实数组
    int m_Capacity; // 数组容量
    int m_Size;     // 数组大小
};

main.cpp

#include"myArray.hpp"

void test() {
    MyArray arr(10);
    MyArray arr2(arr);
    MyArray arr3(20);
    arr3 = arr;
}

int main()
{
    test();
    system("pause");
    return 0;
}

测试结果:

完成代码:

myArray.hpp

#include
#include
using namespace std;

template
class MyArray {
public:
    // 有参拷贝,指定数组容量
    MyArray(int capacity) {
        this->m_Capacity = capacity;
        this->m_Size = 0;
        this->pAddress = new T[this->m_Capacity];
    }
    // 释放堆区指针
    ~MyArray() {
        if (this->pAddress != NULL) {
            delete[] this->pAddress;
            this->pAddress = NULL;
        }
    }
    // 拷贝构造
    MyArray(const MyArray& arr) {
        // 更新容量
        this->m_Capacity = arr.m_Capacity;
        // 更新大小
        this->m_Size = arr.m_Size;
        // 深拷贝
        this->pAddress = new T[arr.m_Capacity];
        // 将arr中数据拷贝过来
        for (int i = 0; i < this->m_Size; i++) {
            this->pAddress[i] = arr.pAddress[i];
        }
    }

    // operator= 防止浅拷贝问题
    MyArray& operator=(const MyArray& arr) {
        // 先判断原来堆区是否有数据,如果有先释放
        if (this->pAddress != NULL) {
            delete[] this->pAddress;
            this->pAddress = NULL;
            this->m_Capacity = 0;
            this->m_Size = 0;
        }
        // 深拷贝
        this->m_Capacity = arr.m_Capacity;
        this->m_Size = arr.m_Size;
        this->pAddress = new T[arr.m_Capacity];

        for (int i = 0; i < this->m_Size; i++) {
            this->pAddress[i] = arr.pAddress[i];
        }
        // 返回自身
        return *this;
    }

    // 尾插法
    void Push_Back(const T& val) {
        // 判断容量是否等于大小
        if (this->m_Capacity == this->m_Size) {
            return;
        }
        this->pAddress[this->m_Size] = val; // 数组尾部插入数据
        this->m_Size++;
    }

    // 尾删法
    void Pop_Back() {
        // 不能为 0
        if (this->m_Size == 0) {
            return;
        }
        // 访问不到最后一个元素
        this->m_Size--;
    }

    // 下标方式访问,本身作为返回
    T& operator[](int index) {
        return this->pAddress[index];
    }

    // 返回数组容量
    int getCapacity() {
        return this->m_Capacity;
    }

    // 返回数组长度
    int getSize() {
        return this->m_Size;
    }
private:
    T* pAddress;    // 指向堆区开辟的真实数组
    int m_Capacity; // 数组容量
    int m_Size;     // 数组大小
};

main.cpp

#include"myArray.hpp"

void test() {
    MyArray arr(10);
    // 重载 [] 和 = 的测试
    for (int i = 0; i < 10; i++) {
        arr[i] = 10;
    }
    for (int i = 0; i < 10; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    // 尾删法测试
    for (int i = 0; i < 10; i++) {
        arr.Pop_Back();
    }
    cout << endl;
    // 尾插法测试
    for (int i = 0; i < 10; i++) {
        arr.Push_Back(i);
    }
    for (int i = 0; i < 10; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    //arr3 = arr;
}

int main()
{
    test();
    system("pause");
    return 0;
}

运行结果:


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1026814.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号