C++容器中实用的查找功能

C++标准中std提供了几种容器,它们包括顺序容器,比如vector, list, deque, queue, stack等,关联容器 ,比如map, set等,其中使用频率比较高的容器是vecotor向量容器、map键值对容器,我们经常会使用这两个容器来存储数据,然后根据不同的场景来查找获取容器内的值。而本文接下来将说明从这两类容器中快速查找获取数据的方法。

一、vector容器查找功能

vector容器自身没有提供查找函数,这里借助标准模版库algorithm提供的find,  使用的时候需要包含该头文件。

1、首先定义vector容器变量,然后存入数据,接着遍历打印容器内的所有数据,最后调用algorithm提供的函数find从vector向量中查找数据,algorithm提供的函数find需要输入三个入参数,第一个参数是容器开始查找的迭代器变量,第二个变量是容器结束查找的迭代器变量,第三个参数是需要查找的数据。

#include <vector>
#include <algorithm>

// 定义vecotor,然后存入数据
std::vector<std::string> vec_str;
vec_str.push_back("abc");
vec_str.push_back("def");
vec_str.push_back("fhj");
vec_str.push_back("123");
vec_str.push_back("456");

// 遍历打印vector容器内的数据
std::vector<std::string>::iterator iter = vec_str.begin();
for(iter = vec_str.begin(); iter != vec_str.end(); iter++)
{
    LOG(INFO) << *iter;
}
LOG(INFO) << "======";

// 调用find函数查找,内容为“def”的信息
iter = find(vec_str.begin(),vec_str.end(), "def");
if (iter != vec_str.end())
{
    LOG(INFO) << "find info: " << *iter;
}
LOG(INFO) << "======";

2、运行程序,输出的内容如下图所示,容器内存在需要查找的数据,返回迭代器变量,我们根据迭代器变量输出数据内容

二、map容器查找功能

map容器自身提供了查找功能,同时它也支持使用标准模版库algorithm提供的find函数。

1、首先定义map容器变量,写入数据,再遍历输出容器内的数据,接着调用map容器自身提供的find函数来查找key为2的数据,返回迭代器变量,然后根据这个迭代器变量输出键值,接下来调用algorithm提供的find函数,需要注意的是第三个参数输入的是迭代器的取值,最后也是返回迭代器变量。

#include <map>
#include <algorithm> 

// 首先定义map容器变量,写入数据
std::map<int, std::string> map_str;
map_str[1] = "aa";
map_str[2] = "bb";
map_str[3] = "cc";

// 遍历输出容器内的所有内容
std::map<int, std::string>::iterator iter_map;
for(iter_map = map_str.begin(); iter_map != map_str.end(); iter_map++)
{
    LOG(INFO) << "key: " << iter_map->first <<" value "<< iter_map->second;
}
LOG(INFO) << "======";

// 调用map容器自身提供的find函数,查找key为2的数据
iter_map = map_str.find(2);
if (iter_map != map_str.end())
{
    LOG(INFO) << "key: " << iter_map->first <<" value: "<< iter_map->second;
}
LOG(INFO) << "======";

// 调用algorithm提供的find函数来查找数据,注意find的第三个参数输入的是迭代器的取值
std::map<int, std::string>::iterator iter_map_ret;
iter_map_ret = find(map_str.begin(), map_str.end(), *iter_map);
if (iter_map_ret != map_str.end())
{
    LOG(INFO) << "result, key: " << iter_map_ret->first <<" value: "<< iter_map_ret->second;
}

2、运行程序,输出的内容如下图所示

三、键自定义的map容器查找功能

map容器使用过程中,有时候为了程序的可维护性以及降低代码的复杂度,需要自定义类作为map的键,在这种场景下,上面的查找方法是否也能够生效呢?接下来让我们进行验证。

1、首先自定义类JKeyPair来作为map的key,  如果自定义对象要作为map的键,那么需要重载operator<运算符,而如果要使用algorithm中的find,需要重载operator==运算符

#include <iostream>

/// 类的定义
class JKeyPair
{
public:
    JKeyPair(const std::string &strName, int iIndex);
    ~JKeyPair();

    std::string GetName(void);
    int GetIndex(void);

    // 自定义map中的key,需要重载operator<运算符(一定要记得加上const, 否则调用出错)
    bool operator<(const JKeyPair &rhs) const;

    // 自定义map中的key,使用algorithm中的find,需要重载operator==运算符(一定要记得加上const, 否则调用出错)
    bool operator==(const JKeyPair& rhs) const;
private:
    std::string m_strName;
    int m_iIndex;
};


/// 类的实现
JKeyPair::JKeyPair(const std::string &strName, int iIndex)
    :m_strName(strName),m_iIndex(iIndex)
{}

JKeyPair::~JKeyPair()
{}

std::string JKeyPair::GetName(void)
{
    return m_strName;
}

int JKeyPair::GetIndex(void)
{
    return m_iIndex;
}

bool JKeyPair::operator<(const JKeyPair &rhs) const
{
    if (m_iIndex < rhs.m_iIndex)
    {
        return true;
    }
    else if ((m_iIndex ==  rhs.m_iIndex)
             && (m_strName < rhs.m_strName))
    {
        return true;
    }
    return false;
}


bool JKeyPair::operator==(const JKeyPair& rhs) const
{
    if ((m_iIndex ==  rhs.m_iIndex)
            && (m_strName == rhs.m_strName))
    {
        return true;
    }
    return false;
}

2、验证调用map自身提供的find函数,其测试代码如下图所示

// 定义map容器变量,key是自定义类型,然后写入数据
std::map<JKeyPair, std::string> map_info;
JKeyPair key_pair_1("one", 1);
map_info[key_pair_1] = "value_one";
JKeyPair key_pair_2("two", 2);
map_info[key_pair_2] = "value_two";
JKeyPair key_pair_3("three", 2);
map_info[key_pair_3] = "value_three";

// 循环遍历容器内数据
std::map<JKeyPair, std::string>::iterator iter;
for(iter = map_info.begin(); iter != map_info.end(); iter++)
{
    JKeyPair key_pair = iter->first;
    LOG(INFO) << "name : " << key_pair.GetName();
    LOG(INFO) << "index : " << key_pair.GetIndex();
    LOG(INFO) << "value : " << iter->second;
}
LOG(INFO) << "======";

// 通过调用map自身提供的函数find,来查找键为key_pair_2的数据
iter = map_info.find(key_pair_2);
if (iter != map_info.end())
{
    LOG(INFO) << "find, value : " << iter->second;
}

3、从运行的结果看,使用自定义对象作为key值,map容器提供的find函数能够正确运行,并且从实际操作过程中,可以得出,map容器提供的find函数,自定义对象不需要重载operator==运算符。

4、接着再验证algorithm中的find,从实际操作过程中,自定义对象需要重载operator==运算符,测试代码段如下图所示,其中find中第三个参数是上一步骤中调用map自身的find返回的迭代器变量

// 使用alogrithm提供的find来查找map容器的数据
std::map<JKeyPair, std::string>::iterator iter_map_key;
iter_map_key = std::find(map_info.begin(), map_info.end(), *iter);
if (iter_map_key != map_info.end())
{
    JKeyPair key_pair = iter_map_key->first;
    LOG(INFO) << "result, self key: " << key_pair.GetName()  << "/" << key_pair.GetIndex()
              <<" value: "<< iter_map_key->second;
}

5、运行后打印的结果信息看,能够正确调用algorithm中的find来查找map中的数据

四、总结

到这里,我们已经将容器vector,map的查找功能介绍完成。接下来梳理总结。vector容器自身没有提供查找函数,因此,需要调用algorithm中的find来快速查找数据。map容器自身既提供了查找函数,也支持使用algorithm中的find来快速查找数据。而自定义对象作为map容器的键的情况下,如果需要支持上面的场景,那么自定义类需要重载operator<和operator==运算符。

快速追踪内存使用情况的方法

函数mtrace是linux中用于开启内存使用记录的函数接口,而函数muntrace是关闭内存使用记录的函数接口,另外环境变量MALLOC_TRACE则是决定内存使用记录是否记录到文件中。

#include <mcheck.h>
void mtrace(void);
void muntrace(void);

一、入门例子

1、首先给出简单的例子,初步了解函数的使用方法。

#include <mcheck.h>
#include <stdlib.h>

int main(int argc, char** argv)
{
    // 设置MALLOC_TRACE环境变量
    setenv("MALLOC_TRACE", "trace.log", 1);
    // 开启内存使用情况记录
    mtrace();
    return 0;
}

2、编译运行上面创建的cpp文件

3、运行成功之后,执行ls -l查看当前目录情况,可以看到生成了trace.log文件,其内容如下图所示, 文件以“= Start”开始。

二、没有内存泄露的情况

1、原来的例子中增加调用malloc申请内存,再调用free释放内存,最后再调用muntrace关闭内存使用情况记录

#include <mcheck.h>
#include <stdlib.h>

int main(int argc, char** argv)
{
    // 设置MALLOC_TRACE环境变量
    setenv("MALLOC_TRACE", "trace.log", 1);
    // 开启内存使用情况记录
    mtrace();

    char *p = (char *)malloc(10000);
    free(p);

    // 关闭内存使用情况记录
    muntrace();
    return 0;
}

2、编译上面的cpp文件,然后运行生成的可执行文件,再查看生成的trace.log文件,其内容如下所示,以“= Start”开始, 以“= End”结束, 其中+号代表申请内存,-号代表释放内存。从这个文件,我们可以发现,cpp文件最后调用muntrace函数之后,生成的tracel.log文件中的最后会以“= End”结束。

三、内存泄露的情况

1、这次在上面cpp文件的基础上,再次调用malloc申请内存,但是不释放内存

#include <mcheck.h>
#include <stdlib.h>

int main(int argc, char** argv)
{
    // 设置MALLOC_TRACE环境变量
    setenv("MALLOC_TRACE", "trace.log", 1);
    // 开启内存使用情况记录
    mtrace();

    char *p = (char *)malloc(10000);
    free(p);

    char *p2 = (char *)malloc(20000);

    // 关闭内存使用情况记录
    muntrace();
    return 0;
}

2、同样的编译cpp文件,然后运行可执行文件,查看生成的trace.log文件,从内容看,文件倒数第二行中申请了内存,但是没有释放内存。

四、转换为可理解的信息

从上面三个例子看,生成的trace.log的内容的可读性比较差,为了提高可读性,我们可以使用mtrace命令工具来解析trace.log文件,mtrace命令是glibc-utils的工具, 如果linux上没有mtrace命令,那么需要先下载安装glibc-utils。下面首先介绍基于fedora系统下下载安装glibc-utils的方法,最后再来介绍将trace.log转换为可读性的信息命令。

1、下载安装glibc-utils

首先终端执行命令yum install glib-utils,  然后会提示“Is this ok [y/N]:”,直接输入y,然后按下回车键。

如果安装成功的话,那么最后会出现“Complete!”的提示信息

2、将trace.log转换为可读性的信息,其格式为:mtrace 可执行文件  生成的记录内存文件, 如下图所示,终端执行mtrace  a.out trace.log之后,界面直接显示“Memory not freed:”表示内存没有释放,并且还指出文件的具体位置。

五、总结

mtrace()是linux上开启记录内存的函数接口,muntrace()是关闭记录内存的函数接口,MALLOC_TRACE是决定是否将记录内存情况写入文件的环境变量,mtrace命令是glibc-utils的工具,可以将记录内存情况的文件转换为可理解的信息。

读写excel文档入门讲解一

最近工作中需要将数据以各种格式存放到excel文档中,工程是基于C++,并且采用QT设计的界面,因此,通过网络搜索寻找到基于QT来读写excel文档的开源库QXlsx,本文将介绍开源库QXlsx的简单使用和效果,一方面提供入门知识,另一方面也给自己的学习成果做一个总结记录,方便后续查找复习。

一、简单的例子

1、 网上下载QXlsx开源库后,需要将QXlsx加载到Qt Creator工具,直接参考开源库代码中的HowToSetProject.md文件即可

2、展示写入数据,并且生成excel文档的例子

// 头文件
#include "xlsxdocument.h"
#include "xlsxformat.h"
#include "xlsxworkbook.h"

QXlsx::Document xlsx;
// 通过单元格名写入字符串
xlsx.write("A1", "Hello!");
// 通过行列方式写入字符串
xlsx.write(2, 1, "Everyone!");

std::string str_file_name = JPathHandler::GetDebugPath() + "excel_hello.xlsx";
// 生成excel文档
xlsx.saveAs(QString::fromStdString(str_file_name));

3、最后生成的excel文档的内容如下图所示

二、自动换行

有时候写入单元格的内容比较长,超过了单元格的展示范围,而我们又不想扩大单元格的宽度,怎么办呢,可以通过设置自动换行的方式,让写入单元格的内容自动适配当前的单元格。

1、携带配置好的格式写入到单元格

QXlsx::Document xlsx2;

// 初始化格式
QXlsx::Format format2;
format2.setFontSize(10);
format2.setHorizontalAlignment(QXlsx::Format::AlignHDistributed);

// 用上面的格式来初始化下面的单元格
xlsx2.write("A1", "onetwothree,onetwothree,onetwothree", format2);

// 最后生成excel文档(注意JPathHandler::GetDebugPath()自定义的类,用于获取路径,不是开源库的接口)
std::string str_file_name2 = JPathHandler::GetDebugPath() + "excel_align.xlsx";
xlsx2.saveAs(QString::fromStdString(str_file_name2));

2、最后生成的excel文档的内容效果如下图所示

三、多个表单

当需要多个表单来保存不同的数据的时候,这就涉及到创建多个表单的问题。那么将用创建两个表单的例子来作为简单的入门知识点。

1、生成两个表单,并且分别往表单中写入数据

QXlsx::Document xlsx3;

// 第一个表单
xlsx3.addSheet("first year");
xlsx3.write("A1", "first");

// 第二个表单
xlsx3.addSheet("second year");
xlsx3.write("A1", "second");

// 最后生成excel文档
std::string str_file_name3 = JPathHandler::GetDebugPath() + "excel_addsheet.xlsx";
xlsx3.saveAs(QString::fromStdString(str_file_name3));

2、最后生成的excel文档的内容效果如下图所示

四、读取excel文档

写入数据到excel文档之后,肯定有需求场景需要读取excel文档数据来展示,所以,接下来将说明读取excel文档的所有数据的方法。

1、先读取excel文档,然后获取每一个表单,读取每一个表单中的单元格内容

// 读取上一个步骤中生成的excel文档
QXlsx::Document xlsx4( QString::fromStdString(str_file_name3) );

int i_index_sheet = 0;
// 循环读取excel中的每一个表单,最后打印输出每个单元格的内容
foreach( QString current_sheetname, xlsx4.sheetNames() )
{
    QXlsx::AbstractSheet* p_current_sheet = xlsx4.sheet( current_sheetname );
    p_current_sheet->workbook()->setActiveSheet(i_index_sheet);
    QXlsx::Worksheet* wsheet = (QXlsx::Worksheet*) p_current_sheet->workbook()->activeSheet();

    int i_max_row = -1;
    int i_max_col = -1;
    QVector<QXlsx::CellLocation> clList = wsheet->getFullCells( &i_max_row, &i_max_col );
    for ( int i_cellindex = 0; i_cellindex < clList.size(); ++i_cellindex )
     {
        QXlsx::CellLocation cell_location = clList.at(i_cellindex);
        QVariant var = cell_location.cell.data()->value();
        QString str = var.toString();
        LOG(INFO)<< std::string( str.toLocal8Bit() );
    }
    i_index_sheet++;
}

2、最后输出的打印信息如下所示

[2019-10-20 21:09:48,039626] [bool JQtAttr::TestXlsx():135] first
[2019-10-20 21:09:48,039675] [bool JQtAttr::TestXlsx():135] second

五、总结

从上面的例子中,可以看出QXlsx::Document是控制整个excel文档的类,它可以控制表单的宽高,控制表单等功能,而QXlsx::Format则是控制单元格格式的类,它可以设置字体、字体大小,对齐方式等。

QXlsx的局限性,它不支持多线程,当表单比较多的时候,加载表单会很耗时。

参考链接:

Handling Microsoft Excel file format

QXlsx

 

设计模式详解十一:Flyweight模式

flyweight意思是轻量级,该模式主要为了解决有些场景会产生许多对象,并且其中的许多对象都是相同的。flyweight提供创建对象接口,并且内部保存对象池,当请求的对象没有在对象池中,那么重新创建对象放入对象池,如果请求的对象存在对象池中,那么直接从对象池取出对象返回给请求者,从而避免对象的频繁创建,造成系统开销的问题。

一、设计模式的结构图

Flyweight模式,FlyweightFactory根据key值提供获取Flyweight对象的接口,

二、程序代码实现

1、基类Flyweight的定义和实现

/// 类的定义
class Flyweight
{
public:
    virtual ~Flyweight();
    virtual void Operation(const std::string& strParam);
    std::string GetState();
    
protected:
    Flyweight(std::string strState);
private:
    std::string m_strState;
};

/// 类的实现
Flyweight::Flyweight(std::string strState)
{
    this->m_strState = strState;
}

Flyweight::~Flyweight()
{
}

void Flyweight::Operation(const std::string& strParam)
{
}
string Flyweight::GetState()
{
    return this->m_strState;
}

2、子类SubFlyweight的定义和实现

/// 类的定义
class SubFlyweight:public Flyweight
{
public:
    SubFlyweight(std::string strState);
    ~SubFlyweight();
    
    void Operation(const std::string& strParam);

protected:
private:
};


/// 类的实现
SubFlyweight::SubFlyweight(std::string strState):Flyweight(strState)
{
    DEBUG_LOG("SubFlyweight Build..... ", strState);
}

SubFlyweight::~SubFlyweight()
{
}

void SubFlyweight::Operation(const string& strParam)
{

}

3、FlyweightFactory工厂类的定义和实现

/// 类的定义
class FlyweightFactory
{
public:
    FlyweightFactory();
    ~FlyweightFactory();
    Flyweight* GetFlyweight(const std::string& key);
    
protected:
private:
    vector<Flyweight*> m_fly;
};


/// 类的实现
FlyweightFactory::FlyweightFactory()
{
}

FlyweightFactory::~FlyweightFactory()
{
}

Flyweight* FlyweightFactory::GetFlyweight(const std::string& key)
{
    vector<Flyweight*>::iterator it = m_fly.begin();
    for (; it != m_fly.end();it++)
    {
        if ((*it)->GetState() == key)
        {
            cout<<"already created by users...."<<endl;
            return *it;
        }
    }
    Flyweight* fw = new SubFlyweight(key);
    m_fly.push_back(fw);
    return fw;
}

三、测试验证结果

1、主程序输入如下所示的测试代码

FlyweightFactory* fc = new FlyweightFactory();
Flyweight* fw1 = fc->GetFlyweight("li");
Flyweight* fw2 = fc->GetFlyweight("zheng!");
Flyweight* fw3 = fc->GetFlyweight("li");

2、运行结果如下所示,第三次请求对象,因为已经存在在对象池中,所以直接从对象池中获取对象并返回

[2019-09-14 19:54:45.160  SubFlyweight:31]  SubFlyweight Build.....  = li
[2019-09-14 19:54:45.160  SubFlyweight:31]  SubFlyweight Build.....  = zheng!
already created by users....

四、总结

FlyweightFactory类采用vector存储对象,当然需要根据具体场景来选择合适的容器,比如,map、hash表等。需要注意的是FlyweightFactory查找的key值是存放在Flyweight对象中的成员变量。

设计模式详解十:外观模式

外观模式,即Facade模式,封装了内部细节,对外提供统一接口。比如做一件事的时候,需要经过几个步骤,但是使用者并不关心内部细节,因此,这时候就可以采用外观模式,将几个步骤封装起来,然后提供一个统一的接口。

一、设计模式的结构图

外观模式对外提供统一接口OperationWrapper, 内部封装两个子系统的接口。

二、程序代码实现

1、定义和实现子系统类1

/// 类的定义
class Subsystem1
{
public:
    Subsystem1();
    ~Subsystem1();
    void Operation();
    
protected:
private:
};


/// 类的实现
Subsystem1::Subsystem1()
{
}

Subsystem1::~Subsystem1()
{
}

void Subsystem1::Operation()
{
    DEBUG_LOG("", "Subsystem1 operation...");
}

2、定义和实现子系统类2

/// 类的定义
class Subsystem2
{
public:
    Subsystem2();
    ~Subsystem2();
    void Operation();

protected:
private:
};


/// 类的实现
Subsystem2::Subsystem2()
{
}

Subsystem2::~Subsystem2()
{
}
void Subsystem2::Operation()
{
    DEBUG_LOG("", "Subsystem2 operation...");
}

3、定义和实现外观模式

/// 类的定义
class Facade
{
public: Facade();
    ~Facade();
    void OperationWrapper();

protected:
private:
    Subsystem1* m_subs1;
    Subsystem2* m_subs2;
};


/// 类的实现
Facade::Facade()
{
    this->m_subs1 = new Subsystem1();
    this->m_subs2 = new Subsystem2();
}

Facade::~Facade()
{
    delete m_subs1;
    delete m_subs2;
}

void Facade::OperationWrapper()
{
    this->m_subs1->Operation();
    this->m_subs2->Operation();
}

从外观模式类看,它负责控制两个子系统类的创建和销毁,对外提供统一接口,内部实现调用两个子系统的函数。

三、测试验证结果

1、主程序输入如下所示的测试代码

Facade* facade = new Facade();
facade->OperationWrapper();

2、运行结果如下所示

[2019-09-08 16:16:18.993  Operation:21]   = Subsystem1 operation...
[2019-09-08 16:16:18.993  Operation:34]   = Subsystem2 operation...

四、总结

外观模式在实际中应用比较广泛,它通常采用单例模式实现,比如采用外观模式封装各个对象的创建过程。

设计模式详解九:迭代器模式

迭代器模式, 即iterator模式,封装集合对象,提供访问集合的接口。

一、设计模式的结构图

迭代器模式的处理逻辑是:iterator迭代器内部保存持有集合对象,用于访问集合,而集合提供创建返回迭代器的函数接口。

二、程序代码实现

1、 定义实现迭代器的基类

#ifndef Object
typedef int Object;
#endif


/// 类定义
class Iterator
{
public:
    virtual ~Iterator();
    virtual void First() = 0;
    virtual void Next() = 0;
    virtual bool IsDone() = 0;
    virtual Object CurrentItem() = 0;
    
protected:
    Iterator();
private:
};


/// 类实现

Iterator::Iterator()
{
    
}

Iterator::~Iterator()
{
    
}

2、 定义实现迭代器的子类

/// 类的定义
class Aggregate;
class ConcreteIterator : public Iterator
{
public:
    ConcreteIterator();
    ConcreteIterator(Aggregate *ag, int idx = 0);
    ~ConcreteIterator();
    void First();
    void Next();
    bool IsDone();
    Object CurrentItem();

protected:
private:
    Aggregate* m_ag;
    int m_idx;
};

/// 类的实现
ConcreteIterator::ConcreteIterator()
{
    
}

ConcreteIterator::ConcreteIterator(Aggregate *ag, int idx)
{
    this->m_ag = ag;
    this->m_idx = idx;
}

ConcreteIterator::~ConcreteIterator()
{
}

Object ConcreteIterator::CurrentItem()
{
    return m_ag->GetItem(m_idx);
}

void ConcreteIterator::First()
{
    m_idx = 0;
}

void ConcreteIterator::Next()
{
    if (m_idx < m_ag->GetSize())
    {
        m_idx++;
    }
}

bool ConcreteIterator::IsDone()
{
    return (m_idx == m_ag->GetSize());
}

3、定义实现集合的基类

#ifndef Object
typedef int Object;
#endif

/// 类的定义
class Iterator;
class Aggregate
{
public:
    virtual ~Aggregate();
    virtual Iterator* CreateIterator() = 0;
    virtual Object GetItem(int idx) = 0;
    virtual int GetSize() = 0;
    
protected:
    Aggregate();
private:
};


/// 类的实现
Aggregate::Aggregate()
{
}

Aggregate::~Aggregate()
{
}

4、定义实现集合的子类

/// 类的定义
class ConcreteAggregate:public Aggregate
{
public:
    enum {SIZE = 3};
    ConcreteAggregate();
    ~ConcreteAggregate();
    Iterator* CreateIterator();
    Object GetItem(int idx);
    int GetSize();
    
protected: private:
    Object m_objs[SIZE];
};


/// 类的实现
ConcreteAggregate::ConcreteAggregate()
{
    for (int i = 0; i < SIZE; i++)
        m_objs[i] = i;
}

ConcreteAggregate::~ConcreteAggregate()
{
}

Iterator* ConcreteAggregate::CreateIterator()
{
    return new ConcreteIterator(this);
}

Object ConcreteAggregate::GetItem(int idx)
{
    if (idx < this->GetSize())
    {
        return m_objs[idx];
    }
    else
    {
        return -1;
    }
}

int ConcreteAggregate::GetSize()
{
    return SIZE;
}

 

三、测试验证结果

1、主程序输入如下所示的测试代码

Aggregate* ag = new ConcreteAggregate();
Iterator* it = new ConcreteIterator(ag);
for (; !(it->IsDone()) ; it->Next())
{
    std::cout<< "item = "<< it->CurrentItem()<<std::endl;
}

2、运行结果如下所示,迭代器访问集合内的所有数据,并输出显示

item = 0
item = 1
item = 2

四、总结

迭代器的实现过程中,为了避免集合过多的暴露公有接口,可以将迭代器声明为集合的友元,来访问集合的私有数据。另外,从上面的代码实现中,可以看出集合提供创建迭代器的接口,而迭代器持有集合对象,内部包装集合的访问接口。

C++11:启动线程的五种方式

程序开发过程中,面对各种各样的需求场景,其中涉及到线程的应用,本文将总结启动线程的五种方式,方便后续复习以及查阅。

  1. 函数指针方式启动线程
  2. 函数对象方式启动线程
  3. Lambda函数的方式启动线程
  4. 类函数指针的方式启动线程
  5. std::bind的方式启动线程

一、函数指针方式

1、 定义实现线程处理函数,然后启动线程调用该处理函数

void ThreadFunction(void)
{
    std::cout << __func__ << ": thread executing "  << std::endl;
}

// 函数指针方式启动线程
void JTestThread::TestStartThread_Method01(void)
{
    std::thread thread_obj(ThreadFunction);
    if (thread_obj.joinable())
    {
        thread_obj.join();
    }
}

2、 运行结果

ThreadFunction: thread executing

二、函数对象方式

1、创建类ThreadOpeartor,然后重载operator()运算符,然后以仿函数的形式作为线程的参数来启动线程

class ThreadOpeartor
{
public:
    void operator()()
    {
        std::cout << __func__ << ":  thread executing "  << std::endl;
    }
    
};

// 函数对象方式启动线程
void JTestThread::TestStartThread_Method02(void)
{
    std::thread thread_obj( (ThreadOpeartor()) ) ;
    if (thread_obj.joinable())
    {
        thread_obj.join();
    }
}

2、运行结果

operator():  thread executing

三、lambda方式

1、lambda函数即匿名函数作为线程的参数来启动线程

// Lambda函数的方式启动线程
void JTestThread::TestStartThread_Method03(void)
{
    std::thread thread_obj( []{
        std::cout << __func__ << ": lambda thread executing" << std::endl;
    } ) ;
    if (thread_obj.joinable())
    {
        thread_obj.join();
    }
}

2、运行结果

operator(): lambda thread executing

四、类函数指针方式

1、定义类成员函数,然后以函数指针作为参数来启动线程

void JTestThread::TestThread04(void)
{
    std::cout << __func__ << ": thread executing" << std::endl;
}

// 类函数指针的方式启动线程
void JTestThread::TestStartThread_Method04(void)
{
    std::thread thread_obj(&JTestThread::TestThread04, this) ;
    if (thread_obj.joinable())
    {
        thread_obj.join();
    }
}

2、运行结果

TestThread04: thread executing

五、std::bind方式

1、类成员函数作为stb::bind的参数, 然后stb::bind作为线程参数来启动线程

void JTestThread::TestThread05(void)
{
    std::cout << __func__ << ": thread executing" << std::endl;
}

// std::bind的方式启动线程
void JTestThread::TestStartThread_Method05(void)
{
    std::thread thread_obj(std::bind(&JTestThread::TestThread05, this)) ;
    if (thread_obj.joinable())
    {
        thread_obj.join();
    }
}

2、运行结果

TestThread05: thread executing

六、总结

  • 函数指针方式:定义普通函数
  • 函数对象方式:重载operator()运算符
  • Lambda函数方式:匿名函数
  • 类函数指针的方式:对象函数指针
  • std::bind的方式: 实际上也是对象函数指针

七、参考链接

如何正确创建线程

怎样通过函数对象创建独立线程

设计模式详解八:Starategy模式

strategy策略模式就是实现和抽象解耦,逻辑处理放到Context中,并在逻辑处理中调用抽象接口,而组合对象实现具体的内容。

一、设计模式的结构图

策略模式context实现主要逻辑,具体实现放到strategy类,strategy是抽象基类,子类继承该基类,实现具体的功能,context与strategy是一对多的组合关系,这样实现的好处就是context可以支持多个strategy类型对象,strategy类型也可以复用到其他不同的逻辑算法。

二、程序代码实现

1、定义实现抽象基类Strategy

class Strategy
{
public:
    Strategy();
    virtual ~Strategy();
    virtual void AlgrithmInterface() = 0;

protected:
private:
};

Strategy::Strategy()
{
}

Strategy::~Strategy()
{
    DEBUG_LOG("", "~Strategy...");
}

void Strategy::AlgrithmInterface()
{
}

2、定义实现基类Strategy的子类ConcreteStrategyA

class ConcreteStrategyA:public Strategy
{
public:
    ConcreteStrategyA();
    virtual ~ConcreteStrategyA();
    void AlgrithmInterface();

protected:
private:
};

ConcreteStrategyA::ConcreteStrategyA()
{
}

ConcreteStrategyA::~ConcreteStrategyA()
{
    DEBUG_LOG("", "~ConcreteStrategyA...");
}

void ConcreteStrategyA::AlgrithmInterface()
{
    DEBUG_LOG("", "ConcreteStrategyA::AlgrithmInterface...");
}

3、定义实现基类Strategy的子类ConcreteStrategyB

class ConcreteStrategyB:public Strategy {
public:
    ConcreteStrategyB();
    virtual ~ConcreteStrategyB();
    void AlgrithmInterface();
    
protected:
private:
};

ConcreteStrategyB::ConcreteStrategyB()
{
}

ConcreteStrategyB::~ConcreteStrategyB()
{
    DEBUG_LOG("", "~ConcreteStrategyB...");
}

void ConcreteStrategyB::AlgrithmInterface()
{
    DEBUG_LOG("", "ConcreteStrategyB::AlgrithmInterface...");
}

4、定义实现Context类

class Context
{
public:
    Context(Strategy* stg);
    ~Context();
    void DoAction();
protected:
private:
    Strategy* m_stg;
};

Context::Context(Strategy* stg)
{
    m_stg = stg;
}

Context::~Context()
{
    if (!m_stg)
        delete m_stg;
}
void Context::DoAction()
{
    m_stg->AlgrithmInterface();
}

三、测试验证结果

1、主程序输入如下所示的测试代码

Strategy* p_strategy;
p_strategy = new ConcreteStrategyA();
Context* p_context = new Context(p_strategy);
p_context->DoAction();
if (NULL != p_context)
{
    delete p_context;
    p_context = NULL;
}

2、运行结果如下图所示,Context对象内部最终调用ConcreteStrategyA对象的算法功能。

[2019-06-30 14:30:24.680  AlgrithmInterface:35]   = ConcreteStrategyA::AlgrithmInterface...

四、总结

策略模式,不变的逻辑算法放到Context对象中实现,而动态变化的算法实现则由Strategy子类来实现,这里有一点需要注意的是Context析构的时候,也会析构Strateby类型的对象,所以,外部定义的Strateby对象不需要再次进行delete。

设计模式详解七:Adapter模式

Adapter模式就是当已经设计好的接口与第三方库的接口不一致的时候,可以起到兼容的作用。Adapter模式一般分为:类模式(继承方式)和对象模式(组合方式)。

一、设计模式的结构图

1、类模式是采用继承的方式来复用Adaptee的接口

2、对象模式是采用组合的方式来调用Adaptee接口

二、程序代码实现

1、定义和实现用户的接口Target

//定义用户接口
class Target
{
public:
    Target();
    virtual ~Target();
    virtual void Request();
    
protected:
private:
};

//实现用户接口
Target::Target()
{
}

Target::~Target()
{
}

void Target::Request()
{
    DEBUG_LOG("", "Target::Request...");
}

2、 定义和实现第三方库接口Adaptee

//第三方
class Adaptee
{
public:
    Adaptee();
    ~Adaptee();
    void SpecificRequest();

protected:
private:
};


Adaptee::Adaptee()
{
}

Adaptee::~Adaptee()
{
}

void Adaptee::SpecificRequest()
{
    DEBUG_LOG("", "Adaptee::SpecificRequest...");
}

3、定义和实现类模式的适配器

//适配器1
class AdapterOne:public Target,private Adaptee
{
public:
    AdapterOne();
    ~AdapterOne();
    void Request();
    
protected:
private:
};


AdapterOne::AdapterOne()
{
}

AdapterOne::~AdapterOne()
{
}

void AdapterOne::Request()
{
    this->SpecificRequest();
}

4、定义和实现对象模式的适配器

//适配器2
class AdapterTwo:public Target
{
public:
    AdapterTwo(Adaptee* ade);
    ~AdapterTwo();
    void Request();

protected:
private:
    Adaptee* _ade;
};


AdapterTwo::AdapterTwo(Adaptee* ade)
{
    this->_ade = ade;
}

AdapterTwo::~AdapterTwo()
{
}

void AdapterTwo::Request()
{
    _ade->SpecificRequest();
}

三、测试验证结果

1、主函数输入如下所示的测试代码段,用来测试对象模式的适配器模式

Adaptee* ade = new Adaptee;
Target* adt = new AdapterTwo(ade);
adt->Request();

2、运行结果如下图所示,调用Request之后,内部实际上调用的是第三方库接口

[2019-06-23 13:51:20.713  SpecificRequest:36]   = Adaptee::SpecificRequest...

四、总结

适配器顾名思义就是一个转接口,将目标对象和第三方连接起来,Target与Adapter之间存在多态的特性,而Adapter内部调用Adaptee完成实际的功能。

设计模式详解六:装饰者模式

Decorator模式,即装饰者模式是通过组合的方式给类添加新的职责。通过组合的好处是不会增加类的继承深度。

一、设计模式的结构图

结构图中,SubComponent和Decorator都继承自同一个基类Component, SubComponet继承自Component基类,这个体现多态的特性,大家应该都明白。而Decorator也继承自Component基类是为什么呢?我的理解是,Decorator是为component添加新的职责,它应该是属于Component,后续才能同SubComponent一样的方式进行使用。Decorator作为SubDecorator的基类,是为了能够提供更多的修饰,比如后续有新的修饰SubDecorator2,那么原来的实现的接口是不需要修改的。

二、程序代码实现

1、定义和实现Component基类

class Component
{
public:
    virtual ~Component();
    virtual void Operation();
protected:
    Component();
private:
};


Component::Component()
{
}

Component::~Component()
{
}

void Component::Operation()
{
}

2、定义和实现SubComponent

class SubComponent:public Component
{
public:
    SubComponent();
    ~SubComponent();
    void Operation();

protected:
private:
};

SubComponent::SubComponent() {}

SubComponent::~SubComponent() {}

void SubComponent::Operation()
{
    DEBUG_LOG("", "SubComponent operation..." );
}

3、定义和实现Decorator类

class Decorator:public Component
{
public:
    Decorator(Component* com);
    virtual ~Decorator();
    void Operation();
    
protected:
    Component* m_com;
private:
};


Decorator::Decorator(Component* com)
{
    this->m_com = com;
}

Decorator::~Decorator()
{
    delete m_com;
}

void Decorator::Operation()  {}

4、定义和实现SubDecorator

class SubDecorator:public Decorator
{
public:
    SubDecorator(Component* com);
    ~SubDecorator();
    void Operation();
    void AddedBehavior();
    
protected:
private:
};


SubDecorator::SubDecorator(Component* com):Decorator(com)  {}

SubDecorator::~SubDecorator()  {}

void SubDecorator::AddedBehavior()
{
    DEBUG_LOG("", "SubDecorator::AddedBehacior...." );
}

void SubDecorator::Operation()
{
    m_com->Operation();
    this->AddedBehavior();
}

三、测试验证结果

程序输入如下所示的代码段,然后运行调试查看结果:

Component* com = new SubComponent();
Decorator* dec = new SubDecorator(com);
dec->Operation();
//com在dec在析构函数中释放
delete dec;

运行结果如下所示,SubDecorator内部调用SubComponent后,并且为SubComponent添加新的行为。

[2019-06-02 16:20:02.135  Operation:29]   = SubComponent operation...
[2019-06-02 16:20:02.135  AddedBehavior:53]   = SubDecorator::AddedBehacior....

四、总结

Decorator模式提供了一种添加职责的方式,采用组合的方式,因此,当需要添加操作的时候,可以考虑Decorator模式来解决。

备注:Decorator与Component之间是整体与部分的关系,一个Decorator可以对应多个Component对象。