Рассылка закрыта
Вы можете найти рассылки сходной тематики в Каталоге рассылок.
C и C++ для начинающих - Выпуск 8. Классы памяти и массивы.
- По-видимому, рассылка набирает обороты, так как я уже начал получать письма. Преимущественно пишут, что С не знают вообще или знают очень слабо и что хотели бы научиться. Значит, правильно я сделал, что начал рассказывать с нуля. Что ж, дерзайте! Берите все выпуски, начиная с первого, читайте, вникайте и (еще раз повторюсь) выполняйте домашние задания! И все у вас получится.
- Также несколько человек пишут нечто вроде: "Хочу подписаться на рассылку", "Подпишите меня, пожалуйста". Я никого подписывать сам не собиаюсь. Если хотите подписаться - подписывайтесь! Сделать это можно на subscribe.ru или прямо отсюда:
- Да, еще большая просьба не писать мне письма без содержания. За сегодняшний день я получил уже 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):
- Начинаем с переменной.
- Смотрим в пределах скобок направо:
- Если там есть круглые скобки, то это функция. Все, что в этих скобках, интерпритируется как параметры этой функции.
- Если там есть квадратные скобки, то это массив. В скобках должно стоять целое положительное число, которое рассматривается как количество элементов массива.
- Теперь посмотрим налево. Если там есть звездочка, то это указатель.
- Выбираемся из скобок на скобки более низкого уровня вложенности и повторяем шаги 2-3.
- Так мы делаем, пока не доберемся до определения типа.
char *(*(*a)(void))[10]; ^ ^ ^ ^^ ^ ^ 7 6 4 21 3 5Итак, по порядку.
- a определяется как...
- ...указатель на...
- ...функцию, не принимающую параметров и возвращающую...
- ...указатель на...
- ...массив из 10...
- ...указателей на...
- ...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])Отсюда еще одно правило: в случае многомерных массивов с указателем отождествляется только самый "верхний" массив. Все остальные должны находиться в точном соотвтетвии.
Вот и все на сегодня. Домашнее задание:
- Написать функцию, возвращающую максимальный элемент одномерного массива. Количество элементов массива передается в функцию как параметр.
- Сделать то же самое для трехмерного массива 4x4x4.
До встречи!
Ведущий рассылки, av
http://subscribe.ru/
E-mail: ask@subscribe.ru | Отписаться | Relayed by Corbina
Рейтингуется SpyLog |
В избранное | ||