Отправляет email-рассылки с помощью сервиса Sendsay

C и C++ для начинающих

  Все выпуски  

C и C++ для начинающих - Выпуск 8. Классы памяти и массивы.


Служба Рассылок Subscribe.Ru проекта Citycat.Ru
Доброго времени суток! Сегодня речь пойдет о классах переменных и о массивах. Но для начала несколько органицационных замечаний.
  1. По-видимому, рассылка набирает обороты, так как я уже начал получать письма. Преимущественно пишут, что С не знают вообще или знают очень слабо и что хотели бы научиться. Значит, правильно я сделал, что начал рассказывать с нуля. Что ж, дерзайте! Берите все выпуски, начиная с первого, читайте, вникайте и (еще раз повторюсь) выполняйте домашние задания! И все у вас получится.
  2. Также несколько человек пишут нечто вроде: "Хочу подписаться на рассылку", "Подпишите меня, пожалуйста". Я никого подписывать сам не собиаюсь. Если хотите подписаться - подписывайтесь! Сделать это можно на subscribe.ru или прямо отсюда:
    • Вводите e-mail:
    • Выбираете формат рассылки:
    • Нажимаете сюда:
    • Да, еще большая просьба не писать мне письма без содержания. За сегодняшний день я получил уже 4 таких.
    • Еще по письмам: убедительная просьба всем поставить антивирус (я рекомендую AVP) и скачать с Интернета последнее обновление. Мне сегодня вместе с вашими письмами пришло 8 (прописью: восемь) копий вируса I-Worm.Hybris (и это за один день!) Не дело это.
    • По совету одного из подписчиков в рассылке открывается раздел "Вопрос - ответ". В ней на вопросы подписчиков будут даваться ответы. Кто их будет давать? Все. И я, и остальные подписчики. Так что, "задавайте любые вопросы, мы вам дадим на них любые ответы..." :-)

    На том организационную часть можно закончить. Теперь начнем собственно тему.

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

    В С есть 4 класса переменных.
    • Автоматические переменные (auto) - любые переменные, объявленные внутри блока { } или внутри функции, считаются по умолчанию автоматическими. Такие переменные создаются при входе в блок и уничтожаются при выходе из него. Как следствие, при входе в блок дважды без промежуточного выхода (например, при рекурсии), переменная будет создана в двух экземплярах.
    • Статические переменные (static) существуют в единственном экземпляре на программу. Они создаются при запуске программы и уничтожаются при выходе из нее. Таким образом, если в функции объявлена статическая переменная, то при входе в функцию переменная уже будет иметь некоторое значение - то, которое она имела при выходе из нее в прошлый раз. Если же функция вызвана более одного раза одновременно (например, при параллельном выполнении в Windows), то модификация переменной в одной части вызовет ее изменение в остальных. Все глобальные переменные (то есть объявленные вне какой-либо функции) считаются статическими.
    • Внешние переменные (extern) - ими могут быть только глобальные переменные. Такая переменная предполагается существующей в каком-то ином месте (стандартной библиотеке или другом файле). Внешние переменные - это один из способов взаимодействия отдельных частей программы, рассредоточенных по разным исходным файлам (позже мы коснемся этой темы).
    • Регистровые переменные (register) - этот класс представляет собой не указание, а рекомендацию компилятору разместить переменную в регистре. Доступ к таким переменным существенно быстрее, что важно для участков кода, к которым предъявляются повышенные требования по скорости выполнения. Но не следует злоупотреблять этой возможностью - если компилятор разместит переменную в регистре, то он не сможет использовать его для других целей, что может не ускорить, а замедлить выполнение программы. Также ввиду того, что переменная не размещяется в памяти, на нее не определен указатель. Просьбу вашу компилятор может и не удовлетворить. В остальном эти переменные идентичны автоматическим.

    Нужный класс переменной задается написанием соответствующего ключевого слова перед описанием типа. Например, static int a.

    Еще об области видимости. Если переменная уже видна в данном блоке, то ее разрешается переопределить с новым типом, значением и/или классом. В этом случае доступ в блоке к внешней переменной будет потерян.

    Теперь о массивах. Задача такая: ввести с клавиатуры 100 чисел и вывести их на экран в обратном порядке. (Нам следует познакомиться с функцией
     int scanf (const char* Format, ...);
    
    объявленной в файле <stdio.h> и осуществляющей ввод со стандартного устройства ввода (по умолчанию - клавиатуры) нескольких значений. Формат строки такой же, как и у printf. Как вы можете догадаться, для того, чтобы функция могла записать в нашу переменную что-либо, мы должны передавать указатель на нее как параметр функции).

    Основная проблема здесь в том, что эти 100 чисел надо где-то хранить. Можно, конечно, извратиться и написать вот так:
     #include <stdio.h>
    
     void func (int counter)
     {
      int a;
    
      scanf ("%d", a);
      if (counter)
       func (counter - 1);
      printf ("%d\n", a);
     }
    
     int main (void)
     {
      func (100);
      return 0;
     }
    
    Но это из серии извращений. Можно все сделать гораздо проще, если воспользоваться массивами.

    Массив - это множество переменных одного типа, характеризующихся одним именем. Как следует из их определения, массивы очень удобны, если приходится иметь дело с большими наборами однотипных данных. Синтакис описания массива таков:
     имя_типа имя_переменной [количество_элементов_массива]
    
    Отдельные элементы массива собственного имени не имеют. Доступ к ним можно получить, используя оператор []. Этот оператор записывается так:
     имя_массива [индекс_элемента]
    
    - и возвращает элемент массива, соответствующий индексу. Обратите внимание: индексация в С начинается с 0, то есть правильными значениями индекса будут все значения в интервале от 0 до количество_элементов_массива - 1.

    Объединяя вышесказанное, получаем программу:
     #include <stdio.h>
    
     int main (void)
     {
      int i, a [100];
    
      for (i = 0; i < 100; i++)
       scanf ("%d", &(a [i]));
      for (i = 99; i >= 0; i--)
       printf ("%d\n", &(a [i]));
    
      return 0;
     }
    
    Вот и вся программа. Какая из этих двух вам понравилась больше?

    Теперь об обещанном в прошлом выпуске правиле разбора сложных типов. Алгоритм такой (взято из MSDN):
    1. Начинаем с переменной.
    2. Смотрим в пределах скобок направо:
      • Если там есть круглые скобки, то это функция. Все, что в этих скобках, интерпритируется как параметры этой функции.
      • Если там есть квадратные скобки, то это массив. В скобках должно стоять целое положительное число, которое рассматривается как количество элементов массива.
    3. Теперь посмотрим налево. Если там есть звездочка, то это указатель.
    4. Выбираемся из скобок на скобки более низкого уровня вложенности и повторяем шаги 2-3.
    5. Так мы делаем, пока не доберемся до определения типа.
    Пример:
     char *(*(*a)(void))[10];
     ^    ^ ^ ^^ ^      ^
     7    6 4 21 3      5
    
    Итак, по порядку.
    1. a определяется как...
    2. ...указатель на...
    3. ...функцию, не принимающую параметров и возвращающую...
    4. ...указатель на...
    5. ...массив из 10...
    6. ...указателей на...
    7. ...char.
    Теперь вопрос на проверку усвоенного: как в С реализованы многомерные массивы?

    Правильный ответ: как массивы, элементами которых являются массивы, которые, в свою очередь... и так требуемое количество раз. Так же производится и их индексация: если у нас есть переменная int a [10][20][30] , то a [3] возвратит некоторое значение типа int [20][30], a [3][5] - соответственно int [30], а a [3][5][1] - int. Последнее значение и будет элементом нашего трехмерного массива.

    Статические массивы можно, как и остальные переменные, инициализировать сразу при их объявлении. При этом элементы массива перечисляются через запятую и все вместе заключаются в фигурные скобки. Пример:
     int a [6] = {1, 2, 3, 4, 5, 6};
    
    Количество элементов в списке может быть меньше формального количества элементов массива - при этом инициализируются первые n элементов массива, где n - количество элементов в списке инициализации. С другой стороны, если указаны все элементы массива, то формальное количество элементов может отсутствовать (в этом случае пишется int a []). Как инициализировать многомерные массивы - догадайтесь сами. После всего рассказанного это достаточно очевидно. Автоматические переменные инициализировать нельзя.

    Теперь вопрос: что напечатает такая программа?
     #include <stdio.h>
    
     int main (void)
     {
      int i, a [10];
    
      a [0] = 5;
    
      printf ("%d\n", *a);
      return 0;
     }
    
    На первый взгляд кажется, что она даже не будет компилироваться - ведь мы пытаемся взять значение по переменной, не являющейся адресом. Но, вопреки ожиданиям, она запустится и напечатает число 5. Как вы можете догадаться из этого, массив в С является указателем на его первый элемент. Запомните это раз и навсегда - это очень важно.

    Теперь можно рассказать о операциях над указателями более подробно. Пусть у нас есть переменная int *a, и мы пишем a += 3. Что получится? Можно подумать, что теперь значениеa сместится на 3 байта относительно своего текущего положения. Однако это не так - компилятор автоматически внесет поправку на размер int. То есть a + 3 будет указывать на третий элемент воображаемого массива, который начинается в памяти там, куда указывает a. Другими словами, a + 3 и &(a [3]) эквивалентны. А так как, например, у меня размер переменной типа int составляет 4 байта, то значение переменной сместится на 12 байт.

    Таким же образом производится и вычитание из указателя целого числа. Но, учитывая, что элементов массива с отрицательным индексом не существует (реальное поведение программы всегда зависит от компилятора. Но стоит придерживаться стандарта), правильнее сказать так: a - 3 указывает на начало массива, третий элемент которого расположен там, куда указывает a. И, наконец, если даны int *a и int *b, то a - b имеет тип int и показывает, какой номер в массиве, расположенный там, куда указывает a, имеет элемент, расположенный там, куда указывает b. Здесь, в отличие от остальных случаев, результат может быть и отрицательным. Вычитать разнотипные указатели нельзя.

    Следующий вопрос, который мы рассмотрим - это передача массивов в функции как параметров. Это можно сделать несколькими способами. Первый, как вы уже могли догадаться, такой:
     void Func (int a [16])
    
    А теперь проведем следственный эксперимент. Пишем программу:
     #include <stdio.h>
    
     void Func (int a [2])
     {
      // Выводим на печать указатель, то есть адрес массива в памяти
      printf ("%d\n", a);
     }
    
     int main (void)
     {
      int a [10];
    
      printf ("%d\n", a);
      Func (a);
    
      return 0;
     }
    
    Несмотря на различие типов int a [2] и int a [10] программа заработает. Вот результат ее работы на моем компьютере:
    
     1245016
     1245016
    
    Таким образом, мы видим, что вместо массива передается указатель на него. Более того, мы могли бы просто написать
     void Func (int a [])
    
    - если массив передается как параметр, его размер компилятору неинтересен. Учитывая, что в С массивы отождествленны с указателями, можно написать даже так:
     void Func (int *a)
    
    Все эти варианты будут работать.

    А теперь вопрос чуть посложнее - о многомерных массивах. Будет ли работать такая программа?
     #include <stdio.h>
    
     void Func (int a [][])
     {
      // Выводим на печать указатель, то есть адрес массива в памяти
      printf ("%d\n", a);
     }
    
     int main (void)
     {
      int a [3][3];
    
      printf ("%d\n", a);
      Func (a);
    
      return 0;
     }
    
    Правильный ответ - не будет. Рассмотрим поподробнее параметр int a [][]. Это массив из неопределенного количества... чего? Ведь у int b [] не определено количество элементов, а, следовательно, и размер - отсюда не определены операции над указателями для а (напомню: они все оперируют с размером), то есть такой указатель не может бать определен. Для того, чтобы программа заработала, необходимо изменить определение функции на такое:
     void Func (int a [][3])
    
    Отсюда еще одно правило: в случае многомерных массивов с указателем отождествляется только самый "верхний" массив. Все остальные должны находиться в точном соотвтетвии.

    Вот и все на сегодня. Домашнее задание:
    1. Написать функцию, возвращающую максимальный элемент одномерного массива. Количество элементов массива передается в функцию как параметр.
    2. Сделать то же самое для трехмерного массива 4x4x4.

    До встречи!

    Ведущий рассылки, av

    http://subscribe.ru/
    E-mail: ask@subscribe.ru
    Отписаться Relayed by Corbina
    Рейтингуется SpyLog

    В избранное