Бритуля - Богиня
решение домашнего задания с предыдущей лекции
Условие:
Напишите программу, в которой будут все типы переменных, которые мы учили ранее. В каждую переменную положите любое значение, отличное от нуля. Объявите указатели для каждого типа и присвойте указателям адреса переменных.
Программа должна:
1. Распечатать содержимое всех переменных, включая указатели (ВАЖНО! кроме содержимого указателя на char!)
2. Распечатать адреса всех переменных, включая указатели (ВАЖНО! кроме адреса переменной char!)
3. Только для указателей: распечатать содержимое переменных, на которые указывают указатели (через указатели)
4. Только для указателей: изменить содержимое переменных, на которые они указывают (через указатели)
5. Распечатать все заново, то есть повторить с 1 по 3 пункт.
решение
Код:






Результат:






лекцияДавайте представим себе такую ситуацию: вы пишете программу, которая хранит в себе цены в магазине. В этом магазине всего 3 вещи и все они имеют порядковые номера. Заходит покупатель, смотрит на вещь, рядом написан его номер. Он вводит этот номер в компьютер и узнает ее стоимость. Как мы напишем это?
Ну, в начале мы объявим переменные:
int first = 0;
int second = 0;
int third = 0;
заполним цены, а потом, через switch-case высветим высветим их стоимость, в зависимости от запроса. Вроде, неплохое решение, правда? А если таких вещей 30? 300? 3000? Мы объявим 3000 переменных? Должно быть какое-то более элегантное решение, скажете вы... я не могу с вами не согласится и на этой лекции мы как раз его узнаем.
У нас замечательно идет сравнение памяти с ящичками, давайте так и продолжим. Давайте немного модернизируем некоторые из наших ящичков: они будут длинными и разбитыми на деления. Каждое деление будет пронумеровано. Цена первой вещи будет лежать в первом делении, второй - во втором и так далее. Весь ящичек будет носить одно название: "Цены".
Теперь получается более удобно! Нам говорят: "Скажи цену на второй предмет". Так... мы идем к ящичку "цены", какой предмет? Второй? Ага... второе деление... достаем от туда число и это число - и есть интересующая нас цена. НО! Так как программисты - люди необычные и у них все не так, как у людей, то первый ящичек получит цифру 0, второй - 1 и так далее))) То есть счет мы начинаем с нуля, а не с единицы. Этому есть, конечно, причина, но о ней мы поговорим позже.
Такой "длинный ящичек" с сгруппированными переменными ОДНОГО ТИПА носит название "Массив" (англ. "array"). Массив объявляется следующим образом: мы пишем тип переменных, пишем имя, далее мы открываем квадратную скобку (клавиша с русской буквой х), пишем число, которое означает количество делений в нашем ящичке, а после закрываем скобку (клавиша с русской буквой ъ), к примеру:
int prices [30]; // я объявил массив с 30-ю ячейками.
prices [5] = 18; // я положил в 6-ой ящик число 18 (почему в 6-ой? написано же 5-ть.... напоминание: начинаем с нуля!)
int curr_num = prices [3]; // я создал переменную curr_num и в нее положил то число, которое лежит в массиве prices, в 4-ой ячейке.
ОЧЕНЬ важное замечание! Во время объявления массива НЕЛЬЗЯ использовать переменную в качестве размера! То есть:
int num = 0;
cin >> num;
int arr [num]; // так писать НЕЛЬЗЯ!!!! Нужно использовать ТОЛЬКО константу!
Вы подумаете, что объявлять массивы, не зная ДО запуска программы их размера, вообще нельзя? Можно, но не так. Как объявляются массивы с размером, который, например, должен ввести пользователь с клавиатуры - мы узнаем позже.
Примечание: есть шанс, что вы попробуете написать int arr [num]; и компилятор скомпилирует это... Это произойдет только по одной причине: потому что конкретный компилятор, с которым мы работаем - умеет это делать, НО! Это фича ТОЛЬКО этого компилятора, все остальные не скомпилируют этот код, поэтому - не привыкайте к халяве)))
Естественно, мы напишем маленький код и тут же заглянем, что произошло в памяти. Итак, код:
int array[5];
array[0] = 11;
array[1] = 12;
array[2] = 13;
array[3] = 14;
array[4] = 15;
Окей, мы создали массив чисел размером 5 ячеек и ВСЕ (убедитесь, что все) - заполнили числами. Как это выглядит теоретически? Вот так:

Это еще пока не взгляд в память, а только логическая схема. Давайте разберем: красным написано имя массива, черным - содержимое, а синим - "номер ячейки". Вроде все понятно, правда? А если подумать? Каким образом в памяти хранится 5 ячеек, с одинаковым именем? Как это сделано на самом деле? Это ОЧЕНЬ важно понять! Как именно это сделано на самом деле!
С++ гарантирует, что как только вы создадите массив, он выделит количество ячеек, которые вы указали ПОДРЯД! То есть один за другим! Допустим, мы создали массив элементов и каждый элемент хранится в одном байте. К примеру - массив char. Один char хранится в одном байте. Теперь, если это массив - то эти байты будут расположены один за другим! Если первый элемент получил адрес в памяти, скажем, 17, то ГАРАНТИРОВАНО!!! второй получит 18! и так далее. Это ОЧЕНЬ ВАЖНО ПОНЯТЬ!
А вот имя массива - это имя указателя, который указывает на первый элемент! То есть, другими словами, как только вы написали:
char arr[3];
случилось следующее: операционная система выделила 3 ячейки под 3 элемента, взяла адрес первого элемента, выделила ЕЩЕ одну ячейку под указатель на этот тип и туда положила адрес первого элемента! То, что я сейчас написал и выделил жирным - НУЖНО ЗНАТЬ И ПОНИМАТЬ как таблицу умножения! Читайте лекцию от начала и до сих пор, пока это на 100% не засядет в голове! Естественно, вся эта память была выделена на стеке! То есть, как только алгоритм выйдет за пределы блока, в котором объявлен массив - ВЕСЬ массив, включая указатель на него (его имя) перестанет существовать!
Окей, самое время посмотреть на память, чтобы то, что я рассказал, визуально представить. Код:
char arr [3];
arr [0] = 'a';
arr [1] = 'b';
arr [2] = 'c';
Память:

Как мы видим, что arr на самом деле, это имя указателя, хранящего в памяти адрес первого элемента. Обратим внимание на адреса элементов: все три элемента идут подряд в памяти!
Примечание: я привел в пример массив char по той причине, что char занимает один байт. Если бы это был int, который лежит в 2-х или в 4-х байтах, то пришлось бы числа адресов писать с пропуском 2 или 4, чтобы быть до конца честным, а это могло бы ввести путанницу.
То есть теперь, когда мы окончательно поняли, что имя массива - это указатель на первый элемент, мы поймем, почему можно написать вот так:
char arr [3];
char * p;
p = arr;
Последнее, что хочу написать в этом разделе, прежде, чем мы пойдем далее - это присвоение начального значения.
Можно написать так:
char arr [3] = { 'a', 'b', 'c' };
Этот код выполнил следующее действие: он создал массив из 3-х char, в первую ячкейку положил букву 'a', во вторую - 'b', а в третью - 'c'. Однако, у этого способа есть минус: число, которое стоит в квадратных скобках, должно быть не меньше, чем число элементов, которые мы кладем. То есть программист должен постоянно сидеть и считать. С++ избавил его от этой необходимости, а именно? Можно писать так:
char arr [ ] = { 'a', 'b', 'c' };
то есть - если есть присвоение первоначального значения таким образом: { 'a', 'b', 'c' } или, для, например массива int - {1, 2, 3}; - нет необходимости писать его размер в квадратных скобках, компилятор сам посчитает сколько нужно!
НО! Иногда нам нужно обнулить весь массив.... то есть:
int arr [ ] = {0, 0, 0};
а если 300 ячеек? Это же убийство! А как мы знаем - обнуление важный элемент.... так вот, в случае, если массив надо "забить" нулями, во время объявления, то можно написать так:
int arr [300] = {0};
только один ноль! Но тогда требуется размер в квадратных скобках)))) И самое главное - это работает ТОЛЬКО с нулями! То есть, написав int arr [300] = {5}; - мы создадим массив размером 300 элементов, а в первую ячейку положим 5. Дальше будет мусор.
Прежде чем мы перейдем к играм с указателями, я хочу познакомить вас с особым типом массива - массивом char. Да, мы уже видели его чуть ранее, но! У него есть маленький секрет! Часто, в программах нужно сохранять слово, а не букву, логично, да? Как сохранять слово? Нужно просто объявить массив char и побуквенно положить в него буквы. То есть слово "word" должно выглядеть примерно так:
char array [ ] = { 'w', 'o', 'r', 'd' };
НО! тут есть маленькая оговорочка.... если пользоваться таким способом, то всегда надо знать, какой размер у слова, чтобы допустим, его распечатать. То есть, там, где мы его распечатаем - нужно 2 параметра: сам массив и его размер, чтобы после буквы 'd' остановиться. Таскать за собой 2 параметра - неудобно. Поэтому, из ситуации вышли следующим образом: после последней буквы ставится 0. Как мы знаем, что 0 и '0' - две разные вещи, то есть нет никаких проблем с этим, даже, если строка должна содержать ноль как ее часть, например: "есть 0 свободных ячеек", так как 0 посередине строки - это вот такой ноль: '0'.
У ноль, который обозначает конец строки пишут так: '\0' (с бекслешем) НО! 0 и '\0' - одно и тоже.
Массив char с нулем в конце называется "стринг"
То есть наше слово выглядит на самом деле вот так:
char array [ ] = { 'w', 'o', 'r', 'd' , '\0'}; // обратите внимание!!! об этом всегда нужно помнить! Если мы хотим создать стринг, то у него всегда должно быть на одну больше ячеек, чем букв, которые составляют слово или предложение. В этой, последней ячейке должен быть ноль!
Однако, вы не можете не согласиться, что таким образом писать строки и слова - неудобно, поэтому, есть другой способ. Стринг можно создать так:
char str [ ] = "hello world";
или так:
char *str = "hello world";
В таком виде программа создаст стринг, то есть массив char нужного размера, плюс ноль в конце. Так, как это полагается делать.
Распечатать стринг на экран можно так:
cout << str << endl;
Теперь, если есть такие, кто помнит мое ограничение на прошлом домашнем задании, по поводу адреса на char, могут понять, почему я запретил его распечатывать. Именно потому, что программа думает, что получила стринг и будет распечатывать память, ячейку за ячейкой, пытаясь перевести содержимое как букву, пока либо не свалится, либо не встретит случайный 0 по дороге.
Примечание: попробовав создать стринг вторым способом, вы получите предупреждение о том, что этот способ устаревший. Это правда так. Это потому, что в С++ есть более удобная форма хранить строки и слова, и мы ее обязательно изучим, но до этого светлого момента - еще очень долгий путь)))
Указатели, как мы уже поняли, это ячейка, хранящая адрес другой ячейки. То есть, число. Как и все числа, адреса можно прибавлять, отнимать и т.д. Единственное, что нужно сказать, что всегда надо помнить, что мы делаем операции с адресами и если мы прибавляем адрес, то необходимо понимать, что мы делаем, иначе - легко залезть не в свою память и операционная система тут же свалит программу.
Пример:
int arr [ ] = {1, 2, 3};
int * p = arr;
cout << *p << endl; // на экран будет выведено 1
p = p + 2;
cout << *p << endl; // на экран будет выведено 3
p = p - 1;
cout << *p << endl; // на экран будет выведено 2
*p = 48;
теперь наш arr выглядит так:
{1, 48, 3}
Разберем:
в начале, р указывает на первую ячейку массива. Действие p = p + 2 законно, потому что мы знаем, что есть три элемента. И у нас есть еще одно знание: эти элементы лежат в памяти подряд, один за другим. Теперь р указывает на 3-ю ячейку, а после p = p - 1 - на вторую.
Некоторые внимательные скажут:
позвольте.... int хранится в 2-х байтах.... то есть, если в р лежит адрес первого байта, первого int, то сделав действие p = p + 2 мы перескочим на середину второго int...
Ответ: нет. Потому что p указывает на int и программа знает, что если мы прибавляем адрес int, то нужно перескакивать 2 байта на каждую прибавленную единицу. То есть - эти действия полностью законны и отработают так, как и должны. Программист не должен переживать по этому поводу.
Всего пару слов. Думаю, что вы уже понимаете, что нет никакой проблемы объявить массивы указателей, например массив:
int * arr [3] = {0};
содержит в себе 3 указателя на int
Более неожиданно, это то, что можно создавать многоярусные массивы в моем примере я покажу двухярусный, но нет никаких проблем создать 3-х, 4-х и так далее, по тому же принципу, то есть:
int arr [2] [3];
arr [0][0] = 1;
arr [0][1] = 2;
arr [0][2] = 3;
arr [1][0] = 4;
arr [1][1] = 5;
arr [1][2] = 6;
Создаст следующее (схема):

Точно так же этот массив можно создать и инициализировать сразу:
int arr [ ] [ ] = { {1, 2, 3} , {4, 5, 6} };
Вот и все, мои родные))) На сегодня достаточно. На следующей лекции мы сделаем нашу работу с массивами еще более удобной и поговорим о циклах.
1. Объявите массив float, длиной 3 элемента, заполните его начальными значениями, отличными от нуля.
2. Распечатайте значения.
3. Объявите указатель на float и присвойте ему адрес первого элемента в массиве, созданном на шаге (1)
4. Распечатайте значения массива, созданного на шаге (1) через указатель.
5. Поменяйте значение 2-го элемента "напрямую"
6. Поменяйте значение 3-го элемента через указатель, объявленный на шаге (3)
7. Выполните шаги 2 и 4 еще раз.
Удачи!
Условие:
Напишите программу, в которой будут все типы переменных, которые мы учили ранее. В каждую переменную положите любое значение, отличное от нуля. Объявите указатели для каждого типа и присвойте указателям адреса переменных.
Программа должна:
1. Распечатать содержимое всех переменных, включая указатели (ВАЖНО! кроме содержимого указателя на char!)
2. Распечатать адреса всех переменных, включая указатели (ВАЖНО! кроме адреса переменной char!)
3. Только для указателей: распечатать содержимое переменных, на которые указывают указатели (через указатели)
4. Только для указателей: изменить содержимое переменных, на которые они указывают (через указатели)
5. Распечатать все заново, то есть повторить с 1 по 3 пункт.
решение
Код:






Результат:






лекцияДавайте представим себе такую ситуацию: вы пишете программу, которая хранит в себе цены в магазине. В этом магазине всего 3 вещи и все они имеют порядковые номера. Заходит покупатель, смотрит на вещь, рядом написан его номер. Он вводит этот номер в компьютер и узнает ее стоимость. Как мы напишем это?
Ну, в начале мы объявим переменные:
int first = 0;
int second = 0;
int third = 0;
заполним цены, а потом, через switch-case высветим высветим их стоимость, в зависимости от запроса. Вроде, неплохое решение, правда? А если таких вещей 30? 300? 3000? Мы объявим 3000 переменных? Должно быть какое-то более элегантное решение, скажете вы... я не могу с вами не согласится и на этой лекции мы как раз его узнаем.
У нас замечательно идет сравнение памяти с ящичками, давайте так и продолжим. Давайте немного модернизируем некоторые из наших ящичков: они будут длинными и разбитыми на деления. Каждое деление будет пронумеровано. Цена первой вещи будет лежать в первом делении, второй - во втором и так далее. Весь ящичек будет носить одно название: "Цены".
Теперь получается более удобно! Нам говорят: "Скажи цену на второй предмет". Так... мы идем к ящичку "цены", какой предмет? Второй? Ага... второе деление... достаем от туда число и это число - и есть интересующая нас цена. НО! Так как программисты - люди необычные и у них все не так, как у людей, то первый ящичек получит цифру 0, второй - 1 и так далее))) То есть счет мы начинаем с нуля, а не с единицы. Этому есть, конечно, причина, но о ней мы поговорим позже.
Такой "длинный ящичек" с сгруппированными переменными ОДНОГО ТИПА носит название "Массив" (англ. "array"). Массив объявляется следующим образом: мы пишем тип переменных, пишем имя, далее мы открываем квадратную скобку (клавиша с русской буквой х), пишем число, которое означает количество делений в нашем ящичке, а после закрываем скобку (клавиша с русской буквой ъ), к примеру:
int prices [30]; // я объявил массив с 30-ю ячейками.
prices [5] = 18; // я положил в 6-ой ящик число 18 (почему в 6-ой? написано же 5-ть.... напоминание: начинаем с нуля!)
int curr_num = prices [3]; // я создал переменную curr_num и в нее положил то число, которое лежит в массиве prices, в 4-ой ячейке.
ОЧЕНЬ важное замечание! Во время объявления массива НЕЛЬЗЯ использовать переменную в качестве размера! То есть:
int num = 0;
cin >> num;
int arr [num]; // так писать НЕЛЬЗЯ!!!! Нужно использовать ТОЛЬКО константу!
Вы подумаете, что объявлять массивы, не зная ДО запуска программы их размера, вообще нельзя? Можно, но не так. Как объявляются массивы с размером, который, например, должен ввести пользователь с клавиатуры - мы узнаем позже.
Примечание: есть шанс, что вы попробуете написать int arr [num]; и компилятор скомпилирует это... Это произойдет только по одной причине: потому что конкретный компилятор, с которым мы работаем - умеет это делать, НО! Это фича ТОЛЬКО этого компилятора, все остальные не скомпилируют этот код, поэтому - не привыкайте к халяве)))
Естественно, мы напишем маленький код и тут же заглянем, что произошло в памяти. Итак, код:
int array[5];
array[0] = 11;
array[1] = 12;
array[2] = 13;
array[3] = 14;
array[4] = 15;
Окей, мы создали массив чисел размером 5 ячеек и ВСЕ (убедитесь, что все) - заполнили числами. Как это выглядит теоретически? Вот так:

Это еще пока не взгляд в память, а только логическая схема. Давайте разберем: красным написано имя массива, черным - содержимое, а синим - "номер ячейки". Вроде все понятно, правда? А если подумать? Каким образом в памяти хранится 5 ячеек, с одинаковым именем? Как это сделано на самом деле? Это ОЧЕНЬ важно понять! Как именно это сделано на самом деле!
С++ гарантирует, что как только вы создадите массив, он выделит количество ячеек, которые вы указали ПОДРЯД! То есть один за другим! Допустим, мы создали массив элементов и каждый элемент хранится в одном байте. К примеру - массив char. Один char хранится в одном байте. Теперь, если это массив - то эти байты будут расположены один за другим! Если первый элемент получил адрес в памяти, скажем, 17, то ГАРАНТИРОВАНО!!! второй получит 18! и так далее. Это ОЧЕНЬ ВАЖНО ПОНЯТЬ!
А вот имя массива - это имя указателя, который указывает на первый элемент! То есть, другими словами, как только вы написали:
char arr[3];
случилось следующее: операционная система выделила 3 ячейки под 3 элемента, взяла адрес первого элемента, выделила ЕЩЕ одну ячейку под указатель на этот тип и туда положила адрес первого элемента! То, что я сейчас написал и выделил жирным - НУЖНО ЗНАТЬ И ПОНИМАТЬ как таблицу умножения! Читайте лекцию от начала и до сих пор, пока это на 100% не засядет в голове! Естественно, вся эта память была выделена на стеке! То есть, как только алгоритм выйдет за пределы блока, в котором объявлен массив - ВЕСЬ массив, включая указатель на него (его имя) перестанет существовать!
Окей, самое время посмотреть на память, чтобы то, что я рассказал, визуально представить. Код:
char arr [3];
arr [0] = 'a';
arr [1] = 'b';
arr [2] = 'c';
Память:

Как мы видим, что arr на самом деле, это имя указателя, хранящего в памяти адрес первого элемента. Обратим внимание на адреса элементов: все три элемента идут подряд в памяти!
Примечание: я привел в пример массив char по той причине, что char занимает один байт. Если бы это был int, который лежит в 2-х или в 4-х байтах, то пришлось бы числа адресов писать с пропуском 2 или 4, чтобы быть до конца честным, а это могло бы ввести путанницу.
То есть теперь, когда мы окончательно поняли, что имя массива - это указатель на первый элемент, мы поймем, почему можно написать вот так:
char arr [3];
char * p;
p = arr;
Последнее, что хочу написать в этом разделе, прежде, чем мы пойдем далее - это присвоение начального значения.
Можно написать так:
char arr [3] = { 'a', 'b', 'c' };
Этот код выполнил следующее действие: он создал массив из 3-х char, в первую ячкейку положил букву 'a', во вторую - 'b', а в третью - 'c'. Однако, у этого способа есть минус: число, которое стоит в квадратных скобках, должно быть не меньше, чем число элементов, которые мы кладем. То есть программист должен постоянно сидеть и считать. С++ избавил его от этой необходимости, а именно? Можно писать так:
char arr [ ] = { 'a', 'b', 'c' };
то есть - если есть присвоение первоначального значения таким образом: { 'a', 'b', 'c' } или, для, например массива int - {1, 2, 3}; - нет необходимости писать его размер в квадратных скобках, компилятор сам посчитает сколько нужно!
НО! Иногда нам нужно обнулить весь массив.... то есть:
int arr [ ] = {0, 0, 0};
а если 300 ячеек? Это же убийство! А как мы знаем - обнуление важный элемент.... так вот, в случае, если массив надо "забить" нулями, во время объявления, то можно написать так:
int arr [300] = {0};
только один ноль! Но тогда требуется размер в квадратных скобках)))) И самое главное - это работает ТОЛЬКО с нулями! То есть, написав int arr [300] = {5}; - мы создадим массив размером 300 элементов, а в первую ячейку положим 5. Дальше будет мусор.
Стринги
Прежде чем мы перейдем к играм с указателями, я хочу познакомить вас с особым типом массива - массивом char. Да, мы уже видели его чуть ранее, но! У него есть маленький секрет! Часто, в программах нужно сохранять слово, а не букву, логично, да? Как сохранять слово? Нужно просто объявить массив char и побуквенно положить в него буквы. То есть слово "word" должно выглядеть примерно так:
char array [ ] = { 'w', 'o', 'r', 'd' };
НО! тут есть маленькая оговорочка.... если пользоваться таким способом, то всегда надо знать, какой размер у слова, чтобы допустим, его распечатать. То есть, там, где мы его распечатаем - нужно 2 параметра: сам массив и его размер, чтобы после буквы 'd' остановиться. Таскать за собой 2 параметра - неудобно. Поэтому, из ситуации вышли следующим образом: после последней буквы ставится 0. Как мы знаем, что 0 и '0' - две разные вещи, то есть нет никаких проблем с этим, даже, если строка должна содержать ноль как ее часть, например: "есть 0 свободных ячеек", так как 0 посередине строки - это вот такой ноль: '0'.
У ноль, который обозначает конец строки пишут так: '\0' (с бекслешем) НО! 0 и '\0' - одно и тоже.
Массив char с нулем в конце называется "стринг"
То есть наше слово выглядит на самом деле вот так:
char array [ ] = { 'w', 'o', 'r', 'd' , '\0'}; // обратите внимание!!! об этом всегда нужно помнить! Если мы хотим создать стринг, то у него всегда должно быть на одну больше ячеек, чем букв, которые составляют слово или предложение. В этой, последней ячейке должен быть ноль!
Однако, вы не можете не согласиться, что таким образом писать строки и слова - неудобно, поэтому, есть другой способ. Стринг можно создать так:
char str [ ] = "hello world";
или так:
char *str = "hello world";
В таком виде программа создаст стринг, то есть массив char нужного размера, плюс ноль в конце. Так, как это полагается делать.
Распечатать стринг на экран можно так:
cout << str << endl;
Теперь, если есть такие, кто помнит мое ограничение на прошлом домашнем задании, по поводу адреса на char, могут понять, почему я запретил его распечатывать. Именно потому, что программа думает, что получила стринг и будет распечатывать память, ячейку за ячейкой, пытаясь перевести содержимое как букву, пока либо не свалится, либо не встретит случайный 0 по дороге.
Примечание: попробовав создать стринг вторым способом, вы получите предупреждение о том, что этот способ устаревший. Это правда так. Это потому, что в С++ есть более удобная форма хранить строки и слова, и мы ее обязательно изучим, но до этого светлого момента - еще очень долгий путь)))
Арифметика указателей
Указатели, как мы уже поняли, это ячейка, хранящая адрес другой ячейки. То есть, число. Как и все числа, адреса можно прибавлять, отнимать и т.д. Единственное, что нужно сказать, что всегда надо помнить, что мы делаем операции с адресами и если мы прибавляем адрес, то необходимо понимать, что мы делаем, иначе - легко залезть не в свою память и операционная система тут же свалит программу.
Пример:
int arr [ ] = {1, 2, 3};
int * p = arr;
cout << *p << endl; // на экран будет выведено 1
p = p + 2;
cout << *p << endl; // на экран будет выведено 3
p = p - 1;
cout << *p << endl; // на экран будет выведено 2
*p = 48;
теперь наш arr выглядит так:
{1, 48, 3}
Разберем:
в начале, р указывает на первую ячейку массива. Действие p = p + 2 законно, потому что мы знаем, что есть три элемента. И у нас есть еще одно знание: эти элементы лежат в памяти подряд, один за другим. Теперь р указывает на 3-ю ячейку, а после p = p - 1 - на вторую.
Некоторые внимательные скажут:
позвольте.... int хранится в 2-х байтах.... то есть, если в р лежит адрес первого байта, первого int, то сделав действие p = p + 2 мы перескочим на середину второго int...
Ответ: нет. Потому что p указывает на int и программа знает, что если мы прибавляем адрес int, то нужно перескакивать 2 байта на каждую прибавленную единицу. То есть - эти действия полностью законны и отработают так, как и должны. Программист не должен переживать по этому поводу.
Многоярусные массивы и массивы указателей
Всего пару слов. Думаю, что вы уже понимаете, что нет никакой проблемы объявить массивы указателей, например массив:
int * arr [3] = {0};
содержит в себе 3 указателя на int
Более неожиданно, это то, что можно создавать многоярусные массивы в моем примере я покажу двухярусный, но нет никаких проблем создать 3-х, 4-х и так далее, по тому же принципу, то есть:
int arr [2] [3];
arr [0][0] = 1;
arr [0][1] = 2;
arr [0][2] = 3;
arr [1][0] = 4;
arr [1][1] = 5;
arr [1][2] = 6;
Создаст следующее (схема):

Точно так же этот массив можно создать и инициализировать сразу:
int arr [ ] [ ] = { {1, 2, 3} , {4, 5, 6} };
Вот и все, мои родные))) На сегодня достаточно. На следующей лекции мы сделаем нашу работу с массивами еще более удобной и поговорим о циклах.
Домашнее задание
1. Объявите массив float, длиной 3 элемента, заполните его начальными значениями, отличными от нуля.
2. Распечатайте значения.
3. Объявите указатель на float и присвойте ему адрес первого элемента в массиве, созданном на шаге (1)
4. Распечатайте значения массива, созданного на шаге (1) через указатель.
5. Поменяйте значение 2-го элемента "напрямую"
6. Поменяйте значение 3-го элемента через указатель, объявленный на шаге (3)
7. Выполните шаги 2 и 4 еще раз.
Удачи!
@темы: C++