问题引出

当在类中需要创建线程时,总是因为线程函数需要定义成静态成员函数,但是又需要访问非静态数据成员这种需求,来做若干重复性的繁琐工作。比如我以前就经常定义一个静态成员函数,然后定一个结构体,结构体形式如下所示,将类指针传入到线程函数中以方便访问费非态成员变量。

struct THREAD_PARAMER
{
CTestClass* pThis;
PVOID pContext;
}

解决问题

其实这里不算解决问题吧,应该是用一些其他的方式来减少这种重复性工作。

根据线程函数的要求,除了可以弄成静态成员函数外,其实也可以是全局函数。所以其实不定义静态成员函数也可以在类中创建线程,那重点就是如何把类对象指针、具体执行的函数、需要传递的上下文参数这三个类内部的信息传递到全局的线程函数中呢?

我想到的方法仍然脱离不了封装,因为实际的线程函数只接受一个参数,如果要传递三个过去,必然需要封装出一个新的类型来进行传递。

所以这里要在全局线程中去间接调用类中的成员函数,达到让这个成员函数伪装成线程函数的目的,首先要做两点:

1、封装API函数CreateThread,直接传递类对象指针、成员函数、上下文参数进去就能创建线程,并执行到成员函数中去。

2、对于不同的类,方法要一致,这里就考虑使用模板参数来代替类类型。

有必要在这里先声明一下,下面的内容都是我自己根据当时知识程度一步一步深入的过程,所以如果要找最好的解决方案,可以直接看最后的版本,或者直接去我的github上迁移代码(肯定是我目前最新的)。

第一版:使用模板类作为容器保存参数(VS2010)

有了上面的总结,经过实验写出了如下代码来简化在类中创建线程,首先上测试代码,这部分代码后面不再改变。

#include <iostream>
#include "ThreadInClass.h" class CTest
{
public:
void SayHelloInThread(char* nscName)
{
ThreadInClass::CThreadActuator<CTest>::StartThreadInClass(this, &CTest::ThreadWork, nscName);
}
DWORD ThreadWork(void* p)
{
HANDLE lhThread = GetCurrentThread();
if(NULL != lhThread)
{
DWORD ldwThreadID = GetThreadId(lhThread);
std::cout << "子线程ID: " << ldwThreadID << std::endl;
CloseHandle(lhThread);
} std::cout << "hello, " << (char*)p << std::endl; return ;
}
}; void main()
{
HANDLE lhMainThread = GetCurrentThread();
if(NULL != lhMainThread)
{
DWORD ldwThreadID = GetThreadId(lhMainThread);
std::cout << "主线程ID: " << ldwThreadID << std::endl;
CloseHandle(lhMainThread);
} CTest loTest;
char* lscName = "colin";
loTest.SayHelloInThread(lscName); system("pause");
return;
}

下面是封装的类模板,注意我这里简化了StartThreadInClass函数没有列出CreateThread的可用参数,如果需要的话在这里加上即可。

#include <iostream>
#include<functional>

using std::function;

#include<Windows.h>

namespace ThreadInClass{

// 参数容器模板类,用于存放要调用的类对象、函数、及其参数。(返回值不用存放,因为返回值要作为线程结束状态,所以必须为DWORD)

template<typename tClassName>

class CRealThreadParamer

    {

private:

        typedef std::function<DWORD(tClassName*, PVOID)> RealExcuteFun;

        RealExcuteFun mfExcuteFun;

tClassName* mpoInstance;

        PVOID  mpoContext;

public:

        CRealThreadParamer(tClassName* npThis, RealExcuteFun nfWorkFun,    PVOID npContext){

            mpoInstance= npThis;

            mfExcuteFun= nfWorkFun;

            mpoContext= npContext;

        }

DWORD Run()

{

return mfExcuteFun(mpoInstance, mpoContext);

        }

};

// 线程创建执行类,用于提供创建线程和执行线程的接口封装

template<typename tClassName>

class CThreadActuator

    {

public:

        typedef CRealThreadParamer<tClassName> CThreadParamer;

        typedef std::function<DWORD(tClassName*, PVOID)> RealExcuteFun;

static HANDLE StartThreadInClass(tClassName* npThis, RealExcuteFun nfWorkFun, PVOID npContext)

        {

CThreadParamer* lpoParamer = new CThreadParamer(npThis, nfWorkFun, npContext);

           return CreateThread(nullptr, , CThreadActuator::ThreadDispatch, (PVOID)lpoParamer, , nullptr);

        }

static DWORD WINAPI ThreadDispatch(PVOID npParam)

        {

if(nullptr == npParam)

               return ;

           else

            {

CThreadParamer* lfThreadParamer = (CThreadParamer*)npParam;

                DWORD ldwRet= lfThreadParamer->Run();

                delete lfThreadParamer;

lfThreadParamer= NULL;

               return ldwRet;

            }

}

};

}

我这里用到了std::funciton,而这里的用法有点类似于使用typddef的方式去声明一种函数类型。

执行结果如下:

第二版:使用bind将参数绑定到一个function上(VS2010)

当再次查看这部分代码时,我发现CReadThreadParamer的作用就是一个提供调用形如DWORD (tClassName*, PVOID)函数的接口,并且一旦创建了,它的调用形式也固定了(因为参数都是构造的时候就传递进去了)。

这让我想到了bind,平常使用这个不多,但是知道它可以绑定到一个函数上,并减少或者增加这个函数的参数来调用。既然我这里参数都是固定死了,那是不是可以使用bind先把这些参数全部绑定上去,然后在调用的时候只需调用形如DWORD()的函数就可以了呢?

经过尝试,CReadThreadParamer现在可以优化成这个样子了:

template<typename tClassName>
class CRealThreadParamer
{
private:
typedef std::function<DWORD(tClassName*, PVOID)> RealExcuteFun; typedef std::function<DWORD()> NewRealExcuteFun;
NewRealExcuteFun mfExcuteFun; public:
CRealThreadParamer(tClassName* npThis, RealExcuteFun nfWorkFun, PVOID npContext)
{
mfExcuteFun = std::tr1::bind(nfWorkFun, npThis, npContext);
} DWORD Run()
{
return mfExcuteFun();
}
};

第三版:传递function类型指针作为参数给线程函数(VS2010)

再细细看了下现在的CRealThreadParamer,构造函数里直接把所有的参数绑定到了实际执行的函数上,所以类内部只需要保存一个std::function类型了。

等等,既然只有一个std::function类型了,那我之前增加这个类来保存三个类中的参数还有什么意义,直接传递这么一个类型不就行了吗?

也就是说,应该是可以在StartThreadInClass的实现中就把所有参数绑定成一个函数调用,然后保存到std::function传递给线程函数,线程函数再执行这个函数就行了。

根据上述思路,进一步优化后,代码简化了很多很多了,如下:

#include <iostream>
#include <functional>
using std::function; #include <Windows.h> namespace ThreadInClass{
// 线程创建执行类,用于提供创建线程和执行线程的接口封装
template<typename tClassName>
class CThreadActuator
{
public:
typedef std::function<DWORD(tClassName*, PVOID)> RealExcuteFun;
typedef std::function<DWORD()> NewRealExcuteFun; static HANDLE StartThreadInClass(tClassName* npThis, RealExcuteFun nfWorkFun, PVOID npContext)
{
NewRealExcuteFun* lpoParamer = new NewRealExcuteFun(std::tr1::bind(nfWorkFun, npThis, npContext));
return CreateThread(nullptr, , CThreadActuator::ThreadDispatch, (PVOID)lpoParamer, , nullptr);
} static DWORD WINAPI ThreadDispatch(PVOID npParam)
{
if(nullptr == npParam)
return ;
else
{
NewRealExcuteFun* lfThreadParamer = (NewRealExcuteFun*)npParam;
DWORD ldwRet = (*lfThreadParamer)();
delete lfThreadParamer;
lfThreadParamer = NULL;
return ldwRet;
}
}
};
}

第四版:使用变长模板参数解决参数类型单一的缺陷(VS2013)

到了第三版,我再没有想到还可以简化的方式了,不过到是发现了,如果在使用的时候,我需要传入的上下文内容比较多,还是需要自己构造一个结构体来存放上下文信息。因为类中用来做线程函数(间接的)的形式是固定为DWORD(PVOID)类型的。

那么有没有一种方式可以让这个函数可以有任意多个不同类型的参数呢?其实是有的,那就是使用C++11的类可变参模板。

在更改代码之前,先测试一下直接使用tuple类型作为上下文参数传递,因为它可以存放很多不同类型的数据到 一个变量中,从某种程度上也是可以满足多个上下文参数的。

测试代码如下:

#include <iostream>
#include "ThreadInClass.h"
#include <tuple>
using std::tr1::tuple; class CTest
{
public:
void SayHelloInThread(char* nscName)
{
ThreadInClass::CThreadActuator<CTest>::StartThreadInClass(this, &CTest::DoSayHelloInThread, nscName);
}
DWORD DoSayHelloInThread(void* p)
{
HANDLE lhThread = GetCurrentThread();
if(NULL != lhThread)
{
DWORD ldwThreadID = GetThreadId(lhThread);
std::cout << "DoSayHelloInThread线程ID: " << ldwThreadID << std::endl;
CloseHandle(lhThread);
} std::cout << "hello, " << (char*)p << std::endl;
return ;
} void PrintSumInThread(tuple<int, int>& roAddTupleInfo)
{
ThreadInClass::CThreadActuator<CTest>::StartThreadInClass(this, &CTest::DoPrintSumInThread, &roAddTupleInfo);
} DWORD DoPrintSumInThread(void* p)
{
HANDLE lhThread = GetCurrentThread();
if(NULL != lhThread)
{
DWORD ldwThreadID = GetThreadId(lhThread);
std::cout << "DoPrintSumInThread线程ID: " << ldwThreadID << std::endl;
CloseHandle(lhThread);
}
std::tr1::tuple<int, int>* lpoT1 = (std::tr1::tuple<int, int>*)p;
int i = std::tr1::get<>(*lpoT1);
int j = std::tr1::get<>((*lpoT1)); std::cout << i << " + " << j << " = " << i + j << std::endl;
return ;
}
}; void main()
{
HANDLE lhMainThread = GetCurrentThread();
if(NULL != lhMainThread)
{
DWORD ldwThreadID = GetThreadId(lhMainThread);
std::cout << "主线程ID: " << ldwThreadID << std::endl;
CloseHandle(lhMainThread);
} CTest loTest;
char* lscName = "colin";
loTest.SayHelloInThread(lscName); tuple<int, int> t1(, );
loTest.PrintSumInThread(t1); system("pause");
return;
}

运行结果:

经实验是可以的。不过相对于使用变参模板而言,这种方式需要使用者自己定义出一个tuple,来存放所有要传递的数据,还是不如直接传递来的直观。

接下来更改代码使用变参模板。

注意:截止到上面测试使用tuple,我一直使用的是VS2010版本。但是当我使用变长参数模板时,发现编译不过去,看错误提示似乎是还不支持,所以下面我更换到了VS2013,但是VS2013上要将类成员函数赋值给std::function类型时,必须使用bind才行,所以传递参数时要注意。

#include <iostream>
#include <functional>
using std::tr1::function;
using std::tr1::bind; #include <Windows.h> namespace ThreadInClass{
template<typename tClassName, typename... ArgsType> // 变参模板
class CThreadActuator
{
public:
typedef function<DWORD()> NewRealExcuteFun; /// 使用变参模板
static HANDLE StartThreadInClass(tClassName* npThis, function<DWORD(tClassName*, ArgsType...)> nfWorkFun, ArgsType... npArgs)
{
NewRealExcuteFun* lpoParamer = new NewRealExcuteFun(bind(nfWorkFun, npThis, npArgs...));
return CreateThread(nullptr, , CThreadActuator::ThreadDispatch, (PVOID)lpoParamer, , nullptr);
} // 真正的线程函数,间接调用类成员函数
static DWORD WINAPI ThreadDispatch(PVOID npParam)
{
if (nullptr == npParam)
return ;
else
{
NewRealExcuteFun* lfThreadParamer = (NewRealExcuteFun*)npParam;
DWORD ldwRet = (*lfThreadParamer)();
delete lfThreadParamer;
lfThreadParamer = NULL;
return ldwRet;
}
}
};
}

附上测试代码:

#include <iostream>
using std::cout;
using std::endl; #include "ThreadInClass.h" #include <tuple>
using std::tr1::tuple;
using std::tr1::get; class CTest
{
public:
void SayHelloInThread(char* nscName)
{
ThreadInClass::CThreadActuator<CTest, char* >::StartThreadInClass(this, bind(&CTest::DoSayHelloInThread,
std::tr1::placeholders::_1, std::tr1::placeholders::_2), nscName);
}
DWORD DoSayHelloInThread(void* p)
{
HANDLE lhThread = GetCurrentThread();
if(NULL != lhThread)
{
DWORD ldwThreadID = GetThreadId(lhThread);
cout << "DoSayHelloInThread线程ID: " << ldwThreadID << endl;
CloseHandle(lhThread);
} cout << "hello, " << (char*)p << endl;
return ;
}
    void PrintSumInThread(tuple<int, int>& roAddTupleInfo)
{
ThreadInClass::CThreadActuator<CTest, tuple<int, int>& >::StartThreadInClass(this, bind(&CTest::DoPrintSumInThread,
std::tr1::placeholders::_1, std::tr1::placeholders::_2), roAddTupleInfo);
} DWORD DoPrintSumInThread(tuple<int, int>& roAddTupleInfo)
{
HANDLE lhThread = GetCurrentThread();
if(NULL != lhThread)
{
DWORD ldwThreadID = GetThreadId(lhThread);
cout << "DoPrintSumInThread线程ID: " << ldwThreadID << endl;
CloseHandle(lhThread);
}
int i = get<>(roAddTupleInfo);
int j = get<>(roAddTupleInfo); cout << i << " + " << j << " = " << i + j << endl;
return ;
} void PrintSumInThread2(int &i, int &j)
{
ThreadInClass::CThreadActuator<CTest, int, int >::StartThreadInClass(this, bind(&CTest::DoPrintSumInThread2,
std::tr1::placeholders::_1, std::tr1::placeholders::_2, std::tr1::placeholders::_3), i, j);
} DWORD DoPrintSumInThread2(int i, int j)
{
HANDLE lhThread = GetCurrentThread();
if (NULL != lhThread)
{
DWORD ldwThreadID = GetThreadId(lhThread);
std::cout << "DoPrintSumInThread2线程ID: " << ldwThreadID << std::endl;
CloseHandle(lhThread);
}
std::cout << i << " + " << j << " = " << i + j << std::endl;
return ;
} }; void main()
{
HANDLE lhMainThread = GetCurrentThread();
if(NULL != lhMainThread)
{
DWORD ldwThreadID = GetThreadId(lhMainThread);
cout << "主线程ID: " << ldwThreadID << endl;
CloseHandle(lhMainThread);
} CTest loTest;
char* lscName = "colin";
loTest.SayHelloInThread(lscName); tuple<int, int> t1(, );
loTest.PrintSumInThread(t1); int i = ;
int j = ;
loTest.PrintSumInThread2(i, j); system("pause");
return;
}

执行结果如下:

第五版:直接传递绑定好所有参数的function(VS2013)

上面有提到,VS2013要将类成员函数赋值给function类型,必须使用bind。

所以实际调用的时候传递参数时,是将成员函数通过bind(&CTest::DoPrintSumInThread2, std::tr1::placeholders::_1, std::tr1::placeholders::_2, std::tr1::placeholders::_3), 这么传递的。其中参数占位符根据成员函数实际的参数个数来定。而参数在这里创建线程的时候也是固定了的,既然如此,我干嘛还用占位符呢?直接传递bind(&CTest::DoPrintSumInThread2, this, i, j)不就行了吗?

经测试上述方案是可行的,仔细想了下,在CreateThread的时候我们需要传递一个参数给线程函数,这个参数类型我们定义成了function类型,而上面这种方式其实也是function啊,并且对于任何已经知道要传递的参数值的成员函数,都可以通过bind,返回function< DWORD() >类型。这就意味着在线程函数里,我根本不需要知道其他信息,只需要执行这个function代表的函数就可以了啊。

茅塞顿开,有了如下代码:

#include <iostream>
#include <functional>
using std::tr1::function;
using std::tr1::bind; #include <Windows.h>
namespace ThreadInClass{
class CThreadActuator
{
public:
typedef function<DWORD()> NewRealExcuteFun; // 使用变参模板
static HANDLE StartThreadInClass(function<DWORD()> nfWorkFun)
{
NewRealExcuteFun* lpoParamer = new NewRealExcuteFun(nfWorkFun);
return CreateThread(nullptr, , CThreadActuator::ThreadDispatch, (PVOID)lpoParamer, , nullptr);
} // 真正的线程函数,间接调用类成员函数
static DWORD WINAPI ThreadDispatch(PVOID npParam)
{
if (nullptr == npParam)
return ;
else
{
NewRealExcuteFun* lpfWorkFun = (NewRealExcuteFun*)npParam;
DWORD ldwRet = (*lpfWorkFun)();
delete lpfWorkFun;
lpfWorkFun = NULL;
return ldwRet;
}
}
};
}
#include <iostream>
using std::cout;
using std::endl;
#include "ThreadInClass.h" class CTest
{
public:
void PrintSumInThread(int &i, int &j)
{
ThreadInClass::CThreadActuator::StartThreadInClass(bind(&CTest::DoPrintSumInThread, this, i, j));
} DWORD DoPrintSumInThread(int i, int j)
{
HANDLE lhThread = GetCurrentThread();
if (NULL != lhThread)
{
DWORD ldwThreadID = GetThreadId(lhThread);
std::cout << "DoPrintSumInThread2线程ID: " << ldwThreadID << std::endl;
CloseHandle(lhThread);
}
std::cout << i << " + " << j << " = " << i + j << std::endl;
return ;
} }; void main()
{
HANDLE lhMainThread = GetCurrentThread();
if(NULL != lhMainThread)
{
DWORD ldwThreadID = GetThreadId(lhMainThread);
cout << "主线程ID: " << ldwThreadID << endl;
CloseHandle(lhMainThread);
} CTest loTest;
int i = ;
int j = ;
loTest.PrintSumInThread(i, j); system("pause");
return;
}

呀,不仅去掉了模板,代码也简洁了好多倍。

第六版

是的,没错还有第6版本。那就是使用c++11的std::thread,使用方式就不多说了,我也是看的别人的介绍。跟我前面介绍的方式差不多,不过额外增加了很多功能就是了。不过也不是说我前面写的都没用,很大程度上thread内部用的方式其实就是差不多的。

然后对比下我的第四版,第四版中是将类类型和变参参数作为类的模板了。而实际上我那个类里面除了StartThreadInClass中使用了这两个模板参数,其他地方都没有使用。所以其实是可以直接定义成这个函数的模板参数的,然后配合第五版的直接绑定所有参数的方式:

#include <iostream>
#include <functional>
using std::tr1::function;
using std::tr1::bind; #include <Windows.h>
namespace ThreadInClass{
class CThreadActuator
{
public:
typedef function<DWORD()> NewRealExcuteFun; // 使用变参模板
template <typename _Fn, typename... _Args>
static HANDLE StartThreadInClass(_Fn nfWorkFun, _Args... args)
{
NewRealExcuteFun* lpfWorkFun = new NewRealExcuteFun(bind(nfWorkFun, args...));
return CreateThread(nullptr, , CThreadActuator::ThreadDispatch, (PVOID)lpfWorkFun, , nullptr);
} // 真正的线程函数,间接调用类成员函数
static DWORD WINAPI ThreadDispatch(PVOID npParam)
{
if (nullptr == npParam)
return ;
else
{
NewRealExcuteFun* lpfWorkFun = (NewRealExcuteFun*)npParam;
DWORD ldwRet = (*lpfWorkFun)();
delete lpfWorkFun;
lpfWorkFun = NULL;
return ldwRet;
}
}
};
}

然后发现这种方式似乎连this指针都不需要传入了,也就是说对于除了类成员函数做线程函数的情况,其他普通函数也可以直接使用了。

调用方式为:

ThreadInClass::CThreadActuator::StartThreadInClass(&AddInThread, i, j);

与std::thread的区别就是,我这里函数的返回类型必须是DWORD,因为在ThreadDispatch函数里我需要返回这个函数的返回值。欧了,这是最终版本了。

至此再也没有新的版本了- -。。

最后附上代码链接:https://github.com/monotone/ThreadInClass.git

利用C++11的function和bind简化类创建线程的更多相关文章

  1. c++11——std::function和bind绑定器

    c++11中增加了std::function和std::bind,可更加方便的使用标准库,同时也可方便的进行延时求值. 可调用对象 c++中的可调用对象存在以下几类: (1)函数指针 (2)具有ope ...

  2. C++ 11 std::function std::bind使用

    cocos new 出新的项目之后,仔细阅读代码,才发现了一句3.0区别于2.0的代码: auto closeItem = MenuItemImage::create( "CloseNorm ...

  3. C++11 中function和bind以及lambda 表达式的用法

    关于std::function 的用法:  其实就可以理解成函数指针 1. 保存自由函数 void printA(int a) { cout<<a<<endl; } std:: ...

  4. C++11 中的function和bind、lambda用法

    std::function 1. std::bind绑定一个成员函数 #include <iostream> #include <functional> struct Foo ...

  5. 使用C++11的function/bind组件封装Thread以及回调函数的使用

    之前在http://www.cnblogs.com/inevermore/p/4008572.html中采用面向对象的方式,封装了Posix的线程,那里采用的是虚函数+继承的方式,用户通过重写Thre ...

  6. 利用function和bind实现回调功能

    介绍一种利用function和bind来实现回调的功能. C++参考手册中对function的介绍: std::function的实例能存储.复制及调用任何可调用的目标,包括:函数.lambda表达式 ...

  7. C++11 学习笔记 std::function和bind绑定器

    C++11 学习笔记 std::function和bind绑定器 一.std::function C++中的可调用对象虽然具有比较统一操作形式(除了类成员指针之外,都是后面加括号进行调用),但定义方法 ...

  8. C++11新特性之九——function、bind以及lamda表达式总结

    本文是C++0x系列的第四篇,主要是内容是C++0x中新增的lambda表达式, function对象和bind机制.之所以把这三块放在一起讲,是因为这三块之间有着非常密切的关系,通过对比学习,加深对 ...

  9. Function.prototype.bind

    解析Function.prototype.bind 简介 对于一个给定的函数,创造一个绑定对象的新函数,这个函数和之前的函数功能一样,this值是它的第一个参数,其它参数,作为新的函数的给定参数. b ...

随机推荐

  1. [转] Linux中gcc,g++常用编译选项

    http://blog.sina.com.cn/s/blog_5ff2a8a201011ro8.html gcc/g++ 在执行编译时,需要4步 1.预处理,生成.i的文件[使用-E参数] 2.将预处 ...

  2. discuz, 使用同一数据库, 只是换个环境, 数据就不一样了

    如题, 本以为是由于某些冲突导致, 细查之后, 发现是开了缓存了, 把缓存关掉或是在后台清理缓存就OK了 后台清理缓存, 全局--性能优化--内存优化  清理缓存 关闭缓存, 修改全局配置文件, co ...

  3. Ubuntu下全命令行安装Android SDK

    为了在AWS云服务器上实现自动化打包Android APP的APK包,我需要远程命令行环境下安装Android SDK,当然还要用代理或者科学上网,这里简单整理一下过程: 首先,由于墙的原因,Andr ...

  4. Java编程思想-泛型-简单泛型例子

    基本类型无法做为类型参数 代码如下: /** * */ package test.thinkinjava.Generics; import java.util.ArrayList; import ja ...

  5. 编写高效SQL最佳实践

    编写高效 SQL 语句的最佳实践 秦玮, 高级软件工程师, IBM 王广成, 软件工程师, IBM 王韵婷, 高级软件工程师, IBM 简介: 本文列举了一些在编写 SQL 查询语句时可能导致 DB2 ...

  6. Spring+SpringMVC+Mybatis+MAVEN+Eclipse+项目完整环境搭建

    1.新建一个Maven项目,创建父项目. 2.创建子项目模块 3.创建javaWeb项目 4.创建后的项目目录结构 5.Maven文件配置 parent父项目pom.xml文件配置 <?xml ...

  7. FLEX 网格布局及响应式处理

    上一篇文章用Flex实现BorderLayout,这一章我们来实现常用的网格布局和响应式处理. 首先我们定义HTML结构,主Box为grid,每项为grid-cell,下面就是我们HTML代码结构. ...

  8. 10个重要的Linux ps命令实战

    Linux作为Unix的衍生操作系统,Linux内建有查看当前进程的工具ps.这个工具能在命令行中使用. PS 命令是什么 查看它的man手册可以看到,ps命令能够给出当前系统中进程的快照.它能捕获系 ...

  9. matplotlib入门--1(条形图, 直方图, 盒须图, 饼图)

    作图首先要进行数据的输入,matplotlib包只提供作图相关功能,本身并没有数据读入.输出函数,针对各种试验或统计文本数据输入可以使用numpy提供的数据输入函数. # -*- coding: gb ...

  10. CodeForces 569A 第六周比赛C踢

    C - C Time Limit:2000MS     Memory Limit:262144KB     64bit IO Format:%I64d & %I64u Submit Statu ...