Символьная информация и строки

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

Для символьных данных введен базовый тип char. Описание символьных переменных имеет вид:

char список_имен_переменных;

Например:

сhar a,z;

Ввод-вывод символьных данных

Для ввода и вывода символьных значений в форматных строках библиотечных функций printf(), scanf() используется спецификация преобразования %с.

Помимо printf(), scanf() для ввода и вывода символов в библиотеке предусмотрены специальные функции обмена:

getchar() - функция без параметров. Позволяет читать из входного потока (обычно с клавиатуры) по одному символу за обращение. Как и при использовании функции scanf(), чтение вводимых данных начинается после нажатия клавиши <Enter>.

putchar(Х) - выводит символьное значение Х в стандартный выходной поток (обычно на экран дисплея).

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

/* Пoдcчeт чиcлa oтличныx oт пpoбeлoв cимвoлoв*/

#include "stdafx.h"

#include <stdio.h>

#include <iostream>

#include <rusconsole.h>

using namespace std;

int main ()

{

char z; /* z - ввoдимый cимвoл */

int k; /*k - кoличecтвo значащих cимвoлoв */

cout<<"Haпишитe пpeдлoжeниe c тoчкoй в кoнцe:\n";

for (k=0; (z=getchar())!='.';) if (z!=' ') k++;

cout<<"\nKoличecтвo cимвoлoв= "<<k<<'\n';

return 0;

} Результат выполнения программы:

Напишите предложение с точкой в конце:

1234567890.

Количество символов=10

Внутренние коды и упорядоченность символов

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

Следующая программа печатает цифры от 0 до 9 и шестнадцатеричные представления их внутренних кодов.

/* Пeчaть кодов ASCII дecятичныx цифp: */

#include <stdio.h>

void main ()

{

char z;

for (z='0'; z<='9'; z++)

{ if (z=='0' || z=='5') printf("\n");

printf(" %c-%x ", z, z);

}

} /* Koнeц пpoгpaммы */

Peзyльтaт выпoлнeния пpoгpaммы:

0-30 1-31 2-32 3-33 4-34

5-35 6-36 7-37 8-38 9-39

Обратите внимание на то, что символьная переменная z является операндом арифметической операции '++', выполняемой над числовым представлением ее внутреннего кода. Для изображения значения символов в форматной строке функции printf() используется спецификация %с. Шестнадцатеричные коды выводятся с помощью спецификации %х.

Воспользовавшись упорядоченностью внутренних кодов букв латинского алфавита, очень просто его напечатать:

/* Пeчaть лaтинcкoгo aлфaвитa: */

#include <stdio.h>

void main ()

{

char z;

for (z='A'; z<='Z'; z++) printf("%c ",z);

printf("\n");

for (z='A'; z<='Z'; z++) printf("%x ",z);

printf("\n\n\n");

for (z='a'; z<='z'; z++) printf("%c ",z);

printf("\n");

for (z='a'; z<='z'; z++) printf("%x ",z);

printf("\n");

}

Результат выполнения программы:

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a

a b c d e f g h i j k l m n o p q r s t u v w x y z

61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a

Для продолжения нажмите любую клавишу...

Строковые литералы или строки

В отличие от других языков (например, от Паскаля) в языке Си нет отдельного типа для строк. Принято, что строка - это массив символов, т.е, она всегда имеет тип char[ ]. Таким образом, строка считается значением типа "массив символов". Количество элементов в таком массиве на 1 больше, чем в изображении соответствующей строковой константы, так как в конец строки добавлен нулевой байт '\0'.

Присвоить значение массиву символов с помощью обычного оператора присваивания нельзя. Поместить строку в массив можно либо с помощью инициализации - при определении символьного массива, либо с помощью функции ввода. В функции scanf() или printf() для символьных строк используется спецификация преобразования %s.

Для стандартных потоков ввода-вывода stdin и stdout соответственно, в библиотеке stdio.h определены функции

gets(Указатель_на_строку) и puts(Указатель_на_строку).

Для универсальных потоков – функции fgets(Указатель_на_строку,размер_строки_макс,имя_потока) и fputs(Указателоь_на_строку,имя_потока).

Пример:

#include "stdafx.h"

#include <stdio.h>

void main ()

{

char z[80];

char В[ ]="Welcome to C program!!!";

char C[]={'W','e','l','c','o','m','e','!','!','!','\0'};

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

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

gets(z);

puts(z);

fgets(z,80,stdin);

fputs(z,stdout);

scanf("%s",z);

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

}

В программе длина массива В - 24 элемента, т.е, длина строки, помещаемой в массив (23 символа), плюс нулевой байт окончания строки. Показано, на примере массива char c[], что можно воспользоваться обычной инициализацией, поместив начальные значения элементов массива в фигурные скобки и не забыв при этом поместить в конце списка начальных значений специальный символ окончания строки '\0'.

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

Строки и указатели

Рассмотрим следующие два определения:

char А[20];

/* Массив, в который можно записать строку */

char *В;

/* Указатель, с которым можно связать строку */

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

scanf("%s",A); scanf("%s",B);

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

В=(char *)malloc(80); выделяет 80 байт и связывает этот блок памяти с указателем В. Теперь применение приведенного выше оператора ввода допустимо.

С помощью указателей типа char* удобно получать доступ к строкам в виде массивов символов. Типичная задача - обработка слов или предложений, каждое из которых представлено в массиве типа char в виде строки (т.е. в конце представления находится нуль-символ '\0'). Использование указателей, а не массивов с фиксированными размерами особенно целесообразно, когда предложения или слова должны быть разной длины. В следующей программе определен и при инициализации связан с набором строк одномерный массив point[] указателей типа char*. Функция printf() и спецификатор преобразования %s допускают использование в качестве параметра указатель на строку. При этом на дисплей (в выходной поток) выводится не значение указателя point[i], а содержимое адресуемой им строки.

Пример:

#include "stdafx.h"

#include <stdio.h>

void main()

{

char* point[]={"The","way","old","friend","do"};

int i,n;

n=sizeof(point)/sizeof(point[0]);

printf("n=%d\n",n);

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

printf("%s\n",point[i]);

}

Результат

n=5

The

way

old

friend

do

Для продолжения нажмите любую клавишу...

3.6 Функции С/С++

3.6.1 Общие сведения о функциях

Определение функции

В соответствии с синтаксисом в языке Си определены три производных типа: массив, указатель, функция. В этой главе рассмотрим функции.

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

Все функции в языке Си имеют рекомендуемый стандартами языка единый формат определения:

тип имя_функции (спецификация_параметров) тело_функции

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

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

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

Спецификация_параметров - это либо пусто, либо список формальных параметров, каждым элемент которого имеет вид:

Обозначеиие_типа имя _параметра

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

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

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

return;

return выражение;

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

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

Описание функции и ее тип

Для корректного обращения к функции необходимо либо:

1. корректно определить функцию, до оператора ее первого вызова, либо

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

Для функции, объявлением служит ее прототип:

тип имя_функции (спецификация_параметров);

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

double f (int n, float x);

double f (int,float);

3.6.2 Передача параметров при вызове функции

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

Пример.

#include <stdio.h>

void test_func(int par, int *var)

{

printf("\n Adress par = %p",&par);

printf("\n Adress var = %p", var);

par++;

(*var)++;

}

int main()

{

int parm=1,varm=10;

printf("Before test\n parm = %d\tvarm = %d\n",parm,varm);

printf("\n Adress parm = %p",&parm);

printf("\n Adress varm = %p", &varm);

test_func(parm,&varm);

printf("\nAfter test\n parm=%d varm=%d\n",parm,varm);

return 0;

}

Результат работы программы:

Before test

parm = 1 varm = 10

Adress parm = 0012FF60

Adress varm = 0012FF54

Adress par = 0012FE7C

Adress var = 0012FF54

After test

parm=1 varm=11

Фактически параметры функций Си эквивалентны параметрам – значениям, а параметры-ссылки параметрам-переменным процедур Паскаля.

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

Пример.

#include <stdio.h>

void test_func(int par)

{

printf("\npar=%d",par);

}

int main()

{

int p1=3,p2=4,p3=5,p4=6;

test_func(p4=(p1=p2*p3)+15);

printf("\np1=%d\n",p1);

return(0);

}

Результат работы:

par=35

p1=20

Ссылки, как параметры функций. Константные параметры функций

Определение: Ссылка – это формальный параметр функции, определенный как адрес переменной с помощью оператора адреса &.Ссылки – это элементы языка С++, не имеющие аналога в языке С!

Пример: int sum(int& a, int& b);

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

Параметры функций могут быть объявлены константными. Рассмотрим примеры возможных объявлений:

int f(int x); //a – по значению

int f(int& x); //b – по ссылке

int f(const int x); //c – константный по значению

int f(const int& x); //d – константный по ссылке

Вариант a – позволяет задавать в качестве фактического параметра задавать выражения и переменные. В теле функции парметр можно использовать как локальную переменную, СО ВСЕМИ СВОЙСТВАМИ ЛОКАЛЬНОЙ ПЕРЕМЕННОЙ. Локальная переменная – копия фактического параметра.

Вариант b - позволяет задавать в качестве фактического параметра только переменные. В теле функции параметр можно использовать как ГЛОБАЛЬНУЮ ЛЕВОДОПУСТИМУЮ переменную, СО ВСЕМИ СВОЙСТВАМИ ГЛОБАЛЬНОЙ ПЕРЕМЕННОЙ. Копия фактического параметра не создается.

Вариант с - позволяет задавать в качестве фактического параметра выражения и переменные. В теле функции параметр нельзя использовать как ЛЕВОДОПУСТИМУЮ ЛОКАЛЬНУЮ переменную, СВОЙСТВА ПРАВОДОПУСТИМОЙ ЛОКАЛЬНОЙ ПЕРЕМЕННОЙ – КОНСТАНТЫ - ОСТАЮТСЯ. Локальная переменная – копия фактического параметра с запретом ее изменения.

Вариант d - позволяет задавать в качестве фактического параметра выражения и переменные. В теле функции параметр нельзя использовать как ЛЕВОДОПУСТИМУЮ ГЛОБАЛЬНУЮ переменную, СВОЙСТВА ПРАВОДОПУСТИМОЙ ГЛОБАЛЬНОЙ ПЕРЕМЕННОЙ – КОНСТАНТЫ - ОСТАЮТСЯ. Вариант d – аналогичен передаче параметра по значению – варианту a, но при передаче параметра не выполняется его копирование в стек параметров, передается только адрес параметра. Копия фактического параметра не создается.

Пример:

#include <iostream>

using namespace std;

void swap(int& x, int&y);

int main()

{

int x = 4, y = 40;

cout<<"Before swap()"<<"\tx = "<<x<<"\ty = "<<y<<endl;

swap(x,y);

cout<<"After swap()"<<"\tx = "<<x<<"\ty = "<<y<<endl;

return 0;

}

void swap(int& x, int& y)

{

x=x-y;

y=x+y;

x=y-x;

}

3.7 Массивы, строки, функции как параметры функций

3.7.1 Массивы как параметры функций

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

Приведем примеры эквивалентных прототипов функций Mult:

1-й вариант:

void Mult(int n,float a[],float b[])

2-й вариант:

void Mult(int n,float *a,float *b)

Следует четко понимать, что конструкции float a[]; и float *a; совершенно равноправны в спецификациях параметров - и в первом и во втором случае речь идет об адресе нулевого элемента массива. Следствием того, что массив всегда передается в функцию как указатель, является то, что внутри функции можно изменять значения элементов массива - фактического параметра, определенного в вызывающей программе. Это возможно и при использовании индексирования, и при разыменовании указателей на элементы массива. Для иллюстрации указанных возможностей рассмотрим функцию, возводящую в квадрат значения элементов одномерного массива, и вызывающую ее программу:

#include <stdio.h>

void quart(int n, float * x)

{

int i;

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

*(x+i)*=*(x+i); // *x *= *x++;

}

void main()

{

float z[ ]={1.0, 2.0, 3.0, 4.0};

int j;

quart(4,z);

for(j=0;j<4;j++)

printf("\n z[%d] =%f\n",j,z[j]);

return;

}

Peзyльтaт выпoлнeния пpoгpaммы:

z[0]=1.000000

z[1]=4.000000

z[2]=9.000000

z[3]=16.000000

Возможный заголовок функции quart может быть таким:

void quart(int n,float x[]);

В теле функции разыменовано выражение, содержащее имя массива – параметра - вместо индексированной переменной x[i] используется *(x+i). Можно изменять внутри тела функции значение указателя на массив: *x *= *x++;. Таким образом, с одной стороны, имя массива является константным указателем, со значением равным адресу нулевого элемента массива. С другой стороны, имя массива, использованное в качестве параметра, является в теле функции указателем-переменной, и может использоваться в качестве леводопустимого выражения.

3.7.2 Строки как параметры функций

Строки в качестве параметров функций могут быть заданы либо как одномерные массивы символов char[], либо как указатель на символьные данные char*. В обоих случаях в функции передается адрес первого элемента строки.

В отличие от массивов, для параметров строк нет необходимости явно указывать их длину. Длина строки определяется позицией символа ‘\0’ в массиве символов.

Приведем ряд примеров, показывающих принципы работы со строками.

Определение длины ASCIIZ строк.

int lenstr(char str[])

{

int len;

for(len =0; str[len]!=’\0’;len++);

return Len;

}

int len_str(char* str)

{

int len;

for(len =0; *(str+len)!=’\0’;len++);

return len;

}

int len_str(char* str)

{

int len;

for(len =0; *str++!=’\0’;len++);

return len;

}

Для ASCIIZ строк признаком окончания строки является символ ‘\0’, который можно определить как EOL:

#ifdef EOL

#undef EOL

#define EOL ‘\0’

#endif

int lenstr(char *str)

{

int len=0;

char c=*str;

while(*str++!=EOL) len++;

return len;

}

Сравнение двух строк

int cmpstr(char str1[], char str2[])

{

int i, len1, len2;

len1 = lenstr(str1),len2=lenstr(str2);

if (len1!=len2) return –1;

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

if(*str1++!=*str2++) return ++i;

return 0;

}

Функция возвращает –1, если длины строк различны, 0 – если строки одинаковы. Если длины одинаковы, то возвращается порядковый номер первого несовпадающего символа.

Конкатенация строк

void catstr(char *str1, char *str2)

{

int i=0, len1;

len1 = lenstr(str1);

while(*(str2+i)!=EOL)

{ *(str1+len1+i)=*(str2+i); i++;}

*(str1+len1+i)=EOL;

}

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

Копирование строк

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

Версия 1

Цикл for, массивы, присваивание в теле цикла, EOL:

void cpystr(char* str1, char* str2)

{

int i;

for(i=0; str2[i]!=EOL; i++) str1[i]=str2[i];

str1[i]=EOL;

}

Версия 2

Цикл while, массивы, присваивание в теле цикла, EOL:

void cpystr(char* str1,char* str2)

{

int i=0;

while(str2[i]!=EOL)

{

str1[i]=str2[i];

i++;

}

str1[i]=EOL;

}

Версия 3

Цикл while, массивы, присваивание в заголовке цикла, без EOL

void cpystr(char* str1,char* str2)

{

int i=0;

while(str1[i]=str2[i]) i++;

}

Версия 4:

Цикл while, арифметика указателей, присваивание в заголовке цикла, без EOL

void cpystr(char* str1,char* str2)

{

while(*str1++=*str2++);

}

Оцените лаконичность.

3.7.3 Функции как параметры функций

Указатели на функции

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

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

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

Переменную типа указателя на функцию вводят отдельно от определения и прототипа какой-либо конкретной функции. Формат такого определения имеет вид:

тип(*имя_указателя)(спецификация_параметров);

Где

тип – определяет тип возвращаемого функцией значения;

имя_указателя – идентификатор, произвольно выбранный программистом;

спецификация_параметров – определяет состав и типы параметров фунции.

Например, запись

int (*poin_to_function) (void);

определяет переменную-указатель с именем poin_to_function на функцию без параметров, причем функция возвращает значение типа int.

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

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

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

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

1. имя функции(адрес -константу);

2. переменную-указатель на функцию с адресом имени функции;

3. выражение разыменования указателя.

Примеры вызова.

#include <stdio.h>

void f1()

{printf("\n Execute f1");}

void f2()

{printf("\n Execute f2");}

void main()

{

void (*point_to_f)(void);/*указатель на функцию*/

f1();/*явный вызов функции*/

point_to_f=f1;/*настройка указателя на функцию*/

point_to_f();/*вызов функции f1() через указатель без разыменования*/

(*point_to_f)();/*вызов f1() с разыменованием указателя*/

point_to_f=f2;/*настройка указателя на функцию*/

point_to_f();/*вызов функции f2() через указатель без разыменования*/

(*point_to_f)();/*вызов f2() с разыменованием указателя*/

}

Результат работы программы:

Execute f1

Execute f1

Execute f1

Execute f2

Execute f2

Формат операций вызова функций через переменные-указатели на функции следующий:

Имя_указателя(список_фактических_параметров);

(*Имя_указателя)(список_фактических_параметров);

При определении указателя на функции он может быть проинициализирован.

Пример.

#include <stdio.h>

void f();/*прототип функции*/

void (*pf)()=f;//Инициализация указателя

void main(){

f();

pf();

(*pf)();}

void f()

{printf("\nExecute f");}

Результат работы программы:

Execute f

Execute f

Execute f

Массивы указателей на функции

Такие массивы по смыслу ничем не отличаются от массивов других объектов:

Формат объявления: тип(*имя_массива[размер])(спецификация параметров); где тип определяет тип возвращаемых функцией значений; имя_массива - произвольный идентификатор; спецификация_параметров - определяет состав и типы параметров функций. Пример. int (*parray[4])(char);

Здесь определен массив из четырех указателей на функции целого типа с формальными параметрами типа символ.

Массивы указателей на функции позволяют реализовать таблицы переходов (jump tables), или таблицы передачи управления. С помощью таблицы переходов удобно организовывать ветвления с возвратом по результатам анализа некоторых условий. Для этого все ветви обработки оформляются в виде однотипных функций т.е. с одинаковой спецификацией параметров и возвращаемым типом. Каждой ветви обработки соответствует своя функция. Определяется массив указателей каждому элементу которого присваивается адрес функции обработки. Затем формируются условия, на основе которых должна выбираться та или иная функция. Для выбора вводится индекс, причем каждому условию соответствует свое значение индекса. Вызов функции можно произвести по ее индексу в массиве:

имя_массива[индекс](список_фактических_параметров); (*имя_массива[индекс])(список_фактических_параметров); Такую схему работы с массивами указателей удобно применять при организации выполнения программ с помощью меню.

#include <stdio.h>

#include<stdlib.h>/*for exit()*/

#define N 2

void a0(char* name)

{

printf("%s - stop! \n",name);

exit(0);

}

void a1(char* name)

{

printf("%s work1\n",name);

}

void a2(char* name)

{

printf("%s work2\n",name);

}

void main()

{

void (pa*[])(char*)={a0,a1,a2};

char string[12];

int number;

printf("\n Input name -");

scanf("%s",string);

printf("\n Numbers of works (0..%d)\n",N);

while(1)

{

scanf("%d",&num);

/*ветвление по условию*/

pa[num](string);

//(*pa[num])(string);

}

}

Указатели на функции как типы функций

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

Тип (*имя_функции_1(спецификация_параметров_1))(спецификация_параметров _2);

Здесь имя_функции_1(спецификация_параметров_1)- заголовок основной функции, а тип и спецификация_параметров_2 - тип и параметры возвращаемой через указатель функции. Возвращаемые указатели на функции применяются при организации меню.

Замечание:

Используя оператор typedef можно определить тип указателя на функцию:

typedef тип (*Имя_типа)(Список_форм_пар);

Здесь Имя_типа – тип указателя на функцию, которая определяется возвращаемым типом тип и списком формальных параметров Список_форм_пар

При объявлении функции, можно указать в качестве ее типа тип указателя на функцию. Синтаксически, такая форма проще.

Пример.

#include <stdio.h>

#include <stdio.h>

#include <stdlib.h>

#define N 4

//typedef void(*T_P2_F)(char*);

//T_P2_F menu(int i);

void (*menu(int i))(char*)//Функция menu

{

void f1(char*);

void f2(char*);

void f3(char*);

void f4(char*);

switch(i)

{

case 1: return f1;//Возвращаем указатели

case 2: return f2;//на функции

case 3: return f3;//

case 4: return f4;//

default: return NULL;//Для контроля

}

}

void f1(char* name)

{

printf("Working %s job - Terminate ",name);

exit(0);

}

void f2(char* name)

{

printf("Working %s job 2",name);

}

void f3(char* name)

{

printf("Working %s job 3",name);

}

void f4(char* name)

{

printf("Working %s job 4",name);

}

void main()

{

void (*p)(char*);//Указатель на тип функции пункта меню

char name[12];

int index;

printf("\n Input name ==> ");

scanf("%s",name);

while(1)

{

printf("\n Menu \n\t1 - Terminate\n\t2..%d - working...",N);

scanf("%d",&index);

if (index>0&&index<=N)

{

p=menu(index);//Присваивание указателю конкретной функции

p(name);//Вызов функции

}

else printf("\n Incorrect job. Repeat choice...");

}

}

3.8 Структурные типы и структуры С

Здесь рассмотрим структуры С. Структуры С++ рассмотрим позднее, после определения понятия класса.

Определение: структура С - это переменная, состоящая из совокупности отдельных переменных (полей, элементов), типы которых могут быть различными.

Для объявления такой переменной предварительно необходимо объявить тег СТРУКТУРНОГО ТИПА.

Синтаксис такого объявления имеет вид:

struct <тег>{<список_объявлений_элементов> };

или

struct[<тег>]{<список_объявлений_элементов> } <декларатор> [, <декларатор>...];

Здесь struct - спецификатор структурного типа. Тег - указатель на имя структурного типа. В фигурных скобках размещаются описания элементов структурного типа. За фигурными скобками могут располагаться имена конкретных структур - переменных.

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

Во второй, кроме определения типа каждый <декларатор> задает имя переменной типа структуры.

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

При объявлении переменных типа структур применяется следующая синтаксическая форма:

struct<тег><декларатор>[,<декларатор>...];

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

Список объявлений элементов - это одно или более объявлений переменных или битовых полей. Каждая переменная, объявленная в этом списке, называется элементом структурного типа. Объявления переменных списка имеют аналогичный синтаксис, за исключением того, что объявления не могут содержать спецификаторов класса памяти или инициализаторов. Элементы структуры могут быть любого типа: основного, массивом, указателем, объединением (union) или структурой.

Объявить структурный тип и тег структурного типа можно с помощью опрератора typedef. Формат такого объявления имеет вид:

typedef struct[<тег>]{<список_объявлений_элементов>}<тип>;

Пример.

//Объявление тега и типа

typedef struct complex_tag

{

doudle real,imag;

} complex_type;

//объявление структур в программе

struct complex_tag cmpl1={1.0,2.0};

complex_type cmpl2={2.0,4.0};

В примере объявление структур совмещено с инициализацией.

Доступ к элементам структур производится с помощью оператора 2- го ранга точка (.):

cmpl.real=1.234,cmpl.imag=5.1295;

Если структуры имеют одинаковый тип или тип и соответствующий тег, то допустимо взаимное присваивание структур:

cmpl1=cmpl2;

Указатели на структуры

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

struct complex_tag *cmpl_1,*cmpl_2,cmpl_3;

complex_type *cmpl_1_1,*cmpl_1_2,cmpl_1_3;

Доступ к полям структур может выполняться либо с помощью оператора разыменования *, либо с помощью оператора косвенного выбора ->:

cmpl_1->real=1.23456;

*(cmpl_2).image=2.3456;;

cmpl_3.real = 1.;

cmpl_1_1->image =.8;

3.9 Реализация динамических структур данных в С/С++. Динамические списки

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

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

Пример.

struct node

{

int data;

struct node * next;

};

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

#include <stdio.h>

#include <stdlib.h>

typedef struct LIST

{

char Name[20];

int Age;

LIST* Next;

} LIST;

// Input function

LIST* input_list(void)

{

LIST* p;

p=(LIST*)malloc(sizeof(LIST));

printf("\nName => ");

scanf("%s",&p->Name);

printf("Age => ");

scanf("%d",&p->Age);

if(p->Age<0)

{

free(p);

return NULL;

}

p->Next=input_list();

return p;

}

void output_list(LIST* p) //Output function

{

if (p==NULL)

{

printf("\nEnd of list...");

return;

}

printf("\n\tName = %s Age = %d",p->Name,p->Age);

output_list(p->Next);

}

void main()

{

LIST *List=NULL;

printf("\nInput data to list...");

List=input_list();

printf("\nNow we print the list... ");

output_list(List);

getchar();

}

3.10 Потоковый ввод-вывод в С/С++.

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

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

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

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

Существуют три типа Функций ввода-вывода:

• потокового ввода-вывода;

• ввода-вывода нижнего уровня;

• ввода-вывода для консоли и портов.

Эти функции соответствуют определенному уровню ввода-вывода - потоковому, нижнему или консольному.

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

Можно дать следующее

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

При работе с потоками можно:

1. открывать и закрывать поток;

2. анализировать ошибки и особые ситуации;

3. выполнять обмен;

4. управлять буферизацией потока;

5. получать и устанавливать указатель текущей позиции в потоке.

Рассмотрим эти вопросы более подробно.

3.10.1 Открытие и закрытие потока

С началом работы любой программы автоматически открываются стандартные потоки, например, стандартный ввод (его имя - stdin) и стандартный вывод (его имя -stdout). По умолчанию они связаны с клавиатурой и экраном терминала соответственно. Поэтому в тех функциях ввода/вывода, которые использовались до сих пор, не указывалось, из какого потока берутся или куда помещаются данные: это известно по умолчанию.

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

Доступ к потоку осуществляется с помощью указателя. Указатель на файл(поток) может быть объявлен следующим образом:

#include <stdio.h>

FILE * fp;

Здесь тип FILE - это структура, определенная в <stdio.h> с помощью средства typedef и содержащая информацию о файле: флаги состояния файла, размер буфера, указатель на буфер. Описанный указатель можно связать с конкретным файлом в момент открытия данного файла с помощью функции fopen(), которая возвращает указатель на файл или NULL в случае ошибки. Синтаксис вызова функции открытия потока:

fp=fopen(<Имя_файла>,<Тип_доступа>).

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

fp =fopen ("ex1.txt", "w");

файл ex1.txt открыт для записи, а в программе на этот файл можно сослаться с помощью указателя fp.

В качестве <Типа_доступа> могут быть указаны следующие параметры:

"w" - существующий файл открывается для записи, при этом старое содержимое файла уничтожается. Маркер файла указывает на начало. Если файл еще не существовал, то он создается (если это возможно);

"r" - существующий файл открывается для чтения (с начала). Если файл не существует - это ошибка;

"a" - файл открывается для добавления новой записи в конец файла.

"w+" - новый файл открывается для записи и последующих исправлений. Если файл уже существует, то предыдущее содержимое уничтожается. Последующие после открытия файла запись в файл и чтение из файла допустимы в любом месте файла;

"r+" - новый файл открывается для чтения и последующих исправлений. Последующие после открытия файла запись в файл и чтение из файла допустимы в любом месте файла. Запись в конец файла запрещена - нельзя изменять размер файла;

"a+" - файл открывается для или создается записи и последующих исправлений. Если файл уже существует, то предыдущее содержимое не уничтожается. Последующие после открытия файла запись в файл и чтение из файла допустимы в любом месте файла;

По умолчанию, файлы, связанные с потоками, хранят текстовую информацию. В текстовом режиме прочитанная из потока комбинация CR и LF - управляющие коды возврата каретки и новой строки - преобразуются в один символ новой строки '\n'. При записи в поток выполняется обратное преобразование.

Чтобы исключить такое преобразование необходимо работать с потоком в бинарном режиме, который определяется постфиксом 'b' режима открытия потока. Постфикс 't' режима открытия потока явно устанавливает текстовый режим работы с потоком.

Пример.

fp = fopen("test_txt.dat",a+t);

fp = fopen("test_txt.dat",a+);

fp = fopen("test_bin.dat",a+b);

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

fclose (указатель_файла);

Функции обмена данными на уровне потоков называют также функциями обмена верхнего уровня.

3.10.2 Анализ и контроль ошибок потока

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

Для вывода на экран - поток stderr - сообщения об ошибке при открытии потока, есть стандартная библиотечная функция perror(), прототип которой находится в заголовочном файле stdio.h:

void perror(const char* s);

Функция perror() выводит строку s, за которой размещаются двоеточие, пробел и сообщение об ошибке. Текст сообщения выбирается функцией perror() на основании номера ошибки. Номер ошибки заносится в переменную int errno, которая определена в заголовочном файле errno.h.

Пример:

#include <stdio.h>

#include <errno.h>

int main(void)

{

FILE *fp;

fp = fopen("perror.dat", "r");

if (!fp)

perror("Unable to open file for reading");

printf("\nerrno = %d ",errno);

getchar();

return 0;

}

Результат работы при отсутствии файла perror.dat:

Unable to open file for reading: No such file or directory

errno = 2

3.10.3 Функции потокового обмена

Обмен с потоками можно выполнять:

-в виде последовательности байтов;

-в виде символов, строк.

Ввод или вывод выполняется начиная с текущей позиции указателя потока. Текущее положение указателя можно получить с помощью функций ftell, fgetpos и задать явно функциями fseek, fsetpos.

В таблице перечислены основные функции обмена с потоками С. Прототипы функций находятся в заголовочном файле <stdio.h>

Таблица 3.10.1 Основные функции обмена с потоками С

fopen открытие потока
fclose закрытие потока
feof Проверка достижения конца файла
ferror Код ошибки работы с потоком
fflush Записывает данные буфера. Буфер чист.
fgetc Чтение символа из потока
getc Чтение символа из потока
getchar Чтение символа из стандартного потока
putc Запись символа в поток
fputc Запись символа в поток
putchar Запись символа в стандартный поток
fgets Чтение строки из потока
gets Чтение строки из потока
fputs Запись строки в поток
puts Запись строки в стандартный поток
fscanf Форматный ввод из потока
scanf Форматный ввод из стандартного потока
sscanf Форматный ввод из строки
fprintf Форматный вывод в поток
printf Форматный вывод в стандартный поток
sprintf Форматный вывод в поток из строки
fread Чтение побайтно данных из потока
fwrite Запись побайтно данных в поток

Далее приведем примеры и дадим, по тексту, более подробные комментарии к рассмотренным функциям.

Пример 1.

#include <stdio.h>

void main ()

{

int n;

char str[50],ch;

FILE *fp;

// заполняам файл

fp = fopen ("ex.txt","w+t");

puts ("Input integer number ");

scanf("%d",&n);

fprintf (fp, "%d", n);//Пишем в файл целое

getc(stdin);//Чистим буфер от CR

puts ("Input symbol"); ch=getc(stdin);

putc(ch,fp);//Пишем в файл символ

getc(stdin);//Чистим буфер от CR

puts ("Input string"); gets(str);

fputs (str, fp);//Пишем в файл строку

fclose (fp);

// читаем из файла

if((fp = fopen("ex.txt","r"))!= NULL)

{

fscanf (fp, "%d", &n);

printf ("\nInput int value is %d ", n);

ch = getc (fp);

printf("\nInput symbol is ");

putchar (ch);

if (fgets (str, 50, fp)!=NULL) {printf("\nInput string is ");puts (str);}

else printf("\nAbnormal file terminated....");

fclose (fp);

} else printf ("\nError file reading....");

}

Второй параметр функции fgets() - количество N считываемых символов, включая '\0'. Эта функция прекращает работу после чтения N-1 символа, либо после чтения '\0'. В любом случае в конец строки добавляется '\0'. Функция fgets() возвращает либо адрес прочитанной строки, либо NULL по исчерпании файла или в случае ошибки.

В случае исчерпания файла или ошибки функция getc() возвращает EOF.

Функция рutс() возвращает записанную литеру либо (в случае ошибки) - EOF.

Функция fputs() возвращает код последнего прочитанного символа, если вce в порядке, и EOF в случае ошибки. Эта функция не переводит строку автоматически.

3.10.4 Управление буферизацией потока

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

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

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

Размер буфера фиксирован - в <stdio.h> описана специальная константа BUFSIZ, величина которой обычно равна 512. Имеется возможность изменить размер буфера, а также связать поток не с системным, а со своим буфером -массивом размера BUFSIZ. Для этого используются функции setbuf() и setvbuf().

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

3.10.5 Управление указателем текущего положения в потоке. Произвольный доступ

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

Для файла определен маркер - указатель чтения/записи. Он определяет текущую позицию, к которой осуществляется доступ.

Для позиционирования маркера файла служит функция:

int fseek (указатель_на_файл, смещение_от_нач_точки, нач_точка)

Второй аргумент имеет тип long, его значение может быть больше и меньше нуля. Он показывает, как далеко (в байтах) следует продвинуться от начальной точки. Третий аргумент является кодом, определяющим положение начальной точки в файле. Установлены следующие значения для кода:

О - начало файла - константа SEEK_SET;

1 - текущая позиция - константа SEEK_CUR;

2 - конец файла - константа SEEK_END.

В случае успеха функция fseek() возвращает 0, а если была ошибка (например, попытка выхода за левую границу файла), то возвращается ненулевое значение.

Для контроля текущей позиции маркера файла служит функция:

long ftell(FILE*)

Для установки маркера файла в начало потока есть стандартная функция void rewind(FILE*).

При открытии потока в режимах "r" и "w" маркер устанавливается на начальный байт потока, а при открытии в режиме "a" - за конечным байтом потока. При выполнении каждой операции обмена маркер файла перемещается на число байтов, соответствующе размеру данных.

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

Пример.

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

#include <stdio.h>

long filesize(FILE *stream);

int main(void)

{

FILE *stream;

stream = fopen("TESTFILE.TXT", "w+");

fprintf(stream, "This is a test");

printf("Filesize of TESTFILE.TXT is %ld bytes\n", filesize(stream));

fclose(stream);

return 0;

}

long filesize(FILE *stream)

{

long curpos, length;

curpos = ftell(stream);//ftell() - текущая позиция указателя файла

fseek(stream, 0L, SEEK_END);

length = ftell(stream);

fseek(stream, curpos, SEEK_SET);

return length;

getchar();

return 0;

}

Модуль 4 Инкапсуляция в С++. Классы, структуры, объединения С++

(6-часов)

Содержание модуля: Класс как пользовательский тип данных С++. Члены класса. Конструкторы и деструкторы класса. Дружественные и встроенные функции классов. Экземпляры классов – переменные С++. Действия с объектами. Массивы объектов, указатели и ссылки на объекты. Структуры и объединения С++.

4.1 Введение

С++ - это расширенная версия языка С, которая поддерживает парадигмы:

- структурного программирования

- объектно-ориентированного программирования

- обобщенного программирования.

Для языка С++ разработан промышленный международный стандарт – C++ - ISO/IEC 14882 “Standart for the C++ Programming Language”.

Следовательно С++ - это язык профессиональной разработки программного обеспечения вычислительных систем разного уровня.

Самое короткое определение языку С++ дал его разработчик Бьерн Страуструп. По Страуструпу С++ - это С с классами. Начнем изученя языка с предварительного знакомства с классами.

4.2 Класс как пользовательский тип данных С++ Члены класса

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

4.2.1 Классы С++

В С++ класс объявляется с помощью ключевого слова class.

Основная синтаксическая форма:

class имя_класса

{

прототипы закрытых функций и переменные класса;

public:

прототипы открытых функций и переменных класса;

private:

прототипы закрытых функций и переменные класса;

protected:

прототипы защищенных функций и переменные класса;

}[список объектов класса];

Имя_клaccа становится именем нового типа данных, которое используется для объявления объектов класса.

Определение: Функции и переменные, объявленные внутри описания класса называются членами – members, этого класса - соответственно функциями-членами класса и членами класса.

По умолчанию все функции и переменные, объявленные в классе, становятся закрытыми. Это означает, что они доступны только для других членов того же класса. Для объявления открытых членов класса используется ключевое слово public, за которым следует двоеточие. Все функции и переменные, объявленные после слова public, доступны как для других членов класса- видны в классе, так и для любой другой части программы, в которой находится этот класс - видны в программе. Следует понимать, что видны публичные члены конкретных объектов – экземпляров класса. Защищенные члены класса аналогичны закрытым, но в отличие от закрытых членов, они наследуются.

Ниже приводится пример объявления класса:

class myclass

{

// закрытая часть myclass

int a;

public://Открытая или интерфейсная часть myclass

void set_a(int num);

int get_a();

};

Этот класс имеет одну закрытую переменную а, и две открытые функции, set_a() и get_a().

Пocкoлькy a являeтcя зaкpытoй пepeмeннoй клacca, oнa нeдocтyпнa для любoй фyнкции не объявленной в типе myclass. Oднaкo пocкoлькy set_a() и get_a() являютcя члeнaми myclass, oни имeют дocтyп к a. Бoлee тoгo, set_a() и get_a(), являяcь oткpытыми членaми myclass, мoгyг вызывaтьcя из любoй чacти пpoгpaммы, иcпoльзyющeй myclass. Такие открытые функции - члены называются интерфейсом класса.

Xoтя фyнкции set_a() и get_a() oбъявлeны в myclass, oни eщe нe oпpeдeлены. Синтаксис языка не запрещает определение функций-членов при описании класса, но обычно это делается только для специальных членов класса – конструкторов и деструкторов, о которых поговорим позже. Для oпpeдeлeния фyнкции-члeнa вы дoлжны cвязaть имя клacca, чаcтью кoтopoгo являeтcя фyнкция-члeн, c имeнeм фyнкции. Это дocтигaeтcя пyтeм нaпиcaния имeни фyнкции вcлeд зa имeнeм клacca c двyмя двoeточиями. Два двоеточия называются оператором расширения (или разрешения) области видимости (scope resolution operator).

Определим функции set_a() и get_a():

void myclass:: set_a(int num) //set_a в области видимости //myclass

{

a=num;

}

int myclass:: get a ()

{

return a;

}

Функции-члены класса всегда имеют доступ к членам класса. Поэтому и set_a() и get_a() имeют дocтyп к пepeмeннoй a, кoтоpaя в типе myclass объявлена зaкpытой.

Основная синтаксическая форма oпpeдeлeния фyнкции-члeнa класса:

Тип_возвр_значения имя_класса::имя_функции(список_параметров)

{

//тело функции…

}

Здecь uмя_клaccа — этo имя тoгo клacca, кoтopoмy пpинaдлeжнт oпpeдeляeмaя функция.

Объявлeниe клacca myclass нe зaдaeт ни oднoгo oбъeктa типa myclass, oнo опpeдeляeт тoлькo тип oбъeктa, кoтopый бyдeт coздaн пpи eгo фaктичecкoм объявлeнии. Чтoбы coздaть oбъeкт, иcпoльзyется имя клacca, кaк cпeцификaтop типa дaнныx.

Haпpимep, определить двa oбъeктa типa myclass можно так:

myclass ob1, ob2; ·// это объекты типа myclass.

Пocлe того как объект класса создан, можно обращаться к oткpытым членам класса, используя оператор точка (.). предположим, что ранее oбъeкты были объявлены, тогда следующие инструкции вызывают set_a() для объектов obl и ob2:

obl.set_a(10); // установка a объекта obl равной 10

ob2.set_a(99); // ycтaнoвкa a oбъeктa ob2 paвнoй 99

Kaк видно из комментариев, эти инструкции устанавливают значение, переменной a объекта obl равной 10 и значение переменной a oбъeктa ob2 paвнoй 99. Каждый объект содержит собственную копию всех данных, объявленных в классе. Этo значит, что a в obl отличного oт a в ob2.

B качествепримера, рассмотрим программу, в которой используется myclass, описанный выше, для задания значений a для obl и ob2 и вывода на экран этих значений для каждого объекта:

#include <iostream>

using namespace std;

class myclass

{

// зaкpытaя чacть myclass

int a;

public:

void set a(int num);

int get_a();

};

void myclass::set_a(int num)

{

a=num;

}

int myclass::get_a()

{

return a;

}

int main()

{

myclass obl, ob2;

ob1.set_a(10);

ob2.set_a(99);

cout << ob1.get_a() << "\n";

cout << ob2.get_a()<< "\n";

return 0;

}

4.2.2 Члены классов С++

Члены классов С++ могут быть закрытыми, открытыми и защищенными. Ключевые слова public, private и protected устанавливают различные уровни доступа к членам класса. Public члены доступны непосредственно в объектах класса, private и protected члены доступны в объектах класса только через интерфейсные – открытые члены.

B предыдущем примере переменная a в myclass является закрытой. Ecли вы пoпытаетесь oбpaтитьcя к a напрямую, не применяя открытые функции-члены класса, тo результатом будет ошибка при компиляции:

#include <iostream>

using namespace std;

int main ()

{

myclass obl, ob2;

obl.a = 10; //Ошибка – а – закрытый член класса

ob2.a = 99; //Ошибка – а – закрытый член класса

cout << obl.get_a.() << "\n";

cout << ob2.get_a() << "\n";

return 0;

}

Еcли a oбъявить в oткpытoй ceкции myclass, тoгдa к нeй мoжнo oбpaтитьcя непосредственно в любой части программы, где определен объект типа myclass:

#include <iostream>

using namespace std;

class myclass {

public:

// теперь a открытый член класса

int a;

// и здесь не нужны функции set_a() и get_a()

};

int main()

{

myclass ob1, ob2;

// здесь есть явный доступ к a

ob1.a = 10;

ob2.a = 99;

cout << ob1.a << "\n";

cout << ob2.a << "\n";

return 0;

}

4.3 Конструкторы и деструкторы классов С++

4.3.1 Конструкторы класса

Обычно для каждого создаваемого объекта требуется выполнение ряда действий по инициализации экземпляра объекта, например, выделения памяти или присваивания начальных значений переменным. Для разрешения этой проблемы в C++ имеется фyнкцuя-конcmpyкmop (constructor function), включаемая в описание класса.

Определение: Конструктором класса назывется функция-член класса с именем класса которая:

- не имеет типа возвращаемого значения;

- автоматически получает управление при появлении экземпляра класса в области видимости программного модуля.

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

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

Пример

#include <iostream>

using namespace std;

class DemoСlass

{int a;

public:DemoСlass(); // объявление конструктора

void show();

};

DemoСlass::DemoСlass()// определение конструктора

{cout << "В конструкторе\n";a=10;//инициализация переменной}

void DemoСlass::show(){cout << a;}

int main()

{

DemoСlass ob;ob.show();

return 0;

}


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: