Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программирование на C / C++ / Лекции по C++ ОККТ "Сервер" [12].pdf
Скачиваний:
124
Добавлен:
02.05.2014
Размер:
1.04 Mб
Скачать

Одесский колледж компьютерных технологий "СЕРВЕР"

int ia[9] = {0, 1, 1, 2, 3, 5, 8, 13, 21}; int *pbeg = ia;

int *pend = ia + 9; while (pbeg != pend){

cout << *pbeg << ' '; ++pbeg;

}

return 0;

}

Указатель pbeg инициализируется адресом первого элемента массива. Каждый проход по циклу увеличивает это указатель на 1, что означает смещение его на следующий элемент. Как понять, где остановиться? В этом примере определён второй указатель, которому присвоен адрес, следующий за последним элементом массива ia. Как только значение pbeg станет равным pend, мы узнаем, что массив кончился.

Динамические массивы

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

int n = 100; //переменная n для размерности массива float *p = new float [n];

В этой строке создаётся переменная-указатель на float, в динамической памяти отводится непрерывная область, достаточная для размещения 100 элементов вещественного типа, и адрес её начала записывается в указатель p. Динамические массивы нельзя при создании инициализировать, и они не обнуляются.

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

Доступ к элементам динамического массива осуществляется точно так же, как к статическим. Например, к 5-му элементу приведённого выше массива можно обратиться как p[5] или *(p+5).

Память, зарезервированная под динамический массив с помощью new[], должна освобождаться оператором delete[]:

delete [] p;

Многомерные массивы

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

35

Одесский колледж компьютерных технологий "СЕРВЕР"

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

Многомерные массивы задаются указанием каждого измерения в квадратных скобках, например, оператор

int matr [6][8];

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

Для доступа к элементу двумерного массива указываются все его индексы, то есть номер строки и номер столбца внутри массива, например: matr[3][6], matr[i][j].

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

int mass2 [][] = { {1,1}, {0,2}, {1,0} }; (массив представляется как массив массивов)

int mass2 [3][2] = {1, 1, 0, 2, 1, 0}; (задаётся общий список элементов)

При работе с многомерными массивами используются вложенные

циклы.

Для создания динамического многомерного массива необходимо указать в операции new все его размерности (самая левая размерность может быть переменной), например:

int nstr = 5;

int **m=(int**) new [nstr][10];

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

int nstr, nstb;

cout<<" Введите количество строк и столбцов :";

cin>>nstr>>nstb;

 

int **a= new int *[nstr];

//1

for ( int i=0; i<nstr; i++)

//2

a[i] = new int [nstb];

//3

В операторе 1 объявляется переменная типа «указатель на указатель на int» и выделяется память под массив указателей на строки массива (количество строк - nstr). В операторе 2 организуется цикл для выделения памяти под каждую строку массива. В операторе 3 каждому элементу массива указателей на строки присваивается адрес начала участка памяти, выделенного под

36

Одесский колледж компьютерных технологий "СЕРВЕР"

строку двумерного массива. Каждая строка состоит из nstb элементов типа int.

Замечание: если при описании переменной используются указатель и [] (массив), то переменная интерпретируется как массив указателей, а не указатель на массив: int *p[10] - массив из 10 указателей на int.

Освобождение памяти из-под массива с любым количеством измерений выполняется с помощью операции delete []. Указатель на константу удалить нельзя.

Строки

В С ++ поддерживается два типа строк - встроенный тип, доставшийся от С, и класс 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";

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

37

Одесский колледж компьютерных технологий "СЕРВЕР"

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

Для ввода и вывода строк из С в С++ перекочевали функции 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-считывание строки

38

Одесский колледж компьютерных технологий "СЕРВЕР"

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 и таким образом сдвигает его к следующему символу.

39

Одесский колледж компьютерных технологий "СЕРВЕР"

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

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;

}

40

Одесский колледж компьютерных технологий "СЕРВЕР"

Ошибка состоит в том, что после завершения цикла 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 и результат воз-

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

 

вращает в s1, добавляет в конец

 

 

нуль-символ

 

strchr

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

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

strcmp

сравнивает строки, возвращает отри-

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

 

цательное (s1<s2),нулевое (s1=s2)

 

 

или положительное (s1>s2) значение

 

strcpy

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

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

strcspn

ищет один из символов одной строки

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

 

в другой, возвращает значение ин-

 

 

декса любого из символов из s2 в

 

 

строке s1

 

strlen

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

size_t strlen(char *s);

41

Одесский колледж компьютерных технологий "СЕРВЕР"

 

символа завершения строки)

 

 

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

ищет символ одной строки, отсутст-

 

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

 

вующий в другой, возвращает индекс

 

 

 

первого символа s1,

отсутствующего

 

 

 

в s2

 

 

 

 

 

 

strstr

ищет подстроку в строке, возвращает

 

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

 

указатель на элемент из s1, с которо-

 

 

 

го начинается 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

управляющих символов (с кодами

 

int iscntrl(int ch);

 

0..31 и 127)

 

 

 

 

 

 

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);

42

Одесский колледж компьютерных технологий "СЕРВЕР"

 

 

стре

 

 

 

 

 

 

 

 

 

 

toupper

 

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

int toupper(int ch);

 

 

 

 

 

 

гистре

 

 

 

 

 

 

 

 

 

 

Пример.

 

 

 

 

 

 

 

 

 

 

 

 

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

 

 

 

 

int k, n;

 

 

 

 

 

 

 

 

 

 

 

 

 

Команда

 

 

 

 

 

Результат

 

 

 

 

n=strlen(s1);

 

n=17

 

 

 

 

 

 

 

 

 

k=strlen(s2);

 

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="На" Если не задать нуль-символ, то строка не бу-

s2[2]='\0';

 

дет обрезана и скопируется вся строка 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];

43

Одесский колледж компьютерных технологий "СЕРВЕР"

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 не только между собой, но и со строками встроенного типа. Можно переписать пример, приведенный выше, так, чтобы специальные символы и знаки препинания

44

Одесский колледж компьютерных технологий "СЕРВЕР"

представлялись встроенным типом, а значимые слова – объектами класса 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>

45