第六章 函数设计
函数接口的两个要素,参数和返回值。
如果参数是指针,且仅作输入用,则应在类型前加const,防止该指针在函数体内被意外修改。
c标准库函数printf采用不确定参数的典型代表,这种风格的函数在编译时丧失了严格的类型安全检查。
getchar的返回类型不是char,二是int类型,其原型如下:int getchar(void);正常情况下,getchar的确返回单个字符,但如果getchar碰到文件结束标识或发生读错误,它必须返回一个标识EOF。为了区别于正常的字符,只好将EOF定义为负数(通常为-1)。因此getchar就成了int型。
有时候函数原本不需要返回值,但为了增加灵活性(如支持链式表达),可以附加返回值。例如字符串拷贝函数strcpy的原型:char *strcpy(char *strDest, const char *strSrc);
可以获得如下灵活性:
char str[20];
int length = strlen(strcpy(str, "Hello World"));
函数内部的实现规则:
在函数体的“入口处”,对参数的有效性进行检查。充分理解并正确使用“断言”(assert)。
在函数体的“出口处”, 对return语句的正确性和效率进行检查。return语句不可以返回指向“栈内存”(例如,函数内的局部变量)的“指针”或者“引用”, 因为该内存在函数体结束时自动销毁。如果函数返回值是一个对象,要考虑return语句的效率。
引用与指针的比较:
1)引用被创建的同时必须被初始化,指针则可以在任何时候进行初始化。
2)不能有NULL引用,引用必须与合法的存储单元关联,指针可以是NULL。
3)一旦引用被初始化,就不能改变引用的关系,指针则可以随时改变所指的对象。
引用的主要功能是传递函数的参数和返回值。c++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。
第七章 内存管理
内存分配方式有三种:
(1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
(2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集
中,效率很高,但是分配的内存容量有限。
(3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。
常见的内存错误:
(1)内存分配未成功,却使用了它。
(2)内存分配成功,但为初始化就是用了它。
(3)内存分配成功并初始化,但操作超越了内存边界。
(4)忘记释放内存,造成内存泄露。
(5)释放了内存却使用它。三种情况:(1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。(2)函数的return 语句写错了,注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。(3)使用free 或delete 释放了内存后,没有将指针设置为NULL。导致产生“野指针”。
指针与数组的区别。(见另一篇博文)
(1)指针消亡了,并不表示它所指的内存会被自动释放。
(2)内存被释放了,并不表示指针会消亡或者成了NULL 指针。
malloc 与free 是C++/C 语言的标准库函数,new/delete 是C++的运算符。它们都可用于申请动态内存和释放内存。
对于非内部数据类型的对象而言,光用maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
因此 C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete 不是库函数。