Урок №56. явное преобразование типов данных
Содержание:
Статические vs Обычные методы
Чем же отличаются статические методы от обычных?
Обычный метод имеет привязку к объекту — экземпляру класса, статический же метод такой привязки не имеет. Обычный метод может обращаться к переменным в своем экземпляре класса, статический — нет: у него просто нет никакого экземпляра класса, связанного с ним.
Отличия двух типов методов можно выразить в таблице:
Способность | Обычный метод | Статический метод |
---|---|---|
Есть связь с экземпляром класса | Да | Нет |
Может вызывать обычные методы класса | Да | Нет |
Может вызывать статические методы класса | Да | Да |
Может обращаться к обычным переменным класса | Да | Нет |
Может обращаться к статическим переменным класса | Да | Да |
Может быть вызван у объекта | Да | Да |
Может быть вызван у класса | Нет | Да |
Зачем такие методы нужны, если они так сильно ограничены? Все дело в том, что у такого подхода тоже есть свои преимущества.
Во-первых, чтобы обратиться к статическим методам и переменным, не надо передавать никакую ссылку на объект.
Во-вторых, иногда бывает нужно, чтобы переменная была в единственном экземпляре. Как, например, переменная (статическая переменная out класса System).
И, в-третьих, иногда нужно вызвать метод еще до того, как будет возможность создавать какие-то объекты. Например, вызов метода main(), с которого начинается выполнение программы: его вызывает Java-машина до создания экземпляра класса.
Есть связь с экземпляром класса
При вызове обычного метода в него передается скрытый параметр — объект, у которого его вызывали. Этот параметр имеет имя . Именно этот скрытый параметр — ссылка на объект, у которого вызвали метод — и отличает обычные методы от статических.
У статических методов такого скрытого параметра нет, поэтому внутри статических методов нельзя пользоваться ключевым словом , и из статического метода нельзя вызвать нестатический: ссылку на экземпляр класса попросту неоткуда взять.
Может вызывать обычные методы класса
В обычном методе класса всегда есть скрытый параметр — — ссылка на объект класса, у которого был вызван метод. Каждый раз, когда вы вызываете обычный метод внутри другого обычного метода, для этого вызова используется скрытый параметр . Пример
Код | Как оно работает |
---|---|
Именно поэтому нельзя вызвать обычный метод из статического. Внутри статического метода просто нет скрытой переменной с именем .
Ну или представьте другой случай: в программе еще не создан ни один объект нашего класса. Статический метод класса можно вызвать? Да. А сможет этот статический метод вызвать обычный метод?
И у какого объекта он его вызовет? Ведь еще не существует ни одного экземпляра нашего класса!
Может вызывать статические методы класса
Статические методы можно вызывать откуда угодно — из любого места программы. А значит, их можно вызывать и из статических методов, и из обычных. Никаких ограничений тут нет.
Может обращаться к обычным переменным класса
Из обычного метода можно обращаться к обычным переменным класса, т.к. при этом произойдет обращение к переменным экземпляра класса, который легко получить из скрытого параметра .
Статический метод не знает, из какого экземпляра класса ему брать значения обычных переменных. У нас вообще легко может быть ситуация, когда статический метод вызван, а ни одного экземпляра класса еще создано в программе не было.
Поэтому статические методы не могут обращаться к обычным переменным класса.
Статический метод вызывает обычный метод, вот только у какого объекта он должен вызваться?
Неизвестно! Поэтому и нельзя вызывать обычный метод из статического, не указывая ссылку на объект!
Может обращаться к статическим переменным класса
Ситуация с обращением к статическим переменным такая же, как и с обращениями к статическим методам. К статическим переменным можно обращаться из любого места в программе. А значит, можно обращаться из статических и обычных методов.
Может быть вызван у объекта
И статические, и обычные методы можно вызывать у объекта. Обычный метод можно, потому что только у объекта его вызвать и можно. Статический метод тоже можно вызывать у объекта: при этом компилятор сам определит тип переменной и вызовет статический метод по ее типу:
Код | Как его видит компилятор |
---|---|
Может быть вызван у класса
У класса можно вызвать только статический метод, для вызова обычного метода нужна ссылка на экземпляр класса. Поэтому нельзя вызвать обычный метод конструкцией вида
C++ не поддерживает статические конструкторы
Если вы можете инициализировать обычную переменную-член через конструктор, то по логике вещей вы должны иметь возможность инициализировать статические переменные-члены через статический конструктор. И, хотя некоторые современные языки программирования действительно поддерживают использование статических конструкторов именно для этой цели, язык C++, к сожалению, не является одним из таковых.
Если ваша статическая переменная может быть инициализирована напрямую, то конструктор не нужен: вы можете определить статическую переменную-член, даже если она является private. Мы делали это в вышеприведенном примере с . Вот еще один пример:
class Something
{
public:
static std::vector<char> s_mychars;
};
std::vector<char> Something::s_mychars = { ‘o’, ‘a’, ‘u’, ‘i’, ‘e’ }; // определяем статическую переменную-член
1 |
classSomething { public staticstd::vector<char>s_mychars; }; std::vector<char>Something::s_mychars={‘o’,’a’,’u’,’i’,’e’};// определяем статическую переменную-член |
Если для инициализации вашей статической переменной-члена требуется выполнить код (например, цикл), то есть несколько разных способов это сделать. Следующий способ является лучшим из них:
#include <iostream>
#include <vector>
class Something
{
private:
static std::vector<char> s_mychars;
public:
class _nested // определяем вложенный класс с именем _nested
{
public:
_nested() // конструктор _nested() инициализирует нашу статическую переменную-член
{
s_mychars.push_back(‘o’);
s_mychars.push_back(‘a’);
s_mychars.push_back(‘u’);
s_mychars.push_back(‘i’);
s_mychars.push_back(‘e’);
}
};
// Статический метод для вывода s_mychars
static void getSomething() {
for (auto const &element : s_mychars)
std::cout << element << ‘ ‘;
}
private:
static _nested s_initializer; // используем статический объект класса _nested для гарантии того, что конструктор _nested() выполнится
};
std::vector<char> Something::s_mychars; // определяем нашу статическую переменную-член
Something::_nested Something::s_initializer; // определяем наш статический s_initializer, который вызовет конструктор _nested() для инициализации s_mychars
int main() {
Something::getSomething();
return 0;
}
1 |
#include <iostream> classSomething { private staticstd::vector<char>s_mychars; public class_nested// определяем вложенный класс с именем _nested { public _nested()// конструктор _nested() инициализирует нашу статическую переменную-член { s_mychars.push_back(‘o’); s_mychars.push_back(‘a’); s_mychars.push_back(‘u’); s_mychars.push_back(‘i’); s_mychars.push_back(‘e’); } }; // Статический метод для вывода s_mychars staticvoidgetSomething(){ for(auto const&element s_mychars) std::cout<<element<<‘ ‘; } private static_nested s_initializer;// используем статический объект класса _nested для гарантии того, что конструктор _nested() выполнится }; std::vector<char>Something::s_mychars;// определяем нашу статическую переменную-член Something::_nested Something::s_initializer;// определяем наш статический s_initializer, который вызовет конструктор _nested() для инициализации s_mychars intmain(){ Something::getSomething(); return; } |
Результат выполнения программы:
При определении статического члена вызовется конструктор по умолчанию _nested() (так как является объектом класса _nested). Мы можем использовать этот конструктор для инициализации любых статических переменных-членов класса Something. Самое крутое здесь — это то, что весь код инициализации скрыт внутри исходного класса со статическим членом.
Классы памяти переменных
По умолчанию, локальные переменные имеют класс auto. Такие переменные располагаются на стеке а их область видимости ограничена своим блоком. Запись
#include <conio.h> #include <stdio.h> void main() { int x = 10; { int x = 20; { int x = 30; printf(«%d\n», x); } printf(«%d\n», x); } printf(«%d\n», x); getch(); }
идентична
#include <conio.h> #include <stdio.h> void main() { int auto x = 10; { int auto x = 20; { int auto x = 30; printf(«%d\n», x); } printf(«%d\n», x); } printf(«%d\n», x); getch(); }
Очевидно, что глобальные переменные не могут быть объявлены как auto, потому что располагаются в data-сегменте.
Следующий класс памяти – register. Когда мы определяем регистровую переменную, то мы просим компилятор, чтобы переменная располагалась в регистре, а не в оперативной памяти. Компилятор может сделать переменную регистровой, если позволяют условия (регистры не заняты, и по мнению компилятора это не приведёт к увеличению издержек). Регистровые переменные определяются с помощью служебного слово register перед типом
register int x = 20; register int y = 30;
Так как регистровая переменная не имеет адреса, то к ней не применима операция взятия адреса, это вызовет ошибку во время компиляции. Аргументы функции также могут быть заданы как register. Внутри функции они будут вести себя также, как и регистровые переменные.
Следующий класс памяти – статический. Переменные, объявленные как static, хранятся в data или в bss сегменте. Отличительной чертой является то, что время их жизни совпадает с временем жизни приложения, как и у глобальных переменных. Но в отличие от глобальных переменных, область видимости ограничена только блоком, в котором они определены.
#include <conio.h> #include <stdio.h> unsigned long long factorial(unsigned char n) { static unsigned char prevArg = 0; static long long prevAns = 1; if (n == prevArg) { printf(«return previous answer\n»); return prevAns; } else { unsigned i = 0; printf(«count new answer\n»); prevAns = 1; for (i = 1; i <= n; i++) { prevAns *= i; } prevArg = n; return prevAns; } } void main() { printf(«!%d == %llu\n», 10, factorial(10)); printf(«!%d == %llu\n», 10, factorial(10)); printf(«!%d == %llu\n», 11, factorial(11)); printf(«!%d == %llu\n», 11, factorial(11)); getch(); }
В этом примере переменные prevArg и prevAns инициализируются единожды, и не уничтожаются после выхода из функции. Переменная prevArg используется для хранения предыдущего аргумента функции, а prevAns для хранения предыдущего результата. Если аргумента функции совпадает с предыдущим, то возвращается ранее вычисленное значение, иначе оно вычисляется по-новому.
Другой показательный пример – функция-генератор, которая при каждом вызове возвращает новое значение.
#include <conio.h> #include <stdio.h> int next() { static int counter = 0; counter++; return counter; } void main() { printf(«%d\n», next()); printf(«%d\n», next()); printf(«%d\n», next()); printf(«%d\n», next()); printf(«%d\n», next()); _getch(); }
Если бы служебное слово static отсутствовало, то каждый раз при вызове функции локальная переменная counter снова создавалась, инициализировалась и уничтожалась после выхода из функции.
Статическая переменная может иметь только константную инициализацию. Например, она не может быть инициализирована вызовом функции.
… static double x = foo(3); //Ошибка …
Переменная, объявленная как static, должна иметь только один экземпляр в данной области видимости и вне этой области видимости не видна. Глобальная переменная, объявленная как static, видна только в своём файле.
Напротив, переменная, объявленная как extern может быть использована в других файлах при условии, что она была определена.
Статические атрибуты
Переменные — члены класса можно объявлять как статические (static).
Используя статические переменные-члены, можнорешить несколько непростых проблем.
Если вы объявляете переменную статической, то может существовать только одна копия этой переменной — независимо от того,сколько объектов данного класса создается. Каждый объект просто использует (совместно с другими) эту одну переменную. Для обычных переменных-членов при создании каждого объекта создается их новая копия, и доступ к каждой копии возможен только через этот объект. Для обычных переменных каждый объект обладает собственными копиями переменных. А копия статической переменной — только одна, и все объекты класса используют её совместно.
Кроме этого, одна и та же статическая переменная будет использоваться всеми классами, производными от класса, в котором эта статическая переменная содержится.
При объявлении статического атрибута необходимо обеспечить для него глобальное определение вне класса. Это необходимо для того, чтобы под статический атрибут была выделена память, причём только один раз. Это делается путем нового объявления статической переменной, причем используется оператор области видимости для того, чтобы идентифицировать тот класс, к которому принадлежит переменная.
Основной смысл поддержки в C++ статических переменных-членов состоит в том, что благодаря им отпадает необходимость в использовании глобальных переменных.
Пример подсчёта созданных экземпляров
class A{ static int counter; int my_number;public: A() { my_number = counter; counter++; } int get_number() const { return my_number; }};int A::counter = 0;int static_counter_of_instances(){ A a, b, c, d, e; A x; cout << «a number » << a.get_number() << endl; cout << «b number » << b.get_number() << endl; cout << «c number » << c.get_number() << endl; cout << «d number » << d.get_number() << endl; cout << «e number » << e.get_number() << endl; for (int i = 0; i < 10; i++) cout << «x number » << x.get_number() << endl; return 0;}
Поскольку статическая переменная — член класса существует ещё до создания объекта этого класса, доступ к ней в программе может быть реализован без всякого объекта:
int main(){ A::counter = 5; …}
Вопрос на засыпку
Почему при доступе к A::counter из main() в последнем случае мы получим ошибку компиляции?
Для доступа к такой переменной можно передвинуть её в зону public, но лучше сделать публичным статический метод для доступа к ней.
Static Variables inside Functions
Static variables when used inside function are initialized only once, and then they hold there value even through function calls.
These static variables are stored on static storage area , not in stack.
0 1 2 3 4
Let’s se the same program’s output without using static variable.
0 0 0 0 0
If we do not use keyword, the variable count, is reinitialized everytime when function is called, and gets destroyed each time when functions ends. But, if we make it static, once initialized count will have a scope till the end of function and it will carry its value through function calls too.
If you don’t initialize a static variable, they are by default initialized to zero.
CStatic::SetBitmap
Associates a new bitmap with the static control.
Return Value
The handle of the bitmap that was previously associated with the static control, or if no bitmap was associated with the static control.
Remarks
The bitmap will be automatically drawn in the static control. By default, it will be drawn in the upper-left corner and the static control will be resized to the size of the bitmap.
You can use various window and static control styles, including these:
-
Use this style always for bitmaps.
-
Use to center the image in the static control. If the image is larger than the static control, it will be clipped. If it is smaller than the static control, the empty space around the image will be filled by the color of the pixel in the upper left corner of the bitmap.
-
MFC provides the class , which you can use when you have to do more with a bitmap image than just call the Win32 function . , which contains one kind of GDI object, is often used in cooperation with , which is a class that is used for displaying a graphic object as a static control.
is an ATL/MFC class that lets you more easily work with device independent bitmaps (DIB). For more information, see Class.
Typical usage is to give CStatic::SetBitmap a GDI object that is returned by the HBITMAP operator of a CBitmap or CImage object. The code to do this resembles the following line.
The following example creates two objects on the heap. It then loads one with a system bitmap using and the other from a file using .
Такие разные реализации
Реализаций интерфейсов так много, что при желании можно организовать вполне себе упорядоченный Map и даже отсортированное множество. Пройдёмся кратко по основным классам.
Реализации List
Класс ArrayList подойдёт в большинстве случаев, если вы уже определились, что вам нужен именно список (а не Map, например).
Строится на базе обычного массива. Если при создании не указать размерность, то под значения выделяется 10 ячеек. При попытке добавить элемент, для которого места уже нет, массив автоматически расширяется — программисту об этом специально заботиться не нужно.
Список проиндексирован. При включении нового элемента в его середину все элементы с большим индексом сдвигаются вправо:
При удалении элемента все остальные с бо́льшим индексом сдвигаются влево:
Класс LinkedList реализует одновременно List и Deque. Это список, в котором у каждого элемента есть ссылка на предыдущий и следующий элементы:
Благодаря этому добавление и удаление элементов выполняется быстро — времязатраты не зависят от размера списка, так как элементы при этих операциях не сдвигаются: просто перестраиваются ссылки.
На собеседованиях часто спрашивают, когда выгоднее использовать LinkedList, а когда — ArrayList.
Правильный ответ таков: если добавлять и удалять элементы с произвольными индексами в списке нужно чаще, чем итерироваться по нему, то лучше LinkedList. В остальных случаях — ArrayList.
В целом так и есть, но вы можете блеснуть эрудицией — рассказать, что под капотом. При добавлении элементов в ArrayList (или их удалении) вызывается нативный метод System.arraycopy. В нём используются ассемблерные инструкции для копирования блоков памяти. Так что даже для больших массивов эти операции выполняются за приемлемое время.
Static Class Objects
Static keyword works in the same way for class objects too. Objects declared static are allocated storage in static storage area, and have scope till the end of program.
Static objects are also initialized using constructors like other normal objects. Assignment to zero, on using static keyword is only for primitive datatypes, not for user defined datatypes.
constructor END destructor
You must be thinking, why was the destructor not called upon the end of the scope of condition, where the reference of object should get destroyed. This is because object was , which has scope till the program’s lifetime, hence destructor for this object was called when function exits.
Перечисление
- Перечисление – это список именованных констант.
- Это похоже на конечные переменные.
- Перечисление является типом данных, который содержит фиксированный набор констант.
- Перечисление определяет тип класса. Делая перечисления как класс, он может иметь конструкторы, методы и переменные экземпляра.
- Перечисление создается с использованием ключевого слова enum.
Пример 1
enum Apple { Jonathan, GoldenDel, RedDel, Winesap, Cortland; } class EnumDemo { public static void main(String args[]) { Apple ap; ap = Apple.RedDel; System.out.println("Value of ap: " + ap);// Value of ap: RedDel ap = Apple.GoldenDel; if(ap == Apple.GoldenDel) System.out.println("ap contains GoldenDel.n"); // ap contains GoldenDel. switch(ap){ case Jonathan: System.out.println("Jonathan is red."); break; case GoldenDel: System.out.println("Golden Delicious is yellow."); // Golden Delicious is yellow break; case RedDel: System.out.println("Red Delicious is red."); break; case Winesap: System.out.println("Winesap is red."); break; case Cortland: System.out.println("Cortland is red."); break; } } }
В этом примере мы использовали перечисление как enum Apple {Jonathan, GoldenDel, RedDel, Winesap, Cortland}. Идентификаторы Jonathan, GoldenDel, RedDel, Winesap и Cortland называются константами перечисления.
Каждый из них неявно объявлен как публичный статический финальный член Apple. Переменная перечисления может быть создана как другая примитивная переменная. Он не использует «новый» для создания объекта.
Apple ap;
‘ap’ имеет тип Apple, единственные значения, которые могут быть назначены (или могут содержать), являются значениями, определенными перечислением.
Например, это назначает
ap = Apple.RedDel;
Все перечисления имеют два предопределенных метода: values() и valueOf(). Синтаксис этих встроенных методов:
public static enum-type [] .values() public static enum-type.valueOf (String str)
Метод values() дает массив, который состоит из списка констант перечисления. Метод valueOf() дает константу перечисления, значение которой соответствует строке, переданной в str.
Пример 2
enum Season { WINTER, SPRING, SUMMER, FALL; } class EnumExample { public static void main(String[] args) { for (Season s : Season.values()) System.out.println(s);//will display all the enum constants of Season Season s = Season.valueOf("WINTER"); System.out.println("S contains " + s);//output: S contains WINTER } }
Вывод:
В приведенном выше примере мы использовали два встроенных метода перечисления.
База для getter-ов
Итак, у нас уже есть возможность выбрать базу, которая содержит указатель и определяет поведение «умного указателя». Теперь нужно снабдить эту базу методами-getter-ами. Для чего нам потребуется один простой класс:
Это шаблонный класс, который зависит от двух параметров, но их смысл уже совсем другой. В качестве параметра Base будет выступать результат показанной выше метафункции . Т.е. в качестве параметра Base задается базовый класс, от которого нужно отнаследоваться.
Важно отметить, что если наследование происходит от , у которого конструктор и оператор копирования запрещены, то компилятор не сможет сгенерировать конструктор и оператор копирования для. Что нам и требуется
В качестве параметра Return_Type будет выступать тип сообщения, указатель/ссылку на который будет возвращаться getter-ами. Фокус в том, что для иммутабельного сообщения типа параметр Return_Type будет иметь значение . Тогда как для мутабельного сообщения типа параметр Return_Type будет иметь значение . Таким образом метод для иммутабельных сообщений будет возвращать , а для мутабельных — просто .
Посредством свободной функции решается проблема работы с сообщениями, которые не отнаследованны от :
Т.е. если сообщение не наследуется от и хранится как , то вызывается вторая перегрузка. А если наследуется, то первая перегрузка.
Выбор конкретной базы для getter-ов
Итак, шаблон требует два параметра. Первый вычисляется метафункцией . Но для того, чтобы сформировать конкретный базовый тип из , нам нужно определиться со значением второго параметра. Для этого предназначена еще одна метафункция:
Обратить внимание можно разве что на вычисление параметра Return_Type. Один из тех немногих случаев, когда east const оказывается полезен ;). Ну и, для повышения читабельности последующего кода, более компактный вариант для работы с ней:
Ну и, для повышения читабельности последующего кода, более компактный вариант для работы с ней: