Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Курсовые / Язык программирования Сpp 25.09.11.doc
Скачиваний:
114
Добавлен:
10.05.2015
Размер:
10.13 Mб
Скачать

5.3. Манипуляторы

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

Приведем пример:

cout<<”Пример манипулятора”<<endl;

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

Некоторые манипуляторы без параметров описаны в следующей таблице.

Манипулятор

Назначение

Функция

dec

Устанавливает флаг десятичной системы счисления.

При вводе и выводе

hex

Устанавливает флаг шестнадцатеричной системы счисления.

При вводе и выводе

oct

Устанавливает флаг восьмеричной системы счисления.

При вводе и выводе

ws

Пропускает пробельные символы – пробел, знаки табуляции ‘\t’ и ‘\v’, символ перевода строки ‘\n’, символ возврата каретки ‘\r’, символ перевода страницы ‘\f’.

Действует только при выводе.

endl

Обеспечивает включение в выходной поток символа новой строки и выгружает содержимое этого потока.

Действует только при выводе.

ends

Вставляет в поток нулевой символ ‘\0’, что соответствует признаку конца строки.

Действует только при выводе.

flush

“Сбрасывает” поток, т.е. переписывает содержимое буфера, связанного с потоком , на соответствующее устройство.

Действует только при выводе.

Здесь полезно заметить, что манипуляторы endl и ends играют важную роль при выводе. Без них нельзя гарантировать, что информация не останется в буфере.

5. Указатели

Познакомимся с еще одним типом переменных – это указатель. Указатель позволяет работать с памятью компьютера.

Как вы знаете, память компьютера образуют пронумерованные восьмиразрядные байты, в которых хранятся значения переменных. Причем каждая переменная может занимать несколько соседних байтов в зависимости от ее типа. Эти несколько байтов называются ячейкой. Номер первого байта ячейки называется ее адресом. Поэтому адреса двух переменные, хранящиеся в соседних ячейках памяти,могут отличаться на единицу - для символьных переменных, на 4 - для целых типаint, на 8 - для типаdouble, и вообще говоря, на практически любое целое число, если это массив, структура или переменная иного типа созданного программистом.

На заре появления вычислительных машин, когда языков программирования, в том виде, в котором мы знаем их сейчас, не было, программисты работали именно с адресами. То есть, каждый оператор программы представлял собой код, состоящих из нулей и единиц, который соответствовал командам типа взять число из памяти с адресом n, поместить число по адресуm, выполнить операцию p и т.п. Такой подход сохранился до сих пор в языках низкого уровня типа ассемблера. Конечно, писать всю программу на подобном языке сложно, но определенные преимущества в этом есть. Например, нужно создать программу работы с массивом, размеры которого заранее неизвестны, или изменить размеры уже созданного массива. В таких задачах можно указать адрес, с которого начинается продолжение массива. Подобные примеры можно продолжить. Вы уже вероятно поняли, что если такой подход в программировании имеет преимущества, то он реализован в С++.

Вся память компьютера делится на несколько кусков или как принято говорить сегментов. Байты с младшими адресами отданы для операционной системы, которая управляет компьютером. После этого идет сегмент, в котором хранится программа, включая значения всех ее переменных и функций. За тем идет сегмент, выделенный для хранения статических и внешних переменных. Дальше идет свободная память, ее программа может использовать в своих целях. Занятую часть свободной памяти принято называть кучей (heap). Таким образом, куча это присоединенная к программе память. Этот сегмент памяти принято называть также динамической памятью. Переменные, значения которых хранятся в динамической памяти, называются динамическими переменными.

Операционная система

Код программы

Статические и внешние данные

Куча

Свободная оперативная память

Стек

Неиспользуемая память

Буферы, ПЗУ, видеопамять

Младшие адреса

Старшие адреса

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

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

Указатель – это переменная, которая хранит адрес первого байта другой переменной или функции, вообще говоря, указатель хранит адрес любого объекта, но о таких указателях мы будем говорить позже. Естественно, что значение указателя, это целое число. Но не нужно думать, что переменная типа указатель это то же самое, что переменная типаint. В противном случае с указателями можно было бы осуществлять математические операции, результаты которых были бы заведомо неверными. Указатель это переменная специального типа, причем каждый указатель может работать только с переменными своего типа. Например, указатель объявленный для чисел типаdoubleне может применяться к другим типам . При объявлении, указателям должны быть даны имена. Для имен указателей применяются обычные правила, например, указатель можно назвать словомpointer.Но такое имя может иметь переменная. Для того, что бы программа при объявлении не перепутала имена указателей и переменных впереди имени указателя ставится знак звездочка '*', т.е синтакс указателей таков:

тип [модификатор] * имя_указателя;

В данном случае:

тип– имя типа переменной, адрес которой будет содержать переменная указатель (на которую он будет указывать), напримерcharилиint;

имя– идентификатор переменной типа указатель;

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

Например,

int *pointer1, x1;

double * pointer2, x2;

char *pointer3;

Такая запись означает, объявление 5 переменных. Переменная x1 – будет содержать целое число тиаint,x2-дробное типаdouble. Кроме того, объявлены 3 переменные типа указатель, о чем свидетельствует звездочка перед их именами. На самом деле имена указателей этоpointer1, pointer2, pointer3. Звездочка является признаком указателя, и не входит в имя. Иногда, для того, чтобы выделить имена указателей используются круглые скобки.

int (*pointer1), x1;

double (* pointer2), x2;

char (*pointer3);

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

Первый указатель рассмотренного примера, pointer1,предназначен для работы с переменными типаint, второй с переменными типаdouble, третий с переменными типаchar. Существует более общий тип указателей с типомvoid *. Указатель типаvoid *отличается от указателей других типов тем, что для него не указывается размер соответствующего ему участка памяти.

Язык С++ дает возможность использования адресов переменных с помощью унарных операций &и*. Операции&и*. можно писать вплотную с именем операнда или через пробел. Для определения адреса переменной используется оператор получения адреса -&. Да и вообще когда появляется оператор & его можно читать как слово адрес, так, например,

pointer1=&x1;

pointer2=&x2;

означает, что указателю pointer1следует присвоить адрес переменнойx1, указателюpointer2адресx2. Теперь нам известны адресаx1иx2, но как с ними работать? Например, как изменить в ячейках с этими адресами значения переменных. Ответ прост. Для этого есть специальный операторы – звездочка '*'. Звездочка это оператор, который читается так –значениепеременной на которую указывает… далее имя указателя. Например,

*pointer1=2; /* значение переменной на которую указывает

* указатель pointer1 присвоить число равное 2 */

*pointer2=3;

Это все равно, что написать

x1=2;

x2=3;

Теперь к переменным x1иx2можно обращаться не только через имена, но и через указатель.

Оператор звездочка, с помощью которого переменной x1было присвоено новое значение, называетсяоператором разыменовывания или косвенной адресации, а переменная-указательразыменованой. По-существу разыменовывание означает для переменной получение второго имени, а не потерю первого, как это могло показаться. В рассмотренном примереx1 и *pointer1это одна и та же переменная. То же самое можно сказать оx2 и *pointer2.

Приведем для примера некоторую законченную программу с использованием указателей.

#include <iostream>

using namespace std;

int main(){

int *pointer1, x1,x2;

x1=1;

pointer1 =&x1; //указателю присвоить адрес x1

*pointer1 =2; /*переменной на которую указывает pointer1 присвоить

значение равное 2 */

x2=3*(*pointer1+5); /* разыменнованная переменная может

быть операндом */

cout<< "\n"<<x1; //Вывести x1

cout<<"\n"<< *pointer1; //Вывести x1, но с помощью указателя

cout<<"\n"<<x2; //Вывести x2

cout<< "\n"<<pointer1; //вывести адрес записанный в указатель

return 0;

}

на экране появится

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

Указатели можно инициализировать при их объявлении, например:

int ir;

int *pir=&ir //здесь инициализирован указатель, но не переменная

Как и любая переменная, указатель хранится в памяти, т.е. имеет адрес и значение. Адрес и значение такой переменной определяются с помощью все тех же унарных операций &и*. Переменная типа указатель занимает в памяти 2 байта.

Говоря об указателях, все время встречается слово переменная. Это не значит, что нельзя применять указатели к константам. Вот пример объявления указателя для константы:

string str="Hello!";

string *const pstr=&str;

При объявлении указателя он сам может быть объявлен константой

char const *const name;

Рассмотрим еще один пример иллюстрирующий возможности указателей.

#include<iostream>

#include<string>

using namespace std;

void main(){

char *psz="Dog Motja"; /*В указателе будет храниться адрес

первого символа строки */

for(int i=strlen(psz)-1;i>=0;i--)/*функция strlen вычисляет длину

строки, описана в string. Длина строки в этом примере (string)=9 .

Нумеруются элементы массива от 0 т.е. в этом случае от 0 до 8*/

cout<<psz[i];

cout<<endl<<psz;

int z;

cin>> z;

}

А вот результат

Из данного примера следует, что оператор & позволяет определить адрес первой ячейки переменной. Зная длину строки можно вывести отдельные элементы символьного массива.

Рассмотрим случай неправильного использования оператора &.

pic=&48; //число 48 не связано ни с одной ячейкой памяти

int ir=5;

pir=&(ir+15); /*арифметическое выражение не связано

с ячейкой памяти */

Поскольку указатели позволяют работать с памятью, то возникает вопрос, а нельзя ли в память сразу загружать значения переменных, минуя их объявление. Ответ – можно, ведь переменная, и её указатель, это одно и тоже. Делается это с помощью оператора new, например, нужно ввести новую переменную с помощью определенного заранее указателя. Фрагмент кода выглядит следующим образом

int *point; /*объявили указатель для переменной типа int,

* т.е. предупредили компилятор, что в программе

* будут использованы указатели с перечисленными

* именами – в данном случае одно */

point=new int; /*просим для переменной типа int дать

* свободный адрес.В дальнейшем к этой

* переменной будем обоащаться через указатель */

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

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

#include <iostream>

using namespace std;

int main(){

int *point1, *point2; //объявление переменных типа

point1=new int;/*выделение места в памяти для переменной

*типа int. Администатор кучи выделит место и

* запишет адрес в point1, т.е. инициализирует

* указатель */

*point1=1; /* переменной на которую указывает point1

* присвоить значение равное 1

point2=point1; /*обратите внимание указателю присваивается

* значение другого указателя, подобно присвоению

* переменных a=b, в итоге оба указателя имеют

* одинаковый адрес */

cout<<"\n *point1="<<*point1;

cout<<"\n *point2="<<*point2;

*point1=2; //новое значение динамической переменной

cout<<"\n *point1="<<*point1;

cout<<"\n *point2="<<*point2;

point1=new int; //новое место в памяти для point1

*point1=3; //новое значение динамической переменной

cout<<"\n *point1="<<*point1;

cout<<"\n *point2="<<*point2;

return 0;

}

В результате выполнения программы на экране должно появиться следующее.

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

При объявлении указателей приходится несколько раз пользоваться звездочкой, например

int * p1,*p2,*p3,*p4;

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

typedef int* point; //создать новый тип указателей с именем point

point p1,p2,p3,p4; //объявление переменных типа point

Первую строку можно прочитать так. Объявляется новый тип указателя для переменных типа int.Далее можно читать так - объявляются переменные типаpoint, эти переменные имеют именаp1,p2,p3,p4. Две строки рассмотренного кода эквивалентны одной, содержащей звездочки.

Для хранения динамических переменных отведен специальный раздел памяти, который называется кучей. Размер кучи указать нельзя, он зависит от памяти компьютера, операционной системы, компилятора и многих других факторов. Если для динамических переменных не хватает размера кучи, то программа останавливается с выводом сообщения об ошибке. Для того, чтобы уменьшить вероятность переполнения кучи нужно освобождать зарезервированную память от динамических переменных, которые далее не используются в программе. С этой целью применяется операторdeleteс указанием на динамическую переменную. Например,

delete point1;

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