目录
一、常量的概述与使用
和C++一样,Java中的常量在定义之后它的值就不可以发生改变。
java中的常量分为字面值常量和自定义常量
字面值常量有以下几种
1、字符串常量:用双引号括起来的内容
2、整数常量:所有整数
3、小数常量:所有小数
4、字符常量:用单引号括起来的内容,里面只能放单个数字、字母或者符号
5、布尔常量:true和false
6、空常量:null
二、进制
进制即进位制,是用来计数的一种方法。
二进制:01
即逢二进一,在计算机中机器码就是二进制表示的,因为传统的计算机设计为利用电压的高低点位来存储和传递信息。高电位表示1,低电位表示0。
八进制:01234567
即逢八进一,每个八进制数字可以用三个二进制数表示,是用来简化表达二进制的一种方法。
十六进制:0123456789ABCDEF
即逢十六进一,因为计算机只能识别01字符,为了能够让计算机使用人员简化表达数据,所以引入了十六进制。
一个十六进制数可以用4位二进制数表示,这样就可以方便的表示计算机中存储的数据,不用写一大串01字符,而且二者之间相互转换也非常方便。
比如一个字节8位,用01字符要写8个字母,用两个16进制就可以表达出来。
计算机中最基础的数据存储单位即bit位,是二进制存储的,只有0和1两种状态。然后根据bit人们又规定了更大的表示数据的大小单位。
1byte=8bit
1k=1024b
1m=1024k
1g=1024m
1t=1024g
三、进制之间的转换
3.1 x进制转换为十进制
比如一个x进制的四位数abcd,如果要转为十进制表达的数y
其中3,2,1,0代表的是幂次,如果是五位数,那么最大幂次就为4。
3.2 十进制转换为X进制
一般我们使用除积倒取余的方法,我们以十进制转换为二进制为例进行说明。
假设一个十进制数12要转换为二进制,其计算方法如下图:
得到的余数倒取即结果:1100,所以十进制的12对应的二进制就是1100。
用win10自带的计算器验证一下结果,是正确的。
3.3 二进制八进制十六进制之间的转换
因为2、8、16都是2的n次幂,他们之间的转换可以有特殊的方式。
一般来说,3位二进制可以用1位八进制数表示,4位二进制可以用1位十六进制数表示。
转换对应表如下:
十进制 | 二进制 | 八进制 | 十六进制 | 十进制 | 二进制 | 八进制 | 十六进制 |
0 | 0000 | 00 | 0 | 8 | 1000 | 10 | 8 |
1 | 0001 | 01 | 1 | 9 | 1001 | 11 | 9 |
2 | 0010 | 02 | 2 | 10 | 1010 | 12 | A |
3 | 0011 | 03 | 3 | 11 | 1011 | 13 | B |
4 | 0100 | 04 | 4 | 12 | 1100 | 14 | C |
5 | 0101 | 05 | 5 | 13 | 1101 | 15 | D |
6 | 0110 | 06 | 6 | 14 | 1110 | 16 | E |
7 | 0111 | 07 | 7 | 15 | 1111 | 17 | F |
这些进制数相互转换时从最低位开始,二进制不足3位或者4位时一般会在前面用0补全。
我们以二进制数10011101转换为八进制和十六进制为例,其转换方法如下图所示:(注意当二进制的位数不够时需要在最高位前面补0,缺几个补几个)
用win10自带的计算器进行验证:
若要从十六进制或者八进制转换为二进制,则从高进制的最低位开始转换,其方法类似。
四、原码、反码、补码
原码:最简单的机器数表示法。用最高位表示符号位,‘1’表示负号,‘0’表示正号。其他位存放该数的二进制的绝对值。
反码:正数的反码还是等于原码。负数的反码就是他的原码除符号位外,按位取反。
补码:正数的补码等于他的原码。负数的补码等于反码+1。
那有人可能会问了,有一个原码能表示数据不就行了嘛?整这么多有啥用,有啥用我们可以通过以下例子进行说明(以二进制的+7和-7进行求和为例):
我们知道+7-7=0,那么只有补码求和得到的结果0000 0000才是0,原码和反码求和得到的结果都是错误的,所以为了能让计算机直接进行求和,我们需要对运算的数据进行变换,使用补码的形式表示数据。这也是为什么我们要学习补码的原因。
五、变量概述
.变量即在程序执行过程中值可以发生改变的量。与其相反的是常量,一经定义就不可以对其值发生改变。
Java存储变量的形式是先在内存空间中根据定义的数据类型开辟对应大小的存储空间, 然后根据用户起的变量名对该存储空间进行访问。
变量的定义形式一般如下:
变量类型 变量名=变量值,例如:int a=5;
六、Java的数据类型
6.1数据类型
数据类型就是表示数据存储的是啥东西,比如你存储的到底是数字还是字符。不同于Python和Matlab,C++和Java在定义数据时必须显式地指定数据类型。
定义数据类型还有个好处就是可以根据你的需求给你开辟空间,不会造成内存空间的浪费。
比如一个变量存储字节类型的数据,结果计算机由于不知道数据类型,怕你不够用造成访问到未知内存,给你了一个非常大的存储空间,这样会导致资源的浪费,程序运行的效率下降等问题。
Java的数据类型和C++的大同小异,主要分为基本数据类型和引用数据类型。
这里我们先只介绍基本数据类型,在面向对象时再介绍引用数据类型。
基本数据类型(4类8种):
- 整数型
- byte:一个字节,可表示的数据范围为[-128,127]
- short:两个字,可表示的数据范围为[-2^15,2^15-1]
- int:占四个字节,可表示的数据范围为[-2^31,2^31-1]
- long:占八个字节,可表示的数据范围为[-2^63,2^63-1](需要注意的是在给long类型赋值时值的最后一位要加“L”来表示这个值为long类型,否则默认的整数类型均为int,可能会报错范围超出)
- 浮点型
- float:占四个字节,可表示的数据范围为[-3.40282346638528860e+38 , -1.40129846432481707e-45] ∪ [1.40129846432481707e-45 ~ 3.40282346638528860e+38] (需要注意的是在给float类型赋值时值的最后一位要加“f”来表示这个值为float类型,否则默认的小数类型均为double,可能会报错精度损失)
- double:占八个字节,可表示的数据范围为[-1.79769313486231570e+308,-4.94065645841246544e-324] ∪ [4.94065645841246544e-324,1.79769313486231570e+308]
- 字符型
- char:占两个字节,可表示的数据范围为[0,65535]
- 布尔型
- boolean:因为boolean类型只有两个值true和false,所以理论上只需要八分之一个字节的空间即可存储boolean的数据,但是Java并没有明确指出boolean变量的存储空间大小。
6.2数据的隐式类型转换
int a=3;
byte b=4;
a=a+b;
System.out.println(a);
当我们输出int类型和byte类型这两个数据的求和时,得到的结果会是一个int类型的7。
这两个数据的类型明明不相同,为什么可以进行运算呢。
因为计算机在进行运算时,会将低精度的数据转换为高精度的数据,即将byte类型的4转换为int类型的4,然后再和3进行求和计算。
这种计算机帮我们“偷偷”进行的数据类型转换称为隐式类型转换。
6.3数据的强制类型转换
如果我们对上面的代码简单改一下:
int a=3;
byte b=4;
b=a+b;
System.out.println(a);
让byte类型的数据去接收a+b求和的结果。
我们分析一下,a+b的结果是一个int类型的7,对byte类型的变量b进行赋值,可能会有精度损失(虽然这里我们的例子是7,但是如果求和的结果数据很大那么转换为byte类型时一定会有精度损失或者超出范围导致结果不正确)。
那么如果我们非要让byte类型的变量去存结果怎么办呢?这个时候就需要用到强制类型转换。
将第三行的代码改成如下形式:
b=(byte)(a+b);
其意义是将a+b求和的结果int型变量转换为byte类型,然后赋值给b,这样就不会有问题。
有人会问这样就不会有精度损失或者超出范围吗?
答案是会,这也是强制类型转换的代价,因为这是用户自己进行的强制类型转换,计算机只负责执行,出了啥问题计算机会说:“这是你要强转的,我可没偷偷帮你转,我都警告过你会有精度损失或者超出范围”。
所以在使用强制类型转换时我们需要考虑其代价,会不会对我们的结果造成很大的影响。
6.4变量相加与常量相加的区别
我们用一个面试题进行说明:
byte b1=1;
byte b2=1;
byte b3=b1+b2;
System.out.println(b3);
在进行编译的时候会报错说损失精度,那为什么两个byte类型的数据进行相加,结果对byte类型的变量赋值还会有问题呢?
原来在程序编译好之后,JVM将这些代码转换为计算机可以直接理解的机器码让CPU进行运算,而JVM只支持int、long、float和double类型的运算,当有其他类型的数据进行运算时,会转换为这些类型从而让JVM转换为机器码进行运算。
回到这个问题,因为在JVM中不支持byte类型的直接运算,所以要转换为支持的int类型进行运算,而转换为int类型后得到的结果为int类型,如果此时对一个byte类型的变量进行赋值那么就会报错精度损失。
我们然后将代码改为如下:
byte b3=1+1;
System.out.println(b3);
发现程序不报错,之前不是说过整型数据的默认存储为int类型,1+1很明显为int类型,那为什么将int类型的结果赋给byte类型的变量b3时不报错呢?
这就是变量相加和常量相加的区别。
- 如果是两个变量相加,在编译时JVM无法判断存储的值为多少,结果会不会超出范围或者损失精度,只能将变量转换为它可以支持的类型进行运算,比如int。
- 如果是两个常量相加,比如1+1,JVM知道1+1的结果没有超出byte类型的范围,那么他就会将结果直接赋值给变量b3,但是如果是100+100,那么结果很显然超过了byte所能表示的范围,所以就会报错损失精度。
这就是常量相加与变量相加的区别。
6.5 long和float的取值范围谁大谁小
通过上面的例子我们知道了在进行变量之间的混合运算时,byte、short、char不会相互转换,都会自动转换为int类型(char在进行转换时可以根据ASCii码表进行转换)。
其他类型进行混合运算时,范围小的数据类型会转换为范围大的数据类型。
其转换顺序如下:
(byte、short、char)--> int --> long --> float --> double
我们从顺序中可以看出long精度比float要低,那么long为什么要比float精度低呢?
从他们占的字节数来看,long占8个字节,float占4个字节,感觉应该是long更大一些才对,我们测试一下将float类型的值传给long类型的变量:
float a1=1.0f;
long a2=10;
a2=a1;
结果程序会报错,说精度有损失,反过来我们将long类型的值传给float类型的变量就不会出错。
那么通过程序我们知道了float表示的范围更大,其原理我们接下来分析一下。
float占4个字节也就是一共有32位二进制数:
- 1位符号位
- 8位指数位(范围为0000 0000至1111 1111即0-255)
- 23位尾数位(即小数位)
IEEE754标准规定指数位中的0代表0,255代表无穷大,其余有效范围为1-254,并且还要减去127。
则指数位表示的真正有效范围为-126至127,对应的范围是2^-126到2^127。
而long的范围我们前面说过是-2^63到2^63-1,所以float能表示的范围当然比long大。
6.6字符与字符串参与运算
我们让系统输出下面这个关系式:
System.out.println('a'+1);
结果会是98。
那么很明显当字符参与运算的时候会将字符转换为一个整型数字然后在跟别的类型变量运算,转换的规则则是参照ASCII码对应表:
ASCII值 | 控制字符 | ASCII值 | 控制字符 | ASCII值 | 控制字符 | ASCII值 | 控制字符 |
---|---|---|---|---|---|---|---|
0 | NUT | 32 | (space) | 64 | @ | 96 | 、 |
1 | SOH | 33 | ! | 65 | A | 97 | a |
2 | STX | 34 | " | 66 | B | 98 | b |
3 | ETX | 35 | # | 67 | C | 99 | c |
4 | EOT | 36 | $ | 68 | D | 100 | d |
5 | ENQ | 37 | % | 69 | E | 101 | e |
6 | ACK | 38 | & | 70 | F | 102 | f |
7 | BEL | 39 | , | 71 | G | 103 | g |
8 | BS | 40 | ( | 72 | H | 104 | h |
9 | HT | 41 | ) | 73 | I | 105 | i |
10 | LF | 42 | * | 74 | J | 106 | j |
11 | VT | 43 | + | 75 | K | 107 | k |
12 | FF | 44 | , | 76 | L | 108 | l |
13 | CR | 45 | - | 77 | M | 109 | m |
14 | SO | 46 | . | 78 | N | 110 | n |
15 | SI | 47 | / | 79 | O | 111 | o |
16 | DLE | 48 | 0 | 80 | P | 112 | p |
17 | DCI | 49 | 1 | 81 | Q | 113 | q |
18 | DC2 | 50 | 2 | 82 | R | 114 | r |
19 | DC3 | 51 | 3 | 83 | S | 115 | s |
20 | DC4 | 52 | 4 | 84 | T | 116 | t |
21 | NAK | 53 | 5 | 85 | U | 117 | u |
22 | SYN | 54 | 6 | 86 | V | 118 | v |
23 | TB | 55 | 7 | 87 | W | 119 | w |
24 | CAN | 56 | 8 | 88 | X | 120 | x |
25 | EM | 57 | 9 | 89 | Y | 121 | y |
26 | SUB | 58 | : | 90 | Z | 122 | z |
27 | ESC | 59 | ; | 91 | [ | 123 | { |
28 | FS | 60 | < | 92 | / | 124 | | |
29 | GS | 61 | = | 93 | ] | 125 | } |
30 | RS | 62 | > | 94 | ^ | 126 | ` |
31 | US | 63 | ? | 95 | _ | 127 | DEL |
我们在里面找到小写字母‘a’,其对应的ASCii码值为97,所以‘a’+1结果是98和程序输出相同。
里面一些我们需要记住的有:
- ASCII码值48至57为数字0到9
- ASCII码值65至90为大写字母A到Z
- ASCII码值97至122为小写字母a到z
接着我们试一下加入字符串来进行运算:
System.out.println('a'+1+"java");
System.out.println("java"+'a'+1);
第一个是先让字符变量a和1相加,再和字符串变量java相加;
第二个是先让字符串变量java和字符变量a相加,然后再加上1,他们的结果分别如下:
可以看到字符变量和整型变量相加时会先转换为字符对应的ASCII值然后得到一个整型的结果,结果与字符串变量相加时这个整型结果会转换为字符串“98”,所以最后得到的结果为字符串“98java”;
而第二个语句是先让字符串“java”和字符变量'a'相加,得到字符串变量"javaa",这个结果再加上1,1此时会从整型变量转换为字符串"1",然后加到最后,得到结果字符串“javaa1”。
由上述结果可以看出任何数据类型用+和字符串运算都会得到新的字符串。
6.7 char数据类型
在给char类型的数据赋值时可以有两种方式
一种是直接赋值一个字符,还有一种方式是赋值字符对应的ASCII值,比如:
char c1='a';
char c2=97;
System.out.println(c1);
System.out.println(c2);
这两种方式赋值出来的结果都是字符'a'。
需要注意的是java的char类型可以存储中文字。
因为java采用Unicode编码,每个字符占两个字节,中文也占两个字节,所以存放单个汉字没有问题。
七、Java的运算符
运算符即对常量或者变量进行操作的符号。
在java中运算符分为算数运算符、赋值运算符、比较(关系或条件)运算符、逻辑运算符、位运算符、三目运算符。
算数运算符又分为:+、-、*、/、%、++、--
其中%为取余操作。
7.1 ++与--运算符的使用
当++和--单独使用时,放在变量前后其结果都相同,这里我们不讨论。
我们讨论一下当自增和自减运算符参与运算的时候放在变量前后的区别。
我们用一个非常经典的例子说明:
int a=3;
int b;
b=a++;
System.out.println("a="+a);
System.out.println("b="+b);
得到的结果是a=4,b=3。
我们可以总结出这样的一个规律:
- 当自增或自减运算符在变量后面时,先将变量当前的值取出进行相关运算,运算完成后再对变量进行自增或者自减操作。
- 当自增或自减运算符在变量前面时,先将变量进行自增或者自减操作,然后再将得到的值参与运算。
我们将程序中自增符号提到变量a前面测试一下结果,得到a=4,b=4。
需要注意的是,在对byte或者short类型的数据a进行自增或自减时,不能将自增或自减运算符替换为a=a+1或者a=a-1。
因为自增自减运算符会将结果自动转换为byte或者short类型,但是a=a+1或者a=a-1右边得到的结果是int类型的,赋值给左边变量就会有精度损失的问题。
7.2赋值运算符
最基本的运算符就是=,其意义就是将右边的值赋值给左边的变量。
扩展的运算符有:+=、-=、*=、/=、%=,
其运算规则为将左边和右边先做等号前面的运算,然后再将值赋值给左边。
比如a+=b就是先让a+b得到一个结果,再将结果赋值给a,与a=a+b的效果相同。
与自增自减运算符相同,类似+=、-=这种扩展运算符,当左边的变量为byte或则和short类型的变量时,会自动将结果转换为对应的数据类型。
所以当左边变量为byte或者short类型时,不能简单地将a+=b转换为a=a+b,否则b为整型时就会出现精度损失的错误。
7.3关系运算符
关系运算符包括比较运算符和条件运算符,主要包括:==、!=、>、<、>=、<=,
关系运算得到的结果是boolean类型的值,要么为true要么为false。
需要注意的是”==“不能写成”=“,否则关系运算就会变成赋值运算。
7.4逻辑运算符
逻辑运算符主要包括:&、|、^、!、&&、||。
分别表示逻辑与、逻辑或、逻辑异或、逻辑非、短路与、短路或。
7.4.1逻辑与、或、异或、非
在java中逻辑与、或、异或、非左右两边可以是Boolean类型的变量或者等式(c++中只能用&&或者||做逻辑运算)。
在进行布尔类型的逻辑运算时:
- 逻辑与遇到false则为false
- 逻辑或遇到true则为true
- 逻辑异或当两边相同为false,两边不同为true
- 逻辑非若true则false,若false则true
7.4.2短路与、或
短路的意思理工科生一般都很了解。
其意义就是如果某个地方产生了短路,后续的电路中不会存在电流。
短路与和短路或也是一样,如果前面的表达式已经可以得出结果,则不执行后面的操作。
而逻辑与和或不管前面的表达式是否可以得出最后的结果,后面的操作都会执行。
我们用一段程序来验证一下两个的区别:
int a1=1,a2=1;
int b1=2,b2=2;
System.out.println((++a1 == 1)&(++b1 ==2));
System.out.println((++a2 == 1)&&(++b2 ==2));
System.out.println("逻辑与&:a="+a1+" b="+b1);
System.out.println("短路与&&:a="+a2+" b="+b2);
结果很显然,当进行逻辑与运算时,先执行(++a1 == 1),a1自增后为2,不等于1,所以结果为false,此时整个逻辑与运算的结果已经知道了一定为false,但是逻辑与会继续执行完后面的式子,所以b1也会自增变为3,那么输出的时候a1=2,b1=3。
但是短路与运算时,如果前面得到的结果已经为false了,那么程序就不会执行后面的语句,b2就不会自增,所以输出的时候a2=2,b2=2。
我们看一下输出结果:
7.5位运算符
位运算符就是按位进行运算,如果运算的数据为整型数据,则需要转换为二进制补码进行位运算。
位运算符主要包括:&、|、^、~。
前面&和|可以作为逻辑运算符,在这里我们也可以当作位运算符进行使用。
如果两边为int类型的数据,就需要先将int类型的数据转换为二进制数字补码,然后按位进行运算。
比如将整型数字120和5进行位与运算,对结果进行输出会得到48,计算机会先将120和50转为二进制补码,然后按位进行与运算,得到的二进制结果再转换为十进制整型进行输出,其计算过程如下:
还有一类位运算符比如:<<、>>、>>>,左边为移动的数据,右边为移动的位数。
<<为左移,将左边最高位丢弃,右边补0
>>为右移,如果当前最高位为0,则左边补0;若最高位为1,则左边补1
>>>为无符号右移,无论当前最高位为0还是1,左边都补0
因为都要转化为二进制补码进行运算,而二进制的左移右移一般对应着2的倍数变化,所以可以得到以下规律:
- 左移<<,向左移动几位就是乘以2的几次幂
- 右移>>,向右移动几位就是除以2的几次幂
7.6三元运算符
三元运算符的格式为:
[条件语句] ? [表达式1] : [表达式2]
其代表的意思是:
当条件语句的布尔值为真,则整个式子的值为表达式1的值;
当条件语句的布尔值为假,则整个式子的值为表达式2的值。
我们用一个例子来说明如何使用三元运算符:
int a=1;
int b=2;
int c=(a>b)?a:b;
System.out.println("c="+c);
该式子的意义为当a>b成立时,则c=a,当a>b不成立即a小于等于b时,c=b。就是找a和b的最大值。
输出结果为c=2。