Пример 4.11 - Операции new и delete в задаче сложения векторов

 

#include <stdio.h>

// Функция сложения векторов

void AddVec(

// Адрес первого слагаемого

int* pA,

// Адрес второго слагаемого

int* pB,

// Адрес суммы векторов

int* pC,

// Длина векторов

int Dim){

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

pC[i]=pA[i]+pB[i];

// Или, по-другому,

// *(pC+i)=*(pA+i)+*(pB+i);

}

void main(void) {

// Создание переменной для хранения размерности

int Dim;

// Предупреждение о вводе

printf("Input Dim: ");

// Ввод

scanf("%d", &Dim);

// Выделение памяти для трех массивов

int* pA=new int[Dim],

* pB=new int[Dim],

* pC=new int[Dim];

// Проверка результатов выполнения операций new

if(!(pA&&pB&&pC)){

// Действия при неудачном выполнении new

printf("Memory is not allocated! \n");

return;

}

// Ввод координат первого слагаемого

printf("Input A \n");

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

scanf("%d", pA+i);

// Ввод координат второго слагаемого

printf("Input B \n");

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

scanf("%d", pB+i);

// Вычисление суммы векторов

AddVec(pA,pB,pC,Dim);

// Вывод компонент суммы

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

printf("%d ", pC[i]);

printf("\n");

// Освобождение памяти

delete[] pA;

delete[] pB;

delete[] pC;

}

 

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

 

Пример 4.12 – Второй вариант функции сложения векторов

 

#include <stdio.h>

// Функция сложения векторов

double* AddVec(

// Адрес первого слагаемого

double* pA,

// Адрес второго слагаемого

double* pB,

// Длина векторов

int Dim){

double* pC=new double[Dim];

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

pC[i]=pA[i]+pB[i];

return pC;

}

void main(void) {

// Создание переменной для хранения размерности

int Dim=3;

double A[]={1,1,1},

B[]={2,2,2};

// Вычисление суммы векторов

double* pC=AddVec(A,B,Dim);

// Вывод компонент суммы

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

printf("%f ", pC[i]);

printf("\n");

// Освобождение памяти

delete[] pC;

}

 

4.5 Объединение массивов

 

Объединение массивов с одинаковой длиной. Объединение массивов с разной длиной.

 

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

Пусть a_0,…,a_m – имена (m+1) массива из элементов типа каждый. По определению, имя каждого массива является константой типа . Для их хранения можно создать новый массив из элементов типа с именем, например, a и длиной (m+1) при помощи команды

 

Type* a[]={a_0,...,a_m-1};

 

или (m+2) команды

 

Type* a[m+1];

a[0]=a_0; ... ; a[m]=a_m;

 

Применяя операцию индексирования к имени a массива, можно получить любой из его элементов, который является адресом одного из исходных массивов. В самом деле, a[i] для любого , , является адресом (именем) i-ого исходного массива. Поэтому для получения доступа к -ому элементу этого i-ого массива необходимо операцию индек­сиро­вания применить еще раз, но уже к a[i]. То есть, выражение a[i][j] экви­валентно a_i[j]. Таким образом, доступ к объектам, хранящимся в разных массивах (областях памяти) легко получить, создав массив из их имен. Следует помнить, что если Type – тип элементов исходных массивов, то Type* - тип элементов массива из массивов. Проиллюстрируем сказанное на примере вычисления суммы элементов из h массивов длины w каждый.

Пример 4.13 - Сумма элементов массивов одинаковой длины

 

#include <stdio.h>

double ItemSum(double** a, int h, int w) {

double S=0;

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

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

S += a[i][j];

return S;

}

void main () {

double a_0[]={1, 2}, a_1[]={4, 5}, a_2[]={7, 8};

double* a[]={ a_0, a_1, a_2 };

double S=ItemSum(a, 3, 2);

printf("Sum=%f \n", S);

}

 

После выполнения приложения на экране дисплея появится сумма объектов, хранящихся в трех массивах:

 

Sum=27.000000

 

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

 

Пример 4.14 - Сумма элементов массивов разной длины

 

#include <stdio.h>

double ItemSum(double** a, int h) {

double S=0;

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

for(int j=1; j<=a[i][0]; j++ )

S += a[i][j];

return S;

}

void main () {

double a_0[]={3,1,2,3}, a_1[]={2,4,5}, a_2[]={4,7,8,1,3};

double* a[]={a_0,a_1,a_2};

double S=ItemSum(a, 3);

printf("Sum=%f\n", S);

}

 

После выполнения приложения на экране дисплея появится сумма объектов, хранящихся в трех исходных массивах:

 

Sum=34.00000

 

 

4.6 Таблицы и массивы

 

Определение таблицы. Массив из элементов таблицы. Доступ к элементам таблицы. Использование стандартной функции rand() для создания таблицы. Массив из строк таблицы. Выделение и освобождение памяти.

 

Пусть и - целые положительные числа. Таблицей из строк и столбцов называется множество A из чисел, имеющих по два индекса: . Первый индекс называется номером строки, второй – номером столбца. Обычно таблицы размещаются в памяти одним из двух способов. В соответствии с одним элементы таблицы упорядочивают тем или иным способом и хранят в массиве длиной . Пусть - тип элементов таблицы, а h и w – ее размеры. Для получения памяти для хранения h*w элементов такой таблицы можно применить команду

 

Type* pT=new Type[h*w];

 

или написать собственную функцию:

 

Type* AllocMemoryTable(int h, int w){

Type* pT=new Type[h*w];

return pT;

}

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

 

a00,…,a0w-1,a10,…,ah-1,w-1,

 

то сделать это просто. В этом случае . С другой стороны, по номеру элемента массива pT легко вычислить номер строки и номер столбца, на пересечении которых он находится в таблице. В самом деле, i=k/w, а j=k%w.

Проиллюстрируем изложенный способ хранения таблицы на примере. Размеры таблицы вводятся с клавиатуры в ходе выполнения приложения, а ее элементы формируются при помощи стандартной функции rand(), объявление которой находится в файле <stdlib.h>. При каждом вызове она возвращает объект типа unsigned int из диапазона [0,232-1]. Последовательность таких чисел, на первый взгляд, кажется беспорядочной. Поэтому их принято называть псевдослучайными числами. Если m – целое положительное число, то из сказанного следует, что значение выражения rand()%(m+1) будет находиться в диапазоне [0,m]. Для отображения на дисплее полученной таблицы напишем собственную функцию PrintTable().

 

Пример 4.15 – Таблица с псевдослучайными числами

 

#include <stdio.h>

#include <stdlib.h>

// Функция отображения таблицы

void PrintTable(int* T, int h,int w){

// Пропуск строки

printf("\n");

for(int i=0;i<h;i++){

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

printf("%d ",T[i*w+j]);

printf("\n");

}

// Пропуск строки

printf("\n");

}

void main(){

int h,w;

int m;

printf("input h=");

scanf("%d",&h);

printf("input w=");

scanf("%d",&w);

m=2;

int n=h*w;

// Выделение памяти

int* pTab=new int[n];

if(pTab==0){

printf("Memory is not allocated! \n");

return;

}

// Формирование таблицы

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

pTab[j]=rand()%m;

// Отображение таблицы

PrintTable(pTab,h,w);

// Освобождение памяти

delete[] pT;

}

 

В результате выполнения приложения с указанными ниже значениями h и w

 

input h=10

input w=20

 

будет сформирована и выведена следующая таблица

 

1 1 0 0 1 0 0 0 0 0 1 1 1 1 1 1 1 0 1 0

1 0 0 1 0 0 1 0 0 1 1 0 1 0 1 0 1 1 1 0

1 1 0 1 1 0 1 1 1 0 1 0 0 1 1 1 1 1 1 0

0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1 1 0

1 1 0 0 0 0 0 0 1 0 0 1 0 1 1 0 0 0 1 1

1 1 1 0 0 0 1 0 1 0 1 1 0 0 0 1 1 1 1 0

0 0 1 0 1 1 1 0 1 0 0 0 1 0 0 0 1 1 1 1

1 1 1 1 1 1 1 0 1 0 0 0 0 0 1 0 0 1 0 1

0 1 0 1 0 1 1 1 0 0 1 0 0 0 0 1 0 1 0 0

1 0 1 1 0 0 0 0 1 1 0 1 0 1 1 1 0 1 1 0

 

В соответствии с другим способом для хранения каждой строки создается отдельный массив длиной w из элементов типа Type. Для хранения адресов массивов со строками также создается массив из h элементов типа Type*. Получение памяти для таблицы начинается с запроса памяти для массива длины h из адресов строк. Только после этого можно запрашивать память для самих строк.Если хотя бы на один запрос памяти получен отказ, то необходимо вернуть операционной системе всю ранее полученную память и завершить исполнение приложения. Запрос памяти для хранения таблицы удобно представить в виде функции AllocMemoryTable(), возвращающей адрес массива с адресами строк:

 

int** AllocMemoryTable(int h, int w){

// Память для массива адресов строк

int** ppTable=new int*[h];

// Проверка результата выполнения операции

if(ppTable==0){

// Сообщение о причине аварийного завершения

printf(“Memory is not allocated! \n”);

// Аварийное завершение

return (int**)0;

}

// Память для h массивов длиной w из элементов строк

for(int i=0; i<h; i++){

ppTable[i]=new int[w];

// Проверка результата выполнения операции

if(ppTable[i]==0){

// Освобождение ранее полученной памяти

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

delete[] ppTable[k];

delete[] ppTable;

// Сообщение о причине аварийного завершения

printf(“Memory is not allocated! \n”);

// Аварийное завершение

return (int**)0;

}

}

}

 

Если ppT – адрес массива с адресами строк, то элемент aij таблицы можно получить при помощи двух операций индексирования. В самом деле, первая операция индексирования позволяет получить адрес ppT[i] i-ой строки. Вторая операция индексирования обеспечивает получение самого элемента ppT[i][j]таблицы.

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

 

void FreeTable(int** ppT, int Height){

for(int k=0; k<h; k++)

delete[] ppT[k];

delete[] ppT;

}

 

 

4.7. Строки

 

Строковые константы. Указатель на строковую константу и указатель-константа. Символьные строки и их применение. Стандартные функции gets() и puts(), strlen(), strcmp(), strcpy(), strcat(), getch() и putchar().

 

В 3.5 было дано определение строковой константы. В соответствии с ним строковой константой длины n называется заключенная в кавычки последовательность из n символов. Таким образом, конструкции вида

 

abcd”, ”Это строка символов”, ”56&%\n

 

являются строковыми константами из 4, 19 и 6 символов соответственно. Для хранения строковой константы длины n выделяется n+1 следующие друг за другом байтов. В первых n байтах с соблюдением очередности размещаются символы самой строковой константы. В (n+1)-ый байт компилятор самостоятельно записывает нуль-символ ‘\0’. Для доступа к символам строковой константы используется указатель на объект типа char, в котором хранится адрес первого символов. Правило создания таких указателей имеет следующий вид:

 

char* pStr=Строковая_константа”;

 

Очевидно, что любое изменение строковой константы, например, при помощи команды pStr[1]=x’ является ошибкой. Для того, чтобы каждая попытка изменения строковой константы распознавалась компилятором в качестве синтаксической ошибки и сопровождалась соответствующим сообщением, необходимо при ее создании использовать указатель на константу. Отметим, что указателю на константу можно присвоить адрес другой строковой константы, как это сделано в следующем примере.

 

Пример 4.16 – Использование указателя на константу

 

// Создание строковой константы

const char* pString=”Фора”;

// Попытка изменения строковой константы является ошибкой

pString[2]=d;

// Правильная команда (создание новой строковой константы)

pString=”Фара”;

 

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

 

Пример 4.17 – Использование указателя-константы

 

// Создание указателя-константы

char* const pString=”Строковая константа”;

// Попытка изменения указателя-константы является ошибкой

pString=”Строка”;

 

По определению, строкой длины n называется массив из n+1 элемента типа char, заканчивающийся нуль-символом ‘\0’. Других нуль-символов в строке быть не должно. Основное назначение строковых констант состоит в использовании их для создания массивов из элементов типа char. По определению, следующие две команды

 

char String[]={‘a, b, c, d, \0‘};

char String[]=”abcd”;

 

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

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

 

char Str[]=”Создание массива из объектов типа char”;

char Str[]=”Создание_массива из объ

ектов типа char”;

 

Для ввода и вывода строк можно использовать стандартные функции scanf() и printf(), указав в качестве символа форматирования тип s. Однако ввод с клавиатуры и отображение строк на дисплее можно выполнить с помощью специализированных функций из стандартной библиотеки. Их прототипы находятся в файле с именем stdio.h. Например, для ввода строки с клавиатуры можно воспользоваться стандартной функцией с прототипом

 

char* gets(char* str);

 

Ее единственным параметром является адрес str заранее полученного блока памяти, в который записываются вводимые с клавиатуры символы. При вызове функции gets() компьютер останавливается. Вводимые с клавиатуры символы помещаются один за другим в блок с адресом str. После нажатия клавиши Enter ввод завершается, а в блок записывается завершающий нуль-символ ‘\0’. При успешном завершении функция возвращает адрес str. В противном случае возвращается нулевой адрес (char*)0. В обоих случаях завершение функции сопровождается переходом на начало следующей строки.

Для отображения на дисплее строки, хранящейся в массиве с адресом str, можно воспользоваться стандартной функцией с прототипом

 

int puts(char* str);

 

Она отображает символы строки, но без завершающего нуль-сим­вола. При успешном завершении функция возвращает неотрицательное значение. В противном случае именованную константу с именем EOF, определенную в файле stdio.h как -1. В обоих случаях завершение выполнения функции сопровождается переходом на новую строку.

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

 

size_t strlen(const char* str);

 

Тип size_t определен в файле string.h как целое без знака. Функция возвращает длину строки без завершающего нуль-символа.

На множестве всех слов задано отношение порядка, получившее название лексикографического. Именно этот порядок применяется при размещении слов в словарях. Для сравнения строк с адресами str1 и str2 используется стандартную функцию с прототипом

 

int strcmp(const char* str1, const char* str2);

 

Возвращаемое значение является отрицательным, если слово из строки str1 стоит в словаре раньше слова str2. При совпадении строк функция возвращает нуль. Если слово из строки str2 расположено в словаре раньше слова из строки str1, то возвращаемое значение является положительным.

Функция с прототипом

 

char* strcpy(char* str1, const char* str2);

 

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

Функция с прототипом

 

char* strcat(char* str1, const char* str2);

 

добавляет (присоединяет) к строке с адресом str1 строку с адресом str2. При этом нуль-символ строки с адресом str1 заменяется первым символом строки с адресом str2. Функция возвращает str1. Программист должен позаботиться о том, чтобы в массиве с именем str1 было достаточно места для хранения символов обеих строк.

Проиллюстрируем применение рассмотренных стандартных функций на примере создания функции TestPassword(), обеспечивающей проверку пароля. Эта функция вызывается из главной для сравнения введенного пользователем слова с паролем, хранящимся в массиве. При вводе правильного пароля разрешается сделать не более оговоренного заранее количества попыток. Если угадать пароль за указанное число попыток не удается, то выполнение защищенного паролем приложения прекращается. Для написания приложения потребуются две стандартных функции, объявления которых хранятся в файле <conio.h> и <stdio.h> соответственно. Функция с прототипом

 

int getch();

 

служит для ввода с клавиатуры последовательности символов. При этом ASCII-коды введенных символов преобразуются к типу int. Для завершения ввода требуется нажатие клавиши Enter с ASCII-кодом ‘\r’. Вводимые символы на дисплее не отображаются

Функция с прототипом

 

int putchar(int ch);

 

предназначена для отображения на экране символа. Его ASCII-код, преобразованный к типу int, является единственным параметром функуции. Она возвращает ASCII-код символа, преобразованный к типу int.

Отметим, что исходный текст приложения хранится в трех файлах.

 

 

Пример 4.18 - Проверка пароля

 

// Файл № 1/3

#include<stdio.h>

#include<string.h>

#include<conio.h>

// Объявление функции проверки пароля

int TestPassword(

// Адрес строки с паролем

char* Password,

// Разрешенное количество попыток

int NumberRepetition);

extern int NumberRepetition;

extern char Password[7];

 

void main(){

// Проверка пароля

int Res=TestPassword(Password, NumberRepetition);

// Анализ результата

if(Res!=0){

puts("Error!");

return;

}

puts("Ok");

// Выполнения приложения

// …

}

// Файл № 2/3

int TestPassword(char* Password, int NumberRepetition){

// Буфер для вводимого слова

char pBuff[255];

// Номер попытки

int i=0;

// Номер вводимого символа

int j;

// Результат сравнения

int Res;

// Ввод и проверка пароля

do{

// Вычисление номера попытки

i++;

// Запрос пароля

printf("Enter password: ");

// или puts("Enter password: ");

j=0;

// Ввод пароля

do{

pBuff[j]=getch();

if(pBuff[j++]!='\r')

putchar('*');

else{

pBuff[--j]='\0';

break;

}

}while(1);

// Сравнение

Res=strcmp(Password, pBuff);

printf("\n");

}while((Res!=0)&&(i<NumberRepetition));

// Возврат результата проверки

return Res;

}

// Файл № 3/3

// Пароль

char Password[]="Secret";

// Число попыток

int NumberRepetition=2;