Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Билеты ЯП.docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
119.39 Кб
Скачать

7. Конструкции языка С/С++. Приоритеты операций.

C++ — компилируемый статически типизированный язык программирования общего назначения.

Базовые конструкции языка C:

  •  алфавит;

  •  константы;

  •  идентификаторы;

  •  ключевые слова;

  •  операции;

  •  комментарии.

Множество представимых символов языка C состоит из алфавита языка C, разделительных символов и остальных, ввод которых возможен с клавиатуры, но которые могут использоваться только в символьных строках, константах и комментариях (буквы русского алфавита, escape-последовательности).

Алфавитом языка называется совокупность символов, используемых в языке. Язык C является чувствительным к регистру, то есть различает прописные и строчные буквы. В алфавит языка C входят:

  •  буквы латинского алфавита (прописные и строчные);

  •  десятичные цифры;

  •  знаки пунктуации.

Знаки пунктуации.

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

Из знаков алфавита составляются следующие конструкции языка C: константы, идентификаторы, ключевые слова, операции.

Разделительные символы – это символы, вводимые программистом с клавиатуры при написании программы для разделения слов. К ним относятся: пробел, горизонтальная и вертикальная табуляции, перевод строки, возврат каретки, новая страница, новая строка, Ctrl-Z – символ индикации конца файла.

Escape-последовательности применяются для представления символов и чисел, которые нельзя непосредственно ввести с клавиатуры, и разделительных символов в символьных строках и константах. Escape-последовательности используются в процессе выполнения программы. Escape-последовательность начинается с символа обратной косой черты (\), называемого escape-символом, после которого следует escape-код. Ниже в таблице перечислены escape-последовательности и их функции.

Можно также использовать escape-последовательности для представления ASCII-кодов символов в восьмеричном или шестнадцатеричном формате. Данное применение показано ниже в таблице.

Формат

Основание

Описание

\OOO

8

От одной до трех восьмеричных цифр, следующих за escape-символом

\xHH или \XHH

16

Одна или две шестнадцатеричных цифры, следующих за \x или \X

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

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

Комментарий – это последовательность символов, воспринимаемая компилятором как пробельные символы. Используется программистом для внесения пояснений в текст программы для себя или другого программиста, а также для выделения неиспользуемых фрагментов кода. Комментарий – это последовательность символов, начинающаяся с открывающей пары символов ‘/*’ и заканчивающаяся закрывающей парой символов ‘*/’.

Идентификаторы – это имена функций, переменных, указателей, констант, меток и типов данных, определяемых пользователем. Идентификатором может быть произвольная последовательность латинских букв, цифр и символа подчеркивания, которая начинается с буквы или символа подчеркивания. В качестве идентификаторов запрещается использовать ключевые слова. В пределах логического блока (блок-оператор или функция) идентификатор должен быть уникальным, иначе компилятор выдаст сообщение об ошибке (подробнее об этом – при рассмотрении классов памяти и областей видимости). Идентификатор может состоять из произвольного числа символов, однако два идентификатора считаются различными, если различны их 32 первые символа. Лучше всего не использовать идентификаторы, начинающиеся с символа подчеркивания, так как такие идентификаторы используются компилятором для своих целей.

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

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

Операции в Си выполняются в соответствии следующей таблице приоритетов операций:

Лексемы

Операция

Класс

Приоритет

Ассоциативность

имена, литералы

простые лексемы

первичный

16

нет

a[k]

индексы

постфиксный

16

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

f(…)

вызов функции

постфиксный

16

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

.

прямой выбор

постфиксный

16

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

->

опосредованный выбор

постфиксный

16

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

(имя типа) {init}

составной литерал (C99)

постфиксный

15

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

++ --

положительное и отрицательное приращение

постфиксный

15

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

sizeof

размер

унарный

15

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

~

побитовое НЕ

унарный

15

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

!

логическое НЕ

унарный

15

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

- +

изменение знака, плюс

унарный

15

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

&

адрес

унарный

15

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

*

опосредование (разыменование)

унарный

15

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

(имя типа)

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

унарный

15

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

* / %

мультипликативные операции

бинарный

13

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

+ -

аддитивные операции

бинарный

12

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

<< >>

сдвиг влево и вправо

бинарный

11

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

< > <= >=

отношения

бинарный

10

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

== !=

равенство/неравенство

бинарный

9

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

&

побитовое И

бинарный

8

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

^

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

бинарный

7

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

|

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

бинарный

6

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

&&

логическое И

бинарный

5

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

||

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

бинарный

4

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

? :

условие

тернарный

3

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

= += -= *= /= %= <<= >>= &= ^= |=

присваивание

бинарный

2

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

,

последовательное вычисление

бинарный

1

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

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

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

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

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

main(){

(последовательность инструкций)

},

ограниченные при помощи двух разделителей:

  • левая фигурная скобка ({) обозначает начало вычислительного блока,

  • правая фигурная скобка (}) обозначает конец вычислительного блока.

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

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

  • оператор if, содержащий проверку одного условия;

  • и оператор switch, содержащий проверку нескольких условий.

Самая простая форма оператора if

if((условие)) (оператор)

(следующий оператор)

Оператор if работает следующим образом:

  • если выполнено условие, указанное в скобках, то выполняется первый оператор, и затем выполняется оператор, указанный после оператора if.

  • если условие, указанное в скобках, не выполнено, то сразу выполняется оператор, указанный после оператора if.

В частности, следующий ниже код, в случае выполнения заданного условия, не будет выполнять никаких действий:

if((условие)) ;

поскольку, фактически, выполняется пустой оператор. Более сложная форма оператора if содержит ключевое слово else:

if((условие)) (оператор)

else (альтернативный оператор)

(следующий оператор)

Здесь, если условие, указанное в скобках, не выполнено, то выполняется оператор, указанный после ключевого слова else.

8. Конструции языка С/С++. Присваивание.

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

Синтаксис и семантика присваивания

Для присваивания одного объекта другому используется оператор =.

Foo f;

Foo f1;

f1 = f;

Присваивание выполняется в третьей строке. Если бы f и f1 были целыми или чем-нибудь столь жепростым, смысл этой строки был бы предельно ясен: содержимое области памяти, на которую

ссылается f, копируется в область памяти, на которую ссылается f1. Только и всего. Но если Fooотносится к нетривиальному классу, в C++ все заметно усложняется. В приведенном примерекомпилятор предоставляет оператор = по умолчанию, который вызывается для выполненияфактического копирования. Как и с конструкторами копий, вы можете спокойно сидеть и смотреть, каккомпилятор вкалывает за вас, или написать свой собственный оператор =. То, что делает версия поумолчанию, вам может и не понравиться, особенно в момент освобождения памяти деструкторомкласса.

class String {private:

char* s;public:

String(char*);

~String();

void Dump(ostream& os);};

String::String(char* str) : s(NULL){

if (str == NULL) { // NULL означает пустую строкуs = new char[1];*s = ‘’;

}

else {

s = new char[strlen(str) + 1];strcpy(s, str);

}}

String::~String(){

delete s;}

void String::Dump(ostream& os){

os << “”” << s << “””;}

String* s1 = new String(“Hello”);String* s2 = new String(“Goodbye”);s2 = s1;

delete s1; // Память освободилась, вроде все нормально...s2->Dump(); // Облом! Ха-ха-ха!delete s2; // Помогите, убивают! Ха-ха-ха!

По умолчанию компилятор копирует содержимое s2->s поверх содержимого s1->s. При этомкопируется значение указателя, а не символы, поэтому после присваивания возникают две большиепроблемы. Два разных объекта ссылаются на одну область памяти, и никто не ссылается на копиюGoodbye, созданную командой String* s2 = new String( "Goodbye");. Дальше — больше; приудалении s1 деструктор освобождает область памяти, на которую ссылается s1. Однако на эту памятьпродолжает ссылаться указатель s2->s. Попытка вывести s2->s дает совершенно безумныерезультаты. «Комедия ошибок» достигает кульминации при попытке удалить s2, поскольку менеджерпамяти попытается освободить ранее освобожденную область. Чего только не бывает в C++!

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

Присваивание и инициализация

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

Foo f;

Foo f1 = f; // Инициализация; f1 еще не существует

f1 = f; // Присваивание: объект f1 уже сконструирован

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

Оператор = по умолчанию, как и конструктор копий по умолчанию, ведет себя четко определеннымобразом. Как и конструктор копий, который рекурсивно вызывает другие конструкторы копий,оператор = по умолчанию не ограничивается простым копированием битов из одного объекта в другой.Последовательность его действий выглядит так:

1.    Присваивание для базовых классов выполняется в порядке их перечисления в спискенаследования. При этом используются перегруженные операторы = базовых классов или вслучае их отсутствия — оператор = по умолчанию.

2.    Присваивание переменных класса выполняется в порядке их перечисления в объявлениикласса. При этом используются перегруженные операторы = базовых классов или в случае ихотсутствия — оператор = по умолчанию.

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

Перегрузка оператора =

Перегрузка оператора = практически не отличается от перегрузки всех остальных операторов. Пока насинтересует сигнатура оператора =, которая выглядит так: Х& X::operator=(const X&).

class String {private:

char* s;public:

String(char*);~String();

String(const String&); // Возможно, тоже решает проблемуString& operator=(const String&);void Dump(ostream& os);};

String::String(char* s) : s(NULL){

if (str == NULL) { // NULL означает пустую строкуs = new char[1];*s = ‘’;}else {

s = new char[strlen(str) + 1];strcpy(s, str);

}}

String::~String(){

delete s;}

String::String(const String& s1) : s(NULL){

s = new char[strlen(s1.s) + 1];

strcpy(s, s1.s);}

String& String::operator=(const String& s1){

if (this == &s1) return *this;

delete s; // Уничтожить предыдущее значение

s = new char[strlen(s1.s) + 1];

strcpy(s, s1.s);

return *this;}

void String::Dump(ostream& os){

os << “”” << s << “””;}

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

Ниже показан обобщенный вид оператора =, который стоит занести в долговременную память (некомпьютерную, а вашу собственную):

1.    Убедитесь, что не выполняется присваивание вида x=x;. Если левая и правая части ссылаютсяна один объект, делать ничего не надо. Если не перехватить этот особый случай, то следующийшаг уничтожит значение до того, как оно будет скопировано.

2.    Удалите предыдущие данные.

3.    Скопируйте значение.

4.    Возвратите указатель *this.

Оператор = возвращает *this, чтобы стало возможным вложенное присваивание вида а=b=с. В C++,как и в С, значением этого выражения является присваиваемая величина. Выражение интерпретируетсясправа налево, как а=(b=с).

А теперь — плохие новости. Как и в случае с конструкторами копий, при перегрузке оператора = C++умывает руки и отправляется домой. Если вы перегрузили оператор =, то на вас ложитсяответственность за выполнение присваивания для переменных и базовых классов; по умолчаниюбазовые классы и переменные левостороннего объекта остаются без изменений.

Присваивание для переменных класса

Иногда переменные класса относятся к простейшим типам данных (например, int), и тогдаприсваивание выполняется с помощью оператора =, предоставленного компилятором. Иногда(например, для класса String) их приходится копировать вручную. В остальных случаях переменныеотносятся к какому-нибудь нетривиальному классу. Лучший выход из положения — присвоить что-нибудь таким переменным. При этом компилятор определяет, существует ли для переменнойперегруженный оператор = или он должен использовать свой собственный вариант по умолчанию.

class Foo {

public:

Foo& operator=(const Foo&);};

class Bar {public:

// Оператор = не перегружен};

class FooBar {private:

Foo f;

Bar b;public:

FooBar& operator=(const FooBar&);};

FooBar& FooBar::operator=(const FooBar& fb){

if (this == &fb) return *this;

f = fb.f; // Используется перегруженный оператор = класса Foo

f = fb.b; // Используется оператор = по умолчанию

return *this;}

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

Присваивание для базовых классов

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

class Foo {...}

class Bar : public Foo {

public:

Bar& operator=(const Bar&);};

Bar& Bar::operator=(const Bar& b){

if (this == &b) return *this;

this->Foo::operator=(b); // Чего-чего?

return *this;}

Другие варианты, которые могут придти в голову (например, *((Foo)this)=b;), не работают —поверьте мне на слово. Все они создают временные копии. Показанный вариант работает, посколькукомпилятор знает, как преобразовать Bar в Foo в аргументе. Он работает независимо от того,перегружали вы Foo::operator= или нет. Даже если не перегружали, оператор все равноприсутствует, и его можно вызвать по полному имени Foo::operator=.

Другие сигнатуры оператора =

Оператор = не ограничен одной сигнатурой. Его можно перегрузить так, чтобы в правой частиприсваивания мог стоять аргумент любого другого типа. Сигнатура Х& X::operator=(const Х&)выделяется на общем фоне тем, что компилятор предоставляет ее версию по умолчанию и используетэту сигнатуру в стандартном алгоритме рекурсивного присваивания.

class String {

// Как раньше

public:

String& operator=(const String&); // Нормальный вариантString& operator=(char*);                           // Перегруженный вариант

String& operator=(int);                                // Вызывает atoi()

};

В показанном фрагменте создается несколько перегруженных вариантов оператора = для различныхтипов данных в правой части выражения. Второй вариант позволяет избежать конструированиявременного объекта String из char* лишь для того, чтобы присвоить его объекту в левой части.Третий вариант выполняет преобразование другого рода. Тем не менее, лишь первый вариантперегружает (то есть заменяет) версию оператора по умолчанию.

9. Ветвление

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

  • Оператор if

  • Пример конструкции ветвления

#include <iostream>

using namespace std;

int main()

{

setlocale(0, "");

double num;

cout << "Введите произвольное число: ";

cin >> num;

if (num < 10) { // Если введенное число меньше 10.

cout << "Это число меньше 10." << endl;

} else { // иначе

cout << "Это число больше либо равно 10." << endl;

}

return 0;

}

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

Оператор if служит для того, чтобы выполнить какую-либо операцию в том случае, когда условие является верным. Условная конструкция в С++ всегда записывается в круглых скобках после оператора if. Внутри фигурных скобок указывается тело условия. Если условие выполнится, то начнется выполнение всех команд, которые находятся между фигурными скобками.

Пример конструкции ветвления

if (num < 10) { // Если введенное число меньше 10.

cout << "Это число меньше 10." << endl;

} else { // иначе

cout << "Это число больше либо равно 10." << endl;

}

Здесь говорится: «Если переменная num меньше 10 — вывести соответствующее сообщение. Иначе, вывести другое сообщение».

Усовершенствуем программу так, чтобы она выводила сообщение, о том, что переменная num равна десяти:

if (num < 10) { // Если введенное число меньше 10.

cout << "Это число меньше 10." << endl;

} else if (num == 10) {

cout << "Это число равно 10." << endl;

} else { // иначе

cout << "Это число больше 10." << endl;

}

Здесь мы проверяем три условия:

  • Первое — когда введенное число меньше 10-ти

  • Второе — когда число равно 10-ти

  • И третье — когда число больше десяти

Заметьте, что во втором условии, при проверке равенства, мы используем оператор равенства — ==, а не оператор присваивания, потому что мы не изменяем значение переменной при проверке, а сравниваем ее текущее значение с числом 10. Если поставить оператор присваивания в условии, то при проверке условия, значение переменной изменится, после чего это условие выполнится. Каждому оператору if соответствует только один оператор else. Совокупность этих операторов — else if означает, что если не выполнилось предыдущее условие, то проверить данное. Если ни одно из условий не верно, то выполняется тело оператора else.

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

#include <iostream>

using namespace std;

int main()

{

setlocale(0, "");

double num;

cout << "Введите произвольное число: ";

cin >> num;

if (num < 10) // Если введенное число меньше 10.

cout << "Это число меньше 10." << endl;

else if (num == 10)

cout << "Это число равно 10." << endl;

else // иначе

cout << "Это число больше 10." << endl;

return 0;

}

Такой метод записи выглядит более компактно. Если при выполнении условия нам требуется выполнить более одной команды, то фигурные скобки необходимы. Например:

#include <iostream>

using namespace std;

int main()

{

setlocale(0, "");

double num;

int k;

cout << "Введите произвольное число: ";

cin >> num;

if (num < 10) { // Если введенное число меньше 10.

cout << "Это число меньше 10." << endl;

k = 1;

} else if (num == 10) {

cout << "Это число равно 10." << endl;

k = 2;

} else { // иначе

cout << "Это число больше 10." << endl;

k = 3;

}

cout << "k = " << k << endl;

return 0;

}

Данная программа проверяет значение переменной num. Если она меньше 10, то присваивает переменной k значение единицы. Если переменная num равна десяти, то присваивает переменной k значение двойки. В противном случае — значение тройки. После выполнения ветвления, значение переменной k выводится на экран.