- Главная
- Информатика
- L-10.YkazatelivyazikeSi
Содержание
- 2. В архитектуре ЭВМ фон Неймана (базовый вычислитель), одним из основных свойств является линейность и однородность оперативной
- 3. BSS-сегмент (block started by symbol) содержит неинициализированные глобальные переменные, или статические переменные без явной инициализации. Этот
- 4. Адрес переменной Указатели. Введение Стандарт 2 0x2c4b1 Память: Адрес: 0x2c4b2 0x2c4b3 0x2c4b4 0x2c4b5 0x2c4b6 0x2c4b7 Оперативная
- 5. При изучении языка Си у начинающих часто возникают вопросы связанные с указателями: Для чего нужен указатель?
- 6. #include int main() { int a, *b; a = 134; b = &a; // %x =
- 7. Пример: Записать по указанному адресу указанное значение (без использования переменных!!!): *((int*)0x8000)=1; /* Представили адрес как указатель
- 8. Имея возможность с помощью операции & определять адрес переменной или другого объекта программы, нужно уметь его
- 9. 0x2c4b1 Обобщим, всё вышесказанное: Указатели 10 … Память: Адрес: … x p 0x2c4b1 0x2c4b2 0x2c4b8 0x2c4b9
- 10. Операции над указателями В языке Си допустимы следующие (основные) операции над указателями: присваивание; получение значения того
- 11. Указатель может складываться с целым числом N. Результат сложения – адрес, смещенный на N компонент соответствующего
- 12. Приведем пример, в котором используются операции над указателями и выводятся (печатаются) получаемые значения. Обратите внимание, что
- 13. На рисунке приводится схема размещения в памяти массива float x[5] и указателей до начала выполнения цикла
- 14. Как рассматривалось выше, унарные операции '*' и '++' или '--' имеют одинаковый приоритет и при размещении
- 15. Отметим, что в Си можно работать с адресами, не уточняя, на переменные какого типа эти адреса
- 16. Через указатель на константу мы не можем изменять значение переменной/константы. Но мы можем присвоить указателю адрес
- 17. Можно создать указатель на указатель, тогда он будет хранить адрес указателя и сможет обращаться к его
- 18. Файл (file) — блок информации на внешнем запоминающем устройстве компьютера, имеющий определённое логическое представление (начиная от
- 19. Указатели и массивы очень тесно связаны в языке Си Имя массива – константный указатель на 0-й
- 20. Синонимичные выражения: Указатели и массивы Передача массива в функцию как параметра: int a[10]; f(a); /* или
- 21. int add(int x, int y) { return x+y; } int sub(int x, int y) { return
- 22. В функцию передается не значение, а адрес переменной: Указатели в параметрах функций Как изменить переменную в
- 23. char **argv // argv: указатель на указатель на char int (*x)[13] // x: указатель на массив
- 24. Массивы реализованы с помощью указателей. Указатели могут использоваться для итерации по массиву (рассмотрим на следующих занятиях)
- 25. Недостатки указателей оператор разыменования и взятия адреса если объявлен указатель, но не проинициализирован, то там хранится
- 26. Выводы Что надо знать об указателях: указатель – это переменная, в которой можно хранить адрес другой
- 28. Скачать презентацию
Слайд 2В архитектуре ЭВМ фон Неймана (базовый вычислитель), одним из основных свойств является
В архитектуре ЭВМ фон Неймана (базовый вычислитель), одним из основных свойств является
То, что в языках высокого уровня называют переменной , на уровне машинного кода представляет собой не более чем область памяти, то есть несколько ячеек памяти, расположенных подряд, или, иначе говоря, имеющих последовательные адреса.
Под адресом области памяти понимается наименьший из адресов ячеек, составляющих область.
Для нас здесь важно то, что любая переменная имеет свой адрес.
Во многих языках программирования, включая Паскаль и Си, адреса считаются информацией, которую можно хранить и обрабатывать; но если при работе на языке ассемблера адреса ничем не отличаются от обычных чисел, то языки высокого уровня вводят для адресов отдельные типы данных.
Как и в Паскале, в языке Си адресный тип привязан к типу переменной, адрес которой имеется в виду.
Отсюда существуют два базовых принципа, которые формулировали для указателей:
Указатель — это переменная, в которой хранится адрес.
Утверждение вида «A указывает на B» означает «A содержит адрес B».
В предыдущих лекциях, ПЗ и ЛР были введены базовые (основные) типы языка Си. Для их определения и описания используются служебные слова: char, short, int, long, signed, unsigned, float, double, enum, void.
В языке Си, кроме базовых типов, разрешено вводить и использовать производные типы, каждый из которых получен на основе более простых типов.
Указатели. Введение
Стандарт языка Си определяет три способа получения производных типов:
массив элементов заданного типа;
указатель на объект заданного типа;
функция, возвращающая значение заданного типа.
Каждая переменная в программе - это объект, имеющий имя и значение.
По имени можно обратиться к переменной и получить (а затем, например, напечатать) ее значение.
В операторе присваивания выполняется обратное действие - имени переменной из левой части оператора присваивания ставится в соответствие значение выражения его правой части.
С точки зрения машинной реализации, имя переменной соответствует адресу того участка памяти, который для нее выделен, а значение переменной - содержимому этого участка памяти.
2
Слайд 3BSS-сегмент (block started by symbol) содержит неинициализированные глобальные переменные, или статические переменные
BSS-сегмент (block started by symbol) содержит неинициализированные глобальные переменные, или статические переменные
Этот сегмент начинается непосредственно за data-сегментом.
Обычно загрузчик программ инициализирует bss область при загрузке приложения нулями.
Дело в том, что в data области переменные инициализированы – то есть затирают своими значениями выделенную область памяти.
Так как переменные в bss области не инициализированы явно, то они теоретически могли бы иметь значение, которое ранее хранилось в этой области, а это уязвимость, которая предоставляет доступ до приватных (возможно) данных.
Поэтому загрузчик вынужден обнулять все значения.
За счёт этого и неинициализированные глобальные переменные, и статические переменные по умолчанию равны нулю.
Указатели. Введение. Упрощенная структура исполняемого файла
Статические данные распределены в специальном статическом сегменте памяти программы
Глобальные данные объявленные вне функций.
Данные, объявленные внутри функций как static – статические локальные данные:
доступны только функции, в которой описаны, но существуют (занимают память) во время выполнения всей программы.
Локальные переменные, объявленные внутри функций
1 Мбайт
1 Мбайт
Стек – область памяти, в которой хранятся локальные переменные и адреса возврата
Слайд 4Адрес переменной
Указатели. Введение
Стандарт
2
0x2c4b1
Память:
Адрес:
0x2c4b2
0x2c4b3
0x2c4b4
0x2c4b5
0x2c4b6
0x2c4b7
Оперативная память организована как последовательность ячеек (байт)
Каждая ячейка имеет собственный
Адрес переменной
Указатели. Введение
Стандарт
2
0x2c4b1
Память:
Адрес:
0x2c4b2
0x2c4b3
0x2c4b4
0x2c4b5
0x2c4b6
0x2c4b7
Оперативная память организована как последовательность ячеек (байт)
Каждая ячейка имеет собственный
Адрес – целое число, чаще записываемое в шестнадцатеричной системе счисления
Память:
Адрес:
x
y
a
0x2c4b1
0x2c4b2
0x2c4b3
0x2c4b4
0x2c4b5
0x2c4b6
0x2c4b7
Каждая переменная размещается в последовательных ячейках (количество ячеек зависит от типа переменной)
Адрес переменной – адрес первой из этих ячеек
10
127
20031
Память:
Адрес:
x
y
a
0x2c4b1
0x2c4b2
0x2c4b3
0x2c4b4
0x2c4b5
0x2c4b6
0x2c4b7
Адрес переменной можно получить с помощью операции &
Например, &x даст адрес x:
printf(“x=%d, &x=%p”, x, &x);
Слайд 5При изучении языка Си у начинающих часто возникают вопросы связанные с указателями:
Для
При изучении языка Си у начинающих часто возникают вопросы связанные с указателями:
Для
Почему всегда пишут “указатель типа” и чем указатель типа uint16_t отличается от указателя типа uint8_t?
И кто вообще выдумал указатель?
Указатель — это переменная (адресного типа!), которая содержит адрес некоторого элемента данных (переменной, константы, функции, структуры).
Указатель, как и другие переменные, имеет тип данных и идентификатор.
Однако указатели используются таким образом, которой принципиально отличается от того, как мы используем «нормальные» переменные, и при объявлении мы должны добавить звездочку, чтобы сообщить компилятору, что данная переменная должна рассматриваться как указатель.
Синтаксис объявления указателей: <тип> *<имя>;
float *рa;
long long *ptr_b;
Для объявления переменной как указателя необходимо перед её именем поставить *, а для получения адреса переменной используется & (унарный оператор взятия адреса).
Идентификатор не обязательно должен содержать символы, которые помечают переменную как указатель (такие как “p”, или “ptr” (pointer)). Тем не менее, рекомендуется использовать это на практике. Это поможет вам сохранить ваши мысли более организованными, и если у вас все указатели будут помечены таким образом, другим инженерам-программистам будет легче понять ваш код.
Указатели объявляются точно так же, как и обычные переменные, только со звёздочкой * между типом данных и идентификатором (справа/посередине/слева???):
int *iPtr; // указатель на значение типа int
double *dPtr; // указатель на значение типа double
// ниже корректный синтаксис (допустимый, но не желательный):
int* iPtr3;
int * iPtr4; // корректный синтаксис (не делайте так)
// объявляем два указателя для переменных типа int:
int *iPtr5, *iPtr6;
Синтаксически язык Cи принимает объявление указателя, когда звёздочка находится рядом с типом данных, с идентификатором или даже посередине! Обратите внимание, эта звёздочка не является оператором разыменования. Это всего лишь часть синтаксиса объявления указателя.
Однако, при объявлении нескольких указателей, звёздочка должна находиться возле каждого идентификатора. Это легко забыть, если вы привыкли указывать звёздочку возле типа данных, а не возле имени переменной.
int* iPtr3, iPtr4; /* iPtr3 - это указатель на значение типа int, а iPtr4 - это обычная переменная типа int! */
1. Указатели в языке Си
Слайд 6#include
int main()
{ int a, *b;
a = 134;
b = &a;
#include
int main()
{ int a, *b;
a = 134;
b = &a;
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:
Итак, Указатель – это специальная переменная для хранения адреса памяти.
* – операция «взять содержимое» – позволяет получить значение объекта по его адресу — определяет значение переменной, которое содержится по адресу, содержащемуся в указателе;
& – операция «взять адрес» – позволяет определить адрес переменной;
Указатель, как и любая переменная, должен быть объявлен.
Тип указателя— это тип переменной, адрес которой он содержит.
Пример: сhar c; // переменная
char *p; // указатель
p = &c; // p = адрес c
NB: в C++ есть ссылки, а в Cи — нет
Ссылка — это тип переменной в языке C++, который работает как псевдоним другого объекта или значения.
Указатели
“p”, или “ptr” (pointer))
char *p;
Слайд 7Пример:
Записать по указанному адресу указанное значение
(без использования переменных!!!):
*((int*)0x8000)=1; /* Представили адрес
Пример:
Записать по указанному адресу указанное значение
(без использования переменных!!!):
*((int*)0x8000)=1; /* Представили адрес
Оператор адреса &
При выполнении инициализации переменной, ей автоматически присваивается свободный адрес памяти, и, любое значение, которое мы присваиваем переменной, сохраняется по этому адресу в памяти.
Например: int b = 8;
При выполнении этой инструкции ЦП (CPU), выделяется часть оперативной памяти.
В качестве примера предположим, что переменной b присваивается ячейка памяти под номером 150. Всякий раз, когда программа встречает переменную b в выражении или в инструкции, она понимает, что для того, чтобы получить значение — ей нужно заглянуть в ячейку памяти под номером 150.
Хорошо, что нам не нужно беспокоиться о том, какие конкретно адреса памяти выделены для определенных переменных.
Мы просто ссылаемся на переменную через присвоенный ей идентификатор, а компилятор конвертирует это имя в соответствующий адрес памяти.
Однако этот подход имеет некоторые ограничения, которые мы обсудим ниже.
Оператор взятия адреса & позволяет узнать, какой адрес памяти присвоен определенной переменной.
Указатели
#include Результат на экране компьютера: Всё довольно просто: Операция & применима только к объектам, имеющим имя и размещенным в памяти. Ее нельзя применять к выражениям, константам-литералам, битовым полям структур.
int main()
{
int a = 7;
printf("\n a = %d", a);
printf("\n &a = %X", &a);
getchar();
return 0;
}
а = 7
&a = 0046FCF0
char ch='G'; // 1 байт
int date=1937; // 2 байта – для старых CPU
float summa=2.015E-6; // 4 байта
В этом примере ( для старых CPU) переменная ch занимает 1 байт, date - 2 байта и summa - 4 байта. В современных 32-разр. ПК переменная типа int может занимать 4 байта, а переменная типа float - 8 байтов.
В соответствии с приведенной таблицей переменные размещены в памяти, начиная с байта, имеющего шестнадцатеричный адрес:
Слайд 8Имея возможность с помощью операции & определять адрес переменной или другого объекта
Имея возможность с помощью операции & определять адрес переменной или другого объекта
Именно для этих целей в языке Си введены переменные типа «указатель».
Кроме того, значением указателя может быть заведомо не равное никакому адресу значение, принимаемое за нулевой адрес.
Для его обозначения в ряде заголовочных файлов, например в файле stdio.h, определена специальная константа NULL.
Помимо адресов, указатель может принимать специальное значение NULL, обозначающее недействительный адрес
NULL – макроконстанта
NULL чаще всего (но не всегда!) равен 0
Разадресовывать указатель со значением NULL небезопасно!
Указатели
int *px; /*указатель*/
px = NULL; /*присвоить NULL*/
int value = 5;
int *ptr = &value; /* инициализируем ptr адресом значения переменной */
Размер указателей
Размер указателя зависит от архитектуры, на которой скомпилирован исполняемый файл:
32-битный исполняемый файл использует 32-битные адреса памяти
следовательно, указатель на 32-битном устройстве занимает 32 бита (4 байта)
с 64-битным исполняемым файлом указатель будет занимать 64 бита (8 байт)
и это вне зависимости от того, на что указывает указатель
Слайд 90x2c4b1
Обобщим, всё вышесказанное:
Указатели
10
…
Память:
Адрес:
…
x
p
0x2c4b1
0x2c4b2
0x2c4b8
0x2c4b9
0x2c4ba
0x2c4bb
Указатель – переменная, хранящая адрес
Операция разадресации * – обратная
0x2c4b1
Обобщим, всё вышесказанное:
Указатели
10
…
Память:
Адрес:
…
x
p
0x2c4b1
0x2c4b2
0x2c4b8
0x2c4b9
0x2c4ba
0x2c4bb
Указатель – переменная, хранящая адрес
Операция разадресации * – обратная
int x; /* целая переменная*/
int *px; /* указатель */
px = &x; /* присвоить адрес */
int x=10, y;
int *px;
px = &x; /* взять адрес */
y = *px; /* взять значение по
адресу px, y=10 */
*px = 20; /* <=> x=20 */
Слайд 10Операции над указателями
В языке Си допустимы следующие (основные) операции над указателями:
присваивание;
Операции над указателями
В языке Си допустимы следующие (основные) операции над указателями:
присваивание;
получение значения того объекта, на который ссылается указатель (синонимы: косвенная адресация, разыменование, раскрытие ссылки);
получение адреса самого указателя;
унарные операции изменения значения указателя;
аддитивные операции и операции сравнений.
Арифметические операции и указатели.
Унарные адресные операции '&' и '*' имеют более высокий приоритет, чем арифметические операции. Рассмотрим следующий пример, иллюстрирующий это правило:
float a=4.0, *u, z;
u=&z;
*u=5;
a=a + *u + 1; // a равно 10; u - не изменилось; z равно 5
При использовании адресной операции '*' в арифметических выражениях следует остерегаться случайного сочетания знаков операций деления '/' и разыменования '*', так как комбинацию '/*' компилятор воспринимает как начало комментария.
Например, выражение a/*u следует заменить таким: a/(*u)
Унарные операции '*' и '++' или '--' имеют одинаковый приоритет и при размещении рядом выполняются справа-налево.
2. Операции над указателями
Рассмотрим операции над указателями подробнее.
Указатель может быть инициализирован:
Указателю можно присваивать значение:
Указатель можно сравнивать: < > <= >= == !=
(т.е. вычислять отношения адресов)
Указатель может складываться с целым числом N. Результат сложения – адрес, смещенный на N компонент соответствующего типа относительно исходного:
int y, *px=NULL, *py=&y, *pz=py; // инициализация
int x=10, y=20, *px, *py;
px=&x; py=px;
int x=10, y=20, *px=&x, *py=&y;
if( px == py ) ...
short x=10, *px=&x; // инициализация
px=px+1;
Слайд 11Указатель может складываться с целым числом N.
Результат сложения – адрес, смещенный
Указатель может складываться с целым числом N. Результат сложения – адрес, смещенный
Операции над указателями
short x=10, *px=&x; // инициализация
px=px+1;
px=px-2;
Указатель может складываться с целым числом N.
Результат сложения – адрес, смещенный на N компонент соответствующего типа относительно исходного:
short x=10, *px=&x; // инициализация
px=px+1;
px=px-2;
px++;
short x=10, *px=&x; // инициализация px=px+1;
px=px-2;
px++; *(px+1)+=1;
Можно вычислять разность однотипных указателей, которая равна относительному смещению с учетом типа указателя:
short *px=0x2c4b1, *py=0x2c4b5, d;
d = py-px; // 2
Слайд 12Приведем пример, в котором используются операции над указателями и выводятся (печатаются) получаемые
Приведем пример, в котором используются операции над указателями и выводятся (печатаются) получаемые
Обратите внимание, что для вывода значений указателей (адресов) в форматной строке функции printf( ) используется спецификация преобразования %p.
Операции над указателями
#include При печати значений разностей указателей и адресов в функции printf( ) использована спецификация преобразования %d - вывод знакового десятичного целого. Адреса указателей: &u1=FFF4 &u2=FFF2
float x[ ] = { 10.0, 20.0, 30.0, 40.0, 50.0 };
void main( )
{
float *u1, *u2;
int i;
printf("\n Адреса указателей: &u1=%p &u2=%p", &u1, &u2 );
printf("\n Адреса элементов массива: \n");
for(i=0; i<5; i++)
{
if (i==3) printf("\n");
printf(" &x[%d] = %p", i, &x[i]);
}
printf("\n Значения элементов массива: \n");
for(i=0; i<5; i++)
{
if (i==3) printf("\n");
printf(" x[%d] = %5.1f ", i, x[i]);
}
for(u1=&x[0], u2=&x[4]; u2>=&x[0]; u1++, u2--)
{
printf("\n u1=%p *u1=%5.1f u2=%p *u2=%5.1f",u1,*u1,u2,*u2);
printf("\n u2-u1=%d", u2-u1);
}
}
Возможный результат выполнения программы (конкретные значения адресов могут быть другими):
Адреса элементов массива:
&x[0]=00A8 &x[1]=00AC &x[2]=00B0
&x[3]=00B4 &x[4]=00B8
Значения элементов массива:
x[0]=10.0 x[1]=20.0 x[2]=30.0
x[3]=40.0 x[4]=50.0
u1=00A8 *u1=10.0 u2=00B8 *u2=50.0
u2-u1=4
u1=00AC *u1=20.0 u2=00B4 *u2=40.0
u2-u1=2
u1=00B0 *u1=30.0 u2=00B0 *u2=30.0
u2-u1=0
u1=00B4 *u1=40.0 u2=00AC *u2=20.0
u2-u1=-2
u1=00B8 *u1=50.0 u2=00A8 *u2=10.0
u2-u1=-4
Слайд 13На рисунке приводится схема размещения в памяти массива
float x[5] и указателей
На рисунке приводится схема размещения в памяти массива float x[5] и указателей
Операции над указателями
Иногда требуется присвоить указателю одного типа значение указателя (адрес объекта) другого типа.
В этом случае используется «приведение типов», механизм которого понятен из следующего примера:
Подобно любым переменным, переменная типа указатель имеет имя, собственный адрес в памяти и значение.
Значение можно использовать, например печатать или присваивать другому указателю, как это сделано в рассмотренных примерах.
Адрес указателя может быть получен с помощью унарной операции &.
Выражение &имя_указателя определяет, где в памяти размещен указатель.
Содержимое этого участка памяти является значением указателя. Соотношение между именем, адресом и значением указателя иллюстрирует схема ниже:
char *z; // z - указатель на символ
int *k; // k - указатель на целое
z=(char *)k; // Преобразование указателей
Слайд 14Как рассматривалось выше, унарные операции '*' и '++'
или '--' имеют одинаковый
Как рассматривалось выше, унарные операции '*' и '++' или '--' имеют одинаковый
Добавление целочисленного значения n к указателю, адресующему некоторый элемент массива, приводит к тому, что указатель получает значение адреса того элемента, который отстоит от текущего на n позиций (элементов).
Если длина элемента массива равна d байтов, то численное значение указателя изменяется на (d*n).
Рассмотрим следующий фрагмент программы, иллюстрирующий перечисленные правила:
Операции над указателями
Слайд 15Отметим, что в Си можно работать с адресами, не уточняя, на переменные
Отметим, что в Си можно работать с адресами, не уточняя, на переменные
Соответствующий тип указателя называется void*; если описать указатель такого типа:
void *z;
то в переменную z можно будет занести совершенно любой адрес, и такое присваивание компилятор рассматривает как легитимное, не выдавая ни ошибок, ни предупреждений.
Более того, разрешено также и присваивание в другую сторону, то есть любому типизированному указателю можно присвоить нетипизированный адрес.
Интересно, что значение адреса можно использовать в качестве логического значения везде, где таковое требуется, в том числе в заголовках операторов ветвления и циклов; при этом «нулевой указатель» (то есть значение NULL) считается «ложью», а любой другой адрес — «истиной».
Типизированные указатели (int *, char *, double *, …) неявно задают длину фрагмента памяти (4,1,8, … байт), начинающегося с адреса, хранимого указателем
Длина важна при разадресации и адресной арифметике
Однако иногда приходится использовать указатели, не подразумевая длины адресуемого фрагмента памяти – void *
Разадресация указателя void * невозможна!
Указатель void * совместим по типу со всеми типизированными указателями
3. Нетипизированный указатель
int i=10, *pi=&i;
double d=3.14, *pd=&d;
void *p;
p=pi; /* Ok */
p=pd; /* Ok */
*p=*p+1; /* Ошибка! */
Указывая тип указателя, мы говорим компилятору, вот тебе адрес начала массива, один элемент массива занимает 2 байта, таких элементов в массиве 10. Итого сколько памяти выделить под этот массив?
20 байт — отвечает компилятор.
Для наглядности возьмите указатель типа void, для него не определено сколько места он занимает — это просто адрес.
Слайд 16Через указатель на константу мы не можем изменять значение переменной/константы.
Но мы
Через указатель на константу мы не можем изменять значение переменной/константы. Но мы
4. Указатели и const
И объединение обоих предыдущих случаев -
константный указатель на константу, который не позволяет менять ни хранимый в нем адрес, ни значение по этому адресу:
int a = 10;
/* указатель указывает на переменную a */
const int *pa = &a;
const int b = 45;
/* указатель указывает на константу b */
pa = &b;
int a = 10;
int *const pa = &a;
printf("value=%d \n", *pa); // 10
*pa = 22; // меняем значение
printf("value=%d \n", *pa); // 22
int b = 45;
// pa = &b; так нельзя сделать
int a = 10;
const int *const pa = &a;
//*pa = 22; так сделать нельзя
int b = 45;
// pa = &b; так сделать нельзя
Мы можем определять константные указатели.
Они не могут изменять адрес, который в них хранится, но могут изменять значение по этому адресу:
Кроме переменных в программе на Си для хранения данных могут использоваться константы, которые предваряются ключевым словом const, и указатели также могут указывать на константы.
Два способа описания константного указателя:
Неизменяемый указатель
Синтаксис: TYPE *const ptrName = &aTYPEVar;
Переменная-указатель – константа (не может изменяться)
Данные, адресуемые указателем – изменяемые
int a=42, b=42;
Int *const ptr=&a;
*ptr=1; /* Ok */
ptr=&b /* Ошибка! Это всё равно, что 5=х; */
Слайд 17Можно создать указатель на указатель, тогда он будет хранить адрес указателя и
Можно создать указатель на указатель, тогда он будет хранить адрес указателя и
<тип> **<имя>;
Ничто не мешает создать и указатель на указатель на указатель, и указатель на указатель на указатель на указатель и так далее.
Это нам понадобится в дальнейшем при работе с двумерными и многомерными массивами.
5. Указатель на указатель
/* Простой пример, как можно работать с указателем на указатель */
#include
#define SIZE 10
void main()
{ int A;
int B;
int *p;
int **pp;
A = 10;
B = 111;
p = &A;
pp = &p;
printf("A = %d\n", A);
*p = 20;
printf("A = %d\n", A);
*(*pp) = 30; //здесь скобки можно не писать
printf("A = %d\n", A);
*pp = &B;
printf("B = %d\n", В);
**pp = 333;
printf("B = %d", B);
getch();
}
Слайд 18Файл (file) — блок информации на внешнем запоминающем устройстве компьютера, имеющий определённое
Файл (file) — блок информации на внешнем запоминающем устройстве компьютера, имеющий определённое
Работа с файлами реализуется средствами операционных систем (ОС).
Файл характеризуется набором параметров: именем, размером, датой создания, датой последней модификации и атрибутами, которые используются операционной системой для его обработки: является ли файл системным, скрытым или предназначен только для чтения.
Файл (по ГОСТ 20886-85) — идентифицированная совокупность экземпляров полностью описанного в конкретной программе типа данных, находящихся вне программы во внешней памяти и доступных программе посредством специальных операций
Указатель файла — это то, что соединяет в единое целое всю систему ввода-вывода языка Си.
Указатель файла — это указатель на структуру типа FILE.
Он указывает на структуру, содержащую различные сведения о файле, например, его имя, статус и указатель текущей позиции в начало файла.
В сущности, указатель файла определяет конкретный файл и используется соответствующим потоком при выполнении функций ввода/вывода.
Чтобы выполнять в файлах операции чтения и записи, программы должны использовать указатели соответствующих файлов.
Чтобы объявить переменную-указатель файла, используйте такого рода оператор:
FILE *fp;
6. Указатель файла
Файлы в Си используются для того, чтобы сохранять результат работы программы Си и использовать его при новом запуске программы .
Например можно сохранять результаты вычислений , статистику игр.
Чтобы работать с файлами в Си необходимо подключить библиотеку stdio.h
#include
Чтобы работать с файлом в си необходимо задать указатель на файл по образцу:
FILE *имя указателя на файл;
Например
FILE *fin;
Задает указатель fin на файл
Дальше необходимо открыть файл и привязать его к файловому указателю.
Для открытия файла в Си на чтение используется команда
Имя указателя на файл= fopen("путь к файлу", "r");
Слайд 19Указатели и массивы очень тесно связаны в языке Си
Имя массива – константный
Указатели и массивы очень тесно связаны в языке Си
Имя массива – константный
7. Указатели и массивы
short a[100];
short *ps;
ps = &a[0];
short a[100];
short *ps;
ps = a;
a[i] == *(a+i) == *(i+a) == i[a]
a[2] == *(a+2) == *(2+a) == 2[a]
short a[100];
for (i=0; i<100; i++)
scanf(“%h”, &a[i]);
short a[100];
for (i=0; i<100; i++)
scanf(“%h”, a+i);
short a[100];
short *ps;
for (ps=a; ps==a+100; ps++)
scanf(“%h”, ps);
short a[100];
short *ps=a;;
for (i=0; i<100; i++)
scanf(“%h”, &ps[i]);
Слайд 20Синонимичные выражения:
Указатели и массивы
Передача массива в функцию как параметра:
int a[10];
f(a); /* или
Синонимичные выражения:
Указатели и массивы
Передача массива в функцию как параметра:
int a[10];
f(a); /* или
f(&a[0]);
void f(int *array)
{
...
}
/* или */
void f(int array[])
{
...
}
Указатели на многомерные массивы
Для вычисления адреса элемента двумерного массива компилятору нужно «знать» количество столбцов в матрице (т.е. мало знать начальный адрес массива)
Пусть нужно передать в функцию массив int array[3][15], чтобы ее вызов выглядел так: f(array)
Возможны следующие идентичные варианты описания функции f:
f(int x[3][15]) { … }
f(int x[][15]) { … }
f(int (*x)[15]) { … }
Важно: в последнем случае нельзя опустить скобки!
f(int *x[15]) { … } /* передается массив из 15 указателей на int, а не указатель на массив из 15 int-ов */
Слайд 21int add(int x, int y) { return x+y; }
int sub(int x, int
int add(int x, int y) { return x+y; }
int sub(int x, int
int mul(int x, int y) { return x*y; }
int div (int x, int y) { return x/y; }
int evaluate(unsigned int op, int x, int y)
{
int (*eval[])(int, int) = { add, sub, mul, div };
if (op>3)
{
printf(“Недопустимая операция”);
return 0;
}
return eval[op](x, y);
}
void main()
{
printf(“%d\n”, evaluate(3, 42, 3));
}
Операции
пронумерованы:
0 – add, 1 – sub,
2 – mul, 3 – div
Укaзатель на функцию содержит адрес тела функции
Описание указателя на функцию:
8. Указатели на функции
Пример (фрагмент):
float myfun(int a, float b)
{
return a+b;
}
...
float (*fptr)(int, float);
fptr = myfun;
...
x=fptr(42, 3.14f);
...
Указатель на функцию, воспринимающую параметры типов int и float и возвращающую float
Вызов функции по указателю
Массив из указателей на функции вида int f(int, int)
Вызов подходящей функции
Слайд 22В функцию передается не значение, а адрес переменной:
Указатели в параметрах функций
Как изменить
В функцию передается не значение, а адрес переменной:
Указатели в параметрах функций
Как изменить
#include Для доступа к значению переменной используется операция разадресации При описании параметров функции используются указатели При вызове функции как параметр передается адрес переменной x = &a; /*в х – адрес a */
y = &b; /*в y – адрес a */
t = *x; /* в t поместить значение,
хранящееся по адресу x */
*х = *y; / *по адресу x записать значение,
хранящееся по адресу y */
*y = t; / *по адресу y записать значение,
хранящееся в t */
void swap(int *x, int *y)
{
int t;
t = *x; *x = *y; *y = t;
}
void main()
{
int a=5, b=10;
swap(&a, &b);
printf(“a=%d, b=%d\n”, a, b);
}
Слайд 23char **argv // argv: указатель на указатель на char
int (*x)[13] // x:
char **argv // argv: указатель на указатель на char
int (*x)[13] // x:
int *x[13] // x: массив из 13 указателей на int
void *comp() // comp: функция, возвращающая указатель на void
void (*comp)() // comp: указатель на функцию, возвращающую void
char (*(*x())())[5] /* x: функция, возвращающая указатель на массив из 5 указателей на функцию, возвращающую char */
char (*(*x[3])())[5] // x: массив из 3 указателей на функцию, возвращающую указатель на массив из 5 char-ов
Примеры сложных описаний с указателями
Слайд 24Массивы реализованы с помощью указателей.
Указатели могут использоваться для итерации по массиву
Массивы реализованы с помощью указателей. Указатели могут использоваться для итерации по массиву
Они являются единственным способом динамического выделения памяти. Это, безусловно, самый распространенный вариант использования указателей.
Указатель занимает 2-8 байт, а объект может занимать несколько Кбайт/Мбайт (и содержать указатели на другие объекты). Объект может быть один, а указателей на него много.
Они могут использоваться для передачи большого количества данных в функцию без копирования этих данных.
Они могут использоваться для передачи одной функции в качестве параметра другой функции.
Вы можете обращаться к объекту по адресу, не зная его имени. В указатель мы можем «подставлять» адреса самых разных объектов. И одна и таже функция сможет обработать эти объекты…
Плюсы указателей
Упрощенная схема размещения исполняемого кода в ОП
Стек имеет ограниченный размер и, следовательно, может содержать только ограниченный объем информации.
В ОС Windows размер стека по умолчанию составляет 1МБ.
На некоторых Unix-системах этот размер может достигать и 8МБ. Если программа пытается поместить в стек слишком много информации, то это приведет к переполнению стека. Переполнение стека («stack overflow») происходит, когда запрашиваемой памяти нет в наличии (вся память уже занята).
Переполнение стека является результатом добавления слишком большого количества переменных в стек и/или создания слишком большого количества вложенных вызовов функций (например, когда функция A() вызывает функцию B(), которая вызывает функцию C(), а та, в свою очередь, вызывает функцию D() и т.д.).
Переполнение стека обычно приводит к сбою в программе
Слайд 25Недостатки указателей
оператор разыменования и взятия адреса
если объявлен указатель, но не проинициализирован, то
Недостатки указателей
оператор разыменования и взятия адреса
если объявлен указатель, но не проинициализирован, то
обращение к неинициализированному указателю – ошибка.
обращение к нулевому указателю – это ошибка.
например, выйти за границы массива
Слайд 26Выводы
Что надо знать об указателях:
указатель – это переменная, в которой можно хранить
Выводы
Что надо знать об указателях:
указатель – это переменная, в которой можно хранить
при объявлении указателя надо указать тип переменных, на которых он будет указывать, а перед именем поставить знак *;
знак & перед именем переменной обозначает ее адрес;
знак * перед указателем в рабочей части программы (не в объявлении) обозначает значение ячейки, на которую указывает указатель;
для обозначения недействительного указателя используется константа NULL (нулевой указатель);
при изменении значения указателя на n он в самом деле сдвигается к n-ому следующему числу данного типа, то есть для указателей на целые числа на n*sizeof(integer) байт;
указатели печатаются по формату %p.