【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 ' ':

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【机器学习】综述:机器学习中的模型评价、模型选择与算法选择

文章目录一、前言二、论文摘要三、简介&#xff1a;基本的模型评估项和技术3.1 性能评估&#xff1a;泛化性能 vs. 模型选择四、Bootstrapping 和不确定性五、交叉验证和超参数优化一、前言 最近在做实验的时候&#xff0c;发现树模型有过拟合的情况发生&#xff0c;为此&…

蓝桥杯每日一真题—— [蓝桥杯 2021 省 AB2] 完全平方数(数论,质因数分解)

文章目录[蓝桥杯 2021 省 AB2] 完全平方数题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1样例 #2样例输入 #2样例输出 #2提示思路&#xff1a;理论补充&#xff1a;完全平方数的一个性质&#xff1a;完全平方数的质因子的指数一定为偶数最终思路&#xff1a;小插曲&am…

直面风口,未来不仅是中文版ChatGPT,还有AGI大时代在等着我们

说到标题的AI2.0这个概念的研究早在2015年就研究起步了&#xff0c;其实大家早已知道&#xff0c;人工智能技术必然是未来科技发展战略中的重要一环&#xff0c;今天我们就从AI2.0入手&#xff0c;以GPT-4及文心一言的发布为切入角度&#xff0c;来谈一谈即将降临的AGI时代。 关…

Linux搭建GitLab私有仓库,并内网穿透实现公网访问

文章目录前言1. 下载Gitlab2. 安装Gitlab3. 启动Gitlab4. 安装cpolar5. 创建隧道配置访问地址6. 固定GitLab访问地址6.1 保留二级子域名6.2 配置二级子域名7. 测试访问二级子域名前言 GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c…

基于Springboot实现商务安全邮箱邮件收发 源码+论文展示

基于Springboot实现商务安全邮箱邮件收发 源码论文开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Ma…

【多线程】定时器和线程池

✨个人主页&#xff1a;bit me&#x1f447; ✨当前专栏&#xff1a;Java EE初阶&#x1f447; ✨每日一语&#xff1a;种一棵树最好的时间是十年前&#xff0c;其次是现在。 目 录⌚️一. 定时器&#x1f4c4;1. 定时器是什么&#x1f4c3;2. 标准库中的定时器&#x1f4d1;3.…

【C语言初阶】初识C语言 | C语言知识预览

文章目录&#x1f490;专栏导读&#x1f490;文章导读&#x1f337;什么是C语言&#xff1f;&#x1f337;第一个C语言程序&#x1f337;数据类型&#x1f337;变量、常量、字符串&#x1f33a;定义变量的方法&#x1f33a;变量的分类&#x1f33a;变量的使用&#x1f33a;变量…

DJ2-5 DNS:Internet 的目录服务

目录 1. DNS 简介 2. DNS 服务器提供的功能 3. 分布式、层次数据库 4. DNS 查询方法 5. DNS 缓存和权威 DNS 服务器记录更新 6. DNS 记录 7. DNS 报文 8. 在 DNS 数据库中插入记录 9. DNS 攻击 1. DNS 简介 名称&#xff1a;Domain Name System DNS 是&#xff1a; …

vue面试题(day06)

文章目录前言请谈谈WXML与标准的html的异同&#xff1f;请谈谈WXSS和CSS的异同&#xff1f;请谈谈微信小程序主要目录和文件的作用&#xff1f;请谈谈小程序的双向绑定和vue的异同&#xff1f;简单描述下微信小程序的相关文件类型&#xff1f;微信小程序有哪些传值(传递数据)方…

【新星计划2023】SQL SERVER (01) -- 基础知识

【新星计划2023】SQL SERVER -- 基础知识1. Introduction1.1 Official Website1.2 Conn Tool2. 基础命令2.1 建库建表2.2 Alter2.3 Drop2.3 Big Data -- Postgres3.Awakening1. Introduction 1.1 Official Website 官方文档&#xff08;小技巧&#xff09; Officail Website: …

十个Python图像处理工具,不可不知

这些Python库提供了一种简单直观的方法来转换图像并理解底层数据。 今天的世界充满了数据&#xff0c;图像是这些数据的重要组成部分。但是&#xff0c;在使用它们之前&#xff0c;必须对这些数字图像进行处理 - 分析和操作&#xff0c;以提高其质量或提取一些可以使用的信息。…

【C++学习】继承

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《C学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; C是面向对象的编程语言&#xff0c;它有很多的特性&#xff0c;但是最重要的就是封装&#xff0c;继承…

【3DoF算法】

VR 3DoF算法介绍 核心&#xff1a;3DoF算法应用场景&#xff0c;在VIO应用中&#xff0c;当只有测量没有观测的情况下&#xff0c;6DoF算法的预测会退化成一个只有测量的3DoF算法&#xff0c;这时候需要使用3DoF算法&#xff0c;来更加稳定准确的获取3DoF位姿&#xff0c;直到…

【VSCode】Windows 下搭建 Fortran 环境

文章目录Part.I 预备知识Part.II 安装与配置Chap.I 编译环境Chap.II 插件Part.III 测试Chap.I 一个示例Chap.II 注意事项Part.I 预备知识 Fortran 是一种比较古老的语言了&#xff0c;当时作为一种科学计算工具&#xff0c;还是比较火的&#xff0c;因为很多有名的软件都是基于…

LFM雷达实现及USRP验证【章节2:LFM雷达测距】

目录 1. 参数设计 几个重要的约束关系 仿真参数设计 2. matlab雷达测距代码 完整源码 代码分析 回顾&#xff1a;LFM的基本原理请详见第一章 本章节将介绍LFM雷达测距的原理及实现 1. 参数设计 几个重要的约束关系 带通采样定理&#xff1a; 因此如果我们B80MHz时&a…

SQL优化13连问,收藏好!

1.日常工作中&#xff0c;你是怎么优化SQL的&#xff1f; 大家可以从这几个维度回答这个问题&#xff1a; 分析慢查询日志 使用explain查看执行计划 索引优化 深分页优化 避免全表扫描 避免返回不必要的数据&#xff08;如select具体字段而不是select*&#xff09; 使用…

【Android -- 开发工具】Xshell 6 安装和使用教程

一、简介 Xshell 其实就是一个远程终端工具&#xff0c;它可以将你的个人电脑和你在远端的机器连接起来&#xff0c;通过向 Xshell 输入命令然后他通过网络将命令传送给远端Linux机器然后远端的Linux机器将其运行结果通过网络传回个人电脑。 二、Xshell 6 的安装 首先&#…

如何通过命令行查看CentOS版本信息和linux系统信息

1.如何查看已安装的CentOS版本信息&#xff1a; 1.cat /proc/version 2.uname -a 3.uname -r 4.cat /etc/centos-release 5.lsb_release -a 6.hostnamectl1. 第一种方式输出的结果是&#xff1a; Linux version 3.10.0-1127.el7.x86_64 (mockbuildkbuilder.bsys.centos.org) …

算法基础-回溯算法

回溯算法大致分为以下几类&#xff1a; 组合&#xff1a;组合、组合总和、电话号码的字母组合 分割&#xff1a;分割回文串、复原IP地址 子集&#xff1a;子集 排列&#xff1a;全排列 棋盘问题&#xff1a;N皇后、解数独 其他&#xff1a;递增子序列、重新安排行程 一、什么是…

gns3:动态路由(ospf) area0 骨干网络(域间)(ABR)+ ospf 连接 rip (外部)(ASBR)+ 区域划分

1.配置好接口ip 全部处于up状态2.配置好lookback口 增加一个虚拟直连网段全部为 255.255.255.0的子网掩码实现上边ospf之间通信r1的全局模式router ospf 1network 192.168.1.0 0.0.0.255 area 1network 1.1.1.0 0.0.0.255 area 1宣告直连 并且划分area 区域为1r2全局模式router…