- •Раздел 4. Разработка по Тема 4.1. Проектирование интерфейса с пользователем
- •4.1.1. Типы пользовательских интерфейсов.
- •4.1.2. Пользовательская и программная модели интерфейса.
- •4.1.3. Разработка диалогов.
- •4.1.4. Основные компоненты графических пользовательских интерфейсов.
- •Тема 4.2. Реализация графических пользовательских интерфейсов.
- •4.2.1. Диалоги, управляемые пользователем.
- •4.2.2. Диалоги, управляемые системой.
- •4.2.3. Использование метафор.
- •4.2.4. Технология Drag and Drop.
- •4.2.5. Интеллектуальные элементы.
- •4.3.1. Базовые типы данных.
- •Константы
- •Область действия имен
- •4.3.2. Указатели и адресная арифметика.
- •4.3.3. Составные типы данных. Структуры
- •Битовые поля
- •Определение типов
- •Перечислимые типы
- •4.3.4. Выражения и операции.
- •4.3.5. Управляющие конструкции. Условные операторы
- •Операторы циклов
- •4.4.1. Статические одномерные массивы.
- •4.4.2. Статические многомерные массивы.
- •4.4.3. Динамические массивы.
- •4.4.4. Массивы указателей.
- •4.5.1. Стеки.
- •4.5.2. Очереди.
- •4.5.3. Списки.
- •4.5.4. Бинарные деревья.
- •4.6.1. Объявление классов и экземпляров классов.
- •4.6.2. Инкапсуляция данных и методов.
- •4.6.3. Конструкторы классов.
- •Конструктор по умолчанию
- •Конструктор копирования
- •4.6.4. Деструкторы классов.
- •4.7.1. Разделы в описании класса.
- •4.7.2. Friend-конструкции.
- •4.7.3. Статические члены классов.
- •4.7.4. Использование описателя const в классах.
- •4.8.1. Вложенность классов.
- •4.8.2. Наследование данных и методов.
- •4.8.3. Типы наследования.
- •4.9.1. Полиморфизм раннего связывания.
- •4.9.2. Полиморфизм позднего связывания и виртуальные функции.
- •4.9.3. Абстрактные методы и классы.
- •4.10.1. Функции консольного ввода-вывода.
- •4.10.2. Функции файлового ввода-вывода.
- •4.10.3. Использование библиотеки классов потокового ввода-вывода.
- •4.11.1. Перегрузка операций.
- •4.11.2. Шаблоны функций.
- •4.11.3. Шаблоны классов.
- •4.11.4. Обработка исключений.
- •Тема 4.12. Com-технология.
- •4.12.1. Основные понятия.
- •4.12.2. Типы интерфейсов.
- •Свойства интерфейсов
- •Типы интерфейсов
- •4.12.3. Типы com-объектов.
- •4.12.4. Фабрика классов.
- •Тема 4.13. Построение com-сервера.
- •4.13.1. Язык idl.
- •Содержимое файла idl
- •4.13.2. Определение пользовательского интерфейса.
- •4.13.3. Реализация пользовательского интерфейса.
- •4.13.4. Создание тестового клиента.
- •Тема 4.14. Обзор платформы ms .Net.
- •4.14.1. Общая идея архитектуры .Net.
- •4.14.2. Достоинства и недостатки .Net.
- •4.14.3. Схема трансляции программ в .Net.
- •4.14.4. Язык msil.
- •4.14.5. Объектно-ориентированная модель .Net.
4.4.2. Статические многомерные массивы.
Большая свобода в выборе средств манипуляции элементами массивов, предоставляемая языками С и C++, иногда вносит путаницу в понимание основных положений, которые позволяют эффективно работать с массивами более высоких размерностей. Действительно, надо затратить некоторые усилия, чтобы осознать эквивалентность следующих выражений: аrr[2] и 2[аrr] при условии, что был объявлен массив, например double arr[6];. Или эквивалентность a[i][j] и *(*(a+i)+j) при условии, что был объявлен двухмерный массив, например int а[3][4];. Следует отметить, что можно никогда не использовать выражения типа 2[аrr] и, тем не менее, создавать работоспособные программы, но нужно уметь производить анализ выражений такого типа, чтобы понять, как компилятор интерпретирует переменные с индексами.
Как уже было отмечено, если объявлен одномерный массив, например float arr[6];, то имя массива аrr (без последующего индекса) может быть использовано как константный указатель на его первый элемент. Особенностью языков С и C++ является тот факт, что выражение аrr[5] трактуется компилятором как *(аrr+5). Действительно, так как аrr — адрес начала массива, то аrr+5 означает (с учетом правил адресной арифметики) адрес шестого, последнего элемента массива. Следовательно, *(аrr+5) — это содержимое по адресу аrr+5, то есть равенство arr[5]==*(arr+5) истинно. Теперь проанализируем, как компилятор трактует выражение 5[аrr]. Сначала он преобразует 5[аrr] в *(5+аrr), после чего очевидно: 5[аrr] <==> *(5+арр) <=> *(арр+5) <=> арр[5]
Таким образом, имя массива и его индекс можно менять местами, а результат при этом остается тем же.
Рассмотрим теперь двухмерный массив int a[2][3];. Встретив описание такого типа, компилятор отводит в памяти место для линейного размещения массива в виде последовательности ячеек.
1-й эл-т 1-й строки |
|
|
Посл. эл-т 1-й строки |
1-й эл-т 2-й строки |
|
|
Посл. эл-т посл. строки |
а[0][0] |
а[0][1] |
… |
а[0][2] |
а[1][0] |
а[1][1] |
… |
а[1][2] |
Двухмерный массив рассматривается как массив массивов. Элементами главного массива из двух элементов являются одномерные массивы, каждый из трех элементов типа int. В языке имеют смысл такие объекты:
• а[0][0] — первый элемент массива типа int;
• а[0] — адрес первого элемента массива типа int*;
• а — адрес первой строки массива типа int**.
Выражение а+1 означает адрес второй строки массива, то есть адрес, сдвинутый на один элемент массива массивов, а таким элементом является строка двухмерного массива. Выражение а+1 подразумевает сдвиг от а на размер одной строки (а не на размер числа типа int). Адресная арифметика всегда осуществляется в единицах базового типа данных. Теперь такой единицей является строка двухмерного массива или массив целых из трех элементов. Имеют место следующие равенства:
а==&а[0]; а[0]==&а[0][0];
Если вывести содержимое этих адресов: а, &а[0], а[0], &а[0][0] (в формате %р), то мы обнаружим, что все они представляют собой одно и то же число, являющееся адресом первого элемента. Но, несмотря на численное равенство, объекты из первого равенства и объекты из второго равенства, например, а[0] и а, принадлежат к разным типам, и их не следует смешивать в одном выражении, так как, несмотря на численное равенство, это объекты разной природы. Поучительно в режиме пошагового выполнения просмотреть следующую программу:
void main()
{
int a[2][3] = { {1,2,3},
{4,5,6}};
printf("\n **а = %d\t\t a[0][0] = %d”
”\n a = %p\t a[0] = %p"
"\n &a[0]= %p\t &a[0][0]= %p"
"\n a+1 = %p\t a[0]+l = %p\n”, **a,a[0][0],a,a[0], &a[0],&a[0][0],a+l,a[0]+l);
printf("\n a == &a[0] = %d"
"\na[0]== &a[0][0] = %d\n\n", a==&a[0],a[0]==&a[0][0]);
}
Массив а[2][3] можно инициализировать в точке его определения так, как показано в примере. Первым параметром функции printf всегда является строка символов. Несмотря на переносы в тексте, все части одной строки склеятся при компиляции. Результат работы первой printf занимает четыре строки на экране. Первая строка вывода будет такой:
**а=1 а[0][0]=1.
Так как а является адресом адреса, то понадобилась двойная разадресация (**а) для того, чтобы добраться до первого элемента массива. Таким образом, выражение **а==а[0][0] является истинным. Вторая и третья строки дают одинаковые численные результаты. Все числа являются адресом первого элемента массива. Они могут быть, например: 0012FF68.. Четвертая строка вывода дает два разных числа: первое — адрес первого элемента второй строки, второе — адрес второго элемента первой строки, например:
а+1 = 0012FF74 а[0]+1 = 0012FF6C
Результат приводит к заключению, что единица в выражении а+1 «весит» 12 байт, так как в строке три элемента по четыре байта (не забывайте о том, что подсчеты надо производить в шестнадцатеричной системе исчисления), а единица в выражении а[0]+1 сдвигает нас от адреса первого элемента на четыре байта (один элемент типа int). Следующие две строки вывода — это единицы, что означает истинность равенств. Равенства: а==&а[0] и а[0]==&а[0][0] проходят без замечаний. Если же испытать другие два равенства: а==а[0] и а==&а[0][0], то они вызовут со стороны компилятора сообщения об ошибке.
Tenepь посмотрим, как компилятор интерпретирует выражение a[i][j]. Сначала вычисляется а+i, что является адресом i+1 строки. Здесь по правилам адресной арифметики 1 — это число байтов в i строках. Затем производится разадресация *(а+i ) и добывается (еще только) адрес первого элемента i+1 строки (отсчет от единицы) После этого вычисляется адрес элемента, сдвинутого на j единиц (уже других единиц): *(a+1)+j. Осталось разадресовать это выражение, чтобы достать элемент a[i][j]. Следовательно, a[i][j]==*(*(a+i )+j). Если задан массив int a[2][3];, то доступ к элементу а[1][1] может быть осуществлен с помощью выражения *(*(а+1)+1). Согласно правилам адресной арифметики и в предположении, что sizeof(int)=4, единицы в этом выражении будут «весить» 12 и 4 байта.