文章目录
前言
void
在英文中作为名词的解释为 “空间,空白,空虚”,在 C 语言中,void
被翻译为"无类型",相应的void *
为"无类型指针"。
void
似乎只有"注释"和限制程序的作用,当然,这里的"注释"不是为我们人提供注释,而是为编译器提供一种所谓的注释。
1.常见void
与void*
应用
1.1.void
作为函数参数类型的示例:
void printMessage(const char* message) {
printf("%s\n", message);
}
在这个例子中,函数 printMessage
接收一个 const char*
类型的参数,该参数表示一个字符串。函数并不需要返回值,因此返回类型为 void
。
1.2.void*
类型作为函数返回值:
void* allocateMemory(size_t size) {
return malloc(size);
}
在这个例子中,函数 allocateMemory
接收一个 size_t
类型的参数,表示需要分配的内存大小。函数使用 malloc
函数动态分配内存,并将返回的指针类型转换为 void*
,然后返回。
1.3.void*
指针的使用示例:
int num = 10;
void* ptr = #
int* intPtr = (int*)ptr;
printf("%d\n", *intPtr); // 输出 10
在这个例子中,整数 num
被创建,并通过 &
运算符获取其地址。然后,将该地址存储在 void*
类型的指针 ptr
中。通过使用 (int*)
运算符将 ptr
转换为 int*
类型的指针 intPtr
,可以访问并输出该整数的值。
1.4.void
作为函数指针的示例:
void (*funcPtr)() = &greet;
funcPtr(); // 调用函数指针
在这个例子中,声明了一个名为 funcPtr
的函数指针,它可以指向不返回任何值的函数。然后将 greet
函数的地址赋值给 funcPtr
,最后通过函数指针调用函数。
1.5.void*
类型的数组示例:
void* arr[3];
int num = 10;
char ch = 'A';
float f = 3.14;
arr[0] = #
arr[1] = &ch;
arr[2] = &f;
printf("%d\n", *(int*)arr[0]); // 输出 10
printf("%c\n", *(char*)arr[1]); // 输出 A
printf("%f\n", *(float*)arr[2]); // 输出 3.14
在这个示例中,函数 printValue
接收一个 void*
类型的参数以及一个代表数据类型的整数参数。根据传入的类型,在函数内部使用合适的类型转换将 void*
指针解引用,并打印相应的值。
2.void
和void*
的使用规则
2.1. void*
指针可以指向任意类型的数据
可以指向任意类型的数据,就是说可以用任意类型的指针对 void*
指针对 void*
指针赋值。例如:
int* a;
void* p;
p = a;
如果要将 void*
指针 p
赋给其他类型的指针,则需要强制类型转换,就本例而言:a=(int *)p
。在内存的分配中我们可以见到 void*
指针使用:内存分配函数 malloc
函数返回的指针就是 void *
型,用户在使用这个指针的时候,要进行强制类型转换,也就是显式说明该指针指向的内存中是存放的什么类型的数据 (int *)malloc(1024)
表示强制规定 malloc
返回的 void*
指针指向的内存中存放的是一个个的 int
型数据。
2.2. void*
指针不允许进行 ++
或 --
操作
在 ANSI C 标准中,不允许对 void*
指针进行一些算术运算如 p++
或 p+=1
等,因为既然 void
是无类型,那么每次算术运算我们就不知道该操作几个字节,例如 char 型操作 sizeof(char)
字节,而 int
则要操作 sizeof(int)
字节。( 而在 GNU 中则允许,因为在默认情况下,GNU 认为 void * 和 char * 一样,既然是确定的,当然可以进行一些算术操作,在这里sizeof(*p)==sizeof(char)。 )
2.3.void
类型定义
void 几乎只有"注释"和限制程序的作用,因为从来没有人会定义一个 void 变量,让我们试着来定义:
void a;
这行语句编译时会出错。即使 void a 的编译不会出错,它也没有任何实际意义。
2.4.void*
指针的互相赋值
众所周知,如果指针 p1
和 p2
的类型相同,那么我们可以直接在 p1
和 p2
间互相赋值;如果 p1
和 p2
指向不同的数据类型,则必须使用强制类型转换运算符把赋值运算符右边的指针类型转换为左边指针的类型。
float *p1;
int *p2;
p1 = p2;
在C语言中,指针之间的赋值会受到类型的限制。然而,在某些情况下,不同类型的指针可以进行隐式转换,并且编译器不会报错。
在您提供的代码中,将一个 int* 类型的指针 p2 赋值给一个 float* 类型的指针 p1。虽然 p1 和 p2
的指针类型不同,但由于 float 和 int 都是基本数据类型,且其大小相同,因此编译器允许将 int* 类型指针赋给 float*
类型的指针。这种隐式转换被称为兼容指针类型的转换。 请注意,虽然这样的赋值在语法上是合法的,但它可能导致难以预测的行为和错误的结果。由于
float 和 int 的内存布局是不同的,使用错误类型的指针可能会导致解释存储在其指向的内存位置上的值时出现问题。
因此,为了代码的可读性和可维护性,通常建议遵循类型的匹配性,不要使用不同类型的指针进行赋值,除非进行明确的类型转换。这样可以避免潜在的错误和问题。
所以最好写成:
float *p1;
int *p2;
p1 = (float *)p2;
而 void *
则不同,任何类型的指针都可以直接赋值给它,无需进行强制类型转换。
void *p1;
int *p2;
p1 = p2;
但这并不意味着,void *
也可以无需强制类型转换地赋给其它类型的指针。因为"无类型"可以包容"有类型",而"有类型"则不能包容"无类型"。
2.5.小心使用void*
指针类型
按照 ANSI(American National Standards Institute) 标准,不能对 void 指针进行算法操作,即下列操作都是不合法的:
void * pvoid;
pvoid++; //ANSI:错误
pvoid += 1; //ANSI:错误
//ANSI标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。
//例如:
int *pint;
pint++; //ANSI:正确
//pint++ 的结果是使其增大 sizeof(int)。
在实际的程序设计中,为迎合 ANSI 标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码:
void * pvoid;
((char *)pvoid)++;
(char *)pvoid += 1;
需要注意的是,不能写成:
(char*)pvoid++; //err
因为++
会先与pvoid
结合!
3.void*
指针所带来的好处
函数中形为指针类型时,我们可以将其定义为 void 指针,这样函数就可以接受任意类型的指针。如:
典型的如内存操作函数 memcpy
、 memmove
和 memset
等等的函数:
void * memcpy ( void * destination, const void * source, size_t num );
void * memmove ( void * destination, const void * source, size_t num );
void * memset ( void * ptr, int value, size_t num );
这样,任何类型的指针都可以传入 memcpy
、 memmove
和 memset
中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型( 泛型编程)。如果memcpy
、 memmove
和 memset
的参数类型不是 void *
,而是 char *
,那才叫真的奇怪了!这样的 memcpy
、 memmove
和 memset
明显不是一个"纯粹的,脱离低级趣味的"函数!void
的出现只是为了一种抽象的需要,如果你正确地理解了面向对象中"抽象基类"的概念,也很容易理解 void
数据类型。正如不能给抽象基类定义一个实例,我们也不能定义一个 void
(让我们类比的称 void
为"抽象数据类型")变量。
- 参考文章地址:http://www.cnblogs.com/archimedes/p/c-void-point.html