C++学习笔记:09 函数模板与类模板

课程《C++语言程序设计进阶》清华大学 郑莉老师)

引入

考虑一个求绝对值函数myabs,对于int,double,float等数据类型需要重载多次,编写多个函数体。函数体逻辑代码完全一致,仅仅是数据类型不同。

在这种情况下,可以使用函数模板

#include <iostream>
using namespace std;
int myabs(int x) { return x >= 0 ? x : -x; }
float myabs(float x) { return x >= 0 ? x : -x; }
double myabs(double x) { return x >= 0 ? x : -x; }
int main() {
    cout << myabs(1) << endl;
    cout << myabs(1.1) << endl;
    cout << myabs(1.2) << endl;
    return 0;
}

myabs()函数可以使用模板函数申明

template<class T>
T myabs(T x) { return x >= 0 ? x : -x; }

函数模板

函数模板定义语法

template <模板参数表>
函数定义

模板参数表的内容:

  • 类型参数:class(或typename) 标识符
  • 常量参数:类型说明符 标识符
  • 模板参数:template <参数表> class 标识符

注意

  • 一个函数模板并非自动可以处理所有类型的数据

  • 只有能够进行函数模板中运算的类型,可以作为类型实参

  • 自定义的类,需要重载模板中的运算符,才能作为类型实参

例如:

include <iostream>
using namespace std;
template <class T>  //定义函数模板
void outputArray(const T* array, int count) {
	for (int i = 0; i < count; i++)
		cout << array[i] << " ";
	cout << endl;
}
int main() {
	const int A_COUNT = 8, B_COUNT = 8, C_COUNT = 20;
	int a[A_COUNT] = { 1, 2, 3, 4, 5, 6, 7, 8 };
	double b[B_COUNT] = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8 };
	char c[C_COUNT] = "Welcome!";
	cout << " a array contains:" << endl;
	outputArray(a, A_COUNT);
	cout << " b array contains:" << endl;
	outputArray(b, B_COUNT);
	cout << " c array contains:" << endl;
	outputArray(c, C_COUNT);
	return 0;
}
/* 
a array contains:
1 2 3 4 5 6 7 8
 b array contains:
1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8
 c array contains:
W e l c o m e !
*/

类模板

类模板的作用

使用类模板使用户可以为类声明一种模式,使得类中的某些数据成员、某些成员函数的参数、某些成员函数的返回值,能取任意类型(包括基本类型的和用户自定义类型)。

类模板的声明

类模板:

template <模板参数表>
class 类名{
    类成员声明
};

如果需要在类模板以外定义其成员函数,则要采用以下的形式:

template <模板参数表>
类型名 类名<模板参数标识符列表>::函数名(参数表)

注意

  • 模板类的声明与定义需要放在同一文件内
  • 模板类中重载<<,只能在类体里面实现,在类外写定义不好使
#include <iostream>
using namespace std;
struct Student {
    int id;
    float gpa;
public:
    friend ostream& operator <<(ostream& os, const Student& in);
};
ostream& operator << (ostream& os, const Student& in) {
    os << "ID:" << in.id << "  " << "GPA:" << in.gpa << endl;
    return os;
}

template<class T>
class Store {
private:
    T item;
    bool haveValue;
public:
    Store();
    T& getElem();
    void putElem(const T& e);
};

template<class T>
Store<T>::Store() {
    this->haveValue = false;
}

template<class T>
T& Store<T>::getElem() {
    if (!haveValue) {
        cout << "No item present!" << endl;
    }
    return this->item;
}

template<class T>
void Store<T>::putElem(const T& e) {
    this->haveValue = true;
    this->item = e;
}

int main() {
    Store<int> s1, s2;
    s1.putElem(3);
    s2.putElem(-7);
    cout << s1.getElem() << "  " << s2.getElem() << endl;

    Student g = { 1000, 23 };
    Store<Student> s3;
    s3.putElem(g);
    cout << s3.getElem() << endl;

    Store<double> d;
    cout << "Retrieving object D... ";
    cout << d.getElem() << endl;

    return 0;
}
/* output
3  -7
ID:1000  GPA:23

Retrieving object D... No item present!
-9.25596e+61
*/

例子

Array数组类

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

template<class T>
class Array {
private:
    T* list;
    int size;
public:
    Array(int sz = 50);
    Array(const Array<T>& a);//复制构造函数
    ~Array();

    Array<T>& operator = (const Array<T>& rhs); //overload =
    T& operator [](int i);  //overload []
    const T& operator [](int i) const;  //overload []

    operator T* ();
    operator const T* () const;
    int getSize() const;
    void resize(int sz);
    void fill(T in) {
        for (int i = 0; i < this->size; i++)
            this->list[i] = in;
    }

    friend ostream& operator <<(ostream& os, const Array<T>& e) {
        os << "size of array = " << e.size << endl;
        for (int i = 0; i < e.size; ++i)
            os << e.list[i] << " ";
        os << endl;
        return os;
    }
};


template<class T>
Array<T>::Array(int sz) {
    this->size = sz;
    this->list = new T[this->size]{};
}

template<class T>
Array<T>::~Array() {
    delete[] this->list;
}

template<class T>
Array<T>::Array(const Array<T>& a) {
    this->size = a.size;
    this->list = new T[this->size];
    for (int i = 0; i < this->size; ++i) {
        this->list[i] = a.list[i];
    }
}

template<class T>
Array<T>& Array<T>::operator =(const Array<T>& rhs) {
    if (&rhs != this) {
        if (this->size != rhs.size) {
            delete[] this->list;
            size = rhs.size;
            list = new T[this->size];
        }
        for (int i = 0; i < this->size; ++i)
            this->size[i] = rhs.size[i];
    }
    return *this;
}

template<class T>
T& Array<T>::operator [](int i) {
    return this->list[i];
}

template<class T>
const T& Array<T>::operator [](int i) const {
    return this->list[i];
}
//重载指针转换运算符
template<class T>
Array<T>::operator T* () {
    return list;
}

template<class T>
Array<T>::operator const T* () const {
    return list;
}

template<class T>
int Array<T>::getSize() const {
    return this->size;
}

template<class T>
void Array<T>::resize(int sz) {
    if (sz == this->size) return;
    T* newlist = new T[sz]{};
    int n = (sz < this->size) ? sz : this->size;
    for (int i = 0; i < n; ++i)
        newlist[i] = this->list[i];
    delete[] this->list;
    list = newlist;
    this->size = sz;
}

int main() {
    Array<int> a(10);
    int n, count = 0;
    cout << "Enter a value >= 2 as upper limit for prime numbers: ";
    cin >> n;
    //求给定范围内的质数
    for (int i = 2; i <= n; i++) { 
        bool isPrime = true;
        for (int j = 0; j < count; j++)
            //若i被a[j]整除,说明i不是质数
            if (i % a[j] == 0) {
                isPrime = false; break;
            }
        if (isPrime) {
            if (count == a.getSize())
                a.resize(count * 2);
            a[count++] = i;
        }
    }
    cout << a;
    return 0;
}
/*
Enter a value >= 2 as upper limit for prime numbers:20
size of array = 10
2 3 5 7 11 13 17 19 0 0
*/

以上例子中重载指针运算符

//重载指针转换运算符
template<class T>
Array<T>::operator T* () {
    return list;
}
template<class T>
Array<T>::operator const T* () const {
    return list;
}
/*
后置const是为了让常对象能调用这个成员函数(常对象只能调用常成员函数,不能调用普通成员函数);“const T*”表示,通过对象名创建动态数组后,通过对象名不能改变数组
*/

为什么要重载指针运算符?参考链接

void read(int* p, int n) {
    for (int i = 0; i < n; i++)
        cin >> p[i];
}
int main() {
    Array<int> a(10);
    read(a, 5);
    cout << a;
    return 0;
}

调用read函数并传入Array类型指针时,因为read函数只接受int类型指针,所以会自动搜索从Array型指针到int型指针的转换方式,如果能搜索到,就执行转换,把转换后的指针交给read函数,搜索不到,编译器就会报错。

虽然重载了指针转换运算符,但是其作用只是为了能把对象名a当类内动态数组名list一样,传入类外函数的参数表作形实结合(实参是对象名,形参是T型指针);

光重载指针转换运算符还不够,要想对象名能像数组名一样使用下标,还要重载下标运算符。

如果接着在main函数写a[i],编译器是不会把这里的a当作数组名,所以仅仅是写a[i]是无法调用数组元素的(编译器不认这种代码),只有重载了下标运算符“[ ]”,编译器才会把a[i]当成list[i];

总结:

重载指针运算符,作用仅限于把“a”转换成“list”;重载下标运算符,作用仅限于把“a[i]”转换成“list[i]”;

posted @ 2021-09-15 14:12  陈橙橙  阅读(133)  评论(0编辑  收藏  举报