ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 03.03.2021

Просмотров: 584

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

Допустима также следующая запись

int *pt = NULL; либо int *pt = 0;

Константа NULL это указатель равный нулю. Пустой указатель можно использовать для проверки, ссылается указатель на конкретный объект или нет.

Оператор delete позволяет высвободить память, которая была ранее зарезервирована оператором new. Высвобожденная память может быть использована повторно.

int *pt = new int;

……………………………………………

delete ps;

В этом примере очищается область памяти, на которую указывает ps, но сам указатель ps не удаляется. Его можно использовать повторно, например, для указания на другую выделенную область памяти.

Пример.

int * ps = new int; // ok

delete ps; // ok

delete ps; // not ok now

int jugs = 5; // ok

int * pi = &jugs; // ok

delete pi; // not allowed, memory not allocated by new

Пример.

int * ps = new int; // allocate memory

int * pq = ps; // set second pointer to same block

delete pq; // delete with second pointer

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

Общая форма для выделения и присвоения области памяти для массива имеет следующий вид:

type_name pointer_name = new type_name [num_elements];

int * psome = new int [10]; // блок из 10 значений типа int

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

delete [] psome;

int * pt = new int;

short * ps = new short [500];

delete [] pt; // результат не определен

delete ps; // результат не определен

Правила применения операторов new и delete.

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

  • Не используйте оператор delete для освобождения одного и того же блока памяти два раза подряд.

  • Если для выделения памяти массиву был применен оператор new [ ], следует использовать оператор delete [ ].

  • Если для выделения памяти единственному элементу был применен оператор new, следует использовать оператор delete.

  • К нулевому указателю оператор delete применять безопасно.

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

psome[i-1]

Заметим также, что первый элемент массива можно получить с помощью выражения

*psome

Пример.

#include <iostream>

int main()

{

using namespace std;

double * p3 = new double [3]; // область памяти для трех элементов типа double

p3[0] = 0.2; // p3 выступает в роле имени массива


p3[1] = 0.5;

p3[2] = 0.8;

cout << “p3[1] is “ << p3[1] << “.\n”;

p3 = p3 + 1; // приращение указателя (для массива не верно)

cout << “Now p3[0] is “ << p3[0] << “ and “;

cout << “p3[1] is “ << p3[1] << “.\n”;

p3 = p3 - 1; // указывает на начало

delete [] p3; // высвобождение памяти

return 0;

}

Функции языка С++

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

  • предусмотреть определение функции

  • предусмотреть прототип функции

  • вызвать функцию

Все функции можно разить на две категории:

функции не возвращающие значений

void имяФункции(списокАргументов)

{

операторы;

return; //необязательный оператор

}

и функции возвращающие значения

имяТипа имяФункции(списокАргументов)

{

операторы;

return значение;

}

В списке списокАргументов указываются типы и количество аргументов (параметров), передаваемых функции. Оператор return обозначает конец функции. Значение может быть константой, переменной или выражением, которое приводится или может быть преобразованно к типу имяТипа. Возвращаемым значением не может быть массив. Если функция включает более одного оператора возврата, выполнение функции прекращается после того как она выполнит первый встречный оператор возврата (if..else).

Прототип описывает компилятору интерфейс функции. Он сообщает компилятору тип

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

Пример:

#include <iostream>

#include <conio.h>

using namespace std;

void cheers(int); // прототип: нет возвращаемого значения

double cube(double x); // прототип: возвращаемое значение типа double

int main()

{

setlocale(LC_ALL,"Russian");

cheers(5); // вызов функции

cout << "Введите число: ";

double side;

cin >> side;

double volume = cube(side); // вызов функции

cout << "Куб числа " << side <<" имеет значение ";

cout << volume << ".\n";

cheers(cube(2)); // приведение типов

_getch();

return 0;

}

void cheers(int n)

{

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

cout << "Куб! ";

cout << "\n";

}

double cube(double x)

{

return x * x * x;

}

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

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


void fifi(float a, float b)

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

Существует два способа передачи параметров в функцию: по значению и по ссылке.

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

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

int cat;

int &tiger = cat;

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

Пример: Задача о трех стаканах.

#include <iostream>

#include <conio.h>

using namespace std;

void swapr(int & a, int & b);

void swapp(int * p, int * q);

void swapv(int a, int b);

int main()

{

setlocale(LC_ALL,"Russian");

int var1 = 300;

int var2 = 350;

cout << "var1 = $" << var1;

cout << " var2 = $" << var2 << endl;

cout << "Использование ссылок:\n";

swapr(var1, var2); // передача переменных

cout << "var1 = $" << var1;

cout << " var2 = $" << var2 << endl;

cout << "Использование указателей:\n";

swapp(&var1, &var2); // передача адресов переменных

cout << "var1 = $" << var1;

cout << " var2 = $" << var2 << endl;

cout << "Использование значений:\n";

swapv(var1, var2); // передача значений переменных

cout << "var1 = $" << var1;

cout << " var2 = $" << var2 << endl;

_getch();

return 0;

}

void swapr(int & a, int & b) // передача по ссылке

{

int temp;

temp = a;

a = b;

b = temp;

}

void swapp(int * p, int * q) // передача по указателю (значению)

{

int temp;

temp = *p;

*p = *q;

*q = temp;

}

void swapv(int a, int b) // передача по значению

{

int temp;

temp = a;

a = b;

b = temp;

}

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

void ref(const int & a, const int & b);

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

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

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

#include <iostream>

#include <conio.h>

using namespace std;

double step(double x, unsigned int e = 2);

int main()

{

setlocale(LC_ALL,"Russian");

double x = step(3.14);

double y = step(2.9, 5);

cout<<x<<' '<<y;

_getch();

return 0;

}

double step(double x, unsigned int e)

{

double result = 1;

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

result *= x;

return result;

}

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


Функции и массивы. Решим задачу о вычислении суммы элементов массива.

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

#include <iostream>

#include <conio.h>

using namespace std;

const int ArSize = 8;

int sum_arr(int *arr, int n);//sum_arr(int arr[], int n);

int main()

{

setlocale(LC_ALL,"Russian");

int mas[ArSize]={1,2,3,4,5,6,7,8};

int sum=sum_arr(mas,ArSize);

cout<<"Сумма элементов массива равна "<<sum;

_getch();

return 0;

}

int sum_arr(int *arr, int n)//sum_arr(int arr[], int n)

{

int sum=0;

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

sum = sum + arr[i];

return sum;

}

Как передается массив, по значению или по ссылке?

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

int fill_mas(double ar[], int limit)

{

double temp;

int i;

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

{

cout<<"Введите "<<i+1<<" элемент массива: ";

cin>>temp;

if(!cin) //устанавливается бит ошибки

{

cin.clear();//очистка значений флагов

while (cin.get() != '\n')

continue;

cout<<"Некорректный ввод\n";

break;

}

else if (temp<0)

break;

ar[i]=temp;

}

return i;

}

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

void show_mas(const double ar[], int n) //изменение массива не допускается

{

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

{

cout<<"Значение "<<i+1<<" элемента массива: ";

cout<<ar[i]<<'\n';

}

}

Спецификатор const может использоваться двумя способами.

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

int age=39;

const int *pt=&age; // значение *pt - константа

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

int age=39;

const int *pt=&age;// *pt - константа, pt - не константа

int * const finger=&age;// finger - константа, *finger - не константа

Рассмотрим программу нахождения суммы всех элементов двумерного массива.

#include <iostream>

#include <conio.h>

using namespace std;

void init_mas2(int **ar2,const int rrow,const int ccol);

void print_mas2(int **ar2,const int rrow,const int ccol);

int sum_mas2(int **ar2,const int rrow,const int ccol);

//если известна размерность столбцов, тогда можно использовать один из вариантов

//int sum_mas2(int (*ar2)[4],const int rrow);

//int sum_mas2(int ar2[][4],const int rrow);

//можно передавать ссылку на первый элемент массива (по Страуструпу)

int main()

{

setlocale(LC_ALL,"Russian");

cout<<"Введите размеры массива: ";

int row, col;

cin>>row>>col;

int **mas=new int *[col];//Выделяем память под двумерный массив

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

mas[i]=new int[col];

cout<<"Введите элементы массива размерности "<<row<<'x'<<col<<"\n";//Инициализация массива

init_mas2(mas,row,col);

cout<<"Полученный массив\n";

print_mas2(mas,row,col);//Распечатка масив

cout<<"Сумма равна "<<sum_mas2(mas,row,col);//Вывод суммы элементов

cout<<mas[5];

_getch();

return 0;

}

void init_mas2(int **ar2, const int rrow, const int ccol)

{

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

for(int j=0;j<ccol;j++)

cin>>ar2[i][j];

}

void print_mas2(int **ar2, const int rrow,const int ccol)

{

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


{

for(int j=0;j<ccol;j++)

cout<<ar2[i][j]<<' ';

cout<<'\n';

}

}

int sum_mas2(int **ar2, const int rrow,const int ccol)

{

int s=0;

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

for(int j=0;j<ccol;j++)

s+=ar2[i][j];

return s;

}

Так как строка в стиле С подобна массиву типа char, который завершается терминальным нулем,

поэтому нет необходимости передавать размер данного массива отдельным параметром.

Пример: Сколько раз определенный символ повторяется в заданной строке.

#include <iostream>

#include <conio.h>

using namespace std;

int ch_in_str(const char *str,char ch);

int main()

{

setlocale(LC_ALL,"Russian");

char str1[15]="minimum";

char *str2="ululate";

int ms=ch_in_str(str1,'m');

int us=ch_in_str(str2,'u');

cout<<ms<<" m символов в строке "<<str1<<'\n';

cout<<us<<" u символов в строке "<<str2<<'\n';

_getch();

return 0;

}

int ch_in_str(const char *str,char ch)

{

int count = 0;

while(*str)

{

if(*str==ch)

count++;

str++;//перемещение указателя на следующий символ

}

return count;

}

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

Рекурсия. Функция, которая вызывает саму себя, называется рекурсивной. Единственная функция в С++, которая не может быть рекурсивной – функция main.

#include <iostream>

#include <conio.h>

using namespace std;

void countdown(int n);

int main()

{

setlocale(LC_ALL,"Russian");

countdown(4);

_getch();

return 0;

}

void countdown(int n)

{

cout<<"Вызов функции . . . "<<n<<'\n';

if(n>0)

countdown(n-1);//функция вызывает саму себя

cout<<n<<"-й возврат\n";

}

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

Задача о Ханойских башнях.

#include <iostream>

#include <conio.h>

using namespace std;

void Xan(int,char,char,char);

int main()

{

setlocale(LC_ALL,"Russian");

cout<<"Введите количество колец.\n";

int n;

cin>>n;

Xan(n,'A','C','B');

_getch();

return 0;

}

void Xan(int n,char a,char c,char b)

{

if(n>1)

{

Xan(n-1,a,b,c);

Xan(1,a,c,b);

Xan(n-1,b,c,a);

}

else

cout<<"Переложить кольцо с "<<a<<" на "<<c<<'\n';

}

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

Пусть имеется прототип функции

double pam(int);

Тогда объявить указатель на функцию можно следующим образом

double (*pf)(int);

pf=pam;

Заметим что

double *pf(int);

функция возвращающая указатель. Функция pam() должна соответствовать pf как по сигнатуре, так и по возвращаемому значению.

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

#include <iostream>

#include <conio.h>

using namespace std;

double batsy(int);

double pam(int);

void estimate(int lines, double (*pf)(int));

int main()

{

setlocale(LC_ALL,"Russian");

int lin;

cout<<"Сколько строк необходимо написать?\n";