Глава 5. Работа с данными

Есть одно справедливое утверждение: "Вы не сможете называть себя профессиональным программистом на C/C++ до тех пор, пока не прекратите мыслить на любых других языках и переводить свои мысли на C/C++". Вы можете разбираться в языках COBOL, FORTRAN, Pascal или PL/I и, зная хотя бы один из этих языков, заставить себя изучить другие языки из этого списка, чтобы успешно программировать на любом из них. Но такой подход может не сработать в случае изучения языков С и C++. Дело в том, что они достаточно сильно отличаются от других языков как по используемым средствам программирования, так и по уникальной логике поиска решений. Если вы не измените свой стиль мышления при написании программ, то не сможете реализовать все возможности и преимущества C/C++. И, что хуже, вы даже не сможете разобраться в логике программ, написанных настоящими специалистами. Техника написания программ на языках C/C++ изобилует таким количеством нюансов, что беглого взгляда на программу достаточно, чтобы оценить профессиональный уровень программиста!

С этой главы мы начнем детальное изучение структуры языков C/C++. Прежде всего следует познакомиться со стандартными типами данных и операторами, с помощью которых можно манипулировать данными.

Идентификаторы

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

Идентификатор представляет собой последовательность символов произвольной длины, содержащую буквы, цифры и символы подчеркивания, но начинающуюся обязательно с буквы или символа подчеркивания. Компилятор распознает только первые 31 символ. (Учтите, что другие программы, принимающее данные от компилятора, например компоновщик, могут распознавать последовательности даже еще меньшей длины.)

Языки С и C++ чувствительны к регистру букв. Другими словами, компилятор распознает прописные и строчные буквы как разные символы. Так, переменные NAME_LENGTH и Name_Length будут рассматриваться как два разных идентификатора, представляющих различные ячейки памяти. То есть вы можете создавать идентификаторы, одинаково читаемые, но отличающиеся написанием одной или нескольких букв.

Использование как прописных, так и строчных букв в идентификаторах позволяет сделать программный код более читабельным. Например, идентификаторы, которые объявлены в файлах заголовков, включаемых в программу с помощью директив #include, часто записывают прописными буквами, чтобы они бросались в глаза. В результате, где бы вы ни встретили идентификаторы, записанные пропис­ными буквами, сразу станет ясно, где они описаны.

Хотя допускается использование символа подчеркивания в начале имени идентификатора, мы не рекомендуем так поступать, поскольку данный способ записи применяется в именах встроенных системных подпрограмм и переменных. Совпадение имени идентификатора с зарезервированным именем вызовет конфликт в работе программы. Два символа подчеркивания (_) в начале имени идентификатора применяются в стандартных библиотеках языка C++.

Среди программистов на С принято негласное соглашение начинать любое имя с префикса типа данных этого идентификатора. Например, все целочисленные идентификаторы должны начинаться буквой i (integer), идентификаторы с плавающей запятой — буквой f (float), строки, завершающиеся нулевым символом, — буквами sz (stringzero), указатели — буквой р (pointer) и т.д. Зная об этих соглашениях, вы, бросив лишь беглый взгляд на текст программы, сможете сразу определить не только идентификаторы, используемые в этой программе, но и их типы данных. Это существенно упрощает восприятие программных текстов.

Ниже показаны примеры идентификаторов:

i
itotal
frangel
szfirst_name
Ifrequency
imax
iMax
iMAX
NULL
EOF  

Попробуйте определить, почему следующие имена идентификаторов недопустимы:

lst_year
#social_security
Not Done!

Первое имя недопустимо, поскольку начинается с цифры. Второе имя начинается с символа #, а третье — содержит недопустимый символ в конце имени. Посмотрите теперь на следующие идентификаторы и попробуйте определить, допустимы ли они:

o
oo
ooo

Как это ни покажется странным, все четыре имени допустимы. В первых трех используется буква о в верхнем регистре. Поскольку длина идентификаторов разная, никаких конфликтов не происходит. Имя четвертого идентификатора состоит из пяти символов подчеркивания (_). Но достаточно ли содержательны эти имена? Определенно нет, хотя они вполне допустимы. Таким образом, программист должен позаботиться о том, чтобы все имена функций, констант, переменных и другие идентификаторы несли какой-то смысл.

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

MAX_RATIO
max_ratio
Max_Ratio

Чувствительность компилятора языка С к регистру букв может вызвать головную боль у начинающих программистов. Например, если вместо printf () в программе будет записано Printf(),то компилятор выдаст сообщение об ошибке вида "unknown identifier" (неизвестный идентификатор). На языке Pascal можно использовать любое написание имен, например: writeln, WRITELN или writeLn.

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

printf("%D",integer_value);

Предположив, что переменная integer_value записана правильно, вы можете не заподозрить ошибки. Тем не менее, ошибка есть. Формат вывода может задаваться в языке С только оператором %d, но не %D.

И последнее замечание касательно идентификаторов: имя идентификатора не должно совпадать (с учетом регистра) с именем ключевого слова.

Ключевые слова

Ключевые слова являются встроенными идентификаторами, каждому из которых соответствует определенное действие. Изменить назначение ключевого слова нельзя. (С помощью директивы препроцессора #define можно создать "псевдоним" ключевого слова, который будет дублировать его действия, возможно, с некоторыми изменениями.) Помните, что имена идентификаторов, создаваемых в программе, не могут совпадать с ключевыми словами языков C/C++ (табл. 5.1).


Таблица 5.1. Ключевые слова языков С/С++ (начинающиеся с символов подчеркивания специфичны для компилятора Microsoft)

asm

else

main

struct

assume

enum

multiple inheritance

switch

auto

except

single inheritance

template

based

explicit

virtual inheritance

this

bool

extern

mutable

thread

break

false

naked

throw

case

_ fastcall

namespace

true

catch

_ finally

new

try

cdecl

float

noreturn

_try

char

for

operator

typedef

class

friend

private

typeid

const

goto

protected

typename

const cast

if

public

union

continue

inline

register

unsigned

declspec

inline

reinterpret cast

using

default

int

return

uuid

delete

int8

short

_ uuidof

dllexport

_ int!6

signed

virtual

dll import

_ int32

sizeof

void

do

_ int64

static

volatile

double

leave

static cast

while

dynamic cast

long

_ stdcall

wmain

Стандартные типы данных

Все программы обрабатывают какую-то информацию. В языках C/C++ данные представляются одним из восьми базовых типов: char (текстовые данные), int (целые числа), float (числа с плавающей запятой одинарной точности), double (числа с плавающей запятой двойной точности), void (пустые значения), bool(логические значения), перечисления и указатели. Остановимся на каждом из типов данных.

Символы

В каждом языке (не только компьютерном) используется определенный набор символов для построения значимых выражений. Например, в книгах, написанных на английском языке, используются комбинации из 26 букв латинского алфавита, десяти цифр и нескольких знаков препинания. Аналогично, выражения на языках С и C++ записываются с помощью 26 строчных букв латинского алфавита:

abcdefghijklmnopqrstuvwxyz

26 прописных букв латинского алфавита:

ABCDEFGHIJKLMNOPQRSTUVWXYZ .

десяти цифр:

0123456789

и следующих специальных символов:

+ -*/=,._:;?\"'~|!#%$&()[]{}^@

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

++   —   ==   &&    ||   <<   >>   >=  <=  +=-=*=/=?:    ::   /*   */   //

В следующем примере программы на языке С показана работа с переменными типа char:

/*
*       char.с
*       Эта программа на языке С демонстрирует работу с переменными типа
*       char и показывает,   как интерпретировать символьное  значение
*       в целочисленном виде.
*/

#include <stdio.h>

#include <ctype.h>

int main() {

char csinglechar, cuppercase, clowercase;

printf("\nВведите одну букву: ");
scanf("%c",Scsinglechar);

cuppercase = toupper(csinglechar);
clowercase = tolower(csinglechar);

printf("ВВЕРХНЕМ регистре буква \'%с\'имеет"

" десятичный ASCII-код %d\n",cuppercase,

" cuppercase);

printf("ASCII-код в шестнадцатеричном формате"

" равен %Х\n", cuppercase); printf("Если прибавить шестнадцать, то получится

" \'%c\'\n",(cuppercase + 16));

printf("ASCII-код полученного значения в

" шестнадцатеричном формате"

" равен %X\n",(cuppercase + 16)); printf("ВНИЖНЕМ регистре буква \'%с\'имеет"

" десятичный ASCII-код%d\n",clowercase,

" clowercase);

return(0); }

В результате работы программы на экран будет выведена следующая информация:

Введите одну букву: d

В ВЕРХНЕМ регистре буква 'D'имеет десятичный ASCII-код 68

ASCII-код в шестнадцатеричном формате равен 44

Если прибавить шестнадцать, то получится 'Т'

ASCII-код полученного значения в шестнадцатеричном формате равен 54

В НИЖНЕМ регистре буква 'd' имеет десятичный ASCII-код 100

Спецификатор формата %X служит указанием отобразить значение переменной в шестнадцатеричном виде в верхнем регистре.

Три типа целых чисел

В C/C++ поддерживаются три типа целых чисел. Наравне со стандартным типом int существуют типы shortint (короткое целое) и longint (длинное целое). Допускается сокращенная запись short и long. Хотя синтаксис самого языка не зависит от используемой платформы, размерность типов данных short, intи long может варьироваться. Гарантируется лишь, что соотношение размерностей таково: short <= int <= long. В MicrosoftVisual C/C++ для переменных типа shortрезервируется 2 байта, для типов intHlong — 4 байта.

Модификаторы signed и unsigned

Компиляторы языков C/C++ позволяют при описании переменных некоторых типов указывать модификатор unsigned. В настоящее время он применяется с четырьмя типами данных: char, short, int и long. Наличие данного модификатора указывает на то, что значение переменной должно интерпретироваться как беззнаковое число, т.е. самый старший бит является битом данных, а не битом знака.

Предположим, мы создали новый тип данных, my_octal, в котором для записи каждого числа выделяется 3 бита. По умолчанию считается, что значения этого типа могут быть как положительными, так и отрицательными. Поскольку из 3-х доступных битов старший будет указывать на наличие знака, то диапазон возможных значений получится от -4 до 3.

Но если при описании переменной типа my_octal указать модификатор unsigned, то тем самым мы освободим первый бит для хранения полезной информации и получим диапазон возможных значений от 0 до 7. Аналогичные преобразования выполняются и для стандартных типов данных, как видно из табл. 5.2.

Таблица 5.2. Характеристики стандартных типов данных языков С/С++

Тип данных

Байтов

Эквивалентные названия

Диапазон значений

int

2/4

signed, signed  int

зависит от системы

unsigned int

2/4

unsigned

зависит от системы

_ int8

1

char, signed  char

от -128 до 127

_ int!6

2

short, short  int, signed  short   int

от -32768 до 32767

_ int32

4

signed, signed int

от -2147483648 до 2147483647

int64

8

нет

от -9223372036854775808 до 9223372036854775807

char

1

signed char

от -128 до 127

unsignedchar

1

нет

от 0 до 255

short

2

short  int, signed  short   int

от -32768 до 32767

unsigned  short

2

unsigned  short   int

от 0 до 65535

long

4

long  int, signed  long  int

от -2147483648 до 2147483647

unsigned  long

4

unsigned  long  int

от 0 до 4294967295

1enura

нет

то же, что int

float

4

нет

приблизительно +/-3.4Е+/-38

iouble

8

long double

приблизительно +/-1,8Е+/-308

Модификаторы signed и unsigned могут использоваться с любыми целочис­ленными типами данных. Тип char по умолчанию интерпретируется как знаковый. Типы данных intи unsignedint имеют размерность системного слова: 2 байта в MS-DOS и 16-разрядных версиях Windows и 4 байта в 32-разрядных операционных системах. При построении переносимых программ не следует полагаться на конкретную размерность целочисленных типов. В то же время в компиляторе MicrosoftC/C++ поддерживается использование целых чисел фиксированной размерности, для чего введены типы _intx. В табл. 5.3 показаны все возможные комбинации типов данных и модификаторов signed и unsigned.

Таблица 5.3. Возможные варианты использования модификаторов типов данных

Полное название Эквиваленты
signed char char
signed int signed, int
signed short int short, signed short
signed long int

long, signed long

unsigned char нет
unsigned int unsigned
unsigned short int unsigned short
unsigned long int unsigned long

Числа с плавающей запятой

В C/C++ используется три типа данных с плавающей запятой: float, double и longdouble. Хотя в стандарте ANSI С не указан точный диапазон их значений, общепринято, что переменные любого из этих типов должны как минимум поддерживать диапазон от 1Е-37 до 1Е+37. Как видно из табл. 5.2, в MicrosoftVisual C/C++ возможности типов данных с плавающей запятой значительно превышают минимальные требования. Тип longdouble отсутствовал в первоначальном варианте языка и был предложен комитетом ANSI. Ранее, в 16-разрядных системах, он имел размерность 80 бит (10 байтов) и диапазон значений приблизительно +/-1,2Е+/-4932. В Windows 95/98/NT он эквивалентен типу double.

В следующем фрагменте программы на языке C++ показано описание и использование переменных с плавающей запятой:

//   float.срр
//   Эта программа на языке C++ демонстрирует использование переменных
//   типа float.

#include <iostream.h>
#include <iomanip.h>

int main() {

long  loriginal_flags =  cin.flags()

floatfvalue;

cout << "Введите число с плавающей запятой: ";

cin >> fvalue;

cout << "Стандартный формат:   " << fvalue << "\n";

cout << setiosflags(ios::scientific);

cout << "Научный формат:      " << fvalue << "\n";

cout << resetiosflags(ios::scientific);

cout << setiosflags(ios::fixed) ;

cout << "Фиксированный формат: " << fvalue << "\n";

cout.flags (loriginal_flags);

return(0);

}

В результате работы программы на экран будет выведена следующая информация:

Введите число с плавающей запятой: 12.34 Стандартный формат:   12.34 Научный формат:       1.234000е+001 Фиксированный формат: 12.340000

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

Перечисления

Перечислением называется список именованных целочисленных констант, называемых перечислителями. Переменная данного типа может принимать значение одной из перечисленных констант, ссылаясь на эту константу по имени. Например, в следующем фрагменте описывается перечисление air__supply с константами EMPTY, useable и FULL и создается переменная instructor_tank данного типа.

enum air_supply { EMPTY, USEABLE, FULL = 5 } instructor_tank;

Все константы имеют тип int, и каждой из них автоматически присваивается значение по умолчанию, если не указано какое-нибудь другое значение. В нашем примере константе EMPTY по умолчанию присвоено нулевое значение, так как это первый элемент в списке. Константе useable присвоено значение 1, так как это второй элемент списка после константы со значением 0. Для константы full вместо значения по умолчанию назначено значение 5. Если бы после константы full стояла еше одна константа, то ей по умолчанию было бы присвоено значение 6.

Имея перечисление air_supply, объявим еще одну переменную — student_tank:

enum air_supply student_tank

Обе переменные теперь можно использовать независимо друг от друга:

instructor_tank = FULL; >>tudent_tank   = EMPTY;

В этом примере переменной instructor_tank присваивается значение 5, а переменной studenttank — 0.

и C++ при описании переменной типа перечисления нет необходимости указывать ключевое слово enum, хотя его наличие не является ошибкой.

Часто ошибочно полагают, что air_supply — это переменная. В действительности это особый тип данных, который можно использовать для создания переменных, таких как instructor_tank и student_tank.

Переменные типа перечисления являются адресуемыми и могут стоять в выражениях слева от оператора присваивания. Именно это и происходит в показанных выше двух строках программы. empty, useableи full— это имена констант, а не переменных, поэтому их значения не могут быть изменены в ходе выполнения программы.

Переменные типа перечисления можно сравнивать .друг с другом и с константа­ми-перечислителями. Эту возможность удобно использовать в программах, как показано в следующем примере:

/*
*        enum.c
*   Эта программа на языке С демонстрирует использование перечислений.
*/

#include <stdio.h>

#include <stdlib.h>

int main () {

enum air_supply { EMPTY, USEABLE, FULL = 5 } instructor_tank;

enum air_supply student_tank;

instructor_tank = FULL;
student_tank = EMPTY;

printf ("Значение переменной instructor__tank равно%d\n",instructor_tank) ;

if (student_tank < USEABLE) {

printf("Наполните бак горючим.\n");

printf("Занятие отменено.\n");

exit(l); }

if (instructor_tank >= student_tank)

printf("Продолжайте занятие.\n"); else

printf("Занятие отменено.\n");

return(0); }

В языке С тип enum эквивалентен типу int. Это позволяет присваивать целочисленные значения непосредственно переменным типа перечисления. В C++ ведется более строгий контроль за типами данных, и такие присваивания не допускаются.

В результате работы программы на экран будет выведена следующая информация:

Значение переменной instructor_tank равно 5 Наполните бак горючим. Занятие отменено.

Новый тип данных языка C++ — bool

Тип данных bool относится к семейству целых типов. Переменные этого типа могут принимать только значения true или false. В C++ все услрвные выражения возвращают логические значения. Например, выражение myvar! = 0, может возвратить только true или false в зависимости от значения переменной myvar.

Значения true и false связаны друг с другом следующими отношениями:

!false == true
!true == false

Рассмотрим следующий фрагмент:

if   (условие) выражение;

Если условие равно true, то выражение будет выполнено, если же условие равно false,то выражение выполнено не будет. При создании условных выражений следует помнить о том, что как в С, так и в C++ любое значение, отличное от нуля, воспринимается как истинное, а равное нулю — как ложное.

Когда к переменной типа bool применяются операции префиксного и постфиксного инкремента (++), переменная принимает значение true. Операторы префиксного и постфиксного декремента (—) не разрешены с переменными типа bool. Кроме того, поскольку тип данных bool относится к целочисленным, переменные этого типа могут быть приведены к типу int, при этом значение true преобразуется в I, aзначение false — в 0.

Квалификаторы

В С и C++ используются два квалификатора доступа: const и volatile. Они появились в стандарте ANSI С для обозначения неизменяемых переменных (const) и переменных, чьи значения могут измениться в любой момент (volatile).

Квапификатор const

Иногда требуется, чтобы значение переменной оставалось постоянным в течение всего времени работы программы. Такие переменные называются константными. Например, если в программе вычисляется длина окружности или площадь круга, то часто придется использовать число пи — 3,14159. В бухгалтерских программах постоянной величиной является НДС. Поскольку НДС время от времени все же может меняться, то удобно будет описать его как переменную, значение которой остается постоянным в процессе выполнения программы.

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

Предположим, вы создаете программу, в которой используется число л. Можно объявить переменную с именем pi и присвоить ей начальное значение 3,14159. Но оно не должно больше меняться, так как это нарушит правильность вычислений с числом я, поэтому должен существовать формальный способ запретить размещение данной переменной в левой части оператора присваивания. Этой цели служит квалификатор const. Например:

const float pi = 3.14159;

const int iMIN = 1, iSALE_PERCENTAGE = 25;

int irow_index = 1, itotal =100;

doubleddistance => 0;

Поскольку значение константной переменной не может меняться в программе, такая переменная может быть только проинициализирована. Это выполняется один раз во время объявления переменной. Так, в показанном выше примере целочисленным константам imin и isale_percentage присвоены постоянные значения 1 и 25 соответственно. В то же время переменные irow_index, itotal и ddistance, инициализируемые аналогичным образом, могут впоследствии быть модифицированы обычным путем.

Константные и простые переменные используются в программе совершенно одинаково. Единственное отличие состоит в том, что начальные значения, присвоенные константным переменным при их инициализации, не могут быть изменены в ходе выполнения программы. Другими словами, константы не являются l-значениями (левосторонними значениями), т.е. не могут располагаться в выражениях слева от оператора присваивания. (Примером l-значения, ссылающегося на изменяемую ячейку памяти, является переменная, в объявлении которой не указан квалификатор const.) При выполнении присваивания оператор записывает значение правого операнда в ячейку памяти, адресуемую именем левого операнда. Таким образом, левый операнд (или единственный операнд унарной операции) должен ссылаться на изменяемую ячейку.

Директива #define

В С и C++ существует другой способ описания констант: директива препроцес­сора #define. Предположим, в начале программы введена такая строка:

#define  SALES_TEAM  10

Общий формат данной строки следующий: директива #define, литерал sales_team(имя константы), литерал 10 (значение константы). Встретив такую команду, препроцессор осуществит глобальную замену в программном коде имени sales_team числом 10. Причем никакие другие значения идентификатору sales_team не могут быть присвоены, поскольку переменная с таким именем в программе формально не описана. В результате идентификатор sales_team может использоваться в программе в качестве константы. Обратите внимание, что строка с директивой #define не заканчивается точкой с запятой. После числа 10 этот символ будет воспринят как часть идентификатора, в результате чего все вхождения SALES_TEAM будут заменены литералом 10;.

Итак, мы рассмотрели два способа создания констант: с помощью квалификатора const и директивы #define. В большинстве случаев они дают одинаковый результат, хотя имеется и существенное различие. Директива #define создает макроконстанту, и ее действие распространяется на весь файл. С помощью же ключевого слова constсоздается переменная. Далее в этой главе при рассмотрении классов памяти мы узнаем, что область видимости переменной можно ограничить определенной частью программы. Это же относится и к переменным, описанным с помощью квалификатора const. Таким образом, последний дает программисту больше гибкости, чем директива #define.

Квалификатор volatile

Ключевое слово volatile указывает на то, что данная переменная в любой момент может быть изменена в результате выполнения внешних действий, не контролируемых программой. Например, следующая строка описывает переменную event_time, значение которой может измениться без ведома программы:

volatile int event_time;

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

Квалификатор volatile также применяется при описании объектов данных, совместно используемых разными процессами в многозадачной среде.

Одновременное применение квалификаторов const и volatile

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

const volatile constant_event_time;

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

Преобразования типов данных

До сих пор в рассмотренных примерах мы в отдельно взятых выражениях использовали переменные только одного типа данных, например intили float.Но часто бывают случаи, когда в операции участвуют переменные разных типов. Такие oперации называются смешанными. В отличие от многих других языков программиpования, языки С и C++ могут автоматически преобразовывать данные из одного ипа в другой.

Данные разных типов по-разному сохраняются в памяти. Возьмем число 10. Формат, в котором оно будет хранится, зависит от назначенного этому числу типа данных. Другими словами, сочетание нулей и единиц в представлении одного и того же числа 10 будет разным в зависимости о того, интерпретируется ли оно как целое или как число с плавающей запятой.

Давайте рассмотрим следующее выражение, где для двух переменных, fresultifvalue, задан тип данных float, а для переменной ivalue — тип int:

Iresult =  fvalue  *   ivalue;

Это типичный пример смешанной операции, в процессе которой компилятор штоматически выполняет определенные преобразования. Целочисленное значение переменной ivalue будет прочитано из памяти, приведено к типу с плавающей запятой, умножено на исходное значение переменной fvalue, и результат, также в шде значения с плавающей запятой, будет сохранен в переменной fresult. Обратите шимание на то, что значение переменной iivalue при этом никак не меняется, ставаясь целочисленным.

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

double
float
long
int
short

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

Посмотрим, что происходит в случае приведения значения типа int к типу float. Предположим, переменные ivaluel и ivalue2 имеют тип int, а переменные fvalue и fresult — тип float. Выполним следующие операции:

ivaluel = 3;
ivalue2 = 4;
fvalue = 7.0;
fresult = fvalue + ivaluel/ivalue2;

Выражение ivaluel/ivalue2 не является смешанной операцией: это деление двух целых чисел, результатом которого будет ноль, поскольку дробная часть (в данном случае 0,75) отбрасывается. Таким образом, переменной fresult будет присвоено значение 7,0.

Как изменится результат, если переменная ivalue2 будет описана как float? В таком случае операция ivaluel/ivalue2 станет смешанной. Компилятор автоматически приведет значение переменной ivaluel к типу float — 3,0, и результат деления будет 0,75. В сумме с переменной fvalue получим 7,75.

Важно помнить, что тип переменной, находящейся слева от оператора присваивания, предопределяет тип результата вычислений. Предположим, что переменные fx и fy описаны как float, а переменная iresuit — как int. Рассмотрим следующий фрагмент:

fx = 7.0;
fy = 2.0;
iresult = 4.0+ fx/fy;

Результатом выполнения операции fx/fy будет 3,5. Можно предположить, что переменной iresult будет присвоена сумма 3,5 + 4,0 = 7,5. Но, поскольку это целочисленная переменная, компилятор преобразует число 7,5 в целое, отбрасывая дробную часть. Полученное значение 7 и присваивается переменной iresult.

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

Иногда возникают ситуации, когда необходимо изменить тип переменной, не дожидаясь автоматического преобразования. Этой цели служит специальный оператор приведения типа. Если где-либо в программе необходимо временно изменить тип переменной, нужно перед ее именем ввести в круглых скобках название соответствующего типа данных. Например, если переменные ivaluel и ivalue2 имеют тип int., а переменные fvalue и fresult — тип float, то благодаря явному преобразованию типов в следующих трех выражениях будут получены одинаковые результаты:

fresult = fvalue+(float)ivaluel/ivalue2;
fresult = fvalue+ivaluel/(float)ivalue2;
fresult = fvalue+(float)ivaluel/(float)ivalue2;

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

Классы памяти

В Visual C/C++ имеется четыре спецификатора класса памяти:

auto
register
static
extern

Спецификатор класса памяти может предшествовать объявлениям переменных и функций, указывая компилятору, как следует хранить переменные в памяти и как получать доступ к переменным или функциям. Переменные, объявленные со спецификаторами auto или register, являются локальными, а со спецификаторами static и extern — глобальными. Память для локальной переменной выделяется заново всякий раз, когда выполнение программы достигает блока, в котором объявлена переменная, и удаляется по завершении выполнения этого блока. Память для глобальной переменной выделяется один раз при запуске программы и удаляется по завершении программы.

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

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

Объявление переменных на внешнем уровне

Переменная, объявленная на внешнем уровне, является глобальной и по умолчанию имеет класс памяти extern. Внешнее объявление может включать инициализацию (явную либо неявную) или просто быть ссылкой на переменную, инициализируемую в другом месте программы.

static  int  ivalue1;              // поумолчанию неявно присваивается 0
static   int   ivalue1   =   10;   // явное  присваивание
int   ivalue2   =20;               // явное  присваивание

Область видимости глобальной переменной распространяется до конца файла. Обращение к переменной не может находиться выше той строки, где она была объявлена.

Переменная объявляется на внешнем уровне только один раз. Если в одном из файлов создана переменная с классом памяти static,то она может быть объявлена под тем же именем с тем же спецификатором static в любом другом исходном файле. Так как статические переменные доступны только в пределах своего файла, конфликтов имен не возникнет.

С помощью спецификатора extern можно объявить переменную, которая будет доступна из любого места программы. Это может быть ссылка на переменную, описанную в другом файле или ниже в том же файле. Последняя особенность делает возможным размещение ссылок на переменную до того, как она будет инициализирована.

В следующем примере программы на языке C++ демонстрируется использование ключевого слова extern:

// 
//       Файл А
//

#include <iostream.h>

extern int ivalue ;      

// переменная ivalue становится доступной до того,
// как будет инициализирована

void function_a(void); void function_b(void);

int main()

ivalue++; // ссылка на объявленную выше переменную

cout << ivalue << "\n"; // выводит значение 11

function_a();

return(0);

int ivalue =10; void function_a(void) ivalue++;

cout << ivalue << "\n"; function b () ;

//инициализация переменной ivalue

//ссылка на объявленную выше переменную

//выводит значение 12


//------------------------------------------------

//   Файл В

#include <iostream.h> extern int ivalue ;

void function_b (void) f

ivalue++;

cout << ivalue << "\n";

// ссылка на переменную ivalue ,
// описанную в файле А
// выводит значение 13


Объявление переменных на внутреннем уровне

При объявлении переменных на внутреннем уровне можно использовать любой из четырех спецификаторов класса памяти (по умолчанию устанавливается класс auto). Переменные, имеющие класс auto, являются локальными. Их область видимости ограничена блоком, в котором они объявлены.

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

Стандарт ANSI С не позволяет запрашивать адрес переменной, сохраненной в регистре. Но это ограничение не распространяется на язык C++. Просто при обнаружении оператора взятия адреса (&), примененного к регистровой переменной, компилятор сохранит переменную в ячейке памяти и возвратит ее адрес.

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

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

#include <iostream.h>

int ivalue1 = 1;

void function_a (void) ;

void main ( )

{  // ссылка на переменную ivalue1, описанную выше extern int ivalue1;

// создание статической переменной, видимой только внутри функции main(),
//а также инициализация ее нулевым значением static int ivalue2;

// создание регистровой переменной с присвоением ей нулевого значения
// и сохранением в регистре процессора (если возможно) register int rvalue = 0;

// создание автоматической переменной с присвоением ей нулевого значения intint_value3 = 0;

// вывод значений 1, 0, 0, 0:

cout << ivalue1 << "\n" << rvalue << "\n"

<< ivalue2 << "\n" << int_value3 << "\n"; function_a () ;

void function_a (void) {

// сохранение адреса глобальной переменной ivalue1

static int *pivalue1 = &ivalue1;

// создание новой, локальной переменной ivalue1;

// тем самым глобальная переменная ivalue1 становится недоступной

int ivalue1 = 32;

// создание новой статической переменной ivalue2,
// видимой только внутри функции function_a () static int ivalue2 = 2;

ivalue2 += 2;

// вывод значений 32, 4 и 1: cout << ivalue1 << "\n" << ivalue2 << "\n" << *pivalue1 << "\n";
cout << ivalue1 << "\n" << ivalue2 << "\n"
<< *pivalue1 << "\n";
}

Поскольку переменная ivalue1 переопределяется внутри функции function_a (), доступ к глобальной переменной ivalue1 блокируется. Тем не менее, с помощью указателя pivalue1 можно получить доступ к глобальной переменной, сославшись на нее по адресу.

Правила определения области видимости переменных

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

Объявление функций

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

Функции, объявленные как статические, можно вызывать внутри файла, в котором они реализованы, но они не доступны из других файлов программы. Кроме того, в разных файлах можно создавать статические функции с одинаковыми именами, что не приведет к конфликтам имен.

Функцию, объявленную как внешняя, можно использовать во всех файлах программы (если только в одном из них она не переопределена как статическая). Если при объявлении функции не указан класс памяти, то по умолчанию она считается внешней.

Операторы

В языках C/C++ есть ряд операторов, которых вы не встретите в других языках программирования. В их числе побитовые операторы, операторы инкрементирования и декрементирования, условный оператор, оператор запятая, а также операторы комбинированного присваивания.

Побитовые операторы

Побитовые операторы обращаются с переменными как с наборами битов, а не как с числами. Эти операторы используются в тех случаях, когда необходимо получить доступ к отдельным битам данных, например при выводе графических изображений на экран. Побитовые операторы могут выполнять действия только над целочисленными значениями. В отличие от логических операторов, с их помощью сравниваются нe два числа целиком, а отдельные их биты. Существует три основных побитовых оператора: И (&), ИЛИ (|) и исключающее ИЛИ (^). Сюда можно также отнести унарный эператор побитового отрицания (~), который инвертирует значения битов числа.

Побитовое И

Оператор & записывает в бит результата единицу только в том случае, если оба cравниваемых бита равны 1, как показано в следующей таблице:

Бит0

Бит1

Результат

0

0

0

0

1

0

1

0

0

1

1

1

Этот оператор часто используют для маскирования отдельных битов числа.

Побитовое ИЛИ

Оператор |  записывает в бит результата единицу в том случае, если хотя бы один лз сравниваемых битов равен 1, как показано в следующей таблице:

Бит 0   Бит 1  Результат
0 0 0
0 1 1
1 0 1
1 1 1

Этот оператор часто используют для установки отдельных битов числа.

Побитовое исключающее ИЛИ

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

Бит 0 Бит 1 Результат
0 0 0
0 1 1
1 0 1
1 1 0

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

0xF1&  0x35 результат 0x31 (шестнадцатеричное)
0361 & 0065  результат 061 (восьмеричное)
11110011 & 00110101 результат 00110001 (двоичное)
0xF1 | 0x35    результат 0xF5 (шестнадцатеричное)
0361 | 0065 результат 0365 (восьмеричное)

11110011 | 00110101

результат 11110111 (двоичное)
0xF1 ^ 0x35 результат 0хС4 (шестнадцатеричное)
0361 ^ 0065 результат 0304 (восьмеричное)
11110011 ^ 00110101 результат 11000110 (двоичное)
~0xF1 результат 0xFF0E (шестнадцатеричное)
~0361 результат 0177416 (восьмеричное)
~11110011 результат 11111111 00001100 (двоичное)

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

Операторы сдвига

Языки C/C++ содержат два оператора сдвига: сдвиг влево (<<) и сдвиг вправо (>>). Первый сдвигает битовое представление целочисленной переменной, указанной слева от оператора, влево на количество битов, указанное справа от оператора. При этом освобождающиеся младшие биты заполняются нулями, а соответствующее количество старших битов теряется.

Сдвиг беззнакового числа на одну позицию влево с заполнением младшего разряда нулем эквивалентен умножению числа на 2, как показано в следующем примере:

unsigned int valuel =.65; // младший байт: 0100 0001

valuel <<= 1;              // младший байт: 1000 0010

cout << valuel;            // будет выведено 130

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

unsigned int valuel = 10;// младший байт: 0000 1010

valuel >>= 1;             // младший байт: 0000 0101

printf("%d",value1);     // будет выведено 5

Инкрементирование и декрементирование

Операции увеличения или уменьшения значения переменной на 1 столь часто встречаются в программах, что разработчики языка С предусмотрели для этих целей специальные операторы инкрементирования (++) и декрементировант (--). Эти операторы можно применять только к переменным, но не константам.

Так, вместо следующей строки

value1  +  1;

можно ввести строку

value1++;

ИЛИ

++value1;

В подобной ситуации, когда оператор ++ является единственным в выражении, не имеет значения место его расположения: до имени переменной или после. Ее значение в любом случае увеличится на единицу.

Операторы ++ и — находят широкое применение в цикле for:

sum =  0;

for(1=1;   i   <=  20;   i++) sum =  sum +  i;

Цикл с декрементом будет выглядеть так:

sum = 0;

for(i = 20; 1 >= 1; i—) sum = sum + i;

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

Например, при постфиксном инкрементировании — i++ — сначала возвращается значение переменной, после чего оно увеличивается на единицу. С другой стороны, оператор префиксного инкрементирования — ++i — указывает, что сначала следует увеличить значение переменной, а затем возвратить его в качестве результата. Рассмотрим примеры. Предположим, имеются следующие переменные:

int   i  =   3,   j,   k  =  0;

Теперь проанализируем, как изменятся значения переменных в следующих выражениях (предполагается, что они выполняются не последовательно, а по отдельности):

k  =  ++i;         //   i = 4,    k = 4

k=i++;             //   i = 4,    k = 3

k =  --i;          //   i = 2,    k = 2   

k  =  i--;         //   i = 2,    k = 3

i  =  j   =  k—;   //   i = 0,    j = 0,   k = -1

Арифметические операторы

В языках C/C++ вы найдете все стандартные арифметические операторы, в частности операторы сложения (+), вычитания (-), умножения (*), деления (/) и деления по модулю (%). Первые четыре понятны и не требуют разъяснений. Возможно, имеет смысл остановится на операции деления по модулю:

int a=3,b=8,c=0,d;

d = b % a;   // результат: 2

d = a % b;   // результат: 3

d = b % с;   // результат: сообщение об ошибке

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

Оператор присваивания

Присваивание в C/C++ отличается от аналогичных операций в других языках программирования тем, что, как и другие операторы C/C++, оператор присваивания не обязан стоять в отдельной строке и может входить в более крупные выражения. В качестве результата оператор возвращает значение, присвоенное левому операнду. Например, следующее выражение вполне корректно:

valuel = 8 * (value2 = 5);

В данном случае сначала переменной value2 будет присвоено значение 5, после чего это значение будет умножено на 8 и результат 40 будет записан в переменную value1.

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

valuel = value2 = value3 = 0;

Второй прием часто можно встретить в условных выражениях цикла while, как в следующем примере:

while ((с  =  getchar())    !=  EOF)   
{
  . 
 
  .
 
}

Вначале переменной с присваивается значение, возвращаемое функцией getchar (}, после чего осуществляется проверка значения переменной на равенство константе eof. Цикл завершается при обнаружении конца файла. Использование круглых скобок необходимо из-за того, что оператор присваивания имеет меньший приоритет, чем подавляющее большинство других операторов, в частности оператор неравенства. Без круглых скобок данная строка будет воспринята следующим образом:

с =   (getchar()    != EOF)

То есть переменной с будет присваиваться значение 1 (true) всякий раз, когда функция getchar () возвращает значение, отличное от признака конца файла.

Комбинированные операторы присваивания

Набор операторов присваивания в языках C/C++ значительно богаче, чем в других языках программирования. Всевозможные комбинированные операторы присваивания позволяют предельно сжать программный код. В следующих строках показаны некоторые выражения присваивания, стандартные для большинства языков высокого уровня:

irow_index = irow_index + irow_increment;
ddepth = ddepth - dl_fathom;
fcalculate_tax = fcalculate_tax * 1.07;
fyards = fya.rds / ifeet_convert;

Теперь посмотрим, как эти же выражения будут выглядеть в C/C++:

irow_index += irow_increment;
ddepth -= dl_fathom; fcalculate_tax *= 1.07;
fyards /= ifeet_convert;

Если вы внимательно рассмотрите эти примеры, то легко поймете синтаксис комбинированных операторов присваивания. Когда в левой и правой частях выражения встречается одна и та же переменная, вы можете удалить ссылку на нее из правой части, а соответствующий оператор объединить с оператором присваивания. Комбинированный оператор указывает, что над переменными из левой и правой частей выражения нужно выполнить операцию, указанную перед знаком равенства, а результат вновь присвоить переменной из левой части выражения. Все комбинированные операторы присваивания перечислены в нижней части табл. 5.4, которая приведена в конце главы.

Операторы сравнения и логические операторы

Операторы сравнения предназначены для проверки равенства или неравенства сравниваемых операндов. Все они возвращают true в случае установления истинности выражения и false в противном случае. Ниже показан список операторов сравнения, используемых в языках С и C++:

Оператор Выполняемая проверка
== Равно (не путать с оператором присваивания)
! = Не равно
> Больше
< Меньше
<= Меньше или равно
>= Больше или равно

Логические операторы И (&&),ИЛИ (| |) и НЕ (!)возвращают значение true или false в зависимости от логического отношения между их операндами. Так, оператор && возвращает true, когда истинны (не равны нулю) оба его аргумента. Оператор | | возвращает false только в том случае, если ложны (равны нулю) оба его аргумента. Оператор ! просто инвертирует значение своего операнда с false на true и наоборот.

Следующий пример программы на языке С демонстрирует применение перечисленных выше операторов сравнения и логических операторов.

/*
*   oprs.c
*  Эта программа на языке С демонстрирует применение
*  операторов сравнения и логических операторов.
*/

#include <stdio.h>

int main ()

{
float foperand1, foperand2;

printf("\nВведите значения переменных foperand1 и foperand2: ");
scanf("%f%f", &foperand1, &foperand2);

printf("\n foperand1 > foperand2 =%d", (foperand1 > foperand2));
printf("\n foperand1 < foperand2 =%d", (foperand1 < foperand2));
printf("\n foperand1 => foperand2 =%d", (foperand1 >= foperand2));
printf("\n foperand1 <= foperand2 = %d",(foperand1 <= foperand2));
printf("\n foperand1 == foperand2 =%d", (foperand1 == foperand2));
printf("\n foperand1 != foperand2 =%d", (foperand1 != foperand2));
printf("\n foperand1 && foperand2 =%d", (foperand1 && foperand2));

return(0);

}


Иногда полученные результаты могут вас удивить. Следует помнить, особенно при сравнении значений типа float и double, что отличие даже в последней цифре после запятой будет воспринято как неравенство.

Ниже показана аналогичная программа на языке C++:

/*
*
oprs.срр
* Эта программа на языке C++ демонстрирует применение
* операторов сравнения и логических операторов..

#include <iostream.h>

int main () {

float foperand1, foperand2;

cout << "\nВведите значения переменных foperand1 и foperand2:

cin >> foperand1 >> foperand2;
cout << "foperand1 > foperand2 = " << (foperand1 > foperand2) << "\n";
cout << "foperand1 < foperand2 = " << (foperand1 < foperand2) << "\n";
cout << "foperand1 >= foperand2 = " << (foperand1 >= foperand2) << "\n";
cout << "foperand1 <= foperand2 = " << (foperand1 <= foperand2) << "\n";
cout << "foperand1 == foperand2 = " << (foperand1 == foperand2) << "\n";
cout << "foperand1 != foperand2 = " << (foperand1 != foperand2) << "\n";
cout << "foperand1 && foperand2 = " << (foperand1 S& foperand2) << "\n";
cout << "foperand1 | I Јoperand2 = " << (foperand1 I I foperand2) << "\n";

return(0);

}

Условный оператор

Оператор ?:  часто применяется в различных условных конструкциях. Он имеет cледующий синтаксис:

Условие ? выражение1 : выражение2

Если условие равно true выполняется выражение1 в противном случае выражение2.

Оператор запятая

Позволяет последовательно выполнить два выражения записанных в одной строке. Синтаксис оператора следующий:

левое_выражение, правое_выражение

Приоритеты выполнения операторов

Последовательность выполнения различных действий в выражении определяется компилятором.Вследствие этого могут получаться неправильные результаты, если не был учтен порядок разбора выражения компилятором. Побочные эффекты также могут возникать при неправильном использовании простого и комбинированного операторов присваивания.Вот почему важно установление четких приоритетов в выполнении операторов и порядка вычисления операндов. К примеру, операция логического И (&&) всегда выполняется слева направо:

while ((с= getchar())    != EOF)   &£,   (с   !=   '\n'))

Оператор & & предопределяет, что сначала будет выполнено выражение слева от него, т.е. переменной с будет присвоено значение, возвращаемое функцией getchar (), и только потом это значение проверяется на равенство символу новой строки.

В табл. 5.4 перечислены все операторы языков С и C++ в порядке уменьшения их приоритета (для компилятора MicrosoftVisual C++) и указывается направление вычисления операндов (ассоциативность): слева направо или справа налево.

Таблица 5.4. Операторы языков С и С++ (в порядке уменьшения приоритета)
Оператор Операция Ассоциативность
:: Расширение области видимости 
:: Доступ к члену класса по имени класса   
[] Доступ к элементу массива   Слева направо
() Вызов функции  Слева направо
() Приведение объекта к другому типу 
. Прямой доступ к члену класса (через объект)        Слева направо
-> Косвенный доступ к члену класса (через указатель на объект)  Слева направо
++ Постфиксный инкремент
-- Постфиксный декремент
new Динамическое создание объекта
delete Динамическое удаление объекта
delete[] Динамическое удаление массива
++ Префиксный инкремент
-- Префиксный декремент
* Раскрытие указателя 
& Взятие адреса
+ Унарный плюс
- Унарный минус
! Логическое НЕ
~ Побитовое НЕ 
sizeof Получение размерности выражения в байтах 
sizeof () Получение размерности типа данных в байтах 
typeid() Получение информации о типе операнда
(тип данных) Приведение типа  Справа налево
const_cast Приведение типа
dynamic_cast Приведение типа
reinterpret_cast Приведение типа 
static_cast

Приведение типа

. *

Прямой доступ к указателю на член класса (через объект)

Слева направо

-> * 

Косвенный доступ к указателю на член класса (через указатель на объект)

Слева направо

Умножение

Слева направо

Деление

Слева направо

%

Деление по модулю

Слева направо

+

Сложение

Слева направо

-

Вычитание

Слева направо

<< 

Сдвиг влево

Слева направо

>>

Сдвиг вправо

Слева направо

<

Меньше

Слева направо

Больше

Слева направо

<= 

Меньше или равно

Слева направо

>= 

Больше или равно

Слева направо

== 

Равно

Слева направо

! = 

Не равно

Слева направо

&

Побитовое И

Слева направо

^

Побитовое исключающее ИЛИ

Слева направо

|  

Побитовое ИЛИ

Слева направо

&&

Логическое И

Слева направо

||

Логическое ИЛИ

Слева направо

? :  

Условное выражение

=

Простое присваивание

Справа налево

*=  

Присваивание с умножением

Справа налево

/=

Присваивание с делением

Справа налево

%=

Присваивание с делением по модулю

Справа налево

+= Присваивание со сложением Справа налево
-=

Присваивание с вычитанием

Справа налево

<<=

Присваивание со сдвигом влево

Справа налево

>>= 

Присваивание со сдвигом вправо

Справа налево

&=

Присваивание с побитовым И

Справа налево

| =

Присваивание с побитовым ИЛИ

Справа налево

^=

Присваивание с побитовым исключающим ИЛИ

Справа налево

,

Запятая

Слева направо