Главная · Поиск книг · Поступления книг · Top 40 · Форумы · Ссылки · Читатели

Настройка текста
Перенос строк


    Прохождения игр    
Demon's Souls |#15| Dragon God
Demon's Souls |#14| Flamelurker
Demon's Souls |#13| Storm King
Demon's Souls |#12| Old Monk & Old Hero

Другие игры...


liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня
Rambler's Top100
Образование - Богатырев А. Весь текст 120.46 Kb

Руководство полного идиота по программированию на C

Предыдущая страница Следующая страница
1 2 3 4 5 6 7 8  9 10 11
     |   есть  |
     |    12   |
     |_________|

Таким образом, УКАЗАТЕЛЬ - это "стрелка, указывающая на некий ящик-переменную".
Начало этой стрелки можно (в свою очередь) хранить в какой-нибудь переменной.

При этом, если стрелка указывает на переменную типа int,
то тип переменной, хранящей начало стрелки, есть int *

Если типа char, то тип - char *

АДРЕС (указатель на) можно взять только от переменной или элемента массива,
но не от выражения.

        int x;
        int arr[5];

        Законно     &x          стрелка на ящик "x"
                    & arr[3]    стрелка на ящик "arr[3]"

        Незаконно   &(2+2)      тут нет именованного "ящика",
                                на который указывает стрелка,
                                да и вообще ящика нет.

     ИСПОЛЬЗОВАНИЕ УКАЗАТЕЛЕЙ

Указатели несколько различно ведут себя СЛЕВА и СПРАВА
от оператора присваивания.
Нас интересует новая операция, применяемая только к указателям:

        *pointer

---------------------------------------------------------------------------

СПРАВА от присваиваний и в формулах
===================================
*pointer        означает
                "взять значение переменной (лежащее в ящике),
                на которую указывает указатель,
                хранящийся в переменной pointer".

В нашем примере - это число 12.

То есть *pointer означает "пройти по стрелке и взять указываемое ею ЗНАЧЕНИЕ".

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

Печатает 12;

        z = *pointer;        /* равноценно z = 12;        */
        z = *pointer + 66;   /* равноценно z = 12 + 66;   */

Заставим теперь указатель указывать на другую переменную
(иначе говоря, "присвоим указателю адрес другой переменной")

        pointer = &var2;

        ________
       /pointer/
     _/_______/_
     |         |
     |  &var2  |
     |         |
     |_______|_|
             |
             |&var2
             |
             V
        ________
       / var2  /
     _/_______/_
     |         |
     |   43    |
     |         |
     |_________|

После этого   z = *pointer;
означает      z = 43;

---------------------------------------------------------------------------

Таким образом, конструкция

        z = *pointer;

            означает

        z = *(&var2);

            означает

        z = var2;

То есть * и & взаимно СТИРАЮТСЯ.

     СЛЕВА от присваивания...

        *pointer = 123;

Означает        "положить значение правой части (т.е. 123)
                 в переменную (ящик), на который указывает указатель,
                 хранящийся в переменной pointer".

Пройти по стрелке и положить значение в указываемую переменную.

В данном случае *pointer обозначает
не ЗНАЧЕНИЕ указываемой переменной,
 а САМУ     указываемую переменную.

        ________
       /pointer/
     _/_______/_
     |         |
     |  &var2  |
     |         |
     |_______|_|
             |
             |Положить туда 123
             |
             V
        ________
       / var2  /
     _/_______/_
     |         |
     |   123   |
     |         |
     |_________|

        pointer  = &var2;
        *pointer = 123;

                означает

        *(&var2) = 123;

                означает

        var2 = 123;

То есть снова * и & взаимно СТИРАЮТ друг друга.

---------------------------------------------------------------------------

ЕщЛ пример:

        *pointer = *pointer + 66;

                или

        *pointer += 66;

---------------------------------------------------------------------------

Вернемся к примеру с функцией (@). Как он работает?

В строке /* #1 */
        Мы вызываем функцию f(), передавая в нее
        УКАЗАТЕЛЬ на переменную y ("адрес переменной y").

В строке /* #2 */
        Отводится локальная переменная с именем ptr,
        которая в качестве начального значения
        получает значение первого аргумента функции в точке вызова -
        то есть УКАЗАТЕЛЬ на y.

В строке /* #3 */
        Мы видим
                *ptr = 7;

        что следует рассматривать как

                *(&y) = 7;          точнее *(&main::y)=7;

        то есть как

                y = 7;              точнее main::y=7;

        Что и хотелось.

При этом отметим, что само имя "y" этой переменной
внутри функции f() НЕВИДИМО и НЕИЗВЕСТНО!

---------------------------------------------------------------------------

ПРИМЕР: обмен значений двух переменных.

void main(){
        int x, y;
        int temporary;  /* вспомогательная переменная */

        x=1; y=2;

        temporary=x; x=y; y=temporary;
        printf("x=%d y=%d\n", x, y);    /* Печатает x=2 y=1 */
}
---------------------------------------------------------------------------

Теперь то же с использованием адресов и указателей:

void swap(int *a, int *b){
        int tmp;

        tmp = *a; *a = *b; *b = tmp;
}

void main(){
        int x, y;

        x = 1; y = 2;
        swap(&x, &y);
        printf("x=%d y=%d\n", x, y);
}
---------------------------------------------------------------------------

ЕщЛ пример:

        int x;
        int *ptr1, *ptr2;

        ptr1 = &x; ptr2 = &x;
        *ptr1 = 77;
        printf("%d\n", *ptr2);          /* Печатает 77 */

То есть на одну переменную МОГУТ указывать несколько указателей.
---------------------------------------------------------------------------

ЕщЛ пример:
        int x;
        int *ptr1;              /* Не инициализирована */

        x = *ptr1;

В ptr1 нет указателя ни на что, там есть мусор.
Указатель указывает "в никуда" (пальцем в небо).
Скорее всего произойдЛт сбой в работе программы.

Мораль: ВСЕГДА инициализируй переменные, указатели в том числе.

     МАССИВЫ

Язык Си работает с именами массивов специальным образом.
Имя массива "a" для

        int a[5];

является на самом деле указателем на его нулевой элемент.

То есть у нас есть переменные (ящики)
с именами

        a[0]    a[1]    ...     a[4].

При этом само имя a при его использовании в программе означает &a[0]

        a
        |
        |
        |
        V
       a[0]    a[1]    a[2]    a[3]    a[4]
     _________________________________________
     |       |       |       |       |       |
     |       |       |       |       |       |
     |       |       |       |       |       |
     -----------------------------------------

Поэтому

        int a[5];

        /* ПередаЛтся не КОПИЯ самого массива, а копия УКАЗАТЕЛЯ на его начало */

        void f(int *a){         /* или f(int a[]), что есть равноценная запись */
                printf("%d\n", a[1]);
                a[2] = 7;
        }

        main (){
                a[1] = 777;
                f(a);           /* аргумент - массив */
                printf("%d\n", a[2]);
        }

Вызов f(a); сделает именно ожидаемые вещи.
В этом примере мы видим два правила:

ПРАВИЛО_1:
        При передаче в функцию имени массива
        в аргумент функции копируется не весь массив (жирновато будет),
        а указатель на его 0-ой элемент.

ПРАВИЛО_2:
        Указатель на начало массива
        МОЖНО индексировать как сам массив.
        Это вторая операция, помимо *pointer,
        применимая к указателям: pointer[n].

Второе правило влечет за собой ряд следствий.

        int a[5];       /* массив */
        int *ptr;       /* указательная переменная */

        ptr = a;        /* законно, означает ptr = &a[0]; */

Теперь

        ptr[0] = 3;     /* означает a[0] = 3;   */
        ptr[1] = 5;     /* означает a[1] = 5;   */

Более того. Возьмем теперь

        ptr = &a[2];

       a[0]    a[1]    a[2]    a[3]    a[4]
     _________________________________________
     |       |       |       |       |       |
 a:  |       |       |       |       |       |
     |       |       |       |       |       |
     ----------------------------------------------
                     |       |       |       | ...
 ptr:                |       |       |       |
                     -----------------------------
        -2      -1     ptr[0]  ptr[1]  ptr[2]

Мы как бы "приложили" к массиву a[] массив ptr[].

В котором

        ptr[0]  есть    a[2]
        ptr[1]  есть    a[3]
        ptr[2]  есть    a[4]
        ptr[3]          находится за концом массива a[], МУСОР

Более того, допустимы отрицательные индексы!

        ptr[-1] есть    a[1]
        ptr[-2] есть    a[0]
        ptr[-3]         находится перед началом массива a[], МУСОР

Итак: индексировать можно И массивы И указатели.

Кстати, для имени массива a[]
        *a означает то же самое, что и a[0].

Это обратное следствие из схожести массивов и указателей.
     19.c

/* Задача: написать функцию инвертирования порядка символов
   в массиве char.
        A B C D ---> D C B A
   В решении можно использовать рекурсию.
 */

/* Мы приведем рекурсивное и нерекурсивное решения (два варианта) */
#include

/* Сначала - несколько служебных функций. */

/* ФУНКЦИЯ ПОДСЧЕТА ДЛИНЫ СТРОКИ.
   Как уже объяснялось, строка текста - это массив char,
   в конце которого помещен символ '\0'.
   Сам символ \0 не считается.
 */
int strlen(char s[]){                   /* функция от массива букв            */
        int counter = 0;                /* счетчик и одновременно индекс      */

        while(s[counter] != '\0')       /* пока не встретился признак конца текста */
                counter++;              /* посчитать символ                   */
        return counter;                 /* сколько символов, отличных от '\0' */
}

/* ФУНКЦИЯ ПЕЧАТИ СТРОКИ.
   Печатаем каждый элемент массива как символ при помощи putchar(c).
   Как только встречаем элемент массива, равный '\0' - останавливаемся.
   Заметьте, что при наличии завершающего символа нам НЕ НАДО
   передавать в функцию размер массива, он нам неинтересен.

   В конце эта функция переводит строку.
*/
int putstr(char s[]){
        int i = 0;      /* индекс */

        while(s[i] != '\0'){
                putchar(s[i]);
                i++;
        }
        putchar('\n');
        return i;
}

/* ТЕПЕРЬ МЫ ЗАНИМАЕМСЯ ФУНКЦИЕЙ ИНВЕРТИРОВАНИЯ.
   Для этого нам нужна вспомогательная функция:
   сдвиг элементов массива на 1 влево.

        Исходный массив: A B C D E F
                         <----------
        Результат:       B C D E F F
                                   -
        Последний элемент удваивается.

        n - размер массива.
        Функция работает так:

        Исходный массив: A B C D E F  n=6
        После i=1        B B C D E F
        После i=2        B C C D E F
        После i=3        B C D D E F
        После i=4        B C D E E F
        После i=5        B C D E F F
        i=6 ==> остановка.

*/
void shiftLeft(char s[], int n){
        int i;

        for(i=1; i < n; i++)
                s[i-1] = s[i];
}

/* Функция инвертирования.
   Идея такова:
        - если длина массива меньше или равна 1, то инвертировать нечего,
          ибо массив состоит из 1 или 0 элементов.
        - если длина массива > 1, то
          a) Спасти нулевой элемент массива.
                A B C D E F
                |
                |
                V
               tmp

          b) Сдвинуть массив влево
                B C D E F F

          c) В последний элемент массива поместить спасенный нулевой элемент.
                         tmp
                          |
                          V
                B C D E F A

          d) Инвертировать начало массива длиной n-1.
               {B C D E F}A

             Получится:
                F E D C B A

             Что и требовалось.

        s[] - массив,
        n   - его длина.
*/
void reverse(char s[], int n){
Предыдущая страница Следующая страница
1 2 3 4 5 6 7 8  9 10 11
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 
Комментарии (6)

Реклама