Вызов функций с помощью массивов
В настоящем же разделе рассказывается о передаче массивов функциям в качестве аргументов. Этот вопрос рассматривается потому, что эта операция является исключением по отношению к обычной передаче параметров, выполняемой путем вызова по значению (Ведь при вызове по значению пришлось бы копировать весь массив!).
Когда в качестве аргумента функции используется массив, то функции передается его адрес. В этом и состоит исключение по отношению к правилу, которое гласит, что при передаче параметров используется вызов по значению. В случае передачи массива функции ее внутренний код работает с реальным содержимым этого массива и вполне может изменить это содержимое. Проанализируйте, например, функцию print_upper(), которая печатает свой строковый аргумент на верхнем регистре:
#include <stdio.h>#include <ctype.h> void print_upper(char *string); int main(void){ char s[80]; printf("Введите строку символов: "); gets(s); print_upper(s); printf("\ns теперь на верхнем регистре: %s", s); return 0;} /* Печатать строку на верхнем регистре. */void print_upper(char *string){ register int t; for(t=0; string[t]; ++t) { string[t] = toupper(string[t]); putchar(string[t]); }}Вот что будет выведено в случае фразы "This is a test." (это тест):
Введите строку символов: This is a test.THIS IS A TEST.s теперь в верхнем регистре: THIS IS A TEST. |
Правда, эта программа не работает с символами кириллицы.
После вызова print_upper() содержимое массива s в main() переводится в символы верхнего регистра. Если вам это не нужно, программу можно написать следующим образом:
#include <stdio.h>#include <ctype.h> void print_upper(char *string); int main(void){ char s[80]; printf("Введите строку символов: "); gets(s); print_upper(s); printf("\ns не изменялась: %s", s); return 0;} void print_upper(char *string){ register int t; for(t=0; string[t]; ++t) putchar(toupper(string[t]));} |
Вот какой на этот раз получится фраза "This is a test.":
Введите строку символов: This is a test.THIS IS A TEST.s не изменилась: This is a test. |
На этот раз содержимое массива не изменилось, потому что внутри print_upper() не изменялись его значения.
Классическим примером передачи массивов в функции является стандартная библиотечная функция gets(). Хотя gets(), которая находится в вашей стандартной библиотеке, и более сложная, чем предлагаемая вам версия xgets(), но с помощью функции xgets() вы сможете получить представление о том, как работает gets().
/* Упрощенная версия стандартной библиотечной функции gets(). */char *xgets(char *s){ char ch, *p; int t; p = s; /* xgets() возвращает указатель s */ for(t=0; t<80; ++t){ ch = getchar(); switch(ch) { case '\n': s[t] = '\0'; /* завершает строку */ return p; case '\b': if(t>0) t--; break; default: s[t] = ch; } } s[79] = '\0'; return p;} |
Функцию xgets() следует вызывать с указателем char *. Им, конечно же, может быть имя символьного массива, которое по определению является указателем char *. В самом начале программы xgets() выполняется цикл for от 0 до 80. Это не даст вводить с клавиатуры строки, содержащие более 80 символов. При попытке ввода большего количества символов происходит возврат из функции. (В настоящей функции gets() такого ограничения нет.) Так как в языке С нет встроенной проверки границ, программист должен сам позаботиться, чтобы в любом массиве, используемом при вызове xgets(), помещалось не менее 80 символов. Когда символы вводятся с клавиатуры, они сразу записываются в строку. Если пользователь нажимает клавишу <Backspase>, то счетчик t уменьшается на 1, а из массива удаляется последний символ, введенный перед нажатием этой клавиши. Когда пользователь нажмет <ENTER>, в конец строки запишется нуль, т.е. признак конца строки. Так как массив, использованный для вызова xgets(), модифицируется, то при возврате из функции в нем будут находиться введенные пользователем символы.
70. Ввод и вывод строк. Строковые функции и символьные массивы. Динамическое выделение памяти. Функции gets(), puts(), fgets(), fputs() и sprintf(). Функции strcpy(), strcat(), strncmp() и strlen().
Функция | Пояснение |
strlen (имя_строки) | определяет длину указанной строки, без учёта нуль-символа |
Копирование строк | |
strcpy (s1,s2) | выполняет побайтное копирование символов из строки s2 в строку s1 |
strncpy (s1,s2, n) | выполняет побайтное копирование n символов из строки s2 в строку s1. возвращает значения s1 |
Объединение строк | |
strcat(s1,s2) | объединяет строку s2 со строкой s1. Результат сохраняется в s1 |
strncat(s1,s2,n) | объединяет n символов строки s2 со строкой s1. Результат сохраняется в s1 |
Сравнение строк | |
strcmp(s1,s2) | сравнивает строку s1 со строкой s2 и возвращает результат типа int: 0 –если строки эквивалентны, >0 – если s1<s2, <0 — если s1>s2 С учётом регистра |
strncmp(s1,s2) | сравнивает n символов строки s1 со строкой s2 и возвращает результат типа int: 0 –если строки эквивалентны, >0 – если s1<s2, <0 — если s1>s2 С учётом регистра |
stricmp(s1,s2) | сравнивает строку s1 со строкой s2 и возвращает результат типа int: 0 –если строки эквивалентны, >0 – если s1<s2, <0 — если s1>s2 Без учёта регистра |
Функции преобразования | |
atof(s1) | преобразует строку s1 в тип double |
atoi(s1) | преобразует строку s1 в тип int |
atol(s1) | преобразует строку s1 в тип long int |
Символ – элементарная единица, некоторый набор которых несет определенный смысл. В языке программирования С++ предусмотрено использование символьных констант. Символьная константа – это целочисленное значение (типа int) представленное в виде символа, заключённого в одинарные кавычки, например 'a'. В таблице ASCII представлены символы и их целочисленные значения.
1 2 3 4 | // объявления символьной переменной char symbol = 'a'; // где symbol – имя переменной типа char // char – тип данных для хранения символов |
Строки в С++ представляются как массивы элементов типа char, заканчивающиеся нуль-терминатором \0 называются С строками или строками в стиле С.
\0 — символ нуль-терминатора.
Символьные строки состоят из набора символьных констант заключённых в двойные кавычки. При объявлении строкового массива необходимо учитывать наличие в конце строки нуль-терминатора, и отводить дополнительный байт под него.
последнее место отводится под нуль-терминатор. |
Строка при объявлении может быть инициализирована начальным значением, например, так:
1 | char string[10] = "abcdefghf"; |
|
Если подсчитать кол-во символов в двойных кавычках после символа равно их окажется 9, а размер строки 10 символов, последнее место отводится под нуль–терминатор, причём компилятор сам добавит его в конец строки.
При объявлении строки не обязательно указывать её размер, но при этом обязательно нужно её инициализировать начальным значением. Тогда размер строки определится автоматически и в конец строки добавится нуль-терминатор.
Для Строка может содержать символы, цифры и специальные знаки. В С++ строки заключаются в двойные кавычки. Имя строки является константным указателем на первый символ. Разработаем программу, с использованием строк.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // symbols.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include <iostream> using namespace std; int main(int argc, char* argv[]) { char string[] = "this is string - "; // объявление и инициализация строки cout << "Enter the string: "; char in_string[500]; // строковый массив для ввода gets(in_string); // функция gets() считывает все введённые символы с пробелами до тех пор, пока не будет нажата клавиша Enter cout << string << in_string << endl; // вывод строкового значения system("pause"); return 0; } |
В строке 12 с помощью функции gets() считаются все введённые символы с пробелами до тех пор, пока во вводимом потоке не встретится код клавиши enter. Если использовать операцию cin то из всего введённого считается последовательность символов до первого пробела.
71. Указатели. Определение переменных-указателей. Разыменование указателей. Объявление переменных-указателей. Простые операторы с указателями. Инициализация указателей. Неправильное использование операции определения адреса.
Указатель – переменная, значением которой является адрес ячейки памяти. То есть указатель ссылается на блок данных из области памяти, причём на самое его начало. Указатель может ссылаться на переменную или функцию. Для этого нужно знать адрес переменной или функции. Так вот, чтобы узнать адрес конкретной переменной в С++ существует унарная операция взятия адреса &. Такая операция извлекает адрес объявленных переменных, для того, чтобы его присвоить указателю.
//объявление указателя
/*тип данных*/ * /*имя указателя*/;
int *pn, n; *pn = 5; n = *pn; |
Основной операцией при работе с указателями является получение доступа к значению, адрес которого хранится в указателе.
Например:
pn = &n; |
Выражение *pn имеет такой же смысл, как имя целой переменной. Операция <<*>> называется разыменованием.
Действие, обратное к разыменованию, позволяет получить адрес переменной по ее имени. Например,
эта операция называется взятие адреса.
72. Указатели. Указатели на массивы. Указатели и многомерные массивы. Указатели на указатели. Указатели на строки.
Указатель — переменная, содержащая адрес объекта. Указатель не несет информации о содержимом объекта, а содержит сведения о том, где размещен объект.
Память компьютера можно представить в виде последовательности пронумерованных однобайтовых ячеек, с которыми можно работать по отдельности или блоками.
Каждая переменная в памяти имеет свой адрес - номер первой ячейки, где она расположена, а также свое значение. Указатель — это тоже переменная, которая размещается в памяти. Она тоже имеет адрес, а ее значение является адресом некоторой другой переменной. Переменная, объявленная как указатель, занимает 4 байта в оперативной памяти (в случае 32-битной версии компилятора).
Указатель, как и любая переменная, должен быть объявлен.
Общая форма объявления указателя
тип *ИмяОбъекта;
Тип указателя— это тип переменной, адрес которой он содержит.
Для работы с указателями в Си определены две операции:
операция * (звездочка) — позволяет получить значение объекта по его адресу - определяет значение переменной, которое содержится по адресу, содержащемуся в указателе;
операция & (амперсанд) — позволяет определить адрес переменной.
Например,
сhar c; // переменная char *p; // указатель p = &c; // p = адрес c |
Для указанного примера обращение к одним и тем же значениям переменной и адреса представлено в таблице
Переменная | Указатель | |
Адрес | &c | p |
Значение | c | *p |
Пример на Си
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a, *b;
system("chcp 1251");
system("cls");
a = 134;
b = &a;
// %x = вывод числа в шестнадцатеричной форме
printf("\n Значение переменной a равно %d = %x шестн.", a,a);
printf("\n Адрес переменной a равен %x шестн.", &a);
printf("\n Данные по адресу указателя b равны %d = %x шестн.", *b,*b);
printf("\n Значение указателя b равно %x шестн.", b);
printf("\n Адрес расположения указателя b равен %x шестн.", &b);
getchar();
return 0;
}
Результат выполнения программы:
Расположение в памяти переменной a и указателя b:
Необходимо помнить, что компиляторы высокого уровня поддерживают прямой способ адресации: младший байт хранится в ячейке, имеющей младший адрес.
Указатель на массив:
Код:
int* myM; myM = new int[10]; |
Возможно объявление переменной, которая содержит адрес другой переменной, которая, в свою очередь, также является указателем. Такая переменная может быть необходима, если в функции нужно изменить адрес какого-либо объекта. Однако наличие более двух звёздочек в объявлении переменной говорит, скорее всего, о плохом проектировании.
int **ppi; | // Объявляем указатель на указатель на целое |
void f(int **ppi) | |
{ int *pi = *ppi; | // Указателю на целое присваивается значение, хранящееся по адресу, |
... | // содержащемуся в указателе на указатель на целое |
} |
Строка – это последовательность (массив) символов (типа char), которая заканчивается специальным символом – признаком конца строки. Это символ записывается как '\0' (не путайте с символом переноса строки '\n') и равен 0. При вводе строки символ конца строки добавляется автоматически. Все функции работы со строками – и стандартные, и создаваемые программистом – должны ориентироваться на этот символ. Если требуется сформировать новую строку, то обязательно надо добавлять признак конца строки. Если этого не сделать, то при дальнейшей работе возникнут ошибки.
Строковым литералом называется последовательность символов, заключённых в двойные кавычки. В строковом литерале на один символ больше, чем используется при его записи – добавляется символ '\0'.
Заголовки стандартных функций работы со строками хранятся в файле <string.h>. Основными из этих функций являются:
| – int strlen(const char *str); |
| – int strcmp(const char *str1, const char *str2); |
| – char *strcpy(char *str1, const char *str2); |
| – char *strcat(char *str1, const char *str2); |
| – char *strchr(const char *str, char c); |
| – char *strstr(const char *str1, const char *str2); |
Ввод/вывод строки:
| – функция scanf с форматом %s; |
| – char *gets (char *buffer); |
| – char *fgets(char *string, int n, FILE *stream); |
| – функция printf с форматом %s; |
| – int puts(const char *string); |
| – int fputs(const char *string, FILE *stream); |
Пример 1. Функция, которая меняет все вхождения буквы «я» на «а», «а» – на «б», «б» – на «в» и т.д. Остальные символы оставаются без изменения
|
73. Указатели. Арифметические операции с указателями. Арифметические операции с указателями и массивы. Операции с указателями.
Указатель — переменная, содержащая адрес объекта. Указатель не несет информации о содержимом объекта, а содержит сведения о том, где размещен объект.
Память компьютера можно представить в виде последовательности пронумерованных однобайтовых ячеек, с которыми можно работать по отдельности или блоками.
Каждая переменная в памяти имеет свой адрес - номер первой ячейки, где она расположена, а также свое значение. Указатель — это тоже переменная, которая размещается в памяти. Она тоже имеет адрес, а ее значение является адресом некоторой другой переменной. Переменная, объявленная как указатель, занимает 4 байта в оперативной памяти (в случае 32-битной версии компилятора).
Указатель, как и любая переменная, должен быть объявлен.
Общая форма объявления указателя
тип *ИмяОбъекта;
Тип указателя— это тип переменной, адрес которой он содержит.
Для работы с указателями в Си определены две операции:
операция * (звездочка) — позволяет получить значение объекта по его адресу - определяет значение переменной, которое содержится по адресу, содержащемуся в указателе;
операция & (амперсанд) — позволяет определить адрес переменной.
Например,
сhar c; // переменная char *p; // указатель p = &c; // p = адрес c |
Для указанного примера обращение к одним и тем же значениям переменной и адреса представлено в таблице
Переменная | Указатель | |
Адрес | &c | p |
Значение | c | *p |
Пример на Си
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a, *b;
system("chcp 1251");
system("cls");
a = 134;
b = &a;
// %x = вывод числа в шестнадцатеричной форме
printf("\n Значение переменной a равно %d = %x шестн.", a,a);
printf("\n Адрес переменной a равен %x шестн.", &a);
printf("\n Данные по адресу указателя b равны %d = %x шестн.", *b,*b);
printf("\n Значение указателя b равно %x шестн.", b);
printf("\n Адрес расположения указателя b равен %x шестн.", &b);
getchar();
return 0;
}
Результат выполнения программы:
Расположение в памяти переменной a и указателя b:
Необходимо помнить, что компиляторы высокого уровня поддерживают прямой способ адресации: младший байт хранится в ячейке, имеющей младший адрес.
/*10ARYSUB.C Программа на С, использующая обычные индексы массива*/ #include "stdafx.h" #include "E:\LECTURE\AlgorithmProgramming 02\Universal_HederFile.h" #define ISIZE 10 void StopWait(void); main() { char string10[ISIZE]; int i; fputs("Enter 11 symbols\n",stdout); for(i=0; i < ISIZE; i++) string10[i]=getchar(); for(i=ISIZE-1;i >+ 0; i--) putchar(string10[i]); fputs("\n",stdout); StopWait(); /* Wait a little */ return (0); } |
В следующих двух программах с помощью индекса выполняется обработка массива из десяти символов. Обе программы считывают десять символов и затем печатают их в обратном порядке. В первой программе используется более традиционный для языков высокого уровня подход: обращение к элементам массива при помощи индексов. Вторая программа работает аналогично за исключением того, что обращение к элементам массива выполняется по адресу с использованием арифметических операций с указателями. Вот первая программа:
/*10ARYPTR.C Программа на С, в которой для доступа к элементам массива используются арифметические операции с указателями.*/ #include "stdafx.h" #include "E:\LECTURE\AlgorithmProgramming 02\Universal_HederFile.h" #define ISIZE 10 void StopWait(void); main() { char string10[ISIZE], *pc; int icount; pc=&string10[0]; fputs("Enter 11 symbols\n",stdout); for(icount=0; icount < ISIZE; icount++) { *pc=getchar(); pc++; } fputs("\n",stdout); pc=&string10[ISIZE-1]; for(icount=0; icount < ISIZE; icount++) { putchar(*pc); pc--; } fputs("\n",stdout); StopWait(); /* Wait a little */ return (0); } |
Вот второй пример:
Над адресами в C++ определены следующие арифметические операции:
· сложение и вычитание указателей с константой;
· вычитание одного указателя из другого;
· инкремент;
· декремент.
Инкремент перемещает указатель к следующему элементу массива, а декремент – к предыдущему;
К указателям могут применяться только две арифметические операции: сложение и вычитание. Для понимания арифметических действий с указателями предположим, что p1 - это указатель на целое, содержащий значение 2000, и будем считать, что целые имеют длину 2 байта. После выражения
p1 ++;
содержимое p1 станет 2002, а не 2001! Каждый раз при увеличении p1 указатель будет указывать на следующее целое. Это справедливо и для уменьшения. Например:
р1 --;
приведет к тому, что p1 получит значение 1998, если считать, что раньше было 2000.
Каждый раз, когда указатель увеличивается, он указывает на следующий элемент базового типа. Каждый раз, когда уменьшается - на предыдущий элемент. В случае указателей на символы это приводит к «обычной» арифметике. Все остальные указатели увеличиваются или уменьшаются на длину базового типа. Рис. демонстрирует данную концепцию.
Естественно, все не ограничивается только уменьшением или увеличением. Можно добавлять или вычитать из указателей целые числа. Выражение
p1 = p1 + 9;
приводит к тому, что указатель p1 указывает на девятый элемент по сравнению с элементом, на который он указывал до присваивания.
Помимо добавления или вычитания указателей и целых чисел, единственную операцию, которую можно выполнять с указателями, - это вычитание одного указателя из другого.
В большинстве случаев вычитание одного указателя из другого имеет смысл только тогда, когда оба указателя указывают на один объект, - как правило, массив. В результате вычитания получается число элементов базового типа, находящихся между указателями. Помимо этих операций не существует других арифметических операций, применимых к указателям. Нельзя умножать или делить указатели, нельзя складывать указатели, нельзя применять битовый сдвиг или маски к указателям, нельзя добавлять или вычитать типы float или double.
К указателям так же применимы операции отношения ==, !=, <,>,<=,>=. Иными словами, указатели можно сравнивать. Например, если i указывает на пятый элемент массива, а j — на первый, то отношение i>j истинно. Кроме того, любой указатель можно сравнивать на равенство с нулем. Однако, все эти утверждения верны, если речь идет об указателях, ссылающихся на один массив. В противном случае результат арифметических операций и операций отношения будет не определен.
74. Указатели. Применение к указателям оператора sizeof. Сложности при использовании операций ++ и --. Сравнение указателей. Переносимость указателей. Использование функции sizeof() с указателями в среде DOS.
Указатель — переменная, содержащая адрес объекта. Указатель не несет информации о содержимом объекта, а содержит сведения о том, где размещен объект.
Память компьютера можно представить в виде последовательности пронумерованных однобайтовых ячеек, с которыми можно работать по отдельности или блоками.
Каждая переменная в памяти имеет свой адрес - номер первой ячейки, где она расположена, а также свое значение. Указатель — это тоже переменная, которая размещается в памяти. Она тоже имеет адрес, а ее значение является адресом некоторой другой переменной. Переменная, объявленная как указатель, занимает 4 байта в оперативной памяти (в случае 32-битной версии компилятора).
Указатель, как и любая переменная, должен быть объявлен.
Общая форма объявления указателя
тип *ИмяОбъекта;
Тип указателя— это тип переменной, адрес которой он содержит.
Для работы с указателями в Си определены две операции:
операция * (звездочка) — позволяет получить значение объекта по его адресу - определяет значение переменной, которое содержится по адресу, содержащемуся в указателе;
операция & (амперсанд) — позволяет определить адрес переменной.
Например,
сhar c; // переменная char *p; // указатель p = &c; // p = адрес c |
Оператор sizeof возвращает размер в байтах объекта или типа данных. Синтаксис его таков:
sizeof ( type name );
sizeof ( object );
sizeof object;
Результат имеет специальный тип size_t, который определен как typedef в заголовочном файле cstddef.
Применение sizeof к указателю дает размер самого указателя, а не объекта, на который он указывает:
int *pi = new int[ 3 ];
size_t pointer_size = sizeof ( pi );
Здесь значением pointer_size будет память под указатель в байтах (4 в 32-битных системах), а не массива ia.
Вот пример программы, использующей оператор sizeof:
#include string
#include iostream
#include cstddef
int main() {
size_t ia;
ia = sizeof( ia ); // правильно
ia = sizeof ia; // правильно
// ia = sizeof int; // ошибка
ia = sizeof( int ); // правильно
int *pi = new int[ 12 ];
cout "pi: " sizeof( pi )
" *pi: " sizeof( pi )
endl;
// sizeof строки не зависит от ее реальной длины
string stl( "foobar" );
string st2( "a mighty oak" );
string *ps = stl;
cout " st1: " sizeof( st1 )
" st2: " sizeof( st2 )
" ps: sizeof( ps )
" *ps: " sizeof( *ps )
endl;
cout "short : " sizeof(short) endl;
cout "shorf" : " sizeof(short*) endl;
cout "short : " sizeof(short) endl;
cout "short[3] : " sizeof(short[3]) endl;
}
Результатом работы программы будет:
pi: 4 *pi: 4
st1: 12 st2: 12 ps: 4 *ps:12
short : 2
short* : 4
short : 2
short[3] : 6
Из данного примера видно, что применение sizeof к указателю позволяет узнать размер памяти, необходимой для хранения адреса.
Над адресами в C++ определены следующие арифметические операции:
· сложение и вычитание указателей с константой;
· вычитание одного указателя из другого;
· инкремент;
· декремент.
Инкремент перемещает указатель к следующему элементу массива, а декремент – к предыдущему;
К указателям могут применяться только две арифметические операции: сложение и вычитание. Для понимания арифметических действий с указателями предположим, что p1 - это указатель на целое, содержащий значение 2000, и будем считать, что целые имеют длину 2 байта. После выражения
p1 ++;
содержимое p1 станет 2002, а не 2001! Каждый раз при увеличении p1 указатель будет указывать на следующее целое. Это справедливо и для уменьшения. Например:
р1 --;
приведет к тому, что p1 получит значение 1998, если считать, что раньше было 2000.
Каждый раз, когда указатель увеличивается, он указывает на следующий элемент базового типа. Каждый раз, когда уменьшается - на предыдущий элемент. В случае указателей на символы это приводит к «обычной» арифметике. Все остальные указатели увеличиваются или уменьшаются на длину базового типа. Рис. демонстрирует данную концепцию.
Естественно, все не ограничивается только уменьшением или увеличением. Можно добавлять или вычитать из указателей целые числа. Выражение
p1 = p1 + 9;
приводит к тому, что указатель p1 указывает на девятый элемент по сравнению с элементом, на который он указывал до присваивания.
Помимо добавления или вычитания указателей и целых чисел, единственную операцию, которую можно выполнять с указателями, - это вычитание одного указателя из другого.
В большинстве случаев вычитание одного указателя из другого имеет смысл только тогда, когда оба указателя указывают на один объект, - как правило, массив. В результате вычитания получается число элементов базового типа, находящихся между указателями. Помимо этих операций не существует других арифметических операций, применимых к указателям. Нельзя умножать или делить указатели, нельзя складывать указатели, нельзя применять битовый сдвиг или маски к указателям, нельзя добавлять или вычитать типы float или double.
К указателям так же применимы операции отношения ==, !=, <,>,<=,>=. Иными словами, указатели можно сравнивать. Например, если i указывает на пятый элемент массива, а j — на первый, то отношение i>j истинно. Кроме того, любой указатель можно сравнивать на равенство с нулем. Однако, все эти утверждения верны, если речь идет об указателях, ссылающихся на один массив. В противном случае результат арифметических операций и операций отношения будет не определен.
Переносимость указателей.
В приведенных примерах адреса представлялись целыми числами. Вы можете предположить, что в С указатели имеют тип int. Это не так. Указатель содержит адрес переменной некоторого типа, однако сам указатель не относится ни какому простому типу данных вроде int, float и им подобному. В конкретной системе С указатель может копироваться в переменную типа int, а переменная int может копироваться в указатель; однако, язык С не гарантирует, что указатели могут храниться в переменных типа int. Для обеспечения переносимости программного кода таких операций следует избегать.
Кроме того, разрешены не все арифметические операции с указателями. Например, запрещается складывать или перемножать два указателя, или делить один указатель на другой.
В этой программе на C++ печатается размер указателей по умолчанию, и их размер с моделями памяти __far и near. Также используется директива препроцессора (#), определяющая PRINT_SIZEOF с аргументом A_POINTER, поэтому печатается не только размер указателя, но и его название.
//10STRIZE.CPP //sizeof с указателями #include <stdio.h> #define PRINT_SIZEOF(A_POINTER) \ printf("sizeof\t("#A_POINTER")\t= %d\n", \ sizeof(A_POINTER)) main() { char *reg_pc; long double *reg_pldbl; char __far *far_pc; long double __far *far_pldbl; char __near *near_pc; long double __near *near_pldbl; PRINT_SIZEOF(reg_pc); PRINT_SIZEOF(reg_pldbl); PRINT_SIZEOF(far_pc); PRINT_SIZEOF(far_pldbl); PRINT_SIZEOF(near_pc); PRINT_SIZEOF(near_pldbl); return (0); } |
Результат выполнения программы:
Sizeof | (reg_pc) | = 2 |
Sizeof | (reg_pldbl) | = 2 |
Sizeof | (far_pc) | = 4 |
Sizeof | (far_pldbl) | = 4 |
Sizeof | (near_pc) | = 2 |
Sizeof | (near_pldbl) | = 2 |
75. Указатели. Указатели на функции. Динамическая память. Использование указателей типа void.
Указатель — переменная, содержащая адрес объекта. Указатель не несет информации о содержимом объекта, а содержит сведения о том, где размещен объект.
Память компьютера можно представить в виде последовательности пронумерованных однобайтовых ячеек, с которыми можно работать по отдельности или блоками.
Каждая переменная в памяти имеет свой адрес - номер первой ячейки, где она расположена, а также свое значение. Указатель — это тоже переменная, которая размещается в памяти. Она тоже имеет адрес, а ее значение является адресом некоторой другой переменной. Переменная, объявленная как указатель, занимает 4 байта в оперативной памяти (в случае 32-битной версии компилятора).
Указатель, как и любая переменная, должен быть объявлен.
Общая форма объявления указателя
тип *ИмяОбъекта;
Тип указателя— это тип переменной, адрес которой он содержит.
Для работы с указателями в Си определены две операции:
операция * (звездочка) — позволяет получить значение объекта по его адресу - определяет значение переменной, которое содержится по адресу, содержащемуся в указателе;
операция & (амперсанд) — позволяет определить адрес переменной.
Например,
сhar c; // переменная char *p; // указатель p = &c; // p = адрес c |
Указатель на функцию можно использовать в нескольких важных случаях. Рассмотрим, например, функцию qsort(). Одним из ее параметров является указатель на функцию. Адресуемая функция осуществляет необходимое сравнение, которое должно выполняться для элементов сортируемого массива. Применение в qsort() указателя на функцию вызвано тем, что процесс сравнения двух элементов может быть весьма сложным, и при помощи одного управляющего флага его представить нельзя. Функцию нельзя передать по значению — то есть, передать ее код. Однако, в С можно передать указатель на код или указатель на функцию.
Следующая ниже программа на С показывает, как описать указатель на функцию и как передать пользовательскую функцию в функцию qsort(), объявленную в файле stdlib.h.
Вот программа на С:
/*10FNCPTR.C Программа на С, иллюстрирующая объявление пользовательской функции*/ #include "stdafx.h" #include "E:\LECTURE\AlgorithmProgramming 02\Universal_HederFile.h" #define IMAXVALUES 10 int icompare_funct(const void *iresult_a, const void *iresult_b); int (*ifunct_ptr)(const void *,const void *); void StopWait(void); main() { int i; int iarray[IMAXVALUES]={0,5,3,2,8,7,9,1,4,6}; ifunct_ptr=icompare_funct; qsort(iarray,IMAXVALUES, sizeof(int), ifunct_ptr); for(i=0; i < IMAXVALUES; i++) printf("%d ",iarray[i]); printf("\n"); StopWait(); /* Wait a little */ return (0); } int icompare_funct(const void *iresult_a, const void *iresult_b) { return((*(int *)iresult_a)-(*(int *) iresult_b)); } |
Динамическая память.
При компиляции программы на С память компьютера разбивается на четыре области, содержащие код программы, все глобальные данные, стек и динамически распределяемую область памяти (иногда ее называют heap-куча). Динамическая память (heap) — это просто свободная область памяти, с которой работают при помощи функций динамического выделения памяти malloc() и free().
При вызове функции malloc() выделяется непрерывный блок памяти для указанного объекта и возвращается указатель на начало этого блока. Функция free() возвращает выделенный блок обратно в динамическую область памяти для повторного использования.
Аргумент, передаваемый функции malloc(), представляет собой объем необходимой памяти в байтах. В следующем фрагменте кода выделяется память для 300 чисел типа float:
float *pf; int inum_floats = 300; pf = (float *) malloc(inum_floats * sizeof(float)); |
Когда блок становится ненужным, его можно вернуть операционной системе, используя, следующий оператор:
free((void *) pf); |
Программы на С и C++ могут в любой момент выделять и освобождать динамическую память. Важно помнить, что на переменные, память для которых выделена в динамической области, не распространяются правила области действия, которые справедливы для других переменных. Эти переменные действуют всегда; поэтому, если вы выделили динамическую область памяти, то не должны забывать о ее освобождении. Если повторно выделить динамическую память, предварительно не освободив ее, то выполнение программы, скорее всего закончится крахом.
Для распределения динамической памяти в большинстве компиляторов С используются описанные выше библиотечные функции malloc() и free(), однако, в C++ эти средства считаются настолько важными, что они включены в ядро языка. В C++ для выделения и освобождения динамической памяти используются операторы new и delete.
Использование указателей типа void.
В Си существует особый тип указателей – указатели типа void или пустые указатели. Эти указатели используются в том случае, когда тип переменной не известен. Так как void не имеет типа, то к нему не применима операция разадресации (взятие содержимого) и адресная арифметика, так как неизвестно представление данных. Тем не менее, если мы работаем с указателем типа void, то нам доступны операции сравнения.
Если необходимо работать с пустым указателем, то сначала нужно явно привести тип. Например
|
|
Переменная не может иметь типа void, этот тип определён только для указателей. Пустые указатели нашли широкое применение при вызове функций. Можно написать функцию общего назначения, которая будет работать с любым типом. Например, напишем функцию swap, которая обменивает местами содержимое двух переменных. У этой функции будет три аргумента – указатели на переменные, которые необходимо обменять местами и их размер.
Наша функция может выглядеть и по-другому. Обойдёмся без дорогостоящего выделения памяти и будем копировать побайтно.
|
Пустые указатели позволяют создавать функции, которые возвращают и принимают одинаковые параметры, но имеют разное название. Это пригодится в дальнейшем, при изучении указателей на функции. Например
|
Объявление указателя на неопределенный тип:
void *ptr;
Такому указателю может быть присвоен указатель на любой тип, но не наоборот
void *ptr; // Указатель на void int i, *ptri; // Целая переменная, указатель на int ptr= &i; // Допустимо ptr= ptri; // Допустимо ptri= (int)ptr; // Допустимо // ptri=ptr; // Недопустимо |
Для последней операции необходимо явное приведение типа.
Над указателем неопределенного типа нельзя выполнять операцию разыменования без явного приведения типа.
76. Указатели. Указатели и массивы. Функции, массивы и указатели. Использование указателей при работе с массивами. Строки (массивы типа char).
Указатель — переменная, содержащая адрес объекта. Указатель не несет информации о содержимом объекта, а содержит сведения о том, где размещен объект.
Память компьютера можно представить в виде последовательности пронумерованных однобайтовых ячеек, с которыми можно работать по отдельности или блоками.
Каждая переменная в памяти имеет свой адрес - номер первой ячейки, где она расположена, а также свое значение. Указатель — это тоже переменная, которая размещается в памяти. Она тоже имеет адрес, а ее значение является адресом некоторой другой переменной. Переменная, объявленная как указатель, занимает 4 байта в оперативной памяти (в случае 32-битной версии компилятора).
Указатель, как и любая переменная, должен быть объявлен.
Общая форма объявления указателя
тип *ИмяОбъекта;
Тип указателя— это тип переменной, адрес которой он содержит.
Для работы с указателями в Си определены две операции:
операция * (звездочка) — позволяет получить значение объекта по его адресу - определяет значение переменной, которое содержится по адресу, содержащемуся в указателе;
операция & (амперсанд) — позволяет определить адрес переменной.
Например,
сhar c; // переменная char *p; // указатель p = &c; // p = адрес c |