двумерный динамический массив.
// объявление двумерного динамического массива на 10 элементов:
float **ptrarray = new float* [2]; // две строки в массиве
for (int count = 0; count < 2; count++)
ptrarray[count] = new float [5]; // и пять столбцов
// где ptrarray – массив указателей на выделенный участок памяти под массив вещественных чисел типа float
Сначала объявляется указатель второго порядка float **ptrarray, который ссылается на массив указателей float* [2],где размер массива равен двум.После чего в циклеforкаждой строке массива объявленного встроке 2выделяется память под пять элементов. В результате получается двумерный динамический массив ptrarray[2][5].Рассмотрим пример высвобождения памяти отводимой под двумерный динамический массив.
// высвобождение памяти отводимой под двумерный динамический массив:
for (int count = 0; count < 2; count++)
delete [] ptrarray[count];
// где 2 – количество строк в массиве
Пример:
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
void main()
{
int *a; // указатель на массив
int i, j, n, m;
system("chcp 1251");
system("cls");
printf("Введите количество строк: ");
scanf("%d", &n);
printf("Введите количество столбцов: ");
scanf("%d", &m);
// Выделение памяти
a = (int*) malloc(n*m*sizeof(int));
// Ввод элементов массива
for(i=0; i<n; i++) // цикл по строкам
{
for(j=0; j<m; j++) // цикл по столбцам
{
printf("a[%d][%d] = ", i, j);
scanf("%d", (a+i*m+j));
}
}
// Вывод элементов массива
for(i=0; i<n; i++) // цикл по строкам
{
for(j=0; j<m; j++) // цикл по столбцам
{
printf("%5d ", *(a+i*m+j)); // 5 знакомест под элемент массива
}
printf("\n");
}
free(a);
getchar(); getchar();
}
Результат выполнения
Введите количество строк: 3
Введите количество столбцов: 4
a[0][0]=1
a[0][1]=2
a[0][2]=3
a[0][3]=4
a[1][0]=5
a[1][1]=6
a[1][2]=7
a[1][3]=8
a[2][0]=9
a[2][1]=10
a[2][2]=11
a[2][3]=12
1 2 3 4
5 6 7 8
9 10 11 12
Возможен также другой способ динамического выделения памяти под двумерный массив - с использованием массива указателей. Для этого необходимо:
- выделить блок оперативной памяти под массив указателей;
- выделить блоки оперативной памяти под одномерные массивы, представляющие собой строки искомой матрицы;
- записать адреса строк в массив указателей.
Функция malloc() – возвращает указатель на первый байт области памяти размером size, которая была выделена из динамически распределяемой области памяти. Если в динамической области памяти не хватает памяти, то возвращается нулевой указатель.
Пример:
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
void main()
{
int **a; // указатель на указатель на строку
int i, j, n, m;
system("chcp 1251");
system("cls");
printf("Введите количество строк: ");
scanf("%d", &n);
printf("Введите количество столбцов: ");
scanf("%d", &m);
// Выделение памяти под указатели на строки
a = (int**)malloc(n*sizeof(int*));
// Ввод элементов массива
for(i=0; i<n; i++) // цикл по строкам
{
// Выделение памяти под хранение строк
a[i] = (int*)malloc(m*sizeof(int));
for(j=0; j<m; j++) // цикл по столбцам
{
printf("a[%d][%d] = ", i, j);
scanf("%d", &a[i][j]);
}
}
// Вывод элементов массива
for(i=0; i<n; i++) // цикл по строкам
{
for(j=0; j<m; j++) // цикл по столбцам
{
printf("%5d ", a[i][j]); // 5 знакомест под элемент массива
}
printf("\n");
free(a[i]); // освобождение памяти под строку
}
free(a);
getchar(); getchar();
}
Результат выполнения программы аналогичен предыдущему случаю.
С помощью динамического выделения памяти под указатели строк можно размещать свободные массивы. Свободным называется двухмерный массив (матрица), размер строк которого может быть различным. Преимущество использования свободного массива заключается в том, что не требуется отводить память компьютера с запасом для размещения строки максимально возможной длины. Фактически свободный массив представляет собой одномерный массив указателей на одномерные массивы данных.
Указатели.
Указатель – это переменная, значением которой является адрес, по которому располагаются данные. Адрес – это номер ячейки памяти, в которой или с которой располагаются данные.
По типу данных в СИ указатели делятся на:
Типизированный указатель – указатель, содержащий адрес данных определенного типа (системного или пользовательского).
Не типизированный указатель – указатель, содержащий адрес данных неопределенного типа (просто адрес).
объявление указателя;
установка указателя;
обращение к значению, расположенному по указателю. Объявление (описание) указателя в языке СИ имеет следующий вид:
тип [near|far] *имя [=значение];
Указатель в СИ при объявлении можно инициализировать, указав через знак присвоения соответствующее значение. Данное значение должно быть адресом, записанном в одном из следующих виде:
•нулевое значение (идентификатор NULL);
•другой указатель;
•адрес переменной (через операцию взятия адреса);
•выражение, представляющее собой арифметику указателей;
•адрес, являющийся результатом выделения динамической памяти.
Пример:
#include <stdio.h>
int main()
{
int var; // обычная целочисленная переменная
int *ptrVar; // целочисленный указатель (ptrVar должен быть типа int, так как он будет ссылаться на переменную типа int)
ptrVar = &var; // присвоили указателю адрес ячейки в памяти, где лежит значение переменной var
scanf( "%d", &var ); // в переменную var положили значение, введенное с клавиатуры
printf( "%d\n", *ptrVar ); // вывод значения через указатель
getchar();
}
Результат выполнения: 6 6
Лекция №3.
Функции.
Функция – это синтаксически выделенный именованный программный модуль, выполняющий определенное действие или группу действий. Каждая функция имеет свой интерфейс и реализацию. Интерфейс функции – заголовок функции, в котором указывается название функции, список ее параметров и тип возвращаемого значения.
Описание функции на языке СИ осуществляется в любом месте программы вне описания других функций и состоит из трех элементов:
1. прототип функции;
2. заголовок функции;
3. тело функции.
Прототип функции – необязательная часть описания функции, предназначенная для объявления некоторой функции, интерфейс которой соответствует данному прототипу.Объявление прототипа имеет следующий вид:
тип имя(список типов формальных параметров);
Параметры функции – значения, передаваемые в функцию при ее вызове.
Заголовок функции – описание интерфейсной части функции, которая содержит: тип возвращаемого значения, имя функции и список формальных параметров функции. Синтаксис объявления заголовка функции:
тип имя(список формальных параметров)
Примеры заголовков функций:
int func(int i, double x, double y)
void func(int ind, char *string)
double func(void)
Тело функции – часть-реализация, содержащая программный код, выполняемый при вызове функции. Тело функции всегда следует сразу после заголовка функции (разделять их нельзя) и заключено в фигурные скобки.
Пример:
Реализация функции на СИ для вычисления факториала числа.
double factorial(unsigned);
...
double factorial(unsigned num)
{
double fact = 1.0;
for(unsigned i=1;i<=num;i++)
fact *= (double)i;
return fact;
}
Структуры.
Структура – это сложный тип данных представляющий собой упорядоченное в памяти множество элементов различного типа. Каждый элемент в структуре имеет свое имя и называется полем.
Объявление в СИ структуры имеет вид:
struct [имя типа]
{
поле_1;
поле_2;
...
поле_N;
} [список переменных];
Объявление полей структуры возможно только без инициализации. Если несколько полей следующих друг за другом в описании структуры имеют один и тот же тип, то для их описания можно использовать синтаксис объявления нескольких переменных одного и того же типа.
Файлы.
Файл – это именованная область данных на каком-либо носителе информации. Типы файлов (относительно языка «СИ»):
текстовые;
бинарные.
Основные операции производимые над файлами:
1.Открытие файлов.
2.Чтение и запись данных.
3.Закрытие файлов.
Дополнительные операции:
1.Навигация по файлу.
2.Обработка ошибок работы с файлами.
3.Удаление и переименование файлов.
4.Описание переменной
Режимы открытия файлов с СИ
r | только чтение |
w | Только запись. Если файл существовал, то он переписывается. |
a | Добавление: открытие файла для записи в конец, или создание файла. |
r+ | Открывает файл для обновления (чтение и запись). |
w+ | Открывает файл для обновления (чтение и запись), переписывая файл, если он существует. |
a+ | Открывает файл для записи в конец файла или для чтения. |
Перенаправление потоков
FILE * freopen(const char *filename, const char *mode, FILE *stream);
Функция возвращает:
Указатель на файл – все нормально,
NULL – ошибка переопределения.
Закрытие файла
int fclose(FILE *stream);
stream - указатель на открытый файл.
Функция возвращает:
0 – файл успешно закрыт.
1 – произошла ошибка закрытия файла.
Проверка на достижение конца файла
int feof(FILE *stream);
stream - указатель на открытый файл.
Функция возвращает:
0 – если конец файла еще не достигнут.
!0 – достигнут конец файла.
Открытие текстовых файлов
Во втором параметре дополнительно указывается символ t (необязательно):
rt, wt, at, rt+, wt+, at+
Чтение из текстового файла
Форматированное чтение
int fscanf(FILE *stream, const char * format, [arg] ...);
Функция возвращает:
>0 – число успешно прочитанных переменных,
0 – ни одна из переменных не была успешно прочитана,
EOF – ошибка или достигнут конец файла.
Чтение строки
char * fgets(char * buffer, int maxlen, FILE *stream);
Функция возвращает:
buffer – все нормально,
NULL – ошибка или достигнут конец файла.
Чтение строки
char * fgets(char * buffer, int maxlen, FILE *stream);
Функция возвращает:
buffer – все нормально,
NULL – ошибка или достигнут конец файла.
Чтение символа
int fgetc(FILE *stream);
Функция возвращает:
код символа – если все нормально,
EOF – если ошибка или достигнут конец файла.
Помещение символа обратно в поток
int ungetc(int c, FILE *stream);
Функция возвращает:
код символа – если все успешно,
EOF – произошла ошибка.
Запись в текстовый файл в СИ
Форматированный вывод
int fprintf(FILE *stream, const char *format, [arg] ...);
Функция возвращает:
число записанных символов – если все нормально,
отрицательное значение – если ошибка.
Запись строки
int fputs(const char *string, FILE *stream);
Функция возвращает:
число записанных символов – все нормально,
EOF – произошла ошибка.
Запись символа
int fputc(int c, FILE *stream);
Функция возвращает:
код записанного символа – все нормально,
EOF – произошла ошибка.
Открытие бинарных файлов
Во втором параметре дополнительно указывается символ b (обязательно):rb, wb, ab, rb+, wb+, ab+
Чтение из бинарных файлов
size_t fread(void *buffer, size_t size, size_t num,FILE *stream);
Функция возвращает количество прочитанных блоков. Если оно меньше num, то произошла ошибка или достигнут
конец файла.
Запись в бинарный файл
size_t fwrite(const void *buffer, size_t size, size_t num, FILE *stream);
Функция возвращает количество записанных блоков. Если оно меньше num, то произошла ошибка.
Навигация по файлу
Чтение текущего смещения в файле:
long int ftell(FILE *stream);
Изменение текущего смещения в файле:
int fseek(FILE *stream, long int offset, int origin);
SEEK_SET (0) – от начала файла.
SEEK_CUR (1) – от текущей позиции.
SEEK_END (2) – от конца файла.
Функция возвращает:
0 – все нормально,
!0 – произошла ошибка.
Перемещение к началу файла:
void rewind(FILE *stream);
Чтение текущей позиции в файле:
int fgetpos(FILE *stream, fpos_t *pos);
Установка текущей позиции в файле:
int fsetpos(FILE *stream, const fpos_t *pos);
Функции возвращают:
0 – все успешно,
!0 – произошла ошибка.
Структура fpos_t:
typedef struct fpos_t {
long off;
mbstate_t wstate;
} fpos_t;
Получение признака ошибки:
int ferror(FILE *stream);
Функция возвращает ненулевое значение, если возникла ошибка.
Функция сброса ошибки:
void clearerr(FILE *stream);
Функция вывода сообщения об ошибке:
void perror(const char *string);
Буферизация
Функция очистки буфера:
int fflush(FILE *stream);
Функция возвращает:
0 – все нормально.
EOF – произошла ошибка.
Функция управления буфером:
void setbuf(FILE *stream, char * buffer);
Создает буфер размером BUFSIZ. Используется до ввода или вывода в поток.
Временные файлы
Функция создания временного файла:
FILE * tmpfile(void);
Создает временный файл в режиме wb+. После закрытия файла, последний автоматически удаляется.
Функция генерации имени временного файла:
char * tmpnam(char *buffer);
Удаление и переименование
Функция удаления файла:
int remove(const char *filename);
Функция переименования файла:
int rename(const char *fname, const char *nname);
Функции возвращают:
0 – в случае успеха,
!0 – в противном случае.
Лекция №4.
Стек.
Стек (stack) является как бы противоположностью очереди, поскольку он работает по принципу "последним пришел — первым вышел" (last-in, first-out, LIFO). Чтобы наглядно представить себе стек, вспомните стопку тарелок. Первая тарелка, стоящая на столе, будет использована последней, а последняя тарелка, положенная наверх — первой. Стеки часто применяются в системном программном обеспечении, включая компиляторы и интерпретаторы.
При работе со стеками операции занесения и извлечения элемента являются основными. Данные операции традиционно называются "затолкать в стек" (push) и "вытолкнуть из стека" (pop). Поэтому для реализации стека необходимо написать две функции: push(), которая "заталкивает" значение в стек, и pop(), которая "выталкивает" значение из стека. Также необходимо выделить область памяти, которая будет использоваться в качестве стека. Для этой цели можно отвести массив или динамически выделить фрагмент памяти с помощью функций языка С, предусмотренных для динамического распределения памяти. Как и в случае очереди, функция извлечения получает из списка элемент и удаляет его, если он не хранится где-либо еше. Ниже приведена общая форма функций push() и pop(), работающих с целочисленным массивом. Стеки данных другого типа можно организовывать, изменив базовый тип данных массива.
int stack[MAX];
int tos=0; /* вершина стека */
/* Затолкать элемент в стек. */
void push(int i)
{
if(tos >= MAX) {
printf("Стак полон\n");
return;
}
stack[tos] = i;
tos++;
}
/* Получить верхний элемент стека. */
int pop(void)
{
tos--;
if(tos < 0) {
printf("Стек пуст\n");
return 0;
}
return stack[tos];
}
Переменная tos ("top of stack" — "вершина стека") содержит индекс вершины стека. При реализации данных функций необходимо учитывать случаи, когда стек заполнен или пуст. В нашем случае признаком пустого стека является равенство tos нулю, а признаком переполнения стека — такое увеличение tos, что его значение указывает куда-нибудь за пределы последней ячейки массива.
Пример работы со стеком.
Действие | Содержимое стека |
push(A) | A |
push(B) | В А |
push(C) | C B A |
рор() извлекает С | В А |
push(F) | F В А |
рор() извлекает F | В А |
рор() извлекает В | А |
рор() извлекает А | пусто |
Стек будет размешаться в динамически распределяемой памяти, а не в массиве фиксированного размера. Хотя применение динамического распределения памяти и не требуется в таком простом примере, мы увидим, как использовать динамическую память для хранения данных стека.
Пример:
/* Простой калькулятор с четырмя действиями. */
#include <stdio.h>
#include <stdlib.h>
#define MAX 100
int *p; /* указатель на область свободной памяти */
int *tos; /* указатель на вершину стека */
int *bos; /* указатель на дно стека */
void push(int i);
int pop(void);
int main(void)
{
int a, b;
char s[80];
p = (int *) malloc(MAX*sizeof(int)); /* получить память для стека */
if(!p) {
printf("Ошибка при выделении памяти\n");
exit(1);
}
tos = p;
bos = p + MAX-1;
printf("Калькулятор с четырьмя действиями\n");
printf("Нажмите 'q' для выхода\n");
do {
printf(": ");
gets(s);
switch(*s) {
case '+':
a = pop();
b = pop();
printf("%d\n", a+b);
push(a+b);
break;
case '-':
a = pop();
b = pop();
printf("%d\n", b-a);
push(b-a);
break;
case '*':
a = pop();
b = pop();
printf("%d\n", b*a);
push(b*a);
break;
case '/':
a = pop();
b = pop();
if(a==0) {
printf("Деление на 0.\n");
break;
}
printf("%d\n", b/a);
push(b/a);
break;
case '.': /* показать содержимое вершины стека */
a = pop();
push(a);
printf("Текущее значение на вершине стека: %d\n", a);
break;
default:
push(atoi(s));
}
} while(*s != 'q');
return 0;
}
/* Занесение элемента в стек. */
void push(int i)
{
if(p > bos) {
printf("Стек полон\n");
return;
}
*p = i;
p++;
}
/* Получение верхнего элемента из стека. */
int pop(void)
{
p--;
if(p < tos) {
printf("Стек пуст\n");
return 0;
}
return *p;
}
Очередь.
Очередь — это линейный список информации, работа с которой происходит по принципу "первым пришел — первым вышел" (first-in, first-out); этот принцип (и очередь как структура данных) иногда еще называется FIFO. Это значит, что первый помещенный в очередь элемент будет получен из нее первым, второй помещенный элемент будет извлечен вторым и т.д. Это единственный способ работы с очередью; произвольный доступ к отдельным элементам не разрешается.
Чтобы представить себе работу очереди, давайте введем две функции: qstore() и qretrieve() (от "store"— "сохранять", "retrieve" — "получать"). Функция qstore() помещает элемент в конец очереди, а функция qretrieve() удаляет элемент из начала очереди и возвращает его значение. В таблице показано действие последовательности таких операций.
Действие | Содержимое очереди |
qstore(A) | A |
qstore(B) | А В |
qstore(C) | A B C |
qretrieve() возвращает А | В С |
qstore(D) | B C D |
qretrieve() возвращает В | C D |
qretrieve() возвращает С | D |
Следует иметь в виду, что операция извлечения удаляет элемент из очереди и уничтожает его, если он не хранится где-нибудь в другом месте. Поэтому после извлечения всех элементов очередь будет пуста.
В программировании очереди применяются при решении многих задач. Один из наиболее популярных видов таких задач — симуляция. Очереди также применяются в планировщиках задач операционных систем и при буферизации ввода/вывода.
Пример:
/* Мини-планировщик событий */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#define MAX 100
char *p[MAX], *qretrieve(void);
int spos = 0;
int rpos = 0;
void enter(void), qstore(char *q), review(void), delete_ap(void);
int main(void)
{
char s[80];
register int t;
for(t=0; t < MAX; ++t) p[t] = NULL; /* иницилизировать массив
пустыми указателями */
for(;;) {
printf("Ввести (E), Список (L), Удалить (R), Выход (Q): ");
gets(s);
*s = toupper(*s);
switch(*s) {
case 'E':
enter();
break;
case 'L':
review();
break;
case 'R':
delete_ap();
break;
case 'Q':
exit(0);
}
}
return 0;
}
/* Вставка в очередь новой встречи. */
void enter(void)
{
char s[256], *p;
do {
printf("Введите встречу %d: ", spos+1);
gets(s);
if(*s==0) break; /* запись не была произведена */
p = (char *) malloc(strlen(s)+1);
if(!p) {
printf("Не хватает памяти.\n");
return;
}
strcpy(p, s);
if(*s) qstore(p);
} while(*s);
}
/* Просмотр содержимого очереди. */
void review(void)
{
register int t;
for(t=rpos; t < spos; ++t)
printf("%d. %s\n", t+1, p[t]);
}
/* Удаление встречи из очереди. */
void delete_ap(void)
{
char *p;
if((p=qretrieve())==NULL) return;
printf("%s\n", p);
}
/* Вставка встречи. */
void qstore(char *q)
{
if(spos==MAX) {
printf("List Full\n");
return;
}
p[spos] = q;
spos++;
}
/* Извлечение встречи. */
char *qretrieve(void)
{
if(rpos==spos) {
printf("Встречь больше нет.\n");
return NULL;
}
rpos++;
return p[rpos-1];
}
Список.
односвязный циклический список это рекурсивное объявление структур, точнее указателя на нее в самой структуре типа:
struct s{
int data;//поле данных
s *next;//следующий элемент
} *first,*curr;//первый и текущий элемент
Инициализация:
first=new s;
curr=new s;
first->next=curr;
чтобы получить первый элемент используй first->data
чтобы добавить новый элемент: curr->next=new s;
curr=curr->next;//переходишь к последнему
и чтобы получить например 50 элемент через цикл перебирай список:
curr=first;//переход к первому
for(int i=0;i<50;i++)
{
if(curr->next!=NULL)
{
curr=curr->next;
}
}