Ввод текстовых строк с клавиатуры 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Ввод текстовых строк с клавиатуры



Вывод текстовых строк на экран сложностей не вызывает:

 

char Str[21] = “Это пример текста”;

cout << setw(40) << right << Str << endl;

cout << “Это текстовый литерал.” << endl;

 

При выводе строк можно использовать форматирование (манипуляторы или функции потока вывода).

Ввод текста с клавиатуры можно осуществить разными способами, каждый из которых имеет определенные особенности.

 

Непосредственное чтение текстовых строк из потока вывода:

 

char Str[21];

cin >> Str; // Пусть введена строка “Это пример текста”

cout << Str << endl; // На экран будет выведено толькослово “Это”

 

Как видно, всю введенную строку текста прочитать не удалось. Такой способ чтения (с помощью оператора >>) обеспечивает ввод символов до первого пробельного символа (по словам), а не до конца строки. Остальные символы введенного с клавиатуры предложения остаются в потоке ввода и могут быть прочитаны из него следующими операторами >>.

Для того чтобы прочесть всю строку полностью, можно воспользоваться одной из функций gets или gets_s (для этого в программу должен быть включен заголовочный файл <stdio.h>):

 

const int N = 21;

char Str [N];

gets (Str); // Пусть введена строка “Это пример текста”

// gets_s (Str, N); Альтернативный вариант

cout << Str << endl; // На экран будет выведено “ Это пример текста”

 

Функция gets имеет один параметр, соответствующий массиву символов, в который осуществляется чтение. Вторая функция (gets_s) имеет второй параметр, задающий максимальную длину массива символов Str.

Не смотря на то, что функция gets_s (в отличие от gets) позиционируется как безопасная, и та и другая при вводе текста, длина которого (вместе с нулевым символом) превышает значение второго параметра (то есть длины символьного массива Str), приводит к возникновению ошибки при выполнении программы

Более предпочтительным, по-видимому, является использование функции потока ввода cin.getline:

 

const int N = 21;

char Str [N];

cin.getline (Str, N); // Пусть введена строка “Это пример текста”

cout << Str << endl; // На экран будет выведено “ Это пример текста”

 

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

Если длина введенного с клавиатуры текста превышает максимальную длину массива Str, в него будет записано (в нашем примере) 20 символов вводимого текста и нулевой символ. Остальные символы введенного текста остаются во входном потоке и могут быть взяты из него следующими инструкциями ввода.

Рассмотри следующий пример:

 

const int N = 11;

char Str [N];

cin.getline (Str, N); // Пусть введена строка “Это пример текста”

cout << Str << endl; // На экран будет выведено “Это пример” – 10 символов

cin.getline (Str, N); // Ожидается чтение остальных символов: “ текста”

cout << Str << endl; // Однако на экран будет выведена пустая строка

 

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

 

const int N = 11;

char Str [N];

cin.getline (Str, N); // Пусть введена строка “Это пример текста”

cout << Str << endl; // На экран будет выведено “Это пример” – 10 символов

cin.clear(); // Сбрасываем состояние потока в норму

cin.getline (Str, N); // Не останавливаясь дочитываем оставшиеся в потоке символы

cout << Str << endl; // На экран выведено: “ текста”

 

Если забирать остатки данных из потока ввода не надо, то следует очистить его с помощью функции cin.sync():

 

const int N = 11;

char Str [N];

cin.getline (Str, N); // Пусть введена строка “Это пример текста”

cout << Str << endl; // На экран будет выведено “Это пример” – 10 символов

cin.clear(); // Сбрасываем состояние потока в норму

cin.sync(); // Очищаем поток от оставшихся символов

cin.getline (Str, N); // Ждем очередного ввода данных. Введено: “Слово”

cout << Str << endl; // На экран выведено: “Слово”

 

Правильное использование функций cin.clear() и cin.sync() является очень существенным фактором при организации консольного ввода данных.

Функция cin.getline может иметь третий параметр, задающий символ, при встрече которого чтение строки из потока прекращается. Например:

cin.getline (Str, N, ‘,’);

При таком использовании этой функции чтение из потока будет прекращено, если из него прочитано N – 1 символов, или встретился символ конца строки ввода, или встретился символ ‘,’.

Обработка текстовых строк

При обработке текстовых строк обычно используется набор типовых операций, к которым можно отнести:

· определение фактической длины текста записанного в символьный массив;

· копирование текста из одной строки в другую;

· объединение двух строк;

· лексикографическое сравнение строк – в алфавитном порядке (больше, меньше, равно)

и др.

 

Написать соответствующие функции обработки достаточно просто. Вот два примера:

 

Пример 1: Функция определения фактической длины строки (с нулевым символом):

 

unsigned my_StrLen(char *S)

{

unsigned L = 0;

while (S[L]) // При достижении символа с числовым значением 0 выход из цикла

++L;

return L;

}

 

Пример 2: Функция добавления строки S2 в конец строки S1 (обе строки должны заканчиваться нулевым символом, и строка S1 должна иметь достаточную длину для добавления символов строки S2):

 

void my_StrCat(char *S1, char *S2)

{

unsigned j = my_StrLen(S1);

for (unsigned i = 0; S2 [i]; ++ i, ++j)

S1 [j] = S2 [i];

S1 [++j] = '\0';

}

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

 

Функция strlen(char *s) – возвращает фактическую длину текстовой строки, хранящейся в символьном массиве s (см. аналог my_StrLen(char *S)).

Функция strcpy(char *dest, char *source) – копирует содержимое строки source в строку dest.

Функция strcat(char *s1, char *s2) – добавляет содержимое строки s2 в конец строки s1.

Функция strcmp(char *s1, char *s2) – осуществляет лексикографическое сравнение строк s1 и s2. Возвращает значение 0, если строки одинаковы (равны), значение большее 0 при s1 > s2 и отрицательное значение при s1 < s2.

 

Пример. Имеются две строки S1 и S2, содержащие некоторые тексты. Необходимо поменять содержимое этих строк так, чтобы строка S2 содержала “больший” текст. Реализация:

char S1 [41], S2 [41], B [41];

cin >> S1;

cin >> S2;

If (strcmp (S1, S2))

{

strcpy(B, S1);

strcpy(S1, S2);

strcpy(S2, B);

}

cout << S1 << endl;

cout << S2 << endl;

 

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

Массивы текстовых строк

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

 

char arr_str [30] [21];

 

Этот массив содержит 30 строк. Каждая строка может содержать текст из максимум 20 символов + нулевой символ.

Для того чтобы обратиться к некоторой строке этого массива достаточно указать только первый индекс. Например:

 

strcpy (arr_str [10], “Иванов”);

cout << arr_str [10] << endl; // На экран выведено “Иванов”

Инициализация такого массива:

 

char arr_str [3] [21] = {“Иванов”, “Петров”, “Сидоров”};

cout << arr_str [1] << endl; // На экране “Петров”

 

или так:

 

char arr_str [ ] [21] = {“Иванов”, “Петров”, “Сидоров”};

cout << arr_str [2] << endl; // На экране “Сидоров”

 

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

 

cout << arr_str [2] [2]; // На экране буква ‘ д ’ из строки “Сидоров”

Указатели

 

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

Указатели

Понятие указателя

Все данные (переменные, константы и др.) хранятся в памяти. Память представляет собой непрерывную последовательность ячеек (байтов), каждая из которых имеет свой номер – адрес:

 

                                       
                                       
                                     
                                                                               

 

При определении, например, некоторой переменной, она располагается в памяти по определенному адресу и занимает столько ячеек, сколько требует тип этой переменной. Пусть, например, имеется переменные int A = 2351 и double B = 3.1 и пусть они располагаются в памяти так:

 

      А = 2351 B = 3.14          
                                       
                                     
                                                                               

 

Говорят, что переменная А располагается по адресу 101 и занимает 4 байта, а переменная B имеет адрес 105 и занимает 8 байт памяти.

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

 

int A = 2351;

double B = 3.14;

cout << “Значение переменной А: ” << A << endl;

cout << “Адрес переменной А: ” << &A << endl;

cout << “Значение переменной В: ” << В << endl;

cout << “Адрес переменной В: ” << &В << endl;

получим следующий результат:

 

Значение переменной А: 2351

Адрес переменной А: 101

Значение переменной В: 3.14

Адрес переменной В: 105

 

Правда, значения адресов переменных будут выведены в шестнадцатеричном формате.

 

Указатели – это тоже обычные переменные, но они служат для хранения адресов памяти.

 

Указатели определяются в программе следующим образом:

 

<тип данных> *<имя переменной>

 

Здесь < тип данных > определяет так называемый базовый тип указателя.

<Имя переменной> является идентификатором переменной-указателя.

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

Например:

 

int *p1;

double *p2;

 

Здесь определены две переменные-указатели (или просто – два указателя). Указатель p1 является переменной-указателем на базовый тип int (или, как говорят, переменная p1 указывает на int - значение), а указатель p2 указывает на double – значение.

Иными словами, переменная p1 предназначена для хранения адресов участков памяти, размер которых соответствует типу int (4 байта), а переменная p2 - для хранения адресов участков памяти, размер которых соответствует типу double (8 байт).

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

Работа с указателями

Присвоить указателю адрес некоторой переменной можно инструкцией присваивания и операцией &, например, так (возьмем предыдущий пример):

 

int A = 2351, *p1;

double B = 3.14, *p2;

p1 = &A; // Указателю p1 присваивается адрес переменной А

p2 = &B; // Указателю p2 присваивается адрес переменной В

cout << “Значение переменной А: ” << A << endl;

cout << “Адрес переменной А: ” << p1 << endl;

cout << “Значение переменной В: ” << В << endl;

cout << “Адрес переменной В: ” << p2 << endl;

 

Результат выполнения этого фрагмента программы будет таким же, как и раньше.

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

Получить значение объекта, на который ссылается некоторый указатель можно с помощью операции * (эту операцию обычно называют разыменованием указателя):

 

int A = 2351, *p1;

double B = 3.14, *p2;

p1 = &A; // Указателю p1 присваивается адрес переменной А

p2 = &B; // Указателю p2 присваивается адрес переменной В

cout << “Значение переменной А: ” << *p1 << endl;

cout << “Адрес переменной А: ” << p1 << endl;

cout << “Значение переменной В: ” << *p2 << endl;

cout << “Адрес переменной В: ” << p2 << endl;

 

Результат выполнения этого фрагмента программы будет таким же, как и в предыдущем примере.

Обращение к указателю с помощью оператора * (например, *p1) означает следующее: взять из памяти по адресу, хранящемуся в указателе (p1 равно 101), столько байт памяти, сколько требуется базовому типу указателя (в данном случае базовый тип указателя int, следовательно – взять 4 байта) и работать с этими байтами, как со значением базового типа указателя (в нашем примере это значение 2351 типа int). Таким образом, *p1 – это (в нашем примере) обычное значение типа int и с ним можно работать как с обычным целым числом.

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

 

*p1 = 4211;

приведет к тому, что переменная A, на которую ссылается указатель p1, станет равна 4211, а не 2351.

Указатели могут использоваться в различных выражениях наравне с обычными переменными и константами:

 

B = (*p1 – 1000) * 2; // Переменная В станет равна значению (4211 - 1000) * 2 = 6422.0

 

Или так:

 

*p2 = (*p1 – 1000) * 2; // Переменная B также будет равна значению 6422.0

 

Внимание. При использовании указателей в выражениях важно помнить, что операция * имеет наивысший приоритет по отношению к другим операциям (за исключением операции унарный – (минус)).

Значения переменных указателей можно инициализировать при их определении, как обычные переменные:

 

int A = 5, B = 10, *p1 = &A, *p2 = &B;

double D = 3.14, *d = &D;

 

Здесь указатели p1 и p2 указывают на переменные A и B соответственно. Указатель d – на переменную D.

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

 

p1 = p2; // Теперь оба указателя p1 и p2 ссылаются на переменную B

d = p1; // Ошибка – базовые типы не совпадают

 

Последней ошибки можно было бы избежать с помощью явного преобразования типов, например, так:

 

d = (double *) p1;

 

Но теперь указатель d будет содержать адрес переменной B типа int, а попытка взять значение по этому адресу через указатель d приведет к тому,что с этого адреса будут взяты не 4, а 8 байт памяти и полученное значение *d типа double будет непредсказуемым. Еще более неприятная ситуация может возникнуть, если попытаться записать данные по указателю d. Такая попытка может завершиться тем, что будут изменены данные, расположенные за переменной B, а это приведет к неправильной работе программы. Обнаружить такие ошибки бывает очень сложно.

Явное преобразование указателей нежелательно. Но если это необходимо, то делать это надо очень аккуратно.

Хотя формально указатели представляют собой целые значения, присваивать им произвольные целые значения нельзя. Например, попытка присвоить указателю p1 значение 10000 (p1 = 10000;) приведет к возникновению ошибки на этапе компиляции программы. Единственным исключением является присвоение указателю нулевого значения:

 

p1 = 10000; // Ошибка

p1 = 0; // Все правильно

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

Указатели можно сравнивать с помощью операций отношения ==, !=, >, <. С помощью таких сравнений можно характеризовать взаимное расположение объектов, на которые ссылаются сравниваемые указатели, в памяти. Но сравнение указателей с помощью операций >, < имеет смысл только в том случае, если сравниваемые указатели содержат адреса связанных между собой переменных (например, элементов массива).

Арифметика указателей

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

Рассмотрим следующий пример:

 

int A = 20, B = 30;

int *p1 = &A;

 

Пусть переменные A и B расположены в памяти, например, так, как это показано на следующем рисунке:

 

    A = 20 B = 30 p1 = 100            
                                       
                                     
                                                                               

 

Указатель p1 содержит адрес переменной A, который равен 100 и * p1 будет равно значению переменной A, то есть 20. Выполним следующую операцию:

 

p1 = p1 + 1;

или, что то же самое:

 

p1++;

Значение указателя изменится и станет равным 104, а не 101, как, наверное, ожидалось. То есть теперь указатель ссылается уже на переменную B и значение * p1 будет равно 30.

Таким образом, добавление или вычитание 1 из указателя приводит к изменению его значения на размер базового типа указателя. В общем случае, например, при выполнении следующей операции:

 

p1 = p1 + N; // N – некоторое целое значение

 

значение указателя увеличится на sizeof(<базовый тип указателя>) * N и в нашем случае это приращение будет равно sizeof(int) * N = 4 * N. Так, если N = 4, а p1= 100, то значение указателя p1 увеличится на 16 и станет равно 116, и указатель будет ссылаться на данные, расположенные по адресу 116.

Внимание. Добавлять к указателям или вычитать из указателей можно только целые значения.

Поскольку упомянутые арифметические операции выполняются по-разному при их применении к указателям и обычным арифметическим типам данных, а также учитывая высший приоритет операции *, при использовании указателей в составе выражений следует внимательно обращаться со скобками. Например, выражения (см. предыдущий рисунок)

*(p1 + 1) и *p1 + 1

 

имеют совершенно разный смысл. Первое выражение даст значение 30, а второе выражение будет равно 21 (в первом выражении сначала изменяется адрес, а затем осуществляется обращение в память по этому измененному адресу; во втором выражении мы обращаемся по старому адресу и к значению, хранящемуся по этому адресу добавляем 1).



Поделиться:


Последнее изменение этой страницы: 2017-02-07; просмотров: 306; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 13.58.112.1 (0.108 с.)