标题:
重
载
函数再
论
重
载
函数是
C++
提出来的概念,但是在
C
中却未必没有。比如
“1+3”
和
“1.0+3.0”
,
虽
然都是加法,做的却不是同的操作:
编译
器要因操作数的不同而
调
用不同的加法操作。只是
C
语
言中除了内部
类
型
变
量可以参与运算以外,没有
“
类
”
这么
高深的概念。
“
结
构体
”
也只是内存数据的
组织
方法,而不
涉
及
对
整个
结
构体的
处
理。所以,在
C
语
言
时
代
编译
器明明做了
类
似于重
载
的事情,却可以像雷
锋
一
样
“
做好事不留名
”
。
C++ 发 展出了 类 ,并且 赋 予了 “ 类 ” 很高的期望, 类 的 对 象也能像内置 类 型 对 象一 样 参与一切运算。那 么 ,就拿加法运算来 说 , 编译 器如何知道 对 某 类对 象的加法 该调 用哪一个 详细 的操作代 码 ?于是,即使不出 现 普通函数的重 载 ,至少运算符是要重 载 的。
林 锐 博士在《高 质 量 C++/C 编 程指南》中 为 重 载 函数的必要性提了另一个理由: 类 的构造函数名称必 须 与 类 名相同,而 类 却 经 常要定 义 多个不同的构造函数。那就只好重 载 了。
对 于普通程序 员 来 说 ,我 们 完全可以不用考 虑 得 这么 深。重 载 函数 给 我 们 至少 还带 来了另一个好 处 :不用 记忆 多个不同的函数名了,也不用 为 了 给 函数起名而 绞 尽 脑 汁了。不 过 本 书还给 出了一个建 议 :并不是任何 时 候都有必要重 载 函数的,有的 时 候不同的函数名可 以直 观 地 带 来好多信息, 滥 用重 载 只是 牺 牲了名称中的信息。
C++ 发 展出了 类 ,并且 赋 予了 “ 类 ” 很高的期望, 类 的 对 象也能像内置 类 型 对 象一 样 参与一切运算。那 么 ,就拿加法运算来 说 , 编译 器如何知道 对 某 类对 象的加法 该调 用哪一个 详细 的操作代 码 ?于是,即使不出 现 普通函数的重 载 ,至少运算符是要重 载 的。
林 锐 博士在《高 质 量 C++/C 编 程指南》中 为 重 载 函数的必要性提了另一个理由: 类 的构造函数名称必 须 与 类 名相同,而 类 却 经 常要定 义 多个不同的构造函数。那就只好重 载 了。
对 于普通程序 员 来 说 ,我 们 完全可以不用考 虑 得 这么 深。重 载 函数 给 我 们 至少 还带 来了另一个好 处 :不用 记忆 多个不同的函数名了,也不用 为 了 给 函数起名而 绞 尽 脑 汁了。不 过 本 书还给 出了一个建 议 :并不是任何 时 候都有必要重 载 函数的,有的 时 候不同的函数名可 以直 观 地 带 来好多信息, 滥 用重 载 只是 牺 牲了名称中的信息。
标题:
:重
载
函数的概念
引用:出
现
在相同作用域中的两个(可以是两个以上
——
偷
猫注)函数,如果具有
相同的名字而形参表不同,
则
称
为
重
载
函数。
本 节开头 第一句 话 就 给 出了重 载 函数的定 义 :重 载 函数必 须 符合两个条件:一是出 现 在相同的作用域中、二是函数名字相同而形参表不同。
其中第一个条件一般人往往是不去想的,其 实 函数名相同而作用域不同的函数大大存在,比如在 MFC 中就有。它 们 是完全不相干的函数。
第二个条件 还 可以 详说 一下:函数名字相同当然不在 话 下, 这 是函数被称 为 “ 重 载 ” 的根源。之于形参表不同,可能表 现 在形参个数不同、可能表 现 在形参 类 型不同、 还 可能表 现 在形参 顺 序不同。
如果要 扩 展 开 来 说 , 还 可以 举 出 许 多 不是重 载 函数的情况。
一、如果既在同一作用域下、名称也相同、形参表也相同, 则 后者被 视为 前者的重 复 声明。 —— 函数可以重 复 声明,因 为 函数的声明并不 产 生目 标 代 码 ,但是函数的定 义 不允 许 重 复 出 现 。
二、如果既在同一作用域下、名称也相同、形参表也相同,但是返回 值 不同, 则 后者被 视为错误 的声明。函数不可以只凭返回 值 来区分,因 为调 用函数的 时 候只凭名称和形参来 选择 函数,而不凭返回 值 。再究其原因,一是因 为 函数的返回 值 可以被 丢 弃;二来即使不 丢 弃,将返回 值赋 予另一个 变 量之前没必要 检查 我需要什 么样 的返回 值 ,而能否 赋值 也与函数本 身无 关 。
三、有些 时 候看起来形参表不同, 实际 上是完全相同的, 书 本第 229 页讲 了四 组这样 的例子:
Record lookup(const Account &acct);
Record lookup(const Account &);// 区 别 在于有没有 给 形参命名
typedef Phone Telno;
Record lookup(const Phone&);
Record lookup(const Telno&);// 只是 给类 型取了个 别 名
Record lookup(const Phone&, const Name&);
Record lookup(const Phone&, const Name& = "");// 区 别 在于 给 形参提供了默 认值
Record lookup(Phone);
Record lookup(const Phone);// 区 别 在于是否 const
其中第三 组 可能会 让 人 产 生函数的形参个数不同的假像,其 实 可缺省的形参并没有减少形参的个数。第四 组 有点不容易搞清:因 为 有的 时 候可以凭是否 const 来重 载 ,比如引用 传递 和指 针传递 。
本 节开头 第一句 话 就 给 出了重 载 函数的定 义 :重 载 函数必 须 符合两个条件:一是出 现 在相同的作用域中、二是函数名字相同而形参表不同。
其中第一个条件一般人往往是不去想的,其 实 函数名相同而作用域不同的函数大大存在,比如在 MFC 中就有。它 们 是完全不相干的函数。
第二个条件 还 可以 详说 一下:函数名字相同当然不在 话 下, 这 是函数被称 为 “ 重 载 ” 的根源。之于形参表不同,可能表 现 在形参个数不同、可能表 现 在形参 类 型不同、 还 可能表 现 在形参 顺 序不同。
如果要 扩 展 开 来 说 , 还 可以 举 出 许 多 不是重 载 函数的情况。
一、如果既在同一作用域下、名称也相同、形参表也相同, 则 后者被 视为 前者的重 复 声明。 —— 函数可以重 复 声明,因 为 函数的声明并不 产 生目 标 代 码 ,但是函数的定 义 不允 许 重 复 出 现 。
二、如果既在同一作用域下、名称也相同、形参表也相同,但是返回 值 不同, 则 后者被 视为错误 的声明。函数不可以只凭返回 值 来区分,因 为调 用函数的 时 候只凭名称和形参来 选择 函数,而不凭返回 值 。再究其原因,一是因 为 函数的返回 值 可以被 丢 弃;二来即使不 丢 弃,将返回 值赋 予另一个 变 量之前没必要 检查 我需要什 么样 的返回 值 ,而能否 赋值 也与函数本 身无 关 。
三、有些 时 候看起来形参表不同, 实际 上是完全相同的, 书 本第 229 页讲 了四 组这样 的例子:
Record lookup(const Account &acct);
Record lookup(const Account &);// 区 别 在于有没有 给 形参命名
typedef Phone Telno;
Record lookup(const Phone&);
Record lookup(const Telno&);// 只是 给类 型取了个 别 名
Record lookup(const Phone&, const Name&);
Record lookup(const Phone&, const Name& = "");// 区 别 在于 给 形参提供了默 认值
Record lookup(Phone);
Record lookup(const Phone);// 区 别 在于是否 const
其中第三 组 可能会 让 人 产 生函数的形参个数不同的假像,其 实 可缺省的形参并没有减少形参的个数。第四 组 有点不容易搞清:因 为 有的 时 候可以凭是否 const 来重 载 ,比如引用 传递 和指 针传递 。
标题:
:文件的
组织
一个程序往往由多个源文件
组
成,
这
些代
码
究竟
应该
放在哪个源文件里、哪些代
码
可以放在同一个源文件里、哪些代
码
必需分
开
放。
这
是一个管理
层
面的
问题
。
说 它是管理 层 面的 问题 ,是因 为这 些代 码 的 组织 往往没有惟一的准 则 。但是它 们还 是有一定的 规 律的。
首先, 软 件的 维护 是一个 复杂 的系 统 工程。代 码 的 组织应该 有利于 维护 。 应该 尽量把直接相 关 的内容放在同一文件、不相 关 的内容放在不同的文件里。如果 这 些代 码还 有 亲 和疏,那就要分不同的文件 夹 来存放了。
其次, 软 件的代 码 是一个 严 格的 组织 体系。不同的内容之 间 可能是并列的,也可能有必要的先后 关 系。于是在 “#include” 的 时 候要注意 顺 序。
最后,也是最重要的一点,有些代 码 在同一工程中可以重用(或必 须 重用),有些代 码 在同一个工程中只能出 现 一次。可以重用的有 类 的声明、函数的声明、 变 量的声明等,不可以重用的是 类 的 实 体、函数的 实 体、 变 量的定 义 等。那 么 ,把可以重用的内容放在 h 文件中,把不可以重用的放在 cpp 文件中是一个好 办 法。
拿 类 的声明和 类 的 实 体 为 例,如果把一个 类 的所有内容一古 脑 放在同一个文件中,将可能出 现问题 。因 为 在其它用到 类实 例的地方都必 须让类 的声明 “ 可 见 ” ,所以我 们 往往在文件 头 部加个 “#include” , 结 果 类 的 实 体也被 编译 多次,在 连 接 时产 生冲突。
在前文中曾提到 过 ,内 联 函数是惟一允 许 (也是必 须 )在 编译时让 函数 实 体可 见 的的函数。所以内 联 函数可以放在 h 文件中。 C++ 规则 中有一句正好与此照 应 :在 类 的声明中直接写出的函数被 认为 是内 联 函数。
Visual C++ 给类 的文件起默 认 名 时 ,文件名往往与 类 名一致。如果 类 名由 “C” 开头 , 则 文件会是除去 开头 的 “C” 字以外的其它文字。如 类 “CMyClass” ,它的代 码 存放在以下两个文件中: “MyClass.h” 和 “MyClass.cpp” 中。原因是 VC++ 建 议类 名以 C 开头 ,至于 为 什 么 在文件名中不出 现开头 的 “C” ,可能是出于微 软 的 习惯 吧 。
说 它是管理 层 面的 问题 ,是因 为这 些代 码 的 组织 往往没有惟一的准 则 。但是它 们还 是有一定的 规 律的。
首先, 软 件的 维护 是一个 复杂 的系 统 工程。代 码 的 组织应该 有利于 维护 。 应该 尽量把直接相 关 的内容放在同一文件、不相 关 的内容放在不同的文件里。如果 这 些代 码还 有 亲 和疏,那就要分不同的文件 夹 来存放了。
其次, 软 件的代 码 是一个 严 格的 组织 体系。不同的内容之 间 可能是并列的,也可能有必要的先后 关 系。于是在 “#include” 的 时 候要注意 顺 序。
最后,也是最重要的一点,有些代 码 在同一工程中可以重用(或必 须 重用),有些代 码 在同一个工程中只能出 现 一次。可以重用的有 类 的声明、函数的声明、 变 量的声明等,不可以重用的是 类 的 实 体、函数的 实 体、 变 量的定 义 等。那 么 ,把可以重用的内容放在 h 文件中,把不可以重用的放在 cpp 文件中是一个好 办 法。
拿 类 的声明和 类 的 实 体 为 例,如果把一个 类 的所有内容一古 脑 放在同一个文件中,将可能出 现问题 。因 为 在其它用到 类实 例的地方都必 须让类 的声明 “ 可 见 ” ,所以我 们 往往在文件 头 部加个 “#include” , 结 果 类 的 实 体也被 编译 多次,在 连 接 时产 生冲突。
在前文中曾提到 过 ,内 联 函数是惟一允 许 (也是必 须 )在 编译时让 函数 实 体可 见 的的函数。所以内 联 函数可以放在 h 文件中。 C++ 规则 中有一句正好与此照 应 :在 类 的声明中直接写出的函数被 认为 是内 联 函数。
Visual C++ 给类 的文件起默 认 名 时 ,文件名往往与 类 名一致。如果 类 名由 “C” 开头 , 则 文件会是除去 开头 的 “C” 字以外的其它文字。如 类 “CMyClass” ,它的代 码 存放在以下两个文件中: “MyClass.h” 和 “MyClass.cpp” 中。原因是 VC++ 建 议类 名以 C 开头 ,至于 为 什 么 在文件名中不出 现开头 的 “C” ,可能是出于微 软 的 习惯 吧 。
标题:
:
类
的构造函数
引用:构造函数是特殊的成
员
函数。
笔 记 :构造函数的确是一 类 “ 特殊 ” 的成 员 函数。它的特殊性至少表 现 在以下几个方面:一是它的 调 用不用程序 员 操心,只要 类对 象被 创 建它就会被 调 用,而且它不允 许 被程序 员显 式地 调 用。二是它 们 是必需的,如果程序 员偷懒 , 编译 器将自 动创 建 简单 的构造函数。三是它 们 的名字不用程序 员 多考 虑 ,直接与 类 名相同。四是它 们 没有返回 值 。
下面 详说这 几个特性:
一、它 们 在 类对 象被 创 建 时 自 动调 用, 创 建 对 象可能有以下方法:程序中用声明 变 量的 语 句直接声明 创 建,或者在程序中用 new 关键 字 动态创 建。 这 两 种 方法都可以 创 建 单 个 对 象,也都可以 创 建 对 象数 组 。只要有一个 对 象被 创 建,构造函数就被 调 用一次。
如果程序 员 想 显 式地 调 用构造函数那是不行的。正因 为 如此,构造函数中 还 有一 种 特定的部分叫 “ 初始化列表 ” ,通 过 它程序 员 可以 调 用基 类 或成 员 的构造函数。必竟 类 的 设计 千差万 别 ,如果某个 类 的基 类 或(和)成 员 有多个构造函数,那 么 , 该类 必 须 能 够 指定用哪一个构造函数,否 则类 的功能将大打折扣。 调 用构造函数不是程序 员 的事,程序 员 不 应该 管也管不了。初始化列表 为 解决 这 个 问题 而生,所以只有构造函数才有初始化列表,其它函数不能有。
上面 说 到的 “ 大打折扣 ” 究竟是怎 样 的折扣呢?如果 不能指定基 类 和成 员 用哪一个构造函数,那就只好 让编译 器去挑了,构造出来的 对 象往往不符合要求,只好 调 用基 类 和成 员 的其它函数,比如 赋值 函数或其它 进 行参数 设 定的函数 —— 当然,基 类 和成 员 必 须 包含 这样 的函数。 这样 就浪 费 了 资 源。
二、 类 必 须 包含构造函数 —— 确切地 说 是必 须 包含无参数构造函数和拷 贝 构造函数 —— 原因是因 为 它 们 的 调 用是自 动 的。如果 这 两个函数根本就没有,你 让 系 统 如何 调 用?所以, C++ 也不含糊,你要是 懒 得写,它就帮你写一个 简单 的。 简单 就意味着至少要 丧 失一些功能,如果 类设计 得比 较复杂 (比如包含指 针 操作) 还 可能引起灾 难 性事故。
三、函数名与 类 名一致。构造函数的名称是必 须 特殊的,即使 这 个特殊不表 现 在与 类 名相同,也必 须 找到另一个 规则 来 实现 。因 为 系 统 要自 动调 用 这 些函数,你就必 须让 系 统 知道哪些函数是构造函数。
第四个特性直接改 变 了 C/C++ 语 言的一条 规则 : C 语 言 规 定,如果函数没有明 显 指出返回 类 型,那 么 C 语 言 认为 返回 值 是 int 型。 C 语 言之所以可以有 这 条 规则 ,一是因 为 返回 int 的函数很多,二是因 为 即使没有返回 值 ,也必 须 指明 void 。当 时 制定 规则 的人无法 预 料到, C++ 中居然会出 现 “ 连 void 都不是的返回 值 ” 的函数, void 虽 然表示不返回任何 值 ,必竟与 类 构造函数的 “ 没有返回 值 ” 是两 码 事。于是, C++ 新 标 准 规 定:在定 义 或声明函数 时 ,没有 显 式指定返回 类 型中不合法的。当然 类 的构造函数除外。
构造函数的出 现 有它的可行院 捅 厝恍浴?尚行允怯捎 贑 ++ 的 类 允 许 包含成 员 函数,既然 类 可以包含普通的成 员 函数,那 么 包含特殊的函数自然也不在 话 下。必然性是由于 类 的 对 象往往必 须经过 特定的初始化。 C++ 到来之前, C 语 言中的数据 类 型只是内置 类 型。 对 于内置 类 型 对 象,如果忘了初始化,大不了 这 个 对 象失去作用,但是不会 导 致大的 问题 。比如一个 int 型 值 ,无 论 内存如何随 机,它的取 值 范 围 都不会超 过 int 能表达的范 围 , 对 它 进 行运算也不会 产 生危 险 (溢出不能算危 险 ,即使初始化 过 的数据也不能保 证 不溢出,而且溢出只是一 种逻辑问题 )。但是 现 在的 类 不 这么简单 了,忘了初始化往往将 带 来运行 错误 。于其 每 次都要考 虑 数据的初始化, 还 不如把 这 个初始化写成 统 一的函数, 让 系 统 自 动调 用来得既安全又方便 。
笔 记 :构造函数的确是一 类 “ 特殊 ” 的成 员 函数。它的特殊性至少表 现 在以下几个方面:一是它的 调 用不用程序 员 操心,只要 类对 象被 创 建它就会被 调 用,而且它不允 许 被程序 员显 式地 调 用。二是它 们 是必需的,如果程序 员偷懒 , 编译 器将自 动创 建 简单 的构造函数。三是它 们 的名字不用程序 员 多考 虑 ,直接与 类 名相同。四是它 们 没有返回 值 。
下面 详说这 几个特性:
一、它 们 在 类对 象被 创 建 时 自 动调 用, 创 建 对 象可能有以下方法:程序中用声明 变 量的 语 句直接声明 创 建,或者在程序中用 new 关键 字 动态创 建。 这 两 种 方法都可以 创 建 单 个 对 象,也都可以 创 建 对 象数 组 。只要有一个 对 象被 创 建,构造函数就被 调 用一次。
如果程序 员 想 显 式地 调 用构造函数那是不行的。正因 为 如此,构造函数中 还 有一 种 特定的部分叫 “ 初始化列表 ” ,通 过 它程序 员 可以 调 用基 类 或成 员 的构造函数。必竟 类 的 设计 千差万 别 ,如果某个 类 的基 类 或(和)成 员 有多个构造函数,那 么 , 该类 必 须 能 够 指定用哪一个构造函数,否 则类 的功能将大打折扣。 调 用构造函数不是程序 员 的事,程序 员 不 应该 管也管不了。初始化列表 为 解决 这 个 问题 而生,所以只有构造函数才有初始化列表,其它函数不能有。
上面 说 到的 “ 大打折扣 ” 究竟是怎 样 的折扣呢?如果 不能指定基 类 和成 员 用哪一个构造函数,那就只好 让编译 器去挑了,构造出来的 对 象往往不符合要求,只好 调 用基 类 和成 员 的其它函数,比如 赋值 函数或其它 进 行参数 设 定的函数 —— 当然,基 类 和成 员 必 须 包含 这样 的函数。 这样 就浪 费 了 资 源。
二、 类 必 须 包含构造函数 —— 确切地 说 是必 须 包含无参数构造函数和拷 贝 构造函数 —— 原因是因 为 它 们 的 调 用是自 动 的。如果 这 两个函数根本就没有,你 让 系 统 如何 调 用?所以, C++ 也不含糊,你要是 懒 得写,它就帮你写一个 简单 的。 简单 就意味着至少要 丧 失一些功能,如果 类设计 得比 较复杂 (比如包含指 针 操作) 还 可能引起灾 难 性事故。
三、函数名与 类 名一致。构造函数的名称是必 须 特殊的,即使 这 个特殊不表 现 在与 类 名相同,也必 须 找到另一个 规则 来 实现 。因 为 系 统 要自 动调 用 这 些函数,你就必 须让 系 统 知道哪些函数是构造函数。
第四个特性直接改 变 了 C/C++ 语 言的一条 规则 : C 语 言 规 定,如果函数没有明 显 指出返回 类 型,那 么 C 语 言 认为 返回 值 是 int 型。 C 语 言之所以可以有 这 条 规则 ,一是因 为 返回 int 的函数很多,二是因 为 即使没有返回 值 ,也必 须 指明 void 。当 时 制定 规则 的人无法 预 料到, C++ 中居然会出 现 “ 连 void 都不是的返回 值 ” 的函数, void 虽 然表示不返回任何 值 ,必竟与 类 构造函数的 “ 没有返回 值 ” 是两 码 事。于是, C++ 新 标 准 规 定:在定 义 或声明函数 时 ,没有 显 式指定返回 类 型中不合法的。当然 类 的构造函数除外。
构造函数的出 现 有它的可行院 捅 厝恍浴?尚行允怯捎 贑 ++ 的 类 允 许 包含成 员 函数,既然 类 可以包含普通的成 员 函数,那 么 包含特殊的函数自然也不在 话 下。必然性是由于 类 的 对 象往往必 须经过 特定的初始化。 C++ 到来之前, C 语 言中的数据 类 型只是内置 类 型。 对 于内置 类 型 对 象,如果忘了初始化,大不了 这 个 对 象失去作用,但是不会 导 致大的 问题 。比如一个 int 型 值 ,无 论 内存如何随 机,它的取 值 范 围 都不会超 过 int 能表达的范 围 , 对 它 进 行运算也不会 产 生危 险 (溢出不能算危 险 ,即使初始化 过 的数据也不能保 证 不溢出,而且溢出只是一 种逻辑问题 )。但是 现 在的 类 不 这么简单 了,忘了初始化往往将 带 来运行 错误 。于其 每 次都要考 虑 数据的初始化, 还 不如把 这 个初始化写成 统 一的函数, 让 系 统 自 动调 用来得既安全又方便 。
标题:
:
类
的成
员
函数
类
与
C
语
言中的
结
构体最大的区
别
就是
类
可以
带
函数,而
结
构体只是一个内存
组
合
。所以,要提
类
就不得不提成
员
函数。
类 的成 员 函数与普通函数(全局函数)相比,最根本的区 别 是 实现 了 类 的封装性。封装性的第一个表 现 是 访问权 限:都是函数,但是你能 访问 哪个不能 访问 哪个却可以 设 定。第二个表 现 是直 观 ,通 过类 成 员 (或指 针 )来 调 用函数, 给 人的直 觉 就是 “ 这 是 类 提供的功能 ” 。你好像 “Bird.Fly();” 一 样 一目了然。
在理解 this 指 针 以前要想 彻 底理解成 员 函数是有困 难 的,我就曾以 为 在 类 的 实 例中保存了函数的副本。要不然, 为 什 么 同一个 类 的不同 对 象 调 用 这 个函数有不同的效果呢?原来,在函数所有的形参之外, 还 有一个不用你操心的参数 this ,它是一个指 针 , 该 指 针 的目 标 就是函数的 调 用者。 这么 一 说 就明白了。
函数形参表后加入 const 就成了 “const 成 员 函数 ” , 这样 的函数保 护 了 调 用者自身不被修改。如 CString 的 GetLength() 函数,你只能 获 取它的 长 度,不能修改它的内容或 长 度。加入 const 的作用倒不是怕 调 用者修改,而是防止 编 写函数的人不小心改 动 了 对 象。因 为 百密 总 有一疏,万一在某个不 该 修改数据的函数中改 变 了数据(比如将 “==” 写成 “=” ),或者万一 调 用了另一个非 const 的成 员 函数都将可能引起 错误 。在 编 写函数前就先加上 const 可以 记编译 器来帮你 检查 。
这 个 const 加在形参表的后面 显 得有些怪怪的,造成 “ 怪怪的 ” 原因就是因 为 函数的形参表中没有 this ,也就没有能用 const 来修 饰 的 东 西了。林 锐说 “ 大概是因 为 其它地方都已 经 被占用了 ” 并不是根本原因。
类 的成 员 函数与普通函数(全局函数)相比,最根本的区 别 是 实现 了 类 的封装性。封装性的第一个表 现 是 访问权 限:都是函数,但是你能 访问 哪个不能 访问 哪个却可以 设 定。第二个表 现 是直 观 ,通 过类 成 员 (或指 针 )来 调 用函数, 给 人的直 觉 就是 “ 这 是 类 提供的功能 ” 。你好像 “Bird.Fly();” 一 样 一目了然。
在理解 this 指 针 以前要想 彻 底理解成 员 函数是有困 难 的,我就曾以 为 在 类 的 实 例中保存了函数的副本。要不然, 为 什 么 同一个 类 的不同 对 象 调 用 这 个函数有不同的效果呢?原来,在函数所有的形参之外, 还 有一个不用你操心的参数 this ,它是一个指 针 , 该 指 针 的目 标 就是函数的 调 用者。 这么 一 说 就明白了。
函数形参表后加入 const 就成了 “const 成 员 函数 ” , 这样 的函数保 护 了 调 用者自身不被修改。如 CString 的 GetLength() 函数,你只能 获 取它的 长 度,不能修改它的内容或 长 度。加入 const 的作用倒不是怕 调 用者修改,而是防止 编 写函数的人不小心改 动 了 对 象。因 为 百密 总 有一疏,万一在某个不 该 修改数据的函数中改 变 了数据(比如将 “==” 写成 “=” ),或者万一 调 用了另一个非 const 的成 员 函数都将可能引起 错误 。在 编 写函数前就先加上 const 可以 记编译 器来帮你 检查 。
这 个 const 加在形参表的后面 显 得有些怪怪的,造成 “ 怪怪的 ” 原因就是因 为 函数的形参表中没有 this ,也就没有能用 const 来修 饰 的 东 西了。林 锐说 “ 大概是因 为 其它地方都已 经 被占用了 ” 并不是根本原因。
标题:
:内
联
函数
内
联
函数
应该
是
为
了改善
C
语
言中的宏替
换
的不足而
产
生的吧。因
为
宏替
换
是
预编译
中直接展
开
的,展
开过
程中将
产
生意想不到的
结
果。
典型的有
“#define MAX(a, b) (a) > (b) ? (a) : (b)”
。
“result = MAX(i, j)+2;”
将被展
开为
“result = (i) > (j) ? (i) : (j) + 2;”
。
虽
然外面再加一
对
括号可以解决以上
问题
,但是
“result = MAX(i++, j);”
被展
开
后将
导
致
i
被自增
1
了两次。
(以上例子摘自林
锐
博士的《高
质
量
C++/C
编
程指
南》第
66
页
,林
锐
管
这
叫做
“
边际
效
应
”
)
C++ 用内 联 来取代宏替 换 ,大大提高了安全性。 虽 然内 联 函数也是 编译时 展 开 的,但是它能 进 行安全 检查 , 还 能 处 理 类 的成 员 函数(原因是内 联 函数能 够处 理 this 指 针 ,宏却不能)。
引用:内 联对编译 器来 说 只是一个建 议 , 编译 器可以 选择 忽略 这 个建 议 。
笔 记 :也就是 说 ,有些函数你想内 联 , 编译 器也不一定会采 纳 。因 为 内 联 函数 虽 然减少了函数 调 用的 开销 ,却增加了程序的体 积 。
内 联 函数是唯一允 许实 体多次被 编译 的函数。原因是 编译 器必 须 先 编译这 个函数体,才能在 编译 函数 调 用的地方 进 行合 理地展 开 。 这 就 说 明在多个 CPP 文件 组 成的工程中,可能有不止一个 CPP 文件中要有函数的 实 体。 既然 这样 ,就放 进头 文件吧 。
C++ 用内 联 来取代宏替 换 ,大大提高了安全性。 虽 然内 联 函数也是 编译时 展 开 的,但是它能 进 行安全 检查 , 还 能 处 理 类 的成 员 函数(原因是内 联 函数能 够处 理 this 指 针 ,宏却不能)。
引用:内 联对编译 器来 说 只是一个建 议 , 编译 器可以 选择 忽略 这 个建 议 。
笔 记 :也就是 说 ,有些函数你想内 联 , 编译 器也不一定会采 纳 。因 为 内 联 函数 虽 然减少了函数 调 用的 开销 ,却增加了程序的体 积 。
内 联 函数是唯一允 许实 体多次被 编译 的函数。原因是 编译 器必 须 先 编译这 个函数体,才能在 编译 函数 调 用的地方 进 行合 理地展 开 。 这 就 说 明在多个 CPP 文件 组 成的工程中,可能有不止一个 CPP 文件中要有函数的 实 体。 既然 这样 ,就放 进头 文件吧 。
对
本文本的
评论
有:
我
觉
得象
这
个
max()
和以前的数
组
越界一
类
的事
,
都可以
归纳为
一句
话
,
那就是
,C
为
我
们
提供了
强
大的工具
,
那些不会使用的人才会出
现这种错误
.
连
个数
组
越界也管理不好的
,
还
是去写武侠小
说
比
较
好
.
比如火 药发 明了以后 , 我 们 可以用来炸山 开 路什 么 的 , 难 道因 为 有人用于 战 争 , 就怪 这 个火 药 功能不 够 完善 吗 ?
比如火 药发 明了以后 , 我 们 可以用来炸山 开 路什 么 的 , 难 道因 为 有人用于 战 争 , 就怪 这 个火 药 功能不 够 完善 吗 ?
是这样的,我们不应该怪C标准不好,
虽然它不能让result = MAX(i++, j);这种问题得到解决,
产生i被自增两次这样的结果,程序员应该自己去避免。
但是,如果标准有进步了,我们倒是因为祝贺它一下。
虽然它不能让result = MAX(i++, j);这种问题得到解决,
产生i被自增两次这样的结果,程序员应该自己去避免。
但是,如果标准有进步了,我们倒是因为祝贺它一下。
标题:
:局部
对
象与静
态
局部
对
象
本
节
首先向
读
者
说
明了
“
名字的作用域
”
和
“
对
象的生命周期
”
这
两个概念,不
难
,理解了就行了。前者是空
间
概念:指程序
还处
在代
码阶
段的
时
候
这
个名字的可
见
范
围
,后者是
时间
概念:指程序运行
过
程中
对
象的存在
时间
。
函数的形参以及函数内部声明的 对 象都是局部 对 象,它 们 的作用域就是函数内部,但是它 们 的生命周期却未必是函数的 执 行 过 程。 这 看起来有点摸不着 头脑 ,原因在于 C++ 的函数中允 许 存在以 关键 字 “static” 声明的静 态对 象。
也就是 说 ,静 态对 象是 这样 一个 对 象:它的生命周期很 长 ,可以跨越 该 函数的 每 次 调 用,哪怕 该 函数 每 24 小 时 才 调 用一次,它也是全天候存在的。但是要想 访问 她,却只有函数正在 执 行的 时 候才行。
签 于以上特性,我 专门 写了两个 测试 函数, 该 函数 试 途返回局部 对 象的引用或指 针 :
int& GetInt()
{
int t=3;
return t;// 警告
}
函数的形参以及函数内部声明的 对 象都是局部 对 象,它 们 的作用域就是函数内部,但是它 们 的生命周期却未必是函数的 执 行 过 程。 这 看起来有点摸不着 头脑 ,原因在于 C++ 的函数中允 许 存在以 关键 字 “static” 声明的静 态对 象。
也就是 说 ,静 态对 象是 这样 一个 对 象:它的生命周期很 长 ,可以跨越 该 函数的 每 次 调 用,哪怕 该 函数 每 24 小 时 才 调 用一次,它也是全天候存在的。但是要想 访问 她,却只有函数正在 执 行的 时 候才行。
签 于以上特性,我 专门 写了两个 测试 函数, 该 函数 试 途返回局部 对 象的引用或指 针 :
int& GetInt()
{
int t=3;
return t;// 警告
}
int* GetInt2()
{
int t = 3;
return &t;// 警告
}
以上两个警告 产 生的原因是函数返回了 临时对 象的引用或地址。但是如果将 t 的声明改成 “static int t=3;” 就不再 显 示警告。
静 态 局部 对 象似乎 为节约 系 统开销 做了准 备 。不 过 我 认为这 个特性不 应该 被 滥 用。只有确 实 有必要 让对 象生 命周期跨越多次 调 用 时 才 应该 把它声明 为 静 态 (比如 统计 函数被 调 用的次数)。否 则 将提高造成 BUG 的可能性,使 “ 高效率 ” 的程序成 为 空中楼 阁 。
{
int t = 3;
return &t;// 警告
}
以上两个警告 产 生的原因是函数返回了 临时对 象的引用或地址。但是如果将 t 的声明改成 “static int t=3;” 就不再 显 示警告。
静 态 局部 对 象似乎 为节约 系 统开销 做了准 备 。不 过 我 认为这 个特性不 应该 被 滥 用。只有确 实 有必要 让对 象生 命周期跨越多次 调 用 时 才 应该 把它声明 为 静 态 (比如 统计 函数被 调 用的次数)。否 则 将提高造成 BUG 的可能性,使 “ 高效率 ” 的程序成 为 空中楼 阁 。
标题:
:默
认实
参
没什
么
比
偷懒
更舒服的了,所以我喜
欢
允
许
默
认实
参的函数,我
还
喜
欢
写允
许
默
认实
参的函数。
在形参表中,如果允 许 某些形参具有默 认值 , 则 它 们 必 须 按从右到左的方向排列。以上 这 个 规 定 C++ 与 BASIC 是一 样 的,但是 C++ 与 BASIC 还 有一点区 别 ,就是在函数 调 用 时 , C++ 必 须 从右 边开 始缺省 实 参,而 BASIC 却可以任意缺省而不 顾 次序(只要有逗号表示那里缺了个 东 西即可)。所以,同 样设计 函数, C++ 比 BASIC 要多考 虑 一个 问题 : “ 设计带 有默 认实 参的函数,其中部分工作就是排列形参,使最少使用默 认实 参的表参排在最前,最可能使用默 认实 参的形参排在最后。 ”
形参的默 认值 竟究写在声明中 还 是 实 体中?我曾 经试过 ,在某 些情况下写在声明中或 实 体中一 样 可行。但是,事 实 上写在 实 体中是 错误 的做法。只有当函数 实 体和函数 调 用在同一个源文件中,而且函数 实 体在 调 用前被 编译时 ,将形参的默 认值 写在 实 体中才可通 过编译 。 实际 上 对 于 这种 情况,函数根本就不用声明。
将默 认值 写在 实 体中不 仅仅 是能否通 过编译 的 问题 , 还关 系到程序 设计 的理念。 “ 一是函数的 实现 本来就与参数是否有缺省 值 无 关 ,所以,没有必要 让 缺省 值 出 现 在函数的定 义 体中。二是参数的缺省 值 可能会改 动 , 显 然修改函数的声明比修改函数的定 义 要方便。 ” (《高 质 量 C++/C 编译 指南》第 63 页 )
读 到 这 里,本 书给 了我一个大大的惊 诧 :原来默 认实 参的默 认值还 可以是任何表达式。以前,我一直是 这样 写的: “int GetInt(int i=3);” 虽 然没人跟我 这样说过 ,但是我始 终 以 为 后面的默 认值 只能是常量。想不到 还 可以是需要求 值 的 变 量甚至是更 复杂 的表达式:
int GetInt(const int i = 3);
int GetInt2(const int j = GetInt());// 居然可以 这样 写
学 习 了,感 谢 《 C++ Primer 》!
在形参表中,如果允 许 某些形参具有默 认值 , 则 它 们 必 须 按从右到左的方向排列。以上 这 个 规 定 C++ 与 BASIC 是一 样 的,但是 C++ 与 BASIC 还 有一点区 别 ,就是在函数 调 用 时 , C++ 必 须 从右 边开 始缺省 实 参,而 BASIC 却可以任意缺省而不 顾 次序(只要有逗号表示那里缺了个 东 西即可)。所以,同 样设计 函数, C++ 比 BASIC 要多考 虑 一个 问题 : “ 设计带 有默 认实 参的函数,其中部分工作就是排列形参,使最少使用默 认实 参的表参排在最前,最可能使用默 认实 参的形参排在最后。 ”
形参的默 认值 竟究写在声明中 还 是 实 体中?我曾 经试过 ,在某 些情况下写在声明中或 实 体中一 样 可行。但是,事 实 上写在 实 体中是 错误 的做法。只有当函数 实 体和函数 调 用在同一个源文件中,而且函数 实 体在 调 用前被 编译时 ,将形参的默 认值 写在 实 体中才可通 过编译 。 实际 上 对 于 这种 情况,函数根本就不用声明。
将默 认值 写在 实 体中不 仅仅 是能否通 过编译 的 问题 , 还关 系到程序 设计 的理念。 “ 一是函数的 实现 本来就与参数是否有缺省 值 无 关 ,所以,没有必要 让 缺省 值 出 现 在函数的定 义 体中。二是参数的缺省 值 可能会改 动 , 显 然修改函数的声明比修改函数的定 义 要方便。 ” (《高 质 量 C++/C 编译 指南》第 63 页 )
读 到 这 里,本 书给 了我一个大大的惊 诧 :原来默 认实 参的默 认值还 可以是任何表达式。以前,我一直是 这样 写的: “int GetInt(int i=3);” 虽 然没人跟我 这样说过 ,但是我始 终 以 为 后面的默 认值 只能是常量。想不到 还 可以是需要求 值 的 变 量甚至是更 复杂 的表达式:
int GetInt(const int i = 3);
int GetInt2(const int j = GetInt());// 居然可以 这样 写
学 习 了,感 谢 《 C++ Primer 》!
标题:
:函数的声明与
实
体
注:本
书
中提到了
“
声明
”
与
“
定
义
”
两个
词
。我倒是
认为
将后者改
为
“
实
体
”
更好。
函数的 实 体就是 实实 在在的函数内容,它 规 定了 这 个函数怎 样执 行, 这 没有什 么 好 说 的。那 么 函数 为 什 么还 要有声明呢?
这样 做的目的之一是告 诉编译 器: 虽 然你 还 没有 见 到函数本身,不知道函数是怎 样执 行的,但是我先告 诉 你 这 个函数的名称、参数与返回 值 ,你就先 编译 吧。至于 这 个函数究竟干什 么 ,等到 连 接的 时 候再 说 。
设计 合理的程序,其代 码 存放在不同的文件中,函数的 实 体只能有一个,存放在某一个源文件中。其它源文件中如果要用到 这 个函数,就在 这 个文件中加入函数的声明。
这样 做的目的之二是函数的提供者与使用者往往不是同一个人,甚至不是同一个企 业 。出于 种种 目的,函数的提供者可能并不想(或不必) 让 使用者知道 这 个函数的具体内容,只要使用者能 调 用就行。 这种 情况下,函数的提供者只需要提供一个声明 给 使用者即可。 ——C 语 言的 库 函数就是 这样 的。
然而 “ 在需要用到函数的文件中加入函数的声明 ” 也有好 办 法与笨 办 法。将声明 语 句重写一遍自然 不 难 ,但是 这样 做有两个明 显 的缺点:一是 烦琐 易 错 、二是不易修改。所以,函数的声明 应该 放在 头 文件中,哪儿要,就在哪儿包含。 这 就好像我家没有 摆许 多盆 鲜 花而是 摆 了 许 多面 镜 子。我在哪儿都能看到 鲜 花, 浇 水却只要 浇 一盆。
这 个理 论 也适用于 C++ 的 “ 类 ” , 类 的声明写 进头 文件,而 实 体却写 进 程序文件。不同的是, 类 的声明不像函数的声明那 样 只有一句 话 ,而是一个完整的 结 构 。
函数的 实 体就是 实实 在在的函数内容,它 规 定了 这 个函数怎 样执 行, 这 没有什 么 好 说 的。那 么 函数 为 什 么还 要有声明呢?
这样 做的目的之一是告 诉编译 器: 虽 然你 还 没有 见 到函数本身,不知道函数是怎 样执 行的,但是我先告 诉 你 这 个函数的名称、参数与返回 值 ,你就先 编译 吧。至于 这 个函数究竟干什 么 ,等到 连 接的 时 候再 说 。
设计 合理的程序,其代 码 存放在不同的文件中,函数的 实 体只能有一个,存放在某一个源文件中。其它源文件中如果要用到 这 个函数,就在 这 个文件中加入函数的声明。
这样 做的目的之二是函数的提供者与使用者往往不是同一个人,甚至不是同一个企 业 。出于 种种 目的,函数的提供者可能并不想(或不必) 让 使用者知道 这 个函数的具体内容,只要使用者能 调 用就行。 这种 情况下,函数的提供者只需要提供一个声明 给 使用者即可。 ——C 语 言的 库 函数就是 这样 的。
然而 “ 在需要用到函数的文件中加入函数的声明 ” 也有好 办 法与笨 办 法。将声明 语 句重写一遍自然 不 难 ,但是 这样 做有两个明 显 的缺点:一是 烦琐 易 错 、二是不易修改。所以,函数的声明 应该 放在 头 文件中,哪儿要,就在哪儿包含。 这 就好像我家没有 摆许 多盆 鲜 花而是 摆 了 许 多面 镜 子。我在哪儿都能看到 鲜 花, 浇 水却只要 浇 一盆。
这 个理 论 也适用于 C++ 的 “ 类 ” , 类 的声明写 进头 文件,而 实 体却写 进 程序文件。不同的是, 类 的声明不像函数的声明那 样 只有一句 话 ,而是一个完整的 结 构 。
标题:
:
递归
引用:直接或
间
接
调
用自己的函数称
为递归
函数。
引用: 递归 函数必 须 定 义 一个 终 止条件,否 则 函数将永 远递归 下去, 这 意味着函数会一直 调 用自身直到程序耗尽。
初 识递归 的 时 候,的确有些不容易搞明白。 记 得当 时 的教科 书为 此画一个 图 ,用一 组 箭 头 来表示要 计 算 A 必 须 先 计 算 B 、要 计 算 B 又要先 计 算 C 、 …… ,用另一 组 箭 头 表示算好了 C 就可以算 B 、算好了 B 就可以算 A 。 …… 实 例程序与一个 图结 合,如此 摆 事 实讲 道理,要 说 明 递归 自然稍容易些。
要写 递归 函数就得 领 悟 递归 的妙用,要写没有 错误 的 递归 函数 则 要 领 悟其数学原理。我倒是 觉 得 这样 的函数与 “ 数学 归纳 法 ” 有些相通之 处 。不同的是,数学 归纳 法 总 是先求 边 界条件,再去往无 穷 方向 归纳 。而 递归 是从无 穷 方向向 边 界 计 算的。函数如何 执 行,与我 们 如何写没有必然的 关 系,于是,我 们 在写程序的 时 候也可以先写 边 界条件。 这样 做可以在程序 开头 先把可能的 问题给 排除掉。 “ 永 远递归 下去 ” 的可能性自然被降低。比如求 阶 乘的函数:
// 程序一、 书 上的例子
int factorial(int val)
{
if (val > 1)
return factorial(val-1);
return 1;
}
// 程序二
int factorial2(int val)
{
if (val <= 1)
return 1;
return factorial2(val-1);
}
程序二的写法与程序一没有区 别 ,但可以告 诉 自己 递归 必 须 有 终 止条件。防止一不小心就写了个 “ 永 远 ” 。
似乎 绝 大多数 递归 函数都可以用循 环 来解决。 这 两 种 方法迁就了不同的 对 象:循 环 用少量的 计 算机 资 源、大量的人力来解决 问题 , 递归则 用大量的 计 算机 资 源、少量的人力来解决 问题 。所以,在 计 算机速度和存 储 量都不大的年代,曾有人反 对递归 。
汉诺 塔 问题 据 说 是只有用 递归 才可以解决的 问题 ,其 实 只有要求解 汉诺 塔的移 动过 程才必 须 用 递归 ,如果只要求解移 动 次数,那 么 用循 环 也不成 问题 。
引用: 递归 函数必 须 定 义 一个 终 止条件,否 则 函数将永 远递归 下去, 这 意味着函数会一直 调 用自身直到程序耗尽。
初 识递归 的 时 候,的确有些不容易搞明白。 记 得当 时 的教科 书为 此画一个 图 ,用一 组 箭 头 来表示要 计 算 A 必 须 先 计 算 B 、要 计 算 B 又要先 计 算 C 、 …… ,用另一 组 箭 头 表示算好了 C 就可以算 B 、算好了 B 就可以算 A 。 …… 实 例程序与一个 图结 合,如此 摆 事 实讲 道理,要 说 明 递归 自然稍容易些。
要写 递归 函数就得 领 悟 递归 的妙用,要写没有 错误 的 递归 函数 则 要 领 悟其数学原理。我倒是 觉 得 这样 的函数与 “ 数学 归纳 法 ” 有些相通之 处 。不同的是,数学 归纳 法 总 是先求 边 界条件,再去往无 穷 方向 归纳 。而 递归 是从无 穷 方向向 边 界 计 算的。函数如何 执 行,与我 们 如何写没有必然的 关 系,于是,我 们 在写程序的 时 候也可以先写 边 界条件。 这样 做可以在程序 开头 先把可能的 问题给 排除掉。 “ 永 远递归 下去 ” 的可能性自然被降低。比如求 阶 乘的函数:
// 程序一、 书 上的例子
int factorial(int val)
{
if (val > 1)
return factorial(val-1);
return 1;
}
// 程序二
int factorial2(int val)
{
if (val <= 1)
return 1;
return factorial2(val-1);
}
程序二的写法与程序一没有区 别 ,但可以告 诉 自己 递归 必 须 有 终 止条件。防止一不小心就写了个 “ 永 远 ” 。
似乎 绝 大多数 递归 函数都可以用循 环 来解决。 这 两 种 方法迁就了不同的 对 象:循 环 用少量的 计 算机 资 源、大量的人力来解决 问题 , 递归则 用大量的 计 算机 资 源、少量的人力来解决 问题 。所以,在 计 算机速度和存 储 量都不大的年代,曾有人反 对递归 。
汉诺 塔 问题 据 说 是只有用 递归 才可以解决的 问题 ,其 实 只有要求解 汉诺 塔的移 动过 程才必 须 用 递归 ,如果只要求解移 动 次数,那 么 用循 环 也不成 问题 。
对本文本的评论有:
阶乘的函数写错了.
int factorial(int val)
{
if (val > 1)
return val* factorial(val-1);
return 1;
}
int factorial(int val)
{
if (val > 1)
return val* factorial(val-1);
return 1;
}
晕,我忘了相乘了,哈哈。
标题:
:
return
语
句
引用:
return
语
句用于
结
束当前正在
执
行的函数,并将控制
权
返回
给调
用此函数的函数。
引用: return 语 句有两 种 形式: reutrn; return expression;…… 第二 种 形式提供了函数的 结 果。
笔 记 :以上第一句 话说 了 return 的两个作用之一: 结 束函数。 return 的作用之二是提供函数的返回 值 。
对 于 return 语 句的两 种 形式,情式一只能用于无返回 值 的函数,情式二可以用于有返回 值 的函数也可用于无返回 值 的函数。
如果函数有返回 值 ,就必 须 用形式二来 结 束, 这 是 显 而易 见 的。
对 于没有返回 值 的函数,可以不写 return 语 句, “ 隐 式的 return 发 生在函数的最后一个 语 句完成 时 ” 。也可以用形式一来 结 束, 这种 用法一般用在函数中 间 ,判断某些条件之后就立即 结 束,后面的 语 句不再 执 行。如果用形式二来返回,那 么 express 必 须 是另一个没有返回 值 的函数。如:
void FuncA();
void FuncB()
{
return FuncA();
}
个人 认为这种 写法不是好 习惯 ,因 为 看起来 FuncB 有了返回 值 ,如果 逻辑 上有 这 需要,我 认为 写成以下格式更好:
void FuncB()
{
FuncA();
return;
}
在 BASIC 中,函数的返回 值 与 结 束是由两个不同的 语 句 实现 的。前者是一个 给 函数名 赋值 的 语 句,后者 则 是 “Exit Function” 语 句。 这种设计 除了不如 C++ 精 练 以外, 还 容易出事。比如在函数 开头 先 给 函数名 赋 一个默 认值 ,然后根据某些条件 给 它 赋 其它特定的 值 并 Exit 。如果写函数 时 不小心漏了某个 赋值语 句,函数将 产 生 BUG 。 C++ 则 不会 产 生 这种类 型的 BUG 。
引用:千万不要返回局部 对 象的引用。
引用:千万不要返回局部 对 象的指 针 。
笔 记 :以上两句是黑体的 标题: , 书 中 专门进 行了 讨论 。不 过这 个 错误虽 然 严 重,却不 难 理解。知道了就好了。
main() 是一个很特殊的函数,它的特殊性在 这 里 还 有体 现 。引用: “ 返回 类 型不是 void 的函数必 须 返回一个 值 ,但此 规则 有一个例外的情况:允 许 主函数 main 没有返回 值 可 结 束。 …… 编译 器会 隐 式地插入返 回 0 的 语 句。 ”
引用: return 语 句有两 种 形式: reutrn; return expression;…… 第二 种 形式提供了函数的 结 果。
笔 记 :以上第一句 话说 了 return 的两个作用之一: 结 束函数。 return 的作用之二是提供函数的返回 值 。
对 于 return 语 句的两 种 形式,情式一只能用于无返回 值 的函数,情式二可以用于有返回 值 的函数也可用于无返回 值 的函数。
如果函数有返回 值 ,就必 须 用形式二来 结 束, 这 是 显 而易 见 的。
对 于没有返回 值 的函数,可以不写 return 语 句, “ 隐 式的 return 发 生在函数的最后一个 语 句完成 时 ” 。也可以用形式一来 结 束, 这种 用法一般用在函数中 间 ,判断某些条件之后就立即 结 束,后面的 语 句不再 执 行。如果用形式二来返回,那 么 express 必 须 是另一个没有返回 值 的函数。如:
void FuncA();
void FuncB()
{
return FuncA();
}
个人 认为这种 写法不是好 习惯 ,因 为 看起来 FuncB 有了返回 值 ,如果 逻辑 上有 这 需要,我 认为 写成以下格式更好:
void FuncB()
{
FuncA();
return;
}
在 BASIC 中,函数的返回 值 与 结 束是由两个不同的 语 句 实现 的。前者是一个 给 函数名 赋值 的 语 句,后者 则 是 “Exit Function” 语 句。 这种设计 除了不如 C++ 精 练 以外, 还 容易出事。比如在函数 开头 先 给 函数名 赋 一个默 认值 ,然后根据某些条件 给 它 赋 其它特定的 值 并 Exit 。如果写函数 时 不小心漏了某个 赋值语 句,函数将 产 生 BUG 。 C++ 则 不会 产 生 这种类 型的 BUG 。
引用:千万不要返回局部 对 象的引用。
引用:千万不要返回局部 对 象的指 针 。
笔 记 :以上两句是黑体的 标题: , 书 中 专门进 行了 讨论 。不 过这 个 错误虽 然 严 重,却不 难 理解。知道了就好了。
main() 是一个很特殊的函数,它的特殊性在 这 里 还 有体 现 。引用: “ 返回 类 型不是 void 的函数必 须 返回一个 值 ,但此 规则 有一个例外的情况:允 许 主函数 main 没有返回 值 可 结 束。 …… 编译 器会 隐 式地插入返 回 0 的 语 句。 ”
标题:
:
传递
数
组
的函数与字符串函数
如果将数
组
作
为实
参来
调
用函数,函数接收到的形参其
实
是一个指
针
。数
组
名是可以
转换为
指
针
的,但是数
组
名和指
针毕
竟不等价。所以,
这样传递
的
结
果是
丢
失了数
组
原有的一些特性。最大的
损
失莫
过
于
sizeof
对
数
组
大小的
测试
。
试
看以下程序:
void FuncA(int *temp)
{
cout << sizeof(temp) << endl;
}
void FuncB(int temp[])
{
cout << sizeof(temp) << endl;
}
void FuncC(int temp[20])
{
cout << sizeof(temp) << endl;
}
int main()
{
int a[10];
cout << sizeof(a) << endl;
FuncA(a);
FuncB(a);
FuncC(a);
return 0;
}
三个函数的写法各有不同,但是 结 果却是一 样 的。其中 FuncC 的写法尤其容易 产 生 误 解。因 为编译 器不管你 传递 的是多大的数 组 (甚至不管是不是数 组 ),但是函数的写法却在暗示程序 员这 个数 组 有 20 个成 员 。如果 实 参成 员 超 过 20 个, 结 果就是 没有起到完全的作用,如果 实 参成 员 不到 20 ,那就指 针 越界了。
为 避免 这样 的 尴 尬,有 时 我 们 将指 针 与容量一起 传 入函数: “void FuncD(int temp[], _size_t Size);” ,或者 传递 两个指 针 : “void FuncE(int* Begin, int* End);” 。 这样 做当然好,不 过 C++ 还 有另一 种办 法可以不用 这么 麻 烦 ,那就是引用 传递 : “void FuncF(int (&temp)[10]);” 。 这样 的函数只允 许 将 int[10] 实 参 传 入,大小不符的数 组 或非数 组 的指 针 都无法 传 入。 这样 就保 证 了 10 这 个 值 的正确性, 连 sizeof 都省了。
C 语 言的字符串 处 理函数大概是 仅 有的可以不受此 约 束的函数了。字符串就是字符数 组 ,但是在 传递 字符数 组时 ,可以只 传 指 针 而不管大小。因 为 C 语 言中的字符串都是以 NULL 尾的。前 阵 子有人在 论坛 提 问 , 问 及字符串和字符指 针 的 关 系。回答是: C 语 言的字符串是用字符数 组 存放的,而 处 理 则 是借助于字符指 针 。但是,要能 进 行 这样 的操作,有两个条件必 须满 足:一是所有字符 连续 放置在以指 针开头 的内存中、不跳 跃 ,二是有一个 规 定的 结 束符。 int[] 数 组 之所以不能 这样 做,是因 为 第二个条件无法 满 足 。
void FuncA(int *temp)
{
cout << sizeof(temp) << endl;
}
void FuncB(int temp[])
{
cout << sizeof(temp) << endl;
}
void FuncC(int temp[20])
{
cout << sizeof(temp) << endl;
}
int main()
{
int a[10];
cout << sizeof(a) << endl;
FuncA(a);
FuncB(a);
FuncC(a);
return 0;
}
三个函数的写法各有不同,但是 结 果却是一 样 的。其中 FuncC 的写法尤其容易 产 生 误 解。因 为编译 器不管你 传递 的是多大的数 组 (甚至不管是不是数 组 ),但是函数的写法却在暗示程序 员这 个数 组 有 20 个成 员 。如果 实 参成 员 超 过 20 个, 结 果就是 没有起到完全的作用,如果 实 参成 员 不到 20 ,那就指 针 越界了。
为 避免 这样 的 尴 尬,有 时 我 们 将指 针 与容量一起 传 入函数: “void FuncD(int temp[], _size_t Size);” ,或者 传递 两个指 针 : “void FuncE(int* Begin, int* End);” 。 这样 做当然好,不 过 C++ 还 有另一 种办 法可以不用 这么 麻 烦 ,那就是引用 传递 : “void FuncF(int (&temp)[10]);” 。 这样 的函数只允 许 将 int[10] 实 参 传 入,大小不符的数 组 或非数 组 的指 针 都无法 传 入。 这样 就保 证 了 10 这 个 值 的正确性, 连 sizeof 都省了。
C 语 言的字符串 处 理函数大概是 仅 有的可以不受此 约 束的函数了。字符串就是字符数 组 ,但是在 传递 字符数 组时 ,可以只 传 指 针 而不管大小。因 为 C 语 言中的字符串都是以 NULL 尾的。前 阵 子有人在 论坛 提 问 , 问 及字符串和字符指 针 的 关 系。回答是: C 语 言的字符串是用字符数 组 存放的,而 处 理 则 是借助于字符指 针 。但是,要能 进 行 这样 的操作,有两个条件必 须满 足:一是所有字符 连续 放置在以指 针开头 的内存中、不跳 跃 ,二是有一个 规 定的 结 束符。 int[] 数 组 之所以不能 这样 做,是因 为 第二个条件无法 满 足 。
标题:
:函数的引用返回
值
引用是
给变
量取一个
别
名,所以引用
传递
会直接
进
行
变
量本身的
传递
。它的最大好
处
是可以把
别处对变
量的改
变
保留下来,第二好
处
是它提高了性能:如果函数的返回
值
是一个引用,那
么
,如上文所
说
,它会
节约
一
组
构造、
赋值
和析构
过
程。但是,函数返回引用往往会
带
来一些意想不到的
错误
:比如返回
临时变
量的引用。
// 一个 错误 的函数
int &Max(int i, int j)
{
return i>j ? i : j;
}
以上函数的 错误 在于, i 和 j 在函数 结 束后会被 释 放。 对 它 们 的引和也将失效。如果用 这 个返回 值给别 的 变 量 赋值 ,将会 获 得一个垃圾。 VC++.Net 会 对 以上 return 语 句 显 示警告。
那 么 ,如果返回一个全局 变 的引用呢? 这 当然是可以的,但是,一来程序 设计 中不建 议 使用 过 多的全局 变 量,二来全局 变 量即使不返回也可以 访问 。 这样 做的唯一用途就是把函数做右 值 来 给 其它 变 量 赋值 。
int m;// 全局 变 量
int &MaxByGlobal(int i, int j)
{
return m = i>j ? i : j;
}
int a, b, c;
c = MaxByGlobal(a, b);// 用法一、用返回 值赋值
MaxByGlobal(a, b); c = m;// 用法二、不用返回 值赋值
当然,以上 这 个 MaxByGlobal 函数也不是一无是 处 ,能用返回 值 来 进 行 赋值 会 给 程序 带 来更好的可 读 性。 只是 这样 的函数 设计 本身不被建 议 。
那 么 ,函数返回引用用得最多的就是返回形参了。因 为 形参可以用引用 传递 ,引用的形参不是函数内部的局部 变 量, 这样 做是可取的:
int &MaxByRef(int &i, int &j)
{
return i>j ? i : j;
}
上面 这 个函数和上文中的 “int Max(int i, int j)” 函数如此相似,但是它省去了三次构造、 赋值 和析构。
另外一 种 用法就是在 类 的成 员 函数中返回 类对 象自身了,典型的是 “operator +=” 函数之 类 。
MyClass &MyClass::operator +=(const MyClass &other)
{
// 某些 语 句
return *this;
}
以上函数返回的是自身的引用。因 为类 的成 员 函数也可以写成全局函数 “MyClass &operator +=(MyClass &Left, const MyClass &right)” ,而且在 类 成 员 函数的 调 用中 实际 存在着 this 指 针 的 传递 。所以,以上 这 个函数依然可以看作返回了形参的引用。
对 于返回引用的函数, 还 有一个好玩的 现 像。即返回 值还 可能可以被 赋值 。如 “(a += b) = c;” 这样 的形式。 这种 写法明 显 不 伦 不 类 ,但是如果函数返回了非 const 的引用, 这 个表达式的确是合理的。所以,上面的 “operator +=” 函数 还 要修改一下,将返回 值 由 “MyClass&” 改 为 “const MyClass&” 。
返回引用并不是 处处 可用的,正如《引用 传递 的 应 用范 围 》中提到的一 样 :不能用引用来 传递临时值 。有 时 候我 们 的确要 产 生一个 临时对 象并返回它,那就不能返回引用。典型的有 “operator +” 函数:
const MyClass MyClass::operator +(const MyClass &other) const
{
MyClass Temp;
// 某些 语 句
return Temp;// 这 里只能返回 对 象,因 为 Temp 必 须 是局部 变 量
}
// 一个 错误 的函数
int &Max(int i, int j)
{
return i>j ? i : j;
}
以上函数的 错误 在于, i 和 j 在函数 结 束后会被 释 放。 对 它 们 的引和也将失效。如果用 这 个返回 值给别 的 变 量 赋值 ,将会 获 得一个垃圾。 VC++.Net 会 对 以上 return 语 句 显 示警告。
那 么 ,如果返回一个全局 变 的引用呢? 这 当然是可以的,但是,一来程序 设计 中不建 议 使用 过 多的全局 变 量,二来全局 变 量即使不返回也可以 访问 。 这样 做的唯一用途就是把函数做右 值 来 给 其它 变 量 赋值 。
int m;// 全局 变 量
int &MaxByGlobal(int i, int j)
{
return m = i>j ? i : j;
}
int a, b, c;
c = MaxByGlobal(a, b);// 用法一、用返回 值赋值
MaxByGlobal(a, b); c = m;// 用法二、不用返回 值赋值
当然,以上 这 个 MaxByGlobal 函数也不是一无是 处 ,能用返回 值 来 进 行 赋值 会 给 程序 带 来更好的可 读 性。 只是 这样 的函数 设计 本身不被建 议 。
那 么 ,函数返回引用用得最多的就是返回形参了。因 为 形参可以用引用 传递 ,引用的形参不是函数内部的局部 变 量, 这样 做是可取的:
int &MaxByRef(int &i, int &j)
{
return i>j ? i : j;
}
上面 这 个函数和上文中的 “int Max(int i, int j)” 函数如此相似,但是它省去了三次构造、 赋值 和析构。
另外一 种 用法就是在 类 的成 员 函数中返回 类对 象自身了,典型的是 “operator +=” 函数之 类 。
MyClass &MyClass::operator +=(const MyClass &other)
{
// 某些 语 句
return *this;
}
以上函数返回的是自身的引用。因 为类 的成 员 函数也可以写成全局函数 “MyClass &operator +=(MyClass &Left, const MyClass &right)” ,而且在 类 成 员 函数的 调 用中 实际 存在着 this 指 针 的 传递 。所以,以上 这 个函数依然可以看作返回了形参的引用。
对 于返回引用的函数, 还 有一个好玩的 现 像。即返回 值还 可能可以被 赋值 。如 “(a += b) = c;” 这样 的形式。 这种 写法明 显 不 伦 不 类 ,但是如果函数返回了非 const 的引用, 这 个表达式的确是合理的。所以,上面的 “operator +=” 函数 还 要修改一下,将返回 值 由 “MyClass&” 改 为 “const MyClass&” 。
返回引用并不是 处处 可用的,正如《引用 传递 的 应 用范 围 》中提到的一 样 :不能用引用来 传递临时值 。有 时 候我 们 的确要 产 生一个 临时对 象并返回它,那就不能返回引用。典型的有 “operator +” 函数:
const MyClass MyClass::operator +(const MyClass &other) const
{
MyClass Temp;
// 某些 语 句
return Temp;// 这 里只能返回 对 象,因 为 Temp 必 须 是局部 变 量
}
标题:
:函数的非引用返回
值
函数最多可以返回一个
值
,也可以不返回任何
值
(也有
“
返回
void”
的
说
法)。之所以最多只能返回一个
值
,因
为
只有
这样
才能在表达式中使用。比如
“y=Sin(x);”
,如果
Sin
函数返回多个
值
,
这
个表达式就失去了意
义
。之于
为
什
么
可以不返回任何
值
,
经历过
BASIC
的人
应该
更能理解。因
为
BASIC
中把有返回
值
的程序段叫函数,没有返回
值
的程序段
则
叫做
“
子程序
”
。很
显
然,
“
子程序
”
就是完成一个特定的功能后
结
束的程序段。
函数的返回 值 没有 类 型限制,可以是内置 类 型 变 量,也可以是 类对 象。无 论 是内置 类 型 还 是 类对 象,都有着 一 样 的 规 律。但是, 这 些 规 律在 C++ 到来之前很少有人去理会, 毕 竟内置 变 量 类 型太 复 通,以至于程序 员 根本不去考 虑 那 么 多 “ 为 什 么 ” 。
在 C 时 代,所有的返回 值 都是局部 变 量。如下列程序:
// 程序一:
int Max(int i, int j)
{
return i>j ? i : j;
}
// 程序二:
char *StrCpy(char *Target, const char *Source)
{
char *Temp=Target;
while(*Source)
{
*Temp++ = *Source++;
}
return Target;
}
程序二 给 人一个 错觉 : 认为该 函数返回的不是函数内部的局部 变 量。 错误 原因在于没有理解指 针 的本 质 。其 实 程序二和程序一一 样 ,返回 值 是形参之一。而形参就是作用域 为 函数内部的局部 变 量。
理解了 “ 返回 值 是局部 变 量 ” 还 不 够 。因 为还 有一个很重要的概念没弄清。比如:
int a, b, c;
char d[10], e[10], *f;
// 其它 语 句
c = Max(a, b);// 语 句一
f = StrCpy(d, e);// 语 句二
以上注 释 的 两行 语 句都有同一个 问题 :如果返回的 变 量作用域 仅 限于函数内部,那 么 函数 结 束以后 该变 量就已 经 不存在了,那 么给 c 和 f 赋值 的是什 么 ?
C 和 C++ 有一个机制保 证 以上 赋值 正常 进 行:在函数 结 束前,先将要返回的局部 变 量 临时 拷 贝 一份到 栈 内存( 这 个内存程序 员 无 须 知道,也无法知道)。然后将局部 变 量 销毁 ,函数正常 结 束。接下来用 栈 中的 临时变 量 对 目 标变 量 进 行 赋值 , 赋值结 束后再把 临时变 量 销毁 。
以上 这 个 过 程凭空多出一次 变 量构造、 复 制与 销毁过 程,好在 对 于内置 类 型 变 量来 说 , 这样 的 过 程所需的性能 赋 出并不太多。但是 C++ 到来以后,函数的返回 值类 型可以是 类类 型。而 类对 象的构造、 复 制与 销毁 可能很 复杂 、很占用系 统资 源。于是 “ 引用 传递 ” 再一次 发挥 了它的威力 。
函数的返回 值 没有 类 型限制,可以是内置 类 型 变 量,也可以是 类对 象。无 论 是内置 类 型 还 是 类对 象,都有着 一 样 的 规 律。但是, 这 些 规 律在 C++ 到来之前很少有人去理会, 毕 竟内置 变 量 类 型太 复 通,以至于程序 员 根本不去考 虑 那 么 多 “ 为 什 么 ” 。
在 C 时 代,所有的返回 值 都是局部 变 量。如下列程序:
// 程序一:
int Max(int i, int j)
{
return i>j ? i : j;
}
// 程序二:
char *StrCpy(char *Target, const char *Source)
{
char *Temp=Target;
while(*Source)
{
*Temp++ = *Source++;
}
return Target;
}
程序二 给 人一个 错觉 : 认为该 函数返回的不是函数内部的局部 变 量。 错误 原因在于没有理解指 针 的本 质 。其 实 程序二和程序一一 样 ,返回 值 是形参之一。而形参就是作用域 为 函数内部的局部 变 量。
理解了 “ 返回 值 是局部 变 量 ” 还 不 够 。因 为还 有一个很重要的概念没弄清。比如:
int a, b, c;
char d[10], e[10], *f;
// 其它 语 句
c = Max(a, b);// 语 句一
f = StrCpy(d, e);// 语 句二
以上注 释 的 两行 语 句都有同一个 问题 :如果返回的 变 量作用域 仅 限于函数内部,那 么 函数 结 束以后 该变 量就已 经 不存在了,那 么给 c 和 f 赋值 的是什 么 ?
C 和 C++ 有一个机制保 证 以上 赋值 正常 进 行:在函数 结 束前,先将要返回的局部 变 量 临时 拷 贝 一份到 栈 内存( 这 个内存程序 员 无 须 知道,也无法知道)。然后将局部 变 量 销毁 ,函数正常 结 束。接下来用 栈 中的 临时变 量 对 目 标变 量 进 行 赋值 , 赋值结 束后再把 临时变 量 销毁 。
以上 这 个 过 程凭空多出一次 变 量构造、 复 制与 销毁过 程,好在 对 于内置 类 型 变 量来 说 , 这样 的 过 程所需的性能 赋 出并不太多。但是 C++ 到来以后,函数的返回 值类 型可以是 类类 型。而 类对 象的构造、 复 制与 销毁 可能很 复杂 、很占用系 统资 源。于是 “ 引用 传递 ” 再一次 发挥 了它的威力 。
标题:
:引用
传递
的
应
用范
围
经过
三篇文章的
细
述,函数的参数
传递应该
比
较
明朗了,
经过
一番
对
比,似乎引用
传递
是最
优
秀的一
种传递
方式。第一、它用法很
简单
,
类
似于
值传递
,第二、它功能很
强
大,
类
似于指
针传递
,第三、它很安全,可以避免指
针传递带
来的危
险
,第四、它效率高,函数中不必要
进
行
对
象的
创
建、
赋值
与
释
放。第五、如果不希望
实
参被改
变
,可以使用
const
修
饰
形参
……
但是,天下没有 这么 便宜的午餐!引用 传递 不是倒 处 能用的。 举 个例子:
void Swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
以上函数可以 进 行两个 int 变 量的交 换 。但是,很多情况下 该 函数不能 调 用:
int ia = ib = 1;
short sa = sb = 2;
const int cia = cib = 3;
Swap(ia, ib);// 正确
Swap(sa, sb);// 错误 , short 不是 int , 虽 然可以 隐 式 转换为 int ,但是 这 个 变 量不存在
Swap(cia, cib);// 错误 , 这 两个参数是 const 的
Swap(4, 5);// 常量不是 变 量, 类 似于将 short 变 量 传递给 函数
Swap(ia+ib, ia-ib);// 错误 ,表达式求 值 后 产 生的 临时值 不是 变 量
其中将 const 参数 传递进 函数的做法, 虽 然看起来有些荒 诞 , 实际 上某些 时 候会不 经 意 间 做的。某个 变 量在定 义 的 时 候并不是 const 的,但是在 调 用某个函数的 时 候将它作 为 const 形参 传 入,而 该 函数内部再 调 用 Swap() 函数 时 , 这 个 变 量已 经 成了局部的 const 变 量。
以上 这 个特性反 过 来 应 用是很有用的。在多人 协 作写程序的 时 候,或者写一个大型程序的 时 候。你不知道某函数是否用 const 来保 护 参数,但是你想保 护 参数。那 么 ,你就在自己写的原 调 函数中将 该 参数保 护 起来。 这样 ,当你 调 用某个没有 显 式指定 const 引用参数的函数 时 , 编译 器就会 报错 。
void funca(const int& a)
{
funcb(a);// 发 生 错误
}
void funcb(int& b)
{
...;
}
int t;
funca(t);
以上程序会在注 释 的那行停止 编译 。因 为 在它 调 用了函数 b ,而 b 没有声明参数 为 const 。 虽 然函数 b 中未必改 动 参数 。
但是,天下没有 这么 便宜的午餐!引用 传递 不是倒 处 能用的。 举 个例子:
void Swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
以上函数可以 进 行两个 int 变 量的交 换 。但是,很多情况下 该 函数不能 调 用:
int ia = ib = 1;
short sa = sb = 2;
const int cia = cib = 3;
Swap(ia, ib);// 正确
Swap(sa, sb);// 错误 , short 不是 int , 虽 然可以 隐 式 转换为 int ,但是 这 个 变 量不存在
Swap(cia, cib);// 错误 , 这 两个参数是 const 的
Swap(4, 5);// 常量不是 变 量, 类 似于将 short 变 量 传递给 函数
Swap(ia+ib, ia-ib);// 错误 ,表达式求 值 后 产 生的 临时值 不是 变 量
其中将 const 参数 传递进 函数的做法, 虽 然看起来有些荒 诞 , 实际 上某些 时 候会不 经 意 间 做的。某个 变 量在定 义 的 时 候并不是 const 的,但是在 调 用某个函数的 时 候将它作 为 const 形参 传 入,而 该 函数内部再 调 用 Swap() 函数 时 , 这 个 变 量已 经 成了局部的 const 变 量。
以上 这 个特性反 过 来 应 用是很有用的。在多人 协 作写程序的 时 候,或者写一个大型程序的 时 候。你不知道某函数是否用 const 来保 护 参数,但是你想保 护 参数。那 么 ,你就在自己写的原 调 函数中将 该 参数保 护 起来。 这样 ,当你 调 用某个没有 显 式指定 const 引用参数的函数 时 , 编译 器就会 报错 。
void funca(const int& a)
{
funcb(a);// 发 生 错误
}
void funcb(int& b)
{
...;
}
int t;
funca(t);
以上程序会在注 释 的那行停止 编译 。因 为 在它 调 用了函数 b ,而 b 没有声明参数 为 const 。 虽 然函数 b 中未必改 动 参数 。
标题:
:形参与
实
参的
关
系之引用
传递
C++
有了
“
引用
传递
”
后,
“
形参的改
变
不影响
实
参
”
被判无效。因
为传递给
函数的并不是一个
值
,而是
变
量自身。在函数中定
义
的形参
虽
然
还
是局部
变
量,但却是一个引用。
虽
然
这
个引用的作用域
仅
限于函数内部,但是由于它与
实
参就是同一回事,所以
对
它的操作完全等同于
对实
参的操作。比如你叫
“
黑旋
风
”
去
买鱼
,或者叫
“
铁
牛
”
去
买鱼
,去的都是同一个人。
C++ 为 什 么 要有 “ 引用 传递 ” 这 回事?一 种说 法是只有引用才能达到操作符重 载 的目的, 这 个以后再 谈 。但是,撇 开这 个不 谈 ,形参是不是引用,直接影响了程序 执 行的效率。前面提到 过 ,函数 调 用 时 要用 实 参的 值 去初始化形参,初始化的 过 程包含了定 义 一个 变 量、然后 给 它 赋 一个 值 两个 过 程,如果 这 个 变 量并不是内部 变 量,而是一个 类对 象,那 么 ,定 义 一个 类对 象可能很 复杂 ,而初始化 这 个 对 象一 样 会很 复杂 。而引用只是 给对 象取一个 别 名,不 涉 及定 义 与初始化,离 开 作用域 时 也不用 释 放。
相比之下,用指 针传递 可以避免 类对 象的定 义 、初始化与 释 放。只需要付出指 针变 量的定 义 、初始化与 释 放的代价。但是,指 针 的 杀伤 力太大。即使是熟 练 的程序 员 ,也不能保 证绝 不出 现 “ 野指 针 ” ,野 针 的代价几乎无一例外是程序崩 溃 。
引用也不是吃素的,如果 说 指 针传递 是 “ 帮你配了一把我家的 钥 匙 ” ,那 么 引用 传递 就是直接把我家的 财产 都交 给 了你。有 时 ,我 们 使用引用 传递仅仅 是 为 了效率,而不希望 实 参被修改,那就要 记 得把形参 标记为 const ,如 “UINT GetLength(const CString&)” 。
顺 便 说 一句,指 针传递 也可以 这样 做。把形参定 义为 指向 const 对 象的指 针 (而不是 const 指 针 ),可以降低 杀伤 力,保 护实 参所 对应 的内存。如果是普通的 值传递 ,那 么 有没有 const 对 函数外 部并不影响。但是,我个人 认为 ,有 时 候加上 const 也是一件好事。如果程序的 逻辑 并不需要改 变 参数,而 实际 上 误 写了代 码 ,加上 const 可以 让编译 器帮我 们 找出 BUG ,如:
int Max(const int a, const int b)
{
return a>b?a:b;
}
VB 没有指 针 的概念,却有 “ 值传递 ” 和 “ 地址 传递 ” 两个概念。比如 “Function Func(ByRef i As Integer) As Integer” , 变 量 i 接受了 实 参后,它的改 变 能影响 实 参。它的 实质 就 类 似于 C++ 中的引用 传递 。
C++ 为 什 么 要有 “ 引用 传递 ” 这 回事?一 种说 法是只有引用才能达到操作符重 载 的目的, 这 个以后再 谈 。但是,撇 开这 个不 谈 ,形参是不是引用,直接影响了程序 执 行的效率。前面提到 过 ,函数 调 用 时 要用 实 参的 值 去初始化形参,初始化的 过 程包含了定 义 一个 变 量、然后 给 它 赋 一个 值 两个 过 程,如果 这 个 变 量并不是内部 变 量,而是一个 类对 象,那 么 ,定 义 一个 类对 象可能很 复杂 ,而初始化 这 个 对 象一 样 会很 复杂 。而引用只是 给对 象取一个 别 名,不 涉 及定 义 与初始化,离 开 作用域 时 也不用 释 放。
相比之下,用指 针传递 可以避免 类对 象的定 义 、初始化与 释 放。只需要付出指 针变 量的定 义 、初始化与 释 放的代价。但是,指 针 的 杀伤 力太大。即使是熟 练 的程序 员 ,也不能保 证绝 不出 现 “ 野指 针 ” ,野 针 的代价几乎无一例外是程序崩 溃 。
引用也不是吃素的,如果 说 指 针传递 是 “ 帮你配了一把我家的 钥 匙 ” ,那 么 引用 传递 就是直接把我家的 财产 都交 给 了你。有 时 ,我 们 使用引用 传递仅仅 是 为 了效率,而不希望 实 参被修改,那就要 记 得把形参 标记为 const ,如 “UINT GetLength(const CString&)” 。
顺 便 说 一句,指 针传递 也可以 这样 做。把形参定 义为 指向 const 对 象的指 针 (而不是 const 指 针 ),可以降低 杀伤 力,保 护实 参所 对应 的内存。如果是普通的 值传递 ,那 么 有没有 const 对 函数外 部并不影响。但是,我个人 认为 ,有 时 候加上 const 也是一件好事。如果程序的 逻辑 并不需要改 变 参数,而 实际 上 误 写了代 码 ,加上 const 可以 让编译 器帮我 们 找出 BUG ,如:
int Max(const int a, const int b)
{
return a>b?a:b;
}
VB 没有指 针 的概念,却有 “ 值传递 ” 和 “ 地址 传递 ” 两个概念。比如 “Function Func(ByRef i As Integer) As Integer” , 变 量 i 接受了 实 参后,它的改 变 能影响 实 参。它的 实质 就 类 似于 C++ 中的引用 传递 。
标题:
:形参与
实
参的相互
关
系
“
形参的改
变
不影响
实
参
”
这
句
话说
起来
轻
巧,但是要完全理解,似乎
还
有几个玄机。
在我 发 表《函数的定 义 》一文后,有朋友 发 表意 见 ,提到了 “ 函数 调 用 过 程中的入 栈 与出 栈 ” ,在此首先作个 说 明:我 读 的是《 C++ Primer 》,而不是《 编译 原理》,入 栈 与出 栈 不 归 我 讨论 。在 现 在 讨论 的尺度内,我 们 可以 这么认为 :形参是函数内部的一个局部 变 量, 该 局部 变 量在函数 开 始 执 行 时 被初始化,而初始化它的 值则 来自 实 参的 值 。也就是 说 ,它的定 义 与初始化 类 似于 “int i=3;” 。只是被分成两行写了,形参的定 义 写在函数的定 义 中,如: “int ttt(int b)” ,初始化写在了 调 用中 “cout << ttt(a) << endl;” 。 —— 参看上一篇文章《形参与 实 参概念》。
那 么 ,在函数中无 论 怎 样 改 动 b 的 值 ,被改的始 终 是形参 这 个局部 变 量,函数 结 束 时 ,离 开这 个局部 变 量的作用域, 变 量被 释 放。
但是, C 语 言的 “ 指 针传递 ” 总 是 给 人 “ 形参能改 变实 参 ” 的感 觉 ,其 实这 是一个 误 解。 对 于指 针传递 来 说 ,函数的形参是一个指 针 , 传给 它的 实 参也 应该 是指 针 (或者能 转为 指 针 的 值 ,比如数 组 名、能 转换为 指 针 的 类 等)。在函数中,如果改 变 了 该 指 针 ( 对 指 针 的改 变 就等同于 让这 个指 针 指向 别处 ),不会影响主 调 函数中的 实 参。但是,由于指 针对应 着一个内存地址,通 过 它可以改 变 内存的内容。所以,无 论 在函数内部的形参 还 是外部的 实 参,它 们 都可以影响同一内存的 值 。所以,指 针传递 可以把函数内部的影响 带 到函数外,但是, 带 到函数外的 绝 不是形参,而是形参所指的内存。
这 就好比我把我家的 钥 匙 给 你配了一把,我手里的 钥 匙是 实 参,你手里的 钥 匙是形参。你无 论 是把 钥 匙折断 还 是磨短,都与我的 钥 匙无 关 ,但是你用它 开 了我家的 门 却可以把我家洗劫一空。你影响的不是我的 钥 匙,而是我的 财产 。
上文 说 到, C++ 有了 “ 引用 传递 ” 后, “ 形参的改 变 不影响 实 参 ” 被判无效。 这 就得提到 “ 引用 传递 ” 的概念了,下文再 续 。
在我 发 表《函数的定 义 》一文后,有朋友 发 表意 见 ,提到了 “ 函数 调 用 过 程中的入 栈 与出 栈 ” ,在此首先作个 说 明:我 读 的是《 C++ Primer 》,而不是《 编译 原理》,入 栈 与出 栈 不 归 我 讨论 。在 现 在 讨论 的尺度内,我 们 可以 这么认为 :形参是函数内部的一个局部 变 量, 该 局部 变 量在函数 开 始 执 行 时 被初始化,而初始化它的 值则 来自 实 参的 值 。也就是 说 ,它的定 义 与初始化 类 似于 “int i=3;” 。只是被分成两行写了,形参的定 义 写在函数的定 义 中,如: “int ttt(int b)” ,初始化写在了 调 用中 “cout << ttt(a) << endl;” 。 —— 参看上一篇文章《形参与 实 参概念》。
那 么 ,在函数中无 论 怎 样 改 动 b 的 值 ,被改的始 终 是形参 这 个局部 变 量,函数 结 束 时 ,离 开这 个局部 变 量的作用域, 变 量被 释 放。
但是, C 语 言的 “ 指 针传递 ” 总 是 给 人 “ 形参能改 变实 参 ” 的感 觉 ,其 实这 是一个 误 解。 对 于指 针传递 来 说 ,函数的形参是一个指 针 , 传给 它的 实 参也 应该 是指 针 (或者能 转为 指 针 的 值 ,比如数 组 名、能 转换为 指 针 的 类 等)。在函数中,如果改 变 了 该 指 针 ( 对 指 针 的改 变 就等同于 让这 个指 针 指向 别处 ),不会影响主 调 函数中的 实 参。但是,由于指 针对应 着一个内存地址,通 过 它可以改 变 内存的内容。所以,无 论 在函数内部的形参 还 是外部的 实 参,它 们 都可以影响同一内存的 值 。所以,指 针传递 可以把函数内部的影响 带 到函数外,但是, 带 到函数外的 绝 不是形参,而是形参所指的内存。
这 就好比我把我家的 钥 匙 给 你配了一把,我手里的 钥 匙是 实 参,你手里的 钥 匙是形参。你无 论 是把 钥 匙折断 还 是磨短,都与我的 钥 匙无 关 ,但是你用它 开 了我家的 门 却可以把我家洗劫一空。你影响的不是我的 钥 匙,而是我的 财产 。
上文 说 到, C++ 有了 “ 引用 传递 ” 后, “ 形参的改 变 不影响 实 参 ” 被判无效。 这 就得提到 “ 引用 传递 ” 的概念了,下文再 续 。
对
本文本的
评论
有:
简单
地
说
,
每
次
调
用函数的
时
候
,
形参把
实
参克隆了一次
,
你再怎
么
折
腾
形参
,
也与
实
参无
关
.
TNND 就是一个入 栈 与出 栈过 程嘛 , 你可以去学学 汇编 .
举 例 :
mov cs1,100 //cs1=100;
push cs1 // 把 cs1 入 栈 ;
pop cs2 // 把 栈 中的内容出 栈给 cs2;
这 与另一句 话 等价 :
mov cs1,100
mov cs2,cs1
为 什 么 会使用上面的那 种 用法呢 ?
因 为 push 和 pop 占用更少的 CPU 周期 . 所以 , 一般 调 用函数都用入 / 出 栈 来 备 拷 贝 参数 .
TNND 就是一个入 栈 与出 栈过 程嘛 , 你可以去学学 汇编 .
举 例 :
mov cs1,100 //cs1=100;
push cs1 // 把 cs1 入 栈 ;
pop cs2 // 把 栈 中的内容出 栈给 cs2;
这 与另一句 话 等价 :
mov cs1,100
mov cs2,cs1
为 什 么 会使用上面的那 种 用法呢 ?
因 为 push 和 pop 占用更少的 CPU 周期 . 所以 , 一般 调 用函数都用入 / 出 栈 来 备 拷 贝 参数 .
标题:
:形参与
实
参概念
说
到形参与
实
参,在
C++
出来之前其
实
很
简单
,就一句
话
:形参的改
变
不影响
实
参。
这
个状
态
直到
C++
有了
“
引用
传递
”
才有改
变
。
要弄清 这 个,首先得弄清形参与 实 参是什 么东 西。因 为 函数是一段 “ 可以重用而不必重写 ” 的代 码 , 每 次重用当然未必完全相同(不可否 认 有些函数 每 次重用都完全相同),那 么 不同在哪里呢?又怎 样产 生不同呢?一 种 方法是依靠随机,随机是个好 东 西,不要 说 客 户 了, 连 程序 员 都无法控制 每 次 调 用的 结 果。第二 种 方法是凭客 观 条件(比如运行 时间 、机器配置)。但是 这 些函数 应 用很窄, 类 似于 “y=Sin(x)” 这样 的函 数就 绝 不能 这样 做。
那 么 ,从 “y=sin(x)” 的形式看来,能决定函数怎 样 运行的唯一因素就是 x 的 值 了。函数的某次运行是受某一个 x 值 的影响并控制的,而下一次运行, 则 会受另一个 x 值 的影响。那 么 , 调 用函数者就有必要告 诉 函数:我要用哪个 值 来控制你,而函数自己 则 有必要保存 这 个 值 ,直到函数 结 束。
为 此,在函数内部建立一个 临时 的、局部的 变 量, 该变 量的作用域就是函数内部, 该变 量的作用 时间 就是从函数 开 始 执 行到 结 束 执 行。如果同一函数在同一 时间 有几个副本在 执 行( 这种 情况在多 线 程程序中会出 现 ),那 么 它 们 是互不相干的,它 们 内 部的 变 量也是互不相干的。 这 个 变 量就叫做 “ 形参 ” ,全称形式参数。
“ 形式 ” 是跟 “ 实际 ” 相 对 的,另一个参数就是 实际 参数,叫 “ 实 参 ” ,在 调 用函数 时 , 这 个 值 将决定函数内部的形参的 值 。 实 参在函数中是否可 见 ? 这 要取决于两个因素:一是 实 参的作用域,二是有没有被形参覆盖。先 说 第一个因素,如果只 谈 C 语 言,那 么 所 谓 的作用域就是全局与局部两 种 ,但是 C++ 中 还 有 “ 类 作用域 ” 这 一概念,由此第一个因素 变 得 复杂 了。第二个因素本身并不 复杂 ,但是如果没有引起程序 员 的注意,那 么 造成的 问题 是很 难发现 的。 试 看下以下程序:
int a;// 全局 变 量
int ttt(int a)// 该 函数的形参也叫 a
{
cout << ++a << endl;
return a;
}
int main()
{
a = 3;
cout << a << endl;
cout<< ttt(a) << endl;
cout << a << endl;
return 0;
}
该 程序中有一个全局的 a 变 量,但是在 ttt() 函数中却被另一个 a 覆盖了,所以, ++a 没有影响到全局的 a ,如果把函数定 义 改 为 “int ttt(int b)” 则 有不同的 结 果 。
以上把 “ 形参 ” 和 “ 实 参 ” 提了 这么 多,主要目的 还 是 讲 清 “ 形参的改 变 不影响 实 参 ” 这 句 话 。字数不少了,留到下篇文章再 续 吧。(我 觉 得我写得不像 读书 笔 记 ,倒像是教材了。 呵呵 )
要弄清 这 个,首先得弄清形参与 实 参是什 么东 西。因 为 函数是一段 “ 可以重用而不必重写 ” 的代 码 , 每 次重用当然未必完全相同(不可否 认 有些函数 每 次重用都完全相同),那 么 不同在哪里呢?又怎 样产 生不同呢?一 种 方法是依靠随机,随机是个好 东 西,不要 说 客 户 了, 连 程序 员 都无法控制 每 次 调 用的 结 果。第二 种 方法是凭客 观 条件(比如运行 时间 、机器配置)。但是 这 些函数 应 用很窄, 类 似于 “y=Sin(x)” 这样 的函 数就 绝 不能 这样 做。
那 么 ,从 “y=sin(x)” 的形式看来,能决定函数怎 样 运行的唯一因素就是 x 的 值 了。函数的某次运行是受某一个 x 值 的影响并控制的,而下一次运行, 则 会受另一个 x 值 的影响。那 么 , 调 用函数者就有必要告 诉 函数:我要用哪个 值 来控制你,而函数自己 则 有必要保存 这 个 值 ,直到函数 结 束。
为 此,在函数内部建立一个 临时 的、局部的 变 量, 该变 量的作用域就是函数内部, 该变 量的作用 时间 就是从函数 开 始 执 行到 结 束 执 行。如果同一函数在同一 时间 有几个副本在 执 行( 这种 情况在多 线 程程序中会出 现 ),那 么 它 们 是互不相干的,它 们 内 部的 变 量也是互不相干的。 这 个 变 量就叫做 “ 形参 ” ,全称形式参数。
“ 形式 ” 是跟 “ 实际 ” 相 对 的,另一个参数就是 实际 参数,叫 “ 实 参 ” ,在 调 用函数 时 , 这 个 值 将决定函数内部的形参的 值 。 实 参在函数中是否可 见 ? 这 要取决于两个因素:一是 实 参的作用域,二是有没有被形参覆盖。先 说 第一个因素,如果只 谈 C 语 言,那 么 所 谓 的作用域就是全局与局部两 种 ,但是 C++ 中 还 有 “ 类 作用域 ” 这 一概念,由此第一个因素 变 得 复杂 了。第二个因素本身并不 复杂 ,但是如果没有引起程序 员 的注意,那 么 造成的 问题 是很 难发现 的。 试 看下以下程序:
int a;// 全局 变 量
int ttt(int a)// 该 函数的形参也叫 a
{
cout << ++a << endl;
return a;
}
int main()
{
a = 3;
cout << a << endl;
cout<< ttt(a) << endl;
cout << a << endl;
return 0;
}
该 程序中有一个全局的 a 变 量,但是在 ttt() 函数中却被另一个 a 覆盖了,所以, ++a 没有影响到全局的 a ,如果把函数定 义 改 为 “int ttt(int b)” 则 有不同的 结 果 。
以上把 “ 形参 ” 和 “ 实 参 ” 提了 这么 多,主要目的 还 是 讲 清 “ 形参的改 变 不影响 实 参 ” 这 句 话 。字数不少了,留到下篇文章再 续 吧。(我 觉 得我写得不像 读书 笔 记 ,倒像是教材了。 呵呵 )
标题:
:函数的定
义
不
记
得在哪本
书
上看到
过
,函数的定
义为
“
有名称的一段代
码
”
。
这
大概地
说
明了函数的
实质
:首先、它是一段代
码
,其次、
这
段代
码
可以被重
复
使用而不必重
复编
写,第三、它是有名字的,在需要重用的
时
候凭名字来
调
用。
这 个 说 法到了 C++ 中 变 得 复杂 了。原因之一是 C++ 支持函数重 载 ,也就是 说 出 现 了同名函数。 虽 然 编译 器在 编译时产 生不同的函数名,但那必竟是 编译 器的事, 对 于程序 员 来 说 就是同一个函数名。原因之二是 C++ 支持运算符重 载 ,可以用一个 类 似于 “+” 号的运算符来 调 用函数。运算符重 载 明 摆 着是 为 了配合 类对 象的运算,因 为 如果没有 类 , 仅针对 内置 类 型,运算符是没必要重 载 的。 —— 我 试验 了一下,自定 义 了一个 “int operator +(int i, int j)” 函数, 结 果没有通 过编译 。
于是,到了 C++ 中,函数的概念被修改 为 “ 函数由函数名以及一 组 操作数 类 型唯一地表示 ” ,依我看, 这样说还 不 够 。 严 格 说 来, 应该说 “ 函数由作用域、函数名以及一 组 操作数 类 型唯一地表示 ” ,理由很 简单 ,因 为 在不同的作用域中可以出 现 名称相同、参数 类 型也相同的函数,除非把 “ 作用域 :: 函数名 ” 合起来看作一个函数名。
函数 对 函数体没有任何 强 制性要求,哪怕函数体 为 空也可以。不 过 ,无 论 是空、一句 语 名, 还 是多句 语 句,花括号一定不可少。在 这 里,包括在花括号内的若干行 语 句不能再 视为 一个 复 合 语 句了 —— 因 为 能放 复 合 语 句的地方也能放 简单语 句,而 简单语 句可以不使用花括号。
不管你如何看待 这组 花括号,有一点是肯定的:花括号内部是一个作用域。那 么 ,内部定 义 的 变 量就只有在内部使用了。 这 就是局部 变 量,在任何函数(包括 main() )内部定 义 的 变 量都是局部 变 量 —— 初学者可能以 为 在 main() 内部定 义 的 变 量是全局 变 量。
有一 种 内部 变 量的定 义 与以往的定 义 方式不一 样 ,那就是函数的参数。不同之 处 在于:一是它 们 用逗号分隔,二是不允 许 用 “int i,j” 这样 的方式定 义 一 组变 量。我想,也 许 正是因 为 所有定 义 用逗号分隔,才造成不允 许 后者的吧, 毕 竟 这样 会 带 来歧 义 ——j 没有指定 类 型。如果用分号来分隔,那 么 后者的 方式也 许 就可以了。 这 是 C++ 标 准的事,我没有能力来 为标 准出 谋 划策,只能妄加猜 测 了。
函数的返回 值 也是一个 类 型,与 变 量的 类 型一 样 ,它可以是内置 类 型,也可以是 类类 型, 还 可以是引用和指 针 。
引用:在 C++ 标 准化之前,如果缺少 显 式返回 类 型,函数的返回 值 将被假定 为 int 型。
笔 记 :据我 测试 ,在 VC++.NET 中, 这样 做是可以的。照 这么说 , VC++.NET 仍然没有按照 C++ 标 准做?或者 说 VC++.NET 迁就了老程序 员 ?
这 个 说 法到了 C++ 中 变 得 复杂 了。原因之一是 C++ 支持函数重 载 ,也就是 说 出 现 了同名函数。 虽 然 编译 器在 编译时产 生不同的函数名,但那必竟是 编译 器的事, 对 于程序 员 来 说 就是同一个函数名。原因之二是 C++ 支持运算符重 载 ,可以用一个 类 似于 “+” 号的运算符来 调 用函数。运算符重 载 明 摆 着是 为 了配合 类对 象的运算,因 为 如果没有 类 , 仅针对 内置 类 型,运算符是没必要重 载 的。 —— 我 试验 了一下,自定 义 了一个 “int operator +(int i, int j)” 函数, 结 果没有通 过编译 。
于是,到了 C++ 中,函数的概念被修改 为 “ 函数由函数名以及一 组 操作数 类 型唯一地表示 ” ,依我看, 这样说还 不 够 。 严 格 说 来, 应该说 “ 函数由作用域、函数名以及一 组 操作数 类 型唯一地表示 ” ,理由很 简单 ,因 为 在不同的作用域中可以出 现 名称相同、参数 类 型也相同的函数,除非把 “ 作用域 :: 函数名 ” 合起来看作一个函数名。
函数 对 函数体没有任何 强 制性要求,哪怕函数体 为 空也可以。不 过 ,无 论 是空、一句 语 名, 还 是多句 语 句,花括号一定不可少。在 这 里,包括在花括号内的若干行 语 句不能再 视为 一个 复 合 语 句了 —— 因 为 能放 复 合 语 句的地方也能放 简单语 句,而 简单语 句可以不使用花括号。
不管你如何看待 这组 花括号,有一点是肯定的:花括号内部是一个作用域。那 么 ,内部定 义 的 变 量就只有在内部使用了。 这 就是局部 变 量,在任何函数(包括 main() )内部定 义 的 变 量都是局部 变 量 —— 初学者可能以 为 在 main() 内部定 义 的 变 量是全局 变 量。
有一 种 内部 变 量的定 义 与以往的定 义 方式不一 样 ,那就是函数的参数。不同之 处 在于:一是它 们 用逗号分隔,二是不允 许 用 “int i,j” 这样 的方式定 义 一 组变 量。我想,也 许 正是因 为 所有定 义 用逗号分隔,才造成不允 许 后者的吧, 毕 竟 这样 会 带 来歧 义 ——j 没有指定 类 型。如果用分号来分隔,那 么 后者的 方式也 许 就可以了。 这 是 C++ 标 准的事,我没有能力来 为标 准出 谋 划策,只能妄加猜 测 了。
函数的返回 值 也是一个 类 型,与 变 量的 类 型一 样 ,它可以是内置 类 型,也可以是 类类 型, 还 可以是引用和指 针 。
引用:在 C++ 标 准化之前,如果缺少 显 式返回 类 型,函数的返回 值 将被假定 为 int 型。
笔 记 :据我 测试 ,在 VC++.NET 中, 这样 做是可以的。照 这么说 , VC++.NET 仍然没有按照 C++ 标 准做?或者 说 VC++.NET 迁就了老程序 员 ?
对本文本的评论有:
函数的参数当然不能使用类似int i,j的方式,因为调用函数的时候,涉及到的不仅仅是定义参数,还有把要处理的变量入栈,调用的函数运行前的第一件事,是把被入栈的变量出栈.
这与int i,j定义变量做的事完全不同,所以,不按定义变量的方式写,也很正常.
如果偷猫兄一定要写得一样,那就自己做一个编译器吧.
这与int i,j定义变量做的事完全不同,所以,不按定义变量的方式写,也很正常.
如果偷猫兄一定要写得一样,那就自己做一个编译器吧.
标题:
:函数概念
进
入第七章学
习
。
“ 函数 ” 这 个概念在 C/C++ 里 头 是很 烦 人的。原因在于,好多 C 语 言入 门书 的第一章第一 节 都 说 “C 语 言是由函数 组 成的 ” ,初学者学到 这 里,就好像是 刚 推 开 C 的大 门 就被一个麻袋套在 头 上,什 么 也看不 见 了。那些 书 本 还举 了一个例子,然后 对 照着例子 说 “ 这 个程序是由 main() 、 scanf() 、 printf() 函数 组 成的 ……” 。我 晕 啊,初学者第一天上 C 的 课 ,哪里会管什 么 函数不函数的。
这 点 BASIC 做得不 错 ,倒不是 说 BASIC 比 C++ 好,而是 BASIC 容易入 门 。在 开头 几 节课 不必理会 这么复杂 的 东 西,学了 “Let 语 句 ” 、 “Print 语 句 ” 就可以 涉 足 简单 的算法了。然后提到的 “ 函数 ” 是包括数学函数在内的 “ 内部函数 ” 。我 们 在数学里学 过 “ 函数 ” 概念,知道 “y=Sin(x)” 是一个函数, 现 在在 BASIC 里学到一 样 的函数,自然容易入 门 。等 这 一切都熟悉了,再去学 习 自己写的函数 —— 自定 义 函数,会更加理解程序中的 “ 函数 ” 概念。
VB 与早期的 BASIC 相比,使用了 “ 事件 驱动 ” 原理。画完界面就得面 对 函数了 ,但是 VB 用 “ 事件 ” 这 个 说 法来回避了。初学者可以不知道 “Private Sub Command1_Click()” 究竟代表什 么 ,只要知道那是 “ 按 钮 控件被 单击 后 执 行的代 码 ” 就 够 了。等到后来,学 习 了 “ 自定 义 函数 ” 后,必然会恍然大悟。
回到 C++ 中,学 习 之初用到的函数的确是 现 成的 库 函数,但是正因 为过 早地提到了函数概念, 导 致了初学者无所适从。有没有 别 的 办 法呢?当然有了,至少《 C++ Primer 》 这 本 书 一直到第七章才 开 始提 “ 函数 ” 二字。
另外: VB 中有 “ 函数 ” 和 “ 子程序 ” 两个不同的概念,如今 “ 子程序 ” 又叫 “ 过 程 ” ,除了使用不同的 关键 字以外,它 们 的惟一区 别 是有没有返回 值 。 C 将它 们 合并了,都叫函数。其 实 , VB 里的函数也可以 丢 弃返回 值 ,只是 VB 里没有与 “void” 对应 的 词 ,无法定 义 不返 值 的函数,才不得已出此下策 。
“ 函数 ” 这 个概念在 C/C++ 里 头 是很 烦 人的。原因在于,好多 C 语 言入 门书 的第一章第一 节 都 说 “C 语 言是由函数 组 成的 ” ,初学者学到 这 里,就好像是 刚 推 开 C 的大 门 就被一个麻袋套在 头 上,什 么 也看不 见 了。那些 书 本 还举 了一个例子,然后 对 照着例子 说 “ 这 个程序是由 main() 、 scanf() 、 printf() 函数 组 成的 ……” 。我 晕 啊,初学者第一天上 C 的 课 ,哪里会管什 么 函数不函数的。
这 点 BASIC 做得不 错 ,倒不是 说 BASIC 比 C++ 好,而是 BASIC 容易入 门 。在 开头 几 节课 不必理会 这么复杂 的 东 西,学了 “Let 语 句 ” 、 “Print 语 句 ” 就可以 涉 足 简单 的算法了。然后提到的 “ 函数 ” 是包括数学函数在内的 “ 内部函数 ” 。我 们 在数学里学 过 “ 函数 ” 概念,知道 “y=Sin(x)” 是一个函数, 现 在在 BASIC 里学到一 样 的函数,自然容易入 门 。等 这 一切都熟悉了,再去学 习 自己写的函数 —— 自定 义 函数,会更加理解程序中的 “ 函数 ” 概念。
VB 与早期的 BASIC 相比,使用了 “ 事件 驱动 ” 原理。画完界面就得面 对 函数了 ,但是 VB 用 “ 事件 ” 这 个 说 法来回避了。初学者可以不知道 “Private Sub Command1_Click()” 究竟代表什 么 ,只要知道那是 “ 按 钮 控件被 单击 后 执 行的代 码 ” 就 够 了。等到后来,学 习 了 “ 自定 义 函数 ” 后,必然会恍然大悟。
回到 C++ 中,学 习 之初用到的函数的确是 现 成的 库 函数,但是正因 为过 早地提到了函数概念, 导 致了初学者无所适从。有没有 别 的 办 法呢?当然有了,至少《 C++ Primer 》 这 本 书 一直到第七章才 开 始提 “ 函数 ” 二字。
另外: VB 中有 “ 函数 ” 和 “ 子程序 ” 两个不同的概念,如今 “ 子程序 ” 又叫 “ 过 程 ” ,除了使用不同的 关键 字以外,它 们 的惟一区 别 是有没有返回 值 。 C 将它 们 合并了,都叫函数。其 实 , VB 里的函数也可以 丢 弃返回 值 ,只是 VB 里没有与 “void” 对应 的 词 ,无法定 义 不返 值 的函数,才不得已出此下策 。
标题:
:
try
、
catch
和
assert
程序
员
是要慢慢成
长
的,比如
错误处
理
这种
事情,就不是一
开
始就面
对
的。当我
们编
的程序
还
很小,小到
“cin>>i; cout<<i;”
这样
的程度,
错误处
理不是我
们
要学
习
的目
标
。但是,一旦
开
始
编
写
实
用的程序,那
么
,无
论
考
虑
多
么
周到,无
论
代
码
多
么
精良。意外
总
是
难
免的。
这
些意外可能来自程序
员
的
设计
不到位、可能来自用
户
的
错误
操作、
还
可能来自机器与网
络
的不确定因素。
没有什 么 比追踪 错误 更 难过 的事了, 记 得有一回我在追踪一个 VB 程序的 错误 。 经过长时间测试 ,我 发现 程序在运行中突然 发 生很大的跳 跃 :函数 A 调 用 B , B 调 用 C ,在 C 的 执 行 过 程中,居然会突然跳到 A 中。后来追 查发现 ,原来 A 中有一行 “On Error Goto” 语 句。 这 一个 语 句,影响了我 调试 C 函数。从那以后,我明白了,除非程序要 发 布了,否 则别 启 动错误处 理。
C++ 与 VB 不一 样 , VB 用一句 “On Error Goto” 启 动 了 错误处 理后,在 该 函数 结 束之前一直有效(除非 显 式地 关闭 它)。如果 发 生了异常, 处 理代 码 要根据异常的 值 来分析异常的 类 型。而 C++ 可以 选择 可能出 现 异常的内容放 进 try 后的 块 中。一个函数内部可以有多个 try 块 ,而 每 个 try 块 又可以附 带 多个 catch 来 处 理。 应该说 , C++ 中的异常 处 理更灵活,当然也更容易出 错 。我前 阵 子 发 生的 错误 就是在 ADO 处 理后只有 “catch(_com_error *e)” ,但是 实际 上出 现 的异常却不是 “_com_error” 类 的, 结 果仍然抓不往异常。
异常 处 理和 assert 之 间 的 关 系有些 让 人 难 以捉摸。一方面它 们 各有各的作用,另一方面它 们 有 时 会互相影响。我就曾 经 在 这 上面吃 过亏 :我的程序是在服 务 器上运行的,从来没人会 盯 着服 务 器看,所以我的程序不允 许弹 出 对话 框。我写了比 较 完善的异常 处 理,无 论 出 现 什 么错误 ,都 记录进 LOG 文件,然后 继续 运行。但是我却 是用 DEBUG 模式 编译 的, 结 果异常到来 时 , try 没起作用,倒是 assert 起作用了, 弹 了个 对话 框在那儿。 这 件事 给 我的启 发 是: 别 以 为 自己是程序的客 户 就可以用 DEBUG 模式 编译 。
没有什 么 比追踪 错误 更 难过 的事了, 记 得有一回我在追踪一个 VB 程序的 错误 。 经过长时间测试 ,我 发现 程序在运行中突然 发 生很大的跳 跃 :函数 A 调 用 B , B 调 用 C ,在 C 的 执 行 过 程中,居然会突然跳到 A 中。后来追 查发现 ,原来 A 中有一行 “On Error Goto” 语 句。 这 一个 语 句,影响了我 调试 C 函数。从那以后,我明白了,除非程序要 发 布了,否 则别 启 动错误处 理。
C++ 与 VB 不一 样 , VB 用一句 “On Error Goto” 启 动 了 错误处 理后,在 该 函数 结 束之前一直有效(除非 显 式地 关闭 它)。如果 发 生了异常, 处 理代 码 要根据异常的 值 来分析异常的 类 型。而 C++ 可以 选择 可能出 现 异常的内容放 进 try 后的 块 中。一个函数内部可以有多个 try 块 ,而 每 个 try 块 又可以附 带 多个 catch 来 处 理。 应该说 , C++ 中的异常 处 理更灵活,当然也更容易出 错 。我前 阵 子 发 生的 错误 就是在 ADO 处 理后只有 “catch(_com_error *e)” ,但是 实际 上出 现 的异常却不是 “_com_error” 类 的, 结 果仍然抓不往异常。
异常 处 理和 assert 之 间 的 关 系有些 让 人 难 以捉摸。一方面它 们 各有各的作用,另一方面它 们 有 时 会互相影响。我就曾 经 在 这 上面吃 过亏 :我的程序是在服 务 器上运行的,从来没人会 盯 着服 务 器看,所以我的程序不允 许弹 出 对话 框。我写了比 较 完善的异常 处 理,无 论 出 现 什 么错误 ,都 记录进 LOG 文件,然后 继续 运行。但是我却 是用 DEBUG 模式 编译 的, 结 果异常到来 时 , try 没起作用,倒是 assert 起作用了, 弹 了个 对话 框在那儿。 这 件事 给 我的启 发 是: 别 以 为 自己是程序的客 户 就可以用 DEBUG 模式 编译 。
对本文本的评论有:
错误捕捉是很烦人,我的感觉是能在try代码段外解决的错误,就尽量在外头自己解决,尽量少依靠try来处理捕获错误.
在网络编程中,有些错误是无法预知的,比如网络连接断了,数据库当了...好象在这些情况下,用try比较好.
我有一次写的一个服务程序,用户用了一段时间后,经常会异常中止,查来查去查不出原因,后来才发现是ORACLE的日志满了,这个错误显然我在写程序的时候没有想过,丢脸啊...
在网络编程中,有些错误是无法预知的,比如网络连接断了,数据库当了...好象在这些情况下,用try比较好.
我有一次写的一个服务程序,用户用了一段时间后,经常会异常中止,查来查去查不出原因,后来才发现是ORACLE的日志满了,这个错误显然我在写程序的时候没有想过,丢脸啊...
标题:
:
break
、
continue
和
goto
break
和
continue
的使用范
围
比
较
一致,两都可以用于循
环
,其中
break
还
可以用于
switch
。功能上也有一定的相似性,
break
就相当于退学,
continue
则
相当于跳
级
。
对
于
break
,程序究竟跳到哪儿比
较
好理解。
但是
continue
究竟跳到哪儿去了,初学者可能有些疑惑,不妨就当它跳到了循
环
体最后一句
语
句的后面。
如果它 们处 在由多重循 环 和 switch 组 成的圈圈里,那 么 它 们 就 对 包括它 们 的最里 层 起作用。于是, 设 想一下子跳出多重循 环 的人可能忘不了 goto 。
引用:从上世 纪 60 年代后期 开 始,不主 张 使用 goto 语 句。 …… 所有使用 goto 的程序都可以改写成不用 goto 。
笔 记 : goto 是一个很有争 议 的 语 句, 语 多 书 本建 议 少用或不用它,我个人的 习惯 是 坚 决不用。不 过 ,至于 “ 上世 纪 60 年代 ” 这 个 说 法,我倒是一直不知道。因 为 我自己学 习 BASIC 已 经 是 1994 年,那 时 候学的是 带 行号的 GW-BASIC , goto 是必 须 用到的 语 句。莫非当 时 我 们 学校 开设 的 课 程居然是落后二十年的内容?
林 锐 博士 对 goto 另有看法,他 说 : “ 错误 是 程序 员 自己造成的,不是 goto 的 过错 。 goto 至少有一 处 可 显 神通,它能从多重循 环 中咻地一下子跳到外面, …… 就像房子着火了,来不及从楼梯一 级 一 级 往下走,可从窗口跳出火坑。 ……” (《高 质 量 C++/C 编 程指南》第 32 页 )
我写的程序目前 还 没有超越三 级 循 环 。从最里 层 往外跳,如果跳一 层 ,就 break ,如果跳两 层 或三 层 ,一是 这种 可能性很小,二是如果真的碰到了,我就用其它条件来控制外 层 循 环 是否 继续 break ,自从 1997 年 进 入 结 构化的程序 设计 以来,我的确完全抛弃了 goto 。 ——VB 中的 “On Error Goto” 除外,出 现错误 ,自然不管在哪一 层 ,都 给 我跳 进错误处 理中。
goto 的目 标 是一个 标 号, 这 个 标 号的起名倒有点意思,因 为标 号只用于 goto ,所以它的名字可以与任何 变 量名以及其它 标识 符一 样 而不 产 生重名。以前的程序是 带 行号的,所以就 “goto 行号 ” , 现 在程序不 带 行号了,但是允 许 在任何地方加 标 号。 编译 器在碰到它 们 的 时 候,大概就是凭其后 头 的冒号来判断 这 个名字不需要 检验 合法性。那 么 , C++ 中已有的 “public:” 算不算 标 号呢?
为 此,我做了个 实验 : 实验 内容一是我在 类 的声明里加入了一行 “pub:” ,二是我在程序段中加入了一行 “public:” 。 结 果 发现 两都都不能通 过编译 。也就是 说 , 实验 一 说 明在 类 定 义这样 的地方不允 许 使用 标 号(也用不着,因 为 它不在任何函数内部, goto 是运行 时 的事,与 编译 无 关 ,而且 goto 不允 许 跨函数跳越。), 实验 二 说 明在程序段中的 标 号不允 许 使用保留字 。
如果它 们处 在由多重循 环 和 switch 组 成的圈圈里,那 么 它 们 就 对 包括它 们 的最里 层 起作用。于是, 设 想一下子跳出多重循 环 的人可能忘不了 goto 。
引用:从上世 纪 60 年代后期 开 始,不主 张 使用 goto 语 句。 …… 所有使用 goto 的程序都可以改写成不用 goto 。
笔 记 : goto 是一个很有争 议 的 语 句, 语 多 书 本建 议 少用或不用它,我个人的 习惯 是 坚 决不用。不 过 ,至于 “ 上世 纪 60 年代 ” 这 个 说 法,我倒是一直不知道。因 为 我自己学 习 BASIC 已 经 是 1994 年,那 时 候学的是 带 行号的 GW-BASIC , goto 是必 须 用到的 语 句。莫非当 时 我 们 学校 开设 的 课 程居然是落后二十年的内容?
林 锐 博士 对 goto 另有看法,他 说 : “ 错误 是 程序 员 自己造成的,不是 goto 的 过错 。 goto 至少有一 处 可 显 神通,它能从多重循 环 中咻地一下子跳到外面, …… 就像房子着火了,来不及从楼梯一 级 一 级 往下走,可从窗口跳出火坑。 ……” (《高 质 量 C++/C 编 程指南》第 32 页 )
我写的程序目前 还 没有超越三 级 循 环 。从最里 层 往外跳,如果跳一 层 ,就 break ,如果跳两 层 或三 层 ,一是 这种 可能性很小,二是如果真的碰到了,我就用其它条件来控制外 层 循 环 是否 继续 break ,自从 1997 年 进 入 结 构化的程序 设计 以来,我的确完全抛弃了 goto 。 ——VB 中的 “On Error Goto” 除外,出 现错误 ,自然不管在哪一 层 ,都 给 我跳 进错误处 理中。
goto 的目 标 是一个 标 号, 这 个 标 号的起名倒有点意思,因 为标 号只用于 goto ,所以它的名字可以与任何 变 量名以及其它 标识 符一 样 而不 产 生重名。以前的程序是 带 行号的,所以就 “goto 行号 ” , 现 在程序不 带 行号了,但是允 许 在任何地方加 标 号。 编译 器在碰到它 们 的 时 候,大概就是凭其后 头 的冒号来判断 这 个名字不需要 检验 合法性。那 么 , C++ 中已有的 “public:” 算不算 标 号呢?
为 此,我做了个 实验 : 实验 内容一是我在 类 的声明里加入了一行 “pub:” ,二是我在程序段中加入了一行 “public:” 。 结 果 发现 两都都不能通 过编译 。也就是 说 , 实验 一 说 明在 类 定 义这样 的地方不允 许 使用 标 号(也用不着,因 为 它不在任何函数内部, goto 是运行 时 的事,与 编译 无 关 ,而且 goto 不允 许 跨函数跳越。), 实验 二 说 明在程序段中的 标 号不允 许 使用保留字 。
对本文本的评论有:
不主张使用GOTO语句是为了让程序看起来顺眼而己.看:模块化的代码.其实的确没什么大不了的.记住,当你的程序被编译成机器代码以后,里面的跳转全是JMP,相当于GOTO.
自从和草莓对骂以来,你就学会了狡辩。
我有跟你讨论机器码吗?
程序设计的风格是为了程序维护,
不是为了编译。
我有跟你讨论机器码吗?
程序设计的风格是为了程序维护,
不是为了编译。
标题:
:
while
、
for
语
句
while
中有一个怪事:
类
似于
“while (int i = GetInt())”
这样
的
语
句,在条件中定
义
一个
变
量,在
for
中非常常
见
,也很好理解。但是用在
while
中却有所不同,如果用在
while
中,那
么每
次循
环
都会
经历
一次
创
建和撤
销
的
过
程。
——
天,
还
是不要
这样
写吧。幸
亏
我
总
是在
while
前面定
义
并初始化
变
量的。
do-while 与 while 有着不一般的 关 系,所以几乎所有的 书 本都是把它 们 放一起 讲 的。当年学 BASIC 时 ,花了不少的功夫去学 习 “ 当型循 环 ” 和 “ 直到型循 环 ” 。的确,当型和直到型都有存在的必要,因 为 程序的确有 这 两 种逻辑 需要。于是 C 、 BASIC 以及 PASCAL 等程序 语 言都提供了 这 两 种 循 环 。不 过 提供 归 提供,怎 么 用却是程序 员 自己的事。就我个人而言,我 还 是喜 欢 用当型循 环 。因 为 当型循 环 可以模 拟 出直到型循 环 的效果来。比如以下四段代 码 ,它 们 是完全一致的:
// 代 码 1
do
{
循 环 体 ;
BoolVal = 表达式 ;
}while (BoolVal);
// 代 码 2
BoolVal = 1;// 先 赋 True 值
while(BoolVal)
{
循 环 体 ;
BoolVal = 表达式 ;
}
// 代 码 3
do
{
循 环 体 ;
}while ( 表达式 )
// 代 码 4
while(1)
{
循 环 体 ;
if (! 表达式 ) break;
}
for 语 句的 执 行 顺 序和 执 行 逻辑 是最 难讲 清的了。如果知道了,就是 这么 回事。如果不知道,不 费 上半天口舌是 说 不清的。原因在于 for 包括四个互相 关联 的 语 句,其中三个在 “for” 后面的括号里,另一个作 为 循 环 体存在。 这 也 难 怪 BASIC 要将 for 语 句定 义为 “For i=M To N Step t” 的格式。
for 括号里的三个 语 句是可以省略的,最牛 B 的省略莫 过 于 “for (;;)” 了。会 这样 写的人,要 么 是 彻彻 底底地明白了 for 的 逻辑 的人,要 么 是一点不懂的人。我 觉 得,如果要我 这样 写,我不如写 “while(1)” 了。
do-while 与 while 有着不一般的 关 系,所以几乎所有的 书 本都是把它 们 放一起 讲 的。当年学 BASIC 时 ,花了不少的功夫去学 习 “ 当型循 环 ” 和 “ 直到型循 环 ” 。的确,当型和直到型都有存在的必要,因 为 程序的确有 这 两 种逻辑 需要。于是 C 、 BASIC 以及 PASCAL 等程序 语 言都提供了 这 两 种 循 环 。不 过 提供 归 提供,怎 么 用却是程序 员 自己的事。就我个人而言,我 还 是喜 欢 用当型循 环 。因 为 当型循 环 可以模 拟 出直到型循 环 的效果来。比如以下四段代 码 ,它 们 是完全一致的:
// 代 码 1
do
{
循 环 体 ;
BoolVal = 表达式 ;
}while (BoolVal);
// 代 码 2
BoolVal = 1;// 先 赋 True 值
while(BoolVal)
{
循 环 体 ;
BoolVal = 表达式 ;
}
// 代 码 3
do
{
循 环 体 ;
}while ( 表达式 )
// 代 码 4
while(1)
{
循 环 体 ;
if (! 表达式 ) break;
}
for 语 句的 执 行 顺 序和 执 行 逻辑 是最 难讲 清的了。如果知道了,就是 这么 回事。如果不知道,不 费 上半天口舌是 说 不清的。原因在于 for 包括四个互相 关联 的 语 句,其中三个在 “for” 后面的括号里,另一个作 为 循 环 体存在。 这 也 难 怪 BASIC 要将 for 语 句定 义为 “For i=M To N Step t” 的格式。
for 括号里的三个 语 句是可以省略的,最牛 B 的省略莫 过 于 “for (;;)” 了。会 这样 写的人,要 么 是 彻彻 底底地明白了 for 的 逻辑 的人,要 么 是一点不懂的人。我 觉 得,如果要我 这样 写,我不如写 “while(1)” 了。
标题:
:
if
、
switch
语
句
本
书
不愧
为经
典
书
,在
if
这
地方能避免
说
教,
讲
得
绘
声
绘
色,真叫人佩服。
大体上 if 要注意的就只有 else 的配 对问题 了。如果在 else 前方有多个没有配 对 的 if ,那就找最近的一个配 对 。如果要改 变这种 默 认 的 “ 拉郎配 ” ,就加上花括号。
还 是引用林 锐 博士的一句 话 吧: “if 、 for 、 while 、 do 等 …… 不 论执 行 语 句有多少都要加 {} 。 这样 可以防止 书 写失 误 。 ” (《高 质 量 C++/C 编 程指南》第 16 页 )
if 语 句曾有一个令我疑惑 了好久的 东 西: “else if” 究竟算什 么 ?因 为 BASIC 里有 “ElseIf” 这 个 关键词 ,而 C++ 中所 谓 的 “else if” 是两个 关 健 词组 成的。中 间 插了个空格。我 们 都知道, C++ 的 语 句与 语 句之 间 插入若干个(包括 0 个)空格、 TAB 、回 车 都是一 样 的,那 么 ,如果我把 else 后插入一个回 车 ,不成了另一 种结 构的 if 语 句了 么 ?后来我仔 细 地分析一下 逻辑关 系,才豁然 开 朗:原来是 BASIC 的 “ElseIf” 干 扰 了我的理解。 C++ 中用哪 种 方法去理解都没区 别 。
都 说 switch 是 为 了 简 化 if 而出 现 的,但是 switch 虽 然可以 简 化 if ,却并不是任何 时 候都能使用。使用 switch 有两个先决因素:一是所有的条件都必 须 是 编译时 常量。也就是 说 如果要在程序运行 时 再决定 case 后的条件,那是不行的。另一个因素是只能拿出若干个整数 值 来比 较 是否相等,既不能是浮点数,也不能比 较 大于或小于。
switch 最容易出 错 的就是 丢 失 break 语 句了。因 为 按常 规 思路,人 们总 以 为 两个 标 号之 间 的 语 句才是 应该执 行的。从 BASIC 过 来的人更加痛苦,因 为 BASIC 里不需要 类 似于 break 这样 的 语 句来表示 结 束。
我的做法是,在打程序框架 时 ,先把 case 标 号和 break 写了,其余的再去完善。即使 逻辑 上不需要 break 语 句,也要写上 “//break;” , 这样 可以提醒自己和 团队 的伙伴:此 处 并未 丢 失 break ,而是的确不需要。
丢 失 default 是最理直气壮的了。因 为 的确有 许 多 时 候并不需要 default ,但是我的 经验 是要加上 default 以及它后面的 break ,原因同上,提醒自己和伙伴我没有 遗 漏 。
大体上 if 要注意的就只有 else 的配 对问题 了。如果在 else 前方有多个没有配 对 的 if ,那就找最近的一个配 对 。如果要改 变这种 默 认 的 “ 拉郎配 ” ,就加上花括号。
还 是引用林 锐 博士的一句 话 吧: “if 、 for 、 while 、 do 等 …… 不 论执 行 语 句有多少都要加 {} 。 这样 可以防止 书 写失 误 。 ” (《高 质 量 C++/C 编 程指南》第 16 页 )
if 语 句曾有一个令我疑惑 了好久的 东 西: “else if” 究竟算什 么 ?因 为 BASIC 里有 “ElseIf” 这 个 关键词 ,而 C++ 中所 谓 的 “else if” 是两个 关 健 词组 成的。中 间 插了个空格。我 们 都知道, C++ 的 语 句与 语 句之 间 插入若干个(包括 0 个)空格、 TAB 、回 车 都是一 样 的,那 么 ,如果我把 else 后插入一个回 车 ,不成了另一 种结 构的 if 语 句了 么 ?后来我仔 细 地分析一下 逻辑关 系,才豁然 开 朗:原来是 BASIC 的 “ElseIf” 干 扰 了我的理解。 C++ 中用哪 种 方法去理解都没区 别 。
都 说 switch 是 为 了 简 化 if 而出 现 的,但是 switch 虽 然可以 简 化 if ,却并不是任何 时 候都能使用。使用 switch 有两个先决因素:一是所有的条件都必 须 是 编译时 常量。也就是 说 如果要在程序运行 时 再决定 case 后的条件,那是不行的。另一个因素是只能拿出若干个整数 值 来比 较 是否相等,既不能是浮点数,也不能比 较 大于或小于。
switch 最容易出 错 的就是 丢 失 break 语 句了。因 为 按常 规 思路,人 们总 以 为 两个 标 号之 间 的 语 句才是 应该执 行的。从 BASIC 过 来的人更加痛苦,因 为 BASIC 里不需要 类 似于 break 这样 的 语 句来表示 结 束。
我的做法是,在打程序框架 时 ,先把 case 标 号和 break 写了,其余的再去完善。即使 逻辑 上不需要 break 语 句,也要写上 “//break;” , 这样 可以提醒自己和 团队 的伙伴:此 处 并未 丢 失 break ,而是的确不需要。
丢 失 default 是最理直气壮的了。因 为 的确有 许 多 时 候并不需要 default ,但是我的 经验 是要加上 default 以及它后面的 break ,原因同上,提醒自己和伙伴我没有 遗 漏 。
标题:
:
简单语
句与
复
合
语
句
祝
贺进
入第
6
章的学
习
。
简单语 句就是只有一句的 语 句, “ 复 合 语 句 ” 也叫 语 句 块 ,是由多句 语 句 组 成的一个整体。 虽 然 BASIC 也有 语 句 块 的概念,但是它 们 却是不同的概念: BASIC 将 简单语 句 视为 特殊的 语 句 块 ,而 C++ 则 将 语 句 块视为 特殊的 简单语 句。个人 认为 , C++ 中 复 合 语 句的存在是 为 了 补 充 C++ 没有 “end if” 之 类语 句的缺陷。
BASIC 中, if 有 end if (行 if 除外)、 while 有 wend , do 有 loop 。也就是 说 ,有 头 就有尾,所以, BASIC 编译 器不担心无法确定 语 句 块 的大小。 C++ 则 不同,它的 这 些 关键 字都没有 结 束 语 句。没有 结 束 标记 , 谁 知道它的主体究竟是几行呢?所以, C++ 只好 规 定:所有 这 些 结 构的 语 句体都只能包含一句,而且必 须 包含一句(有且 仅 有一句)。 换 句 话说 ,如果要多句,你也得做成一句的 样 。
将多行做成一行,就是所 谓 的 “ 复 合 语 句 ” 了。
说 到 简单语 句,空 语 句和空 块 是不能不提的。空 语 句( 块 )也是 语 句( 块 ),只是它 啥 也不干。空 语 句存在的原因,无非也是因 为 C++ 中 规 定了 语 句体必 须 是一句。 刚 才 说 了,那些 结 构的 语 句体是 “ 有且 仅 有一句 ” ,不 仅仅 “ 多于一句要写成一句的 样 ” ,反 过 来 说 ,如果没有任何内容,你 也得 伪 造一句出来。于是 “ 空 语 句 ” 问 世了。
以下 语 句就是一个典型的例子:
int s = 0;
for (int i=1,s=0; i<101; s+=i,++i) ;// 空 语 句
空 语 句的存在 为 C++ 徒增了 难 度与危 险 性,很多初学者弄不清哪些 语 句要以分号 结 尾,哪些 语 句不要, 错误 地在 for() 后面加了个分号, 结 果使循 环 体被取消了循 环 的 资 格,而且有可能出 现 死循 环 。
简单语 句就是只有一句的 语 句, “ 复 合 语 句 ” 也叫 语 句 块 ,是由多句 语 句 组 成的一个整体。 虽 然 BASIC 也有 语 句 块 的概念,但是它 们 却是不同的概念: BASIC 将 简单语 句 视为 特殊的 语 句 块 ,而 C++ 则 将 语 句 块视为 特殊的 简单语 句。个人 认为 , C++ 中 复 合 语 句的存在是 为 了 补 充 C++ 没有 “end if” 之 类语 句的缺陷。
BASIC 中, if 有 end if (行 if 除外)、 while 有 wend , do 有 loop 。也就是 说 ,有 头 就有尾,所以, BASIC 编译 器不担心无法确定 语 句 块 的大小。 C++ 则 不同,它的 这 些 关键 字都没有 结 束 语 句。没有 结 束 标记 , 谁 知道它的主体究竟是几行呢?所以, C++ 只好 规 定:所有 这 些 结 构的 语 句体都只能包含一句,而且必 须 包含一句(有且 仅 有一句)。 换 句 话说 ,如果要多句,你也得做成一句的 样 。
将多行做成一行,就是所 谓 的 “ 复 合 语 句 ” 了。
说 到 简单语 句,空 语 句和空 块 是不能不提的。空 语 句( 块 )也是 语 句( 块 ),只是它 啥 也不干。空 语 句存在的原因,无非也是因 为 C++ 中 规 定了 语 句体必 须 是一句。 刚 才 说 了,那些 结 构的 语 句体是 “ 有且 仅 有一句 ” ,不 仅仅 “ 多于一句要写成一句的 样 ” ,反 过 来 说 ,如果没有任何内容,你 也得 伪 造一句出来。于是 “ 空 语 句 ” 问 世了。
以下 语 句就是一个典型的例子:
int s = 0;
for (int i=1,s=0; i<101; s+=i,++i) ;// 空 语 句
空 语 句的存在 为 C++ 徒增了 难 度与危 险 性,很多初学者弄不清哪些 语 句要以分号 结 尾,哪些 语 句不要, 错误 地在 for() 后面加了个分号, 结 果使循 环 体被取消了循 环 的 资 格,而且有可能出 现 死循 环 。
标题:
:
显
式
转换
引用:
显
式
转换
也称
为强
制
类
型
转换
。
笔 记 :我 觉 得要提 强 制 类 型 转换 ,得从 C 风 格的 说 起。 这 里面可能有我个人的原因。因 为 我个人 习惯 了 C 风 格的 强 制 类 型 转换 。
在 C 语 言中, 强 制 类 型 转换 就是用借助一 对 括号同 时 把 类 型名和表达式列出来,比如 “(int)t” 和 “int(t)” 就是把 t 转为 int 型。
引用:因 为 要覆盖通常的 标 准 转换 ,所以需 显 式使用 强 制 类 型 转换 。 …… 显 式使用 强 制 类 型 转换 的另一个原因是:可能存在多 种转换时 ,需要 选择 一 种 特定的 类 型 转换 。
笔 记 :从外文 图书 翻 译过 来的中国 图书 有个通病,就是 语 言不 伦 不 类 。本 书 算是翻 译 得非常好的了,依然无法 摆 脱 这种 影响。上文的意思无非是 说 :我不希望使用默 认 的 转换规则 的 时 候,就可以 显 式地 规 定按我的要求 转换 。如果要 举 个例子,可以拿上文《 类 型 转换 之 隐 式 转换 》中一个 现 成的例子:
int a = -3;
unsigned b = 3;
if (a == b)// 隐 式 转换 将 转为 unsigned int
if (a == (int)b)// 显 式指定 转换为 int
这种 用法更多地用于指 针类 型的 转换 。因 为 指 针类 型就是指 针 所指向 对 象的 类 型,而指 针 本身是没有 类 型区 别 的。所以,指向任何 类 型的指 针 可以互相 转换 。最典型的就是 void* 和其它 类 型之 间 的互 换 了,比如: “int* p = (int*)malloc(sizeof(int) * MaxSize);”
还 有一 种 用法就是在 编译 器不允 许进 行 隐 式 转换 的 时 候,比如将 const 对 象 转为 非 const 对 象。如:
const int t = 3;
int* p = (int*)&t;// 本来要写作 const int* p = &t;
这种 用法 还 是少用 为 好,理由很 简单 , 编译 器之所以不允 许进 行 转换 ,就是 为 了保 护 数据,你非要破坏 这种 安全性自然不好。即使能确信 这样 做不 产 生 恶 果, 这样 做至少是没有良好 风 格的。
C++ 中 为显 式 类 型 转换 提供了四 种 不同的操作符: static_case 、 dynamic_cast 、 const_cast 、 reinterpret_cast 。个人 认为 与 C 风 格的相比似乎都没有什 么进步 。
引用: 强 制 类 型 转换关闭 或挂起了正常的 类 型 检查 。 强 烈建 议 程序 员 避免使用 强 制 类 型 转换 ,不依 赖强 制 类 型 转换 也能写好很好的 C++ 程序。
笔 记 :我 觉 得要提 强 制 类 型 转换 ,得从 C 风 格的 说 起。 这 里面可能有我个人的原因。因 为 我个人 习惯 了 C 风 格的 强 制 类 型 转换 。
在 C 语 言中, 强 制 类 型 转换 就是用借助一 对 括号同 时 把 类 型名和表达式列出来,比如 “(int)t” 和 “int(t)” 就是把 t 转为 int 型。
引用:因 为 要覆盖通常的 标 准 转换 ,所以需 显 式使用 强 制 类 型 转换 。 …… 显 式使用 强 制 类 型 转换 的另一个原因是:可能存在多 种转换时 ,需要 选择 一 种 特定的 类 型 转换 。
笔 记 :从外文 图书 翻 译过 来的中国 图书 有个通病,就是 语 言不 伦 不 类 。本 书 算是翻 译 得非常好的了,依然无法 摆 脱 这种 影响。上文的意思无非是 说 :我不希望使用默 认 的 转换规则 的 时 候,就可以 显 式地 规 定按我的要求 转换 。如果要 举 个例子,可以拿上文《 类 型 转换 之 隐 式 转换 》中一个 现 成的例子:
int a = -3;
unsigned b = 3;
if (a == b)// 隐 式 转换 将 转为 unsigned int
if (a == (int)b)// 显 式指定 转换为 int
这种 用法更多地用于指 针类 型的 转换 。因 为 指 针类 型就是指 针 所指向 对 象的 类 型,而指 针 本身是没有 类 型区 别 的。所以,指向任何 类 型的指 针 可以互相 转换 。最典型的就是 void* 和其它 类 型之 间 的互 换 了,比如: “int* p = (int*)malloc(sizeof(int) * MaxSize);”
还 有一 种 用法就是在 编译 器不允 许进 行 隐 式 转换 的 时 候,比如将 const 对 象 转为 非 const 对 象。如:
const int t = 3;
int* p = (int*)&t;// 本来要写作 const int* p = &t;
这种 用法 还 是少用 为 好,理由很 简单 , 编译 器之所以不允 许进 行 转换 ,就是 为 了保 护 数据,你非要破坏 这种 安全性自然不好。即使能确信 这样 做不 产 生 恶 果, 这样 做至少是没有良好 风 格的。
C++ 中 为显 式 类 型 转换 提供了四 种 不同的操作符: static_case 、 dynamic_cast 、 const_cast 、 reinterpret_cast 。个人 认为 与 C 风 格的相比似乎都没有什 么进步 。
引用: 强 制 类 型 转换关闭 或挂起了正常的 类 型 检查 。 强 烈建 议 程序 员 避免使用 强 制 类 型 转换 ,不依 赖强 制 类 型 转换 也能写好很好的 C++ 程序。
标题:
:
类对
象的
隐
式
转换
与算
术类
型相比,
类
的
转换
更
复杂
。因
为
算
术转换
只
涉
及到精度的
问题
,而
类对
象的
转换
却
涉
及到能否
转换
以及怎
样转换
的
问题
。
隐 式 转换 就是 隐 式 转换 ,它会出 现 在你没有注意的地方。参看以下代 码 :
class CMyInt
{
public:
CMyInt();
CMyInt(int i);
~CMyInt();
private:
int m_i;
};
CMyInt::CMyInt()
{
m_i = 0;
cout << " 无参数构造 ( 默 认 0)" << endl;
}
CMyInt::CMyInt(int i)
{
m_i = i;
cout << " 从整数构造 , 值为 " << i << endl;
}
CMyInt::~CMyInt()
{
cout << " 析构 " << m_i << endl;
}
隐 式 转换 就是 隐 式 转换 ,它会出 现 在你没有注意的地方。参看以下代 码 :
class CMyInt
{
public:
CMyInt();
CMyInt(int i);
~CMyInt();
private:
int m_i;
};
CMyInt::CMyInt()
{
m_i = 0;
cout << " 无参数构造 ( 默 认 0)" << endl;
}
CMyInt::CMyInt(int i)
{
m_i = i;
cout << " 从整数构造 , 值为 " << i << endl;
}
CMyInt::~CMyInt()
{
cout << " 析构 " << m_i << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
CMyInt a;
a = 3;
return 0;
}
执 行以上代 码 会 发现 ,程序中有两次构造与两次析构,原因是 “a = 3;” 这 个 赋值 表达式要将右 边 的 类 型 转换为 左 边 的 类 型。 而 这 个 转换 是通 过调 用构造函数来 进 行的。
不 过 ,以上代 码 只是 测试 用的, 读 者 们 千万不要在 实际 工作中写 这样 的代 码 。因 为这样 做是很不科学的。 “a = 3;” 这样 的表达式,只要 CMyInt 类 提供了右 值类 型 为 int 的 赋值 操作符重 载 ,就可以避免使用构造函数来 进 行 转换 。操作符重 载 代 码 如下:
在 类 定 义 的 public 段添加一行: “CMyInt& operator = (const int i);” 然后在 类 定 义 外部添加以下代 码 :
CMyInt& CMyInt::operator = (const int i)
{
m_i = i;
cout << "operator =" << i << endl;
return *this;
}
加了以上代 码 ,同 样 是 “a = 3;” 就不使用 类 型 转换 了,改 为 使用 赋值 操作。我写下以上 这 段程序的目的只是 验证 VC++ 中会有 类对 象的 转换 ,它 们 与算 术类 型的 转换 差不多:生成一个合适的 临时对 象,参与运算以后再把 临时对 象 释 放掉 。
{
CMyInt a;
a = 3;
return 0;
}
执 行以上代 码 会 发现 ,程序中有两次构造与两次析构,原因是 “a = 3;” 这 个 赋值 表达式要将右 边 的 类 型 转换为 左 边 的 类 型。 而 这 个 转换 是通 过调 用构造函数来 进 行的。
不 过 ,以上代 码 只是 测试 用的, 读 者 们 千万不要在 实际 工作中写 这样 的代 码 。因 为这样 做是很不科学的。 “a = 3;” 这样 的表达式,只要 CMyInt 类 提供了右 值类 型 为 int 的 赋值 操作符重 载 ,就可以避免使用构造函数来 进 行 转换 。操作符重 载 代 码 如下:
在 类 定 义 的 public 段添加一行: “CMyInt& operator = (const int i);” 然后在 类 定 义 外部添加以下代 码 :
CMyInt& CMyInt::operator = (const int i)
{
m_i = i;
cout << "operator =" << i << endl;
return *this;
}
加了以上代 码 ,同 样 是 “a = 3;” 就不使用 类 型 转换 了,改 为 使用 赋值 操作。我写下以上 这 段程序的目的只是 验证 VC++ 中会有 类对 象的 转换 ,它 们 与算 术类 型的 转换 差不多:生成一个合适的 临时对 象,参与运算以后再把 临时对 象 释 放掉 。