跳至主要內容

04. 容器类型

LincZero大约 8 分钟

04. 容器类型

类型模板类 > 结构体 struct

简概

  • 说明:是C++ OOP的基石,是用户定义的类型
  • 使用场景:C语言没有面向对象性质时模拟使用

使用

字面量方式(与C不同)

  • 类型定义struct 结构名{结构的成员},例struct inflatable {char name[20]; float volume;}

    可以声明位数,例struct inflatable {unsigned int:4; bool goodIn:1;}

    可以使用没有名称的字段来提供间距

  • 声明[struct] 结构名 变量名,例inflatable guest(C++允许省略"struct")

  • 初始化

    • C语言:[struct] 结构名 变量名 = {对应参数},例inflatable guest = {"Tony", 1.88}
    • C++:[struct] 结构名 变量名 {对应参数}(即大括号初始化省略等号)
  • 赋值:结构之间可以相互赋值

  • 访问成员:使用句点运算符,例inflatable.name

数组(与python列表不同)

简概

  • 创建声明:要创建数组,声明应指出:存储在每个元素中的值的类型、数组名、元素数
  • 子类型:int数组、float数组......等等

使用

字面量方式

整体

  • 声明一般方式:通用格式typeName arrayName[arraySize],例如short months[12]

    其中所有的值在编译时都是已知的,即arraySize不能是变量,变量的值是在程序运行时设置的(可以用new方式绕开)

  • 初始化一般方式:使用大括号初始化

    • 一般用法:int ai_foo[4] = {3, 6, 8, 10}
    • 自动填补:若不写完整,如= {1},则其他元素被初始化为0
    • 全零简便写法:基于空值自动填补0的特性,int ai_foo[4] = {0}即全部元素被初始化为0
    • 自动计算个数:int ai_foo[] = {1, 2, 3, 4},自动计算数组长度并计算(主要是字符数组初始化为字符串比较方便)
    • 注意项:大括号初始化法只有在初始化时才能用,后期无法使用
    • 注意项:C语言只能通过大括号初始器(列表初始化)初始化类变量,C++将其作为一种通用初始化方式,并且其新增了一些功能:
      • 可省略等号,如int n_int {1}
      • 可省略大括号里的值(初始化为0),如int n_int {}(优点:能更好防范类型转换错误)
      • 禁止缩窄转换,如不能int n_int {1.0}
  • 数组赋值:不能够使用大括号初始化法赋值、也不能将一个数组赋个另一个数组(赋值的是地址)

  • 多维数组:例int maxtemps[4][5]

元素

  • 使用元素:用方括号标注序列
  • 元素赋值:可以正常赋值

内置函数操作

  • 计算长度sizeof()只能知道总字节数,但可以这样求:sizeof(ari_val) / sizeof(ari_val[0])

【模板容器】Vector

(数组的代替品)

简概

C++标准模板库(STL)提供了数组代替品:模板类vectorarray(第16章)

  • 本质
    • 模板类
    • 动态数组(基本上,是使用new创建动态数组的替代品,也使用new和delete管理内存,但这种工作是自动完成的)
  • 优点:
    • 动态数组(动态联结):
      • 可用在运行阶段设置长度
      • 可在末尾附加新数据、中间插入新数据

使用

  • 头文件:#include <vector>
  • 名称空间:using namespace std
  • 定义:(由于可以动态调整长度,因此可以将初始长度设置为零)
    • 通用格式:vector<typeName> vt[(n_elem)=(0)],n_elem可以是整型常量或变量
    • 举例:vector<int> vn(不传参数则设置长度为零)、vector<double> vd(n)

【模板容器】Array

(数组的替代品)

简概

和vector差不多

使用

  • 头文件:#include <array>,使用命名空间std
  • 定义:
    • 通用格式:array<typeName, n_elem> arr,n_elem只能为整型常量

【高级容器】Tuple元组(C++11)

简概

tuple是一个强大的允许存放多个不同类型数据的容器,是对pair的泛化

std::tuple理论上可以有无数个任意类型的成员变量,而std::pair只能是2个成员,因此在需要保存3个及以上的数据时就需要使用tuple元组了(tuple(元组)在c++11中开始引用的。)

std::tuple<T1, T2, TN> t1; //创建一个空的tuple对象(使用默认构造),对应的元素分别是T1和T2...Tn类型,采用值初始化
std::tuple<T1, T2, TN> t2(v1, v2, ... TN);  //创建一个tuple对象,它的两个元素分别是T1和T2 ...Tn类型; 要获取元素的值需要通过tuple的成员get<Ith>(obj)进行获取(Ith是指获取在tuple中的第几个元素,请看后面具体实例)。
std::tuple<T1&> t3(ref&); // tuple的元素类型可以是一个引用
std::make_tuple(v1, v2); // 像pair一样也可以通过make_tuple进行创建一个tuple对象

【高级容器】Boost::any

参考:【博客园】转 - boost::any的用法、优点和缺点以及源代码分析open in new window

使用

用法示例(一个可以存任意不同类型的数组,类似于python的列表)

补充:Python列表的原理:参考:https://cloud.tencent.com/developer/article/1820193

#include <iostream>
#include <list>
#include <boost/any.hpp>	// boost库

typedef std::list<boost::any> list_any;

//关键部分:可以存放任意类型的对象
void fill_list(list_any& la)
{    
    la.push_back(10);//存放常数 
    la.push_back( std::string("dyunze") );//存放字符串对象;注意la.push_back(“dyunze”)错误,因为会被当错字符串数组
}

//根据类型进行显示
void show_list(list_any& la)
{
    list_any::iterator it;
    boost::any anyone;

    for( it = la.begin(); it != la.end(); it++ )		// 遍历显示内容
    {    
        anyone = *it;

        if( anyone.type() == typeid(int) )				// 显示Int
            std::cout<<boost::any_cast<int>(*it)<<std::endl;
        else if( anyone.type() == typeid(std::string) )	// 显示字符串
            std::cout<<boost::any_cast<std::string>(*it).c_str()<<std::endl;
    }
}

int main()
{
    list_any la;
    fill_list(la);
    show_list(la);

    return 0;
}

优缺点

优点1 —— 能存放类型本身

对设计模式理解的朋友都会知道合成模式。因为多态只有在使用指针或引用的情况下才能显现,所以std容器中只能存放指针或引用(但实际上只能存放指针,无法存放引用,这个好像是c++的不足吧)。如:

std::list<BaseClass*> mylist;

这样,我们就要对指针所指向内容的生存周期操心 (可能需要程序员适时删除申请的内存;但是由于存放指针,插入/删除的效率高), 而使用boost::any就可能避免这种情况,因为我们可以存放类型本身(当然存放指针也可以)。这是boost::any的优点之一。

优点2 —— 可以存放任何类型

而前面提到的mylist只能存放BaseClass类指针以及其继承类的指针。

缺点

由于boost::any可以存放任何类型,自然它用不了多态特性,没有统一的接口,所以在获取容器中的元素时需要实现判别元素的真正类型,这增加了程序员的负担。与面向对象编程思想有些矛盾,但整个标准c++模板库何尝不是如此,用那些牛人的话来说,是“有益补充”。

总之,有利必有弊,没有十全十美的

分析并模仿boost::any

分析

实现any的功能主要由三部分组成:

  1. any类
  2. 真正保存数据的holder类及其基类placeholder
  3. 获取真正数据的模板函数any_cast,类型转换的功能。

模仿

#include <iostream>
#include <list>
#include <cassert>
												//////// 自定义的any类
class any
{
public:
    
    											//////// 保存真正数据的接口类(纯虚基类)I,不用模板
    class placeholder
    {
    public: 
        virtual const std::type_info & type() const = 0;
        virtual placeholder * clone() const = 0;
        virtual ~placeholder(){}
    };

    											//////// 真正保存和获取数据的类(派生类)II,用了模板
    											//////// 即会生成多个placeholder的派生类,他们都继承于placeholder
    template<typename ValueType>
    class holder : public placeholder
    {
    public: 
        ValueType held;         		// 真正的数据,就保存在这里
        holder(const ValueType & value): held(value){}
        virtual const std::type_info & type() const
        {
            return typeid(ValueType);
        }
        virtual placeholder * clone() const
        {
            return new holder(held); 	// 使用了原型模式。即拷贝构造时通过深克隆原型来创建新的对象
        }
    };

public:
    any(): content(NULL){}

    //模板构造函数,参数可以是任意类型,真正的数据保存在content中
    template<typename ValueType>
    any(const ValueType & value): content(new holder<ValueType>(value)){}  

    //拷贝构造函数
    any(const any & other)
        : content(other.content ? other.content->clone() : 0){}

    //析构函数,删除保存数据的content对象
    ~any()
    {
        if(NULL != content)
            delete content;
    }

private:
    //一个placeholde对象指针,指向其子类folder的一个实现
    // 即content( new holder<ValueType>(value) )语句
    placeholder* content;
    											//////// 获取真正数据的模板函数(友元)I
    template<typename ValueType> friend ValueType any_cast(const any& operand);
    
public: 
    //查询真实数据的类型。
    const std::type_info & type() const
    {
        return content ? content->type() : typeid(void);
    }
};

												//////// 获取真正数据的模板函数(友元)II
//获取content->helder数据的方法。用来获取真正的数据
template<typename ValueType>
ValueType any_cast(const any& operand)
{
    assert( operand.type() == typeid(ValueType) );
    return static_cast<any::holder<ValueType> *>(operand.content)->held;
}

使用示例

typedef std::list<any> list_any;

void fill_list(list_any& la)
{    
    la.push_back(10);//存放常数;调用了any的模板构造函数,下同
    la.push_back( std::string("我是string") );//存放字符串对象;注意la.push_back(“dyunze”)错误,因为会被当错字符串数组

    char* p = "我是常量区字符串abc";
    la.push_back(p);//可以存放指针,但要注意指针的失效问题
}

//根据类型进行显示
void show_list(list_any& la)
{
    list_any::iterator it;

    for( it = la.begin(); it != la.end(); it++ )
    {    

        if( (*it).type() == typeid(int) )
            std::cout<<any_cast<int>(*it)<<std::endl;
        else if( (*it).type() == typeid(std::string) )
            std::cout<<any_cast<std::string>(*it).c_str()<<std::endl;
        else if( (*it).type() == typeid(char*) )
            std::cout<<any_cast<char*>(*it)<<std::endl;
    }
}

int main()
{
    list_any la;
    fill_list(la);
    show_list(la);

    return 0;
}