Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лабораторная работа5.docx
Скачиваний:
12
Добавлен:
16.09.2019
Размер:
66.91 Кб
Скачать

Лабораторная работа № 5 Строки

В С ++ поддерживается два типа строк - встроенный тип, доставшийся от С, и класс string стандартной библиотеки С++. Класс string предоставляет больше возможностей и поэтому удобнее в применении, однако на прак-тике нередки ситуации, когда необходимо пользоваться встроенным типом. Начнём с рассмотрения встроенного типа.

Для использования строк нужно включить заголовочный файл <string.h>. Строка представляет собой массив символов, заканчивающийся нуль-символом. Нуль-символ - это символ с кодом, равным 0, что записывается в виде управляющей последовательности '\0'. По положению нуль-символа определяется фактическая длина строки.

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

const int n = 10;

char str[n];

При задании длины необходимо учитывать завершающий нуль-символ. На-пример, в приведенной выше строке можно хранить не 10, а 9 символов.

Строку можно инициализировать строковой константой, при этом нуль-символ формируется автоматически:

char str[10] = "Vasia";

при этом выделяется массив из 10 элементов с номерами от 0 до 9 и все сим-волы строки помещаются в массив. Под эту строку выделяется 10 байт, 5 из которых заняты под символы строки, а шестой - под нуль-символ. Если строка при определении инициализируется, её размерность можно опускать (компилятор сам выделит нужное количество байт):

char str[] = "Vasia"; //выделено и заполнено 6 байт

Оператор

char *str = "Vasia";

создаёт не строковую переменную, а указатель на строковую константу, изменить которую невозможно.

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

Для ввода и вывода строк из С в С++ перекочевали функции gets и puts. Например:

#include <iostream.h>

int main(){

const int n=10;

char s[n];

gets(s); puts(s);

return 0;

}

Функция gets(s) читает символы с клавиатуры до появления символа перевода строки и помещает их в строку s (сам символ перевода строки в строку не включается, вместо него в строку вставляется нуль-символ). Функция puts(s) выводит строку s на экран, заменяя завершающий 0 символом перевода строки.

В С++ строки также можно вводить с помощью методов get() и getline() (позже мы изучим, что такое методы, а пока просто запомните синтаксис).

#include <iostream.h>

int main(){

const int n=10;

char s[n];

cin.getline(s,n); cout <<s<<endl;

cin.get(s,n); cout <<s<<endl;

return 0;

}

Метод getline считывает из входного потока n-1 символов или менее (если символ перевода строки '\n' встретится раньше) и записывает в строковую переменную. Символ перевода строки также считывается (удаляется) из входного потока, но не записывается в строковую переменную, вместо него размещается завершающий 0. Если в строке исходных данных более n-1 сим-волов, следующий ввод будет выполняться из той же строки, начиная с первого несчитанного символа.

Метод get работает аналогично, но оставляет в потоке символ пере-вода строки. В строковую переменную добавляется завершающий 0.

Нельзя обращаться к разновидности метода get с двумя аргументами два раза подряд, не удалив \n из входного потока. Например:

cin.get(s,n); //1-считывание строки

cout<<s<<endl; //2-вывод строки

cin.get(s,n); //3-считывание строки

cout<<s<<endl; //4-вывод строки

cin.get(s,n); //5-считывание строки

cout<<s<<endl; //6-вывод строки

cout<<"Конец - делу венец"<<endl;

При выполнении этого фрагмента вы увидите на экране первую строку, вы-веденную оператором 2, а затем завершающее сообщение, выведенное опера-тором 7. Метод get в данном случае «уткнётся» в символ \n, оставленный во входном потоке от первого вызова этого метода (оператор 1). В результате будут считаны и, соответственно, выведены на экран пустые строки. Возможное решение этой проблемы - удалить символ \n из входного потока путём вызова метода get без параметров, то есть после операторов 1 и 3 нужно вставить

cin.get();

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

#include <iostream.h>

int main(){

const int n=10;

char s[n];

while (cin.getline(s,n)){

cout <<s<<endl;

… //обработка строки

}

return 0;

}

Доступ к строке может осуществляться через указатель типа char*. В примере

char *st = "Цена бутылки вина\n";

компилятор помещает все символы строки в массив и присваивает перемен-ной st адрес первого элемента массива.

Обычно для перебора символов строки применяется адресная арифметика. Поскольку строка всегда заканчивается нуль-символом, можно увеличивать указатель на 1, пока очередным символом не станет нуль. Напри-мер:

while (*st++) { … }

st раскрывается, и получившееся значение проверяется на истинность. Любое отличное от нуля значение считается истинным, и, следовательно, цикл заканчивается, когда будет достигнут символ с кодом 0. Операция инкремента прибавляет 1 к указателю st и таким образом сдвигает его к следующему символу.Вот как можно вычислить длину строки. Поскольку указатель может содержать нулевое значение (ни на что не указывать), перед операцией раскрытия его надо проверять:

int cnt = 0;

if ( st )

while ( *st++ )

++cnt;

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

char *pc1 = 0; //pc1 не адресует никакого массива символов

const char *pc2 = ""; //pc2 адресует нулевой символ

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

Версия первая (неверная):

#include <iostream.h>

const char *st = "Цена бутылки вина\n"

int main(){

int len = 0;

while ( st++ ) ++len;

cout<<len<<": "<<st;

return 0;

}

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

Версия вторая (результат неправильный):

#include <iostream.h>

const char *st = "Цена бутылки вина\n"

int main(){

int len = 0;

while ( *st++ ) ++len;

cout<<len<<": "<<st;

return 0;

}

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

Можно попробовать исправить ошибку:

st = st - len;

cout<<len<<": "<<st;

Теперь программа выдаёт что-то осмысленное, но не до конца. Ответ выглядит так:

18: ена бутылки вина

Мы забыли учесть, что заключительный нулевой символ не был включён в подсчитанную длину. Указатель st должен быть смещён на длину строки плюс 1. Вот, наконец, правильный оператор и правильный результат:

st = st - len - 1;

18: Цена бутылки вина

Однако программа выглядит не очень изящно. Оператор st = st - len - 1; добавлен для того, чтобы исправить ошибку, допущенную на раннем этапе проектирования программы. Этот оператор не вписывается в логику программы, и код теперь трудно понять. Исправления такого рода чисто называют заплатками: они призваны заткнуть дыру в существующей программе. Гораздо лучше было бы пересмотреть логику. Одним из вариантов в нашем случае может быть определение второго указателя, инициализированного значением st:

const char *p = st;

Теперь p можно использовать в цикле вычисления длины, оставив значение st неизменным:

while ( *p++ ) ++len;

Функции стандартной библиотеки для работы со строками Функция

Назначение

Полноеописание

Заголовочный файл <string.h>

strcat

добавляет s2 к s1 и результат воз-вращает в s1, добавляет в конец нуль-символ

char *strcat(char *s1, char *s2);

strchr

ищет символ в строке

char *strchr(char *s, int ch);

strcmp

сравнивает строки, возвращает отрицательное (s1<s2),нулевое (s1=s2) или положительное (s1>s2) значение

int *strcmp(char *s1, char *s2);

strcpy

копирует s2 в s1 и возвращает s1

char *strcpy(char *s1, char *s2);

strcspn

ищет один из символов одной строки в другой, возвращает значение индекса любого из символов из s2 в строке s1

size_t strcspn(char *s1, char *s2);

strlen

возвращает длину строки (безучёта символа завершения строки)

size_t strlen(char *s);

strncat

складывает одну строку с n символами другой

char *strncat(char *s1, char *s2, size_t n);

strncmp

сравнивает одну строку с n символами другой

int *strncmp(char *s1, char *s2, size_t n);

strncpy

копирует первые n символов одной строки в другую

char *strncpy(char *s1, char *s2, size_t n);

strspn

ищет символ одной строки, отсутствующий в другой, возвращает индекс первого символа s1, отсутствующего в s2

size_t strcspn(char *s1, char *s2);

strstr

ищет подстроку в строке, возвращает указатель на элемент из s1, с которого начинается s2

char *strstr(char *s1, char *s2);

strlwr

преобразует строчные символы строки в прописные (обрабатывает только буквы латинского алфавита)

char *strlwr(char * s);

strupr

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

char *strupr(char * s);

strset

заполняет строку указанным при вызове функции символом

char *strset(char * s, char c);

Заголовочный файл <stdlib.h>

atof

преобразует строку в вещественное число

atoi

преобразует строку в целое число

int atoi(const char *str);

atol

преобразует строку в длинное целое число

long atol(const char *str);

strtod

преобразует строку в число

double strtod(const char *str, char **end);

Заголовочный файл <ctype.h>

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

множество

isalnum

букв и цифр (A-Z, a-z, 0-9)

int isalnum(int ch);

isalfa

букв (A-Z, a-z)

int isalfa(int ch);

iscntrl

управляющих символов (с кодами 0..31 и 127)

int iscntrl(int ch);

isdigit

int isdigit(int ch);

isgraph

является ли символ видимым, то есть не является символом пробела, табуляции и т.д.

int isgraph(int ch);

islower

букв нижнего регистра (a-z)

int islower(int ch);

isprint

isgraph + пробел

int isprint(int ch);

isupper

букв верхнего регистра (A-Z)

int isupper(int ch);

ispunct

знаков пунктуации

int ispunct(int ch);

isspace

символов-разделителей

int isspace(int ch);

tolower

возвращает символ в нижнем регистре

int tolower(int ch);

toupper

возвращает символ в верхнем регистре

int toupper(int ch);

Пример.

char *s1="На Дерибасовской \n", *s2="хорошая погода\n", s3;

int k, n; Команда

Результат

n=strlen(s1);

k=strlen(s2);

n=17

k=14

s3= strchr(s2, ' ');

s3 указывает на пробел в строке s2

strcat(s1, s2);

s1="На Дерибасовской хорошая погода\n"

k=strcmp(s2, s3);

k=197 (s2>s3, разница между ними в таблице ASCII 197)

strcpy(s3, s2);

s3="хорошая погода\n"

strncpy(s2, s1,2);

s2[2]='\0';

s2="На" Если не задать нуль-символ, то строка не будет обрезана и скопируется вся строка s1

s2=strstr(s1,s3)

s2 = "хорошая погода\n"

Пример применения функций преобразования

char a[] = "10)Рост - 162 см, вес - 59.5 кг";

int num;

long height;

double weight;

num=atoi(a); //num=10

height=atol(&a[11]); //162 в строке начинается с 11 позиции, height=162

weight=atof(&a[25]); //59.5 в строке начинается с 25 позиции, weight=59.5

cout<<num<<' '<<height<<' '<<weight;

Пример (программа заполняет массив типа double из строки)

#include <iostream.h>

#include <string.h>

#include <stdlib.h>

int main(){

char [] = "2, 38.5, 70, 0, 0, 1", *p = s;

double m[10];

int i = 0;

do{

m[i++] = atof (p);

if (i>9) break;

}while(p=strchr(p, ','),p++)

for (int k=0; k<i; k++) cout <<m[k];

return 0;

}

Для размещения строки в динамической памяти надо описать указатель на char, а затем выделить память с помощью new:

char *p = new char [m];

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

char *str = "Строка символов"

создаёт не строковую переменную, а строковую константу, изменить которую невозможно.

Классом string пользоваться гораздо удобнее, чем строками в стиле С. Рассмотрим основные операции этого класса. Описание использованной выше строки:

#include <string>

string st("Цена бутылки вина\n");

Длину строки без нуль-символа возвращает функция-член size():

st.size()

Пустая строка:

string st2;

Проверка, является ли строка пустой:

if ( ! st.size() )

или так:

if ( st.empty() )

Инициализация объекта типа string другим объектом того же типа:

string st3 ( st );

Сравнение этих строк:

if ( st == st3 );

Копирование строки st3 в st2:

st2 = st3;

Пусть даны две строки:

string s1 ("hello, ");

string s2 ("world\n");

Конкатенация (сложение) этих строк:

string s3 = s1 + s2;

Добавление s2 в конец s1:

s1 += s2;

Операция сложения может конкатенировать объекты класса string не только между собой, но и со строками встроенного типа. Можно переписать пример, приведенный выше, так, чтобы специальные символы и знаки препинания представлялись встроенным типом, а значимые слова – объектами класса string:

const char *pc = ", ";

string s1( "hello" );

strings2( "world" );

string s3 = s1 + pc + s2 +"\n";

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

string s1;

const char *pc = "a character array";

s1 = pc;

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

char *str = s1;

Правильный вариант инициализации выглядит так:

const char *str = s1.c_str();

Функция c_str() возвращает указатель на символьный массив, содержащий строку объекта string в том виде, в котором она находилась бы во встроенном строковом типе.

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

string str( "fa.disney.com" );

int size = str.size();

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

if ( str[ix] == '.')

str[ix] = '_';

Класс string обладает ещё многими интересными свойствами и возможностями. Скажем, предыдущий пример реализуется также вызовом одной-единственной функции replace():

replace( str.begin(), str.end(), '.', '_');

replace() – это один из обобщённых алгоритмов. Эта функция пробегает диапазон от begin() до end() (начало и конец строки) и заменяет элементы, равные третьему своему параметру, на четвёртый.

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

string::npos

в противном случае. Например:

#include <string>

#include <iostream>

int main(){

string name("AnnaBelle");

int pos = name.find("Anna");

if (pos == string::npos)

cout<<"Anna не найдено!\n";

else cout << "Anna найдено в позиции: "<< pos << endl;

}

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

#include <string>

#include <iostream>

int main(){

string numerics("0123456789");

string name("r2d2");

string::size_type pos = 0;

while ((pos=name.find_first_of(numerics, pos))

!=string::npos){

cout<<"найдена цифра в позиции: ";

<< pos << "\tэлемент равен ";

<< name.pos<<endl;

++pos;

}