| 风声竹影's profile听风竹轩的书架BlogListsNetwork | Help |
|
听风竹轩的书架April 04 Java运行时多态性 运行时多态性是面向对象程序设计代码重用的一个最强大机制,动态性的概念也可以被说成“一个接口,多个方法”。Java实现运行时多态性的基础是动态
方法调度,它是一种在运行时而不是在编译期调用重载方法的机制,下面就继承和接口实现两方面谈谈java运行时多态性的实现。 一、通过继承中超类对象引用变量引用子类对象来实现 举例说明:
运行结果为: This is subB This is subC 上述代码中subB和subC是超类superA的子类,我们在类Test中声明了3个引用变量a, b, c,通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用。也许有人会问:“为什么(1)和(2)不输出:This is superA”。java 的这种机制遵循一个原则:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是 在超类中定义过的,也就是说被子类覆盖的方法。 所以,不要被上例中(1)和(2)所迷惑,虽然写成a.fun(),但是由于 (1)中的a被b赋值,指向了子类subB的一个实例,因而(1)所调用的fun()实际上是子类subB的成员方法fun(),它覆盖了超类 superA的成员方法fun();同样(2)调用的是子类subC的成员方法fun()。 另外,如果子类继承的超类是一个抽象类,虽然抽象类不能通过new操作符实例化,但是可以创建抽象类的对象引用指向子类对象,以实现运行时多态性。具体的实现方法同上例。 不过,抽象类的子类必须覆盖实现超类中的所有的抽象方法,否则子类必须被abstract修饰符修饰,当然也就不能被实例化了。 二、通过接口类型变量引用实现接口的类的对象来实现 接口的灵活性就在于“规定一个类必须做什么,而不管你如何做”。我们可以定义一个接口类型的引用变量来引用实现接口的类的实例,当这个引用调用方法时,它会根据实际引用的类的实例来判断具体调用哪个方法,这和上述的超类对象引用访问子类对象的机制相似。 举例说明:
输出结果为: This is B This is C 上例中类B和类C是实现接口InterA的两个类,分别实现了接口的方法fun(),通过将类B和类C的实例赋给接口引用a而实现了方法在运行时的动态绑定,充分利用了“一个接口,多个方法”展示了Java的动态多态性。 需要注意的一点是:Java在利用接口变量调用其实现类的对象的方法时,该方法必须已经在接口中被声明,而且在接口的实现类中该实现方法的类型和参数必须与接口中所定义的精确匹配。 结束语 以上就是java运行时多态性的实现方法,大家在编程过程中可以灵活运用,但是在性能要求较高的代码中不提倡运用运行时多态,毕竟Java的运行时动态方法调用较之普通的方法调用的系统开销是比较大的。 March 23 arm汇编语言调用C函数之参数传递
February 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为“抽象数据类型”)变量。 February 05 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" February 01 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() January 31 memset ,memcpy 和strcpy 的根本区别? 声明: 以下内容为网络整理的结果! 它们用处不同,但大部分情况下可以完成相同的要求。 strcpy 原型:extern char *strcpy(char *dest,char *src); 用法:#include <string.h> 功能:把src所指由NULL结束的字符串复制到dest所指的数组中。 说明:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。 返回指向dest的指针。 例:char a[100],b[50];strcpy(a,b);如用strcpy(b,a),要注意a中的字符串长度(第一个‘\0’之前)是否超过50位,如超过,则会造成b的内存地址溢出。 memcpy 原型:extern void *memcpy(void *dest, void *src, unsigned int count); 用法:#include <string.h> 功能:由src所指内存区域复制count个字节到dest所指内存区域。 说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。可以拿它拷贝任何数据类型的对象。 举例:char a[100],b[50]; memcpy(b, a, sizeof(b));注意如用sizeof(a),会造成b的内存地址溢出。 memset 原型:extern void *memset(void *buffer, int c, int count); 用法:#include <string.h> 功能:把buffer所指内存区域的前count个字节设置成字符c。 说明:返回指向buffer的指针。用来对一段内存空间全部设置为某个字符。 举例:char a[100];memset(a, '\0', sizeof(a)); memset可以方便的清空一个结构类型的变量或数组。 如: struct sample_struct { char csName[16]; int iSeq; int iType; }; 对于变量 struct sample_strcut stTest; 一般情况下,清空stTest的方法: stTest.csName[0]='\0'; stTest.iSeq=0; stTest.iType=0; 用memset就非常方便: memset(&stTest,0,sizeof(struct sample_struct)); 如果是数组: struct sample_struct TEST[10]; 则 memset(TEST,0,sizeof(struct sample_struct)*10); 对这个问题有疑问,不是对函数的疑问,而是因为没有弄懂mem和str的区别。 mem是一段内存,他的长度,必须你自己记住 str也是一段内存,不过它的长度,你不用记,随时都可以计算出来 所以memcpy需要第三个参数,而strcpy不需要 |
|||||||||||
|
|