Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 208
Текст из файла (страница 208)
Многомерные массивы Нередко нам требуются векторы векторов, илн векторы векторов из векторов и т.д. Как такие многомерные массивы представляются в С++? Для ответа на вопрос я сначала покажу, как для этого применить стандартный класс гесгог. Затем я рассмотрю применение лишь встроенных средств языков С и С++ лля построения многомерных массивов.
С.7.1. Векторы Обшее решение можно построить на базе стандартного класса гесгог (в)б.З): гестог<гессог<!п<» и (3, гес(ог<!п(> (5) ); Приложение С. Технические подробности 972 Здесь создается вектор из трех векторов, каждый из которых содержит по 5 целочисленных элементов. Все 15 этих элементов получают умолчательное значение О. Новые значения элементам можно присвоить следующим образом: гоЫ рмп! т ( ) ( 3ог((пг ! = 0; !<т.яее(); (++) ( аког(!пг/ = 0; /<т [(] .е(ее() ! /ее) т РН [7] = 30*И! ) или графически: 00 01 02 03 04 щ[0): щ[1): щ[2): 10 11 12 !3 14 20 2! 22 23 24 гоЫргт! т() ( аког(т! ! = О; !<т.е(ге() ! гг+) ( 3ог(гпг!' = О; 3<т [!] .е(ге(); )ее) еощ « т [[] [у] « ' ~!'; сои! « '~п'; ) что приведет к следующему результату: О 1 2 3 4 70 7! 72 73 74 20 27 22 23 24 Отметим, что т — это объект типа геегог, содержащий другие геегог, а вовсе не простой многомерный массив.
Поэтому, в частности, можно осуществить изменение размера (Э]6.3.8) элемента. Например: 1оЫ !т! та ((и! пе) ( аког((п! г = 0; !<т.е!ге() ! 1+г) т [!) .«егере(пе) Каждый объект, сгенерированный из шаблона ыесго«, содержит число элементов и указатель на элементы, которые в типичном случае содержатся в массиве. Для иллюстрации я задал каждому целому элементу значение, соответствующее его «координатам>. Доступ к элементам осуществляется двойной индексацией.
Например, т [!] [у]— этоуъый элемент г-го вектора. Мы можем осуществить вывод т следующим образом: 973 С.7. Многомерные массивы Не требуется, чтобы вектора типа восток<те>, содержащиеся в гестог<гесгог<1пг», имели одинаковый размер. С.7.2. Массивы Встроенные массивы служат источником многих ошибок в программах — особенно в случае многомерных массивов. А новичков они могут сильно смутить. По-возможности им лучше пользоваться типами гесгог, га!аггау, вп]п» и т.д. Многомерные массивы есть массивы массивов; массив 3-на-5 объявляется так: 1п1 та [31 [5]; У 3 массива с 5 (пг в каждом Инициализировать та можно следующим образом: гоЫ 1па та() ( (ог(ьи1 = Ог 1<3[ 1++) ( Зог(шг1' = О; 1<5[ 144) та [1] [7] = 1О*1г13 ) ) или графически: гпа.
00 01 02 03 04 10 11 12 13' 14 20 21 22 23 24 Массив та — это просто ]5 целых чисел, к которым мы обращаемся так, как будто это 3 массива по 5 чисел в каждом. Кроме того, в памяти машины никакого объекта та, представляющего массив нет — в памяти хранятся лишь элементы. Размеры 3 и 5 существуют лишь в исходном коде. При написании кода мы сами следим за этими размерами. Например, мы можем следующим образом выполнить вывод пю: иоЫрмпг та() ( (ог(1п11= О; 1<3; 1-г-г) ( (ог (ш11' = О; 1<5( 144) сот « та [1] [1] « соиг « ' ~п'; В языке С++ нельзя использовать запятую для отделения размеров массивов друг от друга, ибо запятая — это операция следования (56.2.2).
В целом, большинство ошибок с массивами отлавливаются компилятором. Например: шг Ьад[3,5]; 77 еггот запятая в константных выражениях не допускается 1пгдоод[3] [5]; г73 массива по 5 1пг в каждом шг оисд = Овод [1, 4]; гу егтт яоог((1,43 это аоод(4~[ [пг инициаяиэируется типом [пг" 1пг п(се = Овод [1] [4]; 974 Приложение С. Технические подробности С.У.З. Передача многомерных массивов в функции Рассмотрим функцию, которая должна манипулировать двумерной матрицей. Если размеры матрицы известны на момент компиляции, то никаких проблем нет; гоЫрнпг т35 (!лг т[3] [5] ) ( 3ог(тг! = Ог 1<3 г и«) ( 3ог (1п1 ! = О; /<5; 3Ч «) сои! « ш [1] [1) « ' ~1'; сои! « '),л'; ) Матрица, представленная как многомерный массив, передается с помощью указателя (а не копируется; в5.3).
Размер матрицы по первому измерению не имеет отношения к проблеме нахождения местоположения элемента; он просто сообщает, сколько элементов (здесь 3) и какого типа (здесь — типа !пг[5] ) присутствует. Например, посмотрев на предыдущее определение ша, заметим, что зная лишь размер 5 по второму измерению, мы может определить местоположение элемента та(г] [5) для любого Б Поэтому размер по первой размерности можно передать в качестве аргумента функции: гоЫ рг!п! т(5 (гп! т [] [5], 1л! О!т!) 3ог (гпг г = О; !<О!т1; г»«) ( аког(гпг/ = 0; 3<5; 1»«) сош «ш[!] [(] « ' ~1'; сои! « ' ~п'; ) ) Более сложной является ситуация, когда нужно передавать оба размера.
Очевидное «решение» не работает: гоЫ рыл! ш!! (!лт т [] (], !пг И!ш1, !пг»(!ш2) 3ог(Ы11 = О) !<И!т3; !«») ( 3ог (1пг! = 0; 3<О!т2; 3Ч«) сои! «т [г] [1] « ' ~1'; ~у сюрлриз! сот« '',и'; ) Во-первых, объявление аргумента в виде ш [] [] незаконно, ибо для нахождения элемента массива нужно знать его вторую размерность. Во-вторых, выражение ш(г] [1) совершенно справедливо интерпретируется как * (* (ш»!) «3), но это нето, что предполагал программист. Правильное решение таково: гоЫ рппг т!!'(1лг* т, !лг Йт!, !л! О!т2) ( 3ог(тг! = 0; !<О!т1; 1«») С8. Экономия памяти 979 ( Гог(!п(1'= О;1<4[т2; 1Ъ-';) сои< «т[г*Жт2»1) « '~г'; кузаковыристо соиг « ' ~о'; Выражение для доступа к элементам в функции рипг ои1() эквивалентно тому, что генерирует компилятор, когда знает вторую размерность.
При вызове этой функции, мы передает матрицу как обычный указатель: ои та(о () ( (ог»[3) (5) = ( (0,1,2,3,4), (10,11, 12,13, 14), (20,21,22,23,24) ); рмиг т35(»); рыоГ т(5(ЮЗ); рг(ог т(1'(аг(0) [О) 3 5) ' ) Обратите внимание на а»[0) [О) в последнем вызове; сработало бы и о[0), так как это равносильно, но для г будет ошибка типа. Такой тонкий и запутанный код лучше спрятать поглубже.
Если вам необходимо работать с многомерными массивами напрямую, попробуйте инкапсулировать имеющие к этому отношение участки кода. Тем самым, вы упростите задачу программисту, который потом будет работать с этой программой. Введение пользовательского типа «многомерный массив» с надлежащей операцией индексации избавит пользователей от хлопот с истинным расположением данных в массиве (922.4.6). Стандартный тип гесгог (916.3) такими проблемами не страдает. С.8.
Экономия памяти При написании нетривиальных программ наступает момент, когда приходится экономить память. Есть два способа «выжимания дополнительной памяти» из имеющейся: 1. Помещать в байт более одного объекта. 2. В разное время использовать одну и ту же память для хранения разных объектов. Первый способ достигается применением битовых волей Д1еЫ3, а второй способ — применением обьединений (игооп).
Мы рассмотрим эти конструкции в последующих разделах. Многие случаи применения битовых полей и объединений относятся к крайним формам оптимизации, основанным на предположениях о конкретных схемах распределения памяти, затрудняющих переносимость программ.
Программисту следует дважды подумать перед тем, как применять их. Часто лучше изменить способ управления данными и положиться на динамически выделяемую память (96.2.6), а не на ее статическое распределение. Приложение С. Технические подробности 976 С.8.1. Битовые поля Представляется странным использование целого байта (типы айаг или Ьоо!) для представления битовых величин (например, переключателя с положениями включено/выключено), но сйае — это наименьший объект С++, под который можно выделить адресуемую память (95.1). Однако можно связать малюсенькие объекты друг с другом в битовые поля в структурах, поля которых определяются по количеству отведенных под них битов.
Допускаются неименованные поля. Они не влияют на именованные поля, но помогают расположить последние более эффективно с аппаратной точки зрения: г)' номер страничного кадра гу не используется гг алгоритм согласования с кэшем Этот пример иллюстрирует также другое важное применение битовых полей: именование частей заданной извне раскладки памяти. Поля должны иметь интегральный тип или тип перечисления (94.1.1). Невозможно получить адрес поля. Если не считать такой особенности, то в остальном поля могут использоваться так же, как другие переменные. Отметим, что поле Ьоо! реально представимо единственным битом.