Помощничек
Главная | Обратная связь


Археология
Архитектура
Астрономия
Аудит
Биология
Ботаника
Бухгалтерский учёт
Войное дело
Генетика
География
Геология
Дизайн
Искусство
История
Кино
Кулинария
Культура
Литература
Математика
Медицина
Металлургия
Мифология
Музыка
Психология
Религия
Спорт
Строительство
Техника
Транспорт
Туризм
Усадьба
Физика
Фотография
Химия
Экология
Электричество
Электроника
Энергетика

Пример 4.4 - Указатели-константы



 

// Создание объектов типа int

int i1=1, i2=2;

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

int* const pInt1=&i1;

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

pInt1=&i2; // ошибка

// Изменение объекта с адресом pInt1 разрешается

i1=12;

 

По определению, имя массива является указателем-кон­стан­той, содержащим адрес его элемента с номером нуль. Этот адрес также называется адресом массива и используется для получения доступа к его элементам. По определению, адресом элемента с номером k массива ar является значение выражения ar+k. Сам элемент массива является результатом разыменования его адреса.

Изложенный способ вычисления адреса k-ого элемента массива является следствием из правила, в соответствии с которым к указателю можно прибавлять или вычитать из него объект типа int. Если p – адрес объекта типа Type, а k – объект типа int то результатами вычислений выражений p+k и p-k будут адреса

 

p+k*sizeof(Typep-k*sizeof(Type).

 

Таким образом, конструкция p+k будет адресом элемента типа Type, находящимся в памяти за элементом с адресом p на расстоянии k от него. Конструкция p-k будет адресом элемента типа Type, находящегося перед элементом с адресом p на расстоянии k от него.

Более того, операции ++ и -- можно использовать для получения адресов следующего и предшествующего объектов. То есть ar++ и ar—означают то же, что и ar+1 и ar-1.

С другой стороны, если ar+k и ar+k+j – адреса элементов массива ar с номерами k и k+j, j>0, то результатом вычисления выражения (ar+k+j)-(ar+k) будет объект j типа int. Именно он является решением уравнения (ar+k)+x=(ar+k+j).

Пусть p1 и p2 – указатели на один и тот же тип. Тогда к ним разрешается применять операции сравнения. Если, например, адрес из p1 меньше адреса из p2, то результат операции p1<p2 равняется единице.

В языке Си имеется бинарная операция c первым приоритетом, которая называется операцией “индексирования” и обозначается знаком “[]”. Первым операндом этой операции должен быть указатель p на некоторый тип а, вторым - объект типа int. Результатом вычисления выражения p[i] является объект типа Type, адресом которого является значение выражения p+i. То есть выражения p[i] и *(p+i) эквивалентны. В частности, если p – адрес массива, то p[i] - его элемент с номером i. Номер элемента может быть задан или явным образом, используя допустимые способы представления целых чисел, или именем переменной типа int, или выражением с целочисленным значением.

 

Пример 4.5 - Доступ к элементам массива

 

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

int i=1;

int iNum=A[0]+A[i]+A[(int)1.2+2]+*(A+3);

 

 

4.3 Массивы и функции

 

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

 

Пример 4.6 - Вычисление среднего значения

 

#include <stdio.h>

double Aver(double*,int);

void main(void) {

const int Dim=4;

double s;

double dfMassiv[]={0.1, 0.2, 0.3, 0.4};

s=Aver(dfMassiv, Dim);

printf("%f\n", s);

}

// Определение функции Aver()

double Aver(

// Адрес массива

double* pA,

// Размер массива

int n) {

// Подготовка переменной для хранения суммы

double sum=0.0;

// Вычисление суммы

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

sum+=*(pA+i);

// или s+=pA[i];

// Определение возвращаемого значения

return sum/n;

}

 

Пример 4.7 - Сортировка выбором наименьшего элемента

 

#include <stdio.h>

int IndexMinItem(int*,int);

void SortMin(int*,int);

void PrintAr(int*,int,int);

void main(){

const int Dim=9;

int a[]={14,4,9,2,0,5,13,9,1};

SortMin(a,Dim);

PrintAr(a,Dim,Dim);

}

int IndexMinItem (int* Ar,int Dim){

int min=*Ar;

int index=0;

for(int i=1;i<Dim;i++){

if(Ar[i]<min){

min=Ar[i];

index=i;

}

}

return index;

}

void SortMin(int* Ar,int Dim){

int index;

int temp;

for(int i=0;i<Dim-1;i++){

index=IndexMinItem (Ar+i,Dim-i)+i;

temp=Ar[index];

Ar[index]=Ar[i];

Ar[i]=temp;

}

}

void PrintAr(int* Ar,int Dim,int w){

int h=Dim/w;

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

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

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

printf("\n");

}

w=Dim%w;

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

printf("%d ",Ar[h*w+j]);

printf("\n");

}

 

 

4.4 Динамическое управление памятью

 

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

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

Для управления динамической памятью современные редакции языка Си предоставляют в распоряжение программиста три стандартных функции и три операции. Прототипы функций содержатся одновременно в двух файлах с именами malloc.h и stdlib.h. Функция malloc() спрототипом

 

void* malloc (

// Объем запрашиваемой памяти в байтах

unsigned int uSize);

 

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

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

 

void free (

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

void* pMem);

 

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

 

Пример 4.8 - Применение функций malloc() и free()

 

#include <malloc.h> // или #include <stdlib.h>

#include <stdio.h>

void main (void) {

int* pInt;

// Выделение памяти для 10 объектов типа int

pInt = (int*)malloc (10*sizeof(int));

// Проверка результата вызова функции malloc()

if (pInt == NULL){

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

return;

}

printf ("Memory allocated! \n");

// Использование выделенной памяти

// …

// Возвращение выделенной памяти

free ((void*)pInt);

printf ("Память возвращена\n");

}

 

Вызов функции calloc() с прототипом

 

void* calloc (

// Количество объектов

unsigned int uCountUnit,

// Объем памяти для одного объекта в байтах

unsigned int uSizeUnit);

 

приводит к такому же результату, что и вызов функции malloc() с параметром uCountUnit*uSizeUnit.

В современных редакциях языка Си для получения нужного объема памяти в ходе выполнения приложения служит унарная операция, обозначаемая словом . Ее единственным операндом является идентификатор типа объекта, для которого выделяется память. Если Type - тип объекта, то результатом выполнения операции new Type является адрес типа Type* выделенного блока памяти. Если память не выделена, то результатом является нуль типа Type*.

 

Пример 4.9 - Применение операции new

 

// Выделение памяти для объекта типа int

int* pInt=new int;

// Выделение памяти для массива типа char[10]

char* pChar=new char[10];

// Выделение памяти для указателя на float

float** ppf=new float*;

 

Для освобождения ранее выделенной памяти применяются унарные операции с именами delete и delete[]. В первом случае операндом операции является адрес объекта основного типа данных или адрес адреса, а во втором случае - адрес массива.

 

Пример 4.10 - Применение операции delete

 

// Возвращение ранее выделенной памяти

delete pInt;

delete[] pChar;

delete ppf;

 

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

Рассмотрим использование операций new и delete на примере программы сложения двух векторов, размерность которых программисту неизвестна. Она вводится с клавиатуры только на этапе выполнения приложения. Для сложения векторов используется функция AddVec().

 

Пример 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-1 – имена m массивов из элементов типа каждый. По определению, имя каждого массива является константой типа . Для их хранения можно создать новый массив из элементов типа с именем, например, a и длиной m при помощи команды

 

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

 

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

 

Type* a[m];

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

 

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

Пример 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 Таблицы и массивы

 

Пусть и - целые положительные числа. Таблицей из строк и столбцов называется множество 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);

}

 

В результате выполнения приложения с указанными ниже значениями 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. Строки

 

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

 

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

 

являются строковыми константами из 4, 19 и 6 символов соответственно. Для хранения строковой константы длины n выделяется n+1 следующие друг за другом байтов. В первых n байтах с соблюдением очередности размещаются символы самой строковой константы. В (n+1)-ый байт компилятор самостоятельно записывает нуль-символ. Для доступа к символам строковой константы используется указатель на объект типа 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, заканчивающийся символом с ASCII-кодом равным нулю. Такой символ называется нуль-символом и обозначается в текстах программ восьмеричным нулем: ‘\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.

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

 

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(void){

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

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;

 

 




Поиск по сайту:

©2015-2020 studopedya.ru Все права принадлежат авторам размещенных материалов.