(从后向前看)
标题:
重
载
函数再
论
重
载
函数是
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++ 中会有 类对 象的 转换 ,它 们 与算 术类 型的 转换 差不多:生成一个合适的 临时对 象,参与运算以后再把 临时对 象 释 放掉 。
标题:
:
类
型
转换
之
隐
式
转换
对
于我
们
来
说
,
3+1.5=4.5
。但是
对
于
计
算机来
说
,
这
两个数的相加可不
这么简单
。因
为
3
与
3.0
是不同的数据
类
型,
3.0
与
1.5
是可以相加的,
3
却不能与
1.5
相加。于是,
C++
在
对
上面的表达式
进
行
处
理
时
,有必要
对
其中一个(或两者)
进
行
转换
。
因 为这 个 转换 是 “ 隐 式 ” 的,也就是 说这 个 转换 不 让 程序 员 知道,那 么 ,系 统 就不能必 须 保 证 不 产 生 损 失, 这 个 损 失指的是精度。 为 了不 损 失精度,数据 总 是向精度高的 类 型 转换 。惟一的例外是当某个 变 量用作条件 时 ,它被 转换为 bool 型。
对 算 术类 型的 转换 是 这样 的:所有比 int 小的都 转为 int 或 unsigned int ,即使没有必要也 这么转 。原因很 简单 ,因 为 int 的 长 度正好等于字 长 。 对 CPU 来 说 ,一次 处 理一个字是最快的。如果 int 或 unsigned int 无法达到要求, 则 往 long 、 double 转 化。
如果 每 一个 转换 都 能不造成 损 失,那自然是好事。可是世 间 的事 总 有不随人愿的 时 候。 对 于同一 种 算 术类 型,其 signed 和 unsigned 所能表达的范 围 是一 样 大,但却是互不重叠的两个范 围 。就像 “ 妇联 ” 和 “ 工会 ” 一 样 ,往哪 边转换 都可能会 产 生 损 失。 这 是无法解决的 问题 ,所以,在 VC 中, 试图 比 较 int 和 unsigned int 变 量 时 会 显 示警告。
奇怪的是,据我 测试 ,在 VC++.net 中,只有 进 行 “<” 和 “>” 比 较 的 时 候才会 显 示警告,而 进 行 “==” 和 “!=” 比 较 的 时 候却不 显 示警告。 实际 上 这 两个比 较 也会有同 样 的 问题产 生,比如以下代 码 :
int a = -3;
unsigned b = 4294967293;
if (a == b) cout << "yes" << endl;
测试 运行以上代 码 会 发现 表达式 a==b 的 值为 true 。 这 是从 int 转为 unsigned int 过 程中的副作用, 这 个副作用我 们应该 知道,但是 VC++.net 不 进 行任何警告似乎也有些与理不通。 —— 难 道我 关闭 了某些警告?
其它 隐 式 转换还 包括数 组 名 转换为 指 针 、算 术值 用作条件 时转换为 bool ,枚 举 被 转换为 整数,非 const 对 象 转换为 const 对 象等。其中枚 举转换为 整数没什 么 要提的,枚 举值 本来就是整数的 别 名,非 const 对 象 转为 const 对 象只是 临时 声明它的保 护级别 ,通常用于作 为 参数 传递时 。
因 为这 个 转换 是 “ 隐 式 ” 的,也就是 说这 个 转换 不 让 程序 员 知道,那 么 ,系 统 就不能必 须 保 证 不 产 生 损 失, 这 个 损 失指的是精度。 为 了不 损 失精度,数据 总 是向精度高的 类 型 转换 。惟一的例外是当某个 变 量用作条件 时 ,它被 转换为 bool 型。
对 算 术类 型的 转换 是 这样 的:所有比 int 小的都 转为 int 或 unsigned int ,即使没有必要也 这么转 。原因很 简单 ,因 为 int 的 长 度正好等于字 长 。 对 CPU 来 说 ,一次 处 理一个字是最快的。如果 int 或 unsigned int 无法达到要求, 则 往 long 、 double 转 化。
如果 每 一个 转换 都 能不造成 损 失,那自然是好事。可是世 间 的事 总 有不随人愿的 时 候。 对 于同一 种 算 术类 型,其 signed 和 unsigned 所能表达的范 围 是一 样 大,但却是互不重叠的两个范 围 。就像 “ 妇联 ” 和 “ 工会 ” 一 样 ,往哪 边转换 都可能会 产 生 损 失。 这 是无法解决的 问题 ,所以,在 VC 中, 试图 比 较 int 和 unsigned int 变 量 时 会 显 示警告。
奇怪的是,据我 测试 ,在 VC++.net 中,只有 进 行 “<” 和 “>” 比 较 的 时 候才会 显 示警告,而 进 行 “==” 和 “!=” 比 较 的 时 候却不 显 示警告。 实际 上 这 两个比 较 也会有同 样 的 问题产 生,比如以下代 码 :
int a = -3;
unsigned b = 4294967293;
if (a == b) cout << "yes" << endl;
测试 运行以上代 码 会 发现 表达式 a==b 的 值为 true 。 这 是从 int 转为 unsigned int 过 程中的副作用, 这 个副作用我 们应该 知道,但是 VC++.net 不 进 行任何警告似乎也有些与理不通。 —— 难 道我 关闭 了某些警告?
其它 隐 式 转换还 包括数 组 名 转换为 指 针 、算 术值 用作条件 时转换为 bool ,枚 举 被 转换为 整数,非 const 对 象 转换为 const 对 象等。其中枚 举转换为 整数没什 么 要提的,枚 举值 本来就是整数的 别 名,非 const 对 象 转为 const 对 象只是 临时 声明它的保 护级别 ,通常用于作 为 参数 传递时 。
标题:
:内存管理之
new
和
delete
林
锐
博士曾将内存管理比
喻为
“
雷区
”
(《高
质
量
C++/C
编
程指南》第
44
页
),内存管理
这块难
不
难
?恐怕不好
说
。
“
会者不
难难
者不会
”
嘛。但是
说
内存管理
这块难
以成
为
“
会者
”
,
应该
是没有
错
的。
程序 时时 刻刻与内存打交道,只不 过 以往我 们 不用考 虑 ,甚至不用知道。所以,所 谓 “ 内存管理 ” ,是特指堆内存。
如果把堆内存和 栈 内存的使用放在一起考 虑 ,可以降低 对 内存管理恐惧。
一、内存的分配:
int i(100);// 栈 上分配内存
int *pi = new int(100);// 堆上分配内存
以上两 种 分配,都使用了 100 作 为 初始 值进 行初始化,如果是 进 行 类对 象的分配,它 们还 可以指定使用哪个构造函数,比如:
CString s(s1);// 栈 上分配内存
CString *ps = new CString(s1)// 堆上分配内存
这 里的 s 可以是 char* 指 针 ,也可以是另一个 CString 对 象,它的 类 型将决定上面 这 两行 语 句 调 用哪一个构造函数。
在 这 里,有一点要特 别说 明,如果要使用默 认 构 造函数, 则 new 语 句后面可以用空括号 对 ,而 栈 内分配的 语 句切不可用空括号 对 。如果写成 “CString s();” , 则 并不是定 义 一个 CString 对 象 s ,而是定 义 一个返回 值为 CString 的函数 s 。
上面两 种 分配,也都可以分配 对 象数 组 ,不同的是,用 new 操作符在堆内存分配数 组时 ,只能 调 用默 认 构造函数。而在 栈 上分配却可以指定 对 象成 员 的初始 值 。如:
int a[3] = {1,2,3};// 栈 上分配内存, int 可以 换 成其它 类 型名,后面的初始 值 可作相 应调 整。
int *p = new int[3];// 不能指定 这 三个 对 象的初始 值
二、内存的 访问 :
栈 内存可以通 过对 象 访问 ,也可以通 过 指 针访问 ,堆内存通 过 指 针访问 。方法完全相同。
三、内存的 释 放:
栈 内存在 对 象作用域 结 束后自 动释 放,堆内存要用 delete 。
delete pi;// 释 放内存
delete []p;// 释 放 对 象数 组
对 于 释 放 对 象数 组 ,那个空的 [] 对 不可以 丢 ,否 则 将只 释 放数 组 的第一个元素。 导 致内存泄露。
有了以上 对 比,堆内存似乎没有了任何 难 度。那 么 内存管理的玄机究竟在哪儿呢?在 进 行内存分配与 释 放的 时 候,有几个注意点要 记 住:
1 、 new 操作有可能失 败 ,当系 统 无法分配需要的内存 块时 ,将返回 NULL 值 ,所以在 new 操作之后,要立即判断 pi 的 值 是否 为 NULL 。
int *pi = new int(100);
if (pi = NULL) {...}
2 、堆上分配的内存必 须 delete ,而且只可以 delete 一次。 为 了保 证 内存只被 delete 一次, 请务 必 记 住 delete 以后立即将指 针设为 NULL :
delete pi;
pi = NULL;
虽 然 “pi=NULL;” 不是必 须 的,但 这 是个好 习惯 。将指 针设为 NULL 既可以防止 继续读 写 该 内存,也可以防止再次 释 放 该 内存。
老的 C 程序 员 可能忘不了 malloc 和 free 函数,它 们 也可以 进 行内存的分配与 释 放。但是 C++ 时 代它 们 已 经 落伍了。它 们 只是按 请 求的字 节 数 进 行分配,而不管你用 这块 内存来干什 么 。 这样 做,就等于放弃了 类对 象的构造与析构。 对 于很多 类 来 说 , 这样 做是很危 险 的 。
程序 时时 刻刻与内存打交道,只不 过 以往我 们 不用考 虑 ,甚至不用知道。所以,所 谓 “ 内存管理 ” ,是特指堆内存。
如果把堆内存和 栈 内存的使用放在一起考 虑 ,可以降低 对 内存管理恐惧。
一、内存的分配:
int i(100);// 栈 上分配内存
int *pi = new int(100);// 堆上分配内存
以上两 种 分配,都使用了 100 作 为 初始 值进 行初始化,如果是 进 行 类对 象的分配,它 们还 可以指定使用哪个构造函数,比如:
CString s(s1);// 栈 上分配内存
CString *ps = new CString(s1)// 堆上分配内存
这 里的 s 可以是 char* 指 针 ,也可以是另一个 CString 对 象,它的 类 型将决定上面 这 两行 语 句 调 用哪一个构造函数。
在 这 里,有一点要特 别说 明,如果要使用默 认 构 造函数, 则 new 语 句后面可以用空括号 对 ,而 栈 内分配的 语 句切不可用空括号 对 。如果写成 “CString s();” , 则 并不是定 义 一个 CString 对 象 s ,而是定 义 一个返回 值为 CString 的函数 s 。
上面两 种 分配,也都可以分配 对 象数 组 ,不同的是,用 new 操作符在堆内存分配数 组时 ,只能 调 用默 认 构造函数。而在 栈 上分配却可以指定 对 象成 员 的初始 值 。如:
int a[3] = {1,2,3};// 栈 上分配内存, int 可以 换 成其它 类 型名,后面的初始 值 可作相 应调 整。
int *p = new int[3];// 不能指定 这 三个 对 象的初始 值
二、内存的 访问 :
栈 内存可以通 过对 象 访问 ,也可以通 过 指 针访问 ,堆内存通 过 指 针访问 。方法完全相同。
三、内存的 释 放:
栈 内存在 对 象作用域 结 束后自 动释 放,堆内存要用 delete 。
delete pi;// 释 放内存
delete []p;// 释 放 对 象数 组
对 于 释 放 对 象数 组 ,那个空的 [] 对 不可以 丢 ,否 则 将只 释 放数 组 的第一个元素。 导 致内存泄露。
有了以上 对 比,堆内存似乎没有了任何 难 度。那 么 内存管理的玄机究竟在哪儿呢?在 进 行内存分配与 释 放的 时 候,有几个注意点要 记 住:
1 、 new 操作有可能失 败 ,当系 统 无法分配需要的内存 块时 ,将返回 NULL 值 ,所以在 new 操作之后,要立即判断 pi 的 值 是否 为 NULL 。
int *pi = new int(100);
if (pi = NULL) {...}
2 、堆上分配的内存必 须 delete ,而且只可以 delete 一次。 为 了保 证 内存只被 delete 一次, 请务 必 记 住 delete 以后立即将指 针设为 NULL :
delete pi;
pi = NULL;
虽 然 “pi=NULL;” 不是必 须 的,但 这 是个好 习惯 。将指 针设为 NULL 既可以防止 继续读 写 该 内存,也可以防止再次 释 放 该 内存。
老的 C 程序 员 可能忘不了 malloc 和 free 函数,它 们 也可以 进 行内存的分配与 释 放。但是 C++ 时 代它 们 已 经 落伍了。它 们 只是按 请 求的字 节 数 进 行分配,而不管你用 这块 内存来干什 么 。 这样 做,就等于放弃了 类对 象的构造与析构。 对 于很多 类 来 说 , 这样 做是很危 险 的 。
标题:
:
优
先
级
、
结
合性和求
值顺
序
说
到
优
先
级
,我能熟
练
背出
“
先乘除,后加减
”
,之于
C++
列出的整整
19
个
优
先
级
,
每
个
优
先
级
又包含若干个操作符,我
总
是看了就
头
皮
发
麻。以我的
记
性,
连军
旗里哪个大哪个小都背不出来,
这
几十个操作符
——
还
是
饶
了我吧。
记 住林 锐 博士的 话 : “ 如果代 码 行中的运算符比 较 多,用括号确定表达式的操作 顺 序,避免使用默 认 的 优 先 级 。 ” (《高 质 量 C++/C 编 程指南》第 26 页 ) 这样 做最直接的作用是不用 记忆 了 复杂 的 优 先 级 了,不用 记忆 并不是因 为懒 ,而是 为 了更清晰。 毕 竟程序不只是 编给计 算机运行的,当我 们处 在一个多人 协 作的 团 体中 时 ,程序的清晰度和精确性比性能要高得多。再 说 ,多加几 对 括号是不影响运行效率的。
结 合性和求 值顺 序是容易混淆的两个概念。 每 一个操作符都 规 定了 结 合性,但是只有极少数操作符 规 定求 值顺 序。 结 合性是 说 如果有多个同 级别 的操作符, 这 些操作数 该 如何分 组 。比如 “1+2+3” 究竟分成 “(1+2)+3” 还 是 “1+(2+3)” , 虽 然 这 两 种 分 组 最 终 没有区 别 ,但不等于所有操作符都不 产 生区 别 。即使不 产 生区 别 , 计 算机 毕 竟是 计 算机,它只能按死的 规 范做事,于其 给 它灵 活机制, 还 不如 规 定了 结 合性 让 它遵守。
C++ 只有四个操作符 规 定了求 值顺 序,它 们 是 “&&” 、 “||” 、 “?:” 和 “,” , 记 住 这 四个操作符并不 难 。反 过 来 记 住其它操作符也不 难 , 难 的是在写程序中是否有 这 个意 识 。那 么 多网友 讨论 “j = i++ + i++ + i++;” 的 结 果,正 说 明了 还 有好多人不了解 “ 未定 义 ” 的威力。如果不小心使用了依 赖 于未定 义 求 值 程序的 语 句,将是一个不容易 发现 并改正的 问题 。 比如 “if (a[index++] < a[index]);”
记 住林 锐 博士的 话 : “ 如果代 码 行中的运算符比 较 多,用括号确定表达式的操作 顺 序,避免使用默 认 的 优 先 级 。 ” (《高 质 量 C++/C 编 程指南》第 26 页 ) 这样 做最直接的作用是不用 记忆 了 复杂 的 优 先 级 了,不用 记忆 并不是因 为懒 ,而是 为 了更清晰。 毕 竟程序不只是 编给计 算机运行的,当我 们处 在一个多人 协 作的 团 体中 时 ,程序的清晰度和精确性比性能要高得多。再 说 ,多加几 对 括号是不影响运行效率的。
结 合性和求 值顺 序是容易混淆的两个概念。 每 一个操作符都 规 定了 结 合性,但是只有极少数操作符 规 定求 值顺 序。 结 合性是 说 如果有多个同 级别 的操作符, 这 些操作数 该 如何分 组 。比如 “1+2+3” 究竟分成 “(1+2)+3” 还 是 “1+(2+3)” , 虽 然 这 两 种 分 组 最 终 没有区 别 ,但不等于所有操作符都不 产 生区 别 。即使不 产 生区 别 , 计 算机 毕 竟是 计 算机,它只能按死的 规 范做事,于其 给 它灵 活机制, 还 不如 规 定了 结 合性 让 它遵守。
C++ 只有四个操作符 规 定了求 值顺 序,它 们 是 “&&” 、 “||” 、 “?:” 和 “,” , 记 住 这 四个操作符并不 难 。反 过 来 记 住其它操作符也不 难 , 难 的是在写程序中是否有 这 个意 识 。那 么 多网友 讨论 “j = i++ + i++ + i++;” 的 结 果,正 说 明了 还 有好多人不了解 “ 未定 义 ” 的威力。如果不小心使用了依 赖 于未定 义 求 值 程序的 语 句,将是一个不容易 发现 并改正的 问题 。 比如 “if (a[index++] < a[index]);”
标题:
:
sizeof
和逗号操作符
把
sizeof
说
成操作符可能有些不合
习惯
,因
为
sizeof
的用法与函数没区
别
。但是
sizeof
与函数有着本
质
的区
别
:它是
编译时
常量。也就是
说
,在程序
编译时
,就会求出它的
值
,并且成
为
程序中的常量。
sizeof 本身比 较简单 ,惟一要提的就是它 对 数 组 名和指 针进 行操作的 结 果。
int a[10];
sizeof(a);
该 操作返回的是数 组 所有元素在内存中的 总长 度。但是如果 对 指 针进 行操作,返回的 则 是指 针 本身的 长 度,与指 针 所指 类 型无 关 。
正因 为 数 组 名与指 针 有着千 丝 万 缕 的 关 系,所以有 时 候 这 个特性会 让 人摸不着 头脑 :
int function(int a[10])
{
sizeof(a);
...
}
以上 sizeof 返回的不是数 组 所有成 员 的大小,而是指 针 的大小,因 为 数 组 在参数 传递 中弱化 为 指 针 。
逗号操作符除了在 for 语 句中 应 用以外,我没 发现 在哪儿 还 有用 处 。因 为 在一般情况下,逗号改成分号肯定是可以的,在 for 语 句中因 为 分号的作用另有定 义 ,所以不能随便改。 这 才有了逗号的用武之地 。
sizeof 本身比 较简单 ,惟一要提的就是它 对 数 组 名和指 针进 行操作的 结 果。
int a[10];
sizeof(a);
该 操作返回的是数 组 所有元素在内存中的 总长 度。但是如果 对 指 针进 行操作,返回的 则 是指 针 本身的 长 度,与指 针 所指 类 型无 关 。
正因 为 数 组 名与指 针 有着千 丝 万 缕 的 关 系,所以有 时 候 这 个特性会 让 人摸不着 头脑 :
int function(int a[10])
{
sizeof(a);
...
}
以上 sizeof 返回的不是数 组 所有成 员 的大小,而是指 针 的大小,因 为 数 组 在参数 传递 中弱化 为 指 针 。
逗号操作符除了在 for 语 句中 应 用以外,我没 发现 在哪儿 还 有用 处 。因 为 在一般情况下,逗号改成分号肯定是可以的,在 for 语 句中因 为 分号的作用另有定 义 ,所以不能随便改。 这 才有了逗号的用武之地 。
标题:
:条件操作符
我
觉
得条件操作符的存在就是
为
了
简
化
if-else
语
句。第一,它与
if-else
语
句的功能完全一致;第二,它
虽
然是一行
语
句,但是它
规
定了求解
顺
序,
这
个
顺
序保
证
了有些表达式不被求
值
。
条件操作符是有一定的危 险 性的,危 险 的原因在于它的 优 先 级 特 别 底, 还 容易漏掉括号。它的 优 先 级仅仅 高于 赋值 和逗号运算符,也就是 说 ,只有在与 赋值 或逗号共存 时 ,才可以免去括号,其它情况下都得加上括号。漏加括号的 BUG 是很 难发现 的。
比如 “cout << (i < j) ? i : j;” 这 句的 实际 作用是将表达式 “(i<j)” 的 值输 出,然后 测试 一下 cout 的状 态 ( << 操作符的返回 值 是 cout ),整个表达式的 值 不管是 i 还 是 j ,都被 丢 弃 。
条件操作符是有一定的危 险 性的,危 险 的原因在于它的 优 先 级 特 别 底, 还 容易漏掉括号。它的 优 先 级仅仅 高于 赋值 和逗号运算符,也就是 说 ,只有在与 赋值 或逗号共存 时 ,才可以免去括号,其它情况下都得加上括号。漏加括号的 BUG 是很 难发现 的。
比如 “cout << (i < j) ? i : j;” 这 句的 实际 作用是将表达式 “(i<j)” 的 值输 出,然后 测试 一下 cout 的状 态 ( << 操作符的返回 值 是 cout ),整个表达式的 值 不管是 i 还 是 j ,都被 丢 弃 。
标题:
:箭
头
操作符(
->
)
箭
头
操作符是
C++
发
明的全新操作符,但却不是
C++
才用到的功能。早期的
C
语
言
虽
然没有
类
,却有
结
构体,也允
许
有指向
结
构体
对
象的指
针
。不同的只是没有
发
明
“->”
这
个符号来
进
行
简
化操作。
说
到底,
“->”
的出
现
只是代替原来就可以
实现
的功能。
引用: C++ 语 言 为 包含点操作符和解引用操作符的表达式提供了一个同 义词 :箭 头 操作符( -> )。
笔 记 : 这 一同 义词 的出 现 ,不 仅仅 使程序 简 化而且更易于理解,更重要的是,它降低了出 错 的可能性。出什 么错 呢? 这 就跟操作符的 优 先 级 有 关 了:
p->a();
(*p).a();
以上两行等价,但是第二行却很容易写成 “*p.a();” ,由于点操作符的 优 先 级 高,就成了 “*(p.a());” , 这 里至少包含了两个 错误 :一是 p 不是 对 象,点操作无效;二是 试图对类 成 员 解引用(只有当 该 成 员 返回指 针 才有效)。
也 许 有人要 说 了,第一个 错误 已 经导 致了 编译 不通 过 , 还 要 说 第二个 错误 干什 么 ? 这样 理解就 错 了。 VC++ 为 程序 员 提供了一个 十分 强 大的 库 ,其中有些 类 的 对 象,既可以 进 行点操作也可以 进 行解引用操作的,如果上例中的 p 是那 种类 的 对 象,而且 p.a() 刚 好又返回指 针 ,那 么 上面 这 句将可以通 过编译 ,最 终换 来 难 以 查 找的 BUG 。
记 住,尽量多用箭 头 操作符 。
引用: C++ 语 言 为 包含点操作符和解引用操作符的表达式提供了一个同 义词 :箭 头 操作符( -> )。
笔 记 : 这 一同 义词 的出 现 ,不 仅仅 使程序 简 化而且更易于理解,更重要的是,它降低了出 错 的可能性。出什 么错 呢? 这 就跟操作符的 优 先 级 有 关 了:
p->a();
(*p).a();
以上两行等价,但是第二行却很容易写成 “*p.a();” ,由于点操作符的 优 先 级 高,就成了 “*(p.a());” , 这 里至少包含了两个 错误 :一是 p 不是 对 象,点操作无效;二是 试图对类 成 员 解引用(只有当 该 成 员 返回指 针 才有效)。
也 许 有人要 说 了,第一个 错误 已 经导 致了 编译 不通 过 , 还 要 说 第二个 错误 干什 么 ? 这样 理解就 错 了。 VC++ 为 程序 员 提供了一个 十分 强 大的 库 ,其中有些 类 的 对 象,既可以 进 行点操作也可以 进 行解引用操作的,如果上例中的 p 是那 种类 的 对 象,而且 p.a() 刚 好又返回指 针 ,那 么 上面 这 句将可以通 过编译 ,最 终换 来 难 以 查 找的 BUG 。
记 住,尽量多用箭 头 操作符 。
标题:
:
++
的陷阱
自增和自减符作符是如此常用,以至于没有必要提了。但是任何一本
书
都会下重手来提它,原因是它
虽
然
简单
,却含有玄机。
讲 到前自增和后自增 时 ,几乎所有的 书 都是 这样讲 的:用 “j = i++” 和 “j = ++i” 对 比,告 诉读 者 虽 然 i 都增了 1 ,但是 j 却不一 样 。
这 也没 办 法,因 为绝 大多数 书 在 讲 到 ++ 时还 没有提到 “ 表达式的 值 ” 这 个概念,有些 书 本可能从 头 到尾都不提。 对 于 “j = i++;” 来 说 ,它是一个表达式没 错 ,但是等号右 侧 也是一个表达式, 对 于表达式 “i++” 来 说 ,它是有 值 的, 该 表达式的 值赋 予了 j ,而整个表达式也有一个 值 ,只是 这 个 值 被 丢 弃了。 类 推: “i = j = k = 1;” 语 句中 单 独的 “1” 就是一个表达式,叫常量表达式,它的 值 就是 1 ,它 给 了 k , “k=1” 这 个表达式也有 值 ,它的 值 就是 k 的 值 ,它 给 了 j , …… 最后 给 i 赋值 的表达式也有 值 ,它的 值 就是 i 的 值 , 这 个 值 被 丢 弃。
弄明白了 “ 表达式的 值 ” 后,就可以科学地 讲 解 “++i” 和 “i++” 了,因 为这 两个表达式的 值 是 这样 定 义 的:前置自增 / 减表达式的 值 是修改后的 变 量 值 ,后置 则为 修改前的 值 。
那 么 ,在不 关 心表达式的 值 的 时 候 —— 即只是 给 某 变 量自增或自减一下 —— 究竟用哪个好呢?当然是前置好,因 为 前置只要用一个指令 进 行一下自增自减运算,然后直接返回即可。而后置却要先保存好 这 个初始 值 ,再自增减,然后再返回以前保存的 值 。写 过 操作符重 载 的程序 员应该 更能体会 这 里面的区 别 。
“++i” 或 “i++” 是 这样简洁 ,所以它 们 比 “i = i + 1;” 要美得多,所以 书 上 说 “ 简洁 就是美 ” 。但是,在美的同 时 ,一个美 丽 的陷阱正在招手,程序 员们 必 须 理解并小心。
“if (ia[index++] < ia[index])” 这样 的表达式是危 险 的。前文中已 经 提到了, C++ 中只有极少的几个操作符是 规 定了 求 值顺 序的, “<” 号没有 规 定,那 么 , 这 两个数要比 较 大小,系 统 究竟先求前者 还 是后者?如果先求前者,那 么 求后者的 时 候 index 是 值 有没有增一?
经 常在 论坛 上看到有人 讨论 “j = i++ + i++ + i++;” 的 结 果, 还 有人把自己的 实 践 结 果 贴 出来 证实 自己的理 论 。 这 正是初学者令人悲哀与同情的地方: 对 于 应该 好好掌握的知 识 没有足 够 的 热 情,却把一腔 热 血放在 这 些无 须讨论 的 话题 上, C++ 初学者要走的路不 仅长 ,而且充 满 了 荆 棘 。
讲 到前自增和后自增 时 ,几乎所有的 书 都是 这样讲 的:用 “j = i++” 和 “j = ++i” 对 比,告 诉读 者 虽 然 i 都增了 1 ,但是 j 却不一 样 。
这 也没 办 法,因 为绝 大多数 书 在 讲 到 ++ 时还 没有提到 “ 表达式的 值 ” 这 个概念,有些 书 本可能从 头 到尾都不提。 对 于 “j = i++;” 来 说 ,它是一个表达式没 错 ,但是等号右 侧 也是一个表达式, 对 于表达式 “i++” 来 说 ,它是有 值 的, 该 表达式的 值赋 予了 j ,而整个表达式也有一个 值 ,只是 这 个 值 被 丢 弃了。 类 推: “i = j = k = 1;” 语 句中 单 独的 “1” 就是一个表达式,叫常量表达式,它的 值 就是 1 ,它 给 了 k , “k=1” 这 个表达式也有 值 ,它的 值 就是 k 的 值 ,它 给 了 j , …… 最后 给 i 赋值 的表达式也有 值 ,它的 值 就是 i 的 值 , 这 个 值 被 丢 弃。
弄明白了 “ 表达式的 值 ” 后,就可以科学地 讲 解 “++i” 和 “i++” 了,因 为这 两个表达式的 值 是 这样 定 义 的:前置自增 / 减表达式的 值 是修改后的 变 量 值 ,后置 则为 修改前的 值 。
那 么 ,在不 关 心表达式的 值 的 时 候 —— 即只是 给 某 变 量自增或自减一下 —— 究竟用哪个好呢?当然是前置好,因 为 前置只要用一个指令 进 行一下自增自减运算,然后直接返回即可。而后置却要先保存好 这 个初始 值 ,再自增减,然后再返回以前保存的 值 。写 过 操作符重 载 的程序 员应该 更能体会 这 里面的区 别 。
“++i” 或 “i++” 是 这样简洁 ,所以它 们 比 “i = i + 1;” 要美得多,所以 书 上 说 “ 简洁 就是美 ” 。但是,在美的同 时 ,一个美 丽 的陷阱正在招手,程序 员们 必 须 理解并小心。
“if (ia[index++] < ia[index])” 这样 的表达式是危 险 的。前文中已 经 提到了, C++ 中只有极少的几个操作符是 规 定了 求 值顺 序的, “<” 号没有 规 定,那 么 , 这 两个数要比 较 大小,系 统 究竟先求前者 还 是后者?如果先求前者,那 么 求后者的 时 候 index 是 值 有没有增一?
经 常在 论坛 上看到有人 讨论 “j = i++ + i++ + i++;” 的 结 果, 还 有人把自己的 实 践 结 果 贴 出来 证实 自己的理 论 。 这 正是初学者令人悲哀与同情的地方: 对 于 应该 好好掌握的知 识 没有足 够 的 热 情,却把一腔 热 血放在 这 些无 须讨论 的 话题 上, C++ 初学者要走的路不 仅长 ,而且充 满 了 荆 棘 。
标题:
:回
忆
十几年前的
编
程入
门
本来不想
为这
段写
读书
笔
记
,不
过
突然想起十几年前的一件趣事来,
还
是
记
下来吧。
1993 年的 时 候,学校 开设 了 “ 劳 技 ” 课 , 讲 的是 BASIC 语 言。 对 于当 时连电脑 都没看 过 一眼的我 们 来 说 ,学校 开设这样 的 课 ,真是 让 我 们 无比感 动 。我至今仍然感 谢 我的母校,在片面追求升学率、大量 缩 减副 课 的全局下,我的母校居然 开设 了音 乐 、美 术 、 劳 技等一系列副之又副的 课 。 这让 我至今 难 忘。而令一方面,我今天能 够 在程序界打拼,完全是从那 时 候 开 始陪 养 的 兴 趣。如果我的高中没有 开设这门课 ,我未必就不 进 入程序界,但是入 门 至少要 晚 三年。
这 三年, 我学的 编 程 东 西少之又少, 对 于整个 BASIC 来 说 , 简 直 连 皮毛都不如,而且学校只提供了一次上机机会, 练 的是 开 机与 关 机。我所 谓 的 “ 调试 程序 ” 是在自己的小霸王学 习 机上 进 行的。但是,在 电脑 没有普及的年份,懂得一点点就是很先 进 的了。 进 入大学后,堂堂一个大学的班 级 ,居然只有两个人碰 过电脑 ,大家的基 础 可想而知。在同学 们 拼命学 习 DOS 命令的 时 候,我已 经 遥遥地走在了前面。之后学 习 True Basic 自然一日千里,之后自学 VB 、自学 C 也就 顺 理成章。如果我的高中没有 开设这门课 ,我未必就不 进 入程序界,但是我在 进 大学的 头 一段 时间 肯定会 与其他同学一起拼命 记 DOS 命令。要知道,其他同学至所以学得比我慢,并不是比我笨,而是没有 习惯电脑 的思 维 模式。
该说 “ 赋值 操作符 ” 了。高中 开设 的 BASIC 语 言 课 ,其 课 本是不到半厘米厚的小 书 。但是我 爱 不 释 手地提前 阅读 了。 读 得一知半解就去做后面的 习题 , 发现 一句 “P=P+1” ,心想: 这 怎 么 可能嘛? P 怎 么 可能等于 P 加一呢?移 项 一减,不就相当于 0=1 了 吗 ?一定是 书 上印 错 了。于是,我将其中一个 P 改成了 R ,而且是用黑 钢 笔描的。我描得是如此 细 致,以至于根本看不出那个 R 是 P 改的。等到老 师讲 到 这 一 节 的 时 候,我 虽 然已 经 懂了 这 里 的 “=” 与数学上的 “=” 不一 样 ,但是由于把 P 看成了 R , 这题还 是做 错 了。
当 时 的情况是 这样 的:老 师 喊了几个同学上去做 题 ,没有一个会。老 师说 “ 有没有 谁 会做的?主 动 上来? ” 好几个同学立即喊我的名字,把我逼上去了。 结 果我 这么 一做就做 错 了。老 师 表 扬 了我的勇气,但是同学却 说 我 虽 然平 时 捧着 这 本 书 不放,原来也是个 “ 菜 鸟 ” 。
赋值 操作符就是在那个 时 候 给 我留下深刻印像的,那天我知道了, “=” 号不表示左右相等。
复 合 赋值 操作符也比 较简单 ,理解了它的用法就好了。 C++ 至所以有了 赋值 操作符以后 还 要 复 合 赋值 操作符,不 仅仅 是 为 了 简 化代 码 , 还 可以加快 处 理速度。 “i=i+j” 和 “i+=j” 相比,前者的 i 求 值 了两次。不 过 , 这 点性能差 别对 整个程序性能来 说 不大。 还 有一个区 别 就是 优 先 级 方面的了。 “i*=j+1” 如果不写成 复 合 赋值 ,就要加上括号: i=i*(j+1) 。
1993 年的 时 候,学校 开设 了 “ 劳 技 ” 课 , 讲 的是 BASIC 语 言。 对 于当 时连电脑 都没看 过 一眼的我 们 来 说 ,学校 开设这样 的 课 ,真是 让 我 们 无比感 动 。我至今仍然感 谢 我的母校,在片面追求升学率、大量 缩 减副 课 的全局下,我的母校居然 开设 了音 乐 、美 术 、 劳 技等一系列副之又副的 课 。 这让 我至今 难 忘。而令一方面,我今天能 够 在程序界打拼,完全是从那 时 候 开 始陪 养 的 兴 趣。如果我的高中没有 开设这门课 ,我未必就不 进 入程序界,但是入 门 至少要 晚 三年。
这 三年, 我学的 编 程 东 西少之又少, 对 于整个 BASIC 来 说 , 简 直 连 皮毛都不如,而且学校只提供了一次上机机会, 练 的是 开 机与 关 机。我所 谓 的 “ 调试 程序 ” 是在自己的小霸王学 习 机上 进 行的。但是,在 电脑 没有普及的年份,懂得一点点就是很先 进 的了。 进 入大学后,堂堂一个大学的班 级 ,居然只有两个人碰 过电脑 ,大家的基 础 可想而知。在同学 们 拼命学 习 DOS 命令的 时 候,我已 经 遥遥地走在了前面。之后学 习 True Basic 自然一日千里,之后自学 VB 、自学 C 也就 顺 理成章。如果我的高中没有 开设这门课 ,我未必就不 进 入程序界,但是我在 进 大学的 头 一段 时间 肯定会 与其他同学一起拼命 记 DOS 命令。要知道,其他同学至所以学得比我慢,并不是比我笨,而是没有 习惯电脑 的思 维 模式。
该说 “ 赋值 操作符 ” 了。高中 开设 的 BASIC 语 言 课 ,其 课 本是不到半厘米厚的小 书 。但是我 爱 不 释 手地提前 阅读 了。 读 得一知半解就去做后面的 习题 , 发现 一句 “P=P+1” ,心想: 这 怎 么 可能嘛? P 怎 么 可能等于 P 加一呢?移 项 一减,不就相当于 0=1 了 吗 ?一定是 书 上印 错 了。于是,我将其中一个 P 改成了 R ,而且是用黑 钢 笔描的。我描得是如此 细 致,以至于根本看不出那个 R 是 P 改的。等到老 师讲 到 这 一 节 的 时 候,我 虽 然已 经 懂了 这 里 的 “=” 与数学上的 “=” 不一 样 ,但是由于把 P 看成了 R , 这题还 是做 错 了。
当 时 的情况是 这样 的:老 师 喊了几个同学上去做 题 ,没有一个会。老 师说 “ 有没有 谁 会做的?主 动 上来? ” 好几个同学立即喊我的名字,把我逼上去了。 结 果我 这么 一做就做 错 了。老 师 表 扬 了我的勇气,但是同学却 说 我 虽 然平 时 捧着 这 本 书 不放,原来也是个 “ 菜 鸟 ” 。
赋值 操作符就是在那个 时 候 给 我留下深刻印像的,那天我知道了, “=” 号不表示左右相等。
复 合 赋值 操作符也比 较简单 ,理解了它的用法就好了。 C++ 至所以有了 赋值 操作符以后 还 要 复 合 赋值 操作符,不 仅仅 是 为 了 简 化代 码 , 还 可以加快 处 理速度。 “i=i+j” 和 “i+=j” 相比,前者的 i 求 值 了两次。不 过 , 这 点性能差 别对 整个程序性能来 说 不大。 还 有一个区 别 就是 优 先 级 方面的了。 “i*=j+1” 如果不写成 复 合 赋值 ,就要加上括号: i=i*(j+1) 。
标题:
:
关
系、
逻辑
和位操作符
关
系操作符本身没什
么
好提的,它
们
与我
们
平
时
身
边
的
逻辑
一
样
,所以不
难
理解。有两点可以略提一下:
一、因 为 ASC 字符中没有 “≥” 这 些符号,所以只好用 “>=” 代替。于是 产 生了 BASIC 和 C++ 的两 种 不同符号集: BASIC 用 “<>” 表示不等于, C++ 则 用 “!=” 。
二、程序 设计时 不能用 “if (i < j < k)” 这样 的写法。原因很 简单 ,因 为这种 写法另有含 义 ,或者 说 正因 为 不能 这样 写,才 给这种 写法另外 赋 了一 种 含 义 。
BASIC 和 C++ 的 逻辑 操作符也有完全不同的写法, BASIC 用比 较 直 观 的 关键 字 “And” 、 “Or” 之 类 , C++ 则 用 “&&” 和 “||” 。 这 也没什 么 , 记 住就行了。
逻辑 操作符中的 “&&” 和 “||” 是 C++ 标 准中 为 数不多的指定了求 值顺 序的操作符(除此以外 还 有条件操作符 “?:” 和逗号操作符 “,” , 关 于求 值顺 序,后面 还 将提及)。 这样 的 规 定惟一的缺点是需要 额 外的 记忆 , 优 点 则 是很明 显 的:它可以 让 “ 危 险 ” 的操作 变 得不危 险 。如 “while (i<MaxSize && Array[i]>0)” , 对 于 “Array[i]” 来 说 ,指 针 越界是很可怕的,但是 “&&” 操作符的求 值顺 序保 证 了指 针 不越界。从另一方面 说 ,要保 证 指 针 不越界,就必 须记 住 该 操作符的求 值顺 序(不然就只能分成两个 语 句写 喽 )。
又要提到 bool 值 的比 较 了, “if (i < j < k)” 的 实际 就是 进 行了 bool 值 的比 较 。本 书 在 讲 解的 时 候, 虽 然提到了 “ 将 k 与整数 0 或 1 做比 较 ” ,但是我宁可提醒大家不去 记 住 这 个。 记 住 bool 值 只有 true 和 false 两个 值 ,比 记 住 1 和 0 要好得多,因 为虽 然 false 就是 0 ,但是 true 却不 仅 是 1 。
C++ 中如果只有 逻辑 操作符也就算了,它偏 偏 还 有 “&” 和 “|” 这样 的位操作符。而位操作正是 BASIC 不提供的功能。 这 就 给 初学 C/C++ 的人 带 来了 难 度。 难 点不在于理解,而在于 记忆 。位操作符的作用是 进 行某一 Bit 位的 设 定,在一个字 节掰 成八份用的年代,它非常常用, — 比如 Turbo C 中的屏幕 设 置,就是 3 位表示背景色、 4 位表示前景色、一位表示 闪烁 ,用一个字 节 完全存放了屏幕字体的信息。 现 在的海量存 储 与高速 处 理中,大可不必 这么节约 了(从 处 理速上看,非但没有 节约 ,反而浪 费 了),所以,不会位操作也没什 么 大不了的。
不 过 ,不熟悉位操作的用 户 却都知道 “<<” 和 “>>” 的另一用 处 。 这 事不能怪程序 员 ,几乎所有的 C++ 书 本都会从 “cin >> i;” 和 “cout << i;” 入手。不用知道 这 两个操作符原来是干什 么 的,甚至不用知道 “ 重 载 ” 是怎 么 回事。 C++ 来到 这 个世界, 发 展了 C 语 言,使得 “ 知其然不知道其所以然 ” 的程序 员 也能好好工作。 也 许这 是它的一个 进步 吧 。
一、因 为 ASC 字符中没有 “≥” 这 些符号,所以只好用 “>=” 代替。于是 产 生了 BASIC 和 C++ 的两 种 不同符号集: BASIC 用 “<>” 表示不等于, C++ 则 用 “!=” 。
二、程序 设计时 不能用 “if (i < j < k)” 这样 的写法。原因很 简单 ,因 为这种 写法另有含 义 ,或者 说 正因 为 不能 这样 写,才 给这种 写法另外 赋 了一 种 含 义 。
BASIC 和 C++ 的 逻辑 操作符也有完全不同的写法, BASIC 用比 较 直 观 的 关键 字 “And” 、 “Or” 之 类 , C++ 则 用 “&&” 和 “||” 。 这 也没什 么 , 记 住就行了。
逻辑 操作符中的 “&&” 和 “||” 是 C++ 标 准中 为 数不多的指定了求 值顺 序的操作符(除此以外 还 有条件操作符 “?:” 和逗号操作符 “,” , 关 于求 值顺 序,后面 还 将提及)。 这样 的 规 定惟一的缺点是需要 额 外的 记忆 , 优 点 则 是很明 显 的:它可以 让 “ 危 险 ” 的操作 变 得不危 险 。如 “while (i<MaxSize && Array[i]>0)” , 对 于 “Array[i]” 来 说 ,指 针 越界是很可怕的,但是 “&&” 操作符的求 值顺 序保 证 了指 针 不越界。从另一方面 说 ,要保 证 指 针 不越界,就必 须记 住 该 操作符的求 值顺 序(不然就只能分成两个 语 句写 喽 )。
又要提到 bool 值 的比 较 了, “if (i < j < k)” 的 实际 就是 进 行了 bool 值 的比 较 。本 书 在 讲 解的 时 候, 虽 然提到了 “ 将 k 与整数 0 或 1 做比 较 ” ,但是我宁可提醒大家不去 记 住 这 个。 记 住 bool 值 只有 true 和 false 两个 值 ,比 记 住 1 和 0 要好得多,因 为虽 然 false 就是 0 ,但是 true 却不 仅 是 1 。
C++ 中如果只有 逻辑 操作符也就算了,它偏 偏 还 有 “&” 和 “|” 这样 的位操作符。而位操作正是 BASIC 不提供的功能。 这 就 给 初学 C/C++ 的人 带 来了 难 度。 难 点不在于理解,而在于 记忆 。位操作符的作用是 进 行某一 Bit 位的 设 定,在一个字 节掰 成八份用的年代,它非常常用, — 比如 Turbo C 中的屏幕 设 置,就是 3 位表示背景色、 4 位表示前景色、一位表示 闪烁 ,用一个字 节 完全存放了屏幕字体的信息。 现 在的海量存 储 与高速 处 理中,大可不必 这么节约 了(从 处 理速上看,非但没有 节约 ,反而浪 费 了),所以,不会位操作也没什 么 大不了的。
不 过 ,不熟悉位操作的用 户 却都知道 “<<” 和 “>>” 的另一用 处 。 这 事不能怪程序 员 ,几乎所有的 C++ 书 本都会从 “cin >> i;” 和 “cout << i;” 入手。不用知道 这 两个操作符原来是干什 么 的,甚至不用知道 “ 重 载 ” 是怎 么 回事。 C++ 来到 这 个世界, 发 展了 C 语 言,使得 “ 知其然不知道其所以然 ” 的程序 员 也能好好工作。 也 许这 是它的一个 进步 吧 。
标题:
:可
爱
的算
术
操作符
算
术
操作符是最容易理解的符号了,因
这
它与平
时
做的数学是完全相同的
规则
。就
连
小学的知
识
“
先乘除后加减
”
都完全适用。
不 过 ,就像因 为 与生活的 逻辑 完全一 样导 致易于理解一般,与生活 逻辑 不一致的 问题 就比 较难 以理解了。比如 “ 有 9 个苹果, 3 个小朋友分,平均 每 个小朋友可以分到几个苹果? ” , 现实 中既不可能是 -9 个苹果,也不可能 给 -3 个小朋友分。而是 C/C++ 中的除法和求余却不得不面 对这种 情况。它 们 非但 难 于理解,而且 还 有不确定因素。
引用:如果两个操作数都 为 正,除法和求模操作的 结 果也是正数(或零);如果两个操作数 都是 负 数,除法操作的 结 果 为 正数(或零),而求模操作的 结 果 则为负 数(或零);如果只有一个操作数是 负 数, 这 两 种 操作的 结 果取决于机器;求模 结 果的符号也取决于机器,而除法操作的 值则 是 负 数(或零)。
笔 记 :以上 这 一大堆似乎有些 费 口舌。 这么说 吧,如果两个数同号, 则 一切都那 么简单 。即使是两个 负 数相除,只要把它 们 当成两个正数就可以了 —— 商肯定是正的,余数只要 对应调 整一下符号即可;如果是一正一 负 两个数相除,那 么 商肯定是 负 的,但是究竟是 负 多少可不一定,余数就更 难说 了, 连 是正是 负 都不能确定。
21 % 6 = 3;
21 % 7 = 0;
-21 % -8 = -5;
21 % -5 = ?;// 与机器相 关 ,可能是 1 可能是 -4
21 / 6 = 3;
21 / 3 = 3;
-21 / -28 = 2;
21 / -5 = ?;// 与机器相 关 ,可能是 -4 或 -5
引用:当只有一个操作数 为负 数 时 ,求模操作 结 果 值 的符号可依据分子(被除数)或分母(除数)的符号而定。如果求模的 结 果随分子的符号, 则 除出来的 值 向零一 侧 取整;如果求模与分母的符号匹配, 则 除出来的 值 向 负 无 穷 一 侧 取整。
10 个苹果分 给 -3 个朋友,平均 每 个小朋友可以分到几个 ? 还 剩下几个 ?
不 过 ,就像因 为 与生活的 逻辑 完全一 样导 致易于理解一般,与生活 逻辑 不一致的 问题 就比 较难 以理解了。比如 “ 有 9 个苹果, 3 个小朋友分,平均 每 个小朋友可以分到几个苹果? ” , 现实 中既不可能是 -9 个苹果,也不可能 给 -3 个小朋友分。而是 C/C++ 中的除法和求余却不得不面 对这种 情况。它 们 非但 难 于理解,而且 还 有不确定因素。
引用:如果两个操作数都 为 正,除法和求模操作的 结 果也是正数(或零);如果两个操作数 都是 负 数,除法操作的 结 果 为 正数(或零),而求模操作的 结 果 则为负 数(或零);如果只有一个操作数是 负 数, 这 两 种 操作的 结 果取决于机器;求模 结 果的符号也取决于机器,而除法操作的 值则 是 负 数(或零)。
笔 记 :以上 这 一大堆似乎有些 费 口舌。 这么说 吧,如果两个数同号, 则 一切都那 么简单 。即使是两个 负 数相除,只要把它 们 当成两个正数就可以了 —— 商肯定是正的,余数只要 对应调 整一下符号即可;如果是一正一 负 两个数相除,那 么 商肯定是 负 的,但是究竟是 负 多少可不一定,余数就更 难说 了, 连 是正是 负 都不能确定。
21 % 6 = 3;
21 % 7 = 0;
-21 % -8 = -5;
21 % -5 = ?;// 与机器相 关 ,可能是 1 可能是 -4
21 / 6 = 3;
21 / 3 = 3;
-21 / -28 = 2;
21 / -5 = ?;// 与机器相 关 ,可能是 -4 或 -5
引用:当只有一个操作数 为负 数 时 ,求模操作 结 果 值 的符号可依据分子(被除数)或分母(除数)的符号而定。如果求模的 结 果随分子的符号, 则 除出来的 值 向零一 侧 取整;如果求模与分母的符号匹配, 则 除出来的 值 向 负 无 穷 一 侧 取整。
10 个苹果分 给 -3 个朋友,平均 每 个小朋友可以分到几个 ? 还 剩下几个 ?
标题:
:操作符
第五章
开
始了,看得出来,从
这
章才真正
开
始
讲
解
C++
的基本内容。没有
这
里的内容,前四章都是狗屎。
操作符就是我 们 平 时 理解的 “ 运算符 ” 了,不 过 因 为 C++ 是 计 算机 语 言,它与我 们 平 时 生活有着不一 样 的 逻辑 ,所以,在我 们 平 时 看来 简单 的 “3+4” ,到了 C++ 里,就得分成一个操作符和两个操作数了。
操作符的含 义 以及它能得到的 结 果,不 仅仅 取决于操作符本身, 还 同 时 取决于操作数。当初学 C 语 言的 时 候, 发现 做除法要用 “10/3.0” 而不用 “10/3” 着 实 惊 讶 了一回。特 别 是从 BASIC 走 过 来的人, BASIC 里没有 这么强 的 类 型,所以 10/3 就是浮点数,要整除 还 得用 “Int()” 函数或改用 “/” 运算符(不是 每 个 VB 程序 员 都知道 这 个运算符的哦。)
从 C/C++ 中弄明白了 “10/3.0” 和 “10/3” ,反 过 来再去理解 C 与 BASIC 的区 别 ,不 难发现 C/C++ 这样 做的确比 BASIC 高明得多。而 进 一 步 了解了硬件的运算机制后, 则 可以理解 这样 做不 仅仅 是高明,而且是必 须 。
引用:有些符号既可以表示一元操作也可以表示二元操作。例如 *…… , 这种 两用法相互独立 、各不相 关 ,如果将其 视为 两个不同的符号可能会更容易理解些。 …… 需要根据 该 符号所 处 的上下文来确定它代表一元操作 还 是二元操作。
笔 记 : 这 是一个大家都明白,但是大家都不会去想的 问题 。 细 想起来,我 们 不去想,正是因 为 我 们 早已熟知。然而我 们读 程序可以上下 关联 , 计 算机要做到 这 点就不容易 —— 比如拼音 输 入法的自 动选词 。由此看来, C++ 编译 器是十分 优 秀的人工智能 软 件 。
操作符就是我 们 平 时 理解的 “ 运算符 ” 了,不 过 因 为 C++ 是 计 算机 语 言,它与我 们 平 时 生活有着不一 样 的 逻辑 ,所以,在我 们 平 时 看来 简单 的 “3+4” ,到了 C++ 里,就得分成一个操作符和两个操作数了。
操作符的含 义 以及它能得到的 结 果,不 仅仅 取决于操作符本身, 还 同 时 取决于操作数。当初学 C 语 言的 时 候, 发现 做除法要用 “10/3.0” 而不用 “10/3” 着 实 惊 讶 了一回。特 别 是从 BASIC 走 过 来的人, BASIC 里没有 这么强 的 类 型,所以 10/3 就是浮点数,要整除 还 得用 “Int()” 函数或改用 “/” 运算符(不是 每 个 VB 程序 员 都知道 这 个运算符的哦。)
从 C/C++ 中弄明白了 “10/3.0” 和 “10/3” ,反 过 来再去理解 C 与 BASIC 的区 别 ,不 难发现 C/C++ 这样 做的确比 BASIC 高明得多。而 进 一 步 了解了硬件的运算机制后, 则 可以理解 这样 做不 仅仅 是高明,而且是必 须 。
引用:有些符号既可以表示一元操作也可以表示二元操作。例如 *…… , 这种 两用法相互独立 、各不相 关 ,如果将其 视为 两个不同的符号可能会更容易理解些。 …… 需要根据 该 符号所 处 的上下文来确定它代表一元操作 还 是二元操作。
笔 记 : 这 是一个大家都明白,但是大家都不会去想的 问题 。 细 想起来,我 们 不去想,正是因 为 我 们 早已熟知。然而我 们读 程序可以上下 关联 , 计 算机要做到 这 点就不容易 —— 比如拼音 输 入法的自 动选词 。由此看来, C++ 编译 器是十分 优 秀的人工智能 软 件 。
标题:
:多
维
数
组
引用:
严
格地
说
,
C++
中没有多
维
数
组
。
笔 记 :不只是 C++ 啦, C 中就是 这样 。不 过 ,正因 为 C++ 中没有多 维 数 组 ,而提供了 “ 数 组 的数 组 ” ,所以 C/C++ 在数 组 使用上更灵活。
多 维 数 组 的定 义 和使用没什 么 要多提的,用 过 就懂了。无非是多一 对 括号而已。不 过 ,如果把它跟指 针 一起用,倒是要注意的:二 维 数 组 名 对应 的是 “ 指向指 针 的指 针 ” ,所以,如果要在函数 间传递 多 维 数 组 ,指 针类 型一定要正确:
int a[3][4];
int *p1 = &a[0][0];//a[][] 是一个 int , 对 其取地址就是 int*
int *p2 = a[0];//a[0] 虽 然是 a 有一个元素,但它也是另一个数 组 的数 组 名
int **p3 = a;//a 是一个二 维 数 组 的数 组 名
int **p4 = &a[0];//a[0] 是一个数 组 名,它是 a 数 组 的一个成 员
另外,有一个比 较难记 、容易混淆的用法:
int (*p5)[4] = a;
说 它容易混淆,是因 为 它与 “int *p5[4];” 有着截然不同的意 义 。前者是指定 义 一个指向数 组 的指 针 ,后者 则 是定 义 一个指 针 数 组 。 —— 头 昏 ing...
本人在 实际 使用中, 经 常避 开 多 维 数 组 ,而用其它途径来使用一大堆数 值 。比如可以 这样 用:
int a, b;// 两 维 的元素个数
int *p = new int[a*b];
for (int i=0; i<a; ++i)
for (int j=0; j<b; ++j)
p[i*a+j].....;
delete []p;
用 这种 方法,就是三 维 、四 维 也不用考 虑 “ 指向指 针 的指 针 ” 这么复杂 的 东 西。 不是我不会,而是不高 兴 去想 。
笔 记 :不只是 C++ 啦, C 中就是 这样 。不 过 ,正因 为 C++ 中没有多 维 数 组 ,而提供了 “ 数 组 的数 组 ” ,所以 C/C++ 在数 组 使用上更灵活。
多 维 数 组 的定 义 和使用没什 么 要多提的,用 过 就懂了。无非是多一 对 括号而已。不 过 ,如果把它跟指 针 一起用,倒是要注意的:二 维 数 组 名 对应 的是 “ 指向指 针 的指 针 ” ,所以,如果要在函数 间传递 多 维 数 组 ,指 针类 型一定要正确:
int a[3][4];
int *p1 = &a[0][0];//a[][] 是一个 int , 对 其取地址就是 int*
int *p2 = a[0];//a[0] 虽 然是 a 有一个元素,但它也是另一个数 组 的数 组 名
int **p3 = a;//a 是一个二 维 数 组 的数 组 名
int **p4 = &a[0];//a[0] 是一个数 组 名,它是 a 数 组 的一个成 员
另外,有一个比 较难记 、容易混淆的用法:
int (*p5)[4] = a;
说 它容易混淆,是因 为 它与 “int *p5[4];” 有着截然不同的意 义 。前者是指定 义 一个指向数 组 的指 针 ,后者 则 是定 义 一个指 针 数 组 。 —— 头 昏 ing...
本人在 实际 使用中, 经 常避 开 多 维 数 组 ,而用其它途径来使用一大堆数 值 。比如可以 这样 用:
int a, b;// 两 维 的元素个数
int *p = new int[a*b];
for (int i=0; i<a; ++i)
for (int j=0; j<b; ++j)
p[i*a+j].....;
delete []p;
用 这种 方法,就是三 维 、四 维 也不用考 虑 “ 指向指 针 的指 针 ” 这么复杂 的 东 西。 不是我不会,而是不高 兴 去想 。
标题:
:
动态
数
组
我
晕
,本
书
才
讲
了个
开头
,居然
讲
到
new
和
delete
了。我
“
偷窥
”
了一下:下一章
开
始才
讲
到操作符,而且下章将有
专门
的一
节讲
new
和
delete
。看来
这
里提到它
们
的目的只是
为
了
说
明
string
这样
的
类为
什
么
可以自
动
适
应
大小。
new 的返回 值 是一个指 针 ,不 过 本 书暂时 没有提到 new 也会返回 NULL 的。是的, 暂时还 不用提内容不 够这么复杂 的情况。
引用:在自由存 储 区中 创 建的数 组对 象数 组 是没有名字的,程序 员 只能通 过 其地址 间 接地 访问 堆中的 对 象。
笔 记 : 这 里有两个 问题 ,一是 “ 数 组 的名字 ” 其 实 也是个指 针 ,指 针 当然也可以看成 “ 数 组 的名字 ” , 这 本来就可以互 换 的,正如我前面 说 的一 样 : “5[a]” 完全等价于 “*(5+a)” 。至于 a 是静 态 的数 组 名 还 是 动态 的指 针 ,没有区 别 。第二个 问题 是 这 里提到了 “ 堆 ” ,在没有 讲 解内存之前, 这样说毕 竟理解上有 难 度。 还 是那句 话 , 这 本 书 是 给 有一定基 础 的人看的。
令我耳目一新的是: new 还 能 创 建 const 数 组 。 —— 不是我不懂,而是 实 在没想到。再 说 了, 创 建一大堆 const 的内容,而且只能初始化 为 同一个 值 。那 这 有什 么 用?正如本 书 提到的一 样 : “ 这样 的数 组实际 上用 处 不大 ” 。依我看,不是用 处 不大,而是根本没用。
动态 空 间 的 释 放 应该 用 “delete []p;” 而不是 “delete p;” , 这 是一个只要 记 住就可以的 问题 ,我之所以提上一句,是因 为 有人没有注意 过这 个 细节 ,包括很多自以 为 很了不起的程序 员 。
引用:如果 遗 漏了方括号 对 , 这 是一个 编译 器无法 发现 的 错误 ,将 导 致程序在运行 时 出 错 。
new 的返回 值 是一个指 针 ,不 过 本 书暂时 没有提到 new 也会返回 NULL 的。是的, 暂时还 不用提内容不 够这么复杂 的情况。
引用:在自由存 储 区中 创 建的数 组对 象数 组 是没有名字的,程序 员 只能通 过 其地址 间 接地 访问 堆中的 对 象。
笔 记 : 这 里有两个 问题 ,一是 “ 数 组 的名字 ” 其 实 也是个指 针 ,指 针 当然也可以看成 “ 数 组 的名字 ” , 这 本来就可以互 换 的,正如我前面 说 的一 样 : “5[a]” 完全等价于 “*(5+a)” 。至于 a 是静 态 的数 组 名 还 是 动态 的指 针 ,没有区 别 。第二个 问题 是 这 里提到了 “ 堆 ” ,在没有 讲 解内存之前, 这样说毕 竟理解上有 难 度。 还 是那句 话 , 这 本 书 是 给 有一定基 础 的人看的。
令我耳目一新的是: new 还 能 创 建 const 数 组 。 —— 不是我不懂,而是 实 在没想到。再 说 了, 创 建一大堆 const 的内容,而且只能初始化 为 同一个 值 。那 这 有什 么 用?正如本 书 提到的一 样 : “ 这样 的数 组实际 上用 处 不大 ” 。依我看,不是用 处 不大,而是根本没用。
动态 空 间 的 释 放 应该 用 “delete []p;” 而不是 “delete p;” , 这 是一个只要 记 住就可以的 问题 ,我之所以提上一句,是因 为 有人没有注意 过这 个 细节 ,包括很多自以 为 很了不起的程序 员 。
引用:如果 遗 漏了方括号 对 , 这 是一个 编译 器无法 发现 的 错误 ,将 导 致程序在运行 时 出 错 。
标题:
:
C
风
格字符
串
给这
篇文章定下
这
个
标题:
,是因
为书
中就是
这样说
的。本
书
是
讲
解
C++
的,所以它推荐
读
者尽量使用
C++
的内容,而
实际
上像我
这样
从
C
过
来的人,
还
是
习惯
于使用
C
风
格的字符串。
——
我又想起了那句
话
:
“
原来我只是一个
‘
古代
’
的
C++
程序
员
。
”(
见
《数
组
》一文
)
C 语 言是用字符数 组 来做字符串的(当然 这 个字符数 组 必需要有一个 NULL 结 尾),因 为 字符串是如此常用, C 语 言 还专门开发 了一套 库 函数来 处 理 这 个特殊的数 组 。于是,我 们进 行字符串操作 时 ,可以忘 记 指 针 、忘 记 循 环 、 还 可以忘 记 char 这 个内置 类 型。
正是因 为 如此,林 锐 博士的《高 质 量 C++/C 编 程指南》中 还 特 别强调 ,不可以用指 针 的 赋值 和比 较 来 进 行字符串的 赋值 和比 较 。 这 个警告 对 于从 VB 转过 来的人尤其重要。
使用 C 风 格的字符串有两点是必 须 保 证 的:一是要 给这 个数 组开 劈足 够长 度的空 间 ;二是一定不要忘了 NULL ;其中第二点一般程序 员 不会犯 错 ,因 为毕 竟没几个人用 “chat s[3] = {'a', 'b', '/0'}” 这种 方式来定 义 字符串。第一点就成了重中之重。我 们 在 strcpy 之前,有没有考 虑过 目 标 字符串可能的空 间 不足?
“strn” 风 格的函数既救了大家也可能害了大家, 说 它救了大家,因 为 大家在 strncpy 和 strncat 时 可以控制字符个数,即使源字符串太 长 ,也可以避免内存溢出。但是它存在的危 险 性是它不会 为 目 标 字符串添加 NULL 。
所以, 书 写到 这 里再次做了一个提醒: “ 尽可能使用 标 准 库类 型 string”—— 我都忘了 这 是第几次提醒了,本 书 一而再再而三地提醒 读 者不要做 “ 古代 ” 的 C++ 程序 员 。
C 语 言是用字符数 组 来做字符串的(当然 这 个字符数 组 必需要有一个 NULL 结 尾),因 为 字符串是如此常用, C 语 言 还专门开发 了一套 库 函数来 处 理 这 个特殊的数 组 。于是,我 们进 行字符串操作 时 ,可以忘 记 指 针 、忘 记 循 环 、 还 可以忘 记 char 这 个内置 类 型。
正是因 为 如此,林 锐 博士的《高 质 量 C++/C 编 程指南》中 还 特 别强调 ,不可以用指 针 的 赋值 和比 较 来 进 行字符串的 赋值 和比 较 。 这 个警告 对 于从 VB 转过 来的人尤其重要。
使用 C 风 格的字符串有两点是必 须 保 证 的:一是要 给这 个数 组开 劈足 够长 度的空 间 ;二是一定不要忘了 NULL ;其中第二点一般程序 员 不会犯 错 ,因 为毕 竟没几个人用 “chat s[3] = {'a', 'b', '/0'}” 这种 方式来定 义 字符串。第一点就成了重中之重。我 们 在 strcpy 之前,有没有考 虑过 目 标 字符串可能的空 间 不足?
“strn” 风 格的函数既救了大家也可能害了大家, 说 它救了大家,因 为 大家在 strncpy 和 strncat 时 可以控制字符个数,即使源字符串太 长 ,也可以避免内存溢出。但是它存在的危 险 性是它不会 为 目 标 字符串添加 NULL 。
所以, 书 写到 这 里再次做了一个提醒: “ 尽可能使用 标 准 库类 型 string”—— 我都忘了 这 是第几次提醒了,本 书 一而再再而三地提醒 读 者不要做 “ 古代 ” 的 C++ 程序 员 。
标题:
:指
针
(三)指
针
与数
组
指
针
和数
组
之
间
是什
么关
系呢?
书
中曰
“
密切相
关
”
。其
实
,那
简
真就是同一回事嘛。用到指
针
的
时
候,你未必会用到数
组
;但是只要你用到数
组
,你就必要然用到指
针
(即使你不知道)。
正是因 为 指 针 可以用加或减运算来移 动 它所指的位置,而且 每 加一或减一正好移 动 到相 邻 一个同 类 型的 变 量(不管 这 个 变 量占内存是多少),那 么 我有意将一堆同 类 型的 变 量放在一起,拿一个指 针 指向它 们 中的第一个,再 记 住它 们 的个数, 这 就成了数 组 。
数 组 用一 组 方括号来解引用其中的某个成 员 , 这 也只是指 针 运算的 简 化。比如:
int a[10];
a[5] = 5;
以上 这种 代 码谁 都用 过 , 谁 都能理解。那 么 下面 这 行代 码 呢?
5[a] = 5;
这种 用法恐怕很少有人知道,即使 现 在知道了,恐怕也很 难 理解。 实际 上知道了数 组 运算的 实质 , 这 行代 码 的迷 雾 就会立即消失: C/C++ 语 言 处 理括号的方法很 简单 ,将方括号前面的 值 和方括号内的 值 相加,得到一个新的指 针 ,再取指 针 所指的 对 象 值 。 “a[5]” 就完全等价于 “*(a+5)” , “5[a]” 就完全等价于 “*(5+a)” 。
那 么 “*(a+5)” 是什 么 运算呢?指 针 运算。因 为 在 编译 器 处 理 “int a[10];” 的 时 候,就等于定 义 了一个 “int * const a;” 同 时 将它初始化 为 指向 栈 内存的某 处 。
实际 上,正是因 为 “*(a+5)” 这种 用法 实 在太常用了, C 才 规 定了它的替代用法,后来 这 个替代用法被广 为 接受,而它的 实际 却被人 遗 忘。
以上内容本 书 未有提及, 这 是我看 书 看到 这 里的一点心得,作 为读书 笔 记 写下来。不是 为 了炫耀。本 书虽 然是 给 有一定基 础 的人 读 的,但是 毕 竟它只是按 步 就章地写下 C++ 的 语 法 规则 ,没有必要提及 这 些 技巧性高而又 实 用性少的内容。我之所以要写下来,目的是 为 了便于理解指 针 运算 。
正是因 为 指 针 可以用加或减运算来移 动 它所指的位置,而且 每 加一或减一正好移 动 到相 邻 一个同 类 型的 变 量(不管 这 个 变 量占内存是多少),那 么 我有意将一堆同 类 型的 变 量放在一起,拿一个指 针 指向它 们 中的第一个,再 记 住它 们 的个数, 这 就成了数 组 。
数 组 用一 组 方括号来解引用其中的某个成 员 , 这 也只是指 针 运算的 简 化。比如:
int a[10];
a[5] = 5;
以上 这种 代 码谁 都用 过 , 谁 都能理解。那 么 下面 这 行代 码 呢?
5[a] = 5;
这种 用法恐怕很少有人知道,即使 现 在知道了,恐怕也很 难 理解。 实际 上知道了数 组 运算的 实质 , 这 行代 码 的迷 雾 就会立即消失: C/C++ 语 言 处 理括号的方法很 简单 ,将方括号前面的 值 和方括号内的 值 相加,得到一个新的指 针 ,再取指 针 所指的 对 象 值 。 “a[5]” 就完全等价于 “*(a+5)” , “5[a]” 就完全等价于 “*(5+a)” 。
那 么 “*(a+5)” 是什 么 运算呢?指 针 运算。因 为 在 编译 器 处 理 “int a[10];” 的 时 候,就等于定 义 了一个 “int * const a;” 同 时 将它初始化 为 指向 栈 内存的某 处 。
实际 上,正是因 为 “*(a+5)” 这种 用法 实 在太常用了, C 才 规 定了它的替代用法,后来 这 个替代用法被广 为 接受,而它的 实际 却被人 遗 忘。
以上内容本 书 未有提及, 这 是我看 书 看到 这 里的一点心得,作 为读书 笔 记 写下来。不是 为 了炫耀。本 书虽 然是 给 有一定基 础 的人 读 的,但是 毕 竟它只是按 步 就章地写下 C++ 的 语 法 规则 ,没有必要提及 这 些 技巧性高而又 实 用性少的内容。我之所以要写下来,目的是 为 了便于理解指 针 运算 。
标题:
:指
针
(二)
指
针
的初始化与
赋值
:指
针
是一个
变
量,它可以被
赋值
,也可以被求
值
。指
针
可以接受的
值
只有以下几
种
:
1 、 编译时 可求 值 的 0 值 常量。(必 须 是 0 ,其 实 就是 NULL 啦)
2 、 类 型匹配的 对 象的地址。(也就是用 & 运算符取一个 变 量的地址)
3 、另一 对 象末的下一地址。( 这种 用法主要用在循 环 里,其 实 当指 针 取 这 个 值时 , 对 其所指的内存 进 行存取往往会 导 致灾 难 )
4 、同 类 型的另一个有效指 针 (如 “p=q;” )。
其中第 1 点,将 0 值赋给 指 针 ,主要是 为 了有一个状 态 表示 这 个指 针 是 “ 空的 ” 。 C/C++ 通常 约 定 0 为 NULL , 虽 然的确存在地址 为 0 的内存,但是 别 指望用指 针 来 访问这 个内存。
初学者怎 样 才能消除 对 指 针 的恐惧?我 觉 得首要的一点是清醒地 认识 并且 时 刻提醒自己 “ 指 针 也是一个 变 量 ” 。比如以下两行程序:
int i;
int *p = &i;
看到 这 儿的人几乎无一例外把 p 和 i 联 系起来( 这 当然不是坏事),但是,我 觉 得更重要的是将 p 和 i 分离,心里 记 住, p 是一个 变 量, 该变 量是有它的 值 的, 这 个 值 与 i 的唯一 关 系是:目前 该值 正好等于 变 量 i 在内存中的位置。两 种 情况下 p 与 i 将毫无 关 系:
1 、 p 值 被改 变 ,如 “p = &j;” 或 “p++;”
2 、 i 变 量被 释 放,如离 开 了 i 的作用域。
1 、 编译时 可求 值 的 0 值 常量。(必 须 是 0 ,其 实 就是 NULL 啦)
2 、 类 型匹配的 对 象的地址。(也就是用 & 运算符取一个 变 量的地址)
3 、另一 对 象末的下一地址。( 这种 用法主要用在循 环 里,其 实 当指 针 取 这 个 值时 , 对 其所指的内存 进 行存取往往会 导 致灾 难 )
4 、同 类 型的另一个有效指 针 (如 “p=q;” )。
其中第 1 点,将 0 值赋给 指 针 ,主要是 为 了有一个状 态 表示 这 个指 针 是 “ 空的 ” 。 C/C++ 通常 约 定 0 为 NULL , 虽 然的确存在地址 为 0 的内存,但是 别 指望用指 针 来 访问这 个内存。
初学者怎 样 才能消除 对 指 针 的恐惧?我 觉 得首要的一点是清醒地 认识 并且 时 刻提醒自己 “ 指 针 也是一个 变 量 ” 。比如以下两行程序:
int i;
int *p = &i;
看到 这 儿的人几乎无一例外把 p 和 i 联 系起来( 这 当然不是坏事),但是,我 觉 得更重要的是将 p 和 i 分离,心里 记 住, p 是一个 变 量, 该变 量是有它的 值 的, 这 个 值 与 i 的唯一 关 系是:目前 该值 正好等于 变 量 i 在内存中的位置。两 种 情况下 p 与 i 将毫无 关 系:
1 、 p 值 被改 变 ,如 “p = &j;” 或 “p++;”
2 、 i 变 量被 释 放,如离 开 了 i 的作用域。
标题:
:指
针
指
针
是
C/C++
的精
华
,也是最
难
的部分。
——
所有学
习
C/C++
的人都明白
这
点,当年我初学的
时
候也是
这样
。但是,
现
在再回想指
针
,我却很
难
回
忆
它究竟
难
在哪儿。
应该说这
就叫
“
难
者不会,会者不
难
”
吧。
“
饱汉
不知
饿汉饥
”
是有一定的道理的,即使
饱汉
曾
经饿过
。
本 书 中 规 中矩地 讲 解了指 针 的概念、定 义 与初始化、操作等。正如上面提到的 “ 饱汉 不知 饿汉饥 ” ,我似乎很健忘,以至于不 记 得指 针 的 难 点在哪儿了。
指 针 的灵活性可以把大量的工作化繁 为 易,前提是必 须 首很把足 够 繁的指 针 弄懂。听起来有点像 绕 口令,事 实 就是 这样 ,你 现 在把 难 懂的 东 西弄懂了,日后可以把 难 事化 简 ,大事化小。
从 VB 过 来的人一定会熟悉 “ 值传递 ” 和 “ 地址 传递 ” 这 两个概念, 实际 上, “ 地址 传递 ” 这种说 法正是 为 了弥 补 VB 没有指 针 却有 类 似的需要才 发 明的。我 认为 C/C++ 程序 员 要想深入理解指 针 ,首先要抛弃 这 个概念。在 C/C++ 程序中,即使在函数 调 用中 传递 指 针 ,也不能 说 “ 地址 传递 ” , 还应该说 是 值传递 ,只不 过这 次 传递 的 值 有点特殊,特殊在于借用 这 个 值 ,可以找到其 它 值 。就好像我 给 你一把 钥 匙一 样 ,你通 过钥 匙可以 间 接 获 得更多,但是我 给 你的只不 过 是 钥 匙。
我前 阵 子曾写 过 一篇 关 于指 针 的文章,之所以写那篇文章,是因 为 看到一大堆初学者在 论坛 上提 问 。通 过对 他 们 提的 问题 的分析,我 总结 了几点。下面,首先就先引用我自己写的《 关 于指 针 》中的片段吧(完整的文章 请 到我的个人主 页查 找):
一、指 针 就是 变 量:
虽 然申明指 针 的 时 候也提 类 型,如:
char *p1;
int *p2;
float *p3;
double *p4;
.....
但是, 这 只表示 该 指 针 指 向某 类 型的数据,而不表示 该 指 针 的 类 型。 说 白了,指 针 都是一个 类 型:四字 节 无符号整数(将来的 64 位系 统 中可能有 变 化)。
二、指 针 的加减运算很特殊:
p++ 、 p-- 之 类 的运算并不是 让 p 这 个 “ 四字 节 无符号整数 ” 加一或减一,而是 让 它指向下一个或上一个存 储单 元,它 实际 加减的 值 就是它所指 类 型的 值 的 size 。
比如:
char * 型指 针 , 每 次加减的改 变 量都是 1 ;
float * 型的指 针 , 每 次加减的改 变 量都是 4 ;
void * 型指 针 无法加减。
还 要注意的是:指 针 不能相加,指 针 相减的差 为 int 型。
正是因 为 指 针 有着不同于其它 变 量的运算方式,所以,在任何 时 候用到指 针 都必 须 明确 “ 指 针 的 类 型 ” (即指 针 所指的 变 量的 类 型)。 这 就不 难 理解 为 什 么 函数声明 时 必 须 用 “int abc(char *p)” 而 调 用的 时 候却成了 “a = abc(p);” 这样 的形式了。
三、用指 针 做参数 传递 的是指 针值 ,不是指 针 本身:
要理解参数 传递 ,首先必 须 把 “ 形参 ” 与 “ 实 参 ” 弄明白。
函数 A 在 调 用函数 B 时 ,如果要 传递 一个参数 C , 实际 是在函数 B 中重新建立一个 变 量 C ,并将函数 A 中的 C 值传 入其中,于是函数 B 就可以使用 这 个 值 了,在函数 B 中,无 论 有没有修改 这 个 C 值 , 对 于函数 A 中的 C 都没有影响。函数 B 结 束 时 ,会将所有内存收回,局部 变 量 C 被 销毁 ,函数 B 对变 量 C 所做的一切修改都将被抛弃。
以上示例中,函数 A 中的 变 量 C 称 为 “ 实 参 ” ,函数 B 中的 变 量 C 被称 为 “ 形参 ” , 调 用函数 时 ,会在 B 函数体内建立一个形参, 该 形参的 值 与 实 参的 值 是相同的,但是形参的改 变 不影响 实 参,函数 结 束 时 ,形参被 销毁 , 实 参依然没有 发 生 变 化。
指 针 也是一个 变 量,所以它也符合以上的 规 定,但是,指 针 存放的不 仅仅 是一个 值 ,而是一个内存地址。 B 函数 对这 个地址 进 行了改 动 ,改 动 的并不是形参, 而是形参所指的内存。由于形参的 值 与 实 参的 值 完全相同,所以, 实 参所指的内存也被修改。函数 结 束 时 , 虽 然 这 个形参会被 销毁 ,指 针 的 变 化无法影响 实 参,但此前 对 它所指的内存的修改会持 续 有效。所以,把指 针 作 为 参数可以在被 调 函数( B )中改 变 主 调 函数( A )中的 变 量,好像形参影响了 实 参一 样 。
注意:是 “ 好像 ” 。在 这过 程中,函数 B 影响的不是参数,而是内存。
下面再来看 刚 才的例子: “int abc(char *p)” 和 “a = abc(p);” 。 为 什 么 申 请 中要用 * 号,因 为 函数必 须 知道 这 是指 针 ; 为 什 么调 用 时 不加 * 号,因 为传递 的是 “ 指 针值 ” ,而不是 “ 指 针 所指内存的 值 ” 。
四、指向指 针 的指 针 :
正因 为 指 针 也是一个 变 量,它一 样 要尊守形参与 实 参的 规 定。所以, 虽 然指 针 做参数可以将函数内 对变 量的修改 带 到函数外,但是,函数体内 对 指 针 本身作任何修都将被 丢 弃。如果要 让 指 针 本身被修改而且要影响函数外,那 么 ,被 调 函数就 应该 知道 “ 该 指 针 所在的内存地址 ” 。 这时 ,指 针 不再是指 针 ,而是 “ 普通 变 量 ” 。作 为 参数 传递 的不是 这 个 “ 普通 变 量 ” ,而是指向 这 个 “ 普通 变 量 ” 的指 针 。即 “ 指向指 针 的指 针 ” 。
如果 p 是一个指向指 针 的指 针 ,那 么 *p 就是一个指 针 ,我 们 不 妨就把它看成 q 。要 访问 q 指 针 所指的内存,只要 *q 就是了。用初中数学的 “ 等量代 换 ” 一 换 就知道, *q 就是 **p 。
五、指 针 数 组 。
之所以要把 “ 指 针 数 组 ” 单 独提出来,是因 为 数 组 本身就与指 针 有着千 丝 万 缕 的 关 系。即使你不想用指 针 ,只要你使用了数 组 , 实际 就在与指 针 打交道了。
只要理解了指 针 本身就是 变 量,就不 难 理解 “ 指 针 数 组 ” ,我 们 可以 暂 且把它当成普通数 组 来 处 理, a[0] 、 a[1] 、 a[2]…… 就是数 组 的元素,只是, a[0] 是一个指 针 , a[1] 、 a[2] 也是一个指 针 。那 a 呢?当然也是指 针 ,但 这 是两 码 事。你可以 完全无 视 a 的存在,只去管 a[0] 等元素。 *a[0] 与 *p 没有什 么 本 质 的区 别 。
还 有一个 东 西不得不提一下,它比 较 重要:
指 针 的定 义 有两个可取的方式,它 们 各有 优 缺点: “int *p;” 和 “int* p;” 是完全等价的,后者的好 处 是 让 人体会到 p 是一个 “ 指向 int 的 ” 指 针 ,前者会 让 人 误 解 为 *p 是一个 int 型 变 量( 这 里没有定 义 int 型 变 量);但是前者的好 处 是不会 产 生混淆,如 “int *p, *q;” 让 人一眼就看出定 义 了两个指 针 ,而 “int* p,q;” 会 让 人 误 解成定 义 了两个指 针 ( 实际 上 q 不是指 针 ) 。
本 书 中 规 中矩地 讲 解了指 针 的概念、定 义 与初始化、操作等。正如上面提到的 “ 饱汉 不知 饿汉饥 ” ,我似乎很健忘,以至于不 记 得指 针 的 难 点在哪儿了。
指 针 的灵活性可以把大量的工作化繁 为 易,前提是必 须 首很把足 够 繁的指 针 弄懂。听起来有点像 绕 口令,事 实 就是 这样 ,你 现 在把 难 懂的 东 西弄懂了,日后可以把 难 事化 简 ,大事化小。
从 VB 过 来的人一定会熟悉 “ 值传递 ” 和 “ 地址 传递 ” 这 两个概念, 实际 上, “ 地址 传递 ” 这种说 法正是 为 了弥 补 VB 没有指 针 却有 类 似的需要才 发 明的。我 认为 C/C++ 程序 员 要想深入理解指 针 ,首先要抛弃 这 个概念。在 C/C++ 程序中,即使在函数 调 用中 传递 指 针 ,也不能 说 “ 地址 传递 ” , 还应该说 是 值传递 ,只不 过这 次 传递 的 值 有点特殊,特殊在于借用 这 个 值 ,可以找到其 它 值 。就好像我 给 你一把 钥 匙一 样 ,你通 过钥 匙可以 间 接 获 得更多,但是我 给 你的只不 过 是 钥 匙。
我前 阵 子曾写 过 一篇 关 于指 针 的文章,之所以写那篇文章,是因 为 看到一大堆初学者在 论坛 上提 问 。通 过对 他 们 提的 问题 的分析,我 总结 了几点。下面,首先就先引用我自己写的《 关 于指 针 》中的片段吧(完整的文章 请 到我的个人主 页查 找):
一、指 针 就是 变 量:
虽 然申明指 针 的 时 候也提 类 型,如:
char *p1;
int *p2;
float *p3;
double *p4;
.....
但是, 这 只表示 该 指 针 指 向某 类 型的数据,而不表示 该 指 针 的 类 型。 说 白了,指 针 都是一个 类 型:四字 节 无符号整数(将来的 64 位系 统 中可能有 变 化)。
二、指 针 的加减运算很特殊:
p++ 、 p-- 之 类 的运算并不是 让 p 这 个 “ 四字 节 无符号整数 ” 加一或减一,而是 让 它指向下一个或上一个存 储单 元,它 实际 加减的 值 就是它所指 类 型的 值 的 size 。
比如:
char * 型指 针 , 每 次加减的改 变 量都是 1 ;
float * 型的指 针 , 每 次加减的改 变 量都是 4 ;
void * 型指 针 无法加减。
还 要注意的是:指 针 不能相加,指 针 相减的差 为 int 型。
正是因 为 指 针 有着不同于其它 变 量的运算方式,所以,在任何 时 候用到指 针 都必 须 明确 “ 指 针 的 类 型 ” (即指 针 所指的 变 量的 类 型)。 这 就不 难 理解 为 什 么 函数声明 时 必 须 用 “int abc(char *p)” 而 调 用的 时 候却成了 “a = abc(p);” 这样 的形式了。
三、用指 针 做参数 传递 的是指 针值 ,不是指 针 本身:
要理解参数 传递 ,首先必 须 把 “ 形参 ” 与 “ 实 参 ” 弄明白。
函数 A 在 调 用函数 B 时 ,如果要 传递 一个参数 C , 实际 是在函数 B 中重新建立一个 变 量 C ,并将函数 A 中的 C 值传 入其中,于是函数 B 就可以使用 这 个 值 了,在函数 B 中,无 论 有没有修改 这 个 C 值 , 对 于函数 A 中的 C 都没有影响。函数 B 结 束 时 ,会将所有内存收回,局部 变 量 C 被 销毁 ,函数 B 对变 量 C 所做的一切修改都将被抛弃。
以上示例中,函数 A 中的 变 量 C 称 为 “ 实 参 ” ,函数 B 中的 变 量 C 被称 为 “ 形参 ” , 调 用函数 时 ,会在 B 函数体内建立一个形参, 该 形参的 值 与 实 参的 值 是相同的,但是形参的改 变 不影响 实 参,函数 结 束 时 ,形参被 销毁 , 实 参依然没有 发 生 变 化。
指 针 也是一个 变 量,所以它也符合以上的 规 定,但是,指 针 存放的不 仅仅 是一个 值 ,而是一个内存地址。 B 函数 对这 个地址 进 行了改 动 ,改 动 的并不是形参, 而是形参所指的内存。由于形参的 值 与 实 参的 值 完全相同,所以, 实 参所指的内存也被修改。函数 结 束 时 , 虽 然 这 个形参会被 销毁 ,指 针 的 变 化无法影响 实 参,但此前 对 它所指的内存的修改会持 续 有效。所以,把指 针 作 为 参数可以在被 调 函数( B )中改 变 主 调 函数( A )中的 变 量,好像形参影响了 实 参一 样 。
注意:是 “ 好像 ” 。在 这过 程中,函数 B 影响的不是参数,而是内存。
下面再来看 刚 才的例子: “int abc(char *p)” 和 “a = abc(p);” 。 为 什 么 申 请 中要用 * 号,因 为 函数必 须 知道 这 是指 针 ; 为 什 么调 用 时 不加 * 号,因 为传递 的是 “ 指 针值 ” ,而不是 “ 指 针 所指内存的 值 ” 。
四、指向指 针 的指 针 :
正因 为 指 针 也是一个 变 量,它一 样 要尊守形参与 实 参的 规 定。所以, 虽 然指 针 做参数可以将函数内 对变 量的修改 带 到函数外,但是,函数体内 对 指 针 本身作任何修都将被 丢 弃。如果要 让 指 针 本身被修改而且要影响函数外,那 么 ,被 调 函数就 应该 知道 “ 该 指 针 所在的内存地址 ” 。 这时 ,指 针 不再是指 针 ,而是 “ 普通 变 量 ” 。作 为 参数 传递 的不是 这 个 “ 普通 变 量 ” ,而是指向 这 个 “ 普通 变 量 ” 的指 针 。即 “ 指向指 针 的指 针 ” 。
如果 p 是一个指向指 针 的指 针 ,那 么 *p 就是一个指 针 ,我 们 不 妨就把它看成 q 。要 访问 q 指 针 所指的内存,只要 *q 就是了。用初中数学的 “ 等量代 换 ” 一 换 就知道, *q 就是 **p 。
五、指 针 数 组 。
之所以要把 “ 指 针 数 组 ” 单 独提出来,是因 为 数 组 本身就与指 针 有着千 丝 万 缕 的 关 系。即使你不想用指 针 ,只要你使用了数 组 , 实际 就在与指 针 打交道了。
只要理解了指 针 本身就是 变 量,就不 难 理解 “ 指 针 数 组 ” ,我 们 可以 暂 且把它当成普通数 组 来 处 理, a[0] 、 a[1] 、 a[2]…… 就是数 组 的元素,只是, a[0] 是一个指 针 , a[1] 、 a[2] 也是一个指 针 。那 a 呢?当然也是指 针 ,但 这 是两 码 事。你可以 完全无 视 a 的存在,只去管 a[0] 等元素。 *a[0] 与 *p 没有什 么 本 质 的区 别 。
还 有一个 东 西不得不提一下,它比 较 重要:
指 针 的定 义 有两个可取的方式,它 们 各有 优 缺点: “int *p;” 和 “int* p;” 是完全等价的,后者的好 处 是 让 人体会到 p 是一个 “ 指向 int 的 ” 指 针 ,前者会 让 人 误 解 为 *p 是一个 int 型 变 量( 这 里没有定 义 int 型 变 量);但是前者的好 处 是不会 产 生混淆,如 “int *p, *q;” 让 人一眼就看出定 义 了两个指 针 ,而 “int* p,q;” 会 让 人 误 解成定 义 了两个指 针 ( 实际 上 q 不是指 针 ) 。
标题:
:数
组
进
入本
书
第四章,
开
始
讲
“
数
组
”
了。数
组难
不
难
?
这
不好
说
。但是数
组
非常重要
这
是肯定的,有
许
多基本的算法就是与数
组
一起出
现
的
——
比如冒泡排序法。而离
开
了那些算法,数
组
本身也失去了价
值
。
注意: 阅读 本章 时 要 对 “ 维 数 ” 概念加以小心,按平 时 的理解, “ 维 数 ” 是多 维 数 组 中的概念,但是本 书 中的 “ 维 数 ” 指的是元素个数。 为 了避免干 扰 ,我在 阅读 笔 记 中用 “ 个数 ” 来取代 “ 维 数 ” 。
引用:在出 现标 准 库 之前, C++ 程序大量使用数 组 保存一 组对 象 。而 现 代的 C++ 程序 则 更多地使用 vector 来取代数 组 ,数 组 被 严 格限制于程序内部使用,只有当性能 测试 表明使用 vector 无法达到必要的速度要求 时 ,才使用数 组 。
笔 记 :我汗一个先!原来我只是一个 “ 古代 ” 的 C++ 程序 员 。
数 组 的定 义 必需指明元素的个数,而且必 须 是 “ 常量表达式 ” 。 这 一点想必理解数 组 的人都会操作(即使弄 错 了, 编译 器也会立即 报错 ),但是真正去思考它的人也 许 不多。 这 里的 “ 常量 ” 概念是指程序在 编译阶 段就能求 值 的量。
在 C 时 代,我 们 并不会 过 多地理会 “ 常量 ” 这 个概念,那是因 为 C 时 代没有 const ,而 define 这 个 东 西, 谁 都知道它是在 编译 前就直接替 换 的。但是到了 C++ 时 代,由于 const 的存在,使 “ 常量 ” 这 个概念 变 得更普 杂 了。
const size1 = 5;// 这 是个 编译时 就可以求 值 的常量,它可以在数据的定 义 中使用。它的作用 仅仅 相当于 “define” 。
const size = getsize();// 这 个常量是运行 时 才能求 值 的。
除此之外, “ 常量求达式 ” 的概念 还 指明 这 可是以一个算 术 式,只要 编译时 能求 值 。如 “char username[max_name_size + 1];” 这种 用法非 常常用,因 为 它可以避免在定 义 数 组时 忘掉 NULL 。
数 组 成 员 的初始化可以 显 式指定,也可以不指定。 显 式指定 时 指定成 员 的个数可以小于等于 实际 个数,但不可以比 实际 个数大,如果小于, 则 其它元素 视为 未指定。 对 于未指定的元素的初始 值 ,它 们 遵循 变 量的初始化原 则 , 请 看《 变 量初始化》一文。
绝 大多数 书 本在介 绍 “ 字符串 ” 时 都会提到,字符串其 实 是字符数 组 ,只是因 为 它 们 太常用,才会有 专门 的使用方法。本 书 也不例外。
本 书 没有提数 组 的 实质 ,因 为 “ 指 针 ” 还 没有 进 入 读 者的眼睛。
引用:数 组 的 显 著缺点在于:数 组 的 长 度是固定的, 而且程序 员 无法知道一个 给 定数 组 的 长 度。
笔 记 :其 实这 正在程序 员 心中的一个痛。当我把数 组 作 为 参数 传 送 给 函数 时 ,函数 该 怎 么处 理 这 个 东 西?要 么 是 对 大小有个事先的 约 定 —— 这样 的程序将失去很多通用性,要 么连 大小一起 传递给 人家 。
注意: 阅读 本章 时 要 对 “ 维 数 ” 概念加以小心,按平 时 的理解, “ 维 数 ” 是多 维 数 组 中的概念,但是本 书 中的 “ 维 数 ” 指的是元素个数。 为 了避免干 扰 ,我在 阅读 笔 记 中用 “ 个数 ” 来取代 “ 维 数 ” 。
引用:在出 现标 准 库 之前, C++ 程序大量使用数 组 保存一 组对 象 。而 现 代的 C++ 程序 则 更多地使用 vector 来取代数 组 ,数 组 被 严 格限制于程序内部使用,只有当性能 测试 表明使用 vector 无法达到必要的速度要求 时 ,才使用数 组 。
笔 记 :我汗一个先!原来我只是一个 “ 古代 ” 的 C++ 程序 员 。
数 组 的定 义 必需指明元素的个数,而且必 须 是 “ 常量表达式 ” 。 这 一点想必理解数 组 的人都会操作(即使弄 错 了, 编译 器也会立即 报错 ),但是真正去思考它的人也 许 不多。 这 里的 “ 常量 ” 概念是指程序在 编译阶 段就能求 值 的量。
在 C 时 代,我 们 并不会 过 多地理会 “ 常量 ” 这 个概念,那是因 为 C 时 代没有 const ,而 define 这 个 东 西, 谁 都知道它是在 编译 前就直接替 换 的。但是到了 C++ 时 代,由于 const 的存在,使 “ 常量 ” 这 个概念 变 得更普 杂 了。
const size1 = 5;// 这 是个 编译时 就可以求 值 的常量,它可以在数据的定 义 中使用。它的作用 仅仅 相当于 “define” 。
const size = getsize();// 这 个常量是运行 时 才能求 值 的。
除此之外, “ 常量求达式 ” 的概念 还 指明 这 可是以一个算 术 式,只要 编译时 能求 值 。如 “char username[max_name_size + 1];” 这种 用法非 常常用,因 为 它可以避免在定 义 数 组时 忘掉 NULL 。
数 组 成 员 的初始化可以 显 式指定,也可以不指定。 显 式指定 时 指定成 员 的个数可以小于等于 实际 个数,但不可以比 实际 个数大,如果小于, 则 其它元素 视为 未指定。 对 于未指定的元素的初始 值 ,它 们 遵循 变 量的初始化原 则 , 请 看《 变 量初始化》一文。
绝 大多数 书 本在介 绍 “ 字符串 ” 时 都会提到,字符串其 实 是字符数 组 ,只是因 为 它 们 太常用,才会有 专门 的使用方法。本 书 也不例外。
本 书 没有提数 组 的 实质 ,因 为 “ 指 针 ” 还 没有 进 入 读 者的眼睛。
引用:数 组 的 显 著缺点在于:数 组 的 长 度是固定的, 而且程序 员 无法知道一个 给 定数 组 的 长 度。
笔 记 :其 实这 正在程序 员 心中的一个痛。当我把数 组 作 为 参数 传 送 给 函数 时 ,函数 该 怎 么处 理 这 个 东 西?要 么 是 对 大小有个事先的 约 定 —— 这样 的程序将失去很多通用性,要 么连 大小一起 传递给 人家 。
标题:
:
C++
标
准
库
,想
说爱
你不容
易
第三章就
这样结
束了,本章介
绍
了三个
标
准
库类
型:
string
、
vector
和
bitset
。
可惜的是,整个第三章我都是草草 读过 的。一方面因 为 它 们 不属于 严 格意 义 上的 C++ 内容,另一方面 C 时 代的 东 西在不 经 意 间 抵触着它 们 。
确切地 说 ,它 们 C 时 代的那些 东 西的替代品。它 们 存在的理由就是它 们 更 优 秀。然而 优 秀是一回事, 动 不 动 心又是一回事。
C 语 言在 类 与 对 象方面的缺失,使 C 程序 员 更多地掌握了底 层 的操作。面 对 C++ 标 准 库 中的 string 和 bitset 这 些 东 西, C 程序 员们 都知道,在 C++ 出来以前自己是怎 样 想方法 解决 过问题 的。刻苦才有刻骨 铭 心, 转 到 C++ 以后,是使用更 简单 安全的 标 准 库 , 还 是使用自己曾熟悉的老 办 法, 这 是一个取舍的 问题 。
引用:程序 员应优 先使用 标 准 库类类 型。
笔 记 :本 书 在 这 里放置了 这样 一个提示,大概是想告 诫 我 这样 的 顽 固派: “ 别 再死 纠 着 陈 腐旧套不放了,回 头 吧。 ” 其 实 ,早在本 书 的前言就有了 类 似的内容。可是,我依然不能 让 自己静下心来 细读 并熟 记 第三章。
也 许 有一天我会回 头 重 读 第三章,当然,也 许 永 远 不会。 因 为 像 string 和 vector 这样 的 东 西,在 MFC 中 还 有更好的替代品 。
可惜的是,整个第三章我都是草草 读过 的。一方面因 为 它 们 不属于 严 格意 义 上的 C++ 内容,另一方面 C 时 代的 东 西在不 经 意 间 抵触着它 们 。
确切地 说 ,它 们 C 时 代的那些 东 西的替代品。它 们 存在的理由就是它 们 更 优 秀。然而 优 秀是一回事, 动 不 动 心又是一回事。
C 语 言在 类 与 对 象方面的缺失,使 C 程序 员 更多地掌握了底 层 的操作。面 对 C++ 标 准 库 中的 string 和 bitset 这 些 东 西, C 程序 员们 都知道,在 C++ 出来以前自己是怎 样 想方法 解决 过问题 的。刻苦才有刻骨 铭 心, 转 到 C++ 以后,是使用更 简单 安全的 标 准 库 , 还 是使用自己曾熟悉的老 办 法, 这 是一个取舍的 问题 。
引用:程序 员应优 先使用 标 准 库类类 型。
笔 记 :本 书 在 这 里放置了 这样 一个提示,大概是想告 诫 我 这样 的 顽 固派: “ 别 再死 纠 着 陈 腐旧套不放了,回 头 吧。 ” 其 实 ,早在本 书 的前言就有了 类 似的内容。可是,我依然不能 让 自己静下心来 细读 并熟 记 第三章。
也 许 有一天我会回 头 重 读 第三章,当然,也 许 永 远 不会。 因 为 像 string 和 vector 这样 的 东 西,在 MFC 中 还 有更好的替代品 。
标题:
:
标
准
库
bitset
类
型
每
当使用到布
尔变
量数
组时
,
总
是有点心疼。因
为
布
尔变
量只需要
0
和
1
两
种值
,然而
编译
器
动辄
使用一个字
节
——
甚至四个字
节
来存放一个布
尔变
量。面
对
1/32
的使用效率,叫人怎能不心疼?
要想 节约 空 间 也不是没有 办 法,代价是写更多的代 码 :用 “&” 操作将 变 量的某一个 bit 取出来,一个字 节 就可以存放 8 个布 尔变 量。但是, 这 个代价是比 较 重的,重到足以 让 程序 员 望而生畏的地 步 。
bitset 应 运而生,它可以方便地管理一系列的 bit 位而不用程序 员 自己来写代 码 。
更重要的是, bitset 除了可以 访问 指定下 标 的 bit 位以外, 还 可以把它 们 作 为 一个整数来 进 行某些 统计 ,如:
b.any();//b 中是否存在置 为 1 的二 进 制位?
b.count();//b 中置 为 1 的二 进 制位的个数
……
不 过 , 话说 回来,我 还 是不 习惯 用 bitset ,原因在于我是从 C 语 言 转 到 C++ 的。 详细问题 留到后一篇文章中 讨论 。
要想 节约 空 间 也不是没有 办 法,代价是写更多的代 码 :用 “&” 操作将 变 量的某一个 bit 取出来,一个字 节 就可以存放 8 个布 尔变 量。但是, 这 个代价是比 较 重的,重到足以 让 程序 员 望而生畏的地 步 。
bitset 应 运而生,它可以方便地管理一系列的 bit 位而不用程序 员 自己来写代 码 。
更重要的是, bitset 除了可以 访问 指定下 标 的 bit 位以外, 还 可以把它 们 作 为 一个整数来 进 行某些 统计 ,如:
b.any();//b 中是否存在置 为 1 的二 进 制位?
b.count();//b 中置 为 1 的二 进 制位的个数
……
不 过 , 话说 回来,我 还 是不 习惯 用 bitset ,原因在于我是从 C 语 言 转 到 C++ 的。 详细问题 留到后一篇文章中 讨论 。
标题:
:迭代器:指
针
与数据
库杂
交的后代
迭代器与数据
库
的相似之
处
在于
end()
函数返回
值为
“
指向末元素的下一个
”
。跟数据
记录
集的
eof
这么
相似。
话说
回来,熟
练
于
C/C++
的程序
员
一定不会忘了,利用下
标访问
数
组时
用的
总
是用
“
半
开
半
闭
区
间
”
。就拿
“int a[10]; for(int i=0; i!=10; i++)”
来
说
,下
标为
10
就可以
视为
“
最后一个元素的下一个
”
。只是以前不会有
这么
明
显
的思考。
迭代器与指 针 的相似之 处 在于它的 “ 解引操作符 ” ,居然就是一个 “*” 号。而且存在着 “ivec[n]” 和 “*ivec” 这 两个完全等价的操作。而且 还 支 持算 术 运算来移 动 要 访问 的元素。
迭代的 const 用法与指 针 有个区 别 :
“vector<int>::const_iterator ivec = vec.begin();” 定 义 的 ivec 并不是常量,只是它指向的元素会得到保 护 。如果要定 义 本身 为 const 的迭代器,要用 “const vector<int>::ivec = vec.begin();” 。
指 针 是 这样处 理的:
const int *p = &i;//p 指向的 变 量是 const
int* const p = &i;//p 是 const
地雷:任何改 变 vector 长 度的操作都会使已存在的迭代器失 败 。例如,在 调 用 push_back 之后,就不能再信 赖 指向 vector 的迭代器的 值 了。
笔 记 :我不得不相信所 谓 的迭代器其本 质 就是一个指 针 了。因 为 vector 支持 动态 增 长 ,而且保 证 内存的 连续 。所以, 每 次改 变 它的 长 度都会 导 致 释 放已有内存、重新申 请 内存。 指 针 当然得失效 。
迭代器与指 针 的相似之 处 在于它的 “ 解引操作符 ” ,居然就是一个 “*” 号。而且存在着 “ivec[n]” 和 “*ivec” 这 两个完全等价的操作。而且 还 支 持算 术 运算来移 动 要 访问 的元素。
迭代的 const 用法与指 针 有个区 别 :
“vector<int>::const_iterator ivec = vec.begin();” 定 义 的 ivec 并不是常量,只是它指向的元素会得到保 护 。如果要定 义 本身 为 const 的迭代器,要用 “const vector<int>::ivec = vec.begin();” 。
指 针 是 这样处 理的:
const int *p = &i;//p 指向的 变 量是 const
int* const p = &i;//p 是 const
地雷:任何改 变 vector 长 度的操作都会使已存在的迭代器失 败 。例如,在 调 用 push_back 之后,就不能再信 赖 指向 vector 的迭代器的 值 了。
笔 记 :我不得不相信所 谓 的迭代器其本 质 就是一个指 针 了。因 为 vector 支持 动态 增 长 ,而且保 证 内存的 连续 。所以, 每 次改 变 它的 长 度都会 导 致 释 放已有内存、重新申 请 内存。 指 针 当然得失效 。
标题:
:
for
语
句的条件思
考
对
于
for
语
句,我几乎
总
是
这样
写的:
“for(int i=0; i<Max; i++);”
。但是,按本
书
的
说
法,我
这样
写似乎同
时
犯了两个
错误
。
1 、本 书 中写 for 语 句中的第二个表达式(条件表达式) 时总 是用 != ,而不用 < 。 这 几天来 虽 然心里 觉 得奇怪,但是一直没去思考 这 里面的含 义 。本 书 没有急着告 诉 我 “ 所以然 ” ,只是提醒我 读 完本 书 的第二部分后就会明白。我等着吧 :)
2 、我 总 是 觉 得用 Max 这样 一个 变 量去代替某个需要用函数才 能返回的 值 可以增加运行效率,但是本 书 的建 议 与我的理解相反。它建 议 用 “i<ivec.size()” , “ 因 为 数据 结 构可以 动态 增 长 , …… 如果确 实 增加了新元素的 话 ,那 么测试 已保存的 size 值 作 为 循 环结 束条件就会有 问题 ,因 为 没有将新加入的元素 计 算在内。 ”
同 时 ,本 书为 了打消运行效率的 顾虑 , 还 提到了 “ 内 联 函数 ” 这 个概念。 现 在 还 没有到介 绍 内 联 的 时 候,但是 C++ 必 须 是一个有机体,在 讲 述某一个知 识 点的 时 候,不可能完全避免另一个知 识 点。 所以,我 还 是建 议 初学者不要急着 阅读 本 书 。
1 、本 书 中写 for 语 句中的第二个表达式(条件表达式) 时总 是用 != ,而不用 < 。 这 几天来 虽 然心里 觉 得奇怪,但是一直没去思考 这 里面的含 义 。本 书 没有急着告 诉 我 “ 所以然 ” ,只是提醒我 读 完本 书 的第二部分后就会明白。我等着吧 :)
2 、我 总 是 觉 得用 Max 这样 一个 变 量去代替某个需要用函数才 能返回的 值 可以增加运行效率,但是本 书 的建 议 与我的理解相反。它建 议 用 “i<ivec.size()” , “ 因 为 数据 结 构可以 动态 增 长 , …… 如果确 实 增加了新元素的 话 ,那 么测试 已保存的 size 值 作 为 循 环结 束条件就会有 问题 ,因 为 没有将新加入的元素 计 算在内。 ”
同 时 ,本 书为 了打消运行效率的 顾虑 , 还 提到了 “ 内 联 函数 ” 这 个概念。 现 在 还 没有到介 绍 内 联 的 时 候,但是 C++ 必 须 是一个有机体,在 讲 述某一个知 识 点的 时 候,不可能完全避免另一个知 识 点。 所以,我 还 是建 议 初学者不要急着 阅读 本 书 。
标题:
:
标
准
库
vector
类
型
终
于
让
“
类
模板
”
上
场
了,可惜的是,在本
书
的第三章,
还
不能
彻
底
让类
模板浮出水面,只能将就着提一下。
引用:使用 类 模板可以 编 写一个 类 定 义 或函数定 义 ,而用于多个不同的数据 类 型。 ……vector 并不是一 种 数据 类 型,而只是一个 类 模板,可用来定 义 任意多 种 数据 类 型。
笔 记 : “vector<int> ivec;” 中, “vector<int>” 是一个数据 类 型, C++ 支持 类 模板,以后大量接触 类 模板 时 , 这 个一定要 时 刻注意。
引用:在元素 值 已知的情况下,最好是 动态 地增加元素。 …… 虽 然可以 对给 定元素个数的 vector 对 象 预 先分配内存,但是更有效的方法是先初始化一个空的 vector 对 象,然后再 动态 地增加元素。
将 vector 与 string 对 比,可以 轻 松地 记 住 empty() 、 size() 函数和 [] 、 = 、 == 、 != 等运算符。不 过这 似乎有个前提: 对 C 语 言比 较 熟并且 摈 弃 VB 的 String 变 量 类 型。因 为 , string 虽 然是一个 变 量,但是如果缺少 对 “char []” 的理解,将注定不能理解它。 —— 所以,我很反 对 某些人 说 的 “ 可以不学 C 语 言直 接学 C++” 论调 。
引用:使用 类 模板可以 编 写一个 类 定 义 或函数定 义 ,而用于多个不同的数据 类 型。 ……vector 并不是一 种 数据 类 型,而只是一个 类 模板,可用来定 义 任意多 种 数据 类 型。
笔 记 : “vector<int> ivec;” 中, “vector<int>” 是一个数据 类 型, C++ 支持 类 模板,以后大量接触 类 模板 时 , 这 个一定要 时 刻注意。
引用:在元素 值 已知的情况下,最好是 动态 地增加元素。 …… 虽 然可以 对给 定元素个数的 vector 对 象 预 先分配内存,但是更有效的方法是先初始化一个空的 vector 对 象,然后再 动态 地增加元素。
将 vector 与 string 对 比,可以 轻 松地 记 住 empty() 、 size() 函数和 [] 、 = 、 == 、 != 等运算符。不 过这 似乎有个前提: 对 C 语 言比 较 熟并且 摈 弃 VB 的 String 变 量 类 型。因 为 , string 虽 然是一个 变 量,但是如果缺少 对 “char []” 的理解,将注定不能理解它。 —— 所以,我很反 对 某些人 说 的 “ 可以不学 C 语 言直 接学 C++” 论调 。
标题:
:
标
准
库
string
类
型
习惯
了
VC++
的
CString
类
,而此前用的又是
C
语
言,所以,
压
根没有看一眼
string
。
现
在既然
书
中
专门讲
它,我就看一看吧。
定 义 与初始化:
string s1;
string s2(s1);
string s3("value");
string s4(n, 'c');// 由 n 个 c 组 成的一串
string 对 象的 输 入除了可以 “cin >> s1;” 以外, 还 可以将 cin 和 string 对 象一起作 为 getline() 函数的参数 “getline(cin, s1);” ,而且 这 个函数的返回 值还 是 cin 。
除此之外, string 类还 有 empty() 、 size() 等函数和 [] 、 + 、 = 、 == 等运算符。
地雷: empty() 函数的功能并不是将 对 象置空,而是 测试 是否 为 空。 这 可是与 CString 类 的 Empty() 函数不一 样 的哦!
本 书还详细 介 绍 了 empty::size_type 类 型存在的原因。在使用 VC++.NET 的 时 候,我已 经见过 了 size_t 类 型 ——strlen() 函数的返回 值 就是 size_t 类 型 对 象。当 时 我也没有 细 究 这 个 东 西,想来 应该 是 为 了支持 64 位机而 设 的吧。不 过现 在我知道了,原来我想得 还 不 够 。
引用:通 过这 些配套 类 型, 库类 型的使用就能与机器无 关 。
关 于 “+ 运算必 须 至少包含一个 string 类 型 ” , 这 可能 让 初学者摸不着 头脑 。比如 “s1 + s2” 、 “s1 + "hello"” 、 “"hello" + s1” 是合法的,唯独 “"hello" + "world"” 不合法。我在看懂 “ 运算符重 载 ” 之前也没有明白 这 个。本 书 由于 刚开头 ,离 “ 运算符重 载 ” 还远 着 呢,所以只告 诉读 者 “ 然 ” 、没有告 诉读 者 “ 所以然 ” 。我也不写了。
引用:下 标 操作可作左 值 。
笔 记 :人 类 一思考,上帝就 发 笑。 这 就是一个例 证 。下 标 操作可作左 值 有什 么 好 说 的?早就用 过 啊?比如 “char s[]="abcde"; s[2]='t';” 。可是 现 在的不是指 针变 量,而是 类 成 员 , 对 于 “string s("abcde");” 来 说 , “s[n]” 就不是 简单 操作一个内存了,而是从 “[] 重 载 函数中返回了一个 值 ” 。 这 个 值 可以做左 值 ,并不是想当然的事 。
定 义 与初始化:
string s1;
string s2(s1);
string s3("value");
string s4(n, 'c');// 由 n 个 c 组 成的一串
string 对 象的 输 入除了可以 “cin >> s1;” 以外, 还 可以将 cin 和 string 对 象一起作 为 getline() 函数的参数 “getline(cin, s1);” ,而且 这 个函数的返回 值还 是 cin 。
除此之外, string 类还 有 empty() 、 size() 等函数和 [] 、 + 、 = 、 == 等运算符。
地雷: empty() 函数的功能并不是将 对 象置空,而是 测试 是否 为 空。 这 可是与 CString 类 的 Empty() 函数不一 样 的哦!
本 书还详细 介 绍 了 empty::size_type 类 型存在的原因。在使用 VC++.NET 的 时 候,我已 经见过 了 size_t 类 型 ——strlen() 函数的返回 值 就是 size_t 类 型 对 象。当 时 我也没有 细 究 这 个 东 西,想来 应该 是 为 了支持 64 位机而 设 的吧。不 过现 在我知道了,原来我想得 还 不 够 。
引用:通 过这 些配套 类 型, 库类 型的使用就能与机器无 关 。
关 于 “+ 运算必 须 至少包含一个 string 类 型 ” , 这 可能 让 初学者摸不着 头脑 。比如 “s1 + s2” 、 “s1 + "hello"” 、 “"hello" + s1” 是合法的,唯独 “"hello" + "world"” 不合法。我在看懂 “ 运算符重 载 ” 之前也没有明白 这 个。本 书 由于 刚开头 ,离 “ 运算符重 载 ” 还远 着 呢,所以只告 诉读 者 “ 然 ” 、没有告 诉读 者 “ 所以然 ” 。我也不写了。
引用:下 标 操作可作左 值 。
笔 记 :人 类 一思考,上帝就 发 笑。 这 就是一个例 证 。下 标 操作可作左 值 有什 么 好 说 的?早就用 过 啊?比如 “char s[]="abcde"; s[2]='t';” 。可是 现 在的不是指 针变 量,而是 类 成 员 , 对 于 “string s("abcde");” 来 说 , “s[n]” 就不是 简单 操作一个内存了,而是从 “[] 重 载 函数中返回了一个 值 ” 。 这 个 值 可以做左 值 ,并不是想当然的事 。
标题:
:命名空
间
的
using
声明
虽
然
实际
工作中
绝
少用到
cin
和
cout
,但是我
还
是
记
得要用它
们
必先
“using namespace std;”
,至于
为
什
么这样
做,三个字:不知道。
本 书 从一 开头 就用到了 cin 和 cout ,但是它没有 using ,而是 每 一次用到它 们 都写成 “std::cin” 和 “std::cout” ,同 时 提醒 说这 两个字名是来自 std 命名空 间 的。于是,我似乎明白了 “using namespace std;” 的作用。
也 许 是 C++ 实 在太 难 , 作者 迟迟 不介 绍 更深入的内容,到了第一部分、第三章, 还 没有打算深入 using ,但又不得不 讲 一点,于是提了 这样 两行:
using std::cin;
using std::cout;
至此,究竟什 么 是命名空 间 ,命名空 间 是干什 么 的,我 还 是不懂。也 许书 后面会提吧 。
本 书 从一 开头 就用到了 cin 和 cout ,但是它没有 using ,而是 每 一次用到它 们 都写成 “std::cin” 和 “std::cout” ,同 时 提醒 说这 两个字名是来自 std 命名空 间 的。于是,我似乎明白了 “using namespace std;” 的作用。
也 许 是 C++ 实 在太 难 , 作者 迟迟 不介 绍 更深入的内容,到了第一部分、第三章, 还 没有打算深入 using ,但又不得不 讲 一点,于是提了 这样 两行:
using std::cin;
using std::cout;
至此,究竟什 么 是命名空 间 ,命名空 间 是干什 么 的,我 还 是不懂。也 许书 后面会提吧 。
标题:
:
头
文件
头
文件的用
处
主要是代
码
重用
——
重用不
仅仅
是
为
了减少工作量,
还
可以保
证每
一次重用都是完全相同的内容。
正因 为头 文件可以多次重用,所以要防止有些只能出 现 一次的代 码 放 进头 文件中。比如 变 量的定 义 只能有一次,声明(含 extern 且不含初始化)却可以有多次。函数也是。
引用:一些 const 对 象定 义 在 头 文件中。
笔 记 :看到 这 里, 总 算想通了前面的困惑: 为 什 么 const 常量的作用域 仅为 一个文件。正如我前面的估 计 , const 常量是不 开 劈内存空 间 的,代 码 在 编译 的 时 候就被直接替 换 成常量 值 。而且, 编译 器 对 多个 CPP 是分 开编译 的。所以,它必 须 要 编译 的 时 候知道 该 常量的 值 。如果常量的作用域也是全局的,那 么 我告 诉编译 器 “ 该 常量的 值 在另一个 CPP 中 ” 将使其无可适从。
避免 头 文件被重 复 包含是个比 较 有岐 义 的 说 法, 头 文件既然可以被多次包含, 为 什 么 又要避免重 复 包含? 还 是因 为 CPP 文件的 单 独 编译 。在 编译 任一个 CPP 文件 时 ,都要知道文件的内 容,所以,如果两个 CPP 都包含了同一个 头 文件,那 么头 文件就要被 编译 两次。但是同一个 CPP 如果重 复调 用 —— 甚至可能循 环调 用 —— 了某一个 头 文件, 则 要及 时 避免。 #ifndef...#define...#endif 可以起到 这 个作用。
对 了,我一直没想通 VC++.NET 的 “#pragma once” 是怎 么 工作的。它 为 什 么 不需要用 变 量来 标记 不同的 头 文件? 为 什 么 只需要一行也能 标记 出整个 头 文件的所有内容?
第一部分、第二章 结 束 。
正因 为头 文件可以多次重用,所以要防止有些只能出 现 一次的代 码 放 进头 文件中。比如 变 量的定 义 只能有一次,声明(含 extern 且不含初始化)却可以有多次。函数也是。
引用:一些 const 对 象定 义 在 头 文件中。
笔 记 :看到 这 里, 总 算想通了前面的困惑: 为 什 么 const 常量的作用域 仅为 一个文件。正如我前面的估 计 , const 常量是不 开 劈内存空 间 的,代 码 在 编译 的 时 候就被直接替 换 成常量 值 。而且, 编译 器 对 多个 CPP 是分 开编译 的。所以,它必 须 要 编译 的 时 候知道 该 常量的 值 。如果常量的作用域也是全局的,那 么 我告 诉编译 器 “ 该 常量的 值 在另一个 CPP 中 ” 将使其无可适从。
避免 头 文件被重 复 包含是个比 较 有岐 义 的 说 法, 头 文件既然可以被多次包含, 为 什 么 又要避免重 复 包含? 还 是因 为 CPP 文件的 单 独 编译 。在 编译 任一个 CPP 文件 时 ,都要知道文件的内 容,所以,如果两个 CPP 都包含了同一个 头 文件,那 么头 文件就要被 编译 两次。但是同一个 CPP 如果重 复调 用 —— 甚至可能循 环调 用 —— 了某一个 头 文件, 则 要及 时 避免。 #ifndef...#define...#endif 可以起到 这 个作用。
对 了,我一直没想通 VC++.NET 的 “#pragma once” 是怎 么 工作的。它 为 什 么 不需要用 变 量来 标记 不同的 头 文件? 为 什 么 只需要一行也能 标记 出整个 头 文件的所有内容?
第一部分、第二章 结 束 。
标题:
:
class
与
struct
首次考
虑
class
与
struct
的
关
系源自我
对类对
象占用内存数的
观
察。我
发现
VC++6
的
CString——
这么强
大的
类
——
它占内存居然是
4
字
节
(
sizeof(CString)
为
4
)。那
时
我就
认为
,
类对
象
仅仅
在内存中存放成
员变
量(《
C++ Primer
》称作
“
数据成
员
”
)而不存放成
员
函数。后来,
读
林
锐
博士的《高
质
量
C/C++
编
程指南》
时
,才看到了比
较
正
规
的
说
法:
C++
中
class
与
struct
没有本
质
的区
别
。
——
当
时
我将
这
句
话给
我朋友看
时
,他
还
表
现
出不相信的神色。
读 《 C++ Primer 》 时 ,我已 经 熟知了 class 与 struct 的 关 系。但是我 还 是 细细 地没有放 过书 中的任何一个字, 还 是作点 记录 吧:
引用:用 class 和 struct 关键 字定 义类 的唯一差 别 在于默 认访问级别 :默 认 情况下, struct 的成 员为 public ,而 class 的成 员为 private 。
笔 记 :不 过 ,一般情况下公司与公司 间规 定 协议时 ,喜 欢 将 类 定 义 写成 struct ,我想 这 可能是源于程序 员 从 C 语 言 继 承来的 习惯 , 还 有就是 协议 内容一般只包含数据,不包含函数,也 没有必要 规 定安全的 访问级别 。
引用: 编 程新手 经 常会忘 记类 定 义 后面的分号, 这 是个很普通的 错误 !
笔 记 : 书 中将 这 段文字 标 作一个地雷,我 还 是引用一下吧 。
读 《 C++ Primer 》 时 ,我已 经 熟知了 class 与 struct 的 关 系。但是我 还 是 细细 地没有放 过书 中的任何一个字, 还 是作点 记录 吧:
引用:用 class 和 struct 关键 字定 义类 的唯一差 别 在于默 认访问级别 :默 认 情况下, struct 的成 员为 public ,而 class 的成 员为 private 。
笔 记 :不 过 ,一般情况下公司与公司 间规 定 协议时 ,喜 欢 将 类 定 义 写成 struct ,我想 这 可能是源于程序 员 从 C 语 言 继 承来的 习惯 , 还 有就是 协议 内容一般只包含数据,不包含函数,也 没有必要 规 定安全的 访问级别 。
引用: 编 程新手 经 常会忘 记类 定 义 后面的分号, 这 是个很普通的 错误 !
笔 记 : 书 中将 这 段文字 标 作一个地雷,我 还 是引用一下吧 。
标题:
:枚
举
枚
举
是我向来不太喜
欢
用的
东
西,几乎我
见过
的
每
本
书
都是
这样
介
绍
枚
举
的:
enum weekday {Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};
我看到 这 里, 总 是想:多浪 费 啊,与其 这样 , 还 不如直接用 0-6 这 些数呢。以后要写 “weekday Today = Sunday;” ,哪有 “int Today = 0;” 舒服。
本 书 似乎早看 透了我的心理,于是, 讲 枚 举 不从枚 举 入手,偏从 const 常量入手:
引用:
const int input = 0;
const int output = 1;
const int append = 2;
虽 然 这种 方法也能奏效,但是它有个明 显 的缺点:没有指出 这 些 值 是相 关联 的。 枚 举 提供了一 种 替代方法,不但定 义 了整数常量集,而且 还 把它 们 聚集成 组 。
笔 记 :大 师 就是大 师 ,他 们 写 书 能切中要害, 让读 者明白 标 准制定者的苦心。
另外,本 书 在 这 儿冷不丁地提了一个冷冰冰的概念:常量表达式。
引用:常量表达式是 编译 器在 编译时 就能 够计 算出 结 果的整型表达式。整型字面 值 常量是常表达式, ……
笔 记 : 让 我先汗一个。常量表达式必需是整数?我怎 么 不知道?那 “double pi = 3.14159;” 后面的字面 值 不属于常量表达式 吗 ? 这 是个疑 问 ,先 记 下来。
引用:枚 举类 型的 对 象的初始化或 赋值 ,只能通 过 其枚 举 成 员 或同一枚 举类 型的其它 对 象来 进 行。
笔 记 : 这 跟 现实 生活中的 “ 做人要 专 一 ” 有点相似,呵呵。 简单 地 说 ,你 选择 了用名字来代替数 值 ,那就得始 终 如一地使用名字,不可以用数 值 或其它表达式。 比如 “weekday Today = Sunday;” 不可写成 “weekday Today = 0;” , 虽 然 Sunday 就是 0 。
enum weekday {Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday};
我看到 这 里, 总 是想:多浪 费 啊,与其 这样 , 还 不如直接用 0-6 这 些数呢。以后要写 “weekday Today = Sunday;” ,哪有 “int Today = 0;” 舒服。
本 书 似乎早看 透了我的心理,于是, 讲 枚 举 不从枚 举 入手,偏从 const 常量入手:
引用:
const int input = 0;
const int output = 1;
const int append = 2;
虽 然 这种 方法也能奏效,但是它有个明 显 的缺点:没有指出 这 些 值 是相 关联 的。 枚 举 提供了一 种 替代方法,不但定 义 了整数常量集,而且 还 把它 们 聚集成 组 。
笔 记 :大 师 就是大 师 ,他 们 写 书 能切中要害, 让读 者明白 标 准制定者的苦心。
另外,本 书 在 这 儿冷不丁地提了一个冷冰冰的概念:常量表达式。
引用:常量表达式是 编译 器在 编译时 就能 够计 算出 结 果的整型表达式。整型字面 值 常量是常表达式, ……
笔 记 : 让 我先汗一个。常量表达式必需是整数?我怎 么 不知道?那 “double pi = 3.14159;” 后面的字面 值 不属于常量表达式 吗 ? 这 是个疑 问 ,先 记 下来。
引用:枚 举类 型的 对 象的初始化或 赋值 ,只能通 过 其枚 举 成 员 或同一枚 举类 型的其它 对 象来 进 行。
笔 记 : 这 跟 现实 生活中的 “ 做人要 专 一 ” 有点相似,呵呵。 简单 地 说 ,你 选择 了用名字来代替数 值 ,那就得始 终 如一地使用名字,不可以用数 值 或其它表达式。 比如 “weekday Today = Sunday;” 不可写成 “weekday Today = 0;” , 虽 然 Sunday 就是 0 。
标题:
:
typedef
长
期以来,我一直在疑惑:
typedef
这
个
词
要它干什
么
?因
为
没有它我照
样
可以完成所有任
务
。而有了它我反而
觉
得无法适
应
。比如
“UINT i;”
,
为
什
么
不写作
“unsigned int i;”
?本
书
用
简
短的三句
话
告
诉
我它存在的意
义
:
引用: typedef 通常被用于以下三 种 目的: 为 了 隐 藏特定 类 型的 实现 ,更 强调 使用 类 型的目的; 简 化 复杂 的 类 型定 义 ,使其更易理解;允 许 一 种类 型用于多个目的,同 时 使得 每 次使用 该类 型的目的明确。
笔 记 :第三 种 目的 对 我的启 发 特大。以后就 “typedef unsigned int age;” ,呵呵。
引用: typedef 通常被用于以下三 种 目的: 为 了 隐 藏特定 类 型的 实现 ,更 强调 使用 类 型的目的; 简 化 复杂 的 类 型定 义 ,使其更易理解;允 许 一 种类 型用于多个目的,同 时 使得 每 次使用 该类 型的目的明确。
笔 记 :第三 种 目的 对 我的启 发 特大。以后就 “typedef unsigned int age;” ,呵呵。
标题:
:引用
引用是
C++
的特色,一般用在函数的参数中。按有些
书
本的
说
法,叫
“
普通
变
量的用法,指
针变
量的效果
”
。
书
中本
节
没有
讲诉
引用在函数参数中的用法,只提了
“
给变
量起个
别
名
”
这
一个用
处
(
毕
竟本
书
才
开头
)。
说实
在的,如果撇
开
函数参数,
还
真想不到引用有什
么
用
处
。
引用 这 个概念本身也不 难 理解(除了 对 C 程序 员 来 说 有些不 习惯 以外),但是引用的符号却增加了理解它的 难 度,我 经 常在 论坛 上看到有初学者 对 “&” 和 “*” 两个符号的疑惑,他 们问 的 问题 可以 说 非常基 础 ,但却表 现 出了 这 个 问题 的 难 以理解的特点:
C 语 言中的指 针 已 经够复杂 的了,加再上一个引用,引用与指 针 有着千 丝 万 缕 的 联 系, 这 就算了,而且 还 用了 “&” 这 个符号。真 让 初学者忙昏了 头 。呵呵。下面四行程序,用到了两个 “&” 和两个 “*” ,但是它 们 的意 义 却全然不同:
int a;
int &b = a;//& 用在定 义 中 仅 表示 变 量的性 质为 引用
int *c = &a;//* 用在定 义 中 仅 表示 变 量的性 质为 指 针 , & 用在表达式中表示取地址
int d = *c;//* 用天表达式中表示取指 针变 量所指的 变 量的 值
写下以上文字,我 觉 得有些越 权 了。 这 些内容估 计 在本 书 后面会 详谈 的,我心急了点 。
引用 这 个概念本身也不 难 理解(除了 对 C 程序 员 来 说 有些不 习惯 以外),但是引用的符号却增加了理解它的 难 度,我 经 常在 论坛 上看到有初学者 对 “&” 和 “*” 两个符号的疑惑,他 们问 的 问题 可以 说 非常基 础 ,但却表 现 出了 这 个 问题 的 难 以理解的特点:
C 语 言中的指 针 已 经够复杂 的了,加再上一个引用,引用与指 针 有着千 丝 万 缕 的 联 系, 这 就算了,而且 还 用了 “&” 这 个符号。真 让 初学者忙昏了 头 。呵呵。下面四行程序,用到了两个 “&” 和两个 “*” ,但是它 们 的意 义 却全然不同:
int a;
int &b = a;//& 用在定 义 中 仅 表示 变 量的性 质为 引用
int *c = &a;//* 用在定 义 中 仅 表示 变 量的性 质为 指 针 , & 用在表达式中表示取地址
int d = *c;//* 用天表达式中表示取指 针变 量所指的 变 量的 值
写下以上文字,我 觉 得有些越 权 了。 这 些内容估 计 在本 书 后面会 详谈 的,我心急了点 。
标题:
:
const
常量的作用域
仅为
一个
CPP
文件
如果将
变
量定
义
放在任何
{}
的外面,
则该变
量是全局的,
这
个
规则
不适用于
const
常量。
以前 虽 然心里 隐隐约约 有 这 个感 觉 ,但是从未正面考 虑过这 个 问题 。之所以 隐隐约约 有此感 觉 ,是因 为 我 认为编译 器并不 为 const 常量 开 劈内存空 间 。
我曾 经专门 做 过测试 :程序如下:
const int i = 5;
int *p;
p = (int *)&i;
cout << *p << "/t" << i << endl;
(*p)++;
cout << *p << "/t" << i << endl;
测试结 果 发现 const 常量也是可以通 过 某些途径改 变 其 值 的,但是改 变 不起作用。我的解 释 是, 编 程器在生成机器 码时 将所有的 i 直接替 换 成了 5 。使得上面两个 cout 语 句都成了 “cout << *p << "/t" << 5 << endl;” 。
回想起以前做 过 的 测试 ,便不 难 理解 “const 常量的作用域 仅仅为 定 义 它的 CPP 文件 ” ,因 为编译 器是 对每 个 CPP 文件 单 独 编译 的,只有在 连 接 时 才会 去理会 CPP 之 间 的 关 系。 虽 然本 书 才看了个 开头 , 书 中 还说 “ 我 们 将会在 2.9.1 节 看到 为 何 const 对 象局部于文件 创 建 ” 。我不打算跳 跃 式地 阅读 本 书 ,所以,我 还 是先保留我自己的理解吧。
const 常量也 还 是可以成 为 全局常量的,方法是在定 义 的 时 候就加上 extern 。看到 这 里,我 终 于明白了《 extern 的困惑》中的困惑:原来,那 种 “ 有些多余,而且增加了出 错 的可能性 ” 的做法在常量的 处 理上派上了用 场 。
以前 虽 然心里 隐隐约约 有 这 个感 觉 ,但是从未正面考 虑过这 个 问题 。之所以 隐隐约约 有此感 觉 ,是因 为 我 认为编译 器并不 为 const 常量 开 劈内存空 间 。
我曾 经专门 做 过测试 :程序如下:
const int i = 5;
int *p;
p = (int *)&i;
cout << *p << "/t" << i << endl;
(*p)++;
cout << *p << "/t" << i << endl;
测试结 果 发现 const 常量也是可以通 过 某些途径改 变 其 值 的,但是改 变 不起作用。我的解 释 是, 编 程器在生成机器 码时 将所有的 i 直接替 换 成了 5 。使得上面两个 cout 语 句都成了 “cout << *p << "/t" << 5 << endl;” 。
回想起以前做 过 的 测试 ,便不 难 理解 “const 常量的作用域 仅仅为 定 义 它的 CPP 文件 ” ,因 为编译 器是 对每 个 CPP 文件 单 独 编译 的,只有在 连 接 时 才会 去理会 CPP 之 间 的 关 系。 虽 然本 书 才看了个 开头 , 书 中 还说 “ 我 们 将会在 2.9.1 节 看到 为 何 const 对 象局部于文件 创 建 ” 。我不打算跳 跃 式地 阅读 本 书 ,所以,我 还 是先保留我自己的理解吧。
const 常量也 还 是可以成 为 全局常量的,方法是在定 义 的 时 候就加上 extern 。看到 这 里,我 终 于明白了《 extern 的困惑》中的困惑:原来,那 种 “ 有些多余,而且增加了出 错 的可能性 ” 的做法在常量的 处 理上派上了用 场 。
标题:
:
变
量的作用域
作用域
这
个概念是程序
员
都耳熟能
详
的知
识
点。
这
部分知
识
我几乎可以跳
过
,不
过
我
还
是
认
真
阅读
了相
关
内容。
阅读过
程中
还
是有体会的:
有无数 书 本曾 经 提醒 过 我:少用(尽量不用)全局 变 量,多用局部 变 量。其 实 ,即使是局部 变 量,也 还 有作用域大小的,局部 变 量的作用域也是越小越好。原因自然和少用全局 变 量一个道理。正如 书 中所言: “ 通常把一个 对 象定 义 放在它首次使用的地方是一个很好的 办 法。 ”
以往,我使用局部 变 量 总 是把它放在函数的 开头 ,表示 这 些 变 量在本函数中起作用。 唯一的例外是 for 语 句的循 环变 量( for (int i=0; i<Max; i++); )。以后我就改改,把局部 变 量的作用域 缩 小到 仅仅 用到它的 语 句 块 。
有无数 书 本曾 经 提醒 过 我:少用(尽量不用)全局 变 量,多用局部 变 量。其 实 ,即使是局部 变 量,也 还 有作用域大小的,局部 变 量的作用域也是越小越好。原因自然和少用全局 变 量一个道理。正如 书 中所言: “ 通常把一个 对 象定 义 放在它首次使用的地方是一个很好的 办 法。 ”
以往,我使用局部 变 量 总 是把它放在函数的 开头 ,表示 这 些 变 量在本函数中起作用。 唯一的例外是 for 语 句的循 环变 量( for (int i=0; i<Max; i++); )。以后我就改改,把局部 变 量的作用域 缩 小到 仅仅 用到它的 语 句 块 。
标题:
:
extern
的困惑
extern
用来告
诉
程序:你不用
为
我的
变
量
开
劈内存空
间
,你只要知道
这
个
变
量
别处
已
经
声明
过
了。所以,我
总
是在程序包含多个
CPP
文件
时
才
这样
用:
1 、在某一个 CPP 文件中直接定 义变 量,如 int i = 0;
2 、其它 CPP 文件中声明 变 量,如 extern int i;
但是, 书 中介 绍 exturn 还 可以用来定 义 ,如: extern double pi = 3.1416; 特点是 该 extern 语 句包含 变 量的初始化。
我 觉 得 C++ 标 准 这样 做有些多余,而且增加了出 错 的可能性。因 为 “extern double pi = 3.1416;” 完全可以用 “double pi = 3.1416;” 来代替, 这样 做可以 让 定 义 与声明划清界 线 。 毕 竟定 义 只能有一次,而声明可以无数次。如果没有 这 个特性,可以 让 程序 员简单记为 “ 不 带 extern 的只能有一次, 带 extern 的可以有无数次,而且 extern 同 时 不能指定初始 值 。 ” 这 一特性的支持,使原本 简单 的 规则变 得 复杂 ,但没有 带 来灵活(众所周知 , C++ 的 复杂 是以高度灵活 为补尝 的)。
这 个段落 还让 我 认识 到了我以前使用 extern 的不足。我以往在同一个 CPP 文件中只使用一次 extern ,所以我往往是在文件 头 部用 extern 语 句来声明一下 变 量, 这样 做 虽 然没有什 么错 ,但却会 导 致上下翻 查 :有 时 在文件的某一 处 用到 变 量 时 ,想看一下它的声明,不得不把 滚动 条拖到 顶 上去 查 看。如果在用到它的段落 开头处 再声明,明 显 比 顶 部声明要好一些 。
1 、在某一个 CPP 文件中直接定 义变 量,如 int i = 0;
2 、其它 CPP 文件中声明 变 量,如 extern int i;
但是, 书 中介 绍 exturn 还 可以用来定 义 ,如: extern double pi = 3.1416; 特点是 该 extern 语 句包含 变 量的初始化。
我 觉 得 C++ 标 准 这样 做有些多余,而且增加了出 错 的可能性。因 为 “extern double pi = 3.1416;” 完全可以用 “double pi = 3.1416;” 来代替, 这样 做可以 让 定 义 与声明划清界 线 。 毕 竟定 义 只能有一次,而声明可以无数次。如果没有 这 个特性,可以 让 程序 员简单记为 “ 不 带 extern 的只能有一次, 带 extern 的可以有无数次,而且 extern 同 时 不能指定初始 值 。 ” 这 一特性的支持,使原本 简单 的 规则变 得 复杂 ,但没有 带 来灵活(众所周知 , C++ 的 复杂 是以高度灵活 为补尝 的)。
这 个段落 还让 我 认识 到了我以前使用 extern 的不足。我以往在同一个 CPP 文件中只使用一次 extern ,所以我往往是在文件 头 部用 extern 语 句来声明一下 变 量, 这样 做 虽 然没有什 么错 ,但却会 导 致上下翻 查 :有 时 在文件的某一 处 用到 变 量 时 ,想看一下它的声明,不得不把 滚动 条拖到 顶 上去 查 看。如果在用到它的段落 开头处 再声明,明 显 比 顶 部声明要好一些 。
标题:
:
变
量初始化
int ival(1024);//
直接初始化
int ival = 1024;// 复 制初始化
以前的我常用第二 种 用法,原因很 简单 :从来没 见过 第一 种 用法。直到后来学 习 了林 锐 博士的《高 质 量 C/C++ 编 程指南》。
那本 书 在 讲类 的构造 时说 道: CMyClass b = a; 这种 形式看起来像 赋值 , 实际 上 调 用的是拷 贝 构造函数。
那本 书给 我的感 觉仅仅 停留在 类变 量的初始化中,一直没有 过 渡到内置 变 量 类 型。直到后来,我在用 VC++.NET 的向 导 功能 编 程序 时 ,才 发现 向 导 帮我 产 生了 类 似于 “int i(100);” 这种语 法来初始化。
本 书 重点 强调 :初始化不是 赋值 ,
引用:当定 义 没有初始化的 变 量 时 ,系 统 有 时 候会帮我 们 初始化 变 量。 这时 ,系 统 提供什 么样 的 值 取决于 变 量的 类 型,也取决于 变 量定 义 的位置。 …… 内置 类 型 变 量是否初始化取决于 变 量定 义 的位置。在函数体外定 义 的 变 量都初始化 为 0 ,在函数体内定 义 的内置 类 型 变 量不 进 行自 动 初始化。 …… ( 类类 型)通 过 定 义 一个特殊的构造函数即默 认 构造函数来 实现 的。 这 个构造函数被称作默 认 构造函数,是因 为 它是 “ 默 认 ” 运行的。 …… 不管 变 量在哪里定 义 ,默 认 构造函数都会被使用。
笔 记 :林 锐 博士的《高 质 量 C/C++ 编 程指南》中 说 ,如果 类 没有定 义 无参数构造函数或拷 贝 构造函数,系 统 会自 动产 生 这 两个构造函数,它 们 采用最 简单 的 “ 值传递 ” 和 “ 位拷 贝 ” 来完成构造。
int ival = 1024;// 复 制初始化
以前的我常用第二 种 用法,原因很 简单 :从来没 见过 第一 种 用法。直到后来学 习 了林 锐 博士的《高 质 量 C/C++ 编 程指南》。
那本 书 在 讲类 的构造 时说 道: CMyClass b = a; 这种 形式看起来像 赋值 , 实际 上 调 用的是拷 贝 构造函数。
那本 书给 我的感 觉仅仅 停留在 类变 量的初始化中,一直没有 过 渡到内置 变 量 类 型。直到后来,我在用 VC++.NET 的向 导 功能 编 程序 时 ,才 发现 向 导 帮我 产 生了 类 似于 “int i(100);” 这种语 法来初始化。
本 书 重点 强调 :初始化不是 赋值 ,
引用:当定 义 没有初始化的 变 量 时 ,系 统 有 时 候会帮我 们 初始化 变 量。 这时 ,系 统 提供什 么样 的 值 取决于 变 量的 类 型,也取决于 变 量定 义 的位置。 …… 内置 类 型 变 量是否初始化取决于 变 量定 义 的位置。在函数体外定 义 的 变 量都初始化 为 0 ,在函数体内定 义 的内置 类 型 变 量不 进 行自 动 初始化。 …… ( 类类 型)通 过 定 义 一个特殊的构造函数即默 认 构造函数来 实现 的。 这 个构造函数被称作默 认 构造函数,是因 为 它是 “ 默 认 ” 运行的。 …… 不管 变 量在哪里定 义 ,默 认 构造函数都会被使用。
笔 记 :林 锐 博士的《高 质 量 C/C++ 编 程指南》中 说 ,如果 类 没有定 义 无参数构造函数或拷 贝 构造函数,系 统 会自 动产 生 这 两个构造函数,它 们 采用最 简单 的 “ 值传递 ” 和 “ 位拷 贝 ” 来完成构造。
标题:
:
变
量和
变
量名
引用:
对
象是内存中具有
类
型的区域。
笔 记 : 这 句 话说 得很直白,也只有 这样 面向 C++ 熟 练 工的 书 才可以 这样说 , 毕 竟初学者不知道内存与 变 量的 关 系,或者 还 没考 虑 到。
笔 记 : 这 句 话说 得很直白,也只有 这样 面向 C++ 熟 练 工的 书 才可以 这样说 , 毕 竟初学者不知道内存与 变 量的 关 系,或者 还 没考 虑 到。
引用:
C++
还
保留了一些
词
用作各操作符的替代名。
这
些替代名用于支持某些不支持
标
准
C++
操作符号集的字符集。它
们
也不能用作
标识
符(此
处
指
变
量名,
偷
猫
标
)。
一般的 书 只提到 C++ 变 量名不可以使用 关键 字,本 书还额 外提了 这样 一句,然后列出一个表,有 and 、 bitand 、 compl 、 not_eq 、 or_eq 、 xor_eq 、 and_eq 、 bitor 、 not 、 or 、 xor 。不 过 据我在 VC++.NET 上 测试 , 这 些是可以用作 变 量 标识 符的。
一般的 书 只提到 C++ 变 量名不可以使用 关键 字,本 书还额 外提了 这样 一句,然后列出一个表,有 and 、 bitand 、 compl 、 not_eq 、 or_eq 、 xor_eq 、 and_eq 、 bitor 、 not 、 or 、 xor 。不 过 据我在 VC++.NET 上 测试 , 这 些是可以用作 变 量 标识 符的。
都
说变
量名可以含
“_”
,
还
可以用
“_”
开头
,但我一直没有
试过
光光用一个
“_”
来做
变
量名,正好
习题
里有,我就
试
了一下,果然可以的
。
标题:
:
cout << (wchar_t
类
型
变
量
)
体
验
《
C++ Primer
》
说
了,字符常量或字符串常量前加
L
,表示
wchat_t
类
型,于是我
试
了一下:
程序如下:
char a = "a";
wchar_t b = L"a";
cout << a << endl;
cout << b << endl;
结 果如下:
a
0012FEBC
晕 ,怎 么 出 现这 个 结 果?
让 我再 试 。
char a = "a";
wchar_t b = L"a";
cout << a << endl;
cout << b << endl;
结 果如下:
a
0012FEBC
晕 ,怎 么 出 现这 个 结 果?
让 我再 试 。
程序如下:
char a = 'a';
wchar_t b = L'a';
cout << a << endl;
cout << b << endl;
结 果如下:
a
97
char a = 'a';
wchar_t b = L'a';
cout << a << endl;
cout << b << endl;
结 果如下:
a
97
由此可
见
,
cout
的
<<
运算符没有
对
wchat_t
的重
载
(或不健全)
。
标题:
:内置
类
型之精度
选择
引用:
……
大多数通用机器都是使用和
long
类
型一
样长
的
32
位来表示
int
类
型。整型运算
时
,用
32
位表示
int
类
型和用
64
位表示
long
类
型的机器会出
现应该选择
int
类
型
还
是
long
类
型的
难题
。在
这
些机器上,用
long
类
型
进
行
计
算所付出的运行
时
代价
远远
高于用
int
类
型
进
行同
样计
算的代价。
……
决定使用哪
种
浮点型就容易多了:使用
double
类
型基本上不会有
错
。在
float
类
型中
隐
式的精度
损
失是不能忽
视
的,而
double
类
型精度代价相
对
于
float
类
型精度代价可以忽略。
事
实
上,有些机器上,
double
类
型比
float
类
型的
计
算要快和多。
long double
类
型提供的精度通常没有必要,而且
还
需要承担
额
外的运行代价
。
标题:
:内置
类
型之
int
和
bool
第一部分,第二章
引用: C++ 标 准 规 定了 每 个算 术类 型的最小存 储 空 间 ,但它并不阻止 编译 器使用更大的存 储 空 间 ,事 实 上, 对 于 int 类 型,几乎所有的 编译 器使用的存 储 空 间 都比所要求的大。
笔 记 :确 实 如此, VC++ 中 int 和 long 是一 样 大。 VC++.NET 增加了 对 _int64 的支持。
引用:字符 类 型有两 种 : char 和 wchar_t , wchar_t 类 型用于 扩 展字符集,比如 汉 字和日 语 。
笔 记 :我怎 么 不知道 wchar_t ?我自己用包含 汉 字的字符串 时 用的也是 char 。 :(
看到 bool 型,我心里 对 VC++ 有些气 愤 。因 为 VC++ 里有一个 BOOL 宏。它的原型 为 “typedef int BOOL” 。既然 C++ 标 准已 经 有 bool 型, VC++ 加入 BOOL 的用意很明 显 :迎合更多的 编 程 习惯 。但是,即使非要增加 对 BOOL 的支持,我 认为 原型 应该这样 : “typedef bool BOOL” , 这样 更易于理解。
当然了, 长 期以来我一直没有注意到 bool 确 实 也不 应该 。 但是正是 BOOL 的存在,阻碍了我 对 bool 的理解。
引用: C++ 标 准 规 定了 每 个算 术类 型的最小存 储 空 间 ,但它并不阻止 编译 器使用更大的存 储 空 间 ,事 实 上, 对 于 int 类 型,几乎所有的 编译 器使用的存 储 空 间 都比所要求的大。
笔 记 :确 实 如此, VC++ 中 int 和 long 是一 样 大。 VC++.NET 增加了 对 _int64 的支持。
引用:字符 类 型有两 种 : char 和 wchar_t , wchar_t 类 型用于 扩 展字符集,比如 汉 字和日 语 。
笔 记 :我怎 么 不知道 wchar_t ?我自己用包含 汉 字的字符串 时 用的也是 char 。 :(
看到 bool 型,我心里 对 VC++ 有些气 愤 。因 为 VC++ 里有一个 BOOL 宏。它的原型 为 “typedef int BOOL” 。既然 C++ 标 准已 经 有 bool 型, VC++ 加入 BOOL 的用意很明 显 :迎合更多的 编 程 习惯 。但是,即使非要增加 对 BOOL 的支持,我 认为 原型 应该这样 : “typedef bool BOOL” , 这样 更易于理解。
当然了, 长 期以来我一直没有注意到 bool 确 实 也不 应该 。 但是正是 BOOL 的存在,阻碍了我 对 bool 的理解。
标题:
:第一章:快速入
门
第一章:快速入
门
本章的存在使本 书变 得不像一本 “ 规 范 书 ” ,似乎成了 “ 入 门书 ” , 这 可能是后来版本新加入的内容。以至于 这 一章 节 被排除在任何一个 “ 部分 ” 之外。
本章 “ 无厘 头 ” 地 简 要介 绍 了 cin 、 cout 、注 释 、 while 、 for 、 if 等概念。 这么 多 东 西, 每 一个都介 绍 点皮毛,然后 组 合成一个 综 合 实 例。
我称其 为 “ 无厘 头 ” 有以下原因:如果本 书 面 对 不了解 C++ 的 读 者,那 么这 一章似乎是有用的,但是 C++ 的入 门 者使用本 书显 然很 难 入 门 ,我不了解国外的情况怎 样 ,至少我身 边 的人是 这样 ;如果本 书 面 对 已 经 了解 C++ 的 读 者,那 么 , 这 些 东 西都不用介 绍 , 读 者也可以看懂那个 “ 综 合 实 例 ” ,而且所 谓 的 “ 综 合 实 例 ” 也没有存在的必要。
本章的存在使本 书变 得不像一本 “ 规 范 书 ” ,似乎成了 “ 入 门书 ” , 这 可能是后来版本新加入的内容。以至于 这 一章 节 被排除在任何一个 “ 部分 ” 之外。
本章 “ 无厘 头 ” 地 简 要介 绍 了 cin 、 cout 、注 释 、 while 、 for 、 if 等概念。 这么 多 东 西, 每 一个都介 绍 点皮毛,然后 组 合成一个 综 合 实 例。
我称其 为 “ 无厘 头 ” 有以下原因:如果本 书 面 对 不了解 C++ 的 读 者,那 么这 一章似乎是有用的,但是 C++ 的入 门 者使用本 书显 然很 难 入 门 ,我不了解国外的情况怎 样 ,至少我身 边 的人是 这样 ;如果本 书 面 对 已 经 了解 C++ 的 读 者,那 么 , 这 些 东 西都不用介 绍 , 读 者也可以看懂那个 “ 综 合 实 例 ” ,而且所 谓 的 “ 综 合 实 例 ” 也没有存在的必要。
不
过
有一个小小的收
获
:
int main()
{
return -1;
}
如果返回 值为 -1 , Windows 的 CMD 中运行也没有任何 额 外信息, 说 明 Windows 并不 报 告运行失 败 。
int main()
{
return -1;
}
如果返回 值为 -1 , Windows 的 CMD 中运行也没有任何 额 外信息, 说 明 Windows 并不 报 告运行失 败 。
标题:
:《〈
C++ Primer
〉
阅读
笔
记
》前
言
在
读
本
书
之前,我已
经
有
过
一段
编
写
C++
程序的
历
史,如果
连
C
语
言也算在内,可以追溯到十年前。用
BASIC
语
言
编
程序的
历
史
则
有十四年
(1992-2006)
。
长 期 编 程序中所使用的参考 书 无非有两 种 :介 绍 算法的 书 和介 绍语 法的 书 。我所 买 的参考 书 往往是同 时 介 绍 两者的。而 对语 法的介 绍 , 则 只是基于某一个 编译 器。
于是, 这 十年来,我所学 习 的 “C/C++” ,从本 质 上 说 只是 Turbo C 和 Visual C++ , 对 C/C++ 本身的理解也是被 编译 器 过滤 的内容。不是我不想去了解 C/C++ 的本 质 ,只是我 对 C/C++ 的 “ 法典 ” 有着与生 俱 来的恐惧。
这 个恐惧直到我 发现 了《 C++ Primer 中文版》,当我捧起 这 本 C++ 的 “ 圣 经 ” 时 ,我 终 于能理解 为 什 么每 有几十万人冒着被 踩 死的危 险 前去朝圣。
请 允 许 我用 “ 圣 经 ” 来比 喻这 本 书 ,我知道 这样 并不合适,因 为绝 大多数中国人并不知道 “ 圣 经 ” 的地位,但是我搜遍大 脑 的 每 一个角落也找不到其它合适的比 喻 。 这 源于中国人缺乏信仰。
在半年前,我曾 经带 着无限的敬仰 阅读 了林 锐 博士的《高 质 量 C/C++ 编 程指南》,并且及 时 培 养 / 修正了我的 编 程 习惯 。当然了,正如《 C++ Primer 》所言: “ 什 么 是 C 或 C++ 程序的正确格式存在着无休止的争 论 ……” 。所以,我所修正的只是 “ 自由体 ” ,而不是与林 锐 矛盾的方面。令我感到欣慰的是,《 C++ Primer 》居然也用一定的笔默来 讲 格式与 风 格,不同的是,它同 时 介 绍 几 种风 格,然后作出一个略 带倾 向性的建 议 。同 时 , 书 中 还 告 诫读 者: “ 一旦 选择 了某 种风 格,就要始 终 如一地使用。 ”
从 现 在起,我就要捧起 这 一本四五厘米厚、七百多 页 的圣 经 ,在 阅读过 程中, 难 免会有重点、要点要 记录 。我比 较爱书 ,不愿在 书 上做 标记 ,只好 选择 了 BLOG 这种 形式来做 读书 笔 记 。
长 期 编 程序中所使用的参考 书 无非有两 种 :介 绍 算法的 书 和介 绍语 法的 书 。我所 买 的参考 书 往往是同 时 介 绍 两者的。而 对语 法的介 绍 , 则 只是基于某一个 编译 器。
于是, 这 十年来,我所学 习 的 “C/C++” ,从本 质 上 说 只是 Turbo C 和 Visual C++ , 对 C/C++ 本身的理解也是被 编译 器 过滤 的内容。不是我不想去了解 C/C++ 的本 质 ,只是我 对 C/C++ 的 “ 法典 ” 有着与生 俱 来的恐惧。
这 个恐惧直到我 发现 了《 C++ Primer 中文版》,当我捧起 这 本 C++ 的 “ 圣 经 ” 时 ,我 终 于能理解 为 什 么每 有几十万人冒着被 踩 死的危 险 前去朝圣。
请 允 许 我用 “ 圣 经 ” 来比 喻这 本 书 ,我知道 这样 并不合适,因 为绝 大多数中国人并不知道 “ 圣 经 ” 的地位,但是我搜遍大 脑 的 每 一个角落也找不到其它合适的比 喻 。 这 源于中国人缺乏信仰。
在半年前,我曾 经带 着无限的敬仰 阅读 了林 锐 博士的《高 质 量 C/C++ 编 程指南》,并且及 时 培 养 / 修正了我的 编 程 习惯 。当然了,正如《 C++ Primer 》所言: “ 什 么 是 C 或 C++ 程序的正确格式存在着无休止的争 论 ……” 。所以,我所修正的只是 “ 自由体 ” ,而不是与林 锐 矛盾的方面。令我感到欣慰的是,《 C++ Primer 》居然也用一定的笔默来 讲 格式与 风 格,不同的是,它同 时 介 绍 几 种风 格,然后作出一个略 带倾 向性的建 议 。同 时 , 书 中 还 告 诫读 者: “ 一旦 选择 了某 种风 格,就要始 终 如一地使用。 ”
从 现 在起,我就要捧起 这 一本四五厘米厚、七百多 页 的圣 经 ,在 阅读过 程中, 难 免会有重点、要点要 记录 。我比 较爱书 ,不愿在 书 上做 标记 ,只好 选择 了 BLOG 这种 形式来做 读书 笔 记 。