Краткие теоретические сведения 


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



ЗНАЕТЕ ЛИ ВЫ?

Краткие теоретические сведения



ЛАБОРАТОРНАЯ РАБОТА №8

 

Функции

 

КРАТКИЕ ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ

 

Модульное программирование

 

С увеличением объема программы становится невозможным удерживать в памяти все детали. Естественным способом борьбы со сложностью любой задачи является ее разбиение на части. В C++ задача может быть разделена на более простые и обозримые с помощью функций, после чего программу можно рассматривать в более укрупненном виде – на уровне взаимодействия функций. Это важно, поскольку человек способен помнить ограниченное количество фактов. Использование функций является первым шагом к повышению степени абстракции программы и ведет к упрощению ее структуры.

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

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

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

Скрытие деталей реализации называется инкапсуляцией. Инкапсуляция является ключевой идеей как структурного, так и объектно-ориентированного программирования. Пример инкапсуляции – помещение фрагмента кода в функцию и передача всех необходимых ей данных в качестве параметров. Чтобы использовать такую функцию, требуется знать только ее интерфейс, определяемый заголовком (имя, тип возвращаемого значения и типы параметров). Интерфейсом модуля являются заголовки всех функций и описания доступных извне типов, переменных и констант. Описания глобальных программных объектов во всех модулях программы должны быть согласованы.

Модульность в языке C++ поддерживается с помощью директив препроцессора, пространств имен, классов памяти, исключений и раздельной компиляции (строго говоря, раздельная компиляция не является элементом языка, а относится к его реализации).

 

Функции

 

Вызов функции

 

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

Пример функции, возвращающей сумму двух целых величин:

 

int sum(int a, int b){ return (a + b);}     // определение функции

int a = 2, b = 3, c, d;

с = sum(a, b); // вызов функции

cin >> d;

cout << sum(c, d); // вызов функции

 

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

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

 

#include <iostream.h>

void f(int a)

{

int m = 0;

cout << "n m p\n";

while (a--)

{

static int n = 0;

int p = 0:

cout << n++ << ' ' << m++ << ' ' << p++ << '\n';

}

}

int main(){ f(3); f(2); return 0;}

 

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

n m р

0 0 0

1 1 0

2 2 0

n m р

3 0 0

4 1 0

 

Рекурсивные функции

 

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

Классическим примером рекурсивной функции является вычисление факториала (это не означает, что факториал следует вычислять именно так). Для того чтобы получить значение факториала числа n, требуется умножить на n факториал числа (n-1). Известно также, что 0!=1 и 1!=1.

long fact(long n){

if (n==0 || n==l) return 1;

return (n * fact(n - 1);

}

 

To же самое можно записать короче:

long fact(long n){return (n>l)? n * fact(n - 1): 1;}

Рекурсивные функции чаще всего применяют для компактной реализации рекурсивных алгоритмов, а также для работы со структурами данных, описанными рекурсивно. Любую рекурсивную функцию можно реализовать без применения рекурсии, для этого программист должен обеспечить хранение всех необходимых данных самостоятельно. Достоинством рекурсии является компактная запись, а недостатками – расход времени и памяти(возможно переполнение стека) на повторные вызовы функции и передачу

 

Перегруженные функции

 

В программировании то и дело случается писать функции для схожих действий, выполняемых над различными типами и наборами данных. Возьмите, например, функцию, которая должна возвращать квадрат своего аргумента. В C/C++ возведение в квадрат целого и числа с плавающей точкой – существенно разные операции. Вообще говоря, придется написать две функции – одну, принимающую целый аргумент и возвращающую целое, и вторую, принимающую тип double и возвращающую также double. В С функции должны иметь уникальные имена. Таким образом, перед программистом встает задача придумывать массу имен для различных функций, выполняющих аналогичные действия. Например, SquareInt() и SquareDbl().

В C++ допускаются перегруженные имена функций (термин взят из лингвистики), когда функции с одним именем можно, тем не менее, идентифицировать по их списку параметров, т.е. контексту, в котором имя употребляется.

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

 

int Square(int arg)

{

return arg*arg;

}

double Square(double arg)

{

return arg*arg;

}

char *Square(const char *arg, int n)

{

static char res[256];

int j = 0;

while (*arg && j < n)

{ if (*arg!= ' ') res[j++] = *arg;

res[j++] = *arg++;}

res[j] = 0;

return res;

}

int main(void)

{

int x = 11;

double у = 3.1416;

char msg[] = "Output from overloaded Function!";

printf("Output: %d, %f, %s\n", Square (x), Square (y), Square (msg, 32));

}

Результат работы программы показан на рис. 6.1.

 

Рис. 6.1Пример с тремя перегруженными функциями

 

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

// Возвращает наибольшее из двух целых:

int max(int, int):

// Возвращает подстроку наибольшей длины:

char* max(char*, char*);

// Возвращает наибольшее из первого параметра и длины второго:

int max (int, char*):

// Возвращает наибольшее из второго параметра и длины первого:

int max (char*, int);

void f(int a, int b, char* c, char* d)

{

cout << max (a, b) << max(c, d) << max(a, c) << max(c, b);

}

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

Если точного соответствия не найдено, выполняются продвижения порядковых типов в соответствии с общими правилами преобразования типов, например, bool и char в int, float в double и т. п. Далее выполняются стандартные преобразования типов, например, int в double или указателей в void*. Следующим шагом является выполнение преобразований типа, заданных пользователем, а также поиск соответствий за счет переменного числа аргументов функций. Если соответствие на одном и том же этапе может быть получено более чем одним способом, вызов считается неоднозначным и выдается сообщение об ошибке.

Неоднозначность может появиться при:

- преобразовании типа;

- использовании параметров-ссылок;

- использовании аргументов по умолчанию.

Пример неоднозначности при преобразовании типа:

 

float f(float i)

{

cout << "function float f(float i)" << endl;

return i;

}

double f(double i)

{

cout << "function double f(double i)" << endl;

return i*2;

}

int main()

{

float x = 10.09:

double у = 10.09;

cout << f(x) << endl; // Вызывается f(float)

cout << f(y) << endl; // Вызывается f(double)

cout << f(10) << endl; // Неоднозначность - как преобразовать 10: во float или double?

}

 

Для устранения этой неоднозначности требуется явное приведение типа для константы 10.

Пример неоднозначности при использовании параметров-ссылок: если одна из перегружаемых функций объявлена как int f(int a, int b), а другая – как int f (int a, int &b), то компилятор не сможет узнать, какая из этих функций вызывается, так как нет синтаксических различий между вызовом функции, которая получает параметр по значению, и вызовом функции, которая получает параметр по ссылке.

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

 

int f(int a){return a;}

int f(int a, int b = l){return a * b;}

int main()

{

cout << f(10, 2);  // Вызывается вторая функция f(int, int)

cout << f(10); // Неоднозначность - что вызывается: f(int, int) или f(int)?

}

Ниже приведены правила описания перегруженных функций.

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

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

Функции не могут быть перегружены, если описание их параметров отличается только модификаторами const, volatile или использованием ссылки (например, int и const int или int и int&).

Нельзя перегружать функции, отличающиеся только типом возвращаемого значения.

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

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

void Func(void); // @Func$qv

void Func(int); // @Func$qi

void Func(int, int); // @Func$qii

void Func(*char); // @Func$qpc

void Func(unsigned); // @Func$qui

void Func(const char*); // @Func$qpxc

Тип возвращаемого значения никак не отражается на декорировании имени.

 

Спецификация связи

 

Если функция, написанная на C++, должна вызываться из программы на С, Pascal или языке ассемблера (и наоборот, что часто бывает при использовании существующих динамических библиотек), то механизм декорирования имен C++ создает некоторые трудности. Они могут быть причиной сообщений об ошибках при компоновке вроде “Неопределенный символ ххх в модуле уyу”.

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

Вот примеры:

// Функция, которую можно вызывать из С (прототип):

extern "С" void FuncForC(void);

// Прототип функции из некоторой библиотеки не на C++:

extern "С" void SomeExtFunc (int);

Следует отличать спецификацию связи от соглашений o вызове. Функция, например, может вызываться в соответствии с соглашением Pascal (что означает определенный порядок передачи параметров и имя в верхнем регистре), но иметь спецификацию связи C++, т. е. декорированное имя. Алгоритм здесь такой: сначала формируется (если не указано extern "С") декорированное имя, а затем оно подвергается модификации в соответствии с соглашением о вызове.

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

extern "С"

{

#include "asmlib.h"

#include "someclib.h"

}

 

Задание к лабораторной работе №8

 

Задание 1

Даны две квадратные матрицы n´n. Напечатать ту из них, которая имеет минимальный “след” (т.е. сумму элементов главной диагонали). При решении создать функцию для нахождения следа матрицы и функцию печати матрицы.

 

Задание 2

Написать и протестировать функцию compress (), которая сжимает массив, удаляя из него элементы равные числу, введенному с клавиатуры.

 

Задание 3

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

 

Задание 4

Дана квадратная матрица n´n. Написать и протестировать функцию, которая определяет, является ли данная матрица ортонормированной, т.е. такой, в которой скалярное произведение каждой пары различных строк равно нулю, а скалярное произведение каждой строки на себя равно единице. При решении создать функцию для вычисления скалярного произведения векторов.

 

Задание 5

Написать и протестировать функцию, возвращающую номер самого последнего элемента из массива, который совпадает с заданным с клавиатуры числом. Если такого элемента нет, функция должна возвращать –1.

 

Задание 6

Даны длины отрезков A B C D. Для каждой тройки этих отрезков, из которых можно построить треугольник, найти площадь этого треугольника. При решении создать функцию для нахождения и печати площади треугольника, если он существует.

 

Задание 7

Найти натуральное число из интервала [n1, n2] с максимальной суммой делителей. Для нахождения суммы делителей написать функцию.

 

Задание 8

Написать и протестировать функцию, которая все нулевые элементы заданного массива переносит в его конец, а остальные – в начало, сохраняя порядок их следования. Размерность массива определяется по количеству введенных элементов.

 

 

Задание 9

Написать и протестировать функцию, которая определяет, является ли массив симметричным.

 

Задание 10

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

 

Задание 11

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

Задание 12

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

 

Задание 13

Написать и протестировать функцию shift _ r (mas, n, k), которая циклически сдвигает массив mas длины n вправо на k позиций.

 

Задание 14

Написать и протестировать функцию, которая возвращает наименьшее среди элементов массива X(n), которые не являются элементами массива Y(m).

 

Задание 15

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

 

Содержание отчета

 

Отчет выполняется в редакторе Word 2000 и должен содержать:

 

1. Листинг программ на языке Си, решающих задачи в соответствии с вариантом (номером компьютера) задания;

2. Блок-схему алгоритма программ;

3. Пояснения по методу решения задач;

4. Результаты тестирования программ.

 

При оформлении отчета следует пользоваться копированием листинга и результата тестирования в Word. Последний копируется с помощью комбинации клавиш ALT Prt Sc при условии активности консольного окна, что означает копирование графики окна в буфер обмена Windows. Затем окно вставляется в документ Word как любой другой объект.

 


 

ЛАБОРАТОРНАЯ РАБОТА №9

 

Строки

 

Строки

 

Массив переменных символьного типа (char) образует строку. Существует ряд особенностей работы с массивами этого типа. Например, инициализация строк осуществляется с помощью строковых констант:

 

char Str1[20];//Строка в 20 символов

char Junuary[]="Junuary \0";//инициализация строкового массива

 

В отличие от других типов данных, массив char может использоваться без указания индекса. В связи с этим массив имеет скрытую структуру. В конце массива компилятор языка С++ иногда устанавливает символ 0 (байт равный нулю). Например, это делает функция ввода scanf. Ноль позволяет определять конец строки при ее использовании. Иногда 0 нужно установить программисту. Например, при инициализации строки.

В русском тексте строки русскоязычные. Для введения русскоязычных строк используется функция RUS. Рассмотрим ее подробнее:

 

// функция поддержки русского языка в консольном приложении

char bufRus[256]; //глобальная переменная bufRus используется

                           // в функции RUS

//---------------------------------------------------------------------------

char* RUS(const char* text) //заголовок функции

{

CharToOem(text,bufRus); //тело функции

return bufRus;        //

}

 

Особенность функции состоит в использовании указателей. Аргумент и возвращаемое значение функции имеют тип char*, т. е. адрес строки символов. Следовательно, можно получить адрес строки, а сама строка находится в массиве bufRus, именно его адрес и возвращается функцией. Поэтому можно и прямо обращаться к массиву bufRus, после того как с помощью функции RUS его содержимое заполнится русскоязычной строкой. Приведенная ниже программа печатает массив bufRus побуквенно:

 

char Str[21];

int i=0;//i – счетчик букв

RUS("Проверка");//Занесение русского текста в bufRus

while (bufRus[i]!='\0') //Проверка на конец копируемого массива

{cout<<bufRus[i];i++;} //цикл побуквенной печати

 

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

 

Можно использовать оператор cin. Операция ввода записывается как:

cin>>St;

При использовании оператора cin необходимо учитывать следующее:

- Ввод заканчивается после нажатия клавиши Enter;

- Если в строке есть пробел, то символы после пробела не вводятся;

- Если количество введенных символов меньше объявленной длины строки, то остальные поля не заполняются. Поэтому необходимо сделать предварительное обнуление строки;

- Если количество введенных символов больше объявленной длины строки, то вводятся все символы до нажатия клавиши Enter.

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

Рассмотрим пример объявления символьных строк и использования функции ввода cin.getline ().

 

char myString[4];

cout << RUS("Введите строку из 3 символов") << '\n';

cin.getline(myString, 4);

cout << RUS("Вы ввели: ") << myString;

 

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

Иногда более удобен ввод с помощью функции gets().

При использовании функции gets() необходимо учитывать следующее:

- Вводятся пробелы;

- Если количество введенных символов меньше объявленной длины строки, то остальные поля не заполняются. Поэтому желательно сделать предварительное обнуление строки;

- Количество введенных символов не должно быть больше объявленной длины строки (функция gets не контролирует количество символов, и это может привести к непредсказуемой ошибке);

- Ввод заканчивается после нажатия клавиши Enter.

 

char myString[4];

cout << RUS("Введите строку из 3 символов") << '\n';

gets(myString);

cout << RUS("Вы ввели: ") << myString;

 

Определение длины строк

 

Очень часто при работе со строками необходимо знать, сколько символов содержит строка. Для выяснения информации о длине строки в заголовочном файле string.h описана функция strlen(). Синтаксис этой функции имеет вид:

size_t strlen(const char* string)

Данная функция в качестве единственного параметра принимает указатель на начало строки string, вычисляет количество символов строки и возвращает полученное беззнаковое целое число (size_t). Функция strlen () возвращает значение на единицу меньше, чем отводится под массив по причине резервирования места для символа ' \0'. Следующий фрагмент демонстрирует использование функции strlen ():

 

char Str[] = "ABCDEFGHIJK";

unsigned int i;

i = strlen(Str);

 

Часто функция sizeof () используется при вводе строк в качестве второго параметра конструкции cin.getline (), что делает код более универсальным, так как не требуется явного указания числа вводимых символов. Если теперь потребуется изменить размер символьного массива, достаточно модифицировать лишь одно число при объявлении строки символов:

// Было:

char myString[4];

cin.getline(myString, 4);

// Стало:

char myString[20];

cin.getline(myString, sizeof(myString))

 

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

 

Значения строк могут копироваться из одной в другую. Процедура идентична копированию массива. Например, в приведенной ниже программе содержимое строки bufRus копируется в строку Str. Копирование прекращается в случае достижения нуль-терминатора (‘\0’) строки bufRus, поэтому перед копированием необходимо удостовериться, что длина bufRus меньше или равна длине Str. В противном случае возможно возникновение ошибок.

 

char Str[21];

int i=0;//i – счетчик букв

RUS("Проверка копирования");//Занесение русского текста в bufRus

while (bufRus[i]!='\0') //Проверка на конец копируемого массива

{Str[i]=bufRus[i];i++;} //цикл копирования

cout<<Str;

 

В библиотеке string.h для копирования используют ряд стандартных функций.

Функция strcpy () имеет прототип:

char* strcpy(char* strl, const char* str2)

и выполняет побайтное копирование символов из строки, на которую указывает str2, в строку по указателю strl.

Например, следующий фрагмент копирует в строку Str значение строки "Проверка копирования":

char Str[21];

strcpy(Str, RUS("Проверка копирования"));

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

 

char Str[21],str2[21];

strcpy(Str, RUS("Проверка копирования"));

char* ptr = Str;

ptr += 9;

// ptr теперь указывает на

// подстроку "копирования"

//в строке Str

strcpy(str2, ptr);

cout << str2;

 

При выполнении программы будет выведен текст: копирования.

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

char* strncpy(char* strl, const char* str2, size_t num)

Если длина strl меньше длины str2, происходит урезание символов:

 

char cLong[] = "012345678901234567890123456789";

char cShort[]="abcdef";

strncpy(cShort, cLong, 4);

cout << cShort <<'\n';

cout << cLong <<'\n';

 

В результате будет выведено:

 

0123ef

012345678901234567890123456789

 

То есть из строки cLong в строку cShort скопировано четыре первых символа, заменив, тем самым, исходное значение начала короткой строки.

Функция strdup () в качестве параметра получает указатель на строку-источник, осуществляет распределение памяти, копирует в отведенную область строку и возвращает указатель на начало полученной строки-копии. Синтаксис функции следующий:

char* strdup(const char* source)

В следующем примере производится копирование строки strl в строку str2:

char* str1;

str1=new char[21];//динамическое выделение памяти

strcpy(str1, RUS("Процедура не найдена"));

char* str2;

str2 = strdup(str1);

cout<<str2;

delete[]str1;

 

Сравнение строк

 

Сравнение строк необходимо при поиске строк заданного содержания в тексте. В простейшем случае нужно сравнить 2 строки с начала до конца и ответить на вопрос равны они или нет. Приведенная ниже программа использует для этого цикл сравнения и логическую переменную PrEqu.

 

char Str[21];

strcpy(Str, RUS("Проверка копирования"));

int i=0;bool PrEqu=false; //i - счетчик букв

RUS("Проверка копирования");//Занесение русского текста в bufRus

while (bufRus[i]!='\0') //Проверка на конец копируемого массива

{if (Str[i]!=bufRus[i])

{PrEqu=false;break;}//Если буквы не совпали выход из цикла

                                 // с отрицательным результатом PrEqu=false

i++;PrEqu=true;} //цикл сравнения

if (PrEqu) cout<<RUS("строки равны");

else cout<<RUS("строки не равны");

 

Библиотека функций string.h предлагает к использованию готовые функции, выполняющие сравнение строк. Ниже приводятся функции, выполняющие посимвольное сравнение двух строк

Функция strcmp () имеет синтаксис:

int strcmp(const char* strl, const char* str2)

После сравнения строк strl и str2 данная функция возвращает в результате одно из следующих значений:

• <0-еслистрока strl меньше, чем str2;

• =0 - если строки эквивалентны;

• >0 - если строка strl больше, чем str2. Эта функция производит сравнение, различая прописные и строчные буквы. Следующий пример иллюстрирует работу функции strcmp ():

char strl[]="Ошибка открытия базы";

char str2[]="Ошибка открытия Базы";

int i;

i = strcmp(strl, str2);

В результате переменной i будет присвоено положительное значение, так как строка из strl меньше, чем строка из str2, по той причине, что прописные буквы имеют код символов меньше, чем строчные (слово "базы " в первом случае начинается со строчной литеры, а во втором - с прописной).

Функция stricmp () имеет синтаксис:

int stricmp(const char* strl, const char* str2)

Данная функция сравнивает строки strl и str2, не различая регистра символов. Возвращается одно из следующих целочисленных значений:

• <0-еслистрока strl меньше, чем str2;

• =0 - если строки эквивалентны;

• >0 - если строка strl больше, чем str2.

Следующий фрагмент программы демонстрирует применение функции stricmp ():

char strl[]="Moon";

char str2[]="MOON";

int i;

i = stricmp(strl, str2);

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

Функция strncmp () проводит сравнение определенного числа первых символов двух строк. Регистр символов при этом учитывается. Функция имеет следующий прототип:

int strncmp(const char* strl, const char* str2, size_t num)

Данная функция сравнивает num первых символов двух строк, на которые указывают strl и str2, и возвращает одно из следующих значений:

• <0 - если строка strl меньше, чем str2;

• =0 - если строки эквивалентны;

• >0 - если строка strl больше, чем str2.

Рассмотрим пример использования функции strncmp ().

char strl[]="Ошибка открытия базы";

char str2[]="Ошибка Открытия базы";

int i;

i = strncmp(strl, str2, 12);

В результате сравнения первых 12-ти символов обеих строк переменная i получит положительное значение, так как подстроки "Ошибка откры" и "Ошибка Откры" отличаются одним символом и в первом случае код символа больше, чем во втором.

Функция strnicmp () производит сравнение определенного числа первых символов двух строк, не обращая внимания на регистр символов. Данная функция описана следующим образом:

int strnicmp(const char* strl, const char* str2, size_t num)

Функция возвращает целочисленное значение согласно правилу:

• <0 - если строка strl меньше, чем str2;

• =0 - если строки эквивалентны;

• >0 - если строка strl больше, чем str2. В следующем примере производится сравнение заданного числа символов подстрок:

char strl[]="Opening error";

char str2[]="Opening Error...";

int i;

i = strnicmp(strl, str2, 13);

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

 

 

1.6 Преобразование строк

 

Элементы символьных строк могут быть преобразованы из одного регистра в другой. Для этого используются стандартные функции _strlwr() и _strupr(). Следует отметить, что в некоторых версиях компиляторов имена данных функций могут следовать без ведущего символа подчеркивания.

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

char* _strlwr(char* str)

Следующий фрагмент показывает применение функции _strlwr():

char str[] = "ABRACADABRA";

_strlwr(str);

После вызова функции строка str будет преобразована в "abracadabra".

Функция _ strupr () объявлена следующим образом:

char* _strupr (char* str)

Данная функция преобразует строку символов, на которую указывает str, в прописные буквы (к верхнему регистру). В результате работы функции возвращается указатель на полученную строку.

Следующий пример переводит символы заданной строки в верхний регистр:

char str[]="pacific ocean";

_strupr(str);

cout << str;

В результате будет выведено: PACIFIC OCEAN

Приведенные выше функции преобразования строк, работая с указателями, преобразуют исходную строку, которая не может быть восстановлена, поэтому, если в дальнейшем коде программы потребуется воспользоваться оригиналом символьной строки, перед использованием функций _strlwr() и strupr () необходимо сделать копию строки.

На практике довольно широко используются функции проверки принадлежности символов какому-либо диапазону, такие как isalnum(), isalpha(), isascii(), isdigit() и т.д. (см. табл. 8.2), объявленные в заголовочном файле ctype.h. Ниже рассматривается пример использования этого вида функций.

 

char str[4];

do

{

cout << RUS("Сколько тебе лет?\n");

cout << RUS("Введите 3 символа и нажмите Enter\n");

cin.getline(str,4);

if (isalpha(str[0]))

{

cout << RUS("Первый символ буква,");

cout << RUS("Введите заново\n");

continue;

}

if (iscntrl(str[0]))

{

cout << RUS("Первый символ управляющий,");

cout << RUS("Введите заново\n");

continue;

}

if (ispunct(str[0]))

{

cout << RUS("Первый символ знак пунктуации,");

cout << RUS("Введите заново\n");

continue;

}

for(int i=0; i<strlen(str);i++)

{

if (! isdigit (str [i])) continue;

else

{cout << RUS("Ваш возраст ") << str; goto end;}

}

}

while(true);

end:

 

Здесь пользователю предлагается ввести свой возраст. Функция cin.getline () помещает в строку str введенную последовательность (до трех) символов, после чего следует проверка первого введенного элемента массива на принадлежность к буквенной, управляющей последовательностей или символам пунктуации. Если результат соответствующей проверки положительный, пользователю предлагается ввести данные повторно. В противном случае все введенные элементы строки проверяются на принадлежность к цифровому набору данных. Если хотя бы один из символов не удовлетворяет проверяемому условию, цикл ввода повторяется сначала. После корректного ввода данных на экран выводится сообщение о возрасте пользователя.

 

Обращение строк

 

Функция обращения строки strrev() меняет порядок следования символов на обратный (реверс строки). Данная функция имеет прототип:

char* strrev(char* str)

Следующий пример демонстрирует работу функции strrev ().

char str [6];

strcpy(str, RUS("Привет"));

cout << strrev(str);

В результате на экране будет выведена строка "тевирП". Эта функция изменяет строку-оригинал.

 

Поиск символов

 

Одна из часто встречаемых задач при работе со строками - поиск отдельного символа или даже группы символов. Библиотека string.h предлагает следующий набор стандартных функций.

Функция нахождения символа в строке strс hr () имеет следующий прототип:

char* strchr(const char* string, int c)

Данная функция производит поиск символа с в строке string и в случае успешного поиска возвращает указатель на место первого вхождения символа в строку. Если указанный символ не найден, функция возвращает NULL. Поиск символа осуществляется с начала строки.

Ниже рассматривается фрагмент, осуществляющий поиск заданного символа в строке.

 

char str[6];

strcpy(str, "Привет");

char* pStr;

pStr = strchr(str,'и');

cout << RUS(pStr);

 

В результате работы программы указатель pStr будет указывать на подстроку "ивет", т.е. хранить ад



Поделиться:


Последнее изменение этой страницы: 2021-11-27; просмотров: 145; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

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