嗯,上一讲我们只是简单的引入了一些标准库的知识,如果大家还记得前面内容的话,那么大家一定对标准库的东西不会陌生,尤其是我们这个课堂,因为很多东西都是围绕标准库展开的,那么今天我们来继续说一个新东西——tuple(元组)。
如果有脚本语言编程经验的同学一定对元组这个词不陌生,他有很大的灵活性,那么具体体现在哪里呢?
首先我们来看看昨天我们所说的容器,他们虽然提供了一些很方便操作的接口,但是他们都有一个限制——他们只能储存同一类型的东西,map可以储存两种类型的值,但是这对于喜欢了脚本编程使用元组的同学来说这是不是太过于限制了吧——嗯,一切都是有原因,这就是C++。
那么C++11解决了这个问题,引入元组这个东西,他不再受到元素类型的限制,可以存放任意类型的值,在C++11标准出来之前boost也是有元组的,但是有个数限制,只能储存10个值,但是C++11引入的不再受个数限制,元组在头文件tuple中,所以当我们想要使用元组的时候我们需要包含tuple头文件。
先来简单的说一些tuple的用法吧,其实他的用法很简单,他就像是一个多个元数pair,好吧,我还是举行例子
//=============================================
std::tuple<std::string, int, double,float>tp("
hello"
, 1, 2, 3);
//=============================================
但是对于tuple的元素提取不是像pair那么简单,标准库提供了一个函数get来提取tuple的值:
//=============================================
std::string first = std::get<0>(tp);
int second = std::get<1>(tp);
//…………..
//=============================================
嗯,这么看来如果我们想要打印tuple的话似乎就变得有些为难起来,不过别忘了,C++是万能的,还记得以前我们说的元编程技巧吗?如果忘记可以去补一下,大概还是在80到90讲这个范围吧。
为了很简单的打印元组,我们需要用到元变成技巧,将这些模板参数给消除掉,具体实现如下:
//=============================================
template<int N, class Tuple>
class tuple_helper{
public:
tuple_helper(Tuple t, std::ostream&
os){
tuple_helper<N – 1, Tuple>tp(t, os);
os <<std::get<N – 1>(t) <<"
t"
;
}
};
template<class Tuple>
class tuple_helper<0, Tuple>{
public:
tuple_helper(Tuple t, std::ostream&
os){ }
};
template<class T>
inline
void tuple_print(T t, std::ostream&
os){
tuple_helper<std::tuple_size<T>::value, T>tp(t, os);
}
//=============================================
嗯,是不是挺简单的呢?只是很少能够想到用这么方法来搞定,有了tuple_print之后,我们想要打印一个tuple就变成相当的简单了:
//=============================================
tuple_print(tp,std::cout) ;
//将元组内容打印到标准输出窗口上
std::ofstream outFile("
test.txt);
tuple_print(tp,outFile);
//将元组内容写进文件中
//===============================================
用了这么辅助,一切都变得简单起来了。那么问题来了,tuple只是相当于一个比较大点的pair,这对我们来说可能还是不太方便,就比如我会看到很多人问怎么实现一个图书馆管理这个问题,如果我们来简化一下图书馆所要管理的东西的话,我们可以简单的写成:
//=================================================
图书名,图书编号,位置,价格,是否借出,借用人姓名
//=================================================
为了简便,我们假设就这么点信息,我们就可以简单的声明这些变量如下:
//=================================================
std::string book_name;
long long book_num;
Point
book_pos;
double
book_price;
bool
book_is_out;
std::string book_borrower;
//==================================================
如果是以前我们大概会将这定义为一个结构体,当然这也是最简便的操作了,但是现在我们可以有另一个选择,那就是我们可以使用tuple来管理,假设图书馆的书很多,那么这些数据组合起来可能就是一个很大的矩阵,当然这不是只有图书馆管理书籍这样,在实际开发过程中,处理矩阵数据这是一件很普通的事。
说到矩阵,boost里面有自己的矩阵库,xnamath库也有自己的矩阵库,D3DXMath库也有自己的矩阵,但是这些矩阵都是用于一些特殊的操作,就比如xnamath和D3DXMath的矩阵都是用于3D图形转换用的,对于我们这种大数据的操作不是那么实用,boost的matrix用得不多,不好评价,但是发现操作不是那么简便,但是他提供了矩阵的所有操作,而我却用不到这些功能,我只是需要一个像是矩阵的容器来帮我管理数据就好,所以就自己实现了一个,这个容器是可以储存任意类型元素的容器,这及时我们今天要说的MContainer。
//==============================================
template<class T,class…Args>
class MContainer{
typedef std::tuple<T, Args…>tuple_data;
public:
typedef tuple_data value_type;
//====================================
//迭代器不一定都是指针这么简单的
//但是我们这里使用vector来作为容器
//那么迭代器可以只是value_type的指针
//====================================
typedef value_type* iterator;
HContainer(){ }
~HContainer(){ }
void push_back(T value, Args…args){
m_data.push_back(std::make_tuple(value, args…));
}
std::vector<tuple_data>&
getData(){
return m_data;
}
const std::vector<tuple_data>&
getData() const{
return m_data;
}
unsigned colum(){
if (m_data.empty())
return 0;
return std::tuple_size<tuple_data>::value;
}
unsigned row(){
return m_data.size();
}
template<class U,int N>
U at(unsigned row){
if (N >= std::tuple_size <value_type >::value){
throw std::runtime_error("
colum out of range"
);
}
if (row >= m_data.size()){
throw std::runtime_error("
row out of range"
);
}
return std::get<N>(m_data.at(row));
}
template<class U,
int N>
std::vector<U>getColumData(){
if (N >= std::tuple_size <value_type >::value){
throw std::runtime_error("
colum out of range"
);
}
std::vector<U>result;
std::for_each(m_data.begin(), m_data.end(), [&
](tuple_data data){
result.push_back(std::get<N>(data));
});
return result;
}
tuple_data getRowData(unsigned row){
if (row >= m_data.size()){
throw std::runtime_error("
row out of range"
);
}
return m_data.at(row);
}
friend std::ostream&
operator<<(std::ostream&
os, const HContainer&
c){
std::for_each(c.m_data.begin(), c.m_data.end(), [&
](tuple_data data){
tuple_print(data, os);
os <<std::endl;
});
return os;
}
iterator begin(){
if (m_data.empty())
return nullptr;
return &
m_data[0];
}
iterator end(){
if (m_data.empty())
return nullptr;
return (&
m_data[m_data.size()-1])+1;
}
template<typename T,int N>
std::vector<value_type>
find(const T&
value){
std::vector<value_type>result;
if (m_data.empty())
return result;
for (auto&
d : m_data){
if (std::get<N>(d) == value){
result.push_back(d);
}
}
return result;
}
private:
std::vector<tuple_data>m_data;
};
//============================================
在前面我们的铺垫下,现在我们来看这个MContainer的实现是不是变得挺简单的呢?由于数据结构的特殊性,所以有几个函数的操作相对显得有些怪异,但是想想也是必须的,关于begin()和end()是为能够使用标准库的算法而提供的,有他们我们就可以使用算法对他进行操作了。
我们简单的使用以下:
//===============================================
MContainer<std::string, int, double>hc;
hc.push_back("
zhangsan"
, 50, 500.0);
hc.push_back("
lisi"
, 60, 600.0);
hc.push_back("
hehe"
, 70, 600.0);
hc.push_back("
wanger"
, 70, 700.0);
try{
std::cout <<hc <<std::endl;
std::cout <<hc.at<double, 2>(2) <<std::endl;
auto tp = hc.find<double, 2>(600);
for (auto t : tp){
hlw::tuple_print(t, std::cout);
std::cout <<std::endl;
}
std::cout <<std::endl;
std::for_each(hc.begin(), hc.end(), [&
](HContainer<std::string, int, double>::value_type p){
hlw::tuple_print(p, std::cout);
std::cout <<std::endl;
});
hlw::tuple_print(*(hc.end() – 1), std::cout);
}
catch (std::runtime_error e){
std::cout <<e.what() <<std::endl;
}
//===========================================
关于这个存放任意类型的容器就说到这里,大家可以将这几讲的东西都放在一个头文件文中,用的时候包含该头文件就好,会为我们实际开发过程提供很大的便利,ok,这个容器就说到这里,下一讲再说一个只包含一种类型的矩阵,虽然说这个多类型的容器比较方便,但是如果我们只操作一种数据类型的时候,接下来的MMatrix矩阵会更有优势。
//===========================================
回复D查看目录,回复数字查看相应的章节内容
原文始发于微信公众号(
C/C++的编程教室
):第103讲 扩展标准库(2)
|