- •1. Роль указателей в языке с.
- •3. Определение указателя.
- •4. Объявление указателей.
- •5. Операции получения адреса и значения.
- •6. Правила использования основных операций.
- •7. Указатель на переменную и константу.
- •8. Инициализация указателей.
- •1. Присваивание указателю адреса существующего объекта:
- •9. Работа с указателями.
- •Int *pI;
- •Int I, *pI;
- •Int *pI;
- •Int I, *pI;
- •10. Задание, инициализация, вывод переменных и их указателей.
- •11. Ограничения на использование основных
- •12. Указатель константа.
- •13. Константы и ссылки/указатели
- •5 4 2 1 3 // Порядок интерпретации описания
- •16. Многоуровневая адресация
- •17. Операции над указателями.
- •18. Пустой указатель (null-указатель).
- •19. Ввод адреса с клавиатуры.
- •20. Разность значений указателей.
- •14. Присваивание указателей различного типа.
- •22. Явное преобразование типа указателя
- •23. Роль операции sizeof в управлении памятью
- •25. Работа с областью памяти переменного формата
12. Указатель константа.
Использование констант
Константы — это совсем просто. Константа — это переменная, которую необходимо обязательно инициализировать и которая после этого не меняет своего значения.
Константы есть и в Си, но их никто не использует, ибо они были кривые. Числовые константы в C делают с помощью #define, и это неплохо работает, хотя и имеет свои минусы. Константы всех остальных типов в Cи используют редко.
В Си++ константы и всё, с ними связанное, получило религиозный статус с приходом классов. Но этого мы сейчас касаться не будем.
…А главное отличие — их теперь можно использовать! Например, можно написать так:
const int N = 10;
int A [N];
Возможно, вы не поверите, но в Си этого сделать было нельзя. Хотя значение N известно во время компиляции, но в Си компилятор «закрывал на это глаза». (Безусловно, у них были свои причины на это, для интересующихся — причины зовут «extern», но всё равно получилось не очень хорошо.)
А раз константы можно использовать, отчего же этого не делать? Они многим лучше, чем #define:
они имеют тип, а значит, позволяют «на халяву» найти парочку ошибок в вашей программе;
они могут быть не просто числом или строкой, но и какой-нибудь сложной структурой;
их имена можно использовать при отладке (хотя с современными средствами это почти не актуально);
они не имеют побочных эффектов и других классических проблем с макросами (отсылаю вас к предостережениям о директиве #define, написанных в любой хорошей книге по Си или Си++).
Константы обязательно инициализировать, например:
const int foo = 10; /* можно */
const int bar; /* нельзя */
Это логично, поскольку если мы не инициализируем её сразу, то вообще никогда не сможем ничего ей присвоить, поэтому в таком случае она окажется бесполезной.
13. Константы и ссылки/указатели
Константы очень интересно сочетаются с указателями и ссылками. По крайней мере, это интересно выглядит. Посмотрите сами:
const int *foo
или
int const *foo
Указатель на const int. Значение указателя изменить можно (так, чтобы он указывал на что-нибудь другое), а вот значение переменной, на которую он указывает, менять нельзя.
int *const foo = &x
Константный (неизменный) указатель на int. Значение указателя менять нельзя (прям как будто это ссылка, а не указатель). Значение того, на что он указывает, менять можно. Заметьте, что константный указатель обязательно инициализировать, как и любую другую константу.
const int *const foo = &x
Смесь двух предыдущих пунктов. Ничего нельзя изменить: ни значение указателя, ни значение того, на что он указывает. Опять же, инициализация обязательна.
У ссылок разнообразия значительно меньше, ибо «указательная» часть ссылки и так всегда константна. Значит, бывает только:
const int &foo = x
Ссылка на int, который мы (с помощью этой ссылки) не сможем изменить.
Для константных ссылок можно:
const int& i = 1; // равносильно
int __tmp = 1;
const int& i = __tmp;
И, наконец, пусть наши заявления про неизменность ссылок обретут некий формальный вид. Следующие строки очень похожи по смыслу:
int &foo = x;
int *const bar = &x;
Фактически, после этого foo и bar отличаются только синтаксически (везде нужно писать *bar, но просто foo), и если мы везде заменим foo на *bar или наоборот, ничего не изменится.
Операции взятия адреса объекта и разыменования указателя - взаимно обратны.
TYPE objx;
TYPE *ptrx = &objx; /* инициализируем адресом objx */
*(&objx) = objx;
&(*ptrx) = ptrx;
Вот пример того, как можно заменить условный оператор условным выражением (это удастся не всегда):
if(c) a = 1;
else b = 1;
Предупреждение: такой стиль не способствует понятности программы и даже компактности ее кода.
#include <stdio.h>
int main(int ac, char *av[]){
int a, b, c;
a = b = c = 0;
if(av[1]) c = atoi(av[1]);
*(c ? &a : &b) = 1; /* !!! */
printf("cond=%d a=%d b=%d\n", c, a, b);
return 0;
}
Каким образом инициализируются по умолчанию внешние и статические массивы? Инициализируются ли по умолчанию автоматические массивы? Каким образом можно присваивать значения элементам массива, относящегося к любому классу памяти?
Указатель может быть константой, то есть его значение не может быть изменено во время работы программы.
// Указатель константа
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
int main()
{
int i=5,k;
const int *pi=&i;
k=*pi;
printf("i= %i pi= %u k= %i\n",i,pi,k);
system("pause");
return 0;
}
//i=5 pi=26867883 k=5
// Задание инициация разадресация вывод переменных
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
int main()
{
int i1,i2;
int *pi1,*pi2;
pi1=&i1;
i1=5;
i2=*pi1;
pi2=&i2;
printf("Содержимое i1=%i Адрес i1=%u Содержимое i2=%i Адрес i2=%u\n",i1,pi1,i2,pi2);
char c1,c2;
char *pc1,*pc2;
pc1=&c1;
c1='s';
pc2=&c2;
c2=*pc1;
printf("Содержимое c1=%c Адрес c1=%u Содержимое c2=%c Адрес c2=%u\n",c1,pc1,c2,pc2);
float f1,f2;
float *pf1,*pf2;
pf1=&f1;
f1=5.65;
pf2=&f2;
f2=*pf1;
printf("Содержимое f1=%f Адрес c1=%u Содержимое f2=%f Адрес f2=%u\n",f1,pf1,f2,pf2);
system("pause");
return 0;
}
//i1= 5 pi1=2686788 i2= 5 pi2=2686784
//c1= s pc1=2686775 c2=s pc2=2686774
//f1=5.65 pf1=2686760 f2=5.65 pf2=2686756
// Применение указателей в программе
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
#define pi 3.1415
int main()
{
int a,b,c,*pa=&a,*pb=&b,*pc=&c;
float d,*pd=&d;
printf("Ввести числа а и b\n");
scanf("%i%i",&a,&b);
c=*pa+*pb;
d=cos(pi*(*pa)/180);
printf(" %i + %i = %i \n",*pa,*pb,*pc);
printf("cos( %i )= %f \n",*pa,*pd);
system("pause");
return 0;
}
//18 5
// 21
// 0.95
15. Порядок выполнения операций над указателями.
С помощью комбинаций звездочек, круглых и квадратных скобок можно описывать составные типы и указатели на составные типы, например, в операторе
int *(*р[10])();
объявляется массив из 10 указателей на функции без параметров, возвращающих указатели на int.
По умолчанию квадратные и круглые скобки имеют одинаковый при-оритет, больший, чем звездочка, и рассматриваются слева направо. Для изменения порядка рассмотрения используются круглые скобки.
При интерпретации сложных описаний необходимо придерживаться правила «изнутри наружу»:
1) если справа от имени имеются квадратные скобки, это массив, если скобки круглые — это функция;
2) если слева есть звездочка,это указатель на проинтерпретированную ранее конструкцию;
3) если справа встречается закрывающая круглая скобка, необходимо применить приведенные выше правила внутри скобок, а затем переходить наружу;
4) в последнюю очередь интерпретируется спецификатор типа.
Для приведенного выше описания порядок интерпретации указан цифрами:
int *(*р[10])():