想不到在这里又能拜读到你的作品,真是让人兴奋。
还是那句话,希望你继续努力,为你的努力而喝采
楼主能不能提个意见。
由于我不习惯在电脑上看书,所以总想把教材打印出来,可是现在的一些阅读器都设置了保护,你能不能提供WORD版的下载,方便我们打印出来后阅读,谢谢。
对不起,好久没有更新了。不是我把写书忘了,也不是我偷懒了,前一段时间都在应付考试。好在最后成绩还算可以,也对得起我耽误下来的这些工作。
言归正传,这次我写的是指针。大家一直都说指针是最难学的部分,其实我也这样认为。我觉得这次有些地方还是写得有些不清不楚,所以还希望大家能够多多提意见。如果觉得哪里模糊了概念、难以理解或者不太合理,欢迎来和我交流。
在内容取舍方面,我把字符串和函数指针去掉了。因为C++主要是面向对象的,把C-String放进来讲得很深就有把C和C++风格混淆之嫌。所以我想等到面向对象了,把string和vector一起说。至于函数指针,基本上没什么大用处,大家还没学汇编,对指令什么的还不熟悉,所以不说也罢。
堆内存其实一直是指针里的重头戏,我把关于堆内存到底处在什么位置省略了,我觉得这个只会让初学者更迷茫。大家只要知道堆内存是动态申请的就可以了。要知道什么是堆内存,需要知道什么是堆什么是栈什么是段等等,对初学者来说没必要。对于C里面的malloc和free我也不说了,这个对初级C++程序员来说完全是增加负担,多此一举。
大家如果觉得有哪些重要的地方被我忽略了,希望能够提出来,我再斟酌一下,有必要的话我会补写。最后还是要感谢大家支持!下面给出本章一些节选:
什么是指针
在我们的桌面上,往往有这样一些图标:在它们的左下角有个小箭头,我们双击它,可以调出本机内的一些程序或文件。然而我们发现这些图标所占的存储空间很小,一般也就几百到几千字节。可是那么小的文件怎么会让上百兆的程序执行起来的呢?
后来,我们知道那些有小箭头的图标文件称为快捷方式。它所存储的内容并不是所要调用的程序本身,而是所要调用的程序在本机磁盘上的位置。(比如D:\Tencent\QQ\QQ.exe,如图8.1所示)使用快捷方式的目的就是为了快捷方便,不用去查找程序就能去执行它。不过如果所要调用的程序不存在或位置不正确,那么双击了这个快捷方式就会导致错误发生。
在内存中,可能会有一些数据,我们不知道它的变量名,却知道它在内存中的存储位置,即地址。我们能否利用快捷方式的原理对这些数据进行调用和处理呢?
很幸运,在C++中,也可以给内存中的数据创建“快捷方式”,我们称它为指针(Pointer)。它和整型、字符型、浮点型一样,是一种数据类型。指针中存储的并不是所要调用的数据本身,而是所要调用的数据在内存中的地址。我们可以通过对指针的操作来实现对数据的调用和操作。
更灵活的存储——堆内存
家里要来客人了,我们要客人们泡茶。如果规定只能在确定来几位客人之前就把茶泡好,这就会显得很尴尬:茶泡多了会造成浪费,泡少了怕怠慢了客人。所以,最好的方法就是等客人来了再泡茶,来几位客人泡几杯茶。
然而,我们在使用数组的时候也会面临这种尴尬:数组必须在程序运行前声明,即数组的大小在编译前必须是已知的常量表达式。空间申请得太大会造成浪费,空间申请得太小会造成数据溢出而使得程序异常。所以,为了解决这个问题,我们需要能够在程序运行时根据实际情况申请内存空间。
在C++中,允许我们在程序运行时根据自己的需要申请一定的内存空间,我们把它称为堆内存(Heap)空间。
终于把第十一章写完了。感觉不太好,还是觉得很难把握“讲多少”的尺度。说多了读者看不懂,说少了读者还是看不懂。如果大家对这一章觉得有难以理解的欢迎来和我联系。
这一章主要讲了静态、全局变量,变量的作用域和可见性,头文件和程序的调试。前面一部分内容主要是对以前一些知识的补充。而程序的调试我自认为还是有一些创意的,介绍了如何快速地找到语法错误,如何观察程序的运行和Debug的使用。至少我在别的书上很少看到介绍这方面的知识。建议一些看得懂程序却自己编不好程序的初学者来看看。
另外书本改名了,原因是大家都说这名字太土。封面也重新做了,原因是大家都说原来的难看。希望这次修改没有让它们变得更差……
感谢室友qqqqaug当时想出来的英文名给了我灵感。感谢我的高中同学对我的支持!
本次内容节选:
我们已经说明,变量可以使用的范围是由变量的作用域决定。不同层次的变量的作用域,就好像大小不一的纸片。把它们堆叠起来,就会发生部分纸片被遮盖的情况。我们把这种变量作用域的遮盖情况称为变量的可见性(Visibility)。然而,当两张纸处于同一个层次,显然两者就不可能发生遮盖了。所以,如果我们在同一个层次中声明两个名字相同的变量,那么他们的作用域就不是遮盖,而是冲突了。因此,在某个函数的同一语法层次内不能声明多个名字相同的变量。
#include是一条编译预处理命令。什么叫编译预处理命令呢?我们知道,程序中的每一句语句会在运行的时候能得到体现。比如变量或函数的声明会创建一个变量或者函数,输出语句会在屏幕上输出字符。然而编译预处理命令却不会在运行时体现出来,因为它是写给编译器的信息,而不是程序中需要执行的语句。编译预处理命令不仅仅只有#include一条,在C++中,所有以#开头的命令都是编译预处理命令,比如#if、#else、#endif、#ifdef、#ifndef、#undef和#define等等。
调试主要分四个步骤和两种处理方式。我们把程序的编译和连接统称为编译阶段,把程序的运行和测试统称为运行阶段。在编译阶段发生的错误称为编译错误(Compile Error),在运行阶段发生的错误称为运行时错误(Runtime Error)。对于编译错误,我们通过检查并修正语法错误来解决;对于运行时错误,我们通过检查并修正语意(程序设计思想)错误来解决。
在介绍如何使用Debug工具之前,我们要介绍一下什么是断点(Breakpoint)。当程序运行到断点的时候,它会暂时停止运行后面的语句,供用户观察程序的运行情况,并等待用户发出指令。断点不是语句,而是附在某条语句上的一个标志。
这次更新的周期实在是有点长了,主要是最近真的很忙,一边上课做单片机实验,一边ACM训练,一边帮论坛做手册,一边还在写着自己的书。
通过上次和一位学长的交流,我觉得还是有必要把主函数返回类型改为整型。的确,在有标准存在的情况下,不符合标准的教材就是不合格的产品,所以,无论如何应该贯彻这个标准。于是我把所有的主函数全都改为返回整型了。如有疏漏或写的不自然之处,欢迎来提建议!
本册更新的主要内容是如何编写程序,我从算、找、实现功能三方面作为分类,讲述如何解决问题,编写程序,并在“算法时间”中,指出了掌握好这三者的核心。接下来稍微介绍了一下驱动测试的程序设计,其实这也是偶然的机会在编译原理课上和别的同学讨论看到的,我觉得这种主导思想和我自己的想法很符合,而且很多时候我也是用类似与这样的方法来做一个程序的,所以也拿出来提一下。最后讲了递归,其实我自己并不爱好递归,平时也不经常使用,虽然也用来写过程序。我想主要是要交待一个调用函数的机制,这对了解高级语言和对以后数据结构的学习有好处。
本次内容节选:
当我们遇到一个问题,它往往不是一个直接用程序代码描述的问题,比如:统计销售量和利润,寻找出行的公交线路,将中文翻译成英语等等。所以我们先要把实际问题转化成一个电脑能够解决的问题,而大多数问题一般分为三类:
(1)算:计算利润,计算一元二次方程的根,计算一个数列的和等等。
(2)找:找最大的一个数,找最短的一条路径,找一个字符串的位置等等。
(3)实现功能:实现撤销、重做的功能,实现模拟某种操作的功能等等。有时候实现功能问题可以拆解为若干个“算”和“找”的问题。
大家以前可能有过这样的经历:如果做一份练习卷,是做一道题目核对一次答案的正确率高,还是做完所有题目再核对答案的正确率高?一般来说是前者。造成这种结果的原因有二:如果所做的每道题都是正确的,那么无疑给解答后面的题目增加了信心;如果某道题做错了,那么立刻就能发现问题,并且保证在之后不会再犯同类的错误。
我们开发程序,就如同是做一份练习卷。传统的开发方式是等到整个程序完成后再进行测试,这给开发者的心理带来阴影,也给查找问题的症结带来了麻烦。而如果每做完一个功能模块就测试一次,则可以在小范围内找到问题所在。在积累经验的同时也给自己增加了信心。
以上只是测试驱动程序开发的一种主导思想,在我们编写程序的时候值得借鉴采纳。
可通过递归解决的问题往往能够转化为若干个解决步骤,并且第n步和第n+1步有着类似或相同的限制条件。比如一个数列的递推公式:
an=3an-1+2,an-1=3an-2+2,an-2=3an-3+2,……,a1=3a0+2,a0=1,它的相邻两项有着相同的关系,即求第n项和第n+1项有着相同的方法。唯一的不同就是a0=1,我们把这种与众不同的方法称为递归出口,不断调用函数的递归将在那里终止。