Skip to Content
RozdziałyStruktury danych i modułyStruktury danych

Struktury danych

Typy struktur

  1. Struktura jednostkowa/pusta (ang. unit-like structure)
    struct EmptyStructure; fn main() { let es = EmptyStructure; }
  2. 🤔 Czy i w jakim celu możemy definiować takie struktury?
  3. Struktura a’la krotka (ang. tuple structure)
    struct Color(i32, i32, i32); // fields are without names (are access as in tuple) struct Point(i32, i32, i32); // a different structure with the same types fn main() { let black = Color(0, 0, 0); let origin = Point(0, 0, 0); }
  4. Po prostu struktura 😈:
    struct Point { x : f32, // named attributes like in classes y : f32, }

Tworzenie instancji struktury

  1. W przykładowym projekcie, w katalogu examples dodaj plik rectangle.rs.

  2. Otwórz powyższy plik, a w nim utwórz strukturę danych opisującą prostokąt:

    #[derive(Debug)] // allows to print the structure in debug mode (ie. to use {:?}) struct Rectangle { x : f32, y : f32 }
  3. Dodaj metodę main, w której utwórz nową instancję struktury Rectangle i wypisz ją na konsole:

    fn main() { let r = Rectangle{x: 1.0, y: 2.0}; // create a new instance using constructor println!("{:?}", r); println!("x: {}, y: {}", r.x, r.y); // access particular fields using . operator }
  4. Skompiluj kod i uruchom przykład:

    cargo run --example rectangle
  5. Możesz również utworzyć instancje na bazie innej instancji (poniższy kod dopisz do funkcji main z poprzedniego punktu)

    let r2 = Rectangle{x : 5, ..r1} // the rest of r2 parameters are copied from r1 println!("{:?}", r); println!("x: {}, y: {}", r2.x, r2.y);

Atrybuty struktur danych, w kontekście posiadania wartości, zachowują się w analogicznych sposób jak zmienne.

Jeśli atrybut jest typu, który posiada daną wartość (ang. owned type), to właśność tej wartości należy do instancji struktury. Oznacza to, że wartość ta zostanie usunięta z pamięci w sytuacji, kiedy instancja struktury wyjdzie poza zakres.

Jeśli natomist atrybut struktury jest referencją, to konieczne będzie określenie czasu życia takiej wartości - o czym będzie więcej w sekcji Czas życia.

Dostęp i modyfikacja atrybutów

  1. Instancja niemutowalna - wszystkie atrybuty są niemutowalne
    let r3 = Rectangle{x : 5.0, y : 9.0}; println!("[{}, {}]", r3.x, r3.y);
  2. Próba ustawienia wartości atrubutu
    r3.x = 6.0; // error
  3. Instacja mutowalna
    let mut r4 = Rectangle{x : 5.0, y : 9.0}; println!("[{}, {}]", r4.x, r4.y); r4.x = 6.0; r4.y = 7.0; println!("[{}, {}]", r4.x, r4.y);

Mutowalność struktury jest określana przez właściciela danej instancji i dotyczy całej instancji. Oznacza to, że nie można ustawiać mutowalności pojedynczych pól struktury. Co więcej, dana struktura w określonym kontekście może być mutowalna, a w innym nie (jeśli zmieni się właściciela z mutowalnego na niemutowalny).

Metody

Metody instancyjne

  1. Pod definicją struktury Rectangle dodaj blok będący miejscem implementacji metod związanych z naszą strukturą

    impl Rectangle { // the place for rectangle methods }
  2. Wewnętz powyższego bloku zdefiniuje metodę obliczającą pole prostokąta.

    fn area(&self) -> f32 { self.x * self.y }

    Zwróć uwage na pierwszy parametr metody (&self), który oznacza referencję do instancji struktury (musi to być zawsze pierwszy parametr metod instancyjnej). Referencję self możesz wykorzystać w ciele metody, aby odwołać się do pól instancji struktury lub innych metod instancyjnych.

  3. W funkcji main dodaj kod, który wywoła metodę area na stworzonej wcześniej instrancji Rectangle

    println!("Area of {:?} is {}", r, r.area());
  4. W analogiczny sposób dodaj metodę wyznaczającą obwód prostokąta.

  5. Dodaj metodę umożliwiającą przeskalowanie prostokąta o zadany współczynnik.

    fn scale(&mut self, factor:f32) { self.x = self.x * factor; self.y = self.y * factor; }

    Zwróć uwagę na pierwszy parametr metody (&mut self), który oznacza, że metoda ta będzie modyfikować instancję struktury.

  6. W metodzie main utwórz nową instancję i wywołaj na nią metodę scale:

    let r = Rectangle{ x : 5.0, y : 4.0}; r.scale(2.0); println!("Area of r is {}", r.area());
  7. Czy kod się skompilował? Dlaczego?

  8. Jak poprawić powyższy przykład?

Metody powiązane

  1. W bloku impl utwórz metodę new_square, który utworzy nowy kwadrat (metody tego typu nazywane są metodami fabrycznymi). Blok ten powinien wyglądać następująco:
    impl Rectangle { // ... other methods fn new_square(x : f32) -> Rectangle { // there is no self in the argument list Rectangle{x : x, y : x} } }
  2. Metody powiązane wywołujemy za pomocą nazwy struktury, a następnie dwóch znaków dwukropka, w naszym przypadku będzie to Rectangle::new_square. W metodzie main utwórz nowy kwadrat i wydrukuj “go” na konsolę:
    let square = Rectangle::new_square(5.0); println!("square: {:?}", square);
  3. Możesz uprościć metodę new_square wykorzystując regułę, pozwalającą na automatyczne przypisane parametru funkcji do parametru tworzonej instancji struktury o tej samej nazwie:
    fn new_square(x : f32) -> Rectangle { Rectangle{x, y : x} // the first argument can be passed in simplified form }
Last updated on