C++复习笔记--模板的使用

本文详细介绍了C++中的模板机制,包括函数模板的基本知识、代码实例、相关知识,以及类模板的语法、与函数模板的区别、成员函数创建时机、对象作为函数参数等方面。通过示例代码展示了模板如何提高代码复用性和泛型编程的能力。
摘要由CSDN通过智能技术生成

目录

1--基本概念

2--函数模板

2-1--基本知识

2-2--代码实例:

2-3--相关知识

3--类模板

3-1--类模板的语法

3-2--类模板和函数模板的区别

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

3-4--类模板对象作为函数参数

3-5--类模板与继承

3-6--类模板成员函数的类外实现

3-7--类模板分文件编写

3-8--类模板与友元

3-9--类模板实例


1--基本概念

        模板是C++泛型编程的重要支撑技术,C++提供两种模板机制:函数模板 和 类模板

2--函数模板

2-1--基本知识

        函数模板的作用:建立一个 通用函数,其 函数返回值类型 和 形参类型 不需要进行具体指定,只需用一个 虚拟的类型 来表示;

        使用模板有两种方式:自动类型推导 和 显示指定类型

        自动类型推导,必须推导出一致的数据类型T;        

        模板必须确定出 T 的数据类型,才可正常使用;

        模板的目的:提高复用性,将类型参数化;

        基本语法:

// 函数声明或定义
template<typename T>

// template --- 声明创建模板
// typename --- 表明其符号是一种数据类型,可以用 class 代替
// T --- 通用的数据类型,名称可以替换,通常为大写字母

2-2--代码实例:

        利用函数模板实现数值交换功能:

#include <iostream>

// 函数模板
template<typename T>
// 利用函数模板实现交换函数
void mySwap(T &a, T &b){
    T temp = a;
    a = b;
    b = temp;
}

int main(){
    int a = 10;
    int b = 20;
    std::cout << "before change: " << std::endl;
    std::cout << "a: " << a << " b: " << b << std::endl;
    mySwap(a, b); // 隐式指定模板类型
    std::cout << "After change: " << std::endl;
    std::cout << "a: " << a << " b: " << b << std::endl;

    double c = 10.0;
    double d = 20.0;
    std::cout << "before change: " << std::endl;
    std::cout << "c: " << c << " d: " << d << std::endl;
    mySwap<double>(c, d); // 显示指定模板类型
    std::cout << "After change: " << std::endl;
    std::cout << "c: " << c << " d: " << d << std::endl;

    return 0;
}

        从大到小排列数组:

#include <iostream>

//交换
template<typename T>
void mySwap(T &a, T &b){
    T temp = a;
    a = b;
    b = temp; 
}

// 排序算法
template<typename T>
void mySort(T arr[], int len){
    for(int i = 0; i < len; i++){
        int max = i;
        for(int j = i+1; j < len; j++){
            if(arr[max] < arr[j]){
                max = j;
            }
        }

        if (max != i){
            mySwap(arr[max], arr[i]);
    }
    }
}

//打印
template<typename T>
void printArray(T arr[], int len){
    for(int i = 0; i < len; i++){
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
}

int main(){
    char charArr[] = "bacdfe";
    int num1 = sizeof(charArr) / sizeof(char);
    mySort(charArr, num1);
    printArray(charArr, num1);

    int intArr[] = {6, 5, 2, 3, 1, 4};
    int num2 = sizeof(intArr) / sizeof(int);
    mySort(intArr, num2);
    printArray(intArr, num2);

    return 0;
}

2-3--相关知识

        ① 普通函数和函数模板的区别:

        A. 普通函数调用可以发生隐式类型转换

        B. 函数模板使用自动类型推导时,不能发生隐式类型转换

        C. 函数模板使用显式类型推导时,可以发生隐式类型转换

#include <iostream>

// 普通函数
int myAdd01(int a, int b){
    return a + b;
}

// 函数模板
template<typename T>
T myAdd02(T a, T b){
    return a + b;
}

int main(){
    int a = 10;
    int b = 20;
    int c = 'c'; // 99
    std::cout << myAdd01(a, c) << std::endl; // 发生隐式类型转换
    // std::cout << myAdd02(a, c) << std::endl; // 不能发生隐式类型转换
    std::cout << myAdd02<int>(a, c) << std::endl; // 可以发生隐式类型转换
    return 0;
}

        ② 普通函数和函数模板的调用规则:

        A. 如果函数模板和普通函数都可以实现,优先调用普通函数

        B. 可以通过空模板参数列表来强制调用函数模板;

        C. 函数模板可以发生重载

        D. 如果函数模板可以产生更好的匹配,则会优先调用函数模板

#include <iostream>

// 普通函数
void myPrint(int a, int b){
    std::cout << "Using normal function" << std::endl;
}

// 函数模板
template<typename T>
void myPrint(T a, T b){
    std::cout << "Using template function" << std::endl;
}

// 重载函数模板
template<typename T>
void myPrint(T a, T b, T c){
    std::cout << "Using overloaded template function" << std::endl;
}

int main(){
    int a = 10;
    int b = 20;

    myPrint(a, b); // 优先调用普通函数
    myPrint<>(a, b); // 使用空模板参数列表来强制调用函数模板
    myPrint(a, b, 100); // 调用重载的函数模板

    char c = 'c';
    char d = 'd';
    myPrint(c, d); // 因为普通函数需要发生隐式类型转换,而函数模板不需要,所以函数模板更适配,将调用函数模板
    return 0;
}

         ③ 模板的局限性:

        下面代码中,如果传入的 a 和 b 是数组类型或自定义数据类型,就无法正常运行;

        可以为特定情形的数据类型定义重载函数来解决上述问题;

template<typename T>
void f(T a, T b){
    a = b;
}
#include <iostream>

class Person{
public:
    Person(std::string name, int age){
        this->Name = name;
        this->Age = age;
    }

    std::string Name;
    int Age;
};

// 判断两数是否相等
template<typename T>
bool myCompare(T &a, T &b){
    if (a == b){
        return true;
    }
    else{
        return false;
    }
}

// 为类的比较重载一个新的函数
template<> bool myCompare(Person &p1, Person &p2){
    if(p1.Name == p2.Name && p1.Age == p2.Age){
        return true;
    }
    else{
        return false;
    }
}

void test01(){
    int a = 10;
    int b = 20;
    bool ret = myCompare(a, b);
    if(ret){
        std::cout << "a == b" << std::endl;
    }
    else{
        std::cout << "a != b" << std::endl;
    }
}

void test02(){
    Person p1("Tom", 18);
    Person p2("Tom", 18);
    bool ret = myCompare(p1, p2);
    if(ret){
        std::cout << "p1 == p2" << std::endl;
    }
    else{
        std::cout << "p1 != p2" << std::endl;
    }
}

int main(){
    test01();
    test02();
    return 0;
}

3--类模板

3-1--类模板的语法

        基本语法:

template<class Type1, class Type2>

        简单实例:

# include <iostream>
# include <string>

template<class NameType, class AgeType>
class Person{
public:
    Person(NameType name, AgeType age){
        this->Name = name;
        this->Age = age;
    }

    void show(){
        std::cout << "Name: " << Name << " Age: " << Age << std::endl;
    }
    NameType Name;
    AgeType Age;
};

int main(){

    Person<std::string, int> p1("ZhangSan", 18);
    p1.show();

    return 0;
}

3-2--类模板和函数模板的区别

        类模板和函数模板的区别:

① 类模板不能使用自动类型推导;

② 类模板在模板参数列表中可以有默认参数

        简单实例:

# include <iostream>
# include <string>

template<class NameType, class AgeType = int> // 使用默认参数
class Person{
public:
    Person(NameType name, AgeType age){
        this->Name = name;
        this->Age = age;
    }

    void show(){
        std::cout << "Name: " << Name << " Age: " << Age << std::endl;
    }
    NameType Name;
    AgeType Age;
};

int main(){

    // Person p1("ZhangSan", 18); // 错误使用,不能进行自动类型推导;
    Person<std::string> p1("ZhangSan", 18); // 使用了默认类型 AgeType = int
    p1.show();

    return 0;
}

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

在普通类中,其成员函数在最初就被创建;

在类模板中,其成员函数只有在被调用时才会创建;

3-4--类模板对象作为函数参数

        三种传递方式:

① 指定传入的类型:直接显示对象的数据类型;(最常用)

② 参数模板化: 将对象中的参数变为模板进行传递;

③ 整个类模板化:将对象进行模板化后再传递;

# include <iostream>
# include <string>

template<class T1, class T2>
class Person{
public:
    Person(T1 name, T2 age){
        this -> Name = name;
        this -> Age = age;
    }
    void show(){
        std::cout << "Name: " << Name << " Age: " << Age << std::endl;
    }
    T1 Name;
    T2 Age;
};

// 类模板对象 作为函数参数
void printPerson1(Person<std::string, int> &p){
    p.show();
}

// 参数模板化
template<class T1, class T2>
void printPerson2(Person<T1, T2> &p){
    p.show();
    std::cout << "T1.Type: " << typeid(T1).name() << std::endl;
    std::cout << "T2.Type: " << typeid(T2).name() << std::endl;
}

// 整个类模板化
template<class T>
void printPerson3(T &p){
    p.show();
    std::cout << "T.Type: " << typeid(T).name() << std::endl;
}

int main(){

    Person<std::string, int>p1("Zhangsan", 18); // 实例化对象

    // 类模板对象p1 作为函数参数
    printPerson1(p1);
    // 参数模板化
    printPerson2(p1);
    // 整个类模板化
    printPerson3(p1);

    return 0;
}

3-5--类模板与继承

在类模板中,子类继承父类时,需要指定父类的模板类型;

# include <iostream>
# include <string>

template<class T>
class Base{
    T m;
};

// 需要明确指定父类的模板类型,才能正常继承
class Son1: public Base<int>{ 

};

// 将子类设为类模板,可以不明确指定父类的模板类型
template<class T1, class T2>
class Son2: public Base<T1>{ // T1指定父类T的类型,T2是子类特有的类型模板
    T2 obj;
};

int main(){

    return 0;
}

3-6--类模板成员函数的类外实现

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

# include <iostream>
# include <string>

template<class T1, class T2>
class Person{
public:
    Person(T1 name, T2 age);
    void showPerson();
    T1 Name;
    T2 Age;
};

// 构造函数的类外实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age){
    this -> Name = name;
    this -> Age = age;
}

// 成员函数的类外实现
template<class T1, class T2>
void Person<T1, T2>::showPerson(){
    std::cout << "Name: " << Name << " Age: " << Age << std::endl;
}

int main(){

    Person<std::string, int> p1("Zhangsan", 18);
    p1.showPerson();
    return 0;
}

3-7--类模板分文件编写

        将头文件和 cpp 文件写在一起,后缀名为 .hpp

        代码实例:

// person.hpp

#pragma once
#include <iostream>

template<class T1, class T2>
class Person{
public:
    Person(T1 name, T2 age);
    void showPerson();
    T1 Name;
    T2 Age;
};

// 构造函数的分文件实现
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age){
    this -> Name = name;
    this -> Age = age;
}

// 成员函数的分文件实现
template<class T1, class T2>
void Person<T1, T2>::showPerson(){
    std::cout << "Name: " << Name << " Age: " << Age << std::endl;
}
// Test.cpp

# include <iostream>
# include <string>
# include <person.hpp>

int main(){

    Person<std::string, int> p1("Zhangsan", 18);
    p1.showPerson();
    return 0;
}
# CMakeLists.txt

cmake_minimum_required(VERSION 3.0)

project(Test)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")

set(CMAKE_BUILD_TYPE Debug)

include_directories(include)

add_executable(test Test.cpp include/person.hpp)

3-8--类模板与友元

        类内和类外实现友元函数:

# include <iostream>
# include <string>

// 全局函数 类外实现 (要让编译器提前知道,所以放在最上面)
template<class T1, class T2>
class Person;

template<class T1, class T2>
void printPerson(Person<T1, T2> p){
    std::cout << "Name: " << p.Name << " Age: " << p.Age << std::endl;
}

template<class T1, class T2>
class Person{

// 全局函数 类内实现
// friend void printPerson(Person<T1, T2> p){
//     std::cout << "Name: " << p.Name << " Age: " << p.Age << std::endl;
// }

// 全局函数 类外实现
friend void printPerson<>(Person<T1, T2> p);

public:
    Person(T1 name, T2 age){
        this -> Name = name;
        this -> Age = age;
    }

private:
    T1 Name;
    T2 Age;
};

int main(){

    Person<std::string, int> p1("Zhangsan", 18);
    printPerson(p1);
    return 0;
}

3-9--类模板实例

        利用模板实现自定义通用的数组类,实现的需求如下:

① 可以对内置数据类型以及自定义数据类型的数据进行存储;

② 将数组中的数据存储到堆区;

③ 构造函数中可以传入数组的容量;

④ 提供对应的拷贝构造函数以及 operator= 防止浅拷贝问题;

⑤ 提供尾插法和尾删法对数组中的数据进行增加和删除;

⑥ 可以通过下标的方式访问数组中的元素;

⑦ 可以获取数组中当前元素个数和数组的容量;

        代码:

// MyArray.hpp
#pragma once
#include <iostream>

template<class T>
class MyArray{

public:
    // 有参构造
    MyArray(int capacity){
        std::cout << "有参构造调用" << std::endl;
        this->m_Capacity = capacity;
        this->m_Size = 0;
        this->pAddress = new T[this->m_Capacity];
    }

    // 拷贝构造
    MyArray(const MyArray& arr){
        std::cout << "拷贝构造调用" << std::endl;
        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];
        }
    }

    // 防止浅拷贝
    MyArray& operator=(const MyArray& arr){
        std::cout << "operator=调用" << std::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;
    }

    // 尾插法
    void Push_Back(const T& val){
        // 判断容量是否等于数组大小
        if(this->m_Capacity == this->m_Size){
            std::cout << "Error! The array is full!" << std::endl;
            return;
        }
        this->pAddress[this->m_Size] = val;
        this->m_Size++;
    }

    // 尾删法
    void Pop_Back(){
        if(this->m_Size == 0){
            std::cout << "Error! The array is empty!" << std::endl;
            return;
        }
        this->m_Size--;
    }
    
    // 重载,根据 index 输出数组值
    T& operator[](int index){
        return this->pAddress[index];
    }

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

    // 返回数组的大小
    int getSize(){
        return this->m_Size;
    }

    // 析构函数
    ~MyArray(){
        std::cout << "析构调用" << std::endl;
        if (this->pAddress != NULL){
            delete[] this->pAddress;
            this->pAddress = NULL;
        }
    }

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

        test1:

# include <iostream>
# include <string>
# include "MyArray.hpp"

void printIntArray(MyArray <int>& arr){
    for(int i = 0; i < arr.getSize(); i++){
        std::cout << arr[i] << std::endl; // 调用operator[]
    }
}

class Person{
public:
    Person(){};
    Person(std::string name, int age){
        this -> Name = name;
        this -> Age = age;
    }

    std::string Name;
    int Age;
};

void printPersonArray(MyArray<Person>& arr){
    for(int i = 0; i < arr.getSize(); i++){
        std::cout << "Name: " << arr[i].Name << " Age: " << arr[i].Age << std::endl;
    }
}

int main(){
    test3();
    return 0;
}

         test2:

void test2(){
    MyArray<int>arr1(5); // 有参构造
    for(int i = 0; i < 5; i++){
        // 利用尾插法向数组中插入数据
        arr1.Push_Back(i);
    }
    std::cout << "Print arr1: " << std::endl;
    printIntArray(arr1);

    std::cout << "arr1.size: " << arr1.getSize() << std::endl;
    std::cout << "arr1.capacity: " << arr1.getCapacity() << std::endl;

    MyArray<int>arr2(arr1); // 拷贝构造
    std::cout << "Print arr2: " << std::endl;
    printIntArray(arr2);
    arr2.Pop_Back();
    std::cout << "arr2 调用尾删后: " << std::endl;
    std::cout << "arr2.size: " << arr2.getSize() << std::endl;
    std::cout << "arr2.capacity: " << arr2.getCapacity() << std::endl;
    std::cout << "Print arr2: " << std::endl;
    printIntArray(arr2);
}

        test3:

void test3(){
    MyArray<Person> arr(10);
    Person p1("Zhangsan", 18);
    Person p2("Lisi", 19);
    Person p3("Wangwu", 20);
    Person p4("Zhaoliu", 21);

    arr.Push_Back(p1);
    arr.Push_Back(p2);
    arr.Push_Back(p3);
    arr.Push_Back(p4);
    std::cout << "Print PersonArr: " << std::endl;
    printPersonArray(arr);

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值