Федеральное агентство по образованию Федеральное государственное образовательное учреждение высшего профессионального образования «ЮЖНЫЙ ФЕДЕРАЛЬНЫЙ УНИВЕРСИТЕТ»
Чекулаева А.А. МЕТОДИЧЕСКИЕ УКАЗАНИЯ для проведения лабораторного практикума по дисциплине
ЯЗЫКИ ПРОГРАММИРОВАНИЯ для студентов факультета математики, механики и компьютерных наук
Ростов-на-Дону 2007
Методические указания разработаны сотрудником кафедры прикладной математики и программирования, старшим преподавателем А.А. Чекулаевой.
Ответственный редактор
ст. преподаватель А.А. Чекулаева
Компьютерный набор и верстка
ст. преподаватель А.А. Чекулаева.
Печатается в соответствии с решением кафедры прикладной математики и программирования
факультета математики, механики и компьютерных наук
ЮФУ, протокол №8 от 12 апреля 2007 г.
2
СОДЕРЖАНИЕ ВВЕДЕНИЕ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
ТЕМА 1. СТРОКИ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
ТЕМА 2. ИСПОЛЬЗОВАНИЕ ДИНАМИЧЕСКОЙ ПАМЯТИ. . . . . . . . . . .
10
ТЕМА 3. УКАЗАТЕЛИ И СТРОКИ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 ТЕМА 4. СТРУКТУРЫ, ПЕРЕЧИСЛЕНИЯ, ОБЪЕДИНЕНИЯ . . . . . . . . . . . 26 ТЕМА 5. ФУНКЦИИИ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
5.1 АРГУМЕНТЫ, ПРИНИМАЕМЫЕ ПО УМОЛЧАНИЮ. . . . . . . . . 36 5.2 ПОЛИМОРФИЗМ ИЛИ ПЕРЕГРУЗКА ФУНКЦИЙ. . . . . . . . . . . . 38 5.3 ШАБЛОНЫ ФУНКЦИЙ. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 5.4 СПИСКИ АРГУМЕНТОВ ПЕРЕМЕННОЙ ДЛИНЫ. . . . . . . . . . .
43
ЛИТЕРАТУРА . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
3
ВВЕДЕНИЕ Методические
указания
содержат
набор
задач
для
проведения
лабораторного практикума по курсу «Языки программирования» для студентов 1, 2 курсов механико-математического факультета дневного и вечернего отделения специальности “прикладная математика” по темам: строки, использование динамической памяти, структуры,
перечисления, объединения, функции
(аргументы, принимаемые по умолчанию, перегрузка функций, шаблоны функций, списки аргументов переменной длины) на языке C++. Предполагается, что студенты знакомы со структурой программы на языке С++, основными типами данных, управляющими структурами, с понятиями: описание функции, способы передачи параметров, оператор прототипа, вызов функции. В методических указаниях кратко описываются необходимые понятия по каждой теме, приведены решения типовых задач по каждой теме и контрольные вопросы к ним,
сформулированы задачи для самостоятельного решения.
Студентам необходимо описать алгоритм решения своей задачи с использованием языка программирования С++, откомпилировать программу на компьютере и оттестировать ее с использованием полного набора контрольных (тестовых) примеров. Лабораторные работы рассчитаны на то, что студенты знакомы с теорией по рассматриваемым вопросам или изучают её самостоятельно.
4
1 СТРОКИ
Строка – это последовательность символов, которая заканчивается нулевым символом. Строка может быть представлена строковой константой в кавычках. В этом случае нулевой символ неявно подразумевается. Строку можно хранить в массиве типа char, а также представлять её указателем на данные типа char, который указывает на эту строку. Последний символ каждой строки – нулевой символ (записывается как ‘\0’), он служит для обозначения конца строки. Многие функции, которые обрабатывают строки, обрабатывают строку посимвольно до достижения нулевого символа. Для определения длины строки используется функция strlen(). При использовании этой функции включайте в программу файл заголовков cstring или string.h, который обеспечивает объявление этой и многих других функций, связанных со строками. Упражнение 1. Ниже приводятся описания символьных массивов и строки. Поместите описания в программу, дополните программу операторами вывода каждой строки. Вычислите длину каждой строки, просматривая строки посимвольно до достижения нулевого символа, для этого опишите функцию вычисления длины строки. Для вычисления длины каждой строки используйте также функцию strlen. char a[5]={‘a’,’b’,’c’,’d’,’e’}; // не строка, нет нуль-символа char b[5]={‘a,’b’,’c’,’d’,’\0’}; // строка char c[5]=”abcd”;
//строка, подразумевается нуль-символ в конце
char d[]= “stroka”; //строка, компилятор определяет размер массива char *s=”stroka”; //строка, компилятор также определяет размер массива
5
При такой инициализации указателя произойдёт выделение памяти для 7 символов (а, может , для 6 ?), и адрес первого символа станет значением указателей s и d. Указатель s является переменной, d - указатель константа. char x[1]=”a”; // строка из одного символа ‘а’ и нуль-символа, длина строки равна // двум. char y =’a’; // не строка, а символьная переменная со значением ‘а’. сin
определяет конец ввода строки с помощью пробельных символов –
символов пробела, символов табуляции и символа новой строки. Это означает, что cin считывает только одно слово, автоматически добавляя нулевой символ. Для ввода строки, в которой встречаются пробелы, используют функцию gets из стандартной библиотеки ввода/вывода stdio. Функция gets (из stdio) имеет прототип: char *gets(char *s). Функция вводит символы со стандартного устройства ввода в массив s до тех пор, пока не встретится символ новой строки. После этого к массиву добавляется ограничивающий нуль-символ. Для ввода строки из нескольких слов, разделённых пробелами, используют также вызов функции getline: cin.getline(s,size). Класс istream, которому принадлежит объект cin, содержит строчно-ориентированную функцию-элемент getline(), которая читает целую строку, используя переданный с помощью клавиши enter символ новой строки, чтобы пометить конец ввода.
Функция
требует передачи двух параметров. Первый – имя массива, предназначенного для сохранения введённой строки, второй – ограничивает количество символов, которые нужно считать. Если size=20, функция читает не более 19 символов, оставляя место для добавления в конец нулевого символа.
6
Упражнение 2. Внесите, если надо, программу,
проанализируйте
изменения в программу, выполните
результаты.
Затем
повторите
выполнение
программы, используя для ввода строк функции gets и getline. #include
#include using namespace std; int _tmain(int argc, _TCHAR* argv[]) { const int size=20; // символическая константа char s1[size]; //объявление символьного массива размером size char s2[size]=”violinist”; // объявление массива с инициализацией cout<<”s2: “<<s2<<endl; // вывод строки s2 cout<<”s1=?”; //запрос для ввода массива s1 cin >>s1; // ввод массива – ввод строки s1 cout<<”s1: “<<s1<<endl; // вывод введённой строки cout<<”strlen(s1)=”<<strlen(s1)<<endl; // вывод длины строки cout<<”sizeof(s1)=”<<sizeof(s1)<<endl;
//вывод длины массива в котором
//находится строка cout<<s1[0]<<endl; // вывод начального символа строки s1 s2[4]=’\0’; //размещение нуль-символа в строке s2 cout<<s2<<”\n”; //вывод изменённой строки s2, она теперь состоит из 4-ёх //символов system(“pause”); return 0; }
7
Смешанный ввод строк и чисел Рассмотрим фрагмент программы int n; char s[30]; cin>>n; cin.getline(s,30); // или gets(s); или такой: char c; char s[30]; cin>>c; cin.getline(s,30); // или gets(s) Пользователь никогда не получит возможности ввести строку s. Объект cin, cчитав n (во втором случае считывается с), оставляет символ новой строки, сгенерированный с помощью клавиши функция
ENTER, во входной очереди. Затем
cin.getline() воспринимает символ новой строки так, как будто это
пустая строка, и присваивает нулевую строку
элементу массива
s. Чтобы
исправить ошибку, следует считать и отбросить символ новой строки перед чтением строки. Это можно сделать несколькими способами. Один из способов использовать функции cin.get() без параметров. cin>>n; // ввод числа или символа cin.get(); // считать и отбросить символ новой строки cin.getline(s,30); // считать строку функцией cin.getline(s,30) или gets(s) Упражнение 3. Выполните смешанный ввод строк и чисел (строк и символов). Упражнение 4. Протестируйте две функции создания копии строки. Объясните отличия в тексте. void copy1(char *s1, char *s2) {int i; for((i=0; s1[i]!=’\0’; i++) 8
s2[i]=s1[i]; s2[i]=’\0’; } void copy2(char *s1, char *s2) {int i; for((i=0; s1[i]; i++) s2[i]=s1[i]; s2[i]=0; } Упражнение 5. Протестируйте функцию проверки двух строк на равенство: int eq1 (char *s1, char *s2) { int i=0; while(s1[i] != 0 && s2[i] !=0 && (s1[i] == s2[i])) i++; if (s1[i] == 0 && s2[i] == 0) return 1; else return 0; } Сформулируйте алгоритм выполнения функции. ЗАДАНИЯ 1.1 Написать три функции которые копируют строку s1 в строку s2, используя разные циклы: while, for, do…while, функцию strlen не использовать.
9
Написать программу, в которой вводится строка и создаётся её три копии, путём обращения к каждой функции. После каждого вызова функции выводить на экран исходную строку и её копию. 1.2 Подсчитать количество латинских букв и цифр, входящих в заданную строку, функцию strlen не использовать. Разбить исходную строку на две: в первой должны быть только буквы исходной строки, во второй – только цифры, функцию strlen не использовать. Вывести строки. Создать новую строку, разместив в ней вначале строку из цифр, а затем – строку из букв. Вывести полученную строку. 1.3 Ввести и вывести две символьные строки. Проверить, сколько начальных символов в них совпадают, функцию strlen не использовать. Подсчитать количество символов, оставшихся после последнего совпадения в каждой из строк. Составить и вывести новую строку из оставшихся после последнего совпадения символов (начать символами первой строки, продолжить – символами второй строки). 1.4 Ввести строку, содержащую десятичное число с дробной частью (число может иметь знак). Подсчитать число цифр в целой и в дробной части числа, функцию strlen не использовать. Напечатать строку в обратном порядке. 1.5 Написать функцию удаления из строки всех вхождений заданного символа, функцию strlen не использовать. В функции main ввести строку, вызвать функцию для удаления в ней указанного символа. Вывести строку после её изменения. 1.6 Написать функцию удаления из строки s1 к символов, начиная с символа к0, функцию strlen не использовать.
10
1.7 Написать функцию поиска подстроки
s2 в строке s1, функцию strlen не
использовать. Если строка s1 содержит подстроку s2, то функция возвращает указатель на первое вхождение подстроки, иначе функция возвращает нулевой указатель (NULL). 1.8 Написать функцию удаления подстроки s2 из строки s1, функцию strlen не использовать. 1.9 Строка содержит не более 10 слов, разделённых пробельными символами (пробелы, символы табуляции). Написать функцию с двумя аргументами для выделения в строке отдельных слов: s - символьная строка, p – массив указателей на строки (на слова строки s). Функция разбивает строку на отдельные слова, вставляя вместо первого пробельного символа нуль-символ, начало каждого слова запоминается в массиве указателей, функцию strlen не использовать. Последний элемент массива указателей – NULL (признак конца массива указателей). В основной программе описать массив для хранения строки из слов, массив указателей, ввести строку слов, вывести строку, функцией разбить строку на слова, используя массив указателей вывести слова по одному в строке. 2 ИСПОЛЬЗОВАНИЕ ДИНАМИЧЕСКОЙ ПАМЯТИ Указатель – это переменная, которая содержит адрес значения, а не само значение. Мы говорим, что указатель указывает на адрес, который он содержит. При объявлении указателя необходимо сообщать, на объект какого типа он указывает. Результатом применения операции разыменования (*) к указателю является значение по адресу, на который он указывает. Упражнение
6. Выполните программу, в которой объявляются указатели
и
переменные с инициализацией. Выведите значения переменных, используя
11
указатели. Добавьте в программу объявление и инициализацию переменных, а также операторы вывода. int *ip; // объявление указателя на значение типа int float *fp; // объявление указателя на значение типа float char *cp; //объявление указателя на значение типа char int n=5;
//объявление переменной типа int
ip=&n;
//присваивание адреса значения типа int указателю на значение типа int
cout<<*ip; //печать значения, находящегося по адресу ip
Выделение памяти с помощью оператора new Для выделения памяти используется оператор new в виде : <имя_типа> <имя_указателя> = new <имя_типа>; // при объявлении указателя //выполняем выделение памяти < имя_указателя> = new <имя_типа>; // происходит выделение памяти, указатель //должен быть объявлен раньше Для освобождение памяти используется оператор delete в виде delete <имя_указателя> Упражнение 7. Выполните программу, включив в неё операторы: int *ip=new int; flot *fp; fp=new float; cout<<”vvedite int”<<” vvedite ifloat\n”; cin>> *ip>>*fp; cout<<*ip<<” “<<*fp<<endl; delete ip; delete fp;
12
Замените в операторе вывода *ip и *fp на ip и fp. Объясните причину и результаты изменений при выполнении программы. Динамические массивы Для выделения памяти используется оператор: <имя_типа> <имя_указателя> = new <имя_типа>[<количество_элементов>] Для освобождения памяти используется оператор: delete [] <имя_указателя> Упражнение 8. Выполните программу, включив в неё описание функций ввода, вывода и операторы вызова функций. Ниже приводится описание функции создания динамического целочисленного массива из n элементов (выделение памяти и ввод элементов массива), функция возвращает указатель на созданный массив: int *read(int n) { int i,*a; a=new int[n]; for(i=0;i>a[i]; } return a; } Функции вывода массива: void write(int *a,int n) { int i; for(i=0;i
cout<>n; //вводим размер массива b=read(n); //вызываем функцию создания динамического массива, возвращённое //значение указателя на созданный массив присваиваем указателю b. write(b,n); //печать массива Динамическое распределение памяти в стиле С использует функции malloc/free (stdlib.h). Прототипы функций: void * malloc(size_t size) - функция с неопределённым значением выделяет область памяти для объекта с размером, задаваемым аргументом size. Функция возвращает указатель на выделенную область либо нулевой указатель. void free(void *ptr) – функция освобождает память, указанную в ptr, делая её доступной. Упражнение 9. Выполните программу, используя описание функции create и вызов функции create для динамического создания массивов типа int и float. Введите и выведите каждый массив. Используя
функцию
malloc,
можно
динамического массива любого типа. void* create(int n, int size) { void *p; p=malloc(n*size); return p;} 14
написать
функцию
для
создания
В функции create используются аргументы: n - количество элементов, size – размер элемента. Используем функцию create для создания массива из n элементов целого типа: Перед вызовом функции записываем тип указателя (int *) для преобразования возвращаемого функцией значения указателя void* в объявленный тип. int *ip; int size= sizeof(int); //определение размера элемента int n; cin>>n; // ввод количества элементов ip=(int *) create(n,size); //пример создания массива из m элементов типа float float *fp; size=sizeof(float); //размер элемента int m; cin>>m; // количество элементов fp=(float *) create(m,size); Динамические двумерные массивы Упражнение 10. Выполните программу по созданию динамического двумерного массива, воспользовавшись нижеследующими пояснениями. Организовать ввод двумерного массива, вывод и освобождение памяти. int n, m; cout<”input the number of rows and columns:”; cin>>n>>m; // вводим размеры матрицы
15
//далее объявляется переменная типа “указатель на указатель типа int” и //выделяется память под массив указателей на строки массива int **a=new int * [n]; //организуется цикл для выделения памяти под каждую строку массива for (int i=0; i
int **a int *a[n]
int a[n][m]
… … … …
Рисунок 1 - Схема динамической области памяти, выделенной под двумерный массив
16
Освобождение
памяти, занимаемой
таким
массивом, надо
осуществлять
действиями в обратном порядке, вначале освобождается память, выделенная под строки, а затем – сам одномерный. for (int i=0; i
11. Выполните вывод динамического массива, используя
выражения dp[i] и *(dp+i). double *dp=new double[3]; dp[0]=0.2; dp[1]=0.3; dp[2]=0.4; for (int i=0; i<3; i++) cout<
Рассмотрим выражение b[i]. Компилятор рассматривает это выражение так, как будто вы написали его в виде *(b+i), т.е. b[i] то же самое, что и *(b+i). &b[i] - это то же самое, что и b+i. Если же вместо имени массива используется указатель, компилятор выполняет такое же преобразование: <имя указателя>[i] преобразовывается в *(<имя указателя>+i) ЗАДАНИЯ 2.1 Из массива
a все положительные числа переслать в массив b, все
отрицательные – в массив c. Массивы b и c создать как динамические массивы. 2.2 Матрица a содержит не более к нулевых строк. Переслать все ненулевые строки матрицы a в матрицу b. Матрицу b создать как динамический массив из k строк. 3 УКАЗАТЕЛИ И СТРОКИ char s1[20]=”bear”; // массив хранит значение bear const char *s2=”wren”; // указатель хранит адрес строки ”wren” //употребление const означает, что указатель можно использовать для обращения //к строке, но не для её изменения char *p; //указатель не инициализирован cout<
for( ; *p; ) *q++=*p++; *q=0;
}
void copy4(char *s1, char *s2) {char *p, *q; for(p=s1, q=s2;
*q++ = *p++; );
} //оператор присваивания возвращает значение левой части после присваивания //при последнем повторении цикла *p принимает нулевое значение Упражнение 13. Используйте в программе более компактный вариант функции сравнения двух строк. Какие возможные значения возвращает функция ? Подберите примеры для проверки всех возможных результатов. int eq2 (char *s1, char *s2) { int i=0; while(*s1==*s2 && *s1) { s1++; s2++; } return *s1 - *s2; } Упражнение 14. Выполните программу получения копии строки s1 и вывода полученной копии. См. раздел стандартная библиотека для работы с символьными строками. char s1[20]=”bear”; char *p=new char[strlen(s1)+1]; //получение новой области хранения strcpy(p,s1); //копирует строку в новую область хранения cout<
Функции для проверки типа символьных литер Функции для проверки типа символьных литер (Таблица 1), собраны в библиотеке с заголовочным файлом . Аргумент каждой из них имеет тип int, если обратиться к функции с аргументам типа char - он будет приведён к типу int. Таблица 1 Функции для проверки типа символьных литер Функция
Проверяемое условие
isalpha(c)
с - латинская буква
isdigit(c)
с - десятичная цифра
isxdigit(c)
с – шестнадцатеричная цифра
isalnum(c)
с – латинская буква или цифра
iscntrl(c)
с – управляющий символ
isprint(c)
с – печатаемый символ, включая пробел
ispunct(c)
с
–
отображаемый
символ,
не
относящийся к буквам, цифрам или пробелу isspace(c)
с - пробел, новая строка, табуляция
isupper(c)
с –буква верхнего регистра
islower(c)
с –буква нижнего регистра
Стандартная библиотека для работы с символьными строками Подключите заголовочный файл (string.h>). Тип size_t является системно-зависимым синонимом либо для типа unsigned long, либо для типа unsigned int. 20
size_t strlen(const char
*s) – функция возвращает длину строки, адресуемой
параметром s. Символ конца строки не считается. char * strcpy(char *s1,const char *s2) - функция копирует строку, адресуемую параметром s2 , в область памяти, которая начинается с адреса s1. Функция возвращает указатель на строку s1. char *strncpy (char *s1,const char *s2, size_t n) – функция копирует не более n символов из s2 в s1. Если n больше длины строки s2, то недостающие символы забиваются нулевыми, если меньше, то нуль-терминатор не добавляется. Функция возвращает указатель на строку s1. char *strcat(char *s1,const char *s2) - функция дописывает копию строки s2 в конец строки s1. На место нуль символа строки s1 записывается первый символ строки s2. Функция возвращает указатель на строку s1. Строка s2 не меняется. Под s1 должно быть выделено достаточно места, чтобы можно было присоединить s2. char *strncat(char *s1,const char *s2, size_t n) – функция дописывает не более n первых символов строки s2 в конец строки s1. int strcmp(const char *s1,const * s2) - функция выполняет сравнение строк s1 и s2. Сравнение проводится посимвольно, начиная с первых символов. Функция возвращает положительное целое значение, если строка s1 больше строки s2. Функция возвращает отрицательное значение, если строка s1 меньше строки s2. Функция возвращает нуль, если строки совпадают. Сравнение выполняется вычитанием кодов соответствующих символов строк. Последнее вычитание
21
выполняется, когда коды очередных символов не равны друг другу или встретился конец одной или обеих строк (нулевой код). int strncmp(const char *s1,const * s2,size_t n) – функция сравнивает не более n первых символов строк s1 и s2. char *strstr(const char *s1,const char *s2) - функция поиска подстроки s2 в строке s1. Если строка s1
содержит подстроку s2, то функция возвращает
указатель на первое вхождение подстроки, иначе функция возвращает нулевой указатель (NULL). char *strtok( char *s1,const char *s2) - функция для разбиения строки s1 на лексемы.
Лексема
–
это
последовательность
символов,
отделённая
разделительными символами (обычно пробелами или знаками пунктуации). Чтобы разбить строку на лексемы, требуется многократный вызов функции strtok. При первом вызове функция strtok получает два аргумента: строку, которую нужно разбить на лексемы, и строку, содержащую символы, использующиеся для разбиения. Функция strtok выполняет поиск первого символа в строке s1, который не является разделительным символом. С этого символа
начинается
первая
лексема.
Затем
функция
ищет
следующий
разделительный символ в строке и заменяет его нулевым символом (‘\0’). Этот символ завершает текущую лексему. Функция strtok возвращает указатель на найденную лексему и запоминает значение указателя на символ, следующий за найденной лексемой. При последующих вызовах для продолжения разбиения той же строки функция strtok получает в качестве первого аргумента символ NULL Если при очередном вызове функции strtok обнаруживается, что лексем уже не осталось, то функция возвращает NULL. Функция strtok модифицирует исходную строку. 22
char *strset(char *s,int ch) - функция заполняет всю строку s символом ch (до нуль-символа) и возвращает указатель на s. char *strnset(char *s,int ch,int n) - функция заполняет строку s не более чем n символами ch (не заходя за нуль-символ) и возвращает указатель на s. char *strdup(const char *s) – функция копирования строки с выделением памяти, необходимой для копии строки s. Если память выделена успешно и строка скопирована, то функция возвращает указатель на копию строки, если операцию выполнить не удалось, функция возвращает указатель NULL ЗАДАНИЯ 3.1 Написать функцию, проверяющую принадлежность символа к одному или нескольким перечисленным выше типам. Если функция проверяет несколько типов, они перечисляются через запятую. Например х – буква, нижний регистр, печатаемый символ. В функции main ввести символьную строку, латинские буквы нижнего и верхнего регистров, цифры и пробелы. Выполнить проверку каждого символа строки. 3.2 Описать и инициировать строку в виде массива символов, не указывая длины. Вычислить длину строки и динамически выделить память для копии строки. Выполнить копирование (strcpy) и вывести строку и её копию. С помощью функции strncpy заменить в копии строки 5 символов, начиная со второго, строкой (короткой), введённой с экрана. Вывести полученную строку. 3.3 Ввести две строки разной длины (длина каждой строки задаётся вводом, память для каждой строки выделяется динамически). Объединить эти строки в одну строку, выделив память для неё динамически. 23
3.4 Ввести 4 символьные строки. Найти среди них “наибольшую”. На экран выводить промежуточные результаты в виде <Строка-1> ” больше/меньше” < строка-2>. Отдельно напечатать “наибольшую” строку. Описать функцию сравнения двух строк с выдачей соответствующего сообщения. При выходе из функции на место первого параметра-строки помещать указатель на “большую” строку. Функцию вызывать три раза: для первой и второй строк, для большей из них и третьей, для большей из трёх и четвёртой. Объединить эти строки в одну строку, выделив память для неё динамически и разделив их пробелом и символом ‘;’. 3.5 Написать программу, в которой в символьную строку поместить список названий городов, названия разделить пробелом. В цикле в режиме диалога вводить название города и выводить сообщение, есть ли такой город в строке. Признак конца диалога - пустая строка. 3.6 Выполнить и пояснить программу с заданным фрагментом char s[]=”For Example: sodium nitrate; aluminium chloride”; char *ptr; cout<<s<<endl; ptr=strtok(s,” ”); while (ptr!=NULL) { cout<
3.9 В строку текста поместить слова, разделённые символами: табуляция, запятая, двоеточие. Подсчитать количество слов в строке, у которых чётное количество букв, и вывести на экран эти слова и количество букв в них. 3.10 Описать и ввести строку. Заменить символы со второго по шестой на’*’, и до конца строки на ‘.’ 3.11 Написать функцию, которая работает аналогично библиотечной функции strdup. В функции main ввести строку, с помощью функции strdup и собственной функции создать две копии введённой строки 3.12 Вводить строки длиной до 10 символов. Признаком конца ввода использовать пустую строку. Для ввода строк использовать буферный массив из 11символов. Вычислять длину введённой строки , динамически выделять для неё память нужного размера и копировать в неё строку. Затем все введённые строки конкатенировать, динамически выделив для результирующей строки нужный объём памяти. Вывести отдельно каждую строку и объединённую строку. 3.13. Вводить строки, состоящие из слов, разделённых пробелами. Строки могут быть двух типов: первый тип - когда словами могут быть только числа, второй тип – когда слова состоят только из букв английского алфавита. Тип строки определяется по первому символу первого слова в строке. Если в строке содержатся числа, то подсчитать их среднее арифметическое. Если слова строки состоят из букв английского алфавита, то подсчитать сумму кодов символов для каждого слова в строке. Если проверяемый первый символ не буква и не цифра, выдать соответствующее сообщение. Признаком конца вводимых строк является пустая строка. 3.14 Вводить в массив строк строки, содержащие фамилию, имя и отчество. Размер массива задаётся. В каждой строке имя и отчество заменить инициалами с точкой и разделить между собой одним пробелом. Между фамилией и инициалами также поместить один пробел. Для полученных строк память
25
выделять динамически. Адрес каждой полученной строки помещать в массив указателей. Выдать исходные и полученные строки.
4 СТРУКТУРЫ, ПЕРЕЧИСЛЕНИЯ, ОБЪЕДИНЕНИЯ Описание шаблона структуры struct [<дескриптор >] {<элементы структуры>;}; struct employee { char name [30]; //фамилия int age;
//возраст
float salary;
// зарплата
unsigned number; //номер служащего }; employee x, list[50], *sp; employee – шаблон структуры (новый тип). x, list[50], sp – имя структуры, имя массива структур, имя указателя на структуру employee. Структуры можно инициализировать: employee x={“Sidorov”, 32, 4500.5, 1122}; Примеры обращения к элементам структуры: x.name, x.age, x.salary, x.number // используется имя структуры list[i].name // используется имя массива структуры sp->name или (*sp).name // используется указатель на структуру Чтобы присвоить значение одной структуры другой структуре того же типа, можно использовать оператор присваивания.
26
Можно передавать структуры функциям в качестве аргумента как по значению, так и по ссылке. При передаче по ссылке внутри функции доступ к полям структуры осуществляется посредством ->. Структуру можно использовать, как возвращаемое значение функции. Упражнение 15. Опишите шаблон структуры employee, переменную x типа employee , инициализируйте структуру x и выведите поля структуры. Затем удалите инициализацию и организуйте ввод структуры. Для ввода структуры напишите функцию ввода. И, наконец, опишите массив структур типа employee из двух элементов, введите и выведите поля массива структур. Для ввода и вывода массива структур напишите функции. Перечисления Перечисления позволяют создавать новый тип данных с помощью ключевого слова enum. Пример. Enum spectr {red, orange, yellow, green, blue, violet); spectr b; spectr становится именем нового типа данных. red, orang,… становятся символическими константами для целых чисел от 0 до 5 (перечислители ). Переменная b может принимать любое из перечисленных значений. Например, b=orange. Константы, входящие в перечислимый тип, имеют тип int. По умолчанию первая константа равна 0, следующая равна 1, потом – 2 и т.д. Компилятор не видит разницы между типами int и enum. Поэтому переменным типа перечисления в программе могут присваиваться целочисленные значения. Но в языке С++ такое присваивание должно сопровождаться явным приведением типа.
27
Объединения Объединения – это формат данных, который может содержать различные типы данных, но только один тип одновременно. Объединение должно иметь объём, достаточный для того, чтобы сохранить самый большой элемент (размер объединения соответствует размеру его самого большого элемента). union un {int a; float b; char c[20];}; un x; Переменную х можно использовать для хранения элемента типа int, или типа float,
или символьного массива. Одна из целей применения объединения –
экономия памяти. Упражнение
16.
Напишите программу ввода и вывода структуры типа N.
Воспользуйтесь функцией ввода, которая приводится ниже, функцию вывода напишите сами. union un{int a;long b;float c;}; // шаблон для объединения struct N{char t; un x;};
// шаблон для структуры
Структура содержит символьную переменную t и переменную х типа объединение. Ниже следует функция ввода структуры типа N. Внутри функции используется переменная flag типа перечисление, принимающая значения repeat или end. Цикл повторяется, если flag==repeat. Если flag==end, то происходит выход из цикла. Функция вводит в поле х структуры с указателем p: целое значение, если введен символ ‘i’ в символьную переменную t; значение типа long, если введен символ ‘l’ в символьную переменную t; значение типа float, если введен символ ‘f’ в символьную переменную t, после ввода происходит выход из функции. 28
Если в символьную переменную t введен символ ‘q’, поле х не определяется. Если символьная переменная t получает значение символа, отличное от перечисленных символов, ввод повторяется. void input(N *p) {
enum{repeat,end} flag; flag=repeat; while (flag==repeat) {
flag=end; cout<<"t=?"; cin>>p->t; switch(p->t){
case 'i': {cout<<"int=? "; cin>>p->x.a;break;} case 'f': {cout<<"float=?"; cin>>p->x.c;break;} case 'l': {cout<<"long=?"; cin>>p->x.b;break;} case 'q':break; default: { cout<<”input repeat ? “;flag=repeat;} } }} Упражнение 17. Выполните сортировка массива записей по различным ключам сортировки. Пусть массив состоит из записей, каждая запись содержит следующие поля: фамилия (fio), номер курса студента (kurs) и средний балл (srb).
Требуется
отсортировать массив записей в порядке возрастания значения каждого поля по очереди. С полями этой записи (fio, kurs, srb) будем ассоциировать элементы 29
аfio, аkurs, аsrb - элементы перечислимого типа key. Тип key используется в процедуре сортировки для выделения соответствующего признака сортировки. Записи хранятся в массиве а, ключ сортировки – в переменной w, размер массива – в переменной n. Процедура сортировки использует метод пузырька, который теперь приспособлен для работы с различными ключами сортировки. Каждый ключ подразумевает свой критерий сортировки. Разветвление по ключу осуществляется оператором switch. Массив записей после сортировки по выбранному ключу изменяется. Используйте в программе следующий фрагмент: enum key{afio,akurs,asrb}; struct rec{char fio[10];int kurs;float srb;}; void sort(rec*a,int n,key w) {
int i,j; bool b; rec x; for( i=n-1;i>=1;i--) for( j=0;j<=i-1;j++) {switch (w ){ case afio:{b=strcmp(a[j].fio,a[j+1].fio)>0;break;} case akurs:{b=(a[j].kurs>a[j+1].kurs);break;} case asrb:{b=a[j].srb>a[j+1].srb;break;} default: cout<<3<<' '; } if(b) {x=a[j];a[j]=a[j+1];a[j+1]=x;} }
} 30
void print(rec*a,int n) {
int i; for( i=0;i
} int _tmain(int argc, _TCHAR* argv[]) { rec a[5]; int i,n; cout<<"n=?"; cin>>n; for(i=0;i>a[i].fio; cout<<"kurs"; cin>>a[i].kurs; cout<<"srb";cin>>a[i].srb;}; key w; cout<<"--------------------------\n"; print(a,n); for (w=afio; w<=asrb; {
w=(key)((int)w+1))
cout<<w<<endl; sort(a,n,w); print(a,n); }
Объясните синтаксис оператора в строке, которая приводится ниже: case afio:{b=strcmp(a[j].fio,a[j+1].fio)>0;break;} 31
И, наконец, разберёмся в вопросе: как в цикле можно использовать параметр перечислимого типа? Оператор цикла с параметром w перечислимого типа требует явного приведения типа для получения в w следующего значения из списка перечисленных констант в определении перечислимого типа key: w=(key)((int)w+1)). Поясним поэтапно выполнение оператора цикла: for (w=afio; w<=asrb; {
w=(key)((int)w+1))
cout<<w<<endl; sort(a,n,w); print(a,n); }
1)переменной перечислимого типа w присваиваем первое из перечисленных значений w=afio; 2)значение переменной w сравниваем с конечным значением перечислимого типа: w<=asrb; 3) так как проверяемое условие истинно, то выполняем операторы тела цикла: {
cout<<w<<endl; sort(a,n,w); print(a,n); }
4) “увеличиваем” на 1значение переменной w: (int)w+1(результат имеет тип int); 5)преобразуем результат к перечислимому типу key и присвоим результат переменной w: w=(key)((int)w+1); 6)повторяем пункта, начиная с пункта 2.
32
ЗАДАНИЯ 4.1 Объявить шаблон структуры vector, содержащую координаты вектора в трёхмерном пространстве (координаты – целые числа). Описать функцию ввода вектора с аргументом - указатель на структуру. Второй вариант функции ввода – функция без параметров, возвращаемым значением является структура. Описать функцию вывода вектора (аргумент – параметр значение). Описать функцию сложения двух векторов в двух вариантах: один вариант –функция имеет три аргумента, два входных и один выходной; второй вариант – функция описывается как функция с возвращаемым значением и двумя входными аргументами. В функции main описать четыре структуры типа vector, первую инициализировать при описании, вторую ввести, используя первый вариант функции ввода, третью ввести вторым вариантом функции ввода. Сложить первую и вторую структуры первым вариантом функции сложения, сложить первую и третью структуры вторы вариантом функции сложения, результат сложения помещать в четвёртую структуру. После выполнения каждого сложения выводить оба слагаемых и их сумму. 4.2
Объявить шаблон структуры vector, содержащую координаты вектора в
трёхмерном пространстве (координаты – целые числа). Описать массив из
трёх
векторов, ввести их координаты, Найти их общую сумму, вывести слагаемые и сумму. 4.3 Опишите шаблон структуры tour (страна, продолжительность тура, транспорт, цена). Инициализируйте данные в массиве, состоящим из n элементов типа tour. Выведите на экран информацию о странах, стоимость билета в которые меньше введённого с клавиатуры числа. Если таких стран нет, то программа должна выдать соответствующее сообщение на экран. 33
4.4
Опишите шаблон структуры note (фамилия и имя, номер телефона, день
рождения) Введите с клавиатуры данные в массив, состоящий из n элементов типа note. Поместите в другой массив информацию о людях, день рождения которых приходится на зимние месяцы. Выведите на экран полученный массив. Если таких нет (массив пуст), то программа должна выдать соответствующее сообщение на экран. Описать функции ввода и вывода структуры. 4.5 Объявите шаблон структуры info (наименование товара- name, дата сделки data, тип сделки flag (купля/продажа), количество единиц товара - count, стоимость единицы товара - price). Объявите шаблон структуры account (фамилия клиента - fam, сумма денег на счёте - acc). Для определения типа сделки ввести именованные константы BUY=1 (купля) и SELL=-1 (продажа). Описать функцию ввода структуры типа info без параметров с возвращающим значением типа info. Для вывода информации о сделке написать функцию, которая получает два аргумента структуру типа info и структуру типа account и выводит их на экран. Для обработки результатов сделки написать функцию, которая
получает два
аргумента структуру типа info (сделка) и структуру типа account (кто осуществляет сделку) и возвращает структуру типа account, содержащую изменённую в соответствии со сделкой сумму на счёте. В функции main описать и инициировать две переменные x,y типа account. Описать две переменные b (для купли), s (для продажи) типа info и ввести их с помощью функции ввода. Получить новые значения переменных x и y с помощью функции обработки. Результаты сделок вывести с помощью функции вывода. 4.6 Напишите программу ведения банковских счетов на основании чеков. Структура исходной записи: фамилия клиента, номер счёта, текущее состояние счёта. 34
Структура сведений об операциях: номер счёта клиента, сумма прихода (положительная) или расхода (отрицательная). Вводится массив структур первого и второго типа, выполняется по найденному номеру счёта операция для соответствующего клиента, печатаются сведения в записях и новое содержимое записи клиента. Если окажется, что какой-либо чек выписан на сумму, превышающую остаток счёта, программа должна печатать соответствующее сообщение. 4.7
Описать шаблон структуры person (указатель name на тип char –фамилия
человека, целое значение age – возраст). Написать функцию ввода структуры типа person с аргументом типа указатель на тип person (указатель на ту структуру, которую надо заполнить). Фамилия не более 20 символов вводится во вспомогательный массив, затем вычисляется длина введённой строки, выделяется необходимая для неё память и строка копируется из вспомогательного массива в выделенный блок. Возраст вводится обычным способом. Написать функцию вывода, функция в качестве аргумента получает структуру типа person и выводит её. Если структура не заполнена, выводится сообщение “структура не заполнена”. Написать функцию , в которой объявить массив из 5 элементов типа person. Первые три элемента ввести с помощью функции ввода. Остальные элементы оставить не заполненными (указателю name присвоить NULL). С помощью функции вывода вывести значения всех элементов массива.
4.8 В функции main объявить массив из пяти переменных типа N (см. пример в разделе объединение). Ввести элементы массива, используя описанную выше функцию input. 35
В четвёртом и пятом элементах массива поле х не вводить. Вывести все пять элементов. Для этого описать функцию вывода, которая выводит сообщение о типе значения и само значение, хранящееся в структуре. Если тип значения имеет признак
‘q’ (это говорит об отсутствии значения), то выводится сообщение:
“значения нет”.
5 ФУНКЦИИ 5.1 Аргументы, принимаемые по умолчанию Такие аргументы представляют собой значения, которые используются автоматически, если соответствующие фактические параметры в обращении с функции опущены. Значения по умолчанию аргументам присваиваются в прототипе
(это
значения,
задаваемые
при
инициализации).
Аргументы,
определённые по умолчанию, могут быть представлены в списке аргументов только в порядке – справа налево. При вызове функции они могут принимать задаваемые значения или принимать значения по умолчанию. Если нужно использовать значение по умолчанию для определённого аргумента, следует использовать по умолчанию значения и для всех остальных аргументов, расположенных справа от него. Аргументы, принимаемые по умолчанию, позволяют вызывать одну и ту же функцию с указанием различного количества аргументов. Упражнение 18. В программе используется функция, возвращающая указатель на новую строку, содержащую первые n символов заданной строки. Измените функцию так, чтобы она возвращала
указатель на новую строку, содержащую n
символов заданной строки, начиная с символа i0 заданной строки. n и io аргументы со значениями по умолчанию. Выполните функцию для различных вариантов входных парaметров. Добавьте функцию, которая возвращает указатель 36
на строку, содержащую три символа заданной строки. Номера символов: i0, i1, i2 – аргументы со значениями по умолчанию.. const int Size=80; char *left(const char *s, int n=1); int main() { сhar s1[Size]; сout<<”enter a string:\n”; сin.get(s1,Size); сhar *ps=left(s1,4);//вызов функции сout<
___________________________________ Результаты выполнения программы enter a string forthcoming fort f Один из нежелательных моментов состоит в том, что n может быть больше длины строки. Функция защищена от подобных случаев проверкой: i
позволяет использовать
несколько функций с одним именем. Сигнатурой функции является список её аргументов. Сигнатуры могут различаться по количеству аргументов или по их типам, либо по тому и другому. Язык С++ позволяет определять несколько функций с разными сигнатурами. Упражнение 19. В программе определены две функции с именем left: одна возвращает указатель на первые n символов в строке, вторая возвращает первые n цифр записи целого числа. 38
Определите третью функцию left, которая возвращает массив, с элементами, меньшими максимального элемента заданного вещественного массива из n элементов. В список параметров включить также количество элементов в возвращаемом массиве. Определите ещё одну функцию с именем
left, которая по двум заданным
строкам s1 и s2 строит возвращаемую строку (функция возвращает значение типа char *): к первым n символам строки s1 добавляются n первых символов строки s2. unsigned long left(unsigned long num, unsigned nc); char *left(const char *s, int n=1); int main() { сhar s=”name”; unsigned long n=12345678; int i; char * s1; for(i=1; i<4; i++) { cout<
указатель на новую строку, содержащую n первых
//символов заданной строки 39
char *left(const char *s, int n); { if (n<0) n=0; char * p=new char[n+1]; int i; for(i=0; inc) { nc=d-nc; while (nc--) Num/=10; return num; //возвращает nc цифр } 40
else return num; //если nc>=количества цифр, возвращает всё число } ___________________________________ Результаты выполнения программы 1 n 12 na 123 nam 1234 name Как изменятся результаты, если в основной программе использовать проверку: i<8 ? 5.3 Шаблоны функций Шаблоны функций определяют функцию на основе обобщённого типа, вместо которого
может
быть
подставлен определённый
тип
данных.
Определение шаблона функции: template <список формальных параметров шаблона функции> <Определение шаблона> Каждый формальный параметр имеет вид: class обобщённый тип. Определение шаблона не отличается от определения любой функции. Каждый формальный параметр из заголовка шаблона должен появиться в списке параметров функции по крайней мере один раз.
41
Шаблоны функций дают возможность порождать функции, которые выполняют одинаковые операции над различными типами данных, однако сам шаблон функции при этом определяется один раз. Упражнение 20. Приводится пример использования шаблона функций Добавить шаблоны других функций: функции ввода, функцию проверки, содержится ли заданный элемент в массиве, функцию сортировки массива по не убыванию,
функцию
слияния
двух
упорядоченных
массивов
упорядоченный массив. template void print(T *ar, const int n); main() { const int an=5, bn=3, cn=6; //размеры трёх массивов разного типа int a[an]={10, 3, 30, 4, 1}; float b[bn]={7.7, 2.2, 6.6}; char c[cn]=”HELLO”; // 6-ая позиция для нуля cout<<”a: \n”; print(a,an);// версия для целых чисел cout<<”b: \n”; print(b,bn);// версия для чисел с плавающей точкой cout<<”c: \n”; print(c,cn);// версия для символов return 0; } template void print(T *ar, const int n) { for (int i=0; i
в
один
5.4 Списки аргументов переменной длины Существует возможность создать функцию, число аргументов которой не фиксировано. Макросы и определения заголовочного файла переменных аргументов stdarg.h предоставляют программисту средства, необходимые для построения функций со списком аргументов переменной длины. Таблица 2 Тип и макросы, определённые в заголовочном файле stdarg.h Идентификатор Объяснение va_list
Тип,
предназначающийся
для
хранения
информации,
необходимой макросам va_start, va_arg, va_end. Чтобы получить доступ к объектам переменной длины, необходимо объявить объект типа va_list va_start
Макрос,
который
вызывается
перед
обращением
к
аргументам списка переменной длины. Макрос инициирует объект, объявленный с помощью va_list для использования макросами va_arg и va_end va_arg
Макрос, расширяющийся до выражения со значением и типом следующего аргумента в списке переменной длины. Каждый вызов va_arg изменяет объект, объявленный с помощью va_list так, что объект указывает на следующий аргумент списка.
va_end
Макрос обеспечивает нормальный возврат из функции, на список аргументов которой ссылался макрос va_start
Макрос - это операция, определяемая при помощи директивы препроцессора #define. Как и в случае символических констант вместо идентификатора макроса в программу подставляется заменяющий текст. Допускается определение макроса с аргументом и без них. 43
Упражнение 21. Выполните программу, в ней функция av произвольное количество аргументов. #include <stdarg.h>
получает
double av(int i,...); //многоточие в прототипе функции означает, что функция получает переменное число //аргументов любого типа. Многоточие всегда находится в конце списка параметров main() { double w=37.5, x=22.5, y=1.7, z=10.2; cout<<w<<” “<<x<<” “<
обработки списка аргументов переменной длины функции av. Функция начинается вызовом макроса va_start, инициализирующего объект a. Макрос получает два аргумента: объект a и идентификатор самого правого параметра в списке перед многоточием. В данном случае это i (va_start нужен идентификатор i для определения того, где начинается список аргументов переменной длины). Затем функция av последовательно складывает аргументы из списка в переменной t. Прибавляемое к t значение извлекается из списка аргументов вызовом макроса va_arg. Макрос va_arg получает два аргумента: объект a и тип значения, ожидаемого в списке аргументов функции. В данном случае это double. Макрос возвращает значение аргумента. Функция av вызывает макрос va_end с объектом a для упрощения нормального возврата из av в main.
И наконец,
вычисляется среднее и его значение возвращается в main. ___________________________________ Результаты выполнения программы 37,5 22.5 1.7 10.2 30.000 20.567 17.975 Упражнение 22. Написать программу для ввода и сохранения информации о геометрических фигурах
(квадратах,
прямоугольниках
и
треугольниках).
Информация
накапливается в символьной строке, в которую последовательно вносят записи типа “треугольник 3 4 5;”. Каждая фигура определяется: - числовым признаком типа геометрической фигуры (соответственно 1, 2, 3), - длинами сторон фигуры (одно, два или три целых числа). Данные о фигуре вводятся в диалоге: 45
а) определяется тип фигуры: по запросу вводится целое число (соответственно 1, 2, 3), б) вводятся длины сторон (количество зависит от типа фигуры). Значение признака фигуры 0 (нуль) предназначено для выхода из программы с одновременной печатью сформированной к этому моменту символьной строки. Для формирования результирующей строки написать функцию void fill_str(char * s, int fig,…), которая передаёт указатель на формируемую строку, тип фигуры и длины сторон. Для
занесения
информации
в
строку
можно
использовать
функцию
sprintf.(заголовочный файл <stdio.h>). Функция sprintf - эквивалент функции printf за исключением того, что результат вывода запоминается в массиве s, а не отображается на экране. Прототип функции: int sprintf(char *s,const char *format, …) Пример использования: char s[80]; int x; float y; cin>>x>>y; sprintf(s,”%d %f”,x,y); В результате строка s будет содержать два числа – одно целое, второе с плавающей точкой. Числа будут разделены пробелами.
46
СПИСОК ЛИТЕРАТУРЫ 1 Стивен Прата. Язык программирования C++. Лекции и упражнения: Пер. с англ. – М.: ООО ”ДиаСофтЮП”, 2005. – 1104 с. – ISBN 0-672-32223-4 2 Е.Л. Шиманович. С/С++ в примерах и задачах. – Мн. – Новое знание, 2004. – 528 с. – ISBN 958-475-100-7. 3
М.Э.
Абрамян.
Программирование
и
вычислительная
физика.
Методические указания. /М.Э. Абрамян, Е.О. Захаренко, С.С. Михалкович, Ф.М. Столяр. – Ростов-на-Дону : УПЛ РГУ, 2005. – 41 с. 4 Д. Ван Тассел Стиль, разработка, эффективность, отладка и испытание программ: Пер. с англ. – 2-е изд., испр., - М.: Мир, 1985. – 332 с. 5 Юркин А. Задачник по программированию. – СПб.: Питер, 2002. - 192 c. – ISBN 5-318-00399- 0. 6. Х.М.Дейтел. Как программировать на С++. /П.Дж Дейтел: Пер. с англ. – М.: Бином, 2000. – 1024 с. – ISBN 574840016-4. 7. Герберт Шилд. С++. Базовый курс, 3-е издание.: Пер. с англ. – М.: Издательский дом “Вильямс”, 2005. – 624 с. – ISBN 5-8459-0768-3.
47