如何不用-C---编程-全-

如何不用 C++ 编程(全)

原文:How Not to Program in C++

译者:飞龙

协议:CC BY-NC-SA 4.0

第一部分:程序

章节列表

第一章: 开端

第二章: 起步错误

第三章: 单字符奇迹

第四章: 日常问题

第五章: C 语言代码,C 语言代码断裂

第六章: 提前断裂

第七章: 毫无品味的班级

第八章: 专家的困惑

第九章: 前往地狱之路

第十章: 几个实用的程序

第十一章: 线程化,嵌入式——令人畏惧的

第二部分:提示

第三部分:答案

第一章:开始

概述

在最初,有 ENIAC Mark I。有一天,一个操作员偶然注意到机器出现了故障,并追踪到一只飞进机器并被继电器接触打死的蛾子。

她取出了蛾子,将其贴在日志簿中,并做了记录:“在系统中发现了一个错误。”因此,这是第一个计算机错误。^([1])

我对计算机错误的了解是在很久以后。我 11 岁时编写了我的第一个程序。这个程序只有一条汇编指令长。程序将 2 + 2 相加。结果是 2。程序只有一条指令长,但它仍然有一个错误。

本章包含了一些“第一次”:第一次我熬夜到凌晨 2 点来定位一个错误(程序 3),第一次我在进行的第一次 C 编程测试中的问题(程序 2),当然,任何编程书中的第一个程序,“Hello World”。

开始侧边栏

在 ATM 出现之前,你必须去银行手动存款。通常你会使用你支票簿背面的预印存款纸条。这些纸条底部印有你的账号,用磁性油墨书写。

如果你用完了纸条,银行会为你提供一张。这张纸条底部没有写上号码,所以当它被银行的自动机器处理时,机器就会将其踢出,然后由职员手动输入账号。

一个骗子打印了他自己的“通用”存款纸条版本。它看起来像正常的“通用”存款纸条,除了骗子的账号底部印有磁性油墨。

然后,他去了银行,将这些纸条滑入存放“通用”纸条的箱子中。

这个骗局是这样进行的:一位客户进入银行存款,并得到了一张经过处理的纸条。他填写了它并进行了存款。由于纸条包含一个账号,计算机自动处理了它,并将存款存入底部写明的账户。手写的账号被忽略了。换句话说,我们的骗子正在劫持存款。

被分配到这个案件的侦探感到困惑。存款正在消失,没有人知道原因。他将问题缩小到银行内的存款。他决定尝试进行大量存款,看看会发生什么。由于他使用的是自己的钱,存款必须非常小。非常非常小。实际上,每笔存款是 6 分。

侦探花了一周时间存款。他会去银行,填写一张纸条,排队,存入 6 分的存款,填写一张新的纸条,排队,存入 6 分的存款,以此类推。职员们认为他疯了。有一天,他的一笔存款消失了。所以他让银行查阅其记录,看看那天是否有人存入了 6 分的存款。有人存了,骗子被抓住了。

结束侧边栏

^([1])尽管人们认为这是第一次将单词 bugcomputing 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。)

开始侧边栏

一家教堂刚刚购买了它的第一台电脑,工作人员正在学习如何使用它。教堂秘书决定设置一个用于葬礼服务的标准信函。在需要填写逝者姓名的地方,她输入了单词""。当发生葬礼时,她会将这个词改为逝者的真实姓名。

有一天,有两个葬礼,首先是玛丽女士的,然后是埃德娜女士的。所以秘书使用全局替换将""改为"Mary"。到目前为止一切顺利。接下来,她通过将"Mary"改为"Edna"来生成第二个葬礼的服务内容。这是一个错误。

当牧师开始阅读包含使徒信经的部分时,他惊讶地看到,“由处女 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。)

Start Sidebar

一个气象服务电脑要求气象学家以英寸为单位输入降雨量。现在这些人习惯于处理百分之一英寸,所以当你问他们今天下了多少雨时,他们会说“50”,意思是 50/100 英寸,也就是半英寸。

但是,要输入这个数字到电脑里,你必须输入“0.50”。有一个人忘了这一点,把当天的降雨量输入为“50”。现在 50 英寸的雨量是很多。非常多的雨。然而,电脑捕捉到了这个错误,并发出了适当的消息:

    Build an ark. Gather the animals two by two. . .

End Sidebar

程序 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。)

Start Sidebar

我曾为一家主要的软件制造商工作,负责我们文字处理软件的国际版本。启动屏幕包含了以 mm/dd/yy 格式的发布日期,例如 09/20/83。但欧洲使用 dd/mm/yy 作为其标准。需要指导,我询问老板应该使用哪种格式。他考虑了一下,花了一个月的时间和他的经理们讨论这个问题。在我发布软件一周后,他才给我回复。在此期间,我通过将发布日期定在 11 月 11 日来解决这个问题。没错:11/11/83。

End Sidebar

程序 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。)

Start Sidebar

最小惊讶定律:

程序应该以最不令用户惊讶的方式运行。

End Sidebar

程序 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。)

Start Sidebar

来自 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.

End Sidebar

程序 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。)

Start Sidebar

我曾工作的一家公司有一个通信线路,每天下午 5:00 准时失效。每天早上大约 7:00 它会自动启动。对硬件进行了广泛的检查,但都没有发现问题。最后,指派了一名工程师在下班后留下来监控通信线路。那天晚上问题消失了。

第二个晚上,通信系统如往常一样中断。第二天晚上,工程师加班并解决了问题。经过几次这样的循环后,确定除非有工程师在监控,否则通信线路将在下午 5:00 崩溃。

最后一个晚上,一名工程师决定在离开前对通信调制解调器进行最后的检查。它当时是工作的。他关掉了灯,碰巧回头看了调制解调器一眼。它已经死了。打开灯,它又恢复了。开关灯,他发现调制解调器插在了一个开关电源插座上。

神秘事件解决。当天员工下班时,他们关掉了灯,导致调制解调器失效。第二天他们上班时,又打开了灯。工程师在熬夜排查问题时找不到问题,因为他忘记关灯以便观察设备。

调制解调器插在普通电源插座上,所有的通信问题都消失了。

End Sidebar

程序 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。)

Start Sidebar

UNIX mt 命令的原始版本在无法理解命令时会出现一个不寻常的错误信息:

    mt -f /dev/rst8 funny
    mt: Can't grok "funny"

对于那些不熟悉罗伯特·海因莱因的《陌生人的土地》的人来说,grok 是火星语中“理解”的意思。

这个术语在其他国家并没有很好地传播。一位德国程序员试图在他的英语/德语词典中找到“grok”这个词,结果变得疯狂。

End Sidebar

程序 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 分钟内得到了解决,班级决定是时候出去吃披萨和喝啤酒了。

End Sidebar

程序 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。)

开始侧边栏

奥瓦林计算机定律

  1. 在计算机科学中,没有什么比牢固掌握明显的东西更重要。

  2. 关于计算机,没有什么明显的东西。

结束侧边栏

第五章: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。)

Start Sidebar

加州理工学院有人编写了一个程序,在你登录时给你一个友好的问候。这是一个非常聪明的程序;其中一部分逻辑检查作者的账户,看是否有新版本的程序发布。如果有,程序会用自己的后续版本替换自己。

有一天,作者毕业了,他的账户被删除了。程序检测到这个错误状态,并迅速发出了一条消息:

   ?LGNPFB Program fall down and go boom.

End Sidebar

程序 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.)

Start Sidebar

一家公司遇到了问题。一些客户从其软件中删除了公司名称和版权信息。程序员们被要求想出一个防止这种情况的方法。因此,他们加入了一些代码来校验版权信息,如果结果不正确,就会显示一个错误信息:

    Fatal error:
             Water buffalos need immediate feeding
    Call 1-800-555-1234 for technical support.

这个想法是,这个错误信息会如此奇怪,以至于违法者会打电话给技术支持来弄清楚它的意思。(它的真正意思是,“我非法修改了你的程序。”)

End Sidebar

程序 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。)

Start Sidebar

在你的银行支票底部有一系列数字,表示你的银行和账户号码。骗子在纽约开设了一个账户,存入 5 美元。然后他创建了属于自己的支票。这些支票与他真实的支票相同,只是银行号码被修改了,使其指向洛杉矶的一家银行。

接着,他在纽约又开设了一个账户,并使用 10,000 美元的支票作为初始存款。支票进入了自动分拣设备,计算机看到了洛杉矶的银行号码,于是将支票发送到了洛杉矶。洛杉矶的银行看到这笔支票不是给他们的,于是将支票退回到了纽约的清算所。它又回到了自动分拣设备,计算机看到了洛杉矶的银行号码,于是又将其退回到了洛杉矶。

检查现在处于一个无休止的循环中,在纽约和洛杉矶之间来回穿梭。当它一圈又一圈地转时,骗子去了他最初存款的银行,先存入支票,然后要求取出所有钱。出纳员查看了最近的存款记录,发现是两周前,于是假设支票已经清算。毕竟,纽约的支票只需几天就能到达正确的银行。所以出纳员给了骗子 10,000 美元,然后他消失了。

几周后,支票已经磨损得无法再投入自动分拣设备。因此,它被人工分拣,最终被送到了正确的银行。

(这被称为“大规模利用浮动”)

End Sidebar

程序 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.)

Start Sidebar

"C 编程语言——一种结合了汇编语言灵活性和汇编语言强大功能的语言。"

End Sidebar

程序 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。)

Start Sidebar

在许多小数位数之后,没有人会在乎。

End Sidebar

程序 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。)

Start Sidebar

一个足够高的技术水平与魔法无法区分。

— 亚瑟·C·克拉克

End Sidebar

程序 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.)

Start Sidebar

来自我第一个程序中的一个真实评论。

          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------------------------------------------------------------

End Sidebar

程序 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。)

Start Sidebar

问题的主要原因是解决方案。

End Sidebar

程序 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。)

提示 43an_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。)

提示 286var_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。)

提示 294strcmp可能会让新手困惑。(下一提示 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。)

提示 297i++的值是多少?++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 的队列中。换句话说,赋值运算符(如实现的那样)允许我们复制不同大小的队列。我们不应该被允许这样做。

解决这个问题的有四种方法:

  1. 使用 STL 队列类。

  2. 将赋值运算符设置为私有(并且不允许任何赋值)。

  3. 将赋值运算符修改为,如果队列的大小不同则抛出异常。

  4. 将队列类修改为可以相互赋值不同大小的队列。

答案 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++实际上做的事情是:

  1. 创建一个临时变量(tmp1)并将其赋值为 1+2

  2. 创建一个临时变量(tmp2)并将其赋值为 3+4

  3. 调用 max(tmp1, tmp2)

  4. 这个函数返回对 tmp2 的引用。

    i = &tmp2
    tmp1 destroyed
    tmp2 destroyed
    
    
  5. 变量 i 现在是一个对无物的引用。

问题是由返回参数的引用引起的。这创建了一个悬空引用。

答案 23:程序员没有在输出文本的该行中添加空格:

        13    std::cout << "Twice" << number << "is" <<
        14        (number *2) << '\n';

结果,输出看起来像

        Twice5is10

他应该写成这样:

答案 24: 这是一个经典的死锁问题:

  • 进程 1 需要资源#1 和#2。

  • 进程 2 需要资源#2 和#1。

他们以那个顺序获取资源。记住,线程切换可能随时发生。

因此,我们有一个可能导致以下情况发生的竞争条件:

  1. 进程 1 获取资源#1

  2. 线程切换到进程 2

  3. 进程 2 获取资源#2

  4. 进程 2 试图获取资源#1

  5. 资源#1 不可用,因此进程将休眠直到它被释放(在它工作期间保持资源#2 锁定)

  6. 线程切换到进程 1

  7. 进程 1 试图获取资源#2。由于它被锁定,进程将休眠直到它被释放。(在此期间,资源#1 保持锁定。)

结果是进程 1 在持有资源#1 的同时等待资源#2。它不会放弃资源#1 直到它获取到资源#2。

进程 2 在持有资源#2 的同时等待资源#1。它不会放弃资源#2 直到它获取到资源#1。

避免 4: 定义锁定顺序(例如,你必须按照#1、#2 的顺序获取锁)。在获取多个锁时始终使用此锁定顺序。

备选方案: 在获取多个锁时,使用以下算法:

  1. 尝试获取所有锁(如果不可用则不阻塞)。

  2. 如果你已经拥有了所有东西,那么继续做你的工作。

  3. 如果你没有获取到所有锁,释放那些你没有获取到的锁,稍作休眠,然后再次尝试。

答案 25: 问题在于这个语句:

        if (n2 =! 0)

这是一个 if 语句内的赋值语句。如果我们重写代码以避免快捷方式,我们得到两个语句:

        n2 = !0;
        if (n2)

在这种情况下使用逻辑非(!0)给我们一个结果为 1。所以我们总是将 n2 赋值为 1,然后进行比较和除法。

!=被错误地写成=!,因此产生了意外。

该语句应该读作:

        if (n2 != 0)

答案 26: 问题在于:

            diff[diff_index++] =
                array[i++] - array[i++];

这告诉编译器:

  1. 增加 i

  2. 使用它来索引数组(首次出现)

  3. 增加 i

  4. 使用它来索引数组(第二次出现)

  5. 计算差值

问题在于步骤 1-4 可能以不同的顺序发生:

  1. 增加 i

  2. 增加 i

  3. 使用它来索引数组(首次出现)

  4. 使用它来索引数组(第二次出现)

具有许多副作用语句给 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 是 ew。

所以字符串 \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++ 优先级规则:

  1. *, / 和 % 的优先级高于 + 和 -。

  2. 将 () 放在其他所有内容周围。

答案 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++ 语句是可中断的。特别是,以下情况可能发生:

  1. 程序运行并完成指令 3。此时,标志的值在寄存器 %o2 中。

  2. 发生了线程切换。

  3. 另一个进程修改了标志。

  4. 线程切换回原状态。

  5. 标志的旧值在寄存器 %o2 中。

  6. 该位被设置,并将结果存储。因为这将包含标志的旧值,所以其他线程中进行的任何更改都会意外地被丢弃。

解决此问题的方法是使用锁来防止在语句执行期间发生任务切换。

答案 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:以某种方式始终声明一个拷贝构造函数。主要有三种方式:

  1. 隐式声明它。

  2. 如果你永远不希望任何人能够调用它,将其声明为 private:

    private:
        var_array (const var_array &);
        // No one can copy var_arrays
    
    
  3. 如果默认设置有效,使用注释:

    // 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);

| | 警告  | 缓冲区刷新的规则取决于正在写入的文件类型。规则如下:

  1. 如果 stdout stderr 正在写入屏幕,则输出将被缓冲,直到:

    1. 当写入一行时。

    2. stdin 被读取时。

    3. 当缓冲区满时。

  2. 如果 stdout stderr 正在写入磁盘,则输出将被缓冲,直到:

    1. 当缓冲区满时。

|

(这些可能是你系统上的规则。实际的规则是系统相关的。)

答案 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:

  1. 对齐

一些机器要求长整数值对齐在 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 个字节。

  1. 字节顺序

一些机器使用 ABCD 的字节顺序写入长整数。而另一些使用 DCBA。这阻止了可移植性。

  1. 整数大小

64 位机器正在到来。这意味着在某些系统上,长整型是 64 位,而不是 32 位。

答案 72: 我们有一个名为 safe stack 的派生类数组。在 C++ 中,你可以使用基类指针(stack*)来指向派生类(safe_stack)。系统将只看到对象的基部分,但你仍然可以指向它。

点击展开

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

点击展开

因此,我们有以下两个规则:

  1. 基指针可以指向派生对象。

  2. 对象指针可以指向对象的数组。

从这个结论中,我们可以得出:

  1. 基指针可以指向派生对象的数组。

这是错误的。

问题在于派生对象的数组与基类对象的数组并不相同。

点击展开

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

点击展开

避免 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

  1. 告诉 C++复制 i(称为 tmp_1)。

  2. 增加 i。

  3. 在 tmp_1 上完成其余的工作。

  4. 复制 tmp_1(称为 tmp_2)。

  5. 增加 tmp_2。

  6. 将 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 次(一次读取和一次写入)。

为了加快程序速度,减少操作系统调用。这可以通过两种方式实现:

  1. 通过将输入和输出 fstreams 而不是文件描述符来使用缓冲 I/O 系统。

  2. 一次读取和写入多个字符。

答案 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 的值。所以这个语句实际上做的操作是:

  1. 保存 i 的值。

  2. 增加 i。

  3. 返回保存的值。

所以这些行:

       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 的计算机法则

第七章: 无阶级的班级

奥瓦林文档定律

第十章: 几个工作程序

致维护程序员之歌

posted @ 2025-12-01 09:40  绝不原创的飞龙  阅读(0)  评论(0)    收藏  举报