1 00:00:00,000 --> 00:00:02,490 [Powered by Google Translate] [CS50图书馆] 2 00:00:02,490 --> 00:00:04,220 内特 - 哈迪森] [哈佛大学] 3 00:00:04,220 --> 00:00:07,260 [这是CS50。 CS50.TV] 4 00:00:07,260 --> 00:00:11,510 CS50库是一个有用的工具,我们已经安装在设备上 5 00:00:11,510 --> 00:00:15,870 使您更轻松地编写程序,提示用户输入。 6 00:00:15,870 --> 00:00:21,670 在这段视频中,我们将拉回来的窗帘,看看究竟是什么在CS50库。 7 00:00:21,670 --> 00:00:25,520 >> 在C库上的视频,我们来谈谈如何#include头文件 8 00:00:25,520 --> 00:00:27,570 你的源代码库中, 9 00:00:27,570 --> 00:00:31,150 然后你链接的二进制库文件中的链接阶段 10 00:00:31,150 --> 00:00:33,140 编译过程。 11 00:00:33,140 --> 00:00:36,440 头文件指定的库的接口。 12 00:00:36,440 --> 00:00:41,280 也就是说,他们的所有细节,图书馆的资源供您使用, 13 00:00:41,280 --> 00:00:45,250 像函数声明,常量和数据类型。 14 00:00:45,250 --> 00:00:48,890 二进制库文件包含了执行的库, 15 00:00:48,890 --> 00:00:54,580 这是从库的头文件和库的C源代码文件编译的。 16 00:00:54,580 --> 00:00:59,820 >> 二进制库文件看,因为它是,在二进制,是不是很有趣。 17 00:00:59,820 --> 00:01:03,300 所以,让我们来看看库,而不是在头文件。 18 00:01:03,300 --> 00:01:07,710 在这种情况下,只有一个头称为cs50.h.的文件 19 00:01:07,710 --> 00:01:11,040 我们已经安装了它在用户目录 20 00:01:11,040 --> 00:01:15,150 随着其他系统库的头文件。 21 00:01:15,150 --> 00:01:21,530 >> 你会注意到的第一件事情之一是,cs50.h#包括从其他库的头文件 - 22 00:01:21,530 --> 00:01:25,670 持股量,范围,标准的布尔值,标准库。 23 00:01:25,670 --> 00:01:28,800 同样,不重新发明轮子的原则, 24 00:01:28,800 --> 00:01:33,490 我们已经建立了的CS0库使用的工具,为我们提供。 25 00:01:33,490 --> 00:01:38,690 >> 接下来的事情,你会看到在图书馆,我们定义一个新类型,叫做“string”。 26 00:01:38,690 --> 00:01:42,330 这条线真的仅仅是创建一个别名为char *类型, 27 00:01:42,330 --> 00:01:46,000 所以它不会奇迹般地灌输新的字符串类型与属性 28 00:01:46,000 --> 00:01:49,650 通常与其他语言的字符串对象, 29 00:01:49,650 --> 00:01:50,850 如长度。 30 00:01:50,850 --> 00:01:55,180 我们已经做到了这一点的原因是保护新的程序员的血淋淋的细节 31 00:01:55,180 --> 00:01:57,580 的指针,直到他们准备好了。 32 00:01:57,580 --> 00:02:00,130 >> 下一个部分的头文件声明的功能 33 00:02:00,130 --> 00:02:04,410 ,CS50库的提供以及相关的文档。 34 00:02:04,410 --> 00:02:06,940 在这里,请注意信息的详细程度。 35 00:02:06,940 --> 00:02:10,560 这是超级重要的,让人们知道如何使用这些功能。 36 00:02:10,560 --> 00:02:19,150 我们宣布,在功能来提示用户并返回字符,双打,花车,整数, 37 00:02:19,150 --> 00:02:24,160 长期的渴望,和字符串,用我们自己的字符串类型。 38 00:02:24,160 --> 00:02:26,260 信息隐藏的原则, 39 00:02:26,260 --> 00:02:31,640 我们已经把我们定义在一个单独的C语言实现文件 - cs50.c - 40 00:02:31,640 --> 00:02:35,110 在用户的源代码目录。 41 00:02:35,110 --> 00:02:38,040 我们已经提供了该文件,你可以看一看, 42 00:02:38,040 --> 00:02:41,490 从中汲取教训,并重新编译它,如果你想在不同的机器上, 43 00:02:41,490 --> 00:02:45,510 即使我们认为这是更好的工作设备上的这个类。 44 00:02:45,510 --> 00:02:47,580 无论如何,让我们来看看它现在。 45 00:02:49,020 --> 00:02:54,620 >> 的的功能GETCHAR,GetDouble,GetFloat,调用getInt,GetLongLong 46 00:02:54,620 --> 00:02:58,160 全部建成的GetString函数。 47 00:02:58,160 --> 00:03:01,510 事实证明,基本上都遵循相同的模式。 48 00:03:01,510 --> 00:03:04,870 他们使用一个while循环来提示用户输入的一行。 49 00:03:04,870 --> 00:03:08,430 他们返回一个特殊的值,如果用户输入一个空行。 50 00:03:08,430 --> 00:03:11,750 他们试图解析用户的输入,作为适当的类型, 51 00:03:11,750 --> 00:03:15,010 无论是一个字符,双,花车等。 52 00:03:15,010 --> 00:03:18,710 然后他们返回的结果,如果输入成功解析 53 00:03:18,710 --> 00:03:21,330 或者它们重新提示用户。 54 00:03:21,330 --> 00:03:24,230 >> 在较高的水平,这里没有什么真正棘手的。 55 00:03:24,230 --> 00:03:28,760 你可能会写自己在过去类似的结构化的代码。 56 00:03:28,760 --> 00:03:34,720 也许最神秘的前瞻性部分是sscanf的调用,分析用户的输入。 57 00:03:34,720 --> 00:03:38,160 sscanf函数的输入格式转换系列的一部分。 58 00:03:38,160 --> 00:03:42,300 住在标准io.h,它的工作是解析一个C字符串, 59 00:03:42,300 --> 00:03:46,520 根据一个特定的格式,存储在变量中的解析结果 60 00:03:46,520 --> 00:03:48,720 所提供的主叫方。 61 00:03:48,720 --> 00:03:53,570 由于输入格式转换功能是非常有用的,广泛使用的功能 62 00:03:53,570 --> 00:03:56,160 不超直观的第一, 63 00:03:56,160 --> 00:03:58,300 我们就去了如何sscanf的作品。 64 00:03:58,300 --> 00:04:03,330 >> sscanf的是一个char *的第​​一个参数 - 一个指向字符的指针。 65 00:04:03,330 --> 00:04:05,150 对于机能得以正常工作, 66 00:04:05,150 --> 00:04:08,340 字符应该是一个C字符串的第一个字符, 67 00:04:08,340 --> 00:04:12,270 空\ 0字符终止。 68 00:04:12,270 --> 00:04:15,120 这是要分析的字符串 69 00:04:15,120 --> 00:04:18,269 到sscanf函数的第二个参数是一个格式字符串, 70 00:04:18,269 --> 00:04:20,839 通常通过在一个字符串常量, 71 00:04:20,839 --> 00:04:24,040 ,你可能会看到一个这样的字符串时,使用printf之前。 72 00:04:24,040 --> 00:04:28,650 格式字符串中的百分号表示转换符。 73 00:04:28,650 --> 00:04:30,850 该字符紧跟一个百分号, 74 00:04:30,850 --> 00:04:35,430 表示,我们希望sscanf的转换为C型。 75 00:04:35,430 --> 00:04:40,090 在调用getInt,你看,有一个%d和%C。 76 00:04:40,090 --> 00:04:48,690 这意味着,sscanf的将尝试一个十进制整数 - % - 和一个char - %C。 77 00:04:48,690 --> 00:04:51,510 对于每一个转换中指定的格式字符串, 78 00:04:51,510 --> 00:04:56,620 sscanf的预计,相应的参数后,在其参数列表。 79 00:04:56,620 --> 00:05:00,850 这个参数必须指向一个相应类型的位置 80 00:05:00,850 --> 00:05:04,000 要在其中存储的转换的结果。 81 00:05:04,000 --> 00:05:08,910 >> 典型的方法,这样做是在栈上创建一个变量前sscanf的调用 82 00:05:08,910 --> 00:05:11,440 对于每一个项目,你要解析的字符串 83 00:05:11,440 --> 00:05:15,520 然后使用地址操作符 - 符号 - 通过指针 84 00:05:15,520 --> 00:05:19,100 这些变量的sscanf的调用。 85 00:05:19,100 --> 00:05:22,720 你可以看到,在调用getInt,我们正是这样做的。 86 00:05:22,720 --> 00:05:28,240 前sscanf的调用,我们称为n声明一个int和一个char调用C堆栈上的, 87 00:05:28,240 --> 00:05:32,340 我们sscanf的调用指针传递给他们。 88 00:05:32,340 --> 00:05:35,800 把这些变量在栈上优于使用空间分配 89 00:05:35,800 --> 00:05:39,350 的堆用malloc,避免malloc调用的开销,因为你, 90 00:05:39,350 --> 00:05:43,060 你不必担心内存泄漏。 91 00:05:43,060 --> 00:05:47,280 没有前缀一个百分号字符不提示转换。 92 00:05:47,280 --> 00:05:50,380 相反,他们只需要添加的格式规范。 93 00:05:50,380 --> 00:05:56,500 >> 例如,如果格式字符串在调用getInt%d代替, 94 00:05:56,500 --> 00:05:59,800 sscanf的寻找字母a,后面跟着一个int, 95 00:05:59,800 --> 00:06:04,360 ,虽然它试图将其转换为int,它不会做任何事情的一个。 96 00:06:04,360 --> 00:06:07,440 唯一的例外是空白。 97 00:06:07,440 --> 00:06:11,030 格式字符串中的空格字符匹配任何数量的空白 - 98 00:06:11,030 --> 00:06:12,890 甚至都没有。 99 00:06:12,890 --> 00:06:18,100 所以,这就是为什么注解中提到的可能与领先的和/或尾随的空白。 100 00:06:18,100 --> 00:06:22,910 所以,在这一点上它看起来像我们sscanf的调用尝试将其解析用户输入的字符串 101 00:06:22,910 --> 00:06:25,380 通过检查可能的前导空格, 102 00:06:25,380 --> 00:06:29,300 其次是一个int值将被转换并存储在int变量n 103 00:06:29,300 --> 00:06:33,090 一定量的空白,后面的字符 104 00:06:33,090 --> 00:06:35,810 存储在char变量c。 105 00:06:35,810 --> 00:06:37,790 >> 的返回值呢? 106 00:06:37,790 --> 00:06:41,560 sscanf的解析输入线从开始到结束, 107 00:06:41,560 --> 00:06:44,860 停止当它到达末尾时,或当一个字符在输入 108 00:06:44,860 --> 00:06:49,320 不匹配的格式字符,或当它不能转换。 109 00:06:49,320 --> 00:06:52,690 它的返回值是用来挑选时停止。 110 00:06:52,690 --> 00:06:55,670 如果它停止了,因为它已经达到结束的输入字符串的 111 00:06:55,670 --> 00:07:00,630 之前,作出任何转换之前,不匹配的格式字符串的一部分, 112 00:07:00,630 --> 00:07:04,840 特殊常量EOF返回。 113 00:07:04,840 --> 00:07:08,200 否则,它返回成功转换的数量, 114 00:07:08,200 --> 00:07:14,380 这可能是0,1或2,因为我们已经要求两个转换。 115 00:07:14,380 --> 00:07:19,000 在我们的例子中,我们要确保用户输入一个int,并仅使用一个int。 116 00:07:19,000 --> 00:07:23,370 >> 所以,我们希望sscanf的返回1。知道为什么吗? 117 00:07:23,370 --> 00:07:26,850 如果sscanf函数返回0,则没有转换, 118 00:07:26,850 --> 00:07:31,690 所以用户键入的输入开始时的int以外的东西。 119 00:07:31,690 --> 00:07:37,100 如果sscanf的返回2,然后用户没有正确地键入它在在开始的输入, 120 00:07:37,100 --> 00:07:41,390 但他们在一些非空白字符,然后键入后 121 00:07:41,390 --> 00:07:44,940 因为%c转换成功了。 122 00:07:44,940 --> 00:07:49,570 哇,这是一个相当冗长的解释为一个函数调用。 123 00:07:49,570 --> 00:07:53,460 无论如何,如果你想sscanf的和它的兄弟姐妹的更多信息, 124 00:07:53,460 --> 00:07:57,130 检查手册页,谷歌,或两者。 125 00:07:57,130 --> 00:07:58,780 有很多的格式字符串选项, 126 00:07:58,780 --> 00:08:03,830 而这些可以为您节省大量的手工劳动时,试图解析字符串C. 127 00:08:03,830 --> 00:08:07,180 >> 在图书馆看的是最后一个函数Ge​​tString的。 128 00:08:07,180 --> 00:08:10,310 事实证明,GetString的是一个棘手的功能,正确写, 129 00:08:10,310 --> 00:08:14,290 即使它看起来像一个简单的,共同的任务。 130 00:08:14,290 --> 00:08:16,170 为什么会出现这样的情况呢? 131 00:08:16,170 --> 00:08:21,380 好吧,让我们想想我们要如何来存储线,用户键入的 132 00:08:21,380 --> 00:08:23,880 由于字符串是一个字符序列, 133 00:08:23,880 --> 00:08:26,430 我们可能要存储在一个数组在堆栈上, 134 00:08:26,430 --> 00:08:31,250 但我们需要知道过了多久数组是要当我们声明。 135 00:08:31,250 --> 00:08:34,030 同样,如果我们想要把它的堆, 136 00:08:34,030 --> 00:08:38,090 我们需要通过对malloc我们要保留的字节数, 137 00:08:38,090 --> 00:08:39,730 但这是不可能的。 138 00:08:39,730 --> 00:08:42,760 我们不知道用户输入多少字符 139 00:08:42,760 --> 00:08:46,590 之前,用户实际上并键入它们。 140 00:08:46,590 --> 00:08:50,720 >> 这个问题是只保留一大块的空间,比方说,一个天真的解决方案 141 00:08:50,720 --> 00:08:54,540 在一个块的1000的用户的输入的字符, 142 00:08:54,540 --> 00:08:57,980 假设用户将永远不会输入一个字符串,它长。 143 00:08:57,980 --> 00:09:00,810 这是一个坏主意,原因有两个。 144 00:09:00,810 --> 00:09:05,280 首先,假设,用户通常不键入字符串中的那么长, 145 00:09:05,280 --> 00:09:07,610 你可能会浪费大量的内存。 146 00:09:07,610 --> 00:09:10,530 在现代化的机器,这可能不是一个问题,如果你这样做 147 00:09:10,530 --> 00:09:13,890 在一个或两个分离的情况下, 148 00:09:13,890 --> 00:09:17,630 但是,如果你在一个循环中用户的输入,存储供以后使用, 149 00:09:17,630 --> 00:09:20,870 您可以快速地吸了一吨的内存。 150 00:09:20,870 --> 00:09:24,450 此外,如果你写的是一个较小的计算机程序 - 151 00:09:24,450 --> 00:09:28,100 在内存有限的设备,如智能手机或别的东西 - 152 00:09:28,100 --> 00:09:32,060 该解决方案将导致问题的速度快了很多。 153 00:09:32,060 --> 00:09:36,450 第二,更严重的不这样做的原因是,它让你的程序脆弱 154 00:09:36,450 --> 00:09:39,710 什么所谓的缓冲区溢出攻击。 155 00:09:39,710 --> 00:09:45,840 在编程中,缓冲器是用来临时存储输入或输出数据的存储器, 156 00:09:45,840 --> 00:09:48,980 在这种情况下,这是我们1000字符块。 157 00:09:48,980 --> 00:09:53,370 过去的块数据被写入时发生缓冲区溢出。 158 00:09:53,370 --> 00:09:57,790 >> 例如,如果一个用户实际上在超过1000个字符类型。 159 00:09:57,790 --> 00:10:01,570 你可能已经经历过这样的意外编程时数组。 160 00:10:01,570 --> 00:10:05,620 如果你有10个整数的数组,没有什么可以阻止你试图读取或写入 161 00:10:05,620 --> 00:10:07,810 15日的诠释。 162 00:10:07,810 --> 00:10:10,000 有任何编译器警告或错误。 163 00:10:10,000 --> 00:10:13,250 该计划只是失误直行和访问内存 164 00:10:13,250 --> 00:10:18,150 如认为第15的int,这样就可以覆盖其他变量。 165 00:10:18,150 --> 00:10:22,040 在最坏的情况下,可以覆盖一些程序的内部 166 00:10:22,040 --> 00:10:26,820 控制机制,从而导致你的程序执行不同的指令 167 00:10:26,820 --> 00:10:28,340 比您预期。 168 00:10:28,340 --> 00:10:31,360 >> 现在,它是不是共同做这个意外, 169 00:10:31,360 --> 00:10:35,150 但是,这是一个相当普遍的技术坏人破坏程序 170 00:10:35,150 --> 00:10:39,080 在其他人的电脑上,并把恶意代码。 171 00:10:39,080 --> 00:10:42,910 因此,我们不能只用我们的天真的解决方案。 172 00:10:42,910 --> 00:10:45,590 我们需要一种方法来防止我们的计划是脆弱的 173 00:10:45,590 --> 00:10:47,880 一个缓冲区溢出攻击。 174 00:10:47,880 --> 00:10:51,430 要做到这一点,我们需要确保增长,因为我们阅读我们的缓冲区 175 00:10:51,430 --> 00:10:53,850 更多的来自用户的输入。 176 00:10:53,850 --> 00:10:57,440 该如何解决呢?我们使用堆分配的缓冲区。 177 00:10:57,440 --> 00:10:59,950 因为我们可以改变它的大小调整realloc函数, 178 00:10:59,950 --> 00:11:04,580 而我们跟踪的两个数字 - 在缓冲区中的下一个空槽的索引 179 00:11:04,580 --> 00:11:08,390 和缓冲区的长度或容量。 180 00:11:08,390 --> 00:11:13,210 我们读到chars中从用户1在一个时间使用fgetc函数。 181 00:11:13,210 --> 00:11:19,360 fgetc函数需要的参数 - STDIN - 是一个参考的标准输入字符串, 182 00:11:19,360 --> 00:11:23,810 预连接的输入信道,用于传输用户的输入,这是一个 183 00:11:23,810 --> 00:11:26,270 从终端到该程序。 184 00:11:26,270 --> 00:11:29,890 >> 每当用户键入一个新的角色,我们要检查一下,如果索引 185 00:11:29,890 --> 00:11:35,810 下一个空闲槽加1是大于缓冲区的容量。 186 00:11:35,810 --> 00:11:39,690 +1,因为如果下一个可用的索引为5, 187 00:11:39,690 --> 00:11:44,150 然后我们缓冲区的长度必须是6感谢0索引。 188 00:11:44,150 --> 00:11:48,350 如果我们已经用完了空间,在缓冲区中,然后我们尝试调整它的大小, 189 00:11:48,350 --> 00:11:51,690 一倍,使我们的次数减少,我们调整 190 00:11:51,690 --> 00:11:54,760 如果用户是在一个很长的字符串输入。 191 00:11:54,760 --> 00:11:57,950 如果字符串中已经得到了太长时间,如果我们运行的堆内存, 192 00:11:57,950 --> 00:12:01,350 我们释放我们的缓冲区,并返回null。 193 00:12:01,350 --> 00:12:04,170 >> 最后,我们追加字符的缓冲区。 194 00:12:04,170 --> 00:12:08,200 一旦用户点击进入或返回,标志着一个新的生产线, 195 00:12:08,200 --> 00:12:12,050 或特殊字符 - 控制D - 信号输入, 196 00:12:12,050 --> 00:12:16,240 我们做一个检查,看看如果用户输入的任何所有。 197 00:12:16,240 --> 00:12:18,820 如果没有,则返回null。 198 00:12:18,820 --> 00:12:22,280 否则,因为我们的缓冲区可能是超出我们所需要的, 199 00:12:22,280 --> 00:12:24,830 在最坏的情况下,它几乎两倍大,因为我们需要 200 00:12:24,830 --> 00:12:27,830 因为我们每年翻一番的时间调整, 201 00:12:27,830 --> 00:12:31,840 我们的字符串,只是使用的空间量,我们需要一个新的副本。 202 00:12:31,840 --> 00:12:34,220 我们增加了一个额外的1 malloc调用, 203 00:12:34,220 --> 00:12:37,810 有特殊的NULL终止符的空间 - 在“\ 0”, 204 00:12:37,810 --> 00:12:41,990 我们添加一旦我们在其余的字符复制到字符串, 205 00:12:41,990 --> 00:12:45,060 使用strncpy而不是strcpy 206 00:12:45,060 --> 00:12:48,830 因此,我们可以指定要复制我们到底有多少个字符。 207 00:12:48,830 --> 00:12:51,690 STRCPY复制,直到它击中了\ 0。 208 00:12:51,690 --> 00:12:55,740 然后,我们帮助我们缓冲的副本,并返回给调用者。 209 00:12:55,740 --> 00:12:59,840 >> 谁知道这样一个看似简单的功能,可以这么复杂吗? 210 00:12:59,840 --> 00:13:02,820 现在你知道什么进入CS50库。 211 00:13:02,820 --> 00:13:06,470 >> 我的名字是Nate哈迪森,这是CS50。 212 00:13:06,470 --> 00:13:08,350 [CS50.TV]