Функции с параметрами по умолчанию

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

getline(char*p, int n, char s='\n');

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

Параметры по умолчанию назначаются с конца списка параметров.

Перегрузка функции

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

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

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

1. Преобразовании типа

2. Использовании параметров ссылок

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

/

float f(float a);

double f(double a);

int main (void)

{

int a =...;

float b =...;

double c =...;

f (b);

f (c);

f (a); // неоднозначность. возникнет ошибка компиляции

f ((double)a); // таким образом можно устранить неоднозначность

return 0;

}

/

void f1(int a);

void f1(int&a);

int main (void)

{

int a =...;

f1(a); // неоднозначность

return 0;

}

/

void f2 (int a);

int f2 (int a, int b=3);

int main (void)

{

int a =...;

f2(a); // неоднозначность

return 0;

}

Шаблоны функции

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

template <class T>

имя функции

{

тело функции

}

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

Пример:

Написать функцию-шаблон для вывода двумерного массива на экран.

Функция должна выводить двумерный массив любого типа, следовательно, параметризованный тип выражает тип массива

shablon.h

----------------------

#ifndef SHABLON_H

#define SHABLON_H

#include<iostream> //подключение системных средств для

using namespace std; //возможности использовать потоки ввода-вывода

#include <iomanip>

template <class T>

void OutMas(T*p,int n,int m) {

int i,j;

for (i=0; i<n;i++) {

for (j=0; j<m;j++) {

cout << setw(5) << *(p+i*m+j);

}

cout << endl;

}

return;

}

#endif

----------------------

main.cpp

----------------------

#include<iostream> //подключение системных средств для

using namespace std; //возможности использовать потоки ввода-вывода

#include "shablon.h"

int main (void)

{

int A[2][3]= {1,2,3,4,5,6};

double B[3][3] = {1.5,2.6,3.7,4.8};

cout << "\n Целый массив:\n";

OutMas(A[0],2,3);

cout << "\n Дробный массив:\n";

OutMas(B[0],3,3);

return 0;

}

----------------------

При компиляции этой программы компилятор по вызову функции первый раз, определяет, что тип массива - целый и создает экземпляр функции OutMas, заменяя имя параметризованного типа T на int. Аналогично, видя вызов функции с типом массива - double, компилятор создает еще один экземпляр функции OutMas, заменяя T на double. Процесс создания экземпляра функции называется инстанцирование.

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

Например: OutMas <int>(A[0],2,3);

типы данных, определяемые пользователем

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

С помощью структур, объединений и перечислений можно моделировать сложные объекты, возникающие при решении задач.

4.1. Структуры

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

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

ü Описать шаблон для структуры

ü Объявить переменные, соответствующие этому шаблону

ü Научиться работать со структурной переменной через поля структуры или как с единым целым.

Шаблон структуры

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

struct Tel {

char name[25]; //имя абонента

long number; //номер телефона

};

Данный текст описывает тип структуры с именем шаблона Tel, состоящий из двух полей: строки name и целой переменной number типа long.

Имя поля структуры может быть таким же, как имя объекта вне структуры.

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

Установка шаблона не вызывает никаких действий в программе, машина просто будет «узнавать» созданный нами тип, который называется Tel.

Структурные переменные

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

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

Tel tel1, tel2[5], *tel3;

Инициализация структуры

Объявить структурную переменную можно с инициализацией, например, объявим массив из двух структур с инициализацией:

Tel tel[2]={

"Иванов Ф.А.", 456756,

"Петров В.П.", 632345

};

Доступ к полям структуры

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

При использовании имени структурной переменной доступ к полям структуры осуществляется через символ “.” (точка), а при использовании адреса – через символ “- >” (стрелочка).

Продемонстрируем правила обработки структурной переменной (в дальнейшем – структуры) на примерах.

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

// Обращение к полям структуры через имя переменной

#include<iostream> //подключение системных средств для

using namespace std; //возможности использовать потоки ввода-вывода

struct Tel{

char name[25];

long number;

};

void main(void)

{

Tel tel1, tel2;

cout<<"\nвведите фамилию абонента";

cin>>tel1.name;

cout<<"введите его номер";

cin>>tel1.number;

tel2=tel1; //работа со структурой как с единым целым

cout<<"\nВведено:"<<endl;

cout<<"Фамилия : “<< tel2.name;

cout<<” номер: "<<tel2.number;

}

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

// Обращение к полям структуры через указатель

#include<iostream> //подключение системных средств для

using namespace std; //возможности использовать потоки ввода-вывода

struct Tel{

char name[25];

long number;

};

void main(void)

{

Tel *tel1, *tel2;

/* Выделение памяти для структуры */

tel1=new Tel;

tel2=new Tel;

cout<<"\nвведите фамилию абонента";

cin>>tel1->name;

cout<<"введите его номер";

cin>>tel1->number;

*tel2= *tel1; // нельзя так же сравнивать структуры

cout<<"\nВведено:"<<endl;

cout<<"Фамилия : “<< tel2->name;

cout<<” номер: "<<tel2->number;

}

Массив структур

Пример 3. Объявить массив структур статически, заполнить его данными с клавиатуры, а затем организовать в массиве поиск номера телефона абонента по его фамилии.

/* Массив структур. Обращение к полям структуры через

имя элемента массива */

#include<iostream> //подключение системных средств для

using namespace std; //возможности использовать потоки ввода-вывода

#include <string.h>

struct Tel{

char name[25];

long number;

};

void main(void)

{

Tel tel[5]; /* массив структур - 5 элементов */

char name[25];

int i;

int flag=0; //признак ситуации: абонент не найден

/* ввод данных в массив структур */

for(i=0; i<5; i++)

{

cout<<"\nвведите фамилию абонента";

cin>>tel[i].name;

cout<<"введите его номер";

cin>>tel[i].number;

}

cout<<"\nВыбор телефона по фамилии";

cin>>name;

/* поиск структуры по фамилии абонента */

for(i=0; i<5; i++)

if(!strcmp(name,tel[i].name)) //если строки совпали

{

cout<<"\n номер абонента “<< name ;

cout<<” равен "<<tel[i].number<<endl;

flag=1; //признак ситуации: абонент найден

}

if(flag==0)

cout<<"Абонент не найден"<<endl;

return;

}

Задание 1. Перепишите пример 3, выделяя память под массив динамически и обращаясь к полям структур через адреса структур.

Вложенные структуры

Структура, являющаяся полем другой структуры, на­зывается вложенной.

Заметим, что в характеристики абонента телефона, как правило, входит и адрес абонента. Опишем шаблон структуры Address следующим образом:

struct Address {

char street[25];

int house;

int flat;

};

После этого полное описание характеристик абонента телефона будет выглядеть так:

struct Sprav {

char name[25];

long number;

Address addr;

};

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

Пример 4. Объявить структуру с вложенной структурой. Заполнить поля структуры с помощью констант соответствующего типа.

Sprav sp;

strcpy(sp.name, “Иванов И.И.”);

sp.number=452230;

strcpy(sp.addr.street, “ пр. Машиностроителей ”);

sp.addr.house=17;

sp.addr.flat=98;

Битовые поля структур

Целочисленные данные могут быть помещены в маленький объем памяти с использованием битовых полей. Битовые поля структуры используются для плотной упаковки данных, имеющих небольшой диапазон изменения, например, для хранения флагов состояний, значение которых 0 или 1 и требует 1бита памяти.

В заголовочном файле dos.h описывается шаблон структуры ftime, поля которой содержат время и дату. Размер каждого поля определен в битах, при этом шесть данных занимают 4 байта памяти:

struct ftime {

unsigned ft_sec: 5;

unsigned ft_min: 6;

unsigned ft_hour: 5;

unsigned ft_day: 5;

unsigned ft_month: 4;

unsigned ft_year: 7; //год – 1980

};

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

Передача структуры в функцию

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

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

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

Массив структур передается по адресу (аналогично одномерному числовому массиву).

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

Примечание. Передача структуры по ссылке осуществляется проще.

Пример 5. Написать программу с функциями:

ü Заполнения одной структуры данными с клавиатуры

ü Вывода одной структуры на экран монитора.

Проанализируем характеристики функций:

у первой функции поля переданной структуры изменяются (вследствие ввода), поэтому в функцию следует передать адрес или ссылку объявленной структуры;

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

.

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

#include<iostream> //подключение системных средств для

using namespace std; //возможности использовать потоки ввода-вывода

#include <iomanip>

struct Tel{

char name[25];

long number;

};

void InStruct(Tel & r);

void OutStruct(const Tel & r);

void main(void)

{

Tel tel;

cout<<»\nВведите структуру:»;

InStruct(tel);

cout<<”\nВаша структура:»;

OutStruct(tel);

}

/* Функция заполнения структуры. Исходное данное – ссылка на структуру */

void InStruct(Tel &r)

{

cout<<”nВведите фамилию ”;

cin>>r.name;

cout<<“Введите номер телефона ”;

cin>>r.number;

return;

}

/* Функция вывода структуры. Исходное данное – ссылка на неизменяемый объект */

void OutStruct(const Tel & r)

{

cout<<setw(20)<<r.name<<setw(10)<< r.number<<endl;

return;

}

Двоичный ввод-вывод структур

Хранение большого количества структур на диске удобнее всего осуществлять в двоичном виде, т.к. при этом:

ü Со структурой обращаемся как с единым целым;

ü При вводе-выводе не тратится время на преобразование данных из символьной формы в двоичную и наоборот;

ü При отсутствии вышеуказанного преобразования не происходит потеря точности вещественных чисел;

ü Каждая структура в двоичном файле занимает одинаковое количество байт, что дает возможность, используя функцию fseek, считывать, изменять и удалять любую структуру непосредственно в файле.

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

Функции двоичного ввода-вывода

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

FILE *in, *out;

in=fopen(“file1.dbl”, “rb”); //файл открыт для чтения в двоичном режиме

out=fopen(“file2.dbl”, “wb”); //файл открыт для записи в двоичном режиме

Функция fread производит ввод из потока в структурную переменную и имеет четыре аргумента:

ü Адрес структуры, куда будет производиться чтение;

ü Размер одной структуры в байтах;

ü Количество читаемых структур;

ü Имя потока.

В качестве результата функция возвращает количество реально введенных структур, а в случае обнаружения конца файла – 0.

Пример 6. Прочитать очередную структуру из потока in в переменную tel.

Fread(&tel, sizeof(tel), 1, in);

Пример 7. Пусть из потока in необходимо заполнить массив структур, при этом известно, что структур не более 100.

TEL tel[100];

int n;

n=fread(tel, sizeof(tel[0]), 100, in);

После ввода определяем реальный размер массива – n.

Количество структур в файле можно узнать и поделив размер файла на размер одной структуры.

Функция fwrite производит вывод из структурной переменной в поток и имеет четыре аргумента:

ü Адрес записываемой структуры;

ü Размер одной структуры в байтах;

ü Количество записываемых структур;

ü Имя потока.

В качестве результата функция возвращает количество реально записанных структур.

Пример 8. Записать структуру tel в поток out.

Fwrite(&tel, sizeof(tel), 1, out);

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

Проанализируем функцию:

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

В качестве результата возвратим сигнал (0 или 1) об успешности операции.

Вспомните режим открытия потока, при котором происходит накопление (дозапись) данных.

#include<stdio.h>

int AppendStructFile (char * NameFile)

{

FILE * out;

TEL tel;

out=fopen(NameFile, “ab”);

if (out==NULL)

return 0;

puts(“\nВведите структуру”);

InStruct(tel);

fwrite(&tel, sizeof(tel), 1, out);

return 1;

}

Динамические структуры данных

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

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

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

Элемент любой динамической структуры представляет собой структуру, содержащую два поля – для хранения данных и адреса, указывающего на другую структуру.

struct Info // структура для данных

{

};

struct Node

{

Info info;

Node *next;

};

Наиболее общей динамической структурой является линейный список.

Линейный список называется однонаправленным, если каждый элемент списка содержит адрес следующего элемента. У последнего элемента адрес 0.

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

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

Список определяется адресом начала списка.

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

Линейный список можно рассматривать как абстрактный тип данных, над которым разрешены следующие операции:

ü Начальное формирование списка;

ü Добавление элемента в конец списка;

ü Чтение элемента с заданным ключом;

ü Вставка элемента в заданное место списка (до или после ключа);

ü Удаление элемента с заданным ключом;

ü Сортировка списка по ключу;

ü Вывод списка на экран;

ü Освобождение памяти, занимаемой списком.

Каждая операция реализуется в виде функции.

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

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

Проанализируем функции:

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

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

Функция print выводит список на экран, зная адрес начала списка. Результат не возвращается.

В программе используются переменные: pbegin – для хранения адреса начала списка, pend – для хранения адреса последней структуры в списке.

#include<iostream> //подключение системных средств для

using namespace std; //возможности использовать потоки ввода-вывода

struct Info

{

int d;

};

struct Node

{

Info info;

Node *next;

};

Node * first(void);

Node * add(Node * pend);

void print(Node * pbegin);

void main(void)

{

Node *pbegin, *pend;

int i;

pend=pbegin=first(); //создали список

for(i=0; i<5; i++) //добавили еще 5 элементов в список

pend=add(pend);

print(pbegin); //вывели весь список на экран

return;

}

//Начальное формирование списка

Node * first(void)

{

//Выделяем память под элемент списка

Node * pv=new Node;

cout<<“\nВведите число”;

cin>>pv->info.d;

pv->next=0; //Адрес последней структуры в списке - 0

return pv; //возврат адреса начала списка

}

//Добавление элемента в конец списка

Node * add(Node * pend)

{

//Выделяем память под очередной элемент списка

Node * pv=new Node;

cout<<“\nВведите число”;

cin>>pv->info.d;

pv->next=0; //Адрес последней структуры в списке - 0

pend->next=pv; //Сцепляем по адресу созданную структуру со списком

return pv; //возврат нового адреса последней структуры в списке

}

//Вывод списка на экран

void print(Node * pbegin)

{

Node * pv=pbegin;

while (pv) //пока адрес текущей структуры списка не 0

{

cout<< pv->info.d<<endl;

pv=pv->next; //переход к следующей структуре в списке

}

return;

}

Задание 4. Добавьте к вышеприведенной программе еще какие-либо операции над списком.

Дадим краткие определения других динамических структур данных.

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

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

Бинарное дерево – это структура, состоящая из узлов, каждый из которых содержит, кроме данных, не более двух ссылок на другие бинарные деревья.

4.2. Объединения

Объединения, как и структуры, являются сложным типом данных, который определяет сам программист.

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

Объединение описывается также, как и структура, со служебным словом union. Размер объединения определяется размером максимального поля.

Объединение может использоваться для:

ü Экономии памяти, если известно, что больше одного поля одновременно в программе не используется;

ü Создания неоднородных массивов данных;

ü Разной интерпретации одного и того же битового представления.

Продемонстрируем эти возможности на примерах.

Пример 1. Разместим в одной и той же области памяти целое число, дробное число и символ.

Union A

{

int a1;

double a2;

char a3;

} a;

Размер объединения a – 8 байт и в памяти может храниться либо целое, либо дробное, либо символ. Доступ к полям объединения осуществляется как у структур. Например:

a.a1=2;

a.a2=4.567;

a.a3=’r’;

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

Пример 2. Создать массив, содержащий три вида документов.

Опишем структуры A, B, C содержащие характеристики документов.

struct A

{

};

struct B

{

};

struct C

{

};

union Docum

{

struct A a;

struct B b;

struct C c;

};

Объявим массив из 10 документов различного вида:

struct Data

{

char flag; //для распознания вида документа

union Docum docum; //документ одного из трех видов

} data[10];

Пример 3. При работе системной функции поиска файла на диске findfirst, информация о найденном файле заносится в структуру ffblk, где дата и время создания файла находятся в упакованном виде (в виде данного типа unsigned).

Определить имя, размер и время создания какого-либо файла из текущего каталога.

#include<iostream.h>

#include<dir.h>

struct Time

{

unsigned sec : 5;

unsigned min : 6;

unsigned hour : 5;

};

union

{

unsigned PressTime; //время в упакованном виде

struct Time time;

} t;

void main (void)

{

struct ffblk ffblk;

if( findfirst(“*.*”, &ffblk, 0)!= -1)

{

cout<<“\nИмя файла - “<< ffblk.ff_name<<endl;

cout<<” размер - ”<< ffblk.ff_fsize<<endl;

t.PressTime=ffblk.ff_ftime;

cout<<“\nВремя создания (час.мин.сек)-”<<t.time.hour<<’.’<<, t.time.min<<’.’ <<t.time.sec*2<<endl<<endl;

}

}

4.3. Перечисления

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

Перечислимый тип задается перечислением набора констант. Каждой константе ставится в соответствие целое число явно или по умолчанию от 0 с шагом 1.

Характерным примером может служить перечисление COLORS, описанное в conio.h:

enum COLORS

{

BLACK,

BLUE,

GREEN,

YELLOW,

WHITE

};

Можно создать переменную перечислимого типа

enum COLORS colors;

Присвоить ей конкретное значение

colors=RED;

И использовать ее в программе

textcolor (colors);

Или использовать непосредственно константу перечислимого типа

textbackground (WHITE);

Наши рекомендации