《C缺陷和陷阱》-笔记(2)

news2024/9/23 3:34:02

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

文章目录

前言

一、理解函数声明

1.(*(void(*)( ))0)( );

2.signal 函数接受两个参数:

3.使用typedef 简化函数声明:

二、运算符的优先级问题

1.if (flags FLAG)

2.r= his << 4 + low:

C语言运算符优先表

三、注意作为语句结束标志的分号

1.多写分号的影响

2.少写分号的影响

3.分号被省略

四、悬挂”else引发的问题


前言

要理解一个C程序,仅仅理解组成该程序的符号是不够的。程序员还必须理解这些符号是如何组合成声明、表达式、语句和程序的。本章将讨论一些用法和意义与我们想当然的认识不一致的语法结构


一、理解函数声明

我们可以通过语句来理解函数声明,我们得到的语句如下:

1.(*(void(*)( ))0)( );

任何C变量的声明都由两部分组成:类型以及一组类似表达式的声明符。声明符和表达式有些类似,最简单的声明符就是单个变量。

float f, g;
这个声明的含义是:当对其求值时,表达式f和g的类型为浮点数类型(float )。因为声明符与表达式的相似,所以我们也可以在声明符中任意使用括号:

float ((f));
这个声明的含义是:当对其求值时,((f))的类型为浮点类型,由此可以推知,f也是浮点类型。

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

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

以上这些形式在声明中还可以组合起来:

float * g(),(*h)();
表示*g()与(*h)()是浮点表达式。因为()结合优先级高于*,*g()也就是*(g()): g是一个函数,该函数的返回值类型为指向浮点数的指针。

fIoat (*h)()
表示h是一个指向返回值为浮点类型的函数的指针,
(float ( *)())
表示一个“指向返回值为浮点类型的函数的指针”的类型转换符。

2.signal 函数接受两个参数:

一个整型的信号编号,以及一个指向用户定义的信号处理函数的指针


3.使用typedef 简化函数声明:

void (*sfp)(int):

使用typedef 可以简化上面的函数声明:
typedef void * HANDLER)(int):
HANDLER signal( int, hANDLER);

二、运算符的优先级问题

假设存在一个已定义的常量FLAG,FLAG是一个整数,且该整数值的二进制表示中只有某一位是1,其余各位均为0,亦即该整数是2的某次幂。如果对于整型变量flags ,我们需要判断它在常量FLAG为1的那一位上是否同样也为1,


1.if (flags FLAG)


if语句判断括号内表达式的值是否为0。考虑到可读性,如果对表达式的值是否为0的判断能够显式地加以说明,使得代码起到了注释该段代码的作用。其写法如下,


if (flags  & FLAG != 0)
这个语句现在虽然更好懂了,但却是一个错误的语句。因为!=运算符的优先级要高于&运算符,所以上式实际上被解释为:
if (flags (FLAG != 0))
因此,除了FLAG恰好为1的情形,FLAG为其他数时这个式子都是错误的。


又假设hi和low是两个整数,它们的值介于0到15之间,如果r是一个8位整数,且r的低4位与low各位上的数一致,而r的高4位与hi各位上的数一致。很自然会想到要这样写:


2.r= his << 4 + low:


但是很不幸,这样写是错误的。加法运算的优先级要比移位运算的优先级高,实际上相当于:
r= hi<< ( 4+ 1ow)
对于这种情况,有两种更正方法:

第一种方法是加括号;

第二种方法意识到问题出在程序员混淆了算术运算与逻辑运算,但这种方法牵涉到的移位运算与逻辑运算的相对优先级就更加不是那么明显。两种方法如下:
r=(hi<<4)+low;   //法1:加括号
r= hi<< 4 | low;   //法2:将原来的加号改为按位逻辑或
用添加括号的方法虽然可以完全避免这类问题,但是表达式中有了太多的括号反而不容易理解。因此,记住C语言中运算符的优先级是有益的。

C语言运算符优先表

三、注意作为语句结束标志的分号

1.多写分号的影响

在C程序中如果不小心多写了一个分号可能不会造成什么不良后果:

1.这个分号也许会被视作一个不会产生任何实际效果的空语句;

2.编译器会因为这个多余的分号而产生一条警告信息,根据警告信息的提示能够很容易去掉这个分号。

在if或者while 子句之后的语句就是一条单独的语句,与条件判断部分没有了任何关系。例如:

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

编译器会正常地接受第一行代码中的分号而不会提示任何警告信息

面这段代码的处理就大不相同:
if (x[i]>big)
big=x[i];

前面第一个例子(即在if后多加了一个分号的例子)实际上相当于
if (x[i]>big){}
big= x[i]; 

当然,也就等同于(除非x、I或者big是有副作用的宏)
big=x[i];

2.少写分号的影响

如果不是多写了一个分号,而是遗漏了一个分号,同样会招致麻烦。例如:
if(n<3)
       return 
logrec. date = x[o];
logrec. time = x[1];
logrec. code = x[2]:

此处的return 语句后面遗漏了一个分号;然而这段程序代码仍然会顺利通过

编译而不会报错,只是将语句
logrec. date x[o]
当作了return 语句的操作数。上面这段程序代码实际上相当于:
if(n<3)
       return logrec. date = x[o];
logrec. time = x[1];
logrec. code = x[2];

如果这段代码所在的函数声明其返回值为void,编译器会因为实际返回值的类型与声明返回值的类型不一致而报错。

3.分号被省略

声明的结尾紧跟一个函数定义如果声明结尾的分号被省略,编译器可能会把声明的类型视作函数的返回值类型。

struct logreci {
    int date;
    int time:
    int code;
}
main(
{
}

上面代码段实际的效果是声明函数main的返回值是结构logrec 类型。

struct logreci {
       int date:
       int time;
       int code;


main()

{

}
如果分号没有被省略,函数main的返回值类型会缺省定义为int类型。

四、悬挂”else引发的问题

if(x==0)
if (y == 0) error();
else{
z=x + y:
f(&z);
}

原因在于C语言中有这样的规则,对于x不等于0的情形,程序首先将x与y之和赋值给z,然后以z的地址为参数来调用函数f。

else始终与同一对括号内最近的未匹配的if结合。程序实际上是这个样子的。

f(x==0){
     if(y==0)
            error();

else{

         z = x + y;

        f(&z);
}

}

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

也就是说,如果x不等于0,程序将不会做任何处理。所以正确的应该这样写:

if   x = 0
then     if    y = 0 
            then error 
            fi 
else  
            e: = x + y;
            f(z)
fi 

像上面这样强制使用收尾定界符完全避免了“悬挂”else的问题,付出的代价则是程序稍稍变长了一点。

有些C程序员通过使用宏定义也能达到类似的效果:
# define IF  { if {
# define THEN )  {
# define ELSE  ) else {
# define FI   }}

上例C语言可以写成:

IF x == 0

THEN  IF y == 0

           THEN error ();

           FI

else  
            z = x + y;

  f(&z);
FI


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

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

相关文章

uniapp踩坑之项目:uni.previewImage简易版预览单图片

主要使用uni.previewImage //html <view class"box-card" v-for"(item,index) in DataList" :key"index"><view>图片&#xff1a;</view><image :src"item.Path" tap.stop"clickImg(item.Path)">&l…

如何深度学习

信息爆炸时代&#xff0c;诞生了很多新的学习方式&#xff0c;非常轻松就能掌握知识&#xff0c;比如&#xff0c;每天听一本书&#xff0c;半个小时就能学习一本书的精华&#xff0c;比如订阅名家专栏或者课程&#xff0c;在不长的时间内内就能学到很多知识。 很多人认为这样…

HTML5页面布局

1,图文混排布局 img {float: right;}p {font-size: 32px;} <div class"sec"><img src"penguin.png" alt"penguin"><p>With no change in the rule, the more you grow the more envious you be123 123132312...</p> &l…

安全测试工具Burpsuit和OWASP ZAP使用入门指南

Burpsuit使用入门指南 安装&#xff1a; 网上有很多相关相关保姆级别教程&#xff0c;所以这里不加赘述了尽量使用java8版本&#xff0c;破解版兼容8做的比较好如果发现注册机无法打开或者能打开注册机【run】无法点击唤起软件安装&#xff0c;可以使用命令行工具 java -jar …

JavaScript 实现飞机大战

文章目录 一些关键点概览&#xff1a;核心模块的具体实现示例&#xff1a;飞机类&#xff08;Plane&#xff09;的基本结构&#xff1a;子弹类&#xff08;Bullet&#xff09;的基本结构&#xff1a;敌机类&#xff08;Enemy&#xff09;的基本结构&#xff1a; 基于前面定义的…

膜厚测量仪在半导体应用中及其重要

随着科技的不断发展&#xff0c;半导体行业已成为当今世界的核心产业之一。在这个领域中&#xff0c;半导体膜厚测量仪作为关键设备&#xff0c;其精度和可靠性对于产品质量和生产效率具有至关重要的作用。本文将详细介绍半导体膜厚测量仪的工作原理、应用领域以及其在半导体制…

SpringMVC04、Controller 及 RestFul

4、Controller 及 RestFul 4.1、控制器Controller 控制器复杂提供访问应用程序的行为&#xff0c;通常通过接口定义或注解定义两种方法实现。控制器负责解析用户的请求并将其转换为一个模型。在Spring MVC中一个控制器类可以包含多个方法在Spring MVC中&#xff0c;对于Contr…

GSM8K数据集分享

来源: AINLPer公众号&#xff08;每日干货分享&#xff01;&#xff01;&#xff09; 编辑: ShuYini 校稿: ShuYini 时间: 2024-3-3 先进的语言模型可以在许多任务上与人类表现相媲美&#xff0c;但它们仍然难以执行多步骤数学推理任务。为此OpenAI团队创建了一个高质量、语言多…

Midjourney绘图欣赏系列(八)

Midjourney介绍 Midjourney 是生成式人工智能的一个很好的例子&#xff0c;它根据文本提示创建图像。它与 Dall-E 和 Stable Diffusion 一起成为最流行的 AI 艺术创作工具之一。与竞争对手不同&#xff0c;Midjourney 是自筹资金且闭源的&#xff0c;因此确切了解其幕后内容尚不…

如何通过四维轻云SDK开发打造智慧景区管理平台?

智慧景区管理平台通常是基于GIS技术&#xff0c;在三维实景地图的基础上&#xff0c;接入景区各类传感设备、第三方系统数据&#xff0c;进行业务功能的梳理及开发。但对于没有GIS开发经验的团队而言&#xff0c;地图开发具有一定的技术门槛&#xff0c;尤其是需要在前端解决好…

使用Revman绘制森林图教程

一 打开Revman 软件 mac 版打开界面如下&#xff0c;叉掉 welcome 点击左上角新文件图标 next——》 填写标题 第一项是针对什么健康问题的什么研究 第二项是什么和什么针对什么问题的比较研究 第三项是针对什么专题的什么健康问题的什么研究 第四项是自定义标…

Java 的 System 类常用方法介绍

Java 中的 System 类是一个final类&#xff0c;它提供了与系统相关的属性和方法。它是一个内置的类&#xff0c;可以直接使用&#xff0c;不需要实例化。System 类提供了标准输入、标准输出和错误输出流&#xff0c;以及对外部定义的属性和系统环境的访问。下面是 System 类的一…

C.C语言分支和循环语句

文章目录 一. 什么是语句 二. 分支语句&#xff08;选择结构&#xff09; 2.1. if 语句 2.1.1. 语法结构 2.1.2. 悬空else 2.1.3. 书写形式的对比 2.1.4. 练习 2.2. switch 语句 3.2.1. 语法结构 3.2.2. 在switch语句中的 break 3.2.3. default子句 3.2.4. 练习 三…

电动机工作原理图

电机&#xff08;俗称“马达”&#xff09;是指依据电磁感应定律实现电能转换或传递的一种电磁装置。它的主要作用是产生驱动转矩&#xff0c;作为用电器或各种机械的动力源。 电动机&#xff0c;转子置于旋转磁场中&#xff0c;在旋转磁场的作用下&#xff0c;获得一个转动力…

本金1w+能做好电商吗?可以做什么平台?

我是电商珠珠 有的小伙伴想要做电商&#xff0c;但是害怕风险太高。眼看着别人的店铺都做起来了&#xff0c;自己还不知道选哪个平台入驻。自己准备1w的本金&#xff0c;也不了解各个平台的入驻机制是怎么样的&#xff0c;什么平台更适合像自己一样的新手小白。 我做电商已经…

苹果cms模板保护设置,防止被扒

苹果cms模板保护设置&#xff0c;防止被扒 如今互联网时代&#xff0c;网站模板前端被扒是常有的事&#xff0c;如何防止模板数据被扒&#xff1f; 保护设置方法&#xff1a; 登录宝塔 找到安装模板的网站 设置禁止访问文件 方法参考截图后缀填&#xff1a;php|html 目录填&a…

【Redis知识点总结】(二)——Redis高性能IO模型剖析

Redis知识点总结&#xff08;二&#xff09;——Redis高性能IO模型及其事件驱动框架剖析 IO多路复用传统的阻塞式IO同步非阻塞IOIO多路复用机制 Redis的IO模型Redis的事件驱动框架 IO多路复用 Redis的高性能的秘密&#xff0c;在于它底层使用了IO多路复用这种高性能的网络IO&a…

【Unity】Tag、Layer、LayerMask

文章目录 层&#xff08;Layer&#xff09;什么是LayerLayer的应用场景Layer层的配置&#xff08;Tags & Layers&#xff09;Layer的数据结构LayerMaskLayer的选中和忽略Layer的管理&#xff08;架构思路&#xff09;层碰撞矩阵设置&#xff08;Layer Collision Matrix&…

自动化测试框架(全)

&#x1f345; 视频学习&#xff1a;文末有免费的配套视频可观看 &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 无论是在自动化测试实践&#xff0c;还是日常交流中&#xff0c;经常听到一个词&#xff1a;框架…

java日志-干货系列-拒绝长篇大论

一、java日志系统架构图 二、架构图解释 日志框架&#xff1a;日志框架相较于门面是先出现的&#xff0c;因此日志框架本身就可以单独使用的&#xff0c;并不需要日志门面。 日志门面&#xff1a;简单点说就是统一了操作日志的api&#xff0c;然应用层无需关心底层的日志框架…