void
void关键字的三个use case, 和一个注意的tips
# **Functions that do not return a value ** : void add ( int a, int b )
在C语言中,不加返回值类型限定的函数,就会被编译器作为返回整型值处理。但是很多人却误以为其为void类型。例如:
add ( int a, int b )
{
return a + b;
}
int main(int argc, char* argv[])
{
printf ( "2 + 3 = %d", add ( 2, 3) );
}
void noReturn(int x) // void here means no return value
{
return 5; // error
}
2
3
4
5
6
7
8
9
10
11
12
13
14
程序运行的结果为输出:2 + 3 = 5, 这说明在C语言中不加返回值说明的函数的确为int函数。
C++ 会有严格的类型检查, 对于任何函数都要指定其类型。如果函数没有返回值,一定要声明为void类型。另外,加上void类型声明后,也可以发挥代码的“自注释”作用。代码的“自注释”即代码能自己注释自己。
void add ( int a, int b )
{
int c;
c = a + b;
}
2
3
4
5
# Functions that do not take parameters : void add ( void )
如果函数不接受任何参数, 可以在函数声明时候的参数列表里面写上void
void add ( void )
{
// some system configuration ...
}
// or :
int getValue(void) // void here means no parameters
{
int x{};
std::cin >> x;
return x;
}
2
3
4
5
6
7
8
9
10
11
12
# void pointer (generic pointer): void *
如果函数的参数可以是任意类型指针,那么应声明其参数为void *, 注意两点:
- void*是一种特殊的指针类型,可存放任意对象的地址,但我们对该地址中到底是什么类型的对象并不了解。
- 不能直接操作void*指针所指的对象,因为我们并不知道这个对象到底是什么类型,也就无法确定能在这个对象上做哪些操作。
// void* 可以存放任意对象的地址,
int nValue;
float fValue;
struct Something
{
int n;
float f;
};
Something sValue;
void* ptr;
ptr = &nValue; // valid
ptr = &fValue; // valid
ptr = &sValue; // valid
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 但是void*必须进行强制转换之后才能够对齐进行操作
int value{ 5 };
void* voidPtr{ &value };
// std::cout << *voidPtr << '\n'; // illegal: dereference of void pointer
int* intPtr{ static_cast<int*>(voidPtr) }; // however, if we cast our void pointer to an int pointer...
std::cout << *intPtr << '\n'; // then we can dereference the result
2
3
4
5
6
7
8
9
如内存操作函数memcpy和memset的函数原型分别为:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
2
3
这样,任何类型的指针都可以传入memcpy和memset中,也体现了内存操作函数的意义,因为它操作的对象是一片内存,而不在乎这片内存是什么类型
下面的代码执行正确:
//示例:memset接受任意类型指针
int intarray[100];
memset(intarray, 0, 100*sizeof(int)); //将intarray清0
//示例:memcpy接受任意类型指针
int intarray1[100], intarray2[100];
memcpy(intarray1, intarray2, 100*sizeof(int)); //将intarray2拷贝给intarray1
2
3
4
5
6
7
并且memcpy和memset函数返回的也是void *类型, 代表一个任意类型的指针
按照ANSI(American National Standards Institute)标准,不能对void指针进行算法操作,即下列操作都是不合法的:
void* pvoid;
pvoid++; //ANSI:错误
pvoid += 1; //ANSI:错误
//ANSI标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。
//例如:
int *pint;
pint++; //ANSI:正确 pint++的结果是使其增大sizeof(int)
2
3
4
5
6
7
8
但是GNU(GNU's Not Unix的缩写)则指定void *的算法操作与char *一致。因此下列语句在GNU编译器中也是正确的:
pvoid++; //GNU:正确
pvoid += 1; //GNU:正确void++的执行结果是其增大了1
// 在实际的程序设计中,为迎合ANSI标准,并提高程序的可移植性,我们可以这样实现:
void * pvoid;
(char *)pvoid++; //ANSI:正确;GNU:正确
(char *)pvoid += 1; //ANSI:错误;GNU:正确
2
3
4
5
6
7
GNU和ANSI还有一些区别,总体而言,GNU较ANSI更“开放”,提供了对更多语法的支持。但是我们在真实设计时,还是应该尽可能地匹配ANSI标准。
# void不是一个真实的变量
下面代码都企图让void代表一个真实的变量,因此都是错误的代码:
void a; //错误
function(void a); //错误
2
如果指针p1和p2的类型相同,那么我们可以直接在p1和p2间互相赋值;如果p1和p2指向不同的数据类型,则必须使用强制类型转换运算符把赋值运算符右边的指针类型转换为左边指针的类型。 例如:
float *p1;
int *p2;
p1 = p2; // error! cannot convert from 'int *' to 'float *'”
2
3
必须改为:
p1 = (float *)p2;
但是任何类型的指针都可以直接赋值给void *,无需进行类型转换(反之不成立):
void *p1;
int *p2;
p1 = p2;
2
3
void *则必须进行类型转换, 然后再赋给其它类型的指针。下面这样则会出错:
void *p1;
int *p2;
p2 = p1; // cannot convert from 'void *' to 'int *'
2
3
# Extra:
对于好的编程习惯来说,declare一个指针,则初始化为NULL,如果是类成员 则在构造函数中initialize,当对指针使用delete时候,则置它为NULL.