Давно я не копал яму в Rust, сегодня собираюсь выкопать несколько аккуратно расставленных ям. Да, это ввести некоторые типы данных типов коллекций. Название «Ряд за рядом» кажется очень культурным?
существуетРуководство Rust по попаданию в яму: обычные процедурыВ этой статье мы представили некоторые основные типы данных, все они хранятся в стеке, сегодня мы сосредоточимся на 3 типах данных: строка, вектор и хэш-карта.
String
String类型我们在之前的学习中已经有了较多的接触,但是没有进行过详细的介绍。有些有编程基础的同学可能不屑于学习String类型,毕竟它在所有编程语言中可以说是最常用的类型了,大家也都很熟悉了。 For students with this kind of mentality, I want to say to them: I also thought the same at the beginning, and it was not until I was beaten up by the compiler that I made up my mind to come back and study the String type шутки в сторону.
Ржавая строка разделена на следующие типы:
- str: Представляет строку фиксированной длины.
- String: Представляет растущую строку
- CStr: представляет строку, выделенную C и заимствованную Rust, обычно используемую для взаимодействия с языком C.
- CString: Представляет строку, выделенную Rust и доступную для C.
- OsStr: представляет строку, относящуюся к операционной системе, в основном для совместимости с Windows.
- OsString: изменяемая версия OsStr
- Path: указывает путь
- PathBuf: изменяемая версия Path
В этой статье мы сосредоточимся на первых двух, потому что они чаще всего используются в процессе разработки, а также более запутанны. Для str мы обычно видим его ссылочный тип,&str
. если ты виделРуководство по началу работы с Rust: основные понятияЯ полагаю, что после одной статьи вы поняли концепцию ссылочных типов и владения. То есть тип String имеет право собственности, а &str — нет.
В Rust, String по существу VEC
fn main() {
let mut a = String::from("foo");
println!("{:p}", a.as_ptr());
println!("{:p}", &a);
assert_eq!(a.len(), 3);
a.reserve(10);
assert_eq!(a.capacity(), 13);
}
В этом коде мы видим, что a.as_ptr() получает указатель и указатель, полученный оператором & A.
Здесь мы объясняем, что указатель, полученный as_ptr, является адресом указателя последовательности байтов в куче, а адрес &a является адресом указателя строковой переменной в стеке. Кроме того, длины, полученные методами len() и capacity(), представляют собой количество байтов, а не количество символов. Здесь вы можете сами попробовать длину китайских иероглифов.
Поговорив об основных понятиях строк, я думаю, вы получили общее представление о строках Rust. Далее, давайте взглянем на метод строк CRUD.
создать строку
Без лишних слов давайте покажем различные способы создания строк.
fn main() {
let string: String = String::new();
let string: String = String::from("hello rust");
let string: String = String::with_capacity(10);
let str: &'static str = "Jackey";
let string: String = str.to_owned();
let string: String = str.to_string();
}
Мы чаще используем первые два, далее мы познакомимся с последними методами. with_capacity() — создать пустую строку, параметр представляет количество байтов, выделенных в куче. to_owned и to_string демонстрируют, как преобразовать тип &str в тип String.
изменить строку
Rust Modify Strings Существует много обычных методов, например, после добавления строки, соединяя две строки, строки и т. Д. Обновлено. Следующий код покажет несколько способов изменить строку.
fn main() {
let mut hello = String::from("Hello, ");
hello.push('J'); // 追加单个字符
hello.push_str("ackey! "); //追加字符串
println!("push: {}", hello);
hello.extend(['M', 'y', ' '].iter()); //追加多个字符,参数为迭代器
hello.extend("name".chars());
println!("extend: {}", hello);
hello.insert(0, 'h'); //类似于push,可以指定插入的位置
hello.insert(1, 'a');
hello.insert(2, '!');
hello.insert_str(0, "Haha");
println!("insert: {}", hello);
let left = "Hello, ".to_string();
let right = "World".to_string();
let result = left + &right;
println!("+: {}", result); //使用+连接字符串时,第二个必须为引用
let mut message = "rust".to_string(); //使用+=连接字符串时,字符串必须定义为可变
message += "!";
println!("+=: {}", message);
let s = String::from("foobar");
let s: String = s
.chars()
.enumerate()
.map(|(_i, c)| {c.to_uppercase().to_string()})
.collect();
println!("update chars: {}", s);
let s1 = String::from("hello");
let s2 = String::from("rust");
let s3 = format!("{}-{}", s1, s2);
println!("format: {}", s3);
}
Сделаем несколько дополнительных пояснений к приведенному выше коду.
push аналогичен вставке, параметр, полученный методом с _str, является строкой, иначе он может получить только один символ. Insert может указать позицию для вставки, а push может вставить только в конец строки.
При использовании «+» для объединения строк первый параметр имеет тип String, а второй должен иметь ссылочный тип &str. Это похоже на вызов метода add, который определяется следующим образом:
fn add(self, s: &str) -> String {
Следовательно, право собственности на первый параметр передается функции и передается через возвращаемый результат. То есть после использования оператора +left
Собственности больше нет.
поиск строки
В Rust строки не могут получить указанный символ по позиции. То есть следующий код не компилируется.
let s1 = String::from("hello");
let h = s1[0];
Потому что Rust будет думать, что этот 0 относится к первому байту, а символы в строке Rust могут занимать несколько байтов (помните, я просил вас поэкспериментировать с китайскими иероглифами ранее?). Так что, если вы просто хотите получить байт, компилятор не знает, действительно ли вы хотите получить значение, соответствующее байту или этому символу.
Обычно у нас есть следующие методы при работе со строками:
fn main() {
let hello = "Здравствуйте";
let s = &hello[0..4];
println!("{}", s);
let chars = hello.chars();
for c in chars {
println!("{}", c);
}
let bytes = hello.bytes();
for byte in bytes {
println!("{}", byte);
}
let get = hello.get(0..1);
let mut s = String::from("hello");
let get_mut = s.get_mut(3..5);
let message = String::from("hello-world");
let (left, right) = message.split_at(6);
println!("left: {}, right: {}", left, right);
}
Обычно используются фрагменты символов, или вы можете использовать метод chars для получения итератора Chars, а затем вы можете обрабатывать каждый символ по отдельности. Кроме того, с помощью методов get или get_mut также можно получить диапазон индексов, возвращая указанный фрагмент строки. Возвращаемый результат имеет тип Option, потому что Rust возвращает None, если указанный возвращаемый индекс не может вернуть полный символ. Здесь также можно использовать метод is_char_boundary, чтобы определить, является ли позиция недопустимой границей.
Наконец, методы split_at или split_at_mut также можно использовать для разделения строк. Это требует, чтобы разделение было точно на границе символа, иначе программа вылетит.
удалить строку
Стандартная библиотека ржавчины предоставляет некоторые методы удаления строк, давайте продемонстрируем некоторые:
fn main() {
let mut hello = String::from("hello");
hello.remove(3);
println!("remove: {}", hello);
hello.pop();
println!("pop: {}", hello);
hello.truncate(1);
println!("truncate: {}", hello);
hello.clear();
println!("clear: {}", hello);
}
Результат выглядит следующим образом:
Метод remove используется для удаления символа в строке.В качестве параметра он получает начальную позицию символа.Если это не начальная позиция символа, программа аварийно завершает работу.
Метод pop извлекает символы в конце строки, метод truncate перехватывает указанную длину строки, а метод clear используется для очистки строки.
Итак, мы познакомились с основными понятиями и CRUD строк в Rust, а теперь давайте рассмотрим другой тип коллекций — Vector.
Vector
Вектор — это тип данных, используемый для хранения нескольких данных одного типа. его ключVec<T>
. Давайте посмотрим на CRUD векторов.
создать вектор
fn main() {
let v1: Vec<i32> = Vec::new();
let v2 = vec![1, 2, 3];
}
Приведенный выше код демонстрирует два способа создания вектора. Первый - использовать новую функцию для создания пустого вектора. Поскольку никакие элементы не добавляются, необходимо явно указать тип элементов хранения. Второй состоит в том, чтобы создать векторный набор с начальными значениями, мы используем VEC напрямую! Макрос, а затем укажите начальное значение, вам не нужно указывать тип данных элементов в векторе, потому что компилятор может вывести его сам по себе.
вектор обновления
fn main() {
let mut v = Vec::new();
v.push(1);
v.push(2);
}
После создания пустого вектора, если мы хотим добавить элементы, мы можем напрямую использовать метод push для добавления элементов в конец.
удалить вектор
fn main() {
let mut v = Vec::new();
v.push(1);
v.push(2);
v.push(3);
v.pop();
for i in &v {
println!("{}", i);
}
}
Чтобы удалить один элемент, вы можете использовать метод pop, но чтобы удалить весь вектор, вы можете удалить только его владельца, как и другие структуры.
читать векторные элементы
fn main() {
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
println!("The third element is {}", third);
match v.get(2) {
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element."),
}
let v = vec![100, 32, 57];
for i in &v {
println!("{}", i);
}
}
Когда вам нужно прочитать один указанный элемент, есть два способа его использования, один из них — использование[]
, другой — использовать метод get. Разница между двумя методами заключается в следующем: первый возвращает тип элемента, а get возвращает тип Option. Если указанная вами позиция выходит за пределы допустимого диапазона, программа сразу же выйдет из строя при использовании первого метода, а второй метод вернет None.
Кроме того, элементы могут быть прочитаны путем обхода вектора. Если мы хотим хранить разные типы данных, мы можем использовать типы перечисления.
fn main() {
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
}
HashMap
HashMap хранит данные структуры KV, каждый ключ должен быть одного типа, и каждое значение должно быть одного типа. Поскольку HashMap является наименее используемым среди трех типов коллекций, его необходимо вводить вручную перед использованием.
use std::collections::HashMap;
Создать хэш-карту
Сначала давайте посмотрим, как создать новую хэш-карту и добавить элементы.
use std::collections::HashMap;
fn main() {
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
}
Обратите внимание, что при использовании метода вставкиfield_name
иfield_value
потеряет владение. Так как их снова использовать? Мы можем вывести его только с карты хэши.
Доступ к данным Hash Map
use std::collections::HashMap;
fn main() {
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
let favorite = String::from("Favorite color");
let color = map.get(&favorite);
match color {
Some(x) => println!("{}", x),
None => println!("None"),
}
}
Как видите, мы можем использовать get, чтобы получить значение указанного ключа, метод get возвращает тип Option, если нет указанного значения, он вернет None. Кроме того, для обхода хэш-карты можно использовать цикл for.
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
for (key, value) in &scores {
println!("{}: {}", key, value);
}
}
Обновить хэш-карту
Когда мы вставляем значение в тот же ключ, старое значение будет перезаписано. Если вы хотите вставить только тогда, когда ключ не существует, вы можете использовать запись.
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores);
}
Суммировать
Сегодня я заставил вас выкопать три ямы, строку, вектор и хеш-карту, а также представил CRUD для каждого типа данных. Введение в строку занимает относительно много места, потому что это один из наиболее часто используемых типов данных. Конечно, в этой части еще много актуальных знаний, и вы можете учиться и обмениваться со мной.