本文是本人大一期间在校学习C++课程时所撰写的实验报告的摘录,由于刚上大学,刚接触计算机编程方面的相关知识,故可能会有很多不足甚至错误的地方,还请各位看官指出及纠正。
本文所涉及到的“教材”指:
电子工业出版社《C++ Primary中文版(第5版)》
如需转载或引用请标明出处。
基本知识
类模板
类模板与函数模板类似,用于生成类的蓝图。与函数模板不同的是,编译器不能为类模板推断模板参数类型:为了使用模板,我们必须在模板名后面的尖括号中提供额外的信息——即用来代替模板参数的模板实参列表。
类似函数模板,类模板的定义也以关键字template开始,后跟模板参数列表,我们将模板参数当作替身,代替使用模板时用户需要提供的类型或值。例如有以下类模板定义:
该类模板对于用户指定的每一种元素类型,编译器都生成一个不同的类:
Blob<string> names; //保存string的Blob
Blob<double> prices; //保存double的Blob
这两个定义会实例化出两个不同的类。Names的定义创建了一个Blob类,每个T都被替换为string。prices的定义生成了另外一个Blob类,T被替换为double。
类模板的成员函数
和普通的类的定义一样,我们既可以在类模板内部,也可以在类模板外部为其定义成员函数,且定义在类模板内的成员函数被隐式地声明为内联函数。
需要注意的是,在类模板外定义成员函数时,不仅要说明该成员属于哪个类,而且,从一个模板生成的类的名字中必须包含其模板实参,且其定义要从模板参数开始。例如:
template <typename T> void Blob<T>::check(size_type i, const std::string &msg) const
template <typename T> T& Blob<T>::front()
上面的两个类模板外的函数定义都以template 开头。
类模板的友元
如果一个类模板包含一个非模板友元,则友元被授权可以访问所有的模板实例。如果友元自身是模板,则类可以授权给所有友元模板实例,也可以只授权给特定实例。例如:
template <typename T> class Pal;
class C
{
friend class Pal<C>;
template <typename T> friend class Pal2;
};
其中只有用C实例化的Pal才是类模板C的一个友元;Pal2的所有实例都是类模板C的友元,这种情况无需前置声明。
运算符的重载
在使用类的过程中,有时我们可能会用到类似于把两个类相关成员相加的情况,这时候虽然可以通过编写专门的成员函数完成相加的操作,但未免太过麻烦。若可以简单地使用运算符+即可完成操作,这无疑既简便,又大大提高了代码地可阅读性。因此,可以通过运算符重载重新定义该运算符地含义,且明智地使用运算符重载能令我们的程序更易于编写和阅读。
重载的运算符是具有特殊名字的函数:它们的名字由关键字operator和其后要定义的运算符号共同组成,和其他函数一样,重载的运算符也包含返回类型、参数列表及函数体。重载运算符函数的参数数量与该运算符作用的运算对象数量一样多,且对于二元运算符来说,左侧运算对象传递给第一个参数,右侧运算对象传递给第二个参数。除了重载的函数调用运算符()之外,其他重载运算符不能含有默认实参。对于一个运算符函数来说,它或者是类的成员,或者至少含有一个类类型的参数,这一约定意味着当运算符作用于内置类型的运算对象时,我们无法改变该运算符的含义。
重载运算符的调用
通常情况下,我们将运算符作用于类型正确的实参,从而以这种简介方式“调用”重载的运算符函数。然而,我们也能像调用普通函数一样直接调用运算符函数,先指定函数名字,然后传入数量正确、类型适当的实参:
data1 + data2; //普通的表达式
operator+(data1, data2); //等价的函数调用
这两次调用是等价的,它们都调用了非成员函数operator+,传入data1作为第一个实参、传入data2作为第二个实参。
我们像调用其他成员函数一样显式地调用成员运算符函数。具体做法是,首先指定运行函数地对象(或指针)的名字,然后使用点运算符(或箭头运算符)访问希望调用的函数:
data1 += data2; //基于“调用”的表达式
data1. operator+=(data2); //对成员运算符函数的等价调用
这两条语句都调用了成员函数operator+=,将this绑定到data1的地址、将data2作为实参传入了函数。
重载运算符与类的成员函数
当我们定义重载的运算符时,必须首先决定是将其声明为类的成员还是声明为一个普通的非成员函数。在某些时候我们别无选择,因为有的运算符必须作为成员;另一些情况下,运算符作为普通函数比作为成员函数更好。下面的准则有助于我们在将运算符定义为成员函数还是普通的非成员函数做出抉择:
- 赋值(=)、下标([])、调用(())、和成员访问箭头(->)运算符必须是成员
- 复合赋值运算符一般来说应该是成员,但并非必须,这一点与赋值运算符略有不同
- 改变对象状态的运算符或者与给定类型密切相关的运算符,如递增、递减和解引用运算符,通常应该是成员
- 具有对称性的运算符可能转换任意一端的运算符,例如算数、相等性、关系和位运算符等,因此它们通常应该是普通的非成员函数
此外,如果我们想提供含有类对象的混合类型表达式,则运算符必须定义成非成员函数。
示例代码
Blob.h
#ifndef BLOB_H
#define BLOB_H
#include <iterator>
#include <string>
#include <vector>
#include <cstddef>
#include <stdexcept>
#include <utility>
#include <memory>
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <stdexcept>
//Blob类友元声明必须的前置声明
template <typename> class BlobPtr;
template <typename> class Blob;//运算符==中的参数所需要的
template <typename T> bool operator==(const Blob<T>&, const Blob<T>&);
template <typename T> class Blob {
//每个Blob实例将访问权限授予用相同类型实例化的BlobStr和相等运算符==
friend class BlobPtr<T>;
friend bool operator==<T> (const Blob<T>&, const Blob<T>&);
public:
typedef T value_type; //value_type为T的别名
typedef typename std::vector<T>::size_type size_type; //简写
Blob(); //默认构造函数的声明
template <typename It> Blob(It b, It e); //接受两个It类型(指针)b、e作为参数的构造函数的声明
Blob(T*, std::size_t); //接受一个T类型的指针以及一个size_t作为形参的构造函数的声明
BlobPtr<T> begin() { return BlobPtr<T>(*this); } //返回指向第一个T类型元素的BlobPtr
BlobPtr<T> end() //返回指向尾后T类型元素的BlobPtr
{
BlobPtr<T> ret = BlobPtr<T>(*this, data->size());
return ret;
}
size_type size() const { return data->size(); } //返回该Blob中元素的数量
bool empty() const { return data->empty(); } //判断该Blob是否为空
void push_back(const T &t) {data->push_back(t);} //在该Blob中添加元素
void pop_back(); //在该Blob中删除元素
T& front(); //返回该Blob中的第一个元素
T& back(); //返回该Blob中的最后一个元素
T& at(size_type); //返回该Blob中对应下标的元素
const T& back() const; //const版本的bcak
const T& front() const; //const版本的front
const T& at(size_type) const; //const版本的at
T& operator[](size_type i); //重载下标运算符
const T& operator[](size_type i) const; //const版本的重载下标运算符
void swap(Blob &b) { data.swap(b.data); } //将该Blob中的数据(指针指向)与Blob类b交换
private:
std::shared_ptr<std::vector<T>> data; //data为指向存储类型T的vector的智能共享指针
void check(size_type i, const std::string &msg) const; //当i的位置不合法时抛出异常
};
//接受一个T类型的指针p以及一个size_tn作为形参的构造函数,用从p指向的位置开始往后n个位置的T类型元素初始化Blob中的数据
template <typename T> Blob<T>::Blob(T *p, std::size_t n) : data(new std::vector<T>(p, p + n)) { }
template <typename T> Blob<T>::Blob() : data(new std::vector<T>()) { } //默认构造一个空的vector
//接受两个It类型(指针)b、e作为参数的构造函数,用b、e间的T类型元素初始化Blob中的数据
template <typename T> template <typename It> Blob<T>::Blob(It b, It e) : data(new std::vector<T>(b, e)) { }
template <typename T> void Blob<T>::check(size_type i, const std::string &msg) const //检查函数
{
if (i >= data->size())
throw std::out_of_range(msg);
}
template <typename T> T& Blob<T>::front()
{
check(0, "front on empty Blob"); //先检查该Blob是否为空
return data->front();
}
template <typename T> T& Blob<T>::back()
{
check(0, "back on empty Blob"); //先检查该Blob是否为空
return data->back();
}
template <typename T> void Blob<T>::pop_back()
{
check(0, "pop_back on empty Blob"); //先检查该Blob是否为空
data->pop_back();
}
template <typename T> const T& Blob<T>::front() const
{
check(0, "front on empty Blob"); //先检查该Blob是否为空
return data->front();
}
template <typename T> const T& Blob<T>::back() const
{
check(0, "back on empty Blob"); //先检查该Blob是否为空
return data->back();
}
template <typename T> T& Blob<T>::at(size_type i)
{
check(i, "subscript out of range"); //先检查i的值是否合法,防止访问一个不存在的元素
return (*data)[i]; //(*data)是data指向的vector
}
template <typename T> const T& Blob<T>::at(size_type i) const
{
check(i, "subscript out of range"); //先检查i的值是否合法,防止访问一个不存在的元素
return (*data)[i]; //(*data)是data指向的vector
}
template <typename T> T& Blob<T>::operator[](size_type i)
{
check(i, "subscript out of range"); //先检查i的值是否合法,防止访问一个不存在的元素
return (*data)[i];
}
template <typename T> const T& Blob<T>::operator[](size_type i) const
{
check(i, "subscript out of range"); //先检查i的值是否合法,防止访问一个不存在的元素
return (*data)[i];
}
//重载运算符<<,格式化输出一个Blob
template <typename T> std::ostream& operator<<(std::ostream &os, const Blob<T> a)
{
os << "< ";
for (size_t i = 0; i < a.size(); ++i)
os << a[i] << " ";
os << " >";
return os;
}
//重载运算符==,判断两个Blob是否相等
template <typename T> bool operator==(const Blob<T> lhs, const Blob<T> rhs)
{
if (rhs.size() != lhs.size()) //先判断元素个数是否相等
return false;
for (size_t i = 0; i < lhs.size(); ++i) //再判断每个元素是否对应相等(有顺序要求)
{
if (lhs[i] != rhs[i])
return false;
}
return true;
}
//BlobPtr类友元声明必须的前置声明:接受两个BlobPtr的常量引用为参数的重载运算符==
template <typename T> bool operator==(const BlobPtr<T>&, const BlobPtr<T>&);
//当试图访问不存在的元素时BlobPtr抛出异常
template <typename T> class BlobPtr : public std::iterator<std::bidirectional_iterator_tag,T>
{
//每个BlobPtr实例将访问权限授予用相同类型实例化的相等运算符==
friend bool operator==<T>(const BlobPtr<T>&, const BlobPtr<T>&);
public:
BlobPtr(): curr(0) { } //默认构造函数:初始位置为0
//接受一个Blob的引用以及可选的size_t类型sz作为参数的构造函数:成员wptr指向该Blob的数据,以sz作为初始位置
BlobPtr(Blob<T> &a, size_t sz = 0) : wptr(a.data), curr(sz) { }
T &operator[](std::size_t i) //重载下标运算符
{
std::shared_ptr<std::vector<T>> p = check(i, "subscript out of range"); //先检查i的值是否合法
return (*p)[i]; //(*p)是一个存储T类型元素的vector
}
const T &operator[](std::size_t i) const //重载const版本的下标运算符
{
std::shared_ptr<std::vector<T>> p = check(i, "subscript out of range");
return (*p)[i]; //(*p)是一个存储T类型元素的vector
}
T& operator*() const //重载取地址运算符*
{
std::shared_ptr<std::vector<T>> p = check(curr, "dereference past end");//先检查当前地址是否合法
return (*p)[curr]; //(*p)是一个存储T类型元素的vector
}
T* operator->() const //重载成员访问运算符->
{
return & this->operator*(); //起作用的实际上是重载的取地址运算符*
}
BlobPtr& operator++(); //重载前缀形式的递增运算符++的声明
BlobPtr& operator--(); //重载前缀形式的递减运算符--的声明
BlobPtr operator++(int); //重载后缀形式的递增运算符++的声明
BlobPtr operator--(int); //重载后缀形式的递减运算符--的声明
private:
//检查函数:如果检查通过,则返回一个指向对应vector的shared_ptr
std::shared_ptr<std::vector<T>> check(std::size_t, const std::string&) const;
std::weak_ptr<std::vector<T>> wptr; //使用弱指针weak_ptr,意味着其指向的vector可能会被销毁,方便检查
std::size_t curr; //用于指示位置
};
template <typename T> bool operator==(const BlobPtr<T> &lhs, const BlobPtr<T> &rhs) //重载相等运算符==
{
//当两个参数指向的位置相等且它们各自在vector中的位置相等时,返回1
return lhs.wptr.lock().get() == rhs.wptr.lock().get() && lhs.curr == rhs.curr;
}
template <typename T> bool operator!=(const BlobPtr<T> &lhs, const BlobPtr<T> &rhs) //重载不等运算符!=
{
return !(lhs == rhs); //起作用的实际上是重载的相等运算符==
}
template <typename T> std::shared_ptr<std::vector<T>> BlobPtr<T>::check(std::size_t i, const std::string &msg) const
{
std::shared_ptr<std::vector<T>> ret = wptr.lock(); //检查wptr指向的vector是否存在
if (!ret)
throw std::runtime_error("unbound BlobPtr"); //若不存在,则抛出异常
if (i >= ret->size())
throw std::out_of_range(msg); //若i的值不合理,则抛出异常
return ret; //若通过检查,则返回一个指向该vector的shared_ptr
}
//后缀形式:先返回值,再递增/递减
template <typename T> BlobPtr<T> BlobPtr<T>::operator++(int)
{
//此处无需检查,对前缀形式的递增运算符++的调用会执行检查
BlobPtr ret = *this; //保存当前的BlobPtr
++*this; //前缀形式的递增运算符++执行检查,通过则位置加1
return ret; //返回临时保存的BlobPtr
}
template <typename T> BlobPtr<T> BlobPtr<T>::operator--(int)
{
//此处无需检查,对前缀形式的递减运算符--的调用会执行检查
BlobPtr ret = *this; //保存当前的BlobPtr
--*this; //前缀形式的递减运算符--执行检查,通过则位置加1
return ret; //返回临时保存的BlobPtr
}
//前缀形式:先递增/递减,再返回值
template <typename T> BlobPtr<T>& BlobPtr<T>::operator++()
{
check(curr, "increment past end of BlobPtr"); //检查:如果curr指示的位置已是尾后元素,则不能递增,抛出异常
++curr; //位置加1
return *this;
}
template <typename T> BlobPtr<T>& BlobPtr<T>::operator--()
{
//如果curr指示的是第一个元素,则不能递减
--curr; //位置减1
check(-1, "decrement past begin of BlobPtr"); //检查:如果curr指示的位置在首元素之前,则抛出异常
return *this;
}
#endif
useBlob.cpp
#include <string>
#include <iostream>
using namespace std;
#include "Blob.h"
int main()
{
Blob<string> b1; //默认构造一个空的Blob为b1
cout << b1.size() << endl; //验证b1内的元素个数为0
{ //一个新的作用域
string temp[] = {"a", "an", "the"}; //temp为临时数组,存储几个字符串
//用Blob其中的一个构造函数,初始化b2,它的元素就是temp中的元素
Blob<string> b2(temp, temp + sizeof(temp)/sizeof(*temp));
b1 = b2; //b1和b2指向相同的vector
b2.push_back("about"); //向b2指向的vector中加入新元素
cout << b1.size() << " " << b2.size() << endl; //通过比较b1和b2指向的vector的元素个数,验证它们指向的是同一个vector
} //b2的作用域结束,b2被销毁,但由于仍有一个b1中的shared_ptr指向该vector,所以该vector并未被销毁
cout << b1.size() << endl; //输出此时b1内的元素个数
for(BlobPtr<string> p = b1.begin(); p != b1.end(); ++p) //使用各种重载后的运算符实现遍历操作
cout << *p << endl;
//----------下面是新增的调试内容----------//
int temp[] = { 0,1,2,3,4,5,6,7,8,9 };
Blob<int> b3(temp, temp + sizeof(temp) / sizeof(*temp));
putchar('\n');
cout << b3.front() << " " << b3.back() << endl; //调试front和back函数
cout << b3.at(2) << endl; //调试at函数
cout << b3[2] << endl; //调试重载的下标运算符[]
b3.pop_back(); //调试pop_back函数
cout << b3 << endl; //调试重载后的运算符<<
system("pause");
return 0;
}