当前位置: 首页 > news >正文

【C陷阱与缺陷】----语法陷阱

💯💯💯

要理解一个C程序,必须理解这些程序是如何组成声明,表达式,语句的。虽然现在对C的语法定义很完善,几乎无懈可击,大门有时这些定义与人们的直觉相悖,或容易引起混淆。语法细节决定语义,本篇总结C语法陷阱中的诸多细节,以供参考。

  • 导言:
  • Ⅰ. 理解函数的声明
    • 1.1函数的声明
    • 1.2类型转换
    • 1.3规则:
  • Ⅱ. 运算符的优先级问题
    • 2.1不同类型的运算符优先级问题
      • 优先级最高:() [ ] .
      • 第二高:单目
      • 第三高:双目
    • 2.2同类运算符之间相对优先级问题
  • Ⅲ. 函数调用
  • Ⅳ. 注意作为语句结束标志的分号
  • Ⅴ. “悬挂”else引发的问题
  • Ⅵ. switch语句

导言:

由于一个程序错误可以从不同层面采用不同方式进行考察,而根据程序错误与考察程序的方式之间的相关性,可以将程序错误进行划分为各种陷阱与缺陷:
①.词法“陷阱”
②.语法“陷阱”
③.语义“陷阱”
④.连接问题
⑤.库函数问题
⑥.预处理器问题
⑦.可移植性缺陷

Ⅰ. 理解函数的声明

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

你知道这个表达式表示什么吗?
:调用一个首地址为0的函数。

要理解这个表达式我们需要从两个方面入手:函数如何声明的,与类型如何转换的。

1.1函数的声明

任何C变量的声明都是由两部分组成:类型以及变量。

float f,g;

这个声明的含义是:当对其求值时,表达式f和g的类型为浮点数类型

float ff();

这个声明的含义是:表达式ff()求值结果是一个浮点数,也就是说ff是一个返回值为浮点数类型的函数。

float *pf;

这个声明的函数是*pf是一个浮点数,也就是说,pf指向的数是个浮点数。pf是一个指向浮点数的指针。

float *f(), (*h)();

同理,那*f() ,(*h)(),就是浮点表达式
因为函数调用()结合优先级是高于解引用 *
,*f(),也就是 *(f()):f是个函数,返回值是一个指向浮点数的指向。
h呢是一个函数指针,h指向的函数的返回值是浮点类型。

如果假设pf为函数指针,那么如何调用fp所指向的函数呢?
首先pf就是指针指向的函数,那么对它调用就可以了。
不过注意要这样写:(*pf)();
因为函数运算符()的优先级高于单目运算符。如果
pf两侧没有括号,那么 pf()就与 (pf())一样了。

那我们如果想调用一个首地址为0的函数应该如何调用呢?
这样?:(* 0)();
上式并不能生效,因为运算符必须要一个指针来做操作数。
而且这个指针还应该是一个函数指针,这样经过运算符
作用后的结果才能能作为函数被调用。
所以必须要对上式的0进行类型转换。
转换的发现我们可以描述为:指向函数值为void,参数为void的函数的指针。
也就是这个0必须转换为函数指针,而这个函数指针指向的函数参数为void,返回值也为void。
那该如何转换呢?

1.2类型转换

其实我们一旦找到任何声明一个给定类型的变量,那么该类型的类型转换符就很容易得到了:只要将声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个封装起来即可。

float (*pf)();

pf表示的是一个指向函数指针,指向的是函数参数是void,返回值为float。
也就是指向返回值为浮点类型的函数的指针。
float (*)()去掉变量名与分号
再加上一个括号( float (*)() )
这就表示一个“指向返回值为浮点类型的函数的指针”的类型转换符。
所以我们如果将0类型转换为“一个指向返回值为void的函数的指针”类型,就首先要知道一个该类型是如何声明。

如果pf是一个指向返回值为void类型的函数指针,那么(*pf)()的值为void,pf的声明如下:

void (*pf)();

所以该类型的类型转换符为:

( void (*)() )

所以将0强制类型转换为“指向返回值为void的函数指针”则为如下表示
( void (*)() )0
而对于该函数指针要是调用该函数指针所指向的函数的话,应该如下表示:

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

所以该表达式表达的也就是,调用一个函数,该函数的首地址为0,返回值为0,参数也为0.

1.3规则:

按照使用的方式来声明

Ⅱ. 运算符的优先级问题

2.1不同类型的运算符优先级问题

运算符优先级有那么多,记住它们并不是一件容易的事
在这里插入图片描述

所有以我们应该对它们进行恰当的分组,理解各组运算符之间的相对优先级。这样记忆起来就很快了。

优先级最高:() [ ] .

优先级最高的其实并不是真正意义上的运算符,包括:数组下标,函数调用操作符,结构体成员访问操作符。它们都是从左向右结合的。所以a.b.c的含义是(a.b).c

第二高:单目

单目运算符的优先级仅次于前述运算符。
在所有真正意义上的运算符中,它们的优先级最高。因为函数调用的优先级要高于单目运算符的优先级。
类型转换()也是单目运算符,它的优先级和其他单目运算符的优先级一样。单目运算符是自右向左结合,因此*p++会被编译器解释为 *(p++),即p的地址+1,而不是p指向的对象+1

第三高:双目

优先级比单目运算符要低的,接下来就是双目运算符。双目运算符中,算术运算符的优先级最高,移位运算符次之,关系运算符再次之,接着解释逻辑运算符,赋值运算符,最后是条件运算符。

我们需要记住的两点就是

  1. 任何一个逻辑运算符的优先级低于任何一个关系运算符
  2. 移位运算符的优先级比算术运算符要低,但是比关系运算符高。

算术>移位>关系>逻辑>赋值>条件

2.2同类运算符之间相对优先级问题

属于同一类型的各个运算符之间的相对优先级,理解起来一般没有什么困难。但是,6个关系运算符的优先级并不相同。

1.运算符==和!=的优先级要低于其他关系运算符的优先级。
因此我们如果要比较a与b的相对大小顺序是否和c与d的相对顺序一样,就可以这样写:

a<b==c<d

2.任何两个逻辑运算符都具有不同的优先级。所有的按位运算符优先级要比顺序运算符的优先级高,每个"与"运算符要比对应的"或"运算符优先级高,而按位异或(^运算符)的优先级介于按位与运算符和按位或运算符之间。

3.在所有的运算符中,三目条件运算符优先级最低。这就可以在条件运算符的条件表达式中包含关系运算符的逻辑组合,因为先处理的是关系运算符,最后再处理三目条件逻辑符。

4.所有的赋值运算符的优先级是一样的,而且它们的结合方式是从右到左。

所以
a=b=0;
与
b=0;
a=b;
表达的意思是一样的。

5.在所有的运算符中,逗号运算符的优先级最低。

Ⅲ. 函数调用

C语言要求:在函数调用的时候,即使函数不带参数,也要将函数参数列表括号写下来。因此,如果 f 是一个函数,f();则表示一个函数调用语句,而f;却是一个什么都不做的语句,这个语句虽然计算函数f的地址,但不调用该函数。

Ⅳ. 注意作为语句结束标志的分号

在C语言中如果不小心多写了一个分号可能不会造成什么不良后果:
1.这个分号可能会被视为一个不会产生任何实际效果的空语句
2.或者编译器会因为这个分号,产生警告信息,根据信息去掉这个分号。

但也会有例外发现,有时会造成很大的差别:
1.如果在if或者while语句之后多了一个分号,那么原来紧跟在if或者while之后的语句就是一个单独的语句,与条件判断部分没有任何关系了。
2.当不是多写了一个分号,而是遗漏一个分号,同样会招致麻烦,比如return 语句后面的分号忘记写了,则会将return 后面的语句作为操作数,进行返回。
3.当一个声明的结尾紧跟一个函数定义时,如果声明结尾的分号被省略,编译器可能会把 声明的类型视为函数的返回值类型。

Ⅴ. “悬挂”else引发的问题

C语言中规定:else始终与同一对括号内最近的未匹配的if结合。
也就是就近原则,它会和离它最近的if相结合。当然这必须在同一个括号里。如果在不同的括号里,那么就不遵循了。

int main()
{int a = 0, y = 1;if (a == 0)if (y == 0)printf("正确\n");else{printf("错误\n");}return 0;
}

比如第一个if里面的判断条件为a是否等于0
该代码的本意是else为a不为0时进行的代码,但真正的是else与第二个if相匹配了,
也就是else里的判断条件变成了y不为0时进行的代码。
如果要得到原来的例子中编程者本意的结果,应该这样写:

int main()
{int a = 0, y = 1;if (a == 0){if (y == 0)printf("正确\n");}else{printf("错误\n");}return 0;
}

现在,else与第一个if结合,即使它离第二个if更近也不会改变,因为此时第二个if已经被括号“封装”起来了。

Ⅵ. switch语句

switch语句的特点就是包含break;当遇到break,语句立刻结束。
C语言中switch语句的这种特性,既是它的优势,也是它的一大弱点。说它是弱点,是因为程序员很容易遗漏各个case部分的break语句,造成一些难以理解的程序行为。
说它是优势,是因为如果程序员有意的省略一个break语句,就可以表达出一些采用其他方式很难方便地加以实现的程序控制结构。
特别是对于一些大的swtich,我们经常发现各个分支的处理大同小异:对于某个分支情况的处理只要稍作修改,或者不修改,就也符合程序的要求。
比如这样的一段代码,它的作用是查找符号时跳过程序中的空白字符,在这里,空格键,制表符,换行符的处理都是相同的,除了遇到换行符时程序的代码行计数器需要进行递增。其他都是一样,所以我们可以省略break,程序照样可以运行甚至更好。

case '\n':linecount++;//该处省略break语句case '\t':case ' ':

相关文章:

【C陷阱与缺陷】----语法陷阱

&#x1f4af;&#x1f4af;&#x1f4af; 要理解一个C程序&#xff0c;必须理解这些程序是如何组成声明&#xff0c;表达式&#xff0c;语句的。虽然现在对C的语法定义很完善&#xff0c;几乎无懈可击&#xff0c;大门有时这些定义与人们的直觉相悖&#xff0c;或容易引起混淆…...

虹科分享| 关于TrueNAS十问十答

上一篇文章我们向您介绍了虹科新品HK-TrueNAS企业存储&#xff0c;很多小伙伴会疑问到底什么是NAS存储&#xff0c;之前常用的磁盘、磁带属于什么存储架构&#xff0c;NAS存储好在哪里&#xff0c;什么时候使用NAS&#xff1f;今天我们整理了关于TrueNAS的十问十答&#xff0c;…...

Https 笔记

HTTP TLS TLS 的前身是 SSL 非对称加密的核心&#xff1a; 两个密钥&#xff08;公私&#xff09; https 需要第三方CA&#xff08;证书授权中心&#xff09;申请SSL证书以确定其真实性 证书种包含了特定的公钥和私钥 密钥交换 自己将私钥上锁后发给对方对方也上锁 在还回来…...

【Python+requests+unittest+excel】实现接口自动化测试框架

一、框架结构&#xff1a; 工程目录 二、Case文件设计 三、基础包 base 3.1 封装get/post请求&#xff08;runmethon.py&#xff09; 1 import requests2 import json3 class RunMethod:4 def post_main(self,url,data,headerNone):5 res None6 if heade…...

MySQL终端的使用及其数据类型的使用

什么是数据库&#xff1f;数据库&#xff08;Database&#xff09;是按照数据结构来组织、存储和管理数据的仓库。每个数据库都有一个或多个不同的 API 用于创建&#xff0c;访问&#xff0c;管理&#xff0c;搜索和复制所保存的数据。我们也可以将数据存储在文件中&#xff0c…...

长视频终局:一场考验资金储备的消耗战

赢者通吃&#xff0c;似乎已成为各行各业的常识&#xff0c;但事实真的是这样吗&#xff1f;20世纪70年代&#xff0c;石油价格高涨&#xff0c;在墨西哥湾油田拍卖中高价拍得油田的企业&#xff0c;要么亏损&#xff0c;要么收入低于预期&#xff0c;但仍然有无数企业在高价竞…...

javaEE初阶 — CSS 常用的属性

文章目录CSS 常用的属性1 字体属性1.1 设置字体家族 font-family1.2 设置字体大小 font-size1.3 设置字体粗细 font-weight1.4 文字倾斜 font-style2 文本属性2.1 文本颜色2.2 文本对齐2.3 文本装饰2.4 文本缩进2.5 行高3 背景属性3.1 背景颜色3.2 背景图片3.3 背景位置3.4 背景…...

【面试题】如何取消 script 标签发出的请求

大厂面试题分享 面试题库前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面试题库问题之前在业务上有这样一个场景&#xff0c;通过 script 标签动态引入了一个外部资源&#xff0c;具体方式是这样的const script document.…...

蓝桥杯嵌入式(G4系列):RTC时钟

前言&#xff1a; 关于RTC时钟的HAL库配置我也是第一次&#xff0c;之前都是用库函数的写法&#xff0c;这里写下这篇博客来记录一下自己的学习过程。 STM32Cubemx配置&#xff1a; 首先点击左侧的Timers的RTC&#xff0c;勾选以下选项 进入时钟树配置 进入时间设置&#xff0…...

Linux——进程间通信1

目录 进程间通信目的 进程间通信标准 管道 匿名管道 管道实现进程间通信 管道的特点 进程池 ProcessPool.cc Task.hpp 习题 进程间通信目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程 资源共享&#xff1a;多个进程之间共享同样的资源。 通知事件…...

循环语句——“Python”

各位CSDN的uu们你们好呀&#xff0c;今天小雅兰的内容是Python中的循环语句呀&#xff0c;分为while循环和for循环&#xff0c;下面&#xff0c;让我们进入循环语句的世界吧 循环语句 while循环 for循环 continue和break 循环语句小结 人生重开模拟器 设置初始属性 设置性别…...

Python synonyms查找中文任意词汇的同义词近义词

Python synonyms查找中文任意词汇的同义词近义词 作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 一、安装 对于非专业的开发人员来说可以简单的使用Python一行代码来找到同义词。这…...

三分钟了解http和https

对应测试人员都会听过http请求和响应.在这里给大家介绍http相关的知识 一.http和https基本概念 HTTP&#xff1a;是互联网上应用最为广泛的一种网络协议&#xff0c;是一个客户端和服务器端请求和应答的标准&#xff08;TCP&#xff09;&#xff0c;用于从WWW服务器传输超文本…...

docker应用:搭建私有云盘

简介&#xff1a;NextCloud是一个开源的云存储解决方案&#xff0c;可以在自己的服务器上搭建个人云存储系统。它提供了与市面上主流云存储服务&#xff08;如Dropbox、Google Drive&#xff09;相似的功能&#xff0c;包括文件存储、共享、同步、协作等。NextCloud的主要优势在…...

【C++进阶】面向对象

程序 编写程序是为了让计算机解决现实生活中的实际问题。pascal之父、结构化程序设计先驱Niklaus Wirth提出程序 算法 数据结构。程序是完成一定功能的一些列有序指令的集合。指令 操作码 指令。将指令按一定的顺序进行整合&#xff0c;就形成了程序。 机器语言与汇编语言…...

从ChatGPT与New Bing看程序员为什么要学习算法?

文章目录为什么要学习数据结构和算法&#xff1f;ChatGPT与NEW Bing 的回答想要通关大厂面试&#xff0c;就不能让数据结构和算法拖了后腿业务开发工程师&#xff0c;你真的愿意做一辈子CRUD boy吗&#xff1f;对编程还有追求&#xff1f;不想被行业淘汰&#xff1f;那就不要只…...

SpringBoot-实用开发篇

SpringBoot开发实用篇开发实用篇中因为牵扯到SpringBoot整合各种各样的技术&#xff0c;所以在整合每一个技术之前&#xff0c;都会做一个快速的普及&#xff0c;这样的话内容整个开发实用篇所包含的内容就会比较多。在学习的时候&#xff0c;如果对某一个技术不是很清楚&#…...

Python进阶-----高阶函数->filter() 函数

目录 前言&#xff1a; filter() 函数介绍 filter() 函数使用示例 1.与循环对比 2.与lambda函数综合使用 3.使用None过滤False 4.过滤字典相关数据 前言&#xff1a; 家人们&#xff0c;当你们获取了一个序列的时候&#xff0c;想要把一些内容去掉&#xff0c;保留一部分…...

C/C++面试可能会问三:指针和数组一样吗?

答案&#xff1a;不一样。 哪里不同&#xff1f; 数组名&#xff1a;数组名的值是一个指针常量&#xff0c;也就是数组第一个元素的地址。 它的类型取决于数组元素的类型&#xff1a;如果他们是int类型&#xff0c;那么数组名的类型就是“指向int的常量指针”&#xff1b;如果…...

数字经济新生态,中小企业如何发展营销数字化

五年弹指一挥间&#xff0c;中国数字经济正从尝试探索迈向快速发展&#xff0c;这一趋势&#xff0c;从今年两会的国务院机构改革、总理政府工作报告、部长通道答疑解惑、科技领域大佬提案中都能看出来。 在政府工作报告中&#xff0c;我们可以看到数字经济在不断壮大&#xff…...

基于算法竞赛的c++编程(28)结构体的进阶应用

结构体的嵌套与复杂数据组织 在C中&#xff0c;结构体可以嵌套使用&#xff0c;形成更复杂的数据结构。例如&#xff0c;可以通过嵌套结构体描述多层级数据关系&#xff1a; struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

《基于Apache Flink的流处理》笔记

思维导图 1-3 章 4-7章 8-11 章 参考资料 源码&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...

【JavaSE】绘图与事件入门学习笔记

-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角&#xff0c;以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向&#xff0c;距离坐标原点x个像素;第二个是y坐标&#xff0c;表示当前位置为垂直方向&#xff0c;距离坐标原点y个像素。 坐标体系-像素 …...

tree 树组件大数据卡顿问题优化

问题背景 项目中有用到树组件用来做文件目录&#xff0c;但是由于这个树组件的节点越来越多&#xff0c;导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多&#xff0c;导致的浏览器卡顿&#xff0c;这里很明显就需要用到虚拟列表的技术&…...

Element Plus 表单(el-form)中关于正整数输入的校验规则

目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入&#xff08;联动&#xff09;2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

BLEU评分:机器翻译质量评估的黄金标准

BLEU评分&#xff1a;机器翻译质量评估的黄金标准 1. 引言 在自然语言处理(NLP)领域&#xff0c;衡量一个机器翻译模型的性能至关重要。BLEU (Bilingual Evaluation Understudy) 作为一种自动化评估指标&#xff0c;自2002年由IBM的Kishore Papineni等人提出以来&#xff0c;…...