当前位置:C++技术网 > 资讯 > 奶茶同学带你玩函数指针 - 不定参数篇(上)

奶茶同学带你玩函数指针 - 不定参数篇(上)

更新时间:2016-06-15 15:43:57浏览次数:1+次

以下全是作者自己口胡, 持有不同意见之处请当面提出, 

在 C++技术网 首发, 随后会出现在作者本人的 CSDN Blog 上, 禁止转载! 



在奶茶同学正式带你飞之前, 我们先复习下上节课的知识, 这是上节课的提纲


好了, 你肯定有用过, printf, sprintf等函数, 很方便是不是, 参数想写几个就写几个, 这次, 奶茶同学就要带你玩一下这种高大上(自认为)的功能


假设你有这么一个需求, 你要做一个加法函数, 但有时候是2个数相加, 有时候是3个数, 有几个数你都不知道...肿么办. 莫急, 不定参数帮助你


Talk is cheap, show me the code (废话少说, 放码过来) -------- Linus


#include <iostream>
#include <string>
#include <stdarg.h>
using namespace std;

int Add(int nCount, ...)
{
	int nSum = 0;
	va_list ap;
	va_start(ap, nCount);
	for (int i = 0; i < nCount; i++)
	{
		int n = va_arg(ap, int);
		nSum += n;
	}
	va_end(ap);
	return nSum;
}

int main()
{
	int n = Add(5, 1, 2,3,4, 5);
	n = Add(2, 1, 2);
	n = Add(4, 1, 2, 3, 4);
	return n;		 
}


这个怎么玩呢, Add的第一个参数代表了你后面有几个参数, 然后后面的参数就可以放int了. 

PS: 不定参数不检查参数的类型, 可能会有危险, 但如果你类型不要搞错还是非常好用的


你造吗? 不定参数类型不只是基本类型, 也支持自定义类

#include <iostream>
#include <string>
#include <map>
#include <stdarg.h>
using namespace std;

class Cat 
{
public:
	Cat(int, std::string strName)
	{
		m_strName = strName;
	};
	void SetName(string strName)
	{
		m_strName = strName;
	}
	void ShowName()
	{
		printf("喵星人的名字是:%s\n", m_strName.c_str());
	};
protected:
	std::string m_strName;
};

void MakeCat(char* pFmt, ...)
{
	va_list ap;
	va_start(ap, pFmt);
	VA_ARG(pNewName, char*);
	VA_ARG(pCat, Cat*);
	pCat->SetName(pNewName);
}

int main()
{
	Cat cat(10, "Jack");
	cat.ShowName();
	MakeCat("", "Lucy", &cat);
	cat.ShowName();
}



接下来是正片时间

首先, 是头文件

#include <iostream>
#include <string>
#include <map>
#include <stdarg.h>
using namespace std;


然后是函数指针跟map的定义

typedef void (*pFunc)(__out void* , char*);
map<string, pFunc> functionMap;
__out 是表示这个参数将会作为输出参数


void* 是为了兼容各种对象, 变量, 

用map是为了查找快, 二分法嘛, 快得不要不要的. 

以函数名来查找函数指针. 


接下来, 我们定义一个函数路由, 

bool CallFuncByFunctionName(string strName, __out void* pResult, ... )
{
	map<string, pFunc>::iterator iter = functionMap.find(strName);
	if (iter != functionMap.end())
	{
		va_list ap;
		va_start(ap, pResult);
		char *pDataHead = ap;
		iter->second(pResult, pDataHead);
		va_end(ap);
		return true;
	}
	return false;
}


这里解释一下, 

为什么可以用char* 来承受 va_list ap

不定参数取参数的原理就是, 调用不定参数函数时, 他会把参数按类型大小, 连续地压在同一块内存区. 然后ap就是这块内存的首地址, 既然是首地址, 那么用char* 来承受, 就可以了


map的使用不多说, 当找到对应函数名(key)时, 调用其函数指针(value)


为了方便使用, 我们定义了两个宏

#define VA_ARG(value, type) type value = va_arg(ap, type);
#define MakeAP(value) va_list ap = value;


这个宏的作用在上面讲解不定参数的时候已经有说了, 那两句的意义


然后我们写两个响应函数跟其配合的入口函数

int DisplayCatInfo(char* pName, int nAge)
{
	printf("这里有一只名叫[%s], [%d]的喵\n", pName, nAge);
	return 0;
}
void DisplayCatInfoEntrance(__out void* pResult, char* pDataHead)
{
	MakeAP(pDataHead);
	VA_ARG(pName, char*);
	VA_ARG(nAge, int);
	int* nRes = (int*)pResult;
	*nRes = DisplayCatInfo(pName, nAge);
}

void PlayWithCat(char* pAction)
{
	printf("你跟喵在玩[%s], 喵很高兴\n", pAction);
}
void PlayWithCatEntrance(__out void* pResult, char* pDataHead)
{
	MakeAP(pDataHead);
	VA_ARG(pAction, char*);
	PlayWithCat(pAction);
}


语意方面不用多说, 能看到这里的同学们肯定都明白. 


最后是测试函数

int main()
{
	functionMap["DisplayCatInfo"] = DisplayCatInfoEntrance;
	functionMap["PlayWithCat"] = PlayWithCatEntrance;
	int nRes = 1;
	CallFuncByFunctionName("DisplayCatInfo", &nRes, "Jack", 10);
	CallFuncByFunctionName("PlayWithCat", &nRes, "touch");
}



这么做有什么好处吗? 将会在下期结合到 C++ 反射中来执行派生类在不修改基类接口的前提下进行可持续扩展


顺便吐槽下, 虾神, 水印能小点吗?