首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
80 阅读
2
nvim番外之将配置的插件管理器更新为lazy
58 阅读
3
2018总结与2019规划
54 阅读
4
PDF标准详解(五)——图形状态
33 阅读
5
为 MariaDB 配置远程访问权限
30 阅读
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
Java
emacs
linux
文本编辑器
elisp
反汇编
OLEDB
数据库编程
数据结构
内核编程
Masimaro
累计撰写
308
篇文章
累计收到
27
条评论
首页
栏目
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
页面
归档
友情链接
关于
搜索到
23
篇与
的结果
2019-05-05
Java 函数
之前的几篇文章中,总结了java中的基本语句和基本数据类型等等一系列的最基本的东西,下面就来说说java中的函数部分函数基础在C/C++中有普通的全局函数、类成员函数和类的静态函数,而java中所有内容都必须定义在类中。所以Java中是没有全局函数的,Java里面只有普通的类成员函数(也叫做成员方法)和静态函数(也叫做静态方法)。这两种东西在理解上与C/C++基本一样,定义的格式分别为:public static void test(arglist){ } public void test(arglist){ }基本格式为:修饰符 [static] 返回值 函数名称 形参列表修饰符主要是用来修饰方法的访问限制,比如public 、private等等;如果是静态方法需要加上static 如果是成员方法则不需要;后面是返回值,Java函数可以返回任意类型的值;函数名用来确定一个函数,最后形参列表是传递给函数的参数列表。函数中的内存分布Java中函数的使用方式与C/C++中基本相同,这里就不再额外花费篇幅说明它的使用,我想将重点放在函数调用时内存的分配和使用上,更深一层了解java中函数的运行机制。我们说在X86架构的机器上,每个进程拥有4GB的虚拟地址空间。Java程序也是一个进程,所以它也拥有4GB的虚拟地址空间。每当启动一个Java程序的时候,由Java虚拟机读取.class 文件,然后解释执行其中的二进制字节码。启动java程序时,在进程列表中看到的是一个个的Java虚拟机程序。java虚拟机在加载.class 文件时将它的4GB的虚拟地址空间划分为5个部分,分别是栈、堆、方法区、本地方法栈、寄存器区。其中重点需要关注前3个部分。栈:与C/C++中栈的作用相同,就是用来保存函数中的局部变量和实参值的。堆:与C/C++中堆的作用相同,用来存储Java中new出来的对象方法区:用来保存方法代码和方法名与地址的这么一张表,类似于C/C++中的函数表基本数据类型作为函数的参数class Demo{ public static void main(String[] args){ int n = 10; test(10); System.out.println(n); } public static void test(int i){ System.out.println(i); i++; } }上述代码在函数中改变了形参值,那么在调用之后n的值会不会发生变化呢?答案是:不会变化,在C/C++中很好理解,形参i只是实参n的一个拷贝,i改变不会改变原来的n。这里我们从内存的角度来回答这个问题如上图所示,方法区中存储了两个方法的相关信息,main和test,在调用main的时候,首先从方法区中查找main函数的相关信息,然后在栈中进行参数入栈等操作。然后初始化一个局部变量n,接着调用test函数,调用test函数时首先根据方法区中的函数表找到方法对应的代码位置,然后进行栈寄存器的偏移为函数test分配一个栈空间,接着进行参数入栈,这个时候会将n的值——10拷贝到i所在内存中。这个时候在test中修改了i的值,改变的是形参中拷贝的值,与n无关。所以这里n的值不变引用类型作为函数参数class Demo{ public static void main(String[] args){ String s = "Hello"; test(s); System.out.println(s); //"Hello" } public static void test(String s){ System.out.println(s); //"Hello" s = "World"; } }在C/C++中,经常有这么一句话:“按值传递不能改变实参的值,按引用传递可以改变实参的值”,我们知道String 是一个引用,那么这里传递的是String的引用,我们在函数内部改变了s的值,在外部s的值是不是也改变了呢?我们首先估计会打印一个 "Hello"、一个"World"; 实际运行结果却是打印了两个 "Hello",那么是不是有问题呢?Java中到底存不存在按引用传递呢?为了回答这个问题,我们还是来一张内存图:从上面的内存图来看,在函数中修改的仍然是形参的值,而对实参的值完全没有影响。如果想做到在函数中修改实参的值,请记住一点:拿到实参的地址,通过地址直接修改内存。下面再来看一个例子:class Demo{ public static void main(String[] args){ int[] array = new int[]{1, 2, 3, 4, 5}; test(array); for(int i = 0; i < array.length; i++){ System.out.print(array[i]); } System.out.println(); //98345 } public static void test(int[] array){ for(int i = 0; i < array.length; i++){ System.out.print(array[i]); } System.out.println(); //12345 array[0] = 9; array[1] = 8; } }运行这个实例,可以看到这里它确实改变了,那么这里它发生了什么?跟上面一个字符串的例子相比有什么不同呢?还是来看看内存图这段代码执行的过程中经历了3个主要步骤:new一个数组对象,并且将数组对象的地址赋值给array 实参调用test函数时将array实参中保存的地址复制一份压入函数的参数列表中在test函数中,通过这个地址值来修改对应内存中的内容这段代码与上面两段本质上的区别在于,这段代码通过引用类型中保存的地址值找到并修改了对应内存中内容,而上面的两段代码仅仅是在修改引用类型这个变量本身的值。说到传递引用类型,那么我就想到在C/C++中一个经典的漏洞——缓冲区溢出漏洞,那么java程序中是否也存在这个问题呢?这里我准备了这样一段代码:class Demo{ public static void main(String[] args){ byte[] buf = new byte[7]; test(buf); } public static void test(byte[] buf){ for(int i = 0; i < 10; i++){ buf[i] = (byte)i; } } }如果是在C/C++中,这段代码可以正常执行只是最后可能会报错或者崩溃,但是赋值是成功的,这也就留给了黑客可利用的空间。在Java中执行它会发现,它会报一个越界访问的异常,也就说这里赋值是失败的,不能直接往内存里面写,也就不存在这个漏洞了。返回引用类型Java方法返回基本类型的情况很简单,也就是将函数返回值放到某块内存中,然后进行一个复制操作。这里重点了解一下它在返回引用类型时与C/C++不同的点在C/C++中返回一个类对象的时候,会调用拷贝构造将需要返回的类对象拷贝到对应保存类对象的位置,然后针对函数中的类对象调用它的析构函数进行资源回收,那么Java中返回类对象会进行哪些操作?C/C++中返回一个类对象的指针时,外部需要自己调用delete或者其他操作进行析构。java中的类对象都是引用类型,在函数外部为何不需要额外调用析构呢?带着这些问题,来看下面这段代码:class Demo{ public static void main(String[] args){ String s = test(); System.out.println(s); } public static String test(){ // return new String("hello world"); return "Hello World"; } }这段代码 不管是用new也好还是直接返回也好,效果其实是一样的,下面是对应的内存分布图这段代码首先在函数test中new一个对象,此时对应在堆内存中开辟一块空间来保存"hello world" 值,然后保存内存地址在寄存器或者其他某个位置,接着将这个地址值拷贝到main函数中的s中,最后回收test函数的栈空间。这里实质上是返回了一个堆中的地址值,这里就回答了第一个问题:在返回类对象的时候其实返回的值对象所在的堆内存的地址。接着来回答第二个问题:java中资源回收依赖与一个引用计数。每当对地址值进行一次拷贝时计数器加一,当回收拷贝值所在内存时计数器减一。这里在返回时,先将地址值保存到某个位置(比如C/C++中是将返回值保存在eax寄存器中)。此时计数器 + 1;然后将这个值拷贝到 main 函数的s变量中,此时计数器的值再 + 1,变为2,接着回收test函数栈空间,计数器 - 1,变为1,在main函数指向完成之后,main的栈空间也被回收,此时计数器 - 1,变为0,此时new出来的对象由Java的垃圾回收器进行回收。
2019年05月05日
3 阅读
0 评论
0 点赞
2019-04-27
Java 基本语句、控制结构
上一篇中简单谈了一下自己对Java的一些看法并起了一个头,现在继续总结java的相关语法。java语法总体上与C/C++一样,所以对于一个C/C++程序员来说,天生就能看懂Java代码。在学习java的时候,上手非常快。我感觉自己就是这样,看代码,了解其中一些重点或者易错点的时候发现,与C/C++里面基本类似,甚至很多东西不用刻意去记,好像自己本身就知道坑在哪。所以这里我想简单列举一下语法点,然后尝试用C/C++的视角来解读这些特性。引用类型引用中的指针与内存上一次,我总结一下java中的数据类型,在里面提到,Java中有两大数据类型,分为基本数据类型和引用数据类型。并且说明了简单数据类型,这次就从引用数据类型说起;引用数据类型一般有:数组、字符串、类对象、接口、lambda表达式;这次主要通过数组和字符串来说明它引用数据类型在C/C++中对应指针或者引用。其实关注过我之前C/C++反汇编系列文章的朋友知道,在C/C++中引用实质上就是一个指针。所以这里我也将java中引用类型理解为指针。所以从本质上讲,引用类型都是分配在堆上的动态内存。然后使用一个指针指向这块内存。这是理解引用类型很重要的一点。数组的定义如下char[] a = new char[10]; char[] b = new char[] {'a', 'b', 'c'}; char[] c = {'a', 'b', 'c'};其实这种形式更符合 变量类型 变量名 = 变量值 这种语法结构, char[] 就像是一种数据类型一样,char表示数组中元素类型, []表示这是一个数组类型字符串的简单定义如下:String s = "Hello world";上面说到引用类型都是分配在堆上的。所以字符串和数组实质上都是new 出来的。即使有的写法上并没有new 这个关键字,但是虚拟机还是帮助我们进行了new 操作。有new就一定有delete了。那么哪里会delete呢?这些操作一般都由java的垃圾回收器来处理。我们只管分配。这就帮助程序员从资源回收的工作中解放出来了。这也是java相比于C/C++来说比较优秀的地方。有人可能会说C++中有智能指针,也有垃圾回收机制。确实是这样。但是我觉得还是有点不一样。java是天生就支持垃圾回收,就好像从娘胎生出来就有这个本能,而C/C++是由后天学会的,或者说要刻意的去进行操作。二者还是不一样的。下面有这样一段代码:char[] a = new char[10]; System.out.println(a);我们打印这个a变量,发现它出现的是一个类似16进制数的一个东西。这个东西其实是一个地址的hash值,为什么不用原始值呢?我估计是因为有大神能够根据变量的内存地址进行逆向破解,所以这里为了安全对地址值进行了一个加密。或者为了彻底贯彻Java不操作内存的信念。(我这个推断不知道是不是真的,如果有误,请评论区大牛指正。)这也就证明了我之前说的,引用类型本质上是一个指针。char[] a = new char[]{'a', 'b', 'c'}; char[] b = new char[]{'a', 'b', 'c'}; System.out.println(a); System.out.println(b);上面这段代码,我想学过C/C++的人应该一眼就能看出,这里打印出来的a和b应该是不同的值,这里创建了两块内存。只是内存中放的东西是一样的。char[] a = new char[]{'a', 'b', 'c'}; char[] b = a; b[0] = '0'; b[1] = '1'; b[2] = '2'; System.out.println(a); System.out.println(b);这里从C/C++的角度来看,也很容易理解:定义了两个引用类型的变量,a、b都指向同一块内存,不管通过a还是b来寻址并写内存,下次通过a、b访问对应内存的时候肯定会发现值与最先定义的不同。String s = "hello"; System.out.println(s.hashCode()); s += "world"; System.out.println(s.hashCode());由于Java不具备直接访问内存的能力,不能直接打印出它的内存地址,所以这里用hashCode 得到地址的hash值。通过打印结果说明这个时候s指向的地址已经变了。也就是说虽然可以实现字符串的拼接,但是虚拟机在计算得出拼接的结果后又分配了一块内存用来保存新的值。但是任然用s这个变量来存储地址值,用赵本山的话来说就是“大爷还是那个大爷,大妈已经不是原来的那个大妈了”。也就是说Java分配内存的时候应该是按需分配,需要多少分配多少。不够就回收之前的,再重新按需分配。这就导致了java中字符串和数组的长度是不能改变的。String s = "hello"; System.out.println(s.hashCode()); (s.toCharArray())[0] = 'H'; System.out.println(s.hashCode()); System.out.println(s); // 这里字符串的值不变上述这段代码,通过toCharArray将字符串转化为char类型的数组,然后修改数组中的某一个元素的值,我原来以为这样做相当于在String所在内存中修改,最终打印s时会出现 "Hello" ,但是从结果上来看并没有出现这样的情况,s指向的地址确实没变,但是s也是没变的,那只能解释为toCharArray 又开辟了一块内存,将String中的值一一复制到数组中。在学习中我尝试过各种数据类型强转String s = "Hello World"; char[] a = (char[]) s; int p = (int)s;像这样的代码我发现并不能通过编译。在C/C++中,可以进行任意类型到整型或者指针类型的转化,常见的转化方式就是将变量所在地址进行赋值或者将变量对应的前四个字节进行转化作为int或者指针类型。但是在java中这点好像行不通。Java中强转好像只能在基本数据类型中实现,而在引用类型中通常由函数完成,并且完成时并不是简单的赋值,还涉及到新内存空间的分配问题。越界访问由于C/C++中提供了访问内存的能力,而且由于现代计算机的结构问题,C/C++中存在越界访问的问题,越界访问可以带来程序编写的灵活性,但是也带来的一些安全隐患。对于灵活性,相信学习过Windows或者Linux编程的朋友应该深有体会,系统许多数据结构的定义经常有这类:struct s { char c; } s *p = (s*)new char[100]; 这样就简单的创建了一个字符串的结构。这里C变量只是提供了一个地址值,后续可以根据c的地址来访问它后续的内存。安全问题就是大名鼎鼎的缓冲区溢出漏洞,我在相关博客中也详细谈到了缓冲区溢出漏洞的危害以及基本的利用方式。这里就不在赘述。那么Java中针对这种问题是如何处理的呢? Java中由于不具有内存访问的能力,所以这里它简单记录当前对象的长度,只要访问超过这个长度,立马就会报异常,报一个越界访问的异常。(这里我暂时没有想到对应的java演示代码,所以简单说一下吧)空指针访问还记得C/C++指针中常见的一个NULL吧,既然Java中引用类型相当于一个指针,那么它肯定也存在空指针问题。在Java中空指针定义为null。如果直接访问null引用,一般会报空指针访问异常。char[] c = null; c[0] = 'A'; //异常语句关于引用类型我暂时了解了这么多东西。下面简单列举一下java中的运算符和相关语句结构运算符java中的运算符主要有下列几个:算数运算符: + 、-、 *、 /、 %、 ++、 --、赋值运算符: = 、+=、 -=、%=、/=、*=比较运算符: ==、 >、 <、 >=、 <=、 !=逻辑运算符: &&、 ||、 !、三目运算符位运算符: >>、 <<、 >>>(无符号右移)、 <<<、&、|、~这些运算符用法、要点、执行顺序与C/C++中完全相同。所以这里简单列举。不做任何说明语句结构java中的顺序结构与其他语言中一样,主要有3种顺序结构判断结构: if、if...else、 if...else if...else if...else循环结构:while、for、do while用于与其他语言一样,这里需要注意的是,Java中需要判断的地方只能使用bool值作为判断条件,比如 5 == 3 、 a == 5这样的。在C/C++中有一条编程规范,像判断语句中将常量写在前面就像这样if(5 == a){ //.... }这样主要是为了防止将 == 误写成 = ,因为在C/C++中 只要表达式的值不为0 就是真,像 if(a = 5) 这样的条件是恒成立的。而Java中规定判断条件必须是真或者假,并且规定boolean类型不能转化为其他类型,其他类型也不能转化为boolean,所以 if(a = 5) 这样的语句在Java中编译是不会通过的。
2019年04月27日
3 阅读
0 评论
0 点赞
2019-04-20
java基础语法
最近抽时间在学习Java,目前有了一点心得,在此记录下来。由于我自己之前学过C/C++,而Java的语法与C/C++基本类似,所以这一系列文章我并不想从基础一点点的写,我想根据我已有的C/C++经验,补充一些需要注意的点,或者java中独特的内容,或者将C/C++进行对比来总结一下学习的内容。为什么要学习java最开始接触到Java还是在学校中开设的一门java编程语言的课,那个时候感觉java很麻烦,写个helloworld要那么多代码。后来学到web编程,我自己搭建的环境总是报错,而且还是jar包的错误。从这个时候起,我对java就没什么好感。后来很多培训机构来学校招生,做讲演,难道大学学了4年,最后还是去了培训机构,而那些培训机构号称4个月完全掌握java,这时我感觉如果我去学了java,那么大学4年出来跟培训班4个月出来有什么差别,既然上了大学,学了计算机,要跟培训机构出来的人不一样,既然是学计算机的,当然得学计算机里面最难的语言,所以我大学从3年级开始学了大半年汇编,又从汇编转到C和C++。但是万事逃不过真香定律,在工作之后,慢慢接触了Java,也了解了java,其实Java并不像我想想的那么简单。但是我心里一直抗拒学它。但是最近工作中确实要用到它,之前在一些博文中提到过,现在我开始带领几个人的小队,里面有java的,有做C的,有Python的,我想作为一个leader,虽然不用干他们的活,但是至少得懂。最主要的还是Java那边提个需求实在太难,java程序员总会跟我说很难,要改代码,动不动就说这个需求得改架构。最后做出来的跟我预计的差别太大。为了有理有据的回怼,也为了更好的带队,我想Java还是得会。说到这里我有感而发:领导给你活,并不是要听你抱怨有多难,要很多东西。既然能告诉你要做这个,那么可行性方面肯定提前做过研究,不要说什么很难,做不了。既然给你了,领导要的是你提出一个解决方案,然后告诉我要多长时间。中间出了问题及时反馈就OK。一直抱怨难,动不动就改架构什么的,只会让领导对你的个人能力产生怀疑,甚至会萌发出换人的想法。所以领导拍下来的活,干就对了。上述是一个原因,还有一个原因;我在关注安全漏洞的时候经常会报出来什么Struts2 漏洞、WebLogic 漏洞,这些都是java的开发框架,很多时候大牛们的博客或者公众号上已经写了漏洞原理,甚至有的还有它验证以及构造POC的思路,但是我就是看不懂,不出POC,我完全不知道如何去检测。为了以后能更好的理解这些java漏洞,我想还是需要好好学一下Java从hello world 开始任何语言都是从hello world开始的,java也不例外,这里我给出hello world的代码public class HelloWorld{ public static void main(String[] args){ System.out.println("hello world"); } }将这段代码保存到HelloWord.java文件中,这里文件的名称必须是HelloWorld.java,文件名与主类的名称相同。然后调用javac进行编译javac HelloWorld.java这个时候会生成.class 文件,这里使用java执行代码java HelloWorld注意执行的时候java指令后面跟的是类名而不是具体的.class文件名。这里我想应该是在执行的时候,java命令根据类名去找对应的.class 文件,将文件中的二进制字节码放到虚拟机中执行。然后由虚拟机去类中查找main函数,从main函数中执行。因此这里文件名必须与类名相同,而且必须要有main函数。再来说说这个main函数前面的修饰词,public 应该表示这个是一个公共方法,也就是外部可以访问,static 表示这个方法是一个静态方法,独立与类存在的。这两个修饰符是必须的要的。java强制使用面向对应,一切都定义在类中,但是程序必须要一个入口函数。根据java的逻辑,这个main函数也得定义到类中。但是如果定义成普通函数的话行不行呢。答案是不行的,由于main函数是一个入口函数,一切都从它开始,如果它是一个类函数,那么势必要定义一个类的对象然后再调用对象的main方法,可是既然main是程序的开始,请问如何在调用main之前定义对象呢,因此这里必须得定义成静态的;这个public能不能不加或者改成private或者其他的呢?当然也不行,既然你要将它作为入口函数,那么必然需要由虚拟机调用这个函数,而且是在类外调用,所以这里一定得定义成public,对外开放。在Java中一切即对象,它强制你采用面向对象,这也是当时我拒绝学java的一个理由,认为它太死板。java的跨平台据说SUN公司当年是卖服务器,服务器上的主要程序是由C/C++开发而来的,而C/C++,每次在换一个平台都需要重新编译。这个时候SUN公司的工程师需要一种跨平台的语言,就那种代码写完,编译完成之后不需要再做任何操作,随便放到一个平台上都能跑的那种。而且当时C++ 指针、多继承满天飞,造成程序编写、理解的困难。基于这几点理由,开发出了Java,Java脱胎于C++,但是砍掉了C++中复杂的指针和多继承的内容,在现在看来应该是一个比较正确的决定。相比于现在C++各种新特性的眼花缭乱,java还是很朴实很简单的东西。java的跨平台取决于它的虚拟机。每个平台都需要一个对应的虚拟机。虚拟机就好像一个翻译,而程序就像一个到不同国家旅行的人,比如说一个中国游客可以去美国、去日本、去英国旅行,他在酒店前台时用中文吩咐前台的工作人员给他一间房,去不同的国家有不同的翻译将开房这条指令翻译为前台能听懂的语言。而我们的游客只需要说中文即可。java的虚拟机的工作原理也是这样的。按照统一的规则,根据具体的平台将规则中定义的指令翻译为对应平台上的机器码。比如在Windows上+1操作是 ADD 1, 在Linux上+1操作是 +1, 而在MAC中对应 1+ ,那么在java代码中这个指令可能会编译为 1++, 这个指令不管在哪个平台都不用变,当它放在windows主机上由Windows版的虚拟机将它翻译为 ADD 1,在Linux上由Linux版的虚拟机翻译为+1,在MAC上由MAC版的虚拟机翻译为 1+。java代码执行需要经过两个步骤,首先编译为虚拟机能识别的字节码,然后有虚拟机解释并执行这个字节码。所以java具有两面性,即需要编译,也需要解释执行,那么它到底是解释性的语言还是编译型的呢?我也不知道。基本语法它的语法与C/C++基本类似,类似到你即使没接触过java,看它的代码基本能看懂每条语句都在干嘛。所以针对我来说,我并不关注每个代码怎么写,我只需要知道每个语法点有哪些需要注意的即可。常量与变量常量在java中一般是指那种用字面值表示出来的量比如说 整型的1,浮点型的1.234, 字符 'A' 字符串 'hello world',或者是用关键字 const 定义的。java里面的常量分为:整型常量、字符串常量、浮点数常量、字符常量、布尔常量和空常量(null)。从常量类型可以看出这些也是java中主要的数据类型,java中数据类型主要有:整数类型: byte、short、int、long浮点数类型: float、double字符类型: char布尔类型:boolean这些都是基本数据类型,java中还有引用类型,像字符串、对象、数组、接口、lambda表达式都是引用类型。需要注意的是java中long 是8个字节,而C/C++中long一般是4个字节,longlong才是8个。java中的char占两个字节,所以在C/C++中会将需要一个字节一个字节处理的缓冲定义为char型数组,而在java中就不能这么干了,因为它的char占两个字节,java中对于这种情况一般是定义为byte类型的数组。由于java中的char占两个字节,所以java中char是可以表示中文的char c = '中' //这在java中是正确的,但是C/C++中不能这么写有数据类型自然就涉及到数据类型转化的问题。java与C/C++类似都有显示转化和隐式转化。而且写法也类似。需要注意的几点是:整型字面值会被java编译器默认当做int类型来处理,像 byte num = 5、short s = 5 这样的表达式中5 这个字面值都是int,也就是它里面都发生了强制类型转化,如果想让编译器将其当做long需要在5后面加上L,写成 long l = 5L浮点数字面值会被默认当做double 来处理,如果想让其被作为float处理,需要在后面加上F在进行整数运算的时候,运算符号两侧的变量 或者常量会被先转化为int 在进行处理。例如 下面的代码:short s1 = 5, s2 = 10; short result = s1 + s2;这段代码java会报错,由于在运算的时候s1 与 s2 会被转化为int,然后运算得到的结果也是int,在最后进行结果赋值的时候将int赋值为short会发生错误。要让他不报错可以改为 short result = (short)(s1 + s2);隐式类型转化发生在由表示范围小的向表示范围大的类型,一般是 byte-->short-->int-->long-->float-->double。再来看下面一个例子:short n = 5 + 10;这段代码,从理论上讲,它应该会首先把5 和 10 转化为int,在计算,最后把结果的int转化为short赋值,根据上面说的,它应该会报错才对,但是实际试验的结果却是,它通过了。这就很奇怪了。还记得在学习C/C++中提到的编译器的优化吗。在C/C++中如果你写上面的一段代码,在release版本中,你看不到类似mov eax, 5 add eax, 10 mov n, eax这样的机器指令,只看得到mov n, 15这里编译器进行了优化,你代码中采用了字面值常量进行相加,而常量是不会变化的,因此在程序运行之前就已经知道计算的结果,我就没必要在运行的时候浪费CPU给你计算这个加法值,我直接给你一个结果也是一样的。所以这里java编译器采用了同样的策略。它直接将上面的代码翻译为了 short n = 15。但是这也不对啊,15应该会被当做int,而n是一个short,将int这个表示范围大的转化为short这个表示范围小的,应该会报错才对啊。但是编译确实不报错。这又涉及到java编译器的另一个策略了。当我们直接用字面值常量进行赋值操作的时候,如果字面值没有超过左侧变量的表示范围时,编译器会自动进行强制类型转化。最后的最后我想在你已经拥有其他语言的开发经验的时候,学习新语言的过程无外乎是数据类型、基本语句、控制结构、函数、面向对象、以及常用库这些东西,所以我想我自己的java笔记也按照这些框架来组织。这次是数据类型,下次就是基本语句与控制结构了。
2019年04月20日
2 阅读
0 评论
0 点赞
1
2
3