Указатели, ссылки и массивы в C и C++: точки над i
В этом посте я постараюсь окончательно разобрать такие тонкие понятия в C и C++, как указатели, ссылки и массивы. В частности, я отвечу на вопрос, так являются массивы C указателями или нет.
Обозначения и предположения
- Я буду предполагать, что читатель понимает, что, например, в C++ есть ссылки, а в C — нет, поэтому я не буду постоянно напоминать, о каком именно языке (C/C++ или именно C++) я сейчас говорю, читатель поймёт это из контекста;
- Также, я предполагаю, что читатель уже знает C и C++ на базовом уровне и знает, к примеру, синтаксис объявления ссылки. В этом посте я буду заниматься именно дотошным разбором мелочей;
- Буду обозначать типы так, как выглядело бы объявление переменной TYPE соответствующего типа. Например, тип «массив длины 2 int’ов» я буду обозначать как int TYPE[2] ;
- Я буду предполагать, что мы в основном имеем дело с обычными типами данных, такими как int TYPE , int *TYPE и т. д., для которых операции =, &, * и другие не переопределены и обозначают обычные вещи;
- «Объект» всегда будет означать «всё, что не ссылка», а не «экземпляр класса»;
- Везде, за исключением специально оговоренных случаев, подразумеваются C89 и C++98.
Указатели и ссылки
Указатели. Что такое указатели, я рассказывать не буду. 🙂 Будем считать, что вы это знаете. Напомню лишь следующие вещи (все примеры кода предполагаются находящимися внутри какой-нибудь функции, например, main):
int x; int *y = &x; // От любой переменной можно взять адрес при помощи операции взятия адреса "&". Эта операция возвращает указатель int z = *y; // Указатель можно разыменовать при помощи операции разыменовывания "*". Это операция возвращает тот объект, на который указывает указатель
Также напомню следующее: char — это всегда ровно один байт и во всех стандартах C и C++ sizeof (char) == 1 (но при этом стандарты не гарантируют, что в байте содержится именно 8 бит :)). Далее, если прибавить к указателю на какой-нибудь тип T число, то реальное численное значение этого указателя увеличится на это число, умноженное на sizeof (T) . Т. е. если p имеет тип T *TYPE , то p + 3 эквивалентно (T *)((char *)p + 3 * sizeof (T)) . Аналогичные соображения относятся и к вычитанию.
Ссылки. Теперь по поводу ссылок. Ссылки — это то же самое, что и указатели, но с другим синтаксисом и некоторыми другими важными отличиями, о которых речь пойдёт дальше. Следующий код ничем не отличается от предыдущего, за исключением того, что в нём фигурируют ссылки вместо указателей:
int x; int &y = x; int z = y;
Если слева от знака присваивания стоит ссылка, то нет никакого способа понять, хотим мы присвоить самой ссылке или объекту, на который она ссылается. Поэтому такое присваивание всегда присваивает объекту, а не ссылке. Но это не относится к инициализации ссылки: инициализируется, разумеется, сама ссылка. Поэтому после инициализации ссылки нет никакого способа изменить её саму, т. е. ссылка всегда постоянна (но не её объект).
Lvalue. Те выражения, которым можно присваивать, называются lvalue в C, C++ и многих других языках (это сокращение от «left value», т. е. слева от знака равенства). Остальные выражения называются rvalue. Имена переменных очевидным образом являются lvalue, но не только они. Выражения a[i + 2] , some_struct.some_field , *ptr , *(ptr + 3) — тоже lvalue.
Удивительный факт состоит в том, что ссылки и lvalue — это в каком-то смысле одно и то же. Давайте порассуждаем. Что такое lvalue? Это нечто, чему можно присвоить. Т. е. это некое фиксированное место в памяти, куда можно что-то положить. Т. е. адрес. Т. е. указатель или ссылка (как мы уже знаем, указатели и ссылки — это два синтаксически разных способа в C++ выразить понятие адреса). Причём скорее ссылка, чем указатель, т. к. ссылку можно поместить слева от знака равенства и это будет означать присваивание объекту, на который указывает ссылка. Значит, lvalue — это ссылка.
А что такое ссылка? Это один из синтаксисов для адреса, т. е., опять-таки, чего-то, куда можно класть. И ссылку можно ставить слева от знака равенства. Значит, ссылка — это lvalue.
Окей, но ведь (почти любая) переменная тоже может быть слева от знака равенства. Значит, (такая) переменная — ссылка? Почти. Выражение, представляющее собой переменную — ссылка.
Иными словами, допустим, мы объявили int x . Теперь x — это переменная типа int TYPE и никакого другого. Это int и всё тут. Но если я теперь пишу x + 2 или x = 3 , то в этих выражениях подвыражение x имеет тип int &TYPE . Потому что иначе этот x ничем не отличался бы от, скажем, 10, и ему (как и десятке) нельзя было бы ничего присвоить.
Этот принцип («выражение, являющееся переменной — ссылка») — моя выдумка. Т. е. ни в каком учебнике, стандарте и т. д. я этот принцип не видел. Тем не менее, он многое упрощает и его удобно считать верным. Если бы я реализовывал компилятор, я бы просто считал там переменные в выражениях ссылками, и, вполне возможно, именно так и предполагается в реальных компиляторах.
Более того, удобно считать, что особый тип данных для lvalue (т. е. ссылка) существует даже и в C. Именно так мы и будет дальше предполагать. Просто понятие ссылки нельзя выразить синтаксически в C, ссылку нельзя объявить.
Принцип «любое lvalue — ссылка» — тоже моя выдумка. А вот принцип «любая ссылка — lvalue» — вполне законный, общепризнанный принцип (разумеется, ссылка должна быть ссылкой на изменяемый объект, и этот объект должен допускать присваивание).
Теперь, с учётом наших соглашений, сформулируем строго правила работы со ссылками: если объявлено, скажем, int x , то теперь выражение x имеет тип int &TYPE . Если теперь это выражение (или любое другое выражение типа ссылка) стоит слева от знака равенства, то оно используется именно как ссылка, практически во всех остальных случаях (например, в ситуации x + 2 ) x автоматически конвертируется в тип int TYPE (ещё одной операцией, рядом с которой ссылка не конвертируется в свой объект, является &, как мы увидим далее). Слева от знака равенства может стоять только ссылка. Инициализировать (неконстантную) ссылку может только ссылка.
Операции * и &. Наши соглашения позволяют по-новому взглянуть на операции * и &. Теперь становится понятно следующее: операция * может применяться только к указателю (конкретно это было всегда известно) и она возвращает ссылку на тот же тип. & применяется всегда к ссылке и возвращает указатель того же типа. Таким образом, * и & превращают указатели и ссылки друг в друга. Т. е. по сути они вообще ничего не делают и лишь заменяют сущности одного синтаксиса на сущности другого! Таким образом, & вообще-то не совсем правильно называть операцией взятия адреса: она может быть применена лишь к уже существующему адресу, просто она меняет синтаксическое воплощение этого адреса.
Замечу, что указатели и ссылки объявляются как int *x и int &x . Таким образом, принцип «объявление подсказывает использование» лишний раз подтверждается: объявление указателя напоминает, как превратить его в ссылку, а объявление ссылки — наоборот.
Также замечу, что &*EXPR (здесь EXPR — это произвольное выражение, не обязательно один идентификатор) эквивалентно EXPR всегда, когда имеет смысл (т. е. всегда, когда EXPR — указатель), а *&EXPR тоже эквивалентно EXPR всегда, когда имеет смысл (т. е. когда EXPR — ссылка).
Массивы
Итак, есть такой тип данных — массив. Определяются массивы, например, так:
int x[5];
Выражение в квадратных скобках должно быть непременно константой времени компиляции в C89 и C++98. При этом в квадратных скобках должно стоять число, пустые квадратные скобки не допускаются.
Подобно тому, как все локальные переменные (напомню, мы предполагаем, что все примеры кода находятся внутри функций) находятся на стеке, массивы тоже находятся на стеке. Т. е. приведённый код привёл к выделению прямо на стеке огромного блока памяти размером 5 * sizeof (int) , в котором целиком размещается наш массив. Не нужно думать, что этот код объявил некий указатель, который указывает на память, размещённую где-то там далеко, в куче. Нет, мы объявили массив, самый настоящий. Здесь, на стеке.
Чему будет равно sizeof (x) ? Разумеется, оно будет равно размеру нашего массива, т. е. 5 * sizeof (int) . Если мы пишем
struct foo < int a[5]; int b; >;
то, опять-таки, место для массива будет целиком выделяться прямо внутри структуры, и sizeof от этой структуры будет это подтверждать.
От массива можно взять адрес ( &x ), и это будет самый настоящий указатель на то место, где этот массив расположен. Тип у выражения &x , как легко понять, будет int (*TYPE)[5] . В начале массива размещён его нулевой элемент, поэтому адрес самого массива и адрес его нулевого элемента численно совпадают. Т. е. &x и &(x[0]) численно равны (тут я лихо написал выражение &(x[0]) , на самом деле в нём не всё так просто, к этому мы ещё вернёмся). Но эти выражения имеют разный тип — int (*TYPE)[5] и int *TYPE , поэтому сравнить их при помощи == не получится. Но можно применить трюк с void * : следующее выражение будет истинным: (void *)&x == (void *)&(x[0]) .
Хорошо, будем считать, я вас убедил, что массив — это именно массив, а не что-нибудь ещё. Откуда тогда берётся вся эта путаница между указателями и массивами? Дело в том, что имя массива почти при любых операциях преобразуется в указатель на его нулевой элемент.
Итак, мы объявили int x[5] . Если мы теперь пишем x + 0 , то это преобразует наш x (который имел тип int TYPE[5] , или, более точно, int (&TYPE)[5] ) в &(x[0]) , т. е. в указатель на нулевой элемент массива x. Теперь наш x имеет тип int *TYPE .
Конвертирование имени массива в void * или применение к нему == тоже приводит к предварительному преобразованию этого имени в указатель на первый элемент, поэтому:
&x == x // ошибка компиляции, разные типы: int (*TYPE)[5] и int *TYPE (void *)&x == (void *)x // истина x == x + 0 // истина x == &(x[0]) // истина
- x[2] эквивалентно *(x + 2)
- x + 2 относится к тем операциям, при которых имя массива преобразуется в указатель на его первый элемент, поэтому это происходит
- Далее, в соответствии с моими объяснениями выше, x + 2 эквивалентно (int *)((char *)x + 2 * sizeof (int)) , т. е. x + 2 означает «сдвинуть указатель x на два int’а»
- Наконец, от результата берётся операция разыменования и мы извлекаем тот объект, который размещён по этому сдвинутому указателю
Типы у участвовавших выражений следующие:
x // int (&TYPE)[5], после преобразования типа: int *TYPE x + 2 // int *TYPE *(x + 2) // int &TYPE x[2] // int &TYPE
Также замечу, что слева от квадратных скобок необязательно должен стоять именно массив, там может быть любой указатель. Например, можно написать (x + 2)[3] , и это будет эквивалентно x[5] . Ещё замечу, что *a и a[0] всегда эквивалентны, как в случае, когда a — массив, так и когда a — указатель.
Теперь, как я и обещал, я возвращаюсь к &(x[0]) . Теперь ясно, что в этом выражении сперва x преобразуется в указатель, затем к этому указателю в соответствии с вышеприведённым алгоритмом применяется [0] и в результате получается значение типа int &TYPE , и наконец, при помощи & оно преобразуется к типу int *TYPE . Поэтому, объяснять при помощи этого сложного выражения (внутри которого уже выполняется преобразование массива к указателю) немного более простое понятие преобразования массива к указателю — это был немного мухлёж.
А теперь вопрос на засыпку: что такое &x + 1 ? Что ж, &x — это указатель на весь массив целиком, + 1 приводит к шагу на весь этот массив. Т. е. &x + 1 — это (int (*)[5])((char *)&x + sizeof (int [5])) , т. е. (int (*)[5])((char *)&x + 5 * sizeof (int)) (здесь int (*)[5] — это int (*TYPE)[5] ). Итак, &x + 1 численно равно x + 5 , а не x + 1 , как можно было бы подумать. Да, в результате мы указываем на память, которая находится за пределами массива (сразу после последнего элемента), но кого это волнует? Ведь в C всё равно не проверяется выход за границы массива. Также, заметим, что выражение *(&x + 1) == x + 5 истинно. Ещё его можно записать вот так: (&x)[1] == x + 5 . Также будет истинным *((&x)[1]) == x[5] , или, что тоже самое, (&x)[1][0] == x[5] (если мы, конечно, не схватим segmentation fault за попытку обращения за пределы нашей памяти :)).
Массив нельзя передать как аргумент в функцию. Если вы напишите int x[2] или int x[] в заголовке функции, то это будет эквивалентно int *x и в функцию всегда будет передаваться указатель (sizeof от переданной переменной будет таким, как у указателя). При этом размер массива, указанный в заголовке будет игнорироваться. Вы запросто можете указать в заголовке int x[2] и передать туда массив длины 3.
Однако, в C++ существует способ передать в функцию ссылку на массив:
void f (int (&x)[5]) < // sizeof (x) здесь равен 5 * sizeof (int) >int main (void) < int x[5]; f (x); // OK f (x + 0); // Нельзя int y[7]; f (y); // Нельзя, не тот размер >
При такой передаче вы всё равно передаёте лишь ссылку, а не массив, т. е. массив не копируется. Но всё же вы получаете несколько отличий по сравнению с обычной передачей указателя. Передаётся ссылка на массив. Вместо неё нельзя передать указатель. Нужно передать именно массив указанного размера. Внутри функции ссылка на массив будет вести себя именно как ссылка на массив, например, у неё будет sizeof как у массива.
И что самое интересное, эту передачу можно использовать так:
// Вычисляет длину массива template size_t len (t (&a)[n])
Похожим образом реализована функция std::end в C++11 для массивов.
«Указатель на массив». Строго говоря, «указатель на массив» — это именно указатель на массив и ничто другое. Иными словами:
int (*a)[2]; // Это указатель на массив. Самый настоящий. Он имеет тип int (*TYPE)[2] int b[2]; int *c = b; // Это не указатель на массив. Это просто указатель. Указатель на первый элемент некоего массива int *d = new int[4]; // И это не указатель на массив. Это указатель
Однако, иногда под фразой «указатель на массив» неформально понимают указатель на область памяти, в которой размещён массив, даже если тип у этого указателя неподходящий. В соответствии с таким неформальным пониманием c и d (и b + 0 ) — это указатели на массивы.
Многомерные массивы. Если объявлено int x[5][7] , то x — это не массив длины 5 неких указателей, указывающих куда-то далеко. Нет, x теперь — это единый монолитный блок размером 5 x 7, размещённый на стеке. sizeof (x) равен 5 * 7 * sizeof (int) . Элементы располагаются в памяти так: x[0][0] , x[0][1] , x[0][2] , x[0][3] , x[0][4] , x[0][5] , x[0][6] , x[1][0] и так далее. Когда мы пишем x[0][0] , события развиваются так:
x // int (&TYPE)[5][7], после преобразования: int (*TYPE)[7] x[0] // int (&TYPE)[7], после преобразования: int *TYPE x[0][0] // int &TYPE
То же самое относится к **x . Замечу, что в выражениях, скажем, x[0][0] + 3 и **x + 3 в реальности извлечение из памяти происходит только один раз (несмотря на наличие двух звёздочек), в момент преобразования окончательной ссылки типа int &TYPE просто в int TYPE . Т. е. если бы мы взглянули на ассемблерный код, который генерируется из выражения **x + 3 , мы бы в нём увидели, что операция извлечения данных из памяти выполняется там только один раз. **x + 3 можно ещё по-другому записать как *(int *)x + 3 .
А теперь посмотрим на такую ситуацию:
int **y = new int *[5]; for (int i = 0; i != 5; ++i)
Что теперь есть y? y — это указатель на массив (в неформальном смысле!) указателей на массивы (опять-таки, в неформальном смысле). Нигде здесь не появляется единый блок размера 5 x 7, есть 5 блоков размера 7 * sizeof (int) , которые могут находиться далеко друг от друга. Что есть y[0][0] ?
y // int **&TYPE y[0] // int *&TYPE y[0][0] // int &TYPE
Теперь, когда мы пишем y[0][0] + 3 , извлечение из памяти происходит два раза: извлечение из массива y и последующее извлечение из массива y[0] , который может находиться далеко от массива y. Причина этого в том, что здесь не происходит преобразования имени массива в указатель на его первый элемент, в отличие от примера с многомерным массивом x. Поэтому **y + 3 здесь не эквивалентен *(int *)y + 3 .
Объясню ещё разок. x[2][3] эквивалентно *(*(x + 2) + 3) . И y[2][3] эквивалентно *(*(y + 2) + 3) . Но в первом случае наша задача найти «третий элемент во втором ряду» в едином блоке размера 5 x 7 (разумеется, элементы нумеруются с нуля, поэтому этот третий элемент будет в некотором смысле четвёртым :)). Компилятор вычисляет, что на самом деле нужный элемент находится на 2 * 7 + 3 -м месте в этом блоке и извлекает его. Т. е. x[2][3] здесь эквивалентно ((int *)x)[2 * 7 + 3] , или, что то же самое, *((int *)x + 2 * 7 + 3) . Во втором случае сперва извлекает 2-й элемент в массиве y, а затем 3-й элемент в полученном массиве.
В первом случае, когда мы делаем x + 2 , мы сдвигаемся сразу на 2 * sizeof (int [7]) , т. е. на 2 * 7 * sizeof (int) . Во втором случае, y + 2 — это сдвиг на 2 * sizeof (int *) .
В первом случае (void *)x и (void *)*x (и (void *)&x !) — это один и тот же указатель, во втором — это не так.
Objective-C как первый язык программирования
Данный пост был задуман после того, как на некоторые важные вопросы не было найдено внятных ответов. Я отнюдь не претендую на то, что стал крутым программистом. Нет, всё ещё впереди, но период высиживания уже пройден. Это статья из цикла «Не умеешь сам — научи другого». В смысле, чтобы что-то лучше понять нужно это что-то, кому-то объяснить. Мопед не мой, эта фраза встречалась мной раньше в публикациях на Хабре. Некоторые вещи очень сложно понять. И люди, которые понимают, обычно не могут объяснить начинающему. Может быть меня тоже это ждёт. Это как разговор взрослого и ребёнка. Практически конфликт поколений. Пока мой уровень не перерос в профессионала, нужно изложить моё текущее видение.
В статье, термины которые используются при объяснении различных понятий взяты в кавычки. Статья написана для людей, которые уже научились отличать циклы от массивов и понимают что такое функция и метод.
Если Вы, никогда не программировали, то книга Кернигана и Ритчи (в любом издании), Вам явно будет не по силам. В природе не существует такой силы воли, которая смогла бы заставить Вас дочитать книгу до конца, при этом решить все приведённые задачи.
Очень рекоммендую BecomeAnXcoder. Там всё написано достаточно понятно для людей, которые о программировании знают крайне мало. Информация немного устарела, но для старта сойдёт. Может она Вам уже попадалась на глаза или вы даже скачали её, но отложили в каталог с литературой (ну такой каталог есть у каждого, он занимает десятки гигабайт и всё это мы собираемся прочитать как только появится свободное время, а каталог всё растёт. ).
Понятно, что оптимальный вариант — MAC. На худой конец — Хакинтош или виртуальная машина. И даже если процессор не позволяет запустить ничего из вышеизложенного — начать можно прямо в блокноте с последующей компиляцией в коммандной строке.
На многих форумах можно найти популярный вопрос, задаваемый людьми, которые, по-видимому находятся в тяжело-бронированном танке: «Нужно ли программисту Objective-C учить собственно С?». Везде пишут одно и то же. Те кто побородатее и начинали со структурного программирования — говорят, что просто необходимо стать, как минимум MiddleDeveloper’ом в C, и лишь потом искать путь в лютых дебрях ООП, коим является Objective-C. Те кто не знал С, но уже каким-то образом достиг статуса MiddleDeveloper’а, возражают: мол, выучи как кнопки на iPhone рисуются и как им назначить функцию, а матчасть — потом подтянется, по мере необходимости. Категорически заявляю, что выучить как кнопкам назначаются функции, не есть программирование! Я не призываю потратить полгода на понимание чистого С. Но пару недель — месяц уделить стоит. Это даст некоторый бэкграунд Вашим амбициям. По мере обучения уже Objective-C обязательно будут возникать такие структуры кода, что не будет ничего понятно. Поэтому, предлагаю безо всякого фанатизма, но детально ознакомиться с книгой Крупника А.Б. «Изучаем Си». Она раза в 3 больше предыдущей по объему, но читается легко и непринуждённо. В книге опять же, на достаточно доступном уровне, объясняются основные азы программирования. В ней можно узнать как отличить массив от цикла и что такое указатели.
Недавно на Хабре промелькнула статья, где автор приводил кучу литературы, которую советовал прочитать и пойти работать джуниором за хлеб. Если Вы просто прочитаете всю приведенную в той статье литературу, то ничему не научитесь, а если вы сможете перерешать и запомнить опять же всю литературу, то Вам можно будет идти не джуниором, а солидным разработчиком.
Начало: метод класса и метод экземпляра
Что такое метод? Это набор команд/инструкций или одна команда/инструкция, которая может быть применена к объекту и вызывает в нём необходимые процессы. Если так не совсем понятно, то читайте дальше и поймёте. На самом деле тяжело понять что такое метод класса и метод экземпляра. Все говорят: «со знаком „+“ — это метод класса, а вот со знаком „-“ — это метод экземпляра. Понятно?» И в ответ слышат: «Да-а, метод класса и метод экземпляра». Я лично сначала понимал только то что они разные, а вот что и когда применять — осталось не понятным. Википедия упорно повторяет то же самое, что мы слышали и читали.
В подавляющем большинстве случаев, Вы будете работать с методами экземпляра.
Итак, метод экземпляра. Представим, что у нас есть программа, в которой мы будем использовать 2 класса: Primus и Kitchen.
Любой класс (кроме абстрактного) имеет методы и/или функции. Класс может иметь несколько методов со знаком «+» перед названием метода (метод класса) и несколько методов со знаком «-» перед названием метода (метод экземпляра). Вот те методы, которые со знаком «-» мы активно применяем в отношении объектов — экземпляров класса и собственно класса. А методы со знаком «+» можно применять только в отношении самого класса, где этот метод был объявлен.
Что такое экземпляр класса?
Звучит гордо, но непонятно. Кто помнит из школьного курса: в геометрии точка — родитель для круга, прямой и прочих фигур, потому что каждая фигура состоит из множества точек.
Класс для Objective-C, на некоторых ресурсах интерпретируют как объект и наоборот. На самом деле это немного на так. Можно сказать что объект это экземпляр класса. Как конкретный экземпляр стоящего у Вас на кухне примуса — это экземпляр типа «Примус»: с крутилками, горелкой и пр. Это не просто примус — это Ваш примус. Класс — это пример для подражания. Образец-эталон чего-то, что тоже может исполнять некоторые функции благодаря методам класса (со знаком «+»). Вот здесь можно прочитать немного более развёрнуто.
Также есть «протоколы» и «абстрактные классы». Здесь всё более интересно, но порог понимания выше чем у класса и объекта. Различие состоит в том, что «протокол» не может иметь подкласс или дочерний объектно его на наследуют, ему нужно соответствовать. Как требованию при приёме на работу. Применение протоколов в реальной жизни я затрону в этой статье. Абстрактный класс — это такая сущность, которая как бы класс, но не совсем конкретный класс, потому и абстрактный. Например опять же, Ваш домашний примус — он экземпляр класса «Примус», а не какого-то абстрактного класса «Нагревательный прибор». Вы же не можете иметь дома нагревательный прибор без рода и племени (это может быть также камин и батарея). Вот таким образом, класс «Примус» — может быть наследником абстрактного класса «Нагревательный прибор». А уже Ваш примус — экземпляр конкретного определения примуса. И за своими атрибутами — свойствами, методами, протоколами он при использовании обращается к своему классу и сверяет что он может делать что не может и что он вообще может иметь в наличии.
Представим что есть некий класс «Горелка газовая» — наследник абстрактного класса «Нагревательный прибор». Класс «Горелка газовая» будет родительским для «Примуса» и «Газовой плиты». Для того, чтобы приблизиться к программированию, нужно немного абстрагироваться.
В Objective-C есть родительский класс NSObject. В языке программирования он как точка в геометрии. Это идеальный класс: в нём нет ничего лишнего, он ничего конкретного делает, но из него можно создать всё что угодно. Такой вот кирпичик мироздания. Если в Xcode зажать кнопку «Command» и кликнуть на NSObject, то перед Вами откроется содержимое файла NSObject.h. В нём около 200 строк описания различных методов. Есть как методы со знаком «+» перед названием, а также методы со знаком «-«. Суть методов со знаком «+» такова что их можно применить только непосредственно к самому NSObject. Таким методом является, например,
+(id) alloc;
и применять его можно только так:
[NSObject alloc];
Любой другой объект в Objective-C по определению — потомок NSObject. В нём реализовываются дополнительные методы, которые придают ему черты индивидуальности. Допустим это NSResponder. У этого потомка — NSResponder’а тоже могут быть дочерние объекты. К примеру UIView. Далее по древу наследования можно в качестве примера взять UIScrollView. Потом — UITableView. В каждом случае потомки обрастают дополнительным функционалом. Кроме того, что они умеют делать что-то своё, индивидуальное, они могут делать всё то что может делать их предок. Это и есть — наследование.
Если же вы не хотите, чтобы Ваш класс-потомок абсолютно точно копировал поведение класса-родителя, то можно переделать любой метод класса — родителя по Вашему вкусу. Это есть — полиморфизм.
В Ваших программах вы будете брать нужный Вам класс и добавляя в него нужные методы — расширять его функционал. Также можно брать созданный Вами класс и делать его потомка, опять же расширяя функционал и одновременно создавая уникальный инструмент, для решения нужной задачи. Описанный механизм называется — создание подклассов «subclass».
Не буду вдаваться в широкие описания методов со знаком «+». Нагляднее будет показать, что применение метода
+(id) alloc;
к любому классу или подклассу выделяет под него нужный объём памяти в ОЗУ компьютера.
NSObject *object = [NSObject alloc];
-(id) init;
инициализирует объект, выделяет для него место в ОЗУ и собственно после этого объект начинает существовать и с ним можно работать.
NSObject *object = [[NSObject alloc] init];
Эти 2 метода («alloc» и «init») только что создали объект «object». И данный объект является экземпляром объекта или экземпляром класса NSObject. Если применить эти методы по раздельности
NSObject *object = [NSObject alloc]; [object init];
то объект тоже будет создан. Но есть ненулевая вероятность того Вы создадите не тот объект, которому выделили память. Допустим, вызов метода
NSObject *object = [NSObject alloc];
выделил для него такой адрес в памяти
0x000000000
Так происходит потому что при инициализации, исполняемая среда, всем объектам задаёт значение «nil», чтобы избежать ссылки на место в памяти, заполненное «мусором». Или ссылки на другой конкретный объект, который по факту тоже уже является «мусором», потому что он закончил свой жизненный цикл и уже не «хранится» в памяти, а просто «находится». Для того чтобы понять что такое «мусор», создайте простейшую программку, в которой объявите
int i;
не присваивая ей значение, а потом выведите её значение в консоль
NSLog(@"%i", i);
Вы будете удивлены тем, чему равняется Ваше «i».
Также будет создан экземпляр объекта «isa», который будет рассмотрен позже. Это единственный ненулевой указатель при создании Вашего объекта. Допустим, это ниточка к родительскому объекту, который породит свой экземпляр и даст жизнь нашему объекту.
То есть теоретически объект уже есть после применения метода «alloc». Но он не сможет пройти проверку
if (!self)
Такой кусок кода можно видеть в любом шаблонном объекте в методе «init» или его производных.
В данном случае, объект вроде как есть, но в принципе он равен «nil». Не инициализированный объект — это как машина, которую Вы только собираетесь купить в кредит. Вроде как есть, но она не Ваша. И если не отходя от кассы применить метод «init»
NSObject *object = [[NSObject alloc] init];
то «Бинго!», и объект принадлежит Вам, с ним можно делать всё что хотите. А вот если к нему применить метод
[object init];
то инициализироваться может другой объект. Вроде как вы выплатили кредит за машину, но оказалось что не за ту машину и Вам вместо одного автомобиля дают другой. Они на первый взгляд не отличимы друг от друга. Но у них разные «VIN» номера, а значит при проверке на посту «ГАИ» Вам сообщат что авто не Ваш. Ниже будет приведено дополненное объяснение этого явления.
Как Вы уже поняли метод «+alloc» — метод класса, а метод «-init» — метод экземпляра.
Приведу пример с реальными вещами.
Полная реализация метода включает в себя обычно 2 файла с расширениями *.h и *.m. Бывает и другое количество, в зависимости от того, что это за класс и какие цели он преследует.
В Вашей программе есть класс «Primus». В файле «Primus.h» объявлены методы:
Primus. h //метод класса, можно применить только к классу "Primus" +(Primus *) hotAsHell; //метод экземпляра, инициализирует экземпляр класса "Primus" -(id) initWithGas:(Gas *)gas temperature:(NSInteger)t; //метод экземпляра, задаёт параметры пламени -(void) doFireWithGas:(Gas *)gas; //метод экземпляра, но его применение к текущему классу, вызывает экземпляр совсем другого объекта -(MasterPoRemontu *) masterPoRemontuWithServiceItems:(NSArray *)i serviceSkills:(NSArray *)s;
Также в программе есть класс «Kitchen», где в файле «Kitchen.h», объявлены методы:
Kitchen.h //метод класса, создаёт экземпляр объекта "Kitchen" и применяется непосредственно к объекту "Kitchen" +(Kitchen *) kitchenFurniture:(NSArray *)furniture otherDishes:(NSArray *)dish; //метод экземпляра, применение его к кухне вызывает экземпляр другого объекта -(UkrainianBorsch *) borschWithIngredients:(NSarray)ingredients casserolAndPan:(NSArray)pan;
В файлах «Primus.m» и «Kitchen.m» все объявленные методы должны быть реализованы, то есть описаны: как и что в каждом методе происходит.
Если вы собираетесь в каком-либо классе создавать экземпляр объекта другого класса, то в шапке нужно будет импортировать файл *.h создаваемого объекта, например в шапке файла Kitchen.h мы впишем
#import "Primus.h"
То есть можно будет создать класс «Primus» внутри класса «Kitchen».
Так как мы импортировали заголовочный файл Primus.h в файл «Kitchen.h», то в классе «Kitchen» нам стали доступны те методы экземпляра класса «Primus», которые объявлены в файле Primus.h. Их можно будет применять либо по отношению к классу, либо по отношению к экземпляру класса «Primus».
В файле Primus.m обязательно нужно реализовать все методы, которые были объявлены в файле Primus.h, но также в нём можно реализовать любой метод не объявленный в заголовочном файле Primus.h. Этот метод будет доступен только изнутри класса и его нельзя будет вызвать в другом классе в программе. Так же можно создавать переменные внутри файла Primus.m — они так же не будут доступны для внешнего использования. О них просто не знают. Потому файл типа *.h и называется интерфейсом класса. Только то, что явно в нём указано — доступно извне тому классу, в котором прописано
#import "Primus.h"
Недоступность методов и переменных для других частей программы называется — инкапсуляция.
Для начала создадим экземпляр класса «Primus» внутри класса «Kitchen»
Primus *myPrimus = [Primus hotAsHell];
Как видите, мы посылаем метод c «+» не экземпляру класса «myPrimus», а непосредственно классу «Primus». Экземпляр класса «Primus» под названием «myPrimus» создан. В дальнейшем, методы из «Primus.h» со знаком «-» будут применяться к экземпляру класса — «myPrimus». И если захотим создать новый экземпляр класса «Primus», то можно будет опять воспользоваться методом «+hotAsHell». Также в классе «Primus» есть метод
-(id) initWithGas:(Gas *)gas temperature:(NSInteger)t;
Обычно все методы, название которых начинается с «init», делают то же самое что и метод «init» класса NSObject, только с расширенными возможностями. И в данном случае применение метода «init» в таком ракурсе
Primus *myPrimus = [[Primus alloc] initWithGas:Propan temperature:750];
создаст новый объект класса «Primus» с уникальными характеристиками.
В описанном только что примере был применён метод с аргументами. В данном, конкретном случае, он применяется для создания экземпляра класса «Primus» с чётко заданными характеристиками. По названию характеристик видно, что данный экземпляр класса «Primus» будет работать на газе типа «Propan» и при температуре 750 градусов.
Обратите внимание, что названия методов в Objective-C весьма осмысленны. Временами Вы будете поражены их названиями состоящими из 10 слов, но это способствует лучшему понимаю написанного. Грубо говоря код получается самодокументируемым. Что конечно же не исключает необходимости написания комментариев. Но если взять за правило создавать свои методы, название которых будет нести смысловую нагрузку и таким же образом относиться к созданию переменных, то количество комментариев в коде можно заметно снизить.
В отличие от некоторых других языков программирования в Objective-C методы не вызываются, а посылаются объектам. Таким образом, этот ЯП относится к «message oriented language», на русском языке звучит не так строго, поэтому перевод не привожу.
Также обратите внимание на то, что все методы которые после посылки объекту создают (возвращают) новый объект имеют в скобочках после «+» или «-» либо конкретное название объекта, экземпляр которого они будут возвращать, либо «id». То есть, по том, какой объект указан в скобочках сразу после знака «+» или «-«, можно судить что мы получим после посылки этого метода. Что такое «id»? Это такой тип объекта, который может принимать форму (тип) любого другого объекта. Скажем это «слабый» тип объекта. Не в смысле того, что ему что-то «слабо». Если говорить общепринятыми понятиями, то это слабо типизированный объект. Наличие таких объектов в ЯП делает его слабо типизированным.
Также можно в этих самых скобочках обнаружить тип «void».
Что такое «void»?
Все говорят: «это когда метод ничего не возвращает». А зачем же он нужен? Применение метода этого типа всё же производит манипуляции с объектами, структурами и цифрами. Также объекты и скалярные величины, попавшие в него в качестве аргументов могут измениться либо вообще исчезнуть. Всё зависит от того, что реализовано внутри каждого конкретного метода. Дело в том, что «void» таки ничего не возвращает… нового. Как и было сказано, то что попавшие в него аргументы могут изменяться сами, либо влиять на другие объекты, манипуляция с которыми производится в методе с «void». Он может даже создавать новые объекты внутри себя, но наружу ничего не возвращается в явном виде. Обычно метод, который возвращает что-то, имеет ключевое слово «return» в самом конце. А метод типа «void», «return» в конце не имеет. Он может его иметь внутри своих собственных циклов или условий, чтобы иметь возможность прервать их в нужном месте. Но не в конце. Метод не создаёт ничего принципиально нового, он просто, работая с указателем на какой-то объект (число, массив, ячейка и т.д.), меняет данные по этому адресу или применяет их для расчёта других данных, которые присваивает другим объектам.
Что такое «self»?
Это слово очень часто применяется где попало. Сам факт его применения во всевозможных местах, вносит путаницу в стройные кладовые Ваших знаний.
Вернёмся к классам «Primus» и «Kitchen». Возьмём конкретный метод класса «Primus» и рассмотрим как будет выглядеть его реализация в файле «Primus.m». Например это метод.
-(void) doFireWithGas:(Gas *)gas;
может быть реализован таким образом:
-(void) doFireWithGas:(Gas *)gas < id myPrimus; if (!self)< myPrimus = [self initWithGas:gas temperature:750]; >. . . >
В фигурных скобках происходит реализация или «имплиментация» метода. В данном случае, реализация метода читается так: если не myPrimus, то выполнить команду
[self initWithGas:propan temperature:750];
которая в свою очередь читается как: себе инициализировать с газом:gas температурой:750. То есть, метод посылается себе, а не какому-нибудь другому объекту. В данном случае «себе» — это классу «Primus», так как метод находится внутри класса «Primus». Его объявление — в файле Primus.h и реализация в файле Primus.m.
Также можно заметить, что метод ничего не возвращает «void». То есть получается каламбур: если примуса нет, то мы его создаём, но наружу он из метода не выходит. Зачем его тогда создавать? Допустим его функционал реализован так:
id myPrimus; if (!self) < myPrimus = [self initWithGas:gas temperature:750]; >MasterPoRemonty *master = [myPrimus masterPoRemontuWithServiceItems:someItemsArray serviceSkills:someSkillsAray]; >
И тут уже понятно что где-то внутри метода появляется мастер по ремонту примуса.
Почему был использован знак восклицания «!» в скобках после «if»? В программировании это — знак отрицания. То есть «! objectivec»>if (myPrimus == nil)<>
Это условие имеет в 2 раза большую длину и нужно полностью читать всю строку, чтобы понять его. Это условие небольшое, но если Вам нужно записать что-то вроде этого
if ((myPrimus == nil || yourPrimus == nil) && (mamaPrimus == nil || papaPrimus == nil))<>
то эта структура
if ((!myPrimus || !yourPrimus) && (!mamaPrimus || !papaPrimus))<>
будет в более выигрышной позиции, как по лаконичности так и по физическому размеру. Предыдущая конструкция — банально может просто не влезть в одну строку.
Рассмотрим как выглядела бы реализация этого метода в рамках класса «Kitchen». В файле «Kitchen.m» тот же метод будет реализован так:
-(void) doFireWithGas:(Gas *)gas < Primus *myPrimus; if (!myPrimus)< Primus *myPrimus = [Primus initWithGas:gas temperature:750]; >MasterPoRemonty *master = [myPrimus masterPoRemontuWithServiceItems:someItemsArray serviceSkills:someSkillsAray]; . . >
Видно, что здесь нужно указать, какой объект будет создаваться, как будет называться его экземпляр и потом непосредственно родительскому классу посылать метод.
То есть, хитрое слово «self», применяется внутри класса, как будто вы уже создали его экземпляр.
Primus *myPrimus;
и обращаетесь к экземпляру класса. Только фишка в том, что вы ничего не создавали, и обращаетесь не к экземпляру, а напрямую к классу. Для наглядности я создал объект
id myPrimus;
к которому можно обратиться с методом. Но можно было его не создавать, а реализовать более сложную структуру, которая выполняла бы те же самые функции
if (!self)
Для того, чтобы понять можно ли написать «self» или нельзя — просто представьте себе какой метод какому объекту Вы хотите послать и начните вводить слово «self», но не до конца, например «sel». Xcode предложит несколько вариантов, среди которых будет собственно «self». Слева от него будет описание — какой это класс. Если это именно тот класс, которому вы хотели послать нужный метод (класс внутри которого вы сейчас производите действие), значит используйте «self». Если Xcode указывает, что «self» это не тот объект, который Вам нужен — то используйте имя класса или экземпляра нужного Вам.
Также видно что здесь были задействованы аргументы. У каждого аргумента есть тип, который тоже описан в скобках, но после двоеточия. После скобок с типом аргумента идёт имя аргумента. Если в скобках присутствует знак «*» после типа аргумента, то это объект. Если знака «*» нет, то это скалярный тип, например «NSInteger», который по сути является простым «int». Также «NSInteger» может быть со знаком «*» для передаваемого аргумента. В таком случае мы будем передевавать не саму переменную, а указатель на неё. Тема Указателей выходит за рамки данной статьи. Имя аргумента, в данном методе
-(void) doFireWithGas:(Gas *)gas;
будет звучать как «gas». Вы обратили внимание, что в реализации не было использовано нормального названия газа? Например «Propan». Дело в том, что в аргументе «gas» как раз и передаётся нужное название газа. Откуда? Мы рассмотрели как реализовуется метод
-(void) doFireWithGas:(Gas *)gas;
В нём был задействован метод
-(id) initWithGas:(Gas *)gas temperature:(NSInteger)t;
В этом методе было 2 аргумента «gas» и «t». В посылке метода объекту, температуру мы указали, а тип газа просто продублировали от метода
-(void) doFireWithGas:(Gas *)gas;
То есть этот метод тоже где-то посылается. Рассмотрим поближе метод
(Primus *) hotAsHell;
Его реализация может выглядеть так
+(Primus *) hotAsHell < self = [super init]; if (self)< [self doFireWithGas:propanButan]; >return self; >
Обратите внимание на значение посылаемого аргумента «propanButan». Именно оно передаётся по всему классу, от метода к методу.
Также здесь видно что метод посылается объекту при определённых условиях.
Нужно знать что ни один Ваш метод сам по себе не может сработать. Ему нужен «пинок» снаружи. Нажатие кнопки, возникновения какого-либо события или его должен запустить метод протокола, который среда исполнения запускает в нужный ей момент. Данный метод «hotAsHell» посылается лишь после явного вызова, это не системный метод, он тоже где-то должен быть прописан либо чему-то назначен.
Теперь рассмотрим метод
-(id) initWithGas:(Gas *)gas temperature:(NSInteger)t;
Он состоит из типа возвращаемого объекта, типов аргументов, аргументов и селектора. Селектор это то что остаётся от метода когда от него отнять тип возвращаемого объекта, тип аргументов, и аргументы.
initWithGas:temperature:
именно с двоеточиями. Если аргументов нет, то двоеточий тоже нет, как в случае с «hotAsHell». Часто прийдётся сталкиваться с необходимостью употребить селектор. Теперь вы знаете что употреблять.
Немного выше было использовано понятие «super».
Что такое «super»?
Рассмотрим реализованный выше метод «hotAsHell». В нём есть такая конструкция
self = [super init];
Так принято инициализировать дочерний класс — через инициализацию родительского.
Предположим, что класс «Primus» — непосредственный потомок класса «NSObject». Именно тот класс, который стоит по иерархии сразу над тем классом, с которым мы работаем имеет право называться «super». Команда
[super init];
вызывает метод init экземпляра NSObject. То есть, автоматически создаётся экземпляр прародителя всех объектов — NSObject, с дефолтными (сугубо NSObject’овскими) параметрами. Посылка метода «init» NSObject’y возвращает «self» от NSObject’a. То есть непосредственно экземпляр самого NSObject’a. И этот экземпляр присваивается нашему объекту «Primus». Ведь «self» в данном случае — является именно «Primus’ом». И мы на данном этапе получаем экземпляр объекта «Primus», который ничем не отличается от NSObject’а. Индивидуальные черты ему придаёт наша посылка метода
[self doFireWithGas:propanButan];
а также остальные методы, реализованные в рамках данного класса.
Конструкция
if (self)
это просто перестраховка на случай, если что-то пойдёт не так и дефолтный «Primus» не создастся. т.е. метод init класса родителя не выполнится. В таком случае обычно делают
. else
где пытаются исправить положение дел, но, к сожалению, это не тот случай и «else» нам уже ничем не поможет.
И здесь я хочу Вам напомнить о неинициализированном объекте из начала статьи. Именно при инициализации экземпляра объекта его суперклассом, происходит назначение места в памяти ему и передача указателя на это место в памяти. И когда проводим проверку
if (self = [super init])
то именно в этот самый момент «self» может получить неожиданный адрес в памяти. То есть, полученный указатель уже будет не «self». Суперкласс не возвратит нам что-то из ряда вон выходящее. Экземпляр объекта будет идентичен тому с которым мы собираемся работать. Но экземпляр именно этого объекта не будет тем экземпляром, который нам нужен. Это настолько редкая ошибка, что Вы можете её не наблюдать очень продолжительное время в своих приложениях. Просто в один прекрасный момент Ваше приложение может начать выдавать экзотические ошибки. Вследствие чего придётся потратить время на то, чтобы найти их причину. Выделенная память под Ваш объект не была задействована. Инициализироваться мог случайный указатель, не имеющий никакой связи в объектом, которому была выделена память методом «alloc». Вот тогда «self» не проходит проверку
if (self)
В рассматриваемом методе инициализации. И всё потому что в каком-то месте программы при вызове этого объекта инициализация не была произведена должным образом.
Последнее, что мы должны сделать для завершения инициализации
return self;
Поскольку данный метод не «void», то он ожидает от нас, что в конце мы скажем ему что нужно возвратить. В данном случае будет возвращён объект типа «Primus», потому что «self» в рамках данного класса — именно «Primus». Также тип возвращаемого объекта нам говорит о том что ожидается именно «Primus».
В этих методах
+(Kitchen *) kitchenFurniture:(NSArray *)furniture otherDishes:(NSArray *)dish; -(UkrainianBorsch *) borschWithIngredients:(NSarray)ingredients casserolAndPan:(NSArray)pan; -(MasterPoRemontu *) serviceItems:(NSArray *)i serviceSkills:(NSArray *)s;
тип возвращаемых объектов — «Kitchen», «UkrainianBorsch» и «MasterPoRemontu».
Таким образом, в методе «hotAsHell» мы говорим, что в конце выполнения данного метода желаем получить объект типа «Primus» с заданными параметрами.
Какими именно заданными параметрами?
Опишем такую реализацию метода
- (id) initWithGas:(Gas *)gas temperature:(NSInteger)t
Это значит, что при вызове этого метода
Primus *myPrimus = [[Primus alloc] initWithGas:metan temperature:500];
задаются буквально такие параметры
[self setGas:metan]; [self setTemperature:500];
Параметры задаются переменным, которые объявлены в файле *.h
Обратите внимание на конструкцию слова «setGas» или «setTemperature». Если есть переменная, например «variable», то задать ей нужное значение можно через префикс «set»:
setVariable
при этом первая буква переменной становится заглавной. Таким образом, мы выяснили что в файле Primus.h были объявлены переменные «gas» и «temperature». Но само наличие переменных не даёт нам возможности назначать их с помощью префикса «set»
Для получения такой возможности нужно объявить свойства для этих переменных. Допустим такие:
@property (nonatomic, strong) Gas *gas; @property (nonatomic) NSInteger temperature;
а в файле имплементации нужно будет прописать
@synthesize gas, temperature;
и только после этого станет возможным присвоение им значения через «setGas» и «setTemperature». Эти префиксы называются сэттерами.
В любой удобный момент можно попросить объект «Primus» показать Вам эти значения, обращаясь к его экземпляру
[myPrimus gas]; [myPrimus temperature];
myPrimus.gas; myPrimus.temperature;
например для команды
NSLog(@"Примус газовый работает на газе марки %@, рабочая температура %i", [myPrimus gas], myPrimus.temperature);
Эти методы называются гэттерами. В случае обращения за значениями к названию переменной, ничего дописывать не нужно.
Обратите внимание, что к свойству объекта можно обращаться как через конструкцию
[myPrimus gas];
myPrimus.gas;
Они выполняют аналогичные действия.
Есть одна хитрость при доступе к переменным объекта. Это можно производить не только через свойства, но и через доступ к ним по принципу ключ-значение «key-value coding». То есть можно обратиться к переменной
myPrimus.gas;
[myPrimus valueForKey:@"gas"];
Мы обратились за значением, которое находится в объекте «Primus» в ключевом слове «gas».
А такая конструкция
[myPrimus setGas:metan];
в контексте «key-value coding» будет выглядеть так
[myPrimus setValue:@metan forKey:@"gas"];
Вместо свойств использовать «key-value coding» — вполне нудное занятие. Но иметь понятие о нём необходимо, чтобы в случае, когда без этого не обойтись — воспользоваться.
Также не лишним будет указать тот факт, что с недавнего времени пременными в программировании на Objective-C уже не пользуются. Вместо них используют свойства. Свойства могут быть как доступными извне, так и инкапсулированными. Естественно, что для использования внутри методов можно и нужно использовать переменые. Но объявляние переменных в блоке «ivar», который находится в интерфейсе класса — это уже анахронизм.
runtime
Даже если этого ещё не произошло, есть вероятность того что вскоре Вы встретите в документации слово «runtime» и в пределах той же статьи слово «isa».
Понятие «runtime» можно охарактеризовать как «среда в которой происходит перевод Вашего кода на более низкоуровневый код». «runtime» написан непосредственно с использованием C и Ассемблера. Это ещё не перевод в машинный код, а приведение Вашего кода к языкам C и Ассемблеру. Ваш метод
[myPrimus initWithGas:gelium temperature:nil];
в «runtime» на С выглядит примерно как
objc_msgSend(myPrimus,@selector(initWithGas:temperature:),gelium,nil);
Этого для начинающего Cocoa программиста должно быть достаточно, чтобы понять: дальше лучше в дебри не лезть. Как только Вы перейдёте порог вхождения в клуб, то сами сможете отыскать нужную Вам информацию.
Пока же нас интересует что такое «isa».
Это переменная, которая объявлена непосредственно в NSObject’е. Единственная его переменная. Когда мы вызывали метод
[super init];
то создавался не только экземпляр класса NSobject, но и экземпляр этой переменной «isa», которая ссылается на NSObject. То есть, она конкретно говорит «runtime’у», что она принадлежит объекту NSObject. А значит, и работать с экземпляром Вашего, только что созданного объекта нужно как и с NSObject’ом. В «isa» записывается указатель на тот родительский объект, который нам нужно унаследовать. Допустим, Ваш объект — потомок NSArray или UITableView или CFDictionaryRef или любого другого объекта. В таком случае «isa» указывает на NSArray или UITableView или CFDictionaryRef или любой другой объект соответственно. Так что создание экземпляра любого объекта, создаёт и переменную класса — «isa», которая ссылается непосредственно на родительский класс, вследствие чего «runtime» знает как будет поступать с каждым экземпляром.
Эта информация на этапе обучения нужна не для чего-то конкретного, а в принципе, для более объёмного понимания философии программирования Objective-C.
В процессе чтения книг и различной документации, на глаза Вам не раз попадётся понятие «singleton». Как говорит один популярный интернет-мем: «Нельзя вот так сразу взять и понять, что такое „singleton“.
Что такое синглтон?
Вообразим себе, что Вам нужно создать объект, который при каждом его вызове в любой точке приложения, возвращает один и тот же экземпляр. На самом деле, в процессе создания приложений, Вам действительно нужно будет такое создавать. Так почему же нельзя создать объект, несколько раз и присвоить ему те же данные через метод „initWithSomething:“ или с помощью сеттеров? Всё дело в работе с памятью и быстродействием, да и с собственно, меньшими затратами времени на написание кода. Памяти всегда мало, и даже когда на iPhone6 поставят 2Гб ОЗУ — её опять-таки будет мало. Создание одного экземпляра объекта, и последующее обращение к нему — экономит ресурсы устройства и ускоряет работу приложения. А ведь каждый хочет, чтобы его приложение было быстрым как „Bugatti Veyron“ и юзабельным как слово „хрен“.
Допустим что наш „Primus“ вполне может быть синглтоном. Тогда его метод
+(Primus *) hotAsHell;
при реализации будет выглядеть так
+(Primus*) hotAsHell< static Primus *myPrimus = nil; static dispatch_once_t predicate; dispatch_once (&predicate, ^); return myPrimus; >
Рассмотрим что это значит позже. Пока что выясним, зачем „Primus“ делать синглтоном.
Допустим, это не вполне обычный „Primus“, а раритетный. На нём есть гравировка неизвестного мастера, он имеет крайне низкое потребление газа, а также у него есть специальная коробочка, в которой он идеально укладывается. А теперь задайте себе вопрос: „нужен ли Вам другой примус?“. Конечно же нет! Но при посылке метода
Primus *myPrimus = [[Primus alloc] initWitGas:metan temperature:500];
будет создан один „Primus“, а при посылке метода
Primus *myPrimus = [[Primus alloc] initWitGas:propan temperature:600];
будет создан абсолютно другой „Primus“. Без гравировки и коробочки.
Теперь вернёмся к тому, что написано в реализации метода „hotAsHell“.
Для начала нужно создать экземпляр объекта со свойством „static“, для того, чтобы перекрыть доступ к объекту извне. Затем присвоить ему „nil“, для того чтобы он не взял случайный адрес в памяти. Конструкция
static dispatch_once_t predicate;
создаёт предикат (условие) — »predicate» который тоже не будет виден извне. Условие заключается в том, что запрещается автоматический или динамический вызов блока, стоящего за предикатом. А вот строка
dispatch_once (&predicate, ^);
уже производит все необходимые действия для создания уникального экземпляра объекта. Конкретно «dispatch_once» означает что выражение в скобках после него гарантированно будет запущенно только один раз на протяжении всего жизненного цикла приложения. Также «dispatch_once» гарантирует потокобезопасность. То есть, при запуске приложения в многопоточном режиме эта функция не будет вызвана одновременно в нескольких потоках, и у ж точно не создаст нам ещё один «уникальный „Primus“». Также есть блок
Это вроде маленькой функции или метода. Бывают также большие блоки. В данном блоке происходит инициализация объекта-родителя, после чего его значение присваивается потомку myPrimus.
Вместе вся строка
dispatch_once (&predicate, ^);
означает: один раз за весь жизненный цикл приложения будет инициализирован экземпляр объекта «Primus» под названием «myPrimus» со свойствами объекта-родителя и не будет возможности обратиться к этому блоку другим путём. Но о том что был создан «myPrimus» никто не узнает, потому что этот экземпляр объекта снаружи не виден. Все происходит в фоне и только единожды. И происходит благодаря GCD (Grand Central Dispatch). Рассказ о котором — отдельная тема.
И конечно же в конце мы возвращаем созданный синглтон
return myPrimus;
Синглтон — «Primus» у нас есть, теперь можно добавить ему свойства: коробочка — «Box», гравировка — «Etching», КПД — «Performance». И если объявить для них свойства, то можно будет извне менять эти переменные. Обшить коробочку, почистить гравировку, прочиповать наш «Primus» для повышения КПД. Но это останется наш старый добрый «Primus». Доступ извне к объекту «Primus» будет у тех классов, в шапке которых он объявлен. Но теперь, если делать так
Primus *myPrimus = [Primus hotAsHell];
а потом работать с «myPrimus» как с синглтоном, то ничего работать не будет. Все обращения к переменным синглтона должны происходить в следующей манере
Box *someBox = [Primus hotAsHell].Box;
так можно создать экземпляр «someBox» совсем другого класса коробок и ему присвоить значение, которое имеет коробка нашего синглтона или наоборот
[Primus hotAsHell].Box = someBox;
поменять коробку нашего «Primus».
Синглтон можно применять когда нужно вызвать NSLog с описанием свойств синглтона, а самого синглтона в данном классе нет в принципе. В таком случае его нужно просто объявить в шапке файла и вызвать один раз там где нужно. Синглтон можно и даже рекомендуют использовать в качестве глобальной переменной. Точнее его свойства будут глобальными переменными.
После усвоения основных принципов, можно будет начинать решать предлагаемые в учебниках задачи. И если с написанием своих методов в своих классах и посылкой метода объекту разобраться можно, то в дальнейшем, нужно будет использовать делегаты, протоколы и прочие MVC. Необходимо будет использовать документацию Apple и применять тысячи различных методов, заботливо созданных и описанных купертиновскими программистами.
И тут становится непонятно в принципе как и что работает. Если созданный Вами метод создаёт внутри себя массив, потом вносит в него объекты, потом запускает цикл и что-то в нём делает, а потом этот метод запускает другой Ваш метод, то вроде всё понятно. Но вот Вы открываете документацию по интересующему Вас объекту, а в нём 20 методов, которые могут делать весьма занятные вещи. Кроме того, есть объекты предки, методы которых этот объект тоже может принимать. Кроме того, в начале статьи я писал о протоколах, которым может соответствовать объект. Итого, методов может быть сотни. Какой из них нужно применять? Встречный вопрос: «для чего именно Вам нужен метод?». Правильно заданный вопрос — уже половина ответа. Когда Вы поймёте что хотите сделать, то в описании класса сможете найти интересующий Вас метод и применитьего поназначению. Если Вы хотите чтобы этот экземпляр объекта сделал что-то, присущее только классу от которого он произошёл, то в документации по этому классу нужно внимательно поискать метод, который производит необходимые Вам операции. Послать это метод объекту нужно так
[myPrimus doSomethingWith:Potato];
То есть, в документации Вы узнали, что этот метод берёт указанный аргумент «Potato» и делает с ним что-то, что в конечном итоге приводит Вас к цели если применить метод к объекту «myPrimus». Не нужно реализовывать этот метод, он реализован за Вас для прямого применения. Исключения составляют случаи, когда Вам нужно взять готовый класс из фрэймворка и субклассировать его так, чтобы при посылке стандартных методов его экземпляру, происходили нестандартные действия.
Протоколы
Есть также методы протоколов. Как я уже указывал в начале статьи, у них не может быть потомков. Они являются просто набором методов. Для того, чтобы применить метод протокола внутри себя, объект должен соответствовать этому протоколу. Это указывается в файле *.h
@interface Primus : UIViewController
В данном случае, объект соответствует сразу двум протоколам «UITableViewDataSource» и «UITableViewDelegate».
И чем отличаются методы классов и протоколов?
Если зайти в описание этих протоколов, то там можно найти методы, которые объект может реализовать. Обратите внимание: методы протоколов Вы не посылаете своему объекту, а должны внутри них указать как должен отреагировать ваш объект, при обращении программы к этим методам. Некоторые методы в протоколах могут быть обязательными. И если вы решили что Ваш объект должен соответствовать протоколу, то обязательные методы нужно реализовать в первую очередь. Внутри любого метода протокола может быть любой метод класса. То есть, метод протокола — это контейнер, в котором находятся любые другие методы, собственно как и любой реализуемый Вами метод. Реализовать необходимый функционал внутри каждого метода протокола нужно исходя из потребностей. К примеру, нужно сделать так, чтобы Ваша формочка делала что-то, при определённых условиях. К примеру, меняла цвет, после того, как стала активной/неактивной. Идём в документацию Apple, смотрим какие протоколы реализуют нужные Вам методы. Затем ищем каким протоколам соответствует родительский класс формочки. Если протоколов, которые поддерживают нужный Вам функционал нет в стандартном наборе функций, то добавляем их в < >скобках. В описании этих протоколов ищем методы, которые реализуются после какого-либо события. Допустим
-(UIColor *) areaDidChangeColor:(CGRect)rect isActive:(BOOL)active;
который автоматически выполняется когда аргумент «active» принимает значение «YES». И меняет цвет в части экрана описанной в «rect»:
< if (isActive && myForm == rect)< myColor = [UIColor redColor]; >return myColor; >
Методы протоколов задают параметры работы экземпляра класса, меняют функционал, передают значения. Например:
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
возвращает количество строк в секции «section» для заданной таблицы «tableView».
На этом позволю себе закончить. Если статья поможет целевой аудитории, в которой несколько месяцев назад числился и я, то значит, что я думаю правильно и такие статьи нужны. Статьи не профессионалов, а людей, которые понимают что-то на таком уровне, который тяжело перешагнуть, не имея опоры или трамплина. Надеюсь эта статья кому-то будет трамплином или хотя бы табуреткой. Отсутствие хороших учителей, которые могут нормально что-то объяснить — фундаментальная проблема современности. У меня нет педагогического образования, но в статье изложено понятие в программировании в таком ключе, в каком лично мне было бы понятно.
- Objective-C
- руководство для новичков
- Программирование
- Objective C
Что такое объекты и классы: 1‑я часть гайда по ООП
Почти всё современное программирование построено на принципах ООП, поэтому их должен понимать каждый разработчик. Узнайте основы из этой статьи.
Фото: Sonja Flemming / NBCUniversal / Getty Images
Евгений Кучерявый
Пишет о программировании, в свободное время создаёт игры. Мечтает открыть свою студию и выпускать ламповые RPG.
Это первая статья из серии, посвящённой объектно-ориентированному программированию. Она предназначена для тех, кто хочет понять суть этой парадигмы разработки, а не просто научиться использовать классы и объекты.
Цикл состоит из статей, посвящённых различным аспектам ООП:
- что такое классы и объекты;
- особенности работы с объектами;
- модификаторы доступа, инкапсуляция;
- перегрузка методов;
- полиморфизм;
- наследование и ещё немного полиморфизма;
- абстрактные классы и интерфейсы;
- практикум.
Все примеры в этой серии написаны на языке C#. Для наглядности они будут связаны с разработкой игр, потому что именно в играх (хотя далеко не только в них) активно используются объекты.
Перед тем как приступать к изучению ООП, убедитесь, что знакомы со следующими понятиями:
- переменные и типы данных,
- условные конструкции,
- циклы,
- коллекции (желательно).
Работа будет проходить в Visual Studio 2019, но вполне подойдёт и VS 2017.
В конце каждой статьи будут задания, которые помогут закрепить тему. Выполнять их необязательно, но имейте в виду, что осилить ООП без практики просто невозможно. Если же вам лень выполнять задания, можете просто посмотреть наш вариант решения. Итак, поехали!
Введение в объектно-ориентированное программирование:
- Что такое ООП
- Какие у него плюсы и минусы
- Что такое абстракция, инкапсуляция, наследование и полиморфизм
- Объекты и классы: как их использовать
- Как создать класс
- Как использовать поля и свойства класса
- Как создать метод
- Что такое конструктор объекта
- Домашнее задание
- Что запомнить
Что такое ООП
Объектно-ориентированное программирование (сокращённо ООП) — это парадигма разработки программного обеспечения, согласно которой приложения состоят из объектов.
На объектах и классах строится всё ООП. Поэтому давайте чётко обозначим, чем они отличаются друг от друга.
Класс — это тип данных, созданный пользователем. Он содержит разные свойства и методы, как, например, тип String или Int.
Объект — это экземпляр класса, или его копия, которая находится в памяти компьютера. Например, когда вы создаёте переменную типа String и присваиваете ей значение «Строка», то в памяти создаётся экземпляр класса String.
По-другому можно сказать, что объекты — это сущности, у которых есть свойства и поведение. Обычно объекты являются экземплярами какого-нибудь класса. Например, в игре может быть класс Character («Персонаж»), а его экземплярами будут hero или npc.
Свойства — это данные, которые связаны с конкретным объектом:
- здоровье,
- очки,
- деньги,
- сила,
- ловкость,
- интеллект,
- скорость,
- координаты.
Поведение объекта определяется с помощью методов — специальных блоков кода, которые можно вызывать из разных частей программы. Например, у того же объекта Character могут быть следующие методы:
- идти,
- атаковать,
- говорить,
- подобрать,
- выбросить,
- использовать.
Используя эти свойства и методы, можно значительно ускорить разработку, сделать код более читаемым. К тому же самому программисту проще составлять код, если он думает с помощью объектов.
Разработчики не пишут какую-то функцию, которая будет делать что-то для программы в целом. Вместо этого они мысленно разделяют приложение на отдельные компоненты и продумывают их свойства и поведение.
Такую парадигму используют многие популярные языки:
- C#,
- Java,
- Python,
- JavaScript,
- PHP,
- Kotlin,
- Swift,
- Objective-C,
- C++.
У нас также есть статья по ООП на Python. Поэтому если вы не любите C#, то можете изучить главные принципы на Python.
Плюсы и минусы объектно-ориентированного программирования
Плюсы | Минусы |
---|---|
Легко читается. Не нужно выискивать в коде функции и выяснять, за что они отвечают |
Потребляет больше памяти. Объекты потребляют больше оперативной памяти, чем примитивные типы данных |
Быстро пишется. Можно быстро создать сущности, с которыми должна работать программа |
Снижает производительность. Многие вещи технически реализованы иначе, поэтому они используют больше ресурсов |
Проще реализовать большой набор функций. Так как на написание кода уходит меньше времени, можно гораздо быстрее создать приложение с множеством возможностей |
Сложно начать. Парадигма ООП сложнее функционального программирования, поэтому на старт уходит больше времени |
Меньше повторений. Не нужно писать однотипные функции для разных сущностей |
|
Основные принципы объектно-ориентированного программирования
Всё объектно-ориентированное программирование строится на четырёх понятиях:
Чтобы стало понятнее, представим, что у нас есть класс «Кошка». В нём присутствуют несколько атрибутов — например, «окрас», «порода» и «возраст», а также методов — например, «спать». И когда у нас есть класс, мы можем создать сколько угодно его экземпляров с разными свойствами. Например, мы можем добавить несколько пород кошек:
Теперь перейдём к принципам ООП.
Абстракция
При создании класса мы упрощаем его до тех атрибутов и методов, которые нужны в этом конкретном коде, не пытаясь описать его целиком и отбрасывая всё второстепенное. Скажем, все кошки теоретически умеют охотиться, но если наша программа не предназначена для ловли мышей, то и прописывать этот метод незачем.
Подробно об абстракции и абстрактных классах в ООП можно прочитать в другой нашей статье.
Инкапсуляция
Доступ к данным объекта должен контролироваться, чтобы пользователь не мог изменить их в произвольном порядке и что-то поломать. Поэтому для работы с данными программисты пишут публичные методы, которые составляют интерфейс объекта.
Возвращаясь к нашим кошечкам. Мы можем разрешить изменять атрибут «возраст», но только в большую сторону (к сожалению, с годами никто не молодеет), а атрибут «порода» лучше открыть только для чтения — ведь порода кошки не меняется.
Подробно об инкапсуляции с примерами кода читайте в гайде Skillbox Media.
Наследование
Классы могут передавать свои атрибуты и методы классам-потомкам. Например, мы хотим создать новый класс «Домашняя кошка». Он практически идентичен классу «Кошка», но у него появляются новые атрибуты — «хозяин» и «кличка», а также метод «клянчить вкусняшку». Достаточно объявить «Домашнюю кошку» наследником «Кошки» и прописать новые атрибуты и методы — вся остальная функциональность перейдёт от родителя к потомку.
Больше о наследовании, с примерами кода и полезными практическими советами, читайте в статье «Наследование и ещё немного полиморфизма: 6-я часть гайда по ООП».
Полиморфизм
Этот принцип позволяет применять одни и те же команды к объектам разных классов, даже если они выполняются по-разному. Например, помимо класса «Кошка», у нас есть никак не связанный с ним класс «Попугай» — и у обоих есть метод «спать». Несмотря на то, что кошки и попугаи спят по-разному (кошка сворачивается клубком, а попугай сидит на жёрдочке), для этих действий можно использовать одну команду.
Объекты и классы: как их использовать
Классами в C# является практически всё — строки, числа, массивы и так далее. У каждого из них есть свой набор свойств (например, количество символов в строке или размер типа данных), а также методы, которые позволяют удобно работать с объектами класса (например, отсортировать массив или сложить два числа).
На основе «базовых» классов из C#, мы можем создавать свои. К примеру, возьмём числа типа Int64 и создадим с помощью них числа с плавающей точкой. Такой класс, конечно, уже есть, но мы можем переопределить его по-своему.
Изучая C#, разработчик в первый же день сталкивается с классами и объектами. Например, вот как выглядит первая программа любого новичка:
Как создать класс
Чтобы создать класс, откройте в Visual Studio меню Project и выберите пункт Add Class…:
Затем введите его название и нажмите Add:
Программа создаст отдельный файл с таким кодом:
Если доступ к полям открыт, то с ними можно проводить вычисления или просто получать их значения. Если же нужно запретить доступ к определённым полям — используйте свойства.
Это специальные конструкции, которые позволяют обращаться к полям. Чтобы создать свойства, нужно сначала закрыть доступ к полям с помощью уровня доступа private — тогда они будут доступны только изнутри класса:
Как создать метод
Теперь можно приступить к работе с поведением объектов. Оно реализуется с помощью методов — специальных блоков кода, которые позволяют избежать повторений в проекте.
Методы являются аналогами функций (возвращают значение) и процедур (не возвращают), но с той разницей, что они являются частью какого-то класса. Например, можно в классе Character создать метод Move(), который будет отвечать за движение персонажа.
Если же нужно, чтобы метод что-то возвращал, то указывается его тип и используется оператор return:
Конструктор объекта
В примере выше объект создаётся с уже заданными значениями, но есть возможность указывать свои параметры. Для этого используются конструкторы — специальные методы, которые запускаются при инстанцировании экземпляра класса. С их помощью можно передать объекту параметры и провести необходимые операции.
Вот пример того же класса с конструктором:
Домашнее задание
Создайте консольную игру, в которой игрок сможет управлять персонажем с помощью команд. Возможности, которые должны быть в игре:
- перемещение;
- атака;
- получение опыта за победу над врагами;
- повышение уровня и здоровья персонажа.
Что запомнить
Это лишь вступление в ООП, и ещё многое предстоит изучить, чтобы начать применять его в полную силу. Например, нужно понять, как технически работают классы, как они хранятся в памяти, почему их называют ссылочными типами и так далее.
ООП — сложная, но эффективная парадигма программирования. Её стоит знать всем, кто хочет создавать программы и найти работу, потому что почти все популярные языки её поддерживают. И несмотря на то, что некоторые разработчики утверждают, будто «ООП умерло», потребность в программистах, которые владеют этим подходом, продолжает расти.
Больше интересного про код в нашем телеграм-канале. Подписывайтесь!
Читайте также:
Объект и предмет исследования — как написать, пример
Одной из самых сложных частей студенческой работы является введение. Оно имеет свою структуру, которая предполагает точное описание будущей работы по пунктам.
Самые сложные определения – это объект и предмет исследования. Многие часто путают данные термины и не могут правильно их определить. На самом деле они предполагают почти одинаковый ответ с разной степенью углубленности. Они обозначают то, что изучает исследователь в своей научной работе. Однако при этом объект имеет более широкое значение, чем предмет.
Давайте рассмотрим, что такое объект и предмет исследования и как их правильно написать в введении подробно. Чтобы лучше понять посмотрите наглядный пример из разных видов работ.
01 Что такое объект исследования?
Студентам важно разобраться, что такое объект исследования, потому что его необходимо называть в каждой научной работе.
Один объект исследования может изучаться разными науками и дисциплинами, для которых будет выбран один предмет. К примеру, возьмем человека. Рассматривать человека как существо биологическое свойственно биологам, медикам, антропологам. Исследовать интеллект, разум человека приходится психологам, психотерапевтам, медикам, нейролингвистам. Так как человек социально-биологическое существо, общественную сторону жизни изучают социологи, культурологи, филологи и т.д.
Таким образом, человек является для всех этих областей наук общим объектом. Рассматривать все внутренние системы человека и внешние проявления жизни человека одновременно одной науке невозможно. Поэтому каждый вид науки выделяет для себя определенную часть организма или сторону социальной жизни.
Также, к примеру более узкий объект – язык человека. Он может рассматриваться как орган медиками, биологами, антропологами, либо как психическое явление психологами, либо как знаковая система лингвистами, семиотиками.
В частности, язык как объект лингвистики рассматривается разными дисциплинами: нейролингвистикой, когнитивной лингвистикой, прагмалингвистикой, диахронической лингвистикой и т.д.
Таким образом, объект исследования – это явление или предмет, на который направлено внимание науки. Его не выделяют в отдельный специальный предмет изучения для определенной области, и он может быть рассмотрен разными видами или направлениями наук (медициной, филологией, культурологией и т.д.; либо внутри одной науки – когнитивной психологией, генетической психологией, психоанализом и т.д.).
Объект состоит из разных предметов, процессов и свойств, как человек состоит из разных систем, органов и в разных проявлениях в внешней жизни. Одним словом, это общая тематика, которую можно разобрать на отдельные предметы исследования.
Лайфхак
На сервисе Анти-антиплагиат.рф можно заказать оформление работы по ГОСТ онлайн. Быстро поможем оформить работу по вашей методичке.
Также вы можете заказать проверку оригинальности текста в Антиплагиат ВУЗ. При необходимости поможем повысить уникальность до 80-90%. Кодировка документа не изменит текст, а обработка займет всего несколько минут. Пришлем готовый результат без предоплаты.
02 Что такое предмет исследования?
Предмет исследования – это более глубокое понятие для объяснения того, что изучается в научной работе. Предмет обязательно выделяется из объекта, поэтому в первую очередь всегда нужно указать объект.
Предмет исследования науки социологии – это все социальные отношения людей. Однако для гендерной социологии предметом будет – социальные проявления гендерных различий, т.е. различия между поведением мужчин и женщин. Тогда как общая тематика социальные отношения станет объектом.
Таким образом, на каждом уровне науки меняется объект и предмет. Каждое направление или дисциплина выбирает из общей тематики науки свой предмет исследования.
Поэтому в научных работах выбирают самые глубокие и узкие предметы исследования. Обычно это либо свойства, структура, характеристика (Например, «Анализ работы акционерного общества»), либо отношения между элементами (Например, «Бухгалтерский баланс и анализ финансового состояния и кредитоспособности предприятия»).
03 Объект и предмет исследования – в чем все-таки разница?
Давайте подведем итоги и разберемся, в чем все-таки разница между объектом и предметом исследования.
Самое главное ‒ степень углубленности. Объект существует непосредственно от человеческого восприятия. А предмет ‒ это определенный взгляд на объект с какой-либо стороны. Поэтому объект всегда будет шире предмете.
Изобразим таблицу и покажем какой предмет соответствует для каждого уровня науки.
Вы можете увидеть закономерную цепь расстановки объекта и предмета. Спускаясь на более низкий уровень, предмет становится впоследствии объектом. Поэтому советуем вам проводить для понятия объекта и предмета своего исследования начинать определение издалека. Тогда вы легко придете к тому, что нужно написать в введении научной работы.
04 Как определить предмет и объект исследования в курсовой – примеры
Давайте рассмотрим на разных примерах, как определить предмет и объект исследования в курсовой.
Тема курсовой работы по психологии: «Социальное познание и виртуальная реальность».
«Объект исследования – виртуальная реальность как технология развития навыков в сфере социального познания. Предмет исследования – помощь психологическим больным с расстройствами социального познания посредством технологий виртуальной реальности».
Тема курсовой по математике: «Теория вероятностей».
«Объект исследования ‒ теория вероятностей. Предмет исследования ‒ применение алгоритмов при решении задач».
Тема курсовой по рекламе и пиару: «Этапы создания рекламного ролика на телевидении»
«Объект исследования: телевизионная реклама. Предмет исследования: создание рекламного ролика».
Таким образом, вы можете заметить тесную связь между названием темы работы и объектом, предметом исследования. Также название работы повторяет проблему исследования. Поэтому всегда выбирайте близкую для вас тему, чтобы легче понять, как написать объект и предмет.
05 Как написать предмет и объект исследования в дипломной работе — примеры
Приведем примеры, как написать предмет и объект исследования в дипломной работе.
Тема ВКР по истории: «Женщины в социально-политической жизни российского общества в XIX веке».
«Объектом исследования является процесс развития женского движения XIX века. Предметом исследования является деятельность женщины на разных социальных ступенях российского общества».
Тема диплома по биологии: «Направления регулирования орнитофауны городских парков».
«Объект исследования: птицы парка Ветеранов. Предмет исследования: механизмы регулирования разнообразия и численности птиц».
Тема дипломной работы по маркетингу: «Разработка эффективной стратегии бренда на основе социальных коммуникаций».
«Объектом исследования являются стратегии бренда, основывающиеся на социальных коммуникациях. Предметом исследования выступают особенности эффективности стратегий бренда в условиях возрастающей роли социальных коммуникаций в современном обществе».
Таким образом, выделение объекта и предмета исследования в дипломной работе повторяет схему, как в курсовых. Поэтому тема, объект и предмет исследования почти совпадают.
Мы советуем писать введение уже после того, как будет выполнен ВКР. Так вы сможете лучше понять, какие определения больше подходят.
06 Объект и предмет исследования – примеры правильного написания в диссертации
Теперь посмотрим на пример объекта и предмета исследования в диссертации.
Тема диссертационной работы по юриспруденции: «Гражданско-правовой режим инсайдерской информации».
«Объектом исследования являются правоотношения, возникающие в области правомерного использования, раскрытия инсайдерской информации, обеспечения защиты нарушенных прав субъектов в случае ее неправомерного использования.
В качестве предмета исследования выступают правовые нормы, регулирующие отношения, складывающиеся по поводу инсайдерской информации; локальные акты, договоры, связанные с обращением инсайдерской информации; судебная практика; практика Банка России в сфере выявления неправомерной инсайдерской деятельности; научная доктрина в области гражданского, предпринимательского и информационного права по исследуемой проблематике».
Тема диссертации по педагогике: «Аксиологические ориентиры воспитательной системы А.С. Макаренко».
«Объект исследования: педагогическое наследие А. С. Макаренко. Предмет исследования: воспитательная система А. С. Макаренко в контексте педагогической аксиологии».
Тема диссертации по философии: «Идеология и практика славянского неоязычества».
«Объектом исследования является славянское (русское) неоязычество, его место и роль в религиозной и политической жизни, в общественном сознании россиян. Предмет исследования ‒ истоки формирования неоязычества, его идеология и практика».
Таким образом, выглядят объект и предмет исследования в диссертационной работе.
07 Типичные ошибки при определении предмета и объекта исследования
Давайте рассмотрим, какие ошибки нельзя допускать при написании объекта и предмета исследования.
- Не путайте объект исследования и научную базу. Многие в определение объекта исследования пишут «Открытые сайты, источники интернета» или «монографии и статьи исследователей». Объектом исследования может быть интернет, книга какого-либо писателя, статьи на определенную тематику – если студент изучает именно их. Источники, которые указываются в списке используемой литературы, являются научной базой. Они нужны для теоретической части, чтобы показать степень изученности проблемы или историю развития предмета исследования.
- Не путайте объект и предмет. Объект всегда шире предмета.
- Не забывайте писать определения объекта и предмета в своих научных работах.
Это самые типичные ошибки, которые допускают студенты в своих работах. Из-за них могут не принять ваше исследование на проверку.
Также работу могут не принять из-за несоответствия требованиям ГОСТ. Мы подготовили для вас специальное видео, которое поможет правильно оформить курсовую работу.
08 Как проверить уникальность текста?
После того, как работа будет дописана, вам необходимо проверить уникальность текста.
Вы можете обратиться на бесплатный сервис Антиплагиат ру. Здесь можно загрузить работу любого объема и быстро получить результат анализа на плагиат. Однако прежде вам потребуется зарегистрироваться на сайте, а также конвертироваться работу из формата DOC в PDF или TXT.
Многие опции ограничены, как, например, поиск заимствований через закрытые источники, полный отчет и выгрузка результатов.
Поэтому вы можете воспользоваться услугой от сервиса анти-антиплагиат.рф и проверить уникальность через Антиплагиат ВУЗ. Это расширенная версия Антиплагиат ру, разработанная специально для университетов. В настоящее время получить доступ к системе самостоятельно невозможно.
Однако разработчики анти-антиплагиат.рф получили доступ к лицензионной версии Антиплагиат ВУЗ. Обратившись на наш сервис, вы получите возможность узнать точный процент оригинальности, провести документ через все 30 модулей поиска заимствований и получить полный отчет. Потребуется только загрузить файл на сервис и отправить его, указав адрес электронной почты.
09 Как повысить уникальность текста?
Если вас не устроил ваш результат оригинальности, мы расскажем, как повысить уникальность текста. Существуют такие бесплатные способы:
- Выполнить перевод текста через онлайн переводчик. Для этого скопируйте свою работу и вставьте в окно программы. Затем выберете иностранный язык, например, французский, и переведите. Скопируйте получившийся результат и переведите на узбекский язык. С узбекского – на английский, с английского ‒ немецкий, с немецкого ‒ на русский. Так, вы увидите, как преобразился ваш текст. Осталось отредактировать работу и устранить все ошибки программы. Благодаря этому процент самостоятельности поднимется.
- Используйте пересказ. Вы должны прочитать заимствованную часть документа и попытаться изложить ключевую мысль своими словами.
- Замените слова на синонимы. Вы можете выполнить это самостоятельно, либо обратиться к программе синонимайзер. Она за пару секунд обработает ваш текст. Однако придется самостоятельно доработать результат, чтобы убрать все ошибки.
В итоге, лучше использовать все эти методы одновременно. Тогда вы точно сможете повысить уникальность своей работы.
Либо вы можете обратиться на сервис анти-антиплагиат.рф и заказать услугу «кодировки». Ваш документ будет обработан через специальную программу, которая никак не изменит ваш текст. Однако при этом процент оригинальности возрастет до 80-95%. Нажмите на кнопку «повысить уникальность», чтобы перейти на страницу заказа. Сервис предоставляет услугу без предоплаты. Поэтому вы сможете прежде убедиться в эффективности способа, а уже затем оплатить работу.
Таким образом, выбирайте наиболее удобный для себя способ повышения самостоятельности текста.
Сегодня мы с вами рассмотрели тему: «Объект и предмет исследования — как написать, пример». Обязательно используйте наши примера для образца при написании введения работы. Обратите внимание на ошибки, которые допускают многие люди и ни в коем случае их не допускайте. А мы желаем вам успеха.
Полезные ссылки: