一、获取命令行参数
很多语言获取命令行参数,是通过主函数的参数获得的。
但Rust主函数是个无参数函数,命令行参数只能通过std::env::args()函数获得。
std::env::args()返回一个迭代器,其中包含了程序名和后面所有参数。
实例
rust
fn main() {
let args = std::env::args();
for arg in args {
println!("{}", arg);
}
}
运行结果:
/home/yt/src/foo/target/debug/foo
可以在launch.json中的"args": []设置命令行参数。
我们将它改成"args": ["first", "second"] ,然后保存、再次运行刚才的程序,运行结果:
/home/yt/src/foo/target/debug/foo
first
second
二、输入输出
Rust标准库通过两个特质组织输入输出
Read特质包含了许多方法用于读取数据
Write特质包含了许多方法用于写入数据
Read特质
fn read(&mut self, buf: &mut [u8]) -> Result<usize>
读取一些字节到指定的缓冲区中,返回读取的字节数
fn read_to_string(&mut self, buf: &mut String) -> Result<usize>
读取所有字节,直到EOF为止,然后将它们追加到 buf
Write特质
fn write(&mut self, buf: &[u8]) -> Result<usize>
write() 方法把参数 buf写入输出流。返回Result枚举,如果成功则返回写入的字节数。
fn write_fmt(&mut self, args: Arguments<'_>) -> Result<()>
将格式化的字符串写入输出流,返回遇到的任何错误
(一)标准输入
std::io::stdin()函数返回一个std::io::Stdin的实例,这是一个结构体,代表标准输入流。
结构体Stdin虽然实现了Read特性,但还实现了一个read_line方法,我们常用这个方法,而不是Read特性的方法。
pub fn read_line(&self, buf: &mut String) -> Result<usize>
read_line方法读取一行字符串(包括换行符)追加到buf,返回值是读取的字节数。
实例
rust
use std::io;
fn main() {
let mut str_buf = String::new();
io::stdin().read_line(&mut str_buf).expect("Failed to read line.");
println!("Your input line is \n{}", str_buf);
}
实例
rust
fn main(){
let mut line = String::new();
println!("请输入你的名字:");
let b1 = std::io::stdin().read_line(&mut line).unwrap();
println!("你好 , {}", line);
println!("读取的字节数为:{}", b1);
}
输出结果如下
请输入你的名字:
简单教程
你好 , 简单教程
读取的字节数为:13
注意:
目前Rust标准库还没有读取数字或格式化数据的方法,我们只能读取字符串,然后自己把字符串转换成数字,通常是使用parse把字符串转成数字。参考rust类型转换
实例
rust
一行只有一个数
let mut buf = String::new();
io::stdin().read_line(&mut buf).unwrap();
let num1: i32 = buf.trim().parse().unwrap();
一行有多个数
let mut buf = String::new();
io::stdin().read_line(&mut buf).unwrap();
let mut nums = buf.split_whitespace();
let num1: f64 = nums.next().unwrap().parse().unwrap();
let num2: f64 = nums.next().unwrap().parse().unwrap();
let num3: f64 = nums.next().unwrap().parse().unwrap();
读入数组
let mut buff = String::new();
io::stdin().read_line(&mut buff).unwrap();
let ns: Vec<i32> = buff.split_whitespace().map(|x| x.parse().unwrap()).collect();
for v in ns {
print!("{} ", v);
}
(二)标准输出
std::io::stdout()会返回一个std::io::Stdout的实例,这是个结构体,表示标准输出流。
结构体Stdout实现了Write特质
实例
rust
use std::io::Write;
fn main() {
let b1 = std::io::stdout().write("简单教程 ".as_bytes()).unwrap();
let b2 = std::io::stdout().write(String::from("www.twle.cn").as_bytes()).unwrap();
std::io::stdout().write(format!("\n写入的字节数为:{}\n",(b1+b2)).as_bytes()).unwrap();
}
输出结果如下
简单教程www.twle.cn
写入的字节数为:24
write_fmt方法通常与format_args!()搭配使用。应优先使用write!()宏
rust
std::io::stdout().write_fmt(format_args!("{:.*}", 2, 1.234567)).unwrap();
write!(std::io::stdout(), "{:.*}", 2, 1.234567).unwrap();
(三)格式化输出
std::fmt 模块定义了一系列宏,包括:
format! 从格式化文本创建字符串。
print! 与format! 类似,但将文本输出到标准输出。
println! 与print! 类似,但输出结果追加一个换行符。
eprint! 与print! 类似,但将文本输出到标准错误。
eprintln! 与eprint! 类似,但输出结果追加一个换行符。
write! 第一个参数是 &mut io::Write或 &mut fmt::Write,后面参数与format!一样
writeln! 与write!相同,但追加了一个换行符
以println!为例
第一个参数是格式字符串,它必须是字符串字面量
后面是参数列表,依次对应格式字符串中的"占位符",这一点与C语言中的printf函数很相似。但是,Rust的占位符是{}。
占位符的完整格式为
'{' [ argument] [ ':' [[fill]align][sign]['#']['0'][width]['.' precision]type ]'}'
1.argument
分两种
(1)位置参数
除了依次替换占位符之外,还能在占位符中指定位置,表示让此位置的参数去替换占位符,例如 {1},表示用第二个参数替换该占位符(索引从0开始)
rust
println!("{}{}", 1, 2); // =>"12"
println!("{1}{0}", 1, 2); // =>"21"
一旦开始将两种类型的位置说明符混合在一起,事情就会变得有些棘手
rust
println!("{1} {} {0} {}", 1, 2); // => "2 1 1 2"
(2)命名参数
还可以为参数指定名称,带名称的参数必须放在不带名称参数的后面
rust
println!("{argument}", argument = "test"); // => "test"
println!("{name} {}", 1, name = 2); // => "2 1"
println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b"
如果命名参数没有出现在参数列表中,println! 将引用当前作用域中的同名变量
rust
let argument = 2 + 2;
println!("{argument}"); // => "4"
2.总宽度width
总宽度表示输出的总长度,如果实际长度不够,则要填充和对齐
有两种方法指定宽度
N: N 是整数。
N$:N是位置参数或命名参数
实例
rust
println!("Hello {:5}!", "x"); //Hello x !
println!("Hello {:1$}!", "x", 5); //Hello x !
println!("Hello {1:0$}!", 5, "x"); //Hello x !
println!("Hello {:width$}!", "x", width = 5); //Hello x !
let width = 5;
println!("Hello {:width$}!", "x"); //Hello x !
3.精度.precision
如果是整数,则忽略
如果是小数,精度表示小数点后面位数。默认是6
如果是字符串,精度表示字符数。如果实际大于精度,后面的字符会截掉
有三种可能的方法来指定精度:
.N:N是整数。
.N$:N是位置参数或命名参数
.*:.* 表示第一个参数表示精度,第二个参数才是要输出的值
实例
rust
let v = 3.1415926;
println!("{:.2}", v); // 保留小数点后两位 => 3.14
println!("{:.0}", v); // 不带小数 => 3
println!("{:.1$}", v, 4); // 通过位置参数来设定精度 => 3.1416,相当于{:.4}
println!("{:.prec$}", v, prec=4); // 通过命名参数来设定精度 => 3.1416,相当于{:.4}
let s = "hi你好";
println!("{:.3}", s); // 保留字符串前三个字符 => hi你
println!("Hello {:.*}", 3, "abcdefg"); // {:.*}接收两个参数,第一个是精度,第二个是要输出的值 => Hello abc
4.填充fill
参数位数不够时,要填充。默认填充空格
实例
rust
println!("Hello {:*>5}!", "x"); //Hello ****x!
println!("Hello {:.<5}!", "x"); //Hello x....!
println!("Hello {:0<5}!", "x"); //Hello x0000!
5.对齐align
字符串默认默认左对齐,数字是右对齐
< 左对齐
^ 居中对齐
> 右对齐
rust
println!("Hello {:<5}!", "x"); //Hello x !
println!("Hello {:^5}!", "x"); //Hello x !
println!("Hello {:>5}!", "x"); //Hello x!
6.Sign/#/0
sign可以是+或者-
+表示数字应打印符号。默认情况下从不打印正号,仅打印负号。 使用+之后,就会打印正号。
-当前未使用
#单独使用无效,必须与type一起使用。见type
0表示整数使用0填充,并且是符号感知的。像 {:08} 这样的格式将为整数 1 产生 00000001,而相同格式将为整数 -1 产生 -0000001。 请注意,负版本的零比正版本的少零。 请注意,填充零总是放在符号之后和数字之前。当与 # 标志一起使用时,将应用类似的规则:在前缀之后但在数字之前插入填充零。 前缀包括在总宽度中。这个0是放在对齐之后的,与对齐之前的0是不同的。
实例
rust
println!("Hello {:0<5}!", 5); //Hello 50000!
println!("Hello {:<05}!", 5); //Hello 00005!
println!("Hello {:0<5}!", -5); // 负号也要占用一位宽度 => Hello -5000!
println!("Hello {:<05}!", -5); // 负号也要占用一位宽度 => Hello -0005!
println!("Hello {:+}!", 5); // 显式的输出正号 => Hello +5!
println!("{:#x}!", 27); //0x1b!
println!("{:#010x}!", 27); //0x0000001b!
7.类型type
nothing ⇒ Display,以Display 格式打印
? ⇒ Debug,以Debug 格式打印
x? ⇒ Debug ,以Debug 格式打印,整数以小写十六进制
X? ⇒ Debug ,以Debug 格式打印,整数以大写十六进制
o ⇒ Octal
x ⇒ LowerHex
X ⇒ UpperHex
p ⇒ Pointer
b ⇒ Binary
e ⇒ LowerExp
E ⇒ UpperExp
rust
以Display 格式打印
println!("{}", 27); // 十进制 => 27
println!("{:b}", 27); // 二进制 => 11011
println!("{:o}", 27); // 八进制 => 33
println!("{:x}", 27); // 小写十六进制 => 1b
println!("{:X}", 27); // 大写十六进制 => 1B
当前面有#时,添加前缀
println!("{:#}", 27); // 十进制 => 27
println!("{:#b}", 27); // 二进制 => 0b11011
println!("{:#o}", 27); // 八进制 => 0o33
println!("{:#x}", 27); // 小写十六进制 => 0x1b
println!("{:#X}", 27); // 大写十六进制 => 0x1B
以以Debug 格式打印
let v= vec![1, 2, 3, 10, 11, 12];
println!("{:?}", v); //[1, 2, 3, 10, 11, 12]
println!("{:x?}", v); //[1, 2, 3, a, b, c]
println!("{:X?}", v); //[1, 2, 3, A, B, C]
当前面有#时,添加换行和缩进
let v= vec![1, 2, 3, 10, 11, 12];
println!("{:#?}", v);
println!("{:#x?}", v);
println!("{:#X?}", v);
打印结果如下
[
1,
2,
3,
10,
11,
12,
]
[
0x1,
0x2,
0x3,
0xa,
0xb,
0xc,
]
[
0x1,
0x2,
0x3,
0xA,
0xB,
0xC,
]
以科学计数法
println!("{:2e}", 1000000000); // => 1e9
println!("{:2E}", 1000000000); // => 1E9
以指针
let v= vec![1, 2, 3];
println!("{:p}", v.as_ptr()); // => 0x600002324050
8.转义
有时需要输出 {和},但这两个字符是特殊字符,需要转义:
{{ 转义为 {
}} 转义为 }
\" 转义为 "
rust
println!(" Hello \"{{World}}\" "); // => Hello "{World}"
9.Debug特性
要想以Debug格式打印,类型必须要实现了Debug特性才行。大多数Rust类型都实现了Debug特性,但是对于自定义类型,需要自己实现Debug特性。有一种实现Debug特性的简单方法,就是使用derive属性
rust
#[derive(Debug)]
struct Person {
name: String,
age: u8
}
fn main() {
let i = 3.1415926;
let s = String::from("hello");
let v = vec![1, 2, 3];
let p = Person{name: "sunface".to_string(), age: 18};
println!("{:?}, {:?}, {:?}, {:?}", i, s, v, p);
}
10.Display特性
要想以Display 格式打印,类型必须要实现了Display特性才行。实现了Display特征的Rust类型并不多,往往需要我们自己实现。为类型实现Display特性不像Debug一样能使用derive属性。
为自定义类型实现Display特征
rust
struct Person {
name: String,
age: u8,
}
use std::fmt;
impl fmt::Display for Person {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"大佬在上,请受我一拜,小弟姓名{},年芳{},家里无田又无车,生活苦哈哈",
self.name, self.age
)
}
}
fn main() {
let p = Person {
name: "xx".to_string(),
age: 18,
};
println!("{}", p);
}
为外部类型实现Display特性
由于孤儿原则,无法直接为外部类型实现外部特性,但是可以使用newtype解决此问题。
rust
struct Array(Vec<i32>);
use std::fmt;
impl fmt::Display for Array {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "数组是:{:?}", self.0)
}
}
fn main() {
let arr = Array(vec![1, 2, 3]);
println!("{}", arr);
}
Array就是我们的newtype,它封装了Vec,为Array实现Display特性,相当于间接为Vec实现了Display。
三、文件读写
Rust文件读写