C语言中边界计算与不对称边界(二)

尽管C语言的数组会让新手感到麻烦,然而C语言中数组的这种特别的设计正是其最大优势所在。要理解这一点,以下是一些简单解释。

在所有常见的程序设计错误中,最难于察觉的一类是“栏杆错误”,也常被称为“差一错误”(off-by-one error)。例如这个问题:100英尺长的围栏每隔10英尺需要一根支撑用的栏杆,一共需要多少根栏杆呢?如果不加思索,大家会容易以为是100除以10,即为10根。其实,真正需要的是11根。

因为支撑10英尺长的围栏实际需要2根栏杆,即两端各一根。这个问题的另一种思考方式是,除了最右侧的一段围栏,其他每一段10英尺长的围栏都只在左侧有一根栏杆;而例外的最右侧一段围栏不仅左侧有一根栏杆,右侧也有一根栏杆。

为了避免以上问题中所说的“栏杆错误”,以下是两个注意的通用原则:

(1)首先考虑最简单情况下的特例,然后将得到的结果外推;

(2)仔细计算边界,绝不掉以轻心;

将上面这两点牢记住以后,现在看看整数范围的计算。例如,假定整数x满足边界条件x>=16且x<=37,那么此范围内的X的可能取值个数有多少?即16到37之间一共有多少个元素?很显然,答案与37-16(21)非常接近,那么到底是20、21还是22呢?

根据原则1,我们考虑最简单情况下的特例。这里假定整数x的取值范围上界与下界重合,即x>=16且x<=16,显然合理的x取值是只有一个整数,即16。所以当上界与下界重合时,此范围内满足条件的整数序列只有一个元素。

再考虑一般情况,假定下界为l,上界为h。如果满足条件“上界与下界重合”,即l=h,则h-l=0。根据特例外推的原则,我们可以得出满足条件的整数序列有h-l+1个元素。故以上的问题中的x的取值个数为37-16+1=22。

造成“栏杆错误”的根源正是“h-l+1”中的“+1”。一个字符串中由下标为16到下标为37的字符元素所组成的子串,它的长度是多少呢?稍不留意,就会得到错误的结果21。很自然的,我们会想,是否存在一些编程技巧,能够降低这类错误发生的可能性呢?

当然,这个技巧不但存在,而且还可以一言以蔽之:用第一个入界点和第一个出界点来表示一个数值范围。具体而言,前面的例子我们不应该说整数x满足边界条件x>=16且x<=37,而是说整数x满足边界条件x>=16且x<38。注意,这里下界是“入界点”,即包括在取值范围之中;而上界是“出界点”,即不包括在取值范围之中。这种不对称也许从数学上而言并不优美,但是它对于程序设计的简化效果却足以令人吃惊。

(1)取值范围的大小就是上界与下界之差。

(2)如果取值范围为空,那么上界等于下界

(3)即使取值范围为空,上界也永远不可能小于下界。

对于像C这样的数组下标从0开始的语言,不对称边界给程序设计带来的便利尤为明显;这种数组的上界恰是数组元素的个数。因此,在C语言中定义一个拥有10个元素的数组,那么0就是数组下标的第一个“入界点”,而10就是数组下标中的第一个“出界点”。因而我们经常这样写

int a[10], i;
for(i=0; i<10; i++)
       a[i] = 0;

而不是这样写:

int a[10], i;
for(i=0; i<=9; i++)
       a[i] = 0;

让我们做一个假设,如果C语言的for语句风格类似Algol或者Pascal语言,那么就会带来一个问题:下面这个语句的含义究竟是什么?

for(i=0 to 10)
      a[i] = 0;

如果10是包括在取值范围内的“入界点”,那么i将取11个值,而不是10个值;如果10是不包括在取值范围内的“出界点”,那么原来以其他程序语言为背景的编程者会大为惊讶。

另一种考虑不对称边界的方式是,把上界视作某序列中第一个被占用的元素,而把下界视作序列中第一个被释放的元素。如下图:

C语言中边界计算与不对称边界(二)

时间: 2024-11-12 20:30:51

C语言中边界计算与不对称边界(二)的相关文章

C语言中的边界计算与不对称边界(一)

1.如果一个数组有10个元素,那么这个数组下标的允许取值范围是什么呢? 这个问题对于不同的程序设计语言有着不同的答案.例如,对于Fortran,PL/I以及Snobol4等程序语言,这个数组的下标取值缺省从1开始,而且这些语言也允许编程者另外指定数组下标的起始值.而对于Algol和Pascal语言,数组下标没有缺省的起始值,编程者必须显式的指定每个数组的下界与上界.在标准的Basic语言中,声明一个拥有10个元素的数组,实际上编译器分配了11个元素的空间,下标范围从0到10. 在C语言中,这个数

c语言中如何通过二级指针来操作二维数组

通过二级指针去访问二维数组需要先给二级指针分配等同于二维数组行数的一维数组指针,然后把二维数组的每行首地址赋值给对应位置的一维指针上.之后就可以通过二维指针直接访问了. 参考代码如下,可以看具体注释辅助理解. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <stdio.h>//输入输出头文件. #include <stdlib.h>//本程序需要用到malloc/free函数,引

C语言中复数运算及调用blas,lapack中复数函数进行科学计算

C语言中常用的数据类型主要int, float ,double ,char 等,但在科学运算中复数扮演着重要角色.这里讲下C语言中的复数运算以及如何调用blas,lapack库中的复数函数来进行科学计算. 1.C语言中的复数运算. C语言中若要用的复数,需要包含头文件complex.h,下面看看一些基本的例子 #include <stdio.h> #include"complex.h" int main() { complex a, b, c, d, f; a = 1 +

Java语言中的正则表达式

正则表达式是什么? 正则表达式是一种强大而灵活的文本处理工具.初学正则表达式时,其语法是一个难点,但它确实是一种简洁.动态的语言.正则表达式提供了一种完全通用的方式,能够解决各种字符串处理相关的问题:匹配.选择.编辑以及验证.一般来说,正则表达式就是以某种方式来描述字符串. 日常例子 在文本中查找“halu126”,我们通常会在查找框中输入“halu126”,这就是一个最简单的正则表达式的例子,使用精确的匹配这样的字符串,如果我 们即想在文本中找到“halu126”,又想找到“Halu126”,

Java千问:Java语言中最大的整数再加1等于多少?

已知Java语言中int类型所能表示的最大整数为2147483647,请问以下代码执行结果是什么?一部分人都会认为这段程序压根就无法通过编译,也有人认为,这段程序能够通过编译,但在运行时会抛出异常,但更多的人面对这道题目根本就无从下手.那么正确答案是什么呢?首先告诉大家,这段程序能够顺利通过编译,并且在运行时也不会出现异常,运行的结果是在控制台上输出了数字-2147483648!而-2147483648正好是Java语言中int类型所能表示的最小整数.这个运行结果可能会让很多人感到大跌眼镜,运行

C语言中的函数、数组与指针

1.函数:当程序很小的时候,我们可以使用一个main函数就能搞定,但当程序变大的时候,就超出了人的大脑承受范围,逻辑不清了,这时候就需要把一个大程序分成许多小的模块来组织,于是就出现了函数概念:  函数是C语言代码的基本组成部分,它是一个小的模块,整个程序由很多个功能独立的模块(函数)组成.这就是程序设计的基本分化方法: (1) 写一个函数的关键: 函数定义:函数的定义是这个函数的实现,函数定义中包含了函数体,函数体中的代码段决定了这个函数的功能: 函数声明:函数声明也称函数原型声明,函数的原型

c语言中结构体指针

1.指向结构体的指针变量: C 语言中->是一个整体,它是用于指向结构体,假设我们在程序中定义了一个结构体,然后声明一个指针变量指向这个结构体,那么我们要用指针取出结构体中的数据,就要用到指向运算符"->". 举例说明: struct SunLL { int a; int b; int c; }; struct SunLL  * p;               //定义结构体指针 struct SunLL A = {1,2,3};    //定义一个SunLL类型的变量A

C语言中内存分配那些事儿

C程序的内存结构 C语言的之所以复杂,首先它的内存模型功不可没.不像某些那样的高级语言只需要在使用对象的时候,用new创建.所有之后的事情,你不需要操心.对于C语言,所有与内存相关的东西,都需要熟悉,否则,时间一久,总会踩着雷.下图是典型的一个C程序的内存结构,当然还有一个重要的前提,这样的一种布局是在虚拟内存中的: 关于虚拟内存内核维护了一个页表(page table),用来表示虚拟内存对物理内存地址或者磁盘(交换区,swap area)间的一种映射关系.并非所有的虚拟地址都需要在物理内存上映

Coursera-Getting and Cleaning Data-week4-R语言中的正则表达式以及文本处理

Coursera-Getting and Cleaning Data-Week4 Thursday, January 29, 2015 补上第四周笔记,以及本次课程总结. 第四周课程主要针对text进行处理.里面包括 1.变量名的处理 2.正则表达式 3.日期处理(参见swirl lubridate包练习) 首先,变量名的处理,奉行两个原则,1)统一大小写tolower/toupper:2)去掉在导入数据时,因为特殊字符导致的合并变量 3)不要重复:4)少用代码缩写 使用的函数包括 替换查找: