Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
16
Лекции по дисциплине «Информатики и программирование»
Автор Шульга Т.Э.
В языках программирования существует множество различных типов данных. На предыдущих лекциях нами были рассмотрены только основные типы данных языка программирования С. Для того, что чтобы разобраться в многообразии типов данных вводят различные классификации. Рассмотрим две такие классификации.
Первая из них - классификация по строению элемента. В соответствии с ней все типы модно разделить на скалярные (простые), которые не имеют внутренней структуры и структурированные, которые состоят из элементов других типов. Иными словами, cскалярный (простой) тип данных позволяют хранить только одно значение, а структурированный тип данных позволяют хранить совокупности значений. Все рассмотренные в предыдущей теме типы данных являются скалярными. В общем случае к скалярным типам относятся числовые и символьные типы, указатели, перечисления. К структурированным типам относят, например, массивы, структуры, классы.
Вторая классификация в качестве критерия использует способ создания типа данных. В соответствии с этим критерием, все типы данных делятся на встроенные (стандартные, определенные в стандарте языка) и определяемые программистом. К встроенным типам обычно относят числовые и символьные типы данных, логический тип. К типам, определяемым программистом, относят, например, перечисления, структуры, классы.
В данной лекции рассмотрим такие типы данных как указатели, ссылки, перечисления, массивы, строки.
Указатели это переменные, значениями которых служат адреса участков памяти, выделенных для объектов конкретных типов. Различают три вида указателей указатели на объекты, на функции и на void. Кроме того, различают указатели-переменные и указатели-константы. В простейшем случае определение указателя-переменной на некоторый объект имеет вид
тип *[const] имя [инициал_выр];
где звездочка относится непосредственно к имени. Например,
char *t1, *t2, t3; // t1, t2 -неинициализированные указатели на объекты типа int (то есть в переменных t1, t2 может храниться адрес объекта типа int), a t3 переменная типа int;
const int *t1; указатель на константу целого типа.
В качестве инициализирующего выражения указателя должно использовать константное выражение, частными случаями которого являются
Кроме того, инициализировать указатель можно с помощью операции динамического выделения участка памяти функцией malloc (из библиотеки stdlib.h). Например,
char *p5= maloc(1) или char *p5= maloc(sizeof(char)); //выделили память для перемененной типа char и связали указатель p с эти участком памяти.
Таким образ выделенная память должна быть освобождена с помощью функции free. В противном случае одна будет занята до перезагрузки операционной системы (Это проблема называется проблемой «динамического мусора»). Например, free(p5);
Основная операция над указателем разыменование, унарная операция * (получение значения через указатель). Эта операция также называется косвенным обращением или обращением по адресу. Например,
char d=*p3;// значением переменной c2 будет значение того участка памяти, с которым связан указатель p, то есть c=d= a.
С помощью операции разыменования инициализированного указателя можно не только получать, но и изменять содержимое участка памяти, с которым связан указатель. Например,
*p= b; // значением переменной с и d стал символ b.
Разыменование нулевого указателя приводит к ошибки времени выполнения, так как данная область памяти недоступна. Разыменование неинициализированного указателя приведет либо к ошибке времени выполнения, либо к получению случайных данных из памяти. Поэтому рекомендуется всегда инициализировать указатель при описании, а перед разыменованием проверять его на NULL.
Над указателями-переменными можно также осуществлять операции присваивания, сложение с константой, вычитание, --, ++, сравнение, приведение типов.
Если указатель описан со спецификатором const (то есть является константой-указателем), то он должен быть инициирован и его значения нельзя будет изменить с помощью приведенных выше операций. Например,
char * const p6=p5;
Указатель на void применяется в тех случаях, когда конкретный тип объекта, адрес которого следует хранить, не определен. Таким указателям можно присвоить значение указателя любого типа, а также сравнивать его с любыми указателями.
Ссылка представляет собой синоним имени, уже существующего объекта, и поэтому не занимает место в памяти (в отличии от указателя). Формат описания:
тип & имя инициализатор;
Наличие инициализатора обязательно. После инициализации ссылке не может быть присвоено другое значение. Например,
int a=10;
int& a1=a;
printf(“%d%d”,a,a1);
a1+=2;
printf(“%d%d”,a,a1);
Значением ссылки является адрес объекта, на который указывает ссылка, и, по сути ссылка представляет указатель, который не надо разыменовывать. В основном ссылки используются при работе с функциями.
Перечислимый тип по существу описывает целые константы (типа int), которым приписаны уникальные и удобные для использования имена.
Формат описания:
emun [имя типа] {список констант}
где константы могут быть инициализированы обычным образом.
Например,
enum digit {one=1,two,three};
Здесь one, two, three произвольным образом выбранные программистом идентификаторы для обозначения констант 1,2,3. После такого определения в программе наряду, например, с константой 1 можно использовать ее обозначение one. Фактически такая запись равносильно записи
const int one=1;
const int two=2;
const int three=3;
Обычно перечислимый тип используется для задания числовых рядов или стандартных рядов, таких как месяцы, дни недели.
Если в определении перечислимых констант опустить знаки “=” и не указывать числовых значений, то они будут присваиваться идентификаторам по умолчанию следующим образом: самый левый в фигурных скобках идентификатор получит значение 0, а каждый последующий увеличивается на 1. Имена перечислимых констант должны быть уникальными, а их значения могут совпадать.
Имя типа указывают, если требуется вводить в программе переменные этого типа. Например
digit d1,d2;
Компилятор обычно обеспечивает, чтобы эти переменные принимали значения только из списка констант.
Массив - самая распространенная структура данных, реализованная практически во всех языках программирования. Массив это именованная последовательность однотипных элементов, расположенных в памяти компьютера последовательно. Тип элементов массива называют базовым. К любому элементу массива можно обращаться произвольным образом, так как он имеет определенный номер, называемый индексом.
Описание массива в общем случае имеет формат:
[класс памяти] [const] тип имя [константное выражение]={список инициализирующих элементов};
Константное выражение представляет собой количество элементов массива (размерность массива). Если массив объявлен без инициализирующего списка, то задание размерности массива обязательно. Например,
int a[10]; - массив из 10 целых чисел
int n=10; int a[n]; - недопустимо, так как n не константа
int a[]; - недопустимо, так как нет ни размерности ни инициализирующего списка.
Предпочтительнее задавать размерность массива с помощью именованных констант. Например,
const int n=10;
int mas[n];
Когда массив объявлен без указания размера, но при этом инициализирован списком, его размер вычисляется путем подсчета числа элементов этого списка. Например
int a []={1,2,3,4};//размерность массива равна 4
Явная инициализация массива разрешена только при его определении и возможна двумя способами: либо с указанием размера массива в квадратных скобках, либо без его явного указания, например,
int а[6]={1,2,3,4};//массив из 6 целых чисел с инициализацией первых четырех, остальные будут обнулены.
char str1[]={a, b, c};// массив из 3 элементов типа char
Число элементов в инициализирующем списке должно быть меньше и равно указанной размерности массива, то есть запись int a[3]={1,2,3,4}; недопустима.
Доступ к элементам массива осуществляется с помощью комбинации имя массива + индекс элемента следующими двумя способами.
Работа с элементами массива организовывается обычно в цикле.
Для ввода и вывода элементов массива а можно использовать записи
scanf(“%d%d%d”, &a[0], &a[1], &a[3]);
printf (“%d%d%d”, a[0],a[1],a[3]);
соответственно, при условии, что количество элементов точно известно и невелико. На практике так поступают редко.
При выполнении команды printf(“%d”,a); на экране появится адрес памяти, по которому расположен первый элемент массива a. Комадна scanf("%d",a) считает данные с клавиатуры в первый элемент массива.
Исключение составляют только массивы символов (char), для которых функции printf и scanf работают по-другому.
Например, пусть описан массив
char s[255]=”Hello”;/*обратите внимание, что массив типа char допустимо инициализировать не только списком элементов в скобках {}, но и строковой константой*/
printf(“%s”,s); //на экран выведется, не адрес памяти, а вся строка
scanf("%s",s); // с клавиатуры считаются все символы до первого пробельного разделителя.
Функция malloc, используемая для инициализации указателей, позволяет определять массив еще одним способом (это так называемый динамический массив):
int k;
scanf(“%d”, &k);
int *a=malloc(k*sizeof(int);
Память, зарезервированная под динамический массив, должна освобождаться явным образом функцией free, например free(a);
Многомерные массивы описываются как массивы массивов, например,
int a2[3][2] //массив из 3 массивов, содержащих по 2 целых элемента.
Для обращения к элементу двумерного массива используется два индекса, например, a2[i][j]. Для работы с двумерными массивами используется конструкция вложенных циклов.
Задание для самостоятельной работы! Придумать, как двумерный массив задать динамически.
В языке С тип данных «строка» не определен, но определено понятие С-строка. С-строка это массив символов, заканчивающийся ноль-симолом. Ноль-симовол это символ с кодом, равным нулю, что записывается в виде esp-последовательности \0.
В С строка может быть описана двумя способами:
Например,
char str1[]= “adc”; //массив из 4 элементов типа char, элемент str2[3]= \0 признак конца строки.
Например,
char *str2= “abc”; // указатель на строковую константу
Для строк не определено специальных операций, они обрабатываются как обычные массивы. Однако в отличии от массивов других типов, можно считать все символы строки с клавиатуры можно с помощью одной функции scanf (“%s”, имя_символьного_массива). Следует помнить, что в этом случае считается только символы по первого пробельного разделителя. Кроме того, всю строку можно вывести на экран с помощью одной функции printf (“%s”, имя_символьного массива).
Например
char s[]="Hello";
printf("%s",s);// на экран выведется слово «Hello», а для массивов других типов в данном случае выведется только адрес памяти первого элемента.
scanf("%s",&s);//
// считывается строка для первого пробельного разделителя, для массивов других типов считается первый элемент.
Если на клавиатуре набрать строку «мама мыла раму», то в массив запишется только слово «мама», то есть в массиве будет 5 элементов со значениями м, а, м, а, \0.
printf("%s",s);//на экран выведется вся строка «мама»,
Кроме того строку можно считать с помощью функции gets(имя_символьного массива); Однако, она не контролирует выход за пределы массива.
При выводе символьного массива с помощью функции printf из памяти выводятся подряд все символы, начиная с первого и до тех пор, пока не встретится символ \0. Поэтому, если символьный массив инициализирован списком (а не строковой константой) или заполнятся в цикле ответственность за то, чтобы последний символ был символом конца строки, ложится на плечи программиста.
Например,
char s[]={m, a, m};
printf("%s",s);///на экран выведется mamЁ@kj^^ и т.п.
Существует функции стандартной библиотеки, которые позволяют копировать, сравнивать, объединять строки, выделять подстроки, определять длину строки, считывать строки с клавиатуры и файла. (Они описаны в заголовочных файлах stdio.h, stdlib.h, string.h)
Например,
#include <stdio.h>
#include <string.h>
main ()
{
const int n1=20;
int n2=20;
char str1[n1];
char *str2=malloc (n2);
gets(str1);//считываем строку
printf("Первая строка %s\n",str1);
scanf (“%s”,str2);//считываем строку до первого пробельного разделителя
printf("Вторая строка %s\n",str2);
printf("Длина строк: %d %d\n", strlen(s1), strlen(s2));
if(!strcmp(s1, s2)) printf("Строки равны\n");
strcat(s1, s2);// объединяем строки
printf("Объединенная строка: %s\n", s1);
}