5.    Конструктор копии создает объект класса на основе существующего объекта.

 

Например:

class point

{

protected:

double x;

double y;

public:

point();

point(double xVal = 0, double yVal = 0);

point(const point &pt);

// другие функции-элементы

};

6.    Объявление объекта класса, которое может содержать параметры и, в качестве параметра, имя уже существующего объекта, влечет за собой вызов конструктора. Но какой из конструкторов будет использоваться в каждом конкретном случае? Ответ зависит от того, как много конструкторов вы объявили и с какими аргументами вы объявляете объект класса. Например, рассмотрим следующие объявления объектов последней версии класса point:

point p1; // применяется конструктор по умолчанию

point p2(1.1, 1.3); // используется второй по счету конструктор

point p3(p2); // используется конструктор копии

Поскольку объект p1 объявляется без параметров, компилятор использует

конструктор по умолчанию. Объект p2 объявляется с двумя вещественными

аргументами, поэтому компилятор вызовет второй конструктор. Объект p3

при объявлении имеет параметром объект p2, поэтому компилятор вызовет

конструктор копии, чтобы создать новый объект из объекта p2.

ВНИМАНИЕ:

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

Не полагайтесь на поверхностный конструктор копии для классов имеющих

данные-указатели.

Деструкторы

Классы С++ могут содержать деструкторы, которые автоматически разрушают объекты класса.

Общий синтаксис объявления деструктора:

class className

{

public:

className(); // конструктор по умолчанию

// другие конструкторы

~className(); // объявление деструктора

// другие функции-элементы

};

Пример 3 на синтаксис обявления деструктора:

class String

{

protected:

char *str;

int len;

public:

String();

String(const String& s);

~String();

// другие функции-элементы

};

Деструкторы в С++ имеют следующие особенности и подчиняются следующим правилам:

1.    Имя деструктора должно начинаться со знака тильды (~), за которым должно следовать имя класса.

2.    Нельзя определять тип возвращаемого значения, даже тип void.

3.    Класс может иметь только один деструктор или ни одного. В последнем случае компилятор создаст деструктор по умолчанию.

4.    Деструктор не должен иметь параметров.

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

(см. LIST8-2.CPP)

Объявление иерархии классов

Производный класс

Общая форма (синтаксис) объявления производного класса:

class classname : [<спецификатор доступа>] parentClass

{

<дружественные классы>

private:

<закрытые элементы-данные>

<закрытые конструкторы>

<закрытые функции-элементы>

protected:

<защищенные элементы-данные>

<защищенные конструкторы>

<защищенные функции-элементы>

public:

<открытые элементы-данные>

<открытые конструкторы>

<открытый деструктор>

<открытые функции-элементы>

<дружественные функции и дружественные операции>

};

Пример 4 объявления класса Rectangle и класса-потомка Box:

class Rectangle

{

protected:

double length;

double width;

public:

Rectangle(double len, double wide);

double getLength() const;

double getWidth() const;

double assign(double len, double wide);

double calcArea();

};

class Вох : public Rectangle

{

protected:

double height;

public:

Box(double len, double wide, double height);

double getHeight () const;

assign(double len, double wide, double height);

double calcVolume();

};

(см. LIST8-3.CPP)

Виртуальные функции

Мы уже упоминали о полиморфизме - важной особенности объектно-

ориентированного программирования. Рассмотрим следующий пример (6):

#include <iostream.h>

class X

{

public:

double A(double x) { return x * x; }

double B(double x) { return A(x) / 2; }

};

class Y : public X

{

public:

double A(double x) { return x * x * x; }

};

int main ()

{

Y y;

cout << y.B(3) << endl;

return 0;

}

В классе X объявляются функции A и B, причем функция B вызывает функцию А. Класс Y, потомок класса X, наследует функцию B, но переопределяет функцию A. Цель этого примера - демонстрация полиморфного поведения класса Y. Мы должны получить следующий результат: вызов наследуемой функции X::B должен привести к вызову функции Y::A. Что же выдаст нам наша программа? Ответом будет 4.5, а не 13.5! В чем же дело? Почему компилятор разрешил выражение y.B(3) как вызов наследуемой функции X::B, которая, в свою очередь, вызывает X::A, а не функцию Y::A, что должно было бы произойти в случае полиморфной реакции класса?

Виртуальные функции объявляются следующим образом (синтаксис):

class className1

{

// функции-элементы

virtual returnType functionName(<список параметров>);

};

class className2 : public className1

{

// функции-элементы

virtual returnType functionName(<список параметров>);

};

Пример 7, показывающий, как при помощи виртуальных функций можно реализовать полиморфное поведение классов X и Y:

#include <iostream.h>

class X

{

public:

virtual double A(double x) { return x * x; }

double B (double x) { return A(x) / 2; }

};

class Y : public X

{

public:

virtual double A(double x) { return x * x * x; }

};

main()

{

Y y;

cout << y.B(3) << endl;

return 0;

}

Этот пример выведет вам правильное значение 13.5, потому что в результате вызова наследуемой функции X::B, вызывающей функцию A, в качестве функции A во время выполнения программы будет использована замещающая функция Y::A.

*** Правило виртуальной функции ***

Правило виртуальной функции гласит:

"Виртуальная однажды - виртуальна всегда".

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

Правило это справедливо и для всех языков объектно-ориентированного программирования, поддерживающих виртуальные функции, но не допускающих перегрузку функций. В С++ положение несколько иное. Вы можете объявлять невиртуальные перегруженные функции, совпадающие по имени с виртуальными функциями, но имеющие другой список параметров. И, кроме того, вы не можете наследовать невиртуальные функции, имя которых совпадает с виртуальными функциями. Рассмотрим пример 8, иллюстрирующий сказанное.

#include <iostream.h>

class A

{

public:

A() {}

virtual void foo(char c)

{ cout << "virtual A::foo() returns " << c << endl; }

};

class B : public A

{

public:

B() {}

void foo(const char* s)

{ cout << "B::foo() returns " << s << endl; }

void foo(int i)

{ cout << "B::foo() retuzns " << i << endl; }

virtual void foo(char c)

{ cout << "virtual B::foo() returns " << c << endl; }

};

class C: public B

{

public:

C() {}

void foo(const char* s)

{ cout << "C::foo() returns " << s << endl; }

void foo(double x)

{ cout << "C::foo() returns " << x << endl; }

virtual void foo(char c)

{ cout << "virtual C::foo() returns " << c << endl; }

};

int main()

{

A Aobj;

B Bobj;

C Cobj;

Aobj.foo('A');

Bobj.foo('B');

Bobj.foo(10);

Bobj.foo("Bobj");

Cobj.foo('C');

Cobj.foo(144.123);

Cobj.foo("Cobj");

return 0;

}

В этом примере вводятся три класса - A, B и C - образующих линейную иерархию наследования. В классе A объявляется виртуальная функция foo(char).

Класс B объявляет свою версию виртуальной функции foo(char), но, кроме того, в классе B объявляются невиртуальные перегруженные функции foo(const char*) и foo(int). Класс C объявляет свою версию виртуальной функции foo(char) и невиртуальные перегруженные функции foo(const char*) и foo(double). Обратите внимание на то, что в классе C приходится заново объявлять функцию foo(const char*), поскольку в данном случае функция-элемент B::foo(const char*) не наследуется. Таким образом, в С++ схема наследования отличается от обычной для случая виртуальной и перегруженных функций с одинаковым именем. В функции main объявляются объекты для всех трех классов и вызываются различные версии функции-элемента foo.

Дружественные функции

В С++ функции-элементы имеют доступ ко всем данным-элементам своего класса. Кроме этого, С++ предусматривает такую возможность еще и для дружественных функций. Объявление дружественной функции производится в объявлении класса и начинается с ключевого слова friend. Кроме наличия спецификатора friend, объявление дружественной функции совпадает с объявлением функции-элемента, однако прямого доступа к классу дружественная функция не имеет, поскольку для этого необходим скрытый указатель this, который ей недоступен. Но если вы передаете такой функции указатель на объект дружественного класса, функция будет иметь доступ к его элементам. Когда вы определяете дружественную функцию вне объявления дружественного ей класса, вам не нужно в определении указывать имя класса. Дружественной называется обычная функция, которой открыт доступ ко всем элементам-данным одного или нескольких классов.

Общий вид (синтаксис) объявления дружественной функции следующий:

class className

{

public:

className();

// другие конструкторы

friend returnType friendFunction(<список параметров>);

};

Пример 9:

class String

{

protected:

char *str;

int len;

public:

String();

~String();

// другие функции-элементы

friend String& append(String &str1, String &str2);

friend String& append(const char* str1, String &str2);

friend String& append(String &str1, const char* str2);

};

Дружественные функции могут решать задачи, которые при помощи

функций-элементов решаются с трудом, неуклюже или не могут быть решены вообще.

Рассмотрим простой пример использования дружественных функций. Текст программы FRIEND.CPP представлен в листинге 8.5. Программа следит за памятью, отведенной для хранения массива символов. Эта программа - первый шаг к созданию класса string.

Операции и дружественные операции

Последняя программа использовала функции-элементы и дружественную функцию, которые реализовали действия, выполняемые в стандартных типах с помощью операций вроде = и +. Подход типичен для языков C и Pascal, потому что эти языки не поддерживают определяемые пользователем операции. В отличии от них C++ позволяет вам объявлять операции и дружественные операции. Эти операции включают в себя: +, -, *, /, %, ==, !=, <=, <, >=, >, +=, -=, *=, /=, %=, [],

(), << и >>. Обратитесь к описанию языка C++, где обсуждаются детали определения этих операций. С++ трактует операции и дружественные операции как специальный тип функций-элементов и дружественных функций.

 Общий синтаксис для объявления операций и дружественных операций:

class className

{

public:

// конструкторы и деструктор

// функции-элементы

// унарная операция

returnType operator operatorSymbol();

// бинарная операция

returnType operator operatorSymbol(operand);

// унарная дружественная операция

friend returnType operator operatorSymbol(operand);

// бинарная дружественная операция

friend returnType operator operatorSymbol(firstOperand, secondOperand);

};

Пример 10:

class String

{

protected:

char *str;

int num;

public:

String();

~String();

// другие функции-элементы

// операция присваивания

String& operator =(String& s);

String& operator +=(String& s);

// операции конкатенации

friend String& operator +(String& s1, String& s2);

friend String& operator +(const char* s1, String& s2);

friend String& operator +(String& s1, const char* s2);

// операции отношения

friend int operator >(String& s1, String& s2);

friend int operator =>(String& s1, String& s2);

friend int operator <(String& sl, String& s2);

friend int operator <=(String& sl, String& s2);

friend int operator ==(String& s1, String& s2);

friend int operator !=(String& sl, String& s2);

};

Код, который вы пишете, будет использовать операции и дружественные операции точно так же, как и предопределенные операции. Следовательно, вы можете создавать операции, чтобы поддерживать действия над классами, моделирующими, например, комплексные числа, строки, векторы и матрицы. Эти операции дают возможность вам записывать выражения в более привычной форме, чем использование вызовов функций.

Виртуальные функции

Мы уже упоминали о полиморфизме - важной особенности объектно-ориентированного программирования. Рассмотрим следующий пример (6):

#include <iostream.h>

class X

{

public:

double A(double x) { return x * x; }

double B(double x) { return A(x) / 2; }

};

class Y : public X

{

public:

double A(double x) { return x * x * x; }

};

int main ()

{

Y y;

cout << y.B(3) << endl;

return 0;

}

В классе X объявляются функции A и B, причем функция B вызывает функцию А. Класс Y, потомок класса X, наследует функцию B, но переопределяет функцию A. Цель этого примера - демонстрация полиморфного поведения класса Y. Мы должны получить следующий результат: вызов наследуемой функции X::B должен привести к вызову функции Y::A. Что же выдаст нам наша программа? Ответом будет 4.5, а не 13.5! В чем же дело? Почему компилятор разрешил выражение y.B(3) как вызов наследуемой функции X::B, которая, в свою очередь, вызывает X::A, а не функцию Y::A, что должно было бы произойти в случае полиморфной реакции класса?

Виртуальные функции объявляются следующим образом (синтаксис):

class className1

{

// функции-элементы

virtual returnType functionName(<список параметров>);

};

class className2 : public className1

{

// функции-элементы

virtual returnType functionName(<список параметров>);

};

Пример 7, показывающий, как при помощи виртуальных функций можно реализовать полиморфное поведение классов X и Y:

#include <iostream.h>

class X

{

public:

virtual double A(double x) { return x * x; }

double B (double x) { return A(x) / 2; }

};

class Y : public X

{

public:

virtual double A(double x) { return x * x * x; }

};

main()

{

Y y;

cout << y.B(3) << endl;

return 0;

}

Этот пример выведет вам правильное значение 13.5, потому что в результате вызова наследуемой функции X::B, вызывающей функцию A, в качестве функции A во время выполнения программы будет использована замещающая функция Y::A.

Правило виртуальной функции

Правило виртуальной функции гласит:

"Виртуальная однажды - виртуальна всегда".

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

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

Рассмотрим пример 8, иллюстрирующий сказанное.

#include <iostream.h>

class A

{

public:

A() {}

virtual void foo(char c)

{ cout << "virtual A::foo() returns " << c << endl; }

};

class B : public A

{

public:

B() {}

void foo(const char* s)

{ cout << "B::foo() returns " << s << endl; }

void foo(int i)

{ cout << "B::foo() retuzns " << i << endl; }

virtual void foo(char c)

{ cout << "virtual B::foo() returns " << c << endl; }

};

class C: public B

{

public:

C() {}

void foo(const char* s)

{ cout << "C::foo() returns " << s << endl; }

void foo(double x)

{ cout << "C::foo() returns " << x << endl; }

virtual void foo(char c)

{ cout << "virtual C::foo() returns " << c << endl; }

};

int main()

{

A Aobj;

B Bobj;

C Cobj;

Aobj.foo('A');

Bobj.foo('B');

Bobj.foo(10);

Bobj.foo("Bobj");

Cobj.foo('C');

Cobj.foo(144.123);

Cobj.foo("Cobj");

return 0;

}

В этом примере вводятся три класса - A, B и C - образующих линейную иерархию наследования. В классе A объявляется виртуальная функция foo(char).

Класс B объявляет свою версию виртуальной функции foo(char), но, кроме того, в классе B объявляются невиртуальные перегруженные функции foo(const char*) и foo(int). Класс C объявляет свою версию виртуальной функции foo(char) и невиртуальные перегруженные функции foo(const char*) и foo(double). Обратите внимание на то, что в классе C приходится заново объявлять функцию foo(const char*), поскольку в данном случае функция-элемент B::foo(const char*) не наследуется. Таким образом, в С++ схема наследования отличается от обычной для случая виртуальной и перегруженных функций с одинаковым именем. В функции main объявляются объекты для всех трех классов и вызываются различные версии функции-элемента foo.

Дружественные функции

В С++ функции-элементы имеют доступ ко всем данным-элементам своего класса. Кроме этого, С++ предусматривает такую возможность еще и для дружественных функций. Объявление дружественной функции производится в объявлении класса и начинается с ключевого слова friend. Кроме наличия спецификатора friend, объявление дружественной функции совпадает с объявлением функции-элемента, однако прямого доступа к классу дружественная функция не имеет, поскольку для этого необходим скрытый указатель this, который ей недоступен. Но если вы передаете такой функции указатель на объект дружественного класса, функция будет иметь доступ к его элементам. Когда вы определяете дружественную функцию вне объявления дружественного ей класса, вам не нужно в определении указывать имя класса. Дружественной называется обычная функция, которой открыт доступ ко всем элементам-данным одного или нескольких классов.

Общий вид (синтаксис) объявления дружественной функции следующий:

class className

{

public:

className();

// другие конструкторы

friend returnType friendFunction(<список параметров>);

};

Пример 9:

class String

{

protected:

char *str;

int len;

public:

String();

~String();

// другие функции-элементы

friend String& append(String &str1, String &str2);

friend String& append(const char* str1, String &str2);

friend String& append(String &str1, const char* str2);

};

Дружественные функции могут решать задачи, которые при помощи

функций-элементов решаются с трудом, неуклюже или не могут быть решены вообще.

Рассмотрим простой пример использования дружественных функций.

Текст программы FRIEND.CPP представлен в листинге 8.5. Программа следит за памятью, отведенной для хранения массива символов. Эта программа - первый шаг к созданию класса string.

Операции и дружественные операции

Последняя программа использовала функции-элементы и дружественную функцию, которые реализовали действия, выполняемые в стандартных типах с помощью операций вроде = и +. Подход типичен для языков C и Pascal, потому что эти языки не поддерживают определяемые пользователем операции. В отличии от них C++ позволяет вам объявлять операции и дружественные операции. Эти операции включают в себя: +, -, *, /, %, ==, !=, <=, <, >=, >, +=, -=, *=, /=, %=, [], (), << и >>. Обратитесь к описанию языка C++, где обсуждаются детали определения этих операций. С++ трактует операции и дружественные операции как специальный тип функций-элементов и дружественных функций.

Общий синтаксис для объявления операций и дружественных операций:

class className

{

public:

// конструкторы и деструктор

// функции-элементы

// унарная операция

returnType operator operatorSymbol();

// бинарная операция

returnType operator operatorSymbol(operand);

// унарная дружественная операция

friend returnType operator operatorSymbol(operand);

// бинарная дружественная операция

friend returnType operator operatorSymbol(firstOperand, secondOperand);

};

Пример 10:

class String

{

protected:

char *str;

int num;

public:

String();

~String();

// другие функции-элементы

// операция присваивания

String& operator =(String& s);

String& operator +=(String& s);

// операции конкатенации

friend String& operator +(String& s1, String& s2);

friend String& operator +(const char* s1, String& s2);

friend String& operator +(String& s1, const char* s2);

// операции отношения

friend int operator >(String& s1, String& s2);

friend int operator =>(String& s1, String& s2);

friend int operator <(String& sl, String& s2);

friend int operator <=(String& sl, String& s2);

friend int operator ==(String& s1, String& s2);

friend int operator !=(String& sl, String& s2);

};

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

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

ИСХОДНЫЕ ТЕКСТЫ ПРИМЕРОВ

(Листинг 8.1. исходный текст программы RECT.CPP

// Программа C++, иллюстрирующая использование класса.

// Программа моделирует прямоугольник.)

// Листинг 8.2. Исходный текст программы ARRAY.CPP

// Программа демонстрируюет использование конструкторов и деструкторов:

// - создает динамический массив (объект),

// - присваивает значения элементам динамического массива,

// - выводит значения элементов динамического массива,

// - удаляет динамический массив.

// Листинг 8.3. Исходный текст программы CIRCLE.CPP

// Простой пример иерархии классов.

// Листинг 8.4. Исходный текст программы VIRTUAL.CPP

// Программа демонстрирует использование виртуальных функций

// для моделирования квадратов и прямоугольников и вывода их

// размеров и площади

ВОПРОСЫ И ОТВЕТЫ

Что случится, если я объявлю конструктор по умолчанию, конструктор копии и другие конструкторы в защищенной области?

Программы, использующие ваш класс, не смогут создавать объекты этого класса. Однако они смогут объявлять классы-потомки с открытыми конструкторами.

Могу я задать цепочку вызовов функций-элементов ?

Да, можете, только если указанные в цепочке функции-элементы возвращают ссылку на тот же самый класс. Например, если в классе String объявлены следующие функции-элементы:

String& upperCase();

String& reverse();

Stringa mapChar(char find, char replace);

вы можете написать следующий оператор обработки объекта класса

String:

s.upperCase().reverse().mapChar(' ', '+');

Что может случиться, если класс полагается на конструктор копии, созданный компилятором, и при этом класс использует указатели в качестве элементов-данных?

Эти конструкторы выполняют побитовую копию объекта. Следовательно, соответствующие элементы-указатели в обоих объектах будут ссылаться на те же самые динамические данные. Этот способ создания копии объекта - верный путь к различным неприятностям.

Могу ли я создавать массив объектов?

Да, можете. Однако соответствующий класс должен иметь заданный по умолчанию конструктор. При создании массива используется ранее упомянутый конструктор.

Могу ли я использовать указатель при создании объекта класса?

Да, можете, но в этом случае вы должны использовать операции new и delete, чтобы распределять и освобождать память для данного объекта.

Вот пример, использующий класс Complex. Не забудьте, что для обращения к элементам классов или структур используется операция ->, если вы ссылаетесь на них при помощи указателей.

Complex *pC;

pC = new Complex;

// операции с объектом, к которому обращаются по указателю pC

delete pC;

или

Complex *pC = new Complex;

// операции с объектом, к которому обращаются по указателю pC

delete pC;

Контрольные вопросы

1. Найдите ошибку в следующем объявлении класса:

class String {

char *str;

unsigned len;

String ();

String(const String& s);

String(unsigned size, char = ' ');

String(unsigned size);

String& assign(String& s);

~String();

unsigned getLen() const;

char* getString();

// другие функции-элементы

};

2. Найдите ошибку в следующем объявлении класса:

class String {

protected:

char *str;

unsigned len;

public:

String();

String(const char* s);

String(const String& s);

String(unsigned size, char = ' ');

String(unsigned size);

~String();

// другие функции-элементы

3. Верно или нет? Следующий оператор, который создает объект s класса String, объявленного ранее, является правильным:

s = String("Hello Borland C++");

4. Если в программе OPERATOR.CPP вы следующим образом измените объявления объектов, будет ли программа компилироваться без ошибок?

String s1 = String("Kevin");

String s2 = String(" Нау");

String s3 = s1;

ФАЙЛОВЫЕ ОПЕРАЦИИ ВВОДА/ВЫВОДА

Сегодняшний урок посвящен файловым операциям ввода/вывода с использованием библиотеки управления потоками C++. У вас есть две возможности: либо использовать функции файлового ввода/вывода, описанные в заголовочном файле STDIO.H, либо функции stream-библиотеки C++. Каждая из этих библиотек имеет множество мощных и удобных функций. Сегодня будут представлены основные операторы, которые позволят вам читать и записывать данные в файл. Вы изучите следующие темы:

Стандартные функции потоков ввода/вывода

-      Последовательный ввод/вывод потока с текстовой информацией

-      Последовательный ввод/вывод двоичных данных

-      Прямой доступ к потоку двоичных данных

Stream-библиотека C++

Stream-библиотека (известная также как библиотека iostream) выполнена в виде иерархии классов, которые описаны в нескольких заголовочных файлах. Файл IOSTREAM.H, используемый до сих пор, - это только один из них. Другой, который будет интересен в этой главе, - FSTREAM.H. Файл IOSTREAM.H поддерживает основные классы для ввода/вывода потока. Файл FSTREAM.H содержит определения для основных классов файлового ввода/вывода.

Существуют дополнительные файлы библиотеки ввода/вывода, в которых имеются более специализированные функции ввода/вывода.

ОБЩИЕ ФУНКЦИИ ПОТОКОВОГО ВВОДА/ВЫВОДА

В этом разделе представлены функции-элементы ввода/вывода, являющиеся общими как для последовательного, так и для прямого доступа. Эти функции включают open, close, good и fail в дополнение к операции !. Функция open открывает файловый поток для ввода, вывода, добавления, а также для ввода и вывода. Эта функция позволяет указывать тип данных, с которыми вы собираетесь работать: двоичные или текстовые.

При работе с файловым вводом/выводом очень важно знать различие между текстовым и двоичным режимами. Текстовый режим предназначен для текстовых файлов, в которых имеются строки обычного текста. Двоичный режим используется для любых других и особенно для файлов, которые сохраняются в форматах, неудобных для чтения человеком.

Существуют некоторые особые тонкости, связанные с файлами текстового режима, на которые следует обратить особое внимание и запомнить. Первая из них - символ EOF (26 в коде ASCII или Ctrl+Z) - представляет собой метку (символ) конца файла. В текстовом режиме, где встречается символ EOF, система C++ низкого уровня автоматически продвигается к концу файла; вы ничего не можете прочитать после специального символа. Это может вызвать проблемы, если такой специальный символ окажется в середине файла.

Другая особенность текстового режима заключается в том, как интерпретируются строки текстового файла. Каждая строка заканчивается последовательностью конца строки (EOL). На компьютерах PC и совместимых с ними EOL-последовательность представлена двумя символами кода ASCII: CR (13 в коде ASCII или Ctrl+M) и LF (10 в коде ASCII или Ctrl+J). Эта CRLF-последовательность используется функциями чтения и записи текстовой строки, которые автоматически, вставляют ее в файл или удаляют из него. Заметьте, что на большинстве других, систем (UNIX и Macintosh) EOF просто является символом LF.

Функция-компонент open

Прототип функции open

void open (const char* filename, int mode, int m = filebuf::openprot);

Параметр filename задает имя открываемого файла. Параметр mode указывает режим ввода/вывода. Далее следует список аргументов для mode, описанных в заголовочном файле FSTREAM.H:

-      in открыть поток для ввода,

-      out открыть поток для вывода,

-      ate установить указатель потока на конец файла,

-      app открыть поток для добавления,

-      trunk удалить содержимое файла, если он уже существует (bc++5),

-      nocreate инициировать ошибку, если уже не существует,

-      noreplace инициировать ошибку, если файл уже существует,

-      binary открыть в двоичном режиме.

Пример 1.

// открыть поток для ввода

fstream f;

f.open("simple.txt", ios::in);

// открыть поток для вывода fstream f;

fstream f;

f.open ("simple.txt", ios::out);

// открыть поток ввода/вывода для двоичных данных fstream f;

fstream f;

f.open("simple.txt", ios::in | ios::out | ios::binary);

Внимание: Классы файловых потоков предусматривают конструкторы, которые выполняют действия (и имеют такие же параметры) функции-компонента open.

Функция close закрывает поток и освобождает использовавшиеся ресурсы. Эти ресурсы включают буфер памяти для операции потокового ввода/вывода.

Функция-компонент close

Прототип для функции close:

void close();

Пример 2.

fstream f;

// открыть поток

f.open ( "simple.txt", ios:: in);

// работа с файлом

// закрыть поток

f.close ();

Stream-библиотека C++ включает в себя набор основных функций, которые контролируют состояние ошибки потоковой операции. Эти функции включают следующие:

1.    Функция good() возвращает ненулевое значение, если при выполнении потоковой операции не возникает ошибки. Объявление функции good: int good();

2.    Функция fail() возвращает ненулевое значение, если при выполнении потоковой операции возникает ошибка. Объявление функции fail: int fail();

3.    Перегруженная операция ! применяется к экземпляру потока для определения состояния ошибки.

Stream-библиотека C++ предоставляет дополнительные функции для установки и опроса других аспектов и типов ошибок потока.

ПОСЛЕДОВАТЕЛЬНЫЙ ТЕКСТОВЫЙ ПОТОК ВВОДА/ВЫВОДА

Функции и операции последовательного текстового ввода/вывода являются довольно простыми. Вы уже имели дело со многими из них в предыдущих уроках. Эти функции и операции включают:

-      Операция извлечения из потока << записывает строки или символы в поток.

-      Операция помещения в поток >> читает символы потока.

-      Функция getline читает строку из потока.

Функция-элемент getline

Прототипы функции-элемента getline:

istream& getline (char* buffer, int size, char delimiter = '\n');

istream& getline (signed char* buffer, int size, char delimiter = '\n');

istream& getline (unsigned char* buffer, int size, char delimiter = '\n');

Параметр buffer - это указатель на строку, принимающую символы из потока. Параметр size задает максимальное число символов для чтения. Параметр delimiter указывает разделяющий символ, который вызывает прекращение ввода строки до того, как будет введено количество символов, указанное в параметре size. По умолчанию параметру delimiter присваивается значение '\n'.

Пример 3.

fstream f;

char textLine[MAX];

f.open("sample.txt", ios::in);

while (!f.eof()) {

f.getline(textLine, MAX);

cout << textLine << endl;

}

f.close();

Рассмотрим пример. В листинге 10.1 приведен исходный код программы TRIM.CPP. Программа выполняет следующие задачи:

-      Выдает запрос на ввод имени входного текстового файла.

-      Выдает запрос на ввод имени выходного текстового файла. (Программа проверяет имена этих файлов на совпадение, и в случае положительного результата повторяет запрос на ввод имени выходного файла).

-      Читает строки из входного файла и удаляет из них <висящие> пробелы.

-      Записывает эти строки в выходной файл и также в стандартное окно вывода.

Листинг 10.1. Исходный код программы TRIM.CPP

 // C++ программа демонстрации последовательного файлового

 // ввода/вывода

Программа в листинге 10.1 не объявляет никаких классов, вместо этого она фокусируется на использовании файловых потоков для ввода и вывода текста. Эта программа описывает функции trimStr, getInputFilename, getOutputFilename, processLines и обязательную функцию main.

Функция trimStr вычищает <висящие> пробелы в строках, передаваемых через параметр s. Эта функция объявляет переменную i и присваивает ей индекс символа, находящегося сразу за завершающим нулем. Функция использует цикл while, начинающийся в строке 14, чтобы выполнить обратное сканирование символов в строке s до первого символа, не являющегося пробелом. Оператор в строке 16 присваивает завершающий нуль символу, стоящему справа от последнего символа, не являющегося пробелом, в строке s.

Функция getInputFilename получает имя входного файла и открывает соответствующий файловый поток. Параметр inFile передает это имя вызывающей функции. Ссылочный параметр f передает открытый входной поток вызывающей функции. Функция getInputFilename объявляет локальный флажок ok и использует цикл do-while (строки с 23 по 34), чтобы открыть входной файл. Строка 25 содержит первый оператор тела цикла, в котором флажок ok инициализируется значением true. Оператор вывода в строке 26 запрашивает ввод имени входного файла; в строке 27 с помощью вызова функции getline это имя принимается и сохраняется в переменной inFile. Оператор в строке 28 пытается открыть входной файл, используя параметр потока f. Оператор open использует значение ios::in для указания на то, что входной текстовый файл был открыт. Если вызов возвращает ошибку, оператор if (строка 29) определит это, сообщит об ошибке открытия файла пользователю и присвоит переменной ok значение false. При значении ok, равном true, цикл do-while будет выполняться и сохранять ответы пользователя до тех, пока не произойдет успешное открытие файла.

Функция getOutputFilename подобна функции getInputFilename в попытках получить имя файла от пользователя и открыть файл. Однако в этом случае файл открывается на запись и его имя сравнивается с именем входного файла. В строке 47 проверяется совпадение имен входного и выходного файлов, и, если пользователь ввел одно и то же имя, ему предлагается снова ввести имя выходного файла.

Функция processLines читает строки их входного файлового потока, приводя их в порядок и записывая в выходной файловый поток. Параметры fin и font передают файловые указатели входного и выходного потоков, соответственно. Эта функция объявляет локальную строковую переменную line и использует (строки с 69 по 74) цикл while для обработки текстовых строк. Предложение while содержит вызов функции getline, которая читает следующую строку входного потока fin и присваивает переменной line содержимое этой строки. В теле этого цикла просто вызывается функция trimStr, а затем line передается в потоки fout и cout. Заметьте, что команды для получения строки текста из файла и передачи ее в файл в точности совпадают с теми, которые могли бы использоваться для получения текста с экрана и передачи его на экран. Это происходит потому, что используемые вами cout и cin - на самом деле только потоки, которые открываются автоматически и работают непосредственно с

экраном.

Функция main, как обычно со всеми уже описанными функциями, довольно простая. Она только объявляет переменные файловых потоков fin, fout и inFile, outFile для сохранения имен этих потоков. Далее входной и выходной файлы открываются в функциях getInputFilename и getOutputFilename. Наконец, функция processLine приводит в порядок и копирует содержимое файла, а функция-компонент close вызывается для каждого из потоков.

ПОСЛЕДОВАТЕЛЬНЫЙ ДВОИЧНЫЙ ФАЙЛОВЫЙ ВВОД/ВЫВОД

Stream-библиотека C++ имеет перегруженные потоковые функции-элементы write и read для последовательного двоичного файлового ввода/вывода. Функция write посылает ряд байт в выходной поток. Эта функция может записывать любую переменную или экземпляр в поток.

Функция-элемент write

Прототип перегруженной функции-элемента:

ostream& write(const char* buff, int num);

ostream& write(const signed char* buff, int num);

ostream& write(const unsigned char* buff, int num);

Параметр buff - это указатель на буфер, содержащий данные, которые будут посылаться в выходной поток. Параметр num указывает число байт в буфере, которые передаются в этот поток.

Пример 4.

const MAX = 80;

char buff[MAX+1] = "Hello World!";

int len = strlen (buff) + 1;

fstream f;

f.open("CALC.DAT", ios::out | ios::binary);

f.write((const unsigned char*) &len, sizeof(len));

f.write((const unsigned char*) buff, len);

f.close();

В этом примере открывается файл CALC.DAT, записывается целое, содержащее число байт в строке и записывается сама строка перед тем, как файл закрывается.

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

Функция-элемент read

Прототип перегруженной функции-элемента read:

ostream& read(char* buff, int num);

ostream& read(signed char* buff, int num);

ostream& read(unsigned char* buff, int num);

Параметр buff - это указатель на буфер, который принимает данные из входного потока. Параметр num указывает число считываемых из потока байт.

Пример 5.

const MAX = 80;

char buff [MAX+1];

int len;

fstream f;

f.open("CALC.DAT", ios::in | ios::binary);

f.read((unsigned char*) &len, sizeof(len));

f.read((unsigned char*) buff, len);

f.close();

В этом примере считывается информация, записанная в предыдущем примере.

Рассмотрим пример, выполняющий последовательный двоичный потоковый ввод/вывод. В листинге 10.2 представлен исходный код программы ARRAY.CPP. Эта программа объявляет класс, который моделирует численный динамический массив. Операции ввода/вывода позволяют программе читать и писать как отдельные элементы массива, так и целый массив в двоичный файл. Эта программа создает массивы arr1, arr2 и аrrЗ, а затем выполняет следующие задачи:

-      Присваивает значения элементам массива arr1. (Этот массив имеет 10 элементов).

-      Присваивает значения элементам массива аrrЗ. (Этот массив имеет 20 элементов).

-      Отображает значения массива arr1.

-      Записывает элементы массива arr1 в файл ARRAY1.DAT (по одному за операцию).

-      Читает элементы массива arr1 из этого файла в массив arr2 (по одному за операцию). (Массив arr2 имеет 10 элементов, то есть он одного размера с массивом arr1).

-      Отображает элементы массива arr2.

-      Отображает элементы массива аrrЗ.

-      Записывает элементы массива аrrЗ в файл ARRAY3.DAT, все сразу.

-      Читает (все сразу) данные из файла ARRAY3.DAT и сохраняет их в массиве arr1.

-      Отображает значения массива arr1. (Выход показывает, что массив arr1 имеет тот же размер, что и массив arr3).

Листинг 10.2. Исходный код программы ARRAY.CPP

// C++ демонстрация последовательного двоичного

 // ввода/вывода

Программа листинга 10.2 объявляет версию класса Array, который похож на приводимый в главе 8 в листинге 8.2. Основное отличие в том, что здесь мы использовали operator[ ] для замены и функции store, и recall. Эта операция проверяет правильность указания индекса и возвращает значение в badIndex, если аргумент выходит за диапазон массива. В дополнение к operator[ ] мы добавили функции-элементы writeElem, readElem, writeArray и readArray для выполнения последовательного двоичного файлового ввода/вывода. Мы также добавили функции resize и getPrt как вспомогательные функции соответственно для изменения размера массива и для возвращения указателя на соответствующий элемент массива.

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

Функция writeElem, определенная в строках с 43 по 49, записывает одиночные элементы массива в выходной поток. Параметр os представляет выходной поток. Параметр index определяет элемент массива для записи. Функция writeElem возвращает true, если индекс правильный и если операция по выводу осуществляется без ошибок. После того, как writeElem записывает элемент массива, внутренний указатель потока продвигается в следующее положение.

Функция readElem, определяемая в строках с 51 по 57, считывает одиночный элемент массива из входного потока. Параметр Is представляет входной поток. Параметр index определяет индекс элемента массива для чтения. Эта функция возвращает true, если индекс массива правильный и если операция по вводу осуществляется без ошибок. После того, как readElem считывает элемент массива, внутренний указатель потока продвигается в следующее положение.

Функции writeElem и readElem позволяют экземпляру класса соответственно писать и читать элементы данных из различных потоков.

Функция writeArray, определенная в строках с 59 по 69, записывает все элементы массива в двоичный файл. Параметр filename определяет имя выходного файла. Функция открывает выходной поток и записывает значение элемента класса size, а затем и элементы динамического массива. Функция writeArray возвращает true, если массив в поток записан успешно. Иначе она возвращает false. Эта функция открывает локальный выходной поток, используя потоковую функцию open и передавая ей имя файла и режим ввода/вывода. Режим ввода/вывода представляет собой выражение ios::out|ios::binary, которое определяет, что поток открывается только для вывода двоичных записей. Эта функция дважды вызывает потоковую функцию write - первый раз для записи компонента класса size, второй - для записи элементов динамического массива.

Функция readArray, определенная в строках с 71 по 83, читает все элементы массива из двоичного файла. Параметр filename определяет имя входного файла. Функция открывает входной поток и считывает значение компонента класса size, а затем считывает элементы динамического массива. Функция readArray возвращает true, если она успешно считывает массив из потока. В противном случае, возвращается false. Функция открывает локальный входной поток, используя потоковую функцию open и передавая ей имя файла и аргументы режима ввода/вывода. Аргумент режима ввода/вывода - это выражение ios::in | ios::binary, которое определяет, что поток открыт только для двоичного ввода. Функция делает два вызова потоковой функции read, первый - для чтения элемента класса size, и второй - для чтения элементов динамического массива. Другим свойством функции readArray является то, что она изменяет размер экземпляра класса Array для настройки его в соответствии с данными двоичного файла, вызывая функцию-элемент resize. Это означает, что динамический массив, который доступен посредством экземпляра класса, может либо уменьшаться, либо расширяться в зависимости от размера массива, сохраняемого в файле.

Функция-элемент resize, которая начинается в строке 65, на самом деле очень простая. Она проверяет, является ли требуемый размер тем же, что и установленный ранее. Если нет, то память, зарезервированная функцией dataPtr, освобождается, а затем создается новая область памяти, соответствующая новому размеру. Этот новый размер присваивается компоненту класса size.

Функция dispArray чаще всего является функцией-элементом, но я решил сделать ее здесь обычной функцией, чтобы лучше показать, как использование функции operator[ ] позволяет тем, кто работает с классом Array, обращаться к нему таким же способом, какой они применяют к элементам стандартного массива. В этом случае есть простой цикл for, который выполняется для каждого элемента arr и отображает его содержимое.

Наконец, мы подходим к функции main (строка 104). Обычно она в основном

только запускает функции, которые уже были созданы, для выполнения

следующих задач:

-      Объявляет (строка 108) три экземпляра класса Array с именами arr1, arr2 и аrr3. (Первые два экземпляра имеют тот же самый размер динамического массива, заданный константой SIZE1, в то время как аrr3 имеет больший размер, определенный константой SIZE2).

-      Объявляет (строка 111) файловый поток f и открывает его (используя конструктор потока) для доступа к файлу ARRAY1.DAT в двоичном режиме.

-      Использует циклы for (строки с 114 по 116), чтобы произвольно присвоить значения экземплярам arr1 и аrr3.

-      Отображает элементы экземпляра arr1 (строка 119).

-      Записывает элементы массива arr1 в выходной файловый поток f, используя цикл for (строка 122) для вызова функции-компонента writeElem с выходным файловым потоком f и переменной цикла i.

-      Закрывает файловый поток f, вызывая функцию-элемент close этого потока.

-      Открывает (строка 127) файловый поток f для доступа к файлу ARRAY1.DAT. (На это раз сообщение open определяет режим двоичного ввода)

-      Считывает элементы в arr2 (которому до сих пор не присваивались никакие значения) из входного файлового потока f, используя цикл for (строка 128).

-      Закрывает входной поток (строка 130). D Отображает элементы экземпляров arr2 и аrr3 (строки 132 и 133).

-      Записывает все содержимое аrr3, вызывая функцию-компонент writeArray. (Функция writeArray имеет аргумент имени файла ARRAY3.DAT).

-      Считывает массив файла ARRAY3.DAT в экземпляр arr1, вызывая функцию-компонент readArray и передавая ей в качестве аргумента имени файла ARRAY3.DAT.

-      Отображает новые элементы экземпляра arr1.

Файловый ввод/вывод с прямым доступом

Файловые операции ввода/вывода прямого доступа также используют потоковые функции-элементы read и write, представленные в предыдущем разделе. Stream-библиотека имеет ряд функций, позволяющих вам передвигать указатель потока в любое необходимое положение. Функция-элемент seekg - одна из таких функций.

Функция-элемент seekg

Прототип для перегруженной функции-компонента seekg:

istream& seekg(long pos);

istream& seekg(long offset, seek_dir dir);

Параметр pos в первой версии определяет абсолютное положение байта в потоке. Во второй версии параметр offset определяет относительное смещение, в зависимости от аргумента dir. Аргументы для последнего параметра:

 ios::beg С начала файла

 ios::cur С текущей позиции файла

 ios::end С конца файла

Пример

const BLOCK SIZE = 80

char buff[BLOCK_SIZE] = "Hello World!";

f.open("CALC.DAT", ios::in | ios::out | ios::binary);

f.seekg(3 * BLOCK_SIZE); // продвинутся к блоку 4

f.read((const unsigned char*)buff, BLOCK_SIZE);

cout < buff < endl;

fclose ();

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

Рассмотрим пример файлового ввода/вывода прямого доступа. В листинге 10.3 приведен исходный код программы VIRTUAL.CPP и реализует виртуальный массив. Программа выполняет следующие задачи:

-      Использует внутренний список имен для создания объекта виртуального массива.

-      Отображает элементы неупорядоченного объекта виртуального массива.

-      Сортирует элементы объекта виртуального массива.

-      Отображает элементы сортированного объекта виртуального массива.

-     

Листинг 10.3. Исходный код прогшраммы VIRTUAL.CPP

// C++ демонстрация файлового ввода/вывода прямого доступа

Программа листинга 10.3 объявляет класс VmArray. Этот класс моделирует динамический базирующийся на диске массив, который сохраняет все его элементы в двоичном файле прямого доступа. Заметьте, что в этом классе объявлен экземпляр класса fstream и что не существует указателя на динамический массив. Класс объявляет конструктор, деструктор и ряд функций-компонентов.

Конструктор класса имеет два параметра: Size и filename. Параметр Size задает размер виртуального массива. Параметр filename именует двоичный файл, который сохраняет элементы экземпляров класса. Конструктор открывает поток f, используя потоковую функцию open и передавая ей в качестве аргументов filename и выражение для режима работы с файлом ios::in | ios::out | ios::binary.

Это выражение указывает, что поток открывается для двоичного ввода/вывода (то есть режима прямого доступа). Если конструктор успешно открывает файловый поток, он заполняет файл пустыми строками. Деструктор класса выполняет простую задачу закрытия файлового потока f.

Функции setElem и getElem поддерживают прямой доступ к элементам массива. Эти функции используют потоковую функцию seekg, чтобы устанавливать указатель потока на соответствующий элемент массива. Затем функция setElem вызывает потоковую функцию write для сохранения элемента массива (передаваемый параметром str). Напротив, функция getElem называет потоковую функцию read, чтобы получить элемент массива (возвращаемый через аргумент str). Обе функции возвращают результат типа bad, который указывает на успешность операции ввода/вывода.

Класс VmArray также объявляет функцию BubbleSort для сортировки элементов виртуального массива. Эта функция использует функции-элементы getElem и setElem для доступа и свопинга элементов массива. Затем, наконец, запускается последняя функция-элемент display для элементов виртуального массива, которая посылает их на экран. Функция main выполняет следующие

задачи:

-      Объявляет экземпляр arr класса VmArray. (Этот экземпляр сохраняет 10 строк в двоичном файле ARR.DAT)

-      Присваивает случайное значение элементам экземпляра аот, используя цикл for (строки 97 и 98).

-      Отображает несортированные элементы экземпляра arr, вызывая функцию-элемент display.

-      Сортирует массив, вызывая функцию BubbleSort.

-      Отображает сортированные элементы экземпляра arr.

Заключение

Сегодняшний урок представил краткое введение в библиотеку ввода/вывода C++ и вынес на обсуждение следующие вопросы:

-      Общие функции ввода/вывода, включая open, close, good, fail и оператор !.

-      Функция open открывает файловый поток ввода/вывода и поддерживает попеременный и множественный режимы ввода/вывода. Функция close закрывает файловый поток. Функции good и fail индицируют успешную или ошибочную, соответственно, потоковую операцию ввода/вывода.

-      C++ позволяет выполнять последовательный потоковый ввод/вывод для текста с использованием операций < и >, так же как и при помощи потоковой функции getline. Операция < позволяет записать символы и строки (а также и другие предопределенные типы данных). Операция > применяется для

-      получения символов. Функция getline позволяет вашему приложению считывать строки с клавиатуры или из текстового файла.

-      Последовательный потоковый ввод/вывод двоичных данных использует потоковые функции write или read для записи или считывания данных из переменных любого типа.

-      Потоковый ввод/вывод прямого доступа для двоичных данных использует функцию seekg в объединении с функциями read и write. Функция seekg позволяет вам передвигать потоковый указатель либо в абсолютное, либо в относительное положение в потоке.

Вопросы и ответы

Как можно эмулировать прямой доступ к строкам в текстовом файле?

Сначала считывайте строки из файла как текст, получайте длину строк (плюс два символа для конца каждой строки) и сохраняйте накапливаемую длину в специальном массиве. (Назовите его, например, lineIndex) Этот массив сохраняет позицию байта, где начинается каждая строка. Последний элемент массива будет содержать размер файла. Для доступа к строке номер i, используйте функцию seek или seekg, чтобы найти смещение для lineIndex[i]. Размер строки номер i равен lineIndex[i+1] - lineIndex[i+1].

Как написать процедуру общего назначения для копирования между входным ивыходным файловым потоком?

Вам необходимо использовать потоковую функцию gcount() для получения ряда байт фактически читаемых в последнем неформатированном потоковом вводе. Вот функция copyStream:

void copyStream(fstreamit fin, fstreamil fout,

unsigned char* buffer, int buffSize)

{

int n;

while (fin. read (buffer, buffaize))

{

n = fin.gcount();

fout.write (buffer, n);

}

}

Практикум Контрольные вопросы

1. Верно или нет? Потоковые функции ввода/вывода read и write способны правильно считывать и записывать данные любого типа.

2. Верно или нет? Потоковые функции ввода/вывода read и write способны правильно считывать и записывать данные любого типа, не имеющих указателей.

3. Верно или нет? Функции seek и seekg расширяют файл, когда вы передаете индекс, который на один или более байт превышает текущий конец файла.

4. Верно или нет? Аргументы функций seek и seekg не требуют проверки диапазона.

Упражнение

Создайте программу VSEARCH.CPP, модифицируя программу VIRTUAL.CPP. Класс VmArray в VSEARCH.CPP должен иметь функцию binSearch, которая проводит двоичный поиск в элементах сортированного массива. Добавьте цикл в конец функции main для поиска в массиве arr, используя неупорядоченные данные инициализирующего списка. (Элементы этого списка доступны при использовании данных-указателей.)


Информация о работе «Лекции по C++»
Раздел: Информатика, программирование
Количество знаков с пробелами: 229074
Количество таблиц: 6
Количество изображений: 0

Похожие работы

Скачать
20747
1
0

... (изложение), аналитический (что мы узнали? Как это можно применить в своих знаниях, в практике, в дальнейшей жизни?…), дискуссионный методы («Порассуждаем?»). Дидактические требования к лекции, методика её подготовки и чтения. Безусловно, помимо таких составляющих, как личность учителя, знание им предмета, эрудиция, на результат, итоговый выход проведения лекции, будет оказывать её основная ...

Скачать
33311
7
3

... вес, объем, урожайность. Абсолютная шкала является безразмерной шкалой отношений. Тема 2. Вариационные ряды. Пример 1. Приведем оценки 45 студентов по курсу статистика в порядке сдачи экзамена: 5 3 3 4 2 4 4 3 5 4 4 5 5 4 4 3 3 3 2 5 5 4 4 4 3 4 3 4 5 4 4 4 4 3 3 4 3 4 3 2 3 2 3 3 3 При таком представлении информации трудно делать какие-либо выводы об успеваемости. Произведем группировку данным ...

Скачать
18566
0
0

... подводит итоги. Выбор варианта лекции определяется образовательными целями, индивидуальным стилем преподавателя. Чтобы получить обратную связь, понять, что думают, чувствуют студенты, преподавателю следует искать способ активизации аудитории, мотивирования студентов к дальнейшему обучению. Выводы Лекция – это важнейшая форма обучения, характеризуемая логически стройным, последовательны

Скачать
41331
2
0

... – 5); ·           принцип приоритета общечеловеческих ценностей перед профессиональными (4). Очевидно, что данное деление условно, но оно удобно для классификации. Заключение Лекции присущи три основные педагогические функции, которые определяют ее возможности в учебном процессе: познавательная, развивающая и организующая. Познавательная функция выражается в возможности средствами лекции ...

0 комментариев


Наверх