用C编写泛型函数,如何处理字符串
我有一个函数,它接受一个用C编写泛型函数,如何处理字符串,c,C,我有一个函数,它接受一个void**参数和一个指示其数据类型的整数 void foo (void** values, int datatype) 在函数内部,根据数据类型,我以以下方式对其进行malloc: if (datatype == 1) *values = (int*) malloc (5 * sizeof(int)); else if (datatype == 2) *values = (float*) malloc (5 * sizeof(float)); 到目前
void**
参数和一个指示其数据类型的整数
void foo (void** values, int datatype)
在函数内部,根据数据类型,我以以下方式对其进行malloc:
if (datatype == 1)
*values = (int*) malloc (5 * sizeof(int));
else if (datatype == 2)
*values = (float*) malloc (5 * sizeof(float));
到目前为止一切都很好。然而,当字符串出现在图片中时,事情就变得复杂了。void**
必须是void***
,因为我需要这样做:
*values = (char**) malloc (5 * sizeof(char*));
for(i=0;i<5;i++)
(*values)[i] = (char*) malloc (10);
..
strncpy( (*values)[0], "hello", 5);
所以我只是把void**
转换成char***
。我试过了,忽略了警告,效果很好。
但这安全吗?还有更优雅的选择吗
这种情况应如何处理?我是否可以将char***
传递给期望使用void**
但在其内部正确强制转换的函数
void foo (void** values, int datatype) {
if(datatype == 3) {
char*** tmp_vals = (char***) values;
*tmp_vals = (char**) malloc (5 * sizeof(char*));
...
(*tmp_vals)[i] = (char*) malloc (10 * sizeof(char));
strncpy ( (*tmp_vals)[i], "hello", 5);
}
不,这是技术上未定义的行为。它可能在您的计算机上运行,但在将来的某些计算机上可能会失败,这些计算机使用不同的表示实现了不同的指针类型,这是C语言标准所允许的
如果函数需要一个void**
,那么最好传递一个void**
。任何指针类型都可以隐式转换为void*
,但这只能在顶层工作:char*
可以转换为void*
,而char**
可以隐式转换为void*
(因为char**
是“指向char*
的指针”),但是char**
不能转换为void**
,同样char***
也不能转换为void**
调用此函数的正确方法是向其传递正确的void**
,然后将生成的void*
指针转换回其原始类型:
void foo(void **values, int datatype)
{
if(datatype == 3)
{
char ***str_values = ...;
*values = str_values; // Implicit cast from char*** to void*
}
else
...
}
...
void *values;
foo(&values, 2);
char ***real_values = (char ***)values;
假设*值
实际上指向了字符***
,那么此强制转换是有效的,并且在任何代码路径中都没有任何未定义的行为。您根本不需要(也可能不应该)使用void**
,只需使用常规的void*
。根据C11 6.3.2.3.1,“指向void
的指针可以转换为指向任何对象类型的指针,也可以转换为指向任何对象类型的指针。指向任何对象类型的指针可以转换为指向void
的指针,然后再转换回来;结果应与原始指针相同。”指针变量(包括指向另一个指针)是一个对象void**
不是“指向void
的指针”。您可以自由、安全地在void*
之间进行转换,但不能保证您能够安全地在void**
之间进行转换
所以你可以做:
void foo (void* values, int datatype) {
if ( datatype == 1 ) {
int ** pnvalues = values;
*pnvalues = malloc(5 * sizeof int);
/* Rest of function */
}
依此类推,然后将其称为类似:
int * new_int_array;
foo(&new_int_array, 1);
&new\u int\u array
属于int**
类型,它将通过foo()
隐式转换为void*
,而foo()
将其转换回类型int**
,并取消引用以间接修改new\u int\u array
,以指向它动态分配的新内存
对于指向动态字符串数组的指针:
void foo (void* values, int datatype) {
/* Deal with previous datatypes */
} else if ( datatype == 3 ) {
char *** psvalues = values;
*psvalues = malloc(5 * sizeof char *);
*psvalues[0] = malloc(5);
/* Rest of function */
}
以此类推,称之为:
char ** new_string_array;
foo(&new_string_array, 3);
类似地,
&new\u string\u array
类型为char***
,再次隐式转换为void*
,并且foo()
将其转换回,并间接使new\u string\u array
指向新分配的内存块 Avoid*
只是指向未指定类型的指针;它可以是指向int
、或char
、或char*
、或char**
、或任何您想要的对象的指针,只要您确保在解除引用时将其视为适当的类型(或原始类型可以安全地解释为的类型)
因此,void**
只是指向void*
的指针,它可以是指向任何类型的指针,例如char*
。因此,是的,如果您正在分配某些类型的对象的数组,并且在一种情况下,这些对象是char*
,那么您可以使用void**
来引用它们,这样您就可以将其称为char***
直接看到这种结构通常是不常见的,因为通常您会将一些类型或长度信息附加到数组中,而不是使用char***
您有一个struct-typed\u对象**foo
或类似的struct-typed\u对象
有一个类型标记和指针,然后将从这些元素中提取的指针强制转换为适当的类型,或者有一个struct-typed_-array*foo
,它是一个包含类型和数组的结构
关于风格的几个注释。首先,做这种事情会使你的代码难以阅读。要非常小心地组织它,并清楚地记录它,以便人们(包括你自己)能够了解发生了什么。另外,不要强制转换malloc的结果;void*
自动升级到其指定的类型,如果忘记包含
或更新类型声明但忘记更新强制转换,则强制转换malloc
的结果可能会导致细微的错误。有关更多信息,请参阅
通常,将声明中的*
附加到变量名,而不是类型名是一个好习惯,因为它实际上是这样解析的。下面声明了一个char
和一个char*
,但是如果您按照编写它们的方式编写,您可能希望它声明两个char*
:
char *foo, bar;
或者用另一种方式写:
char* foo, bar;
通过一些技巧,你可以做到。见示例:
int sizes[] = { 0, sizeof(int), sizeof(float), sizeof(char *) }
void *foo(datatype) {
void *rc = (void*)malloc(5 * sizes[datatype]);
switch(datatype) {
case 1: {
int *p_int = (int*)rc;
for(int i = 0; i < 5; i++)
p_int[i] = 1;
} break;
case 3: {
char **p_ch = (char**)rc;
for(int i = 0; i < 5; i++)
p_ch[i] = strdup("hello");
} break;
} // switch
return rc;
} // foo
int size[]={0,sizeof(int),sizeof(float),sizeof(char*)}
void*foo(数据类型){
void*rc=(void*)malloc(5*个大小[数据类型]);
交换机(数据类型){
案例1:{
int*p_int=(int*)rc;
对于(int i=0;i<5;i++)
p_int[i]=1;
}中断;
案例3:{
中国
/*_Just for reference_ the functions required for variable arguments can be defined as:
#define va_list char*
#define va_arg(ap,type) (*(type *)(((ap)+=(((sizeof(type))+(sizeof(int)-1)) \
& (~(sizeof(int)-1))))-(((sizeof(type))+ \
(sizeof(int)-1)) & (~(sizeof(int)-1)))))
#define va_end(ap) (void) 0
#define va_start(ap,arg) (void)((ap)=(((char *)&(arg))+(((sizeof(arg))+ \
(sizeof(int)-1)) & (~(sizeof(int)-1)))))
*/
#define INT '0'
#define DOUBLE '1'
#define STRING '2'
void yourfunc(char *fmt_string, ...){
va_list args;
va_start (args, fmt_string);
while(*fmt_string){
switch(*fmt_string++){
case INT: some_intfxn(va_arg(ap, int));
case DOUBLE: some_doublefxn(va_arg(ap, double));
case STRING: some_stringfxn(va_arg(ap, char *));
/* extend this as you like using pointers and casting to your type */
default: handlfailfunc();
}
}
va_end (args);
}
#define INT '0'
#define DOUBLE '1'
#define STRING '2'
void yourfunc(datatype, ...){ /*leaving "..." for future while on datatype(s)*/
va_list args;
va_start (args, datatype);
switch(datatype){
case INT: some_intfxn(va_arg(ap, int));
case DOUBLE: some_doublefxn(va_arg(ap, double));
case STRING: some_stringfxn(va_arg(ap, char *));
/* extend this as you like using pointers and casting to your type */
default: handlfailfunc();
}
va_end (args);
}