| 风声竹影 的个人资料听风竹轩的书架日志列表网络 | 帮助 |
|
2月12日 void指针类型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)。 但是大名鼎鼎的GNU(GNU's Not Unix的缩写)则不这么认定,它指定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, 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为“抽象数据类型”)变量。 2月5日 C语言参考手册阅读笔记:预处理指令之#,##,可变参数收藏 --------------------------------------------------------------- 3.3.8 将记号转换为字符串 [C语言参考手册,p38(r54)] --------------------------------------------------------------- 标准C语言中有一种机制可以将宏参数(扩展之后)转换为字符串型常量。在此之前,编程人员要利用许多C语言预处理器中的漏洞以不同方式达到相同结果。 在标准C语言中,宏定义中出现的#记号被当作一元“字符串化”运算符,后面为宏正式参数名。宏扩展期间,#和正式参数名换成相应的包含在字符串引号当中的 实际参数。生成字符串时,记号参数表中的每个空白序列换成一个空格符,任何嵌人引号和反斜杠前面加上一个反斜杠以保留其在字符串中的含义。参数开头和末尾 的空白符忽略,因此空参数扩展为空字符串""(即使逗号之间有空白符)。 例:考虑TEST宏的标准c语言定义 #define TEST(a,b) printf(#a "<" #b "=%d\n", (a)<(b)) 语句TEST(0,0xFFFF);TEST('\n', 10);展开如下: printf("0" "<" "0xFFFF" "=%d\n", (0)<(0xFFFF)); printf("'\\n'" "<" "10 "=%d\n", ('\n')<(10)); --------------------------------------------------------------- 3.3.9 宏扩展中的记号合并 [C语言参考手册,p38(r54)] --------------------------------------------------------------- 标准C语言中合并记号形成新记号时,由宏定义中的合并运算符##控制。重新扫描更多宏之前,宏替换表中任何运算符##中间的两个记号合并成一个记号。因此必须有这种记号:##不能放在替换表开头或末尾。如果合并之后得不到有效记号,则结果是未定义的。 例子: #define TEMP(i) temp ## i TEMP(1) = TEMP(2 + k) + x; 预处理之后变成: temp1 = temp2 + k + x; 上例中,扩展TEMP()+x时可能出现一个奇妙的情形。宏定义是有效的,但##右边没有要组合的记号(除非遇到+,但这不是我们所要的)。要解决这个问 题,应把正式参数i看成是专为##扩展的特殊的“空”记号。这样,TEMP()+x扩展的结果将如我们预料的那样为temp+x。 不能用记号拼接产生通用字符名。 和宏参数转换成字符串时一样(见3.3.8节),编程人员可以利用许多非标准C语言实现中的漏洞获得这种合并功能。尽管C语言的原始定义明确地把宏体描述 成记号序列,而不是字符序列,但许多语言编译器把宏体当作字符序列一样扩展和重新扫描。这在编译器处理注释语句时更加明显,编译器将注释语句完全删除而不 是换成空格,因此一些巧妙编写的程序就可以利用这个特性。 例:考虑下列例子: #define INC ++ #define TAB internal_table #define INCTAB table_of_increments #define CONC(x,y) x/**/y CONC(INC,TAB) 标准C语言将CONC体解释为两个记号x和y,用空格分开(注释语句变成空格)。调用CONC(INC,TAB)扩展为两个记号INC TAB。但有些非标准C语言实现直接删除注释语句,然后重新扫描宏体中的记号,这样就把CONC(INC,TAB)扩展为一个记号INCTAB; ----------------------------------------------- 步骤 标准C语言扩展 可能的非标准扩展 ----------------------------------------------- 1 CONC(INC,TAB) CONC(INC,TAB) 2 INC/**/TAB INC/**/TAB 3 INC TAB INCTAB 4 ++ internal_table table_of_increments 测试代码: #define XVARNAME(i) x##i #define PXVARNAME(n) printf("x"#n" = %d\n",x##n) int k = 0; int b = 0; int XVARNAME(k)=12; PXVARNAME(k + 1); //xk + 1 = 13 PXVARNAME(k); //xk = 12 b = XVARNAME(k) + 0; printf("b = %d, k = %d\r\n", b, k); //b = 12, k = 0 b = XVARNAME(k + 10) + 0; printf("b = %d, k = %d\r\n", b, k); //b = 22, k = 0 --------------------------------------------------------------- 3.3.10 宏中的可变参数表 [C语言参考手册,p38(r54)] notice: Visual C++ 直至8.0版才开始支持C99的可变宏定义 --------------------------------------------------------------- #ifdef DEBUG #define myprintf(...) fprintf(stderr, __VA_ARGS__) #else #define myprintf(...) fprintf(__VA_ARGS__) #endif 特别地: #define make_em_str(...) #__VA_ARGS__ 下列调用 make_em_str(a,b,c,d) 展开成字符串 "a,b,c,d" 2月1日 memcpy和memmove的区别与实现区别:
void* memcpy(void* dest, void* source, size_t count) { void* ret = dest; //copy from lower address to higher address while (count--) *dest++ = *source;
}
memmove memmove - Copy source buffer to destination buffer void* memmove(void* dest, void* source, size_t count) { void* ret = dest;
{ //Non-Overlapping Buffers while (count --) *dest++ = *source++; } else { //Overlapping Buffers
source += count - 1; while (count--) *dest-- = *source--;l } return ret; }
void* mymemcpy( void* dest, const void* src, size_t count ) return dest; 宽字符处理函数函数与普通函数对照表 字符分类: iswalnum() isalnum() 测试字符是否为数字或字母 iswalpha() isalpha() 测试字符是否是字母 iswcntrl() iscntrl() 测试字符是否是控制符 iswdigit() isdigit() 测试字符是否为数字 iswgraph() isgraph() 测试字符是否是可见字符 iswlower() islower() 测试字符是否是小写字符 iswprint() isprint() 测试字符是否是可打印字符 iswpunct() ispunct() 测试字符是否是标点符号 iswspace() isspace() 测试字符是否是空白符号 iswupper() isupper() 测试字符是否是大写字符 iswxdigit() isxdigit() 测试字符是否是十六进制的数字![]() ![]() 大小写转换: towlower() tolower() 把字符转换为小写 towupper() toupper() 把字符转换为大写![]() ![]() 字符比较: wcscoll() strcoll() 比较字符串![]() ![]() 日期和时间转换: strftime() 根据指定的字符串格式和locale设置格式化日期和时间 wcsftime() 根据指定的字符串格式和locale设置格式化日期和时间, 并返回宽字符串 strptime() 根据指定格式把字符串转换为时间值, 是strftime的反过程![]() ![]() 打印和扫描字符串: fprintf() /fwprintf() 使用vararg参量的格式化输出 fscanf() /fwscanf() 格式化读入 printf() 使用vararg参量的格式化输出到标准输出 scanf() 从标准输入的格式化读入 sprintf() /swprintf() 根据vararg参量表格式化成字符串 sscanf() 以字符串作格式化读入 vfprintf() /vfwprintf() 使用stdarg参量表格式化输出到文件 vprintf() 使用stdarg参量表格式化输出到标准输出 vsprintf() /vswprintf() 格式化stdarg参量表并写到字符串![]() ![]() 数字转换: wcstod() strtod() 把宽字符的初始部分转换为双精度浮点数 wcstol() strtol() 把宽字符的初始部分转换为长整数 wcstoul() strtoul() 把宽字符的初始部分转换为无符号长整数![]() ![]() 多字节字符和宽字符转换及操作: mblen() 根据locale的设置确定字符的字节数 mbstowcs() 把多字节字符串转换为宽字符串 mbtowc() /btowc() 把多字节字符转换为宽字符 wcstombs() 把宽字符串转换为多字节字符串 wctomb() /wctob() 把宽字符转换为多字节字符![]() ![]() 输入和输出: fgetwc() fgetc() 从流中读入一个字符并转换为宽字符 fgetws() fgets() 从流中读入一个字符串并转换为宽字符串 fputwc() fputc() 把宽字符转换为多字节字符并且输出到标准输出 fputws() fputs() 把宽字符串转换为多字节字符并且输出到标准输出串 getwc() getc() 从标准输入中读取字符, 并且转换为宽字符 getwchar() getchar() 从标准输入中读取字符, 并且转换为宽字符 None gets() 使用fgetws() putwc() putc() 把宽字符转换成多字节字符并且写到标准输出 putwchar() putchar() 把宽字符转换成多字节字符并且写到标准输出 None puts() 使用fputws() ungetwc() ungetc() 把一个宽字符放回到输入流中![]() ![]() 字符串操作: wcscat() strcat() 把一个字符串接到另一个字符串的尾部 wcsncat() strncat() 类似于wcscat() , 而且指定粘接字符串的粘接长度. wcschr() strchr() 查找子字符串的第一个位置 wcsrchr() strrchr() 从尾部开始查找子字符串出现的第一个位置 wcspbrk() strpbrk() 从一字符字符串中查找另一字符串中任何一个字符第一次出现的位置 wcswcs() /wcsstr() strchr() 在一字符串中查找另一字符串第一次出现的位置 wcscspn() strcspn() 返回不包含第二个字符串的的初始数目 wcsspn() strspn() 返回包含第二个字符串的初始数目 wcscpy() strcpy() 拷贝字符串 wcsncpy() strncpy() 类似于wcscpy() , 同时指定拷贝的数目 wcscmp() strcmp() 比较两个宽字符串 wcsncmp() strncmp() 类似于wcscmp() , 还要指定比较字符字符串的数目 wcslen() strlen() 获得宽字符串的数目 wcstok() strtok() 根据标示符把宽字符串分解成一系列字符串 wcswidth() None 获得宽字符串的宽度 wcwidth() None 获得宽字符的宽度![]() ![]() 另外还有对应于memory操作的 wmemcpy() , wmemchr() , wmemcmp() ,wmemmove() , wmemset() |
|
|