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

      Комментарии к записи Указатели и операции с ними отключены

Типы данных

Программы оперируют с различными данными, которые могут быть простыми и структурированными. Простые данные — это целые и вещественные числа, символы и указатели (адреса объектов в памяти). Целые числа не имеют, а вещественные имеют дробную часть. Структурированные данные — это массивы и структуры; они будут рассмотрены ниже.

В языке различают понятия тип данных и модификатор типа. Тип данных — это, например, целый, а модификатор — со знаком или без знака. Целое со знаком будет иметь как положительные, так и отрицательные значения, а целое без знака — только положительные значения. В языке Си можно выделить пять базовых типов, которые задаются следующими ключевыми словами:

  • char — символьный;
  • int — целый;
  • float — вещественный;
  • double — вещественный двойной точности;
  • void — не имеющий значения.

Дадим им краткую характеристику:

  1. Переменная типа char имеет размер 1 байт, ее значениями являются различные символы из кодовой таблицы, например: ‘ф’, ‘:’, ‘j’ (при записи в программе они заключаются в одинарные кавычки).
  2. Размер переменной типа int в стандарте языка Си не определен. В большинстве систем программирования размер переменной типа int соответствует размеру целого машинного слова. Например, в компиляторах для 16-разрядных процессоров переменная типа int имеет размер 2 байта. В этом случае знаковые значения этой переменной могут лежать в диапазоне от -32768 до 32767.
  3. Ключевое слово float позволяет определить переменные вещественного типа. Их значения имеют дробную часть, отделяемую точкой, например: -5.6, 31.28 и т.п. Вещественные числа могут быть записаны также в форме с плавающей точкой, например: -1.09e+4. Число перед символом е называется мантиссой, а после е — порядком. Переменная типа float занимает в памяти 32 бита. Она может принимать значения в диапазоне от 3.4е-38 до 3.4e+38.
  4. Ключевое слово double позволяет определить вещественную переменную двойной точности. Она занимает в памяти в два раза больше места, чем переменная типа float (т.е. ее размер 64 бита). Переменная типа double может принимать значения в диапазоне от 1.7e-308 до 1.7e+308.
  5. Ключевое слово void (не имеющий значения) используется для нейтрализации значения объекта, например, для объявления функции, не возвращающей никаких значений.

Объект некоторого базового типа может быть модифицирован. С этой целью используются специальные ключевые слова, называемые модификаторами. В стандарте ANSI языка Си имеются следующие модификаторы типа:

  • unsigned
  • signed
  • short
  • long

Модификаторы записываются перед спецификаторами типа, например: unsigned char. Если после модификатора опущен спецификатор, то компилятор предполагает, что этим спецификатором является int. Таким образом, следующие строки:

long а;

long int а;

являются идентичными и определяют объект а как длинный целый. Табл. 1 иллюстрирует возможные сочетания модификаторов (unsigned, signed, short, long) со спецификаторами (char, int, float и double), а также показывает размер и диапазон значений объекта (для 16-разрядных компиляторов).

Тип Размер в байтах (битах) Интервал изменения
char 1 (8) от -128 до 127
unsigned char 1 (8) от 0 до 255
signed char 1 (8) от -128 до 127
int 2 (16) от -32768 до 32767
unsigned int 2 (16) от 0 до 65535
signed int 2 (16) от -32768 до 32767
short int 2 (16) от -32768 до 32767
unsigned short int 2 (16) от 0 до 65535
signed short int 2 (16) от -32768 до 32767
long int 4 (32) от -2147483648 до 2147483647
unsigned long int 4 (32) от 0 до 4294967295
signed long int 4 (32) от -2147483648 до 2147483647
float 4 (32) от 3.4Е-38 до 3.4Е+38
double 8 (64) от 1.7Е-308 до 1.7Е+308
long double 10 (80) от 3.4Е-4932 до 3.4Е+4932

Переменные и константы

Все переменные до их использования должны быть определены (объявлены). При этом задается тип, а затем идет список из одной или более переменных этого типа, разделенных запятыми. Например:

int a, b, c; char x, y;

В языке различают понятия объявления переменной и ее определения. Объявление устанавливает свойства объекта: его тип (например, целый), размер (например, 4 байта) и т.д. Определение наряду с этим вызывает выделение памяти (в приведенном примере дано определение переменных).

Переменные можно разделять по строкам произвольным образом, например:

float a; float b;

Переменные в языке Си могут быть инициализированы при их определении:

int a = 25, h = 6; char g = ‘Q’, k = ‘m’; float r = 1.89; long double n = r*123;

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

int a; /* Определение глобальной переменной */ int function (int b, char c); /* Объявление функции (т.е. описание ее заголовка)*/ void main (void){ //Тело программы int d, e; //Определение локальных переменных float f; //Определение локальной переменной … }

Наряду с переменными в языке существуют следующие виды констант:

  • вещественные, например 123.456, 5.61е-4. Они могут снабжаться суффиксом F (или f), например 123.456F, 5.61e-4f;
  • целые, например 125;
  • короткие целые, в конце записи которых добавляется буква (суффикс) H (или h), например 275h, 344H;
  • длинные целые, в конце записи которых добавляется буква (суффикс) L (или l), например 361327L;
  • беззнаковые, в конце записи которых добавляется буква U (или u), например 62125U;
  • восьмеричные, в которых перед первой значащей цифрой записывается нуль (0), например 071;
  • шестнадцатеричные, в которых перед первой значащей цифрой записывается пара символов нуль-икс (0x), например 0x5F;
  • символьные — единственный символ, заключенный в одинарные кавычки, например ‘О’, ‘2’, ‘.’ и т.п. Символы, не имеющие графического представления, можно записывать, используя специальные комбинации, например \n (код 10), \0 (код 0). Эти комбинации выглядят как два символа, хотя фактически это один символ. Так же можно представить любой двоичный образ одного байта: ‘\NNN’, где NNN — от одной до трех восьмеричных цифр. Допускается и шестнадцатеричное задание кодов символов, которое представляется в виде: ‘\х2В’, ‘\хЗ6’ и т.п.;
  • строковые — последовательность из нуля символов и более, заключенная в двойные кавычки, например: Это строковая константа. Кавычки не входят в строку, а лишь ограничивают ее. Строка представляет собой массив из перечисленных элементов, в конце которого помещается байт с символом ‘\0’. Таким образом, число байтов, необходимых для хранения строки, на единицу превышает число символов между двойными кавычками;
  • константное выражение, состоящее из одних констант, которое вычисляется во время трансляции (например: а=60+301);
  • типа long double, в конце записи которых добавляется буква L (или l), например: 1234567.89L.

Операции языка Си

Любое выражение языка состоит из операндов (переменных, констант и др.), соединенных знаками операций. Знак операции — это символ или группа символов, которые сообщают компилятору о необходимости выполнения определенных арифметических, логических или других действий.

Операции выполняются в строгой последовательности. Величина, определяющая преимущественное право на выполнение той или иной операции, называется приоритетом. В табл. 2 перечислены различные операции языка Си. Их приоритеты для каждой группы одинаковы (группы выделены цветом). Чем большим преимуществом пользуется соответствующая группа операций, тем выше она расположена в таблице. Порядок выполнения операций может регулироваться с помощью круглых скобок.

Таблица 2

Знак операции Назначение операции
( ) Вызов функции
[ ] Выделение элемента массива
. Выделение элемента записи
Выделение элемента записи
! Логическое отрицание
~ Поразрядное отрицание
Изменение знака
++ Увеличение на единицу
Уменьшение на единицу
Взятие адреса
* Обращение по адресу
(тип) Преобразование типа (т.е. (float) a)
sizeof( ) Определение размера в байтах
* Умножение
/ Деление
% Определение остатка от деления
+ Сложение
Вычитание
Сдвиг влево
Сдвиг вправо
Меньше, чем
Меньше или равно
Больше, чем
= Больше или равно
= = Равно
!= Не равно
Поразрядное логическое И
^ Поразрядное исключающее ИЛИ
| Поразрядное логическое ИЛИ
Логическое И
|| Логическое ИЛИ
?: Условная (тернарная) операция
= Присваивание
+=, — =, *=, /=, %=, =, =, |=, ^= Составные операции присваивания (например, а *= b (т.е. a = a * b) и т.д.)
, Операция запятая

Для исключения путаницы в понятиях операция и оператор, отметим, что оператор — это наименьшая исполняемая единица программы. Различают операторы выражения, действие которых состоит в вычислении заданных выражений (например: a = sin(b)+c; j++;), операторы объявления, составные операторы, пустые операторы, операторы метки, цикла и т.д. Для обозначения конца оператора в языке Си используется точка с запятой. Что касается составного оператора (или блока), представляющего собой набор логически связанных операторов, помещенных между открывающей ({) и закрывающей (}) фигурными скобками (операторными скобками), то за ним точка с запятой не ставится. Отметим, что блок отличается от составного оператора наличием определений в теле блока.

Охарактеризуем основные операции языка Си. Сначала рассмотрим одну из них — операцию присваивания (=). Выражение вида

х = у;

присваивает переменной х значение переменной у. Операцию = разрешается использовать многократно в одном выражении, например:

x = y = z = 100;

Различают унарные и бинарные операции. У первых из них один операнд, а у вторых — два. Начнем их рассмотрение с операций, отнесенных к первой из следующих традиционных групп:

  1. Арифметические операции.
  2. Логические операции и операции отношения.
  3. Операции с битами.

Арифметические операции задаются следующими символами (табл. 2): +, -, *, /, %. Последнюю из них нельзя применять к переменным вещественного типа. Например:

a = b + c; x = y — z; r = t * v; s = k / l; p = q % w;

Логические операции отношения задаются следующими символами (см. табл. 2):(И), || (ИЛИ), ! (НЕ), , =,

Таблица 3

x y xy x||y !x

Битовые операции можно применять к переменным, имеющим типы int, char, а также их вариантам (например, long int). Их нельзя применять к переменным типов float, double, void (или более сложных типов). Эти операции задаются следующими символами: ~ (поразрядное отрицание),(сдвиг вправо),(поразрядное И), ^ (поразрядное исключающее ИЛИ), | (поразрядное ИЛИ).

Примеры: если a = 0000 1111 и b = 1000 1000, то

~a = 1111 0000, a1 = 0000 0111, ab = 0000 1000, a ^ b = 1000 0111, a | b = 1000 1111.

В языке предусмотрены две нетрадиционные операции инкремента (++) и декремента (—). Они предназначены для увеличения и уменьшения на единицу значения операнда. Операции ++ и — можно записывать как перед операндом, так и после него. В первом случае (++n или —n) значение операнда (n) изменяется перед его использованием в соответствующем выражении, а во втором (n++ или n—) — после его использования. Рассмотрим две следующие строки программы:

a = b + c++; a1 = b1 + ++c1;

Предположим, что b = b1 = 2, c = c1 = 4. Тогда после выполнения операций: a = 6, b = 2, c = 5, a1 = 7, b1 = 2, c1 = 5.

Широкое распространение находят также выражения с еще одной нетрадиционной тернарной или условной операцией ?:. В формуле

y = x ? a: b;

y = a, если x не равно нулю (т.е. истинно), и y = b, если х равно нулю (ложно). Следующее выражение

y = (ab) ? a: b;

позволяет присвоить переменной у значение большей переменной (а или b), т.е. y = max(a, b).

Еще одним отличием языка является то, что выражение вида а = а + 5; можно записать в другой форме: a += 5;. Вместо знака + можно использовать и символы других бинарных операций (см. табл. 2).

Преобразование типов

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

  1. Если один из операндов в выражении имеет тип long double, то остальные тоже преобразуются к типу long double.
  2. В противном случае, если один из операндов в выражении имеет тип double, то остальные тоже преобразуются к типу double.
  3. В противном случае, если один из операндов в выражении имеет тип float, то остальные тоже преобразуются к типу float.
  4. В противном случае, если один из операндов в выражении имеет тип unsigned long, то остальные тоже преобразуются к типу unsigned long.
  5. В противном случае, если один из операндов в выражении имеет тип long, то остальные тоже преобразуются к типу long.
  6. В противном случае, если один из операндов в выражении имеет тип unsigned, то остальные тоже преобразуются. к типу unsigned.
  7. В противном случае все операнды преобразуются к типу int. При этом тип char преобразуется в int со знаком; тип unsigned char в int, у которого старший байт всегда нулевой; тип signed char в int, у которого в знаковый разряд передается знак из сhar; тип short в int (знаковый или беззнаковый).

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

В языке Си можно явно указать тип любого выражения. Для этого используется операция преобразования (приведения) типа. Она применяется следующим образом:

(тип) выражение

(здесь можно указать любой допустимый в языке Си тип).

Рассмотрим пример:

int a = 30000; float b; …….. b = (float) a * 12;

(переменная a целого типа явно преобразована к типу float; если этого не сделать, то результат будет потерян, т.к. a * 1232767).

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

Указатели — это переменные, показывающие место или адрес памяти, где расположены другие объекты (переменные, функции и др.). Так как указатель содержит адрес некоторого объекта, то через него можно обращаться к этому объекту.

Унарная операциядает адрес объекта, поэтому оператор

у = х;

присваивает адрес переменной х переменной у. Операциюнельзя применять к константам и выражениям; конструкции вида (х+7) или 28 недопустимы.

Унарная операция * воспринимает свой операнд как адрес некоторого объекта и использует этот адрес для выборки содержимого, поэтому оператор

z = *y;

присваивает z значение переменной, записанной по адресу у. Если

y = x; z = *у;

то z = x.

Объекты, состоящие из знака * и адреса (например, *а), необходимо определить. Делается это, например, так:

int *а, *b, *с; char *d;

Определение вида char *d говорит о том, что значение, записанное по адресу d, имеет тип char.

Указатели могут встречаться и в выражениях. Если у — указатель на целое, т.е. имело место объявление int *у, то *у может появиться там же, где и любая другая переменная, не являющаяся указателем. Таким образом, следующие выражения вполне допустимы:

*у = 7; *x *=5; (*z)++;

Первое из них заносит число 7 в ячейку памяти по адресу у, второе увеличивает значение по адресу х в пять раз, третье добавляет единицу к содержимому ячейки памяти с адресом z. В последнем случае круглые скобки необходимы, так как операции с одинаковым приоритетом выполняются справа налево. В результате если, например, *z = 5, то (*z)++ приведет к тому, что *z = 6, а *z++ всего лишь изменит сам адрес z (операция ++ выполняется над адресом z, а не над значением *z по этому адресу).

Указатели можно использовать как операнды в арифметических операциях. Если у — указатель, то унарная операция y++ увеличивает его значение; теперь оно является адресом следующего элемента. Указатели и целые числа можно складывать. Конструкция у + n (у — указатель, n — целое число) задает адрес n-гo объекта, на который указывает у. Это справедливо для любых объектов (int, char, float и др.); транслятор будет масштабировать приращение адреса в соответствии с типом, указанным в определении объекта.

Любой адрес можно проверить на равенство (==) или неравенство (!=) со специальным значением NULL, которое позволяет определить ничего не адресующий указатель.

Операторы цикла

Циклы организуются, чтобы выполнить некоторый оператор или группу операторов определенное число раз. В языке Си три оператора цикла: for, while и do — while. Первый из них формально записывается, в следующем виде:

for (выражение_1; выражение_2; выражение_3) тело_цикла

Тело цикла составляет либо один оператор, либо несколько операторов, заключенных в фигурные скобки { … } (после блока точка с запятой не ставится). В выражениях 1, 2, 3 фигурирует специальная переменная, называемая управляющей. По ее значению устанавливается необходимость повторения цикла или выхода из него.

Выражение_1 присваивает начальное значение управляющей переменной, выражение_З изменяет его на каждом шаге, а выражение_2 проверяет, не достигло ли оно граничного значения, устанавливающего необходимость выхода из цикла.

Примеры:

for (i = 1; i10; i++) { … } for (сh = ‘a’; ch != ‘p’;) scanf (%c, ch); /* Цикл будет выполняться до тех пор, пока с клавиатуры не будет введен символ ‘p’ */

Любое из трех выражений в цикле for может отсутствовать, однако точка с запятой должна оставаться. Таким образом, for ( ; ; ) {…} — это бесконечный цикл, из которого можно выйти лишь другими способами.

В языке Си принято следующее правило. Любое выражение с операцией присваивания, заключенное в круглые скобки, имеет значение, равное присваиваемому. Например, выражение (а=7+2) имеет значение 9. После этого можно записать другое выражение, например: ((а=7+2)((сh = getch( )) == ‘i’)

позволяет вводить значение переменной сh и давать истинный результат только тогда, когда введенным значением является буква ‘i’. В скобках можно записывать и несколько формул, составляющих сложное выражение. Для этих целей используется операция запятая. Формулы будут вычисляться слева направо, и все выражение примет значение последней вычисленной формулы. Например, если имеются две переменные типа char, то выражение

z = (х = у, у = getch( ));

определяет следующие действия: значение переменной у присваивается переменной х; вводится символ с клавиатуры и присваивается переменной у; z получает значение переменной у. Скобки здесь необходимы, поскольку операция запятая имеет более низкий приоритет, чем операция присваивания, записанная после переменной z. Операция запятая находит широкое применение для построения выражений цикла for и позволяет параллельно изменять значения нескольких управляющих переменных.

Допускаются вложенные конструкции, т.е. в теле некоторого цикла могут встречаться другие операторы for.

Оператор while формально записывается в таком виде:

while (выражение) тело_цикла

Выражение в скобках может принимать ненулевое (истинное) или нулевое (ложное) значение. Если оно истинно, то выполняется тело цикла и выражение вычисляется снова. Если выражение ложно, то цикл while заканчивается.

Оператор do-while формально записывается следующим образом:

do {тело_цикла} while (выражение);

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

Допускается вложенность одних циклов в другие, т.е. в теле любого цикла могут появляться операторы for, while и do — while.

В теле цикла могут использоваться новые операторы break и continue. Оператор break обеспечивает немедленный выход из цикла, оператор continue вызывает прекращение очередной и начало следующей итерации.

Статьи к прочтению:

Основы ООП C++. Урок 9 — Указатели


Похожие статьи: