c陷阱与缺陷

2023-11-12

第一章     词法陷阱

1.这一章没有太多“干货”,唯一比较有趣的就是 1.3 语法分析中的“贪心法” 所讲内容。这个“贪心”就是编译器会读入字符,如果能新读入的字符和之前所读入字符能组成符号,则编译器会继续读入下一个字符,直到读入的字符不能和之前的字符组成符号。 
比如,

/* a---b和(a--)-b等价 */
/* a+++++b和((a++)++)+b等价 */

2.用单引号引起的一个字符实际上代表一个整数,整数值对应于该字符在编译器采用的字符集中的序列值。   

   用双引号引起的字符串,代表的却是一个指向无名数组起始字符的指针,该数组双引号之间的字符以及一个额外的二进制值为零的字符‘\0’初始化。

   例如 “yes”  ‘yes’  

    前者含义是“依次包含‘y’、‘e’、's'以及空字符'\0'的4个连续内存单元的首地址”,后者并没有明确的定义,但大多数c编译器理解为“一个整数值,由'y','e','s'所代表的整数值按照特定编译器实现中定义的方式组合得到”




第二章     语法“陷阱”


1.理解函数声明


  • 两部分组成:类型 + 一组类似表达式的声明符(declarator),后者的求值应返回一个给定类型的结果
  • float f, f; 等同于 float ((f)), (g);       理解:由1可知
  •      3.typedef void (*funcptr)(); (*(funcptr)0)(); 等同于 (*(void(*)())0)(); 显示调用开机子例程
  •      4.一旦我们知道了如何声明一个给定类型的变量,那么该类型的类型转换符就很容易得到了:只需把声明中的变量名和声明末尾的分号去掉
  •         再将剩余的部分用括号封装起来 例如:
  •         float (*h)()      ----->       (float (*)())
  •      5.*fp()=*(fp())      ANSI C把它作为*((*fp))()的简写形式

    



这个小节,作者给出一个有趣的函数用

(*(void (*)())0)();

当我第一次看到这个函数调用的时候,直接就懵了,完全不知道它要干啥。其实这个函数就是为了调用在地址0处的返回值为void类型的函数指针的函数。我知道这个中文解释也特别绕,下面我就一步步的分析这个语句。

第一,返回值为void类型的函数指针

void (*pfun)()

这个就是上面那个语句中的

void(*)()

void(*)()0

便是将0这个地址转换成void (*)()类型。如果这个不理解,这个语句该懂吧

(int *)0          //理解:类型不过是告诉编译器分配内存的大小,所以这个的意思是告诉编译器从零开始给我分配(int *)类型大小的内存空间,并且可以存放int类型变量的                           地址

对,这个例子就是将0这个地址转化成int类型。读和写这个地址都是按照32bit或者16bi进行操作(由操作系统是32bit还是16bit决定)。

第二,通过指针访问函数
一般而言,我们使用func()来调用函数,如果是使用函数指针pfun的话,我们应该这样使用

(*pfun)()

而不是

*pfun()

因为()的优先级高于*,如果是后者的话,该语句就等价于

*(pfun()) == *((*pfun)())

这并不是我们想要的结果。说了这么多,只要我们结合一和二就很容易理解这个语句是做什么的了。说实话,他这个用法也比较奇葩,因为他不是用函数的间接地址(函数名)而是用直接地址(这个例子中是0)来调用函数,因此理解起来比较费力。对于函数指针本身,我将在之后的文章中详细讲解如何使用。

2.运算符的优先级问题

  • 任何一个逻辑运算符的优先级低于任何一个关系运算符(有关系的人排在前面)
  • 移位运算符的优先级比算术运算符要低,但比关系运算符要高
  • 关系运算符的优先级并不相同,!===要低于其他的
  • 任何两个逻辑运算符都有不同的优先级, 按位运算符比顺序运算符要高
  • 结合性为自右向左的有:单目运算;三目运算;assignments
       

      注意的例子:*p++会被编译器解释成*(p++),即取指针p所指向的对象,然后将p递增1;而不是(*p)++即取指针p所指向的对象,然后将该对                                象递增1

3.函数调用要包括函数列表

       即使函数不带参数,但调用时也得包括函数列表, f()是一个调用语句,而f仅计算函数f的地址却不调用该函数


第三章    语义陷阱

1.指针和数组

1这节给出了C中数组两个特别需要注意的地方: 
第一,C语言只有一维数组,其元素可以为任何数据类型。

第二,对于一个数组,我们只能做两件事,确定该数组的大小,以及获得指向该数组下标为0的元素的指针。其他有关数组的操作,哪怕他们乍看上去是对数组下标进行运算的,实际上都是通过指针进行的。t

2.除了a被用作运算符sizeof的参数的情形,在其他所有的情形中数组名都代表指向数组a中下标为0的元素的指针

2.作为参数的数组声明

       1.C语言中,我们无法将一个数组作为函数参数直接传递,如果我们使用数组名作为参数,那么数组名就会立刻被转换为指向该数组第一 个元素的指针

       2.C语言会自动地将作为参数的数组声明转换为相应的指针声明

3.空指针并非空字符串

  • 编译器保证由0转换而来的指针不等于任何有效的指针,即NULL
  • 当常数0被转化为指针使用时,其绝不能被解除引用(dereference),即if (p == (char *) 0)是合法的,但if (strcmp(p, (char *)0) == 0)...非法
  • p是空指针,则printf(p)printf("%s", p)的行为均为未定义
4.不对称边界
  • ”off-by-one error” 差一错误
  • 左闭右开, for (i = 0; i < 10; i++), 而不写成for (i = 0; i <= 9 ; i++)
  • 入界点(可用序列中的第一个元素为0),出界点(不可用序列中的第一个元素)为10
  • --n >= 0至少要与等效的n-- >0一样快或更快,第一个结果先将n减1,再将结果与0比较;第二个表达式则先保存n,从n中减1,然后比较保存值与0的大小()
  • 坚持“不对称原则”
  • 数组中实际不存在的“溢界”元素的地址位于数组所占内存之后,这个地址可以用于赋值和比较,但如果引用该元素,则非法

5.求值顺序

C语言中只规定了四个运算符有明确规定的求值顺序,它们分别是&&, ||, ?:和,。所以=左右两边是没有规定求值顺序的。这节给出一个例子:

	i = 0;
	while( i < n )
  	  y[ i ] = x[ i++ ];

由于没有说明到底是先算左边还是先算右边,所以可能左边用y[ i+1 ]前的结果接收了右边x[ i++ ]后的结果。当然,也可能左边用y[ i+1 ]的结果接收右边x[ i++ ]后的结果。这和编译器有关,我们应该避免这种写法。

6.整数溢出

这节讲了如何避免有符号数的溢出问题,比如两个有符号非负数a和b,如何判断相加是否溢出?文中给了两个方法,我准备在日后写篇如何防止溢出的文章详细讨论更多情况。

  • 无符号运算没有溢出一说
  • 如果算数运算中一个操作数是有符号整数,另一个无符号整数,则均会转换为无符号整数
  • 如果两个都是有符号整数,则溢出结果未定义
  • 正确检测溢出的方法if ((unsigned)a +(unsigned)b < INT_MAX) 或 if (a < INT_MAX - b), INT_MAX在<limits.h>

/* 方法0 错误方法 */
if( a + b < 0 )

/* 方法1 */
if( ( unsigned )a + ( unsigned )b > INT_MAX )

/* 方法2 */
if( a > INT_MAX - b )

为什么方法0不正确?因为对于有些系统,对于有符号数的溢出,它并不会在状态寄存器中标记“负”,而是会标记“溢出”。这样a+b其实就没有小于0,因此这种判断方式不正确(至少某些情况不正确)。

7.为函数main提供返回值

在某些情形下函数main的返回值却并非无关紧要,大多数c语言实现都通过函数main的返回值来告知操作系统该函数的执行时成功还是失败,典型的处理方案是,返回值为0代表程序执行成功,返回值非0则表示程序执行失败

第四章  连接

1.连接器

1.c语言中的一个重要思想就是分别编译,即若干个源程序可以在不同的时候单独进行编译,然后在恰当的时候整合到一起。

2.连接器通常把目标模块看成是由一组外部对象组成的。每个外部对象代表这机器内存中的某个部分,并通过一个外部名称来识别,因此,程序中的每个函数和每个外部变量,如果没有声明static,就都是一个外部变量。

3.连接器的工作:连接器的输入是一组目标模块和库文件。连接器的输出是一个载入模块。连接器读入目标模块和库文件,同时生成载入模块。对每个目标模块中的每个外部对象,连接器都要检查载入模块,看石佛已有同名的外部对象。如果没有,连接器就将该外部对象添加到载入模块中;如果有,连接器就要开始处理命名冲突。

2.声明与定义

1.int a         如果其位置出现在所有的函数体之外,那么它就被称为外部对象a的定义。这个语句说明了a是一个外部整型变量,同时为a分配了存储空间。

2.extern  a   这并不是对a的定义,这个语句仍然说明了a是一个外部整型变量,但是因为它包括了extern关键字,就显示的说明了a的存储空间是在程序的其他地方分配的。从连接器的角度看,上述声明是一个对外部变量a的引用,而不是对a的定义。


3.命名冲突与static修饰符

全局变量在不同文件中不能多次定义,我们定义了一次以后,在其他文件中使用extern修饰符进行访问。为了避免在不同文件中定义同名的全局变量,我们应该使用static修饰符。static修饰的变量和函数的作用域仅限于其所在的。


4.形参、实参与返回值                                                                                                                                                                                                                         1.如果一个函数在被定义或声明之前被调用,那么它的返回类型就默认为整型。

2.为避免错误,在函数调用前应该先声明或者定义       

3任何C函数都含有一个形参列表,该变量在函数调用时被初始化

        4.检查外部类型

     1.char filename[] = "/etc/passwd";extern char* filename;,前者中filename的类型为“字符数组”,后者的类型为“字符指针”,这两个对filename的声明使用存储空间的方式不同(图不一样)

2.应修改为char filename[] = "/etc/passwd";extern char filename[]; 或者char* filename = "/etc/passwd";(文件一), extern char* filename;(文件二)
                                                                                                                                                                                                                                                               5.头文件

我们可以通过把extern修饰的变量放入头文件,只要include这个头文件的文件都可以访问这个全局变量。

五 、库函数

1.预处理用得好事半功倍,用得不好bug满天。在这章,作者给出了一些比较常见的错误使用,比如用宏错误定义函数或者函数参数,用宏错误定 义数据类型。

/* 多了空格 */
#define f (x) ((x) - )

/* 优先级考虑不周到,如果x = a - b结果不对*/
#define abs(x) x>=0?x:-x

/* 正确使用应该全部添加括号,包括最外面也要添加括号,这是为了避免一些比较特殊情况,比如 abs(a) + 1 */
#define abs(x) (((x)>=0)?(x):-(x))

/* 错误的在数据类型上使用宏定义 */
#define T1 struct foo *
T1 a, b;   理解:展开为struct foo *a,b; 此时a是结构体指针,而b并不是。

/* 正确的方法 */
typedef struct foo * T2
T2 c, d;

除了上面这些易错点,在使用宏定义的时候,尤其需要注意++以及--的情况。当遇到++/--的时候,宏定义出错的概率会高很多。

                                                                                                                                                                                                                                                             

附录

printf

  • %之后的称为格式码(指明了格式转换的类型)
  • 修饰符, %和格式码之间 %3.1g(宽度修饰符、精度修饰符等)
  • 标志, %和域宽修饰符之间,如%-14s(左对齐), %+d
  • #对数值的输出格式进行微调,0%o%#o,针对数值0,其分别打印00和0;%#x%#X打印出的16进制数前加上0x或0X;#用在浮点数中则其要求小数点必须打印出来(即使小数点后没有数字),如果用于%g或%G格式项,打印出的数值尾缀的0 将不会被去掉
  • printf允许间接指定域宽,只需用*替换域宽修饰符或精度修饰符或两者,printf("%*.*s", 12, 5, str);
  • printf("%*%\n", n) 打印出n-1个空白字符,后面再跟一个%符号
  • 新增格式码:%p 打印出该指针所指向的地址; %n 指出已经打印的字符数,这个数被存储在对应参数所指向的整数中(一个整型指针),如下int n; printf("hello\n%n", &n)


 道理:

“思考”是一切错误之源;我可以轻易地举出事实来证明这一点:犯了错的人总是会说,“哦,可是我原以为。。。”只要大键琴的各种部件还没有粘合到一起,你就应该反复思考直到真正理解,这种“思考”是无妨的。你应该在不用粘合剂的情况下把所有的部件拼装起来(称之为演习或排练),研究它们是如何结合的,并与装配图仔细对照。
在你吧某些部件粘合起来之后,还应该在检查一遍,我听过很多次这种不幸的故事:“昨晚我做了什么什么,可是今天早上我再看就.....”
 亲爱的制作者,如果你昨晚就好好看了的话,那么你可能已经把不合适的部件拆下来重新装好了,很多制作者是利用业余时间来动手DIY一个大键琴,所以经常忍不住要赶到深夜,但是,根据我接听求助电话的经验,大多数错误都出在制作者在上床睡觉之前做的最后一件工作,所以,在你准备最后做一点什么之前,还是早点休息吧。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c陷阱与缺陷 的相关文章

  • 检查两个数是否是彼此的排列?

    给定两个数字 a b 使得 1 例如 123 是 312 的有效排列 我也不想对数字中的数字进行排序 如果您指的是数字的字符 例如 1927 和 9721 则 至少 有几种方法 如果允许排序 一种方法是简单地sprintf将它们放入两个缓冲
  • 无法使用已与其底层 RCW 分离的 COM 对象。在 oledb 中

    我收到此错误 但我不知道我做错了什么 下面的代码在backrgroundworker中 将异常详细信息复制到剪贴板 System Runtime InteropServices InvalidComObjectException 未处理 通
  • 获取按下的按钮的返回值

    我有一个在特定事件中弹出的表单 它从数组中提取按钮并将标签值设置为特定值 因此 如果您要按下或单击此按钮 该函数应返回标签值 我怎样才能做到这一点 我如何知道点击了哪个按钮 此时代码返回 DialogResult 但我想从函数返回 Tag
  • 如何在列表框项目之间画一条线

    我希望能够用水平线分隔列表框中的每个项目 这只是我用于绘制项目的一些代码 private void symptomsList DrawItem object sender System Windows Forms DrawItemEvent
  • C++ 子字符串返回错误结果

    我有这个字符串 std string date 20121020 我正在做 std cout lt lt Date lt lt date lt lt n std cout lt lt Year lt lt date substr 0 4 l
  • 使闭包捕获的变量变得易失性

    闭包捕获的变量如何与不同线程交互 在下面的示例代码中 我想将totalEvents 声明为易失性的 但C 不允许这样做 是的 我知道这是错误的代码 这只是一个例子 private void WaitFor10Events volatile
  • C - 找到极限之间的所有友好数字

    首先是定义 一对友好的数字由两个不同的整数组成 其中 第一个整数的除数之和等于第二个整数 并且 第二个整数的除数之和等于第一个整数 完美数是等于其自身约数之和的数 我想做的是制作一个程序 询问用户一个下限和一个上限 然后向他 她提供这两个限
  • 将目录压缩为单个文件的方法有哪些

    不知道怎么问 所以我会解释一下情况 我需要存储一些压缩文件 最初的想法是创建一个文件夹并存储所需数量的压缩文件 并创建一个文件来保存有关每个压缩文件的数据 但是 我不被允许创建许多文件 只能有一个 我决定创建一个压缩文件 其中包含有关进一步
  • C 预处理器库

    我的任务是开发源分析工具C程序 并且我需要在分析本身之前预处理代码 我想知道什么是最好的图书馆 我需要一些重量轻 便于携带的东西 与其推出自己的 为什么不使用cpp这是的一部分gcc suite http gcc gnu org onlin
  • 指针减法混乱

    当我们从另一个指针中减去一个指针时 差值不等于它们相距多少字节 而是等于它们相距多少个整数 如果指向整数 为什么这样 这个想法是你指向内存块 06 07 08 09 10 11 mem 18 24 17 53 7 14 data 如果你有i
  • C# 中的递归自定义配置

    我正在尝试创建一个遵循以下递归结构的自定义配置部分
  • 在数据库中搜索时忽略空文本框

    此代码能够搜索数据并将其加载到DataGridView基于搜索表单文本框中提供的值 如果我将任何文本框留空 则不会有搜索结果 因为 SQL 查询是用 AND 组合的 如何在搜索 从 SQL 查询或 C 代码 时忽略空文本框 private
  • 将 unsigned char * (uint8_t *) 转换为 const char *

    我有一个带有 uint8 t 参数的函数 uint8 t ihex decode uint8 t in size t len uint8 t out uint8 t i hn ln for i 0 i lt len i 2 hn in i
  • 实体框架 4 DB 优先依赖注入?

    我更喜欢创建自己的数据库 设置索引 唯一约束等 使用 edmx 实体框架设计器 从数据库生成域模型是轻而易举的事 现在我有兴趣使用依赖注入来设置一些存储库 我查看了 StackOverflow 上的一些文章和帖子 似乎重点关注代码优先方法
  • 如何使我的表单标题栏遵循 Windows 深色主题?

    我已经下载了Windows 10更新包括黑暗主题 文件资源管理器等都是深色主题 但是当我创建自己的 C 表单应用程序时 标题栏是亮白色的 如何使我自己的桌面应用程序遵循我在 Windows 中设置的深色主题 你需要调用DwmSetWindo
  • 需要哪个版本的 Visual C++ 运行时库?

    microsoft 的最新 vcredist 2010 版 是否包含以前的版本 2008 SP1 和 2005 SP1 还是我需要安装全部 3 个版本 谢谢 你需要所有这些
  • 控制到达非 void 函数末尾 -wreturn-type

    这是查找四个数字中的最大值的代码 include
  • 如何让Gtk+窗口背景透明?

    我想让 Gtk 窗口的背景透明 以便只有窗口中的小部件可见 我找到了一些教程 http mikehearn wordpress com 2006 03 26 gtk windows with alpha channels https web
  • Validation.ErrorTemplate 的 Wpf 动态资源查找

    在我的 App xaml 中 我定义了一个资源Validation ErrorTemplate 这取决于动态BorderBrush资源 我打算定义独特的BorderBrush在我拥有的每个窗口以及窗口内的不同块内
  • 限制C#中的并行线程数

    我正在编写一个 C 程序来生成并通过 FTP 上传 50 万个文件 我想并行处理4个文件 因为机器有4个核心 文件生成需要更长的时间 是否可以将以下 Powershell 示例转换为 C 或者是否有更好的框架 例如 C 中的 Actor 框

随机推荐

  • 反思深度学习与传统计算机视觉的关系

    来源 算法与数学之美 某种程度上 深度学习最大的优势就是自动创建没有人会想到的特性能力 如今 深度学习在众多领域都有一席之地 尤其是在计算机视觉领域 尽管许多人都为之深深着迷 然而 深网就相当于一个黑盒子 我们大多数人 甚至是该领域接受过培
  • 【C++进阶】二叉搜索树递归与非递归的模拟实现(附源码)

    一 什么是二叉搜索树 二叉搜索树又称二叉排序树 它或者是一棵空树 或者是具有以下性质的二叉树 根据二叉搜索树的性质 它的中序遍历结果就是一个升序列 二 二叉搜索树的模拟实现 节点 Node 在实现二叉搜索树之前 要先定义一个节点 成员变量包
  • Shiro权限框架-在线并发登录人数控制(9)

    1 实现原理 在实际开发中 我们可能会遇到这样的需求 一个账号只允许同时一个在线 当账号在其他地方登陆的时候 会踢出前面登陆的账号 那我们怎么实现 自定义过滤器 继承AccessControlFilter 使用redis队列控制账号在线数目
  • 基于Directshow的H.264流媒体播放器设计

    0引言 DirectsHow应用框架完成了流媒体处理的底层工作 使得编程者无需关心数据如何输入 以及处理完后如何输出 而只需关心如何对输入数据进行处理 H 264视频编解码标准具有高压缩比和优良的网络亲和性 被普遍认为是最有影响力的流媒体视
  • 变分推断

    变分推断 MATLAB实现变分贝叶斯蒙特卡洛模拟的贝叶斯推断 目录 变分推断 MATLAB实现变分贝叶斯蒙特卡洛模拟的贝叶斯推断 效果一览 基本介绍 研究内容 模型描述 模型设计 参考资料 效果一览 基本介绍 MATLAB实现变分贝叶斯蒙特
  • input输入框获取焦点之后,显示搜索记录下拉表,点击其他地方搜索记录框消失

    给input框绑定一个focus事件 获取焦点时给全局绑定一个点击事件 判断下次点击的地方在不在输入框和下拉框内 不在则下拉框消失 记得清除这个点击事件 具体代码如下 处理搜索框获取焦点 handleInputSearch this isO
  • elementui中多个table同步滚动

    问题描述 element admin中同时使用多个table 要求头部固定 给每个并列的table设置max height 通过监听一侧的table滚动情况去控制另外一侧的table滚动 问题分析 table分成两部分 左侧是一个table
  • 用python画基本初等函数的图像(未完成)

    要用到matplotlib库 一 绘制 y x 图像 import numpy as np import matplotlib pyplot as plt x np linspace 50 50 200 定义x的范围为 50到50 分为20
  • UVA-11059 最大乘积 题解答案代码 算法竞赛入门经典第二版

    GitHub jzplp aoapc UVA Answer 算法竞赛入门经典 例题和习题答案 刘汝佳 第二版 数据量不大 暴力即可 include
  • 【AI】《动手学-深度学习-PyTorch版》笔记(二十):图像增强、微调

    AI学习目录汇总 1 图像增强 图像增强可以扩展训练样本数量 减小对某个属性的依赖 比如 裁剪图像 可以减少模型对对象出现位置的依赖 调整亮度 颜色等因素来降低模型对颜色的敏感度等 1 1 准备工作 头文件 matplotlib inlin
  • 01C++11多线程编程之thread,join,detach,joinable以及简说detach传引用地址的大坑

    01C 11多线程编程之thread join detach joinable以及简说detach传引用地址的大坑 1 thread类对象创建线程与join回收线程 1 thread创建线程很简单 定义一个对象然后传一个可调用对象即可 可调
  • Java反射机制【看这一篇就够啦!!!】

    Welcome Huihui s Code World 接下来看看由辉辉所写的关于反射机制的相关操作吧 目录 Welcome Huihui s Code World 一 是什么 二 为什么要使用 三 怎么使用 辉辉小贴士 什么是Class类
  • 数字电路设计之同步电路的一些经验

    在设计的过程中 异步复位电路对硬件要求更低 更容易实现 但是使用同步复位电路却有着诸多优点 使得在实际的工业设计中更多使用的是同步复位电路 使用同步电路一般有以下好处 第一个就是避免毛刺 使用逻辑电路就一定会有毛刺 使用同步电路就有效避免毛
  • Vue生成二维码组件封装

    1 使用方法 1 1 载入 JavaScript 文件 1 2 调用 简单方式 new QRCode document getElementById qrcode your content 设置参数方式 var qrcode new QRC
  • Ubuntu彻底卸载pycharm的方法

    1 查看配置信息位置 首先在解压的pycharm 2020 2 1文件夹中 查看Install Linux tar txt 找到配置信息的位置 下图中蓝色标识 2 卸载安装文件 首先找到安装文件所在的目录 cd 切换至其目录 然后 sudo
  • 深入了解React:组件化开发与状态管理

    简介 React是一个流行的JavaScript库 用于构建用户界面 它以其高效的虚拟DOM和组件化开发的思想而闻名 使得构建复杂的Web应用程序变得更加简单和可维护 本篇博客将带您深入了解React的基本概念 组件化开发和状态管理 帮助您
  • FISCO BCOS(三十六)———Python Sdk window环境部署及应用开发

    1 环境要求 Python环境 python 3 6 3 3 7 3 最好是3 7 3 因为我是 FISCO BCOS节点 可以直接建链 可以是节点前置 也可以是一键部署 2 安装python 2 1 python下载地址 https ww
  • COM 组件设计与应用(十一)

    COM 组件设计与应用 十一 IDispatch 及双接口的调用作者 杨老师 下载源代码一 前言 前段时间 由于工作比较忙 没有能及时地写作 其间收到了很多网友的来信询问和鼓励 在此一并表示感谢 咳 我也需要工作来养家糊口呀 上回书介绍了两
  • linux系统函数总结(一)

    realpath include
  • c陷阱与缺陷

    第一章 词法陷阱 1 这一章没有太多 干货 唯一比较有趣的就是 1 3 语法分析中的 贪心法 所讲内容 这个 贪心 就是编译器会读入字符 如果能新读入的字符和之前所读入字符能组成符号 则编译器会继续读入下一个字符 直到读入的字符不能和之前的