C语言词法陷阱

目录

前言

1、理解函数声明

最简单的声明符

函数和指针类型

其它组合 

typedef简化

2、运算符优先级

C语言运算符优先级表

补充内容

3、作为语句结束标志的分号

多写分号

漏写分号

分号与函数声明 

4、switch语句

5、函数调用

6、“悬挂”else引发的问题


前言

        要理解一个C程序,仅仅理解组成该程序的符号,程序员还必须理解这些符号如何组成声明、表达式、语句和程序的。虽然这些组合方式的定义都很完善,几乎无懈可击,但有时这些定义与人们的直觉相悖,或者容易引起混淆。本篇将讨论一些用法和意义与我们想当然的认识不一致的语法结构。

1、理解函数声明

        任何一个C变量的声明都由两部分组成:类型一组类似表达式的声明符。声明符从表面上看与表达式有些类似,对它求值应该返回一个声明中给定类型的结果.

最简单的声明符

最简单的声明符就是单个变量:

float f,g;

含义:当对其求值时,表达式f和g的类型都是浮点数类型

因为声明符与表达式相似,所以我们也可以在声明符中使用一些有意义的括号:

float ((f));

含义:当对其求值时,((f))的类型为浮点型,即f的类型为浮点型

函数和指针类型

float ff();

含义:表达式ff()的求值结果是一个浮点数,即ff是一个返回值为浮点类型的函数

float *pf

含义: *pf是一个浮点数,即pf是一个指向浮点数的指针

其它组合 

以上这些声明形式还可以组合起来,就像在表达式中进行组合一样:

float *g();

解释:*g()与(*h)()都是浮点表达式,因为()的结合优先级高于*,*g()等价于*(g()) ,g是一个函数,该函数的返回类型为指向浮点数的指针;

Void (* signal(int ,void(*)(int) ) (int)

解释:signal是一个函数名,(int , void(*)(int))是signal函数的两个参数,一个是int型,另一个是函数指针类型,该函数指针类型指向的函数参数是int型,返回类型是void,signal(int,void(*)(int))就相当于对signal函数的声明,如果我们把这个函数声明的整体假设为m,那么这行代码就会变成void(* m)(int),*m就是一个新的函数指针,该函数指针指向的函数参数是int型,返回的类型为void型。

typedef简化

typedef可以简化上面的函数声明:

//未简化版本
Void (* signal(int ,void(*)(int) ) (int)typedef void (*HANDLER)(int);//简化版本
HANDLER signal (int,HANDLER);

 解释:将 void (*)(int) 定义为 HANDLER 类型,这是因为void(*)(int)是一个无主的函数指针类型我们现在将它的主人规定为HANDLER,同样的将原来式子变为Void (* signal(int ,HANDLER ) (int)后我们可以将signal(int ,HANDLER )看作一个声明符k,那么这时候就变成了void (*k)(int),所以此时k的类型就是void (*)(int)也就是HANDLER,即HADNLER k,最后将k替换为原式子即可

2、运算符优先级

C语言运算符优先级表

运算符结合性
()、[ ]、->、.自左向右
!、~、++、-、(类型)、*、&、sizeof自右向左
*、/、%自左向右
+、-自左向右
<<、>>自左向右
>、>=、<、<=自左向右
==、!=自左向右
&自左向右
^自左向右
|自左向右
&&自左向右
||自左向右
?:自右向左
assignments自右向左
=、/=、*=、%=、+=、-=、<<=、>>=、&=、^=、|=自右向左
,自左向右

如果想要查看更详细的优先级表格请查看:C语言进制转换、操作符万字详解 

补充内容

1、优先级最高者其实并不是真正意义上的运算符,包括数组下标、函数调用操作符、各结构体成员选择操作符。它们都是自左向右结合,因此a.b.c的含义是(a.b).c,而不是a.(b.c)

2、单目运算符的优先级仅次于前述运算符,在所有真正意义上的运算符中,它们的优先级最高

3、类型转换也是单目运算符,它的优先级和其它单目运算符的优先级一样,单目运算符是自右向左结合,因此*p++会被编译器解释称*(p++),即取指针p所指向的对象,然后将p递增1;而不是(*p)++,即取指针p所指向的对象,然后将该对象递增1

4、运算符优先级:单目运算符>算术运算符>移位运算符>关系运算符>逻辑运算符>赋值运算符>条件运算符(三目运算符)

5、最需要注意的是它们的优先级,算术运算符>移位运算符>关系运算符>逻辑运算符

6、同一类型的各个运算符之间的相对优先级相同,但是6个关系运算符的优先级并不完全相同,关系运算符==和!=的优先级要低于其它关系运算符

7、任何两个逻辑运算符都具有不同的优先级,所有的按位与运算符优先级要高于顺序运算符,任何的与运算符要比或运算符优先级高,按位异或运算符的优先级小于按位与运算符大于按位或运算符

8、赋值运算符的优先级都一样,结合方式均为自右向左,涉及赋值运算符时经常会引起优先级的混淆,比如我们想要利用循环语句复制一个文件到另一个文件时,写出来这样的语句:

while(c=getc(in) != EOF)putc(c,out);

        c似乎时先被赋予getc(in)的返回值,然后与EOF比较是否到达文件结尾以便决定是否终止循环,然而由于赋值运算符的优先级要低于任何一个比较运算符,因此c的值实际上是函数getc(in)的返回值与EOF比较的结果,此处函数getc(in)的返回值只是一个临时变量,在与EOF比较后就被“丢弃”了,因此,最后得到的文件“副本”中只包括了一组二进制值为1的字节流,故实际上代码应该写成:

while((c=getc(in)) != EOF)putc(c,out);

如果表达式再复杂一点,这类错误就很难被察觉,例如:

if( (t = BTYPE(pt1->aty) == STRTY)  || t == UNIONTY)

这段代码的本意是首先赋值给t,然后判断t是否等于STYPR或者UNIONTY,但是实际结果却大相径庭:根据BTYPE(pt1->aty)的值是否等于STRTY,t的取值或者为1或者为0;如果t取值为0,还将进一步与UNIONTY比较

3、作为语句结束标志的分号

多写分号

        在C程序中,如果不小心多写了一个分号,可能不会造成什么不良后果,这个分号可能会被视为一个不会产生任何实际效果的空语句;或者编译器会因为这个多余的符号报错,根据报错信息很容易就能去掉这个分号,但是在if或while语句之后仅跟一条语句时,如果此时多了一个分号,那么原来紧跟在if与while之后的语句就是一条单独的语句,与条件判断部分没有了任何关系:

if (x[i] > big);big = x[i];

它相当于:

if (x[i] > big) {}big = x[i];

漏写分号

如果不是多写一个分号,而是遗漏了一个分号,同样会引起不必要的麻烦:

if(n<3)return
logrec.date = x[0];
logrec.time = x[1];
logrec.code = x[2];

这段代码可以正常运行,只不过将logrec.date = x[0];当作了return语句的操作数,即:

if(n<3)return logrec.date = x[0];
logrec.time = x[1];
logrec.code = x[2];

        如果这段代码所在的函数声明其返回值为void,编译器会因为实际返回值的类型与声明返回值的类型不一致而报错,然而如果一个函数不需要返回值(即返回值为void)我们通常会在函数声明时省略返回值类型,但是此时对编译器而言会隐含地将函数返回值类型视为int类型,如果是这样,上面的出错误就不会被编译器检测到,在上面的例子中,当n>=3时,第一个赋值语句会被直接跳过,由此造成的错误可能会是一个潜伏很深、极难发现的Bug。

分号与函数声明 

当一个声明的结尾紧跟一个函数定义时,有分号与没分号的实际效果相差极为不同,如果声明结尾的分号被省略,编译器可能会把声明的类型视作函数的返回值类型:

struct logrec{int date;int time;int code;...
}main()
{...
}

在第一个}与main之间遗漏了一个分号,因此,上面代码段的实际效果是声明函数main的返回值是struct logrec类型 ,这样会产生报错,同时需要注意的是根据 C 语言标准,如果 main() 函数没有显式指定返回值或者使用了不匹配的返回类型,那么编译器会默认将其视为具有 int 返回类型

4、switch语句

case语句是switch语句的优势所在,但也是它的一大弱点

        说是它的一大弱点,是因为程序员很容易遗漏各个case部分的break语句,造成一些难以理解的程序行为,说它是优势所在,是因为如果程序员有意去忽略一个break语句,则可以表达出一些采用其他方式很难方便地加以实现地程序控制结构。特别是对于一些大地switch语句,我们常常会发现各个分支地处理大同小异:对某个分支情况的处理只要稍微改动,剩余部分就完全等同于另一个分支情况下地处理。

        考虑这样一段代码,它的作用是一个编译器在查找符号时跳过程序中的空白字符空格键、制表符和换行符的处理都是相同的,不过在遇到换行符时,程序的代码行计数器需要进行递增:

case ‘\n’:linecount++;/*这里没有break*/case ‘\t’://不执行任何具体语句,只需要在遇到换行符时linecount++case ‘\t’://不执行任何具体语句,只需要在遇到换行符时linecount++...

5、函数调用

        与其它程序设计语言不同,C语言要求:在函数调用时,即使函数不带参数,也应该包括参数列表。因此,如果f是一个函数,那么:

f()

是一个函数调用语句,而:

f;

 却是一个什么都不做的语句,确切的说,这个语句计算函数f的地址,但并不调用该函数......

6、“悬挂”else引发的问题

问题产生原因:else会与离其最近的if语句匹配

看这样一段代码:

//当if个数大于else个数时就会出现悬空else问题,具体情况如下://代码运行结果为空#include <stdio.h>
int main()
{
int a = 0;
int b = 2;
if(a == 1)if(b == 2)printf("hehe\n");   
elseprintf("haha\n");return 0;
}                

这段代码的实际情况是:

//代码运行结果为空#include <stdio.h>
int main()
{
int a = 0;
int b = 2;
if(a == 1)   //a != 1,表达式结果为假内部if...else...语句不执行{if(b == 2)printf("hehe\n");   elseprintf("haha\n");}
return 0;
}             

 若想要else与第一个if匹配只需要这样改:

//代码运行结果为空#include <stdio.h>
int main()
{
int a = 0;
int b = 2;
if(a == 1)   //a != 1,表达式结果为假内部if...else...语句不执行{if(b == 2)printf("hehe\n");   elseprintf("haha\n");}
return 0;
}             

结论:if语句中对于{}的运用是十分重要的,可以增强代码的可读性以及减少问题的发生 

~over~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/202031.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

初识消息队列

1、消息 消息&#xff08;Message&#xff09;是指在应用间传送的数据。消息可以非常简单&#xff0c;比如只包含文本字符串&#xff0c;也可以更复杂&#xff0c;可能包含嵌入对象。 2、消息队列 消息队列&#xff08;Message Queue&#xff09;是一种应用间的通信方式&#…

学生档案管理系统研究

摘 要 学生档案管理系统是一个教育单位不可缺少的部分,它的内容对于学校的决策者和管理者来说都至关重要,所以学生档案管理系统应该能够为用户提供充足的信息和快捷的查询手段。但一直以来人们使用传统人工的方式管理文件档案&#xff0c;这种管理方式存在着许多缺点,如:效率低…

Ant Design正式推出企业级设计体系,抢先了解!

企业级产品设计体系AntDesign是蚂蚁集团体经过大量项目实践和总结&#xff0c;逐步打磨出的产品。随着这两年B端产品的逐渐白热化&#xff0c;越来越多的用户对更好的用户体验有了进一步的要求。 作为专门为国内生产研究团队量身定制的在线协作工具&#xff0c;设计师可以直接在…

Kubernetes(K8s)DashBoard的使用-11

DashBoard 之前在kubernetes中完成的所有操作都是通过命令行工具kubectl完成的。其实&#xff0c;为了提供更丰富的用户体验&#xff0c;kubernetes还开发了一个基于web的用户界面&#xff08;Dashboard&#xff09;。用户可以使用Dashboard部署容器化的应用&#xff0c;还可以…

tomcat AJP文件包含漏洞(CVE-2020-1938)

漏洞介绍 CVE-2020-1938 是一个影响 Tomcat 的 AJP 文件包含漏洞。攻击者可以利用该漏洞通过 Tomcat AJP Connector 读取或包含 Tomcat 上所有 webapp 目录下的任意文件,例如配置文件或源码。 如果目标应用有文件上传功能,攻击者还可以利用文件包含漏洞实现远程代码执行,造成…

【EI会议征稿中】第三届光学与机器视觉国际学术会议(ICOMV 2024)

第三届光学与机器视觉国际学术会议(ICOMV 2024) 2024 3rd International Conference on Optics and Machine Vision 第三届光学与机器视觉国际学术会议(ICOMV 2024)将于2024年1月19-21日在中国南昌举行。本次会议将围绕“光学”与"机器视觉”等研究领域展开讨论&#xf…

群晖Docker搭建HomeAssistant,结合内网穿透实现远程访问智能家居控制中心

使用群晖Docker搭建HomeAssistant并实现异地公网访问 文章目录 使用群晖Docker搭建HomeAssistant并实现异地公网访问一、下载HomeAssistant镜像二、内网穿透HomeAssistant&#xff0c;实现异地控制智能家居三、使用固定域名访问HomeAssistant HomeAssistant是一个可以控制 苹果…

java8 升级 java11

1.安装java11 1.1 安装参考 ​​​​​​LINUX安装JDK_liunx上安装ocean-CSDN博客 1.2 检查 java -version 2.Maven 项目pom文件修改 <properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEnc…

外包干了一个月,技术明显进步。。。。。

先说一下自己的情况&#xff0c;本科生生&#xff0c;19年通过校招进入南京某软件公司&#xff0c;干了接近3年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了3年的功能测试…

论文阅读 - VGAER: Graph Neural Network Reconstruction based Community Detection

https://arxiv.org/pdf/2201.04066.pdf 社群检测是网络科学中一个基础而重要的问题&#xff0c;但基于图神经网络的社群检测算法为数不多&#xff0c;其中无监督算法几乎是空白。 本文通过将高阶模块化信息与网络特征融合&#xff0c;首次提出了基于变异图自动编码器重构的社群…

Verilog中generate的用法

&#xff08;一&#xff09;generate语法 generate 在设计中&#xff0c;很多情况下需要编写很多结构相同但是参数不同的赋值语句或者逻辑语句&#xff0c;如果在参数量很大的的情况下&#xff0c;原本的列举就会显得心有余而力不足。c语言中常用for语句来解决此类问题&#xf…

手写深拷贝

手写深拷贝 前言: 需要先了解 JS 的数据类型 一、浅拷贝、深拷贝区别 浅拷贝会创建一个新的对象&#xff0c;新对象有着与原始对象相同的属性值&#xff0c;如果 属性是基本类型&#xff0c;拷贝的就是基本类型的值属性是引用类型&#xff0c;拷贝的就是内存地址&#xff0…

云服务器节点选择

随着云计算的发展&#xff0c;云服务器已经成为许多企业和个人的首选。但是&#xff0c;在选择云服务器节点时&#xff0c;面临的一个重要问题是如何在性能和成本之间取得平衡。本文将探讨云服务器节点选择的关键因素&#xff0c;并提供一些建议&#xff0c;以帮助用户做出明智…

算法——二分查找

二分算法简介&#xff1a; 二分查找算法只适用于数组有序的情况&#xff1f;&#xff08;只要数组中存在某种规律就可以用&#xff09;模版&#xff1a; 朴素的二分模版查找左边界的二分模版查找右边界的二分模版 朴素二分模版 while(left < right){int mid left (right-l…

Qt之QGraphicsView —— 笔记1:绘制简单图元(附完整源码)

效果 相关类介绍 QGraphicsView类提供了一个小部件,用于显示QGraphicsScene的内容。QGraphicsView在可滚动视口中可视化。QGraphicsView将滚动其视口,以确保该点在视图中居中。 QGraphicsScene类 提供了一个用于管理大量二维图形项的场景。请注意,QGraphicsScene没有自己的视…

【Openstack Train】十六、swift安装

OpenStack Swift是一个分布式对象存储系统&#xff0c;它可以为大规模的数据存储提供高可用性、可扩展性和数据安全性。Swift是OpenStack的一个核心组件&#xff0c;它允许用户将大量的数据存储在云上&#xff0c;并且可以随时访问、检索和管理这些数据。 Swift的设计目标是为了…

Meta开源最大多模态视频数据集—Ego-Exo4D

社交、科技巨头Meta联合15所大学的研究机构&#xff0c;经过两年多的努力发布了首个多模态视频训练数据集和基础套件Ego-Exo4D&#xff0c;用于训练和研究AI大模型。 据悉&#xff0c;该数据集收集了来自13个城市839名参与者的视频,总时长超过1400小时,包含舞蹈、足球、篮球、…

网络通信的流程,浏览器地址?

1.没有交换机的通信 在一个机房内,有两台电脑相互需要通信 假设现在有三台电脑: 随着电脑的增加,线的数量也在增加,因此显得很臃肿&#xff0c;次数交换机诞生&#xff0c;很好的解决了这一方面&#xff0c; 交换机不需要进行多条线的连接: 通过给设备分配,ip地址来实现局域网…

掌握终端,尽在ZOC for Mac – 最强大的终端仿真器!

在数字时代&#xff0c;终端仿真器是专业人士和开发者必备的工具之一。而ZOC for Mac将为您提供无与伦比的终端体验&#xff0c;助力您更轻松地管理远程连接、维护服务器和进行编程任务。 ZOC for Mac的卓越功能&#xff1a; 多协议支持&#xff1a;ZOC支持Telnet、SSH、SSH2、…