利用C++11的function和bind简化类创建线程
问题引出
当在类中需要创建线程时,总是因为线程函数需要定义成静态成员函数,但是又需要访问非静态数据成员这种需求,来做若干重复性的繁琐工作。比如我以前就经常定义一个静态成员函数,然后定一个结构体,结构体形式如下所示,将类指针传入到线程函数中以方便访问费非态成员变量。
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函数里我需要返回这个函数的返回值。欧了,这是最终版本了。
至此再也没有新的版本了- -。。
利用C++11的function和bind简化类创建线程的更多相关文章
- c++11——std::function和bind绑定器
c++11中增加了std::function和std::bind,可更加方便的使用标准库,同时也可方便的进行延时求值. 可调用对象 c++中的可调用对象存在以下几类: (1)函数指针 (2)具有ope ...
- C++ 11 std::function std::bind使用
cocos new 出新的项目之后,仔细阅读代码,才发现了一句3.0区别于2.0的代码: auto closeItem = MenuItemImage::create( "CloseNorm ...
- C++11 中function和bind以及lambda 表达式的用法
关于std::function 的用法: 其实就可以理解成函数指针 1. 保存自由函数 void printA(int a) { cout<<a<<endl; } std:: ...
- C++11 中的function和bind、lambda用法
std::function 1. std::bind绑定一个成员函数 #include <iostream> #include <functional> struct Foo ...
- 使用C++11的function/bind组件封装Thread以及回调函数的使用
之前在http://www.cnblogs.com/inevermore/p/4008572.html中采用面向对象的方式,封装了Posix的线程,那里采用的是虚函数+继承的方式,用户通过重写Thre ...
- 利用function和bind实现回调功能
介绍一种利用function和bind来实现回调的功能. C++参考手册中对function的介绍: std::function的实例能存储.复制及调用任何可调用的目标,包括:函数.lambda表达式 ...
- C++11 学习笔记 std::function和bind绑定器
C++11 学习笔记 std::function和bind绑定器 一.std::function C++中的可调用对象虽然具有比较统一操作形式(除了类成员指针之外,都是后面加括号进行调用),但定义方法 ...
- C++11新特性之九——function、bind以及lamda表达式总结
本文是C++0x系列的第四篇,主要是内容是C++0x中新增的lambda表达式, function对象和bind机制.之所以把这三块放在一起讲,是因为这三块之间有着非常密切的关系,通过对比学习,加深对 ...
- Function.prototype.bind
解析Function.prototype.bind 简介 对于一个给定的函数,创造一个绑定对象的新函数,这个函数和之前的函数功能一样,this值是它的第一个参数,其它参数,作为新的函数的给定参数. b ...
随机推荐
- AsyncTask 解析
[转载自 http://blog.csdn.net/yanbober ] 1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个知识点.前面我们分析了Handl ...
- XML 序列化与PULL解析
简介 Pull解析XML XmlPullParser解析器的运行方式与SAX解析器相似.它提供了类似的事件(开始元素和结束元素),但需要使用parser.next()方法来提取它们.事件将作为数值代码 ...
- 使用Javascript限制文本框只允许输入数字
很多时候需要用到限制文本框的数字输入,试过许多方法,都不太理想,遂决定自己实现一个来玩玩.曾经使用过的方法通过onkeydown事件来控制只允许数字: <input onkeydown=&quo ...
- (转)ThinkPHP系统常量
__ROOT__ : 网站根目录地址 __APP__ : 当前项目(入口文件)地址 __URL__ : 当前模块地址 __ACTION__ : 当前操作地址 __SELF__ : 当前 URL 地址 ...
- Android打开系统的Document文档图片选择
打开Document UI 过滤图片 private void startAcitivty() { Intent intent = new Intent(); intent.setAction(&qu ...
- iOS面试题整理(一)
代码规范 这是一个重点考察项,曾经在微博上发过一个风格纠错题: 也曾在面试时让人当场改过,槽点不少,能够有 10 处以上修改的就基本达到标准了(处女座的人在这方面表现都很优秀 一个区分度很大的面试题 ...
- 了解<hx>标签,为你的网页添加标题
文章的段落用<p>标签,那么文章的标题用什么标签呢?在本节我们将使用<hx>标签来制作文章的标题.标题标签一共有6个,h1.h2.h3.h4.h5.h6分别为一级标题.二级标题 ...
- Delphi ComboBox的属性和事件、及几个鼠标事件的触发
临时做了两个小的测试程序,为了彻底弄清楚combobox的OnClick.OnChange.OnDropDown.OnCloseUp.OnSelect事件的触发及其先后顺序. 另附常用鼠标事件的触发情 ...
- Vim中安装delimitMate,auto-pairs插件不能输入中文问题
在安装了delimitMate插件之后发现不能正常使用中文输入了,输入法系统是ibus. 解决办法是在ibus的设置中的“在应用程序中使用内嵌编辑模式”这一项去除就可以正常输入中文了,看来可能是ibu ...
- js性能优化--学习笔记
<高性能网站建设进阶指南>: 1.使用局部变量,避免深入作用域查找,局部变量是读写速度最快的:把函数中使用次数超过一次的对象属性和数组存储为局部变量是一个好方法:比如for循环中的.len ...