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
函数中,我们分别用 int
和 double
类型调用了这个函数模板,它会根据实际调用时的参数类型自动推断出模板参数 T
的类型。
函数模板的优点是可以提高代码的复用性和灵活性,因为您只需要编写一个模板函数,就可以适用于多种数据类型。
函数模板在C++中有多种用法,以下是其中一些常见的用法:
-
泛型函数: 使用函数模板可以创建通用函数,可以处理多种数据类型而无需为每种类型编写单独的函数。
template <typename T> T max(T a, T b) { return a > b ? a : b; }
-
多个模板参数: 函数模板可以有多个模板参数,使其能够处理多个不同类型的参数。
template <typename T, typename U> T add(T a, U b) { return a + b; }
-
非类型模板参数: 模板参数不仅可以是类型,还可以是非类型,比如整数值。
template <int N> void printNTimes(const std::string& str) { for (int i = 0; i < N; ++i) { std::cout << str; } }
-
模板特化: 可以针对特定类型或特定模板参数值进行模板特化,提供特定实现。
template <> const char* max<const char*>(const char* a, const char* b) { return strcmp(a, b) > 0 ? a : b; }
-
模板类成员函数: 在类模板中定义成员函数时,可以使用函数模板。
template <typename T> class MyContainer { public: template <typename U> void add(U element); };
-
模板别名: 可以使用
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)
代码的逐行解释如下:
-
template <class T1, class T2>
:定义了一个类模板,其中T1
和T2
是模板参数,用于表示 Pair 类中的关键字和值的类型。 -
class Pair
:定义了一个名为Pair
的类模板。 -
private:
:下面的成员变量和成员函数都是私有的,外部不可访问。 -
T1 key;
和T2 value;
:定义了两个成员变量,分别表示 Pair 对象的关键字和值。 -
public:
:下面的成员函数是公共的,外部可以访问。 -
Pair(T1 k, T2 v):key(k), value(v) {};
:Pair 类的构造函数,用于初始化关键字和值。 -
bool operator < (const Pair<T1, T2> & p) const;
:重载了<
运算符,用于比较两个 Pair 对象的关键字大小。 -
template <class T3, class T4>
和friend ostream & operator<< (ostream & o, const Pair<T3,T4> & p);
:声明了一个友元函数模板,用于输出 Pair 对象的内容。 -
bool Pair<T1,T2>::operator< (const Pair<T1, T2> & p) const
:重载<
运算符的实现,用于比较两个 Pair 对象的关键字大小。 -
template <class T1, class T2>
和ostream & operator << (ostream & o, const Pair<T1, T2> & p)
:友元函数模板的实现,用于输出 Pair 对象的内容。 -
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
成员可以是静态数据成员(静态变量)或静态成员函数(静态方法)。
-
静态数据成员:静态数据成员是类的所有实例共享的变量。它们在类的所有实例中都是相同的。静态数据成员在类内声明,在类外初始化,且必须初始化,通常在类的实现文件中进行初始化。
class MyClass { public: static int staticVar; // 静态数据成员声明 }; int MyClass::staticVar = 0; // 静态数据成员初始化
-
静态成员函数:静态成员函数属于类,而不属于类的任何实例。它们可以访问类的静态数据成员和其他静态成员函数,但不能访问非静态成员。静态成员函数可以直接通过类名调用,而不需要创建类的实例。
class MyClass { public: static void staticMethod() { cout << "This is a static method" << endl; } }; MyClass::staticMethod(); // 调用静态成员函数
静态成员常用于表示与类相关的全局信息,或者用于在多个对象之间共享信息。
在 C++ 中,static
成员有以下作用:
-
共享数据:静态数据成员是所有类对象共享的,可以用于表示类的某个属性在所有对象中的唯一实例。例如,可以用静态成员来记录类的实例个数或者记录某个属性的全局值。
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; }
-
独立于对象:静态成员函数属于类而不是对象,可以在不创建对象的情况下调用。这使得它们可以用于执行与类相关的操作,而无需实例化类。
class MathUtils { public: static int add(int a, int b) { return a + b; } }; int sum = MathUtils::add(3, 4); // 调用静态成员函数
-
命名空间的替代:静态成员可以用于替代全局变量和全局函数,将相关的数据和函数封装在类的作用域内,提高了代码的可读性和可维护性。
静态成员的主要作用是提供与类相关的全局数据和函数,同时避免了全局命名空间的污染。
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