Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Массивы указателей
Как и объекты любых других типов, указатели могут быть собраны в массив. В следующем операторе объявлен массив из 10 указателей на объекты типа int:
int *x[10];
Для присвоения, например, адреса переменной var третьему элементу массива указателей, необходимо написать:
x[2] = &var;
В результате этой операции, следующее выражение принимает то же значение, что и var:
*x[2]
Для передачи массива указателей в функцию используется тот же метод, что и для любого другого массива: имя массива без индекса записывается как формальный параметр функции. Например, следующая функция может принять массив x в качестве аргумента:
void display_array(int *q[])
{
int t;
for(t=0; t<10; t++)
printf("%d ", *q[t]);
}
Необходимо помнить, что q это не указатель на целые, а указатель на массив указателей на целые. Поэтому параметрq нужно объявить как массив указателей на целые. Нельзя объявить q просто как указатель на целые, потому что он представляет собой указатель на указатель.
Массивы указателей часто используются при работе со строками. Например, можно написать функцию, выводящую нужную строку с сообщением об ошибке по индексу num:
void syntax_error(int num)
{
static char *err[] = {
"Нельзя открыть файл\n",
"Ошибка при чтении\n",
"Ошибка при записи\n",
"Некачественный носитель\n"
};
printf("%s", err[num]);
}
Массив err содержит указатели на строки с сообщениями об ошибках. Здесь строковые константы в выражении инициализации создают указатели на строки. Аргументом функции printf() служит один из указателей массива err, который в соответствии с индексом num указывает на нужную строку с сообщением об ошибке. Например, если в функциюsyntax_error() передается num со значением 2, то выводится сообщение Ошибка при записи.
Отметим, что аргумент командной строки argv (см. главу 6) также является массивом указателей на строковые константы.
Иногда указатель может ссылаться на указатель, который ссылается на число. Это называется многоуровневой адресацией. Иногда применение таких указателей существенно усложняет программу, делает ее плохо читаемой и подверженной ошибкам. Рисунок иллюстрирует концепцию многоуровневой адресации. На рисунке видно, что значением "нормального" указателя является адрес объекта, содержащего нужное значение. В случае двухуровневой адресации первый указатель содержит адрес второго указателя, который содержит адрес объекта с нужным значением.
Многоуровневая адресация может иметь сколько угодно уровней, однако уровни глубже второго, т.е. указатели более глубокие, чем "указатели на указатели" применяются крайне редко. Дело в том, что при использовании таких указателей часто встречаются концептуальные ошибки из-за того, что смысл таких указателей представить трудно.
Переменная, являющаяся указателем на указатель, должна быть соответствующим образом объявлена. Это делается с помощью двух звездочек перед именем переменной. Например, в следующем операторе newbalance объявлена как указатель на указатель на переменную типа float:
float **newbalance;
Следует хорошо понимать, что newbalance это не указатель на число типа float, а указатель на указатель на число типа float.
Указатель Переменная +--------+ +--------+ | Адрес |------->|Значение| +--------+ +--------+
Одноуровневая адресация Указатель Указатель Переменная +--------+ +--------+ +--------+ | Адрес |----->| Адрес |----->|Значение| +--------+ +--------+ +--------+ Многоуровневая адресация |
Рис. Одноуровневая и многоуровневая адресация |
При двухуровневой адресации для доступа к значению объекта нужно поставить перед идентификатором две звездочки:
#include <stdio.h>
int main(void)
{
int x, *p, **q;
x = 10;
p = &x;
q = &p;
printf("%d", **q); /* печать значения x */
return 0;
}
Здесь p объявлена как указатель на целое, a q как указатель на указатель на целое. Функция printf() выводит на экран число 10.
Указатели на функции очень мощное средство языка С. Хотя нельзя не отметить, что это весьма трудный для понимания термин. Функция располагается в памяти по определенному адресу, который можно присвоить указателю в качестве его значения. Адресом функции является ее точка входа. Именно этот адрес используется при вызове функции. Так как указатель хранит адрес функции, то она может быть вызвана с помощью этого указателя. Он позволяет также передавать ее другим функциям в качестве аргумента.
В программе на С адресом функции служит ее имя без скобок и аргументов (это похоже на адрес массива, который равен имени массива без индексов). Рассмотрим следующую программу, в которой сравниваются две строки, введенные пользователем. Обратите внимание на объявление функции check() и указатель p внутри main(). Указатель p, как вы увидите, является указателем на функцию.
#include <stdio.h>
#include <string.h>
void check(char *a, char *b,
int (*cmp)(const char *, const char *));
int main(void)
{
char s1[80], s2[80];
int (*p)(const char *, const char *);
/* указатель на функцию */
p = strcmp;
/* присваивает адрес функции strcmp указателю p */
printf("Введите две строки.\n");
gets(s1);
gets(s2);
check(s1, s2, p); /* Передает адрес функции strcmp
посредством указателя p */
return 0;
}
void check(char *a, char *b,
int (*cmp)(const char *, const char *))
{
printf("Проверка на совпадение.\n");
if(!(*cmp)(a, b)) printf("Равны");
else printf("Не равны");
}
Проанализируем эту программу подробно. В первую очередь рассмотрим объявление указателя p в main():
int (*p)(const char *, const char *);
Это объявление сообщает компилятору, что p это указатель на функцию, имеющую два параметра типа const char * и возвращающую значение типа int. Скобки вокруг p необходимы для правильной интерпретации объявления компилятором. Подобная форма объявления используется также для указателей на любые другие функции, нужно лишь внести изменения в зависимости от возвращаемого типа и параметров функции.
Теперь рассмотрим функцию check(). В ней объявлены три параметра: два указателя на символьный тип (a и b) и указатель на функцию cmp. Обратите внимание на то, что указатель функции cmp объявлен в том же формате, что и p. Поэтому в cmp можно хранить значение указателя на функцию, имеющую два параметра типа const char * и возвращающую значение int. Как и в объявлении p, круглые скобки вокруг *cmp необходимы для правильной интерпретации этого объявления компилятором.
Вначале в программе указателю p присваивается адрес стандартной библиотечной функции strcmp(), которая сравнивает строки. Потом программа просит пользователя ввести две строки и передает указатели на них функции check(), которая их сравнивает. Внутри check() выражение
(*cmp)(a, b)
вызывает функцию strcmp(), на которую указывает cmp, с аргументами a и b. Скобки вокруг *cmp обязательны. Существует и другой, более простой, способ вызова функции с помощью указателя:
cmp(a, b);
Однако первый способ используется чаще (и мы рекомендуем использовать именно его), потому что при втором способе вызова указатель cmp очень похож на имя функции, что может сбить с толку читающего программу. В то же время у первого способа записи есть свои преимущества, например, хорошо видно, что функция вызывается с помощью указателя на функцию, а не имени функции. Следует отметить, что первоначально в С был определен именно первый способ вызова.
Вызов функции check() можно записать, используя непосредственно имя strcmp():
check(s1, s2, strcmp);
В этом случае вводить в программу дополнительный указатель p нет необходимости.
У читателя может возникнуть вопрос: какая польза от вызова функции с помощью указателя на функцию? Ведь в данном случае никаких преимуществ не достигнуто, этим мы только усложнили программу. Тем не менее, во многих случаях оказывается более выгодным передать имя функции как параметр или даже создать массив функций. Например, в программе интерпретатора синтаксический анализатор (программа, анализирующая выражения) часто вызывает различные вспомогательные функции, такие как вычисление математических функций, процедуры ввода-вывода и т.п. В таких случаях чаще всего создают список функций и вызывают их с помощью индексов.
Альтернативный подход использование оператора switch с длинным списком меток case делает программу более громоздкой и подверженной ошибкам.
В следующем примере рассматривается расширенная версия предыдущей программы. В этой версии функция check()устроена так, что может выполнять разные операции над строками s1 и s2 (например, сравнивать каждый символ с соответствующим символом другой строки или сравнивать числа, записанные в строках) в зависимости от того, какая функция указана в списке аргументов. Например, строки "0123" и "123" отличаются, однако представляют одно и то же числовое значение.
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
void check(char *a, char *b,
int (*cmp)(const char *, const char *));
int compvalues(const char *a, const char *b);
int main(void)
{
char s1[80], s2[80];
printf("Введите два значения или две строки.\n");
gets(s1);
gets(s2);
if(isdigit(*s1)) {
printf("Проверка значений на равенство.\n");
check(s1, s2, compvalues);
}
else {
printf("Проверка строк на равенство.\n");
check(s1, s2, strcmp);
}
return 0;
}
void check(char *a, char *b,
int (*cmp)(const char *, const char *))
{
if(!(*cmp)(a, b)) printf("Равны");
else printf("Не равны");
}
int compvalues(const char *a, const char *b)
{
if(atoi(a)==atoi(b)) return 0;
else return 1;
}
Если в этом примере ввести первый символ первой строки как цифру, то check() использует compvalues(), в противном случае strcmp(). Функция check() вызывает ту функцию, имя которой указано в списке аргументов при вызове check(), поэтому она в разных ситуациях может вызывать разные функции. Ниже приведены результаты работы этой программы в двух случаях:
Введите два значения или две строки.
тест
тест
Проверка строк на равенство.
Равны
Введите два значения или две строки.
0123
123
Проверка значений на равенство.
Равны
Сравнение строк 0123 и 123 показывает равенство их значений.
Обратите внимание, что в языке С нулем начинаются восьмеричные константы. Если бы эта запись была в выражении, то 0123 не было бы равно 123. Однако здесь функция atoi() обрабатывает это число как десятичное.
Многие обрабатывающие процедуры нуждаются в получении информации о функциях, к которым они должны обратиться за недостающими данными.
Одним из таких примеров является функция вычисления определенного интеграла кроме параметров-значений, задающих пределы интегрирования, ей необходимо сообщить имя подпрограммы, вычисляющей значения подынтегральной функции. В такой ситуации обычно используют указатель на функцию.
Объявление указателя pf на функцию f(x), аргумент которой и возвращаемое значение имеют тип double, выглядит следующим образом:
double (*pf)(double x);
Оно напоминает прототип функции, в котором имя функции заменено именем указателя, заключенным в круглые скобки.
Простейшая функция, вычисляющая определенный интеграл методом прямоугольников, может быть организована следующим образом:
double int_rect(double a, double b, double (*f)(double x))
{ int i, n=100;
double s=0,h=(b-a)/n;
for(i=0; i<=n; i++) s += f(a+i*h);
return s*h;
}
Последним аргументом процедуры int_rect является указатель на функцию. Поэтому в качестве фактического параметра мы должны подставить имя подынтегральной функции. Таковым, в частности, может быть имя любой стандартной функции из библиотеки math.h:
cout << int_rect(0,M_PI,sin) << endl; //результат= 1.99984
cout << int_rect(0,M_PI,cos) << endl; //результат=-4.18544e-17
В качестве второго примера рассмотрим программу нахождения корня уравнения y=f(x), если известно, что на интервале [ x1, x2 ] эта функция меняет знак. Алгоритм базируется на делении отрезка пополам. В точке xmid=(x1+x2)/2 смотрим знак функции f, который совпадет либо со знаком f(x1), либо со знаком f(x2). Выбираем ту половину отрезка, на концах которой функция принимает разные знаки. Затем исследуем его середину и т.д. Как только длина очередного отрезка станет достаточно малой или значение функции в центре отрезка окажется меньше заданной точности, процесс поиска корня прекращается.
#include <iostream.h>
#include <conio.h>
#include <math.h>
double y(double x) //функция f(x)=x2-4
{ return x*x-4; }
double root(double x1,double x2,double eps,double(*f)(double x))
{ double f12,f1,f2,xmid;
f1=f(x1); f2=f(x2);
if(f1*f2>0)
{ cerr<<"Error: sign(f1)=sign(f2)"; getch(); exit(0); }
while(x2-x1 > eps)
{ xmid=(x1+x2)/2.;
f12=f(xmid);
if(fabs(f12) < eps)
return xmid;
if(f12*f1>0) { x1=xmid; f1=f12; }
else {x2=xmid; f2=f12; }
}
return (x1+x2)/2.;
}void main()
{ cout<<root(0,10,1e-4,y);
getch();
}
//=== Результат работы ===
2.00001
Функция main() возвращает целое число, которое принимает вызывающий процесс обычно этим процессом является операционная система. Возврат значения из main() эквивалентен вызову функции exit() с тем же самым значением. Еслиmain() нe возвращает значение явно, то вызывающий процесс получает формально неопределенное значение. На практике же большинство компиляторов С автоматически возвращают 0, но если встает вопрос переносимости, то на такой результат полагаться с уверенностью нельзя.
Иногда при запуске программы бывает полезно передать ей какую-либо информацию. Обычно такая информация передается функции main() с помощью аргументов командной строки. Аргумент командной строки это информация, которая вводится в командной строке операционной системы вслед за именем программы. Например, чтобы запустить компиляцию программы, необходимо в командной строке после подсказки набрать примерно следующее:
cc имя_программы
имя_программы представляет собой аргумент командной строки, он указывает имя той программы, которую вы собираетесь компилировать.
Чтобы принять аргументы командной строки, используются два специальных встроенных аргумента: argc и argv. Параметрargc содержит количество аргументов в командной строке и является целым числом, причем он всегда не меньше 1, потому что первым аргументом считается имя программы. А параметр argv является указателем на массив указателей на строки. В этом массиве каждый элемент указывает на какой-либо аргумент командной строки. Все аргументы командной строки являются строковыми, поэтому преобразование каких бы то ни было чисел в нужный двоичный формат должно быть предусмотрено в программе при ее разработке.
Вот простой пример использования аргумента командной строки. На экран выводятся слово Привет и ваше имя, которое надо указать в виде аргумента командной строки.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if(argc!=2) {
printf("Вы забыли ввести свое имя.\n");
exit(1);
}
printf("Привет %s", argv[1]);
return 0;
}
Если вы назвали эту программу name (имя) и ваше имя Том, то для запуска программы следует в командную строку ввести name Том. В результате выполнения программы на экране появится сообщение Привет, Том.
Во многих средах все аргументы командной строки необходимо отделять друг от друга пробелом или табуляцией. Запятые, точки с запятой и тому подобные символы разделителями не считаются. Например,
run Spot, run
состоит из трех символьных строк, в то время как
Эрик, Рик, Фред
представляет собой одну символьную строку запятые, как правило, разделителями не считаются.
Если в строке имеются пробелы, то, чтобы из нее не получилось несколько аргументов, в некоторых средах эту строку можно заключать в двойные кавычки. В результате вся строка будет считаться одним аргументом. Чтобы подробнее узнать, как в вашей операционной системе задаются параметры командной строки, изучите документацию этой системы.
Очень важно правильно объявлять argv. Вот как это делают чаще всего:
char *argv[];
Пустые квадратные скобки указывают на то, что у массива неопределенная длина. Теперь получить доступ к отдельным аргументам можно с помощью индексации массива argv. Например, argv[0] указывает на первую символьную строку, которой всегда является имя программы; argv[1] указывает на первый аргумент и так далее.
Другим небольшим примером использования аргументов командной строки является приведенная далее программа countdown(счет в обратном порядке). Эта программа считает в обратном порядке, начиная с какого-либо значения (указанного в командной строке), и подает звуковой сигнал, когда доходит до 0. Обратите внимание, что первый аргумент, содержащий начальное значение, преобразуется в целое значение с помощью стандартной функции atoi(). Если вторым аргументом командной строки (а если считать аргументом имя программы, то третьим) является строка "display" (вывод на экран), то результат отсчета (в обратном порядке) будет выводиться на экран.
/* Программа счета в обратном порядке. */
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
int main(int argc, char *argv[])
{
int disp, count;
if(argc<2) {
printf("В командной строке необходимо ввести число,
с которого\n");
printf("начинается отсчет. Попробуйте снова.\n");
exit(1);
}
if(argc==3 && !strcmp(argv[2], "display")) disp = 1;
else disp = 0;
for(count=atoi(argv[1]); count; --count)
if(disp) printf("%d\n", count);
putchar('\a'); /* здесь подается звуковой сигнал */
printf("Счет закончен");
return 0;
}
Обратите внимание, если аргументы командной строки не будут указаны, то будет выведено сообщение об ошибке. В программах с аргументами командной строки часто делается следующее: в случае, когда пользователь запускает эти программы без ввода нужной информации, выводятся инструкции о том, как правильно указывать аргументы.
Чтобы получить доступ к отдельному символу одного из аргументов командной строки, введите в argv второй индекс. Например, следующая программа посимвольно выводит все аргументы, с которыми ее вызвали:
#include <stdio.h>
int main(int argc, char *argv[])
{
int t, i;
for(t=0; t<argc; ++t) {
i = 0;
while(argv[t][i]) {
putchar(argv[t][i]);
++i;
}
printf("\n");
}
return 0;
}
Помните, первый индекс argv обеспечивает доступ к строке, а второй индекс доступ к ее отдельным символам.
Обычно argc и argv используют для того, чтобы передать программе начальные команды, которые понадобятся ей при запуске. Например, аргументы командной строки часто указывают такие данные, как имя файла, параметр или альтернативное поведение. Использование аргументов командной строки придает вашей программе "профессиональный внешний вид" и облегчает ее использование в пакетных файлах.
Имена argc и argv являются традиционными, но не обязательными. Эти два параметра в функции main() вы можете назвать как угодно. Кроме того, в некоторых компиляторах для main() могут поддерживаться-дополнительные аргументы, поэтому обязательно изучите документацию к вашему компилятору.
Когда для программы не требуются параметры командной строки, то чаще всего явно декларируют функцию main() как не имеющую параметров. В таком случае в списке параметров этой функции используют ключевое слово void.
Указатели используются для динамического выделения памяти компьютера для хранения данных. Динамическое распределение означает, что программа выделяет память для данных во время своего выполнения. Память для глобальных переменных выделяется во время компиляции, а для нестатических локальных переменных в стеке. Во время выполнения программы ни глобальным, ни локальным переменным не может быть выделена дополнительная память. Но довольно часто такая необходимость возникает, причем объем требуемой памяти заранее неизвестен. Такое случается, например, при использовании динамических структур данных, таких как связные списки или двоичные деревья. Такие структуры данных при выполнении программы расширяются или сокращаются по мере необходимости. Для реализации таких структур в программе нужны средства, способные по мере необходимости выделять и освобождать для них память.
Память, выделяемая в С функциями динамического распределения данных, находится в т.н. динамически распределяемой области памяти (heap). Динамически распределяемая область памяти это свободная область памяти, не используемая программой, операционной системой или другими программами. Размер динамически распределяемой области памяти заранее неизвестен, но как правило в ней достаточно памяти для размещения данных программы. Большинство компиляторов поддерживают библиотечные функции, позволяющие получить текущий размер динамически распределяемой области памяти, однако эти функции не определены в Стандарте С. Хотя размер динамически распределяемой области памяти очень большой, все же она конечна и может быть исчерпана.
Основу системы динамического распределения в С составляют функции malloc() и free(). Эти функции работают совместно. Функция malloc() выделяет память, а free() освобождает ее. Это значит, что при каждом запросе функцияmalloc() выделяет требуемый участок свободной памяти, a free() освобождает его, то есть возвращает системе. В программу, использующую эти функции, должен быть включен заголовочный файл <stdlib.h>.
Прототип функции malloc() следующий:
void *malloc(size_t количество_байтов);
Здесь количество_байтов размер памяти, необходимой для размещения данных. (Тип size_t определен в <stdlib.h> как некоторый целый без знака.) Функция malloc() возвращает указатель типа void *, поэтому его можно присвоить указателю любого типа. При успешном выполнении malloc() возвращает указатель на первый байт непрерывного участка памяти, выделенного в динамически распределяемой области памяти. Если в динамически распределяемой области памяти недостаточно свободной памяти для выполнения запроса, то память не выделяется и malloc() возвращает нуль.
При выполнении следующего фрагмента программы выделяется непрерывный участок памяти объемом 1000 байтов:
char *p;
p = malloc(1000); /* выделение 1000 байтов */
После присвоения указатель p ссылается на первый из 1000 байтов выделенного участка памяти.
В следующем примере выделяется память для 50 целых. Для повышения мобильности (переносимости программы с одной машины на другую) используется оператор sizeof.
int *p;
p = malloc(50*sizeof(int));
Поскольку динамически распределяемая область памяти не бесконечна, при каждом размещении данных необходимо проверять, состоялось ли оно. Если malloc() не смогла по какой-либо причине выделить требуемый участок памяти, то она возвращает нуль. В следующем примере показано, как выполняется проверка успешности размещения:
p = malloc(100);
if(!p) {
printf("Нехватка памяти.\n");
exit(1);
}
Конечно, вместо выхода из программы exit() можно поставить какой-либо обработчик ошибки. Обязательным здесь можно назвать лишь требование не использовать указатель р, если он равен нулю.
Функция free() противоположна функции malloc() в том смысле, что она возвращает системе участок памяти, выделенный ранее с помощью функции malloc(). Иными словами, она освобождает участок памяти, который может быть вновь использован функцией malloc(). Функция free() имеет следующий прототип:
void free(void *p)
Здесь р указатель на участок памяти, выделенный перед этим функцией malloc(). Функцию free() ни в коем случае нельзя вызывать с неправильным аргументом, это мгновенно разрушит всю систему распределения памяти.
Подсистема динамического распределения в С используется совместно с указателями для создания различных программных конструкций, таких как связные списки и двоичные деревья.
Довольно часто возникает необходимость выделить память динамически, используя malloc(), но работать с этой памятью удобнее так, будто это массив, который можно индексировать. В этом случае нужно создать динамический массив. Сделать это несложно, потому что каждый указатель можно индексировать как массив. В следующем примере одномерный динамический массив содержит строку:
/* Динамическое распределение строки, строка вводится
пользователем, а затем распечатывается справа налево. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
char *s;
register int t;
s = malloc(80);
if(!s) {
printf("Требуемая память не выделена.\n");
exit(1);
}
gets(s);
for(t=strlen(s)-1; t>=0; t--) putchar(s[t]);
free(s);
return 0;
}
Перед первым использованием s программа проверяет, успешно ли прошло выделение памяти. Эта проверка необходима для предотвращения случайного использования нулевого указателя. Обратите внимание на то, что указатель s используется в функции gets(), а также при выводе на экран (но на этот раз уже как обыкновенный массив).
Можно также динамически выделить память для многомерного массива. Для этого нужно объявить указатель, определяющий все, кроме самого левого измерения массива. В следующем примере двухмерный динамический массив содержит таблицу чисел от 1 до 10 в степенях 1, 2, 3 и 4.
#include <stdio.h>
#include <stdlib.h>
int pwr(int a, int b);
int main(void)
{
/* Объявление указателя на массив из 10 строк
в которых хранятсяцелые числа (int). */
int (*p)[10];
register int i, j;
/* выделение памяти для массива 4 x 10 */
p = malloc(40*sizeof(int));
if(!p) {
printf("Требуемая память не выделена.\n");
exit(1);
}
for(j=1; j<11; j++)
for(i=1; i<5; i++) p[i-1][j-1] = pwr(j, i);
for(j=1; j<11; j++) {
for(i=1; i<5; i++) printf("%10d ", p[i-1][j-1]);
printf("\n");
}
return 0;
}
/* Возведение чисел в степень. */
pwr(int a, int b)
{
register int t=1;
for(; b; b--) t = t*a;
return t;
}
Программа выводит на экран следующее:
1 1 1 1
2 4 8 16
3 9 27 81
4 16 64 256
5 25 125 625
6 36 216 1296
7 49 343 2401
8 64 512 4096
9 81 729 6561
10 100 1000 10000
Указатель р в главной программе (main()) объявлен как
int (*p)[10]
Следует отметить, что скобки вокруг *р обязательны. Такое объявление означает, что р указывает на массив из 10 целых. Если увеличить указатель р на 1, то он будет указывать на следующие 10 целых чисел. Таким образом, р это указатель на двухмерный массив с 10 числами в каждой строке. Поэтому р можно индексировать как обычный двухмерный массив. Разница только в том, что здесь память выделена с помощью malloc(), а для обыкновенного массива память выделяет компилятор.
Как упоминалось ранее, в C++ нужно преобразовывать типы указателей явно. Поэтому чтобы данная программа была правильной и в С, и в C++, необходимо выполнить явное приведение типа значения, возвращаемого функцией malloc(). Для этого строчку, в которой указателю р присваивается это значение, нужно переписать следующим образом:
p = (int (*)[10]) malloc(40*sizeof(int));
Многие программисты используют явное преобразование типов указателей для обеспечения совместимости с C++.
Динамическим называется массив, размер которого может меняться во время исполнения программы. Для изменения размера динамического массива язык программирования, поддерживающий такие массивы, должен предоставлять встроенную функцию или оператор. Динамические массивы дают возможность более гибкой работы с данными, так как позволяют не прогнозировать хранимые объёмы данных, а регулировать размер массива в соответствии с реально необходимыми объёмами. В отличие от динамических массивов существуют статические массивы и массивы переменной длины. Размер статического массива определяется на момент компиляции программы. Размер массива переменной длины определяется во время выполнения программы. Отличием динамического массива от массива переменной длины является автоматическое изменение размеров, что не трудно реализуется в случаях его отсутствия, поэтому часто не различают массивы переменной длины с динамическими массивами.
Одномерный динамический массив:
Создаем массив с 10-ю элементами типа int:
Си:
int *mas = malloc (sizeof(int) * 10);
С++:
int *mas = new int[10];
Получить доступ к значению каждого элемента можно по индексу (порядковый номер):
mas[0] = 2; // присвоили значение 2 нулевому элементу массива mas
mas[1] = 7; // присвоили значение 7 первому элементу массива mas
//... и т.д.
Следовательно, если брать такой подход, то вам понадобится около десяти строк кода, чтобы проинициализировать весь массив. Для того, чтобы этого избежать, напишем то же самое в цикле:
for(int i = 0; i < 10; i++){
cin>>mas[i]; // пользователь вводит значение каждого i-ого элемента массива
}
После чего работаем с массивом. Так же его можно вывести на экран:
for(int i = 0; i < 10; i++){
cout << mas[i] << endl;
}
Для освобождения из памяти одномерного динамического массива используем:
Си:
free(mas);
С++: оператор delete:
delete []mas;
Строго говоря, вышеописанная реализация массива не является динамической, т.к. нет изменения размера массива во время работы, а всего лишь массив переменной длины. Возможным решением является realloc, но можно применить только при использовании malloc, но не new. Для того чтобы изменить размер такого массива необходимо объявить ещё один массив нужного размера, скопировать в него все данные и освободить память, занимаемую старым массивом. В С++ библиотечным решением является std::vector. В С89 нет массивов переменной длины, они есть только в С99 (который поддерживают не все компиляторы). Некоторые (довольно старые) компиляторы С++ также не поддерживают
Пример динамического массива на Си
float *array1; // Одномерный массив
int **array2; // Двумерный массив
array1 = (float*) malloc(10 * sizeof(float)); // выделение 10 блоков по sizeof(float) байт каждый
array2 = (int**) malloc(16 * sizeof(int*)); // выделение 16 блоков по sizeof(int*) байт каждый. Сюда будут записаны указатели на одномерные массивы-строки
for(i = 0; i < 16; ++i)
array2[i] = (int*) malloc(8 * sizeof(int)); // выделение 8 блоков по sizeof(int) байт каждый. Это одномерные массивы - строки матрицы.
// Обращение к массиву
array1[i] = 5.0; // Записи эквивалентны. Первая с использованием индекса,
*(array1+i) = 5.0; // вторая с операцией разыменования.
array2[i][j] = 6; // Записи эквивалентны. Первая с использованием индекса,
*(*(array2+i)+j) = 6; // вторая с операцией разыменования.
free(array1); // Важно не забывать возвращать выделенную память системе.
for(i = 0; i < 16; ++i)
free(array2[i]); // Возвращаем память, используемую для строк матрицы.
free(array2); // Возвращаем память, используемую для столбцов матрицы.
Пример динамического массива на С++
float *array1; // Одномерный массив
int **array2; // Многомерный массив
array1 = new float[10]; // выделение 10 блоков размером типа float
array2 = new int*[16]; // выделение 16 блоков размером типа указателя на int
for(int i = 0; i < 16; ++i)
array2[i] = new int[8];
// Работаем с массивами.
delete []array1; // Важно не забывать возвращать выделенную память системе.
for(int i = 0; i < 16; ++i)
delete []array2[i]; // Возвращаем память, используемую для строк матрицы.
delete []array2; // Возвращаем память, используемую для столбцов матрицы.