在我们看程序的时候,通常会遇到void *的问题。尤其是void *后面跟着一个常数的时候,就更不明白了。以下是我在百度里搜出来的解释: (void*)0是把void指针指向的空间地址改为全为0,而(void*)-1就是把void指针指向的空间地址改为全为1,也就是FFFFFFFF(占4字节即32位)。其实,void指针的本身空间地址并没有改变,改变的只是void本身空间存储的一个地址,也就是改为00000000或FFFFFFFF。 如下例子: void *a,*b; a=(void*)0; b=(void*)-1; printf("%X %X\n",&a,&b); printf("%X %X\n",a,b); PS:http://zhidao.baidu.com/question/418197547.html 以下是我根据上面的解释,写的c语言程序: #include<stdio.h> void *TestFunction(void *arg) { printf("This is the TestFunction!\n"); return (void *)1; } main() { void *p; p=TestFunction(NULL); printf("This is the main Function!\n"); printf("the return data is:%d\n",(int *)p); return 0; } 运行结果: This is the TestFunction! This is the main Function! the return data is:1 请按任意键继续. . . 从这个程序中可以看出,上面的解释是正确的。但是,注意一下,对于一个void *的指针,系统分配的是4个内存单元,对于int *的指针,系统分配的也是4个内存单元。因此,在程序中可以直接(int *)p,但是,当指针分配的内存单元不同的时候,强制转换可能会造成错误的结果。 (转载)(void*)0 的理解 一般把(void*)0定义为NULL 表示这是个空指针 void的含义 void的字面意思是“无类型”,void *则为“无类型指针”,void *可以指向任何类型的数据。 众所周知,如果指针p1和p2的类型相同,那么我们可以直接在p1和p2间互相赋值;如果p1和p2指向不同的数据类型,则必须使用强制类型 转换运算符把赋值运算符右边的指针类型转换为左边指针的类型。 例如: float *p1; int *p2; p1 = p2; 其中p1 = p2语句会编译出错,提示“‘=‘ : cannot convert from ‘int *‘ to ‘float *‘”,必须改为: p1 = (float *)p2; 而void *则不同,任何类型的指针都可以直接赋值给它,无需进行强制类型转换: void *p1; int *p2; p1 = p2; 但这并不意味着,void *也可以无需强制类型转换地赋给其它类型的指针。因为“无类型”可以包容“有类型”,而“有类型”则不能包 容“无类型”。道理很简单,我们可以说“男人和女人都是人”,但不能说“人是男人”或者“人是女人”。下面的语句编译出错: void *p1; int *p2; p2 = p1; 提示“‘=‘ : cannot convert from ‘void *‘ to ‘int *‘”。 在C语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。但是许多程序员却误以为其为void类型。例如: add ( int a, int b ) { return a + b; } int main(int argc, char* argv[]) { printf ( "2 + 3 = %d", add ( 2, 3) ); } 程序运行的结果为输出: 2 + 3 = 5 这说明不加返回值说明的函数的确为int函数。 理解*(void**)b #include <stdio.h> void main() { int *p; int a=2; unsigned long b=1245048; p=&a; printf("%d/n",*p); printf("%p/n",&a); printf("%d/n",&a); printf("%d/n",(void*)b); //printf("%d/n",*(void*)b); // 被注释的一行,运行此行会提示错误。 printf("%d/n",*(int*)b); printf("%d/n",*(void**)b); } P 为指向整型变量的指针。将 a 的地址赋予 p ,打印出 *p (解引用,即取该地址的值)为 2 。 再打印出 &a ,即 a 地址,为 0012FF78 (参数 p 表示用指针的格式,即内存地址,打印出来)。 再打印出 &a 的地址的十进制数字为 1245048( 参数 d 表示为十进制 ) 。 定义一个整型变量 b ,赋值为 1245048 ( 即 unsigned long b=1245048;) 。 如此, printf("%d/n",(void*)b); 表示:将 b 强制转换为一个指针,并打印出来。结果是 1245048 。 为什么如此呢?因为此时 b 虽然转换为一个指针,但 printf 的时候却没加上 * 号,故此没进行解引用(也即是取该地址的值),因而打印出来的仍然是该指针的值。(注意区别指针的值,和该指针指向的地址的值) 那么,加上 * 号进行解引用再打印出来,可否?实验结果不行,出现语法错误。概因 b 只是强制转换为指针,并没有指明是什么样类型(其实是 void 型)的指针。 那么,强制转换的时候便声明该指针类型,可以吗?可以看看 printf("%d/n",*(int*)b); ,将 b 强制转换为 int* 类型的指针,解引用成功,输出 2 。 最后来到我们的问题, *(void**)b 究竟是什么?相信看到这里你大概知道了。 (void**) 代表的是指向指针的指针,如此,先 (void**)b ,即,将 b 强制转换为指向指针的指针,然后再给它加上一个 * 解引用,如此,便取得了该指针指向的地址的值, 2 。 你可能会说,这里不也是声明为 void 型的指针了吗?这里行为什么上边不行呢?不,这里其实是不一样的。上边是 void 型指针,本质即指针,而这里是 void 型的指针的指针,本质是指针的指针。 void 型指针,不知道指向的地址内容要怎么去引用它,而 void 型的指针的指针,却知道其指向的地址内容是一个地址。 当然,我们也可以 printf("%d/n",*(int**)b); 输出结果是一样的。因为指向指针的指针本来就是指针(恩,有点拗口),只要类型正确,决定该指针输出什么的是其指向哪里的值,而不是指针本身。 我们可以做个试验, printf("%d/n",*(int****)b); ,看起来很晕吧,将 b 转换为指向指针的指针的指针的指针,结果呢?一样都是 2 。 void pointer 2009-12-25 16:55:17| 分类: C |举报|字号 订阅 void的字面意思是“无类型”,void *则为“无类型指针”,void *可以指向任何类型的数据。 小心使用void指针类型 众所周知,如果指针p1和p2的类型相同,那么我们可以直接在p1和p2间互相赋值;如果p1和p2指向不同的数据类型,则必须使用强制类型转换运算符把赋值运算符右边的指针类型转换为左边指针的类型。 例如: float *p1; int *p2; 1 = p2; 其中p1 = p2语句会编译出错,提示“‘=‘ : cannot convert from ‘int *‘ to ‘float *‘”,必须改为: p1 = (float *)p2; 而void *则不同,任何类型的指针都可以直接赋值给它,无需进行强制类型转换: void *p1; int *p2; p1 = p2; 但这并不意味着,void *也可以无需强制类型转换地赋给其它类型的指针。因为“无类型”可以包容“有类型”,而“有类型”则不能包容“无类型”。 按照ANSI(American National Standards Institute)标准,不能对void指针进行算法操作,即下列操作都是不合法的: void * pvoid; pvoid++; //ANSI:错误 pvoid += 1; //ANSI:错误 //ANSI标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。 //例如: int *pint; pint++; //ANSI:正确 pint++的结果是使其增大sizeof(int)。 但是大名鼎鼎的GNU(GNU‘s Not Unix的缩写)则不这么认定,它指定void *的算法操作与char *一致。 因此下列语句在GNU编译器中皆正确: void++; //GNU:正确 void += 1; //GNU:正确 void++的执行结果是其增大了1。 在实际的程序设计中,为迎合ANSI标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码: void * pvoid; (char *)pvoid++; //ANSI:正确;GNU:正确 (char *)pvoid += 1; //ANSI:错误;GNU:正确 GNU和ANSI还有一些区别,总体而言,GNU较ANSI更“开放”,提供了对更多语法的支持。但是我们在真实设计时,还是应该尽可能地迎合ANSI标准。 如果函数的参数可以是任意类型指针,那么应声明其参数为void * 典型的如内存操作函数memcpy和memset的函数原型分别为: void * memcpy(void *dest, const void *src, size_t len); void * memset ( void * buffer, int c, size_t num ); 这样,任何类型的指针都可以传入memcpy和memset中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型。如果memcpy和memset的参数类型不是void *,而是char *,那才叫真的奇怪了!memcpy和memset函数返回的也是void *类型. void的出现只是为了一种抽象的需要,如果你正确地理解了面向对象中“抽象基类”的概念,也很容易理解void数据类型。正如不能给抽象基类定义一个实例,我们也不能定义一个void(让我们类比的称void为“抽象数据类型”)变量。 关于void 2008-12-28 11:18:30 分类: C/C++ void的字面意思是“无类型”,void *则为“无类型指针”,void *可以指向任何类型的数据。 void几乎只有“注释”和限制程序的作用,因为从来没有人会定义一个void变量,让我们试着来定义: void a; 这行语句编译时会出错,提示“illegal use of type ‘void‘”。不过,即使void a的编译不会出错,它也没有任何实际意义。 void真正发挥的作用在于: (1) 对函数返回的限定; (2) 对函数参数的限定。 众所周知,如果指针p1和p2的类型相同,那么我们可以直接在p1和p2间互相赋值;如果p1和p2指向不同的数据类型,则必须使用强制类型转换运算符把赋值运算符右边的指针类型转换为左边指针的类型。 例如: float *p1; int *p2; p1 = p2; 其中p1 = p2语句会编译出错,提示“‘=‘ : cannot convert from ‘int *‘ to ‘float *‘”,必须改为:p1 = (float *)p2; 而void *则不同,任何类型的指针都可以直接赋值给它,无需进行强制类型转换: void *p1; int *p2; p1 = p2; 但这并不意味着,void *也可以无需强制类型转换地赋给其它类型的指针。因为“无类型”可以包容“有类型”,而“有类型”则不能包容“无类型”。道理很简单,我们可以说“男人和女人都是人”,但不能说“人是男人”或者“人是女人”。下面的语句编译出错: void *p1; int *p2; p2 = p1; 提示“‘=‘ : cannot convert from ‘void *‘ to ‘int *‘”。 此外对于void指针类型 ANSI C标准规定用动态存储分配函数时返回void指针,它可以用来指向一个抽象的类型的数据,再将它的值赋给另一指针变量时要进行强制类型转换使之适合于被赋值的变量的类型,如: char *p1; void *p2; ... p1=(char *)p2; 或:p2=(void *)p1; 也可以将一个函数定义为void *类型,如: void *fun(char ch1,char ch2) 表示函数fun返回的是一个地址,它指向“空类型”,如需要引用此地址,也需要根据情况对之进行类型转换,如: p1=(char *)fun(ch1,ch2); 关于delete void*可能出错的情况 代码如下: class deleteVoid { void* data; const int size; const char id; public: deleteVoid(int sz,char c): size(sz),id(c) { data = new char[size]; cout<<"Construct deleteVoid"<<id<<","<<"size="<<size<<endl; } ~deleteVoid() { cout<<"Destruct deleteVoid"<<id<<","<<"size="<<size<<endl; delete []data; } }; int main() { deleteVoid* a = new deleteVoid(40, ‘a‘); delete a; void* b = new deleteVoid(30, ‘b‘); delete b; return 0; } 输出如下: Construct deleteVoida,size=40 Destruct deleteVoida,size=40 Construct deleteVoidb,size=30 因为delete a知道a指向一个deleteVoid对象,所以析构函数会调用,从而释放了被分配的data内存,但是,正如在进行delete b的操作中,如果通过void* 类型的指针对一个对象进行操作,则只会释放deletVoid对象的内存,而不会调用析构函数,也就不会释放data所指向的内存,编译这个程序时,编译器会认为我们知道所做的一切,于是我们不会看到任何警告信息。但因此我们会丢失大量内存。 C/C++中的void和void* 一、void void关键字表示“空类型”的概念。但是,这里的“空类型”不表示“任意类型”,而是表示不存在的意思,也就是说C/C++不允许你写语句void a,不存在类型为void的东西. void表示“不存在”的意思,可以从void的两个应用中加以体现: 1、void作为函数的返回值类型时,表示函数返回值不存在,即函数没有返回值。 例如: void FunctionNoReturn_0(std::string strName) { std::cout << strName << std::endl; return; } void FunctionNoReturn_1(std::string strName) { std::cout << strName << std::endl; } FunctionNoReturn_1函数体中虽然没有显式的Return;语句。但是,有隐式的Return;表示函数不存在返回值。 FunctionNoReturnType(void) { return 10; } 在C语言中,凡是不加返回值类型限定的函数,就会被编译器作为返回整型值处理,而不是没有返回值。所以,FunctionNoReturnType函数返回10是正确的。 在C++中,每个函数必须限定返回值类型,不允许不加返回值限定。所以,C++编译器会对FunctionNoReturnType报错。 2、void作为函数的参数的限定时,表示函数形参不存在,即函数没有形参。 例如: void FunctionNoArgument_0(void) { return; } void FunctionNoArgument_1() { return; } 注意:void FunctionNoArgument_1();这也表示没有形参。 在C语言中,FunctionNoArgument_1(10);是合法的。编译器不会报错。 在C语言中,FunctionNoArgument_0(10);是不合法的。编译器会报错。 在C++语言中,FunctionNoArgument_1(10);和FunctionNoArgument_0(10); 都是不合法的。编译器会报错。 C语言中不报错,也没什么关系的。因为,参数10对于函数的执行的结果没有影响。但是,对于代码的维护可能会造成隐藏的危害,可能会给别人造成误解。 说明:既然提供了void的这两种用法,就去运用。即函数没返回值就将其返回值类型写为void,函数没有形参就将其形参写为void。不了解编译器默认操作时,不要依赖。即使了解其默认操作,也别依赖,因为肯定有人不了解的,这样别人就看不懂你的代码了。 二、void* void*表示“空类型指针”,与void不同,void*表示“任意类型的指针”或表示“该指针与一地址值相关,但是不清楚在此地址上的对象的类型”。(为什么不用void表示任意类型的数据呢?大家都知道,C/C++是静态类型的语言,定义变量就会分配内存,然而,不同类型的变量所占内存不同,如果定义一个任意类型的变量,如何为其分配内存呢?所以,C、C++中没有任意类型的变量。但是,所有指针类型的变量,无论是int*、char*、string*、Student*等等,他们的内存空间都是相同的,所以可以定义“任意类型的指针”)。 C++/ANSI C: void*指针只支持几种有限的操作:与另一个指针进行比较;向函数传递void指针或从函数返回void*指针;给另一个void*指针赋值。不允许使用void*指针操作它所指向的对象,例如,不允许对void*指针进行解引用。不允许对void*指针进行算术操作。 GNU C: GNU C指定void*指针的算术操作与char*一致。 void*表示“任意类型的指针”,主要运用于内存操作函数的形参类型和返回值类型(内存操作与内存中数据类型无关,即任意类型都可以)。 memcpy 原型:extern void *memcpy(void *dest, void *src, unsigned int count); 用法:#include 功能:由src所指内存区域复制count个字节到dest所指内存区域。 说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。 注意:与strcpy相比,memcpy并不是遇到‘\0‘就结束,而是一定会拷贝完n个字节。 memset 原型:extern void *memset(void *buffer, int c, int count); 用法:#include 功能:把buffer所指内存区域的前count个字节设置成字符c。 说明:返回指向buffer的指针。 void及void指针含义的深刻解析 void的含义 void即“无类型”,void *则为“无类型指针”,可以指向任何数据类型。 void指针使用规范 ①void指针可以指向任意类型的数据,亦即可用任意数据类型的指针对void指针赋值。例如: int * pint; void *pvoid; pvoid = pint; /* 不过不能 pint= pvoid; */ 如果要将pvoid赋给其他类型指针,则需要强制类型转换如:pint= (int *)pvoid; ②在ANSIC标准中,不允许对void指针进行算术运算如pvoid++或pvoid+=1等,而在GNU中则允许,因为在缺省情况下,GNU认为void *与char *一样。sizeof(*pvoid )== sizeof( char). void的作用 ①对函数返回的限定。 ②对函数参数的限定。 当函数不需要返回值时,必须使用void限定。例如: void func(int, int); 当函数不允许接受参数时,必须使用void限定。例如: int func(void)。 由于void指针可以指向任意类型的数据,亦即可用任意数据类型的指针对void指针赋值,因此还可以用void指针来作为函数形参,这样函数就可以接受任意数据类型的指针作为参数。例如: void * memcpy( void *dest, const void *src, size_t len ); void * memset( void * buffer, int c, size_t num); ------------------------------------------------------------------------------ 1. 综述 许多初学者对C/C++语言中的void及void指针类型不甚理解,因此在使用上出现了一些错误。本文将对void关键字的深刻含义进行解说,并详述void及void指针类型的使用方法与技巧。 2.void的含义 void的字面意思是“无类型”,void*则为“无类型指针”,void*可以指向任何类型的数据。void几乎只有“注释”和限制程序的作用,因为从来没有人会定义一个void变量,让我们试着来定义: void a; 这行语句编译时会出错,提示“illegal use of type’void’”。不过,即使voida的编译不会出错,它也没有任何实际意义。 void真正发挥的作用在于: (1)对函数返回的限定; (2)对函数参数的限定。 众所周知,如果指针p1和p2的类型相同,那么我们可以直接在p1和p2间互相赋值;如果p1和p2指向不同的数据类型,则必须使用强制类型转换运算符把赋值运算符右边的指针类型转换为左边指针的类型。 例如: float * p1; in t* p2; p1 = p2; 其中p1 = p2语句会编译出错,提示“’=’:cannotconvertfrom’int *’to’float *’”,必须改为: p1=(float*)p2; 而void*则不同,任何类型的指针都可以直接赋值给它,无需进行强制类型转换: void * p1; int * p2; p1 = p2; 但这并不意味着,void*也可以无需强制类型转换地赋给其它类型的指针。因为“无类型”可以包容“有类型”,而“有类型”则不能包容“无类型”。道理很简单,我们可以说“男人和女人都是人”,但不能说“人是男人”或者“人是女人”。下面的语句编译出错: void * p1; int * p2; p2 = p1; 提示“’=’:cannotconvertfrom’void*’to’int*’”。 3.void的使用 下面给出void关键字的使用规则: 规则一如果函数没有返回值,那么应声明为void类型 在C语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。但是许多程序员却误以为其为void类型。例如: add(inta,intb) { return a+b; } int main(int argc,char * argv[]) { printf(/"2+3=%d/",add(2,3)); } 程序运行的结果为输出: 2+3=5 这说明不加返回值说明的函数的确为int函数。 林锐博士《高质量C/C++编程》中提到:“C++语言有很严格的类型安全检查,不允许上述情况(指函数不加类型声明)发生”。可是编译器并不一定这么认定,譬如在VisualC++6.0中上述add函数的编译无错也无警告且运行正确,所以不能寄希望于编译器会做严格的类型检查。 因此,为了避免混乱,我们在编写C/C++程序时,对于任何函数都必须一个不漏地指定其类型。如果函数没有返回值,一定要声明为void类型。这既是程序良好可读性的需要,也是编程规范性的要求。另外,加上void类型声明后,也可以发挥代码的“自注释”作用。代码的“自注释”即代码能自己注释自己。 [Page] 规则二如果函数无参数,那么应声明其参数为void 在C++语言中声明一个这样的函数: int function(void) { return1; } 则进行下面的调用是不合法的: function(2); 因为在C++中,函数参数为void的意思是这个函数不接受任何参数。 我们在TurboC2.0中编译: #include"stdio.h" fun() { return1; } main() { printf(/"%d/",fun(2)); getchar(); } 编译正确且输出1,这说明,在C语言中,可以给无参数的函数传送任意类型的参数,但是在C++编译器中编译同样的代码则会出错。在C++中,不能向无参数的函数传送任何参数,出错提示“’fun’:functiondoesnottake1parameters”。 所以,无论在C还是C++中,若函数不接受任何参数,一定要指明参数为void。 规则三小心使用void指针类型 按照ANSI(AmericanNationalStandardsInstitute)标准,不能对void指针进行算法操作,即下列操作都是不合法的: void * pvoid; pvoid ++;//ANSI:错误 pvoid += 1;//ANSI:错误 ANSI标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。例如: int * pint; pint ++;//ANSI:正确 pint++的结果是使其增大sizeof(int)。 但是大名鼎鼎的GNU(GNU’sNotUnix的缩写)则不这么认定,它指定void * 的算法操作与char * 一致。 因此下列语句在GNU编译器中皆正确: pvoid ++;//GNU:正确 pvoid += 1;//GNU:正确 pvoid++的执行结果是其增大了1。 在实际的程序设计中,为迎合ANSI标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码: void * pvoid; (char*)pvoid ++;//ANSI:正确;GNU:正确 (char*)pvoid += 1;//ANSI:错误;GNU:正确 GNU和ANSI还有一些区别,总体而言,GNU较ANSI更“开放”,提供了对更多语法的支持。但是我们在真实设计时,还是应该尽可能地迎合ANSI标准。 规则四如果函数的参数可以是任意类型指针,那么应声明其参数为void* 典型的如内存操作函数memcpy和memset的函数原型分别为: void * memcpy(void*dest,constvoid*src,size_tlen); void * memset(void*buffer,intc,size_tnum); 这样,任何类型的指针都可以传入memcpy和memset中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型。如果 memcpy和memset的参数类型不是void*,而是char*,那才叫真的奇怪了!这样的memcpy和memset明显不是一个“纯粹的,脱离低级趣味的”函数! 下面的代码执行正确: //示例:memset接受任意类型指针 int intarray[100];[Page] memset(intarray,0,100*sizeof(int));//将intarray清0 //示例:memcpy接受任意类型指针 int intarray1[100],intarray2[100]; memcpy(intarray1,intarray2,100*sizeof(int));//将intarray2拷贝给intarray1 有趣的是,memcpy和memset函数返回的也是void*类型,标准库函数的编写者是多么地富有学问啊! 规则五void不能代表一个真实的变量 下面代码都企图让void代表一个真实的变量,因此都是错误的代码: void a;//错误 function(void a);//错误 void体现了一种抽象,这个世界上的变量都是“有类型”的,譬如一个人不是男人就是女人(还有人妖?)。 void的出现只是为了一种抽象的需要,如果你正确地理解了面向对象中“抽象基类”的概念,也很容易理解void数据类型。正如不能给抽象基类定义一个实例,我们也不能定义一个void(让我们类比的称void为“抽象数据类型”)变量。 4.总结 小小的void蕴藏着很丰富的设计哲学,作为一名程序设计人员,对问题进行深一个层次的思考必然使我们受益匪浅。 void (*b[10]) (void (*)()); void (*b[10]) (void (*)()); 看到这行代码,相信程序员们都会倒吸一口冷气吧。如果非常不幸的在维护的代码中看到类似的表述,除了想办法找出写出这种天书的猛人来,只能自己硬着头皮搞清楚了。 在C和C++中,构造这类声明表达式只有一条简单的规则:按照使用的方式来声明。 C变量的声明都是由两部分组成的:类型,以及一组类似表达式的声明符(declarator)。声明符类似于表达式,对它求值应该返回一个声明中给定类型的结果。例如,我们来看一个简单的声明: float f; 这里f就是声明符,对其求值,应该得到一个float型的数值。 然后看括号的作用,比如: float ((f)); 很简单,这个声明的含义就是,((f))的类型为浮点类型。因为括号和f之间没有其他的修饰了,所以,我们可以推知,f也是浮点型。 再来看稍微复杂点的: float f(); 我们注意到有一个()表达式出现,这个声明符的含义是一个函数。所以这个声明的含义就是,f()的求值结果是一个浮点数,也就是说,f是个返回值为浮点数的函数。 OK,这些都很简单,再看一个: float *f(); 这个就是表示*f()是个浮点表达式,而()的结合优先级高于*,所以,*f()也就是*(f()),f是个函数,返回值是个指针,这个指针指向一个浮点数。 如果*f是先结合的呢?比如: float (*f)(); 这时,f就不是和()结合而成为一个函数名,而是和*结合成为一个指针名,这个指针,是指向函数入口的函数指针,而这个函数,返回值是浮点型。 现在我们知道怎么声明一个指定类型的变量了。在这个声明表达式中,把变量名,和声明末尾的分号去掉,剩余的部分用一个括号整个括起来,这样就得到了这个变量的类型声明了,比如: float (*f)(); 这个表示f是一个指向返回值为浮点型的函数指针。而我们把f这个变量名,和最后的;号去掉,就得到: ( float (*)() ) 这个就表示,这个表达式是一个类型,即“指向返回值为浮点型的函数的指针”。如果用这个类型去修饰一个变量名,我们就叫它类型转换符。 现在有了这些预备知识,我们可以回头看标题的声明到底是什么意思了: void (*b[10]) (void (*)()); 首先,表达式的后半部分被两个()分隔开了,我们分别分析它们。( *b[10] ),其中出现了变量名b,很容易就知道,b是一个有10个元素的数组,每个元素都是一个指针。 然后,看(void(*)()),其中没有出现变量名,所以它代表了一个类型,即“指向返回值为void型的函数的指针“,而我们知道,C语法中,类型修饰符是必须出现在变量名的左边的,而在整个表达式中这个类型符是在变量名b的右边,所以, (void(*)())最外层的这个(),表示了定义了一个函数,这个函数有一个参数,就是一个指针,具体来说,就是“指向返回值为void型的函数的指针“。 这样就很清楚了,b数组里,每一个指针元素,都是一个函数指针,这个函数有一个参数,这个参数是一个函数指针。整个表达式最左边的void,则定义了b数组中函数指针所指向函数的返回值类型。 在一串绕口令式的解说后,我们终于看到了事实的真相:这行代码就是逗你玩... 如果不是故意偷懒,这样代码的作者真是在玩弄阅读者的感情,如果非要实现这样一个复杂的定义的话,我们也是有更好的方法的,就是使用typedef来进行类型声明。 在面对void (*b[10]) (void (*)());时,我们可以先声明后半部分的类型: typdef void (*pFunParam)(); 即表示,类型pFunParam,是一个函数指针。 然后,针对整个表达式声明一个类型: typedef void (*pFun)(pFunParam); 即表示,类型pFun,是一个函数指针。此函数的参数类型为pFunParam。 最后,进行变量的声明: pFun b[10]; 这样,就清晰许多了吧。最重要的时,利用这样的方式,将编写者的设计思路也体现在了代码中。 最后,介绍一个更快速的阅读方法:从右向左。 编译器在进行代码扫描时是从左向右的,而我们在解读这个表达式的时候,从右向左的方法会更容易些,如: 从右向左扫描以上表达式,首先我们看到一个“)”,因为其右边再没有变量,所以我们知道这是一个函数定义,然后,又看到一对“()“,显然也是函数定义,在就是(*),显然和后面的()联合起来表达了一个函数指针,再左边的void,即修饰了此指针的类型;然后出现了和最右边”)"对应的”(”,显然这个函数定义也完成了,再左边的[10]表达了一个数组,左边是它的名字b,而更左边的“*”和后面的“(“一起表达了一个函数指针,这个函数指针定义了b数组中元素的类型。再往左,指针元素的定义void也出现了。这样,我们就又看到了此表达式的真相。 void指针 指针有两个属性:指向变量/对象的地址和长度 但是指针只存储地址,长度则取决于指针的类型 编译器根据指针的类型从指针指向的地址向后寻址 指针类型不同则寻址范围也不同,比如: int*从指定地址向后寻找4字节作为变量的存储单元 double*从指定地址向后寻找8字节作为变量的存储单元 1.void指针是一种特别的指针 void *vp //说它特别是因为它没有类型 //或者说这个类型不能判断出指向对象的长度 2.任何指针都可以赋值给void指针 type *p; vp=p; //不需转换 //只获得变量/对象地址而不获得大小 3.void指针赋值给其他类型的指针时都要进行转换 type *p=(type*)vp; //转换类型也就是获得指向变量/对象大小 转:http://icoding.spaces.live.com/blog/cns!209684E38D520BA6!130.entry 4.void指针不能复引用 *vp//错误 因为void指针只知道,指向变量/对象的起始地址 而不知道指向变量/对象的大小(占几个字节)所以无法正确引用 5.void指针不能参与指针运算,除非进行转换 (type*)vp++; //vp==vp+sizeof(type) #include<iostream> #include<stdlib.h> #include<string> using namespace std; typedef struct tag_st { char id[10]; float fa[2]; }ST; //我在程序里面这样使用的 int main() { ST * P=(ST *)malloc(sizeof(ST)); strcpy(P->id,"hello!"); P->fa[0]=1.1; P->fa[1]=2.1; ST * Q=(ST *)malloc(sizeof(ST)); strcpy(Q->id,"world!"); Q->fa[0]=3.1; Q->fa[1]=4.1; void ** plink=(void **)P; *((ST *)(plink)) = * Q; //plink要先强制转换一下,目的是为了让它先知道要覆盖的大小. //P的内容竟然给Q的内容覆盖掉了. cout<<P->id<<" "<<P->fa[0]<<" "<<P->fa[1]<<endl; return 0; } c语言void概述 2008-08-23 14:57:47| 分类: C/C++ |举报|字号 订阅 本文来自百度 1.概述 许多初学者对C/C++语言中的void及void指针类型不甚理解,因此在使用上出现了一些错误。本文将对void关键字的深刻含义进行解说,并 详述void及void指针类型的使用方法与技巧。 2.void的含义 void的字面意思是“无类型”,void *则为“无类型指针”,void *可以指向任何类型的数据。 void几乎只有“注释”和限制程序的作用,因为从来没有人会定义一个void变量,让我们试着来定义: void a; 这行语句编译时会出错,提示“illegal use of type ‘void‘”。不过,即使void a的编译不会出错,它也没有任何实际意义。 void真正发挥的作用在于: (1) 对函数返回的限定; (2) 对函数参数的限定。 我们将在第三节对以上二点进行具体说明。 众所周知,如果指针p1和p2的类型相同,那么我们可以直接在p1和p2间互相赋值;如果p1和p2指向不同的数据类型,则必须使用强制类型 转换运算符把赋值运算符右边的指针类型转换为左边指针的类型。 例如: float *p1; int *p2; p1 = p2; 其中p1 = p2语句会编译出错,提示“‘=‘ : cannot convert from ‘int *‘ to ‘float *‘”,必须改为: p1 = (float *)p2; 而void *则不同,任何类型的指针都可以直接赋值给它,无需进行强制类型转换: void *p1; int *p2; p1 = p2; 但这并不意味着,void *也可以无需强制类型转换地赋给其它类型的指针。因为“无类型”可以包容“有类型”,而“有类型”则不能包 容“无类型”。道理很简单,我们可以说“男人和女人都是人”,但不能说“人是男人”或者“人是女人”。下面的语句编译出错: void *p1; int *p2; p2 = p1; 提示“‘=‘ : cannot convert from ‘void *‘ to ‘int *‘”。 3.void的使用 下面给出void关键字的使用规则: 规则一 如果函数没有返回值,那么应声明为void类型 在C语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。但是许多程序员却误以为其为void类型。例如: add ( int a, int b ) { return a + b; } int main(int argc, char* argv[]) { printf ( "2 + 3 = %d", add ( 2, 3) ); } 程序运行的结果为输出: 2 + 3 = 5 这说明不加返回值说明的函数的确为int函数。 林锐博士《高质量C/C++编程》中提到:“C++语言有很严格的类型安全检查,不允许上述情况(指函数不加类型声明)发生”。可是编译 器并不一定这么认定,譬如在Visual C++6.0中上述add函数的编译无错也无警告且运行正确,所以不能寄希望于编译器会做严格的类型检查。 因此,为了避免混乱,我们在编写C/C++程序时,对于任何函数都必须一个不漏地指定其类型。如果函数没有返回值,一定要声明为void类 型。这既是程序良好可读性的需要,也是编程规范性的要求。另外,加上void类型声明后,也可以发挥代码的“自注释”作用。代码的“自注 释”即代码能自己注释自己。 规则二如果函数无参数,那么应声明其参数为void 在C++语言中声明一个这样的函数: int function(void) { return 1; } 则进行下面的调用是不合法的: function(2); 因为在C++中,函数参数为void的意思是这个函数不接受任何参数。 我们在Turbo C 2.0中编译: #include "stdio.h" fun() { return 1; } main() { printf("%d",fun(2)); getchar(); } 编译正确且输出1,这说明,在C语言中,可以给无参数的函数传送任意类型的参数,但是在C++编译器中编译同样的代码则会出错。在C++ 中,不能向无参数的函数传送任何参数,出错提示“‘fun‘ : function does not take 1 parameters”。 所以,无论在C还是C++中,若函数不接受任何参数,一定要指明参数为void。 规则三 小心使用void指针类型 按照ANSI(American National Standards Institute)标准,不能对void指针进行算法操作,即下列操作都是不合法的: void * pvoid; pvoid++; //ANSI:错误 pvoid += 1; //ANSI:错误 //ANSI标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。 //例如: int *pint; pint++; //ANSI:正确 pint++的结果是使其增大sizeof(int)。( 在VC6.0上测试是sizeof(int)的倍数) 但是大名鼎鼎的GNU(GNU‘s Not Unix的缩写)则不这么认定,它指定void *的算法操作与char *一致。 因此下列语句在GNU编译器中皆正确: pvoid++; //GNU:正确 pvoid += 1; //GNU:正确 pvoid++的执行结果是其增大了1。( 在VC6.0上测试是sizeof(int)的倍数) 在实际的程序设计中,为迎合ANSI标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码: void * pvoid; (char *)pvoid++; //ANSI:正确;GNU:正确 (char *)pvoid += 1; //ANSI:错误;GNU:正确 GNU和ANSI还有一些区别,总体而言,GNU较ANSI更“开放”,提供了对更多语法的支持。但是我们在真实设计时,还是应该尽可能地迎合 ANSI标准。 规则四如果函数的参数可以是任意类型指针,那么应声明其参数为void * 典型的如内存操作函数memcpy和memset的函数原型分别为: void * memcpy(void *dest, const void *src, size_t len); void * memset ( void * buffer, int c, size_t num ); 这样,任何类型的指针都可以传入memcpy和memset中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不 论这片内存是什么类型。如果memcpy和memset的参数类型不是void *,而是char *,那才叫真的奇怪了!这样的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 有趣的是,memcpy和memset函数返回的也是void *类型,标准库函数的编写者是多么地富有学问啊! 规则五 void不能代表一个真实的变量 下面代码都企图让void代表一个真实的变量,因此都是错误的代码: void a; //错误 function(void a); //错误 void体现了一种抽象,这个世界上的变量都是“有类型”的,譬如一个人不是男人就是女人(还有人妖?)。 void的出现只是为了一种抽象的需要,如果你正确地理解了面向对象中“抽象基类”的概念,也很容易理解void数据类型。正如不能给抽 象基类定义一个实例,我们也不能定义一个void(让我们类比的称void为“抽象数据类型”)变量。 4.总结 小小的void蕴藏着很丰富的设计哲学,作为一名程序设计人员,对问题进行深一个层次的思考必然使我们受益匪浅。 -------------------------------------------//下面自己整理了点 不论什么类型的指针(void*, char*, int*, float*...) 默认初始值都是0xCCCCCCCC #include<iostream.h> #include <memory.h> //#include <string.h> void main() { void *p1; int a = 10; int *p2 = &a; cout << p1 << endl; cout << (int)*p2 << endl; p1 = p2; cout << *(int*)p1 << endl;//!!!!!!! 用空类型操作输出值! cout << (int)*p2 << endl; } /* 输出: 0xCCCCCCCC 10 10 10 */ 在声明同时赋值NULL,在delete后立即设置为NULL。 在debug版本下指针默认初始值为0xCCCCCCCC,在Release版本下初始值为0x0000000A,(在我电脑上VC6.0)。对于指针如果暂时没有合适的初始化值,就应该把它置为NULL(0)。 对于好的编程习惯来说,declare一个指针,则初始化为NULL,如果是类成员 则在构造函数中initialize,当对指针使用delete时候,则置它为NULL. 0xCCCCCCCC只是在debug状态下VC生成的未定义过的指针值,用来提示这个指针是未被初始化的,在release状态下不会等于这个值(除非巧合)。对于指针如果暂时没有合适的初始化值,就应该把它置为NULL(0)。 error C2036:‘void *‘ : unknown size void*和void**的区别 想必很多同志都从网上下载过SSDT HOOK驱动程序,这一份是很久之前的一份了,而且网上流行的也就这 一份,里面的RtlAPI也有过时的,企图编译,可是会出现:“‘PVOID’unknown size”这个错误,导致编译 失败。当然这个错误在自己编写的驱动中或者是应用程序中也是常见的错误,比如下面的代码就会出现这样 的问题: PVOID SSDTServiceBase;//windows define :typedef void* PVOID; ULONG Index; ULONG Address=(ULONG)(SSDTServiceBase+4*Index); 咋一看,怎么会出错呢?其实,是类型单位大小未知,C/C++一个隐含的语法:“不允许对一个指向未知 尺寸类型(unknown size)的指针进行直接运算”。void* 和void都属于 未知大小类型,当然VC中对void加 以限制了,所以你用void a;申明一个未知类型变量是错误的。当然,作为由人开发的编译器和C语言,不可 能对每种情况都考虑到,这是很正常的,void*是可以用来声明变量是正确的,因为这定义了一个指针。看到 这你也许会想到,是类型不匹配导致的,其实另有其因: unknown size:故名思意,就是未知的大小;这里的大小是指某个指针所指向的类型的大小;由于void* 所指向的是一个void类型的(虽然不可能有这个类型的数据,因为它占用0个字节),所以系统判定,void* 指顶的地址变量unknown size;从而导致错误; 由于很多人没有装WDK,不过也可以在VC++上来演示:(本文代码均在VC++6.0 WindowsXP SP2上运行通过) 你把下面这段代码运行: #include<iostream> #include<windows.h> using namespace std; int main() { void * a=(void*)(0x90000000);//nothing special cout<<(ULONG)(a+20000000)<<endl; return 0; } 会得到一个错误: error C2036: ‘void *‘ : unknown size 该错误很少有人会注意,这种类型的错误在驱动编译器里会当做错误被捕获。 在运行下面这段代码:(注意修改的地方) #include<iostream> #include<windows.h> using namespace std; int main() { void * a=(void*)(0x90000000);//nothing special cout<<(ULONG)((ULONG*)a+20000000)<<endl; return 0; } 或者: #include<iostream> #include<windows.h> using namespace std; int main() { ULONG* a=(ULONG*)(0x90000000);//nothing special cout<<(ULONG)(a+20000000)<<endl; return 0; } 大家发现unknown size 的错误没有了,这就证明了我上面说的,因为ULONG*类型的地址变量指向的内存结构 单元大小是可知的(32位机为4bytes); 大家再测试一段代码: #include<iostream> #include<windows.h> using namespace std; int main() { void**a=(void**)(0x90000000);//nothing special cout<<ULONG(a+20000000)<<endl; return 0; } 大家惊奇的发现,unknown size 又没有了!这是为什么呢? 下面是我思考的void* 和void **的区别: 昨天我问同学,地址和 地址的地址 的有什么不同吗?他想了想,说应该没有什么不同吧,其实, 我也这么认为的,因为我以前获取函数地址的时候。用“&函数名”和直接用“函数名”得到的结果是一样的 。也就是说地址的地址还是它本身,这个大家可以在VC++中验证。大家记住这样几个概念: 1.地址的地址要么没意义,要么还是它本是(如果你直接像“&(&a)”这样写,是会发生错误的,如果对于一个函数名,用&fun来获取发现与fun的值一样,VC++6.0编译器中) 2.指针指向地址,但是指针的地址是另外一个值 3.指针本身是一个占有内存的长整数,在超过其运用范围的时候会自己释放(它所指向的内存需要手动释放),而地址是一个“虚值”,不占用内存; 在32位机中,地址是一个4字节长的正整数,而指针是用来存放地址的,当我们定义一个指针,如 char*p;的时候,系统为p分配了一个4字节的内存空间,里面存放的值,实际上是一个unsigned long类型( 但是如果未转换为unsigned long ,在VC++中则还是会出错),用来标识地址;这个地址标识着一个char单 元的首地址,当然char只占有1一个字节。 接下来,我们定义个void*p,我们知道这也是一个指针p,p本身是一个占有四字节的内存单元首地址, 但是它指向的却是void类型的,也就是无类型,如果直接用它参加运算,会导致“unknown size”的错误; 我们再来看看void ** P,我们可以把它差分成(void*)*p,这样也许大家都明白了,这个p是一个这样的指 针:它是一个指向void*类型的指针;而void*这个类型我们上面讨论了,实际是一个unsigned long类型;所 以我们知道,这样的类型是规定了单元大小(4字节)的。 对于上面讨论的,我们可以总结出一点:C语言中对指向规定了SIZE的类型的指针,一律把它当成一个 ULONG(unsigned long);否则当做未知类型(如void*)。我们可以得出一个规范的写法,只要保证参与运 算的指针所指向的类型是可知的就行了,只要这样,编译器就能把它当做ULONG(unsigned long)去运算了 。 我们有一种最为规范的写法: (ULONG)(ULONG((TYPE*)a)+20000000);//TYPE为C语言知道单位大小的类型如int,char ,bool等等等 当然去掉第二个ULONG也是对的,因为这只是强制转换了一下ULONG,相当于(ULONG)(ULONG),我之所以说 它最规范,是因为,我们实际给他的是一个地址类型“(TYPE*)a”,根据标准的C语法,强制转换一下便于理 解。 还有一点就是,直接强制转化为ULONG也是对的,因为这是个未知类型的32位正整数,所以强制转换也可 以用ULONG;当然用其他的类型不会报错,但会导致数据截断等错误。 static void和void区别(转) 2012-02-09 14:32 星期四 static关键字 1.作用于变量: 用static声明局部变量-------局部变量指在代码块{}内部定义的变量,只在代码块内部有效(作用域),其缺省的存储方式是自动变量或说是动态存储的,即指令执行到变量定义处时才给变量分配存储单元,跳出代码块时释放内存单元(生命期)。用static声明局部变量时,则改变变量的存储方式(生命期),使变量成为静态的局部变量,即编译时就为变量分配内存,直到程序退出才释放存储单元。这样,使得该局部变量有记忆功能,可以记忆上次的数据,不过由于仍是局部变量,因而只能在代码块内部使用(作用域不变)。 用static声明外部变量-------外部变量指在所有代码块{}之外定义的变量,它缺省为静态变量,编译时分配内存,程序结束时释放内存单元。同时其作用域很广,整个文件都有效甚至别的文件也能引用它。为了限制某些外部变量的作用域,使其只在本文件中有效,而不能被其他文件引用,可以用static关键字对其作出声明。 总结:用static声明局部变量,使其变为静态存储方式,作用域不变;用static声明外部变量,其本身就是静态变量,这只会改变其连接方式,使其只在本文件内部有效,而其他文件不可连接或引用该变量。 2.作用于函数: 使用static用于函数定义时,对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的。这样的函数又叫作静态函数。使用静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。 如果想要其他文件可以引用本地函数,则要在函数定义时使用关键字extern,表示该函数是外部函数,可供其他文件调用。另外在要引用别的文件中定义的外部函数的文件中,使用extern声明要用的外部函数即可。 参考资料: ①《 C程序设计(第二版) 》,谭浩强 ②《 Pointers on C 》,Kenneth A.Reek void和void指针 void的含义 void即“无类型”,void *则为“无类型指针”,可以指向任何数据类型。 void指针使用规范 ①void指针可以指向任意类型的数据,亦即可用任意数据类型的指针对void指针赋值。例如: int *pint; void *pvoid; pvoid = pint; /* 不过不能 pint = pvoid; */ 如果要将pvoid赋给其他类型指针,则需要强制类型转换如:pint = (int *)pvoid; ②在ANSI C标准中,不允许对void指针进行算术运算如pvoid++或pvoid+=1等,而在GNU中则允许,因为在缺省情况下,GNU认为void *与char *一样。sizeof( *pvoid )== sizeof( char ). void的作用 ①对函数返回的限定。 ②对函数参数的限定。 当函数不需要返回值时,必须使用void限定。例如: void func(int, int); 当函数不允许接受参数时,必须使用void限定。例如: int func(void)。 由于void指针可以指向任意类型的数据,亦即可用任意数据类型的指针对void指针赋值,因此还可以用void指针来作为函数形参,这样函数就可以接受任意数据类型的指针作为参数。例如: void * memcpy( void *dest, const void *src, size_t len ); void * memset( void * buffer, int c, size_t num ); 参考资料:《 C/C++语言void及void指针深层探索 》,宋宝华 函数指针 函数指针是什么? 先来看函数调用是怎么回事。一个函数占用一段连续内存。当调用一个函数时,实际上是跳转到函数入口地址,执行函数体的代码,完成后返回。如何找到对应的入口地址?这是由函数名来标记的,实际上,函数名就是函数的入口地址。 函数指针是一种特殊类型的指针,它指向一个函数的入口地址。 注意:除了void类型指针是无类型的指针外,其他所有指针都是有对应类型的,例如int *pint、struct studentdata *psdata等,只有指明了指针所指的数据类型,编译器才能为指针分配或预计分配相应大小的存储空间,指针的算术运算如pint++等才是有意义的。因此,定义了某种类型的指针之后,除非使用强制类型转换,那么它只能指向相应数据类型的变量或常量,不同类型的指针或数据之间不可混用。所以指针的类型实际上是一种身份标志的作用。 函数指针如何表明自己的身份呢?为了避免混乱,必须也要作出相应规定,不同函数的函数指针不能混用。例如,int func1(int arg11, char arg12)与int func2(char arg)的函数指针就不能混用,要定义可以指向func1的函数指针应该这样: int (*pfunc1)(int, char) = func1; 定义可以指向func2的函数指针则该如下: int (*pfunc2)(char) = func2; 从函数指针的定义可以看出,函数指针的类型实际上是由函数签名决定的。函数签名就象是函数的身份证,一个函数的函数签名是独一无二的,具有相同函数签名的函数实际上就是同一函数。函数签名包括函数名、函数形参类型的有序列表和函数返回值类型。 一个函数指针的定义规定了它只能指向特定类型的函数。如果两个函数的形参列表和返回值类型相同,只有函数名和函数体不同,则可以使用相同类型的函数指针。例如,如果还有一个函数int func3(char arg),则上面定义的可以指向函数func2的函数指针也可以用于指向func3,即: pfunc2 = func3; 再使用pfunc2(char ARG)就可以调用函数func3,这时指令计数器(PC)指向函数入口,从此开始执行函数体代码。 注意:对函数指针进行算术运算也是没有意义的。 如何使用函数指针? ①定义合适类型的函数指针变量; int (*pfunc)(int, int); ②给函数指针变量赋值,使它指向某个函数入口; int example(int, int); pfunc = example; /*将函数入口地址赋给函数指针变量*/ 或者:pfunc = &example; /*函数名总是被编译器转换为函数指针(入口地址)来使用,因此与上面一句等价 */ ③使用函数指针来调用相应的函数; retval = pfunc(10, 16); 或者:retval = (*pfunc)(10, 16); 上面两句都与retval = example(10, 16);等价。 理解:一个指针变量p实际上也和普通的变量一样,要占存储空间(通常与平台的虚拟地址一样宽),也有其自身的存储地址&p;不同的是,在指针变量p的值有特殊的意义,它是另外一个变量或常量的地址值,也就是说,在地址为&p的存储单元上存放着另外一个数据的地址。因此,*p实际上是将p看作它指向的数据的地址来使用,*操作符是引用相应地址中的数据,也就是对地址为p的存储单元中存放的数据进行操作。 一个函数指针变量则更为特殊。比如上面的例子,pfunc变量本身的值是函数example()的入口地址。因此pfunc可以代替其所指函数的函数名来使用。至于*pfunc,如果按照上面的理解,它实际上是地址pfunc的内容,也即函数example()的入口地址的内容,就有点含糊了。不过,从另一方面来理解,如果使用pfunc = &example来初始化pfunc,则*pfunc == *(&example) == example,又与pfunc等价。因此,就有了两种使用函数指针来调用相应函数的形式。 值得注意的是,不可用*pfunc来对pfunc的值初始化。即*pfunc = example的写法是错误的。 为什么要使用函数指针? 前面介绍了函数指针的基本知识和使用规范。下面介绍函数指针的实际用途。不过首先要对前面的知识再做一个补充,因为下面的应用很可能用到这一特性。前面指出,除函数名之外的函数签名内容(函数返回值类型和形参列表)决定了函数指针的类型。实际上还有一种特殊的或说通用的函数指针,在定义这类函数指针时,只需要指定函数返回值类型,而留空形参列表,这样就可以指向返回值类型相同的所有函数。例如: int (*pfunc)(); 这样定义的pfunc就可以指向前面提到的func1和func2,因为他们都返回整型值。 注意: int (*pfunc)()与int (*pfunc)(void)不是一回事,后者不允许接受任何参数。 函数指针最常见的三个用途是: ①作为参数传递给其他函数。 这样可以把多个函数用一个函数体封装起来,得到一个具有多个函数功能的新函数,根据传递的函数指针变量值的不同,执行不同的函数功能。这是函数嵌套调用难以实现的。参数的传递可以由程序员设定,也可以由用户输入读取,因此具有较大的灵活性和交互性。 另外还可以用于回调函数。使用void配合,还可以将对不同数据类型的数据进行相同处理的多个函数封装为一个函数,增强函数的生命力。 ②用于散转程序。 这种程序首先建立一个函数表(实际上是一个函数指针数组),表中存放了各个函数的入口地址(或函数名),根据条件的设定来查表选择执行相应的函数。这样也可以将多个函数封装为一个函数或者程序,散转分支条件可以由程序员设定,也可以由用户输入读取,甚至是外设的某种特定状态(这种状态可以是不受人为控制的)。 ③实现C的面向对象的类的封装。 C语言中的struct与C++中的class有很大不同,除了缺省的成员属性外(struct的成员缺省为public的,可随意使用,而class成员缺省为private的),struct还很难实现类成员函数的封装。struct的成员一般都是数据成员,而非函数成员。因此,为了在C语言中,为某个struct定义一套自己的函数对结构数据成员进行操作,可以在struct结构体中增加函数指针变量成员,在初始化时使它指向特定函数即可。 应用举例: ①假设定义了四个函数:add(int, int)、sub(int, int)、mul(int, int)、div(int, int),可以将其封装为一个四则运算计算器函数: double calculator(int x, int y, int (*pfunc)(int, int)) { double result; result = pfunc(x, y); return result; } 又例如,在一个链表查询程序中,要通过比较节点的特征值来查询节点,不同类型的数据的比较方式不一样,整型等可以直接比较,字符串却要用专门的字符串操作函数,为了使代码可重用性更高,可以使用一个比较函数来代替各种不同数据类型的直接比较代码,同时,比较函数也必然是数据类型相关的,因此要使用void指针和函数指针来转换为类型无关的比较函数,根据相应的数据类型,调用相应的函数(传递相应的函数指针)。一个实例是: int (*compare)(void const *, void const *); 这个函数指针可以接受任意类型的数据的指针参数,同时返回int值作为比较结果标志。一个比较整型数据的比较函数是: int compare_ints(void const *a, void const *b) { if( *(int *)a == *(int *)b ) return 0; else return 1; } ②散转程序。通过一个转移表(函数指针数组)来实现。还是上面定义的四个四则运算函数,可以建立这样一个转移表(注意初始化该转移表的语句前面应有add等相应函数原型声明或定义): double (*calculator[])(int, int) = { add, sub, mul, div }; 这样,calculator[0] == add, calculator[1] == sub, ... 使用result = calculator[oper](x, y);就可以代替下面整个switch语句: switch( oper ) { case 0: result = add(x, y); break; case 1: result = sub(x, y); break; ... } ③C的面向对象化。一个对象包括数据和对数据的操作。C语言中的struct只有数据成员,因此要增加一些“伪数据成员”即函数指针来实现对数据的操作。例如: #ifndef C_Class #define C_Class struct #endif C_Class student{ C_Class student *student_this char name; int height; int gender; int classnum; ... void (*Oper)( C_Class student *student_this ); ... } 参考资料: ①《 Pointers on C 》,Kenneth A.Reek ②《 C程序设计(第二版)》,谭浩强 ③《 C语言嵌入式系统编程修炼之道 》 errno与错误处理 errno是什么? 在/usr/include/errno.h中,include了,在该文件中定义了不同的errno的值(错误类型编号)所对应的宏以及错误类型. 基本使用: #include extern int errno; 1.使用perror( const char *msg )函数来将错误类型所对应的错误信息以字符串形式打印到终端. 首先输出用户自定义的字符串msg(可以为空,即""),然后打印错误信息. 2.使用stderr( int errnum )将错误信息转换为字符串. 3.注意,必须在函数表明操作失败后立刻对errno的值进行检查以找出对应错误.在使用它之前必须总是先将其值copy到另外一个变量保存起来,因为很多函数(象fprintf之类)自身就可能会改变errno的值. func( ); errortype = errno; printf( "%d\n", errortype ); 或者: if( errortype == ... ) { do ... } else { do .... }
时间: 2024-10-10 04:18:20