《C陷阱与缺陷》学习笔记(一)
前言和导读
“得心应手的工具在初学时的困难程度往往超过那些easy上手的工具。”比較认同这句话。
我至今认为自己事实上还是个刚入了门的刚開始学习的人。
第一章 “词法”陷阱
因为之前学过编译原理,对编译器词法分析(主要是符号识别过程)比較了解,理解起来不困难。
在讲到"="和"=="、"|"和"||"、"&"和"&&"时,联想起曾经见过一些程序中出现了类似于"#define || OR"这种语句。当时以为可能是为了照应习惯其它语言的使用者的阅读偏好,如今看来这样做确实能够避免一些错误。当然使用不使用这种编程风格就是另外一回事了。对于词法分析的运用到的贪心法,之前尽管知道原理和规则,但确实没有意识到这是种贪心法。这样一来,对于easy引起编译器错误的格式,写成还有一种格式更好一些。
y = x/*p // 本须要进行指针取值、除法和赋值,这个颜色已表示编译器并不这么觉得,因此仅仅能写成下一种形式
y = x/(*p)
至于为整型常量用0补首位以便对齐,反而使得编译器将其误觉得八进制数的情况倒是从来遇到也没考虑到过。
“单引號' '中的字符上代表一个整数,双引號" " 引起的字符串代表的是一个指向无名数组起始字符的指针。”前半句曾经就知道。后半句有点意思。后半句解释了为什么char *slash = '/' 这个语句有错误。
同一时候。书中指出整型一般为16位或32位,可以容纳多个字符(一般为8位),所以用单引號的'yes'也许可以被一些编译器正确识别。仅仅是巧合而已。有的编译器会将'yes'多余字符忽略,仅仅取第一个整数'y'。有的则依次取值、覆盖再取值,最后结果是仅仅取了最后一个整数相当于's'。
11月16日
第二章 “语法”陷阱
模拟调用首地址为0的子例程的语句(*(void(*)())0) ()以及它从(*fp)()生成而来的过程。把常数0转型为“指向返回值为void的函数的指针”的类型就是(void(*)()) 0,用它取代fp就生成了这个语句。
使用typedef会更方便直观。依据补充阅读,在编程中使用typedef目的一般有两个。一个是给变量一个易记且意义明白的新名字,还有一个是简化一些比較复杂的类型声明。
前者早已知道,后者确实是个盲点。因此曾经总把typedef当做还有一种#define。
typedef void (*funcptr)();
(*(funcptr)0)();
利用这个特性,不难理解signal函数的声明。它接受两个參数(一个整型的信号编号和指向用户定义的信号处理函数的指针)。返回值是一个指向调用前的用户信号处理函数的指针。
void (*signal(int,void(*)(int)))(int)
/*以下是简化后的函数声明*/
typedef void (*HDNDLER)(int);
HANDLER signal(int, HANDLER)
关于运算符优先级。曾经的处理方式是无脑加括号,全然不关注;可是括号太多了确实会造成阅读困难。
依据原书所看到的做出归纳。
多余的和缺失的分号会造成的错误一般不会出现。可是所给的样例比較醒目地提醒了结构定义后假设不加分号的结果——可能导致其后定义的函数返回类型为这样的结构:
struct logrec{
......
}
main() //当然一般使用void main()
有时候,switch...case...结构中不一定每个分支语句都须要break,书中字符处理的样例就不再反复。
没有參数的函数调用也应该有一个空的參数列表。
else总与近期的if配对。
其它的一些感想:对于词法分析。空格、换行确实是可行的分隔方法;然而在语法分析时却不那么好用了。多余的空格和换行符往往会被删掉,因此必须明确语句结构的配对原则和分号" ; "及逗号" , "的正确使用,还有大括号" { } "的配对。当然这个结论应该是初学伊始就牢牢树立的观点了。
有的语言特点和编程风格(特别是通过宏定义改变语法结构的外观)不少是从包含C语言的前身以及其它语言的编程风格继承而来,但后者并非必要的。
11月16日~17日
第三章 “语义”陷阱
数组和指针:数组名实际上是一个指向数组首元素的指针。仅仅有在作为sizeof()的參数时例外。
尽管C语言仅仅有一维数组。但用一维数组模拟多维数组是能够的。
a[i]是*(a+i)的简记,这也就能够解释为什么数组首元下标从0開始。也正因此,a[i]和i[a]具有同样的含义。汇编中后者并不少见。但作者不推荐这种写法。在多维数组中,下标表示法比*(*(calender+4)+7)这种表示方法简单多了。
字符串拷贝中暗含的陷阱:malloc分配空间可能失败;分配后须要及时释放。分配内存大小的限定。三者结合起来的样例:
C中无法将一个数组作为函数參数直接传递。假设使用数组名作为參数。那么数组名会立马被转换为指向该数组第1个元素的指针。char hello[] = "hello"声明了一个字符数组后。printf("%s\n",hello)和printf("%s\n",&hello[0])是等效的(相同是在终端显示hello)。但这样的情况限于其作为函数參数的情况,书中指出,假设这样的自己主动转换在其它情形下成立是错误的,"extern char *hello"与"extern char hello[]"有着天差地别,这是第四章内容,尚未涉及。由此延伸而来。假设一个指针參数并不实际代表一个数组。即使从技术上而言是正确的,採用数组形式的记法常常会起到误导作用。
反之。假设一个指针參数代表一个数组。以main函数第二个參数为例来说明:
main(int argc, char* argv[]) { }
//强调argv是一个指向某数组的起始元素的指针。该数组的元素为字符指针类型 main(int argc, char** argv) { }
//两种写法全然等价,能够任选一种最能清楚反应自己意图的写法
避免“举隅法”(不必深究这个语言学名词),包括的“陷阱”即为混淆指针和指针所指的数据。比方char *p,*q;p="ABC";q=p;。
除了0。C语言将一个整数转换为一个指针。最后得到的结果取决于编译器实现。而0。编译器保证其转换而来的指针不等于不论什么有效的指针。
#define NULL 0 是出于代码文档化的考虑。因此是不能使用赋值为0的指针变量所指向的内存中存储的内容的。这是没有定义的。相关的语句在不同计算机上会有不同效果。第七章会具体讨论这个问题。
在“边界计算和不正确称计算”这个“陷阱”处,作者以int i,a[10]举例。说明了为什么for(i=0; i<10; i++) a[i] = 0 ;比for(i=0; i<=9; i++) a[i] = 0 ;要好:入界点和出界点恰好为0和10,而且对于下标为从0開始的C语言,出界点恰是数组元素个数。
对于这个问题的还有一种考虑方法:把上界视作某序列第一个被占用的元素。把下界视作第一个被释放的元素。这样的考虑方式处理不同类型的缓冲区时非常实用,所举样例是一个指向缓冲区的指针,让它总是指向第一个未占用的字符。这样对其赋值就有
*bufptr++ = c;的形式。对于样例还有很多其它细节能够揣摩。
在之后的还有一个样例中,作者觉得,技巧性非常强的代码,“假设没有非常好的理由,我们不应该尝试去做。
但假设是‘师出有名’,那么理解这种代码应该怎样写就非常重要了。
”对于那个详细的样例。“仅仅要我们记住前面的两个原则,特例外推法和细致计算边界,我们应该全然有信心做对。”
作者提到,之前讨论了运算符优先级的问题。“求值顺序则全然是还有一码事”。前者是保证a + b * c应该解释成a + (b * c)而不是(a+b) * c这样一类的规则,求值顺序是保证if (cout != 0 && sum/count < smallaverage) {...}即使count为0也不会产生用0作除数的错误的规则。
这里有一篇别人的日志能够參考。
要点在于,C语言中仅仅有四个运算符(&&、||、?
:和,)存在规定的求值顺序。特别指出用于分隔函数參数的逗号并不是逗号运算符。其它运算符对其操作数求值的顺序是没有定义的,赋值运算符并不能保证不论什么求值顺序。
i = 0;
while (i < n)
y[i] = x[i++];
//这里有个如果:y[i]的地址在i的自增操作运行前被求值,但实际并没有不论什么保证。有的C语言实现可能如此。有的则相反。
// y[i++] = x[i];相同有这样的问题 /*改进*/
i = 0;
while (i < n) {
y[i] = x[i++];
i++;
}
/*简写*/
for (i = 0; i < n; i++)
y[i] = x[i];
作者特别提到。尽管用&、|、~和&&、||、!相应运算相互替代时,程序执行结果可能是正确的,并详解了一下,但这侥幸成分非常大并且绝少有C编译器可以检測出,因此这样的错误还是应该要避免。
无符号数运算不会溢出:全部无符号运算都是以2的n次方为模,在这个意义上确实没有“溢出”这一说。相关解释能够參考这里。
对有符号数运算溢出的检測方法:
if ((unsigned)a + (unsigned)b > INTMAX)
//INT_MAX代表可能的最大数值。ANSI C在<limits.h>定义
//其它C语言实现中或许要自定义
complain(); /*还有一种可行方法*/
if (a > INT_MAX - b)
complain();
为main提供返回值的原因:大多数C语言实现都通过main返回值来告诉操作系统该函数运行是成功还是失败。一般0代表成功,非0为失败。不给出返回值的结果是隐含地返回了某个“垃圾”整数(未显式声明返回类型则默觉得整型)。
《C陷阱与缺陷》学习笔记(一)的更多相关文章
- 《Hadoop》大数据技术开发实战学习笔记(二)
搭建Hadoop 2.x分布式集群 1.Hadoop集群角色分配 2.上传Hadoop并解压 在centos01中,将安装文件上传到/opt/softwares/目录,然后解压安装文件到/opt/mo ...
- 《Hadoop大数据技术开发实战》学习笔记(一)
基于CentOS7系统 新建用户 1.使用"su-"命令切换到root用户,然后执行命令: adduser zonkidd 2.执行以下命令,设置用户zonkidd的密码: pas ...
- 超人学院Hadoop大数据技术资源分享
超人学院Hadoop大数据技术资源分享 http://bbs.superwu.cn/forum.php?mod=viewthread&tid=807&fromuid=645 很多其它精 ...
- java大数据最全课程学习笔记(1)--Hadoop简介和安装及伪分布式
Hadoop简介和安装及伪分布式 大数据概念 大数据概论 大数据(Big Data): 指无法在一定时间范围内用常规软件工具进行捕捉,管理和处理的数据集合,是需要新处理模式才能具有更强的决策力,洞察发 ...
- hadoop大数据技术架构详解
大数据的时代已经来了,信息的爆炸式增长使得越来越多的行业面临这大量数据需要存储和分析的挑战.Hadoop作为一个开源的分布式并行处理平台,以其高拓展.高效率.高可靠等优点越来越受到欢迎.这同时也带动了 ...
- 除Hadoop大数据技术外,还需了解的九大技术
除Hadoop外的9个大数据技术: 1.Apache Flink 2.Apache Samza 3.Google Cloud Data Flow 4.StreamSets 5.Tensor Flow ...
- 大数据技术之_09_Flume学习_Flume概述+Flume快速入门+Flume企业开发案例+Flume监控之Ganglia+Flume高级之自定义MySQLSource+Flume企业真实面试题(重点)
第1章 Flume概述1.1 Flume定义1.2 Flume组成架构1.2.1 Agent1.2.2 Source1.2.3 Channel1.2.4 Sink1.2.5 Event1.3 Flum ...
- 大数据技术之_19_Spark学习_01_Spark 基础解析 + Spark 概述 + Spark 集群安装 + 执行 Spark 程序
第1章 Spark 概述1.1 什么是 Spark1.2 Spark 特点1.3 Spark 的用户和用途第2章 Spark 集群安装2.1 集群角色2.2 机器准备2.3 下载 Spark 安装包2 ...
- 大数据技术之_16_Scala学习_01_Scala 语言概述
第一章 Scala 语言概述1.1 why is Scala 语言?1.2 Scala 语言诞生小故事1.3 Scala 和 Java 以及 jvm 的关系分析图1.4 Scala 语言的特点1.5 ...
- 大数据技术之_16_Scala学习_04_函数式编程-基础+面向对象编程-基础
第五章 函数式编程-基础5.1 函数式编程内容说明5.1.1 函数式编程内容5.1.2 函数式编程授课顺序5.2 函数式编程介绍5.2.1 几个概念的说明5.2.2 方法.函数.函数式编程和面向对象编 ...
随机推荐
- BZOJ 1067 降雨量(RMQ-ST+有毒的分类讨论)
1067: [SCOI2007]降雨量 Time Limit: 1 Sec Memory Limit: 162 MB Submit: 4399 Solved: 1182 [Submit][Stat ...
- [NOI2008][bzoj1061] 志愿者招募 [费用流+巧妙的建图]
题面 传送门 思路 引入:网络流? 看到这道题,第一想法是用一个dp来完成决策 但是,显然这道题的数据并不允许我们进行dp,尤其是有10000种志愿者的情况下 那么我们就要想别的办法来解决: 贪心?这 ...
- BZOJ3999 [TJOI2015]旅游 【树剖 + 线段树】
题目 为了提高智商,ZJY准备去往一个新世界去旅游.这个世界的城市布局像一棵树.每两座城市之间只有一条路径可 以互达.每座城市都有一种宝石,有一定的价格.ZJY为了赚取最高利益,她会选择从A城市买入再 ...
- 笔记:CS231n+assignment1(作业一)
CS231n的课后作业非常的好,这里记录一下自己对作业一些笔记. 一.第一个是KNN的代码,这里的trick是计算距离的三种方法,核心的话还是python和machine learning中非常实用的 ...
- 【ubuntu】配置zsh
1. 修改默认shell为zsh chsh -s /bin/zsh echo $SHELL$ 2. 下载oh-my-zsh wget https://raw.github.com/robbyrusse ...
- python数据结构之字典
1.python字典的定义 1.用大括号{},以逗号分隔每个键值对,键与值之间用冒号连接 2.键:需要不可变的数据结构,值可以是任意的数据对象 3.字典是无序的,键在字典中必须是唯一,在字典中取值的方 ...
- Struts框架详解
1.Struts应用框架介绍 (1)框架 框架最简单的形式是指已开发过并已测试过的软件的程序块,这些程序块可以在多个软件开发工程中重用.框架提供了一个概括的体系结构模版,可以用这个模板来构建特定领域中 ...
- Xor路
Xor路 (xor.pas/c/cpp)128MB1s 给定一棵有N个点和N-1条边的树,请你求出树中的最长路径,以及总共有多少条最长路径. 这里路径长度是用xor定义的,即若经过的边的权值为a1, ...
- 51Nod 1001 数组中和等于K的数对 And 1015 水仙花数
1001 数组中和等于K的数对 基准时间限制:1 秒 空间限制:131072 KB 分值: 5 难度:1级算法题 给出一个整数K和一个无序数组A,A的元素为N个互不相同的整数,找出数组A中所有和等于K ...
- Javascript&Html-history对象
Javascript&Html-history对象 history对象保存着用户的上网记录,这些记录从用户打开浏览器开始. 用户借助history对象实现的跳转. history.go(-1) ...