Ключевое слово using как директива

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

#include <iostream.h>

namespace NAME

{ int n1=1;

int n2=2;

}

// int n1; приводит к неоднозначности в main для переменной n1

int main()

{ NAME::n1=3;

// n1=3; // error 'n1' : undeclared identifier

// n2=4; // error 'n2' : undeclared identifier

using namespace NAME; // далее n1 и n2 доступны

n2=4;

cout << n1 <<" "<< n2 << endl; // результат 3 4

{ n1=5;

n2=6;

cout << n1 <<" "<< n2 << endl; // результат 5 6

}

return 0;

}

В результате выполнения программы получим:

3 4

5 6

Область действия директивы using распространяется на блок, в котором она использована, и на все вложенные блоки.

Если одно из имен относится к глобальной области, а другое объявлено внутри пространства имен, то возникает неоднозначность. Это проявится только при использовании этого имени, а не при объявлении.

#include <iostream>

using namespace std;

В данном фрагменте стандартный заголовочный файл библиотеки ввода/вывода iostream, не имеет расширения. Содержимое этого файла все определяется как часть namespace std.

Для достижения переносимости рекомендуется использовать директиву using, хотя и существуют компиляторы, не поддерживающие данную возможность. Основная проблема, которую призвана решить такая конструкция - это независимость от ограничения на длину имени файла в различных операционных системах. Более того, компиляторы Microsoft последних версий вообще не поддерживают вариант с подключением файлов стандартной библиотеки с расширением .h (т.е конструкция #include <iostream.h> в Visual C++ 7.1 не компилируется).

Ключевое слово using как объявление

Объявление using имя::член подобно директиве, при этом оно обеспечивает более подробный уровень управления. Обычно using используется для объявления некоторого имени (из пространства имен) как принадлежащего текущей области действия (блоку).

#include <iostream.h>

namespace NAME

{ int n1=1;

int n2=2;

}

int main()

{ NAME::n1=3;

// n1=4; error 'n1' надо указывать полностью NAME::n1

// n2=5; error 'n2' : undeclared identifier

// int n2; следующая строка приводит к ошибке

using NAME::n2; // далее n2 доступно

n2=6;

cout <<NAME::n1<<" "<< n2 << endl; // результат 3 6

{ NAME::n1=7;

n2=8;

cout <<NAME::n1<<" "<< n2 << endl;// результат 7 8

}

return 1;

}

В результате выполнения программы получим:

3 6

7 8

Объявление using добавляет определенное имя в текущую область действия. В примере к переменной n2 можно обращаться без указания принадлежности классу, а для n1 необходимо полное имя. Объявление using обеспечивает более подробное управление именами, переносимыми в пространство имен. Это и есть ее основное отличие от директивы, которая переносит все имена пространства имен.

Внесение в локальную область (блок) имени, для которого выполнено явное объявление (и наоборот), является серьезной ошибкой.

Псевдоним пространства имен

Псевдоним пространства имен существует для того, чтобы назначить другое имя именованному пространству имен.

namespace spisok_name_peremen // пространство имен

{ int n1=1;

. . .

}

NAME = spisok_name_peremen; // псевдоним пространства имен

Пространству имен spisok_name_peremen назначается псевдоним NAME. В этом случае результат выполнения инструкций:

cout <<NAME::n1<< endl;

cout<< spisok_name_peremen::n1<<endl;

будет одинаков.

Организация ввода-вывода

Системы ввода-вывода С и С++ основываются на понятии потока. Поток в С++ это абстрактное понятие, относящееся к переносу информации от источника к приемнику. В языке С++ реализованы 2 иерархии классов, обеспечивающих операции ввода-вывода, базовыми классами которых являются streambuf и ios. На рис.6 приведена диаграмма классов, базовым для которых является ios (рис. 6).

Ключевое слово using как директива - student2.ru В С++ используется достаточно гибкий способ выполнения операций ввода-вывода классов с помощью перегрузки операторов << (вывода) и >> (ввода). Операторы, перегружающие эти операции, обычно называют инсертером и экстрактором. Для обеспечения работы с потоками ввода-вывода необходимо включить файл iostream.h, содержащий класс iostream. Этот класс является производным от ряда классов, таких как ostream, обеспечивающего вывод данных в поток, и istream - соответственно чтения из потока. Приводимый ниже пример показывает, как можно перегрузить оператор ввода-вывода для произвольных классов.

#include "iostream.h"

Class cls

{ char c;

short i;

public :

cls(char C,short I ) : c(C), i(I){}

~cls(){}

friend ostream &operator<<(ostream &,const cls);

friend istream &operator>>(istream &,cls &);

};

ostream &operator<<(ostream &out,const cls obj)

{ out << obj.c<<obj.i << endl;

return out;

}

istream &operator>>(istream &in,cls &obj)

{ in >> obj.st>>obj.i;

return in;

}

main()

{ cls s(’a’,10),ss(’ ’,0);

сout<<"abc"<<endl;

cout<<s<<ss<<endl;

cin >> ss;

return 0;

}

Общая форма функции перегрузки оператора ввода-вывода имеет вид:

istream &operator>>(istream &поток,имя_класса &объект)

ostream &operator<<(ostream &поток,const имя_класса объект)

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

cout <<s<<ss;

Это соответствует

(cout.operator<<(a)).operator<<(b);

В приведенном примере функция operator не является компонентом класса cls, так как левым аргументом (в списке параметров) такой функции должен быть this-указатель для объекта, инициирующего перегрузку. Доступ к private-данным класса cls осуществляется через friend-функцию operator этого класса.

Рассмотрим использование перегрузки операторов << и >> для определения новых манипуляторов. Ранее мы рассмотрели использование стандартных манипуляторов форматирования выводимой информации. Однако можно определить и новые манипуляторы без изменения стандартных. В качестве примера рассмотрим переопределение манипулятора с параметрами, задающего для выводимого дробного числа ширину поля и точность.

#include<iostream.h>

Class manip

{ int n,m;

ostream & (*f)(ostream&,int,int) ;

public:

manip(ostream& (*F)(ostream&,int,int), int N, int M) :

f(F), n(N), m(M) {}

friend ostream& operator<<(ostream& s, const manip& obj)

{return obj.f(s,obj.n,obj.m);}

};

ostream& f_man(ostream & s,int n,int m)

{ s.width(n);

s.flags(ios::fixed);

s.precision(m);

return s;

}

manip wp(int n,int m)

{ return manip(f_man,n,m);

}

void main(void)

{ cout<< 2.3456 << endl;

cout<<wp(8,1)<<2.3456 << endl;

}

Компонента-функция put и вывод символов

Компонента-функция ostream::put() используется для вывода одиночного символа:

char c=’a’;

. . .

cout.put(c);

Вызовы функции put() могут быть сцеплены:

cout.put(c).put(’b’).put(’\n’);

в этом случае на экран выведется буква а, затем b и далее символ новой строки.

Компоненты-функции get и getline для ввода символов.

Функция istream::get() может быть использована в нескольких вариантах.

Первый вариант – функция используется без аргументов. Вводит символ из соответствующего потока одиночный символ и возвращает его значение. Если из потока прочитан признак конца файла, то get возвращает EOF.

#include<iostream.h>

main(void)

{ char c;

cout << сin.eof()<< "вводите текст" << endl;

while((c=cin.get())!=EOF)

cout.put(c);

cout << endl<<cin.eof();

return 0; }

В программе считывается из потока cin очередной символ и выводится с помощью функции put. При считывании признака конца файла завершается цикл while. До и после цикла выводится значение cin.eof(), равное false (выводится 0) до начала цикла и true (выводится 1) после его окончания.

Второй вариант – когда функция get() используется с одним символьным аргументом. Функция возвращает false при считывании признака конца файла, иначе - ссылку на объект класса istream, для которого вызывалась функция get.

. . .

while(сin.get(с))

cout.put(c);

. . .

При третьем варианте функция get() принимает три параметра: указатель на символьный массив (строку), максимальное число символов и ограничитель ввода (по умолчанию ’\n’). Ввод прекращается, когда считано число символов на один меньшее максимального или считан символ-ограничитель. При этом в вводимую строку добавляется нуль-символ. Символ-ограничитель из входного потока не удаляется, это при повторном вызове функции get приведет к формированию пустой строки.

сhar s[30];

. . .

сin.get(s,20)) // аналогично cin.get(s,20,’\n’)

cout<<s<<endl;

. . .

Функция isteram::getline() действует аналогично функции get() с тремя параметрами с тем отличием, что символ-ограничитель удаляется из входного потока.

Ниже коротко рассмотрены другие функции-компоненты класса istream.

Состояние потока

Потоки istream или ostream имеют связанное с ними состояние. В классе ios, базовом для классов istream и ostream, имеется несколько public функций, позволяющих проверять и устанавливать состояние потока:

inline int ios::bad() const { return state & badbit; }

inline int ios::eof() const { return state & eofbit; }

inline int ios::fail() const { return state & (badbit | failbit); }

inline int ios::good() const { return state == 0; }

Состояние потока фиксируется в элементах перечисления, объявленного в классе ios:

public:

enum io_state { goodbit = 0x00,

eofbit = 0x01,

failbit = 0x02,

badbit = 0x04 };

Кроме отмеченных выше функций в классе ios есть еще несколько функций, позволяющих прочитать (rdstate) и очистить (clear) состояние потока:

inline int ios::rdstate() const { return state; }

inline void ios::clear(int _i=0){ lock(); state = _i; unlock(); }

Так, если было установлено состояние ошибки, то попытка выполнить ввод/вывод будет игнорироваться до тех пор, пока не будет устранена причина ошибки и биты ошибки не будут сброшены функцией clear().

#include "iostream.h"

main()

{ int i, flags;

char c;

do{ cin >> i;

flags=cin.rdstate(); // чтение состояния потока cin

if(flags & ios::failbit)

{ cout << "error in stream"<< endl;

cin.clear(0); // сброс всех состояний потока

cin>>c; // удаление не int значения из потока

}

else cout << i<<endl;

} while(flags); // пока ошибка во входном потоке

return 0;

}

В приведенном примере функция cin.clear(0) выполняет сброс всех установленных бит ошибки. Если требуется сбросить, например, только badbit, то clear(ios::badbit). На примере ниже показано, что состояние потока может быть проанализировано также при работе с файлами.

#include "fstream.h"

#include "iostream.h"

main()

{ ifstream in("file");

int state;

char ss[10];

while(1)

{ state=in.rdstate(); // чтение состояния потока in (файла)

if (state) // ошибка в потоке

{ if(state&ios::badbit) cout<<"ошибка открытия файла"<<endl;

else if(state&ios::eofbit) cout<<"в файле больше нет данных"<<endl;

return 0;

}

else in >> ss;

}

}

Необходимо также отметить, что в классе ios выполнена перегрузка операции ! :

inline int ios::operator!() const { return state&(badbit|failbit); }

то есть операция ! возвращает ненулевое значение в случае установки одного из бит badbit или failbit, иначе нулевое значение, например:

if(!cin) cout << ”ошибка потока cin”<<endl; // проверка состояния

else cin>>i; // входного потока

Строковые потоки

Особой разновидностью потоков являются строковые потоки, представленные классом strstream:

class strstream : public iostream

{ public:

strstream();

strstream(char *s, streamsize n,

ios_base::openmode=ios_base::in | ios_base::out);

strstreambuf *rdbuf() const;

void freeze(bool frz=true);

char *str();

streamsize pcount() const;

};

Важное свойство класса strstream состоит в том, что в нем автоматически выделяется требуемый объем памяти для хранения строковых данных. Все операции со строковыми потоками происходят в памяти в специально выделенном для них буфере strstreambuf. Строковые потоки позволяют облегчить формирование данных в памяти. В примере демонстрируется ввод данных в буфер, копирование их в компоненту s класса string и их просмотр.

#include "strstrea.h"

Class string

{ char s[80];

public:

string(char *S) {for(int i=0;s[i++]=*S++;);}

void see(){cout<<s<<endl;}

};

void fun(const char *s)

{ strstream st; // создание объекта st

st << s << ends; // вывод данных в поток (в буфер)

string obj(st.str()); // создание объекта класса string

st.rdbuf()->freeze(0); // освобождение памяти в буфере

obj.see(); // просмотр скопированной из буфера строки

}

main()

{ fun("1234");

}

Вначале создается объект st типа strstream. Далее при выводе переданной в функцию символьной строки в поток добавлен манипулятор ends, который соответствует добавлению ’\0’. Функция str() класса strstream обращается непосредственно к хранимой в буфере строке. Следует отметить, что при обращении к функии str() объект st перестает контролировать эту память и при уничтожении объекта память не освобождается. Для ее освобождения требуется вызвать функцию st.rdbuf()->freeze(0).

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

#include "strstrea.h"

void fun(const char *s,int n)

{ char *buf=new char[n]; // входной буфер

strstream st(buf,n,ios::in|ios::out); // связывание потока st и буфера byf

st << s << ends; // ввод информации в буфер

if(st.good()) cout << buf<<endl; // проверка состояния потока

else cerr<<"error"<<endl; // вывод сообщения об ошибке

}

main()

{ fun("123456789",5);

fun("123456789",15);

return 1;

}

В результате выполнения программы получим:

error

Наши рекомендации