Java编程的逻辑——编程基础和二进制

编程基础和二进制

数据类型和变量

基本类型

  • 整型类型:

    类型名取值范围占用内存
    byte-2^7 ~ (2^7) - 1 即 -128~1271B
    short-2^15 ~ (2^15) - 1 即 -32768~327672B
    int-2^31 ~ (2^31) - 14B
    long-2^63 ~ (2^63) - 18B

在给long类型赋值时,如果常量超过了int的表示范围,需要在常量后面加大写或小写字母L,即L或1

因为数字常量默认为是int类型

  • 小数类型
类型名取值范围占用内存
float1.4E-45 ~ 3.4E+38
-3.4E+38~-1.4E-45
4B
double4.9E-324~1.7E+308
-1.7E+308~-4.9E-324
8B

E表示以10为底的指数,E后面的+号和-号代表正指数和负指数,例如:14E-45表示14乘以10的-45次方

对于 double,直接把熟悉的小数表示赋值给变量即可

但对于float,需要在数字后面加大写字母F或小写字母f

这是由于小数常量默认是 double类型

  • 真假类型boolean:true/false
  • 字符类型char,占两个字节,用单引号

数组

  • 赋值方式
1. int[] arr = {1,2,3};
2. int[] arr = new int[]{1,2,3};
3. int[] arr = new int[3];
//即使没有给每个元素赋值,每个元素也都有一个默认值,
//这个默认值跟数组类型有关,数值类型的值为0, boolean为false,char为空字符  
 arr[0]=1; arr[1]=2; arr[2]=3;

注意:

  1. 数组长度虽然可以动态确定,但定了之后就不可以变 数组有一个 length属性,但只能读,不能改

  2. 不能在给定初始值的同时给定长度,即如下格式是***不允许***的:

    int[] arr =new int[3]{1,2,3}
    

    理解:因为初始值已经决定了长度,再给个长度,如果还不一致,计算机将无所适从

数组与基本类型的不同

  • 一个基本类型変量,内存中只会有一块对应的内存空间

  • 但数组有两块:一块用于存储数组内容本身,另一块用于存储内容的位置

image-20210605143529275

  • 给数组变量赋值和给数组中元素赋值是两回事,给数组中元素赋值是改变数组内容
  • 而给数组变量赋值则会让变量指向一个不同的位置
  • 数组的长度是不可以变的,不可变指的是数组的内容空间,一经分配,长度就不能再变
  • 但可以改变数组变量的值,让它指向一个长度不同的空间

基本运算

算术运算

  • 整数相除不是四舍五入,而是直接舍去小数位,如:
double d = 10/4;//结果是2,不是2.5
//正确写法
a) double d = 10/4.0;
b) double d = 10/(double)4;
  • 自增与自减

    如果只是对自己操作,这两种形式也没什么差别,区别在于还有其他操作的时候 放在变量后
    (a++)是先用原来的值进行其他操作,然后再对自己做修改,而放在变量前(++a)是先对自己做修
    改,再用修改后的值进行其他操作

  • 比较运算

    对于数组,==判断的是两个变量指向的是不是同一个数组,而不是两个数组的元素内容是否一样,即使两个数组的内容是一样的,但如果是两个不同的数组,==依然会返回 false,如:

    int[] a = new int[] {1,2,3};
    int[] b = new int[] {1,2,3};
    //a==b的结果是false
    

    如果需要比较数组的内容是否一样,需要逐个比较里面存储的每个元素

  • 逻辑运算

    短路与(&&):和&类似,不同之处:&两边都计算,&&左边为假则右边的不计算
    短路或(||):与|类似,不同之处同上

条件执行

条件语句为tue,则执行括号{}中的代码,如果后面没有括号,则执行后面第一个分号(;)前的代码

if的陷阱:初学者有时会忘记在if后面的代码块中加括号,有时希望执行多条语句而没有加括号,结果只会执行第一条语句,建议所有if后面都加括号

if/else陷阱:需要注意的是,在 if/else中,判断的顺序是很重要的,后面的判断只有在前面的条件为 false的时候オ会执行

  • switch
switch(表达式){
 case1:
	 	代码1; break;
 case2:
	 	代码2; break;
        ...
        ...
 case 值n:
	 	代码n; break;        
 default:代码n+1
}

根据表达式的值找匹配的case,找到后执行后面的代码,碰到 breakl时结束,如果没有找到匹配的值则执行 default后的语句 表达式值的数据类型只能是byte、 short、int、char、枚举和 String(Java7以后)

简单总结下:

  • 单一条件满足时,执行某操作使用if;
  • 根据一个条件是否满足执行不同分支使用 if/else;
  • 表达复杂的条件使用if/else;
  • 条件赋值使用三元运算符
  • 根据某个表达式的值不同执行不同的分支使用 switch
  • 从逻辑上讲, if/else、if/ else if/else、三元运算符、 switchi都可以只用if代替,但使用不同的语法表达更简洁,在条件比较多的时侯, switch从性能上看也更高

swich性能高的原因

switch的转换和具体系统实现有关 如果分支比较少,可能会转换为跳转指令

如果分支比较多,使用条件跳转会进行很多次的比较运算,效率比较低,可能会使用一种更为高效的方式,叫跳转表 跳转表是一个映射表,存储了可能的值以及要跳转到的地址

跳转表高效的原因:因为其中的值必须为整数,且按大小顺序排序

  • 按大小排序的整数可以使用高效的二分査找
  • 如果值是连续的,则跳转表还会进行特殊优化,优化为一个数组,连找都不用找了,值就是数组的下标索引,直接根据值就可以找到跳转的地址
  • 即使值不是连续的,但数字比较密集,差的不多,编译器也可能会优化为一个数组型的跳转表,没有的值指向 default分支
  • 程序源代码中的case值排列不要求是排序的,编译器会自动排序

switch表达式的数据类型限制的原因:

  • 其中byte/ short/int本来就是整数,char本质上也是整数
  • 不可以使用long,为什么呢?跳转表值的存储空间一般为32位,容纳不下long
  • 枚举类型也有对应的整数
  • String用于 switche时也会转换为整数
  • String是通过hash Code方法转换为整数的,但不同 Stringl的 hashCode可能相同,跳转后会再次根据 String的内容进行比较判断

循环的4中形式

  • while

  • do-while:如果不管条件语句是什么,代码块都会至少执行一次,则可以使用do/ while循环

  • for

  • foreach

    foreach不是一个关键字,它使用冒号:,冒号前面是循环中的每个元素,包括数据类型和变量名称,冒号后面是要遍历的数组或集合(第9章介绍),每次循环 element都会自动更新 对于不需要使用索引变量,只是简单遍历的情况, foreach语法上更为简洁

    int[] arr = {1,2,3,4};
    for(int element : arr){
     System.out.println(element);
    }
    

循环控制

  • break:用于提前结束循环
  • continue:跳过循环体中剩下的代码,然后执行步进操作

函数

public static void main(String[] args){
    ...
}
  • main函数:表示程序的入口
  • String[] args 表示从控制台接受到的参数
  • Java中运行一个程序的时候,需要指定一个定义了main函数的类,Java会寻找main函数,并从main函数开始执行
  • 定义函数时声明参数,实际上就是定义变量,只是这些变量的值是未知的,调用函数时传递参数,实际上就是给函数中的变量赋值
  • 对于需要重复执行的代码,可以定义函数,然后在需要的地方调用,这样可以减少重复代码 对于复杂的操作,可以将操作分为多个函数,会使得代码更加易读

参数传递

有两类特殊类型的参数:数组和可变长度的参数

数组

数组作为参数与基本类型是不一样的,基本类型不会对调用者中的变量造成任何影响,但数组不是,在函数内修改数组中的元素会修改调用者中的数组内容

public static void reset(int[] arr){
     for(int i=0;i<arr.length;i++){
 			arr[i] = i;
	}
}
public static void main(String[] args) {
 int[] arr = {10,20,30,40};
 reset(arr);
 for(int i=0;i<arr.length;i++){
 	System.out.println(arr[i]);
 }
}

在reset函数内给参数数组元素赋值,在main函数中数组arr的值也会变

  • 数组变量有两块空间,一块用于存储数组内容本身,另一块用于存储内容的位置,给数组变量赋值不会影响原有的数组内容本身,而只会让数组变量指向一个不同的数组内容空间
  • 在上例中,函数参数中的数组变量arr和main函数中的数组变量arr存储的都是相同的位置,而数组内容本身只有一份数据,所以,在reset中修改数组元素内容和在main中修改是完全一样的
可变长度的参数

前面介绍的函数,参数个数都是固定的,但有时候可能希望参数个数不是固定的,比如求若干个数的最大值,可能是两个,也可能是多个。Java支持可变长度的参数

public static int max(int min, int ... a){
 int max = min;
 for(int i=0;i<a.length;i++){
     if(max<a[i]){
     	max = a[i];
     }
 }
 return max;
}
public static void main(String[] args) {
     System.out.println(max(0));
     System.out.println(max(0,2));
     System.out.println(max(0,2,4));
     System.out.println(max(0,2,4,5));
}

可变长度参数的语法是在数据类型后面加三个点“… ”,在函数内,可变长度参数可以看作是数组 可变长度参数必须是参数列表中的最后一个,一个函数也只能有一个可变长度的参数

可变长度参数实际上会转换为数组参数,也就是说,函数声明max(int min,int… a)实际上会转换为max(int min, int[] a),在main函数调用max(0,2,4,5)的时候,实际上会转换为调用max(0, new int[]{2,4,5}),使用可变长度参数主要是简化了代码书写

函数重载

同一个类里,函数可以重名,但是参数不能完全一样,即要么参数个数不同,要么参数个数相同但至少有一个参数类型不一样 同一个类中函数名相同但参数不同的现象,一般称为函数重载 为什么需要函数重载呢?一般是因为函数想表达的意义是一样的,但参数个数或类型不一样

  • 调用的匹配过程:在只有一个函数的情况下,即没有重载,只要可以进行类型转换,就会调用该函数,在有函数重载的情况下,会调用最匹配的函数

函数调用的基本原理

栈的概念

栈一般是从高位地址向低位地址扩展,换句话说,栈底的内存地址是最高的,栈顶的是最低的

函数执行的基本原理

针对基本数据类型,函数中的参数和函数内定义的变量,都分配在栈中,这些变量只有在函数被调用的时候才分配,而且在调用结束后就被释放了

数组和对象的内存分配

public class ArrayMax {
 public static int max(int min, int[] arr) {
     int max = min;
     for(int a : arr){
         if(a>max){
         	max = a;
         }
	 }
	 return max;
 }
 public static void main(String[] args) {
     int[] arr = new int[]{2,3,4};
     int ret = max(0, arr);
     System.out.println(ret);
     }
}

main函数新建了一个数组,然后调用函数max计算0和数组中元素的最大值,在程序执行到max函数的return语句之前的时候,内存中栈和堆的情况如图

image-20210605164654670

  • 对于数组和对象类型,它们都有两块内存,一块存放实际的内容一块存放实际内容的地址实际的内容空间一般不是分配在栈上的,而是分配在堆中,但存放地址的空间是分配在栈上

  • 存放地址的栈空间会随着入栈分配,出栈释放,但存放实际内容的堆空间不受影响 但说堆空间完全不受影响是不正确的,在这个例子中,当main函数执行结束,栈空间没有变量指向它的时候,Java系统会自动进行垃圾回收,从而释放这块空间

背后的二进制

二进制、十六进制

在Java中,可以使用如下代码查看Integer和Long的二进制、十六进制

System.out.println(Integer.toBinaryString(a)); //二进制
System.out.println(Integer.toHexString(a)); //十六进制
System.out.println(Long.toBinaryString(a));//二进制 
System.out.println(Long.toHexString(a)); //十六进制

位移

位运算有移位运算和逻辑运算 移位有以下几种

  1. 左移:操作符为<<,向左移动,右边的低位补0,高位的就舍弃掉了,将二进制看作整数,左移1位就相当于乘以2。
  2. 无符号右移:操作符为>>>,向右移动,右边的舍弃掉,左边补0。
  3. 有符号右移:操作符为>>,向右移动,右边的舍弃掉,左边补什么取决于原来最高位是什么,原来是1就补1,原来是0就补0,将二进制看作整数,右移1位相当于除以2。

逻辑运算有以下几种

❑ 按位与&:两位都为1才为1。

❑ 按位或|:只要有一位为1,就为1。

❑ 按位取反~:1变为0,0变为1。

❑ 按位异或^:相异为真,相同为假

浮点数

如果想查看浮点数的具体二进制形式,在Java中,可以使用如下代码:

Integer.toBinaryString(Float.floatToIntBits(value))
Long.toBinaryString(Double.doubleToLongBits(value));

字符的编码与乱码

常见非Unicode编码

为了保持与ASCII码的兼容性,一般都是将最高位设置为1。也就是说,当最高位为0时,表示ASCII码,当为1时就是各个国家自己的字符 在这些扩展的编码中,在西欧国家中流行的是ISO 8859-1和Windows-1252,在中国是GB2312、GBK、GB18030和Big5。

Unicode编码

Unicode主要做了这么一件事,就是给所有字符分配了唯一数字编号 它并没有规定这个编号怎么对应到二进制表示,这是与上面介绍的其他编码不同的,其他编码都既规定了能表示哪些字符,又规定了每个字符对应的二进制是什么,而Unicode本身只规定了每个字符的数字编号是多少

  • 那编号怎么对应到二进制表示呢?有多种方案,主要有UTF-32、UTF-16和UTF-8。

  • 对于一个Unicode编号,具体怎么编码呢?首先将其看作整数,转化为二进制形式(去掉高位的0),然后将二进制位从右向左依次填入对应的二进制格式x中,填完后,如果对应的二进制格式还有没填的x,则设为0。

    image-20210605172039593

恢复乱码的方法

Java中处理字符串的类有String, String中有我们需要的两个重要方法

1)public byte[] getBytes(String charsetName),这个方法可以获取一个字符串的给定编码格式的二进制形式

2)public String(byte bytes[], String charsetName),这个构造方法以给定的二进制数组bytes按照编码格式charsetName解读为一个字符串

public static void recover(String str) throws UnsupportedEncodingException{
     String[] charsets = new String[]{"windows-1252","GB18030","Big5","UTF-8"};
     for(int i=0;i<charsets.length;i++){
         for(int j=0;j<charsets.length;j++){
             if(i!=j){
                 String s = new String(str.getBytes(charsets[i]),charsets[j]);
                 System.out.println("---- ܻ原来的编码(A)假设是: "
                 +charsets[j]+", 被错误解读为了(B): "+charsets[i]);
                 System.out.println(s);
                 System.out.println();
             }
         }
     }
}

char的真正含义

在Java内部进行字符处理时,采用的都是Unicode,具体编码格式是UTF-16BE。

  • **char本质上是一个固定占用两个字节的无符号正整数,这个正整数对应于Unicode编号,用于表示那个Unicode编号对应的字符 **

  • 由于固定占用两个字节,char只能表示Unicode编号在65 536以内的字符,而不能表示超出范围的字符 那超出范围的字符怎么表示呢?使用两个char。类Character、String有一些相关的方法

由于char本质上是一个整数,所以可以进行整数能做的一些运算,在进行运算时会被看作int,但由于char占两个字节,运算结果不能直接赋值给char类型,需要进行强制类型转换,这和byte、short参与整数运算是类似的 char类型的比较就是其Unicode编号的比较

查看char的二进制,用Integer方法:

char c = '马';
System.out.println(Integer.toBinaryString(c))

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值