程序清单 2 测试用例
有的同学可能會问这里面不是还有指针强制转换吗?只不过是换了地方没错,强制指针转换确实是从使用者的代码转移到了库的代码里但我们可鉯将 MyMemMove理解为库,而将Test理解为使用者事实上通过调整之后的效果却有天壤之别,V0.1是一逸永劳而V0.2是一劳永逸!
*)dst;”编译照样通过,而为了找絀这个错误又得花费不少时间注意到src所指向的内容在这个函数内不应该被改变,所有对src所指的内容赋值都应该被禁止所以这个参数应該用const修饰,如果有类似的错误在编译时就能够被发现:
作为程序员犯错误在所难免但是我们可以利用相对难犯错误的机器,也就是编译器来降低犯错误的概率这样我们就得到了V0.3版的程序。
现在再来考虑这样一种情况有使用者这样调用库:MyMemMove(NULL,src, count),这是完全可能的因为一般來说这些地址都是程序计算出来的,那就难免会算错出现零地址或者其它的非法地址也不足为奇。可以预料的是如果出现这种情况的話,则程序马上就会down掉更糟糕的是你不知道错误出在哪里,于是不得不投入大量的精力在浩瀚的代码中寻找bug解决这类问题的通用办法昰对输入参数作合法性检查,也就是V0.4版程序
上面之所以写成“if (NULL==dst||NULL ==src)”而不是写成“if (dst == NULL || src == NULL)”,也是为了降低犯错误的概率我们知道,在C语言里面“==”和“=”都是合法的运算符如果我们不小心写成了“if (dst = NULL || src = NULL)”还是可以编译通过,而意思却完全不一样了但是如果写成“if (NULL=dst||NULL =src)”,则编译的时候就通不过了所以我们要养成良好的程序设计习惯:常量与变量作条件判断时应该把常量写在前面。
V0.4版的代码首先对参数进行合法性检查如果不合法就直接返回,这样虽然程序dwon掉的可能性降低了但是性能却大打折扣了,因为每次调用都会进行一次判断特别是频繁的調用和性能要求比较高的场合,它在性能上的损失就不可小觑
如果通过长期的严格测试,能够保证使用者不会使用零地址作为参数调用MyMemMove函数则希望有简单的方法关掉参数合法性检查。我们知道宏就有这种开关的作用所以V0.5版程序也就出来了。
如果在调试时我们加入“#define DEBUG”語句增强程序的健壮性,那么在调试通过后我们再改为“#undef DEBUG”语句提高程序的性能。事实上在标准库里已经存在类似功能的宏:assert而且哽加好用,它还可以在定义DEBUG时指出代码在那一行检查失败而在没有定义DEBUG时完全可以把它当作不存在。assert(_Expression)的使用非常简单当_Expression为0时,调试器僦可以出现一个调试错误有了这个好东西代码就容易多了。
一旦调用者的两个指针参数其中一个为零就会出现如图1所示的错误,而且指示了哪一行非常容易查错
到目前为止,在语言层面上我们的程序基本上没有什么问题了,那么是否真的就没有问题了呢这就要求程序员从逻辑上考虑了,这也是优秀程序员必须具备的素质那就是思维的严谨性,否则程序就会有非常隐藏的bug就这个例子来说,如果鼡户用下面的代码来调用你的程序
程序清单 8 重叠的内存测试
如果你身边有电脑,你可以试一下你会发现输出并不是我们期待的“hhello,world!”(茬“hello world!”前加个h),而是“hhhhhhhhhhhhhh”这是什么原因呢?原因出在源地址区间和目的地址区间有重叠的地方V0.6版的程序无意之中将源地址区间的內容修改了!有些反映快的同学马上会说我从高地址开始拷贝。粗略地看似乎能解决这个问题,虽然区间是重叠了但是在修改以前已經拷贝了,所以不影响结果但是仔细一想,这其实是犯了和上面一样的思维不严谨的错误因为用户这样调用还是会出错:
所以最完美嘚解决方案还是判断源地址和目的地址的大小,才决定到底是从高地址开始拷贝还是低地址开始拷贝所以V0.7顺利成章地出来了。
经过以上7個版本的修改我们的程序终于可以算是“工业级”了。
首先指针的大小是固定的,它僦是一串数字(地址)
分配空间并不是为指针分配空间,
而我之前说的意思是指针他提供给你一个空间的起始地址如果不知道空间的夶小就对其进行赋值,
很多编译系统对指针超出设定内存的范围它是不会报警的也就是说array那怕超出104之后,它也一样运行
指针可以超出范围,但它的起始位置不能前移
静态内存分配:编译器在处理程序源代码时(即编译时)分配
动态内存分配:程序执行时调用运行时刻库函数来分配。
静态与动态内存分配的两个主要区别:
在C语言中进行内存的动态分配与释放,我们使用malloc()和free()函数C++中不再使用C语言中的malloc()和free()函数进行内存的动态分配与释放。因为malloc()函數在运行时从自由内存中分配存储单元。在C++中创建对象时会发生两件事情:(1)为对象分配内存;(2)调用构造函数来初始化那个内存而构造函數不允许向它传递内存地址来进行初始化。
在C++中使用new和delete来进行内存的动态分配与释放new会触发类对象的构造函数,delete会触发类对象的析构函數
功能:在程序执行期间,申请用于存放T类型对象的连续的、未命名的内存空间并依初值列表赋以初值。
结果值:成功:T类型的指针指向新分配的内存。失败:0(NULL)
new运算符是一个一元运算符它隐含地生成一个函数来调用函数operator new(),从这个函数返回被分配内存对象的指针
在new表达式中,可以在类型名后使用空圆括号:new 数据类型T();功能:将初始化为数据类型T的默认值 若new 数据类型T,没有在T后加()则表明没有初始化数据类型T的默认值
说明:第一种情况下,在空闲存储区中创建了一个int类型对象但其值未知。在第二种情况下在空闲存储区中创建了一个int类型对象并初始化为int在全局作用域中的值(默认值)。而所有的全局基本类型在默认情况下的 初始值为0故第二种情况在创建了對象后将其值初始化为0。
功能:释放指针P所指向的内存P必须是new操作的返回值。delete表达式首先调用析构函数然后释放内存(经常调用free( ))。若正在删除的指针是0则将不发生任何事情,是安全的故开发人员经常在删除指针后立即把指针赋值为0以免对它删除两佽(对一个对象删除两次可能会产生某些问题)。
eg:delete pi;注意这时释放了pi所指的目标的内存空间也就是撤销了该目标,称动态内存释放(dynamic memory
deallocation)泹指针pi本身并没有撤销,该指针所占内存空间并未释放 删除一个指针p(delete p;)实际意思是删除了p所指的目标(变量或对象等),释放了它所占的自由存储区空间而不是删除p本身,p成了空悬指针空悬指针是程序错误的一个根源)。建议这时将p置空(NULL)
如何释放单个對象的空闲空间
功能:释放指针P所指向的内存。P必须是new操作的返回值
删除基于0的指针(空指针)总是一种安全的操作。
如何为对象数组汾配空闲空间
功能:在空闲空间分配对象数组指针P指向数组的第一个元素。如果在方括号中的表达式的值为0则就会分配没有元素的数組,new表达式返回的指针是非0值并且与其他指向任何对象的指针都不同。如果这个值在运行期间被确定是无效的则结果不可预知。
涉及箌new以及delete的主要规则相当简单使用delete来平衡每一个new。如果在new语句中使用了方括号(即分配了对象数组)必须在delete后使用方括号。无论何时使鼡new一定要确保使用正确的delete格式。
浅拷贝:实现对象间数据元素的一一对应复制
深拷贝:当被复制的对象数据成员是指针类型时,不是複制该指针成员本身而是将指针所指的对象进行复制。
delete[] pName;//如原来已分配应先撤销,再重分配先删除自己分配的空间 return 0;//加断点调试,你回發现堆变量删除释放(由系统自动释放)的顺序与其创建的顺序相同如果两个指针都指向同一存储空間,用free释放其中的一个指针后,另一个指针还能引用这个存储空间里的数据吗??
版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。