如何不用-C---编程-全-
如何不用 C++ 编程(全)
原文:How Not to Program in C++
译者:飞龙
第一部分:程序
章节列表
第一章: 开端
第二章: 起步错误
第三章: 单字符奇迹
第四章: 日常问题
第五章: C 语言代码,C 语言代码断裂
第六章: 提前断裂
第七章: 毫无品味的班级
第八章: 专家的困惑
第九章: 前往地狱之路
第十章: 几个实用的程序
第十一章: 线程化,嵌入式——令人畏惧的
第二部分:提示
第三部分:答案
第一章:开始
概述
在最初,有 ENIAC Mark I。有一天,一个操作员偶然注意到机器出现了故障,并追踪到一只飞进机器并被继电器接触打死的蛾子。
她取出了蛾子,将其贴在日志簿中,并做了记录:“在系统中发现了一个错误。”因此,这是第一个计算机错误。^([1])
我对计算机错误的了解是在很久以后。我 11 岁时编写了我的第一个程序。这个程序只有一条汇编指令长。程序将 2 + 2 相加。结果是 2。程序只有一条指令长,但它仍然有一个错误。
本章包含了一些“第一次”:第一次我熬夜到凌晨 2 点来定位一个错误(程序 3),第一次我在进行的第一次 C 编程测试中的问题(程序 2),当然,任何编程书中的第一个程序,“Hello World”。
![]() |
|---|
在 ATM 出现之前,你必须去银行手动存款。通常你会使用你支票簿背面的预印存款纸条。这些纸条底部印有你的账号,用磁性油墨书写。
如果你用完了纸条,银行会为你提供一张。这张纸条底部没有写上号码,所以当它被银行的自动机器处理时,机器就会将其踢出,然后由职员手动输入账号。
一个骗子打印了他自己的“通用”存款纸条版本。它看起来像正常的“通用”存款纸条,除了骗子的账号底部印有磁性油墨。
然后,他去了银行,将这些纸条滑入存放“通用”纸条的箱子中。
这个骗局是这样进行的:一位客户进入银行存款,并得到了一张经过处理的纸条。他填写了它并进行了存款。由于纸条包含一个账号,计算机自动处理了它,并将存款存入底部写明的账户。手写的账号被忽略了。换句话说,我们的骗子正在劫持存款。
被分配到这个案件的侦探感到困惑。存款正在消失,没有人知道原因。他将问题缩小到银行内的存款。他决定尝试进行大量存款,看看会发生什么。由于他使用的是自己的钱,存款必须非常小。非常非常小。实际上,每笔存款是 6 分。
侦探花了一周时间存款。他会去银行,填写一张纸条,排队,存入 6 分的存款,填写一张新的纸条,排队,存入 6 分的存款,以此类推。职员们认为他疯了。有一天,他的一笔存款消失了。所以他让银行查阅其记录,看看那天是否有人存入了 6 分的存款。有人存了,骗子被抓住了。
![]() |
|---|
^([1])尽管人们认为这是第一次将单词 bug 与 computing machine 结合使用,但实际上并非如此。在此之前,术语 bug 已经存在很长时间,用来描述各种机械故障。但为什么让真相破坏一个好故事呢?
程序 1:Hello World
"Hello World"似乎几乎是每本编程书中的第一个程序,这个也不例外。但这个程序是出问题的。
你怎么能把像"Hello World"这样简单的东西弄坏呢?看看下面:
1 /************************************************
2 * The "standard" hello world program. *
3 *************************************************/
4 #include <iostream>
5
6 void main(void)
7 {
8 std::cout << "Hello world!\n";
9 }
(下一个提示 228。答案 6。)
![]() |
|---|
| 用户: |
| 助手: |
| 用户: |
| 助手: |
| 用户: |
| 助手: |
| 用户: |
| 助手: |
| 用户: |
| 助手: |
| 用户(惊讶): |
![]() |
程序 2:教师的难题
我曾经教过 C 语言编程。这是我从第一次测试中出的第一个问题。
这个想法很简单:我想看看学生是否知道自动变量:
16 int i = 0;
和一个静态变量:
26 static int i = 0;
然而,考试结束后,我不得不做出一个尴尬的承认:如果我做了自己的测试,我也会错过这个问题。所以我不得不站在所有人面前告诉他们,“对于问题#1,有两种方式可以获得满分。第一种是给出正确答案;另一种是给出我认为正确的答案。
那么正确答案是什么呢?
1 /***********************************************
2 * Test question: *
3 * What does the following program print? *
4 * *
5 * Note: The question is designed to tell if *
6 * the student knows the difference between *
7 * automatic and static variables. *
8 ***********************************************/
9 #include <stdio.h>
10 /***********************************************
11 * first -- Demonstration of automatic *
12 * variables. *
13 ***********************************************/
14 int first(void)
15 {
16 int i = 0; // Demonstration variable
17
18 return (i++);
19 }
20 /***********************************************
21 * second -- Demonstration of a static *
22 * variable. *
23 ***********************************************/
24 int second(void)
25 {
26 static int i = 0; // Demonstration variable
27
28 return (i++);
29 }
30
31 int main()
32 {
33 int counter; // Call counter
34
35 for (counter = 0; counter < 3; counter++)
36 printf("First %d\n", first());
37
38 for (counter = 0; counter < 3; counter++)
39 printf("Second %d\n", second());
40
41 return (o);
42 }
(下一个提示 139。答案 102。)
![]() |
|---|
一家教堂刚刚购买了它的第一台电脑,工作人员正在学习如何使用它。教堂秘书决定设置一个用于葬礼服务的标准信函。在需要填写逝者姓名的地方,她输入了单词"
有一天,有两个葬礼,首先是玛丽女士的,然后是埃德娜女士的。所以秘书使用全局替换将"
当牧师开始阅读包含使徒信经的部分时,他惊讶地看到,“由处女 Edna 所生。”
![]() |
|---|
程序 3:凌晨惊喜
这个程序是我的一位朋友在我们上大学时编写的。作业要求编写一个矩阵乘法例程。然而,该函数本身必须用汇编语言编写。为了使其尽可能快地运行,他使用了我为矩阵矢量化设计的算法。
为了测试系统,他在 SAIL ^([2])中编写了一个简短的测试函数。当我们测试程序时,得到了错误的答案。我们俩从晚上 8 点一直检查到第二天凌晨 2 点,翻阅了那段代码的每一行。当我们最终找到错误时,我们都忍不住笑出声来,因为那是一个如此愚蠢的错误。
下面的程序是那个著名代码的简化版本。它完全用一种语言(C)编写,并使用了一个更简单的乘法算法。但原始的 bug 仍然存在。这是怎么回事?
1 /**************************************************
2 * matrix-test -- Test matrix multiply *
3 **************************************************/
4 #include <stdio.h>
5
6 /**************************************************
7 * matrix_multiply -- Multiple two matrixes *
8 **************************************************/
9 static void matrix_multiply(
10 int result[3][3], /* The result */
11 int matrixl[3][3],/* One multiplicand */
12 int matrix2[3][3] /* The other multiplicand */
13 )
14 {
15 /* Index into the elements of the matrix */
16 int row, col, element;
17
18 for(row = 0; row < 3; ++row)
19 {
20 for(col = 0; col < 3; ++col)
21 {
22 result[row][col] = 0;
23 for(element = 0; element < 3; ++element)
24 {
25 result[row][col] +=
26 matrix1[row][element] *
27 matrix2[element][col];
28 }
29 }
32 }
33 }
34
35 /************************************************
36 * matrix_print -- Output the matrix *
37 ************************************************/
38 static void matrix_print(
39 int matrix[3][3] /* The matrix to print */
40 )
41 {
42 int row, col; /* Index into the matrix */
43
44 for (row = 0; row < 3; ++row)
45 {
46 for (col = 0; col < 3; ++col)
47 {
48 printf("%o\t", matrix[row][col]);
49 }
50 printf("\n");
51 }
52 }
53
54 int main(void)
55 {
56 /* One matrix for multiplication */
57 int matrix_a[3][3] = {
58 {45, 82, 26},
59 {32, 11, 13},
60 {89, 81, 25}
61 };
62 /* Another matrix for multiplication */
63 int matrix_b[3][3] = {
64 {32, 43, 50},
65 {33, 40, 52},
66 {20, 12, 32}
67 };
68 /* Place to put result */
69 int result[3][3];
70
71 matrix_multiply(result, matrix_a, matrix_b);
72 matrix_print(result);
73 return (o);
74 }
75
(下一个提示 34。答案 53。)
^([2])SAIL 是用于 PDP-10 的旧系统编程语言。调试器被称为 BAIL。后来,创建了一个语言的不依赖机器版本,称为 MAIN SAIL。它比 C 语言早几年。
第二章:起步错误
我们都曾是新手程序员。当时,我们为了让最简单的程序编译而挣扎数小时。但我们当时年轻无知,犯了很多愚蠢的错误。现在我们是专业程序员,我们不再犯愚蠢的错误。我们犯的是聪明的错误(但我们称之为“专业错误”)。
在本章中,我们展示了多个程序,旨在提醒你早期的编程错误,从而让你重温那些你可能更愿意忘记的经历。
程序 4:典型的初始问题
一个经典的数学问题是将数字 1 加到 100。但是这个程序似乎得出了错误的答案:
1 /************************************************
2 * A program to sum the numbers from 1 to 100 *
3 * using a brute force algorithm. *
4 ************************************************/
5 #include <iostream>
6
7 int main()
8 {
9 int sum; // The running sum
10 int count; // The current number
11
12 for (count = 1; count <= 100; ++count)
13 sum += count;
14
15 std::cout <<
16 "The sum of the numbers " <<
17 "between 1 and 100 is " <<
18 sum << '\n';
19 return (0);
20 }
(提示 116。答案 51。)
![]() |
|---|
一家电子组装公司遇到了盗窃问题。数千个电子零件神秘消失。公司实施了多项新的安全措施,但短缺问题仍然持续。所有的零件都去哪里了?
最后,一个清洁工解决了这个谜团。他正在屋顶上更换灯泡时,偶然发现了三个鸟巢。鸟儿们从工厂的地板上取来了零件,并用它们来筑巢。据估计,每个鸟巢的价值为 10,000 美元。
![]() |
|---|
程序 5:第一次错误
每个新手程序员一开始都会学习简单的表达式以及如何打印它们。但下面的程序有点太简单了。问题是什么?
1 /************************************************
2 * A program to answer the question of a five *
3 * year old: *
4 * "What is 2 + 2?" *
5 ************************************************/
6 #include <iostream>
7
8 int main()
9 {
10 int result; // Result of the addition
11
12 result = 2+2;
13 std::cout << "The answer is " << result;
14 return (0);
15 }
(下一个提示 251。答案 43。)
![]() |
|---|
一个聪明的程序员想出了一个抢劫银行的方法。他从每个储户那里偷了大约 1/2 美分。当银行复利时,结果并不总是整数。例如,利息可能是 3.2 美分或 8.6 美分。银行通常会四舍五入这个数字,所以 3.2 变成了 3,8.6 变成了 9。结果是,大约一半的时间,数字会被向上四舍五入,另一半时间会被向下四舍五入。所以结果大致上是平衡的。
一个歪曲的程序员改变了算法,使其总是截断。因此,3.2 变成了 3,8.6 变成了 8。这留下很多分币的零头在浮动。程序员收集了这些零头,并将它们加到账户列表的最后一个名字上。由于他在 ZZYMOCK 的名字下开了一个账户,所以这个账户就是他的。
这个小偷非常聪明。他从每个人那里偷不到一美分。没有人注意到。毕竟,有多少人会检查他们的利息到小数点后最后一位?有多少人会检查他们的利息?
但这个人被抓住了。似乎 ZZYSKI 开了一个账户。现在他的名字是名单上的最后一个。当他收到他的第一份对账单时,他非常惊讶地发现他在一个 200 美元的账户上获得了 38,238.83 美元的利息。
![]() |
|---|
程序 6:必须要有我的空间
这是由一个人在第一周学习编程时编写的一个简短的经验性程序。它的设计目的是打印一个简单的答案。但事情并不完全按预期进行。
1 /************************************************
2 * Double a number. *
3 ************************************************/
4 #include <iostream>
5
6 int main(void)
7 {
8 int number; // A number to double
9
10 std::cout << "Enter a number:";
11 std::cin >> number;
12
13 std::cout << "Twice" << number << "is" <<
14 (number * 2) << '\n';
15 return (0);
16 }
(下一个提示 247。答案 23。)
![]() |
|---|
我曾经教过一段时间编程。当时,我对教学知之甚少,发现很难判断应该给学生布置多少作业。有一次,因为我的作业太难,我被沃斯堡警察拦了下来。这是真的。
我正在沃斯堡的街道上开车,停在红灯前。一辆警车停在我旁边。我看着警官,他看了我一会儿,然后示意我摇下车窗。我承认我有点担心。毕竟,我开的是一辆未修复的 1958 年雪佛兰,排气管已经掉落过三次了。
我按照指示摇下了窗户,他俯身对我说:“史蒂夫,你这周的作业太难了。”
那时我才知道,我的一个学生为沃斯堡警察局工作。不用说,我给了学生们额外的一周时间来提交他们的作业。
![]() |
|---|
程序 7:歪曲的方块
这是一个简短的程序,用于计算并打印从 1 到 5 的数字的平方。它足够简单,那么问题在哪里?
1 /************************************************
2 * squares -- Print the squares of the numbers *
3 * from 1 to 5\. *
4 ************************************************/
5 #include <iostream>
6
7 int main()
8 {
9 // An array for the squares
10 int array[5];
11
12 int i; // Index into the array
13
14 for (i = 1; i <= 5; ++i) {
15 array[i] = i*i;
16 }
17
18 for (i = 1; i <= 5; ++i) {
19 std::cout << i << " squared is " <<
20 array[i] << '\n';
21 }
22 return (0);
23 }
(下一个提示 103。答案 90。)
![]() |
|---|
美国公司计算机房附近发现:
ACHTUNG! ALLES LOOKENSPEEPERS!
计算机机器不适用于手指戳戳和抓抓。容易抓取弹簧装置,吹气装置和弹出软木塞,用尖刺火花。不适用于笨拙的人工作。橡胶颈部的视力保持者让棉花采摘的手放入口袋;放松并观看闪烁的灯光。
![]() |
|---|
程序 8:疯狂字符
新手程序员决定查看如何使用字符变量中的 if 语句。以下程序简单、明显,但却是错误的!
1 /************************************************
2 * Check the flag. *
3 ************************************************/
4 #include <iostream>
5
6 int main()
7 {
8 char ch; // The flag
9
10 ch = 0xFF; // Set the flag
11
12 // Check the flag
13 if (ch == 0xFF)
14 std::cout << "Success\n";
15 else
16 std::cout << "Fails\n";
17
18 return (0);
19 }
(下一个提示 131。答案 8。)
![]() |
|---|
在一家德国公司的计算机室附近找到:
注意
这间房间充满了特殊的电子设备。只有专家才能抓取手指并按电脑上的按钮!所以所有“左撇子”都请远离,不要打扰在这里工作的聪明才智。否则你会被赶出去,并被踢到别处!另外:请保持安静,只惊讶地看着闪烁的灯光。
![]() |
|---|
程序 9:无注释
这个程序计算三角形的面积。公式很简单,程序也很简单,很明显一切都在正常工作。但在这段代码中隐藏着一个惊喜:
1 /************************************************
2 * triangle -- Compute the area of a triangle *
3 ************************************************/
4 #include <iostream>
5 int main()
6 {
7 int base = 0; /* Base of the triangle */
8 int height = 0; /* Height of the triangle */
9
10 base = 5; /* Set the base of the triangle
11 height = 2; /* Set the height */
12
13 // Area of the triangle
14 int area = (base * height) / 2;
15
16 std::cout << "The area is " <<
17 area << std::endl;
18 return (0);
19 }
(下一个提示 41。答案 62。)
![]() |
|---|
一位系统管理员在处理一个网络路由器时遇到了很多麻烦。奇怪的错误代码,如“E6”和“B2”,出现在显示屏上。于是他联系了制造商,并连接到了现场服务。
| 系统管理员: | "你能告诉我错误代码 E6 是什么意思吗?" |
|---|---|
| 技术人员: | "通信线路 6 有短路。" |
| 系统管理员: | "那在哪里有文档说明?" |
| 技术人员: | "在技术参考手册中。" |
| 系统管理员: | "我们这里遇到了很多问题,你能给我传真一份那份手册的副本吗?" |
| 技术人员(不情愿地): "嗯,好吧。但是这是我唯一的一份副本,所以你得答应我传真回来。" | |
![]() |
|
程序 10:不尽人意的除法
这是一个简单的程序,旨在确定浮点数使用了多少有效数字。思路很简单:选择一个重复的分数,比如 1/3(0.333333),打印出来,看看能得到多少位数字。
然而,这个程序员的成果让他感到困惑。他知道电脑不可能那么愚蠢。那么发生了什么?
1 /************************************************
2 * divide -- Program to figure out how many *
3 * digits are printed in floating point *
4 * by print 1/3 or 0.333333\. *
5 ************************************************/
6 #include <iostream>
7
8 int main()
9 {
10 float result; // Result of the divide
11
12 result = 1/3; // Assign result something
13
14 std::cout << "Result is " << result << '\n';
15 return (0);
16 }
(下一个提示 292。答案 27。)
![]() |
|---|
一个气象服务电脑要求气象学家以英寸为单位输入降雨量。现在这些人习惯于处理百分之一英寸,所以当你问他们今天下了多少雨时,他们会说“50”,意思是 50/100 英寸,也就是半英寸。
但是,要输入这个数字到电脑里,你必须输入“0.50”。有一个人忘了这一点,把当天的降雨量输入为“50”。现在 50 英寸的雨量是很多。非常多的雨。然而,电脑捕捉到了这个错误,并发出了适当的消息:
Build an ark. Gather the animals two by two. . .
![]() |
|---|
程序 11:两个文件太多
这又是另一种做“Hello World”并搞砸的方法。发生了什么?
文件:sub.cpp
1 // The string to print
2 char str[] = "Hello World!\n";
文件:main.cpp
1 /************************************************
2 * print string -- Print a simple string. *
3 ************************************************/
4 #include <iostream>
5
6 extern char *str; // The string to print
7
8 int main()
9 {
10 std::cout << str << std::endl;
11 return (0);
12 }
(下一个提示 269。答案 7。)
![]() |
|---|
我认识的一个程序员以为他找到了永远不挨罚单的方法。他挑选的个性化车牌有三种选择:1) 0O0O0O,2) O0O0O0,和 3) I1I1I1。他认为如果警察看到这辆车,字母“O”和数字“0”看起来如此相似,几乎不可能正确地记下车牌号码。
很不幸,他的计划没有成功。发放车牌的 DMV 职员搞混了,结果他拿到了一块车牌,上面写着“OOOOOO”。
![]() |
|---|
程序 12:急急忙忙等待
这个程序所基于的代码是由我多年前在一家公司工作的资深系统程序员编写的。
它被设计用来通过串行线发送数据。尽管串行线能够每秒传输 960 个字符,但我们幸运地每秒只能得到 300 个字符。
为什么?
1 /************************************************
2 * send_file -- Send a file to a remote link *
3 * (Stripped down for this example.) *
4 ************************************************/
5 #include <iostream>
6 #include <fstream>
7 #include <stdlib.h>
8
9 // Size of a block
10 const int BLOCK_SIZE = 256;
11
12 /************************************************
13 * send_block -- Send a block to the output port*
14 ************************************************/
15 void send_block(
16 std::istream &in_file, // The file to read
17 std::ostream &serial_out // The file to write
18 )
19 {
20 int i; // Character counter
21
22 for (i = 0; i < BLOCK_SIZE; ++i) {
23 int ch; // Character to copy
24
25 ch = in_file.get();
26 serial_out.put(ch);
27 serial_out.flush();
28 }
29 }
30
31 int main()
32 {
33 // The input file
34 std::ifstream in_file("file.in");
35
36 // The output device (faked)
37 std::ofstream out_file("/dev/null");
38
39 if (in_file.bad())
40 {
41 std::cerr <<
42 "Error: Unable to open input file\n";
43 exit (8);
44 }
45
46 if (out_file.bad())
47 {
48 std::cerr <<
49 "Error: Unable to open output file\n";
50 exit (8);
51 }
52
53 while (! in_file.eof())
54 {
55 // The original program output
56 // a block header here
57 send_block(in_file, out_file);
58 // The original program output a block
59 // trailer here. It also checked for
60 // a response and resent the block
61 // on error
62 }
63 return (0);
64 }
(下一个提示 183。答案 65。)
![]() |
|---|
一位系统管理员习惯于在实际上安装升级至少两周前宣布升级已经安装。通常,在宣布的那天会有大量的投诉,比如,“我的软件刚刚崩溃,全都是因为你的升级。”管理员知道这不可能是因为升级,因为他还没有真正进行升级。
当他实际上安装升级(他通常秘密进行)时,随后收到的任何投诉可能都是合理的。
无线电爱好者也会使用这个技巧。他们会安装一座新的无线电塔,然后让它断开连接几周。这样邻居就有两周的时间来抱怨由新天线引起的电视干扰。
![]() |
|---|
程序 13:程序有点问题
为什么这个程序在某些数值上会失败?此外,这个程序除了它旨在说明的问题外,还存在一个错误。另一个问题在哪里?
1 /************************************************
2 * Billing -- Print out how much we owe *
3 * customers or they owe us. *
4 ************************************************/
5 #include <iostream>
6
7 // Number of pennies in a dollar
8 const int DOLLAR = 100;
9
10 /************************************************
11 * billing -- do the billing. *
12 * If the customer owes us money *
13 * -- output debt. *
14 * If we owe more than $100 *
15 * -- output credit. *
16 * Between $0 and $100 just ignore the *
17 * account. *
18 ************************************************/
19 int billing(
20 // Current balance (in cents)
21 const int balance
22 ) {
23 if (balance < 0)
24 if (balance < - (100*DOLLAR))
25 std::cout << "Credit " << -balance << endl;
26 else
27 std::cout << "Debt " << balance << endl;
28
29 return (0);
30 }
31
32 int main()
33 {
34 /* Test code */
35 billing(50);
36 billing(-10);
37 return (0);
38 }
(下一提示 44。答案 31。)
程序 14:位移编程
程序员知道左移等同于乘以 2 的幂。换句话说:
-
x << 1 等同于 x * 2 (2 = 2¹)
-
x << 2 等同于 x * 4 (4 = 2²)
-
x << 3 等同于 x * 8 (8 = 2³)
程序员使用这个技巧来快速执行简单的计算。但出了点问题:
1 /************ *********** ************ **********
2 * Simple syntax testing. *
3 ************************************************/
4 #include <iostream>
5
6 int main(void)
7 {
8 int x,y; // Two numbers
9
10 x = 1;
11
12 y = x<<2 + 1; // x<<2 = 4 so y = 4+1 = 5
13 std::cout << "Y=" << y << std::endl;
14 return (0);
15 }
(下一提示 266。答案 49。)
![]() |
|---|
一名黑客接到了一个编写程序的作业,该程序模拟了一个四功能计算器。作业要求程序能够进行加、减、乘、除运算。然而,作业没有指定使用什么类型的数字,所以黑客的程序使用了罗马数字(IV + III = VII)。还需要一份用户手册,但作业没有说明使用什么语言,所以程序员提供了一份详尽的手册——用拉丁文写的。
![]() |
|---|
程序 15:无词
以下程序旨在检查一个单词是否是关键字。为什么程序不起作用?
1 /************************************************
2 * test the keyword finding function: "keyword" *
3 ************************************************/
4 #include <cstring>
5 #include <iostream>
6
7 /************************************************
8 * keyword -- return true if a keyword found *
9 ************************************************/
10 bool keyword(
11 const char word[] // The work to look for
12 )
13 {
14 // A set of keywords
15 static const char *key_list[] = {
16 "bool",
17 "int",
18 "const",
19 NULL
20 };
21 int i; // Index into the list
22
23 // Look for the keyword
24 for (i = 0; key_list[i] != 0; ++i) {
25 if (std::strcmp(word, key_list[i]))
26 return (true);
27 }
28 return (false);
29 }
30 int main()
31 {
32 std::cout << "keyword(bool) = " <<
33 keyword("bool") << '\n';
34
35 std::cout << "keyword(sam) = " <<
36 keyword("sam") << '\n';
37 return (0);
38 }
(下一提示 294。答案 76。)
程序 16:慢但稳
为什么这个程序这么慢?在我的系统上复制文件需要一分钟三十四秒,而 Linux 的 cp 命令完成同样的操作却不到半秒。有什么方法可以使程序更快?
1 /************************************************
2 * copy input file to output file. *
3 ************************************************/
4 #include <iostream>
5 #include <unistd.h>
6 #include <fcntl.h>
7
8 int main() {
9 // The fd of the input file
10 int in_fd = open("file.in", O_RDONLY);
11
12 // The fd of the output file
13 int out_fd = open("file.out",
14 O_WRONLY|O_CREAT, 0666);
15
16 char ch; // Character to copy
17
18 if (in_fd < 0) {
19 std::cout <<
20 "Error could not open input file\n";
21 exit (8);
22 }
23
24 if (out_fd < 0) {
25 std::cout <<
26 "Error could not open output file\n";
27 exit (8);
28 }
29 while (1) {
30 if (read(in_fd, &ch, 1) != 1)
31 break;
32
33 write(out_fd, &ch, 1);
34 }
35 close(in_fd);
36 close(out_fd);
37 return (0);
38 }
(下一提示 6。答案 96。)
第三章:一个字符的奇迹
本章中的所有程序都运行正常,并且完成了它们应该完成的工作——除了有一个或两个字符放错了位置。当然,这些字符导致了真正的惊喜和彻底的失败。
程序 17:再次问候
我们又做到了。我们打破了“Hello World”。问题出在哪里:
1 #include <iostream>
2
3 int main()
4 {
5 std::cout << "Hello World!/n";
6 return (0);
7 }
(下一提示 172。答案 69。)
![]() |
|---|
真正的程序员不会用 COBOL 编写代码。COBOL 是给软弱的程序员的。
真正的程序员编写的程序第一次永远不会正常工作。但是如果你把它们扔到机器上,它们可以在“仅仅几个”30 小时的调试会话中修复成工作状态。
真正的程序员不会从早上 9 点工作到下午 5 点。如果任何真正的程序员在早上 9 点还在,那是因为他们整夜都没睡。
真正的程序员不会编写文档。文档是给那些不能阅读列表或目标卡片的傻瓜。
真正的程序员不会用 Pascal、BLISS、Ada 或任何那些粉红色的计算机科学语言编写代码。强类型是为那些记忆力差的人准备的。
![]() |
|---|
程序 18:经典
如果你是一名程序员,你已经在程序中犯过这个错误。如果你正在成为程序员,你将会犯这个错误。直到你弄清楚它是什么,这会让你抓狂。
那么,这个程序做了什么:
1 /************************************************
2 * Test the logic for a simple accounting *
3 * program. *
4 ************************************************/
5 #include <iostream>
6
7 int main()
8 {
9 // Amount owed (if any) by the user
10 int amount;
11
12 std::cout << "Enter current balance: ";
13 std::cin >> amount;
14
15 if (amount = 0)
16 std::cout << "You owe nothing\n";
17 else
18 std::cout << "You owe " << amount << "\n";
19
20 return (0);
21 }
(下一个提示 155。答案 47。)
![]() |
|---|
我曾为一家主要的软件制造商工作,负责我们文字处理软件的国际版本。启动屏幕包含了以 mm/dd/yy 格式的发布日期,例如 09/20/83。但欧洲使用 dd/mm/yy 作为其标准。需要指导,我询问老板应该使用哪种格式。他考虑了一下,花了一个月的时间和他的经理们讨论这个问题。在我发布软件一周后,他才给我回复。在此期间,我通过将发布日期定在 11 月 11 日来解决这个问题。没错:11/11/83。
![]() |
|---|
程序 19:嫌疑质数
该程序是一个简单的程序,旨在检查 2 到 9 之间的数字是否为质数。所使用的算法相当简单,它使用暴力方法来完成工作,但看起来应该能行得通。那么,实际上发生了什么呢?
1 /************************************************
2 * prime -- A very dump program to check to see *
3 * if the numbers 2-9 are prime. *
4 ************************************************/
5 #include <iostream>
6
7 int main()
8 {
9 int i; // Number we are checking
10
11 for (i = 2; i < 10; ++i) {
12 switch(i) {
13 case 2:
14 case 3:
15 case 5:
16 case 7:
17 std::cout << i << " is prime\n";
18 break;
19 default:
20 std::cout << i <<
21 " is not prime\n";
22 break;
23 }
24 }
25 return (0);
26 }
(下一个提示 354。答案 67。)
![]() |
|---|
华盛顿州的福利计算机过去存储一个人的年龄为两位数字。一位女士的年龄对于系统来说太大了。当她达到 100 岁时,计算机记录她的年龄为 00。101 被存储为 01。直到她 107 岁时,这个问题才成为问题,政府派了一名逃学巡查员到她家去看她为什么不在一年级。
![]() |
|---|
程序 20:比预期的更简单
这个程序本应生成从 1 到 10 的数字的平方列表。它确实生成了一个平方列表,但这不是程序员预期的。
1 /************************************************
2 * Print out the square of the numbers *
3 * from 1 to 10 *
4 ************************************************/
5 #include <iostream>
6
7 int main()
8 {
9 int index; /* Index into the table */
10
11 for (index = 1; index <= 10; ++index);
12 std::cout << index << " squared " <<
13 (index * index) << '\n';
14
15 return (0);
16 }
(下一提示 193。答案 34。)
![]() |
|---|
真正的程序员不会用 PL/I 编写代码。PL/I 是给那些无法决定是写 COBOL 还是 FORTRAN 的程序员准备的。
真正的程序员在玩《冒险》或《 rogue》游戏时思考得更好。
真正的程序员不会用 FORTRAN 编写代码。FORTRAN 是给管道应力狂热者和晶体学怪人准备的。FORTRAN 是给胆小鬼工程师准备的,他们穿白色袜子。
真正的程序不会使用共享文本。否则,它们在调用完函数后如何使用这些函数作为临时空间呢?
真正的软件工程师不会调试程序;他们会验证正确性。这个过程不一定涉及到在计算机上执行任何东西,除非可能是一个正确性验证辅助包。
真正的软件工程师不喜欢几条通道外那些难以解释且油腻的硬件,它们可能随时停止工作。他们对硬件人员非常不信任,希望系统在所有级别上都能是虚拟的。他们希望有个人电脑(你知道没有人会绊倒某物并杀死你的 DFA 在途中),除了它们需要 8 兆字节来运行它们的正确性验证辅助包。
![]() |
|---|
项目 21:无评论
以下程序会打印什么?为什么?
1 /************************************************
2 * demonstrate how to do a divide. *
3 ************************************************/
4 #include <iostream>
5
6 /************************************************
7 * div -- Do a divide *
8 * *
9 * Returns: Result of the divide. *
10 * *
11 * divisor is reset to 1\. *
12 ************************************************/
13 static int div(
14 int *divisor // Pointer to the divisor
15 )
16 {
17 int result = 5; // Dividend
18
19 result=result/*divisor; /* Do divide */;
20 *divisor=l;
21 return (result);
22 }
23
24 int main()
25 {
26 int num = 5; // Divisor
27
28 std::cout << "Division " <<
29 div(&num) << std::endl;
30 return (0);
31 }
(下一 提示 168. 答案 91.)
![]() |
|---|
最神秘的错误奖项授予:
Error: Success
我仍在试图弄清楚这个问题。
![]() |
|---|
程序 22:参数过大
这段代码的想法很简单:通过限制它为 MAX 来确保大小不要太大。但那并不是我们现在所做的事情。
1 /************************************************
2 * Test the logic to limit the size of a *
3 * variable. *
4 ************************************************/
5 #include <iostream>
6
7 int main()
8 {
9 int size = 20; // Size to be limited
10 const int MAX = 25; // The limit
11
12 if (size > MAX)
13 std::cout << "Size is too large\n";
14 size = MAX;
15
16 std::cout << "Size is " << size << '\n';
17 return(0);
18 }
(下一 提示 304。 答案 4。)
![]() |
|---|
UNIX 命令 true 什么也不做。实际上,这个程序的第一版是一个 0 行的批处理文件(UNIX 称之为 shell 脚本)。多年来,各种源代码控制的无聊内容和其他垃圾被添加到其中,直到这个 0 行程序增长到看起来像这样:
#! /bin/sh
#
# @(#)true.sh 1.5 88/02/07 SMI; from UCB
#
exit 0
1.5 是一个版本号。这意味着他们必须经过这个程序的前四个版本才能得到这个版本。为什么他们必须四次重新发明一个空程序,这让我感到困惑。
![]() |
|---|
程序 23:长与短
程序员想测试他自己的 strlen 版本。这个函数足够简单,但也许太简单了。那么以下字符串的长度是多少?
Sam
This is a test
Hello World
1 /************************************************
2 * Compute the length of a string entered by *
3 * the user. *
4 ************************************************/
5 #include <iostream>
6
7 /************************************************
8 * length -- Find the length of a string *
9 * (strlen does a better job.) *
10 * *
11 * Returns: *
12 * length of the string. *
13 ************************************************/
14 static int length(
15 const char string[] // String to check
16 )
17 {
18 int index; // index into the string
19
20 /*
21 * Loop until we reach the
22 * end of string character
23 */
24 for (index=0; string[index] != '\0';++index)
25 /* do nothing */
26
27 return (index);
28 }
29
30 int main()
31 {
32 char line[100]; // Input line from user
33
34 while (1) {
35 std::cout << "Enter a string: ";
36 std::cin.getline(line, sizeof(line));
37
38 std::cout << "Length is " <<
39 length(line) << '\n';
40 }
41 return (0);
42 }
(下一个提示 114。答案 97。)
![]() |
|---|
一位客户打电话给服务中心:
-
客户:电脑闻起来很奇怪。
-
服务人员:你能检查一下电脑的背面吗?
电话里,服务人员听到客户走到他的电脑旁。然后传来一声尖叫和一声巨响。
- 客户(愤怒):电脑咬了我!
服务人员不得不亲眼看看,因此他安排了一次现场服务。当他到达时,他注意到从电脑机箱到调制解调器的扁平电缆已经熔化。所有的绝缘材料都不见了,只剩下一些裸露的电线。
服务人员拿出他信任的伏特欧姆表测试了电线。电线上有 110 伏特!(正常情况下是 5 伏特。)几分钟之后,他将问题追溯到插座。安装这些插座的电工在一组插头上反接了电源和地线。这种不正确的布线导致调制解调器的地线处于 110 伏特。当调制解调器和电脑连接时,结果是大量电流通过一些非常细小的线路。这导致了绝缘材料的熔化。当客户触摸线路时,110 伏特的电压导致电脑咬了他。
![]() |
|---|
程序 24:过于简单的除法
这个程序用于除以两个整数。尽管它过于简单以至于不太可能失败,但它确实失败了。
1 /************************************************
2 * Simple divide program. *
3 ************************************************/
4 #include <iostream>
5
6 int main()
7 {
8 int n1, n2; // Two integers
9
10 std::cout << "Enter two integers: ";
11 std::cin >> n1 >> n2;
12
13 if (n2 =! 0)
14 std::cout << "Result is: " <<
15 (n1/n2) << '\n';
16 else
17 std::cout << "Can not divide by zero\n";
18
19 return (0);
20 }
(下一个提示 70。答案 25。)
![]() |
|---|
真正的用户害怕他们会损坏机器——但他们从不害怕打你的脸。
真正的用户会找到一种奇特的输入值组合,导致系统关闭数天。
真正的用户讨厌真正的程序员。
真正的程序员并不讨厌真正的用户。真正的程序员只是认为真正的用户完全无关紧要。
真正的用户知道你的家庭电话号码。
真正的用户从不清楚自己想要什么,但他们总是知道当你的程序没有提供他们想要的东西时。
真正的用户从不使用帮助键。
![]() |
|---|
程序 25:最大惊喜
这个程序中的循环被设计用来打印问候语十次。但是程序有不同的想法。那么会发生什么呢?
| 注意 | 这个程序在 GNU 编译器和其他不完全按照标准要求实现预处理器指令的系统上无法编译。(它们做得更好,但不幸的是这破坏了这个程序。) |
|---|
1 /************************************************
2 * Print a bunch of greetings. *
3 ************************************************/
4 #include <iostream>
5
6 #define MAX =10
7
8 int main()
9 {
10 int counter; // Current greeting
11
12 for (counter =MAX; counter > 0; --counter)
13 std::cout <<"Hi there\n";
14
15 return (0);
16 }
(下一个提示 194。答案 112。)
![]() |
|---|
一所大型大学的计算机中心位于一个非常古老的建筑中。他们遇到了一个相当令人烦恼的问题。在夜间,当操作员离开房间时,电脑会重新启动。
被叫来的一位计算机服务技术人员很快发现,只有在操作员去洗手间时系统才会重新启动。当他出去喝水时,什么都没有发生。
一系列的服务技术人员被叫来查看这个问题。很多诊断设备被放在了电脑上。
最后他们发现了问题的原因。那座建筑的地线与水管相连。操作员体重约 300 磅,当他坐在马桶上时,他会向前弯曲几英寸,刚好足以分离水管。这打破了与地线的连接,导致了一个故障,使电脑重新启动。
![]() |
|---|
程序 26:问题区域
这个程序本应确保宽度和高度不会变得太小。它对宽度有效,但高度存在问题。
1 /************************************************
2 * Test the logic to limit the width and height *
3 * of a rectangle. *
4 ************************************************/
5 #include <iostream>
6
7 int main()
8 {
9 // The smallest legal value
10 // of width and height
11 const int MIN = 10;
12
13 int width = 5; // Current width
14 int height = 50; // Current height
15
16 if (width < MIN) {
17 std::cout << "Width is too small\n";
18 width = MIN;
19
20 if (height < MIN)
21 std::cout << "Height is too small\n";
22 height = MIN;
23 }
24
25 std::cout << "area(" << width << ", " <<
26 height << ")=" <<
27 (width * height) << '\n';
28 return (0);
29 }
(下一 提示 290. 答案 13.)
第四章:日常问题
每天程序员都会创建新的程序。每天这些程序员都会犯错误。这些错误不是新手犯的简单错误,也不够复杂到被认为是高级问题。这些错误,嗯,就是你的日常错误。
程序 27:“和”以及“和和”
这个程序旨在测试两个数字是否非零。问题是程序员使用了太多的缩写,出了些问题:
1 /************************************************
2 * if_test -- Simple test of the if statement. *
3 ************************************************/
4 #include <iostream>
5
6 int main()
7 {
8 int i1 = 12; // A number
9 int i2 = 3; // Another number
10
11 if (i1 & i2)
12 std::cout << "Both numbers are non-zero\n";
13 else
14 std::cout << "At least one number is zero\n";
15 return (0);
16 }
(下一提示 351。答案 17。)
![]() |
|---|
一位秘书刚刚完成了一份备忘录,保存时遇到了问题。“你有足够的空间吗?”当地的计算机专家问道。
“哦,当然,”她回答说。“我收到一条消息说‘磁盘空间正常。’”
计算机专家看了看她的肩膀,果然看到了这条信息:
Disk space: OK.
然后他删除了一些文件,信息显示“磁盘空间:4K。”再删除几个文件后,信息显示“磁盘空间:32K,”她能够保存她的备忘录。
![]() |
|---|
程序 28:零错误
该程序旨在将数组清零。那么为什么它不起作用呢?是不是 memset 函数出了问题?
1 /************************************************
2 * zero_array -- Demonstrate how to use memset *
3 * to zero an array. *
4 ************************************************/
5 #include <iostream>
6 #include <cstring>
7
8 int main()
9 {
10 // An array to zero
11 int array[5] = {1, 3, 5, 7, 9};
12
13 // Index into the array
14 int i;
15
16 // Zero the array
17 memset(array, sizeof(array), '\0');
18
19 // Print the array
20 for (i = 0; i < 5; ++i)
21 {
22 std::cout << "array[" << i << "]= " <<
23 array[i] << std::endl;
24 }
25 return (0);
26 }
(下一个提示 50。答案 20。)
![]() |
|---|
来自 Xerox 计算机的 FORTRAN 手册:
The primary purpose of the DATA statement is to give names to constants; instead
of referring to π as 3.141592653589793 at every appearance, the variable PI can
be given that value with a DATA statement and used instead of the longer form of
the constant. This also simplifies modifying the program, should the value of π
change.
![]() |
|---|
程序 29:简单,亲爱的读者
以下程序旨在打印一个 3x3 矩阵。但结果并不是矩阵的元素;而是其他东西。这是怎么回事?
1 /************************************************
2 * print_element -- Print an element in a *
3 * matrix. *
4 ************************************************/
5 #include <iostream>
6
7 // A simple matrix
8 int matrix[3][3] = {
9 {11, 12, 13},
10 {21, 22, 23},
11 {31, 32, 33}
12 };
13
14 int main()
15 {
16 std::cout << "Element[1,2] is " <<
17 matrix[1,2] << std::endl;
18 return (0);
19 }
(下一条提示 89。答案 86。)
![]() |
|---|
我知道的一个绘图程序拥有史上最恭顺的错误信息:
This humble and worthless program is devastated to report to you that I can not
accept your scale value of 1000 because the base and thoughtless programmer who
wrote me has restricted the value of this variable to between 1 and 100.
![]() |
|---|
程序 30:一点麻烦
这个程序使用一个变量来存储八个权限标志。程序员希望为指定的用户设置管理(P_ADMIN)和备份主(P_BACKUP)权限,然后验证这些位是否已正确设置。实际上发生了什么?
1 /************************************************
2 * print_privs -- Print some of the privilege *
3 * flags. *
4 ************************************************/
5 #include <iostream>
6
7 #define CI const int
8 CI P_USER = (1 << 1); // Normal user privileges
9 CI P_REBOOT = (1 << 2); // Can reboot systems
10 CI P_KILL = (1 << 3); // Can kill any process
11 CI P_TAPE = (1 << 4); // Can use tape devices
12 CI P_RAW = (1 << 5); // Can do raw io
13 CI P_DRIVER = (1 << 6); // Can load drivers
14 CI P_ADMIN = (1 << 7); // Can do administration
15 CI P_BACKUP = (1 << 8); // Can do backups
16
17 int main()
18 {
19 // The privileges
20 unsigned char privs = 0;
21
22 // Set some privs
23 privs |= P_ADMIN;
24 privs |= P_BACKUP;
25
26 std::cout << "Privileges: ";
27
28 if ((privs & P_ADMIN) != 0)
29 std::cout << "Administration ";
30
31 if ((privs & P_BACKUP) != 0)
32 std::cout << "Backup ";
33
34 std::cout << std::endl;
35 return (0);
36 }
(下一个提示 7. 答案 11.)
程序 31:非常小的数字
这个程序员很聪明。他决定使用位域来存储标志以避免在程序 30 中看到的问题。但他又创造了自己的一套新问题:
1 /************************************************
2 * printer status -- Print the status of the *
3 * printer. *
4 ************************************************/
5 #include <iostream>
6
7 /*
8 * Printer status information.
9 */
10 struct status {
11 // True if the printer is on-line
12 int on_line:1;
13
14 // Is the printer ready
15 int ready:1;
16
17 // Got paper
18 int paper_out:1;
19
20 // Waiting for manual feed paper
21 int manual_feed:1;
22 };
23
24 int main()
25 {
26 // Current printer status
27 status printer_status;
28
29 // Tell the world we're on-line
30 printer_status.on_line = 1;
31
32 // Are we on-line?
33 if (printer_status.on_line == 1)
34 std::cout << "Printer is on-line\n";
35 else
36 std::cout << "Printer down\n";
37 return (0);
38 }
(下一个提示 167. 答案 42.)
程序 32:双字符麻烦
为什么我们永远找不到双字符?
1 /************************************************
2 * test the find_double array. *
3 ************************************* **********/
4 #include <iostream>
5 char test[] = "This is a test for double letters\n";
6 /************************************************
7 * find_double -- Find double letters in an *
8 * array. *
9 * *
10 * Returns: *
11 * number of double letters in a string. *
12 ************************************************/
13 static int find_double(
14 const char str[] // String to check
15 ) {
16 int index; // Index into the string
17
18 for (index = 0; str[index] != '\0'; ++index) {
19 /*
20 * Start prev_ch out with a strange value
21 * so we don't match on the first
22 * character of the string.
23 */
24 char prev_ch = '\0';
25
26 if (prev_ch == str[index])
27 return (index-1);
29 prev_ch = str[index];
30 }
31 return (-1);
32 }
33
34 int main() {
35 std::cout << "find_double= " <<
36 find_double(test) << std::endl;
37 return (0);
38 }
(下一个提示 261. 答案 106.)
程序 33:不良字符
以下程序应该输出 ABC。它实际上做了什么?
1 /************************************************
2 * Toy program to print three characters. *
3 ************************************************/
4 #include <iostream>
5
6 int main()
7 {
8 //A character to be printed
9 char ch = 'A';
10
11 std::cout << ch; // Output A
12 std::cout << ch+1; // Output B
13 std::cout << ch+2; // Output C
14 std::cout << std::endl;
15 return (0);
16 }
(下一个提示 124。答案 45。)
![]() |
|---|
最小惊讶定律:
程序应该以最不令用户惊讶的方式运行。
![]() |
|---|
程序 34:非美分
这是一个简单的支票簿程序。程序在一段时间内表现不错,但添加了大量条目后,总金额偏差了几美分。钱都去哪了?
1 /************************************************
2 * check -- Very simple checkbook program. *
3 * *
4 * Allows you to add entries to your checkbook *
5 * and displays the total each time. *
6 * *
7 * Restrictions: Will never replace Quicken. *
8 ************************************************/
9 #include <iostream>
10 #include <fstream>
11 #include <string>
12 #include <vector>
13 #include <fstream>
14 #include <iomanip>
15
16 /************************************************
17 * check_info -- Information about a single *
18 * check *
19 ************************************************/
20 class check_info {
21 public:
22 // Date the check was written
23 std::string date;
24
25 // What the entry is about
26 std::string what;
27
28 // Amount of check or deposit
29 float amount;
30 public:
31 check_info():
32 date(""),
33 what(""),
34 amount(0.00)
35 {};
36 // Destructor defaults
37 // Copy constructor defaults
38 // Assignment operator defaults
39 public:
40 void read(std::istream &in file);
41 void print(std::ostream &out_file);
42 };
43
44 // The STL vector to hold the check data
45 typedef std::vector<check_info> check_vector;
46
47 /************************************************
48 * check_info::read -- Read the check *
49 * information from a file. *
50 * *
51 * Warning: Minimal error checking *
52 ************************************************/
53 void check_info::read(
54 std::istream &in_file // File for input
55 ) {
56 std::getline(in_file, date);
57 std::getline(in_file, what);
58 in_file >> amount;
59 in_file.ignore(); // Finish the line
60 }
61 /************************************************
62 * check_info::print -- Print the check *
63 * information to a report. *
64 ************************************************/
65 void check_info::print(
66 std::ostream &out_file // File for output
67 ) {
68 out_file <<
69 std::setiosflags(std::ios::left) <<
70 std::setw(10) << date <<
71 std::setw(50) << what <<
72 std::resetiosflags(std::ios::left) <<
73 std::setw(8) << std::setprecision(2) <<
74 std::setiosflags(std::ios::fixed) <<
75 amount << std::endl;
76 }
77
78 int main()
79 {
80 // Checkbook to test
81 check_vector checkbook;
82
83 // File to read the check data from
84 std::ifstream in_file("checks.txt");
85
86 if (in_file.bad()) {
87 std::cerr << "Error opening input file\n";
88 exit (8);
89 }
90 while (1) {
91 check_info next_info; // Current check
92
93 next_info.read(in_file);
94 if (in_file.fail())
95 break;
96
97 checkbook.push_back(next_info);
98 }
99 double total = 0.00; // Total in the bank
100 for (check_vector::iterator
101 cur_check = checkbook.begin();
102 cur_check != checkbook.end();
103 cur_check++)
104 {
105 cur_check->print(std::cout);
106 total += cur_check->amount;
107 }
108 std::cout << "Total " << std::setw(62) <<
109 std::setprecision(2) <<
110 total << std::endl;
111 return (0);
112 }
(下一个提示 39。答案 107。)
程序 35:所以你想打印一百万
我不知道我们可以在 C++ 常量中使用逗号。那么为什么下面的程序可以编译?它做了什么?
1 /************************************************
2 * print the value on one million. *
3 ************************************************/
4 #include <iostream>
5
6 int main()
7 {
8 // Variable to hold a million
9 long int one_million;
10
11 // Set the variable
12 one_million = 1,000,000;
13
14 std::cout <<
15 "One million " << one_million <<
16 std::endl;
17 return (0);
18 }
(下一个提示 55。答案 44。)
![]() |
|---|
| **Q: ** | 需要多少个程序员才能换一个灯泡? |
|
| **Q: ** | 需要多少个微软程序员才能换一个灯泡? ||
答案
A: 无。这是一个硬件问题。 A: 无。微软刚刚宣布黑暗是尖端技术领域最新的创新。
|
|
| |
程序 36:堆栈过高
为什么这个程序会耗尽堆栈空间?
1 /************************************************
2 * test the data_holder class. *
3 ************************************************/
4 #include <iostream>
5 /************************************************
6 * data_holder -- A class to hold a single *
7 * integer *
8 * *
9 * Member functions: *
10 * get -- Get value *
11 * *
12 * Note: By default the value of the data is 5\. *
13 * *
14 * Warning: More member functions need to be *
15 * added to this to make it useful. *
16 ************************************************/
17 class data_holder {
18 private:
19 int data; // Data to store
20 public:
21 // Constructor -- Set value to default (5)
22 data_holder(void):data(5) {};
23
24 // Destructor defaults
25 //
26 // Copy constructor
27 data_holder(const data_holder &old) {
28 *this = old;
29 }
30
31 // Assignment operator
32 data_holder operator = (
33 data_holder old_data_holder) {
34 data = old_data_holder.data;
35 return (*this);
36 }
37
38 // Get the data item
39 int get(void)
40 {
41 return (data);
42 }
43 };
44
45 int main() {
46 // A data holder
47 data_holder var1;
48
49 // Copy of a data holder
50 data_holder var2(var1);
51 return (0);
52 }
(下一提示 53。答案 12。)
![]() |
|---|
来自 UNIX 文档:
The device names /dev/rmto, /dev/rmt4, /dev/rmt8, /dev/rmt12 are the rewinding
low density, rewinding high density, non-rewinding low density and non-rewinding
high density tape drives respectively.
来自 UNIX 文档中关于 FED 命令的说明:
BUGS
The terminal this program runs on has been stolen.
来自 UNIX 文档中关于 TUNEFS 命令(调整文件系统)的说明:
You can tune a file system but you can't tune a fish.
![]() |
|---|
程序 37:这个程序有目的
以下程序旨在将数组数据归零,但有时它会做其他事情。
1 /************************************************
2 * Pointer demonstration. *
3 ************************************************/
4 #include <iostream>
5
6 static int data[16]; // Data to be stored
7 static int n_data = 0; // Number of items stored
8
9 int main()
10 {
11 int *data_ptr; // Pointer to current item
12
13 // Zero the data array
14 for (data_ptr = data+16-1;
15 data_ptr >= data;
16 --data_ptr)
17 {
18 *data_ptr = 0;
19 }
20
21 // Enter data into the array
22 for (n_data = 0; n_data < 16; ++n_data) {
23 std::cout <<
24 "Enter an item or 0 to end: ";
25 std::cin >> data[n_data];
26
27 if (data[n_data] == 0)
28 break;
29 }
30
31 // Index for summing
32 int index;
33
34 // Total of the items in the array
35 int total = 0;
36
37 // Add up the items in the array
38 for (index = 0; index < n_data; ++index)
39 total += data[index];
40
41 // Print the total
42 std::cout << "The total is: " <<
43 total << std::endl;
44
45 return (0);
46 }
(下一个提示 87。答案 21。)
![]() |
|---|
我曾工作的一家公司有一个通信线路,每天下午 5:00 准时失效。每天早上大约 7:00 它会自动启动。对硬件进行了广泛的检查,但都没有发现问题。最后,指派了一名工程师在下班后留下来监控通信线路。那天晚上问题消失了。
第二个晚上,通信系统如往常一样中断。第二天晚上,工程师加班并解决了问题。经过几次这样的循环后,确定除非有工程师在监控,否则通信线路将在下午 5:00 崩溃。
最后一个晚上,一名工程师决定在离开前对通信调制解调器进行最后的检查。它当时是工作的。他关掉了灯,碰巧回头看了调制解调器一眼。它已经死了。打开灯,它又恢复了。开关灯,他发现调制解调器插在了一个开关电源插座上。
神秘事件解决。当天员工下班时,他们关掉了灯,导致调制解调器失效。第二天他们上班时,又打开了灯。工程师在熬夜排查问题时找不到问题,因为他忘记关灯以便观察设备。
调制解调器插在普通电源插座上,所有的通信问题都消失了。
![]() |
|---|
程序 38:良好的值
这是一段明显的代码。那么它实际上会打印出什么呢?
文件:main.cpp
1 /************************************************
2 * test the check_for_even function. *
3 ************************************************/
4 #include <iostream>
5
6 int value = 21; // Value of the system size
7
8 // Checks global value for even or not.
9 extern void check_for_even(void);
10
11 int main(void)
12 {
13 check_for_even();
14 std::cout << "Value is " << value << '\n';
15 return (o);
16 }
文件:check.cpp
1 #include <iostream>
2
3 // Value of the control system size
4 int value = 30;
5
6 /************************************************
7 * check_for_even -- Check to see if global *
8 * value is even. *
9 ************************************************/
10 void check_for_even(void)
11 {
12 if ((value % 2) == 0)
13 std::cout << "Ok\n";
14 else
15 std::cout << "Value problem\n";
16 }
(下一提示 248。答案 57。)
程序 39:幼儿园算术修订版
我们都知道 1 + 1 = 2,1 + 1 + 1 = 3。
我们都知道 1/3 + 1/3 + 1/3 等于 3/3 或 1。
以下计算机程序演示了这一点。但不知何故,它不起作用。为什么?
1 /************************************************
2 * test out basic arithmetic that we learned in *
3 * first grade. *
4 ************************************************/
5 #include <iostream>
6
7 int main()
8 {
9 float third = 1.0 / 3.0; // The value 1/3
10 float one = 1.0; // The value 1
11
12 if ((third+third+third) == one)
13 {
14 std::cout <<
15 "Equal 1 = 1/3 + 1/3 + 1/3\n";
16 }
17 else
18 {
19 std::cout <<
20 "NOT EQUAL 1 != 1/3 + 1/3 + 1/3\n";
21 }
22 return (0);
23 }
(下一个提示 113。答案 54。)
![]() |
|---|
一个学生刚刚输入了他的第一个 BASIC 程序,并使用 RUN 命令开始执行。计算机打印出一组数字,然后迅速地将它们从屏幕上滚动掉,以至于这位可怜的家伙还没来得及阅读。
学生想了一会儿,然后问道:“如果我输入 WALK,它会走得更慢吗?”
![]() |
|---|
程序 40:难以置信的准确性
这个程序旨在计算浮点数的准确性。想法很简单。计算以下内容,直到数字相等:
1.0 == 1.5 (1 + 1/2 or 1 + 1/21) (1.1 binary)
1.0 == 1.25 (1 + 1/4 or 1 + 1/22) (l.oi binary)
1.0 == 1.125 (1 + 1/8 or 1 + 1/23) (1.001 binary)
1.0 == 1.0625 (1 + 1/16 or 1 + 1/24) (1.0001 binary)
1.0 == 1.03125 (1 + 1/32 or 1 + 1/25) (1.00001 binary)
这将给出准确性的数字位数。
这个程序是在一台 32 位浮点数的 PC 级机器上运行的。那么你预计 32 位浮点格式中有多少二进制位?
这个程序没有给出正确答案。为什么?
1 /************************************************
2 * accuracy test. *
3 * *
4 * This program figures out how many bits *
5 * accuracy you have on your system. It does *
6 * this by adding up checking the series: *
7 * *
8 * 1.0 == 1.1 (binary) *
9 * 1.0 == 1.01 (binary) *
10 * 1.0 == 1.001 (binary) *
11 * .... *
12 * *
13 * Until the numbers are equal. The result is *
14 * the number of bits that are stored in the *
15 * fraction part of the floating point number. *
16 ************************************************/
17 #include <iostream>
18
19 int main()
20 {
21 /* two numbers to work with */
22 float number1, number2;
23
24 /* loop counter and accuracy check */
25 int counter;
26
27 number1 = 1.0;
28 number2 = 1.0;
29 counter = 0;
30
31 while (number1 + number2 != number1) {
32 ++counter; // One more bit accurate
33
34 // Turn numbers like 0.1 binary
35 // into 0.01 binary.
36 number2 = number2 / 2.0;
37 }
38 std::cout << counter << " bits accuracy.\n";
39 return (0);
40 }
(下一个提示 352。答案 73。)
![]() |
|---|
现代打字机使用所谓的 QWERTY 键盘(以键盘顶行字母命名)。这是标准设计。你可能想知道为什么选择了这种特定的布局。答案是简单的:这是为了让打字变得困难。
在手动打字机的时代,机器制造商有一个问题。人们打字太快,按键会卡住。解决方案是将按键排列得慢一些,从而防止卡住。
已经创建了一种名为 Dvorak 键盘的新标准键盘布局,它可以大大提高打字速度,但由于许多人已经熟悉 QWERTY 键盘,其接受度受到了限制。
![]() |
|---|
程序 41:一点麻烦
bit_out 输出一个 16 位的值,打印出每个位的值。它生成工作的图形表示,但输出看起来有点奇怪。发生了什么?
1 /************************************************
2 * bit test -- Test the routine to print out *
3 * the bits in a flag. *
4 ************************************************/
5 #include <iostream>
6 /************************************************
7 * bit_out -- print a graphical *
8 * representation of each bit in a *
9 * 16 bit word. *
10 * *
11 * For example: *
12 * 0×55AF will print -X-X-X-XX-X-XXXX *
13 ************************************************/
14 void bit_out(
15 const short int value // Value to print
16 )
17 {
18 // The bit we are printing now
19 short int bit = (1<<16);
20
21 int count; // Loop counter
22
23 for (count = 0; count < 16; ++count)
24 {
25 if ((bit & value) != 0)
26 std::cout << "X";
27 else
28 std::cout << '-';
29 bit >>= 1;
30 }
31 std::cout << std::endl;
32 }
33 int main()
34 {
35 bit_out(0×55AF);
36 return (0);
37 }
(下一提示 332. 答案 2.)
程序 42:更多麻烦
我们通过修改第 19 行修复了程序 41。所以现在程序应该能正常工作,对吧?当然不是。一个正常工作的程序在这本书里会做什么呢?
1 /************************************************
2 * bit test -- Test the routine to print out *
3 * the bits in a flag. *
4 ************************************************/
5 #include <iostream>
6 /************************************************
7 * bit_out -- print a graphical *
8 * representation of each bit in a *
9 * 16 bit word. *
10 * *
11 * For example: *
12 * 0×55AF will print -X-X-X-XX-X-XXXX *
13 ************************************************/
14 void bit_out(
15 const short int value // Value to print
16 )
17 {
18 // The bit we are printing now
19 short int bit = (1<<15);
20
21 int count; // Loop counter
22
23 for (count = 0; count < 16; ++count)
24 {
25 if ((bit & value) != 0)
26 std::cout << "X";
27 else
28 std::cout << '-';
29 bit >>= 1;
30 }
31 std::cout << std::endl;
32 }
33 int main()
34 {
35 bit_out(0×55AF);
36 return (0);
37 }
(下一个提示 180。答案 19。)
程序 43:无根据
我们知道 2 是一个整型。那么为什么 C++ 会认为它是浮点型并调用错误的函数?
1 /************************************************
2 * demonstrate the use of derived classes. *
3 ************************************************/
4 #include <iostream>
5
6 /************************************************
7 * base -- A sample base class. *
8 * Prints various values. *
9 ************************************************/
10 class base
11 {
12 // Constructor defaults
13 // Destructor defaults
14 // Copy constructor defaults
15 // Assignment operator defaults
16 public:
17 // Print a floating point number
18 void print_it(
19 float value // The value to print
20 )
21 {
22 std::cout <<
23 "Base (float=" << value << ")\n";
24 }
25 // Print an integer value
26 void print_it(
27 int value // The value to print
28 )
29 {
30 std::cout <<
31 "Base (int=" << value << ")\n";
32 }
33 };
34
35 class der
36 {
37 // Constructor defaults
38 // Destructor defaults
39 // Copy constructor defaults
40 // Assignment operator defaults
41 public:
42 // Print a floating point number
43 void print_it(
44 float value // The value to print
45 )
46 {
47 std::cout <<
48 "Der (float=" << value << ")\n";
49 }
50 };
51
52 int main()
53 {
54 der a_var; // A class to play with
55
56 // Print a value using der::print_it(float)
57 a_var.print_it(1.0);
58
59 // Print a value using base::print_it(int)
60 a_var.print_it(2);
61 return (0);
62 }
(下一提示 330。答案 58。)
![]() |
|---|
UNIX mt 命令的原始版本在无法理解命令时会出现一个不寻常的错误信息:
mt -f /dev/rst8 funny
mt: Can't grok "funny"
对于那些不熟悉罗伯特·海因莱因的《陌生人的土地》的人来说,grok 是火星语中“理解”的意思。
这个术语在其他国家并没有很好地传播。一位德国程序员试图在他的英语/德语词典中找到“grok”这个词,结果变得疯狂。
![]() |
|---|
程序 44:排序问题
以下代码本应找出数组相邻元素之间的差异。为什么它不起作用?
1 /************************************************
2 * diff elements -- Print the differences *
3 * between adjacent elements of any array. *
4 ************************************************/
5 #include <iostream>
6
7 // Any array containing pairs of values.
8 // Ends with the sentinel -1
9 static int array[12] =
10 {
11 44, 8,
12 50, 33,
13 50, 32,
14 75, 39,
15 83, 33,
16 -1, -1
17 };
18
19 // Array to hold the differences
20 static int diff[6];
21
22 int main()
23 {
24 int i; // Index into the array
25
26 // Index into the diff results
27 int diff_index;
28
29 i = 0;
30 diff_index = 0;
31 // Difference adjacent elements of an array
32 while (array[i] != 0)
33 {
34 diff[diff_index++] =
35 array[i++] - array[i++];
36 }
37
38 // Print the results
39 for (i = 0; i < 6; ++i)
40 {
41 std::cout << "diff[" << i << "]= " <<
42 diff[i] << std::endl;
43 }
44 return (0);
45 }
(下一个提示 177。答案 26。)
![]() |
|---|
真正的计算机科学家钦佩 ADA 的压倒性美学价值,但他们发现实际上用它编程很困难,因为它太大而难以实现。大多数计算机科学家没有注意到这一点,因为他们还在争论要为 ADA 添加什么。
真正的计算机科学家厌恶实际硬件的想法。硬件有局限性;软件没有。真遗憾图灵机在 I/O 方面如此糟糕。
真正的计算机科学家不会注释他们的代码。标识符太长,他们负担不起磁盘空间。
真正的计算机科学家不会用汇编语言编程。他们不会用比 2B 铅笔更不便携的东西来编写。
真正的计算机科学家不会编写代码。他们偶尔会摆弄一下“编程系统”,但这些系统如此高级,几乎不能算数(而且很少能准确计算;精度是为应用而准备的)。
真正的计算机科学家只为可能在未来硬件上运行的编程语言编写规范。没有人相信他们能为任何人类能够适应一个星球的东西编写规范。
![]() |
|---|
程序 45:三重惊喜
a、b、c 是否按降序排列?程序是否与你的看法一致?
1 /************************************************
2 * test to see if three variables are in order. *
3 ************************************************/
4 #include <iostream>
5
6 int main()
7 {
8 int a,b,c; // Three simple variables
9
10 a = 7;
11 b = 5;
12 c = 3;
13
14 // Test to see if they are in order
15 if (a > b > c)
16 std::cout << "a,b,c are in order\n";
17 else
18 std::cout << "a,b,c are mixed up\n";
19 return (o);
20 }
(下一个提示 312。答案 80。)
![]() |
|---|
所有 DEC 计算机的调试器都称为 DDT。在 PDP-10 DDT 手册中有一个关于这个名字是如何产生的脚注:
历史脚注:DDT 是在 1961 年为 PDP-1 计算机在麻省理工学院开发的。当时 DDT 代表 "DEC 调试带"。从那时起,在线调试程序的想法已经传播到整个计算机行业。现在所有 DEC 计算机都有可用的 DDT 程序。由于现在除了磁带之外的其他媒体经常被使用,因此采用了更具描述性的名称 "动态调试技术",同时保留了 DDT 这个缩写。由于 DDT-10 和另一种著名的杀虫剂二氯二苯三氯乙烷(C[14] H[9] Cl[5])攻击的是不同且显然互斥的虫类,因此两者之间的混淆应该是最小的。
![]() |
|---|
程序 46:一切正常
为什么以下程序有时会崩溃?
1 /************************************************
2 * list -- Test out the command list decoder. *
3 * *
4 * Read a command from the input and check to *
5 * see if the command decoder can find it. *
6 ************************************************/
7 #include <iostream>
8 #include <cstring>
9
10 static inline void do_open() {
11 std::cout << "do_open called\n";
12 }
13 static inline void do_close() {
14 std::cout << "do_close called\n";
15 }
16 static inline void do_save() {
17 std::cout << "do_save called\n";
18 }
19 static inline void do_quit() {
20 exit(0);
21 }
22 /*
23 * The command as a string and
24 * as a function to execute
25 */
26 struct cmd_info {
27 char *const cmd;
28 void (*funct)();
29 };
30
31 /*
32 * List of all possible commands
33 */
34 static cmd_info cmd_list[] = {
35 {"open", do_open},
36 {"close", do_close},
37 {"save", do_save},
38 {"quit", do_quit},
39 {NULL, NULL}
40 };
41
42 /************************************************
43 * do_cmd -- Decode a command an execute it. *
44 * If the command is not found, output an *
45 * error. *
46 ************************************************/
47 static void do_cmd(
48 const char *const cmd
49 ) {
50 struct cmd_info *cur_cmd;
51
52 cur_cmd = cmd_list;
53
54 while (
55 (std::strcmp(cur_cmd->cmd, cmd) != 0) &&
56 cur_cmd != NULL)
57 {
58 cur_cmd++;
59 }
60 if (cur_cmd == NULL) {
61 std::cout << "Command not found\n";
62 } else {
63 cur_cmd->funct();
64 }
65 }
66
67 /************************************************
68 * main -- Simple test program. *
69 ************************************************/
70 int main()
71 {
72 char cmd[100];
73 while (1) {
74 std::cout << "Cmd: ";
75 std::cin.getline(cmd, sizeof(cmd));
76
77 do_cmd(cmd);
78 }
79 }
(下一提示 135。答案 70。)
程序 47:微软的落后
为什么以下程序在 MS-DOS 下编译和运行时无法打开文件?
1 /************************************************
2 * read config file -- Open a configuration *
3 * file and read in the data. *
4 * *
5 * Designed to work on both UNIX and MS-DOS. *
6 * *
7 * Note: Incomplete program. *
8 ************************************************/
9 #include <iostream>
10 #include <fstream>
11
12 #ifdef MS_DOS
13
14 // DOS path
15 const char name[] = "\root\new\table";
16
17 #else /* MS_DOS */
18
19 // UNIX path
20 const char name[] = "/root/new/table";
21
22 #endif /* MS_DOS */
23
24
25 int main() {
26 // The file to read
27 std::ifstream in_file(name);
28
29 if (in_file.bad())
30 {
31 std::cerr <<
32 "Error: Could not open " << std::endl;
33 std::cerr << name << std::endl;
34 exit (8);
35 }
36
37 return (0);
38 }
(下一个提示 217. 答案 37.)
程序 48:文件怪癖
这个程序一开始运行得很好,但之后它拒绝识别包含魔数(magic number)的文件:
1 /************************************************
2 * scan -- Scan a directory tree for files that *
3 * begin with a magic number. *
4 ************************************************/
5 #include <iostream>
6 #include <dirent.h>
7 #include <fcntl.h>
8 #include <unistd.h>
9
10 // Linux executable magic #
11 const long int MAGIC = 0×464c457f;
12
13 /************************************************
14 * next_file -- find a list of files with *
15 * magic numbers that match the given *
16 * number. *
17 * *
18 * Returns the name of the file or *
19 * NULL if no more files. *
20 ************************************************/
21 char *next_file(
22 DIR *dir // Directory we are scanning
23 )
24 {
25 // The current directory entry
26 struct dirent *cur_ent;
27
28 while (1) {
29 cur_ent = readdir(dir);
30 if (cur_ent == NULL)
31 return (NULL);
32
33 // Open the fd for the input file
34 int fd = open(cur_ent->d_name, 0_RDONLY);
35 if (fd < 0)
36 continue; // Can't get the file
37 // so try again
38
39 int magic; // The file's magic number
40
41 // Size of the latest read
42 int read_size =
43 read(fd, &magic, sizeof(magic));
44
45 if (read_size != sizeof(magic))
46 continue;
47
48 if (magic == MAGIC)
49 {
50 close(fd);
51 return (cur_ent->d_name);
52 }
53 }
54 }
55
56 /************************************************
57 * scan_dir -- Scan a directory for the *
58 * files we want. *
59 ************************************************/
60 void scan_dir(
61 const char dir_name[] // Directory name to use
62 )
63 {
64 // The directory we are reading
65 DIR *dir_info = opendir(dir_name);
66 if (dir_info == NULL)
67 return;
68
69 chdir(dir_name);
70
71 while (1) {
72 char *name = next_file(dir_info);
73 if (name == NULL)
74 break;
75 std::cout << "Found: " << name << '\n';
76 }
77 }
78
79 int main()
80 {
81 scan_dir(".");
82 return (0);
83 }
(下一提示 226。答案 60。)
程序 49:就像从链接上掉下来一样简单
为什么以下程序有时会崩溃?
1 #include <iostream>
2 #include <string>
3 /************************************************
4 * linked_list -- Class to handle a linked list *
5 * containing a list of strings. *
6 * *
7 * Member functions: *
8 * add -- Add an item to the list *
9 * is_in -- Check to see if a string is *
10 * in the list. *
11 ************************************************/
12 class linked_list {
13 private:
14 /*
15 * Node in the list
16 */
17 struct node {
18 // String in this node
19 std::string data;
20
21 // Pointer to next node
22 struct node *next;
23 };
24 //First item in the list
25 struct node *first;
26 public:
27 // Constructor
28 linked_list(void): first(NULL) {};
29 // Destructor
30 ~linked_list();
31 private:
32 // No copy constructor
33 linked_list(const linked_list &);
34
35 // No assignment operator
36 linked_list& operator = (const linked_list &);
37 public:
38 // Add an item to the list
39 void add(
40 // Item to add
41 const std::string &what
42 ) {
43 // Create a node to add
44 struct node *new_ptr = new node;
45
46 // Add the node
47 new_ptr->next = first;
48 new_ptr->data = what;
49 first = new_ptr;
50 }
51 bool is_in(const std::string &what);
52 };
53 /************************************************
54 * is_in -- see if a string is in a *
55 * linked list. *
56 * *
57 * Returns true if string's on the list, *
58 * otherwise false. *
59 ************************************************/
60 bool linked_list::is_in(
61 // String to check for
62 const std::string &what
63 ) {
64 /* current structure we are looking at */
65 struct node *current_ptr;
66
67 current_ptr = first;
68
69 while (current_ptr != NULL) {
70 if (current_ptr->data == what)
71 return (true);
72
73 current_ptr = current_ptr->next;
74 }
75 return (false);
76 }
77
78 /************************************************
79 * linked_list::~linked_list -- Delete the *
80 * data in the linked list. *
81 ************************************************/
82 linked_list::~linked_list(void) {
83 while (first != NULL) {
84 delete first;
85 first = first->next;
86 }
87 }
88
89 int main() {
90 linked_list list; // A list to play with
91
92 list.add("Sam");
93 list.add("Joe");
94 list.add("Mac");
95
96 if (list.is_in("Harry"))
97 std::cout << "Harry is on the list\n";
98 else
99 std::cout << "Could not find Harry\n";
100 return (0);
101 }
(下一个提示 186。答案 77。)
![]() |
|---|
一个清洁工在机房的地板上发现了一道划痕,并决定将其清除。首先她尝试了蜡,然后是氨基清洁剂,最后,作为最后的手段,她使用了钢丝绒。这种组合证明是致命的。不是对划痕而言,而是对电脑而言。
第二天,当计算人员来上班时,他们发现所有机器都停了。打开机柜,他们发现所有电路板上都有大量的短路。
发生了什么事?清洁工首先在地板上涂了一层蜡。氨气使蜡蒸发,冷却风扇将其吸入电脑中。因此,每块电路板都被涂上了一层均匀的粘性蜡。这还不算太糟糕,但接下来是钢丝绒。钢丝纤维被吸入机器中,它们粘附在机器内部的蜡涂层上。
![]() |
|---|
程序 50:真理究竟是什么?
计算机将“真理将使你自由”变成了“真理会让你困惑得要命。”
1 /************************************************
2 * test bool_name, a function turn booleans into*
3 * text. *
4 ************************************************/
5 #include <iostream>
6 #include <string>
7
8 /************************************************
9 * bool_name -- given a boolean value, return *
10 * the text version. *
11 * *
12 * Returns: *
13 * Strings "true" or "false" depending *
14 * on value. *
15 ************************************************/
16 static const std::string &bool_name(
17 const bool value // The value to check
18 )
19 {
20 // The "true" value
21 const std::string true_name("true");
22
23 // The "false" value
24 const std::string false_name("false");
25
26 if (value == true)
27 return (true_name);
28
29 return (false_name);
30 }
31
32 int main() {
33 std::cout << "True is " <<
34 bool_name(true) << std::endl;
35
36 std::cout << "False is " <<
37 bool_name(false) << std::endl;
38 return (0);
39 }
(下一提示 319。答案 30。)
程序 51:多余的加号
程序员在定义 ++x 和 x++ 操作符时试图做正确的事情。以下程序会打印什么,为什么?
1 /************************************************
2 * Demonstrate how to define and use increment *
3 * operator. *
4 ************************************************/
5 #include <iostream>
6
7 /************************************************
8 * num -- Class to hold a single number *
9 ************************************************/
10 class num
11 {
12 // Constructor defaults
13 // Destructor defaults
14 // Copy Constructor defaults
15 // Assignment operator defaults
16 public:
17 // Value stored in the function
18 int value;
19
20 // Increment operator (i++)
21 num operator ++(int)
22 {
23 num copy(*this); // Copy for return
24
25 value++;
26 return (copy);
27 }
28
29 // Increment operator (++i)
30 num &operator ++(void)
31 {
32 value++;
33 return (*this);
34 }
35 };
36
37 int main()
38 {
39 num i; // A value to play with
40
41 i.value = 1;
42 ++++i;
43 std::cout << "i is " << i.value << std::endl;
44
45 i.value = 1;
46 i++++;
47 std::cout << "i is " << i.value << std::endl;
48 return (0);
49 }
(下一个提示 246。答案 87。)
程序 52:消失矩形的案例
我们样本的面积是多少?
1 /************************************************
2 * Demonstration of the rectangle class. *
3 ************************************************/
4 #include <iostream>
5
6 /************************************************
7 * rectangle -- hold constant information about *
8 * a rectangle. *
9 * *
10 * Members: *
11 * area -- Area of the rectangle. *
12 * width -- width of the rectangle. *
13 * height - length of the rectangle. *
14 ************************************************/
15 class rectangle
16 {
17 public:
18 const int area; // Rectangle's Area
19 const int width; // Rectangle's Width
20 const int height; // Rectangle's Height
21
22 public:
23 // Create a rectangle and assign the
24 // initial values
25 rectangle(
26 const int i_width, // Initial width
27 const int i_height // Initial height
28 ) : width(i_width),
29 height(i_height),
30 area(width*height)
31 {}
32 // Destructor defaults
33 // Copy constructor defaults
34 // Assignment operator defaults
35 };
36
37 int main()
38 {
39 // Rectangle to play with
40 rectangle sample(10, 5);
41
42 std::cout << "Area of sample is " <<
43 sample.area << std::endl;
44 return (0);
45 }
(下一提示 210。答案 93。)
![]() |
|---|
人侵计算机
一所大型大学的系统管理员负责维护数百台 DEC 机器的运行。他很快学会了如何诊断损坏的机器并找出哪块板子坏了。为了获得稳定的备件供应,他不得不购买服务合同。这意味着 DEC 服务代表应该下来,诊断机器,找出哪块板子坏了并更换它。实际上,大学员工有严格的命令,永远不要让 DEC 服务代表接近机器。
典型的服务通常从管理员告诉 DEC 哪块板子坏了开始。服务代表会下来,在管理员的桌子上找到损坏的板子,并用一块好的板子替换它。不需要运行诊断或其他工作,这一切都为他准备好了。
几年后,DEC 实施了“智能备件”计划。这个想法是,你有一个受过培训的人在现场,可以找出哪块板子坏了,并从 DEC 订购替换件。当然,这对大学来说很合适,因为它已经这样做了好几年。
但问题是,你必须参加 DEC 的课程,学习如何诊断系统。系统管理员抓住了这个机会。他需要休假。他几乎在两天的课程中睡着了。第三天是实验室。讲师设置了三台机器。学生们被分成小组,应该花整个上午的时间找出他们的机器出了什么问题。
我们的英雄打开了他的机器,看了一分钟闪烁的灯光,然后说:“硬盘卡坏了。”讲师有点惊讶。“你怎么知道?”
“灯光不对。”
管理员走到下一台机器前,看了看,然后说:“内存接口卡坏了。”在下一台机器上,“处理器卡坏了。”
三台机器已经检查完毕。这个练习原本需要三个小组整个上午的时间,而这个家伙只用了两分钟就诊断了所有机器。(我之前和他谈过,他告诉我如果他知道自己在计时,他会工作得更快。)
毫不畏惧,讲师走到下午问题预留的机器旁。这台机器是由现场服务设置的,应该有一个非常困难的问题,几乎不可能找到。
讲师知道问题不能通过查看灯光来识别,他等待看看我们的英雄会做什么。这个人打开后盖,甚至在按下“开启”开关之前就指向一块板子说:“这块板子坏了;芯片 U18。它会导致间歇性的数据总线奇偶校验错误。”
现在讲师知道这个人很厉害,但即使没有打开机器也能找出坏板?这是不可能的。
“你怎么知道它是坏的?”他问。
管理员指向角落里一个小小的蓝色标签。“看到那个点了吗?我把它放在那里是为了确保 DEC 不会把我的板子还给我。这块板子是大学的。是我最初发现了问题并向 DEC 现场服务展示了它。”
上午和下午的问题现在在约 10 分钟内得到了解决,班级决定是时候出去吃披萨和喝啤酒了。
![]() |
|---|
程序 53:最大困惑
最大函数很简单,测试代码也很简单,答案是……好吧,你自己想吧。
1 /************************************************
2 * test_max -- Test the max function. *
3 ************************************************/
4 #include <iostream>
5
6 /************************************************
7 * max -- return the larger of two integers. *
8 * *
9 * Returns: *
10 * biggest of the two numbers. *
11 ************************************************/
12 const int &max(
13 const int &i1, // A number
14 const int &i2 // Another number
15 )
16 {
17 if (i1 > i2)
18 return (i1);
19 return (i2);
20 }
21
22 int main()
23 {
24 // I is the biggest of the two expression
25 const int &i = max(1+2, 3+4);
26
27 std::cout <<
28 "The biggest expression is " <<
29 i << std::endl;
30
31 return (0);
32 }
(下一页 提示 289。 答案 22。)
![]() |
|---|
人类难免会犯错误。要真正搞砸,你需要一台电脑。
![]() |
|---|
程序 54:跳入深水区
为什么这个程序会泄漏内存?
1 /************************************************
2 * Combine strings with a variable length *
3 * string class. *
4 ************************************************/
5 #include <setjmp.h>
6 #include <iostream>
7 #include <cstring>
8
9 // Place to store jump information
10 static jmp_buf top_level;
11
12 // Longest string combination allowed.
13 static const unsigned int MAX_LENGTH = 30;
14
15 /************************************************
16 * combine -- Combine two strings with *
17 * limit checking *
18 ************************************************/
19 static std::string combine(
20 const std::string &first, // First string
21 const std::string &second // Second string
22 )
23 {
24 // Strings put together
25 std::string together = first + second;
26
27 if (together.length() > MAX_LENGTH) {
28 longjmp(top_level, 5);
29 }
30 return (together);
31 }
32
33 int main()
34 {
35 std::string first("First ");
36 int i;
37
38 for (i = 0; i < 10; i++) {
39
40 // Save our place
41 if (setjmp(top_level) == 0)
42 {
43 first = combine(first,
44 std::string("second "));
45 } else {
46 std::cout <<
47 "Length limit exceeded\n";
48 break;
49 }
50 }
51 return (0);
52 }
(下一提示 146。答案 66。)
程序 55:害羞的编程
布朗农夫,一个养羊人,有一个邻居只需看一眼羊群就能立刻说出有多少只羊。他想知道他的朋友怎么能这么快地数数,于是他问他。
"爱恩,你怎么能这么快地知道你有多少只羊?"
"简单," 爱恩回答说。 "我只是数数腿,然后除以 4。"
布朗农夫对此印象深刻,他写了一个简短的 C++程序来验证爱恩的羊群计数算法。它对大型羊群不起作用。为什么?
1 /************************************************
2 * sheep -- Count sheep by counting the *
3 * number of legs and dividing by 4\. *
4 ************************************************/
5 #include <iostream>
6
7 /*
8 * The number of legs in some different
9 * size herds.
10 */
11 const short int small_herd = 100;
12 const short int medium_herd = 1000;
13 const short int large_herd = 10000;
14
15 /************************************************
16 * report_sheep -- Given the number of legs, *
17 * tell us how many sheep we have. *
18 ************************************************/
19 static void report_sheep(
20 const short int legs // Number of legs
21 )
22 {
23 std::cout <<
24 "The number of sheep is: " <<
25 (legs/4) << std::endl;
26 }
27
28 int main() {
29 report_sheep(small_herd*4); // Expect 100
30 report_sheep(medium_herd*4);// Expect 1000
31 report_sheep(large_herd*4); // Expect 10000
32 return (0);
33 }
(下一个提示 165。 答案 1。)
程序 56:程序中的魔法消失了
以下程序旨在检查两个目录中的两个文件是否包含一个魔数。
在我们的测试案例中,我们有以下文件:
first/first
second/second
这两个文件都包含魔数。
程序输出什么,为什么?
1 /************************************************
2 * scan_dir -- Scan directories for magic files *
3 * and report the results. *
4 * *
5 * Test on the directories "first" and "second".*
6 ************************************************/
7 #include <iostream>
8 #include <dirent.h>
9 #include <fcntl.h>
10 #include <unistd.h>
11 const long int MAGIC = 0×464c457f; // Linux executable magic #
12 /************************************************
13 * next_file -- find a list of files with magic *
14 * numbers that match the given number. *
15 * *
16 * Returns the name of the file or *
17 * NULL if no more files. *
18 ************************************************/
19 char *next_file(
20 DIR *dir // Directory to scan
21 ) {
22 // Current entry in the dir
23 struct dirent *cur_ent;
24
25 while (1) {
26
27 cur_ent = readdir(dir);
28 if (cur_ent == NULL)
29 return (NULL);
30
31 int fd = open(cur_ent->d_name, 0_RDONLY);
32 if (fd < 0) {
33 // Can't get the file so try again
34 continue;
35 }
36
37 int magic; // The file's magic number
38
39 // Size of the header read
40 int read_size =
41 read(fd, &magic, sizeof(magic));
42
43 if (read_size != sizeof(magic)) {
44 close(fd);
45 continue;
46 }
47
48 if (magic == MAGIC) {
49 close(fd);
50 return (cur_ent->d_name);
51 }
52 close(fd);
53 }
54 }
55 /************************************************
56 * scan_dir -- Scan a directory for the files *
57 * we want. *
58 ************************************************/
59 char *scan_dir(
60 const char dir_name[] // Directory name to use
61 ) {
62 // Directory to scan
63 DIR *dir_info = opendir(dir_name);
64 if (dir_info == NULL)
65 return (NULL);
66
67 chdir(dir_name);
68
69 // Name of the file we just found
70 char *name = next_file(dir_info);
71 closedir(dir_info);
72
73 chdir(".."); // Undo the original chdir
74
75 return (name);
76 }
77
78 int main() {
79 // Find a file in the directory "first"
80 char *first_ptr = scan_dir("first");
81
82 // Find a file in the directory "second"
83 char *second_ptr = scan_dir("second");
84
85 // Print the information about the dir first
86 if (first_ptr == NULL) {
87 std::cout << "First: NULL ";
88 } else {
89 std::cout << "First: " << first_ptr << " ";
90 }
91 std::cout << '\n';
92
93 // Print the information about the dir second
94 if (second_ptr == NULL) {
95 std::cout << "Second: NULL ";
96 } else {
97 std::cout << "Second: " << second_ptr << " ";
98 }
99 std::cout << '\n';
100 return (0);
101 }
(下一个提示 86. 答案 100.)
![]() |
|---|
真正的软件工程师从早上 9 点工作到下午 5 点,因为这是正式规范中描述工作的方式。加班工作会感觉像是使用未记录的外部过程。
![]() |
|---|
程序 57:如何不读取文件
以下代码中存在哪些可移植性问题?
1 #include <iostream>
2
3 /*
4 * A data structure consisting of a flag
5 * which indicates which long int parameter
6 * follows.
7 */
8 struct data
9 {
10 // Flag indicating what's to follow
11 char flag;
12
13 // Value of the parameter
14 long int value;
15 };
16
17 /************************************************
18 * read_data -- Read data from the given file *
19 ************************************************/
20 void read_data(
21 std::istream &in_file, // File to read
22 struct data &what // Data to get
23 )
24 {
25 in_file.read(
26 dynamic_cast<char *>(&what),
27 sizeof(what));
28 }
(提示 161 答案 71。)
![]() |
|---|
一个电子考勤程序有一个有趣的方式来结束:
Timecard entry complete
Press 'Enter' to exit the program.
![]() |
|---|
程序 58:奇怪的名称
子程序 tmp_name 的设计目的是返回一个临时文件的名字。其想法是在每次调用时生成一个唯一的名字:/var/tmp/tmp.0, /var/tmp/tmp.1, /var/tmp/tmp.2, ...
生成的名字当然都是唯一的,但并非程序员所期望的。
1 /************************************************
2 * tmp_test -- test out the tmp_name function. *
3 ************************************************/
4 #include <iostream>
5 #include <cstdio>
6 #include <cstring>
7 #include <sys/param.h>
8 /************************************************
9 * tmp_name -- return a temporary file name *
10 * *
11 * Each time this function is called, a new *
12 * name will be returned. *
13 * *
14 * Returns: Pointer to the new file name. *
15 ************************************************/
16 char *tmp_name(void) {
17 // The name we are generating
18 char name[MAXPATHLEN];
19
20 // The base of the generated name
21 const char DIR[] = "/var/tmp/tmp";
22
23 // Sequence number for last digit
24 static int sequence = 0;
25
26 ++sequence; /* Move to the next file name */
27
28 sprintf(name, "%s.%d", DIR, sequence);
29 return(name);
30 }
31 int main() {
32 char *a_name = tmp_name(); // A tmp name
33 std::cout << "Name: " << a_name << std::endl;
34 return(o);
35 }
(下一个提示 176. 答案 18.)
程序 59:怪异名称之子
这个程序设计用来每次调用 tmp_name 时生成独特的名称。为了测试它,我们决定打印几个名称。然而,我们的测试并没有成功。为什么?
1 /************************************************
2 * test the tmp_name function. *
3 ************************************************/
4 #include <iostream>
5 #include <cstdio>
6 #include <cstring>
7 #include <sys/param.h>
8 /************************************************
9 * tmp_name -- return a temporary file name. *
10 * *
11 * Each time this function is called, a new *
12 * name will be returned. *
13 * *
14 * Returns *
15 * Pointer to the new file name. *
16 ************************************************/
17 char *tmp_name(void)
18 {
19 // The name we are generating
20 static char name[MAXPATHLEN];
21
22 // The directory to put the temporary file in
23 const char DIR[] = "/var/tmp/tmp";
24
25 // Sequence number for last digit
26 static int sequence = 0;
27
28 ++sequence; /* Move to the next file name */
29
30 std::sprintf(name, "%s.%d", DIR, sequence);
31 return(name);
32 }
33
34 int main()
35 {
36 // The first temporary name
37 char *a_name = tmp_name();
38
39 // The second temporary name
40 char *b_name = tmp_name();
41
42 std::cout << "Name (a): " << a_name << endl;
43 std::cout << "Name (b): " << b_name << endl;
44 return(0);
45 }
(下一个提示 322。答案 64。)
![]() |
|---|
我被分配去编写一个新建的灯光面板的代码。灯光 #1 应该是 "数据失败",灯光 #2 是 "更换过滤器",灯光 #3 是 "油压低" 等等。
短暂的测试揭示出面板的线路接错了。灯光 #1 是 "油压低",灯光 #2 也是 "油压低",以此类推。
我仔细记下了灯光的编号,然后找到了硬件设计师。
"伍迪,"我说,“这些灯光的线路接错了。”
"你现在知道它们是如何接线的了吗?"
我把我的清单交给他。他接过我的清单,快速地看了一眼,走到复印机旁复印了一份。然后他给了我一份复印件(甚至不是原件)并说:“这是新的规格说明书。”
![]() |
|---|
程序 60:怪异名字的孙子
因此,我们再次修复了我们的程序,现在正在使用 C++ 字符串。但事情仍然没有正常工作。为什么?
1 #include <iostream>
2 #include <string>
3
4 /************************************************
5 * tmp_name -- return a temporary file name *
6 * *
7 * Each time this function is called, a new *
8 * name will be returned. *
9 * *
10 * Returns *
11 * String containing the name. *
12 ************************************************/
13 std::string &tmp_name()
14 {
15 // The name we are generating
16 std::string name;
17
18 // Sequence number for last digit
19 static int sequence = 0;
20
21 ++sequence; // Move to the next file name
22
23 name = "tmp";
24
25 // Put in the squence digit
26 name += static_cast<char>(sequence + '0');
27
28 return(name);
29 }
30
31 int main()
32 {
33 std::string name1 = tmp_name();
34
35 std::cout <<"Name1: " << name1 << '\n';
36 return(0);
37 }
(下一提示 361。答案 36。)
程序 61:缓慢地查阅词典
我在加州理工学院读大二的时候写了以下程序。(最初是用 Pascal 编写的。)我拼写能力很差,所以我需要一些东西来帮助我在词典中查找单词。
我决定编写一个程序,将词典读入到一个二叉树(这是我刚刚学到的一种数据结构)中,并在其中查找单词。
二叉树应该是一种高效的数据结构,但这个程序执行起来却花费了非常长的时间。
为什么?
1 /************************************************
2 * find_word -- find a word in the dictionary. *
3 * *
4 * Usage: *
5 * find_word <word-start> [<word-start>...] *
6 ************************************************/
7 #include <iostream>
8 #include <fstream>
9 #include <iomanip>
10 #include <cctype>
11 #include <cstring>
12 #include <cstdlib>
13
14 /************************************************
15 * tree -- A simple binary tree class *
16 * *
17 * Member functions: *
18 * enter -- Add an entry to the tree *
19 * find -- See if an entry is in the tree. *
20 ************************************************/
21 class tree
22 {
23 private:
24 // The basic node of a tree
25 class node {
26 private:
27 // tree to the right
28 node *right;
29
30 // tree to the left
31 node *left;
32 public:
33 // data for this tree
34 char *data;
35
36 public:
37 node() :
38 right(NULL), left(NULL),
39 data(NULL) {}
40 // Destructor defaults
41 private:
42 // No copy constructor
43 node(const node &);
44
45 // No assignment operator
46 node & operator = (const node &);
47
48 // Let tree manipulate our data
49 friend class tree;
50
51 };
52
53 // the top of the tree
54 node *root;
55
56 // Enter a new node into a tree or
57 // sub-tree
58 void enter_one(
59 // Node of sub-tree to look at
60 node *&node,
61
62 // Word to add
63 const char *const data
64 );
65
66 // Find an item in the tree
67 void find_one(
68 // Prefix to search for
69 const char start[],
70
71 // Node to start search
72 const node *const node,
73
74 // Keep looking flag
75 const bool look
76 );
77 public:
78 tree(void) { root = NULL;}
79 // Destructor defaults
80 private:
81 // No copy constructor
82 tree(const tree &);
83
84 // No assignment operator
85 tree & operator = (const tree &);
86
87 public:
88 // Add a new data to our tree
89 void enter(
90 // Data to add
91 const char *const data
92 ) {
93 enter_one(root, data);
94 }
95
96 // Find all words that start
97 // with the given prefix
98 void find(
99 const char start[] // Starting string
100 )
101 {
102 find_one(start, root, true);
103 }
104 };
105
106 /************************************************
107 * tree::enter_one -- enter a data into *
108 * the tree *
109 ************************************************/
110 void tree::enter_one(
111 node *&new_node, // Sub-tree to look at
112 const char *const data // Word to add
113 )
114 {
115 int result; // result of strcmp
116
117 // see if we have reached the end
118 if (new_node == NULL) {
119 new_node = new node;
120
121 new_node->left = NULL;
122 new_node->right = NULL;
123 new_node->data = strdup(data);
124 }
125
126 result = strcmp(new_node->data, data);
127 if (result == 0) {
128 return;
129 }
130
131 if (result < 0)
132 enter_one(new_node->right, data);
133 else
134 enter_one(new_node->left, data);
135 }
136
137 /************************************************
138 * tree::find_one -- find words that match this *
139 * one in the tree. *
140 ************************************************/
141 void tree::find_one(
142 const char start[], // Start of the work
143 const node *const top,// Top node
144 const bool look // Keep looking
145 )
146 {
147 if (top == NULL)
148 return; // short tree
149
150 // Result of checking our prefix
151 // against the word
152 int cmp = strncmp(start,
153 top->data, strlen(start));
154
155 if ((cmp < 0) && (look))
156 find_one(start, top->left, true);
157 else if ((cmp > 0) && (look))
158 find_one(start, top->right, true);
159
160 if (cmp != 0)
161 return;
162
163 /*
164 * We found a string that starts this one.
165 * Keep searching and print things.
166 */
167 find_one(start, top->left, false);
168 std::cout << top->data << '\n';
169 find_one(start, top->right, false);
170 }
171
172 int main(int argc, char *argv[])
173 {
174 // A tree to hold a set of words
175 tree dict_tree;
176
177 // The dictionary to search
178 std::ifstream dict_file("/usr/dict/words");
179
180 if (dict_file.bad()) {
181 std::cerr <<
182 "Error: Unable to open "
183 "dictionary file\n";
184 exit (8);
185 }
186
187 /*
188 * Read the dictionary and construct the tree
189 */
190 while (1) {
191 char line[100]; // Line from the file
192
193 dict_file.getline(line, sizeof(line));
194
195 if (dict_file.eof())
196 break;
197
198 dict_tree.enter(strdup(line));
199 }
200 /*
201 * Search for each word
202 */
203 while (argc > 1) {
204 std::cout << "------ " << argv[1] << '\n';
205 dict_tree.find(argv[1]);
206 ++argv;
207 --argc;
208 }
209 return (0);
210 }
(下一个提示 42. 答案 74.)
程序 62:一路打包
有什么比给两个常量赋值并打印它们更简单的事情吗?然而,在这件如此简单的事情中存在一个问题。为什么其中一个邮政编码是错误的?
1 /************************************************
2 * print_zip -- Print out a couple of zip codes.*
3 ************************************************/
4 #include <iostream>
5 #include <iomanip>
6
7 int main()
8 {
9 // Zip code for San Diego
10 const long int san_diego_zip = 92126;
11
12 // Zip code for Boston
13 const long int boston_zip = 02126;
14
15 std::cout << "San Diego " << std::setw(5) <<
16 std::setfill('0') <<
17 san_diego_zip << std::endl;
18
19 std::cout << "Boston " << std::setw(5) <<
20 std::setfill('0') <<
21 boston_zip << std::endl;
22
23 return (0);
24 }
(下一个提示 206。答案 15。)
![]() |
|---|
奥瓦林计算机定律
-
在计算机科学中,没有什么比牢固掌握明显的东西更重要。
-
关于计算机,没有什么明显的东西。
![]() |
|---|
第五章:C 代码,C 代码中断
尽管语言设计者做出了努力,但仍然有很多 C 代码存在。C 是一种语言,并且有自己的问题集。以下是一些只有 C 程序员才能犯的独特和特殊错误。
程序 63:名字游戏
这个程序本应将你的名字和姓氏组合起来并打印出来。
一个示例运行应该看起来像:
First: John
Last: Smith
Hello: John Smith
Thank you for using Acme Software.
但程序实际上做了什么?
1 /************************************************
2 * Greetings -- Ask the user for his first *
3 * name and his last name. *
4 * Then issue a greeting. *
5 ************************************************/
6 #include <stdio.h>
7 #include <string.h>
8 int main()
9 {
10 char first[100]; /* The first name */
11 char last[100]; /* The last name */
12 char full_name[201];/* The full name */
13
14 /* Get the first name */
15 printf("First: ");
16 fgets(first, sizeof(first), stdin);
17
18 /* Get the last name */
19 printf("Last: ");
20 fgets(last, sizeof(last), stdin);
21
22 /* Make full_name = "<first> <last>" */
23 strcpy(full_name, first);
24 strcat(full_name, " ");
25 strcat(full_name, last);
26
27 /* Greet the user by name */
28 printf("Hello %s\n", full_name);
29 printf("Thank you for "
30 "using Acme Software.\n");
31 return (0);
32 }
(下一个提示 340. 答案 33.)
程序 64:眼中的 π
文件 math.h 定义了常量 M_PI。当我们打印这个常量时,我们会得到什么?
1 /************************************************
2 * PI -- Test program to see verify that *
3 * the value of "pi" in math.h is *
4 * correct. *
5 ************************************************/
6 /* math.h defines M_PI */
7 #include <math.h>
8 #include <stdio.h>
9
10 int main()
11 {
12 printf("pi is %d\n", M_PI);
13 return (0);
14 }
(下一个提示 198。答案 10。)
![]() |
|---|
加州理工学院有人编写了一个程序,在你登录时给你一个友好的问候。这是一个非常聪明的程序;其中一部分逻辑检查作者的账户,看是否有新版本的程序发布。如果有,程序会用自己的后续版本替换自己。
有一天,作者毕业了,他的账户被删除了。程序检测到这个错误状态,并迅速发出了一条消息:
?LGNPFB Program fall down and go boom.
![]() |
|---|
程序 65:临时疯狂
有时返回一个错误的文件名。有时程序会崩溃。为什么?
1 /************************************************
2 * full_test -- Test the full_name function *
3 ************************************************/
4 #define PATH "/usr/tmp"
5
6 /************************************************
7 * full_name -- Given the name of a file, *
8 * return a full path name. *
9 * *
10 * Returns: Absolute path to the file name. *
11 ************************************************/
12 char *full_name(
13 const char name[] /* Base file name */
14 )
15 {
16 /* Full file name */
17 static char file_name[100];
18
19 strcpy(file_name, PATH);
20 strcat(file_name, '/');
21 strcat(file_name, name);
22 return (file_name);
23 }
24
25 int main()
26 {
27 /* Test the full_name funtion */
28 printf("Full name is %s\n",
29 full_name("data"));
30 return (0);
31 }
32
(提示 320。答案 41。)
程序 66:无缓冲区
程序员决定通过增加缓冲区的大小来加速缓冲 I/O。通常这会使事情变得更快,但在这个情况下,它却使事情变得奇怪。为什么?
1 /************************************************
2 * buffer demo. Show how big buffers can speed *
3 * up I/O. *
4 ************************************************/
5 #include <stdio.h>
6
7 /* Nice big buffer */
8 #define BUF_SIZE (50 * 1024)
9
10 /************************************************
11 * print_stuff -- Print a bunch of stuff in a *
12 * big buffer. *
13 ************************************************/
14 void print_stuff(void)
15 {
16 // Buffer to hold the data
17 char buffer[BUF_SIZE];
18
19 // Printing counter.
20 int i;
21
22 /* Things go much faster with this */
23 setbuf(stdout, buffer);
24
25 for (i = 0; i < 10; ++i)
26 printf("Hello world\n");
27 }
28
29
30 int main()
31 {
32 print_stuff();
33 printf("That's all\n");
34 return (0);
35 }
(下一个提示 74。答案 83。)
程序 67:让我们来玩“隐藏问题”游戏
以下程序在 UNIX 系统上因浮点除法错误而崩溃。这令人困惑,因为我们没有进行任何浮点运算。
为了找到问题,我们添加了几条 printf 语句,并发现问题发生在函数调用之前某个地方。我们可以这样判断,因为我们从未看到“开始”信息。
1 /************************************************
2 * Compute a simple average. Because this *
3 * takes a long time (?) we output some *
4 * chatter as we progress through the system. *
5 ************************************************/
6 #include <stdio.h>
7
8 /************************************************
9 * average -- Compute the average given the *
10 * total of the series and the number *
11 * of items in the series. *
12 * *
13 * Returns: *
14 * The average. *
15 ************************************************/
16 int average(
17 const int total,// The total of the series
18 const int count // The number of items
19 )
20 {
21 return (total/count);
22 }
23
24 int main()
25 {
26 int ave; // Average of the number
27
28 printf("Starting....");
29 ave = average(32, 0);
30 printf("..done\n");
31
32 printf("The answer is %d\n", ave);
33 return (0);
34 }
(下一提示 108。答案 68。)
程序 68:计算错误
这里的任务是制作一个四则运算计算器。用户应该输入一个运算符和一个数字,然后计算器开始工作。例如:
Enter operator and value: + 10
Total: 10
但事情并没有像预期的那样进行。
1 /************************************************
2 * calc -- Simple 4 function calculator. *
3 * *
4 * Usage: *
5 * $ calc *
6 * Enter operator and value: + 5 *
7 * *
8 * At the end of each operation the accumulated *
9 * results are printed. *
10 ************************************************/
11 #include <stdio.h>
12 int main() {
13 char oper; /* Operator for our calculator */
14 int result;/* Current result */
15 int value; /* Value for the operation */
16
17 result = 0;
18 while (1)
19 {
20 char line[100]; // Line from the user
21 printf("Enter operator and value:");
22
23 fgets(line, sizeof(line), stdin);
24 sscanf(line, "%c %d", oper, value);
25
26 switch (oper) {
27 case '+':
28 result += value; break;
29 case '-':
30 result -= value; break;
31 case '*':
32 result *= value; break;
33 case '/':
34 if (value == 0)
35 printf("Divide by 0 error\n");
36 else
37 result /= value;
38 break;
39 case 'q':
40 exit (0);
41 default:
42 printf("Bad operator entered\n"); break;
43 }
44 printf("Total: %d\n", result);
45 }
46 }
(下一个 提示 73. 答案 95.)
![]() |
|---|
一家公司遇到了问题。一些客户从其软件中删除了公司名称和版权信息。程序员们被要求想出一个防止这种情况的方法。因此,他们加入了一些代码来校验版权信息,如果结果不正确,就会显示一个错误信息:
Fatal error:
Water buffalos need immediate feeding
Call 1-800-555-1234 for technical support.
这个想法是,这个错误信息会如此奇怪,以至于违法者会打电话给技术支持来弄清楚它的意思。(它的真正意思是,“我非法修改了你的程序。”)
![]() |
|---|
程序 69:求和问题
这个程序被设计用来加三个数字,1、2 和 3。但是当我们运行它时,程序产生了以下结果:
Sum is 1343432864
为什么?
1 /************************************************
2 * sum_test -- Test the sum function *
3 ************************************************/
4 #include <stdio.h>
5
6 /************************************************
7 * sum -- Sum up three numbers *
8 * *
9 * Returns: The sum of the numbers. *
10 ************************************************/
11 int sum(i1, i2, i3)
12 {
13 int i1; /* The first number */
14 int i2; /* The second number */
15 int i3; /* The third number */
16
17 return (i1 + i2 + i3);
18 }
19
20 int main()
21 {
22 printf("Sum is %d\n", sum(1, 2, 3));
23 return (0);
24 }
25
(下一个提示 69. 答案 94.)
![]() |
|---|
"Yacc" 从一个极具启发性的用户群体中受益良多,他们不断推动我超越我的倾向,甚至超越我的能力去寻找“一个更多功能”。他们不愿意学习如何按照我的方式做事的烦恼通常导致我按照他们的方式做事;大多数时候,他们是对的。
- S. C. Johnson, "Yacc 指南致谢"
![]() |
|---|
程序 70:两个简单
为什么 2 + 2 = 5986?
1 /************************************************
2 * two_plus_two -- So what is 2+2 anyway? *
3 ************************************************/
4 #include <stdio.h>
5
6 int main()
7 {
8 /* Result of the addition */
9 int answer = 2 + 2;
10
11 printf("The answer is %d\n");
12 return (0);
13 }
(下一个提示 164。答案 85。)
![]() |
|---|
在你的银行支票底部有一系列数字,表示你的银行和账户号码。骗子在纽约开设了一个账户,存入 5 美元。然后他创建了属于自己的支票。这些支票与他真实的支票相同,只是银行号码被修改了,使其指向洛杉矶的一家银行。
接着,他在纽约又开设了一个账户,并使用 10,000 美元的支票作为初始存款。支票进入了自动分拣设备,计算机看到了洛杉矶的银行号码,于是将支票发送到了洛杉矶。洛杉矶的银行看到这笔支票不是给他们的,于是将支票退回到了纽约的清算所。它又回到了自动分拣设备,计算机看到了洛杉矶的银行号码,于是又将其退回到了洛杉矶。
检查现在处于一个无休止的循环中,在纽约和洛杉矶之间来回穿梭。当它一圈又一圈地转时,骗子去了他最初存款的银行,先存入支票,然后要求取出所有钱。出纳员查看了最近的存款记录,发现是两周前,于是假设支票已经清算。毕竟,纽约的支票只需几天就能到达正确的银行。所以出纳员给了骗子 10,000 美元,然后他消失了。
几周后,支票已经磨损得无法再投入自动分拣设备。因此,它被人工分拣,最终被送到了正确的银行。
(这被称为“大规模利用浮动”)
![]() |
|---|
程序 71:未同步
这里的任务是制作一个四功能计算器。用户应该输入一个运算符和一个数字,然后计算器开始工作。例如:
Enter operator and value: + 10
Total: 10
但事情并没有像预期的那样进行。
1 /************************************************
2 * calc -- Simple 4 function calculator. *
3 * *
4 * Usage: *
5 * $ calc *
6 * Enter operator and value: + 5 *
7 * *
8 * At the end of each operation the acculated *
9 * results are printed. *
10 ************************************************/
11 #include <stdio.h>
12 #include <stdlib.h>
13 int main() {
14 char oper; /* Operator for our calculator */
15 int result;/* Current result */
16 int value; /* Value for the operation */
17
18 result = 0;
19 while (1)
20 {
21 printf("Enter operator and value:");
22 scanf("%c %d", &oper, &value);
23
24 switch (oper) {
25 case '+':
26 result += value;
27 break;
28 case '-':
29 result -= value;
30 break;
31 case '*':
32 result *= value;
33 break;
34 case '/':
35 if (value == 0)
36 printf("Divide by 0 error\n");
37 else
38 result /= value;
39 break;
40 case 'q':
41 exit (0);
42 default:
43 printf("Bad operator entered\n"); break;
44 }
45 printf("Total: %d\n", result);
46 }
47 }
(下一个提示 224。答案 28。)
![]() |
|---|
真正的程序员不屑于结构化编程。结构化编程是为那些过早接受如厕训练的强迫性神经质者准备的。这些人会打领带,并在其他方面干净的桌子上仔细排列铅笔。
真正的程序员不会带棕色纸袋午餐。如果自动售货机没有卖,他们就不会吃。自动售货机不卖千层面。
![]() |
|---|
程序 72:没有尽头
这个简单的程序是将标准输入复制到标准输出。这是学生编写的第一个与输入/输出相关的程序之一。
1 /************************************************
2 * copy -- Copy stdin to stdout. *
3 ************************************************/
4 #include <stdio.h>
5
6 int main()
7 {
8 // Character to copy
9 char ch;
10
11 while ((ch = getchar()) != EOF)
12 {
13 putchar(ch);
14 }
15 return (0);
16 }
(下一条提示 15。答案 63。)
![]() |
|---|
"构建软件设计的两种方式。一种方式是让它如此简单,以至于显然没有缺陷,另一种方式是让它如此复杂,以至于没有明显的缺陷。"
—— C. A. R. Hoare
![]() |
|---|
第六章:过早损坏
C++预处理器使语言具有更多的灵活性。它也为你提供了很多新的出错方式。
程序 73:无意义
sam 和 joe 的变量类型有哪些?
1 /************************************************
2 * Toy program that declares two variables *
3 ************************************************
4 #define CHAR_PTR char *
5
6 int main()
7 {
8 CHAR_PTR sam, joe;
9
10 return (0);
11 }
(下一个提示 298。答案 78。)
![]() |
|---|
我参与了第一台商用水射流切割机的研发工作。这台机器本质上是一把巨大的喷枪,用高压水射流切割出运动鞋内垫。
由于这是第一个制作的,我们花了很多时间调整它。大约一年。我们与购买该机器的制鞋商有协议。如果我们把切割好的部件寄回给他们,他们会免费提供原材料给我们测试。
大约一年时间里我们进行了测试。由于我们希望得到一致的结果,所以我们几乎总是使用相同的测试尺寸:9 个右脚。我们尽职尽责地将切割好的部件装箱并寄给制鞋商,以便他们可以用这些部件制作鞋子。或者我们认为是这样。
在我们计划发货的前一周,我们接到制鞋厂某人的电话。
制鞋厂: "你们是那些一直把所有 9 个右脚发给我们的人吗?"
我们: "是的。"
制鞋厂: "终于找到你们了。我一直在试图追踪你们已经一年了。采购部门没有你们任何切割部件订单的记录,而且很难找到你们。"
我们: "有什么问题吗?"
植物: "是的。你意识到你给我们发了 10,000 个右脚而没有左脚吗?"
![]() |
|---|
程序 74:重大错误
为什么以下程序在第 16 行报告语法错误?第 16 行有什么问题?
1 /************************************************
2 * gross -- Print out a table of 1 to 10 gross. *
3 ************************************************/
4 // A Gross is a dozen - dozen
5 #define GROSS (12 ** 2)
6
7 #include <iostream>
8
9 int main()
10 {
11 int i; // Index into the table
12
13 for (i = 1; i <= 10; ++i)
14 {
15 std::cout << i << " gross is " <<
16 (GROSS * i) << '\n';
17 }
18
19 return (0);
20 }
(下一 提示 275。 答案 79。)
![]() |
|---|
编写无错误的程序有两种方法。只有第三种方法有效。
![]() |
|---|
程序 75:快速退出
ABORT 宏的设计是为了发出错误信息并退出。当出现问题时,程序应该终止。
当我们出现错误时,程序会退出。即使没有错误,程序也会退出。实际上,无论发生什么,程序都会退出。
为什么?
1 /************************************************
2 * Test the square_root function. *
3 ************************************************/
4 #include <iostream>
5 #include <math.h>
6
7 /************************************************
8 * ABORT -- print an error message and abort. *
9 ************************************************/
10 #define ABORT(msg) \
11 std::cerr << msg << std::endl;exit(8);
12 /************************************************
13 * square_root -- Find the square root of the *
14 * value. *
15 * *
16 * Returns: *
17 * The square root. *
18 ************************************************/
19 static int square_root(
20 const int value
22 ) {
23 if (value < 0)
24 ABORT("Illegal square root");
25
26 return (int(sqrt(float(value))));
27 }
28
29 int main() {
30 int square; // A number that's square
31 int root; // The square root of the number
32
33 square = 5 * 5;
34 root = square_root(square);
35
36 std::cout << "Answer is: " << root << '\n';
37 return (0);
38 }
(下一提示 33. 答案 105.)
程序 76:双倍麻烦
宏变量 DOUBLE 的设计目的是将其参数的值加倍。测试程序打印出从 1 到 5 的数字的 DOUBLE 值。然而,出了些问题。发生了什么?
1 /************************************************
2 * Double -- Print double table. *
3 * *
4 * Print the numbers 1 through 5 and their *
5 * doubles. *
6 ************************************************/
7 #include <iostream>
8
9 /************************************************
10 * DOUBLE -- Given a number return its double. *
11 ************************************************/
12 #define DOUBLE(x) (x * 2)
13
14 int main()
15 {
16 int i; // Number to print and to double
17
18 for (i = 0; i < 5; ++i) {
19 std::cout << "The double of " << i+1 <<
20 " is " << DOUBLE(i+1) << std::endl;
21 }
22
23 return (0);
24 }
(下一个 提示 133. 答案 46.)
![]() |
|---|
"C 编程语言——一种结合了汇编语言灵活性和汇编语言强大功能的语言。"
![]() |
|---|
程序 77:无值
以下程序无法编译,因为值未定义。我们从未使用变量 value,那么问题出在哪里呢?
1 /************************************************
2 * double -- Print a double table for the *
3 * numbers 1 through 10\. *
4 ************************************************/
5 #include <iostream>
6
7 /************************************************
8 * DOUBLE -- Macro to double the value of a *
9 * number. *
10 ************************************************/
11 #define DOUBLE (value) ((value) + (value))
12
13 int main()
14 {
15 // Counter for the double list
16 int counter;
17
18 for (counter = 1; counter <= 10; ++counter)
19 {
20 std::cout << "Twice " << counter << " is " <<
21 DOUBLE(counter) << '\n';
22 }
23
24 return (0);
25 }
(下一提示 118。答案 113。)
程序 78:误差范围
如果我们的纸张宽度为 8.5 英寸,并且使用 1 英寸作为边距(每边 1/2 英寸),我们还能剩下多少可用空间?任何人都能看出结果是 7.5 英寸。但这个程序看问题的角度不同。发生了什么?
1 /************************************************
2 * paper_size -- Find the usable width on *
3 * a page. *
4 ************************************************/
5 #define PAPER_WIDTH 8.5; // Width of the page
6 #define MARGIN 1.0; // Total margins
7 // Usable space on the page
8 #define USABLE PAPER_WIDTH -MARGIN;
9
10 #include <iostream>
11
12 int main()
13 {
14 // The usable width
15 double text_width = USABLE;
16
17 std::cout << "Text width is " <<
18 text_width << '\n';
19 return (0);
20 }
(下一个提示 45。答案 82。)
![]() |
|---|
在我的业余时间,我把《冒险》游戏移植到了公司电脑上,并且花了很多额外的时间玩游戏。一天早上,我的经理把我叫进了他的办公室。
"你把《冒险》游戏放到系统上了吗?”他问。
"我是在业余时间完成的,”我回答。
"哦,我并不是在批评你,”他向我保证。“事实上,我想表扬你。自从这个项目开始以来,我们每天都有营销部门负责人比尔(Bill)的拜访。他每天都会进来,玩玩软件,然后坚持要求做出改变。但就在过去的一周里,他全部的时间都花在了玩《冒险》游戏上,没有时间提出更改请求。我只是想谢谢你让他不要打扰我。”
![]() |
|---|
程序 79:公平交易
C++ 没有幂运算符,所以我们定义了自己的宏来计算 X²。我们决定通过打印从 1 到 10 的数的平方来测试这个宏。但我们实际上打印了什么呢?
1 /************************************************
2 * Print out the square of the numbers *
3 * from 1 to 10\. *
4 ************************************************/
5 #include <iostream>
6
7 /*********************************** ************
8 * macro to square a number. *
9 ************************************************/
10 #define SQR(x) ((x) * (x))
11
12 int main()
13 {
14 int number; // The number we are squaring
15
16 number = 1;
17
18 while (number <= 10) {
19 std::cout << number << " squared is " <<
20 SQR(++number) << std::endl;
21 }
22
23 return (0);
24 }
(下一提示 200。答案 88。)
程序 80:面积炸弹
我们需要计算一个矩形的面积。我们有两部分顶部和侧面。但为什么下面的宏报告了一个错误的面积?
1 /************************************************
2 * Find the area of a rectangle. The top of *
3 * the rectangle consists of two parts, *
4 * cleverly called PART1 and PART2\. *
5 * The side is called SIDE. *
6 * *
7 * So our rectangle looks like: *
8 * <- TOP_PART1 ->|<-- TOP_PART2 -> | *
9 * +------------------------------+ ^ *
10 * | | | *
11 * | | | *
12 * | | | SIDE *
13 * | | | *
14 * | | | *
15 * +------------------------------+ V *
16 ************************************************/
17
18 // First leg of top is 37 feet
19 #define TOP_PART1 37
20
21 // Second part of the top is 33 feet
22 #define TOP_PART2 33
23
24 // Total top size
25 #define TOP_TOTAL TOP_PART1 + TOP_PART2
26
27 #define SIDE 10 // 10 Feet on a side
28
29 // Area of the rectangle
30 #define AREA TOP_TOTAL * SIDE
31
32 #include <iostream>
33
34 int main() {
35 std::cout << "The area is " <<
36 AREA << std::endl;
37 return (0);
38 }
(下一个提示 28. 答案 29.)
第七章:毫无品味的类
当 Bjarne Stroustrup 发明 C++时,他不仅创造了一种伟大的编程语言,而且还创造了一种赋予程序员巨大权力的伟大语言。他还给了程序员一套全新的出错方式。多亏了他的努力,本章中所有的程序才成为可能。
程序 81:感谢记忆
为什么这个程序会内存泄漏?
1 /************************************************
2 * play with a variable size stack class. *
3 ************************************************/
4
5 /************************************************
6 * stack -- Simple stack class *
7 * *
8 * Member functions: *
9 * push -- Push data on to the stack *
10 * pop -- remove an item from the stack. *
11 ************************************************/
12 class stack
13 {
14 private:
15 int *data; // The data
16 const int size; // The size of the data
17
18 // Number of items in the data
19 int count;
20 public:
21 // Create the stack
22 stack(
23 // Max size of the stack
24 const int _size
25 ):size(_size), count(0)
26 {
27 data = new int[size];
28 }
29 ~stack(void) {}
30 private:
31 // No copy constructor
32 stack(const stack &);
33
34 // No assignment operator
35 stack & operator = (const stack &);
36 public:
37 // Push something on the stack
38 void push(
39 // Value to put on stack
40 const int value
41 )
42 {
43 data[count] = value;
44 ++count;
45 }
46 // Remove an item from the stack
47 int pop(void)
48 {
49 --count;
50 return (data[count]);
51 }
52 };
53
54 int main()
55 {
56 stack a_stack(30);
57
58 a_stack.push(1);
59 a_stack.push(3);
60 a_stack.push(5);
61 a_stack.push(7);
62 return (0);
63 }
(下一提示 56。答案 32。)
程序 82:消失的数组案例
我们有一个简单的数组类和一个更简单的测试程序。然而不知何故,内存被破坏了。
1 /*************************************************
2 * var_array -- Test variable length array *
3 * class. *
4 *************************************************/
5 #include <memory.h>
6
7 /*************************************************
8 * var_array -- Variable length array *
9 * *
10 * Member functions: *
11 * operator [] -- Return a reference to *
12 * the item in the array. *
13 *************************************************/
14
15 class var_array
16 {
17 private:
18 int *data; // The data
19 const int size; // The size of the data
20 public:
21 // Create the var_array
22 var_array(const int _size):
23 size(_size)
24 {
25 data = new int[size];
26 memset(data, '\0',
27 size * sizeof(int));
28 }
29 // Destroy the var_array
30 ~var_array(void) {
31 delete []data;
32 }
33 public:
34 // Get an item in the array
35 int &operator [] (
36 // Index into the array
37 const unsigned index
38 )
39 {
40 return (data[index]);
41 }
42 };
43
44 /************************************************
45 * store_it -- Store data in the var_array *
46 ************************************************/
47 static void store_it(
48 // Array to use for storage
49 var_array test_array
50 )
51 {
52 test_array[1] = 1;
53 test_array[3] = 3;
54 test_array[5] = 5;
55 test_array[7] = 7;
56 }
57 int main()
58 {
59 var_array test_array(30);
60
61 store_it(test_array);
62 return (0);
63 }
(下一提示 189. 答案 59.)
![]() |
|---|
奥瓦林文档定律
90% 的时间,文档会丢失。在剩下的 10%中,9% 的时间是针对程序早期版本,因此完全无用。剩下的 1% 的时间里,你有了文档和正确的文档版本,它将用日语编写。
我把这个笑话讲给在摩托罗拉工作的同事听,他笑了几分钟,然后拿出他的日立 FORTRAN 手册,它是用日语写的。
![]() |
|---|
程序 83:混乱的输出
一位 C++学生想看看构造函数和析构函数是如何被调用的,因此他编写了以下程序。然而,他学到的比他预想的要多。问题是什么?
1 /************************************************
2 * Class tester. Test constructor / destructor*
3 * calling. *
4 ************************************************/
5 #include <iostream>
6
7 /************************************************
8 * tester -- Class that tells the world when *
9 * it's created and destroyed. *
10 ************************************************/
11 class tester {
12 public:
13 tester(void) {
14 std::cout <<
15 "tester::tester() called\n";
16 }
17 ~tester(void) {
18 std::cout <<
19 "tester::~tester() called\n";
20 }
21 };
22
23 static tester a_var; // Variable to test with
24
25 int main()
26 {
27 std::cout << "In main\n";
28 return (0);
29 }
(下一提示 157。答案 111。)
程序 84:建设项目
学生想看到复制构造函数和运算符 = 被调用的时候,所以他写了这个程序。但是结果让他很惊讶。发生了什么?
1 #include <iostream>
2 /************************************************
3 * trouble -- A class designed to store a *
4 * single data item. *
5 * *
6 * Member function: *
7 * put -- put something in the class *
8 * get -- get an item from the class *
9 ************************************************/
10 class trouble {
11 private:
12 int data; // An item to be stored
13 public:
14 trouble(void) { data = 0; }
15
16 trouble(const trouble &i_trouble) {
17 std::cout << "Copy Constructor called\n";
18 *this = i_trouble;
19 }
20 trouble operator = (const trouble &i_trouble) {
21 std::cout << "= operator called\n";
22 data = i_trouble.data;
23 return (*this);
24 }
25 public:
26 // Put an item in the class
27 void put(const int value) {
28 data = value;
29 }
30 // Get an item from the class
31 int get(void) {
32 return (data);
33 }
34 };
35
36 int main() {
37 trouble first; // A place to put an item
38 first.put(99);
39
40 trouble second(first); // A copy of this space
41
42 std::cout << "Second.get " << second.get() << '\n';
43
44 return (0);
45 }
(下一提示 291。答案 109。)
![]() |
|---|
真正的程序员不会注释他们的代码。如果代码难以编写,那么它也应该难以理解。
真正的程序员不会绘制流程图。毕竟,流程图是文盲的文档形式。穴居人绘制流程图;看看这对他们有多大的好处。
真正的程序员不会打网球,或者任何需要你换衣服的其他运动。登山是可以的,而且真正的程序员会穿着他们的登山靴去工作,以防机器房中间突然冒出一座山。
真正的程序员不会用 BASIC 编写。实际上,任何程序员在青春期之后都不会用 BASIC 编写。
真正的程序员不会编写规格说明——用户应该觉得自己很幸运能获得任何程序,并且接受他们所得到的。
真正的程序员不会注释他们的代码。如果代码难以编写,那么它也应该难以理解。
真正的程序员不会编写应用程序;他们直接在裸机上编程。应用程序编程是那些无法进行系统编程的菜鸟做的事情。
真正的程序员不会吃千层面。实际上,真正的程序员甚至不知道如何拼写千层面。他们吃 twinkies 和四川菜。
![]() |
|---|
程序 85:队列过长
这个程序创建了一个非常简单、格式良好的队列类。然而,当我们使用它时,内存会损坏。为什么?
1 /************************************************
2 * test the variable length queue class. *
3 ************************************************/
4 #include <iostream>
5
6 /************************************************
7 * queue -- Variable length queue class. *
8 * *
9 * Member functions: *
10 * queue(size) -- Create a queue that can *
11 * hold up to size elements. *
12 * *
13 * get -- Return an item from the queue. *
14 * (Elements are gotten in First *
15 * In First Out (FIFO) order.) *
16 * put -- Add an item to the queue. *
17 * *
18 * WARNING: No safety check is made to make *
19 * sure something is in the queue before *
20 * it is removed. *
21 ************************************************/
22 class queue
23 {
24 private:
25 int *data; // The data
26 int in_index; // Input index
27 int out_index; // Output index
28 int size; // # items in the queue
29
30 // Copy data from another queue to me.
31 void copy_me(
32 // Stack to copy from
33 const queue &other
34 )
35 {
36 int i; // Current element
37
38 for (i = 0; i < size; ++i) {
39 data[i] = other.data[i];
40 }
41 }
42
43 // Inc_index -- Increment an
44 // index with wrapping
45 void inc_index(int &index)
46 {
47 ++index;
48 if (index == size)
49 {
50 // Wrap
51 index = 0;
52 }
53 }
54
55 public:
56 // Create a queue of the given size
57 queue(const int _size):
58 in_index(o), out_index(o), size(_size)
59 {
60 data = new int[size];
61 }
62
63 // Destructor
64 ~queue(void) {}
65
66 // Copy constructor
67 queue(const queue &other):
68 in_index(other.in_index),
69 out_index(other.out_index),
70 size(other.size)
71 {
72 data = new int[size];
73 copy_me(other);
74 }
75 // Assignment operator
76 queue & operator = (const queue &other)
77 {
78 copy_me(other);
79 return (*this);
80 };
81 public:
82 // Put an item on the queue
83 void put(
84 // Value to Put on the queue
85 const int value
86 )
87 {
88 data[in_index] = value;
89 inc_index(in_index);
90 }
91 // Return first element from the queue
92 int get(void)
93 {
94 // Value to return
95 int value = data[out_index];
96
97 inc_index(out_index);
98 return (value);
99 }
100 };
101
102 int main()
103 {
104 // Queue to play around with
105 queue a_queue(30);
106
107 // Loop counter for playing with the queue
108 int i;
109
110 for (i = 0; i < 30; ++i)
111 a_queue.put(i);
112
113 // Create a new queue, same as the current one
114 queue save_queue(20);
115 save_queue = a_queue;
116
117 std::cout << "Value is " <<
118 a_queue.get() << std::endl;
119
120 std::cout << "Value is " <<
121 a_queue.get() << std::endl;
122
123 std::cout << "Value is " <<
124 a_queue.get() << std::endl;
125
126 std::cout << "Value is " <<
127 a_queue.get() << std::endl;
128
129 return (0);
130 }
(下一提示 334。答案 14。)
程序 86:缺乏自我意识
以下程序旨在测试我们的简单数组。然而,有一个问题导致程序以意想不到的方式失败。
1 /************************************************
2 * array_test -- Test the use of the array class*
3 ************************************************/
4 #include <iostream>
5
6 /************************************************
7 * array -- Classic variable length array class.*
8 * *
9 * Member functions: *
10 * operator [] -- Return an item *
11 * in the array. *
12 ************************************************/
13 class array {
14 protected:
15 // Size of the array
16 int size;
17
18 // The array data itself
19 int *data;
20 public:
21 // Constructor.
22 // Set the size of the array
23 // and create data
24 array(const int i_size):
25 size(i_size),
26 data(new int[size])
27 {
28 // Clear the data
29 memset(data, '\0',
30 size * sizeof(data[0]));
31 }
32 // Destructor -- Return data to the heap
33 virtual ~array(void)
34 {
35 delete []data;
36 data = NULL;
37 }
38 // Copy constructor.
39 // Delete the old data and copy
40 array(const array &old_array)
41 {
42 delete []data;
43 data = new int[old_array.size];
44
45 memcpy(data, old_array.data,
46 size * sizeof(data[o]));
47 }
48 // operator =.
49 // Delete the old data and copy
50 array & operator = (
51 const array &old_array)
52 {
53 delete []data;
54 data = new int[old_array.size];
55
56 memcpy(data, old_array.data,
57 size * sizeof(data[0]));
58 return (*this);
59 }
60 public:
61 // Get a reference to an item in the array
62 int &operator [](const unsigned int item)
63 {
64 return data[item];
65 }
66 };
67
68 /**********************************************
69 * three_more_elements -- *
70 * Copy from_array to to_array and *
71 * put on three more elements. *
72 **********************************************/
73 void three_more_elements(
74 // Original array
75 array to_array,
76
77 // New array with modifications
78 const array &from_array
79 )
80 {
81 to_array = from_array;
82 to_array[10] = 1;
83 to_array[11] = 3;
84 to_array[11] = 5;
85 }
86 int main()
87 {
88 array an_array(30); // Simple test array
89
90 an_array[2] = 2; // Put in an element
91 // Put on a few more
92 three_more_elements(an_array, an_array);
93 return(0);
94 }
(下一个提示 8。答案 75。)
![]() |
|---|
国际商业机器公司(IBM)的 Yorktown Heights 研究中心的一位程序员遇到了一个问题。当他坐着时,一切正常。当他站起来时,电脑就失败了。这个问题很有趣,因为它完全可以重复。当他站起来时,机器总是失败,当他坐下时,它总是工作。这个问题没有丝毫的不稳定。
计算机办公室的人感到困惑。毕竟,电脑怎么能知道这个人是在站着还是坐着呢?提出了各种各样的理论,比如静电、磁场,甚至是顽皮上帝的行为。
最有可能的理论是地毯下有东西松了。这是一个不错的理论,但不幸的是,它不符合事实。松散的电线往往会引起间歇性问题,但这个问题是 100%可重复的。
最后,一位细心的工程师注意到了一些情况。当程序员坐下时,他会进行触摸打字。当他站起来时,他使用的是点字法。仔细检查键盘后发现,有两个键被反转了。当这个人坐下并触摸打字时,这并不重要。但是当他站起来并使用点字法时,他被反转的键误导,输入了错误的数据。
当按键帽被更换后,问题消失了。
![]() |
|---|
程序 87:异常的异常
这个栈类被设计得更加健壮,如果栈出现任何问题都会抛出异常。然而,测试程序仍然会终止并转储核心。为什么?
1 /************************************************
2 * stack_test -- Yet another testing of a *
3 * stack class. *
4 ************************************************/
5 #include <iostream>
6
7 /************************************************
8 * problem -- Class to hold a "problem". Used *
9 * for exception throwing and catching. *
10 * *
11 * Holds a single string which describes the *
12 * error. *
13 ************************************************/
14 class problem
15 {
16 public:
17 // The reason for the exception
18 char *what;
19
20 // Constructor.
21 // Create stack with messages.
22 problem(char *_what):what(_what){}
23 };
24
25 // Max data we put in a stack
26 // (private to the stack class)
27 const int MAX_DATA = 100;
28 /************************************************
29 * stack -- Classic stack. *
30 * *
31 * Member functions: *
32 * push -- Push an item on the stack. *
33 * pop -- Remove an item from the stack. *
34 * *
35 * Exceptions: *
36 * Pushing too much data on a stack or *
37 * removing data from an empty stack *
38 * causes an exception of the "problem" *
39 * class to be thrown. *
40 * *
41 * Also if you don't empty a stack *
42 * before you're finished, an exception *
43 * is thrown. *
44 *************************************************/
45 class stack {
46 private:
47 // The stack's data
48 int data[MAX_DATA];
49
50 // Number of elements
51 // currently in the stack
52 int count;
53
54 public:
55 // Constructor
56 stack(void) : count(0) {};
57
58 // Destructor -- Check for non
59 ~stack(void)
60 {
61 if (count != 0)
62 {
63 throw(
64 problem("Stack not empty"));
65 }
66 }
67
68 // Push an item on the stack
69 void push(
70 const int what // Item to store
71 )
72 {
73 data[count] = what;
74 ++count;
75 }
76 // Remove an item from the stack
77 int pop(void)
78 {
79 if (count == 0)
80 throw(
81 problem("Stack underflow"));
82 --count;
83 return (data[count]);
84 }
85 };
86
87 /************************************************
88 * push_three -- Push three items onto a stack *
89 * *
90 * Exceptions: *
91 * If i3 is less than zero, a "problem" *
92 * class exception is thrown. *
93 ************************************************/
94 static void push_three(
95 const int i1, // First value to push
96 const int i2, // Second value to push
97 const int i3 // Third value to push
98 )
99 {
100 // Stack on which to push things
101 stack a_stack;
102
103 a_stack.push(i1);
104 a_stack.push(i2);
105 a_stack.push(i3);
106 if (i3 < 0)
107 throw (problem("Bad data"));
108 }
109
110 int main(void)
111 {
112 try {
113 push_three(1, 3, -5);
114 }
115 catch (problem &info) {
116
117 std::cout << "Exception caught: " <<
118 info.what << std::endl;
119
120 exit (8);
121 }
122 catch (...) {
123 std::cout <<
124 "Caught strange exception " <<
125 std::endl;
126
127 exit (9);
128 }
129 std::cout << "Normal exit" << std::endl;
130 return (0);
131 }
(提示 110。答案 55。)
程序 88:文件此!
由于某些程序要求受损,以下函数必须从 FILE 复制到 ostream。为什么它无法工作?
1 /************************************************
2 * copy -- Copy the input file to the output *
3 * file. *
4 ************************************************/
5 #include <cstdio>
6 #include <iostream>
7 #include <fstream>
8
9 /************************************************
10 * copy_it -- Copy the data *
12 void copy_it(
13 FILE *in_file, // Input file
14 std::ostream &out_file // Output file
15 )
16 {
17 int ch; // Current char
18
19 while (1) {
20 ch = std::fgetc(in_file);
21 if (ch == EOF)
22 break;
23 out_file << ch;
24 }
25 }
26
27 int main()
28 {
29 // The input file
30 FILE *in_file = std::fopen("in.txt", "r");
31 // The output file
32 std::ofstream out_file("out.txt");
33
34 // Check for errors
35 if (in_file == NULL) {
36 std::cerr <<
37 "Error: Could not open input\n";
38 exit (8);
39 }
40 if (out_file.bad()) {
41 std::cerr <<
42 "Error: Could not open output\n";
43 exit (8);
44 }
45 // Copy data
46 copy_it(in_file, out_file);
47
48 // Finish output file
49 std::fclose(in_file);
50 return (o);
51 }
(下一个提示 10。答案 99。)
![]() |
|---|
来自旧版 Apple C 编译器的错误信息:
"符号表已满 - 致命堆错误;请从您当地的 Apple 经销商购买 RAM 升级"
"字符串字面量太长(我允许你有 512 个字符,这比 ANSI 说的多 3 个)"
"类型(强制类型转换)必须是标量;ANSI 3.3.4;第 39 页,第 10-11 行(我知道你不在乎,我只是想烦你)"
"一行中错误太多(少做一点)"
"无法将 void 类型强制转换为 void 类型(因为 ANSI 规范如此规定,这就是原因)"
"... 然后主说,'看哪,switch 语句中只能有 case 或 default 标签'"
"在这个程序的这个点上,typedef 名称让我感到惊讶"
"'Volatile'和'Register'不可混合"
"你不能修改一个常量,向上游的浮点数,与国税局争论,或者满足这个编译器"
"这个联合体已经有了完美的定义"
"嗯?"
"不能乱搞'void *'"
"这个结构体已经有了完美的定义"
"我们已经处理过这个函数"
"此标签是来自此标签所在块外部的 goto 的目标,并且此块有一个具有初始化器的自动变量,而且你的窗口不够宽,无法阅读整个错误信息"
"叫我偏执狂吧,但在这个注释中找到'/*'让我感到怀疑"
![]() |
|---|
程序 89:只是因为我有偏执狂,并不意味着程序要来对付我
为了说明 setjmp 库函数的问题,我创建了一个 v_string 类。这个函数的测试代码(除去 setjmp 问题)如下所示。
现在我总是小心翼翼地编写代码以避免错误和内存泄漏。然而,这个程序失败了,因为我过于小心。这是怎么回事?
1 /************************************************
2 * Combine strings with a variable length *
3 * string class. *
4 *************************************************/
5 #include <iostream>
6 #include <cstring>
7
8 /************************************************
9 * v_string -- variable length C style string *
10 * *
11 * Member functions: *
12 * set -- set the value of the string. *
13 * get -- get the data from the string. *
14 ************************************************/
15 class v_string
16 {
17 public:
18 const char *data; // The data
19 // Default constructor
20 v_string(): data(NULL)
21 {}
22 v_string(const char *const i_data):
23 data(strdup(i_data))
24 {}
25 // Destructor
26 ~v_string(void)
27 {
28 // Note: delete works
29 // even if data is NULL
30 delete [] data;
31 data = NULL;
32 }
33 // Copy constructor
34 v_string(const v_string &old)
35 {
36 if (data != NULL)
37 {
38 delete[] data;
39 data = NULL;
40 }
41 data = strdup(old.data);
42 }
43 // operator =
44 v_string & operator = (
45 const v_string &old)
46 {
47 if (this == &old)
48 return (*this);
49
50 if (data != NULL)
51 {
52 delete[] data;
53 data = NULL;
54 }
55 if (old.data == NULL)
56 {
57 data = NULL;
58 return (*this);
59 }
60
61 data = strdup(old.data);
62 return (*this);
63 }
64 public:
65 // Set a value
66 void set(
67 // New string value
68 const char *const new_data
69 )
70 {
71 if (data != NULL)
72 {
73 delete [] data;
74 data = NULL;
75 }
76 data = strdup(new_data);
77
78 }
79 // Returns the value of the string
80 const char * const get(void) const
81 {
82 return (data);
83 }
84 };
85 /************************************************
86 * operator + -- Combine two v_strings *
87 ************************************************/
88 v_string operator + (
89 const v_string &first, // First string
90 const v_string &second // Second string
91 )
92 {
93 char tmp[100]; // Combined string
94
95 strcpy(tmp, first.get());
96 strcat(tmp, second.get());
97
98 // Strings put together
99 v_string together(tmp);
100 return (together);
101 }
102
103 /************************************************
104 * combine -- Combine two strings and *
105 * print the result. *
106 ************************************************/
107 static void combine(
108 const v_string &first, // First string
109 const v_string &second // Second string
110 )
111 {
112 v_string together; // Strings put together
113 together = first + second;
114
115 std::cout << "Combination " <<
116 together.get() << '\n';
117 }
118
119 int main()
120 {
121 // Strings to combine
122 v_string first("First:");
123 v_string second("Second");
124 combine(first, second);
125 return (0);
126 }
(下一 提示 65. 答案 115.)
程序 90:就像滚下树干一样简单
为了追踪内存泄漏,我们这位聪明的程序员决定通过重新定义全局函数,在新和删除操作中添加日志信息。尽管 C++允许这样做,但他的程序仍然崩溃。为什么?
1 /************************************************
2 * simple debugging library that overrides the *
3 * standard new and delete operators so that we *
4 * log all results. *
5 ************************************************/
6 #include <iostream>
7 #include <fstream>
8 #include <cstdlib>
9
10 // Define the file to write the log data to
11 std::ofstream log_file("mem.log");
12
13 /************************************************
14 * operator new -- Override the system new so *
15 * that it logs the operation. This is *
16 * useful for debugging. *
17 * *
18 * Note: We have verified that the real new *
19 * calls malloc on this system. *
20 * *
21 * Returns a pointer to the newly created area. *
22 ************************************************/
23 void *operator new(
24 // Size of the memory to allocate
25 const size_t size
26 )
27 {
28 // Result of the malloc
29 void *result = (void *)malloc(size);
30
31 log_file <<
32 result << " =new(" <<
33 size << ")" << std::endl;
34
35 return (result);
36 }
37
38 /************************************************
39 * operator delete -- Override the system *
40 * delete to log the operation. This is *
41 * useful for debugging. *
42 * *
43 * Note: We have verified that the real delete *
44 * calls free on this system. *
45 ************************************************/
46 void operator delete(
47 void *data // Data to delete
48 )
49 {
50 log_file << data << " Delete" << std::endl;
51 free (data);
52 }
53
54 // Dummy main
55 int main()
56 {
57 return (0);
58 }
(下一个提示 212. 答案 110.)
![]() |
|---|
高级编程语言定律:让程序员用英语编写代码,你会发现程序员无法用英语编写代码。
![]() |
|---|
程序 91:栈错误
在下面的程序中,我们定义了一个不安全的类,栈,以及它的一个更安全的版本,safe_stack。我们的测试程序创建了一个包含五个栈的数组,并对其推送了一些测试数据。它打印出栈的大小。但结果并不是我们预期的。
1 /************************************************
2 * stack_test -- Test the use of the classes *
3 * stack and safe_stack. *
4 ************************************************/
5 #include <iostream>
6
7 // The largest stack we can use
8 // (private to class stack and safe_stack)
9 const int STACK_MAX = 100;
10 /************************************************
11 * stack -- Class to provide a classic stack. *
12 * *
13 * Member functions: *
14 * push -- Push data on to the stack. *
15 * pop -- Return the top item from the *
16 * stack. *
17 * *
18 * Warning: There are no checks to make sure *
19 * that stack limits are not exceeded. *
20 ************************************************/
21 class stack {
22 protected:
23 int count; // Number of items in the stack
24 int *data; // The stack data
25 public:
26 // Initialize the stack
27 stack(void): count(0)
28 {
29 data = new int[STACK_MAX];
30 }
31 // Destructor
32 virtual ~stack(void) {
33 delete data;
34 data = NULL;
35 }
36 private:
37 // No copy constructor
38 stack(const stack &);
39
40 // No assignment operator
41 stack & operator = (const stack &);
42 public:
43 // Push an item on the stack
44 void push(
45 const int item // Item to push
46 ) {
47 data[count] = item;
48 ++count;
49 }
50 // Remove the an item from the stack
51 int pop(void) {
52 --count;
53 return (data[count]);
54 }
55
56 // Function to count things in
57 // an array of stacks
58 friend void stack_counter(
59 stack stack_array[],
60 const int n_stacks
61 );
62 };
63
64 /***********************************************
65 * safe_stack -- Like stack, but checks for *
66 * errors. *
67 * *
68 * Member functions: push and pop *
69 * (just like stack) *
70 ***********************************************/
71 class safe_stack : public stack {
72 public:
73 const int max; // Limit of the stack
74 public:
75 safe_stack(void): max(STACK_MAX) {};
76 // Destructor defaults
77 private:
78 // No copy constructor
79 safe_stack(const safe_stack &);
80
81 // No assignment operator
82 safe_stack & operator =
83 (const safe_stack &);
84 public:
85 // Push an item on the stack
86 void push(
87 // Data to push on the stack
88 const int data
89 ) {
90 if (count >= (STACK_MAX-1)) {
91 std::cout << "Stack push error\n";
92 exit (8);
93 }
94 stack::push(data);
95 }
96 // Pop an item off the stack
97 int pop(void) {
98 if (count <= o) {
99 std::cout << "Stack pop error\n";
100 exit (8);
101 }
102 return (stack::pop());
103 }
104 };
105
106
107 /************************************************
108 * stack_counter -- Display the count of the *
109 * number of items in an array of stacks. *
110 ************************************************/
111 void stack_counter(
112 // Array of stacks to check
113 stack *stack_array,
114
115 // Number of stacks to check
116 const int n_stacks
117 )
118 {
119 int i;
120
121 for (i = 0; i < n_stacks; ++i)
122 {
123 std::cout << "Stack " << i << " has " <<
124 stack_array[i].count << " elements\n";
125 }
126 }
127
128 // A set of very safe stacks for testing
129 static safe_stack stack_array[5];
130
131 int main()
132 {
133
134 stack_array[0].push(0);
135
136 stack_array[1].push(0);
137 stack_array[1].push(1);
138
139 stack_array[2].push(0);
140 stack_array[2].push(1);
141 stack_array[2].push(2);
142
143 stack_array[3].push(0);
144 stack_array[3].push(1);
145 stack_array[3].push(2);
146 stack_array[3].push(3);
147
148 stack_array[4].push(0);
149 stack_array[4].push(1);
150 stack_array[4].push(2);
151 stack_array[4].push(3);
152 stack_array[4].push(4);
153
154 stack_counter(stack_array, 5);
155 return (0);
156 }
(下一提示 296。答案 72。)
![]() |
|---|
没有什么是不能通过足够的应用蛮力和无知来解决的。
![]() |
|---|
程序 92:命名游戏
以下程序会打印什么?
文件:first.cpp
1 #include <string>
2
3 // The first name of the key person
4 std::string first_name = "Bill";
文件:last.cpp
1 /***********************************************
2 * print_name -- Print the name of a person. *
3 ***********************************************/
4 #include <iostream>
5 #include <string>
6
7 // The first name
8 extern std::string first_name;
9
10 // The last name
11 std::string last_name = "Jones";
12
13 // The full name
14 std::string full_name =
15 first_name + " " + last_name;
16
17 int main()
18 {
19 // Print the name
20 std::cout << full_name << std::endl;
21 return (0);
22 }
(下一个提示 244。答案 3。)
![]() |
|---|
在许多小数位数之后,没有人会在乎。
![]() |
|---|
程序 93:无魔法
类信息发生了奇怪的事情。你英勇的作者被分配了找出发生了什么任务。经过一番尝试,我决定可能发生的情况是有人拿到了一个坏指针,并在类上随意操作。
为了尝试找出类被覆盖的地方,我在类的数据开始和结束部分放置了一些魔法数字。我预期这些魔法数字会在出错时被改变。但令我惊讶的是,事情出错的时间比预期的要早得多。
那么为什么魔法会从类中消失?
1 #include <stdlib.h>
2 #include <iostream>
3 #include <cstring>
4
5 /************************************************
6 * info -- A class to hold information. *
7 * *
8 * Note: *
9 * Because someone is walking all over our *
10 * memory and destroying our data, we *
11 * have put two guards at the beginning *
12 * and end of our class. If someone *
13 * messes with us these numbers will *
14 * be destroyed. *
15 * *
16 * Member functions: *
17 * set_data -- Store a string in our data. *
18 * get_data -- Get the data string. *
19 * check_magic -- Check the magic numbers. *
20 ************************************************/
21 // Magic numbers for the start and end of the
22 // data in the class info
23 const int START_MAGIC = 0x11223344;
24 const int END_MAGIC = 0x5567788;
25 class info
26 {
27 private:
28 // Magic protection constant
29 const int start_magic;
30
31 // String to be stored
32 char data[30];
33
34 // Magic protection constant
35 const int end_magic;
36 public:
37 info(void):
38 start_magic(START_MAGIC),
39 end_magic(END_MAGIC)
40 {}
41
42 // Copy constructor defaults
43 // Assignment operator defaults
44 // Destructor defaults
45
46 // Store some data in the class
47 void set_data(
48 // Data to be stored
49 const char what[]
50 )
51 {
52 strcpy(data, what);
53 }
54
55 // Get the data from the class
56 char *get_data(void)
57 {
58 return (data);
59 }
60
61 // Verify that the magic
62 // numbers are correct
63 void check_magic(void)
64 {
65 if ((start_magic != START_MAGIC) ||
66 (end_magic != END_MAGIC))
67 {
68 std::cout <<
69 "Info has lost its magic\n";
70 }
71 }
72 };
73
74 /************************************************
75 * new_info -- Create a new version of the *
76 * info class. *
77 ************************************************/
78 struct info *new_info(void)
79 {
80 struct info *result; // Newly created result.
81
82 result = (struct info *)
83 malloc(sizeof(struct info));
84
85 // Make sure the structure is clear
86 memset(result, '\0', sizeof(result));
87
88 return (result);
89 }
90 int main()
91 {
92 // An info class to play with
93 class info *a_info = new_info();
94
95 a_info->set_data("Data");
96 a_info->check_magic();
97 return (0);
98 }
(下一提示 153。答案 98。)
![]() |
|---|
诅咒是所有程序员都理解的一种语言。
![]() |
|---|
程序 94:速度致命
new 和 delete 函数调用代价高昂。如果你想加快你的程序速度,并且你知道自己在做什么,你可以覆盖它们并创建你自己的类特定的 new 和 delete。这正是这个程序员所做的事情。分配算法非常简单,但不知何故内存被破坏了。为什么?
1 /***********************************************
2 * bit_test -- Test out our new high speed *
3 * bit_array. *
4 ***********************************************/
5 #include <iostream>
6 #include <memory.h>
7
8 // The size of a fast bit_array.
9 // (Private to fast bit array)
10 const int BIT_ARRAY_MAX = 64; // Size in bits
11
12 // Number of bits in a byte
13 const int BITS_PER_BYTE = 8;
14 /************************************************
15 * fast_bit_array -- A bit array using fast *
16 * allocate technology. *
17 * *
18 * Member functions: *
19 * get -- Get an element from the *
20 * array. *
21 * set -- Set the value of an element *
22 * in the array. *
23 * *
24 * new -- used to quickly allocate a bit *
25 * array. *
26 * delete -- used to quickly deallocate *
27 * a bit array. *
28 ************************************************/
29 class fast_bit_array
30 {
31 protected:
32 // Array data
33 unsigned char
34 data[BIT_ARRAY_MAX/BITS_PER_BYTE];
35
36 public:
37 fast_bit_array(void)
38 {
39 memset(data, '\0', sizeof(data));
40 }
41 // Destructor defaults
42 private:
43 // No copy constructor
44 fast_bit_array(const fast_bit_array &);
45
46 // No assignment operator
47 fast_bit_array & operator =
48 (const fast_bit_array &);
49 public:
50 // Set the value on an item
51 void set(
52 // Index into the array
53 const unsigned int index,
54
55 // Value to put in the array
56 const unsigned int value
57 )
58 {
59 // Index into the bit in the byte
60 unsigned int bit_index = index % 8;
61
62 // Byte in the array to use
63 unsigned int byte_index = index / 8;
64
65 if (value)
66 {
67 data[byte_index] |=
68 (1 << bit_index);
69 }
70 else
71 {
72 data[byte_index] &=
73 ~(1 << bit_index);
74 }
75 }
76 // Return the value of an element
77 int get(unsigned int index)
78 {
79 // Index into the bit in the byte
80 unsigned int bit_index = index % 8;
81 // Byte in the array to use
82 unsigned int byte_index = index / 8;
83
84 return (
85 (data[byte_index] &
86 (1 << bit_index)) != o);
87 }
88 // Allocate a new fast_bit_array
89 void *operator new(const size_t);
90
91 // Delete a fast bit array.
92 void operator delete(void *ptr);
93 };
94
95 /************************************************
96 * The following routines handle the local *
97 * new/delete for the fast_bit_array. *
98 ************************************************/
99 // Max number of fast_bit_arrays we can use at once
100 const int N_FAST_BIT_ARRAYS = 30;
101
102 // If true, the bit array slot is allocated
103 // false indicates a free slot
104 static bool
105 bit_array_used[N_FAST_BIT_ARRAYS] = {false};
106
107 // Space for our fast bit arrays.
108 static char
109 bit_array_mem[N_FAST_BIT_ARRAYS]
110 [sizeof(fast_bit_array)];
111
112 // Handle new for "fast_bit_array".
113 // (This is much quicker than the
114 // system version of new)
115 /************************************************
116 * fast_bit_array -- new *
117 * *
118 * This is a high speed allocation routine for *
119 * the fast_bit_array class. The method used *
120 * for this is simple, but we know that only *
121 * a few bit_arrays will be allocated. *
122 * *
123 * Returns a pointer to the new memory. *
124 ************************************************/
125 void *fast_bit_array::operator new(const size_t)
126 {
127 int i; // Index into the bit array slots
128
129 // Look for a free slot
130 for (i = 0; i < N_FAST_BIT_ARRAYS; ++i)
131 {
132 if (!bit_array_used[i])
133 {
134 // Free slot found, allocate the space
135 bit_array_used[i] = true;
136 return(bit_array_mem[i]);
137 }
138 }
139 std::cout << "Error: Out of local memory\n";
140 exit (8);
141 }
142
143 /************************************************
144 * fast_bit_array -- delete *
145 * *
146 * Quickly free the space used by a *
147 * fast bit array. *
148 ************************************************/
149 void fast_bit_array::operator delete(
150 void *ptr // Pointer to the space to return
151 )
152 {
153 int i; // Slot index
154
155 for (i = 0; i < N_FAST_BIT_ARRAYS; ++i)
156 {
157 // Is this the right slot
158 if (ptr == bit_array_mem[i])
159 {
160 // Right slot, free it
161 bit_array_used[i] = false;
162 return;
163 }
164 }
165 std::cout <<
166 "Error: Freed memory we didn't have\n";
167 exit (8);
168 }
169
170
171 /************************************************
172 * safe_bit_array -- A safer bit array. *
173 * *
174 * Like bit array, but with error checking. *
175 ************************************************/
176 class safe_bit_array : public fast_bit_array
177 {
178 public:
179 // Sequence number generator
180 static int bit_array_counter;
181
182 // Our bit array number
183 int sequence;
184
185 safe_bit_array(void)
186 {
187 sequence = bit_array_counter;
188 ++bit_array_counter;
189 };
190 // Destructor defaults
191 private:
192 // No copy constructor
193 safe_bit_array(const safe_bit_array &);
194
195 // No assignment operator
196 safe_bit_array & operator = (
197 const safe_bit_array &);
198 public:
199 // Set the value on an item
200 void set(
201 // Where to put the item
202 const unsigned int index,
203 // Item to put
204 const unsigned int value
205 )
206 {
207 if (index >= (BIT_ARRAY_MAX-1))
208 {
209 std::cout <<
210 "Bit array set error "
211 "for bit array #"
212 << sequence << "\n";
213 exit (8);
214 }
215 fast_bit_array::set(index, value);
216 }
217 // Return the value of an element
218 int get(unsigned int index)
219 {
220 if (index >= (BIT_ARRAY_MAX-1))
221 {
222 std::cout <<
223 "Bit array get error "
224 "for bit array #"
225 << sequence << "\n";
226 exit (8);
227 }
228 return (fast_bit_array::get(index));
229 }
230 };
231
232 // Sequence information
233 int safe_bit_array::bit_array_counter = 0;
234
235 int main()
236 {
237 // Create a nice new safe bit array
238 safe_bit_array *a_bit_array =
239 new safe_bit_array;
240
241 a_bit_array->set(5, 1);
242 // Return the bit_array to the system
243 delete a_bit_array;
244 return (0); 245 }
(下一提示 305。答案 56。)
![]() |
|---|
一个足够高的技术水平与魔法无法区分。
— 亚瑟·C·克拉克
![]() |
|---|
程序 95:发送错误信息
为什么这个程序会产生奇怪的结果?
1 /************************************************
2 * hello -- write hello using our message system*
3 * to the log file and the screen. *
4 ************************************************/
5 #include <iostream>
6 #include <fstream>
7
8 // The log file
9 std::ofstream log_file("prog.log");
10
11 /************************************************
12 * print_msg_one -- Write a message to the *
13 * given file. *
14 ************************************************/
15 void print_msg_one(
16 // File to write the message to
17 std::ostream out_file,
18
19 // Where to send it
20 const char msg[]
21 ) {
22 out_file << msg << std::endl;
23 }
24 /************************************************
25 * print_msg -- send a message to the console *
26 * and to the log file. *
27 ************************************************/
28 void print_msg(
29 const char msg[] // Message to log
30 ) {
31 print_msg_one(std::cout, msg);
32 print_msg_one(log_file, msg);
33 }
34 int main()
35 {
36 print_msg("Hello World!");
37 return (0);
38 }
(下一个提示 328. 答案 40.)
程序 96:纯粹的乐趣
这个程序基于一个简单的想法。让列表类处理链表,而派生类处理数据。
但当运行时,它崩溃了。为什么?
1 /************************************************
2 * simple linked list test. *
3 ************************************************/
4 #include <iostream>
5 #include <malloc.h>
6 #include <string>
7 /************************************************
8 * list -- Linked list class. *
9 * Stores a pointer to void so you can *
10 * stick any data you want to in it. *
11 * *
12 * Member functions: *
13 * clear -- clear the list *
14 * add_node -- Add an item to the list *
15 ************************************************/
16 class list {
17 private:
18 /*
19 * Node -- A node in the linked list
20 */
21 class node {
22 private:
23 // Data for this node
24 void *data;
25
26 // Pointer to next node
27 class node *next;
28
29 // List class does the work
30 friend class list;
31 // Constructor defaults
32 // Destructor defaults
33
34 // No copy constructor
35 node(const node &);
36
37 // No assignment operator
38 node &operator = (const node &);
39 public:
40 node(void) :
41 data(NULL), next(NULL) {}
42 };
43 //--------------------------------------
44
45 node *first; // First node in the list
46
47 /*
48 * Delete the data for the node.
49 * Because we don't know what type of
50 * data we have, the derived class does
51 * the work of deleting the data
52 * through the delete_data function.
53 */
54 virtual void delete_data(void *data) = 0;
55 public:
56 // Delete all the data in the list
57 void clear(void) {
58 while (first != NULL)
59 {
60 // Pointer to the next node
61 class node *next;
62
63 next = first->next;
64 delete_data(first->data);
65 delete first;
66 first = next;
67 }
68 }
69
70 // Constructor
71 list(void): first(NULL) {};
72
73 // Destructor. Delete all data
74 virtual ~list(void) {
75 clear();
76 }
77
78 // Add a node to the list
79 void add_node(
80 void *data // Data to be added
81 ) {
82 class node *new_node;
83
84 new_node = new node;
85 new_node->data = data;
86 new_node->next = first;
87 first = new_node;
88 }
89 };
90 /************************************************
91 * string_list -- A linked list containing *
92 * strings. *
93 * *
94 * Uses the list class to provide a linked list *
95 * of strings. *
96 * *
97 * Member functions: *
98 * add_node -- Adds a node to the list. *
99 ************************************************/
100 class string_list : private list
101 {
102 private:
103 // Delete a node
104 void delete_data(
105 void *data // Data to delete
106 ) {
107 free(data);
108 data = NULL;
109 }
110 public:
111 // Add a new node to the list
112 void add_node(
113 // String to add
114 const char *const data
115 ) {
116 list::add_node((void *)strdup(data));
117 }
118 };
119
120 int main()
121 {
122 // List to test things with
123 string_list *the_list = new string_list;
124
125 the_list->add_node("Hello");
126 the_list->add_node("World");
127
128 delete the_list;
129 the_list = NULL;
130 return (0);
131 }
(下一个提示 119。答案 101。)
第八章:专家困惑
欢迎来到本书最艰难的部分之一。本节中的少数几个程序甚至能难倒最资深的 C 或 C++程序员。你可能认为你了解所有关于编程的知识,但接下来提出的问题是最困难、最复杂的问题。
本章只有三个问题。如果你能解决一个,你可以认为自己是个专家。解决两个,我会感到惊讶。解决所有三个,你可以认为自己是个冠军。
程序 97:再次你好
以下程序会打印什么?
1 /************************************************
2 * Normally I would put in a comment explaining *
3 * what this program is nominally used for. *
4 * But in this case I can figure out no *
5 * practical use for this program. *
6 ************************************************/
7 #include <stdio.h>
8 #include <unistd.h>
9 #include <stdlib.h>
10
11 int main()
12 {
13 printf("Hello ");
14 fork();
15 printf("\n");
16 exit(0);
17 }
(下一个提示 214。答案 50。)
![]() |
|---|
莎士比亚给了我们一个古老的问题,“生存还是毁灭?”计算机科学给了我们答案:“FF”。
0x2B | ~0x2B == 0xFF
![]() |
|---|
程序 98:调试抵抗
程序员有一个巧妙的想法。他会把一大堆代码放在一个:
if (debugging)
语句中。然后他会运行程序,当他需要调试输出时,他会使用交互式调试器将调试从 0 改为 1。但他的代码即将给他一个惊喜。
1 /***********************************************
2 * Code fragment to demonstrate how to use the *
3 * debugger to turn on debugging. All you *
4 * have to do is put a breakpoint on the "if" *
5 * line and change the debugging variable. *
6 ***********************************************/
7 extern void dump_variables(void);
8
9 void do_work()
10 {
11 static int debugging = 0;
12
13 if (debugging)
14 {
15 dump_variables();
16 }
17 // Do real work
18 }
(下一 提示 147。 答案 84。)
![]() |
|---|
在 UNIX 操作系统下创建文件非常容易。因此,用户倾向于创建大量文件,占用大量文件空间。据说,所有 UNIX 系统的唯一标准就是每天的消息提醒用户清理他们的文件。
— 早期 UNIX 管理员指南
![]() |
|---|
程序 99:幽灵文件
在我们的目录中没有名为 delete.me 的文件。那么为什么这个程序一直告诉我们要删除它?
1 /*************************************************
2 * delete_check -- Check to see if the file *
3 * delete.me exists and tell the user *
4 * to delete it if it does. *
5 *************************************************/
6 #include <iostream>
7 #include <unistd.h>
8 #include <cstdio>
9
10 int main()
11 {
12 // Test for the existence of the file
13 if (access("delete.me", F_OK)) {
14 bool remove = true;
15 }
16 if (remove) {
17 std::cout <<
18 "Please remove 'delete.me'\n";
19 }
20 return (0);
21 }
(下一个提示 98。答案 35。)
![]() |
|---|
其中我谈到了最灾难性的变化,
关于洪水和田野中的移动事故,
关于微乎其微的“逃生”在即将到来的致命呼吸中。
- 莎士比亚,关于编程移植
血腥的教训,一旦学会,就会回来困扰发明者。
- 莎士比亚,关于维护编程
![]() |
|---|
第九章:通往地狱的通道
C++ 应该是一种可移植的语言。这是一个可爱的短语,“应该”,它解释了我们如何找到这一章的所有程序。
程序 100:下到里约
Rio 是一个 MP3 音乐播放器。我为这个设备编写了一些 Linux 软件。每个数据块都以一个 16 字节的控制结构结束。我仔细地安排了结构语句,以确保块结构正确,然而当我测试程序时,我的 Rio 播放器不断丢失数据块。
那么发生了什么?
1 /*************************************************
2 * A small part of a set of routines to *
3 * download music to a RIO mp3 player. *
4 * *
5 * Full sources for the original can be found *
6 * at http://www.oualline.com. *
7 * *
8 * This just tests the writing of the end of *
9 * block structure to the device. *
10 *************************************************/
11
12 #include <stdio.h>
13 /*
14 * The 16 byte end of block structure for a Rio.
15 * (We'd label the fields if we knew what they
16 * were.)
17 */
18 struct end_block_struct
19 {
20 unsigned long int next_512_pos; // [0123]
21 unsigned char next_8k_pos1; // [4]
22 unsigned char next_8k_pos2; // [5]
23
24 unsigned long int prev_251_pos; // [6789]
25 unsigned char prev_8k_pos1; // [10]
26 unsigned char prev_8k_pos2; // [11]
27
28 unsigned short check_sum; // [12,13]
29 unsigned short prev_32K_pos; // [14,15]
30 };
31
32 /*
33 * Macro to print offset of the
34 * field in the structure
35 */
36 #define OFFSET(what) \
37 printf(#what " %d\n", int(&ptr->what));
38
39 int main()
40 {
41 // A structure for debugging the structure
42 struct end_block_struct *ptr = NULL;
43
44 printf("Structure size %d\n",
45 sizeof(end_block_struct));
46 OFFSET(next_512_pos);
47 OFFSET(next_8k_pos1);
48 OFFSET(next_8k_pos2);
49
50 OFFSET(prev_251_pos);
51 OFFSET(prev_8k_pos1);
52 OFFSET(prev_8k_pos2);
53
54 OFFSET(check_sum);
55 OFFSET(prev_32K_pos);
56 return (0);
57 }
(下一个提示 343。答案 103。)
![]() |
|---|
一所大型大学计算机化了其课程安排。一些课程标题必须缩写以适应计算机对其长度的限制。大多数课程缩写得很好,然而“人类性学,中级课程”变成了“性学 Int. 课程。”
![]() |
|---|
程序 101:无路可退
为什么以下程序在 UNIX 上输出正确的文件,而在 Microsoft Windows 上输出错误的文件?程序写出了 128 个字符,但 Microsoft Windows 包含 129 个字符。为什么?
1 /*************************************************
2 * Create a test file containing binary data. *
3 *************************************************/
4 #include <iostream>
5 #include <fstream>
6 #include <stdlib.h>
7
8 int main()
9 {
10 // current character to write
11 unsigned char cur_char;
12
13 // output file
14 std::ofstream out_file;
15
16 out_file.open("test.out", std::ios::out);
17 if (out_file.bad())
18 {
19 std::cerr << "Can not open output file\n";
20 exit (8);
21 }
22
23 for (cur_char = 0;
24 cur_char < 128;
25 ++cur_char)
26 {
27 out_file << cur_char;
28 }
29 return (0);
30 }
(下一个提示 349。答案 5。)
![]() |
|---|
犯错误是人类的天性;要真正搞砸,你需要一台电脑。要保持事情搞砸,你需要一个官僚机构。
![]() |
|---|
程序 102:一路飞奔
在大多数 UNIX 系统上,这个程序可以正常工作。在 MS-DOS 上则不行。为什么?
1 /********************************************
2 * Check a couple of zip codes. *
3 ********************************************/
4 #include <iostream>
5
6 int main()
7 {
8 // A couple of zip codes
9 const int cleveland_zip = 44101;
10 const int pittsburgh_zip = 15201;
11
12 if (cleveland_zip < pittsburgh_zip)
13 {
14 std::cout <<
15 "Cleveland < Pittsburgh (Wrong)\n";
16 }
17 else
18 {
19 std::cout <<
20 "Pittsburgh < Cleveland (Right)\n";
21 }
22
23 return (0);
24 }
(下一个提示 104。答案 104。)
![]() |
|---|
曾经有一位程序员为一家银行编写了一个批量生成信件的程序。银行想要向其最富有的 1000 位客户发送一封特别定制的信件。不幸的是,对于这位程序员来说,他没有充分调试他的代码。更糟糕的是,银行没有检查第一批批量生成的信件。
结果:最富有的 1000 位客户都收到了一封以“亲爱的富家子弟”开头的信。
![]() |
|---|
第十章:一些实际程序
程序员喜欢技巧。在本章中,我们将探讨一些使用极其巧妙技巧来完成工作的实际程序。
这些算法的一个有趣之处在于,在野外,它们都完全没有任何注释,从而给了所有后来的程序员一个机会,自己去解开这些谜题。现在轮到你了。
程序 103:快速更换
以下任务最快的方法是什么:
The variable i has the value 2 or 1\. If i is 2 change it to 1\. If i is 1 change
it to 2.
(下一提示 134。答案 48。)
![]() |
|---|
每年都会举办一个名为“混淆 C 语言竞赛”的比赛。参赛者试图找出如何编写尽可能困难且难以阅读的程序。毕竟,他们是程序员,他们知道在最佳情况下程序也很难理解。这个比赛给了他们一个机会,在最糟糕的情况下理解一个程序。
一些奖项有有趣的标题:
-
在复杂方式下执行的最佳简单任务。
-
在复杂方式下执行的最佳非简单任务。
-
最难以辨认的代码。
-
在混乱中最为全面。
-
最像键盘随机打字。
-
对规则的滥用最严重。
-
最奇怪的源布局。
-
对 ANSI C 的最佳滥用。
![]() |
|---|
程序 104:没有什么特别
下面子程序中那个有趣的 if 语句有什么用?看起来完全无用:
1 /************************************************
2 * sum_file -- Sum the first 1000 integers in *
3 * a file. *
4 ************************************************/
5 #include <iostream>
6 #include <fstream>
7 /************************************************
8 * get_data -- Get an integer from a file. *
9 * *
10 * Returns: The integer gotten from the file *
12 ************************************************/
12 int get_data(
13 // The file containing the input
14 std::istream &in_file
15 ) {
16 int data; // The data we just read
17 static volatile int seq = 0; // Data sequence number
19
19 ++seq;
20 if (seq == 500)
21 seq = seq; // What's this for?
22
23 in_file.read(&data, sizeof(data));
24 return (data);
25 }
26
27 int main() {
28 int i; // Data index
29 int sum = 0; // Sum of the data so far
30
31 // The input file
32 std::ifstream in_file("file.in");
33
34 for (i = 0; i < 1000; ++i) {
35 sum = sum + get_data(in_file);
36 }
37 std::cout << "Sum is " << sum << '\n';
38 return (0);
39 }
(下一个提示 175. 答案 81.)
程序 105:挥舞旗帜
可爱的技巧的问题之一是,太多的程序员没有添加任何注释来告诉你发生了什么。这里是我从 UNIX stty 命令中找到的一些代码的再现。发生了什么?
1 #include <stdio.h>
2
3 int main()
4 {
5 int flags = 0x5; // Some sample flags
6
7 printf("-parity\n" + ((flags & 0x1) != 0));
8 printf("-break\n" + ((flags & 0x2) != 0));
9 printf("-xon\n" + ((flags & 0x4) != 0));
10 printf("-rts\n" + ((flags & 0x8) != 0));
11 return (0);
12 }
13
(下一个提示 301. 答案 108.)
![]() |
|---|
致维护程序员之歌
再次踏上那条孤独的黑暗道路
进入别人的不可能的代码中
通过 "if" 和 "switch" 和 "do" 和 "while"
它扭曲和旋转了数英里
充满陷阱和技巧的巧妙代码
你必须发现它是如何运作的
然后我再次出现,再次询问,
"这个程序到底在做什么呢?"
![]() |
|---|
第十一章:线程化、嵌入式——令人畏惧
概述
当 NASA 试图发射第一艘太空飞船时,他们把航天器推出到发射台,宇航员登上飞船,并开始倒计时。然后计算机报告了自检失败。他们尝试了又尝试,试图找出问题所在。最后,他们不得不取消发射。
问题最终被追踪到一个竞争条件,每次系统启动时发生这种条件的概率是 64 分之一。
必须处理多个进程和嵌入式系统的程序员有一系列他们自己的问题要担心。这些问题通常比普通错误更难找到,因为错误是随机发生的,而虫子可以通过测试来抵抗发现。此外,看起来完美合理且合理的代码可能包含隐藏的错误。
本章致力于探讨困扰嵌入式程序员的神秘、随机和狡猾的虫子。
程序 106:清理垃圾
我们有一个通过 in_port_ptr 指向的内存映射输入端口。该设备可以缓冲最多三个字符。为了初始化设备,我们需要清空缓冲区并清除任何旧的垃圾。这正是这个函数应该做的事情。但有时它不起作用。为什么?
1 /***********************************************
2 * clear port -- Clear the input port. *
3 ***********************************************/
4 // Input register
5 char *in_port_ptr = (char *)0xFFFFFFE0;
6
7 // Output register
8 char *out_port_ptr = (char *)0xFFFFFFE1;
9
10 /***********************************************
11 * clear_input -- Clear the input device by *
12 * reading enough characters to empty the *
13 * buffer. (It doesn't matter if we read *
14 * extra, just so long as we read enough.)*
15 ***********************************************/
16 void clear_input(void)
17 {
18 char ch; // Dummy character
19
20 ch = *in_port_ptr; // Grab data
21 ch = *in_port_ptr; // Grab data
22 ch = *in_port_ptr; // Grab data
23 }
(下一个 提示 129。 答案 9。)
![]() |
|---|
程序优化的第一规则:
不要做。
程序优化的第二规则:
还不要做。
![]() |
|---|
程序 107:更好的垃圾收集器
我们通过添加关键字 "volatile" 修复了程序 106。但问题仍然没有完全解决。
1 /*************************************************
2 * clear port -- Clear the input port. *
3 *************************************************/
4 // Input register
5 const char *volatile in_port_ptr =
6 (char *)0xFFFFFFE0;
7
8 // Output register
9 const char *volatile out_port_ptr =
10 (char *)0xFFFFFFE1;
11
12 /***********************************************
13 * clear_input -- Clear the input device by *
14 * reading enough characters to empty the *
15 * buffer. (It doesn't matter if we read *
16 * extra, just so long as we read enough.)*
17 ***********************************************/
18 void clear_input(void)
19 {
20 char ch; // Dummy character
21
22 ch = *in_port_ptr; // Grab data
23 ch = *in_port_ptr; // Grab data
24 ch = *in_port_ptr; // Grab data 25 }
(下一个提示 336。答案 61。)
![]() |
|---|
一位用户遇到了一个大问题,联系了技术支持。技术人员尝试了几个小时通过电话解决问题,但失败了,于是他要求用户发送他一份磁盘的副本。第二天,通过联邦快递,技术人员收到了一封信,里面包含磁盘的复印件。用户并不完全笨拙:他知道他有一个双面磁盘,所以他将两面都复制了。
令人惊讶的是,技术人员能够从复印件中找出问题所在。原来用户使用的是软件的错误版本。
![]() |
|---|
程序 108:短时延迟
程序员需要在程序中创建一个精确的短延迟。他发现如果他执行 1,863 次乘法,就能产生正确的延迟。这一事实已经被转化为以下子程序。但在某些情况下,该函数会失败。为什么?
1 /*************************************************
2 * bit_delay -- Delay one bit time for *
3 * serial output. *
4 * *
5 * Note: This function is highly system *
6 * dependent. If you change the *
7 * processor or clock it will go bad. *
8 *************************************************/
9 void bit_delay(void)
10 {
11 int i; // Loop counter
12 int result; // Result of the multiply
13
14 // We know that 1863 multiplies delay
15 // the proper amount
16 for (i = 0; i < 1863; ++i)
17 {
18 result = 12 * 34;
19 }
20 }
(Next 提示 342. 答案 16.)
![]() |
|---|
来自我第一个程序中的一个真实评论。
C------------------------------------------------------------
C This program works just like PLOT10 except it works with
C metric data files (MDF). The reason that I didn't add a new
C format to PLOT10 was that PLOT10 is so convoluted that I
C can't understand it.
C
C I have no idea what the input units are nor do I have any idea
C what the output units are but I do know that if you divide
C by 3 the plots look about the right size.
C------------------------------------------------------------
![]() |
|---|
程序 109:短期回顾
程序员试图通过将乘数改为变量来修复程序 108,但循环仍然太短。发生了什么?
1 /***********************************************
2 * bit_delay -- Delay one bit time for *
3 * serial output. *
4 * *
5 * Note: This function is highly system *
6 * dependent. If you change the *
7 * processor or clock it will go bad. *
8 ***********************************************/
9 void bit_delay(void)
10 {
11 int i; // Loop counter
12 int result; // Result of the multiply
13
14 // Factors for multiplication
15 int factor1 = 12;
16 int factor2 = 34;
17
18 // We know that 1863 multiples
19 // delay the proper amount
20 for (i = 0; i < 1863; ++i)
21 {
22 result = factor1 * factor2;
23 }
24 }
(下一个提示 107. 答案 89.)
![]() |
|---|
我曾经收到一个用德语编写的交叉引用程序。我找了一个懂德语但不懂编程的翻译员来处理它。她把 "is called by" 翻译成了 "is shouted at."
![]() |
|---|
程序 110:短期 III
程序 109 已修复。现在的延迟更接近我们预期的值。不是我们预期的那个值,但很接近。现在发生了什么?
1 /***********************************************
2 * bit_delay -- Delay one bit time for *
3 * serial output, *
4 * *
5 * Note: This function is highly system *
6 * dependent. If you change the *
7 * processor or clock it will go bad. *
8 ***********************************************/
9 void bit_delay(void)
10 {
11 int i; // Loop counter
12 volatile int result;// Result of the multiply
13
14 // Factors for multiplication
15 int factorl = 12;
16 int factor2 = 34;
17
18 // We know that 1863 multiplies
19 // delay the proper amount
20 for (i = 0; i < 1863; ++i)
21 {
22 result = factor1 * factor2;
23 }
24 }
(下一 提示 95。 答案 39。)
![]() |
|---|
当 1 的值足够大时,1 等于 2。
![]() |
|---|
程序 111:赛道上的一个肿块
这个程序启动了两个线程。一个线程将数据读入缓冲区,另一个线程将数据写入文件。
但数据有时会损坏。为什么?
1 /***********************************************
2 * Starts two threads *
3 * *
4 * 1) Reads data from /dev/input and puts *
5 * it into a buffer. *
6 * *
7 * 2) Takes data from the buffer and *
8 * writes the data to /dev/output.*
9 ***********************************************/
10 #include <cstdio>
11 #include <stdlib.h>
12 #include <pthread.h>
13 #include <unistd.h>
14 #include <sys/fcntl.h>
15
16 static const int BUF_SIZE = 1024; // Buffer size
17 static char buffer[BUF_SIZE]; // The data buffer
18
19 // Pointer to end of buffer
20 static char *end_ptr = buffer + BUF_SIZE;
21
22 // Next character read goes here
23 static char *in_ptr = buffer;
24
25 // Next character written comes from here
26 static char *out_ptr = buffer;
27
28 static int count = 0; // Number of characters in the buffer
29
30 /***********************************************
31 * reader -- Read data and put it in the global *
32 * variable buffer. When data is *
33 * installed the variable count is *
34 * increment and the buffer pointer *
35 * advanced. *
36 ************************************************/
37 static void *reader(void *) {
38 // File we are reading
39 int in_fd = open("/dev/input", 0_RDONLY);
40
41 while (1) {
42 char ch; // Character we just got
43
44 while (count >= BUF_SIZE)
45 sleep(1);
46
47 read(in_fd, &ch, 1);
48
49 ++count;
50 *in_ptr = ch;
51 ++in_ptr;
52
53 if (in_ptr == end_ptr)
54 in_ptr = buffer;
55 }
56 }
57
58 /***********************************************
59 * writer -- Write data from the buffer to *
60 * the output device. Gets the data *
61 * from the global buffer. Global variable*
62 * count is decrement for each character *
63 * taken from the buffer and the buffer *
64 * pointer advanced. *
65 ***********************************************/
66 static void writer(void)
67 {
68 // Device to write to
69 int out_fd = open("/dev/output", 0_RDONLY);
70
71 while (1) {
72 char ch; // Character to transfer
73
74 while (count <= 0)
75 sleep(1);
76
77 ch = *out_ptr;
78
79 --count;
80 ++out_ptr;
81
82 if (out_ptr == end_ptr)
83 out_ptr = buffer;
84
85 write(out_fd, &ch, 1);
86 }
87 }
88
89 int main() {
90 int status; /* Status of last system call */
91
92 /* Information on the status thread */
93 pthread_t reader_thread;
94
95 status = pthread_create(&reader_hread, NULL, reader, NULL);
96
97 if (status != 0) {
98 perror("ERROR: Thread create failed:\n ");
99 exit (8);
100 }
101
102 writer();
103 return (0);
104 }
(下一个提示 222。答案 92。)
![]() |
|---|
多年来,系统安装程序已经开发了许多不同的方法来在假天花板上拉线。其中一种更创新的方法是“小狗”法。一个人拿一只小狗,把绳子系在它的项圈上,然后把狗放在天花板上。然后主人走到他们希望电缆出来的地方,叫狗。狗跑向主人。他们把电缆系在绳子上,把它拉过去,然后安装电缆。
![]() |
|---|
程序 112:急急忙忙等待
由于某种原因,这个程序运行了一段时间后停止:
1 #include <cstdio>
2 #include <stdlib.h>
3 #include <pthread.h>
4 #include <sys/fcntl.h>
5
6 // Resource protection mutexes
7 static pthread_mutex_t resource1 =
8 PTHREAD_MUTEX_INITIALIZER;
9
10 static pthread_mutex_t resource2 =
11 PTHREAD_MUTEX_INITIALIZER;
12
13 /************************************************
14 * A couple of routines to do work. Or they *
15 * would do work if we had any to do. *
16 ***********************************************/
17 static void wait_for_work(void) {} // Dummy
18 static void do_work(void) {} // Dummy
19
20 /***********************************************
21 * process_1 -- First process of two. *
22 * *
23 * Grab both resources and then do the work *
24 ***********************************************/
25 static void *process_1(void *)
26 {
27 while (1) {
28 wait_for_work();
29
30 pthread_mutex_lock(&resource1);
31 pthread_mutex_lock(&resource2);
32
33 do_work();
34
35 pthread_mutex_unlock(&resource2);
36 pthread_mutex_unlock(&resource1);
37 }
38 }
39
40 /************************************************
41 * process_2 -- Second process of two. *
42 * *
43 * Grab both resources and then do the work. *
44 * (but slightly different work from *
45 * process_1) *
46 ************************************************/
47 static void process_2(void)
48 {
49 while (1) {
50 wait_for_work();
51
52 pthread_mutex_lock(&resource2);
53 pthread_mutex_lock(&resource1);
54
55 do_work();
56
57 pthread_mutex_unlock(&resources1);
58 pthread_mutex_unlock(&resource2);
59 }
60 }
61
62 int main()
63 {
64 int status; /* Status of last system call */
65
66 /* Information on the status thread */
67 pthread_t thread1;
68
69 status = pthread_create(&thread1,
70 NULL, process_1, NULL);
71
72 if (status != o) {
73 perror(
74 "ERROR: Thread create failed:\n ");
75 exit (8);
76 }
77
78 process_2();
79 return (0);
80 }
(下一提示 97。答案 24。)
程序 113:旗帜挥舞
本程序包含 UNIX 终端驱动程序的一小部分。(UNIX 终端驱动程序使用了大量的标志。)
当这段代码被移植到 Celerity C1000 计算机时,我们开始遇到问题。大约每周一次,标志会神秘地被设置或清除。你能发现发生了什么吗?
1 /************************************************
2 * flag -- Demonstrate the use of flag setting *
3 * and clearing. This is a demonstration *
4 * program that does not run in real life. *
5 * But it is a good example of a very tiny *
6 * part of the code in a terminal driver. *
7 ************************************************/
8 #include <cstdio>
9 #include <stdlib.h>
10 #include <pthread.h>
11
12
13 const char XOFF = 'S' - '@';// Turns off output
14 const char XON = '0' - '@'; // Turns on output
15
16 static int flags = 0; // State flags
17 //
18 // ^S in effect
19 const int STOP_OUTPUT = (1 << 0);
20
21 // CD is present
22 const int CD_SIGNAL = (1 << 1);
23
24 /***********************************************
25 * read_ch -- read a single character. *
26 * *
27 * Returns the character read. *
28 ***********************************************/
29 static char read_ch(void)
30 {
31 // Dummy function
32 return ('x');
33 }
34
35 /************************************************
36 * write_ch -- write a character to the output *
37 * (Whatever that is.) *
38 ************************************************/
39 static void write_ch(const char ch)
40 {
41 // Dummy function
42 }
43 /************************************************
44 * do_input -- handle the reading and *
45 * processing of characters. *
46 ************************************************/
47 static void *do_input(void *)
48 {
49 while (1)
50 {
51 char ch; // Character we just read
52
53 ch = read_ch();
54
55 switch (ch) {
56 case XOFF:
57 flags |= STOP_OUTPUT;
58 break;
59 case XON:
60 flags &= ~STOP_OUTPUT;
61 break;
62 default:
63 write_ch(ch);
64 break;
65 }
66 }
67 }
68
69 /************************************************
70 * wait_for_cd_change -- wait for the CD signal *
71 * to change and return the value of the *
72 * signal. *
73 ************************************************/
74 static int wait_for_cd_change(void)
75 {
76 // Dummy
77 return (1);
78 }
79 /***********************************************
80 * do_signals -- Monitor signals and set flags *
81 * based on the signal changes. *
82 ***********************************************/
83 void do_signals(void)
84 {
85 while (1) {
86 // The current cd level
87 int level = wait_for_cd_change();
88 if (level) {
89 flags |= CD_SIGNAL;
90 } else {
91 flags &= ~CD_SIGNAL;
92 }
93 }
94 }
95
96 int main()
97 {
98 int status; // Status of last system call
99
100 // Information on the status thread
101 pthread_t input_thread;
102
103 status = pthread_create(&input_thread,
104 NULL, do_input, NULL);
105
106 if (status != 0) {
107 perror(
108 "ERROR: Thread create failed:\n ");
109 exit (8);
110 }
111
112 do_signals();
113 return(o);
114 }
(下一提示 22。答案 52。)
![]() |
|---|
问题的主要原因是解决方案。
![]() |
|---|
程序 114:缓慢进展
该程序包含两个线程。第一个线程,sum,执行一些耗时的工作。第二个线程,status_monitor,每次用户按下回车键时都会显示一个进度报告。但在多次测试运行后,程序员开始怀疑状态报告是不正确的。为什么?
1 /************************************************
2 * Sum -- This program sums the sine of the *
3 * numbers from 1 to MAX. (For no good *
4 * reason other than to have something *
5 * to do that takes a long time.) *
6 * *
7 * Since this takes a long time, we have a *
8 * second thread that displays the progress of *
9 * the call. *
10 ************************************************/
11 #include <cstdio>
12 #include <cmath>
13 #include <pthread.h>
14 #include <stdlib.h>
15
16 /* Counter of what we've summed so far */
17 static int counter;
18
19 /************************************************
20 * status_monitor -- Monitor the status and *
21 * tell the user how far things have *
22 * progressed. *
23 * *
24 * This thread merely waits for the user to *
25 * press <enter> and then reports the current *
26 * value of counter. *
27 ************************************************/
28 static void *status_monitor(void *) {
29 /* buffer to stuff that comes in */
30 char buffer[3];
31
32 while (1) {
33 fgets(buffer, sizeof(buffer), stdin);
34 printf("Progress %d\n", counter);
35 fflush(stdout);
36 }
37 }
38
39 /************************************************
40 * sum -- Sum the sine of the numbers from 0 to *
41 * 0x3FFFFFFF. Actually we don't care *
42 * about the answer, all we're trying to *
43 * do is create some sort of compute *
44 * bound job so that the status_monitor *
45 * can be demonstrated. *
46 ************************************************/
47 static void sum(void) {
48 static double sum = 0; /* Sum so far */
49
50 for (counter = 0;
51 counter < 0x3FFFFFF;
52 ++counter)
53 {
54 sum += sin(double(counter));
55 }
56
57 printf("Total %f\n", sum);
58 exit (0);
59 }
60
61 int main() {
62 // Status of last system call
63 int status;
64
65 // Information on the status thread
66 pthread_t status_thread;
67
68 status = pthread_create(&status_thread, NULL,
69 status_monitor, NULL);
70
71 if (status != o) {
72 perror(
73 "ERROR: Thread create failed:\n ");
74 exit (8);
75 }
76
77 sum();
78
79 return(0);
80 }
(下一个提示 350. 答案 114.)
第二部分:提示
提示 1:在铁路的早期,他们在铁轨交叉的地方遇到了火车相撞的问题。因此,他们通过了一项法律:
-
当两列火车在铁轨交叉的地方相遇时,双方都应停车,并保持停车状态,直到另一列火车通过。
-
(答案 24。)
提示 2:UNIX 使用 <换行符> 来结束行。Microsoft Windows 使用 <回车符><换行符>。(答案 5。)
提示 3:该语句:
if (n2 =! 0)
除非它的任务是让你困惑,否则它没有在履行其职责,在这种情况下,它做得非常出色。(下一条提示 82。答案 25。)
提示 4:构造函数正确地初始化了魔法数字。或者如果它被调用的话。但众所周知,没有调用构造函数就无法创建变量。嗯,几乎每个人都知道。(下一条提示 300。答案 98。)
提示 5:简单宏和参数化宏之间有什么区别?(答案 113。)
提示 6:操作系统调用很昂贵。(答案 96。)
提示 7:无符号字符权限
(下一条提示 313。答案 11。)
提示 8:树木外科医生定律:不要砍掉你站着的树枝。(下一条提示 317。答案 75。)
提示 9:该程序在复杂指令集机器(如 80x86 CPU)上不会失败,但在 RISC 机器(如 Sparcs)上会失败。它还在 Celerity 1000 ^([1]), 我就是在那里找到问题的。(下一条提示 143。答案 52。)
提示 10:这个程序一次读取一个字符的数据。它本应一次写入一个字符的数据。(下一条提示 102。答案 99。)
提示 11:当程序员尝试设置调试变量时,他会收到错误信息:
debugging -- no such variable or class
(下一条提示 105。答案 84。)
提示 12:检查预处理器输出。(答案 82。)
提示 13:g++ 编译器发出警告:
var.cpp: In function 'int main()':
var.cpp:14: warning: unused variable 'bool remove'
var.cpp:16: warning: the address of 'int remove(const char*)', will always be
'true'
(答案 35。)
提示 14:优化器可以随意对你的代码进行游戏。(下一条提示 114。)
提示 15:结果是系统相关的。(下一条提示 278。答案 63。)
提示 16:M_PI 是正确的,但打印的结果是错误的。(下一条提示 170。答案 10。)
提示 17:逗号操作符返回第二个表达式的结果。所以表达式 5,9 的值是 9。(下一条提示 348。答案 86。)
提示 18:你不能。(下一条提示 344。答案 80。)
提示 19:printf 函数迷失了方向,开始编造东西。(下一条提示 31。答案 85。)
提示 20: 循环体执行的次数可能比你想象的要少。(下一提示 36。答案 89。)
提示 21: 如果你使用 MS-DOS,结果取决于内存模型。(下一提示 130。答案 21。)
提示 22: 失败是系统依赖的。(下一提示 9。答案 52。)
提示 23: 当何时调用 true_name 的析构函数?何时使用字符串?(答案 30。)
提示 24: 在调用 tmp_name 和使用结果之间调用更多函数,你得到坏结果的可能性就越大。(下一提示 85。答案 18。)
提示 25: C++ 只部分是类型安全的。(下一提示 63。答案 7。)
提示 26: 静态数据是危险的。(答案 100。)
提示 27: 需要的资源:资源 1,资源 2 - 或者是资源 2,资源 1?(下一提示 1。答案 24。)
提示 28: 通过预处理器运行程序。(下一提示 327。答案 29。)
提示 29: 循环执行了多少次?(下一提示 20。答案 89。)
提示 30: Borland 编译器允许你在编译时定义字符变量的默认值是 signed 还是 unsigned。(下一提示 60。答案 8。)
提示 31: C 不对 printf 调用进行参数检查。(下一提示 277。答案 85。)
提示 32: 答案取决于谁编写了你的堆管理库。 (答案 77。)
提示 33: 通过预处理器运行程序并查看结果。(下一提示 179。答案 105。)
提示 34: 结果是:
11072 12627 16262
3157 3664 5034
13605 16307 22366
(下一提示 158。答案 53。)
提示 35: 预处理器语法不是 C++ 语法。(下一提示 284。答案 82。)
提示 36: 计算完结果后我们怎么办?(下一提示 152。答案 89。)
提示 37: 任何编写 i++++ 的人都应该被枪毙。(下一提示 272。答案 87。)
提示 38: 语句:
counter = = 10;
是一个有效的 C++ 语句。它不做任何事情,但它有效。 (下一提示 205。答案 112。)
提示 39: 你能精确地将 1/3 表示为小数吗?计算机能否精确地将 0.1 表示为浮点数?(两者的答案相同。) (答案 107。)
提示 40: 问题出在更早的行,而不是第 16 行。(下一提示 346。答案 79。)
提示 41: 高度从未被分配为 2。(下一提示 78。答案 62。)
提示 42: 字典文件是按字母顺序排列的。(下一提示 311。答案 74。)
提示 43:an_array.operator = (an_array) 做什么?(答案 75.)
提示 44:缩进不正确。(下一个 提示 156. 答案 31.)
提示 45:g++ 编译器输出警告:
semi.cpp: In function 'int main()':
semi.cpp:15: warning: statement with no effect
(下一个 提示 35. 答案 82.)
提示 46:所见即所得——字面意思。 (下一个 提示 307. 答案 69.)
提示 47:输出是:
One million 1
(下一个 提示 59. 答案 44.)
提示 48:函数调用结束时缓冲区会发生什么?(答案 83.)
提示 49:编译器可以决定一些多部分语句的执行顺序。(答案 26.)
提示 50:此程序不会生成编译器警告。(下一个 提示 318. 答案 20.)
提示 51:两个问题与结构体数据中放置的内容有关。(我知道这使总数达到四个,但有一个同时属于这两个类别。) (答案 71.)
提示 52:你知道这个提示应该是什么,不是吗?(下一个 提示 207. 答案 42.)
提示 53:我们有两个函数在无限递归中相互调用结果。因为只有三个成员函数,所以应该不难找出哪些函数导致了问题。(下一个 提示 125. 答案 12.)
提示 54:运算符优先级。(答案 49.)
提示 55:我不知道你可以在 C++ 数字中放置逗号。(下一个 提示 335. 答案 44.)
提示 56:delete 在哪里被调用?(答案 32.)
提示 57:双精度是 64 位。C 标准是将所有浮点运算都在双精度下进行。
所有这些都与问题无关。(顺便说一句,从 64 位浮点格式中获取 64 位分数是一个巧妙的方法。) (下一个 提示 94. 答案 73.)
提示 58:0d 是回车符的 ASCII 码。(下一个 提示 234. 答案 5.)
提示 59:g++ 编译器发出警告:
comma.cpp: In function 'int main()':
comma.cpp:12: warning: left-hand operand of comma expression has no effect
(下一个 提示 126. 答案 44.)
提示 60:g++ 编译器发出警告:
chff.cpp: In function `int main()':
chff.cpp:13: warning: comparison is always 0 due to limited range of data type
(答案 8.)
提示 61:未捕获的异常是 class problem 类。真的!(下一个 提示 339. 答案 55.)
提示 62:字符 "A" 的整数值为 65。字符 "A" + 1 的整数值为 66。这与输出有关:
A6667
(答案 45.)
提示 63:在 C++ 中,外部变量不是类型安全的。(答案 7.)
提示 64:fork 系统调用创建了一个具有重复内存的重复进程。 (下一个 提示 252. 答案 50.)
提示 65:程序崩溃。 (下一个 提示 282. 答案 115.)
提示 66:缩进不正确。(答案 97.)
提示 67:不要指望缩进是正确的。 (答案 13。)
提示 68:该程序返回给操作系统的退出代码是什么? (答案 6。)
提示 69:结果是系统相关的。 (下一提示 279。答案 94。)
提示 70:一些示例运行:
Enter two integers: 100 3
Result is: 100
Enter two integers: 37 0
Result is: 37
(下一提示 3。答案 25。)
提示 71:换行符出现在不希望出现的地方。 (答案 33。)
提示 72:C++字符串为我们处理一切。但它们在我们背后做的一件事会引发麻烦。 (下一提示 162。答案 36。)
提示 73:程序运行时崩溃。 (下一提示 182。答案 95。)
提示 74:在大多数系统中,使用以下命令:
$ program
将会工作,并使用以下命令:
$ program >output.txt
将会失败。 (下一提示 197。答案 83。)
提示 75:out_file 是什么类型的参数? (下一提示 159。答案 40。)
提示 76:在二进制中,3 是 0011。在二进制中,12 是 1100。 (下一提示 218。答案 17。)
提示 77:当打开失败时发生错误。 (下一提示 288。答案 60。)
提示 78:该语句
11 height = 2;
不是一个可执行语句;它看起来像是一个。 (下一提示 287。答案 62。)
提示 79:何时初始化 a_var 和调用构造函数? (下一提示 137。答案 111。)
提示 80:计算机不知道基本的数学。 (下一提示 268。答案 1。)
提示 81:没有原型——没有参数检查。 (下一提示 174。答案 41。)
提示 82:g++ 编译器生成以下警告:
not_z.cpp: In function `int main()':
not_z.cpp:13: warning: suggest parentheses around assignment used as truth
value
(下一提示 262。答案 25。)
提示 83:有两个声明,但只声明了一个变量。 (下一提示 148。答案 57。)
提示 84:0x8000 >> 1 是什么? (答案 19。)
提示 85:指向的是什么?谁拥有它?拥有多久? (答案 18。)
提示 86:每个指针指向的数据由谁拥有。 (下一提示 26。答案 100。)
提示 87:结果是系统相关的。 (下一提示 21。答案 21。)
提示 88:该语句的数据放在哪里:
printf("That's all\n");
(下一提示 48。答案 83。)
提示 89:这是正确的,合法的标准 C++,即使有些人可能看起来不这么认为。 (下一提示 211。答案 86。)
提示 90:将输出通过预处理器运行。 (下一提示 273。答案 88。)
提示 91:g++ 编译器给出以下警告:
hbit.cpp: In function 'void bit_out(short int)':
hbit.cpp:19: warning: overflow in implicit constant conversion
(答案 2。)
提示 92:很明显,问题必须在第 28 行之前,因为我们没有看到“Starting....”信息。(下一提示 111。答案 68。)
提示 93:输出如下:
i is 3
i is 2
(答案 87。)
提示 94:这取决于实现。在一些较旧的系统上,你会得到正确的位数精度。在一般模拟浮点数的系统中,会报告准确的结果,而在具有浮点协处理器的系统中,会报告夸张的结果。(答案 73。)
提示 95:结果是编译时开关相关的。(下一提示 331。答案 39。)
提示 96:gcc 警告如下:
sum.c: In function 'sum':
sum.c:13: warning: declaration of 'i1' shadows a parameter
sum.c:14: warning: declaration of 'i2' shadows a parameter
sum.c:15: warning: declaration of 'i3' shadows a parameter
(答案 94。)
提示 97:竞争条件。平局会导致死机。(下一提示 27。答案 24。)
提示 98:remove 是一个标志.remove 不是一个标志。(下一提示 221。答案 35。)
提示 99:人类将波士顿的邮编打印为 02126。C++看待事物的方式不同。(下一提示 308。答案 15。)
提示 100:示例输出:
Area of sample is 0
(下一提示 326。答案 93。)
提示 101:这是一个老 C 程序员将旧的 C 程序移植到 C++的例子。(下一提示 120。答案 98。)
提示 102:输出看起来像这样:
47421068117101321161113211511110910132981149710511
01009710997103101100321121141111031149710932114101
11311710511410110910111011611544101161041013210211
...
(下一提示 160。答案 99。)
提示 103:结果是系统相关的。(下一提示 314。答案 90。)
提示 104:在 MS-DOS 系统上,克利夫兰的邮编是一个负数。(下一提示 223。答案 104。)
提示 105:优化器可以在这段代码上做很多工作。(答案 84。)
提示 106:g++ 编译器发出警告:
comment.cpp:19:35: warning: "/*" within comment
(答案 91。)
提示 107:结果是编译时开关相关的。(下一提示 29。答案 89。)
提示 108:什么是缓冲区?(下一提示 263。答案 68。)
提示 109:setjmp 和 longjmp不做什么?(答案 66。)
提示 110:异常没有被捕获。乍一看,这似乎是不可能的,因为我们只捕获了一个异常类,即 problem。即使我们没有捕获它,catch(...)也应该捕获其他所有内容。(下一提示 173。答案 55。)
提示 111:在编程中,没有什么明显的事情。(答案 68。)
提示 112:这是一个错误。(下一提示 227。答案 38。)
提示 113:用十进制写 1/3。(下一提示 302。答案 54。)
提示 114:每次都是同一个数字。(下一提示 66。答案 97。)
提示 115:两个 if,一个 else。那么 else 属于哪个 if?(答案 31。)
提示 116:结果是系统相关的。你可能幸运地得到正确答案,或者你可能得到随机数。(答案 51。)
提示 117:short int 的范围是多少?(答案 1。)
提示 118:有趣的宏定义。(下一提示 190。答案 113。)
提示 119:炸弹操作取决于编译器。在便宜的编译器上你会得到一个核心转储。更好的编译器会打印出一个错误信息,告诉你调用了一个纯虚函数。(下一提示 237。答案 101。)
提示 120:无法更改类中的常量。然而,如果我们通过调试器运行它,我们会发现魔法数字是 0 而不是预期的值。(下一提示 4。答案 98。)
提示 121:这个问题取决于编译器标志。(下一提示 14。答案 114。)
提示 122:输出是:
11 squared is 121
不是程序员期望的 1 到 10 的平方。(答案 34。)
提示 123:打印的项不是一个整数。(下一提示 149。答案 86。)
提示 124:变量 ch 是一个字符。ch+1 是什么?(下一提示 283。答案 45。)
提示 125:计算复制构造函数被调用的次数。(下一提示 235。答案 12。)
提示 126:"000" 是一个合法的 C++ 语句。完全无用,但合法。(答案 44。)
提示 127:是的,缓冲 I/O 对这样的程序很有用。但这里的方式不是使用缓冲 I/O 库 iostream。(答案 65。)
提示 128:乘法操作进行了多少次?(答案 39。)
提示 129:结果可能取决于编译时使用的编译器标志。(下一提示 310。答案 9。)
提示 130:英特尔机器有一个非常损坏的段指针架构。(下一提示 231。答案 21。)
提示 131:结果是编译器相关的。(下一提示 141。答案 8。)
提示 132:进程切换可以在任何时候发生。(下一提示 276。答案 92。)
提示 133:预处理器不是 C++。(下一提示 360。答案 46。)
提示 134:这里有一种方法:
if (i = = 2)
i=1;
else
i = 2;
但有一个更快的方法。(下一提示 140。答案 48。)
提示 135:答案取决于系统。(下一提示 264。答案 70。)
提示 136:八进制。(答案 15。)
提示 137:std::cout 何时初始化?(答案 111。)
提示 138:g++ 警告:
comment.cpp:11: warning: '/*' within comment
(答案 62。)
提示 139:我期望程序打印:
First 1
First 1
First 1
Second 1
Second 2
Second 3
这并不是打印出来的内容。(下一提示 297。答案 102。)
提示 140: 这是另一种方法:
i = (i == 2) ? 1 : 2;
但有一个更快的方法。(下一提示 216。答案 48。)
提示 141: 结果可以通过某些编译器的编译时开关进行更改。(下一提示 30。答案 8。)
提示 142: 派生类的构造函数按“基类,派生类”的顺序调用。析构函数按“派生类,基类”的顺序调用。(答案 101。)
提示 143: 语句:
flags |= CD_SIGNAL;
应该设置标志中的一个位。大多数时候它确实是这样做的。(答案 52。)
提示 144: 输出是:
----------------
(下一提示 91。答案 2。)
提示 145: 正在传递的变量的类型是什么?就函数而言,参数类型是什么?(下一提示 315。答案 72。)
提示 146: C++ 的 std::string 类分配内存。但它也会销毁它,并且被精心设计以避免内存泄漏。(下一提示 359。答案 66。)
提示 147: 一些编译器,包括用于此程序的编译器,允许您进行优化和调试。(下一提示 11。答案 84。)
提示 148: g++ 会发出警告:
/tmp/cckuUagE.o: In function 'std::string::_M_data() const':
/home/sdo/local/include/g++-v3/i586-pc-linux-gnu/bits/gthr-
single.h(.data+0x0): multiple definition of 'value'
/tmp/ccenmAbd.o(.data+0x0):/home/sdo/local/include/g++-v3/i586-pc-linux-gnu/
bits/gthr-single.h: first defined here
collect2: ld returned 1 exit status
(答案 57。)
提示 149: 打印的项是一个指针。(下一提示 347。答案 86。)
提示 150: 首次初始化字符串 first_name 是什么时候?full_name 是什么时候?谁强制执行这个顺序?(答案 3。)
提示 151: '\n' 是换行符。(答案 37。)
提示 152: 如果我们对结果不采取任何行动,那么为什么还要计算它?(答案 89。)
提示 153: 声明
struct info *new_info(void)
包含一个线索。(下一提示 101。答案 98。)
提示 154: 有多少个指针?它们指向多少个东西?(下一提示 209。答案 64。)
提示 155: g++ 编译器输出警告:
equal.cpp: In function 'int main()':
equal.cpp:15: warning: suggest parentheses around assignment used as truth value
(下一提示 208。答案 47。)
提示 156: 输出是:
Customer must pay -10
(下一提示 115。答案 31。)
提示 157: 您的结果可能会有所不同。(下一提示 79。答案 111。)
提示 158: SAIL 和 C 几乎没有相同的语法。这两种语言完全不同。然而,同一个单字符错误导致两个程序都出错。(下一提示 220。答案 53。)
提示 159: 通常 C++ 使用“按值传递”来传递参数。这意味着值被复制到子程序中。(下一提示 233。答案 40。)
提示 160: 输出包含一堆整数。(答案 99。)
提示 161: 我数了 3 个。(下一提示 293。答案 71。)
提示 162: 这看起来很像程序 58。(下一提示 178。答案 36。)
提示 163: ++i 返回什么?i++ 返回什么?(下一提示 93。答案 87。)
提示 164: 您的结果可能会有所不同。(下一提示 19。答案 85。)
提示 165: 每个人都知道 (x*4)/4 = x。这是基本的数学。(下一提示 80。答案 1。)
提示 166: 如果你认为编译时开关与优化有关,那你就错了。(下一提示 358。答案 63。)
提示 167: 什么样的数字可以用一个 3 位有符号数表示?(下一提示 169。答案 42。)
提示 168: 输出是:
Division 5
(下一提示 202。答案 91。)
提示 169: 什么样的数字可以用一个 2 位有符号数表示?(下一提示 52。答案 42。)
提示 170: 打印的内容是:
pi is 1413754136
结果取决于机器。(下一提示 203。答案 10。)
提示 171: 并非所有位数组的大小都相同。(下一提示 353。答案 56。)
提示 172: 看到的就是得到的。(下一提示 46。答案 69。)
提示 173: 那么未捕获的异常是从哪里来的?(下一提示 61。答案 55。)
提示 174: gcc 生成了以下警告:
strcat.c: In function `full_name':
strcat.c:19: warning: implicit declaration of function `strcpy'
strcat.c:20: warning: implicit declaration of function `strcat'
strcat.c:20: warning: passing arg 2 of `strcat' makes pointer from integer witho
ut a cast
strcat.c: In function `main':
strcat.c:28: warning: implicit declaration of function `printf'
(答案 41。)
提示 175: 如果程序正常运行,这个语句绝对是毫无用处的。(下一提示 232。答案 80。)
提示 176: 您的结果可能会有所不同。(下一提示 24。答案 18。)
提示 177: 结果取决于系统和编译器。(下一提示 49。答案 26。)
提示 178: 这是程序 58 中的错误的一个 C++ 版本。(答案 36。)
提示 179: ABORT 语句看起来像是一个语句。它不是。(答案 105。)
提示 180: 输出是:
-xxxxxxxxxxxxxxx
(下一提示 303。答案 19。)
提示 181: scanf 停止读取时文件会留在哪里?(答案 28。)
提示 182: 程序在执行 sscanf 时会崩溃。(下一提示 254。答案 95。)
提示 183: 在这种情况下,缓冲 I/O 有用吗?(下一提示 213。答案 65。)
提示 184: 问题涉及到过度使用 delete。(下一提示 188。答案 115。)
提示 185: 我们并不总是在打开文件后关闭它。结果是文件描述符耗尽。我们需要添加一些 close(fd) 语句。(这是 3 个问题中的 2 个。)(答案 60。)
提示 186: 程序使用内联函数,这些函数由某人指向。这可能是问题的一部分吗?(下一提示 219。答案 77。)
提示 187: 在以下内容的末尾看起来有一个无用的分号:
result=result/*divisor; /* Do divide */;
它不是没有用的。(下一提示 245。答案 91。)
提示 188:在拷贝构造函数中使用 delete 运算符。正在删除什么?(答案 115。)
提示 189:an_array 变量有多少个实例?(下一提示 329。答案 59。)
提示 190:通过预处理器运行它。(下一提示 5。答案 113。)
提示 191:返回了什么?(下一提示 23。答案 30。)
提示 192:输出是系统相关的。(下一提示 90。答案 88。)
提示 193:缩进不正确。(下一提示 122。答案 34。)
提示 194:结果是系统相关的。(下一提示 324。答案 112。)
提示 195:prev_ch 被创建了很多次。(答案 106。)
提示 196:volatile 修改了什么?(答案 61。)
提示 197:setbuf 导致数据被放在哪里?(下一提示 88。答案 83。)
提示 198:M_PI 在 math.h 中定义为
#define M_PI 3.14159265358979323846 /* pi */
(下一提示 16。答案 10。)
提示 199:这个函数:
trouble operator = (const trouble &i_trouble)
return?(下一提示 333。答案 109。)
提示 200:它没有打印出预期的内容。(下一提示 192。答案 88。)
提示 201:输出:
The area is 367
(下一提示 259。答案 29。)
提示 202:那么为什么除法没有发生呢?(下一提示 187。答案 91。)
提示 203:g++ 编译器报告的警告:
pi.c: In function 'main':
pi.c:12: warning: int format, double arg (arg 2)
(答案 10。)
提示 204:输出是:
Y=8
(下一提示 54。答案 49。)
提示 205:MAX 不是 10。(答案 112。)
提示 206:从 C++ 的角度来看,打印是正确的。(下一提示 99。答案 15。)
提示 207:g++ 编译器发出警告:
bit.cpp: In function 'int main()':
bit.cpp:33: warning: comparison is always 0 due to width of bitfield
(答案 42。)
提示 208:典型运行:
$ equal
Enter current balance: 10
You owe 0
$ equal
Enter current balance: 0
You owe 0
$ equal
Enter current balance: -10
You owe 0
(下一提示 267。答案 47。)
提示 209:只有一个名称变量和两个指针。(答案 64。)
提示 210:常量成员的初始化顺序是什么?(下一提示 100。答案 93。)
提示 211:结果是系统相关的。(下一提示 123。答案 86。)
提示 212:结果是系统相关的。(下一提示 225。答案 110。)
提示 213:在这个情况下使用了缓冲 I/O 吗?(下一提示 127。答案 65。)
提示 214:显然,它只打印一次 Hello 和两个新行。但同样明显,如果它做了同样的事情,它就不会出现在这本书里。(下一提示 64。答案 50。)
提示 215:看起来第 10 行和第 11 行有注释。这并不完全正确。第 10 行和第 11 行都有注释。(下一条提示 138。答案 62。)
提示 216:最快的方法不使用比较,只使用一次减法。( 答案 48。)
提示 217:程序输出:
Error: Could not open
oot
ewable
(下一条提示 243。答案 37。)
提示 218:"按位与" 不等于 "逻辑与"。(答案 17。)
提示 219:程序使用了内联函数,有人指向这些函数。这可能是问题的一部分吗?不。这与它无关。C++ 处理这种情况非常好。(下一条提示 271。答案 77。)
提示 220:如果这是一个密码,数字的频率分析可能会提供线索。实际上,这不是一个密码,但数字的频率分析可能会很有趣。(下一条提示 341。答案 53。)
提示 221:remove 是两件事。(下一条提示 13。答案 35。)
提示 222:竞争条件。(下一条提示 132。答案 92。)
提示 223:大多数 UNIX 编译器使用 32 位整数。在 MS-DOS(我的意思是 MS-DOS,而不是 Microsoft Windows),整数通常是 16 位。(下一条提示 258。答案 104。)
提示 224:典型运行:
% calc
Enter operator and value:+ 5
Total: 5
Enter operator and value:+ 10
Bad operator entered
Total: 5
Enter operator and value:Bad operator entered
Total: 5
Enter operator and value :q
Bad operator entered
Total: 5
Enter operator and value:q
(下一条提示 257。答案 28。)
提示 225:你认为初始化 log_file 需要什么?(答案 110。)
提示 226:这个程序中至少有三个错误,它们的性质相似。(下一条提示 77。答案 60。)
提示 227:如果余额为 0 会发生什么?(答案 38。)
提示 228:这个程序可以在所有已知的 C++ 编译器上编译并运行。然而它是错误的!这是怎么发生的?(下一条提示 321。答案 66。)
提示 229:09 后的字节是错误的。(下一条提示 58。答案 5。)
提示 230:对齐和填充。(下一条提示 249。答案 103。)
提示 231:在英特尔机器上,在某些内存模型中,编译器生成的代码仅用于操作指针的地址部分,而将段保留不变。( 答案 21。)
提示 232:只有在你以交互式调试器运行程序时,这个声明才有用。(下一条提示 309。答案 81。)
提示 233:复制 ostream 变量意味着什么?(答案 40。)
提示 234:0a 是换行符的 ASCII 码。(下一条提示 2。答案 5。)
提示 235:复制构造函数在两个地方被调用。( 答案 12。)
提示 236:var_array::~var_array 被调用了多少次?(下一条提示 286。答案 59。)
提示 237: 编译器会非常努力地防止你调用纯虚函数。你不能声明抽象类的实例,并且任何基类都必须有一个派生版本,该版本定义了所有纯虚函数。这意味着任何纯虚函数在基类中都将有一个实际的定义。
那么,如果我们知道在派生类中必须有一个虚函数的实现,我们是如何调用一个的呢?(下一 提示 142。答案 101。)
提示 238: 常识告诉你,如果你将数组声明为
int array[5]
然后元素是:
array[l], array[2], array[3], array[4], array[5]
常识与编程无关。(答案 90。)
提示 239: 以下是 MS-DOS 输出的十六进制转储:
000000 00 01 02 03 04 05 06 07 08 09 0d 0a 0b 0c 0d 0e
000010 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e
000020 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e
000030 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e
000040 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e
000050 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e
000060 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e
000070 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e
000080 7f
(下一 提示 229。答案 5。)
提示 240: 逗号是 C++ 操作符。(下一 提示 47。答案 44。)
提示 241: 线程切换可以发生在任何两行之间,如下两行:
++count; // We've got a new character
*in_ptr = ch;// Store the character
(答案 92。)
提示 242: g++ 编译器报告了警告:
def.cpp: In function 'int main()':
def.cpp:19: warning: label 'defualt' defined but not used
(答案 67。)
提示 243: 在 UNIX 上,名称长度为 15 个字符。在 MS-DOS 上,它只有 12 个字符长。(下一 提示 151。答案 37。)
提示 244: 在编译程序时使用 g++。
g++ -g -Wall -o last last.cpp first.cpp
它可以工作。但当编译为:
g++ -g -Wall -o last first.cpp last.cpp
它失败了。
(下一 提示 150。答案 3。)
提示 245: 如果你编辑器中有语法高亮,看看当你将此代码放入其中时会发生什么。(下一 提示 338。答案 91。)
提示 246: 显然答案是 3。(i++ 是 2,再加一个 ++ 就是 3。)但在编程中没有什么显然的。(下一 提示 37。答案 87。)
提示 247: 空格。(下一 提示 325。答案 23。)
提示 248: 这个程序中有两个变量声明。(下一 提示 83。答案 57。)
提示 249: 6 不能被 4 整除。(答案 103。)
提示 250: 重新定义的新函数应该可以工作,因为显然所有位数组都是相同的大小。(下一 提示 171。答案 56。)
提示 251: 注意你的结尾。(答案 43。)
提示 252: fork 系统调用创建了一个具有重复内存的重复进程,这包括已缓存的 printf 数据。(答案 50。)
提示 253: 普通人通过说“1, 2, 3, 4, 5”来数五个东西。C++ 程序员说,“0, 1, 2, 3, 4”。(下一 提示 238。答案 90。)
提示 254: gcc 警告:
calc2.c: In function 'main':
calc2.c:24: warning: format argument is not a pointer (arg 3)
calc2.c:24: warning: format argument is not a pointer (arg 4)
(答案 95。)
提示 255: 预处理器不理解 C++ 语法。(下一 提示 295。答案 78。)
提示 256: 如果你尝试调试程序,问题通常就会消失。(下一 提示 121。答案 114。)
提示 257: 你的结果可能会有所不同。(提示 181。 答案 28。)
提示 258: 16 位整数可以从 32767 到 -32768。(答案 104。)
提示 259: 结果是 367 (330 + 37)。(答案 29。)
提示 260: strcmp 不返回 true/false。(答案 76。)
提示 261: char prev_ch = '\0'; 在 prev_ch 被创建时执行。(提示 195。 答案 106。)
提示 262: 语句
if (n2 =! 0)
改变 n2。(答案 25。)
提示 263: UNIX 设计师在他们无限的智慧中发布了以下信息:
Floating exception (core dumped)
对于整数除以零。(提示 92。 答案 68。)
提示 264: 一些系统允许你取消对 NULL 的引用;而另一些系统则不允许。(答案 70。)
提示 265: 不在本书中!(提示 72。 答案 36。)
提示 266: 表达式 x << 2 实际上是 4。然而,我们在这个程序中不使用这个表达式。(提示 204。 答案 49。)
提示 267: 语句:
if (amount = 0)
不比较 0 和 amount。(答案 47。)
提示 268: 这个程序的输出如下:
The number of sheep is: 100
The number of sheep is: 1000
The number of sheep is: -6384
(提示 117。 答案 1。)
提示 269: char * != char[] (提示 25。 答案 7。)
提示 270: 输出如下:
Size is 25
not
Size is 20
如程序员所期望的那样。(答案 4。)
提示 271: 答案取决于系统。(提示 32。 答案 77。)
提示 272: 如果射击他或她不可行,应该让他或她明白什么是好的编程风格。(提示 163。 答案 87。)
提示 273: 在一个系统上的输出看起来像:
3 squared is 9
5 squared is 25
7 squared is 49
9 squared is 81
11 squared is 121
(答案 88。)
提示 274: switch 语句没有默认情况;它看起来是这样的。(提示 242。 答案 67。)
提示 275: 第 16 行没有问题。我们问它是为了愚弄你。(提示 40。 答案 79。)
提示 276: 读者执行这两行:
++count; // We've got a new character
*in_ptr = ch;// Store the character
(提示 241。 答案 92。)
提示 277: gcc 警告:
two.c: In function 'main':
two.c:11: warning: too few arguments for format
two.c:9: warning: unused variable `answer'
(答案 85。)
提示 278: 一些编译器有一个可以改变程序行为的开关。这个开关不会解决问题,但程序的行为会不同。(提示 166。 答案 63。)
提示 279: 有多个 i1。(提示 96。 答案 94。)
提示 280: 了解 "and" 和 "and and" 之间的区别。(提示 76。 答案 17。)
提示 281: 程序打印:
Result is 0
(答案 27。)
提示 282: 如果你检查代码,我总是确保在覆盖它之前删除变量 data。(提示 184。 答案 115。)
提示 283:输出是:
A6667
(下一提示 62。答案 45。)
提示 284:这个语句:
-1.0;
是一个合法的 C++ 语句。尽管完全无用,但这个语句是合法的。(下一提示 12。答案 82。)
提示 285:因为没有标题,所以我们没有标准函数的原型;它们是隐式声明的。(下一提示 81。答案 41。)
提示 286:var_array::~var_array被调用两次。(答案 59。)
提示 287:如果你有一个带有语法高亮的编辑器,它将以一种颜色显示基数,以另一种颜色显示高度。(下一提示 215。答案 62。)
提示 288:即使文件存在且权限允许读取文件,打开也会失败。(下一提示 306。答案 60。)
提示 289:这个语句
int &i = 3+4;
是非法的。但不用担心;我们不会使用它——至少不是以这种形式。(答案 22。)
提示 290:输出是:
Width is too small
area(10, 10) = 100
程序员期望的是:
Width is too small
area(10, 50) = 500
(下一提示 67。答案 13。)
提示 291:复制构造函数被调用的次数比你想象的要多。(下一提示 316。答案 109。)
提示 292:记住“1”和“1.0”是不同的。(下一提示 281。答案 27。)
提示 293:两个问题涉及到结构体数据中的字节数。(下一提示 51。答案 71。)
提示 294:strcmp可能会让新手困惑。(下一提示 260。答案 76。)
提示 295:将输出通过预处理器运行。(答案 78。)
提示 296:示例输出:
Stack 0 has 1 elements
Stack 1 has 100 elements
Stack 2 has 134516168 elements
Stack 3 has 134525376 elements
Stack 4 has 4 elements
(下一提示 145。答案 72。)
提示 297:i++的值是多少?++i的值是多少?(答案 102。)
提示 298:它们是不同的。(下一提示 255。答案 78。)
提示 299:数字看起来是八进制的。(答案 53。)
提示 300:在类内部更改常量是不可能的,但这个程序却做到了。不调用构造函数就创建类是不可能的,但这个程序却做到了。(答案 98。)
提示 301:这个程序打印:
parity
-break
xon
-rts
(答案 108。)
提示 302:将 1/3 以十进制形式三次列在一列中。现在将它们加起来。(答案 54。)
提示 303:0x8000(1000 0000 0000(b))是 (1<<15)。这是正确的值,也是程序员期望的。(下一提示 84。答案 19。)
提示 304:缩进不正确。(下一提示 270。答案 4。)
提示 305:自己创建新的函数可以大大加快速度——如果你做对了的话。(下一提示 250。答案 56。)
提示 306: 打开失败,出现 EMFILE 错误。(进程已经打开了最大数量的文件。)(下一 提示 185。答案 60。)
提示 307: 字符串:
"Hello World!/n"
包含 14 个字符。(答案 69。)
提示 308: 程序打印
San Diego 92126
Boston 01110
(下一 提示 136。答案 15。)
提示 309: 程序员认为当读取数据项 #500 时会发生一些奇怪的事情。(答案 81。)
提示 310: in_port_ptr 被读取了多少次?(下一 提示 356。答案 9。)
提示 311: 使用的数据结构是一个不平衡的二叉树。(下一 提示 323。答案 74。)
提示 312: 我不知道你可以进行像 a<b<c 这样的三元比较。(下一 提示 18。答案 80。)
提示 313: 一个字符有 8 位。它们的编号是什么?(答案 11。)
提示 314: 有时当你运行程序时,你会得到错误的答案,有时你会因为段错误而崩溃(Windows 用户会得到一个 UAE - Unexpected Application Error),有时一切都会正常工作。(下一 提示 253。答案 90。)
提示 315: 数组的元素大小是多少?(答案 72。)
sizeof(stack) != sizeof(safe_stack)
提示 316: 样本输出:
Copy Constructor called
= operator called
Copy Constructor called
= operator called
Copy Constructor called
= operator called
...
(下一 提示 199。答案 109。)
提示 317: 这个程序试图在删除数据后立即复制数据。(下一 提示 43。答案 75。)
提示 318: memset 的参数有哪些?(下一 提示 337。答案 20。)
提示 319: 结果是系统相关的。(下一 提示 191。答案 30。)
提示 320: 我们没有在这个程序中包含任何头文件是有原因的。(下一 提示 285。答案 41。)
提示 321: 程序是非标准的。(下一 提示 68。答案 6。)
提示 322: 嘿,这不是我们在程序 58 中做的那个程序吗?不,实际上这是你应用了修正后的程序。注意第 22 行的优雅静态声明。(但仍然存在问题。)(下一 提示 154。答案 64。)
提示 323: 使用的数数据结构是一个 非常 不平衡的二叉树。(答案 74。)
提示 324: 检查预处理器输出。(下一 提示 38。答案 112。)
提示 325: 这个程序需要 "blank","blank",和 "blank"。(答案 23。)
提示 326: g++ 编译器报告了警告:
rect.cpp: In constructor `rectangle::rectangle(int, int)':
rect.cpp:20: warning: member initializers for 'const int rectangle::height'
rect.cpp:18: warning: and 'const int rectangle::area'
rect.cpp:31: warning: will be re-ordered to match declaration order
(答案 93。)
提示 327: 预处理器遵循自己的规则。(下一 提示 201。答案 29。)
提示 328: out_file 是什么?(下一 提示 75。答案 40。)
提示 329: 当 store_it 执行时,有两个 var_array 类的实例。 (下一 提示 355。 答案 59。)
提示 330: 当派生类中没有定义函数时,C++ 会去基类中寻找它。
所以什么阻止 C++ 调用 base::print_it(int)? (答案 58。)
提示 331: 我们确实将乘法的结果存储到结果中 1863 次。所以循环正在执行。 (下一 提示 128。 答案 39。)
提示 332: 16 位字的最左边的位是多少? (下一 提示 144。 答案 2。)
提示 333: 操作符 = 如何返回其结果? (答案 109。)
提示 334: 赋值运算符有问题。 (下一 提示 357。 答案 14。)
提示 335: 你不能。 (下一 提示 240。 答案 44。)
提示 336: 有事物和指向事物的指针。 (下一 提示 196。 答案 61。)
提示 337: sizeof(array) 不是一个字符,'\0' 不是一个整数。C++ 并不足够聪明来注意到这一点。 (答案 20。)
提示 338: 注释以 /* 开始并以 */ 结束。 (下一 提示 106。 答案 91。)
提示 339: 这是第二次我们抛出有问题的异常。 (下一 提示 345。 答案 55。)
提示 340: 一个名字比你想象的要复杂。 (下一 提示 71。 答案 33。)
提示 341: 如果你进行了频率分析,你会发现在输出中缺少数字 8 和 9。 (下一 提示 299。 答案 53。)
提示 342: 12 * 34 = 408。它总是 408。每个人都知道这一点,包括编译器。 (答案 16。)
提示 343: 对齐。 (下一 提示 230。 答案 103。)
提示 344: 为什么计算机执行测试 1>c? (答案 80。)
提示 345: 第二个异常是从堆栈析构函数抛出的。 (答案 55。)
提示 346: 问题出在第 5 行。 (答案 79。)
提示 347: g++ 编译器发出警告:
array2.cpp: In function 'int main()':
array2.cpp:17: warning: left-hand operand of comma expression has no effect
(下一 提示 17。 答案 86。)
提示 348: 矩阵 [2] 是一个指针。 (答案 86。)
提示 349: MS-DOS 版本插入一个字符。 (下一 提示 239。 答案 5。)
提示 350: 问题取决于编译器。 (下一 提示 256。 答案 114。)
提示 351: 程序打印:
At least one number is zero.
(下一 提示 280。 答案 17。)
提示 352: 程序报告 64 位精度。 (下一 提示 57。 答案 73。)
提示 353: 重新定义的新函数为什么将大小作为参数传递。 (答案 56。)
提示 354:程序打印:
2 is prime
3 is prime
5 is prime
7 is prime
我们原本期待收到一些消息告诉我们 4、6、8 和 9 不是质数。但不知何故,那些消息消失了。
(下一提示 274。答案 67。)
提示 355:拷贝构造函数是如何实现的?(下一提示 236。答案 59。)
提示 356:代码要读取多少次*in_port_ptr 才能正常工作?(至少在表面层面上。)答案 9。]
提示 357:下面被赋予的是什么
save_queue = a_queue
(答案 14。)
提示 358:编译时开关与如何处理 char 到 int 的转换有关。(答案 63。)
提示 359:setjmp 和 longjmp 的作用是什么?(下一提示 109。答案 66。)
提示 360:通过预处理器运行它。(答案 46。)
提示 361:嘿,Steve,你不能让这个程序正确运行吗?(下一提示 265。答案 36。)
^([1])Celerity 1000 是首批 RISC 小型计算机之一。不幸的是,创建它的公司已经不再营业。
第三部分:答案
答案 1: 问题在于一个大型羊群包含 10,000 只羊。那是 40,000 条腿。short int 的最大值是 32,767。这小于 40,000,所以 (10,000*4) 会导致溢出,结果输出错误的数据。
答案 2: 问题在于这个语句:
// The bit we are printing now
short int bit = (1<<16);
并没有将变量位设置为 1000 0000 0000 0000(b),而是设置为 1 0000 0000 0000 0000(b)。不幸的是,它无法容纳 17 位,所以结果是它被设置为零。
因为它是零,位测试语句将始终失败,给出以下结果:
---------------
答案 3: 全局类在 main 之前初始化。编译器不保证顺序。特别是,没有任何东西可以保证在它被使用之前 first_name 已经初始化。所以如果编译器选择了错误的顺序,程序将输出错误的数据或崩溃。
答案 4: 程序员以为他在 if 语句中放了两条语句,但他忘记了花括号。
所以这个语句:
if (size > MAX)
std::cout << "Size is too large\n";
size = MAX;
正确缩进看起来像:
if (size > MAX)
std::cout << "Size is too large\n";
size = MAX;
程序员应该写的是:
if (size > MAX)
{
std::cout << "Size is too large\n";
size = MAX;
}
答案 5: 问题在于文件类型没有被指定为二进制(ios::bin)。Microsoft Windows 运行时库编辑字符输出,并在每个 <line-feed (0xA)> 前插入 <carriage-return (0xD)>。这解释了文件中在 0A 字符之前额外的 0D。
答案 6: 问题在于这一行:
6 void main()
main 函数不是一个 void 函数。它是一个 int。该函数返回一个退出代码给操作系统。一个正确编写的 "Hello World" 看起来像:
1 /************************************************
2 * The "standard" hello world program. *
3 ************************************************/
4 #include <ostream>
5
6 int main()
7 {
8 std::cout << "Hello world!\n";
9 return (0);
10 }
当我的妻子第一次学习编程时,这是她被教的第一段程序(void 版本)。我将 void 改为 int,她交了作业。助教判错了,又改了回去。
不必说,我对这件事感到非常不高兴,并给他写了一封非常傲慢的信,告诉他 main 是一个 int,并引用了 C++ 标准的章节和段落来证明这一点。他回信时非常和善。
答案 7: 问题在于 sub.cpp 将 str 定义为一个字符数组(char [])。main.cpp 中的 extern 声明将 str 定义为一个字符指针(char *)。
现在字符数组和字符指针在 C++ 中的几乎所有地方都是可互换的 几乎。这是它们不可互换的少数情况之一。在这种情况下,程序 main 认为 str 是一个字符指针,所以它去那个位置读取前四个字节,期望得到一个地址。前四个字节是 "Hell",这不是一个地址,所以程序崩溃。
避免 1: 总是在头文件中定义 extern。这个头文件应该总是被定义该项目的模块以及使用它的每个模块包含。
答案 8: 问题在于 ch 可以是一个有符号字符。这意味着如果 ch 在比较时转换为有符号整数,你得到 int(ch)=-1 (0xFFFFFFF)。这并不是 0xFF,比较失败了。
避免 2:当使用字符变量来存储数字时要小心。它们可能不会按你的预期工作。
答案 9:问题在于优化器查看代码,看到我们读取*in_port_ptr 三次然后丢弃结果。优化器随后推断出,通过删除第 20、21 和 22 行,它可以优化程序并产生相同的结果。
解决方案是声明端口指针为 volatile。在程序 107 中我们已经这样做了,但有些地方不太对劲。
答案 10:答案是 printf 格式(%d)与参数类型(double)不匹配。程序员应该这样写:
12 printf("pi is %f\n", M_PI);
答案 11:字符有 8 位,编号为 0 到 7。这些位可以用常数(1 << 0)到(1 << 7)来表示。
没有位号 8,所以表达式
privs |= P_BACKUP; // P_BACKUP = (1 << 8)
因为它设置了一个超出字符边界的位,所以什么也不做。结果是,只有管理权限真正被设置。
答案 12:运算符=函数调用接受一个类型为 data_holder 的单个参数。这种类型的参数是按值传递参数,因此会调用复制构造函数。创建复制构造函数的程序员决定走捷径,并使用运算符=来实现复制。所以运算符=调用复制构造函数,它又调用运算符=,然后调用复制构造函数……以此类推,直到栈空间耗尽。
运算符=函数应该将其参数类型取为常量引用:
data_holder &operator = (
const data_holder &old_data_holder) {
它也应该返回一个指向数据持有者的引用。
避免 3:如果可能,在传递参数时使用 const 引用。这避免了按值传递参数时的额外开销。
答案 13:问题出在 if 语句上。在第一个中:
if (width < MIN) {
std::cout << "Width is too small\n";
width = MIN;
程序员忘记了添加闭合的花括号。这没关系;他通过忘记为下一个 if 语句添加开括号来弥补:
if (height < MIN)
std::cout << "Height is too small\n";
height = MIN;
}
如果我们正确缩进代码,我们就可以看到问题:
if (width < MIN) {
std::cout << "Width is too small\n";
width = MIN;
if (height < MIN)
std::cout << "Height is too small\n";
height = MIN;
}
程序员应该写的是:
if (width < MIN) {
std::cout << "Width is too small\n";
width = MIN;
}
if (height < MIN) {
std::cout << "Height is too small\n";
height = MIN;
}
答案 14:语句:
save_queue = a_queue
将大小为 30 的队列复制到大小为 20 的队列中。换句话说,赋值运算符(如实现的那样)允许我们复制不同大小的队列。我们不应该被允许这样做。
解决这个问题的有四种方法:
-
使用 STL 队列类。
-
将赋值运算符设置为私有(并且不允许任何赋值)。
-
将赋值运算符修改为,如果队列的大小不同则抛出异常。
-
将队列类修改为可以相互赋值不同大小的队列。
答案 15:常数 02126 是八进制,因为最高位是零。所以在 C++中,02126(八进制)等于 1110(十进制),并不是波士顿的邮政编码。
答案 16:问题在于编译器知道 12 * 34 等于多少,所以它不是直接进行乘法运算,而是优化了语句,将其转换为:
18 result = 408;
由于没有进行乘法运算,时间计算有误。程序 109 是尝试解决这个问题。
答案 17:问题在于程序员使用了位与(&)而不是逻辑与(&&)。两个数字的位与给出了我们:
3 0011
& 12 1100
=========
0 0000
因此结果是 0,跳过了 if 子句,执行 else 子句。
一些程序员使用缩写:
if (x)
for
if (x != 0)
(我反对这种缩写。)
这就是我不喜欢捷径的一个例子。编写 if 语句的更好方法是:
if ((i1 != 0) && (i2 != 0))
在发现这个错误不久后,我告诉了一个同事。我解释了发生了什么,并说:“我现在知道'and'和'and and'之间的区别了。”我不确定我更惊讶的是我提出了这个句子,还是他理解了这个句子。
答案 18:问题在于 tmp_name 返回一个指向局部变量 name 的指针。当函数结束时,所有非静态局部变量的存储空间都会被回收。这包括 name 的存储空间。因此,返回的指针指向一个随机、未分配的内存区域。
接下来的函数调用可能会覆盖那个存储空间,使 a_name 看起来非常奇怪。
解决这个问题的方法是将 name 声明为静态。
(参见程序 59 中的类似问题。)
答案 19:问题在于该语句
bit >>= 1;
并没有将位向右移动一位。相反,它执行了一个“有符号”的位移,这会复制符号位。因此
0x8000 >> 1 1000 0000 0000 0000 (b)
不是
0x4000 0100 0000 0000 0000 (b)
如预期的那样,但结果是
0xC000 1100 0000 0000 0000 (b)
由于这个问题,位测试给出了错误的结果。
答案 20:memset 的参数是:
memset(
void *ptr,// Pointer to the data
int value,// Value to set
size_t size// Number of bytes to fill
);
在这种情况下,值是 sizeof(array),要填充的字节数是 0。由于 size=0,没有进行任何操作。
程序员应该写成:
memset(array, '\0', sizeof(array));
答案 21:C++标准规定所有指针都必须指向数组或其上方。你不能指向数组下方。
在这个例子中,我们有一个在英特尔机器上的数组。在英特尔怪异指针术语中,数组的地址是:
5880:0000
data_ptr 变量最初位于:
5880:001E
然后只要它大于 data,它就会递减。在其递减过程中,data_ptr 会移动到
5880:0000
这等于数组 data 的地址,所以它再次递减。(记住在这个内存模型中,只有地址部分被改变。)结果是:
5880:FFFE
现在
data_ptr >= data
被评估。但是 data_ptr 现在比 data 大得多,所以程序继续执行。
结果是程序覆盖了随机数据,这可能导致系统崩溃。但如果不会,data_ptr 将下降到:
5880:0000
wrap,并且过程将再次继续。
答案 22:问题在于函数 max 返回一个参数的引用。该参数是 3+4,这是一个表达式。
当调用 min 时,C++实际上做的事情是:
-
创建一个临时变量(tmp1)并将其赋值为 1+2
-
创建一个临时变量(tmp2)并将其赋值为 3+4
-
调用 max(tmp1, tmp2)
-
这个函数返回对 tmp2 的引用。
i = &tmp2 tmp1 destroyed tmp2 destroyed -
变量 i 现在是一个对无物的引用。
问题是由返回参数的引用引起的。这创建了一个悬空引用。
答案 23:程序员没有在输出文本的该行中添加空格:
13 std::cout << "Twice" << number << "is" <<
14 (number *2) << '\n';
结果,输出看起来像
Twice5is10
他应该写成这样:

答案 24: 这是一个经典的死锁问题:
-
进程 1 需要资源#1 和#2。
-
进程 2 需要资源#2 和#1。
他们以那个顺序获取资源。记住,线程切换可能随时发生。
因此,我们有一个可能导致以下情况发生的竞争条件:
-
进程 1 获取资源#1
-
线程切换到进程 2
-
进程 2 获取资源#2
-
进程 2 试图获取资源#1
-
资源#1 不可用,因此进程将休眠直到它被释放(在它工作期间保持资源#2 锁定)
-
线程切换到进程 1
-
进程 1 试图获取资源#2。由于它被锁定,进程将休眠直到它被释放。(在此期间,资源#1 保持锁定。)
结果是进程 1 在持有资源#1 的同时等待资源#2。它不会放弃资源#1 直到它获取到资源#2。
进程 2 在持有资源#2 的同时等待资源#1。它不会放弃资源#2 直到它获取到资源#1。
避免 4: 定义锁定顺序(例如,你必须按照#1、#2 的顺序获取锁)。在获取多个锁时始终使用此锁定顺序。
备选方案: 在获取多个锁时,使用以下算法:
-
尝试获取所有锁(如果不可用则不阻塞)。
-
如果你已经拥有了所有东西,那么继续做你的工作。
-
如果你没有获取到所有锁,释放那些你没有获取到的锁,稍作休眠,然后再次尝试。
答案 25: 问题在于这个语句:
if (n2 =! 0)
这是一个 if 语句内的赋值语句。如果我们重写代码以避免快捷方式,我们得到两个语句:
n2 = !0;
if (n2)
在这种情况下使用逻辑非(!0)给我们一个结果为 1。所以我们总是将 n2 赋值为 1,然后进行比较和除法。
!=被错误地写成=!,因此产生了意外。
该语句应该读作:
if (n2 != 0)
答案 26: 问题在于:
diff[diff_index++] =
array[i++] - array[i++];
这告诉编译器:
-
增加 i
-
使用它来索引数组(首次出现)
-
增加 i
-
使用它来索引数组(第二次出现)
-
计算差值
问题在于步骤 1-4 可能以不同的顺序发生:
-
增加 i
-
增加 i
-
使用它来索引数组(首次出现)
-
使用它来索引数组(第二次出现)
具有许多副作用语句给 C++编译器提供了出错的空间。
避免 5: 将像++和--这样的副作用操作单独放在一行。
答案 27: 问题在于“1”是一个整数。数字“3”也是一个整数。所以“1/3”是整数除法。
因此,语句:
12 result = 1/3; // Assign result something
对 1 除以 3 进行整数除法。整数除法会截断小数部分,因此结果是 0。整数“0”被转换为浮点数并分配给结果。
程序员应该这样写:
12 result = 1.0 / 3.0;// Assign result something
答案 28: scanf 函数非常难以使用。在这个程序中,语句:
22 scanf("%c %d", &oper, &value);
获取一个字符和一个整数。下一次调用 scanf 时,它将读取另一个字符和整数。那么下一个字符是什么呢?让我们看看示例运行:
% calc
Enter operator and value:+ 5
Total: 5
Enter operator and value:+ 10
Bad operator entered
Total: 5
Enter operator and value:Bad operator entered
Total: 5
Enter operator and value:q
Bad operator entered
Total: 5
Enter operator and value:q
我们输入的第一行是:
+ 5
第一次调用 scanf 后,输入指针位于 5 后的换行符之前。下一次 scanf 尝试读取操作符并获取换行符。它继续读取并看到加号而不是数字。结果是很多困惑。
避免 6:scanf 函数很难正确使用。但我有一个简单的方法来处理这个问题:我从不使用它。相反,我总是使用 fgets 和 sscanf 的组合。
fgets(line, sizeof(line), stdin);
sscanf(line, "%c %d", &operator, &value);
答案 29:预处理器不理解 C++ 语法。当我们定义 TOTAL 为 37 + 33 时,它实际上是 37 + 33,而不是 70。
面积宏定义为:
37 + 33 * 10
运算符优先级占上风,给出了错误的答案。
避免 7:尽可能使用常量而不是定义宏。
避免 8:将括号放在所有定义非简单数字的 #define 语句周围。
示例:
// Total top size
#define TOP_TOTAL (TOP_PART1 + TOP_PART2)
答案 30:问题在于函数返回了一个局部变量的引用。这是件坏事,因为局部变量在返回时被销毁;引用被称为 悬空引用。它指向不再存在的东西。
当我们尝试打印不再存在的字符串时,我们会遇到麻烦。
避免 9:不要返回局部变量的引用。
答案 31:问题在于 else 子句与最近的 if 语句配对。正确缩进的代码如下:
23 if (balance < 0)
24 if (balance < - (100*DOLLAR))
25 cout << "Credit " << -balance << endl;
26 else
27 cout << "Debt " << balance << endl;
这不是程序员想要的结果。他想要做的是:
if (balance < 0) {
if (balance < - (100*DOLLAR))
cout << "Credit " << -balance << endl;
} else
cout << "Debt " << balance << endl;
避免 10:如果在一个 if、for、while 或其他控制语句的控制下有多个语句条件控制,请使用 {} 包围这些语句。
(这是一种说法:不要写这样的代码。)
![]() |
|---|
奖励问题:这解决了大多数问题,但这个程序中仍然有一个错误。是什么?(下一个 提示 112。 答案 38。)
![]() |
|---|
答案 32:问题在于内存是在构造函数中分配的,但从未被释放。
避免 11:在析构函数中删除在构造函数中创建的。
这个规则没有被遵循,所以每次我们创建一个栈,一些堆就会永久消失。
答案 33:程序打印:
First: John
Last: Smith
Hello: John
Smith
问题在于 fgets 会获取包括换行符在内的整行。所以当读取第一个名字时,它读取为 John\n。同样的事情发生在 Smith 上,结果是我们的有趣输出。
答案 34:for 语句的末尾有一个额外的分号:
for (index = 1; index <= 10; ++index);
这意味着 for 循环根本控制不了什么。正确缩进的程序如下:
for (index = 1; index <= 10; ++index);
std::cout << index << " squared " <<
(index * index) << '\n';
或者如果我们添加一些注释,看起来是这样的:
for (index = 1; index <= 10; ++index)
/* Do nothing */;
std::cout << index << " squared " <<
(index * index) << '\n';
从这里我们可以看出,std::cout 行不在 for 循环内部。
答案 35:问题在于我们声明了一个名为 remove 的局部变量。还有一个名为 remove 的标准函数。我们的局部变量隐藏了局部变量作用域内的函数。
这个作用域在第一行 15 的第一个 if 语句结束时结束。
下一个语句:
16 if (remove) {
检查函数 remove 的地址是否非零,如果是,则执行下一个语句。

避免 12:避免隐藏变量。
答案 36:问题在于我们返回的字符串被定义为:
15 // The name we are generating
16 std::string name;
这是一个局部变量。子程序返回对这个字符串的引用。但由于它是一个局部变量,它在函数结束时被销毁。这意味着当我们使用结果时,持有结果的变量已经被销毁了。
答案 37:问题在于反斜杠字符被用作转义字符。所以 \n 是换行符。\new 是
所以字符串 \root\new\table 解码为
"<return>oot<newline>ew<tab>able"
程序员真正想要的是:
const char name[] = "\\root\\new\\table"; // DOS path
具有讽刺意味的是,这个规则不适用于 #include 文件名。
#include "\usr\include\table.h"
它工作正常且正确。
答案 38:问题在于这个语句:
if (balance < 0)
这用于检查客户是否欠公司东西。因此,客户可以看到类似的消息:
You owe 0.
| 注意 | 这实际上发生在一个人的身上。他收到了一张 $0.00 的账单。他打电话给公司,他们道歉了,下个月他又收到了一张 $0.00 的账单。这种情况持续了好几个月。每次他打电话给公司,他们都会道歉并告诉他他们会解决这个问题,但什么也没有发生。 |
|---|
他甚至被收取了 5% 的滞纳金。这使得他的账单达到了 $0.00。
最后,他给他们寄了一张 $0.00 的支票。
那周他收到了银行的一个令人不快的电话。“你为什么写出了这样一张支票?”他们质问道。
看起来支票使他的电脑系统崩溃了。所以支票被退回,下周他收到了一张 $0.00 的账单。
答案 39:问题在于优化器很聪明。它看到我们在计算
factor1 * factor2;
在 for 循环内。如果我们把这个移动到 for 循环外,答案不会改变,但会更快。所以这个程序的优化版本只乘一次:
17 int register1 = factor1 * factor2;
18 // We know that 1863 multiplies
19 // delay the proper amount
20 for (i = 0; i < 1863; ++i)
21 {
22 result = register1;
23 }
为了解决这个问题,我们需要声明我们的因子为易失性。
1 /************************************************
2 * bit_delay -- Delay one bit time for *
3 * serial output. *
4 * *
5 * Note: This function is highly system *
6 * dependent. If you change the *
7 * processor or clock it will go bad. *
8 *************************************************/
9 void bit_delay(void)
10 {
11 int i; // Loop counter
12 volatile int result;// Result of the multiply
13
14 // Factors for multiplication
15 volatile int factor1 = 12;
16 volatile int factor2 = 34;
17
18 // We know that 1863 multiples delay
19 // the proper amount
20 for (i = 0; i < 1863; ++i)
21 {
22 result = factor1 * factor2;
23 }
24 }
正是这类事情使得嵌入式编程变得如此简单。
答案 40:问题在于 ostream 是按“按值传递”传递的。你不能复制流变量。(如果你这样做,意味着系统必须复制文件。)参数应该改为“按引用传递”参数:
void print_msg_one(
// File to write the message to
class ostream &out_file,
// Where to send it
const char msg[]
)
答案 41:问题在于这个语句:
strcat(file_name, '/');
strcat 函数接受两个字符串作为参数。在这个例子中,我们给了它一个字符串和一个字符。因为没有原型,C 语言无法进行参数检查;错误的参数被传递给了 strcat,这使得它非常困惑。
避免 13:所有函数都应该显式声明。永远不要让 C 隐式声明它们。确保包含定义你使用的所有函数原型的头文件。
答案 42:有符号的一位数可以有两个值:0 和-1。
该语句:
printer_status.online = 1;
失败,因为一位宽的字段无法容纳值 1。(所以它溢出并将变量赋值为-1!)结果是下一个语句:
if (printer_status == 1)
失败。
避免 14:单比特字段应该是无符号的。
答案 43:在 MS-DOS 上,你可能会得到类似以下的结果:
The answer is 4C:>#
(# is the cursor)
在 UNIX 上,你可能会得到类似以下的结果:
The answer is 4$ #
问题在于程序员没有在 std::cout 语句的末尾添加换行符。结果是程序运行,输出一条语句,然后退出,光标定位在行尾。命令处理器随后运行并输出其提示符(MS-DOS 为 C:>,UNIX 为$)紧挨着程序的输出。
程序员应该这样写是:
std::cout << "The answer is " << result << '\n';
答案 44:逗号可以用来分隔 C++语句。它的用法如下:
if (x)
std::cout << "X set. Clearing\n", x = 0;
(请不要这样编程,谢谢!)
该语句
one_million = 1,000,000;
等于:
one_million = 1,
000,
000;
或者
one_million = 1;
000;
000;
从这个中,我们可以看到为什么我们得到 1 作为输出。
答案 45:问题是表达式 ch+1 是一个整数(值为 66)。C++检测到这一点并调用 std::cout.operator <<(int)函数,输出一个整数。
程序员应该这样写:
std::cout << static_cast<char>(ch+1);
std::cout << static_cast<char>(ch+2);
答案 46:输出如下:
The double of 1 is 2
The double of 2 is 3
The double of 3 is 4
The double of 4 is 5
The double of 5 is 6
原因是 DOUBLE(i+1)展开为:
(i+1 * 2)
当 C++看到这个表达式时,它会将 1 乘以 2,然后加上 i。这个结果并不是程序员想要的。
避免 15:尽可能使用内联函数而不是宏。
避免 16:尽可能总是将宏的参数用括号括起来。例如:
#define DOUBLE(x) ((x) * 2)
答案 47:该语句:
if (amount = 0)
将 0 赋值给 amount,然后比较结果看它是否不为零。它是零,所以执行 else 子句。
程序员应该这样写这个语句:
if (amount == 0)
| 注意 | 我在教编程时最令人欣慰的经历之一是在课程结束大约两个月后遇到一个学生。“Steve,”他说。“我必须告诉你,在课堂上我觉得你对这个‘=’与‘==’的东西有点过分——直到昨天。你知道,我写了我的第一个真正的程序,猜猜我犯了什么错误?” |
|---|
答案 48:使用以下语句:
i = 3 - i;
| | 注意 | 这个算法最初被发现隐藏在一篇文章中,作为如何不完成工作的例子。作者的“理想”做法是使用以下代码:
switch (i) {
case 1
i = 2;
break;
case 2:
i = 1;
break;
default:
std::cerr << "Error: i is not 1 or 2\n";
exit (99)
}
作者试图说明的是,你应该检查代码中的非法值。细心的读者可能会注意到这段代码中存在语法错误。原文中“理想”解决方案也存在类似问题。换句话说,作者展示的“理想”代码无法工作。|
答案 49:问题是 C++的运算符优先级并不是程序员所想的。+运算符在<<之前,所以
y = x<<2 + 1;
被解析为:
y = x << (2+1);
结果是 1<<4 或 8。
避免 17:使用简单的 C++ 优先级规则:
-
*, / 和 % 的优先级高于 + 和 -。
-
将 () 放在其他所有内容周围。
答案 50:它打印
Hello
Hello
问题在于,当发生 fork 时,printf 缓冲区中有数据。fork 创建了两个进程副本和 printf 缓冲区中的两个数据副本。因此,当缓冲区稍后(在两个进程中)被刷新时,我们都会收到来自它们的 Hello。
答案 51:程序员从未费心初始化 sum。你不能指望一个未初始化的值包含任何东西。所以 sum 可能从 0、5190、123、5 或其他值开始。
程序员应该编写的是:
9 int sum = 0;
答案 52:问题是以下行
flags |= CD_SIGNAL;
此操作无法防止线程切换。在复杂的指令机器上,此操作的汇编代码如下:
; 80x86 assembly
orb $2,flags
线程切换仅在指令边界上发生。因此,在 80x86 机器家族上无法中断此操作。
但在像 Sparc 这样的 RISC 机器上,代码看起来略有不同:
1\. sethi %hi(flags),%o0 ; Get the address of the flags in %o0,%o1
2\. sethi %hi(flags),%o1
3\. ld [%o1+%lo(flags)],%o2 ;%o2 = contents of the variable flags
4\. or %o2,2,%o1 ;%o1 = The results of seeting the flag
5\. st %o1,[%o0+%lo(flags)] ;Store results in %o0
因此,现在 C++ 语句是可中断的。特别是,以下情况可能发生:
-
程序运行并完成指令 3。此时,标志的值在寄存器 %o2 中。
-
发生了线程切换。
-
另一个进程修改了标志。
-
线程切换回原状态。
-
标志的旧值在寄存器 %o2 中。
-
该位被设置,并将结果存储。因为这将包含标志的旧值,所以其他线程中进行的任何更改都会意外地被丢弃。
解决此问题的方法是使用锁来防止在语句执行期间发生任务切换。
答案 53:该语句:
48 printf("%o\t", matrix[row][col]);
以八进制形式打印答案。程序员犯了一个错误,本应使用 %d 而使用了 %o。结果是数字是正确的,只是基数错误。
答案 54:问题是你在浮点数中不能精确表示 1/3。让我们看看当我们以十进制形式添加这些数字时会发生什么。
1/3 = 0.33333
1/3 = 0.33333
1/3 = 0.33333
-------------
0.99999
由于舍入误差,结果不是 1。
记住,当使用浮点数时,数字不是精确的。
答案 55:问题是我们在析构函数中抛出异常。
当程序到达以下行:
if (i3 < 0)
throw (problem("Bad data"));
异常代码接管。它销毁所有局部变量。这包括变量 a_stack。
当 a_stack 被销毁时,会调用析构函数:
~stack(void) {
if (count != 0) {
throw (problem("Stack not empty"));
}
}
析构函数抛出异常。C++ 不喜欢你在异常中抛出异常。当发生这种情况时,程序会调用 terminate() 函数。
如果你想捕获第二个异常和其他类似的异常问题,请使用标准函数 set_terminate 来设置一个处理意外问题的函数。
避免 18:不要在析构函数中抛出异常。
答案 56:问题是重新定义的新函数实现不正确。程序员假设当一个人执行
new fast_bit_array
分配的对象大小是 sizeof(fast_bit_array)。当 fast_bit_array 用作基类时,这并不正确。在这种情况下,分配的内存大小是派生类 safe_bit_array 的大小,它比 fast_bit_array 大,从而导致内存混淆。
避免 19:除非你确定自己在做什么,否则不要定义自己的 operator new 函数。如果你确定你知道自己在做什么,确保你真的真的确定。即使如此,除非绝对必要,否则也不要这样做。
答案 57:问题是存在两个变量声明:
文件:main.cpp
int value = 20;
文件:check.cpp
int value = 30;
这意味着值被设置为 20 或 30。但哪一个?结果是编译器依赖的。如果你想使值在它们声明的文件中局部化,你需要将它们声明为 static:
文件:main.cpp
static int value = 20;
文件:check.cpp
static int value = 30;
或者更好的是,给它们两个不同的名字。
答案 58:根据 C++标准,一旦你定义了一个与基类成员函数同名派生类成员函数,所有同名的成员函数都将被隐藏:
所以 der::print_it(float)隐藏了 base::print_it(float)和 base::print_it(int)。
当我们调用 print_it(2)时,C++会寻找它可以使用的 print_it 版本。唯一可见的 print_it 是 der::print_it(float)。C++更愿意有一个以int作为参数的函数,但它知道如何将int转换为float,所以它将 2 提升为 2.0 并使用 der::print_it(float)。
答案 59:问题是我们没有定义拷贝构造函数。当这种情况发生时,C++会为你定义一个,但通常做得不好。
拷贝构造函数定义为:
var_array(const var_array &other) {
data = other.data;
size = other.size;
}
拷贝构造函数被调用以创建一个用于 store_it 函数的 an_array 的副本。数据指针被复制。
当在 pushy 的末尾调用 var_array::^~var_array 时,它将数据返回到堆中。
当在 main 函数的末尾调用 var_array::^~var_array 时,它将相同的数据返回到堆中。因为我们删除了相同的内存两次,结果是堆损坏。
避免 20:以某种方式始终声明一个拷贝构造函数。主要有三种方式:
-
隐式声明它。
-
如果你永远不希望任何人能够调用它,将其声明为 private:
private: var_array (const var_array &); // No one can copy var_arrays -
如果默认设置有效,使用注释:
// Copy Constructor defaults
在你的程序中。这样,你告诉阅读你代码的人你已经考虑过这个问题,并且知道 C++的默认设置不会成问题。
答案 60:程序员有一个非常不好的习惯,那就是在打开文件后不关闭它们。很快,打开的文件数达到最大,系统不再允许他打开更多的文件。
需要在代码的关键点添加关闭语句:
int fd = open(cur_ent->d_name, O_RDONLY);
if (fd < 0)
continue; // Can't get the file so try again
int magic; // The file's magic number
int read_size = read(fd, &magic, sizeof(magic));
if (read_size != sizeof(magic)) {
close(fd); // <---- added
continue;
}
if (magic == MAGIC) {
close(fd); // <---- added
return (cur_ent->d_name);
}
close(fd); // <---- added
此外,程序员使用 opendir 打开一个目录。他从未关闭它。所以需要一个 closedir。
void scan_dir(
const char dir_name[] // Directory name to use
)
{
DIR *dir_info = opendir(dir_name);
if (dir_info == NULL)
return;
chdir(dir_name);
while (1) {
char *name = next_file(dir_info);
if (name == NULL)
break;
std::cout << "Found: " << name << '\n';
}
closedir(dir_info); // <---- added
}
**答案 61:问题是这个语句:
5 const char *volatile in_port_ptr =
6 (char *)0xFFFFFFE0;
告诉 C++,指针是易变的。被指向的数据不是易变的。结果是,优化器仍然将我们优化掉。解决方案是将 volatile 放在它修改的数据处。我们还添加了一个 const 到声明中,以确保指针不能被修改。结果声明是:
4 // Input register
5 volatile char *const in_port_ptr =
6 (char *)0xFFFFFFE0;
7
8 // Output register
10 volatile char *const out_port_ptr =
11 (char *)0xFFFFFFE1;
这告诉 C++:
-
in_port_ptr 是一个 const 指针,不能被修改。
-
*in_port_ptr 是一个易变的 char,其值可以在正常的 C++编程规则之外被改变。
答案 62: 问题在于注释:
10 base = 5; /* Set the base of the triangle
不包含一个关闭注释。因此,它继续吞噬下面的语句:
10 base = 5; /* Set the base of the triangle
11 height = 2; /* Initialize the height */
从这里很容易看出为什么没有设置高度。
答案 63: 问题在于 getchar 返回一个 int。我们将其赋值给一个字符。一些系统将字符视为无符号字符。结果是,当我们得到 EOF(-1)时,系统将其分配
ch = (unsigned char)(-1)
或者 ch = 0xFF。然后它将 0xFF 与-1(它们不相同)进行比较,并且不会退出循环。
这个程序也是一个风格上的灾难。每个 C++程序员的目标应该是编写一个清晰的程序。这个程序是为了紧凑而编写的。一个更好的程序是:
1 /************************************************
2 * copy -- Copy stdin to stdout. *
3 ************************************************/
4 #include <stdio.h>
5
6 int main()
7 {
8
9 while (1) {
10 {
11 int ch; // Character to copy
12
13 ch = getchar();
14
15 if (ch == EOF)
16 break;
17
18 putchar(ch);
19 }
20 return (0);
21 }
答案 64: 输出是:
Name (a): /var/tmp/tmp.2
Name (b): /var/tmp/tmp.2
原因是,尽管我们有两个指针,但它们都指向一个变量名。当 tmp_name 第一次被调用时:
a_name --> name = "/var/tmp/tmp.1"
在第二次调用之后:
b_name --> name = "/var/tmp/tmp.2"
但 a_name 也指向 name,所以:
a_name --> name = "/var/tmp/tmp.2"
b_name --> name = "/var/tmp/tmp.2"
第二次调用覆盖了用于存储第一次调用结果的存储空间。
解决这个问题的方法之一是在每次调用后复制字符串,或者让调用者提供自己的字符数组来存储名称。
另一个解决方案是使用处理自己的内存分配的 C++风格字符串。
答案 65: 每个 put 后面都跟着一个 flush。这意味着每个字符输出都会进行系统调用。系统调用很昂贵,并且占用大量的 CPU 时间。
换句话说,尽管 I/O 库是为缓冲 I/O 设计的,但过多的 flush 调用会导致它逐个字符进行无缓冲 I/O。
我们需要在每个块的末尾刷新,以确保远程系统接收到一个完整的块。那是块,不是字符,因此我们可以通过将 flush 移动到块发送之后来加快系统速度:
for (i = 0; i < BLOCK_SIZE; ++i) {
int ch;
ch = in_file.get();
serial_out.put(ch);
}
serial_out.fflush();
答案 66: setjmp 在代码中标记了一个位置。longjmp 调用跳转到它。它直接跳转到它,不经过 go,不收集$200。它还跳过了堆栈上所有变量的析构函数。在这种情况下,因为 std::string 的析构函数返回字符串分配的内存,所以我们有一个内存泄漏。
那是因为 setjmp 和 longjmp 函数是 C 函数,不应该在 C++中使用。
避免 21: 不要在 C++程序中使用 setjmp 和 longjmp。使用异常代替。
答案 67: 在默认情况下:
defualt:
std::cout << i << " is not prime\n";
break;
"default"关键字拼写错误。结果是,C++编译器认为"defualt"是一个 goto 标签。
答案 68: printf 函数缓冲其输出。它实际上不会写入任何内容,直到缓冲区满或发送换行符。
因此,程序遇到 printf,"开始" 消息进入缓冲区而不是屏幕,并且 average 函数执行并得到除以零错误。
结果是,“开始”消息丢失,让我们以为平均函数从未被调用过。
解决这个问题的方法是显式地在开始消息后刷新缓冲区:
printf("Starting....");
fflush(stdout);
| | 警告 | 缓冲区刷新的规则取决于正在写入的文件类型。规则如下:
-
如果 stdout 或 stderr 正在写入屏幕,则输出将被缓冲,直到:
-
当写入一行时。
-
当 stdin 被读取时。
-
当缓冲区满时。
-
-
如果 stdout 或 stderr 正在写入磁盘,则输出将被缓冲,直到:
- 当缓冲区满时。
|
(这些可能是你系统上的规则。实际的规则是系统相关的。)
答案 69: 问题在于程序员编写了:
std::cout << "Hello World!/n";
而不是:
std::cout << "Hello World!\n";
因此,输出实际上是:
Hello World/n
答案 70: 问题在于以下语句:
54 while (
55 (std::strcmp(cur_cmd->cmd, cmd) != 0) &&
56 cur_cmd != NULL)
该语句检查 cur_cmd->cmd 指向的数据,然后检查 cur_cmd->cmd 是否有效。在某些系统上,解引用 NULL(如果我们处于列表的末尾,我们会这样做)会导致核心转储。
在 MS-DOS 和其他脑损伤系统上,没有内存保护,因此允许解引用 NULL,尽管你会得到奇怪的结果。微软 Windows 修复了这个问题,解引用 NULL 指针将导致一般保护故障 (GPF)。
循环应该这样编写:
while (
(cur_cmd != NULL) &&
(std::strcmp(cur_cmd->cmd, cmd) != 0))
但即使这样也很棘手。该语句取决于 C++ 标准是否正确实现。该 C++ 标准指出,对于 &&,第一部分将被评估。如果第一个项为假,则跳过第二个项。为了安全起见,最好写成这样:
while (1) {
if (cur_cmd == NULL)
break;
if (std::strcmp(cur_cmd->cmd, cmd) == 0)
break;
答案 71:
- 对齐
一些机器要求长整数值对齐在 2 字节或 4 字节边界上。一些则不需要。C++ 将在结构中插入填充以使对齐。
因此,在一台机器上,结构将是:
struct data {
char flag; // 1 byte
long int value; // 4 bytes
};
总共 5 个字节。而在另一台机器上可能如下:
struct data {
char flag; // 1 byte
char pad[3]; // 3 bytes (automatic padding)
long int value; // 4 bytes
};
总共 8 个字节。
- 字节顺序
一些机器使用 ABCD 的字节顺序写入长整数。而另一些使用 DCBA。这阻止了可移植性。
- 整数大小
64 位机器正在到来。这意味着在某些系统上,长整型是 64 位,而不是 32 位。
答案 72: 我们有一个名为 safe stack 的派生类数组。在 C++ 中,你可以使用基类指针(stack*)来指向派生类(safe_stack)。系统将只看到对象的基部分,但你仍然可以指向它。

现在指针可以指向一个类的单个实例或对象的数组。

因此,我们有以下两个规则:
-
基指针可以指向派生对象。
-
对象指针可以指向对象的数组。
从这个结论中,我们可以得出:
- 基指针可以指向派生对象的数组。
这是错误的。
问题在于派生对象的数组与基类对象的数组并不相同。

因此,如果我们用一个基指针指向派生数组,内存布局将会错误。

避免 22:使用 STL 向量模板而不是数组。它可以避免很多问题。
避免 23:不要将基类数组作为参数传递。
答案 73:问题在于编译器如何为程序生成机器代码。
语句:
if (number1 + number2 == number1)
生成如下:
movefp_0, number1
add fp_0, number2
movefp_1, number1
fcmpfp_0, fp_1
jump_zero out_of_the_while
在这个例子中,fp_0 和 fp_1 是浮点寄存器。在浮点协处理器中,寄存器具有可用的最大精度。因此,在这种情况下,虽然数字可能只有 32 位,但浮点处理器以 80 位进行操作,从而报告出高精度。
这种问题在大多数具有浮点处理器的机器上都会发生。另一方面,如果你有一个使用软件进行浮点运算的旧机器,你可能会得到正确答案。这是因为,通常,软件浮点运算只使用足够的位来完成工作。
为了修复程序,我们需要将主循环转换为:
while (1)
{
// Volatile keeps the optimizer from
// putting the result in a register
volatile float result;
result = number1 + number2;
if (result == number1)
break;
答案 74:问题在于单词按字母顺序存储在输入文件中,而树是不平衡的。因此,当插入单词时,会构建以下数据结构:

结果是我们得到一个链表,而不是树。单词被添加到链表的末尾(代价高昂),并且通过线性搜索进行查找(同样代价高昂)。
平衡二叉树可以解决这个问题。
答案 75:问题在于我们的代码中有以下语句:
an_array = an_array;
这被伪装成:
82 to_array = from_array;
操作符=函数删除目标数组的数据。这很好,除非源数组是同一栈,因此其数据也会被销毁。
解决方法是显式检查操作符=函数中的自赋值:
array & operator = (const arrary &old_array) {
if (this == &old_array)
return;
避免 24:操作符=函数应检查自赋值。
答案 76:问题在于 strcmp 在字符串相等时返回 0,否则返回非零值。这意味着如果你有如下语句:
if (strcmp(x,y))
只有当字符串不相等时,if 语句才会执行。
避免 25:使用
if (strmp(x,y) != 0)
来测试两个字符串是否相等。这比 if (strcmp(x,y))更清晰,并且它有效。
避免 26:尽可能使用 C++字符串类而不是旧的 C 风格字符串。这样,你可以使用关系运算符(<,>, ==等)而不是 strcmp。
答案 77:问题在于以下代码:
while (first != NULL) {
delete first;
first = first->next;
}
它删除了数据,然后又使用了它。在删除之后,它们真的应该消失。
避免 27:在删除或释放后,始终将指针设置为 NULL。
当代码带有一些额外的保护时,问题就很明显了:
delete first
first = NULL;
first = first->next;
Also, because of the added protection of setting first to NULL, if we do attempt to use the pointer, we will abort in a well-defined manner (on most systems).
答案 78:变量的类型是:
-
sam 是一个字符指针(char *)。
-
joe 是一个字符(char)。
预处理器处理后的声明结果为:
char * sam, joe;
避免 28:使用 typedef 定义新类型,而不是使用#define。
答案 79:C++没有**运算符。(至少对于整数。)所以(12 ** 2)是一个无效的结构。
问题在于这种糟糕的语法被隐藏在一个直到第 16 行才展开的预处理器宏中。这就是为什么第 16 行有语法错误。
避免 29:尽可能使用 const 代替预处理器宏。以下语句:
const int GROSS = (12 ** 2);
仍然会生成错误消息,但至少行号是正确的。
答案 80:问题是比较的结果是一个整数 1 或 0。所以表达式:
if (a > b > c)
becomes
if ((a > b) > c)
因为 a 大于 b,所以 a > b 的结果是 1,所以我们现在有
if (1 > c)
这是错误的,所以 else 子句被执行。
答案 81:程序员怀疑在读取数据项#500 时发生了奇怪的事情。他希望在读取这个项目之前设置一个断点。
问题在于,如果他将在 get_data 的顶部设置断点,他将不得不执行 500 个调试器继续命令,才能到达他想要的位置。
所以他在这一行设置断点:
seq = seq;
| 注意 | 更高级的调试器允许用户设置跳过计数以跳过前x个断点停止。我们友好的程序员没有这样的工具。 |
|---|
答案 82:程序员使用分号来结束#define 声明。因为预处理器对事物相当字面,分号变成了文本的一部分。结果是 USABLE 被定义为:
8.5; -1.0;;
text_width 的初始化现在变为
double text_width = 8.5; -1.0;;
或者,正确缩进后,
double text_width = 8.5;
-1.0;
;
从这里我们可以看到我们的问题。
避免 30:尽可能使用 const 代替#define。
答案 83:问题是缓冲区是一个局部变量。这意味着它在函数调用结束时消失。不幸的是,printf 不知道这一点,所以它仍然会在之后填充数据。
The
printf("That's all\n");
仍然会尝试使用局部变量。
为了解决这个问题,将缓冲区声明为静态:
static char buffer[BUFSIZ];
答案 84:问题是优化器。优化器知道变量 debugging 为零。它始终为零。
现在我们知道了这一点,让我们看看这个语句:
if (debugging)
这总是错误的,因为调试始终为零。所以这个块永远不会被执行。这意味着我们可以优化代码:
13 if (debugging)
14 {
15 dump_variables();
16 }
into the statement:
// Nothing
现在让我们看看调试使用的次数。它在第 11 行初始化,并在第 13 行使用。第 13 行被优化掉了,所以调试从未被使用。如果一个变量从未被使用,它可以被优化掉。
结果是一个看起来像这样的优化程序:
9 void do_work()
10 {
11 // Declaration optimized out
12
13 // Block optimized out
14 //
15 //
16 // End of block that was removed
17 // Do real work
18 }
现在程序员想使用调试变量来帮助他调试问题。问题是优化后没有调试变量。
问题在于 C++不知道程序员打算使用魔法(调试器)在背后更改变量。如果你打算这样做,你必须告诉编译器。这是通过声明调试变量 volatile 来完成的。
static volatile int debugging = 0;
"volatile"关键字告诉 C++,“某些奇怪的事情,如中断例程、调试器命令或其他事情,可能会在背后更改这个变量。你不能对其值做出任何假设。”
答案 85:printf 语句:
11 printf("The answer is %d\n");
告诉 C 打印一个整数,但未能提供。printf 函数不知道这一点,所以它会从堆栈中取出下一个数字(某个随机数字)并打印它。
程序员应该写的是:
printf("The answer is %d\n", answer);
答案 86:问题是使用了 matrix[1,2]。C++中的逗号运算符仅仅返回第二部分的结果。所以表达式"1,2"告诉 C++扔掉第一部分(1),值是 2。所以 matrix[1,2]实际上是 matrix[2]。这是一个指向整数数组的指针,C++会将其视为指针进行打印。这就是为什么打印出奇怪值的原因。
程序员真正想要的是:
matrix[1][2]
答案 87:前缀++的版本返回增加后的数字。
因此
++++i;
告诉 C++增加 i,返回结果,然后再次增加变量 i。
后缀++(i++)的版本返回变量的副本,然后增加它。
所以
++++i
-
告诉 C++复制 i(称为 tmp_1)。
-
增加 i。
-
在 tmp_1 上完成其余的工作。
-
复制 tmp_1(称为 tmp_2)。
-
增加 tmp_2。
-
将 tmp_1 作为表达式的值返回。
| 注意 | C++不会让你在整数上使用++++。只有通过一些额外的类愚蠢行为,你才能逃脱。 |
|---|
避免 31:单独使用++和--。
答案 88:问题是这个宏:
#define SQR(x) ((x) * (x))
当调用时
SQR(++number)
这展开为
((++number) * (++number))
这两次增加了数字,而不是程序员想要的增加一次。更糟糕的是,编译器可以就各种操作的顺序做出一些决定;因此,这个表达式的结果是编译器依赖的。
避免 32:使用内联函数而不是参数化宏。
避免 33:将++和-放在单独的行上。
答案 89:优化器知道尽管子程序计算了 result 的值,但它没有对它做任何事情。所以程序是否计算 result 都会工作相同。因此,优化器查看循环:
20 for (i = 0; i < 1863; ++i)
21 {
22 result = factor1 * factor2;
23 }
被优化:
20 for (i = 0; i < 1863; ++i)
21 {
22 /* Do nothing */;
23 }
当然,我们不需要做 1,863 次无意义的事情,所以这被优化为:
20 /* No loop needed */
21 {
22 /* Do nothing */;
23 }
这是最优化的程度。要防止优化器这样做,我们需要声明变量 result 是 volatile。程序 110 显示了添加此修复后会发生什么。
答案 90:C++使用零基索引。所以对于数组[5],有效的元素是:
array[0], array[1], array[2], array[3], array[4]
然而,程序员使用的是元素 1-5。没有数组 [5],所以程序修改了随机内存,导致内存损坏。
这就是为什么大多数 C++ 程序不使用类似以下语句:
for (i = 1; i <= 5; ++i) {
他们使用以下方式计数:
for (i = 0; i < 5; ++i) {
答案 91:问题是这个语句:
result=result/*divisor; /* Do divide */;
第一个 /*(位于语句中间的)开始了一个注释;它不会进行除法。因此,这个语句是:
result = result /* a very big comment */;
避免 34:在运算符周围留空格。这不仅避免了问题,还使程序更容易阅读。
result=result / *divisor; /* Do divide */;
答案 92:问题是线程切换可以在任何时候发生。
当计数 > 0 时,写者将从缓冲区中删除一个字符。读者执行以下两个步骤:
++count; // We've got a new character
*in_ptr = ch;// Store the character
但线程切换可能发生在这两个步骤之间。
因此,以下情况可能发生:
reader:++count;// We've got a new character
thread switch to writer
writer: check count > 0 -- it is
writer: Get the character
thread switch to reader
reader: Put the character in the buffer AFTER writer has already read it.
一种解决方案是改变步骤的顺序
++count; // We've got a new character
*in_ptr = ch;// Store the character
到
*in_ptr = ch;// Store the character
++count; // We've got a new character
根据保护共享数据的指令序列,很难且复杂。
更好、更简单的方法是在执行一组无法中断的语句时告诉任务管理器。在 pthreads 中,这是通过互斥锁来实现的:
pthread_mutex_lock(&buffer_mutex);
++count;
*in_ptr = ch;
++in_ptr;
pthread_mutex_unlock(&buffer_mutex);
答案 93:成员变量按声明顺序初始化。
在这种情况下,以下语句:
) : width(i_width),
height(i_height),
area(width*height)
在声明顺序中执行:1) 区域,2) 宽度,3) 高度。这意味着区域使用未定义的宽度和高度值进行初始化,然后宽度和高宽度进行初始化。
避免 35:编写构造函数,使变量按声明的顺序初始化。(如果你不这样做,编译器会为你做,并导致混淆。)
避免 36:永远不要使用成员变量来初始化其他成员变量。
答案 94:在 K&R 风格的函数中,参数声明紧接在第一个花括号之前。
这意味着以下声明:
int sum(i1, i2, i3)
{
声明三个默认(int)类型的参数。之后声明的任何内容都被视为局部变量。
特别是
int sum(i1, i2, i3)
{
int i1; /* Local variable, not parameter */
int i2; /* Local variable, not parameter */
int i3; /* Local variable, not parameter */
结果是,程序不是对三个参数求和,而是对三个未初始化的局部变量求和。难怪我们得到了一个奇怪的结果。
答案 95:问题是这个语句:
24 sscanf(line, "%c %d", oper, value);
sscanf 函数接受指针作为其参数。(记住 C 语言不会检查参数的正确类型。)在这种情况下,我们给了 sscanf 一个字符和一个整数。我们应该给它一个字符指针和一个整数指针:
24 sscanf(line, "%c %d", &oper, &value);
答案 96:程序使用原始 I/O 来完成其工作(使用 read 和 write 系统调用)。这个程序对每个字符进行一次原始读取和一次原始写入。操作调用很昂贵,而这个程序每复制一个字节就使用 2 次(一次读取和一次写入)。
为了加快程序速度,减少操作系统调用。这可以通过两种方式实现:
-
通过将输入和输出 fstreams 而不是文件描述符来使用缓冲 I/O 系统。
-
一次读取和写入多个字符。
答案 97:问题在于这个陈述:
for (index = 0; string[index] != '\0'; ++index)
/* do nothing */
return (index);
在 /* do nothing */ 语句之后没有分号。
返回是 for 语句的一部分。代码在正确缩进后应该看起来像这样:
for (index = 0; string[index] != '\0'; ++index)
/* do nothing */
return (index);
从这段代码中我们可以看到,第一次循环时,for 循环的索引将是零,并执行返回操作。这就是为什么所有字符串的长度都是零的原因。
程序员想要的是:
for (index = 0; string[index] != '\0'; ++index)
/* do nothing */;
return (index);
答案 98: 问题在于类不是由 C++ new 操作符分配的,而是使用旧的 C malloc 操作符。这为类创建了空间,但没有调用构造函数。
然后为了更添乱,memset 被调用以将类清零。
result =
(struct info *)malloc(sizeof(struct info));
memset(result, '\0', sizeof(result));
程序员应该写的是:
result = new info;
| 注意 | 作者首先在一个他试图调试的大型库中发现了这个问题。由于库的大小很大,混乱很复杂,他花了一个星期才找到 malloc 的位置。 |
|---|
答案 99: 语句:
out_file << ch;
并不会向输出发送字符。无论其名称如何,ch 变量都是整型。结果是整数被打印到输出。这就是为什么输出文件充满了整数。
这是 C++ 自动输出参数类型检测会阻碍你的一个情况。旧的 C printf 语句会正确处理:
printf("%c", ch);
但在使用 C++ 时,你必须进行类型转换才能得到这种情况下的正确结果:
out_file << static_cast<char>(ch);
答案 100: 程序输出:
First: second Second: second
问题是 readdir 返回的是指向静态数据的指针。这些数据由 readdir 拥有,并被后续调用覆盖。
所以发生的情况是这样的:我们调用 scan_dir 并将 first_ptr 设置为指向字符串 first。这正是我们想要的,但包含名称的数组是静态的,当我们再次调用 readdir 时,它使用相同的缓冲区来存储第二个名称。因此,现在 first_ptr 指向 second,这就是我们遇到麻烦的原因。

答案 101: 在基类析构函数中,我们调用 clear 函数。
这个函数调用了一个纯虚函数,delete_data。
在析构过程中,派生类首先被删除。当派生类消失时,delete_data 的定义也随之消失。接下来,调用基类析构函数。在这种情况下,我们的列表类间接调用纯虚函数 delete_data。因为没有派生类,运行时系统导致程序崩溃。
避免 37: 不要从抽象类的构造函数或析构函数中调用纯虚函数。
答案 102: 我期望的结果是:
First 1
First 1
First 1
Second 1
Second 2
Second 3
但结果是:
First 0
First 0
First 0
Second 0
Second 1
Second 2
问题在于这个语句:
return (i++);
现在,我知道这个函数将 i 加一并返回。问题是 i++ 是增量之前的 i 的值。所以这个语句实际上做的操作是:
-
保存 i 的值。
-
增加 i。
-
返回保存的值。
所以这些行:
i = 1;
return (i++);
导致返回 1,而不是预期的 2。
避免 38: 将 ++ 和 - 单独放在一行上。
答案 103: 问题在于在某些系统中,长整型必须对齐在四字节边界上。所以让我们看看我们的结构:
struct end_block_struct
{
unsigned long int next_512_pos; // [0123]
unsigned char next_8k_pos1; // [4]
unsigned char next_8k_pos2; // [5]
unsigned long int prev_251_pos; // [6789]
6 不能被 4 整除,所以编译器添加两个填充字节以跳转到 8。所以实际上我们有:
struct end_block_struct
{
unsigned long int next_512_pos; // [0123]
unsigned char next_8k_pos1; // [4]
unsigned char next_8k_pos2; // [5]
unsigned char pad1, pad2; // [67]
unsigned long int prev_251_pos; // [89 10 11]
这不是缩进的样子。
避免 39:在你的代码中放置如下语句
assert(sizeof(end_block_struct) == 16);
来捕获导致此问题的编译器。
另一种避免方法是使结构中的每个成员都是一个字节,并自己组装短整型和长整型。然而,这需要更多的工作。
答案 104:邮政编码 44101 对于 MS-DOS 的 16 位整数来说太大。16 位整数可以存储的最大数是 32,767。结果是数字溢出到符号位,导致问题。
| 注意 | Win32 系统使用 32 位整数,所以这个问题在当前版本的 Microsoft Windows 上不会发生。 |
|---|
答案 105:ABORT 宏展开成两个语句。因此,if 语句的结果是:
if (value < 0)
std::cerr << "Illegal root" << std::endl;exit (8);
或者正确缩进:
if (value < 0)
std::cerr << "Illegal root" << std::endl;
exit (8);
从这个输出中很容易看出我们为什么总是退出。
避免 40:使用内联函数而不是多语句宏。
inline void ABORT(const char msg[]) {
std::cerr << msg << std::endl;
exit(8);
}
避免 41:如果你必须使用多语句宏,请用花括号括起来:
#define ABORT(msg) \
{std::cerr << msg << std::endl;exit(8);}
答案 106:问题是这个语句:
char prev_ch = '\0';
因为 prev_ch 是一个自动变量,所以这个变量在每个循环的开始时创建和初始化。这意味着对于第一个 if,变量 prev_ch 将始终持有 '\0',我们永远不会匹配双字母。
答案 107:这个程序犯了一个大错误,即使用浮点数表示货币。浮点数可能不是精确的。当累加大量浮点数时,可能会出现一些错误。
解决方案是将程序中的货币存储方式从分数美元更改为整数美分。
避免 42:不要使用浮点数表示货币或任何其他需要精确表示的东西。
答案 108:printf 调用打印你给出的任何字符串。如果你对一个字符字符串加 1,你得到的是去掉第一个字符的字符串。
因此:
printf("-xxx") prints -xxx
printf("-xxx" + 1) prints xxx
表达式 ((flags & 0x4) != 0) 根据位是否设置返回 0 或 1。
如果位被设置("-word" + 0),程序员会打印 -word。如果它被清除("-word" + 1),输出是 word。
| 注意 | 如果你在代码中表现得如此聪明,请注释以告诉维护程序员你有多聪明。 |
|---|
答案 109:问题是操作符 = 函数。它定义为:
trouble operator = (const trouble &i_trouble)
{
std::cout << "= operator called\n";
data = i_trouble.data;
return (*this);
}
这个函数的返回值是类麻烦。但是有一个问题。因为这个函数没有返回引用,所以必须创建变量的副本。这意味着必须调用复制构造函数。这调用操作符 = 函数,它执行返回,调用复制构造函数,等等。
解决方案是让操作符 = 函数返回类的引用:
trouble& operator = (const trouble &i_trouble)
答案 110:log_file 的初始化可以调用 new。当然,我们的 new new 使用了 log_file,因此 log_file 可能在使用它构建之前就被使用,这会使整个混乱。
避免 43:除非你真的知道你在做什么,否则不要重新定义全局的 new 和 delete。真的知道你在做什么。即使如此,也不要这样做。
答案 111:问题是全局变量的初始化顺序没有保证。在这种情况下,a_var 假设 std::cout 已经初始化。这可能不是事实。
让我们假设最坏的情况,假设初始化顺序是 a_var,std::cout。在这种情况下,a_var 被创建。构造函数被调用,并向 std::cout 输出一条消息。因为 std::cout 尚未创建,所以事情变得非常混乱,程序崩溃。
答案 112:问题是 MAX 被定义为 literally 的文本 “=10”。这意味着
for (counter =MAX; counter > 0; --counter)
展开为
for (counter ==10; counter > 0; --counter)
这并没有初始化计数器(它只是比较计数器与 10 并抛出结果)。因为计数器没有初始化,所以我们得到一个随机的问候次数。
| | 注意 | GNU 预处理器在宏展开周围保留空格,以便 GNU 版本的展开:
*for (counter = =10 ; counter > 0; --counter)*
很不幸,好的 GNU 技术正在剥夺我们调试奇怪失败程序的机会。|
答案 113:在名称 DOUBLE 后面的空格使这个宏成为一个简单的文本替换宏。因此,
#define DOUBLE (value) ((value) + (value))
将 DOUBLE 替换为:
(value) ((value) + (value))
真的!
这意味着这一行
std::cout << "Twice " << counter << " is " <<
DOUBLE(counter) << '\n';
看起来像:
std::cout << "Twice " << counter << " is " <<
(value) ((value) + (value)) (counter) << '\n';
(增加了缩进。)
解决方案:将 DOUBLE 定义为
#define DOUBLE(value) ((value) + (value))
避免 44:尽可能使用内联函数而不是参数化宏。例如:
inline DOUBLE(const int value) {
return (value + value);
}
答案 114:问题是优化器可以自由地重写代码。一些优化器会将变量保留在寄存器中以提高代码速度。例如,这个程序的优化版本看起来像:
1 /************************************************
2 * sum -- Sum the sine of the numbers from 0 to *
3 * 0X3FFFFFFF. Actually we don't care *
4 * about the answer, all we're trying to *
5 * do is create some sort of compute *
6 * bound job so that the status_monitor *
7 * can be demonstrated. *
8 ************************************************/
9 /* --- After the optimizer --- */
10 /* --- gets through with it --- */
11 static void sum(void)
12 {
13 static double sum = 0; /* Sum so far */
14 register int reg_counter = counter;
15
16 for (reg_counter = 0;
17 reg_counter < 0x3FFFFFF; ++reg_counter)
18 {
19 sum += sin(double(reg_counter));
20 }
21 printf("Total %f\n", sum);
22 counter = reg_counter;
23 exit (0);
24 }
从这个例子中,我们可以看到计数器仅在程序完成后才更新。如果我们试图在其他线程中的任何时间检查它,我们就会崩溃。
解决方案是声明变量为 volatile:
volatile int counter;
然后,编译器将不会对如何进行优化做出任何假设,并将生成保持计数器更新的代码。
答案 115:我试图确保在覆盖变量之前删除变量数据,这样我就不会出现内存泄漏。我甚至在以下代码中删除了它:
34 // Copy constructor
35 v_string(const v_string &old)
36 {
37 if (data != NULL)
38 {
39 delete[] data;
40 data = NULL;
41 }
42 data = strdup(old.data);
43 }
这是拷贝构造函数。它首先检查数据中是否有内容,如果有,则删除它。但数据可能有什么内容呢?我们刚刚创建了类,还没有初始化它。所以我们在删除一个随机指针,结果导致崩溃。正确编写的拷贝构造函数应该是:
34 // Copy constructor
35 v_string(const v_string &old):
36 data(strdup(old.data))
37 {}
侧边栏列表
第四章: 日常问题
Man Bytes Computer
Oualline 的计算机法则
第七章: 无阶级的班级
奥瓦林文档定律
第十章: 几个工作程序
致维护程序员之歌


浙公网安备 33010602011771号