《C缺陷和陷阱》-笔记

news2024/11/20 7:05: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/1500540.html

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

相关文章

面试经典150题【61-70】

文章目录 面试经典150题【61-70】61.旋转链表86.分隔链表104. 二叉树的最大深度100.相同的树226.翻转二叉树101.对称二叉树105.从前序与中序遍历序列构造二叉树106.从后序和中序遍历序列构造二叉树117.填充每个节点的下一个右侧节点指针II114.二叉树展开为链表 面试经典150题【…

修复 因 fstab 中UUID 错误导致系统无法正常工作的问题

操作系统&#xff1a; PVE 8.0 /debian 12 &#xff08;bookworm&#xff09; 问题症状&#xff1a;可以正常启动进入系统&#xff0c;但是系统盘以只读方式挂载 问题原因&#xff1a;/etc/fstab 中引导区的UUID 被错误修改导致 解决方法&#xff1a; 重启系统&#xff0c;在…

QT:用opencv的KNN识别图片中的LED数字(一)

前言 一款功能测试的软件demo,使用了QT作为界面,主要使用了opencv的KNN识别,使用gstreamer作为管道,用来打开图片。后期会写一篇打开摄像头实时识别的文章。 (正在写,未完成,稍候) 效果一预览: 效果二预览: 效果三预览: 正在写。。。 设计思路 1. 软件UI设…

吴恩达深度学习笔记:深度学习引言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 …

【C++】C++模板基础知识篇

个人主页 &#xff1a; zxctscl 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 文章目录 1. 泛型编程2. 函数模板2.1 函数模板概念2.2 函数模板格式2.3 函数模板的原理2.4 函数模板的实例化2.5 模板参数的匹配原则 3. 类模板3.1 类模板的定义格式3.2 类模板的实例化…

Trans论文复现:考虑源荷不平衡性的微电网鲁棒定价方法程序代码!

适用平台&#xff1a;MatlabYalmipCplex/Gurobi 程序针对目前微电网中高比例新能源发电的波动性和间歇性&#xff0c;提出了考虑源荷不平衡特性的微电网鲁棒定价方法&#xff0c;综合考虑电力市场边际收益和边际成本&#xff0c;利用价格波动来平衡电源和负荷。程序算例丰富、注…

腾讯云轻量 2核2G4M新用户首购活动,99续费同价来了!!

阿里云199一年续费同价&#xff0c;腾讯云99一年续费同价&#xff0c;平台卷起来&#xff0c;对用户的角度来说&#xff0c;真的是香麻了~ 腾讯云新春采购节&#xff0c;2核2G4兆的基础配置&#xff0c;新官方直接放大招&#xff0c;99一年&#xff0c;活动期间内&#xff0c;…

EXSI create datastore

文章目录 1. 简介2. 清空磁盘3. 删除表4. 创建database 1. 简介 在 ESXi 环境中创建数据存储(Datastore)的步骤如下: 登录 vSphere Web Client 打开 Web 浏览器,输入 ESXi 主机或 vCenter Server 的 IP 地址,使用有权限的账户登录。 在 ESXi 环境中创建数据存储(Datastore)…

二、TensorFlow结构分析(4)

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

【Kafka系列 07】Kafka 如何保证消息不丢失

一、Kafka 消息不丢失的边界 一直以来&#xff0c;很多人对于 Kafka 丢失消息这件事情都有着自己的理解&#xff0c;因而也就有着自己的解决之道。在讨论具体的应对方法之前&#xff0c;我觉得我们首先要明确&#xff0c;在 Kafka 的世界里什么才算是消息丢失&#xff0c;或者…

华为云开年采购季Web及移动App上云体验,助力软件行业创新发展

随着云化、智能化浪潮的进一步深入&#xff0c;越来越多的应用软件开发商选择将核心产品从本地IDC机房搬迁到公有云上。但同时&#xff0c;软件开发商们也非常在意公有云厂商的可靠性与安全性&#xff0c;希望能够选择一家更加稳定可靠的云服务商&#xff0c;确保自身业务的连续…

02极简LLM逻辑与PyTorch快速入门

文章目录 02极简LLM逻辑与PyTorch快速入门极简LLM逻辑PyTorch环境安装&#xff08;重要&#xff0c;不难&#xff09;PyTorch 主要概念Tensors张量张量常见的形式&#xff1a;scalar、vector、matrix、n-dimensinal张量初始化张量参数&#xff1a;shape、datatype、device张量运…

从huggingface下载模型像本地加载但是UnicodeDecodeError

我自己是在Linux下出现了这个问题 原文&#xff1a;https://github.com/huggingface/transformers/issues/13674 The path for the AutoModel should be to a directory pointing to a pytorch_model.bin and to a config.json. Since you’re pointing to the .bin file dire…

论文笔记:Efficient Bootstrapping for Confidential Transactions

EcoBoost: Efficient Bootstrapping for Confidential Transactions 设计了一种被称为EcoBoost的新方法&#xff0c;以提高支持机密交易的区块链的引导效率。具体来说&#xff0c;利用随机抽样来验证高概率保密交易的正确性。因此&#xff0c;与事务数量相比**&#xff0c;验证…

Promise async await

简介&#xff1a;回调 JS会提供很多函数&#xff0c;允许异步行为。换句话说&#xff0c;现在开始执行的行为。但它们会在稍后完成。异步执行某项功能的函数应该提供一个 callback 参数用于在相应事件完成时调用。处理Error&#xff1a; 加载成功时&#xff0c;它会调用 callb…

Z Potentials | 星爵,他的征途不止向量数据库

纵观过去几十年的科技发展史&#xff0c;每一代新的技术架构的出现往往都伴随着新的数据范式的出现&#xff0c;也催生了多家百亿到千亿美金数据平台的诞生。如果说 2023 年科技领域的关键词是 LLM&#xff0c;那么数据库领域的关键词一定非向量数据库莫属。向量数据库是一种专…

我们是如何测试人工智能产品的

在当今数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;技术已经成为我们生活中不可或缺的一部分。然而&#xff0c;要构建出可信赖的AI系统并非易事。这需要我们不仅深入理解人工智能的核心原理&#xff0c;还需要将这些理论知识应用到实际场景中。 为了帮助大家系…

一个不错的空间视频收集论坛

该网站收录了来自世界各地的空间视频、空间照片和全景照片,以突出令人惊叹的 Apple Vision Pro 的功能。 网站地址:

Java 客户端向服务端上传文件(TCP通信)

一、实验内容 编写一个客户端向服务端上传文件的程序&#xff0c;要求使用TCP通信的的知识&#xff0c;完成将本地机器输入的路径下的文件上传到D盘中名称为upload的文件夹中。并把客户端的IP地址加上count标识作为上传后文件的文件名&#xff0c;即IP&#xff08;count&#…

mysql中insert … select锁范围

1、执行 insert … select 的时候&#xff0c;对目标表也不是锁全表&#xff0c;而是只锁住需要访问的资源。 例如&#xff0c; CREATE TABLE t (id int(11) NOT NULL AUTO_INCREMENT,c int(11) DEFAULT NULL,d int(11) DEFAULT NULL,PRIMARY KEY (id),UNIQUE KEY c (c) ) EN…