北京大学程序设计MOOC作业详解-07-输入输出和模板
话不多说,直接上代码,有问题请留言。
第一题:
template <class T>
T SumArray(
T* begin, T* end) {
T ret = *begin++;
for (; begin != end; ++begin) {
ret += *begin;
}
return ret;
}
需要注意的是:由于不同类型间初始化的方式不同,所以在使用模板初始化变量时,一定要在声明时立即初始化。然后,*begin++;
是需要注意的,这个运算要从前往后看,首先解引用得到第一个元素的值,然后指针往后移一位,参见《C++ Primer》的内容:
第二题:
template<class T, class Func>
void MyForeach(T* begin, T* end, Func func) {
for (; begin != end; ++begin) {
func(*begin);
}
}
需要注意的是:如果是迭代器类型,函数模板的类型请声明为T,然后参数用T*;如果是函数指针类型,就直接声明Func,不再加指针标识符Func*。
这是和调用有关的,因为指针还要解引用,如果直接声明T那没法解引用(编译期);而函数指针不需要解引用,函数名就是函数指针,直接用函数名调用。
第三题:
template<class T, class F>
T* Filter(T* begin, T* end, T* itr, F func) {
T* ret = itr;
for (; begin != end; ++begin) {
if (func(*begin)) {
*ret++ = *begin;
}
}
return ret;
}
需要注意的是:这个题比较重要,先看Filter的调用方式:
string as1[5] = {"Tom","Mike","Jack","Ted","Lucy"};
string as2[5];
string * p = Filter(as1,as1+5,as2,LongerThan3);
for(int i = 0;i < p - as2; ++i)
cout << as2[i];
从上述调用代码可知,前两个参数应是迭代器;第三个参数不应该变,是接收过滤值的首地址;第四个参数是比较器;返回值是过滤值的末尾指针。
所以,最开始的初值,设为T* ret = itr;
,但是迭代器itr不会改变,指向首地址。
第四题:
这个题需要详细分析
第四题分析:
首先,MyCin是用无参构造的方式实例化对象的。并且还能支持输入,所以必须重载输入运算符,且是直接用iostream库中的cin进行输入。由于可以连续输入,所以输入函数必须返回引用,因此声明如下:
MyCin& operator>>(int& v);
这里和一般的重载输入运算符不同,常见的重载输入运算符函数如下:
friend istream& operator>>(istream& is, int& v);
为什么会不同呢?因为这里不是用istream的输入流输入的,而是用MyCin的对象完成输入的,所以不传入istream&参数,而是在函数里直接用cin。
此外,还需要思考while(cin >> n);
为什么能起作用。首先while是要先判断一个逻辑运算的,也就是cin输入是否合法(到文件尾)。这说明:cin对象要有一个到bool逻辑的强制类型转换方法。因此,MyCin的设计也要有类型转换运算符函数,这就要有一个bool的成员变量,来记录输入是否合法,并返回这个变量。
AC代码如下:
public:
bool isValid;
MyCin() : isValid(true) { }
MyCin& operator>>(int& v) {
cin >> v;
if (v == -1) {
isValid = false;
}
return *this;
}
operator bool() {
return isValid;
}
第五题:
这个题也要详细分析。
在看测试代码之前,先了解一些关于输入流迭代器istream_iterator的知识:
从这里可以知道,构造函数时就要输入,这说明成员变量至少要有能存储输入信息的数据结构,可以是变量,也可也是数组,这要看后面的内容。
从这个例子我们可以知道,如果没有执行++,则只会输入一个数据,并不会输入第二个数据,这仍然不能确定是用变量还是用数组存储。按照队列的结构,++运算符可以理解成移动头指针,不一定不代表未读取,再接着看例子吧。
从这个例子来看,仍然可以用队列进行存储,但是题目并没有给出队列的头文件,也无法自己在非代码填空区填写队列代码。那我们来参考一下STL的实现吧:
综上所述,可以知道是用变量存储的,且每次移动都伴随着一次输入,而解引用则是返回上一次输入的值。为了避免第一次就解引用,而没用输入,所以要在构造函数里就完成一次输入。
AC代码如下:
template <class T>
class CMyistream_iterator
{
T value{};
istream& istr;
public:
CMyistream_iterator(istream& is) : istr(is) {
istr >> value;
}
void operator++(int) {
istr >> value;
}
T operator*() {
return value;
}
};
一个小技巧,如果在模板里,不知道初始值是什么,就写:T value{};
表示默认值。
第六题:
模板类基础用法,AC代码如下:
template <class T>
class myclass {
private:
T* p;
int size;
public:
myclass(const T* _p, int _size) : size(_size), p(new T[_size]()) {
for (int i = 0; i < size; ++i) {
p[i] = _p[i];
}
}
~myclass( ) {
delete [] p;
}
void Show()
{
for( int i = 0;i < size;i ++ ) {
cout << p[i] << ",";
}
cout << endl;
}
};
第七题:
template <class T1,class T2>
void mysort(
T1* begin, T1* end, T2 cmp
) {
for (T1* itr1 = begin; end != itr1; ++itr1) {
for (T1* itr2 = itr1 + 1; end != itr2; ++itr2) {
if (!cmp(*itr1, *itr2)) {
T1 temp = *itr1;
*itr1 = *itr2;
*itr2 = temp;
}
}
}
}
从这个代码可以知道,为什么自定义排序必须支持比大小,或者支持小于运算符。这里用的是冒泡排序,如果是快速排序或者归并排序,也差不多,主要是调用比较函数。