С++ для начинающих


Инструкции объявления


В С++ определение объекта,  например

int ival;

рассматривается как инструкция объявления (хотя в данном случае более правильно было бы сказать определения). Ее можно использовать в любом месте программы, где разрешено употреблять инструкции. В следующем примере объявления помечены комментарием //#n, где n – порядковый номер.

#include <fstream>

#include <string>

#include <vector>

int main()

{

    string fileName; // #1

    cout << "Введите имя файла: ";

    cin >> fileName;

    if ( fileName.empty() ) {

        // странный случай



        cerr << "Пустое имя файла. Завершение работы.\n";

        return -1;

    }

    ifstream inFile( fileName.c_str() ); // #2

        if ( ! inFile ) {

        cerr << "Невозможно открыть файл.\n";

        return -2;

    }

    string inBuf;          // #3

    vector< string > text; // #4

    while ( inFile >> inBuf ) {

        for ( int ix = 0; ix < inBuf .size(); ++ix ) // #5

            // можно обойтись без ch,

            // но мы использовали его для иллюстрации

            if (( char ch = inBuf[ix] )=='.'){ // #6

                 ch = '_';

                 inBuf[ix] = ch;

            }

        text.push_back( inBuf );

    }

    if ( text.empty() )

        return 0;

    // одна инструкция объявления,

    // определяющая сразу два объекта

    vector<string>::iterator iter = text.begin(), // #7

                             iend = text.end();

    while ( iter != -iend ) {

        cout << *iter << '\n';

        ++iter;

    }

    return 0;

}

Программа содержит семь инструкций объявления и восемь определений объектов. Объявления действуют локально; переменная объявляется непосредственно перед первым использованием объекта.

В 70-е годы философия программирования уделяла особое внимание тому, чтобы определения всех объектов находились в начале программы или блока, перед исполняемыми инструкциями. (В С, например, определение переменной не является инструкцией и обязано располагаться в начале блока.) В некотором смысле это была реакция на идиому использования переменных без предварительного объявления, чреватую ошибками. Такую идиому поддерживал, например, FORTRAN.


        //      передать ее конструктору;

        // в противном случае вызвать конструктор по умолчанию

        if ( ix < vec_size )

           new( p+offset*ix ) Account( init_values[ix].first,

                                       init_values[ix].second );

        else new( p+offset*ix ) Account;

   }

   // отлично: элементы распределены и инициализированы;

   // вернуть указатель на первый элемент

   return (Account*)p;

}

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

char *p = new char[sizeof(Account)*elems];

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

for ( int ix = 0; ix < elems; ++ix )

   {

       if ( ix < vec_size )

           new( p+offset*ix ) Account( init_values[ix].first,

                                       init_values[ix].second );

        else new( p+offset*ix ) Account;

   }

В разделе 14.3 говорилось, что оператор размещения new позволяет применить конструктор класса к уже выделенной области памяти. В данном случае мы используем new для поочередного применения конструктора класса Account к каждому из выделенных элементов массива. Поскольку при создании инициализированного массива мы подменили стандартный механизм выделения памяти, то должны сами позаботиться о ее освобождении. Оператор delete работать не будет:

delete [] ps;

Почему? Потому что ps (мы предполагаем, что эта переменная была инициализирована вызовом init_heap_array()) указывает на блок памяти, полученный не с помощью стандартного оператора new, поэтому число элементов в массиве компилятору неизвестно. Так что всю работу придется сделать самим:

void

Account::

dealloc_heap_array( Account *ps, size_t elems )



{

   for ( int ix = 0; ix < elems; ++ix )

       ps[ix].Account::~Account();

   delete [] reinterpret_cast<char*>(ps);

}

Если в функции инициализации мы пользовались арифметическими операциями над указателями для доступа к элементам:

new( p+offset*ix ) Account;

то здесь мы обращаемся к ним, задавая индекс в массиве ps:

ps[ix].Account::~Account();

Хотя и ps, и p адресуют одну и ту же область памяти, ps объявлен как указатель на объект класса Account, а p – как указатель на char. Индексирование p дало бы ix-й байт, а не ix-й объект класса Account. Поскольку с p ассоциирован не тот тип, что нужно, арифметические операции над указателями приходится программировать самостоятельно.

Мы объявляем обе функции статическими членами класса:

typedef pair<char*, double> value_pair;

class Account {

public:

     // ...

           static Account* init_heap_array(

                  vector<value_pair> &init_values,

                  vector<value_pair>::size_type elem_count = 0 );

           static void dealloc_heap_array( Account*, size_t );

     // ...

};


Содержание раздела