
- •Int main (int argv, char * argc[])
- •Int main (int argv, char * argc[]);
- •Void func1 ( a& a );
- •Void func2 ( a a );
- •Void swap (int a, int b)
- •Void swap (int &a, int &b)
- •Int Year;
- •Int Month;
- •Создание и уничтожение объектов в c#. Интерфейс iDisposable и освобождение ресурсов.
- •Int m_nYear;
- •Int m_nMonth;
- •Int m_nDay;
- •Inline int even(int X)
- •System.Collections.Generic.IEqualityComparer(Of t)
- •Public:
- •Virtual int GetHashCode()
- •Ассоциативные и последовательные контейнеры stl. Понятие итератора. Общие свойства контейнеров. Особенности и применение каждого из контейнеров.
- •// Do what you need with element using *I ;
- •Vint v1, v2(100);
- •Vint v3(v2.Begin(), --v2.End());
- •Sort(V.Begin(), V.End());
- •Setstr s, s2;
- •Использование контейнеров stl. Методы резервирования памяти контейнерами stl. Требования к элементам контейнеров.
- •C.Insert(s);
- •Обобщенные контейнеры .Net. Интерфейс iEnumerable и оператор foreach. Сравнение обобщенных контейнеров и ArrayList.
- •IEnumerable - интерфейс
- •Int m_nCount;
- •Int sprintf(buffer, format-string[, argument...]); char *buffer;
- •Язык регулярных выражений. Классы символов, исчислители, последовательности и несимвольные подстановки. Валидация email адреса при помощи регулярных выражений.
- •If (rx.IsMatch(testString))
- •Язык регулярных выражений. Классы символов, исчислители, альтернативы и подстановки. Применение регулярных выражений для изменения формата даты.
- •If (rx.IsMatch(testString))
- •Язык регулярных выражений. Классы символов, исчислители, альтернативы и последовательности. Выбор всех атрибутов href из html текста.
- •If (rx.IsMatch(testString))
- •Обработка исключений. Правила перехвата исключений. Назначение системы обработки исключений. Для чего система обработки исключений не предназначена.
- •Int operator / (cMyClass o, int I) {
- •Void main() {
- •Наследование, иерархии классов и обобщенная обработка данных. Чистый полиморфизм (полиморфизм виртуальных методов). Интерфейсы и абстрактные классы.
- •Int m_nCount;};
- •Int m_nCount;
- •Int m_nCount;
- •Int m_nCount;
- •Virtual string Iam(){return "furniture";}
- •Рекурсия. Рекуррентные структуры данных и рекурсивные алгоритмы. Алгоритм просмотра дерева каталогов файловой структуры.
- •Рекуррентное определение выражений. Алгоритм анализа и вычисления выражений (программа ”калькулятор”).
Понятие указателя. Операции над указателями в C++. Указатели и ссылки. Копирование, сравнение и вычисление длины z-строк.
Указатели представляют в программе адреса памяти.
Указатель – это переменная, значением которой является адрес другой переменной. Так как указатель может ссылаться на переменные разных типов, с указателем в языке С связывается тип того объекта, на который он ссылается. Для описания указателей используется операция косвенной адресации *.
Рассмотрим применение указателей более подробно. Следующий пример показывает, как объявить указатель и как задать его начальное значение.
int x = 5;
int *p = &x;
Здесь x – обычная целочисленная переменная, а вот переменная p объявленная со звездочкой – указатель. Значением указателя p является адрес целого числа. В примере это адрес переменной x. В данном случае унарная операция & (операция с одним операндом) означает взятие адреса стоящей за ней величины.
Существует выделенное значение для всех указателей, которое означает, что указатель не готов к работе. Это значение 0. Иногда для указателей используют константу NULL, которая и определена как 0.
Получение значения переменной:
cout << *p << endl;
выведет 5 – значение, которое хранится в той области памяти, на которую показывает p. Применена та же операция *. Т.е. звездочка, примененная к указателю, означает значение по адресу в указателе. В C это правило называется: “Как объявляется, так и используется”.
Еще эту операцию называют “разыменование”. Дело в том, что по переменной p мы получили значение из области памяти, имя которой нам не известно. Тип значения полученного операцией разыменования всегда совпадает с типом указателя.
Над типизированными указателями в C определены операции ++, -- и операция сложения с целым. Эти операции изменяют указатель не на байт, а на размер того типа, на который они указывают. Также над указателями определены и операции сравнения.
В C и C++ нет встроенного типа строк. Вместо них используются z-строки. Фактически, z-строка – это массив символов, а признаком конца строки является символ с кодом 0.
Рассмотрим следующий текст:
char s[32] = “Hi, Kristy!”;
char *p = s;
for (; *p; p++);
int n = p – s;
В первой строке определяется массив из 32 символов, а в качестве начального значения будет задана z-строка “Hi, Kristy!”. Вторая строка определяет указатель на char, а его начальное значение указывает на начало массива s (имя массива в C – это всегда константный указатель на первый элемент массива).
Основная строка третья. Она определяет цикл с пустым телом, а заканчивается цикл, когда символ, на который указывает p равен 0. В конце каждой итерации выполняется переход к следующему символу. Таким образом, после выполнения этого цикла, указатель p будет показывать на завершающий 0 z-строки s.
В последней строке показано, как можно вычислить длину строки, имея указатель на ее начало и конец. Вычитание 2 указателей – это целое число, которое равно количеству элементов, на которые показывает указатель, расположенных между ними. В данном случае количество символов.
Пример (копирование значений из одной строки в другую):
char s[32] = “You are a crazy driver!”, t[32];
for (char *p=s, *d=t; *d++ = *p++; );
Теперь в первой строке кода объявляется 2 массива из 32 символов – s и t. Массив s заполнен z-строкой, а t – содержит случайный ‘мусор’.
Вторая строка определяет цикл с пустым телом. Перед началом цикла будут заведены 2 вспомогательных указателя p и d, которые показывают на начала массивов s и t соответственно. Что бы понять, что происходит при вычислении условия цикла *d++ = *p++ полезно ответить на 3 вспомогательных вопроса:
- почему это не синтаксическая ошибка;
- как вычисляется данное выражение;
- когда и почему цикл завершится.
Ответы:
1) Это не ошибка, потому, что операция присваивания в C является выражением, т.е. ее можно вычислить, и значением будет величина, присвоенная переменной в левой части. Кроме того, в C любое выражение может рассматриваться как логическое. Если значение выражения 0 – то это ложь, иначе истина.
2) Вычисление конструкции происходит следующим образом. Правая часть вычисляется получением значения, на которое указывает указатель p. Т.е. если по адресу, на который указывает p, хранится символ ‘Y’, то *p и есть символ ‘Y’. ++ сработает после всех вычислений. Оставшаяся часть *d = означает, что присвоить надро не указателю d, а изменить значение по адресу, куда указывает d. Т.е. если d показывает на первый элемент массива t, то это элемент массива t будет изменен. Как отмечалось, после выполнения присваивания выполнится ++ для обоих указателей, т.е. они начнут указывать на 1 символ правее.
3) Цикл закончится по обычной для z-строки причине. Будет скопирован завершающий 0. По этой причине выражение окажется ложным.
Пример. Сравнение двух строк.
В C принята функция сравнения, которая возвращает значение больше 0, если первая строка больше второй, меньше 0 – если вторая больше и равное 0, если строки равны.
char s[32] = “You are a crazy driver!”, t[32]= “Hi, Kristy!”;
for (char *p=s, *d=t; *d == *p && *p; d++, p++);
cout << *p-*s << endl;
Рассмотрим изменения в операторе for. Вместо присваивания в условии стоит операция сравнения. Цикл буде продолжаться, пока равны символы, на которые указывают указатели p и d. Однако, если строки оказались равными, то сравнение продолжилось бы и за пределами строк. Что бы ограничить процесс окончанием строк добавлено еще одно условие продолжения - *p, т.е. код символа, на который указывает p не равен 0.
++ вынесено в конец итерации. Дело в том, что в предыдущем примере после копирования 0 указатели обязательно буду передвинуты за пределы строк. ++ избежать нельзя! Нам же нужно найти первый различающийся символ и остановиться на нем. Поэтому передвигать значения указателей надо только если символы равны.
*p-*s обеспечивает необходимы результат сравнения.
Передача параметров в методы и функции в C++ и C#. Особенности массивов в C++, ReferenceType и ValueType в .Net.
Передача параметров. Пример функции:
Int main (int argv, char * argc[])
{return 0;}
Первая строка называется заголовком функции или ее прототипом. Впереди тип значения возвращаемого функцией, затем имя функции и затем в круглых скобках список ее аргументов или формальных параметров. Этот список еще называется сигнатурой функции.
Тело функции заключается в фигурные скобки. Наша функция возвращает значение целого типа, значит, должен быть хотя бы один оператор return, за которым следует целочисленное выражение. Return завершает работу функции и возвращает тому, кто ее вызывал указанное значение.
Правило в том, что сначала в программе функция должна быть объявлена, а затем может быть использована. Поскольку выполнение начинается с main, то все функции должны быть помещены перед main.
Объявление функции заключается в указании ее прототипа. Для main это выглядело бы так:
Int main (int argv, char * argc[]);
Определение функции, это ее полное описание, включая и тело, а объявление – указание только ее прототипа. Объявление завершается точкой с запятой. В прототипе допускается не указывать имена параметров, а только их типы, порядок и количество. Однако имена параметров часто помогают понять их смысл.
Передача параметров:
В C++ можно передавать параметры в функцию по значению (func2) и по ссылке (func1):
Void func1 ( a& a );
Void func2 ( a a );
в func2 внутрь функции передается на самом деле не a, а его копия. Т.е сначала вызывается copy-constructor, а по возвращении еще и деструктор. Это может привести к очень большим затратам на создание и уничтожение вот этих временных объектов. Если объект достаточно "тяжеловесный" или операция проводится в цикле, то затраты могут оказаться очень большими. Это же объясняет и тормознутость в куче мест STL.
Рассмотрим классический пример, функцию, которая меняет значения двух переменных местами – swap. Рассмотрим текст:
Void swap (int a, int b)
{int c = a;
a = b;
b = c; }
int x=3, y=5;
swap (x, y);
Переменные x и y останутся неизменными. Дело в том, что в C++ принята схема передачи параметров в функции “по значению”. Это означает, что будут вычислены значения параметров, заведены новые временные переменные, туда поместятся полученные значения и функция будет работать над этими временными переменными. Откуда взялись значения этих переменных функция не знает, а переменные x и y не знают, что их значения были прочитаны и куда-то переданы. Такая функция не может изменить значения своих фактических параметров.
Усовершенствовать ее не сложно, достаточно написать в заголовке
Void swap (int &a, int &b)
Эти амперсанды указывают компилятору, что нужно передать параметры “по ссылке”. Т.е. не будут создаваться новые вспомогательные переменные, а в функции будут использоваться оригинальные переменные (просто с другими именами - ссылками). Вот теперь она работает.
Последний новый момент в этой функции – void. Это означает, что функция не возвращает никакого значения. Если нужно выйти из нее используется оператор return без параметра.
Массивы:
В C++ можно объявлять и одномерные массивы (вектора):
int m[5] = {1,2,3,4,5};
объявляет массив из 5 целых и инициализирует его значениями от 1 до 5. Инициализацию можно опускать или проводить только для нескольких первых элементов. Индексируются массивы C всегда начиная с 0. Таким образом, m[0] == 1, m[4] == 5, а m[5] вообще нет.
В C имя массива по определению является константным указателем на нулевой элемент.
int m[5] = {1,2,3,4,5};
int *p = m;
При инициализации указателя p нет амперсанта перед именем массива. Имя массива уже указатель!
Можно сказать, что в C нет двумерных массивов. Однако этот “недостаток” компенсируется тем, что можно создавать массивы любого типа, в том числе массивы массивов. Например:
int v[5][3];
Понимать это объявление нужно следующим образом (так поступает компилятор). Сначала движемся от объявленного имени вправо. Таким образом, объявлена переменная v, которая представляет собой массив из 5 элементов, каждый из которых является массивом из 3 элементов. Теперь движемся от имени влево – каждый элемент массива из 3 элементов является целым.
Особенность использования массивов в C++ заключается в том, что по имени массива нельзя определить его размерность и размеры по каждому измерению. По определению, многомерный массив не существует, он рассматривается как одномерный массив, каждый элемент которого, в свою очередь, представляет собой массив. При необходимости передать в функцию многомерный массив нужно указать значение каждой размерности, что "отвергается" компилятором.
Опероатор sizeof. В C++ достаточно часто используется оператор sizeof. Он может применяться или к типу данных или к переменной. В любом случае он возвращает размер переменной указанного типа в байтах. Например, что бы получить количество элементов массива целых v, можно написать sizeof(v)/sizeof(int);
Типы:
Ссылочный тип (reference type) и тип, хранящий значение (value type). В С# при работе с типами данных унаследованными от System.ValueType работает немного по другому - например при выполнение метода в, его теле будет объявлена переменная int x к примеру. То помять под нее будет выделена в стеке, а не в куче. И не надо явно писать int x = new int();Ну и конечно при присвоении происходит присвоение значения а не ссылки на объект. Отсюда и название ValueType.
Известно два способа передачи параметров при вызове метода:
по значению (в силу специфики механизма передачи параметров только входные),
по ссылке (входные и/или выходные).
По значению – БЕЗ спецификаторов (для размерных типов этот способ предполагается по умолчанию). Локальная копия значения в методе. Параметр представляет собой означенную переменную. Его можно использовать в методе наряду с переменными, объявленными в теле метода. Изменение значения параметра НЕ влияет на значение параметра в выражении вызова.
Для организации передачи по ссылке параметра РАЗМЕРНОГО типа требуется явная спецификация ref или out. По ссылке – спецификатор ref (для ссылочных типов предполагается по умолчанию (другого способа передачи параметра для ссылочных типов просто нет), для размерных типов спецификатор необходимо явно указывать).
Параметр по ссылке и параметр по значению – большая разница! Это основание для перегрузки метода!
И еще: если пользователь создает значимый тип – то нужно создавать структуру. А при создании ссылочного типа просто нужно создать класс.
ReferenceType и ValueType в .Net.:
Стандарт языка C++ включает следующий набор фундаментальных типов.
1. Логический тип (bool).
2. Символьный тип (char).
3. Целые типы. Целые типы могут быть одного из трех размеров - short, int, long, сопровождаемые описателем signed или unsigned, который указывает, как интерпретируется значение, - со знаком или без оного.
4. Типы с плавающей точкой. Эти типы также могут быть одного из трех размеров - float, double, long double.
Кроме того, в языке есть
5. Тип void, используемый для указания на отсутствие информации.
Язык позволяет конструировать типы.
6. Указатели (например, int* - типизированный указатель на переменную типа int).
7. Ссылки (например, double& - типизированная ссылка на переменную типа double).
8. Массивы (например, char[] - массив элементов типа char).
Язык позволяет конструировать пользовательские типы
9. Перечислимые типы (enum) для представления значений из конкретного множества.
10. Структуры (struct).
11. Классы.
Первые три вида типов называются интегральными или счетными. Значения их перечислимы и упорядочены. Целые типы и типы с плавающей точкой относятся к арифметическому типу. Типы подразделяются также на встроенные и типы, определенные пользователем.
Создание и разрушение объектов в C++. Конструктор копирования. Порядок создания производных и составных объектов.
Создание и разрушение объектов:
Как известно, для хранения временной информации в программе существуют переменные. Обычный сценарий в следующем: объявить переменную, а затем присвоить ей значение и вообще, использовать выделенную под нее память для нужд алгоритма. В этой простейшей схеме необходимо заранее предвидеть, сколько нужно переменных или памяти. Изменить это количество в процессе работы программы нельзя. Такая память называется статической.
В реальной работе с данными часто возникает ситуация когда предвидеть количество данных в момент разработки программы невозможно. Например, мы не можем знать, какого размера файл придется читать при исполнении программы.
Для решения такого сорта проблем предусматривается возможность динамического выделения памяти. Вот как это реализовано в C++.
Допустим, что в процессе исполнения программы выясняется, что нужна память под n целых чисел. Получить ее можно так:
int *p = new int[n];
Здесь объявлен указатель на целые p и ему присвоено начальное значение, которое вернет оператор new. Оператор new позволяет выделить память под динамический объект (объект, создаваемый в процессе выполнения программы). Этот оператор исполняет две функции: он автоматически возвращает указатель соответствующего типа и вызывает соответствующий конструктор, если тип данных — класс. Оператор new дает пользователю полный контроль над продолжительностью жизни данных.
New – это оператор выделения динамической памяти в C++. Синтаксис его надо понимать так. Сразу за new следует тип элементов, под которые нужно выделить память. Если нужно выделить более 1 элемента, то далее следуют квадратные скобки и целое выражение. Вычисляется это выражение не во время компиляции, а в процессе выполнения программы, т.е. в него могут входить и переменные или вызовы функций. Возвращает такой оператор адрес первого элемента выделенного блока. Этот адрес необходимо обязательно сохранить до окончания использования выделенной памяти.
Если о статической памяти программист может не беспокоиться - ее выделяет и освобождает компилятор. То за динамическую он несет полную ответственность. Программист должен позаботиться об освобождении выделенной памяти. Делается это при помощи оператора delete. Наш пример выглядел бы так:
delete [] p;
Применять delete нужно обязательно к тому указателю, который вернул оператор new, иначе операция завершится ошибкой. Еще одна тонкость в том, что если выделяли память при помощи new с квадратными скобками, то и delete должен включать квадратные скобки как в нашем примере. Количество элементов помнить не нужно.
Таким образом, правило использование динамической памяти можно сформулировать так: выделять память по мере необходимости, запоминать адрес, который возвращает оператор new, когда необходимость в выделенном блоке отпадает – освобождать его оператором delete.
Cтатический объект в C++ создается при помощи типов: int, char. Инициализация статических переменных происходит до вызова main. Его можно уничтожить объект деструктором при помощи функции delete–уничтожение. Объект существует пока работает блок (до завершения работы блока), как только закрывается блок, вызывается деструктор.
Конструктор копирования:
Конструктором копирования (в англоязычной литературе используется термин copy constructor) называется специальный конструктор в языке программирования C++, применяемый для создания нового объекта как копии уже существующего. Такой конструктор принимает как минимум один аргумент: константную ссылку на копируемый объект.
Один из важнейших видов перегруженного конструктора. Он позволяет устранить проблемы, которые могут возникнуть при присваивании одного объекта другому.
Пример:
В = А;
При обычном присваивании будет создаваться побитовая копия А, значит объект В будет точной копией объекта А. Следовательно, объект В также будет ссылаться на область памяти, выделенную объектом А, не выделяя свой собственный участок. Но при таком выделении может возникнуть проблема: если будет вызван деструктор, то одна и та же память будет освобождаться дважды. Эта ситуация может возникнуть еще дважды: 1) когда функции передается копия параметра; 2) когда создается временный объект;
Для решения этих проблем применяется конструктор копирования, который не использует побитовое копирование.
Применительно к примеру мы получим, что объекты А и В будут использовать разные области памяти.
Конструктор копирования вызывается только при инициализации!
Обычно компилятор автоматически создает конструктор копирования для каждого класса (известные как неявные конструкторы копирования, т.е. конструкторы копирования, заданные неявным образом), но в некоторых случаях программист создает конструктор копирования, называемый в таком случае явным конструктором копирования (или "конструктором копирования, заданным явным образом"). В подобных случаях компилятор не создает неявные конструкторы.
В общем, такой конструктор, который определяет, как будет происходить присвоение полей одного уже существующего объекта другим
Пример:
CStr (const Cstr &x)
{
s = x.s;
f = x.f + 2;
}
где, s и f - поля класса CStr
Нужен, когда нет ссылок на внешние ресурсы.
Производные объекты:
Сначала вызывается конструктор базого класса, потом производного (или при вызове конструктора производного класса базовый объект уже создан.
Составные объекты:
Составные объекты – когда в поле класса присутствует объект другого класса. Когда работает конструктор - объекты-поля уже созданы.
В C можно объявлять составные типы данных, имеющие элементы разного типа. Это структуры. Пример:
struct Date {