Urok7

Урок 7.1.7. Ссылки

7.1.7. Ссылки

В языке С++ введен тип данных, которого нет в С, – ссылка. Ссылка позволяет определить еще одно имя для переменной, синоним (eng. alias).

Формат объявления ссылки выглядит следующим образом:

<тип> &<идентификатор1> = <идентификатор2>;

Ссылка при объявлении всегда должна быть проинициализирована, после этого её нельзя будет изменить. Например:

int c, a, b = 5;
int &ref_a = a;  //ref_a является ссылкой на a
ref_a = b; // a = b, т.е. a = 5
ref_a++;   // a = 6, ref_a = 6;
c = ref_a;  // c = 6;

int *p = &ref_a; // p инициализируется  адресом a
int &ir = 1024;   // ошибка: не адрес, только ссылка на постоянный объект
// может быть проинициализирована таким значением как 1024
const int &ir = 1024;   // верно: ссылка на константный объект
const int &ar = a + b;  // верно: ссылка на константный объект
int const &ar = a;         // const (константная ссылка) здесь избыточно,
// т.к. ссылка и так не может меняться

double d[10];
double &rd9 = d[9]; // имя(псевдоним) элемента d[9]

Ссылку можно рассматривать как постоянный указатель, который всегда разадресован и для которого не надо выполнять операцию разадресации (*).

Подчеркнем, что ссылка не создает копии объекта, а является именем объекта. Т.е. выражение &ref_a == &a всегда истинно.

Из этого правила имеется два исключения, когда при объявлении ссылки создается копия во временной памяти.

1. При объявлении ссылки на константу. При этом создается внутренняя переменная, ей присваивается константа, а затем ссылка инициализируется этой переменной.

Например, есть объявление:

char &ref = ‘\0’;

компилятор создаст код:

char temp = ‘\0’;
char &ref = temp;

Это делается по соображениям безопасности. Компилятор оптимизирует размещение одинаковых констант, размещая их одной области памяти. Поэтому выполнение кода:

char &ref = ‘\0’;
ref = ‘\t’;
char n = ‘\0’;

могло бы привести к тому, что переменная n получила бы значение ‘\t’. Более того, ‘\0’ в программе была бы заменена на ‘\t’.

2. При инициализации ссылки переменной другого типа. Например, объявление:

unsigned int ui = 77;
int &r_ui = ui;

интерпретируется следующим образом:

int t = (int)ui;
int &r_ui = t;

Когда при объявлении ссылки создается копия объекта во временной памяти, компилятор выдает соответствующее предупреждение. В случае выдачи сообщения об ошибке положение можно исправить следующим образом:

const int &r_ui = ui; //ссылку на константный объект можно объявить
//и при несовпадении типов.

Основной причиной введения ссылок в C++ явилась необходимость передачи параметров в функции через ссылку и получения возвращаемого значения виде ссылки при реализации перегружаемых операций. Операции применяются  к объектам пользовательских типов (классов). Передача по значению объектов, возможно больших, в качестве операндов вызовет лишние затраты на их копирование. Кроме того, передача операндов с использованием указателей, приводит к необходимости взятия адресов операндов в выражениях.

Урок 7. Производные однородные типы данных

7.  Производные однородные типы данных

Производные типы данных характеризуются тем, что это более сложные
объекты программы чем те, которые были рассмотрены до сих пор. Производные типы
данных не отражают архитектурные особенности компьютера один к одному, как это
имеет место для основных типов char, int, float и double, но их машинное
представление выражается через эти основные типы. Производные типы данных могут
быть однородными и смешанными. Для однородного производного данного, например,
массива, все элементы, состовляющие данное, имеют один и тот же тип, а для смешанного
производного данного, например, структуры, элементы, состовляющие данное, могут
иметь различные типы. В этой главе мы рассмотрим производные однородные типы
данных.

Урок 7.1. Указатели

7.1. Указатели

Указатель представляет собой переменную, значением которой является адрес
памяти. Значение указателя "показывает" (отсюда и термин -
указатель), по какому адресу памяти размещено некоторое данное, а из описания
указателя известен тип этого данного. В различной литературе по
программированию можно встретить другой термин - ссылка (ссылка на данное),
являющийся синонимом термина указатель. Мы же в дальнейшем будем употреблять
термин указатель и применительно к нему, возможно, будем говорить
"ссылается на".

Урок 7.1.1. Описание указателей

7.1.1. Описание указателей

Указатель представляет собой переменную, значением которой является адрес памяти. Значение указателя "показывает" (отсюда и термин – указатель), по какому адресу памяти размещено некоторое данное, а из описания указателя известен тип этого данного.

Указатели описываются операторами вида:

<тип> *<объект1>,*<объект2>,...,*<объектN>;

где

<тип> – это любой, допустимый в Си тип данных; * звездочка говорит о том, что следующий за ней <объект> является объектом, указывающим на данное <тип>; <объект> в простейшем случае это <идентификатор> и тогда он представляет собой скалярную переменную, являющуюся указателем.

Более сложным объектом может быть <идентификатор> [<кол-во>]...[<кол-во>], и тогда идентификатор представляет собой имя n-мерного массива указателей, содержащего <кол-во> элементов по n-му измерению. Если в качестве объекта в оператор описания подставить конструкцию вида *<объект>, то тогда объект является указателем на указатель данного <тип>.
Если вправо от идентификатора объекта записаны парные пустые круглые скобки, то такой идентификатор является указателем на функцию. При описании указателей можно использовать круглые скобки для задания нужной интерпретации. Правила интерпретации сложных описаний будут рассмотрены чуть ниже, пока же исследуем простые примеры описаний,

 int *p1; p1 – указатель на данные целого типа.
 float *p2; p2 – указатель на данные с плавающей точкой.
 char *p3; p3 – указатель на символьные данные.
 int *p4[3]; p4 – массив указателей на данные целого типа, каждый из элементовкоторого p4[0], р4[1], p4[2] является указателем на целое.
 void *p5; p5 – указатель на данные, тип которых заранее не определен.
 char **p6 p6 – указатель на указатель данных типа сhar.

Более сложные примеры описания указателей:

 int (*fn)(); fn – указатель на функцию, возвращающую целое значение.
 char (*pt)[5]; pt – указатель на пятиэлементный массив символьных данных.
 int (*uk)[5][6]; uk – указатель на двухмерный массив данных целого типа размером 5 на 6.
 int *(*vc[10])(); vc – массив из десяти указателей на функции, возвращающие указатели нацелые значения.

При интерпретации сложных описаний квадратные скобки, задающие массив, и круглые пустые скобки, задающие функцию (и те и другие стоят справа от идентификатора) обладают приоритетом перед звездочкой (слева от идентификатора). Квадратные и круглые скобки имеют один и тот же приоритет и связываются слева направо. Тип данного, на который ссылается указатель, рассматривается на последнем шаге, когда объект уже полностью проинтерпретирован. Непустые круглые скобки используются для того, чтобы установить необходимый (и, естественно, допустимый в языке) порядок интерпретации. Для интерпретации сложных описаний данных вообще, в том числе и указателей, известно простое правило, называемое "изнутри наружу" и состоящее из четырех шагов.

1. Начать с идентификатора и посмотреть вправо, есть ли квадратные или круглые скобки.

2. Если они есть, то проинтерпретировать эту часть описания и затем посмотреть налево в поиске звездочки.

3. Если на любой стадии справа встретится закрывающая круглая скобка, то вначале необходимо применить все эти правила внутри круглых скобок, а затем продолжить интерпретацию.

4. Интерпретировать описатель типа.

Применим это правило для интерпретации последнего из вышеприведенных примеров:

 int * ( * vc[10] ) ();
 6  5    3  1   2     4

Цифрами показан порядок просмотра и интерпретации описания в соответствии с правилом: объявляется переменная vc (1) как массив из десяти (2) указателей (3) на функции (4), возвращающие указатели (5) на целые значения (6). И, наконец, последнее в этом разделе замечание по поводу описания указателей. Если мы хотим описать указатель, используя который нельзя изменить в программе значение данного, на которое он указывает, то можем использовать модификатор const. Например:

const char *p7;

Указателю p7 могут быть присвоены различные значения, но данные, на которые он указывает, не могут быть изменены.
 

Урок 7.1.2. Указатели и модели памяти

7.1.2. Указатели и модели памяти

Размер памяти, выделяемой компилятором под указатель, зависит от модели памяти, для которой будет компилироваться программа. Память для программы в Си требуется в следующих четырех целях: для размещения программного кода, для размещения статических данных, для размещения динамических данных по запросу из программы (память под них выделяется из области, называемой куча) и для организации стека, в котором создаются локальные переменные и заносятся параметры при вызовах функций.

В зависимости от объема кода программы и данных, программу нужно скомпилировать для одной из шести моделей памяти, предлагаемых Турбо Си.

1. Tiny (крошечная). Под код программы, статические данные, кучу и стек отводится в сумме 64К памяти. В такой модели памяти для указателя требуется два байта (близкий указатель).

2. Small (маленькая). Под код программы отводится 64К, под стек, кучу и статические данные отводится в сумме 64К. В такой модели памяти программа работает также с близкими указателями.

3. Medium (средняя). Под код программы отводится 1Мгб памяти. Это обозначает, что все обращения к функциям и возвраты из функций, осуществляемые через указатель, должны использовать указатель длиной в четыре байта, чтобы в нем мог разместиться далекий адрес памяти (далекий указатель). Под стек, кучу и статические данные выделяется 64К памяти и, следовательно, для адресации данных используются близкие указатели.

4. Compact (компактная). Под код программы отводится 64К. Под данные отводится всего 1Мгб, но объем статических данных ограничивается размером 64К. Под стек выделяется 64К. Адресация внутри программы осуществляется близкими указателями, адресация – данных далекими указателями.

5. Large (большая). Под код программы отводится 1Мгб, под статические данные – 64К, куча может занимать до 1Мгб памяти. Как программа, так и данные адресуются далекими указателями.

6. Huge (огромная). Аналогична большой модели, но объем статических данных может превышать 64К. Для любой адресации необходимы далекие указатели.

7. Flat (плоская) — в плоской модели код и данные используют одно и то же адресное пространство. Фактически несегментированная модель памяти. Для для 32-битных процессоров плоская модель памяти позволяет адресовать 4 Гб оперативной памяти, для 64-битных — гипотетически до 16 эксабайт, фактически до 256 Тб.
(Эксаба?йт (Eb, Эб, Эбайт) — единица измерения количества информации, равная 1018.)

В зависимости от соотношения объемов код–данные, выбирается модель памяти для компиляции вашей программы. Нередки случаи, когда при неправильном выборе модели памяти программа работает некорректно, хотя на первый взгляд – все в порядке.

В некоторых случаях возникает необходимость задать размер указателя, отличный от размера, устанавливаемого при компиляции в той или иной модели памяти. Это можно сделать при помощи модификаторов описания указателей near, far и huge:

  • модификатор near устанавливает 16-тибитный, т.е. близкий указатель. При использовании near-указателей данные программы ограничены размером сегмента 64К;
  • модификатор far устанавливает 32-ухбитный, т.е. далекий указатель. При использовании far-указателей можно ссылаться на данные в пределах 1 мегабайта адресного пространства;
  • модификатор huge также устанавливает 32-ухбитный указатель аналогичный far-указателю, однако между ними есть различия. Во-первых, операции отношения ==, !=, <, >, <= и >= работают правильно с huge-указателями, а с far-указателями они работают неправильно. Во-вторых, все арифметические операции над указателями типа huge воздействуют как на адрес сегмента, так и на смещение, а при использовании указателей типа far воздействуют только на смещение.

Из-за того, что арифметические операции при работе с far-указателями воздействуют только на смещение, то хотя far-указатель и может адресовать данные в пределах одного мегабайта, данные, к которым идет обращение, должны начинаться и заканчиваться в пределах сегмента.

Например, все элементы массива, расположенного в нескольких сегментах, доступны при помощи huge-указателя, а при помощи far-указателя можно получить доступ только к элементам, расположенным в пределах данного сегмента, остальные будут недоступны для far-указателя, т.к. арифметические операции изменяют смещение, но оставляют неизменным адрес сегмента.

Попутно заметим, что для near–указателя ограничения еще более жесткие: все данные должны располагаться в пределах одного сегмента (для huge u far эти пределы – 1 Мб).

Рассмотрим способы описания указателей с модификаторами near, far и huge. Когда одно из этих ключевых слов встречается в описании, то оно модифицирует объект, расположенный справа от ключевого слова, например:

char far *ptr;            ptr описан как far-указатель,
int huge *ptr;           ptr описан как huge-указатель,
float near *ptr;         ptr описан как near-указатель,
int *(far *ptr);           ptr описан как far-указатель на указатель к данным целого типа,
char far b = 'B';       b описан как символьная переменная в дальнем сегменте данных;
char far* far ptrb = &b;  ptrb - дальний указатель на переменную в дальнем сегменте данных.

Для удобства основные данные этого параграфа собраны в таблицу:

DOS

Модель Код Данные Стек Указатель по умолчанию
Tiny

код+данные+стек+куча до 64Кбайт

near
Small 64 Кбайт данные+стек+куча до 64Кбайт near
Medium 1 Мбайт данные+стек+куча до 64Кбайт near
Compact 64 Кбайт 64 Кбайт 64 Кбайт far
Large 1 Мбайт 64 Кбайт 64 Кбайт far
Huge 1 Мбайт >64 Кбайт 64 Кбайт far

16-бит Windows EXE

Small 64 Кбайт данные+стек+куча до 64Кбайт near
Medium 1 Мбайт данные+стек+куча до 64Кбайт near
Compact 64 Кбайт данные+стек до  64 Кбайт far
Large 1 Мбайт данные+стек до  64 Кбайт far

16-бит Windows DLL

Small 64 Кбайт 64 Кбайт

-

far
Medium 1 Мбайт 64 Кбайт

-

far
Compact 64 Кбайт 64 Кбайт

-

far
Large 1 Мбайт 64 Кбайт

-

far

32-бит Windows

Flat 2 Гбайт 1 Мбайт 1 Мбайт far

 

Translate Переводчик

Подписка на новости

SmartResponder.ru
Ваш e-mail: *
Ваше имя: *

Хостинг для Wordpress сайтов