一道简单的C语言题。有关double的溢出
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一道简单的C语言题。有关double的溢出相关的知识,希望对你有一定的参考价值。
#include<stdio.h>
#include<math.h>
#include<string.h>
__int64 jiecheng(int n)
__int64 sum;
int i;
sum=1;
for(i=1;i<=n;i++)
sum*=i;
return sum;
void main()
__int64 t,n,m;
double sum;
int i;
scanf("%I64d",&t);
while(t--)
sum=0;
scanf("%I64d %I64d",&n,&m);
if(m>n)
printf("0\n");
continue;
else
for(i=0;i<=m;i++)
sum=sum+pow(-1,i)/jiecheng(i);//算m个人都选错的概率
sum=sum*jiecheng(m);//M个人都选错的数目
sum=sum*jiecheng(n)/(jiecheng(m)*jiecheng(n-m));//乘以Cnm
printf("%.0f\n",sum);
看来double肯定溢出了.怎么能不溢出呢?
题目
不容易系列之(4)——考新郎
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 2000 Accepted Submission(s): 738
Problem Description
国庆期间,省城HZ刚刚举行了一场盛大的集体婚礼,为了使婚礼进行的丰富一些,司仪临时想出了有一个有意思的节目,叫做"考新郎",具体的操作是这样的:
首先,给每位新娘打扮得几乎一模一样,并盖上大大的红盖头随机坐成一排;
然后,让各位新郎寻找自己的新娘.每人只准找一个,并且不允许多人找一个.
最后,揭开盖头,如果找错了对象就要当众跪搓衣板...
看来做新郎也不是容易的事情...
假设一共有N对新婚夫妇,其中有M个新郎找错了新娘,求发生这种情况一共有多少种可能.
Input
输入数据的第一行是一个整数C,表示测试实例的个数,然后是C行数据,每行包含两个整数N和M(1<M<=N<=20)。
Output
对于每个测试实例,请输出一共有多少种发生这种情况的可能,每个实例的输出占一行。
Sample Input
2
2 2
3 2
Sample Output
1
3
Author
lcy
Source
递推求解专题练习(For Beginner)
Recommend
lcy
地址:http://acm.hdu.edu.cn/showproblem.php?pid=2049
这道题关键在于怎么求组合数C(N, a),其实可以不用求阶乘,也可以不溢出
有一个公式:C(N, a) = C(N-1, a-1) + C(N-1, a); (a != 0 && a != N)
根据这个公式就可以不溢出递推求出很大的组合数了,试试吧~
没看见最下面写着“递推求解专题练习”么?
你是杭电的么?HDU的acm越来越红火了~ 参考技术A 当用最大的数据作为输入:20 20
的时候,输出应该是:895014631192902121
而你的程序输出却是:895014631192902400
所以,不对啊。
原因是:产生了向下溢出,也就是把精度丢失了
不需要算阶乘的。直接用错排公式,和组合数公式。
组合数,比阶层要小很多,所以,不会溢出。本回答被提问者采纳 参考技术B printf("%.0f\n",sum);溢出应该是发生在这里.改为
printf("%.0lf\n",sum);双精度浮点输出
有关java类对象初始化的话题,从一道面试题切入
最近在整理东西时,刚好碰到以前看的一道有关java类、对象初始化相关题目,觉得答案并不是非常好(记忆点比较差,不是很连贯)。加上刚好复习完类加载全过程的五个阶段(加载-验证-准备-解析-初始化),所以如果周志明大大诚不我欺的话,无论是类加载过程、还是实例化过程的顺序我都已经了然于心了才对。
一道面试题
标题:2015携程JAVA工程师笔试题(基础却又没多少人做对的面向对象面试题)
地址:https://zhuanlan.zhihu.com/p/25746159
该题代码如下:
public class Base { private String baseName = "base"; public Base() { callName(); } public void callName() { System.out.println(baseName); } static class Sub extends Base { private String baseName = "sub"; public void callName() { System.out.println(baseName); } } public static void main(String[] args) { Base b = new Sub(); } }
问题:main函数运行后输出什么?
关于类初始化、实例化的问题该如何分析
输出base?sub?还是null?
这个题从第一次搜集,到复习,两遍......在不看答案的情况下愣是没想起来......好在COPY AND PASTE工程师有一样法宝就是“能run 的先 run一遍再说”。憋着不看答案,run完之后,结果是null。不是很震惊,因为如果是base或sub的话,这道题的层次就不会很高。
嗯,那为什么是null呢?看一下原链接给的提示(文末总结)是什么:
看完之后,一口老血吐了出来,错别字先不提,主要是记忆点比较模糊,像这种“父类静态块 ->子类静态块 ->父类初始化语句”,还有这种“父类构造函器 ->子类初始化语句 子类构造器”,怎么记?死记硬背吗?但是顺序是没有错的,每个位置的表达上还是有些问题。
看得出来答主是按照本题的代码进行分析,最后我也会给出一个执行顺序和原文总结中顺序一致的代码出来,其实就是把静态语句块(static{}),实例初始化块({}),Sub类的无参构造器补齐之后(同时调整一下类的位置)观察它们的输出顺序的结果。
整理概念
工欲善其事必先利其器,这里有必要把一些概念理清楚。
(1)类初始化阶段,和 实例初始化
因为类加载有“初始化”阶段,而实例初始化也常被工程师们叫做“初始化”,因此,为了区分这两个“初始化”,类加载过程的初始化我叫它“类初始化阶段”,实例初始化过程我叫它“实例初始化”(不用简称,免得混乱)。
先看下类加载的初始化阶段到底是什么:
上图是《深入java虚拟机》周志明大大的第二版书,第7章第2节内容的第一张图。这7个阶段中和程序员比较有关系的是loading、preparation和initialization。后面会讲到。
类的初始化阶段是其生命周期中第5个阶段。让我们看看这个阶段干了些什么:初始化阶段是执行类构造器<cinit>()方法的过程。而<cinit>()方法是由编译器自动收集的类中所有类变量的赋值动作(static变量,且必须有赋值动作的语句)和静态语句块(static{})后合并产生的。更直白的说法就是<cinit>()方法是由static赋值动作加上static{}块合并而成的,它只针对静态变量(前面标蓝字体的原因)。收集的顺序就是你的static语句和static{}块的书写顺序。
(2)静态语句块(static{}),和 实例初始化块({})
原题代码中没有书写static{} 也没有 {} 。后面我的补充代码会展示出来。
(3)类加载,和 类初始化
所谓的类加载应该指的是类生命周期中的前五个阶段,从loading到initialization。在《深入java虚拟机》书中7.2节提到的五个必须进行“初始化”的情况中就包括“先初始化父类”这一条。通常在泛指的时候,可以把“类加载”和“类初始化”对等起来,它们表示类声明周期的前五个阶段,这样在口头表述的时候比较便利。
分析题目
将类初始化阶段和实例初始化这两个概念区分开后,再分析这个题目。
main函数中的代码非常简单:Base b = new Sub(); 。但是注意,这行代码是在Base.java文件的main方法中,而运行main函数,JVM首先要加载这个main函数所在的类,即运行这行代码时JVM会先加载Base到方法区中,可能会对加载顺序的判断有一丢丢影响(所以后面我重写了代码,Base和Sub放在Xase中,让Xase类运行main方法)。
好,绕了一点弯路,重新回到题目上,前面提到最近刚好复习了类加载过程的五个阶段(生命周期中到“使用”之前的那五个阶段),不知道书上有没有提到实例初始化过程?这个阶段涉及到如本题中main函数所示的代码如何初始化(b的静态类型是Base,实际类型是Sub),不过其实也就是子类覆盖父类方法如何执行的问题,剩下的那些实例变量和实例初始化块执行顺序都是很容易理解的。
经过自己debug(原文中也提到)也能发现Sub类覆盖Base的callName后实际运行时该方法的指针是指向Sub的callName方法的(debug时将看到执行Base构造函数中的callName时会跳转到Sub的callName方法上)。所以到这里可以“假装”我们也掌握了实例初始化相关的知识点。
有了以上两个知识点,开始分析代码,在main函数中执行代码Base b = new Sub(); JVM会按顺序将 Base、Sub类的class文件加载到方法区(完成生命周期的前五个阶段)。由于原文中main方法在Base中,所以Base已经加载过,所以执行这段代码时,只是加载Sub的class文件(后面加载类简称加载)。JVM如果要加载Sub类,要先保证它的父类Base被加载。因此无论是代码顺序保证的,还是类加载机制保证的,Base类都会先于Sub类被加载到方法区。
(1)加载类阶段
好了,目前为止Base和Sub都加载完了,由类加载机制保证父类先加载,所以 父类的类初始化 先于 子类的类初始化 完成。
即,实例初始化前,有 父类的类初始化 -> 子类的类初始化。
前面说过,<cinit>()方法的执行是按 有赋值动作static语句 和 static语句块 的书写顺序执行的,经过类的类初始化阶段后,每个类的类变量都已经初始化完成。
本题中没有类变量,但学习时我们可以自己加上,后面我会再贴出另一道类初始化和实例化相关的题,该题中你可以认识到 static语句的赋值操作语句 和 非赋值操作语句 是如何被<cinit>()收集的(你可以在每个static语句上都打上断点,了解为什么书里介绍<cinit>()时说的是“只搜集 类变量的赋值操作”)。
(2)实例化阶段
接下来执行 new Sub();
由于Sub没有定义构造函数,JVM生成一个默认构造函数(无参构造函数)。子类的任意构造函数中都有一个隐式的super()来调用父类的默认构造函数,保证继承的实例字段正确初始化。如果父类自己写了带参的构造函数,子类构造函数要明确指定调用一个父类的构造函数。
这里,两个类都是无参构造函数,所以Sub的默认构造函数可以调用到Base的无参构造函数。
到这里可以明确的关系有:父类的类初始化 -> 子类的类初始化 -> ...... -> 父类构造函数 -> ...... -> 子类构造函数。
你会看到每次推导出来的顺序阶段都加上蓝色字体,便于比较。
类变量的初始化过程上面介绍过,叫做 <cinit>()方法,通过 debug static语句可以看到debug栈的提示。<cinit>()方法是按static书写顺序执行的。那么实例初始化过程呢?其实实例初始化也对应一个<init>()方法,它应该(还没看到对应的资料)也是类似的执行过程,作用是帮我们初始化类的实例变量(搜集 实例变量赋值操作 和 实例初始化块)。
因为 <cinit>()方法只是我们广义上说的 类初始化 的第5个阶段 initialization,因此在这里并不想用<cinit>()去替换"类初始化“。
那么 <init>()方法 和 构造函数 执行顺序如何?把 构造函数 放在 {}块和实例变量赋值操作 的前面,通过 debug 可以确定的关系:init()方法 -> 构造函数。
那么 <inti>()方法中 实例初始化块{} 以及 实例变量赋值操作 的调用顺序是怎样的?还是通过 debug,可以知道顺序为: 和 <cinit>()方法类似,是按书写的顺序,并且只搜集有赋值操作的语句。如 private int a = 2; 会搜集。 private int b; 则不搜集。
接下来请允许我用 <init>()方法 来表示 “实例初始化块{} 以及 实例变量赋值操作 ”。它看起来比 <cinit>()更能表达一个阶段。
因此,目前可以确定的调用顺序为(加上标号):①父类的类初始化 -> ②子类的类初始化 -> ③父类的<init>()方法 -> ④父类构造函数 -> ⑤子类的<init>()方法 -> ⑥子类构造函数。
前面分析到执行Sub构造函数之前,会先调用Base的构造函数,在Base构造函数中调用前, Base的<init>()方法已经执行完,所以baseName = "base" 。
查阅《深入java虚拟机》7.3.3节,在 准备阶段(对应上述①),生命周期的第3个阶段 会为 类变量 赋“零值”,但是 baseName的定义不是 类变量,只是普通的实例变量,可以套用这个规则吗?
既然<cinit>()方法是修改 类变量的零值,<init>()方法应该相应的会修改 实例变量的零值。实例变量在堆上分配时应该也有零值(道理应该是这样,JVM设计不会搞这么复杂,两种变量还搞两套零值,通过debug也能看到两种变量零值是一样的)。baseName为 String类型对应 reference类型,所以它的零值为null(其他例如 int零值为 0,long零值为 0L),执行完<init>()方法后(对应上述③),baseName = "base"。
继续分析,Base的构造函数(对应上述④)又调用了 callName方法,由于Sub类重写了Base类的 callName,所以 debug时可以看到跳转到Sub的 callName中,此时,程序只运行到上述④的阶段,Sub类的 <init>()方法 和 Sub类的构造函数(对应上述⑤ 和 ⑥)都没有运行到。根据 上一段的分析可知,此时 Sub的实例变量 baseName只有零值 null,因此 callName() 执行后将输出 null(也是本题的结果)。
接着,Base构造函数调用结束,进入上述 ⑤ 和 ⑥ 的执行过程。执行完 ⑤ 后 Sub的 baseName = "sub"。⑥ 过程没有执行任何东西,整个过程就结束了。
总结
通过这道面试题的学习,你应该了解到,new ClassName(); 其实分两个大的阶段,第一个是先加载类(五个阶段)到方法区,对应上述①和②两个阶段;第二个才是使用加载好的class对象创建实例(实例初始化过程),对应上述③~⑥ 四个阶段。
最后调整过后的代码如下:
/** * https://zhuanlan.zhihu.com/p/25746159 * 2015携程JAVA工程师笔试题(基础却又没多少人做对的面向对象面试题) */ public class Xase { { System.out.println("I‘m Xase {}"); } static { System.out.println("I‘m Xase static {}"); } static class Base { public Base() { System.out.println("Base constructor"); callName(); } { // base的实例初始化块 System.out.println("BASE {}"); } private String baseName = "base"; static { // base的静态语句块 System.out.println("Base static {}"); } public void callName() { System.out.println(baseName); } } static class Sub extends Base { private String baseName = "sub"; public Sub() { System.out.println("Sub constructor"); } { System.out.println("Sub {}"); b = 3; } private int a = 2; private int b; static { // sub的静态语句块 System.out.println("Sub static {}"); } public void callName() { System.out.println(baseName); } } public static void main(String[] args) { Base b = new Sub(); } }
Xase包裹了Base和Sub,作为一道面试题“拥挤”一点也不是什么毛病。其中main方法在上述代码中放在Xase类中,如果你不想显示Xase的静态代码块的打印,可以把main方法挪到Sub方法里。
debug时,在每一个 {} ,static{} ,构造函数,实例变量 和 类变量上都打上断点,就可以清楚观察到对象实例化时各个代码调用的顺序。
附我的Xase代码输出:
最后的最后
说好的附加题,http://www.cnblogs.com/javaee6/p/3714716.html
这道题中可以直接应用《深入理解java虚拟机》7.3.3节的零值规则,大不了不对还可以吐槽一下周大大。分析的过程也相对少一些(比开头那题少40%~50%分析量吧)。
掌握了真正的分析方法后,这种类型的题已经不是什么问题。
其实,要全面一点的话,内部类那一块的初始化也应该练一练,但是之前没有找到好的题目,如果大家有好的题目可以实战,欢迎在评论区砸我~哈哈
以上是关于一道简单的C语言题。有关double的溢出的主要内容,如果未能解决你的问题,请参考以下文章