输入输出与流程
输入与输出
读取输入
格式化输出
文件输入与输出
控制流程
块作用域
条件语句
循环
while
do/while
for
多重选择:switch语句
中断控制流程语句
输入输出与流程控制
上期,我们讲述了String作为字符串的一些重要的内容,字符串可以说在程序设计当中是十分重要的,那么今天我们来认识两位新朋友:输入输出和流程控制,废话不多说,上菜!
输入与输出
很明显,这是两个操作:输入、输出。可以这么说,说到输入与输出就真正做到了人机交互。
读取输入
看到这个标题也许有点懵了,什么叫读取输入。
所谓读取输入,就是指机器读取用户的输入指令,以便进行人机交互,通俗点来说就是:给计算机传递你想表达的信息,比如:比个心。
这么来说,似乎有了一定理解,但还不够深,我们来换个方式介绍:比较
相信在前面的文章中,经常见到System.out.println(xxxx)
,相信很多人第一次见都不知道表示什么意思,其实这是一个标准的输出流。即在控制台打印一串我们所需要的信息。
所谓控制台就是我们经常见到的简朴到不能再简朴的黑框框了,因为我们还没有学习到GUI收集用户数据,因此现在的输入输出控制都是在控制台中进行的。
可见,其实我们打印信息输出很简单,只要用这句语句就可以,但是机器读取标准的输入流就没那么简单了。
输出流是System.out
,那么输入流必然就是System.in
了。要想在控制台进行输入,首先需要构造一个与标准输入流关联的Scanner类对象。
这里就要复习一下之前的内容了,Java
中一切皆对象,可见其实对象可以是任何东西,并且由类实例化成对象,因此对象并不是一个绝对的事物,下面我们看看Scanner类的API
:
这里需要注意的是Scanner在java.util
包。
当使用的类不是定义在基本的java.lang
包下,必须使用import导入相应的包才能够进行访问。即在使用Scanner时需要使用import java.util.*
语句才能够使用。
看完了Scanner的介绍,来看看怎么实现输入吧
Scanner in=new Scanner(System.in);
//这表示一个标准的输入流,能够接收用户的输入信息。
这里其实出现了很多新事物,比如new这个关键词,它和我们介绍的内容关系不大,因此不做过多展开,只需要知道它构造了一个对象,没错,就是Scanner类对象。
其实这行代码还只是告诉计算机我们即将进行输入。用户输入的真正的内容其实并不在这里,这里需要了解Scanner类的各种实用方法:
方法名 | 方法解释 |
---|---|
Scanner(InputStream in ) | 用给定的输入流创建一个Scanner对象 |
String nextLine () | 读取输入的下一行内容,并且返回String类型的内容 |
String next() | 读取输入的下一个单词(以空格作为分隔符),并返回String类型 |
int nextInt () | 读取并转换下一个表示整数的字符序列 |
double nextDouble () | 读取并转换下一个表示浮点数的字符序列 |
boolean hasNext () | 检测输入中是否还有其他单词 |
boolean hasNextInt () | 检测是否还有下一个表示整数的字符序列 |
boolean hasNextDouble () | 检测是否还有下一个表示浮点数的字符序列 |
需要注意的是虽然可以嵌套,但不能在嵌套的声明块中声明同名的变量。
所以总结起来块其实很简单:
由多条 Java
语句组成,并用大括号括起来可以确定变量的作用域 可以对块进行嵌套,但不能在嵌套中声明同名变量。
现在我们用一个方法来举个栗子;
Scanner in =new Scanner(System.in);
System.out.println("What is your name?");
String name=in.nextLine();
这里使用nextLine()
是因为在输入行中可能出现包含空格。
如果想读取一个单词(以空白符作为分隔符)。可以使用String firstname=in.next()
如果想读取一个整数,可以使用nextInt()
方法
System.out.println("How old are you?");
int age=in.nextInt();
这里运行一段程序康康:
这里我们可以注意到,jack和21都是我们肉眼可以看见的,那么也许有人会问了,这个如果有一个是密码,会不会就很不安全。
是的,如果是密码的话,用Scanner就很不安全,当然Java
的设计者肯定也考虑过这个问题,因此在Java6
特别引入了Console类来实现输入不可见,要想读取一个密码,可以这么写:
Console cons=System.console();
String username=cons.readLine("Username:");
char[] password=cons.readPassword("Password:");
采用Console对象读取数据时不如Scanner方便,必须每次读取一行输入,而没有读取单个单词或数值的方法。
好了输入就说到这里,下面来说说输出;
格式化输出
了解输出之前,先了解一下什么是格式化?
所谓格式化输出,就是按照一定的格式来输出内容,对要输出的内容进行一定的处理。
我们可以使用System.out.println(x)
将数值X输出到控制台,这条命令将以X的类型所允许的最大非0数位个数来打印输出X,下面来看个栗子;
double x=10000.0/3.0;
System.out.println(x);
//最后打印:3333.33333333335
庆幸的是Java
沿用了C
语言的printf
方法
例如:
System.out.printf("%8.2f",x)
输出结果为3333.33;
这串语句会以一个字段宽度打印x,包括了8字符,另外精度为小数点后2个字符,简单来说就是打印一个前导空格和7个字符。
当然我们可以为printf
提供多个参数;例如:
System.out.printf("Hello,%s,Next year,you will be %d",name,age);
转换符
上面的栗子中出现了很多%后带着s,d字母的表达式,也有表示数字的8.2f
,这是怎么回事呢?
其实这个%有自己的名字,叫做格式说明符,后面的s,d之类的叫做转换符。
f表示浮点数,s表示字符串,d表示十进制数下面我们简单地列出转换符:
转换符 | 类型 | 示例 |
---|---|---|
d | 十进制整数 | 159 |
x | 十六进制整数 | 9 f |
o | 八进制整数 | 237 |
f | 定点浮点数 | 25.9 |
e | 指数浮点数 | 1.59 e+01 |
g | 通用浮点数(e和f中较短的一个) | 无 |
a | 十六进制浮点数 | ox1.fccdp3 |
s | 字符串 | Hello |
c | 字符 | H |
b | 布尔 | true |
% | 百分号 | % |
n | 与平台有关的行分隔符 | 无 |
tx | 日期时间 | 已过时,此时应采用java.time |
TX | 日期时间(强制大写) | 已过时,此时应采用java.time |
h | 散列码 | 42628b2 |
另外还可以指定控制格式化输出外观的各种标志
下面给出所有的标志
标志 | 目的 | 示例 |
---|---|---|
+ | 打印正数和负数的符号 | +3333.33 |
空格 | 在正数之前添加空格 | | 3333.33| |
0 | 数字面前补0 | 003333.33 |
- | 左对齐 | |3333.33 | |
( | 将负数括在括号内 | (3333.33) |
, | 添加分组分隔符 | 3,333.33 |
#(对于f格式) | 包含小数点 | 3,333. |
#(对于o或x的格式) | 添加前缀ox或者o | oxcafe |
$ | 指定要格式化的参数索引。例如:%1x 将以十进制和十六进制格式打印第一个参数 | 159 9F |
< | 格式化前面说明的数值。例如:%d%<x将以十进制和十六进制打印同一个数值 | 159 9F |
其实我们可以使用s转换符格式化任意的对象。
同时我们可以使用静态的String.format
方法创建一个格式化的字符串,而不打印字符串。
String message=String.format("Hello,%s,Next year,you will be %d",name,age)
好的我们已经大概了解了printf
方法的一些基本属性了,下面来说说针对文件的输入与输出。
文件输入与输出
我们知道计算机上的数据都是通过文件来装载的,那么现在我们来看看在Java
中如何实现文件的输入与输出。
其实要想读取一个文件,需要向上述的标准输入一样,构造一个Scanner对象,来看一个栗子;
Scanner in =new Scanner(Path.of("mylife.txt"),StandardCharsets.UTF_8);
当然如果文件名中有反斜杠符号,就要记住在每个反斜杠之前再加一个额外的反斜杠转义:"c:\\mydirectory\\mylife.txt"
下面我们看看它在idea中的运行情况:
首先我们在 /home/javalover/
路径下创建了一个文件名为hello.txt
。后在idea上进行尝试读取。
这里需要注意的是在文档路径之后还多了一串代码。
这个是为了指定编码格式为UTF_8
格式,这对于互联网上的文件很常见(不过并不是普遍适用)。
但是如果省略了这一步,Java
会以默认编码来显示,但这并不好,因为不同的机器上会表现出不同的内容,就好像日文中的大米的写法和中文中大米的写法肯定也是不一样的
上面我们介绍了读取已经存在的文件,那么也肯定会有写入文件这种操作,的确,要想写入文件,要构造一个PrintWriter
对象,在进行构造时需要提供文件名和字符编码。
PrintWriter out=new PrintWriter("mylife.txt",StandardCharsets.UTF_8);
同样的我们在idea上来做一下测试
但是这里很快会出现一个问题,假如该路径上的文件不存在的话,那怎么办?
不用担心,可以创建该文件,可以像输出到``System.out一样使用
print和
println及
printf`命令。
其实可以构造一个带有字符串参数的Scanner,但这个Scanner会把字符串解释为数据,而不是文件名。
例如:
System.out.println("请输入一个数")
Scanner in =new Scanner("mylife.txt");
编译器不会报错,但是它会把双引号中的内容直接输出出来,并且我们想要的并不是这样的结果。
同时还需要注意的是,在指定文件名的时候可以采用绝对路径和相对路径两种方式来表示。
相对路径: “mylife.txt”
就是一个相对路径,文件是相对于Java
虚拟机启动目录的位置,启动目录就是命令解释器的当前目录.如果使用集成开发环境,那么启动目录将由IDE
控制。绝对路径,如果你觉得相对路径太麻烦了,可以考虑使用绝对路径名。
这里需要注意的是,如果用一个不存在的文件构造一个Scanner,或者用一个无法创建的文件名构造一个PrintWriter
,就会产生异常
如果你已经知道它可能会出现异常,这就需要在main方法中用throws子句标记。
public static void main(String[]args) throws IOException{
Scanner in =new Scanner(Path.of("mylife.txt"),StandardCharsets.UTF_8);
}
好了,说完了输入与输出,现在我们来说说控制流程叭!
控制流程
与其他程序设计语言一样,Java
也通过使用条件语句和循环语句来实现控制流程。
相信对C
和C++
熟悉的人,应该对Java
的控制流程并不陌生,因为大体都差不多,只是需要注意的是Java
中没有goto语句,但在Java
中break语句可以带标签。
块作用域
在深入学习控制结构之前,需要了解块(block)的概念。
块是指由若干条
Java
语句组成的语句,并用一对大括号括起来
块确定了变量的作用域,一个块可以嵌套在另一个块中
我们来看一个栗子加深理解一下:
public static void main(String[]args){
int n;
//n被定义在main方法中
{
int n;//Error,会报错
int k;
//k被定义在一个块内
}
}
条件语句
在日常的学习生活中,一定经常听过这样的话:只有满足综测分达到某个层次才能申请奖学金
条件即影响事物存在发展的因素。
在Java
中,条件语句的形式为:
if(condition) statement;
这里的条件必须要小括号括起来
与大多数程序设计语言一样,Java
常希望在某个条件为真时执行多条语句,在这种情况下,可以使用块语句。
if(yourSales>=target){
performance="Satifactory";
bonus=100;
}
这个栗子的意思就是当yourSales
大于等于target
时,将执行大括号中所有语句。
这里需要明白的一点是:不使用块时,if后只能够执行一条语句,后续的语句并不属于条件语句中的作用域,但使用块时,可以将希望执行的语句都放置在语句块中,可以避免歧义。
在Java
中更为一般的条件语句如下所示:
if(condition) statement1 else statement2
即符合条件,则执行语句1,否则则执行语句2.
if(yourSales>=target){
performance="satisfactory";
bonus=100=0.01*(yourSales-target);
}else{
performance="satisfactory";
bonus=0;
}
其中else的部分是可选的,else子句与最近的if构成一组。
即if else if
的格式。非常常见。也可以叫做if/else if的多分支结构
下面来看个栗子;
if(yourSales>=2*target){
performance="Excellent";
bonus=1000;
}
else if(youSales>=1.5*target){
performance="Fine";
bonus=500;
}
else if(yourSales>=target){
performance="Satisfactory";
bonus=100;
}
else{
System.out.println("You are fired!");
}
好了,看完了条件语句,下面讲讲最重要的循环。
循环
什么是循环呢?
循环一般指事物周而复始地运动或变化。
在Java
中一般常用的循环结构有三种:while,do while和for
下面我们一一来介绍他们的用法:
while
当条件为true时,while循环执行一条语句(也可以是执行一个块语句)。
while(condition) statement
有点类似于if条件语句,但与if不同的是,它会一直重复判断while括号内的条件,直到不符合时才退出循环。
而且有趣的是如果它刚开始就不符合条件,那么这个循环中的语句一次也不会执行。
下面来看看while循环的流程图:
graph TD
A[Start] --> B(balance< goal)
B --> |Yes| C[update balance]
C --> D[years++]
D --> A
B --> |NO| E[print years]
F[Vertical flow chart ]
下面来看个栗子:
这是一个工资按照一定比例来增长直到超过目标金额才停止的栗子。
while(balance<goal){
balance+=payment;
double interset=balance*intersetRate/100;
balance+=interset;
years++;
}
System.out.println("years"+years);
回到刚刚前面的情况,while语句最先检测前面的条件,如果它不满足,则一次也不执行语句块内的内容
如果非常有必要至少执行一次语句块中的内容,那该怎么办?
很显然,while是不合适的,那么下面介绍的循环结构就能够很巧妙的解决至少执行一次的问题。
do/while
这个循环结构非常有意思,它会先执行语句块中的内容再去判断循环的条件。
格式如下:
do statement while(condition);
这种循环语句先执行语句(通常是一个语句块),然后再检测循环条件,如果为true,重复执行语句,然后再次检测循环条件,以此类推。(本质上这个循环体中语句至少可以执行一次)
下面来看个栗子叭(首先计算退休账户中的余额,再询问是否退休?)
do{
balance+=payment;
double interest=balance*interestRate/100;
balance+=interest;
year++;
//打印现在的工资
System.out.println(balance);
//询问是否需要退休?
Scanner in=new Scanner(System.in);
String input=in.nextLine();
}while(input.equals("N"))
下面来看个流程图
st=>start: start
op1=>operation: update balance
op2=>operation: print balance
op3=>operation: read input
cond=>condition: input="N"
sub1=>subroutine: return
io=>inputoutput: NO
e=>end: 结束框
st->op1->op2->op3->cond
cond(yes)->sub1(left)->op1
cond(no)->e
for
下面我们来说说for循环语句,这是一种支持迭代的通用结构,由一个计数器或类似变量控制迭代次数,每次迭代后这个变量都会更新。
下面来看个栗子:
question:将数字1~10输出到控制台中;
for(int i=1;i<=10;i++){
System.out.println(i);
}
for后的小括号的内容很重要!
第一部分是对i值的初始化,可以将i当做是计数器。 第二部分给出了每次新一轮循环执行前要检测的循环条件。 第三部分指定如何更新计数器。
尽管Java
允许在for循环的每个部分都放了表达式,但有一条不成文的规定就是for语句的3个部分应该对同一个计数器变量进行初始化、检测和更新
在循环中,检测两个浮点数是否相等时要格外小心,来看个for循环
for(double x=0;x!=10;x+=0.1){
xxxxxxxxx
}
很不幸,这个循环可能永远都不会结束,由于舍入的误差,可能永远达不到精确的最终值,因为0.1无法准确地用二进制表示
当for语句的第一部分中声明了第一个变量,那么这个变量(通常是i)就拓展到了for循环体的末尾。即:花括号结束的地方
此时的i如果在循环体外部使用,会报错,因为i的作用域仅限于循环体内。
因此,如果希望在循环体的外部使用循环计数器(通常是i)的值,就需要在循环体外部定义这个变量;
而且我们可以在不同的for循环中定义相同的变量。
int i;
for(i=1;i<=10;i++){
System.out.println(i);
}
其实细心的人就会发现,其实for循环语句只不过是while循环的一种简化形式;
例如:
for(int i=10;i>0;i--){
System.out.println(i);
}
可以用while循环来写
int i=10;
while(i>0){
System.out.println(i);
i--;
}
多重选择:switch语句
好了,讲完了循环,让我们来说说怎么处理多个选项。
在处理多个选项时。使用if/else语句显然有些笨拙,Java
有个与C++
、C
完全一样的switch语句。
让我们来看看用法叭
Scanner in=new Scanner(System.in);
System.out.printl("选择一个选项:1、2、3、4");
int choice=in.nextInt();
switch(choice){
case 1:
break;
case 2:
```
break;
case 3:
````
break;
case 4:
```
break;
default
````
break;
}
可以注意到switch语句将从与选项值相匹配的case标签开始执行,直到遇到break语句,或者执行到switch语句的结束处为止。
如果没有匹配的case标签,而有default子句,那么就执行这个子句。
需要注意的是如果每个case后没有break,那么程序将会在第一个满足case标签的语句执行完之后,继续执行后续的case标签的语句,直到语句结束。
这样就产生了一种穿透效果。
这里说到了case标签,简单介绍一下
case标签可以表示的内容如下:
类型为byte、char、short或int的常量表达式 枚举常量 从 Java7
开始,case标签还可以是字符串字面量。
举个栗子:
String input="YES";
switch(input.toLowercase()){
case "yes"
System.out.println("yes");
break;
case "no"
System.out.println("no");
break;
default
System.out.println("null");
}
这就表示了case标签可以作为字符串字面量来参与程序的运行。
当在switch语句中使用枚举常量时,不需要在每个标签中指定枚举名,可以用switch的表达式值推导得出。
Size s=SMALL;
switch(s){
case SMALL//这里并不需要使用Size.SMALL,可以直接使用SMALL
System.out.println("small");
break;
case xxx
xxxx
break;
default
xxxx
}
中断控制流程语句
中断控制流程,顾名思义,就是在程序运行过程中中断一次运行。
有意思的是,Java
设计者把goto作为保留字,但并没有打算在实际的运用中使用它。
不少学者认为goto语句很容易导致错误,但偶尔使用还是有益处的,所以Java
的设计者保留了goto语句,并且新加了一条语句:带标签的break。
那么既然有带标签的break,那么自然也有不带标签的break。显然之前的switch语句中的break就是不带标签的break,来看个栗子:
while(years<=100){
balance+=payment;
double interset=balance*intersetRate/100;
balance+=interset;
if(balance>=goal)
break;
years++;
}
很明显,在循环开始时,如果year>100;或者在循环体中balance>=goal.则退出循环结构。
好的,说完了不带标签的break,下面来说说带标签的break。
它一般用于跳出多重嵌套的循环语句
下面一个示例说明了break语句的工作状态(值得注意的是,标签必须放在最希望跳出的外层循环之前,并且带一个冒号)
Scanner input=new Scanner(System.in);
int n;
//定义一个标签
read_data:
while(````)
{
for(````){
System.out.println("Enter a number>=0:")
n=input.nextInt();
if(n<0)
break read_data;
}
}
如果输入有误,执行带标签的break会跳转到带标签的语句块的末尾。
然而事实上,带标签这样的特权并不是只有break才有的,甚至可以将它应用到if语句或者语句块中;
如下图所示
label:
{
if(condition) break label;
}
并且需要注意的是只能跳出语句块不能够跳入语句块。
最后还有一个continue语句,与break语句一样。
它将中端正常的控制流程,并将控制转移到最内层循环的首部
例如:
Scanner input=new Scanner(System.in);
int n;
while(sum<goal){
System.out.println("Enter a number:")
n=input.nextInt();
if(n<0)continue;
sum+=n;
}
如果n<0,则continue语句将越过当前循环体的剩余部分,立刻跳到循环首部。
如果将continue语句运用到for语句中,那么就可以跳到for循环的更新部分
来看个栗子:
Scanner inout =new Scanner(System.in);
int n;
for(int count=0;count<=100;count++){
System.out.println("Enter a number,-1 to quit");
n =input.nextInt();
if(n<0)continue;
sumn+=n;
}
如果n<0,则continue语句跳到count++处
还有一种带标签的continue语句,将跳到与标签匹配的循环的首部。
好了,以上便是我们的输入输出与流程控制的相关内容。