关于数据类型的习题

开篇说两句

这一部分内容是针对第二部分知识点的练习题。之所以单独成一个部分,原因有二:

  1. 编程是个系统性、实践性很强的技术,但包含的内容非常多,想要学会、学好,就必须多写、多思考。
  2. 如果把这些题目都加到前一部分,每一篇文章的篇幅都会很长(现在看起来已经很长了),所以第二部分侧重理论,这一部分则侧重实践.

溢出问题

我们都知道,整数类型一共有四种,我们需要根据实际情况选择合适的类型。如果使用到的数值比较小,但选用的数据类型占用空间比较多,就会造成计算机资源的浪费;如果使用到的数值比较大,但选用的数据类型占用空间比较少,就会出现溢出,导致最终结果错误,并造成不可估量的损失。1996年,欧洲的Ariane 5火箭因为溢出错误,发射37秒后爆炸,直接损失高达6亿美元。
所谓溢出,是指待使用的数据需要的内存空间超过了目标变量占用的内存空间,导致数据无法完全存入内存,进而发生错误。例如,byte类型占用一个字节的内存空间,它能表示的数值范围是-128到127之间。当我们把一个超出这个范围的数值存入这个内存空间时,就像把一瓶矿泉水(500ML)导入一个300ML的水杯中,必然有一部分水会溢出
溢出比较容易发生在涉及整型变量的情况下。在两种情况下比较容易出现溢出:数据类型转换,计算。
我们来看程序:

/*
 * 关于溢出问题的演示 
 */
public class Overflow {
    public static void main(String[] args) {
        //这是最简单的溢出的例子
        byte b;
        b=127;
        System.out.println("b="+b+",b+1="+(++b));
        //因为赋值带来的溢出问题
        int i=70023;
        //70023超出了short类型所能表示的最大值,产生溢出
        short s=(short)i;
        //赋值前后数值不同,比较容易发现
        System.out.println("i="+i+",s="+s);
        //计算中产生的溢出较难发现。例如,计算15的阶乘
        int result,r;//result用于保存结果,r用于控制循环次数
        for(result=1,r=15;r>=1;r--) {
            result=result*r;
        }
        //实际上,15的阶乘为1,307,674,368,000‬
        System.out.println("15!="+result);
        //在循环中,用于控制循环次数的变量往往会进行自增或自减运算,也容易发生溢出
        b=0;
        while(true) {
            System.out.println("b="+b);
            b++;
            //每次循环b的值加1,但当b为负数时,循环结束
            if(b<0) {
                System.out.println("b通过自增变成负数了,不可理解,我要停下来了");
                break;
            }
        }
        //同样的,自减也会将一个负数变成正数
        b=0;
        while(true) {
            System.out.println("b="+b);
            b--;
            //每次循环b的值加1,但当b为负数时,循环结束
            if(b>0) {
                System.out.println("b通过自减变成正数了,不可理解,我要停下来了");
                break;
            }
        }

    }
}

发生溢出的背后是什么情况?我们来了解一点关于整数存储的知识。在计算机中,整数分为两部分存储,一是符号位(0表示正数,1表示负数),二是数值位。以byte类型为例,它占用1个字节,8个字位的存储空间,其中最左边一位(这是按照书写习惯区分的左右,电路上一般称高位低位)表示正负号,剩下的表示数值。如果是正数,直接将其转换为二进制形式存入内存,例如127的二进制形式就是01111111;如果是负数,则使用补码,先求绝对值,转换为二进制,取反,加1,例如,-128的二进制形式就是10000000。当我们给byte类型的127加1时,它的二进制形式就从01111111变成了10000000,由于最左边从0变成了1,因此计算机会把10000000当成负数来处理,最终识别成了-128
赋值时发生的溢出与此类似,比较大的数值赋给空间小的变量时,计算机会截取一部分进行赋值。例如70023,可以存入int类型的变量中(4个字节),当赋值给short类型时(2个字节),就会截取右边两个字节(00000000000000010001000110000111只保留右边两个字节:0001000110000111‬),所以int类型的70023赋值给了short类型变量,就成了4487。
有兴趣的朋友,可以手工算一下,为什么byte类型的变量b,从0开始自减,最终会变成正数。
由于溢出问题,我们在编程时,要慎重选择合适的数据类型,避免出现溢出;但也不能为了省事,所有数据选择long这样的类型,那样会造成大量计算机资源的浪费。

03

 

实数的精度

虽然实数可以用来表示非常大或者非常小的数据,但要注意,无论是float还是double,精确度都是有限的。double类型可以表示高达10的308次方这个级别的数值,也可以表示小到10的-308次方这个级别,但仍不代表Java中的实数是绝对精确的,原因如下:

  1. 实数在内存中分为三个部分,符号位,指数位,尾数位。符号位表示正负,指数位和尾数位共同表示数值。这个机制决定了实数不可能无限制精确,只能实现部分精确,float类型有7到8位有效数字,double类型有16到17位有效数字。有效数字部分是准确的,其余部分则不能保证准确。
  2. 十进制和二进制无法在实数领域精确转换,例如十进制的0.1,就无法精确转换为二进制。

因此,实数类型的精确度是有限的,使用时应该注意。

/*
 * 实数的精确度问题 
 */
public class Precision {
    public static void main(String[] args) {
        float f=987654321F;
        //输出f的值,我们可以发现最后几位是不准确的
        System.out.println("单精度实数f="+f);
        System.out.printf("单精度实数f=%f\n",f);
        System.out.println("比f大5的数字是:"+(f+5));
        System.out.printf("比f大5的数字是:%f\n",(f+5));
        //实数取余数
        System.out.println("5.6除以3.2的余数是:"+(5.6%3.2));
        //实数相除
        System.out.println("10.0除以3.0="+(10.0/3.0));
    }
}

03

 

字符和整数

计算机不能直接表示字符,需要通过字符和二进制编号的映射关系,才能处理字符。因此在计算机当中字符实际上就是一个一个的编号。因此我们可以针对这些字符进行处理,也可以针对这些编号进行处理。字符类型数据在进行运算的时候会转换为int类型。下面我们来看一个程序:

/*
* 字符型数据的整数特性
*/
public class CharInt {
    public static void main(String[] args) {
        char c='A';
        System.out.println("输出字符:"+c);
        //字符可以被转化为整数
        System.out.println("输出字符对应的编号:"+(int)c);
        //在运算过程中字符数据会被自动转换为int类型
        System.out.println("字符和整数在一起运算:"+(c+2));
        System.out.println("字符运算的结果也可以是字符:"+(char)(c+2));
        //显示所有的字符,这里只显示前128个
        //如果想查看所有字符及其编码,可以将循环条件设置为  i<=Character.MAX_VALUE
        for (int i=Character.MIN_VALUE;i<=128;i++) {
            System.out.println("编号为:" + i + ",十六进制形式是:" + Integer.toHexString(i) + ",对应字符是:" + (char) i);
        }
        System.out.println("字符会发生溢出吗?"+(char)(Character.MAX_VALUE+98));
    }
}

需要说明的是,字符型数据也会发生类似“溢出”的情况。(char)(Character.MAX_VALUE+98)的结果是字母’a’。这是由于字符型数据在内存中占用了2字节空间,所能表示的最大值是1111111111111111(二进制形式,16个1),再加1,就会变成10000000000000000(二进制形式,17位,1后面有16个0,由于进行加1操作时,字符型数据会转换为int数据,所以这17位数据在内存中是真实存在的)。转换为char类型时,计算机会截取整数(4字节)中最后两个字节,所以这时的数据就是0000000000000000(二进制形式,16个0)。看来有些像“溢出”现象。

03

字母大小写转换

如果我们登录一个网站,经常需要输入验证码。在很多时候,输入的验证码是不区分字母大小写的。怎么实现这个效果呢?我们可以把输入的字符统一转换为大写或者小写字母就可以了。转换的原理也很简单,大写字母加上32就可以转换为小写字母,反过来,小写字母减去32就可以转换为大写字母。这是因为在字符编码中,同一个字母的大小写字符的编号相差32。

UpperLower.java程序

/*
该类包含将英文字符转换为大写或小写的方法
 */
public class UpperLower {
    //大小字母之间的差值
    int l = 'a' - 'A';

    //所有字母转换为大写,当字母为小写,减去l即可
    public String upper(String s) {
        char[] chars = s.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            //判断字母是否为小写
            if (chars[i] >= 'a' && chars[i] <= 'z') {
                chars[i] = (char) (chars[i] - l);
            }
        }
        return new String(chars);
    }

    //所有字母转换为小写,当字母为大写,加上l即可
    public String lower(String s) {
        char[] chars = s.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            //判断字母是否为大写
            if (chars[i] >= 'A' && chars[i] <= 'Z') {
                chars[i] = (char) (chars[i] + l);
            }
        }
        return new String(chars);
    }
}

UpperLowerMain.java程序,要求用户输入字符串,调用UpperLower类中的方法,实现大小写转换

/*
用户输入英文字符串,并选择转换为大写还是小写
 */
import java.util.Scanner;
public class UpperLowerMain {
    public static void main(String[] args) {
        UpperLower ul=new UpperLower();
        Scanner sc=new Scanner(System.in);
        System.out.println("请输入一个英文字符串:");
        String s=sc.nextLine();
        System.out.println("请选择转换类型,1:转换为大写;2:转换为小写");
        int x=sc.nextInt();
        if(x==1){
            System.out.println(ul.upper(s));
        }else if(x==2){
            System.out.println(ul.lower(s));
        }else{
            System.out.println("选择错误");
        }
        sc.close();
    }
}

03

 

凯撒算法

凯撒算法据说由凯撒大帝发明。这是一种基于字符替换的加密算法,以英文字母为例,基本思路是:设定一个密钥key为n,在加密时,就将明文中的字母替换为其后的第n个字符。例如,现有单词Apple,key为4,那么加密后的数据就是Ettpj。对于x、y、z等处于末端的字母,有两种处理方式:一是直接使用字符集中后面的字符替代,例如字符’X’会被替换为’\‘;二是使用开头部分字母替代,此时’X’可以替换为’B’。
第一种方法比较简单,这里就不演示了,有兴趣的同学可以自己尝试。第二种方法如果不好理解,我们可以把字母表看做一个环:

A B C D E F G H I J
Z                 K
Y                 L
X                 M
W V U T S R Q P O N

下面是第二种方法的程序,该程序由两个源文件组成。
Caesar.java

/*
凯撒算法的加密解密程序
 */
public class Caesar {
    //加密算法,将参数字符串s用密钥key加密,返回加密后的字符串
    public String encode(String s,int key){
        //将字符串转换为字符数组,方便针对每一个字符加密
        char[] c=s.toCharArray();
        //依次处理每一个字符
        for(int i=0;i<c.length;i++){
            //大写字母的处理
            if(c[i]>='A' && c[i]<='Z'){
                //(c[i]-'A'+key)%26用于求从'A'字符开始偏移的位置
                //(c[i]-'A'+key)表示加密后字符相对'A'的偏移量
                //如果小于26,取余结果为其本身
                //如果大于26,取余结果为重新从首字符'A'的偏移量
                //把26个字母排列成一圈,会更好理解,同学们可以试试看
                c[i]=(char)('A'+(c[i]-'A'+key)%26);
            }
            //小写字母的处理
            if(c[i]>='a' && c[i]<='z'){
                //(c[i]-'A'+key)%26用于求从'a'字符开始偏移的位置
                c[i]=(char)('a'+(c[i]-'a'+key)%26);
            }
        }
        //非字母不处理,将字符数组转换为字符串后返回
        return new String(c);
    }

    //解密算法,将参数字符串用密钥key解密,返回解密后的字符串
    public String decode(String s,int key){
        //用26-key重新加密即可
        return encode(s,26-key);
    }
}

CaesarMain.java

/*
可以让用户选择加密解密功能的程序
 */
import java.util.Scanner;

public class CaesarMain {
    public static void main(String[] args) {
        //创建Caesar对象,用于加密解密
        Caesar caesar=new Caesar();
        //Scanner对象sc用于捕捉用户输入
        Scanner sc=new Scanner(System.in);
        //choice用于存储用户的选择,encode用于存储待加密信息,decode用于存储待解密信息,key为密钥
        int choice;
        String encode;
        String decode;
        int key;
        //用户可以选择何时退出
        while (true){
            //用户的选择
            System.out.println("1表示加密,2表示解密,0表示退出,请选择:");
            //捕获整数
            choice=sc.nextInt();
            //捕获缓冲区中剩余的字符,\n,换行符
            sc.nextLine();
            //如果用户选择0,关闭Scanner对象sc,退出
            if(choice==0){
                sc.close();
                break;
            }else if(choice==1){
                //如果用户选择1,要求用户输入待加密信息和密钥,加密并输出密文
                System.out.println("请输入要加密的信息:");
                encode=sc.nextLine();
                System.out.println("请输入密钥:");
                key=sc.nextInt();
                System.out.println(caesar.encode(encode,key));
            }else if(choice==2){
                //如果用户选择2,要求用户输入待解密信息和密钥,加密并输出密文
                System.out.println("请输入要解密的信息:");
                encode=sc.nextLine();
                System.out.println("请输入密钥:");
                key=sc.nextInt();
                System.out.println(caesar.decode(encode,key));
            }else{
                System.out.println("选择错误,请重新选择");
            }
        }
    }
}

CaesarMain.java中有这么一行sc.nextLine();。这是由于nextInt()方法会将缓冲区中的数据转换为int类型数据,但缓冲区并没有被彻底清空(该方法会把一些分隔符排除掉,包括回车、换行等),所以需要再次调用nextLine()方法将缓冲区剩下的数据处理掉。否则剩下的数据会在下一次调用输入方法时被捕获,从而保存到encode或者decode字符串中。

03

 

字符集转换

字符集就是前面提到的将字符和二进制编号对应起来的“关系表”。由于种种原因,历史上出现了很多种字符集,而且有很多到现在还在使用。不同字符集对同一个字符的编号是不同的,如果处理不当,很容易出现乱码。Java中可以方便地转换信息的字符集。这里介绍一种针对字符串的处理方法。我们可以在创建字符串对象的时候,在构造方法中指定要使用的字符集。
看程序:

import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Set;

public class ChangeCharSet {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String s = "现代汉语词典";
        //将字符串转换为字节型数组
        byte[] bytes = s.getBytes();
        //使用GBK编码重新生成字符串
        String sGBK = new String(bytes, "GBK");
        //输出新字符串,由于GBK不是默认字符集,所以会输出乱码
        System.out.println("sGBK为GBK字符集:"+sGBK);
        //使用UTF-8输出
        System.out.println("转换为UTF-8字符集输出:"+new String(sGBK.getBytes("GBK"),"UTF-8"));
        //查看Java支持的字符编码
        System.out.println("Java支持的字符集包括:");
        Map<String, Charset> map = Charset.availableCharsets();
        Set<Map.Entry<String, Charset>> entrySet = map.entrySet();
        for (Map.Entry<String, Charset> entry : entrySet) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }
    }
}

程序的最后,显示了Java目前支持的字符集。程序运行结果为(这里只展示前面一部分):

sGBK为GBK字符集:鐜颁唬姹夎璇嶅吀
转换为UTF-8字符集输出:现代汉语词典
Big5=Big5
Big5-HKSCS=Big5-HKSCS
CESU-8=CESU-8
EUC-JP=EUC-JP
EUC-KR=EUC-KR
GB18030=GB18030
GB2312=GB2312
GBK=GBK

注意,System.out.println()方法默认是按照系统字符集或设定字符集输出数据的。本例中,Java程序被设置为使用UTF-8字符集,所以输出GBK字符集的信息时会出现乱码。具体设定方法在第一部分讲过,还记得吗?

03

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值