Przegląd rozdziału
W każdej (niestatycznej) funkcji składowej słowo kluczowe this jest stałym wskaźnikiem przechowującym adres bieżącego ukrytego obiektu. Możemy mieć funkcje zwracające *this przez odwołanie, aby umożliwić łączenie metod, gdzie można wywołać kilka funkcji składowych na tym samym obiekcie w jednym wyrażeniu.
Wolę umieścić definicje klas w pliku nagłówkowym o tej samej nazwie co klasa. Trywialne funkcje składowe (takie jak funkcje dostępu, konstruktory z pustymi treściami itp.) można zdefiniować w definicji klasy.
Wolę definiować nietrywialne funkcje składowe w pliku źródłowym o tej samej nazwie co klasa.
Typ zdefiniowany wewnątrz typu klasy nazywany jest typem zagnieżdżonym (lub typem składowym). Aliasy typów mogą być również zagnieżdżane.
Funkcje składowe zdefiniowane w definicji szablonu klasy mogą korzystać z parametrów szablonu samego szablonu klasy. Funkcje składowe zdefiniowane poza definicją szablonu klasy muszą ponownie dostarczać deklarację parametrów szablonu i powinny być zdefiniowane (w tym samym pliku) tuż pod definicją szablonu klasy.
Statyczne zmienne składowe są składowymi o statycznym czasie trwania, które są współdzielone przez wszystkie obiekty klasy. Elementy statyczne istnieją nawet wtedy, gdy nie utworzono instancji obiektów klasy. Wolę uzyskiwać do nich dostęp za pomocą nazwy klasy, operatora rozpoznawania zakresu i nazwy elementów.
Utworzenie elementów statycznych inline pozwala na ich inicjalizację w definicji klasy.
Statyczne funkcje składowe to funkcje składowe, które można wywoływać bez obiektu. Nie mają *this wskaźnika i nie mają dostępu do niestatycznych elementów danych.
W treści klasy deklaracja znajomego (za pomocą słowa kluczowego friend ) może zostać użyta do poinformowania kompilatora, że inna klasa lub funkcja jest teraz przyjacielem. A przyjaciel to klasa lub funkcja (członkowa lub niebędąca członkiem), której przyznano pełny dostęp do prywatnych i chronionych członków innej klasy. Funkcja zaprzyjaźniona to funkcja (członkowa lub niebędąca członkiem), która może uzyskać dostęp do prywatnych i chronionych elementów klasy, tak jakby była członkiem tej klasy. Klasa przyjaciół to klasa, która ma dostęp do prywatnych i chronionych członków innej klasy.
Czas quizu
Pytanie nr 1
Stwórzmy losowy generator potworów. To powinno być zabawne.
a) Najpierw utwórzmy ograniczone wyliczenie typów potworów o nazwie MonsterType. Uwzględnij następujące typy potworów: Smok, Goblin, Ogr, Ork, Szkielet, Troll, Wampir i Zombie. Dodaj dodatkowy moduł wyliczający maxMonsterTypes, abyśmy mogli policzyć, ile jest modułów wyliczających.
Pokaż rozwiązanie
enum class MonsterType
{
dragon,
goblin,
ogre,
orc,
skeleton,
troll,
vampire,
zombie,
maxMonsterTypes,
};
b) Teraz utwórzmy naszą klasę Monster . Nasz Monster będzie miał 4 atrybuty (zmienne składowe): typ (MonsterType), nazwę (std::string), ryk (std::string) i liczbę punktów wytrzymałości (int).
Pokaż rozwiązanie
#include <string>
enum class MonsterType
{
dragon,
goblin,
ogre,
orc,
skeleton,
troll,
vampire,
zombie,
maxMonsterTypes,
};
class Monster
{
private:
MonsterType m_type{};
std::string m_name{"???"};
std::string m_roar{"???"};
int m_hitPoints{};
};
C) enum class MonsterType jest specyficzne dla Monster, więc utwórz MonsterType zagnieżdżone wyliczenie bez zakresu inside Monster i zmień jego nazwę na Type.
Pokaż rozwiązanie
#include <string>
class Monster
{
public:
enum Type
{
dragon,
goblin,
ogre,
orc,
skeleton,
troll,
vampire,
zombie,
maxMonsterTypes,
};
private:
Type m_type{};
std::string m_name{"???"};
std::string m_roar{"???"};
int m_hitPoints{};
};
d) Utwórz konstruktor, który umożliwi inicjalizację wszystkich zmiennych składowych.
Powinien się skompilować następujący program:
int main()
{
Monster skeleton{ Monster::skeleton, "Bones", "*rattle*", 4 };
return 0;
}
Pokaż rozwiązanie
#include <string>
#include <string_view>
class Monster
{
public:
enum Type
{
dragon,
goblin,
ogre,
orc,
skeleton,
troll,
vampire,
zombie,
maxMonsterTypes,
};
private:
Type m_type{};
std::string m_name{"???"};
std::string m_roar{"???"};
int m_hitPoints{};
public:
Monster(Type type, std::string_view name, std::string_view roar, int hitPoints)
: m_type{ type }, m_name{ name }, m_roar{ roar }, m_hitPoints{ hitPoints }
{
}
};
int main()
{
Monster skeleton{ Monster::skeleton, "Bones", "*rattle*", 4 };
return 0;
}
e) Teraz chcemy móc wydrukować naszego potwora, abyśmy mogli sprawdzić, czy jest poprawny. Napisz dwie funkcje: jedną o nazwie getTypeString() , która zwraca typ potwora w postaci ciągu znaków, i drugą o nazwie print() , która dopasowuje dane wyjściowe w przykładowym programie poniżej.
Powinien się skompilować następujący program:
int main()
{
Monster skeleton{ Monster::skeleton, "Bones", "*rattle*", 4 };
skeleton.print();
Monster vampire{ Monster::vampire, "Nibblez", "*hiss*", 0 };
vampire.print();
return 0;
}
i wydrukuj:
Bones the skeleton has 4 hit points and says *rattle*.
Nibblez the vampire is dead.
Pokaż rozwiązanie
#include <iostream>
#include <string>
#include <string_view>
class Monster
{
public:
enum Type
{
dragon,
goblin,
ogre,
orc,
skeleton,
troll,
vampire,
zombie,
maxMonsterTypes,
};
private:
Type m_type{};
std::string m_name{"???"};
std::string m_roar{"???"};
int m_hitPoints{};
public:
Monster(Type type, std::string_view name, std::string_view roar, int hitPoints)
: m_type{ type }, m_name{ name }, m_roar{ roar }, m_hitPoints{ hitPoints }
{
}
constexpr std::string_view getTypeString() const
{
switch (m_type)
{
case dragon: return "dragon";
case goblin: return "goblin";
case ogre: return "ogre";
case orc: return "orc";
case skeleton: return "skeleton";
case troll: return "troll";
case vampire: return "vampire";
case zombie: return "zombie";
default: return "???";
}
}
void print() const
{
std::cout << m_name << " the " << getTypeString();
if (m_hitPoints <= 0)
std::cout << " is dead.\n";
else
std::cout << " has " << m_hitPoints << " hit points and says " << m_roar << ".\n";
}
};
int main()
{
Monster skeleton{ Monster::skeleton, "Bones", "*rattle*", 4 };
skeleton.print();
Monster vampire{ Monster::vampire, "Nibblez", "*hiss*", 0 };
vampire.print();
return 0;
}
f) Teraz możemy utworzyć losowy generator potworów. Zastanówmy się, jak będzie działać nasz MonsterGenerator . W idealnym przypadku poprosimy go o Monster i utworzy dla nas losowy. Ponieważ MonsterGenerator nie ma żadnego stanu, jest to dobry kandydat na przestrzeń nazw.
Utwórz MonsterGenerator przestrzeń nazw. Utwórz funkcję w nazwie generate(). To powinno zwrócić Monster. Na razie spraw, aby zwrócił Monster{ Monster::skeleton, "Bones", "*rattle*", 4};
Powinien się skompilować następujący program:
int main()
{
Monster m{ MonsterGenerator::generate() };
m.print();
return 0;
}
i wydrukuj:
Bones the skeleton has 4 hit points and says *rattle*
Pokaż rozwiązanie
#include <iostream>
#include <string>
#include <string_view>
class Monster
{
public:
enum Type
{
dragon,
goblin,
ogre,
orc,
skeleton,
troll,
vampire,
zombie,
maxMonsterTypes,
};
private:
Type m_type{};
std::string m_name{};
std::string m_roar{};
int m_hitPoints{};
public:
Monster(Type type, std::string_view name, const std::string& roar, int hitPoints)
: m_type{ type }, m_name{ name }, m_roar{ roar }, m_hitPoints{ hitPoints }
{
}
constexpr std::string_view getTypeString() const
{
switch (m_type)
{
case Type::dragon: return "dragon";
case Type::goblin: return "goblin";
case Type::ogre: return "ogre";
case Type::orc: return "orc";
case Type::skeleton: return "skeleton";
case Type::troll: return "troll";
case Type::vampire: return "vampire";
case Type::zombie: return "zombie";
default: return "???";
}
}
void print() const
{
if (m_hitPoints <= 0)
std::cout << m_name << " is dead.\n";
else
std::cout << m_name << " the " << getTypeString() << " has " << m_hitPoints << " hit points and says " << m_roar << ".\n";
}
};
namespace MonsterGenerator
{
Monster generate()
{
return Monster{ Monster::skeleton, "Bones", "*rattle*", 4 };
}
};
int main()
{
Monster m{ MonsterGenerator::generate() };
m.print();
return 0;
}
g) Dodaj jeszcze dwie funkcje do przestrzeni nazw MonsterGenerator . getName(int) pobierze liczbę od 0 do 5 (włącznie) i zwróci wybraną przez ciebie nazwę. getRoar(int) pobierze również liczbę od 0 do 5 (włącznie) i zwróci wybrany przez ciebie ryk. Zaktualizuj także swoją generate() funkcję, aby wywołać getName(0) i getRoar(0).
Powinien się skompilować następujący program:
int main()
{
Monster m{ MonsterGenerator::generate() };
m.print();
return 0;
}
i wydrukuj:
Blarg the skeleton has 4 hit points and says *ROAR*
Twoje imię i dźwięk będą się różnić w zależności od tego, co wybierzesz.
Pokaż rozwiązanie
#include <iostream>
#include <string>
#include <string_view>
class Monster
{
public:
enum Type
{
dragon,
goblin,
ogre,
orc,
skeleton,
troll,
vampire,
zombie,
maxMonsterTypes,
};
private:
Type m_type{};
std::string m_name{"???"};
std::string m_roar{"???"};
int m_hitPoints{};
public:
Monster(Type type, std::string_view name, std::string_view roar, int hitPoints)
: m_type{ type }, m_name{ name }, m_roar{ roar }, m_hitPoints{ hitPoints }
{
}
constexpr std::string_view getTypeString() const
{
switch (m_type)
{
case dragon: return "dragon";
case goblin: return "goblin";
case ogre: return "ogre";
case orc: return "orc";
case skeleton: return "skeleton";
case troll: return "troll";
case vampire: return "vampire";
case zombie: return "zombie";
default: return "???";
}
}
void print() const
{
std::cout << m_name << " the " << getTypeString();
if (m_hitPoints <= 0)
std::cout << " is dead.\n";
else
std::cout << " has " << m_hitPoints << " hit points and says " << m_roar << ".\n";
}
};
namespace MonsterGenerator
{
std::string_view getName(int n)
{
switch (n)
{
case 0: return "Blarg";
case 1: return "Moog";
case 2: return "Pksh";
case 3: return "Tyrn";
case 4: return "Mort";
case 5: return "Hans";
default: return "???";
}
}
std::string_view getRoar(int n)
{
switch (n)
{
case 0: return "*ROAR*";
case 1: return "*peep*";
case 2: return "*squeal*";
case 3: return "*whine*";
case 4: return "*growl*";
case 5: return "*burp*";
default: return "???";
}
}
Monster generate()
{
return Monster{ Monster::skeleton, getName(0), getRoar(0), 4 };
}
};
int main()
{
Monster m{ MonsterGenerator::generate() };
m.print();
return 0;
}
h) Teraz losowo wygenerujemy potwora. Pobierz kod „Random.h” z 8.15 -- Globalne liczby losowe (Random.h) i zapisz go jako Random.h. Następnie użyj Random::get() , aby wygenerować losowy typ potwora, losową nazwę, losowy ryk i losowe punkty wytrzymałości (od 1 do 100).
Powinien się skompilować następujący program:
#include "Random.h"
int main()
{
Monster m{ MonsterGenerator::generate() };
m.print();
return 0;
}
i wydrukuj coś takiego:
Mort the zombie has 61 hit points and says *growl*
Pokaż rozwiązanie
#include "Random.h"
#include <iostream>
#include <string>
#include <string_view>
class Monster
{
public:
enum Type
{
dragon,
goblin,
ogre,
orc,
skeleton,
troll,
vampire,
zombie,
maxMonsterTypes,
};
private:
Type m_type{};
std::string m_name{"???"};
std::string m_roar{"???"};
int m_hitPoints{};
public:
Monster(Type type, std::string_view name, std::string_view roar, int hitPoints)
: m_type{ type }, m_name{ name }, m_roar{ roar }, m_hitPoints{ hitPoints }
{
}
constexpr std::string_view getTypeString() const
{
switch (m_type)
{
case dragon: return "dragon";
case goblin: return "goblin";
case ogre: return "ogre";
case orc: return "orc";
case skeleton: return "skeleton";
case troll: return "troll";
case vampire: return "vampire";
case zombie: return "zombie";
default: return "???";
}
}
void print() const
{
std::cout << m_name << " the " << getTypeString();
if (m_hitPoints <= 0)
std::cout << " is dead.\n";
else
std::cout << " has " << m_hitPoints << " hit points and says " << m_roar << ".\n";
}
};
namespace MonsterGenerator
{
std::string_view getName(int n)
{
switch (n)
{
case 0: return "Blarg";
case 1: return "Moog";
case 2: return "Pksh";
case 3: return "Tyrn";
case 4: return "Mort";
case 5: return "Hans";
default: return "???";
}
}
std::string_view getRoar(int n)
{
switch (n)
{
case 0: return "*ROAR*";
case 1: return "*peep*";
case 2: return "*squeal*";
case 3: return "*whine*";
case 4: return "*growl*";
case 5: return "*burp*";
default: return "???";
}
}
Monster generate()
{
return Monster{
static_cast<Monster::Type>(Random::get(0, Monster::maxMonsterTypes-1)),
getName(Random::get(0,5)),
getRoar(Random::get(0,5)),
Random::get(1, 100)
};
}
};
int main()
{
Monster m{ MonsterGenerator::generate() };
m.print();
return 0;
}