Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Указатели в С.doc
Скачиваний:
2
Добавлен:
01.07.2025
Размер:
214 Кб
Скачать
☆

Указатели и константы

В операциях с указателями участвуют два объекта: сам указатель и объект, на который он ссылается. Помещение ключевого слова const перед объявлением указателя делает константным объект, а не указатель. Для объявления самого указателя в качестве константы, используется оператор объявления * const, а не просто *.

Примеры:

const int *p1; // указатель на константу типа int

int const *p2; // указатель на константу типа int

int *const p3; // константный указатель на объект типа int

const int *const p4; // константный указатель на константу типа int

int const *const p4; // константный указатель на константу типа int

Первый и второй варианты записи являются синонимами и обозначают, что константой является объект, на который указывает указатель. То есть нельзя изменять значение, хранящееся в указываемом объекте.

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

Четвёртый и пятый вариант являются синонимами и указывают, что константен как указатель, так и указываемый объект.

Указатели на строки

Поскольку текстовая строка имеет тип const char [] и является массивом, к ней применимы все ранее приведённые соображения о массивах и указателях.

Строковый литерал можно присвоить переменной типа char *. Это сделано для совместимости с ранними версиями языка C, в которых не было ключевого слова const.

Однако изменение строкового литерала через такой указатель является ошибкой:

void f()

{

char *p=”text”;

p[3] = ‘a’; // ОШИБКА

}

Память под строковые литералы выделяется статически, поэтому их свободно можно возвращать в качестве значения функции:

const char *access(int i)

{

…

return “access denied”;

}

Будут ли одинаковые литералы записываться в одно место памяти или нет – зависит от реализации.

Строки оканчиваются нуль-байтом (‘\0’), что делает их удобными для использования указателей. Рассмотрим копирование строк:

const char src[]=”Строка, которую надо скопировать”;

char dst[sizeof src];

const char *p_src = src;

char *p_dst = dst;

while (*p_dst++ = *p_src++) ;

Создаются два указателя – константный указатель p_src, который хранит адрес копируемого элемента и указатель p_dst, хранящий адрес, куда будет скопирован элемент. Копирование будет продолжаться до тех пор, пока не будет скопирован нуль-байт, завершающий строку.

Примечание.

Цикл

while (*p_dst++ = *p_src++) ;

может быть записан в виде:

while (*p_src) // while (*p_src!=”\0”)

{

*p_dst = *p_src;

p_dst++ ;

p_src++;

}

*p_dst =”\0”;

Указатель на void

В Cи существует особый тип указателей – указатели типа void или пустые указатели. Эти указатели используются в том случае, когда тип переменной не известен. Так как void не имеет типа, то к нему не применима операция разадресации (взятие содержимого) и адресная арифметика, так как неизвестно представление данных. Тем не менее, если мы работаем с указателем типа void, то нам доступны операции сравнения. Указатель на объект любого типа можно присвоить переменной типа void*. void* можно присваивать, сравнивать и явно преобразовать в указатель любого другого типа.

Если необходимо работать с пустым указателем, то сначала нужно явно привести тип. Например:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

#include <conio.h>

#include <stdio.h>

 

int main()

{

    void *p = NULL;

    int intVar     = 10;

    char charVar   = 'A';

    float floatVar = 24.3;

    float *floatPtr = NULL;

     p = &intVar;

    *((int*) p) = 20;

    printf("intVar = %d\n", intVar);

 

    p = &charVar;

    printf("charVar = %c\n", *((char*) p));

 

    p = &floatVar;

    floatPtr = (float*) p;

    printf("floatVar = %.3f", *floatPtr);

 

    getch();

}

Переменная не может иметь типа void, этот тип определён только для указателей. Пустые указатели нашли широкое применение при вызове функций. Можно написать функцию общего назначения, которая будет работать с любым типом. Например, напишем функцию swap, которая обменивает местами содержимое двух переменных. У этой функции будет три аргумента – указатели на переменные, которые необходимо обменять местами и их размер.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

#include <conio.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

void swap(void *a, void *b, size_t size) {

    char* tmp;

    //создаём временную переменную для обмена

    tmp = (char*) malloc(size);

    memcpy(tmp, a, size);

    memcpy(a, b, size);

    memcpy(b, tmp, size);

    free(tmp);

}

 

int main() {

    float a = 10.f;

    float b = 20.f;

    double c = 555;

    double d = 777;

    unsigned long e = 2ll;

    unsigned long f = 3ll;

 

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

    swap(&a, &b, sizeof(float));

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

 

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

    swap(&c, &d, sizeof(double));

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

 

    printf("e = %ld, f = %ld \n", e, f);

    swap(&e, &f, sizeof(unsigned long));

    printf("e = %ld, f = %ld \n", e, f);

 

    getch();

}

Наша функция может выглядеть и по-другому. Обойдёмся без дорогостоящего выделения памяти и будем копировать побайтно.

?

6

7

8

9

10

11

12

13

14

void swap(void *a, void *b, size_t size) {

    char tmp;

    size_t i;

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

        tmp = *((char*) b + i);

        *((char*) b + i) = *((char*) a + i);

        *((char*) a + i) = tmp;

    }

}

Пустые указатели позволяют создавать функции, которые возвращают и принимают одинаковые параметры, но имеют разное название. Это пригодится в дальнейшем, при изучении указателей на функции. Например

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

int cmpInt(void* a, void* b) {

    return *((int*) a) - *((int*) b);

}

 

int cmpString(void *a, void* b) {

    return strcmp((char*) a, (char*) b);

}

 

int cmpFloat(void* a, void* b) {

    float fdiff = *((float*) a) - *((float*) b);

    if (fabs(fdiff) < 0.000001f) {

        return 0;

    }

    if (fdiff < 0) {

        return -1;

    } else {

        return 1;

    }

}

Массивы, как параметры функции

При передаче массива как аргумента функции происходит неявное преобразование имени массива в указатель на его начальный элемент с потерей информации о размере массива. Таким образом, массив всегда передаётся по указателю – его копия не создаётся .