《C缺陷和陷阱》-笔记

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

目录

文章目录

前言

一、理解函数声明

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.mfbz.cn/a/440272.html

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

相关文章

【HTML】HTML基础8.1(表单标签)

目录 效果 基础知识 标签 ① ② 代码 效果 基础知识 表单的组成元素 表单控件用户所填写的信息提示信息提示用户需要填的信息表单域包含表单元素的区域 标签 ① <form action"" method""></form> <form>标签确定了一个表单域&…

Centos安装Miniconda

Centos安装Miniconda 一、前言二、安装1、下载Miniconda2、执行安装脚本3、加载环境变量使之生效&#xff1a;4、配置conda国内镜像&#xff1a; 三、conda常用命令1、创建环境2、查看所有环境3、删除一个环境4、激活指定环境5、退出当前环境 一、前言 需要在Centos中使用pytho…

吴恩达深度学习笔记:深度学习引言1.1-1.5

目录 第一门课&#xff1a;神经网络和深度学习 (Neural Networks and Deep Learning)第一周&#xff1a;深度学习引言(Introduction to Deep Learning)1.1 欢迎(Welcome)1.2 什么是神经网络&#xff1f;(What is a Neural Network)1.3 神经网络的监督学习(Supervised Learning …

《数字图像处理》读书笔记

本文笔记来自——数字图像处理_第三版_中_冈萨雷斯 1.使用数字图像处理领域的实例 如果光谱波段根据光子能量进行分组&#xff0c;则可得到下图的光谱&#xff0c;范围从伽马射线&#xff08;最高能量&#xff09;到无线电波&#xff08;最低能量&#xff09;。 1.1伽马射线成…

C++学习笔记:AVL树

AVL树 什么是AVL树?AVL树节点的定义AVL树的插入平衡因子调整旋转调整左旋转右旋转左右双旋右左双旋 AVL树完整代码实现 什么是AVL树? AVL是1962年,两位俄罗斯数学家G.M.Adelson-Velskii和E.M.Landis 为了解决如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找…

Mac M1:通过docker安装RocketMQ、RocketMQ-Dashboard

0. 引言 最近本地启动以前docker安装的rocketmq发现报错了&#xff0c;因为是从老mac迁移过来的&#xff0c;发现支持的芯片还是amd的&#xff0c;于是重新在docker下安装rocketmq&#xff0c;并记录下步骤&#xff0c;方便大家后续参考。 1. 步骤 1、先下载项目源码 git c…

景联文科技:专业提供高质量大语言模型训练数据

2024年&#xff0c;数字经济被再次写入政府工作报告中&#xff0c;报告指出要深化大数据、人工智能等研发应用&#xff0c;打造具有国际竞争力的数字产业集群。 大模型作为生成式人工智能的基础&#xff0c;日益成为国际科技竞争的焦点。人大代表杨剑宇指出&#xff0c;尽管我国…

货运物流小程序开发功能 发货运输更简单

随着互联网的快速发展&#xff0c;线上接单已经成为物流行业的主流趋势。货运物流接单小程序作为物流企业的得力助手&#xff0c;能够提高运输效率、降低成本、提升服务质量&#xff0c;成为物流行业的发展新方向。 1. 用户注册与登录功能&#xff1a;用户可以通过手机号、邮箱…

nodejs web服务器 -- 搭建开发环境

一、配置目录结构 1、使用npm生成package.json&#xff0c;我创建了一个nodejs_network 文件夹&#xff0c;cd到这个文件夹下&#xff0c;执行&#xff1a; npm init -y 其中-y的含义是yes的意思&#xff0c;在init的时候省去了敲回车的步骤&#xff0c;如此就生成了默认的pac…

基于Leatlet标注Geojson下载器实现

在上一篇文章中&#xff0c;我们学习了Leaflet的基础知识&#xff0c;包括如何创建地图、添加图层等。在本文中&#xff0c;我们将深入学习Leaflet中标注的创建和管理&#xff0c;包括如何添加标注、自定义标注图标、创建图层组、批量添加和删除标注、为标注添加属性和弹出框等…

二、TensorFlow结构分析(4)

TF数据流图图与TensorBoard会话张量Tensor变量OP高级API 目录 1、变量 2、高级API 1、变量 2、高级API

[嵌入式系统-37]:龙芯1B 开发学习套件 -6-协处理器CP0之CPU异常处理与外部中断控制器的中断处理

目录 一、MPIS CPU Core与32个异常exception 1.1 龙芯1B的MIPS CPU IP Core 1.2 MIP32指令系统 1.3 MIPS CPU寄存器 1.4 MIPS CPU的异常向量与异常向量号 1.5 龙芯异常exception与中断interrupt的区别 二、协议处理器CP0的中断控制与8个中断 2.1 CP0概述 2.2 协处理器…

Word文档一键转换成电子书,告别繁琐操作!

你是否曾经为了将Word文档转换为电子书而苦恼&#xff1f;手动复制粘贴、调整格式、排版等等繁琐的操作&#xff0c;不仅耗时费力&#xff0c;还容易出错。现在我教你只需轻轻一点&#xff0c;即可将Word文档轻松转换为电子书&#xff0c;无需任何手动操作 一、Word转换电子书步…

基于React低代码平台开发:直击最新应用构建

文章目录 前言一、React与低代码平台的结合优势二、基于React的低代码平台开发挑战三、基于React的低代码平台开发实践四、未来展望《低代码平台开发实践&#xff1a;基于React》编辑推荐内容简介作者简介目录前言为什么要写这本书读者对象如何阅读本书 前言 随着数字化转型的…

Word论文格式怎么设置 Word论文查重功能在哪里 论文格式要求及字体大小 论文查重怎么查 WPS论文查重准确吗

Word文档是由Microsoft Word处理软件创建和编辑的文档。Word文档通常用于创建各种类型的文档&#xff0c;如信函、报告、简历、论文等。本篇文章将为大家介绍Word论文格式怎么设置以及Word论文查重功能在哪里。 一、Word论文格式怎么设置 一个好的论文格式&#xff0c;是论文…

【框架设计】MVC、MVP、MVVM对比图

1. MVC&#xff08;Model-View-Controller&#xff09; 2. MVP&#xff08;Model-View-Presenter&#xff09; 3. MVVM&#xff08;Model-View-ViewModel&#xff09;

ai数字人虚拟直播:AI大模型带给你不一样的体验

AI数字人虚拟直播&#xff0c;这一新兴的科技形式&#xff0c;正逐渐融入人们的生活之中。通过AI大模型的技术支持&#xff0c;数字人可以实现高度仿真的互动体验&#xff0c;让观众感受到前所未有的沉浸式乐趣。 数字人虚拟直播的魅力在于其超越了传统直播形式的局限性&#…

Python爬虫——Scrapy-1

目录 简介 安装 基本使用 1. 创建爬虫的项目 2. 创建爬虫文件 3. 运行爬虫代码 scrapy项目组成 scrapy工作原理 ​编辑 58同城 scrapy架构组成 汽车之家 总结 简介 Scrapy 是一个基于 Python 的开源网络爬虫框架&#xff0c;它可以帮助开发者快速、高效地构…

数据结构从入门到精通——栈

栈 前言一、栈1.1栈的概念及结构1.2栈的实现1.3栈的面试题 二、栈的具体实现代码栈的初始化栈的销毁入栈出栈返回栈顶元素返回栈中的元素个数检测是否为空Stack.hStack.ctest.c 前言 栈&#xff0c;作为一种后进先出&#xff08;LIFO&#xff09;的数据结构&#xff0c;在计算…

Windows系统搭建it-tools工具箱并结合内网穿透实现公网远程访问

文章目录 1. 使用Docker本地部署it-tools2. 本地访问it-tools3. 安装cpolar内网穿透4. 固定it-tools公网地址 本篇文章将介绍如何在Windows上使用Docker本地部署IT- Tools&#xff0c;并且同样可以结合cpolar实现公网访问。 在前一篇文章中我们讲解了如何在Linux中使用Docker搭…
最新文章