- •Адреса, указатели, ссылки, массивы Указатели и адреса объектов
- •Объявление и инициализация указателей
- •Инициализация указателей
- •Указатели константы и на константы
- •Типы указателей и операции над ними
- •Указатели на основные типы данных - на арифметические данные и на символьные.
- •Операции над указателями
- •Понятие указателя на указатель
- •Аддитивные операции
- •I. Вычитание
- •Вычитание из указателя целое число:
- •II. Сложение указателя с целым значением
- •Объявление ссылки
- •Инициализация ссылок не обязательна:
- •Массивы
- •Форма объявления одномерного массива (вектора) type имя массива [k];
- •Type имя массива [ k 1] [ k2] …[kn];
- •Внутреннее представление массивов в оперативной памяти
- •Инициализация числовых и символьных массивов.
- •Инициализации символьных массивов
- •Массивы и указатели
- •Указатели и строки
- •Ввод/вывод элементов массивов Ввод/вывод числовых массивов
- •Ввод/вывод символьных массивов
- •Создание динамических массивов.
- •Имя указателя [индекс элемента]
- •Массивы указателей
- •Создание двумерного динамического массива с помощью динамического массива указателей.
- •Указатель на массив. Многомерные массивы динамической памяти.
- •Определение типа массива
- •Определение типа указателя на массив
Адреса, указатели, ссылки, массивы Указатели и адреса объектов
Понятие переменной определялось как имя ячейки памяти, в которой хранится значение указанного типа.
Каждая ячейка памяти имеет свой уникальный адрес.
Чтобы получить адрес переменной (простой или структурированной) используется унарная операция &, которая применима к объектам, размещенным в памяти и имеющим имена.
Указатель – это объект (переменная), значениями которого являются адреса участков памяти, выделенных для объектов конкретных типов.
------------------------------------------------------------------
До сих пор мы использовали только объявление простых переменных.
В операторе объявления предоставляется тип и символическое имя значения (идентификатор).
Он также заставляет программу выделить память для этого значения и внутренне отслеживать ее местоположение.
Давайте рассмотрим другую стратегию, важность которой проявляется при разработке классов C++.
Эта стратегия основана на указателях, которые представляют собой переменные, хранящие адреса значений вместо самих значений.
Но прежде, чем обратиться к указателям, давайте поговорим о том, как явно получить адрес обычной переменной.
Для этого применяется операция взятия адреса, обозначаемая символом &, к переменной, адрес которой интересует.
Например, если home — переменная, то
&home — ее адрес.
В листинге демонстрируется использование этой операции.
// address.срр -- использование операции & для нахождения адреса
#include <iostream>
int main ()
{
using namespace std;
int donuts = 6;
double cups = 4.5;
cout<< "donuts value = " << donuts;
cout<< " and donuts address = " <<&donuts << endl;
cout<< "cups value = " << cups;
cout<< " and cups address = " <<&cups <<endl;
return 0;
}
Ниже показан вывод программы в одной из систем:
donuts value = 6 and donuts address = 0x0065fd44
cups value = 4.5 and cups address = 0x0065fd40
/*В показанной здесь конкретной реализации cout используется шестнадцатеричная нотация при отображении значений адресов, т.к. это обычная нотация, применяемая для указания адресов памяти.
Наша реализация сохраняет donuts в памяти с большим адресами, чем cups. Разница между этими двумя адресами составляет 0x0065fd44-0x0065fd40, или 4 байта.
Таким образом, использование обычных переменных трактует значение как именованную величину, а ее местоположение — как производную величину
Теперь рассмотрим стратегию указателей, которая представляет важнейшую часть философии программирования C++ в части управления памятью.
Указатели и философия C++
Объектно-ориентированное программирование (ООП) отличается от традиционного процедурного программирования в том, что ООП делает особый акцент на принятии решений во время выполнения вместо времени компиляции.
Время выполнения означает период работы программы, а время компиляции — период сборки программы компилятором в единое целое.
Решения, принимаемые во время выполнения — это вроде того, как, будучи в отпуске, вы принимаете решение о том, какие достопримечательности стоит осмотреть, в зависимости от погоды и вашего настроения, в то время как решения, принимаемые вовремя компиляции, больше похожи на следование заранее разработанному плану, вне зависимости от любых условий.
Решения времени выполнения обеспечивают гибкость, позволяющую программе приспосабливаться к текущим условиям.
Например, рассмотрим выделение памяти для массива.
Традиционный способ предполагает объявление массива. Чтобы объявить массив в C++, вы должны заранее решить, какого он должен быть размера. Таким образом, размер массива устанавливается во время компиляции программы, т.е. это решение времени компиляции.
Возможно, вы думаете, что массив из 20 элементов будет достаточным в течение 80% времени, но однажды программе понадобится разместить 200 элементов.
Чтобы обезопасить себя, вы используете массив размером в 200 элементов.
Это приводит к тому, что ваша программа большую часть времени расходует память впустую.
ООП пытается сделать программы более гибкими, откладывая принятие таких решений на стадию выполнения.
Таким образом после того, как программа запущена, она самостоятельно сможет решить, когда ей нужно размещать 20 элементов, а когда 200.
Короче говоря, с помощью ООП вы можете сделать выяснение размера массива решением времени выполнения.
Чтобы обеспечить такой подход, язык должен предоставлять возможность создавать массивы — или что-то им подобное — непосредственно во время работы программы.
Как вы вскоре увидите, метод, используемый C++, включает применение ключевого слова new для запроса необходимого объема памяти и применение указателей для нахождения выделенной по запросу памяти.
Принятие решений во время выполнения не является уникальной особенностью ООП.
Но язык C++ делает написание соответствующего кода более прямолинейным, чем это позволяет С.
Новая стратегия хранения данных изменяет трактовку местоположения как именованной величины, а значения — как производной величины.
Для этого предусмотрен специальный тип переменной — указатель, который может хранить адрес значения.
Таким образом, имя указателя представляет местоположение.
Применяя операцию *, называемую косвенным значением или операцией разыменования, можно получить значение, хранящееся в указанном месте.
Да, это тот же символ *, который применяется для обозначения арифметической операции умножения;
C++ использует контекст для определения того, что подразумевается в каждом конкретном случае — умножение или разыменование.
Предположим, например, что manly — это указатель. В таком случае manly представляет адрес, a *manly — значение, находящееся по этому адресу.
Комбинация *manly становится эквивалентом простой переменной типа int.
Эти идеи демонстрируются в листинге. Также там показано, как объявляется указатель.
Листинг pointer. срр
// pointer.срр -- наша первая переменная-указатель
#include<iostream>
int main () {
using namespace std;
int updates =6; // объявление переменной
int * p_updates; // объявление указателя на int
p_updates = &updates; // присвоить адрес int указателю
// значения:
cout<<"Values: updates = " <<updates;
cout<< ", *p_updates = " << *p_updates<<endl;
//адреса:
cout<< "Addresses: &updates = " <<&updates;
cout<< ", p_updates = " <<p_updates<<endl;
// Изменить значение через указатель
*p_updates = *p_updates + 1;
cout<< "Now updates = " << updates <<endl;
return 0;
}
Ниже показан пример выполнения программы:
Values: updates = 6, *p_updates = 6
Addresses: &updates = 0x0065fd48, p_updates = 0x0065fd48
Now updates = 7
*/
