查看原文
其他

输入输出与流程

树莓蛋黄派 Java规途 2023-07-04


  • 输入与输出

    • 读取输入

    • 格式化输出

    • 文件输入与输出

  • 控制流程

    • 块作用域

    • 条件语句

    • 循环

    • 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数字面前补0003333.33
-左对齐|3333.33  |
(将负数括在括号内(3333.33)
添加分组分隔符3,333.33
#(对于f格式)包含小数点3,333.
#(对于o或x的格式)添加前缀ox或者ooxcafe
$指定要格式化的参数索引。例如:%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一样使用printprintlnprintf`命令。

其实可以构造一个带有字符串参数的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也通过使用条件语句和循环语句来实现控制流程。

相信对CC++熟悉的人,应该对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流程图

下面来看个栗子:

这是一个工资按照一定比例来增长直到超过目标金额才停止的栗子。

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"))

只要用户回答“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
do-while循环流程图

for

下面我们来说说for循环语句,这是一种支持迭代的通用结构,由一个计数器或类似变量控制迭代次数,每次迭代后这个变量都会更新。

下面来看个栗子:

question:将数字1~10输出到控制台中;

forint 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语句,将跳到与标签匹配的循环的首部。

好了,以上便是我们的输入输出与流程控制的相关内容。

拜拜


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存