Golang实现按指定字符后回车退出的功能

Golang实现按指定字符后回车退出的功能 大家好,

我正在尝试学习Coursera上的新Golang系列课程,但被一个问题集难住了。题目要求在一个切片达到其初始限制大小3后,动态地为其分配空间。我遇到的问题是,如果用户决定按’X’键然后按回车,程序应该退出。以下是我的代码(注意:我还没有实现所有功能):

// 编写一个程序,提示用户输入整数并将整数存储在已排序的切片中。
// 程序应写成一个循环。在进入循环之前,程序应创建一个大小为3的空整数切片。
// 在每次循环过程中,程序提示用户输入一个要添加到切片的整数。
// 程序将该整数添加到切片中,对切片进行排序,并按排序顺序打印切片的内容。
// 切片的大小必须能够增长,以适应用户决定输入的任何数量的整数。
// 只有当用户输入字符'X'而不是整数时,程序才应退出(退出循环)。

package main

import (
	"bufio"
	"fmt"
	"log"
	"os"
)

func main() {
	userSlice := make([]int, 3) //创建一个大小为3的切片

	// var intToAdd rune

	i := 0
	for i < len(userSlice) {
		fmt.Println("欢迎使用Slices,请输入一个数字添加到切片:")
		// fmt.Scan(&intToAdd)
		inputReader := bufio.NewReader(os.Stdin)
		intToAdd, _, err := inputReader.ReadRune()
		if err != nil {
			log.Fatal(err)
		}
		if intToAdd == 'X' {
			fmt.Println("感谢使用Slices!")
			break
		}
		userSlice[i] = int(intToAdd)
		fmt.Println("当前切片:")
		for _, v := range userSlice {

			fmt.Println(v)
		}
		i++
	}

}

更多关于Golang实现按指定字符后回车退出的功能的实战教程也可以访问 https://www.itying.com/category-94-b0.html

20 回复

不,学习新东西很难。

更多关于Golang实现按指定字符后回车退出的功能的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


哈哈,更像是继续糟糕的工作。

你确实为我指明了正确的方向。谢谢!我会继续努力的。谢谢!

@jayts,谢谢我的朋友,我一定会的!

@jayts,谢谢!我会尝试一下并在这里反馈。干杯!

@FistOfJustice 感谢你的额外解释。我现在明白你的问题了,但看起来 Johan 已经给了你一个很好的答案。如果你需要更多帮助,请告诉我们。

祝你好运,并继续努力! 😊

func main() {
    fmt.Println("hello world")
}

我完全理解你的解释。我想我会尝试其他方法。谢谢!

@jayts@johandalabacka,再次感谢你们的巨大帮助。对于自己需要求助,我感到有些失望。谢谢!

@jayts,感谢回复。我最初尝试使用 int 类型,但当我这样做时,就无法再使用 ‘X’ 然后按回车键来退出程序了。我会尝试您的建议,看看是否可行。谢谢!

编辑:我忘了提到,append 似乎会动态地重新分配切片中的额外空间。例如,假设切片的原始大小为 3,然后您使用 append 向列表添加元素,那么列表的大小就会变为 4。这就是我使用 append 时注意到的情况。谢谢!

你好

inputReader.ReadRune() 会读取下一个符文并返回其字符编码,所以如果你输入 A,你将得到符文 60。如果你想将数字作为字符串读取然后进行转换,可以使用 bufio.Scanner 从标准输入读取。

bufio.NewScanner(os.stdin) - 创建一个新的扫描器 scanner.Scan() 等待直到有更多输入(用户按下回车键) scanner.Text() 获取输入的文本

https://golang.org/pkg/strconv/ 包含将字符串转换为数字的函数 https://golang.org/pkg/sort/ 包含对整数切片进行排序的函数

最后,你可以使用 append 来扩展切片。

希望我为你指明了一些正确的方向,不要犹豫,随时回来再问! 🙂

关于 append() 如何重新分配切片…

请注意理解切片的长度与其容量之间的区别。Go 语言内置了 len()cap() 函数来获取这两个数值。长度是指切片中实际包含的元素数量,而容量是指切片当前可用的空间大小(即,已分配了多少空间)。

append() 进行重新分配时,它并不仅仅是增加一个空间来放入下一个元素。它会分配一个更大的容量,这样下次你再追加元素时,就不需要再次重新分配了。如果你持续追加元素,每次当空间耗尽需要重新分配时,它都会比上一次分配更多的额外空间。

你可以修改你的程序,在持续向切片添加整数时观察其长度和容量的变化。这可能会带来一些启发。wink

你进展得不错。数字消失的原因是切片排序改变了顺序,但你仍然在特定索引处插入值。

[0, 0, 0] 你在索引 0 处输入 9,得到 [9, 0, 0],切片排序后变为 [0, 0, 9] [0, 0, 9] 你在索引 1 处输入 6,得到 [0, 6, 9],切片排序后变为 [0, 6, 9] [0, 6, 9] 你在索引 2 处输入 4,得到 [0, 6, 4],切片排序后变为 [0, 4, 6],此时 9 就消失了

你不能在数组完全填满之前对整个数组进行排序。当 i < 3 时,你必须对 userslice 的一个切片进行排序,例如 sort.Ints(userslice[x:y]),其中 x 和 y 可以指定一个或两个。

ReadByte 读取一个介于 0 到 256 之间的字节值。然后你写入一个 9 并按下换行键,stdio 将包含这两个字节:57(即 ‘9’)和 10(即 ‘\n’ 或换行符)。第一次读取将得到 57,你检查它是否为 X,然后减去 48(即 ‘0’),接着它会读取另一个字节 10,你再次检查它是否为 X,然后减去 48。由于字节的最大值是 255,10 - 48 将是 -38,但它会变成 256 - 38,即 218。

package main

import (
	"fmt"
)

func main() {
	var b byte = 0
	fmt.Println(b - 38)
}

将打印 218

这有点难以解释,但我认为你应该尝试另一种方法:读取字符串,检查它是否为 X,否则尝试将字符串转换为整数。你的程序无法处理像 87 这样的数字,因为它会把 8 和 7 当作单独的数字处理。

你已经非常接近解决这个问题了!正如Johan所说,问题在于当你对切片进行排序后,如果 i 仍然小于 3,你可能会在下次放入另一个整数时覆盖切片中的某个元素。

这里有一个你可以尝试的方法:永远不要对 userSlice 进行排序。当你想按排序顺序打印它时,使用 make() 创建一个具有相同元素数量的新切片,然后使用Go内置的 copy() 函数使新切片成为 userSlice 的副本。接着对这个新切片进行排序并打印。

(如果你想提高效率,可以在 i < 3 时使用这个方法,而在 i >= 3 时使用你现在的做法。)

我让这个方法成功运行了,但我不打算向你展示代码,因为我不想破坏你的练习。如果你还没有学习过 copy(),你可以在这里阅读Go内置函数的相关信息:

https://golang.org/src/builtin/builtin.go

在该页面中查找 func copy(dst, src []Type) int,并阅读它上方解释 copy() 如何工作的注释。这可能也是一个了解其他内置函数信息的好时机!

我不太理解您是如何描述您的问题的,因为程序本应在用户输入 X 然后按 Enter 时退出。这部分是正常工作的。

关于其余部分,我有以下建议:

您可以在 for 循环之前只调用一次 bufio.NewReader()。然后在循环内部继续使用 inputReader。您不必每次调用其方法时都创建一个新的 Reader

由于说明没有要求您支持 Unicode,使用 ReadRune() 可能不是最佳选择。尝试使用 bufio 包中的其他方法,例如 ReadBytes()ReadString()。看起来您曾尝试使用 fmt.Scan 并且几乎让它工作了,但您给了它错误的类型参数。请尝试:

var intToAdd int
...
fmt.Scan(&intToAdd)

您现在这样做的方式是,使用 int() 将一个 rune 转换为整数,从而得到一个单独 rune 的内部(二进制)表示的十进制值。我认为说明希望您将一个字符串,如 "34",转换为 int。为此请使用 strconv 包。https://golang.org/pkg/strconv/ (只需从顶部开始阅读。您会看到的。)或者直接使用 fmt.Scan()

要重新分配切片,您只需要使用 Go 内置的 append() 函数。不要使用:

userSlice[i] = 34

而是使用:

userSlice = append(userSlice,34)

append() 会检查第二个参数是否能放入 userSlice,如果不能,它会为您重新分配切片。您需要将 userSlice 设置为 append() 的返回值,因为如果 append() 重新分配了切片,它会返回一个新的切片,并且您之后必须使用那个切片。

我还没有使用过 Scanner 类型,但 Johan 建议使用它可能是个好主意。它在 bufio 包中,你可以在那里查阅相关信息。

如果你想使用 ReadByte()ReadBytes(),可能会更复杂一些。你需要将字节读入一个切片,然后将其转换为字符串,接着再用 strconv.Atoi() 将该字符串转换为整数。

以下是一些实现我所描述操作的代码。首先是一些声明。

    var intToAdd int
    var byteSlice []byte
    var i int

现在,读取输入,

byteSlice, _ = reader.ReadBytes('\n')

将输入中的字节读入 byteSlice,包括换行符。

if len(byteSlice) == 2 && byteSlice[0] == 'X' { os.Exit(0) }

如果第一个字节是 ‘X’ 则退出。(为了示例简单,我假设没有错误,并且第二个字节是 ‘\n’。)

s := string(byteSlice[:len(byteSlice)-1]) 

将切片转换为字符串。(注意,我从长度中减去了 1 以忽略末尾的换行符,否则会干扰 strconv.Atoi()。)

intToAdd, _ = strconv.Atoi(s)

将字符串转换为整数。

if(i < 3) { userSlice[i] = intToAdd; i++ } else { userSlice = append(userSlice,intToAdd) }

前三个我没有使用 append(),因为 append() 会添加到现有切片的末尾。当你使用 make() 创建切片时,那 3 个元素被初始化为零。

我通过编写一个解决你所提问题的程序来确保所有这些都能正常工作。所以,如果你想尝试这个方法,它应该如我所述那样工作。或者至少你可以查看一下,或许能从中学习。

感谢 @jayts!我的程序遇到了一些问题。目前,我已修改代码来测试字符 ‘X’,如果不是 ‘X’,则将数字添加到切片中。我还没有尝试使用 append 来动态增加空间(有点像 Java 中的 ArrayList add 方法)。请看一下代码和输出:

// Write a program which prompts the user to enter integers and stores the integers in a sorted slice.
// The program should be written as a loop. Before entering the loop, the program should create an empty
// integer slice of size (length) 3. During each pass through the loop, the program prompts the user to enter an
// integer to be added to the slice. The program adds the integer to the slice, sorts the slice, and prints
// the contents of the slice in sorted order. The slice must grow in size to accommodate any number of integers
// which the user decides to enter. The program should only quit (exiting the loop) when the user enters the
// character ‘X’ instead of an integer.

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	userSlice := make([]int, 3) //Create a Slice of size 3

	inputReader := bufio.NewReader(os.Stdin)

	for i := 0; i < len(userSlice); i++ {

		fmt.Println("Welcome to Slices, Please enter a number to add to the slice:")

		checkForX, _ := inputReader.ReadByte()

		if checkForX == 'X' {
			fmt.Println("Thanks for using Slices!")
			os.Exit(0)
		} else {

			userSlice[i] = int(checkForX - '0')
			fmt.Println("Slice so far:", userSlice)
		}

	}

}

以下是输出,我甚至没有尝试添加另一个值,但它却自己添加到了切片中。我想知道为什么:

$ go run slices.go
Welcome to Slices, Please enter a number to add tothe slice:9
Slice so far: [9 0 0]
Welcome to Slices, Please enter a number to add tothe slice:
Slice so far: [9 218 0]
Welcome to Slices, Please enter a number to add tothe slice:X
Thanks for using Slices!

编辑:请注意,第二次没有输入任何内容,但切片中的第二个位置却不知为何被填充了。这很奇怪。

要实现按’X’后回车退出的功能,你的代码需要处理整行输入而不是单个字符。以下是修改后的实现:

package main

import (
	"bufio"
	"fmt"
	"os"
	"sort"
	"strconv"
)

func main() {
	userSlice := make([]int, 0, 3) // 使用长度为0,容量为3的切片

	scanner := bufio.NewScanner(os.Stdin)

	for {
		fmt.Print("请输入一个整数(输入X退出): ")
		
		if !scanner.Scan() {
			break
		}
		
		input := scanner.Text()
		
		if input == "X" {
			fmt.Println("感谢使用Slices!")
			break
		}
		
		num, err := strconv.Atoi(input)
		if err != nil {
			fmt.Println("无效输入,请输入整数或X退出")
			continue
		}
		
		userSlice = append(userSlice, num)
		sort.Ints(userSlice)
		
		fmt.Println("当前切片(已排序):")
		for i, v := range userSlice {
			fmt.Printf("[%d]: %d\n", i, v)
		}
		fmt.Println()
	}
}

关键修改点:

  1. 使用bufio.Scanner读取整行输入ReadRune()只读取单个字符,而Scanner可以读取整行,包括回车

  2. 正确检查退出条件:直接比较输入字符串是否为"X"

  3. 使用append()动态扩展切片:当切片容量不足时自动分配新空间

  4. 添加输入验证:使用strconv.Atoi()将字符串转换为整数

  5. 实现排序功能:使用sort.Ints()对切片进行排序

示例运行:

请输入一个整数(输入X退出): 5
当前切片(已排序):
[0]: 5

请输入一个整数(输入X退出): 2
当前切片(已排序):
[0]: 2
[1]: 5

请输入一个整数(输入X退出): 8
当前切片(已排序):
[0]: 2
[1]: 5
[2]: 8

请输入一个整数(输入X退出): 3
当前切片(已排序):
[0]: 2
[1]: 3
[2]: 5
[3]: 8

请输入一个整数(输入X退出): X
感谢使用Slices!

这个实现完全满足题目要求:初始容量为3,动态扩展,按’X’加回车退出,并保持切片始终排序。

回到顶部