Область видимости переменных 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Область видимости переменных



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

Листинг 2.10. Пример использования локальных переменных.

#include
int main()
{
for(int i=0;i < 10;i++)
{
int k = i*i;
printf(“%d\n”,k);
}
return 0;
}

В этом примере объявляются две переменные i и k. Причем переменная k объявлена внутри цикла for. Спрашивается можно ли ее использовать и за пределами этого цикла? В данном случае ответ будет отрицательный, т.к. переменная k «пропадает» за пределами тела цикла и существует только внутри него. Условия данного примера можно обобщить и сказать, что обычная переменная объявленная внутри фигурных скобок {} видна только в них и не может быт использована за их пределами. По этой причине переменные объявленные, например, внутри функции main() недоступны в других функциях и наоборот. Однако если объявить переменную вначале программы, а не внутри функции, то они становятся доступными всем функциям и будут иметь глобальную область видимости. Такие переменные называются глобальными. Следующий пример показывает объявление и использование глобальных переменных.

Листинг 2.11. Определение глобальных переменных.

#include
int global_var = 0;
void my_func(void) {global_var = 10;}
int main()
{
printf(“%d\n”,global_var);
my_func();
printf(“%d\n”,global_var);
return 0;
}

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

0
10

В этом примере объявлена глобальная переменная global_var, которая может использоваться и в функции main() и в функции my_func() и соответственно менять свое значение.

В языке С++ можно задавать переменные с одинаковыми именами и типами если они принадлежат разной области видимости. Например, можно задать глобальную переменную var типа int и такую же переменную внутри функции main(). В этом случае простое обращение к переменной по ее имени будет означать работу с локальной переменной, а если необходимо работать с глобальной, то перед ее именем необходимо поставить два двоеточия ‘::’. Следующий пример показывает такой способ работы с переменными.

Листинг 2.12. Работа с глобальными и локальными переменными.

#include
int var = 5;
int main()
{
int var = 10;
printf(“var = %d,::var = %d”,var,::var);
return 0;
}

В результате получим

var = 10,::var = 5

В языке С++ можно задавать класс переменных, которые будучи объявленными внутри фигурных скобок {} не исчезают и за их пределами, но в то же время имеют область видимости только внутри них. Класс таких переменных называется статическим и задается с помощью ключевого слова static. Поясним сказанное на следующем примере.

Листинг 2.13. Использование статических переменных.

#include
void iter(void);
int main()
{
for(int i=0;i < 4;i++)
iter();
return 0;
}
void iter(void)
{
int var = 1;
static int var_st = 1;
printf(“var = %d, var_st = %d\n”,var++,var_st++);
}

В результате на экране появятся следующие строчки:

var = 1, var_st = 1
var = 1, var_st = 2
var = 1, var_st = 3
var = 1, var_st = 4

Анализ полученных результатов показывает, что статическая переменная var_st объявленная внутри функции iter() не исчезает после ее завершения и при повторном вызове функции не инициализируется заново, т.к. она уже существует.

В языке С++ переменную можно задать как константу, т.е. ее значение нельзя изменять в процессе выполнения программы. Это бывает полезно, когда программист хочет обеспечить «защиту» переменной от изменения. Это достигается путем использования ключевого слова const, которое ставится перед типом переменной:

const int var_const = 10; //правильная инициализация
const int array[] = {10,20,30}; // правильная инициализация
var_const = 5; //ошибка

Например, ранее рассмотренная функция strcpy() копирования одной строки в другую принимает следующие параметры:

char* strcpy(char* dest, const char* src);

Здесь идентификатор const гарантирует, что строка src не будет изменена внутри функции strcpy().

Контрольные вопросы и задания

1. Запишите условный оператор if для определения знака переменной var.

2. В каких случаях следует использовать оператор switch?

3. Используя условный оператор, выполните проверку на принадлежность значения переменной диапазону [10; 20).

4. Приведите программу замены малых латинских букв большими с использованием оператора switch.

5. Как записывается логическое равенство в операторе if?

6. Приведите обозначение логического знака «не равно».

7. Какими символами обозначаются логические операции И и ИЛИ в условном операторе if?

8. В чем отличия между операторами while и do while?

9. Дайте понятие вложенного цикла.

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

11. Запишите функцию возведения числа в квадрат.

12. Дайте понятие рекурсии.

13. В каких задачах целесообразно использовать рекурсивные функции?

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

15. Для чего используется ключевое слово const в языке С++?

16. Дайте понятие статических переменных и какие особенности их использования существуют?

Массивы

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

<тип данных> <имя массива>[число элементов];

Например,

int array_int[100]; //одномерный массив 100 целочисленных элементов
double array_d[25]; //одномерный массив 25 вещественных элементов

Как видно из примеров, объявление массивов отличается от объявления обычных переменных наличием квадратных скобок []. Также имена массивов выбираются по тем же правилам, что и имена переменных. Обращение к отдельному элементу массива осуществляется по номеру его индекса. Следующий фрагмент программы демонстрирует запись в массив значений линейной функции f(x)=kx+b и вывода значений на экран:

double k=0.5, b = 10.0;
double f[100];
for(int x=0;i < 100;i++)
{
f[i] = k*x+b;
printf(“%.2f ”,f[i]);
}

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

int powers[4] = {1, 2, 4, 6};

В этом случае элементу powers[0] присваивается значение 1, powers[1] – 2, и т.д. Особенностью инициализации массивов является то, что их размер можно задавать только константами, а не переменными. Например, следующая программа приведет к ошибке при компиляции:

int N=100;
float array_f[N]; //ошибка, так нельзя

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

#include
#define N 100
int main()
{
float array_f[N];
return 0;
}

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

#define SIZE 4
int data[SIZE]={512, 1024};
for(int i = 0;i < SIZE;i++)
printf(“%d, \n”,data[i]);

Результат работы программы будет следующим:

512, 1024, 0, 0

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

int data[] = {2, 16, 32, 64, 128, 256};

В результате инициализируется одномерный массив размерностью 6 элементов. Здесь остается последний вопрос: что будет, если значение индекса при обращении к элементу массива превысит его размерность? В этом случае ни программа, ни компилятор не выдадут значение об ошибке, но при этом в программе могут возникать непредвиденные ошибки. Поэтому программисту следует обращать особое внимание на то, чтобы индексы при обращении к элементам массива не выходили за его пределы. Также следует отметить, что первый элемент массива всегда имеет индекс 0, второй – 1, и т.д.

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

int array2D[100][20]; //двумерный массив 100х20 элементов

Нумерация элементов также начинается с нуля, т.е. array2D[0][0] соответствует первому элементу, array2D[0][1] – элементу первой строки, второго столбца и т.д. Для начальной инициализации двумерного массива может использоваться следующая конструкция:

long array2D[3][2] = {{1, 2}, {3, 4}, {5, 6}};

или

long array2D[][] = {{1, 2}, {3, 4}, {5, 6}};

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

Работа со строками

В языке С++ нет специального типа данных для строковых переменных. Для этих целей используются массивы символов (тип char). Следующий пример демонстрирует использование строк в программе:

char str_1[100] = {‘П’,’р’,’и’,’в’,’е’,’т’,’\0’};
char str_2[100] = “Привет”;
char str_3[] = “Привет”;
printf(“%s\n%s\n%s\n”,str_1,str_2,str_3);

В приведенном примере показаны три способа инициализации строковых переменных. Первый способ является классическим объявлением массива, второй и третий используются специально для строк. Причем в последнем случае, компилятор сам определяет нужную длину массива для записи строки. Анализируя первый и второй способы инициализации массива символов возникает вопрос: каким образом язык С++ «знает» где заканчивается строка? Действительно, массив str_2 содержит 100 элементов, а массив str_3 меньше 100, тем не менее длина строки и в первом и во втором случаях одна и та же. Такой эффект достигается за счет использования специальных управляющих кодов, которые говорят где заканчивается строка или где используется перенос внутри одной строки и т.п. В частности символ ‘\0’ означает в языке С++ конец строки и все символы после него игнорируются как символы строки. Следующий пример показывает особенность использования данного специального символа.

char str1[10] = {‘Л’,’е’,’к’,’ц’,’и’,’я’,’\0’};
char str2[10] = {‘Л’,’е’,’к’,’ц’, ’\0’,’и’,’я’ };
char str3[10] = {‘Л’,’е’, ’\0’,’к’,’ц’,’и’,’я’ };
printf(“%s\n%s\n%s\n”,str1,str2,str3);

Результатом работы данного кода будет вывод следующих трех строк:

Лекция
Лекц
Ле

Из этого примера видно как символ конца строки ‘\0’ влияет на длину строк. Таким образом, чтобы подсчитать длину строки (число символов) необходимо считать символы до тех пор, пока не встретится символ ‘\0’ или не будет достигнут конец массива. В листинге 3.1 представлена программа вычисления длины строки.

Листинг 3.1. Программа вычисления длины строки.

#include
int main(void)
{
char str[] = “Привет мир!”;
int size_array = sizeof(str);
int length = 0;
while(length < size_array && str[length]!= ‘\0’) length++;
printf(“Длина строки = %d.\n”,length);

return 0;
}

В представленном примере сначала выполняется инициализация строки в массиве str. Затем вычисляется размер массива с помощью функции sizeof(), которая возвращает число байт занимаемое массивом в памяти ЭВМ. Учитывая, что тип char также представляет собой один байт, то данная функция даст размер массива. После этого инициализируется счетчик символов length и выполняется цикл while с очевидными условиями. В результате переменная length будет содержать число символов в строке, либо размер массива. Подобная функция вычисления размера строк уже реализована в стандартной библиотеке языка С++ string.h со следующим синтаксисом:

int strlen(char* str);

где char* str – указатель на строку (об указателях речь пойдет ниже). Следующая программа показывает правило использования функции strlen().

Листинг 3.2. Пример использования функции strlen().

#include
#include
int main(void) {
char str[] = “Привет мир!”;
int length = strlen(str);
printf(“Длина строки = %d.\n”,length);
return 0;
}

Результатом работы программы будет вывод на экран числа 11. Учитывая, что первый символ имеет нулевой индекс, то можно заметить, что данная функция считает и символ ‘\0’.

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

char str1[] = “Это первая строка”;
char str2[] = “Это вторая строка”;

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

str1 = str2;

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

char str1[] = “Это первая строка”;
char str2[] = “Это вторая строка”;
int size_array = sizeof(str1);
int i=0;
while(i < size_array && str1[i]!= ‘\0’) {
str2[i] = str1[i];
i++;
}
str2[i] = ‘\0’;

В приведенном фрагменте программы выполняется перебор элементов массива str1 с помощью цикла while и значение i-го элемента записывается в массив str2. Данная операция выполняется до тех пор, пока либо не будет достигнут конец массива, либо не встретится символ конца строки ‘\0’. Затем, после выполнения цикла, в конец массива str2 записывается символ ‘\0’. Таким образом, выполняется копирование одной строки в другую. Подобная функция также реализована в библиотеке языка С++ string.h и имеет следующее определение:

char* strcpy(char* dest, char* src);

Она выполняет копирование строки src в строку dest и возвращает строку dest. В листинге 3.3 показано правило использования функции strcpy().

Листинг 3.3. Пример использования функции strcpy().

#include
#include
int main(void) {
char src[] = “Привет мир!”;
char dest[100];
strcpy(dest,src);
printf(“%s\n”,dest);
return 0;
}

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

char str1[] = “Это первая строка”;
char str2[] = “Это вторая строка”;
int length = strlen(str1);
int length2 = strlen(str2);

if(length!= length2) {
printf(“Срока %s не равна строке %s\n”,str1,str2);
return 0;
}

for(int i=0;i < length;i++)
if(str1[i]!= str2[i]) {
printf(“Срока %s не равна строке %s\n”,str1,str2);
break;
}
}
if(i == length)
printf(“Срока %s равна строке %s\n”,str1,str2);

Приведенный пример показывает возможность досрочного завершения программы путем использования оператора return. Данный оператор вызывается, если длины строк не совпадают. Также реализуется цикл, в котором сравниваются элементы массивов str1 и str2. Если хотя бы один элемент не будет совпадать, то на экран выведется сообщение «Строка … не равна строке…» и цикл завершится с помощью оператора break. В противном случае (равенства всех элементов) переменная i будет равна переменной length и тогда на экран выводится сообщение о совпадении строк. Подобный алгоритм сравнения двух строк реализован в функции

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

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

char str1[] = “Это первая строка”;
char str2[] = “Это вторая строка”;
if(strcmp(str1,str2) == 0) printf(“Срока %s равна строке %s\n”,str1,str2);
else printf(“Срока %s не равна строке %s\n”,str1,str2);

В языке С++ имеется несколько функций, позволяющих вводить строки с клавиатуры. Самой распространенной из них является ранее рассмотренная функция scanf(), которой в качестве параметра передается ссылка на массив символов:

char str[100];
scanf(“%s”,str);

В результате выполнения этого кода, переменная str будет содержать введенную пользователем последовательность символов. Кроме функции scanf() также часто используют функцию gets() библиотеки stdio.h, которая в качестве аргумента принимает ссылку на массив символов:

gest(str);

Данная функция считывает символы до тех пор, пока пользователь не нажмет клавишу Enter, т.е. введет символ перевода строки ‘\n’. Затем она записывает вместо символа ‘\n’ символ ‘\0’ и передает строку вызывающей программе.

Для вывода строк на экран помимо функции printf() можно использовать также функцию puts() библиотеки stdio.h, которая более проста в использовании. Следующий пример демонстрирует применение данной функции.

#define DEF “Заданная строка”
char str[] = “Это первая строка”;
puts(str);
puts(DEF);
puts(&str[4]);

Результат работы следующий:

Это первая строка
Заданная строка
первая строка

Как видно из полученных результатов, функция puts() автоматически переводит курсор на следующую строку и последующие строки выводятся с новой строки. Кроме того, строки можно задавать с помощью директивы #define и выводить их с помощью функции puts. Также можно менять начало вывода строки путем изменения адреса ее начала.

Еще одной удобной функцией работы со строками является функция sprintf() библиотеки stdio.h. Ее действие аналогично рассмотренной ранее функции printf() с той лишь разницей, что результат вывода заносится в строковую переменную, а не на экран:

int age;
char name[100], str[100];
printf(“Введите Ваше имя: ”);
scanf(“%s”,name);
printf(“Введите Ваш возраст: ”);
scanf(“%d”,&age);
sprintf(str,”Здравствуйте %s. Ваш возраст %d лет”,name,age);
puts(str);

В результате массив str будет содержать строку «Здравствуйте … Ваш возраст…».

Анализ последнего примера показывает, что с помощью функции sprintf() можно преобразовывать числовые переменные в строковые, объединять несколько строк в одну и т.п. Вместе с тем библиотека содержит специальные функции по преобразованию строк в цифры. Дело в том, что строка «100» и цифра 100 в памяти компьютера представляются по-разному. Строка «100» - это последовательность трех символов ‘1’,’0’,’0’, а число 100 – это значение, которое может быть представлено в виде одного байта или храниться в переменной типа int. При программировании часто бывает необходимо выполнять преобразование строк в числа. Это осуществляется с помощью функций atoi(), atol(), atof(). В листинге 3.4 показаны особенности применения данных функций.

Листинг 3.4. Программа преобразования строк в цифры.

#include
#include
int main()
{
char str_i[] = “120”;
char str_l[] = “120000”;
char str_f[] = “120.50”;
int var_i = atoi(str_i);
long var_l = atol(str_l);
float var_f = atof(str_f);

return 0;
}

В результате выполнение данной программы, переменные var_i, var_l и var_f будут содержать значения 120, 120000 и 120.50 соответственно.

Обработка элементов массива

В практике программирования существуют устоявшиеся подходы к удалению, добавлению и сортировке элементов массива. Рассмотрим сначала алгоритм добавления элемента в массив. Допустим, что имеется строка

char str[100] = “Я не пропустил ниодной лекции по информатике.”;

в которой необходимо добавить 17-й символ пробел. При добавлении нового элемента в массив уже имеющиеся элементы всегда сдвигаются вправо относительно добавляемого элемента. Это связано с тем, что при сдвиге вправо, номер первого элемента остается неизменным и равен 0. Если бы сдвиг осуществлялся влево, то номер первого элемента массива становился бы отрицательным, что недопустимо для языка С++. Таким образом, для добавления 17-го элемента нужно выполнить сдвиг вправо элементов, находящихся после 17-го символа и на 17 позицию записать символ пробела. Для реализации данного алгоритма можно воспользоваться циклом for, в котором счетчик цикла меняется, начиная с последнего символа строки до номера вставляемого элемента, в данном случае 17-го. В результате получаем следующий алгоритм:

int size = strlen(str);
for(int i = size;i >= 17;i--)
str[i+1] = str[i];
str[17] = ‘ ‘;

Здесь функция strlen() возвращает номер символа конца строки ‘\0’ поэтому при сдвиге символов будет сдвинут и этот специальный символ, который необходим для корректного представления строк. Цикл for инициализирует переменную i величиной size, которая будет указывать на символ ‘\0’ в строке str. Данный цикл выполняется до тех пор, пока величина i не будет меньше 17 и уменьшается на единицу на каждом шаге его работы. Непосредственно сдвиг элементов массива выполняется внутри цикла for, где i+1 элементу присваивается значение i-го элемента. Наконец, после цикла 17-му элементу строки присваивается символ пробела. Подобный алгоритм добавления нового элемента остается справедливым для любых типов одномерных массивов.

При удалении элемента из массива используется операция сдвига элементов влево относительно удаляемого. Воспользуемся тем же примером строки и поставим задачу удаления 4 пробела (нумерация начинается с нуля). Для этого достаточно все элементы, имеющие индексы больше 4, сместить на один символ влево. Это также можно реализовать с помощью цикла for, в котором счетчик цикла будет меняться от 5 и до символа конца строки. В результате получим следующий алгоритм:

int size = strlen(str);
for(int i = 5;i <= size;i++)
str[i-1] = str[i];

Непосредственное смещение элементов выполняется в теле цикла for, в котором i-1 элементу присваивается значение i-го элемента и после этого значение i увеличивается на единицу. Цикл выполняется до тех пор, пока все символы строки, начиная с 5-го, не будут смещены на единицу влево, включая и символ конца строки.

Наконец, рассмотрим алгоритм упорядочивания элементов массива по возрастанию и убыванию. Существует много разных методов упорядочения элементов. Рассмотрим наиболее простой и распространенный известный под названием «метод всплывающего пузырька». Идея метода заключается в переборе всех элементов массива, начиная с первого (нулевого) и поиска наибольшего (или наименьшего) значения. Найденное значение ставится на место первого элемента, а первый элемент на место найденного. Данная процедура повторяется, начиная уже со второго элемента массива где также находится наибольшее (или наименьшее) значение элемента из оставшихся. Затем, выполняется замена: найденный элемент перемещается на место второго, а второй на место найденного. После такой перестановки всех элементов, исключая последний, массив будет упорядочен по убыванию (или возрастанию). Описанный алгоритм сортироваки упорядочивания по убыванию значений элементов приведен ниже.

int array[10] = {2,5,3,7,9,10,4,6,1,8};
for(int i = 0;i < 9;i++)
{
int max = array[i];
int pos_max = i;
for(int j = i+1;j < 10;j++)
if(max < array[j]) {
max = array[j];
pos_max = j;
}
int temp = array[i];
array[i] = array[pos_max];
array[pos_max] = temp;
}

В данном алгоритме задается цикл, в котором последовательно просматриваются элементы массива array. Внутри цикла инициализируется переменная max, в которую записывается максимальное найденное значение, и переменная pos_max, в которой хранится значение индекса найденного максимального элемента. Затем, реализуется вложенный цикл for, в котором выполняется перебор элементов массива, начиная с i+1, и заканчивая последним 10. В теле вложенного цикла выполняется проверка для поиска максимального элемента, и если текущий элемент считается таковым, то значения переменных max и pos_max меняются. После вложенного цикла осуществляется перестановка элементов массива и процесс повторяется.

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

for(int i = 0;i < 9;i++)
{
int min = array[i];
int pos_min = i;
for(int j = i+1;j < 10;j++)
if(min > array[j]) {
min = array[j];
pos_min = j;
}
int temp = array[i];
array[i] = array[pos_min];
array[pos_min] = temp;
}

Структуры

При разработке программ важным является выбор эффективного способа представления данных. Во многих случаях недостаточно объявить простую переменную или массив, а нужна более гибкая форма представления данных. Таким элементом может быть структура, которая позволяет включать в себя разные типы данных, а также другие структуры. Приведем пример, в котором использование структуры позволяет эффективно представить данные. Таким примером будет инвентарный перечень книг, в котором для каждой книги необходимо указывать ее наименование, автора и год издания. Причем количество книг может быть разным, но будем полгать, что не более 100. Для хранения информации об одной книге целесообразно использовать структуру, которая задается в языке С++ с помощью ключевого слова struct, за которым следует ее имя. Само определение структуры, т.е. то, что она будет содержать, записывается в фигурных скобках {}. В данном случае структура будет иметь следующий вид:

struct book {
char title[100]; //наименование книги
char author[100]; //автор
int year; //год издания
};

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

struct book lib; //объявляется переменная типа book

После объявления переменной lib имеется возможность работать со структурой как с единым объектом данных, который имеет три поля: title, author и year. Обращение к тому или иному полю структуры осуществляется через точку: lib.title, lib.author и lib.year. Таким образом, для записи в структуру информации можно использовать следующий фрагмент программы:

printf(“Введите наименование книги: “);
scanf(“%s”,lib.title);
printf(“Введите автора книги: “);
scanf(“%s”,lib.author);
printf(“Введите год издания книги: “);
scanf(“%d”,&lib.year);

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

struct book lib[100];

В этом случае программу ввода и хранения информации по книгам можно записать в виде:

Листинг 3.5. Инвентарный перечень книг.

#include
struct book {
char title[100]; //наименование книги
char author[100]; //автор
int year; //год издания
};

int main()
{
int cnt_book = 0, ch;
struct book lib[100];
do
{
printf(“Введите наименование книги: “);
scanf(“%s”,lib[cnt_book].title);
printf(“Введите автора книги: “);
scanf(“%s”,lib[cnt_book].author);
printf(“Введите год издания книги: “);
scanf(“%d”,&lib.year);
printf(“Нажмите q для завершения ввода: ”);
cnt_book++;
}
while(scanf(“%d”,ch) == 1 && cnt_book < 100);
return 0;
}

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

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

struct bool lib = {
“Евгений Онегин”,
“Пушкин А.С.”,
1995
};

При выполнении данного фрагмента программы в переменные структуры title, author и year будет записана соответственно информация: “Евгений Онегин”, “Пушкин А.С.”, 1995. Здесь следует обратить внимание, что последовательность данных при инициализации должна соответствовать последовательности полей в структуре. Это накладывает определенные ограничения, т.к. при инициализации необходимо помнить последовательность полей в структуре. Стандарт C99 допускает более гибкий механизм инициализации полей структуры:

struct book lib = {.year = 1995,
.author = “Пушкин А.С.”,
.title = “Евгений Онегин” };

или

struct book lib = {.year = 1995,
.title = “Евгений Онегин” };

или

struct book lib = {.author = “Пушкин А.С.”,
.title = “Евгений Онегин”,
1995 };

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

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

struct tag_fio {
char last[100];
char first[100];
char otch[100];
};
struct tag_people {
struct tag_fio fio; //вложенная структура
char job[100];
int old;
};

Рассмотрим способ инициализации и доступ к полям структуры people на следующем примере.

Листинг 3.6. Работа с вложенными структурами.

int main()
{
struct people man = {
{“Иванов”, “Иван”, “Иванович”},
“Электрик”,
50 };
printf(“Ф.И.О.:%s %s %s\n”,man.fio.last,man.fio.first,
man.fio.otch);
printf(“Профессия: %s \n”,man.job);
printf(“Возраст: %d\n”,man.old);
return 0;
}

В данном примере показано, что для инициализации структуры внутри другой структуры следует использовать дополнительные фигурные скобки, в которых содержится информация для инициализации полей фамилии, имени и отчества сотрудника. Для того чтобы получить доступ к полям вложенной структуры выполняется сначала обращение к ней по имени man.fio, а затем к ее полям: man.fio.last, man.fio.first и man.fio.otch. Используя данное правило, можно создавать многоуровневые вложения для эффективного хранения и извлечения данных.

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

Листинг 3.7. Передача структур через аргументы функции.

#include
struct tag_people {
char name[100];
char job[100];
int old;
};
void show_struct(struct tag_people man);
int main()
{
struct tag_people person = {“Иванов”,”Электрик”,30};
show_struct(person);
return 0;
}
void show_struct(struct tag_people man)
{
printf(“Имя: %s\n”,man.name);
printf(“Профессия: %s\n”,man.job);
printf(“Возраст: %d\n”,man.old);
}

В приведенном примере используется функция с именем show_struct, которая имеет тип аргумента struct tag_people и переменную-структуру man. При передаче структуры функции создается ее копия, которая доступная в теле функции show_struct под именем man. Следовательно, любые изменения полей структуры с именем man никак не повлияют на содержание структуры с именем person. Вместе с тем иногда необходимо выполнять изменение полей структуры функции и возвращать измененные данные вызывающей программе. Для этого можно задать функцию, которая будет возвращать структуру, как показано в листинге 3.8.

Листинг 3.8. Функции, принимающие и возвращающие струткуру.

#include
struct tag_people {
char name[100];
char job[100];
int old;
};
void show_struct(struct tag_people man);
struct tag_people get_struct();
int main()
{
struct tag_people person;
person = get_struct();
show_struct(person);
return 0;
}
void show_struct(struct tag_people man)
{
printf(“Имя: %s\n”,man.name);
printf(“Профессия: %s\n”,man.job);
printf(“Возраст: %d\n”,man.old);
}
struct tag_people get_struct()
{
struct tag_people man;
scanf(“%s”,man.name);
scanf(“%s”,man.job);
scanf(“%d”,man.old);
return man;
}

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

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

void show_struct(struct people mans[], int size);

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

Листинг 3.9. Передача массив структур функции.

#include
#define N 2
struct tag_people {
char name[100];
char job[100];
int old;
};
void show_struct(struct people mans[], int size);
int main()
{
struct people persons[N] = {
{ “Иванов”, «Электрик», 35 },
{ “Петров”, «Преподаватель», 50 },
};
show_struct(persons, N);
}
void show_struct(struct people mans[], int size)
{
for(int i = 0;i < size;i++) {
printf(“Имя: %s\n”,mans[i].name);
printf(“Профессия: %s\n”,mans[i].job);
printf(“Возраст: %d\n”,mans[i].old);
}
}

При передаче аргумента persons выполняется копирование информации в массив mans и указывается дополнительный параметр size, для определения числа элементов массива mans. Затем в функции show_struct() реализуется цикл, в котором выполняется отображение информации массива структуры на экран монитора.

Битовые поля

Стандарт C99, который часто является основой языка С++, позволяет описывать данные на уровне битов. Это достигается путем использования битовых полей, представляющие собой переменные типов signed или unsigned int, у которых используются лишь несколько бит для хранения данных. Такие переменные обычно записываются в структуру и единую последовательность бит. Рассмотрим пример, в котором задается структура flags, внутри которой задано 8 битовых полей:

struct {
unsigned int first: 1;
unsigned int second: 1;
unsigned int third: 1;
unsigned int forth: 1;
unsigned int fifth: 1;
unsigned int sixth: 1;
unsigned int sevnth: 1;
unsigned int eighth: 1;
} flags;

Теперь, для определения того или иного бита переменной flags достаточно воспользоваться операцией

flags.first = 1;
flags.third = 1;

В этом случае будут установлены первый и третий биты, а остальные равны нулю, что соответствует числу 5. Данное значение можно отобразить, воспользовавшись функцией printf():

printf(“flags = %d.\n”,flags);

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

flags = 5; //неверно, так нельзя

Также нельзя присваивать значение flags переменным, например, следующая запись приведет к сообщению об ошибке:

int var = flags; //ошибка, структуру нельзя присваивать переменной

Так как поля first,…, eighth могут содержать только один бит информации, то они принимают значения 0 или 1 для типа unsigned int и 0 и -1 - для типа signed int. Если полю присваивается значение за пределами этого диапазона, то оно выбирает первый бит из присваиваемого числа.

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

struct {
unsigned int code1: 2;
unsigned int code2: 2;
unsigned int code3: 8;
} prcode;

Здесь создается два двух битовых поля и одно восьмибитовое. В результате возможны следующие операции присваивания:

prcode.code1 = 0;
prcode.code2 = 3;
prcode.code3 = 128;

Структуры flags и prcode удобно использовать в условных операторах if и switch. Рассмотрим пример использования структуры битовых полей для описания свойств окна пользовательского интерфейса.

struct tag_window {
unsigned int show: 1 //показать или скрыть
unsigned int style: 3 //WS_BORDER, WS_CAPTION, WS_DLGFRAME
unsigned int color: 3 //RED, GREEN, BLUE
} window;

Определим следующие константы:

#define WS_BORDER 1 //001
#define WS_CAPTION 2 //010
#define WS_DLGFRAME 4 //100
#define RED 1
#define GREEN 2
#define BLUE 4
#define SW_SHOW 0
#define SW_HIDE 1

Пример инициализации структуры битовых полей

window.show = SW_SHOW;
window.style = WS_BORDER | WS_CAPTION;
window.color = RED | GREEN | BLUE; //белый цвет

Объединения

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

union tag_value {
int var_i;
double var_f;
char var_ch;
};

Данное объединение будет занимать в памяти 8 байт, ровно столько, сколько занимает переменная самого большого объема, в данном случае var_f типа double. Все остальные переменные var_i и var_ch будут находиться в той же области памяти, что и переменная var_f. Благодаря такому подходу происходит экономия памяти, но платой за это является возможность хранения значения только одной переменной из трех в определенный момент времени. Как видно из постановки задачи такое ограничение не будет влиять на работоспособность программы. Если бы вместо объединения использовалась структура tag_value, то можно было бы сохранять значения всех трех переменных одновременно, но при этом требовалось бы больше памяти.

Для того чтобы программа «знала» какой тип переменной содержит объединение tag_value, необходимо ввести переменную, значение которой будет указывать номер используемой переменной: 0 – var_i, 1 – var_f и 2 – var_ch. Причем эту переменную удобно представить с объединением в виде структуры следующим образом:

struct tag_var {
union tag_value value;
short type_var;
};

Таким образом, получаем следующий алгоритм записи разнородной информации в объединение tag_value.



Поделиться:


Последнее изменение этой страницы: 2017-02-05; просмотров: 392; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.117.152.251 (0.123 с.)