Ссылки, методы и классы
Ссылки (&)
Заголовок раздела «Ссылки (&)»Ссылка — это по сути указатель, адрес которого нельзя изменить, но с упрощенным синтаксисом.
Можно думать о ней и как о псевдониме переменной (участка памяти).
🤓 описание ссылки как «указателя, который нельзя переприсвоить» — деталь реализации. Стандарт технически не требует от ссылок никакого конкретного представления: формально это просто псевдоним уже существующего объекта, который нельзя переприсвоить и который не может ни на что не указывать. На практике же каждый современный компилятор на любом современном железе реализует ссылки именно как указатели.
int main(){ int a = 5;
int* pointer = &a; int& reference = a;
*pointer = 10; // a = 10 reference = 15; // a = 15
int b = 20; // Так нельзя сделать, чтобы `reference` ссылалась на `b`. // Следующая строка запишет значение `b` (20) в `a`. reference = b; // a = 20 reference = 10; // a = 10
return 0;}Ссылки можно передавать в функции. Под капотом они передаются как указатели.
void foo(int& a){ a = 10; a = a + 5;}
int main(){ int a = 5; foo(a); // a = 15 return 0;}Та же программа, но с использованием указателя:
void foo(int* a){ *a = 10; *a = *a + 5;}
int main(){ int a = 5; foo(&a); // a = 15 return 0;}К ссылке можно применить & и получить указатель на переменную.
Ссылка — более ограниченная версия указателя, идеальная для аргументов функций. Считается хорошей практикой использовать ссылки вместо указателей при передаче аргументов в функции.
Рассмотрим функцию, принимающую переменную-структуру.
Не беспокойтесь пока о том, как устроен
std::string, просто считайте его концептуально строкой.
#include <iostream>
struct Person{ int age; std::string name;};
void printAge(Person* person){ std::cout << person->age;}
int main(){ Person person; person.age = 20; person.name = "John";
printAge(&person);
return 0;}static-методы
Заголовок раздела «static-методы»Мы можем превратить эту функцию в функцию-член, также называемую методом. Сначала сделаем её статической функцией-членом, чтобы увидеть, как работает квалификация имени через область видимости класса.
Заметьте, методы существуют только в C++, но не в C.
Член (member) — это то, что объявлено в
structилиclass. Это может быть как метод, так и поле.
#include <iostream>
struct Person{ int age; std::string name;
// `static` делает функцию *статической функцией-членом*, // которую обычно вызывают через оператор разрешения области видимости (::). static void printAge(Person* person) { std::cout << person->age; }};
int main(){ Person person; person.age = 20; person.name = "John";
// Так вызывается статическая функция-член. // `Person::printAge` — квалифицированное имя: `printAge` ищется // в области видимости класса `Person`. // Идея в том, что мы можем связать операции, работающие с `Person`, с самим типом. Person::printAge(&person);
return 0;}Объявление можно отделить от определения, как и с обычными функциями. Заметьте, другого синтаксиса для этого нет: в определении нужно написать имя структуры.
struct Person{ int age; std::string name;
static void printAge(Person* person);};
// Считайте `Person::printAge` именем этой функции.void Person::printAge(Person* person){ std::cout << person->age;}Экземпляры и объекты
Заголовок раздела «Экземпляры и объекты»Объект — это некоторая память — переменная, элемент массива или любое другое хранилище, — которая содержит данные некоторого типа. Так, если бы у вас была структура Person и код вида:
Person person;можно сказать, что переменная person ссылается на объект типа Person. Иными словами, объект — это значение, живущее в какой-то памяти, и сама эта память.
Экземпляр означает практически то же, что объект, но смещает фокус с памяти на тип данных, хранящихся в этой памяти. Так что можно сказать, что person — экземпляр Person.
Нестатические (instance) методы
Заголовок раздела «Нестатические (instance) методы»Теперь сделаем функцию нестатической.
У нестатической функции-члена более простой синтаксис вызова,
и она автоматически передает указатель на экземпляр первым скрытым параметром с именем this.
struct Person{ int age; std::string name;
// Метод экземпляра. void printAge() { // `this` — указатель на экземпляр (`Person*`). std::cout << this->age; }};
int main(){ Person person; person.age = 20; person.name = "John";
// Так вызывается метод экземпляра. // Компилятор автоматически передает указатель на экземпляр первым параметром. person.printAge();
return 0;}Это позволяет нам сочетать функциональность с типом данных и получить семантику «объект.действие».
this-> писать необязательно.
struct Person{ // ... void printAge() { // `age` ссылается на `this->age`. std::cout << age; }};Определение можно отделить от объявления так же, как вы делали это со статическими методами.
Доступность и class
Заголовок раздела «Доступность и class»По умолчанию все члены структуры public.
Это значит, к ним можно обращаться через экземпляр структуры.
Можно явно сделать их private, запретив доступ.
Поначалу это может показаться совершенно бесполезным. Ведь в чем смысл запрещать доступ к какой-то памяти? Одна из идей — ограничить способы доступа к данным и их изменения, чтобы с помощью методов стало очевидно, какой способ правильный.
Это не уникально для ООП: то же можно сделать на уровне модуля, объявив некоторые функции static
(видимыми только внутри единицы трансляции, где они определены).
А вот модификаторы доступности — вещь, характерная именно для ООП.
struct Person{private: int age; std::string name;
public: void setAge(int age) { this->age = age; }
void printAge() { std::cout << this->age; }
void printName() { std::cout << this->name; }};
int main(){ Person person; person.age = 15; // ошибка времени компиляции: нельзя обратиться к закрытому полю. person.name = "John"; // та же ошибка
person.setAge(15); // компилируется person.printAge(); // компилируется person.printName(); // компилируется
return 0;}Одни поля можно сделать private, а другие — public.
struct Person{private: int age;
public: std::string name;};
int main(){ Person person; person.age = 10; // ошибка времени компиляции: нельзя обратиться к закрытому полю. person.name = "John"; // работает return 0;}class эквивалентен struct; единственная разница
в том, что в его начале подразумевается private:.
То есть все члены по умолчанию публичны в struct, но приватны в class.
class— ключевое слово, которое обычно используется в контексте ООП. Хорошим тоном считается использоватьclassвместоstruct, если вы объявляете методы, которые инкапсулируют данные (поля), предоставляя к ним четко определенные схемы доступа.
struct Person{private: int age; std::string name;};
// то же самое, что
class Person{ int age; std::string name;};
// и наоборот ...
class Person{public: int age; std::string name;};
// то же самое, что
struct Person{ int age; std::string name;};