C++面向对象程序设计-北京大学-郭炜【课程笔记(十)】(本节类容简单,以截图为主)

开始课程:P37 5_5. 函数模板
课程链接:程序设计与算法(三)C++面向对象程序设计 北京大学 郭炜
课程PPT:github提供的对应课程PPT

1、函数模板(比较简单以截图为主)

函数模板是C++中一种用于创建通用函数的机制,它可以让您编写与数据类型无关的函数。函数模板通过在函数定义中使用泛型类型来实现这一点,这些泛型类型在调用函数时会被具体的类型替换。这使得您可以编写一次函数模板,然后可以用不同的数据类型调用它,而无需为每种类型编写不同的函数。

以下是一个简单的函数模板示例,演示了如何编写一个模板函数来比较两个值的大小:

#include <iostream>

// 定义一个函数模板
template <typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

int main() {
    // 使用函数模板
    std::cout << "Max of 10 and 20 is: " << max(10, 20) << std::endl;   // T 被推断为 int
    std::cout << "Max of 10.5 and 5.5 is: " << max(10.5, 5.5) << std::endl; // T 被推断为 double

    return 0;
}

在这个例子中,max 函数是一个函数模板,用于比较两个值的大小。在 main 函数中,我们分别用 intdouble 类型调用了这个函数模板,它会根据实际调用时的参数类型自动推断出模板参数 T 的类型。

函数模板的优点是可以提高代码的复用性和灵活性,因为您只需要编写一个模板函数,就可以适用于多种数据类型。
函数模板在C++中有多种用法,以下是其中一些常见的用法:

  1. 泛型函数: 使用函数模板可以创建通用函数,可以处理多种数据类型而无需为每种类型编写单独的函数。

    template <typename T>
    T max(T a, T b) {
        return a > b ? a : b;
    }
    
  2. 多个模板参数: 函数模板可以有多个模板参数,使其能够处理多个不同类型的参数。

    template <typename T, typename U>
    T add(T a, U b) {
        return a + b;
    }
    
  3. 非类型模板参数: 模板参数不仅可以是类型,还可以是非类型,比如整数值。

    template <int N>
    void printNTimes(const std::string& str) {
        for (int i = 0; i < N; ++i) {
            std::cout << str;
        }
    }
    
  4. 模板特化: 可以针对特定类型或特定模板参数值进行模板特化,提供特定实现。

    template <>
    const char* max<const char*>(const char* a, const char* b) {
        return strcmp(a, b) > 0 ? a : b;
    }
    
  5. 模板类成员函数: 在类模板中定义成员函数时,可以使用函数模板。

    template <typename T>
    class MyContainer {
    public:
        template <typename U>
        void add(U element);
    };
    
  6. 模板别名: 可以使用 using 关键字为函数模板定义别名。

    template <typename T>
    using Ptr = std::shared_ptr<T>;
    

这些都是函数模板的常见用法,每种用法都有助于提高代码的通用性和灵活性。

请添加图片描述

以上函数重载的方式如果需要很多其他类型,是不是还要继续写重载类型的函数,很麻烦,所以采用函数模板,以简化书写。
请添加图片描述请添加图片描述
请添加图片描述请添加图片描述

// 不通过参数实例化函数模板
#include <iostream>
using namespace std;

template <class T>
T Inc(T n)
{
    return 1+n;
}

int main()
{
    cout << Inc<double>(4)/2 << endl;;   // 输出2.5
    return 0;
}
// OUT
2.5

2.2、函数模板的重载

请添加图片描述

2.3、函数模板和函数的次序

请添加图片描述
请添加图片描述
请添加图片描述
例题

#include<iostream>
using namespace std;

template<class T, class Pred>  // 函有两个类型参数
void Map(T s, T e, T x, Pred op)  // op是一个函数
{
    // 一个s为起点,e为终点(不包含终点)的区间中的每个元素通过op做一个变化,
    // 把变化的结果拷贝到目标区间中,目标区间的起点是x
    for(; s != e; ++s, ++x)
    {
        *x = op(*s);
    }
}

int Cube(int x) {return x * x * x;}
double Square(double x) {return x * x;}
int a[5] = {1,2,3,4,5} , b[5];
double d[5] = {1.1, 2.1, 3.1, 4.1, 5.1}, c[5];

int main()
{
    Map(a, a+5, b, Square);
    for(int i=0; i<5; ++i)
    {
        cout << b[i] << ",";
    }
    cout << endl;

    Map(a, a+5, b, Cube);
    for(int i=0; i<5; ++i) cout << b[i] << ",";
    cout << endl;

    Map(d, d+5, c, Square);
    for(int i=0; i<5; ++i) cout << c[i] << ",";
    cout << endl;

    return 0;
}
// OUT
1,4,9,16,25,
1,8,27,64,125,
1.21,4.41,9.61,16.81,26.01,

请添加图片描述

在C++中,pred 并不是一个标准的类型,通常用于表示"predicate"的缩写,即谓词。在STL(标准模板库)中,谓词是一种用于比较元素或执行某种操作的函数或函数对象。谓词通常被用作算法的参数,例如在 std::sort 中,可以通过谓词指定元素的比较方式。

一个常见的谓词示例是一个返回 bool 类型的函数或函数对象,用于比较两个元素是否满足某种条件。例如,以下是一个简单的谓词函数,用于比较两个整数是否相等:

bool isEqual(int a, int b) {
    return a == b;
}

在使用STL算法时,可以将这个谓词函数传递给算法,如下所示:

#include <algorithm>
#include <vector>
#include <iostream>

bool isEqual(int a, int b) {
    return a == b;
}

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5, 4, 3, 2, 1};

    // 使用谓词函数在容器中查找元素
    auto it = std::find_if(vec.begin(), vec.end(), [](int val){ return isEqual(val, 3); });

    if (it != vec.end()) {
        std::cout << "Found element: " << *it << std::endl;
    } else {
        std::cout << "Element not found." << std::endl;
    }

    return 0;
}

在这个例子中,isEqual 函数被用作谓词函数,用于在 std::find_if 算法中比较容器中的元素是否等于3。

2、类模板

请添加图片描述
类模板的定义:
请添加图片描述请添加图片描述
例题:pair类比大小(用到了运算符重载)
这里不难,仔细听课就可听懂:本例题课程链接
请添加图片描述
请添加图片描述

注意事项!!!:同一类模板的两个模板类是不兼容的
请添加图片描述
例题:

#include <iostream>
using namespace std;

template <class T>
class A
{
    public:
        template<class T2>
        void Func(T2 t) {cout << t;}  // 成员函数模板
};

int main()
{
    A<int> a;   // int指的是类中的T,但是本class中没有用到T
    a.Func('K');  // 这里T2被变成char,成员函数模板 Func被实例化
    a.Func("Hello");  // 这里T2给的是char *或const char *,成员函数模板 Func被实例化

    return 0;
}
// OUT
KHello

2.1、类模板的“<类型参数表>“中可以出现非类型参数;

请添加图片描述

2.2、类模板与派生的关系

(用的不多,理解即可)

  • 类模板从类模板派生
  • 类模板从模板类派生
  • 类模板从普通类派生
  • 普通类从模板类派生

2.2.1、类模板从类模板派生

#include <iostream>
using namespace std;
// 类模板
template <class T1, class T2>  
class A
{
    T1 v1; T2 v2;
};

// 类模板从类模板派生
template <class T1, class T2>
class B:public A<T2, T1>
{
    T1 v3; T2 v4;
};

// 类模板从派生类模板派生
template <class T>
class C:public B<T, T>
{
    T v5;
};

int main()
{
    B<int, double> obj1;   // 编译器中如下
    // class B<int, double>:
    //     public A<double, int>
    // {
    //     int v3; double v4;
    // };
    C<int> obj2;  // 编译器中如下
    // class A<double, int>
    // {
    //     double v1; int v2;
    // };

    return 0;
}

请添加图片描述

2.2.2、类模板从模板类派生

请添加图片描述

2.2.3、类模板从普通类派生

请添加图片描述

2.2.4、普通类从模板类派生

请添加图片描述

2.3、类模板与友元的关系

  • 函数、类、类的成员函数作为类模板的友元
  • 函数模板作为类模板的友元
  • 函数模板作为类的友元
  • 类模板作为类模板的友元

2.3.1、函数、类、类的成员函数作为类模板的友元

请添加图片描述

2.3.2、函数模板作为类模板的友元

#include<iostream>
#include<string>
using namespace std;

template <class T1, class T2>
class Pair
{
    private:
        T1 key;    // 关键字
        T2 value;  // 值
    public:
        Pair(T1 k, T2 v):key(k), value(v) {};  // 构造函数,用于初始化 key 和 value
        bool operator < (const Pair<T1, T2> & p) const;  // 重载 < 运算符,用于比较关键字的大小
        template <class T3, class T4>
        friend ostream & operator<< (ostream & o, const Pair<T3,T4> & p);  // 友元函数模板声明,用于输出 Pair 对象的内容
};

// Pair 类模板的 < 运算符重载实现
template<class T1, class T2>
bool Pair<T1,T2>::operator< (const Pair<T1, T2> & p) const
{
    // "小"的意思就是关键字小
    return key < p.key;
}

// 友元函数模板的实现,用于输出 Pair 对象的内容
template <class T1, class T2>
ostream & operator << (ostream & o, const Pair<T1, T2> & p)
{
    o << "(" << p.key << "," << p.value << ")" ;
    return o;
}

int main()
{
    Pair<string, int> student("Tom", 29);  // 创建一个 Pair 对象,关键字是字符串类型,值是整数类型
    Pair<int, double> obj(12, 3.14);  // 创建一个 Pair 对象,关键字是整数类型,值是双精度浮点数类型
    cout << student << " " << obj;  // 输出两个 Pair 对象的内容

    return 0;
}

// OUT
(Tom,29) (12,3.14)

代码的逐行解释如下:

  1. template <class T1, class T2>:定义了一个类模板,其中 T1T2 是模板参数,用于表示 Pair 类中的关键字和值的类型。

  2. class Pair:定义了一个名为 Pair 的类模板。

  3. private::下面的成员变量和成员函数都是私有的,外部不可访问。

  4. T1 key;T2 value;:定义了两个成员变量,分别表示 Pair 对象的关键字和值。

  5. public::下面的成员函数是公共的,外部可以访问。

  6. Pair(T1 k, T2 v):key(k), value(v) {};:Pair 类的构造函数,用于初始化关键字和值。

  7. bool operator < (const Pair<T1, T2> & p) const;:重载了 < 运算符,用于比较两个 Pair 对象的关键字大小。

  8. template <class T3, class T4>friend ostream & operator<< (ostream & o, const Pair<T3,T4> & p);:声明了一个友元函数模板,用于输出 Pair 对象的内容。

  9. bool Pair<T1,T2>::operator< (const Pair<T1, T2> & p) const:重载 < 运算符的实现,用于比较两个 Pair 对象的关键字大小。

  10. template <class T1, class T2>ostream & operator << (ostream & o, const Pair<T1, T2> & p):友元函数模板的实现,用于输出 Pair 对象的内容。

  11. main() 函数:主函数,用于创建 Pair 对象并输出其内容。

任意从
template <class T1, class T2>
ostream & operator << (ostream & o, const Pair<T1, T2> & p)
生成的函数,都是任意Pair模板类的友元

2.3.3、函数模板作为类的友元

在这里插入图片描述

2.3.4、类模板作为类模板的友元

请添加图片描述

2.4、类模板与static成员

2.4.1、介绍static成员

在 C++ 中,static 成员是属于类而不是类的实例的成员。static 成员可以被所有该类的实例共享,而不是每个实例都有自己的一份。static 成员可以是静态数据成员(静态变量)或静态成员函数(静态方法)。

  1. 静态数据成员:静态数据成员是类的所有实例共享的变量。它们在类的所有实例中都是相同的。静态数据成员在类内声明,在类外初始化,且必须初始化,通常在类的实现文件中进行初始化。

    class MyClass {
        public:
            static int staticVar;  // 静态数据成员声明
    };
    
    int MyClass::staticVar = 0;  // 静态数据成员初始化
    
  2. 静态成员函数:静态成员函数属于类,而不属于类的任何实例。它们可以访问类的静态数据成员和其他静态成员函数,但不能访问非静态成员。静态成员函数可以直接通过类名调用,而不需要创建类的实例。

    class MyClass {
        public:
            static void staticMethod() {
                cout << "This is a static method" << endl;
            }
    };
    
    MyClass::staticMethod();  // 调用静态成员函数
    

静态成员常用于表示与类相关的全局信息,或者用于在多个对象之间共享信息。
在 C++ 中,static 成员有以下作用:

  1. 共享数据:静态数据成员是所有类对象共享的,可以用于表示类的某个属性在所有对象中的唯一实例。例如,可以用静态成员来记录类的实例个数或者记录某个属性的全局值。

    class MyClass {
        public:
            static int count;  // 所有对象共享的计数器
    
            MyClass() {
                count++;  // 每创建一个对象,计数器加一
            }
    };
    
    int MyClass::count = 0;  // 初始化静态成员
    
    int main() {
        MyClass obj1, obj2, obj3;
        cout << "Number of objects created: " << MyClass::count << endl;
        return 0;
    }
    
  2. 独立于对象:静态成员函数属于类而不是对象,可以在不创建对象的情况下调用。这使得它们可以用于执行与类相关的操作,而无需实例化类。

    class MathUtils {
        public:
            static int add(int a, int b) {
                return a + b;
            }
    };
    
    int sum = MathUtils::add(3, 4);  // 调用静态成员函数
    
  3. 命名空间的替代:静态成员可以用于替代全局变量和全局函数,将相关的数据和函数封装在类的作用域内,提高了代码的可读性和可维护性。

静态成员的主要作用是提供与类相关的全局数据和函数,同时避免了全局命名空间的污染。

2.4.2、类模板与static成员

#include<iostream>
using namespace std;
template <class T>
class A
{
    private:
        static int count;
    public:
    A() {count ++;}
    ~A() 
    {
        count --;
        cout << "count = " << count << endl; 
    }
    A(A &) {count ++ ;}
    static void PrintCount() {cout << count << endl;}
};

template<> int A<int>::count = 0;
template<> int A<double>::count = 0;

int main()
{
    A<int> ia;
    A<double> da;
    ia.PrintCount();
    da.PrintCount();

    return 0;
}
// OUT
1
1
count = 0
count = 0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

☞源仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值