Указатели и приведение типов 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Указатели и приведение типов



 

Так как указатель хранит адрес, можно кастовать его до другого типа. Это может понадобиться, например, если мы хотим взять часть переменной, или если мы знаем, что переменная хранит нужный нам тип.

 

#include <conio.h>

#include <stdio.h>

 

#define SIZE 10

 

void main() {

int A = 10;

int *intPtr;

char *charPtr;

 

intPtr = &A;

printf("%d\n", *intPtr);

printf("--------------------\n");

charPtr = (char*)intPtr;

printf("%d ", *charPtr);

charPtr++;

printf("%d ", *charPtr);

charPtr++;

printf("%d ", *charPtr);

charPtr++;

printf("%d ", *charPtr);

 

getch();

}

 

В этом примере мы пользуемся тем, что размер типа int равен 4 байта, а char 1 байт. За счёт этого, получив адрес первого байта, можно пройти по остальным байтам числа и вывести их содержимое.

NULL pointer - нулевой указатель

Указатель до инициализации хранит мусор, как и любая другая переменная. Но в то же время, этот "мусор" вполне может оказаться валидным адресом. Пусть, к примеру, у нас есть указатель. Каким образом узнать, инициализирован он или нет? В общем случае никак. Для решения этой проблемы был введён макрос NULL библиотеки stdlib.

Принято при определении указателя, если он не инициализируется конкретным значением, делать его равным NULL.

 

int *ptr = NULL;

 

По стандарту гарантировано, что в этом случае указатель равен NULL, и равен нулю, и может быть использован как булево значение false. Хотя в зависимости от реализации NULL может и не быть равным 0 (в смысле, не равен нулю в побитовом представлении, как например, int или float).

Это значит, что в данном случае

 

int *ptr = NULL;

if (ptr == 0) {

...

}

 

вполне корректная операция, а в случае

 

int a = 0;

if (a == NULL) {

...

}

 

поведение не определено. То есть указатель можно сравнивать с нулём, или с NULL, но нельзя NULL сравнивать с переменной целого типа или типа с плавающей точкой.

 

#include <stdlib.h>

#include <stdio.h>

#include <conio.h>

 

void main() {

int *a = NULL;

unsigned length, i;

 

printf("Enter length of array: ");

scanf("%d", &length);

 

if (length > 0) {

//При выделении памяти возвращается указатель.

//Если память не была выделена, то возвращается NULL

if ((a = (int*) malloc(length * sizeof(int)))!= NULL) {

for (i = 0; i < length; i++) {

a[i] = i * i;

}

} else {

printf("Error: can't allocate memory");

}

}

 

//Если переменая была инициализирована, то очищаем её

if (a!= NULL) {

free(a);

}

getch();

}

 

Пример работы с указателями

Так как размер типа char всегда равен 1 байт, то с его помощью можно реализовать операцию swap – обмена местами содержимого двух переменных.

 

#include <conio.h>

#include <conio.h>

#include <stdio.h>

 

void main() {

int length;

char *p1, *p2;

char tmp;

float a = 5.0f;

float b = 3.0f;

 

printf("a = %.3f\n", a);

printf("b = %.3f\n", b);

 

p1 = (char*) &a;

p2 = (char*) &b;

//Узнаём сколько байт перемещать

length = sizeof(float);

while (length--) {

//Обмениваем местами содержимое переменных побайтно

tmp = *p1;

*p1 = *p2;

*p2 = tmp;

//не забываем перемещаться вперёд

p1++;

p2++;

}

 

printf("a = %.3f\n", a);

printf("b = %.3f\n", b);

 

getch();

}

 

В этом примере можно поменять тип переменных a и b на double или любой другой (с соответствующим изменением вывода и вызова sizeof), всё равно мы будет обменивать местами байты двух переменных.

 


Тема №8

Указатели и массивы

Пусть есть массив

 

int A[5] = {1, 2, 3, 4, 5};

 

Мы уже показали, что указатели очень похожи на массивы. В частности, массив хранит адрес, откуда начинаются его элементы. Используя указатель можно также получить доступ до элементов массива

 

int *p = A;

 

тогда вызов A[3] эквивалентен вызову *(p + 3)

 

На самом деле оператор [ ] является синтаксическим сахаром – он выполняет точно такую же работу. То есть вызов A[3] также эквивалентен вызову *(A + 3)

 

#include <conio.h>

#include <stdio.h>

 

void main() {

int A[5] = {1, 2, 3, 4, 5};

int *p = A;

 

printf("%d\n", A[3]);

printf("%d\n", *(A + 3));

printf("%d\n", *(p + 3));

 

getch();

}

 

Но важно понимать – указатели - это не массивы!

 

Отличие массива от указателя

 

Массив - непосредственно указывает на первый элемент, указатель – переменная, которая хранит адрес первого элемента.

Тогда почему возможна следующая ситуация?

 

#include <conio.h>

#include <stdio.h>

 

void main() {

int A[5] = {1, 2, 3, 4, 5};

int *p = A;

 

printf("%d\n", *(A+1));

printf("%d\n", *(p+1));

getch();

}

 

Это правильный код, который будет работать. Дело в том, что компилятор подменяет массив на указатель. Данный пример работает, потому что мы действительно работаем с указателем (хотя помним, что массив отличается от указателя). То же самое происходит и при вызове функции. Если функция требует указатель, то можно передавать в качестве аргумента массив, так как он будет подменён указателем.

В си существует одна занимательная особенность. Если A[i] это всего лишь синтаксический сахар, и A[i] == *(A + i), то от смены слагаемых местами ничего не должно поменяться, т. е. A[i] == *(A + i) == *(i + A) == i[A]. Как бы странно это ни звучало, но это действительно так. Следующий код вполне валиден:

 

int a[] = {1, 2, 3, 4, 5};

printf("%d\n", a[3]);

printf("%d\n", 3[a]);

 



Поделиться:


Последнее изменение этой страницы: 2017-01-20; просмотров: 272; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 54.144.233.198 (0.018 с.)