北京大学程序设计MOOC作业详解-08-标准模板库STL(一)
第一题:
这个题要分析分析,首先看它的测试代码:
int main()
{
int t;
cin >> t;
while( t -- ) {
int m ;
cin >> m;
for(int i = 0;i < m; ++i)
cin >> a[i];
GoodCopy<int>()(a,a+m,b);
Print(b,b+m);
GoodCopy<int>()(a,a+m,a+m/2);
Print(a+m/2,a+m/2 + m);
for(int i = 0;i < m; ++i)
cin >> c[i];
GoodCopy<string>()(c,c+m,d);
Print(c,c+m);
GoodCopy<string>()(c,c+m,c+m/2);
Print(c+m/2,c+m/2 + m);
}
return 0;
}
有代码GoodCopy<int>()(a,a+m,a+m/2);
,这涉及到一个严重的问题:数据覆盖。比如说数组a[] = {1, 2, 3, 4, 5, 6, _, _, _};
,这里腾出来三个位置(数组大小为6),正确的拷贝应是:{_, _, _, 1, 2, 3, 4, 5, 6}
。这需要从后往前拷贝,如果是从前往后,则会变成:{_, _, _, 1, 2, 3, 1, 2, 3}
。
因此在算法的设计上,要有考究,必须从后往前拷贝, AC代码如下:
template <class T>
struct GoodCopy {
template<class T_ = T>
void operator()(T_ begin, T_ end, T_ ptr) {
for (; end != begin; --end) {
*(ptr +(end - begin - 1)) = *(end - 1);
}
}
};
第二题:
这个题先看调用代码:
sort(a,a+10,Closer<int ,int (*)(int ,int)> (n,Distance1));
首先总结自定义sort排序的几种方法:
1 自定义cmp函数,声明如下:
bool cmp(const Type& obj1, const Type& obj2);
2 自定义重载小于号运算符,声明如下:
class Type {
public:
bool operator<(const Type& obj) const;
};
3 自定义函数对象,即仿函数。仿函数是指,可以将对象看作一种函数,用调用函数的方式去调用,核心是自定义重载圆括号运算符,声明如下:
struct Comperator {
bool operator()(const Type& obj1, const Type& obj2);
};
此时,传入Comperator类型的对象,例如Comperator cmp;
,就可以通过cmp(obj1, obj2);
的方式进行比大小。
综上所述,自定义排序规则的核心,是要有比大小的功能。
还需要注意,仿函数不是通过类调用的,而是通过对象调用的!所以必须实例化,先看看本题实例化的办法:
Closer<int ,int (*)(int ,int)> (n,Distance1)
显然这是一个匿名对象,第一个参数是对比的对象,第二个参数是比较的函数。然后,两个对象obj1和obj2谁排在前面呢?就看它们和n的相对大小。
AC代码如下:
template <class T1,class T2>
struct Closer {
T1 m_val;
T2 m_func;
template<class T1_ = T1, class T2_ = T2>
Closer(T1_ val, T2_ func) : m_val(val), m_func(func) { }
template<class T1_ = T1>
bool operator()(T1_ n1, T1_ n2) {
int d1 = m_func(n1, m_val);
int d2 = m_func(n2, m_val);
if (d1 != d2) {
return (d1 < d2);
}
return (n1 < n2);
}
};
第三题:实现三维数组模板类
这个题有点难度,我第一次实现如下:
class CArray3D
{
// 在此处补充你的代码
public:
class CArray2D {
private:
T** m_data;
size_t m_row, m_col;
public:
CArray2D(size_t row = 0, size_t col = 0) : m_row(row), m_col(col) {
if (0 == row) {
m_data = nullptr;
return;
}
m_data = new T * [row];
for (size_t j = 0; j < row; ++j) {
m_data[j] = new T[col]();
}
}
CArray2D(const CArray2D& arr2D)
: m_row(arr2D.m_row), m_col(arr2D.m_col) {
if (arr2D.m_data) {
m_data = new T * [m_row];
for (size_t j = 0; j < m_row; ++j) {
m_data[j] = new T[m_col]();
for (size_t k = 0; k < m_col; ++k) {
m_data[j][k] = arr2D.m_data[j][k];
}
}
}
else {
m_data = nullptr;
}
}
CArray2D& operator=(const CArray2D& arr2D) {
if (this == &arr2D) { return *this; }
if (m_data) {
for (size_t j = 0; j < m_row; ++j) {
delete[] m_data[j];
}
delete[] m_data;
}
m_row = arr2D.m_row, m_col = arr2D.m_col;
if (!arr2D.m_data) {
m_data = nullptr;
return *this;
}
m_data = new T * [m_row];
for (size_t j = 0; j < m_row; ++j) {
m_data[j] = new T[m_col]();
for (size_t k = 0; k < m_col; ++k) {
m_data[j][k] = arr2D.m_data[j][k];
}
}
return *this;
}
T*& operator[](size_t idx) {
return m_data[idx];
}
operator T** () {
return m_data;
}
~CArray2D() {
if (m_data) {
for (size_t j = 0; j < m_row; ++j) {
delete[] m_data[j];
}
delete[] m_data;
m_data = nullptr;
}
}
};
CArray3D(size_t layer, size_t row, size_t col) : m_layer(layer) {
m_arr2D = new CArray2D[m_layer];
for (size_t i = 0; i < m_layer; ++i) {
m_arr2D[i] = CArray2D(row, col);
}
}
CArray2D& operator[](size_t layer) {
return m_arr2D[layer];
}
~CArray3D() {
if (m_arr2D) {
delete[] m_arr2D;
m_arr2D = nullptr;
}
}
private:
CArray2D* m_arr2D;
size_t m_layer;
};
但是memset这个重要操作没法实现,测试代码有这个调用:
memset(a[1], -1, 20 * sizeof(int));
memset(a[1][1], 0, 5 * sizeof(int));
旨在对三维数组的某一层或者某一行赋值,按照我们的实现,对某一行赋值,也就是memset(a[1][1], 0, 5 * sizeof(int));
是没有问题的。但是对某一层赋值是不行的,这是因为memset只能给一段连续的内存空间赋值。但是从二维数组的实现可以看到:
m_data = new T * [row];
for (size_t j = 0; j < row; ++j) {
m_data[j] = new T[col]();
}
这个二维数组,并非new在一块,地址间可能是分散的,所以需要用连续的存储,修改代码得到:
class CArray3D
{
// 在此处补充你的代码
public:
class CArray2D {
private:
T* m_data;
size_t m_row, m_col;
public:
CArray2D(size_t row = 0, size_t col = 0) : m_row(row), m_col(col) {
if (!row || !col) {
m_data = nullptr;
return;
}
m_data = new T[row * col];
}
CArray2D(const CArray2D& arr2D)
: m_row(arr2D.m_row), m_col(arr2D.m_col) {
if (arr2D.m_data) {
m_data = new T[m_row * m_col];
for (size_t j = 0; j < m_row * m_col; ++j) {
m_data[j] = arr2D.m_data[j];
}
}
else {
m_data = nullptr;
}
}
CArray2D& operator=(const CArray2D& arr2D) {
if (this == &arr2D) { return *this; }
if (m_data) {
delete[] m_data;
}
m_row = arr2D.m_row, m_col = arr2D.m_col;
if (!arr2D.m_data) {
m_data = nullptr;
return *this;
}
m_data = new T[m_row * m_col];
for (size_t j = 0; j < m_row * m_col; ++j) {
m_data[j] = arr2D.m_data[j];
}
return *this;
}
T* operator[](size_t idx) {
return (m_data + idx * m_col);
}
operator T* () {
return m_data;
}
~CArray2D() {
if (m_data) {
delete[] m_data;
m_data = nullptr;
}
}
};
CArray3D(size_t layer, size_t row, size_t col) : m_layer(layer) {
m_arr2D = new CArray2D[m_layer];
for (size_t i = 0; i < m_layer; ++i) {
m_arr2D[i] = CArray2D(row, col);
}
}
CArray2D& operator[](size_t layer) {
return m_arr2D[layer];
}
~CArray3D() {
if (m_arr2D) {
delete[] m_arr2D;
m_arr2D = nullptr;
}
}
private:
CArray2D* m_arr2D;
size_t m_layer;
};
这里需要注意一点:核心思想是将二维数组变成了一维数组,但是在memset取行的时候,移动的步长是列数。因为赋值必须一定到对应行的开头,而移动的距离则是列数。
思考:这样做有普适性吗?即:任意多维度的数组,都能这么做吗?其实有点离谱,memset的需求不是特别合理,建议调研openCV或者其他的包含多维张量的第三方库,看看是怎么实现的,我也没有很多研究。
第四题:
这个题很简单,和之前的排序一样,用写一个函数对象,完成过滤器的功能。AC代码如下:
template<class T>
struct FilterClass {
T m, n;
FilterClass(T m_, T n_) : m(m_), n(n_) { }
bool operator()(T val) {
return (m < val) && (val < n);
}
};
第五题:
浅用了一手lambda表达式,lambda表达式很重要,在后面再详细解说。AC代码如下:
[](double n1, double n2) {return n1 > n2; }
第六题:
写一个自己的ostream_iterator,吸取上次的经验,先去看看STL是怎么实现的:
ostream_iterator& operator=(const _Ty& _Val) { // insert value into output stream, followed by delimiter
*_Myostr << _Val;
if (_Mydelim) {
*_Myostr << _Mydelim;
}
return *this;
}
_NODISCARD ostream_iterator& operator*() noexcept /* strengthened */ {
return *this;
}
ostream_iterator& operator++() noexcept /* strengthened */ {
return *this;
}
到这里大家就都明白了,参照STL,完成AC代码:
template<class T>
class myostream_iteraotr
{
// 在此处补充你的代码
string m_delim;
ostream& m_out;
public:
myostream_iteraotr(ostream& out, const string& delim)
: m_delim(delim), m_out(out) { }
myostream_iteraotr& operator++() {
return *this;
}
myostream_iteraotr& operator*() {
return *this;
}
template<class T_ = T>
void operator=(const T_& val) {
m_out << val << m_delim;
}
};
详细解释这个代码:这个代码还是有一定难度的,首先明确功能:
1)要有前置++运算符函数,但显然并不做什么事情;
2)一个关键代码:*x = *s;
,这既包括重载解引用运算,又包括重载赋值运算,但是解引用运算是无参的,所以只能在赋值运算上输出。
第七题:
第七题分析:
这个题考察STL中list的用法,AC代码如下:
#include <list>
#include <vector>
#include <string>
#include <iostream>
using namespace std;
class List {
private:
int m_id;
list<int> m_list;
public:
List(int id) : m_id(id) { }
void addNum(int val) {
m_list.push_back(val);
}
void unique() {
m_list.sort();
m_list.unique();
}
void out() {
m_list.sort();
for (auto& it : m_list) {
cout << it << " ";
}
cout << endl;
}
void merge(List& rhs) {
m_list.merge(rhs.m_list);
}
int getId() const {
return m_id;
}
};
List& findEleInVec(vector<List>& lst, int id) {
for (List& cur : lst) {
if (id == cur.getId()) {
return cur;
}
}
}
int main() {
vector<List> listVec;
int T;
cin >> T;
string cmd;
while (T--) {
cin >> cmd;
int id;
if ("new" == cmd) {
cin >> id;
listVec.emplace_back(id);
}
else if ("add" == cmd) {
int val;
cin >> id >> val;
findEleInVec(listVec, id).addNum(val);
}
else if ("out" == cmd) {
cin >> id;
findEleInVec(listVec, id).out();
}
else if ("merge" == cmd) {
int id2;
cin >> id >> id2;
List& mg = findEleInVec(listVec, id2);
findEleInVec(listVec, id).merge(mg);
}
else if ("unique" == cmd) {
cin >> id;
findEleInVec(listVec, id).unique();
}
}
return 0;
}
有两个点需要注意:
1)执行unique算法前,必须先排序,这是为了O(n)实现;
2)本题要求从小到大输出,记得排序。