c++达到什么进攻战术基础配合包括能进游戏公司,进公司后一般做什么,很怕进去什么东西都不会写,有人教吗这个

17人已关注
行有不得者,反求诸己前言:这个问题放了好久,主要是因为自己本身不是学游戏的。了解不深。所以我就去跟做游戏的同学和老师,深入了解探讨了下。以下观点有一部分是采集自学校老师的意见,不全部都是自己的观点。如果有不对的地方,请大家指正!言归正传,C++这门语言从诞生到今天已经经历了将近30个年头。不可否认,它的学习难度都比其它语言较高。而它的学习难度,主要来自于它的复杂性。现在C++的使用范围比以前已经少了很多,java、C#、python等语言在很多方面已经可以代替C++。但是也有很多地方是其他语言完全无法替代的,主要集中在需要运行效率比较高的行业;比如游戏、高效的服务器。  现在学习java、C#等语言的人数远远高于C++,主要是因为C++的入门门槛太高。  所以在此我们不讨论具体的技术细节,只说我们在哪个阶段应该学习什么东西。有个系统的规划。  一、技术能力  1. 语言我们要成为一个程序员,学的东西会很多很杂,但是最开始一定要从语言开始学习。学习语言的目的就是打好基础,特别是自学的,一定规划好自己的学习路线,一步一个脚印学习下去。学习语言最关键的莫过于多读书,但不能随便找一本C++书籍就去读了。对于学习C++书籍的推荐,见我另一个问题的答案:自己根据自己学习的阶段去读这些书,不要盲目敲代码!  2. GUIC++方面的GUI库有很多种,比如MFC、WTL、wxWidgets、QT。这些GUI库都各有自己的特点,其实我们只要先了解一种就可以了,只要深入了解了一种GUI库。需要的时候再学习其他的就够了,本质上都差不多,很快就可以上手了。MFC虽然设计上有很多问题,但是作为入门还是不错的,而且学习资料很多,碰到问题也好解决。学习MFC的推荐下面这个链接,里面有别人学习MFC游戏开发的笔记,可以多学习学习。  3. 数据结构和算法很多人都忽视了数据结构和算法方面的知识,尤其是一些编程语言的库做得非常好;几乎不需要自己去实现一些数据结构和算法,导致现在很多程序员不重视甚至忽略这方面的知识。但是,当我们想让我们的程序跑的更快、内存占用更少的时候,这些知识就非常非常重要了。很多程序员都是刚开始的时候不重视这些,但是工作几年后又来补习这些知识。最开始可能不需要学习的太深入,但是基本的数据结构和算法一定要知道。推荐《数据结构(C语言版)》。在数据结构和算法的学习中,最好不要去关心面向对象方面的技巧,就用C语言来实现。这样能更关注于算法本身的内容。另外,如果有精力剩余的话,推荐看一本书:(下面是亚马逊关于这本书的链接,有兴趣的可以点进去购买。)原书名:Introduction to Algorithms 中文名: 作者:Thomas H.Cormen,Charles E.Leiserson,Ronald L.Rivest,Clifford Stein 本书俗称CLRS(作者名字的简写),算法的经典教材,堪称算法分析著作中的“独孤九剑”。作者之一Ronald L.Rivest 由于其在公开秘钥密码算法RSA上的贡献获得了ACM图灵奖。全书内容全面,结构清晰,6个部分1000多页把数据结构算法的主要内容都包含了。作者用图表,伪码解释每一个算法,通俗易懂而不失严谨性,英文比较简单,语言流畅。因此,这本书更适合初学者,不要求读者拥有很强的数学背景和丰富的编程经验。书中习题安排合理,难度适中,在网上有全部习题的答案;网上还有作者在MIT讲述本书的课程的录像,可谓资源丰富。  4. 数据库学习数据库的基础知识,并且掌握一种数据库使用。推荐使用Oracle,而且最好不要用一些封装好的接口。而应该直接用Oracle提供的数据库API,可能对数据库了解的会更深入。  5. 并行CPU主频已经不能遵循摩尔定律了,现在CPU发展的趋势是多核心。无论是多线程,还是多进程,都是为了更好的利用CPU的性能,提供更好的用户体验。这就要求我们如果要写出高效的应用程序,必然要涉及到并行计算。多花些精力在并行计算上吧,一定会有丰富的回报。  6. 网络编程这里所指的网络编程是指socket编程。现在C++的应用很多都是在做服务器开发,如何开发一个高并发、大吞吐量、高稳定性的服务器就是我们要考虑的了。  7. 设计模式  设计模式不是具体的技术,更多的是如何让代码更容易阅读、更好扩展、更容易使用。  8. 库的使用C++标准库仅仅提供了一些很基本的功能,所以我们经常会引入一些第三方库。最著名的恐怕就是被称为准标准库的boost库,它提供了我们编程中用到的各方面的技术,文本处理、算法、网络、多线程、图像处理等等,几乎无所不包。其它也有一些专著于某一方面的库,比如ACE是网络通信方面的,TinyXML是解析xml的,OGRE是图形渲染方面的。  9. 操作系统的知识程序员需要了解的操作系统知识和普通用户是不一样的,一个高手是需要深入了解操作系统的方方面面,而不是停留在使用层面。至于应该了解哪些知识,Windows上的去看《Windows核心编程》,Linux的去看《深入理解Linux内核》,应该可以知道自己应该学什么了。以上都是学习C++游戏开发的前期准备,也就是打好基础。上面都是基本功,看起来有点多,但是磨刀不误砍柴工。这对于你后面学习游戏开发有非常大的帮助。举个我身边的例子,他就是先学习数据结构和算法,去参加ACM竞赛,后面转的C++游戏开发。学的非常快。这就是基本功扎实的好处!!!!二、图形图像处理:如果你不想开发游戏引擎的话,简单了解OpenGL或者DirectX,反之深入了解。 DirectX是微软的多媒体编程接口,在Windows的平台下,配合支持DX的高端显卡能把游戏场景的特效等等发挥得淋漓尽致,而OpenGL是一个跨平台的编程接口,是硬件无关的编程接口。前者主要Windows下开发游戏,后者主要是移动端。如果是开发iOS游戏,需要了解Object C,有些小游戏使用JAVA和Flash,网页游戏还可能需要了解 HTML5, DIV, CSS等知识;网络游戏还需要了解网络编程,加解密和数据库知识。另外很多游戏开发公司都可能会使用不同游戏开发引擎,这些引擎会把上述的知识点封装;使得你可以不需要了解底层的细节,调用它指定的API就可以实现一些功能。不过你如果了解底层细节,显然对调试问题更有好处。建议多看一下cocos2d-x官网的讨论和API范例。再往上走的话,可能会需要了解不同的显卡的特点,扬长避短。了解一些不常用的脚本编程技术可以减少你架构编译调试测试的时间。  三、项目经验了解上面的这些知识后,不多加练练可没用。特别是对游戏开发来说,项目经验更加重要。一个好的项目可以让你把各种技术进行综合运用,并且能学到一些新的知识。比如做播放器的要学习编解码器方面的知识。做游戏也是一样,要学习图形方面的知识,很多人会忽略项目的经验,而单纯的谈技术能力,这是错误的。其实这就是理论和实践的关系,技术就是理论知识,做项目就是实践,理论对实践有指导作用。实践能加深我们对理论的深入理解。建议前期多去做几个小游戏试试手,然后慢慢加大游戏的难度。(ps:网上有特别多的开发游戏教程,一开始照着教程,边学边做。)最后尝试自己想个游戏做出来或者去找个游戏公司实习。  总而言之,道路还是很艰辛的,且行且珍惜!!!前言:这个问题放了好久,主要是因为自己本身不是学游戏的。了解不深。所以我就去跟做游戏的同学和老师,深入了解探讨了下。以下观点有一部分是采集自学校老师的意见,不全部都是自己的观点。如果有不对的地方,请大家指正!言归正传,C++这门语言从诞生到今天已经经历了将近30个年头。不可否认,它的学习难度都比其它语言较高。而它的学习难度,主要来自于它的复杂性。现在C++的使用范围比以前已经少了很多,java、C#、python等语言在很多方面已经可以代替C++。但是也有很多地方是其他语言完全无法替代的,主要集中在需要运行效率比较高的行业;比如游戏、高效的服务器。  现在学习java、C#等语言的人数远远高于C++,主要是因为C++的入门门槛太高。  所以在此我们不讨论具体的技术细节,只说我们在哪个阶段应该学习什么东西。有个系统的规划。  一、技术能力  1. 语言我们要成为一个程序员,学的东西会很多很杂,但是最开始一定要从语言开始学习。学习语言的目的就是打好基础,特别是自学的,一定规划好自己的学习路线,一步一个脚印学习下去。学习语言最关键的莫过于多读书,但不能随便找一本C++书籍就去读了。对于学习C++书籍的推荐,见我另一个问题的答案:自己根据自己学习的阶段去读这些书,不要盲目敲代码!  2. GUIC++方面的GUI库有很多种,比如MFC、WTL、wxWidgets、QT。这些GUI库都各有自己的特点,其实我们只要先了解一种就可以了,只要深入了解了一种GUI库。需要的时候再学习其他的就够了,本质上都差不多,很快就可以上手了。MFC虽然设计上有很多问题,但是作为入门还是不错的,而且学习资料很多,碰到问题也好解决。学习MFC的推荐下面这个链接,里面有别人学习MFC游戏开发的笔记,可以多学习学习。  3. 数据结构和算法很多人都忽视了数据结构和算法方面的知识,尤其是一些编程语言的库做得非常好;几乎不需要自己去实现一些数据结构和算法,导致现在很多程序员不重视甚至忽略这方面的知识。但是,当我们想让我们的程序跑的更快、内存占用更少的时候,这些知识就非常非常重要了。很多程序员都是刚开始的时候不重视这些,但是工作几年后又来补习这些知识。最开始可能不需要学习的太深入,但是基本的数据结构和算法一定要知道。推荐《数据结构(C语言版)》。在数据结构和算法的学习中,最好不要去关心面向对象方面的技巧,就用C语言来实现。这样能更关注于算法本身的内容。另外,如果有精力剩余的话,推荐看一本书:(下面是亚马逊关于这本书的链接,有兴趣的可以点进去购买。)原书名:Introduction to Algorithms 中文名: 作者:Thomas H.Cormen,Charles E.Leiserson,Ronald L.Rivest,Clifford Stein 本书俗称CLRS(作者名字的简写),算法的经典教材,堪称算法分析著作中的“独孤九剑”。作者之一Ronald L.Rivest 由于其在公开秘钥密码算法RSA上的贡献获得了ACM图灵奖。全书内容全面,结构清晰,6个部分1000多页把数据结构算法的主要内容都包含了。作者用图表,伪码解释每一个算法,通俗易懂而不失严谨性,英文比较简单,语言流畅。因此,这本书更适合初学者,不要求读者拥有很强的数学背景和丰富的编程经验。书中习题安排合理,难度适中,在网上有全部习题的答案;网上还有作者在MIT讲述本书的课程的录像,可谓资源丰富。  4. 数据库学习数据库的基础知识,并且掌握一种数据库使用。推荐使用Oracle,而且最好不要用一些封装好的接口。而应该直接用Oracle提供的数据库API,可能对数据库了解的会更深入。  5. 并行CPU主频已经不能遵循摩尔定律了,现在CPU发展的趋势是多核心。无论是多线程,还是多进程,都是为了更好的利用CPU的性能,提供更好的用户体验。这就要求我们如果要写出高效的应用程序,必然要涉及到并行计算。多花些精力在并行计算上吧,一定会有丰富的回报。  6. 网络编程这里所指的网络编程是指socket编程。现在C++的应用很多都是在做服务器开发,如何开发一个高并发、大吞吐量、高稳定性的服务器就是我们要考虑的了。  7. 设计模式  设计模式不是具体的技术,更多的是如何让代码更容易阅读、更好扩展、更容易使用。  8. 库的使用C++标准库仅仅提供了一些很基本的功能,所以我们经常会引入一些第三方库。最著名的恐怕就是被称为准标准库的boost库,它提供了我们编程中用到的各方面的技术,文本处理、算法、网络、多线程、图像处理等等,几乎无所不包。其它也有一些专著于某一方面的库,比如ACE是网络通信方面的,TinyXML是解析xml的,OGRE是图形渲染方面的。  9. 操作系统的知识程序员需要了解的操作系统知识和普通用户是不一样的,一个高手是需要深入了解操作系统的方方面面,而不是停留在使用层面。至于应该了解哪些知识,Windows上的去看《Windows核心编程》,Linux的去看《深入理解Linux内核》,应该可以知道自己应该学什么了。以上都是学习C++游戏开发的前期准备,也就是打好基础。上面都是基本功,看起来有点多,但是磨刀不误砍柴工。这对于你后面学习游戏开发有非常大的帮助。举个我身边的例子,他就是先学习数据结构和算法,去参加ACM竞赛,后面转的C++游戏开发。学的非常快。这就是基本功扎实的好处!!!!二、图形图像处理:如果你不想开发游戏引擎的话,简单了解OpenGL或者DirectX,反之深入了解。 DirectX是微软的多媒体编程接口,在Windows的平台下,配合支持DX的高端显卡能把游戏场景的特效等等发挥得淋漓尽致,而OpenGL是一个跨平台的编程接口,是硬件无关的编程接口。前者主要Windows下开发游戏,后者主要是移动端。如果是开发iOS游戏,需要了解Object C,有些小游戏使用JAVA和Flash,网页游戏还可能需要了解 HTML5, DIV, CSS等知识;网络游戏还需要了解网络编程,加解密和数据库知识。另外很多游戏开发公司都可能会使用不同游戏开发引擎,这些引擎会把上述的知识点封装;使得你可以不需要了解底层的细节,调用它指定的API就可以实现一些功能。不过你如果了解底层细节,显然对调试问题更有好处。建议多看一下cocos2d-x官网的讨论和API范例。再往上走的话,可能会需要了解不同的显卡的特点,扬长避短。了解一些不常用的脚本编程技术可以减少你架构编译调试测试的时间。  三、项目经验了解上面的这些知识后,不多加练练可没用。特别是对游戏开发来说,项目经验更加重要。一个好的项目可以让你把各种技术进行综合运用,并且能学到一些新的知识。比如做播放器的要学习编解码器方面的知识。做游戏也是一样,要学习图形方面的知识,很多人会忽略项目的经验,而单纯的谈技术能力,这是错误的。其实这就是理论和实践的关系,技术就是理论知识,做项目就是实践,理论对实践有指导作用。实践能加深我们对理论的深入理解。建议前期多去做几个小游戏试试手,然后慢慢加大游戏的难度。(ps:网上有特别多的开发游戏教程,一开始照着教程,边学边做。)最后尝试自己想个游戏做出来或者去找个游戏公司实习。  总而言之,道路还是很艰辛的,且行且珍惜!!!
bravo!不是有一个笑话吗:&招聘文案上的:精通c++. (微笑)我一开始也往c++上使劲过。一开始的课程都感觉so easy,到后面的经历了windows编程和游戏开发这种课程的时候,我毅然决然的投入了web开发的队伍。不过我们老师说了,c++虽然很难学,但是学好了就可以一直用很久。而java这种技术,更新特别快,你之后的学习是一个一直动态进阶的过程, java学习是一条斜线,而c++的是一个曲线。如果真的对c++感兴趣的话真的可以坚持一把,这年头会写c++的越来越少了。。。不是有一个笑话吗:&招聘文案上的:精通c++. (微笑)我一开始也往c++上使劲过。一开始的课程都感觉so easy,到后面的经历了windows编程和游戏开发这种课程的时候,我毅然决然的投入了web开发的队伍。不过我们老师说了,c++虽然很难学,但是学好了就可以一直用很久。而java这种技术,更新特别快,你之后的学习是一个一直动态进阶的过程, java学习是一条斜线,而c++的是一个曲线。如果真的对c++感兴趣的话真的可以坚持一把,这年头会写c++的越来越少了。。。
昆明理工大学津桥学院
Breath And Life首先谢邀,我是学JAVA的,该怎么说呢,对C++真的不敢乱评论,只能说搞游戏还有很多的路,我也接触过unity之类的,感觉更好上手,最后说一句,任何一门语言学精了都是前途不可限量的,如果真的确定走C++这条路,那就坚持下去好好学,编程本来就难,c++比起其他语言更难学,但是学好了就会很牛,我感觉现在学C++的少了,你一旦通过招聘了,你的待遇就很高了首先谢邀,我是学JAVA的,该怎么说呢,对C++真的不敢乱评论,只能说搞游戏还有很多的路,我也接触过unity之类的,感觉更好上手,最后说一句,任何一门语言学精了都是前途不可限量的,如果真的确定走C++这条路,那就坚持下去好好学,编程本来就难,c++比起其他语言更难学,但是学好了就会很牛,我感觉现在学C++的少了,你一旦通过招聘了,你的待遇就很高了
后可以回答该问题
相关标签:
相关标签:
关注我们咨询服务合作法律法规京ICP备号
下载申请方APP
即刻拥有你的学业规划助手C++基础资料|开发技术 - 美国主机支持论坛
查看完整版本: [--
&Pages: ( 3 total )
C++基础资料
先后看过几次关于C++类型转换的资料,甚至还用过几次,可对其了解还是很模糊,本次特别予以总结。不是随便写的,真的是用心写的,姑且看吧。C++命名的强制类型转换也称作新式类型转换,也是C语言类型转换的一个演进。看一个C语言的类型转换:1char *hopeItWorks = (char *)0x00ff0000;《C++必知必会》一书把该种类型转换描述成“龌龊”,因为这种类型转换相对于C++的类型转换来说力度要大,大到有时你并不清楚具体发生了哪些转换,即有可能转换即使超出了你的期望而没有任何提醒,最终导致程序运行失败。个人观点,该书作者(译者)描述太过于夸张了,C语言是个奔放的语言,其设计理念之一就是:程序员清楚自已在做什么。 C++的类型转换其实把C语言类型转换细分了一下,一分为四。(当然,同时兼容老式类型转换)1、const_cast该操作符允许添加或移除表达式中类型的const或volatiel修饰符:1const Person *getEmployee()2{3&&&&// do some thing and return const Person *4}5Person *anEmployee = const_cast&Person *&(getEmployee()); // C++类型转换6Person *anEmployee = (Person *)getEmployee();// C类型转换上面给出两种类型转换,都是去掉函数所返回指针的const属性,表面上看是一样的,但是如果函数发生改变后,就能看出这两种转换的差异:1const Employee *getEmployee() // 返回类型变了2{3&&&&// do some thing and return const Employee *4}5Person *anEmployee = const_cast&Person *&(getEmployee()); // C++类型转换,编译提示错误6Person *anEmployee = (Person *)getEmployee();// C类型转换,编译不提示错误这里函数返回类型发生改变,在const_cast试图将const Employee * 转换为Employee *时就已经超出了其能力范围,会在编译阶段给出提示,这往往是很有用的,当然,C++可以做到能力更强的类型转换,且向下看。 2、static_cast编译器隐式执行的任何类型转换都可以由static_cast显式完成:1double d = 97.02char ch = static_cast&char&d;//没有编译告警对于从一个较大的算式类型到一个较小类型的赋值,编译器通常产生警告,当我们显式地提供强制类型转换时,警告信息就会关闭。此种类型转换告诉编译器:我想要一个静静的(static)转换,你懂的,请闭嘴。静态转换常用于基类的指针或引用,转型为一个派生类的指针为引用。需要注意的是这不是一个非常安全的转换,损失的精度需要程序员去考虑。使用此转换的一个考虑是:可跨平台移植。3、reinterpret_cast顾名思义,重新解释。它从位(bit)的角度来看待一个对象,从而允许将一个东西看作另一个完全不同的东西:1char *hopeItWorks = reinterpret_cast&char *&(0x00ff0000); // 把int假装成指针2int *hopeless = reinterpret_cast&int *&(hopeItWorks);//把char *假装成int *这是个非常危险的转换,在C++类型转换里面威力也最大,非常容易出错。举个例子,一本横向排版的书,硬是按照竖向排版的方式进行解读,一般是读不出合理的内容。给出一种几乎是错误的转换:1int *2char *pc = reinterpret_cast&char *&(ip);3std::string str(pc); // 使用pc初始化一个string 对象,很可能出现运行时错误4、dynamic_cast顾名思义,动态的转换。大家知道,C++是具有多态特性的,该种转换仅用于对多态类型进行向下转型(也就是说,被转型的表达式的类型,必须是一个指向带有虚函数的类类型的指针)。上面讲的static_cast也可以从基类指换到派生类,但是不安全的。dynamic_cast执行运行期检查工作,来判定转型的正常性,当然是要会出代价的,好比,多态性也会带来性能开销一样。01const Shape *getNextShape()02{03&&&&// do some thing and return const Shape *04}05const Circle *cp = dynamic_cast&const Circle *&(getNextShape());06if(cp)07{08&&&&...09}10注: Circle类派生于Shape类。至此,C++四种转换都讲完了。总结一下,C++类型转换其实是对C语言类型转换的细分及扩充(扩充类类型之间的转换)。《C++Primer》中有一句特别好:强制类型转换关闭或挂起了正常的类型检查。强烈建议程序员避免使用强制类型转换,不依赖强制类型转换也能写出很好的C++程序。
“It is often convenient to define a name for a pointer to function type to avoid using the somewhat nonobvious declaration syntax all the time.” --《C++程序设计语言》// =====================================================================================// //&&&&&& Filename:&&FunPoint.C// //&&&&Description:&&// //&&&&&&&&Version:&&1.0//&&&&&&&&Created:&&日 22时19分15秒//&&&&&& Revision:&&none//&&&&&& Compiler:&&g++// //&&&&&&&& Author:&&宋志民 (Song Zhi-Min), // // =====================================================================================#include &iostream&typedef bool (*COMPARE)(int a, int b);bool greater_than(int a, int b){&&&&return a &}bool lesser_than(int a, int b){&&&&return a &}bool equal(int a, int b){&&&&return a ==}bool compare(int a, int b, COMPARE cmp){&&&&return cmp(a, b);}int main(){&&&&int a = 5;&&&&int b = 6;&&&&cout && &a = & && a &&&&&&cout && &b = & && b &&&&&&cout &&&&&&cout && &a & b is & && compare(a, b, &greater_than) &&&&&&cout && &a & b is & && compare(a, b, &lesser_than) &&&&&&cout && &a = b is & && compare(a, b, &equal) &&&&&&cout &&&&&&COMPARE compare_group[] = {&greater_than, &lesser_than, &equal};&&&&COMPARE* which = compare_&&&&cout && &a & b is & && compare(a, b, which[0]) &&&&&&cout && &a & b is & && compare(a, b, which[1]) &&&&&&cout && &a = b is & && compare(a, b, which[2]) &&}
标准C++的一个新特征是RTTI(Run-Time Type Information运行时类型信息),它为程序在运行时确定对象类型,提供了一种标准方法。在标准C++中,有三个支持RTTI的元素:1.&&&&&&&& 关键字dynamic_cast(动态强制转换):操作符dynamic_cast将一个指向基类的指针转换为一个指向派生类的指针(如果不能正确转换,则返回0——空指针),格式为:dynamic_cast & type-id & ( exdivssion )dynamic_cast在转化过程中要用到相关类的类型信息类type_info中的信息。该关键字在前面的小小节1.2)中已经介绍过。2.&&&&&&&& 关键字typeid(类型标识符):用来检测指针类型(返回type_info类对象的指针),格式为:typeid ( exdivssion ) 或 typeid ( type-id )其中,exdivssion为结果为对象的表达式,type-id为类名。3.&&&&&&&& 类type_info(类型信息):存储特定类型的有关信息,定义在&typeinfo&头文件中。type_info类的具体内容由编译器实现来决定,但是至少必须包含返回字符串的name()成员函数。下面是type_info类的VC05实现版本:class type_info { // VC05中定义的简化public:&&&&virtual ~type_info();&&&&bool operator==(const type_info& rhs)&&&&bool operator!=(const type_info& rhs)&&&&int before(const type_info& rhs)&&&&const char* name()&&&&const char* raw_name()private:&&&&void *_m_&&&&char _m_d_name[1];&&&&type_info(const type_info& rhs);&&&&type_info& operator=(const type_info& rhs);&&&&static const char *_Name_base(const type_info *prhs, __type_info_node* __ptype_info_node);&&&&static void _Type_info_dtor(type_info *prhs);};例如:(可建立一个名为tmp的“Visual C++/常规/空项目”型项目,将如下两个文件加入到该项目中)// tmp.htemplate&class T& class A { }; // tmp.cpp#include &typeinfo.h&#include &iostream&#include &tmp.h& int main( ){&&&&&& A&int&&&&&&& A&char&&&&&&& cout && typeid(a).name() &&&&&&&& cout && typeid(b).name() &&&&&&&& if (typeid(a) == typeid(b)) cout && &a==b& &&&&&&&& else cout && &a!=b& && endl &&&&&&&& cout && &&&&&&&&&&&& cout && typeid(int).name() &&&&&&&& cout && typeid(i).name() &&&&&&&& if (typeid(int) == typeid(i)) cout && &typeid(int) = typeid(i)& &&&&&&&& else cout && &typeid(int) != typeid(i)&&&&&&&&& cout &&} 输出结果为:class A&int&class A&char&a!=b intinttypeid(int) = typeid(i) 注意:只有对包含虚函数的抽象类层次,使用RTTI才有实际意义。8)类型名(typename)对于有的嵌套类中的标识符,本来应该作为类型来处理,但是编译器并不知道这一点,而可能把它当成了静态变量。对模板中出现的一个标识符,若编译器既可以把它当作一个类型,又可以把它视为一个变量、对象、枚举、函数或模板时,则编译器一般不会认为这个标识符是类型,而认为它是一个其他元素(例如是变量或对象)。解决办法是,使用标准C++新增加的关键字typename,来明确告诉编译器,它后面的标识符是一个类型名,而不是其他什么东西。例如:template&class T& class X {&&&&&& typename T:: // 如果没有typename来说明,编译器会将T::id当成静态变量public:&&&&&& void f ( ) { i.g( ); }};class Y {public:&&&&&& class id {&&&&&& public:&&&&&&&&&&&&&&void g( ) { }&&&&&& };};int main ( ) {&&&&&& X&Y&&&&&&& xy.f ( );}最后一种用法是说,可以用typename来代替模板声明中的类型参数class,即:可将template&class T& ……改为template&typename T& ……而且这样更名符其实。因为除了类类型外,基本数据类型和结构等类型,也是可以作为模板的类型参数的。例如:(能够打印任意标准C++序列容器中的数据的函数模板)// PrintSeq.cpp#include &iostream&#include &list&#include &memory&#include &vector& template&class T, template&class U, class = allocator&U& & class Seq&void printSeq(Seq&T&& seq) {&&&&&& for (typename Seq&T&::iterator b = seq.begin(); b != seq.end(); b++)&&&&&&&&&&&&&&cout && *b &&} int main ( ) {&&&&&& // 处理矢量&&&&&& vector&int&&&&&&& v.push_back(1);&&v.push_back(2);&&&&&& printSeq(v);&&&&&& // 处理表&&&&&& list&int&&&&&&& lst.push_back(3);&&&&&&&& lst.push_back(4);&&&&&& printSeq(lst);}输出为:1234注意:关键字typename并不能创建一个新类型名,它只是通知编译器,将标识符解释为类型。若想创建一个新类型名,你可以使用关键字typedef。例如typename Seq&T&::iterator It; // 告诉编译器iterator是类型,It是该类型的变量typedef typename Seq&T&::iterator It; // 创建了一个与iterator等价的新类型名It
指针和引用的区别(1)引用总是指向一个对象,没有所谓的 null reference .所有当有可能指向一个对象也由可能不指向对象则必须使用 指针. 由于C++ 要求 reference 总是指向一个对象所以 reference要求有初值. String & rs = string1; 由于没有所谓的 null reference 所以所以在使用前不需要进行测试其是否有值.,而使用指针则需要测试其的有效性. (2)指针可以被重新赋值而reference则总是指向最初或地的对象. (3)必须使用reference的场合. Operator[] 操作符 由于该操作符很特别地必须返回 [能够被当做assignment 赋值对象] 的东西,所以需要给他返回一个 reference. (4)其实引用在函数的参数中使用很经常. void Get***(const int& a) //这样使用了引用有可以保证不修改被引用的值 { } 引用和指针★ 相同点:1. 都是地址的概念;指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。★ 区别:1. 指针是一个实体,而引用仅是个别名;2. 引用使用时无需解引用(*),指针需要解引用;3. 引用只能在定义时被初始化一次,之后不可变;指针可变;引用“从一而终” ^_^4. 引用没有 const,指针有 const,const 的指针不可变;5. 引用不能为空,指针可以为空;6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;typeid(T) == typeid(T&) 恒为真,sizeof(T) == sizeof(T&) 恒为真,但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。7. 指针和引用的自增(++)运算意义不一样;★ 联系1. 引用在语言内部用指针实现(如何实现?)。2. 对一般应用而言,把引用理解为指针,不会犯严重语义错误。引用是操作受限了的指针(仅容许取内容操作)。引用是C++中的概念,初学者容易把引用和指针混淆一起。一下程序中,n 是m 的一 个引用(reference),m 是被引用物(referent)。
int &n = n 相当于m 的别名(绰号),对n 的任何操作就是对m 的操作。例如有人名叫王小毛, 他的绰号是“三毛”。说“三毛”怎么怎么的,其实就是对王小毛说三道四。所以n 既不 是m 的拷贝,也不是指向m 的指针,其实n 就是m 它自己。 引用的一些规则如下: (1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。 (2)不能有NULL 引用,引用必须与合法的存储单元关联(指针则可以是NULL)。 (3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。 以下示例程序中,k 被初始化为i 的引用。语句k = j 并不能将k 修改成为j 的引 用,只是把k 的值改变成为6。由于k 是i 的引用,所以i 的值也变成了6。 int i = 5; int j = 6; int &k = k = // k 和i 的值都变成了6; 上面的程序看起来象在玩文字游戏,没有体现出引用的价值。引用的主要功能是传 递函数的参数和返回值。C++语言中,函数的参数和返回值的传递方式有三种:值传递、 指针传递和引用传递。 以下是“值传递”的示例程序。由于Func1 函数体内的x 是外部变量n 的一份拷贝, 改变x 的值不会影响n, 所以n 的值仍然是0。 void Func1(int x) { x = x + 10; } int n = 0; Func1(n); cout && “n = ” && n &&// n = 0 以下是“指针传递”的示例程序。由于Func2 函数体内的x 是指向外部变量n 的指 针,改变该指针的内容将导致n 的值改变,所以n 的值成为10。 void Func2(int *x) { (* x) = (* x) + 10; } ⋯ int n = 0; Func2(&n); cout && “n = ” && n && // n = 10 以下是“引用传递”的示例程序。由于Func3 函数体内的x 是外部变量n 的引用,x 和n 是同一个东西,改变x 等于改变n,所以n 的值成为10。 void Func3(int &x) { x = x + 10; } ⋯ int n = 0; Func3(n); cout && “n = ” && n && // n = 10 对比上述三个示例程序,会发现“引用传递”的性质象“指针传递”,而书写方式象 “值传递”。实际上“引用”可以做的任何事情“指针”也都能够做,为什么还要“引用” 这东西? 答案是“用适当的工具做恰如其分的工作”。 指针能够毫无约束地操作内存中的如何东西,尽管指针功能强大,但是非常危险。 就象一把刀,它可以用来砍树、裁纸、修指甲、理发等等,谁敢这样用? 如果的确只需要借用一下某个对象的“别名”,那么就用“引用”,而不要用“指针”, 以免发生意外。比如说,某人需要一份证明,本来在文件上盖上公章的印子就行了,如 果把取公章的钥匙交给他,那么他就获得了不该有的权利。 ---------- 摘自『高质量c++编程』指针与引用,在More Effective C++ 的条款一有详细讲述,我给你转过来 条款一:指针与引用的区别 指针与引用看上去完全不同(指针用操作符’*’和’-&’,引用使用操作符’.’),但是它们似乎有相同的功能。指针与引用都是让你间接引用其他对象。你如何决定在什么时候使用指针,在什么时候使用引用呢? 首先,要认识到在任何情况下都不能用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。 “但是,请等一下”,你怀疑地问,“这样的代码会产生什么样的后果?” char *pc = 0; // 设置指针为空值 char& rc = * // 让引用指向空值 这是非常有害的,毫无疑问。结果将是不确定的(编译器能产生一些输出,导致任何事情都有可能发生),应该躲开写出这样代码的人除非他们同意改正错误。如果你担心这样的代码会出现在你的软件里,那么你最好完全避免使用引用,要不然就去让更优秀的程序员去做。我们以后将忽略一个引用指向空值的可能性。 因为引用肯定会指向一个对象,在C里,引用应被初始化。 string& // 错误,引用必须被初始化 string s(&xyzzy&); string& rs = // 正确,rs指向s 指针没有这样的限制。 string * // 未初始化的指针 // 合法但危险 不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。 void printDouble(const double& rd) { cout && // 不需要测试rd,它 } // 肯定指向一个double值 相反,指针则应该总是被测试,防止其为空: void printDouble(const double *pd) { if (pd) { // 检查是否为NULL cout && * } } 指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。 string s1(&Nancy&); string s2(&Clancy&); string& rs = s1; // rs 引用 s1 string *ps = &s1; // ps 指向 s1 rs = s2; // rs 仍旧引用s1, // 但是 s1的值现在是 // &Clancy& ps = &s2; // ps 现在指向 s2; // s1 没有改变 总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。 还有一种情况,就是当你重载某个操作符时,你应该使用引用。最普通的例子是操作符[]。这个操作符典型的用法是返回一个目标对象,其能被赋值。 vector&int& v(10); // 建立整形向量(vector),大小为10; // 向量是一个在标准C库中的一个模板(见条款35) v[5] = 10; // 这个被赋值的目标对象就是操作符[]返回的值 如果操作符[]返回一个指针,那么后一个语句就得这样写: *v[5] = 10; 但是这样会使得v看上去象是一个向量指针。因此你会选择让操作符返回一个引用。(这有一个有趣的例外,参见条款30) 当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针 假设你有 void func(int* p, int&r); int a = 1; int b = 1; func(&a,b); 指针本身的值(地址值)是以pass by value进行的,你能改变地址值,但这并不会改变指针所指向的变量的值,p = //a is still 1 但能用指针来改变指针所指向的变量的值, *p = 123131; // a now is 123131 但引用本身是以pass by reference进行的,改变其值即改变引用所对应的变量的值 r = 1231; // b now is 1231
C++中extern “C”含义深层探索1.引言C++语言的创建初衷是“a better C”,但是这并不意味着C++中类似C语言的全局变量和函数所采用的编译和连接方式与C语言完全相同。作为一种欲与C兼容的语言,C++保留了一部分过程式语言的特点(被世人称为“不彻底地面向对象”),因而它可以定义不属于任何类的全局变量和函数。但是,C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与C有明显的不同。 2.从标准头文件说起某企业曾经给出如下的一道面试题:面试题为什么标准头文件都有类似以下的结构?&&&&#ifndef __INCvxWorksh&&&&#define __INCvxWorksh&&&&#ifdef __cplusplus&&&&extern &C& {&&&&#endif&&&&/*...*/&&&&#ifdef __cplusplus&&&&}&&&&#endif&&&&#endif /* __INCvxWorksh */分析显然,头文件中的编译宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止该头文件被重复引用。那么#ifdef __cplusplusextern &C& {#endif#ifdef __cplusplus}#endif的作用又是什么呢?我们将在下文一一道来。 3.深层揭密extern &C&extern &C& 包含双重含义,从字面上即可得到:首先,被它修饰的目标是“extern”的;其次,被它修饰的目标是“C”的。让我们来详细解读这两重含义。被extern &C&限定的函数或变量是extern类型的;&&extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。记住,下列语句:仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。与extern对应的关键字是static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。被extern &C&修饰的变量和函数是按照C语言方式编译和连接的;未加extern “C”声明时的编译方式首先看看C++中对类似C的函数是怎样编译的。作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:void foo( int x, int y );该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以&.&来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。未加extern &C&声明时的连接方式假设在C++中,模块A的头文件如下:// 模块A头文件 moduleA.h#ifndef MODULE_A_H#define MODULE_A_Hint foo( int x, int y );#endif在模块B中引用该函数:// 模块B实现文件 moduleB.cpp#include &moduleA.h&foo(2,3);实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!加extern &C&声明后的编译和连接方式加extern &C&声明后,模块A的头文件变为:// 模块A头文件 moduleA.h#ifndef MODULE_A_H#define MODULE_A_Hextern &C& int foo( int x, int y );#endif在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:(1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。如果在模块A中函数声明了foo为extern &C&类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):实现C++与C及其它语言的混合编程。明白了C++中extern &C&的设立动机,我们下面来具体分析extern &C&通常的使用技巧。 4.extern &C&的惯用法(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:extern &C&{#include &cExample.h&}而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern &C&声明,在.c文件中包含了extern &C&时会出现编译语法错误。笔者编写的C++引用C函数例子工程中包含的三个文件的源代码如下:/* c语言头文件:cExample.h */#ifndef C_EXAMPLE_H#define C_EXAMPLE_Hextern int add(int x,int y);&&&& //注:写成extern &C& int add(int , int ); 也可以#endif/* c语言实现文件:cExample.c */#include &cExample.h&int add( int x, int y ){return x +}// c++实现文件,调用add:cppFile.cppextern &C&{#include &cExample.h&&&&&&&&&//注:此处不妥,如果这样编译通不过,换成 extern &C& int add(int , int ); 可以通过}int main(int argc, char* argv[]){add(2,3);return 0;}如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern &C& { }。(2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern &C&,但是在C语言中不能直接引用声明了extern &C&的该头文件,应该仅将C文件中将C++中定义的extern &C&函数声明为extern类型。笔者编写的C引用C++函数例子工程中包含的三个文件的源代码如下://C++头文件 cppExample.h#ifndef CPP_EXAMPLE_H#define CPP_EXAMPLE_Hextern &C& int add( int x, int y );#endif//C++实现文件 cppExample.cpp#include &cppExample.h&int add( int x, int y ){return x +}/* C实现文件 cFile.c/* 这样会编译出错:#include &cExample.h& */extern int add( int x, int y );int main( int argc, char* argv[] ){add( 2, 3 );return 0;}如果深入理解了第3节中所阐述的extern &C&在编译和连接阶段发挥的作用,就能真正理解本节所阐述的从C++引用C函数和C引用C++函数的惯用法。对第4节给出的示例代码,需要特别留意各个细节。
仅仅为了获取函数名,就在函数体中嵌入硬编码的字符串,这种方法单调乏味还易导致错误,不如看一下怎样使用新的C99特性,在程序运行时获取函数名吧。 对象反射库、调试工具及代码分析器,经常会需要在运行时访问函数的名称,直到不久前,唯一能完成此项任务并且可移植的方法,是手工在函数体内嵌入一个带有该函数名的硬编码字符串,不必说,这种方法非常单调无奇,并且容易导致错误。本文将要演示怎样使用新的C99特性,在运行时获取函数名。那么怎样以编程的方式从当前运行的函数中得到函数名呢?答案是:使用__FUNCTION__ 及相关宏。引出问题通常,在调试中最让人心烦的阶段,是不断地检查是否已调用了特定的函数。对此问题的解决方法,一般是添加一个cout或printf()——如果你使用C语言,如下所示:void myfunc(){cout&&&myfunc()&&&//其他代码}通常在一个典型的工程中,会包含有数千个函数,要在每个函数中都加入一条这样的输出语句,无疑难过上“蜀山”啊,因此,需要有一种机制,可以自动地完成这项操作。 获取函数名 作为一个C++程序员,可能经常遇到 __TIME__、__FILE__、__DATE__ 这样的宏,它们会在编译时,分别转换为包含编译时间、处理的转换单元名称及当前时间的字符串。 在最新的ISO C标准中,如大家所知的C99,加入了另一个有用的、类似宏的表达式__func__,其会报告未修饰过的(也就是未裁剪过的)、正在被访问的函数名。请注意,__func__不是一个宏,因为预处理器对此函数一无所知;相反,它是作为一个隐式声明的常量字符数组实现的: static const char __func__[] = &function-name&;在function-name处,为实际的函数名。为激活此特性,某些编译器需要使用特定的编译标志,请查看相应的编译器文档,以获取具体的资料。 有了它,我们可免去大多数通过手工修改,来显示函数名的苦差事,以上的例子可如下所示进行重写: void myfunc(){cout&&&__FUNCTION__&&&}官方C99标准为此目的定义的__func__标识符,确实值得大家关注,然而,ISO C++却不完全支持所有的C99扩展,因此,大多数的编译器提供商都使用 __FUNCTION__ 取而代之,而 __FUNCTION__ 通常是一个定义为 __func__ 的宏,之所以使用这个名字,是因为它已受到了大多数的广泛支持。 在Visual Studio 2005中,默认情况下,此特性是激活的,但不能与/EP和/P编译选项同时使用。请注意在IDE环境中,不能识别__func__ ,而要用__FUNCTION__ 代替。 Comeau的用户也应使用 __FUNCTION__ ,而不是 __func__ 。 C++ BuilderX的用户则应使用稍稍不同的名字:__FUNC__ 。 GCC 3.0及更高的版本同时支持 __func__ 和__FUNCTION__ 。 一旦可自动获取当前函数名,你可以定义一个如下所示显示任何函数名的函数: void show_name(const char * name){cout&&name&&}void myfunc(){show_name(__FUNCTION__); //输出:myfunc}void foo(){show_name(__FUNCTION__); //输出:foo}因为 __FUNCTION__ 会在函数大括号开始之后就立即初始化,所以,foo()及myfunc()函数可在参数列表中安全地使用它,而不用担心重载。 签名与修饰名 __FUNCTION__ 特性最初是为C语言设计的,然而,C++程序员也会经常需要有关他们函数的额外信息,在Visual Studio 2005中,还支持另外两种非标准的扩展特性:__FUNCDNAME__ 与 __FUNCSIG__ ,其分别转译为一个函数的修饰名与签名。函数的修饰名非常有用,例如,在你想要检查两个编译器是否共享同样的ABI时,就可派得上用场,另外,它还能帮助你破解那些含义模糊的链接错误,甚至还可用它从一个DLL中调用另一个用C++链接的函数。在下例中,show_name()报告了函数的修饰名: void myfunc(){show_name(__FUNCDNAME__); //输出:?myfunc@@YAXXZ}一个函数的签名由函数名、参数列表、返回类型、内含的命名空间组成。如果它是一个成员函数,它的类名和const/volatile限定符也将是签名的一部分。以下的代码演示了一个独立的函数与一个const成员函数签名间的不同之处,两个函数的名称、返回类型、参数完全相同: void myfunc(){show_name(__FUNCSIG__); // void __cdecl myfunc(void)}struct S{void myfunc() const {show_name(__FUNCSIG__); //void __thiscall S::myfunc(void) const}};
1、内存分配方式 内存分配方式有三种: (1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。 (2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 (3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。 2、常见的内存错误及其对策 发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。有时用户怒气冲冲地把你找来,程序却没有发生任何问题,你一走,错误又发作了。 常见的内存错误及其对策如下: * 内存分配未成功,却使用了它 。 编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行 检查。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。 * 内存分配虽然成功,但是尚未初始化就引用它。 犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。 * 内存分配成功并且已经初始化,但操作越过了内存的边界。 例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。 * 忘记了释放内存,造成内存泄露。 含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。 动 态内存的申请与释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误(new/delete同理) 。 * 释放了内存却继续使用它。 有三种情况: (1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。 (2)函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。 (3)使用free或delete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。 【规则1】用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。 【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。 【规则3】避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。 【规则4】动态内存的申请与释放必须配对,防止内存泄漏。 【规则5】用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。 3、指针与数组的对比 C /C程序中,指针和数组在不少地方可以相互替换着用,让人产生一种错觉,以为两者是等价的。 数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。 指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。下面以字符串为例比较指针与数组的特性。3.1 修改内容示例3-1中,字符数组a的容量是6个字符,其内容为hello。a的内容可以改变,如a[0]= ‘X’。指针p指向常量字符串“world”(位于静态存储区,内容为world),常量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句 p[0]= ‘X’有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。1char a[] = “hello”;2a[0] = ‘X’;3cout && a &&4char *p = “world”; // 注意p指向常量字符串5p[0] = ‘X’; // 编译器不能发现该错误6cout && p &&示例3.1 修改数组和指针的内容 3.2 内容复制与比较 不能对数组名进行直接复制与比较。若想把数组a的内容复制给数组b,不能用语句 b = a ,否则将产生编译错误。应该用标准库函数strcpy进行复制。同理,比较b和a的内容是否相同,不能用if(b==a) 来判断,应该用标准库函数strcmp进行比较。 语句p = a 并不能把a的内容复制指针p,而是把a的地址赋给了p。要想复制a的内容,可以先用库函数malloc为p申请一块容量为strlen(a) 1个字符的内存,再用strcpy进行字符串复制。同理,语句if(p==a) 比较的不是内容而是地址,应该用库函数strcmp来比较。01// 数组…02char a[] = &hello&;03char b[10];04strcpy(b, a); // 不能用 b =05if(strcmp(b, a) == 0) // 不能用 if (b == a)06…07// 指针…08int len = strlen(a);09char *p = (char *)malloc(sizeof(char)*(len));10strcpy(p,a); // 不要用 p =11if(strcmp(p, a) == 0) // 不要用 if (p == a)12…示例3.2 数组和指针的内容复制与比较 3.3 计算内存容量 用运算符sizeof可以计算出数组的容量(字节数)。sizeof(a)的值是12(注意别忘了’’)。指针p指向a,但是 sizeof(p)的值却是4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p所指的内存容量。 C /C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。 注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。不论数组a的容量是多少,sizeof(a)始终等于sizeof(char *)。1char a[] = &hello world&;2char *p =3cout&& sizeof(a) && // 12字节4cout&& sizeof(p) && // 4字节示例3.3(a) 计算数组和指针的内存容量1void Func(char a[100])2{3 cout&& sizeof(a) && // 4字节而不是100字节4}示例3.3(b) 数组退化为指针 4、指针参数是如何传递内存的? 如果函数的参数是一个指针,不要指望用该指针去申请动态内存。Test函数的语句GetMemory(str, 200)并没有使str获得期望的内存,str依旧是NULL,为什么?01void GetMemory(char *p, int num)02{03 p = (char *)malloc(sizeof(char) * num);04}05void Test(void)06{07 char *str = NULL;08 GetMemory(str, 100); // str 仍然为 NULL09 strcpy(str, &hello&); // 运行错误10}示例4.1 试图用指针参数申请动态内存 毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把 _p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。 如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,见示例4.2。01void GetMemory2(char **p, int num)02{03 *p = (char *)malloc(sizeof(char) * num);04}05void Test2(void)06{07 char *str = NULL;08 GetMemory2(&str, 100); // 注意参数是 &str,而不是str09 strcpy(str, &hello&);10 cout&& str &&11 free(str);12}示例4.2用指向指针的指针申请动态内存 由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。这种方法更加简单,见示例4.3。01char *GetMemory3(int num)02{03 char *p = (char *)malloc(sizeof(char) * num);04 05}06void Test3(void)07{08 char *str = NULL;09 str = GetMemory3(100);10 strcpy(str, &hello&);11 cout&& str &&12 free(str);13}示例4.3 用函数返回值来传递动态内存 用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把return语句用错了。这里强调不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡,见示例4.4。01char *GetString(void)02{03 char p[] = &hello world&;04  // 编译器将提出警告05}06void Test4(void)07{08 char *str = NULL;09 str = GetString(); // str 的内容是垃圾10 cout&& str &&11}示例4.4 return语句返回指向“栈内存”的指针 用调试器逐步跟踪Test4,发现执行str = GetString语句后str不再是NULL指针,但是str的内容不是“hello world”而是垃圾。 如果把示例4.4改写成示例4.5,会怎么样?01char *GetString2(void)02{03 char *p = &hello world&;04 05}06void Test5(void)07{08 char *str = NULL;09 str = GetString2();10 cout&& str &&11}示例4.5 return语句返回常量字符串 函数Test5运行虽然不会出错,但是函数GetString2的设计概念却是错误的。因为GetString2内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。 5、杜绝“野指针” “野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。 “野指针”的成因主要有两种: (1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如1char *p = NULL;2char *str = (char *) malloc(100);(2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。 (3)指针操作超越了变量的作用范围。这种情况让人防不胜防,示例程序如下:01class A02{03 public:04  void Func(void){ cout && “Func of class A” && }05};06void Test(void)07{08 A *p;09 {10  A11  p = &a; // 注意 a 的生命期12 }13 p-&Func(); // p是“野指针”14}函数Test在执行语句p-&Func()时,对象a已经消失,而p是指向a的,所以p就成了“野指针”。但奇怪的是我运行这个程序时居然没有出错,这可能与编译器有关。 6、有了malloc/free为什么还要new/delete? malloc与free是C /C语言的标准库函数,new/delete是C 的运算符。它们都可用于申请动态内存和释放内存。 对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。 因此C 语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。我们先看一看malloc/free和new/delete如何实现对象的动态内存管理,见示例6。01class Obj02{03 public :04  Obj(void){ cout && “Initialization” && }05  ~Obj(void){ cout && “Destroy” && }06  void Initialize(void){ cout && “Initialization” && }07  void Destroy(void){ cout && “Destroy” && }08};09void UseMallocFree(void)10{11 Obj *a = (obj *)malloc(sizeof(obj)); // 申请动态内存12 a-&Initialize(); // 初始化13 //…14 a-&Destroy(); // 清除工作15 free(a); // 释放内存16}17void UseNewDelete(void)18{19 Obj *a = new O // 申请动态内存并且初始化20 //…21  // 清除并且释放内存22}示例6 用malloc/free和new/delete如何实现对象的动态内存管理 类Obj的函数Initialize模拟了构造函数的功能,函数Destroy模拟了析构函数的功能。函数UseMallocFree中,由于 malloc/free不能执行构造函数与析构函数,必须调用成员函数Initialize和Destroy来完成初始化与清除工作。函数 UseNewDelete则简单得多。 所以我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。 既然new/delete的功能完全覆盖了malloc/free,为什么C 不把malloc/free淘汰出局呢?这是因为C 程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。 如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存 ”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。 7、内存耗尽怎么办? 如果在申请动态内存时找不到足够大的内存块,malloc和new将返回NULL指针,宣告内存申请失败。通常有三种方式处理“内存耗尽”问题。 (1)判断指针是否为NULL,如果是则马上用return语句终止本函数。例如:1void Func(void)2{3 A *a = new A;4 if(a == NULL)5 {6  7 }8 …9}(2)判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行。例如:01void Func(void)02{03 A *a = new A;04 if(a == NULL)05 {06  cout && “Memory Exhausted” &&07  exit(1);08 }09 …10}(3)为new和malloc设置异常处理函数。例如Visual C 可以用_set_new_hander函数为new设置用户自己定义的异常处理函数,也可以让malloc享用与new相同的异常处理函数。详细内容请参考C 使用手册。 上述(1)(2)方式使用最普遍。如果一个函数内有多处需要申请动态内存,那么方式(1)就显得力不从心(释放内存很麻烦),应该用方式(2)来处理。 很多人不忍心用exit(1),问:“不编写出错处理程序,让操作系统自己解决行不行?” 不行。如果发生“内存耗尽”这样的事情,一般说来应用程序已经无药可救。如果不用exit(1) 把坏程序杀死,它可能会害死操作系统。道理如同:如果不把歹徒击毙,歹徒在老死之前会犯下更多的罪。 有一个很重要的现象要告诉大家。对于32位以上的应用程序而言,无论怎样使用malloc与new,几乎不可能导致“内存耗尽”。我在Windows 98下用Visual C 编写了测试程序,见示例7。这个程序会无休止地运行下去,根本不会终止。因为32位操作系统支持“虚存”,内存用完了,自动用硬盘空间顶替。我只听到硬盘嘎吱嘎吱地响,Window 98已经累得对键盘、鼠标毫无反应。 我可以得出这么一个结论: 对于32位以上的应用程序,“内存耗尽”错误处理程序毫无用处。这下可把Unix和Windows程序员们乐坏了:反正错误处理程序不起作用,我就不写了,省了很多麻烦。 我不想误导读者,必须强调:不加错误处理将导致程序的质量很差,千万不可因小失大。01void main(void)02{03 float *p = NULL;04 while(TRUE)05 {06  p = new float[1000000];07  cout && “eat memory” &&08  if(p==NULL)09   exit(1);10 }11}8、malloc/free 的使用要点 函数malloc的原型如下:1void * malloc(size_t size);用malloc申请一块长度为length的整数类型的内存,程序如下:1int *p = (int *) malloc(sizeof(int) * length);我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。 * malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。 * malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。我们通常记不住int, float等数据类型的变量的确切字节数。例如int变量在16位系统下是2个字节,在32位下是4个字节;而float变量在16位系统下是4个字节,在32位下也是4个字节。最好用以下程序作一次测试:1cout && sizeof(char) &&2cout && sizeof(int) &&3cout && sizeof(unsigned int) &&4cout && sizeof(long) &&5cout && sizeof(unsigned long) &&6cout && sizeof(float) &&7cout && sizeof(double) &&8cout && sizeof(void *) &&在malloc的“()”中使用sizeof运算符是良好的风格,但要当心有时我们会昏了头,写出 p = malloc(sizeof(p))这样的程序来。 * 函数free的原型如下:1void free( void * memblock );为什么free 函数不象malloc函数那样复杂呢?这是因为指针p的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。如果p是 NULL指针,那么free对p无论操作多少次都不会出问题。如果p不是NULL指针,那么free对p连续操作两次就会导致程序运行错误。 9、new/delete 的使用要点 运算符new使用起来要比函数malloc简单得多,例如:1int *p1 = (int *)malloc(sizeof(int) * length);2int *p2 = new int[length];这是因为new内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new的语句也可以有多种形式。例如01class Obj02{03 public :04  Obj(void); // 无参数的构造函数05  Obj(int x); // 带一个参数的构造函数06  …07}08void Test(void)09{10 Obj *a = new O11 Obj *b = new Obj(1); // 初值为112 …13 14 15}如果用new创建对象数组,那么只能使用对象的无参数构造函数。例如1Obj *objects = new Obj[100]; // 创建100个动态对象不能写成1Obj *objects = new Obj[100](1);// 创建100个动态对象的同时赋初值1在用delete释放对象数组时,留意不要丢了符号‘[]’。例如1delete [] // 正确的用法2 // 错误的用法后者相当于delete objects[0],漏掉了另外99个对象。
printf系列函数,包括fprintf、sprintf函数等,其功能是将C语言的所有基本数据类型按用户要求进行格式化输出。&&&&printf函数几乎是所有学习C语言的人接触到的第一个函数,是C语言标准中使用频率最高的函数。&&&&printf函数是C语言标准函数中最著名的可变参数函数,看见printf这个函数名,就想起了C语言的说法一点也不过分,因此,可以说是C语言标准函数中的最具标志性的函数。&&&&printf系列函数。在DOS环境下,这一系列输出函数涵盖了PC机所能用到的所有输出设备,所以printf系列函数也是C语言中最复杂的函数。&&&&&&&&当然,随着DOS时代的结束,不仅printf系列函数的作用减弱了,就连C语言本身也被压缩到了最小的应用领域。 &&&&本文写的sprintfA函数,也是应一个小友要求写的几个函数之一,包括我昨天发布的《自己动手写C语言浮点数转换字符串函数》中的FloatToStr函数,是用来学习用的。之所以取名为sprintfA,不仅是区别系统本身的sprintf函数,同时也因为在Windows下,A表示的是传统的ANSI函数。因为在Windows下,printf系列函数也“与时俱进”了,如wprintf等就是在宽字符环境下的输出函数。由于我在sprintfA函数中使用了Windows的宽字符转换函数,因此该函数只适用于Windows环境。&&&&由于sprintfA函数代码比较长,将分为多篇文章发布,《自己动手写C语言浮点数转换字符串函数》一文中的代码也应算作一篇:&&&&一、数据定义:view plainprint?typedef struct&&{&&&&&&INT&&&&&& // 数据长度类型&&&&&&INT&&&&&&// 数据最小宽度&&&&&&INT&&// 数据精度&&&&&&BOOL&&&&&&// 是否居左&&&&&&BOOL&&&&&&// 是否前导零&&&&&&INT&& // 浮点数: 1强制小数位; 16进制: -1: 0x, 1: 0X&&&&&&INT&& // 符号:-1: '-'; 1: '+'&&&&&&LPSTR&&&&// 参数指针&&}FormatR&&&&typedef long long&&&&&&&&&& LLONG, *PLLONG;&&typedef unsigned long long&&ULLONG, *PULLONG;&&&&#define TYPE_CHAR&&&&&& 0&&#define TYPE_SHORT&&&&&&1&&#define TYPE_GENERAL&&&&2&&#define TYPE_LONG&&&&&& 3&&#define TYPE_LLONG&&&&&&4&&&&#define PTR_SIZE&&&&&&&&sizeof(VOID*)&&#define TypeSize(size)&&(((size + PTR_SIZE - 1) / PTR_SIZE) * PTR_SIZE)&&&&#define TS_PTR&&&&&&&&&&PTR_SIZE&&#define TS_CHAR&&&&&&&& TypeSize(sizeof(CHAR))&&#define TS_WCHAR&&&&&&&&TypeSize(sizeof(WCHAR))&&#define TS_SHORT&&&&&&&&TypeSize(sizeof(SHORT))&&#define TS_INT&&&&&&&&&&TypeSize(sizeof(INT))&&#define TS_LONG&&&&&&&& TypeSize(sizeof(LONG))&&#define TS_LLONG&&&&&&&&TypeSize(sizeof(LLONG))&&#define TS_FLOAT&&&&&&&&TypeSize(sizeof(FLOAT))&&#define TS_DOUBLE&&&&&& TypeSize(sizeof(double))&&#define TS_EXTENDED&&&& TypeSize(sizeof(EXTENDED))&&&&#define CHAR_SPACE&&&&&&' '&&#define CHAR_ZERO&&&&&& '0'&&#define CHAR_POS&&&&&&&&'+'&&#define CHAR_NEG&&&&&&&&'-'&&&&#define HEX_PREFIX_U&&&&&0X&&&#define HEX_PREFIX_L&&&&&0x&&&&&#define MAX_DIGITS_SIZE 40&&&&&&FormatRec是一个数据格式化结构,它包含了sprintfA格式化各种数据所需的基本信息。&&&&TYPE_XXXX是数据类型标记,对应于FormatRec.type字段。&&&&TS_XXXX是各种数据类型在sprintfA可变参数传递时所占的栈字节长度。除指针类型和INT类型长度直接用sizeof关键字确定栈字节长度外,其它数据类型所占栈长度则用TypeSize宏配合计算取得,这样就使得这些数据所占栈字节长度在各种环境下都是正确的,比如字符型长度为1字节,TypeSizesizeof(CHAR)),在32位编译环境时等于4,在64位编译环境时则等于8。&& 对于带任意类型可变参数的函数来说,实参数据类型的栈字节长度正确与否,完全取决于程序员。比如在sprintfA的格式参数中定义了%Ld,应该是个64位整数类型,而在对应的可变参数部分却给了一个int类型,在32位编译器环境下,就存在2个错误,一是数据类型不正确,二是栈字节长度不匹配,64位整数长度为8字节,而INT的长度却只有4字节,其结果就是这个数据以及其后的所有数据都会出现错误的显示结果,甚至还有可能造成程序崩溃。这也是一些C语言初学者在使用printf系列函数时最容易犯的错误,他们混淆了一般函数与带可变参数函数调用的区别, 对于一般的C函数,形参的数据类型是固定的,在调用时,如果实参与形参数据类型不匹配,编译器视情况会作出错误、警告或者转换等处理,而对于不同精度的相同数据类型,编译器大都会自动进行扩展或截断;而调用带可变参数函数时,由于函数原型的形参说明部分为“...”,编译器就没法将int扩展为_int64了。&&&&另外,还有有关浮点数部分的数据定义在《自己动手写C语言浮点数转换字符串函数》。&&&&二、函数主体。view plainprint?// 获取字符串中的数字。参数:字符串,数字指针。返回字符串中最后一个数字位置&&static LPSTR GetControlNum(LPCSTR s, INT *value)&&{&&&&&&register LPCSTR p =&&&&&&register INT&&&&&&for (v = 0; *p &= '0' && *p &= '9'; p ++)&&&&&&&&&&v = v * 10 + (*p - '0');&&&&&&*value =&&&&&&return (LPSTR)(p - 1);&&}&&view plainprint?LPSTR _sprintfA(LPSTR buffer, LPCSTR format, ...)&&{&&&&&&FormatR&&&&&&BOOL&&&&&&CHAR&&&&&&LPCSTR&&&&&&&&&& // ?&&&&&&register LPCSTR pf =&&&&&&register LPSTR pb =&&&&&&va_list paramL&&&&&&&&va_start(paramList, format);&&&&&&rec.param = (LPSTR)paramL&&&&&&while (TRUE)&&&&&&{&&&&&&&&&&while (*pf && *pf != '%')&&&&&&&&&&&&&&*pb ++ = *pf ++;&&&&&&&&&&if (*pf == 0)&&&&&&&&&&if (*(pf + 1) == '%')&& // 处理%%&&&&&&&&&&{&&&&&&&&&&&&&&*pb ++ = '%';&&&&&&&&&&&&&&pf += 2;&&&&&&&&&&&&&&&&&&&&&&&&}&&&&&&&&&&psave =&&&&&&&& // ?&&&&&&&&&&rec.width = rec.decimals = rec.negative = 0;&&&&&&&&&&rec.left = rec.zero = FALSE;&&&&&&&&&&rec.type = TYPE_GENERAL;&&&&&&&&&&rec.precision = -1;&&&&&&&&&&// 解析前导符号&&&&&&&&&&flag = TRUE;&&&&&&&&&&while (flag)&&&&&&&&&&{&&&&&&&&&&&&&&pf ++;&&&&&&&&&&&&&&switch (*pf)&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&case '0':&&&&&&&&&&&&&&&&&&&&&&rec.zero = TRUE;&&&&&&&&&&&&&&&&&&&&&&flag = FALSE;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case '-':&&&&&&&&&&&&&&&&&&&&&&rec.left = TRUE;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case '+':&&&&&&&&&&&&&&&&&&&&&&rec.negative = 1;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case '#':&&&&&&&&&&&&&&&&&&&&&&rec.decimals = 1;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&default:&&&&&&&&&&&&&&&&&&&&&&pf --;&&&&&&&&&&&&&&&&&&&&&&flag = FALSE;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&}&&&&&&&&&&}&&&&&&&&&&// 解析输出宽度和精度&&&&&&&&&&flag = TRUE;&&&&&&&&&&while (flag)&&&&&&&&&&{&&&&&&&&&&&&&&pf ++;&&&&&&&&&&&&&&switch (*pf)&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&case '.':&&&&&& // 如小数点后为'*','0' - '9'继续处理精度和宽度&&&&&&&&&&&&&&&&&&&&&&rec.precision = 0;&&&&&&&&&&&&&&&&&&&&&&c = *(pf + 1);&&&&&&&&&&&&&&&&&&&&&&flag = (c == '*' || (c &= '0' && c &= '9'));&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case '*':&&&&&& // 处理'*'表示的宽度参数和精度参数&&&&&&&&&&&&&&&&&&&&&&if (*(pf - 1) == '.')&&&&&&&&&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&&&&&&&&&rec.precision = *(PINT)rec.&&&&&&&&&&&&&&&&&&&&&&&&&&flag = FALSE;&&&&&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&&&else&&&&&&&&&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&&&&&&&&&rec.width = *(PINT)rec.&&&&&&&&&&&&&&&&&&&&&&&&&&flag = *(pf + 1) == '.';&&&&&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&&&rec.param += TS_PTR;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&default:&&&&&&&&// 处理格式串中数字表示的宽度和精度&&&&&&&&&&&&&&&&&&&&&&if (*(pf - 1) == '.')&&&&&&&&&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&&&&&&&&&pf = GetControlNum(pf, &rec.precision);&&&&&&&&&&&&&&&&&&&&&&&&&&flag = FALSE;&&&&&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&&&else&&&&&&&&&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&&&&&&&&&pf = GetControlNum(pf, &rec.width);&&&&&&&&&&&&&&&&&&&&&&&&&&flag = *(pf + 1) == '.';&&&&&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&}&&&&&&&&&&}&&&&&&&&&&// 解析数据类型精度&&&&&&&&&&flag = TRUE;&&&&&&&&&&while (flag)&&&&&&&&&&{&&&&&&&&&&&&&&pf ++;&&&&&&&&&&&&&&switch(*pf)&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&case 'L':&&&&&&&&&&&&&&&&&&&&&&rec.type = TYPE_LLONG;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case 'l':&&&&&&&&&&&&&&&&&&&&&&if (rec.type & TYPE_LLONG)&&&&&&&&&&&&&&&&&&&&&&&&&&rec.type ++;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case 'H':&&&&&&&&&&&&&&&&&&&&&&rec.type = TYPE_CHAR;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case 'h':&&&&&&&&&&&&&&&&&&&&&&if (rec.type & TYPE_CHAR)&&&&&&&&&&&&&&&&&&&&&&&&&&rec.type --;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&default:&&&&&&&&&&&&&&&&&&&&&&flag = FALSE;&&&&&&&&&&&&&&}&&&&&&&&&&}&&&&&&&&&&// 解析数据类型,并格式化&&&&&&&&&&c = *pf ++;&&&&&&&&&&switch (c)&&&&&&&&&&{&&&&&&&&&&&&&&case 's':&&&&&&&&&&&&&&&&&&pb = FormatStrA(pb, &rec);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case 'c':&&&&&&&&&&&&&&&&&&pb = FormatCharA(pb, &rec);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case 'd':&&&&&&&&&&&&&&case 'i':&&&&&&&&&&&&&&case 'u':&&&&&&&&&&&&&&&&&&pb = FormatIntA(pb, &rec, c == 'u');&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case 'f':&&&&&&&&&&&&&&&&&&pb = FormatFloatFA(pb, &rec);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case 'e':&&&&&&&&&&&&&&case 'E':&&&&&&&&&&&&&&&&&&pb = FormatFloatEA(pb, &rec, c);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case 'g':&&&&&&&&&&&&&&case 'G':&&&&&&&&&&&&&&&&&&pb = FormatFloatGA(pb, &rec, c);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case 'x':&&&&&&&&&&&&&&&&&&if (rec.decimals)&&&&&&&&&&&&&&&&&&&&&&rec.decimals = -1;&&&&&&&&&&&&&&case 'X':&&&&&&&&&&&&&&&&&&pb = FormatHexA(pb, &rec, c);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case 'o':&&&&&&&&&&&&&&&&&&pb = FormatOctalA(pb, &rec);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case 'p':&&&&&&&&&&&&&&&&&&pb = FormatPointerA(pb, &rec);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&case 'n':&&&&&&&&&&&&&&&&&&GetPosSizeA(pb, buffer, &rec);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&default:&&&&&&&&&&&&// 错误:拷贝format剩余字符,返回&&//&&&&&&&&&&&&&&pf = psave + 1; // ? 也可处理为忽略后继续&&//&&&&&&&&&&&&&&&&&&&&&&&&// ?&&&&&&&&&&&&&&&&&&lstrcpyA(pb, psave);&&&&&&&&&&&&&&&&&&&&&&&&&&&&}&&&&&&}&&&&&&va_end(paramList);&&&&&&*pb = 0;&&&&&&&&}&&&&&&sprintfA函数的主体部分就是一个简单的解释器,通过一个主循环,对字符串参数format逐字符的作如下解析:&&&&1)如果不是数据格式前缀字符'%',直接拷贝到输出缓冲区buffer;&&&&2)如果'%'后接着一个'%'字符,则表示要输出后面这个'%';&&&&3)紧接着'%'后面的,应该是数据格式前导字符。共有4个前导字符:&&&&&&&&1、'0':前导零标志。如果数据被格式化后的长度小于规定的格式化宽度,则在被格式化后的数据前补0;&&&&&&&&2、'-':左对齐标记。&&&&&&&&3、'+':正数符号输出标记。正数在正常格式输出时,其符号是省略了的,'+'则表示要输出这个符号;&&&&&&&&4、'#':对浮点数,这是强制小数点('.')输出标记。无论这个数有没有小数部分,都必须输出这个小数位符号;对整数的十六进制输出,则是十六进制前缀(0x或者0X)输出标记。&&&&前导字符不是必须的,也可有多个前导符同时出现在'%'后面,但'0'必须排在最后一个,其余顺序可任意。&&&&4)解析数据输出宽度和精度。宽度是指数据输出时必须达到的字节数,如果格式化后的数据长度小于宽度,应用空格或者零补齐;精度则是数据要求格式化的长度,视数据类型不同而有所区别,如浮点数是指小数部分的长度,而其它数据则是指全部数据格式化长度,大于精度的数据是保留还是截断,小于精度是忽略还是补齐(零或空格),后面涉及具体数据类型时再说明。&&&&宽度和精度一般以'.'为分隔符,左边是宽度,右边是精度,如果只有宽度则'.'可忽略。宽度和精度可用固定数字表示,如“10.6”,也可用可变形式“*.*”表示。可变形式的宽度和精度必须在sprintf的可变参数部分有其对应的整数实参。&&&&宽度和精度部分也不是必须的。&&&&5)分析数据类型精度字符。在C语言中,相同类型的基本数据可能有不同的精度,如整数有长短之分,浮点数有精度之分,而字符有ANSI和UNICODE之分等等。在sprintfA中,是靠分析类型精度字符来取得的。字符'l'和'h'分别表示长数据和短数据,在16位编译器环境下,一个'l'或'h'就够了,而32位及以上编译器中,随着数据精度的提高,必须靠多个类型精度字符才能表示完整,为此,也可用字符'L'和'H'分别表示数据类型的最大精度和最小精度。sprintfA的数据类型精度分析有较高的容错处理,你可以输入任意多个类型精度字符。&&&&类型精度字符也不是必须的,缺省情况下,按一般类型精度处理。&&&&6)解析数据类型字符。数据类型字符的作用有2个,一是确定将要输出的数据类型,如x是整型数,e是浮点数等;二是确定要输出的形式,x是以小写十六进制输出整型数,e则是以指数形式输出浮点数。&&&&数据类型字符是必须的。数据类型字符解析完毕,各种信息写入FormatRec结构,接着就是具体的各种数据的格式化过程了,其代码将在后面给出。&&&&7)错误处理。如果在'%'字符后,出现上述各种字符以外的字符,或者上述各种字符排列顺序错误,就需要进行错误处理。printf系列函数的错误处理在不同的编译器中的处理方式是不一样的,主要有2种处理方式:一是忽略本次数据分析,format指针退回到'%'之后,继续循环('%'后的字符作一般字符处理);二是不再作分析,直接将'%'后的所有字符输出到buffer后退出函数。本文sprintfA函数采用了后一种处理方式,前一种处理方式在函数主体中也能找到,就是被注释了的语句。&&&&如果没有错误,则回到1),继续下一数据分析。&&&&&&&&&&&& 三、格式化字符及字符串。view plainprint?// 宽字符串转换ANSI字符串。参数:ANSI字符串,宽字符串,转换字符数(0不转换)。&&// 返回实际转换字符个数&&static INT WStrToStr(LPSTR dst, LPCWSTR src, INT count)&&{&&&&&&return WideCharToMultiByte(CP_THREAD_ACP, 0, src, -1,&&&&&&&&&&dst, count & 0? count + 1: 0, NULL, NULL) - 1;&&}&&&&// 格式化字符。参数:缓冲区,格式记录。返回缓冲区尾偏移&&static LPSTR FormatCharA(LPSTR buffer, FormatRec *rec)&&{&&&&&&INT len,&&&&&&LPSTR p =&&&&&&&&if (rec-&type == TYPE_LONG)&&&&&&{&&&&&&&&&&len = WStrToStr(NULL, (LPCWSTR)rec-&param, 0);&&&&&&&&&&if (len == 0) len = sizeof(CHAR);&&&&&&}&&&&&&else len = sizeof(CHAR);&&&&&&spaces = rec-&width -&&&&&&if (rec-&left == FALSE && spaces & 0)&&&&&&{&&&&&&&&&&memset(p, CHAR_SPACE, spaces);&&&&&&&&&&p +=&&&&&&}&&&&&&if (rec-&type == TYPE_LONG)&&&&&&{&&&&&&&&&&WStrToStr(p, (LPCWSTR)rec-&param, len);&&&&&&&&&&p +=&&&&&&}&&&&&&else *p ++ = *(LPCSTR)rec-&&&&&&&if (rec-&left == TRUE && spaces & 0)&&&&&&{&&&&&&&&&&memset(p, CHAR_SPACE, spaces);&&&&&&&&&&p +=&&&&&&}&&&&&&rec-&param += rec-&type == TYPE_LONG? TS_WCHAR : TS_CHAR;&&&&&&&&}&&&&// 格式化字符串。参数:缓冲区,格式记录。返回缓冲区尾偏移&&static LPSTR FormatStrA(LPSTR buffer, FormatRec *rec)&&{&&&&&&INT len,&&&&&&LPSTR p =&&&&&&&&if (rec-&type == TYPE_LONG)&&&&&&&&&&len = WStrToStr(NULL, *(LPCWSTR*)rec-&param, 0);&&&&&&else&&&&&&&&&&len = lstrlenA(*(LPCSTR*)rec-&param);&&&&&&if (rec-&precision &= 0 && len & rec-&precision)&&&&&&&&&&len = rec-&&&&&&&spaces = rec-&width -&&&&&&if (rec-&left == FALSE && spaces & 0)&&&&&&{&&&&&&&&&&memset(p, CHAR_SPACE, spaces);&&&&&&&&&&p +=&&&&&&}&&&&&&if (rec-&type == TYPE_LONG)&&&&&&&&&&WStrToStr(p, *(LPCWSTR*)rec-&param, len);&&&&&&else&&&&&&&&&&memcpy(p, *(LPCSTR*)rec-&param, len);&&&&&&p +=&&&&&&if (rec-&left == T

我要回帖

更多关于 进攻战术基础配合方法 的文章

 

随机推荐