Re[5]: Можно ли создавать в классе массив?
От: LaptevVV Россия  
Дата: 16.02.09 10:07
Оценка:
Здравствуйте, Аноним, Вы писали:

AG>>Кроме статического члена класса, массив-член класс не может иметь инициализатор через фигурные скобки.

А>В какой книге это написанно?
Вот:

Поля-массивы в классе
В С++ нет запрета на объявление в классе поля-массива. Естественно, размер класса с полем-массивом увеличивается на размер массива. Однако использование массива в классе недостаточно хорошо отражено в стандарте. Это вызывает многочисленные вопросы, особенно связанные с инициализацией. Мы начнем разбираться с этими вопросами на самом простом примере, в котором объявляются и инициализируются несколько полей-массивов (листинг 4.1).
Листинг 4.1. Поля-массивы в классе

class Arrays
{    int m0[10];
    static const unsigned int k = 10;
    enum { n = 10 };
    int m1[k];
    int m2[n];
    public: 
        Arrays()                         // конструктор
        { for (int i = 0; i < k; ++i)
            m0[i]=m1[i]=m2[i]=0; 
        }
};

В классе Arrays объявлено три поля-массива: m0, m1 и m2. Как видим, задать количество элементов массива можно либо явной константой, либо константным выражением со статическими и (или) перечислимыми константами, причем константы должны быть определены раньше массива. Задавать количество элементов поля-массива обязательно.
Нельзя задать количество элементов как значение другого поля, заполняемого конструктором, даже если это поле константное. Например, пусть в классе объявлены следующие поля:
const int k;
int m[k];

В этом случае система Visual C++.NET 2003 выдает ошибку компиляции C2327, которая сигнализирует о том, что k в данном случае не является ни статической константой, ни константой перечислимого типа.
Конструктор без аргументов Array() обнуляет массивы, выполняя цикл в теле. Такой способ инициализации поля-массива — в теле конструктора — является наиболее простым и позволяет присваивать элементам массива любые значения. Однако наиболее часто выполняется обнуление, поэтому для массивов разрешается применять инициализацию нулем. Тогда наш конструктор выглядит значительно проще:
Arrays(): m0(), m1(), m2() {}

Для массива объектов некоторого типа такая запись означает вызов конструктора по умолчанию (без аргументов), и не забудьте, что этот вызов выполняется для каждого элемента массива.
К сожалению, применение списков инициализации для полей-массивов этим и ограничивается — в скобках нельзя ничего указывать. Например, при написании следующей конструкции система Visual C++.NET 2003 выдаст сообщение об ошибке С2536, сигнализируя о том, что явная инициализация массивов запрещена:
Arrays(): m0(1,2,3,4,5), m1(), m2() {}

Попытки написать в скобках некоторое одиночное выражение (например, вызов функции, заполняющей массив) компилятор отвергает по той же причине.
Явная инициализация не проходит даже для символьных массивов. Например, объявим в классе Arrays символьный массив:
char s[4];

После этого попытаемся в списке инициализации конструктора присвоить этому массиву символьную константу:
Arrays(): m0(), m1(), m2(), s("abc") {}

Тогда в системе Visual C++.NET 2003 мы получим сообщение об ошибке:
error C2536: 'Arrays::Arrays::s' :
cannot specify explicit initializer for arrays

Это сообщение говорит о том, что нельзя определять явный инициализатор для массивов.
Тем не менее, можно задать в классе указатель на символ:
char *s;

Затем можно инициализировать указатель символьной константой в списке инициализации конструктора, как было показано ранее.
Как это ни странно, но большие проблемы возникают при попытках объявить в классе константный массив встроенного типа! Как мы уже выяснили, константы нельзя инициализировать в теле конструктора, значения им присваиваются только в списке инициализации конструктора. Однако для константного массива встроенного типа не работает даже инициализация нулем.

ПРИМЕЧАНИЕ
Этот вопрос практически не отражен в стандарте, поэтому компиляторы ведут себя по-разному. В системе Visual C++.NET 2003 выдается ошибка компиляции C2439, а Borland C++ Builder 6 выдает только предупреждение W8038 о том, что массив не инициализируется.

Не проходит и отмена константности. Например, зададим массив m0 как константный, а в теле конструктора определим инициализацию в цикле:
for (int i = 0; i < 10; ++i) 
    const_cast<int>(m0[i]) = 0;

Однако и Visual C++.NET 2003, и Borland C++ Builder 6 отказываются компилировать такой цикл.
Но для константного массива из объектов не встроенного типа задавать инициализацию нулем разрешается. Для этого в классе должен быть определен конструктор без аргументов, который вызывается для инициализации каждого элемента константного поля-массива. Например, вполне можно инициализировать константный массив денег:
const TMoney ss[10];
Для этого достаточно задать в списке инициализации конструктора инициализацию нулем ss(). Как реально инициализируется такой массив, конечно, зависит от реализации конструктора по умолчанию.

Статические поля-массивы
Методы перевода перевода регистра в классе, реализующем строки, должны обеспечивать перевод и русских, и английских букв. Простейшая реализация методов преобразования в верхний и нижний регистры может быть такой, как в листинге 4.19. Поскольку перевод регистра — разовое действие, то пусть обе операции возвращают значение.
Листинг 4.19. Простейшая реализация методов изменения регистра строки
TString TString::operator++()    // toUpper
{ const char lower[59] 
      = "abcdefghijklmnopqrstuvwxyzабвгдежзийклмнопрстуфхцчшщъыьэюя";
  const char upper[59] 
      = "ABCDEFGHIJKLMNOPQRSTUVWXYZАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"; 
  for(int i = 0; i<this->Length(); i++)
  { for (int j = 0; j < 59; j++) if (s[i] == lower[j]) s[i]=upper[j]; }
  return *this;
}
TString TString::operator--()    // toLower
{ const char lower[59] 
    = "abcdefghijklmnopqrstuvwxyzабвгдежзийклмнопрстуфхцчшщъыьэюя";
  const char upper[59] 
    = "ABCDEFGHIJKLMNOPQRSTUVWXYZАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"; 
   for(int i = 0; i<this->Length(); i++)
  { for (int j = 0; j < 59; j++) if (s[i] == upper[j]) s[i]=lower[j]; }
  return *this;
}

Методы прекрасно работают, однако таком виде они нас, конечно, не устраивают — любой программист тут же скажет, что использование локальных массивов очень неэффективно. Массивы заново создаются при каждом входе в функцию — на это тратится и время и место при выполнении программы.
Первое побуждение — вынести массивы из методов и объявить два поля-массива. Но тут нас ожидают некоторые сложности. Во-первых, поля нельзя объявить константными, хотя по сути своей они таковыми и являются. Дело в том, что тогда мы не сможем эти поля инициализировать — константные массивы встроенного типа никаким образом не инициализируются . Но если массивы не константные, то их придется заполнять в каждом конструкторе, написав для этого приватную функцию.

Во-вторых, и это гораздо важнее, неконстантные поля-массивы существенно увеличивают размер класса — на 118 байт. Пока объектов немного, это не доставляет беспокойства. Но представьте, что нам надо объявить массив размером в 1000 элементов типа TString. Ситуация отнюдь не надуманная, так как подобный массив, например, в качестве буфера может использовать простой текстовый редактор. Но такой массив имеет уже 118 000 лишних байтов, которые еще и заполняются во время создания — ведь конструктор вызывается для каждого элемента массива. Ситуация совершенно неприемлемая как с точки зрения расхода памяти, так и с точки зрения эффективного выполнения программы.

Так как массивы у нас символьные, то можно было бы обойтись указателями на символ — тем более, что их можно инициализировать символьной константой. Ситуация с памятью несколько улучшается: два указателя занимают в классе всего 8 байт. Однако указатели тоже будут инициализироваться для каждого создаваемого объекта.

Массивы нужно все-таки иметь в единственном экземпляре; они, очевидно, должны быть объявлены константными и проинициализированы один раз при объявлении. Мы можем это сделать, если объявим их глобальными. Однако такое решение совершенно неприемлемо с точки зрения инкапсуляции — глобальные переменные излишне доступны и их использование способствует появлению трудноуловимых ошибок. Массивы по сути своей являются (и должны быть) частью класса TString.

В языке С++ есть средство, позволяющее сделать именно то, что нам требуется: объявить массивы в единственном экземпляре, сделать их константными, проинициализировать при объявлении и, тем не менее, локализовать их в классе. Такие константные поля-массивы надо объявить в классе статическими (см. п.п. 9.4.2 в [1]):
static const char lower[59];        // маленикие буквы
static const char upper[59];        // БОЛЬШИЕ БУКВЫ

Инициализация (определение) статических полей выполняется вне класса:
const char TString::lower[59] = "abcdefghijklmnopqrstuvwxyzабвгдежзийклмнопрстуфхцчшщъыьэюя";
const char TString::upper[59] = "ABCDEFGHIJKLMNOPQRSTUVWXYZАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ";

Этот оператор присваивания является определением статического члена класса (а в классе — только объявление). Такой оператор присваивания — единственная форма инициализации константных статических массивов (и любых других константных статических полей встроенного нецелочисленного типа). Обратите внимание, что слово static в операторе инициализации отсутствует — его там писать нельзя. Статические массивы, как и статические константы (и вообще любые статические поля), являются частью класса, создаются в единственном экземпляре при запуске программы (до создания любых объектов класса), поэтому их нельзя инициализировать в конструкторе.

ПРИМЕЧАНИЕ
Помимо статических полей, в классе можно объявлять и статические методы. Такие методы являются методами класса и могут вызываться до создания первого объекта этого класса.

Cтатические константы места в классе не занимают. Точно так же не увеличивают размер класса и любые статические поля — они хранятся вне класса. Но принцип инкапсуляции выполняется — доступ к таким полям имеют только методы и «друзья» класса. Наши методы перевода регистра упрощаются (листинг 4.20).
Листинг 4.20. Реализация методов перевода регистра
TString TString::operator++()            // toUpper
{ for(int i = 0; i < this->Length(); i++)
  { for (int j = 0; j < 59; j++) if (s[i] == lower[j]) s[i]=upper[j]; }
  return *this;
}
TString TString::operator--()            // toLower
{ for(int i = 0; i < this->Length(); i++)
  { for (int j = 0; j < 59; j++) if (s[i] == upper[j]) s[i]=lower[j]; }
  return *this;
}

Проверим работу методов.
TString t ("Мама мыла Милу");    std::cout << t << std::endl;
++t;    std::cout << t << std::endl;
--t;    std::cout << t << std::endl;
TString b = "abCDefGH";  std::cout << b << std::endl;
++b; std::cout << b << std::endl;
--b; std::cout << b << std::endl;

При выполнении этого фрагмента на экране появятся следующие строки:
Мама мыла Милу
МАМА МЫЛА МИЛУ
мама мыла милу
abCDefGH
ABCDEFGH
abcdefgh

Как видим, все работает правильно.

Хочешь быть счастливым — будь им!
Без булдырабыз!!!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.