变参函数的使用方法一、标记加偏移地址法

     基础知识:其实参数传入函数的时候,其实实际传入的是一个堆中连续内存区的首地址(函数在栈中),里面以传入的顺序放入了用户传递的参数。

     而C语言有一种叫“变参函数”的特性方便用户传入不定数量的参数以方便用户进行某些如“字符串拼接”函数等功能的编写。
     其中要注意内存对齐的问题,例子中的currentParams是指向参数内存区地址的指针,所以读取的时候要注意做好指针类型转换,转换完之后以便实现内存对齐后的正确取值。先把void*指针转化为一个确定类型的指针如doublue*,再在前面加*即可取得对应内容,如*((double*)currentParams)。

      而有些地方取出的整数其实是一个地址,例如字符串传入的时候其实传入的是字符数组连续内存区的首地址,所以得到这个地址之后,例如得到了*(int*)currentParams这个整数,已知这个整数是传入的一个字符数组的连续内存区的首地址,我想通过printf输出,因此需要把这个整数代表的地址转换为一个char*指针——(char*)(*(int*)currentParams)才可以被printf顺利识别为一个字符串。其实指针保存的都是内存地址这种整数,不同类型的指针都是这样,只是有了类型声明,方便程序读内容时对齐内存,以免如4个字节的int类数据被读了一个字节出来,也避免了4个字节一个单元的int数组被以8个字节一组的方式(如double类型、struct{int a, int b}型)读错。
 
     以下是读取变参函数变量的一个例子,这套代码可以稍加修改加上一些汇编代码,实现如输出内容到串口设备等的自定义printf函数。也可以说printf函数就是一种最常用的变参函数之一:
     
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

void testMultiParams(char *paramsFormat, ...){
	int paramsLength = 0, i, j = 0;
	/*不能拿paramsFormat加偏移地址拿参数,paramsFormat存的地址是那堆字符的连续内存区的首地址,不是参数的首地址
	  所以要拿指向这个连续内存区的首地址的数据存储区的地址(向“根”寻址——找到指向这个地址的地址)*/ 
	void* firstParamsAddress = (void*) (&paramsFormat + sizeof(char)); 
	void* currentParams = firstParamsAddress;
	for(i = 0; i < strlen(paramsFormat); i++){
		if(paramsFormat[i] == '%') 
			paramsLength++;
	}
	printf("参数长度%d\n", paramsLength); 
	for(i = 1; i <= strlen(paramsFormat); i++){
		if(paramsFormat[i - 1] == '%') {
			switch(paramsFormat[i]){
				case 'd':
					printf("第%d参数内容是:%d\n", ++j, *((int*)currentParams));
					currentParams+=sizeof(int);
					break;
				case 'f':
					printf("第%d参数内容是:%f\n", ++j, *((double*)currentParams));
					currentParams+=sizeof(double);
					break;
				case 'c':
					printf("第%d参数内容是:%c\n", ++j, ((char*)currentParams)[0]);
					currentParams+=sizeof(char*);
					break;
				case 's':
					/*字符串形参数是保存着指向一个“字符数组首地址”这个整数的地址,取得该地址,
					  并取得地址中保存了的内容——“字符数组首地址”这个地址整数,然后转为字符串地址 
					 (向“叶”寻址——地址的地址)*/ 
					printf("第%d参数内容是:%s\n", ++j, (char*)(*(int*)currentParams)); 
					currentParams+=sizeof(char*);
					break;
				case 'l':
					if(paramsFormat[i+1] == 'd'){
						printf("第%d参数内容是:%ld\n", ++j, *(long*)currentParams);
						currentParams+=sizeof(long);
						i++;
					}
					break;
				default:
					printf("第%d参数类型标记错误,正在跳过...\n", j+1);
					while(paramsFormat[++i] != '%');
					currentParams+=sizeof(void*);
					++j;
					break;
			}
		}	
	}	
}

int main(){
	testMultiParams("%d", 123);
	testMultiParams("%d%d", 123, 345);
	testMultiParams("%d%s%d", 123, "asdasdasd", 678);
	testMultiParams("%d%s%d%ld%s", 123, "变参函数测试", 678, 678999999L, "模仿Java多态");
	testMultiParams("%d%s%d%yyy%s", 3, "错误情况测试", 678, 678999999L, "测试");
	printf("%.2f\n", 3.1415f);
	testMultiParams("%d%s%c%f%ld%s", 3, "测试", 'b', 3.1415f, 678999999L, "测试");
	getchar(); 
	return 0;
}



 



进阶用法,字符串拼接例子
#include "stdio.h"
#include "stdlib.h"
#include "string.h"

/**数组长度判断宏**/ 
#define ARR_LENGTH(arr)    sizeof(arr)/sizeof(arr[0])    

char* stringAllPlus(int paramsCount, ...){
	void* firstParamsAddress = (void*) (¶msCount + 1); 
	void* currentParams = firstParamsAddress;
	char* totalStr = NULL;
	char* cursorPointer = NULL;
	int i, j, totalStrLength = 0, offset = 0;
	/*统计传入的字符串总长度*/ 
	for(i = 0; i < paramsCount; i++){	
		totalStrLength += strlen((char*)(*(int*)currentParams));
		currentParams+=sizeof(char*);	
	}
	/*恢复游标到连续地址空间的头地址*/ 
	currentParams = firstParamsAddress;
	/*printf("Total String length:%d\n", totalStrLength);*/
	/*拼接成一个字符串*/ 
	if(totalStrLength <= 0) 
		return NULL;
	totalStr = cursorPointer = (char*) malloc(sizeof(char) * totalStrLength + 1);
	memset((void*)cursorPointer, 0, totalStrLength);
	for(i = 0; i < paramsCount; i++){	
		memcpy((void*)cursorPointer, (void*)(*(int*)currentParams), strlen((char*)(*(int*)currentParams)));	
		cursorPointer+=strlen((char*)(*(int*)currentParams));
		currentParams+=sizeof(char*);	
	}
	totalStr[totalStrLength] = '\0';
	return totalStr;
}

void stringAllPlus2(int paramsCount, ...){
	char* firstParamsAddress = (char*) (¶msCount + 1);
 	printf("%s\n", ((int*)firstParamsAddress)[0]);
 	printf("%s\n", ((int*)firstParamsAddress)[1]);
} 

char* stringAllPlus3(int paramsCount, ...){
	int* paramsAddress = (int*) (¶msCount + 1);
	char* cursorPointer = NULL;
	char* totalStr = NULL;
	int i, totalStrLength = 0;
	/*统计传入的字符串总长度*/ 
	for(i = 0; i < paramsCount; i++)
		totalStrLength += strlen((char*) paramsAddress[i]);	
	/*拼接成一个字符串*/ 
	if(totalStrLength <= 0) 
		return NULL;
	totalStr = malloc(totalStrLength + 1); 
	cursorPointer = totalStr; 
	memset(cursorPointer, 0, totalStrLength);
	for(i = 0; i < paramsCount; i++){	
		memcpy((void*) cursorPointer, (void*) paramsAddress[i], sizeof((char*) paramsAddress[i]));	
		cursorPointer += strlen((char*) paramsAddress[i]);
	}
	totalStr[totalStrLength] = '\0';
	return totalStr;
} 

void test(int a, ...){
	int* arr = &a;
	void* arr2 = (void*)&a;
	printf("%d,%d,%d\n", *(int*)(&a), *(int*)(&a + 1), *(int*)(&a + 2));
	printf("%d\n", ((int*)&a)[0]);
	printf("%d\n", ((int*)&a)[1]);
	printf("%d\n", ((int*)&a)[2]);
	printf("%d\n", arr[0]);
	printf("%d\n", arr[1]);
	printf("%d\n", arr[2]);
	printf("%d\n", ((int*)arr2)[0]);
	printf("%d\n", ((int*)arr2)[1]);
	printf("%c\n", ((char*)arr2)[2]); //错误用法,内存对齐错误 
}

int main(){
	char* temp = stringAllPlus3(3, "abc", "sdfsdfsdf\n", "fghhhhhhhhh");
	char* totalStr[] = {stringAllPlus3(3, "abc", "哈哈哈", "fgh"), stringAllPlus3(2, "you ", "suck")};	
	char* temp2 = "";
	int i;
	printf("total str:%s\n", temp);
	printf("total str:%s\n", totalStr[0]);
	printf("total str:%s\n", totalStr[1]);
	for(i = 'a'; i <= 'z'; i++){
		temp2 = stringAllPlus(2, temp2, &i);
	}
	printf("%s\n", temp2);
	test(33, 55, 97);
	stringAllPlus2(2, "you ", "suck");
	return 0;
}




猜你喜欢

转载自blog.csdn.net/cjzjolly/article/details/78602760