- Главная
- Информатика
- Память. Размеры объектов. Приведение типов. Глубокое копирование
Содержание
- 2. Сгенерированные операции При определении классов некоторые операции над их объектами всегда существуют: • Конструктор копирования •
- 3. Память. Создание объектов. Явное Есть два способа создать объект явно. Во-первых, это можно сделать при объявлении:
- 4. Размеры типов данных sizeof( Т ) - определяет сколько байт потребуется для хранения в памяти объекта
- 5. Память методов класса Каждый объект имеет собственные независимые поля данных. С другой стороны, все объекты одного
- 6. Копирование памяти memcpy и memcpy_s void *memcpy(void *dst, const void *src, size_t n); dst — адрес
- 7. Перемещение памяти Области памяти не должны перекрываться , иначе данные могут быть скопированы неправильно. Для правильного
- 8. Инициализация памяти void * memset ( void * ptr, int val, size_t num ); Функция memset
- 9. Адреса памяти Любой объект - это некоторая область памяти, адрес - выражение, ссылающееся на объект или
- 10. Указатели Указатели - необходимы для более эффективного использования языка программирования. Замечательным примером важности использования указателей является
- 11. Преобразования указателей Всюду, где указатели присваиваются, инициализируются, сравниваются или используются иным образом, могут происходить следующие преобразования:
- 12. Ссылки Ссылку (reference) можно интерпретировать как автоматически разыменовываемый постоянный указатель или альтернативное имя объекта. Указатели и
- 13. Указатели на члены классов Всюду, где указатели на члены инициализируются, присваиваются, сравниваются или используются иным образом,
- 14. Приведение типов x = dynamic_cast (р) // Пытается привести указатель р к типу D* (если приведение
- 15. Обнаружение утечек памяти #define _CRTDBG_MAP_ALLOC #include После последней области видимости вызвать функцию: _CrtDumpMemoryLeaks(); int _tmain(int argc,
- 16. STL. Строки. Класс string из стандартной библиотеки шаблонов представляет собой специализацию общего шаблонного класса basic_string для
- 17. Операции со строками s1 = s2 Присвоение строки s2 строке s1; строка s2 может быть объектом
- 18. Потоки строк Объект класса string можно использовать в качестве источника ввода для потока istream или цели
- 19. Итераторы Итераторы — это клей, скрепляющий алгоритмы стандартной библиотеки с их данными. Итераторы можно также назвать
- 20. Категории итераторов input iterator - Можем перемещаться вперед с помощью оператора ++ и считывать каждый элемент
- 21. Потоковые классы Поток — это общее название потока данных. В C++ поток представляет собой объект некоторого
- 22. Общая структура потоковых классов
- 23. Файлы Обычно мы имеем намного больше данных, чем способна вместить основная память нашего компьютера, поэтому большая
- 24. Работа с файлами Для того чтобы прочитать файл, мы должны • знать его имя; • открыть
- 25. std::vector представляют собой контейнеры последовательностей, представляющие массивы, которые могут меняться по размеру. Подобно массивам, векторы используют
- 26. std::list template > class list; Списки представляют собой контейнеры последовательностей, которые позволяют выполнять операции вставки и
- 27. Глубокое копирование Копирование называется глубоким, по причине того, что в классе могут находится члены-данных неизвестного размера,
- 28. int _tmain ( int argc, _TCHAR* argv[ ]) { vector data1; /// первая База Данных list
- 29. // Описываем базовый и производный классы class base { int *i; public: base(){ i=new int; *i=11;}
- 30. int _tmain ( int argc, _TCHAR* argv[ ]) { vector data1; /// первая База Данных list
- 31. Последняя попытка: предположим, что у нас есть специальная функция (не конструктор) для копирования содержимого одной БД
- 32. Домашнее задание Проект 27 Представьте, что вы – управляющая компания и у вас в подчинении 5
- 34. Скачать презентацию
Слайд 2Сгенерированные операции
При определении классов некоторые операции над их объектами всегда существуют:
• Конструктор
Сгенерированные операции
При определении классов некоторые операции над их объектами всегда существуют:
• Конструктор
• Оператор присваивания копированием
• Деструктор.
Это – правило трех.
Слайд 3Память. Создание объектов.
Явное
Есть два способа создать объект явно. Во-первых, это можно сделать
Память. Создание объектов.
Явное
Есть два способа создать объект явно. Во-первых, это можно сделать
DisplayItem item1;
DisplayItem* item2 = new DisplayItem(Point(100, 100));
DisplayItem* item3 = 0;
Неявное
Часто объекты создаются неявно. Так, передача параметра по значению в C++ создает в стеке временную копию объекта. Более того, создание объектов транзитивно: создание объекта тянет за собой создание других объектов, входящих в него. Переопределение семантики копирующего конструктора и оператора присваивания в C++ разрешает явное управление тем, когда части объекта создаются и уничтожаются. К тому же в C++ можно переопределять и оператор new, тем самым изменяя политику управления памятью в "куче" для отдельных классов.
Слайд 4Размеры типов данных
sizeof( Т ) - определяет сколько байт потребуется для хранения
Размеры типов данных
sizeof( Т ) - определяет сколько байт потребуется для хранения
int x=9;
MyType* obj=0;
int size= sizeof(x);
size= sizeof(int);
size= sizeof(MyType);
size= sizeof( obj );
size= sizeof(MyType* );
Слайд 5Память методов класса
Каждый объект имеет собственные независимые поля данных. С другой стороны,
Память методов класса
Каждый объект имеет собственные независимые поля данных. С другой стороны,
Методы класса создаются и помещаются в память компьютера всего один раз - при создании первого объекта класса.
struct A{
void f(){ cout<<"aaa"<< endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
A *a= new A;
a -> f();
return 0;
}
Слайд 6Копирование памяти
memcpy и memcpy_s
void *memcpy(void *dst, const void *src, size_t n);
dst —
Копирование памяти
memcpy и memcpy_s
void *memcpy(void *dst, const void *src, size_t n);
dst —
srс — адрес источника
n — количество байт для копирования
Функция копирует n байт из области памяти, на которую указывает src, в область памяти, на которую указывает dst. Функция возвращает адрес назначения dst.
Слайд 7Перемещение памяти
Области памяти не должны перекрываться , иначе данные могут быть скопированы
Перемещение памяти
Области памяти не должны перекрываться , иначе данные могут быть скопированы
Для правильного копирования перекрывающихся областей нужно использовать функцию memmove().
void * memmove ( void * destination, const void * source, size_t num );
#include
#include
int main ()
{
char str[ ] = "memmove can be very useful......";
memmove (str+20,str+15,11);
puts (str);
return 0;
}
Output:
memmove can be very very useful.
Слайд 8Инициализация памяти
void * memset ( void * ptr, int val, size_t num
Инициализация памяти
void * memset ( void * ptr, int val, size_t num
Функция memset заполняет num байтов блока памяти, через указатель ptr. Код заполняемого символа передаётся в функцию через параметр val.
ptr - Указатель на блок памяти для заполнения.
val - val передается в виде целого числа, но функция заполняет блок памяти символом, преобразуя это число в символ.
num - Количество байт, которые необходимо заполнить указанным символом.
#include
#include
int main (){
char str[ ] = "almost every programmer should know memset!";
memset (str,'-',6);
puts (str);
return 0;
}
Output:
------ every programmer should know memset!
Слайд 9Адреса памяти
Любой объект - это некоторая область памяти, адрес - выражение,
ссылающееся на
Адреса памяти
Любой объект - это некоторая область памяти, адрес - выражение,
ссылающееся на
соответствующее объекту, на который указывает E.
Термин "адрес" ("lvalue" т.е. left value - левая величина) появляется из оператора присваивания E1 = E2, где левый операнд E1 должен "адресовать" изменяемую переменную.
Адрес может изменяться, если он не является именем функции,
именем массива или const.
Слайд 10Указатели
Указатели - необходимы для более эффективного использования языка программирования.
Замечательным примером важности
Указатели
Указатели - необходимы для более эффективного использования языка программирования.
Замечательным примером важности
Идея указателей несложна. Начать нужно с того, что каждый байт памяти компьютера имеет адрес. Адреса это те же числа, которые мы используем для домов на улице. Числа начинаются с 0, а затем возрастают 1, 2, 3 и т. д. Если у нас есть 1 Мбайт памяти, то наибольшим адресом будет число 1 048 575 ( хотя обычно памяти много больше) .
Загружаясь в память, наша программа занимает некоторое количество этих адресов. Это означает, что каждая переменная и каждая функция нашей программы начинается с какого-либо конкретного адреса.
Мы можем получить адрес переменной, используя операцию получения адреса &:
int main(){
int var1 = 11; int var2 = 22; int var3 = 33;
cout << &var1 << endl // напечатаем адреса этих переменных
<< &var2 << endl << &var3 << endl;
return 0; }
Слайд 11Преобразования указателей
Всюду, где указатели присваиваются, инициализируются, сравниваются или используются иным образом,
Преобразования указателей
Всюду, где указатели присваиваются, инициализируются, сравниваются или используются иным образом,
- Константное выражение, которое сводится к нулю, преобразуется в указатель, обычно называемый пустым указателем – void* . Гарантируется, что значение такого указателя будет отлично от любого указателя на объект или функцию.
- Указатель на объект любого типа, не являющегося const или volatile, можно преобразовать в void*.
- Указатель на функцию можно преобразовать в void*, при условии, что для void* отводится достаточно памяти, чтобы хранить этот указатель.
- Указатель на данный класс можно преобразовать в указатель на доступный базовый класс данного класса, если такое преобразование не содержит двусмысленность. Базовый класс считается доступным, если доступны его общие члены. Результатом преобразования будет указатель на объект типа базового класса, вложенный в объект типа производного класса. - Пустой указатель (0) преобразуется сам в себя.
- Выражение типа "массив T" может преобразовываться в указатель на начальный элемент массива.
- Выражение типа "функция, возвращающая T" преобразуется в "указатель на функцию, возвращающую T", за исключением тех случаев, когда оно используется как операнд адресной операции & или операции вызова функции ().
Слайд 12Ссылки
Ссылку (reference) можно интерпретировать как автоматически разыменовываемый постоянный указатель или альтернативное имя
Ссылки
Ссылку (reference) можно интерпретировать как автоматически разыменовываемый постоянный указатель или альтернативное имя
Присвоение чего-либо указателю изменяет значение указателя, а не объекта, на который он установлен.
Для того чтобы получить указатель, как правило, необходимо использовать оператор new или &.
Для доступа к объекту, на который установлен указатель, используются операторы * и [ ].
Присвоение ссылке нового значения изменяет то, на что она ссылается, а не саму ссылку.
После инициализации ссылку невозможно установить на другой объект.
Присвоение ссылок основано на глубоком копировании (новое значение присваивается объекту, на который указывает ссылка); присвоение указателей не использует глубокое копирование (новое значение присваивается указателю, а не объекту).
Слайд 13Указатели на члены классов
Всюду, где указатели на члены инициализируются, присваиваются, сравниваются или
Указатели на члены классов
Всюду, где указатели на члены инициализируются, присваиваются, сравниваются или
- Константное выражение , которое сводится к нулю, преобразуется в указатель на член. Гарантируется, что его значение будет отлично от любых других указателей на члены.
- Указатель на член данного класса можно преобразовать в указатель на член производного от данного класса, при условии, что допустимо обратное преобразование от указателя на член производного класса в указатель член базового класса, и что оно выполнимо однозначным образом.
- Правило преобразования указателей на члены (т.е. от указателя на член базового класса к указателю на член производного класса) выглядит перевернутым, если сравнивать его с правилом для указателей на объекты (т.е. от указателя на производный объект к указателю на базовый объект). Это необходимо для гарантии надежности типов.
Отметим, что указатель на член не является указателем на объект или указателем на функцию и правила преобразований таких указателей не применимы для указателей на члены. В частности указатель на член нельзя преобразовать в void*.
Слайд 14Приведение типов
x = dynamic_cast (р) // Пытается привести указатель р к типу
Приведение типов
x = dynamic_cast
(если приведение не удалось, то результат = 0)
x= dynamic_cast
x= static_cast
x=reinterpret_cast
x=const_cast
x=(T)v Приведение в стиле языка С.
х=Т (v) Функциональное приведение.
Динамическое приведение обычно используется для навигации по иерархии классов, если указатель р - указатель на базовый класс, а класс D — производный от базового класса. Если операнд v не относится к типу D*, то эта операция возвращает число 0.
Слайд 15Обнаружение утечек памяти
#define _CRTDBG_MAP_ALLOC
#include
После последней области видимости вызвать функцию:
_CrtDumpMemoryLeaks();
int
Обнаружение утечек памяти
#define _CRTDBG_MAP_ALLOC int
#include
После последней области видимости вызвать функцию:
_CrtDumpMemoryLeaks();
{
int * b = new int;
// delete b;
_CrtDumpMemoryLeaks();
return 0;
}
Detected memory leaks!
Dumping objects ->
{187} normal block at 0x00A15730, 4 bytes long.
Data: < > 98 DC B2 00
Object dump complete.
Слайд 16STL. Строки.
Класс string из стандартной библиотеки шаблонов представляет собой специализацию общего шаблонного
STL. Строки.
Класс string из стандартной библиотеки шаблонов представляет собой специализацию общего шаблонного
Класс wstring из стандартной библиотеки шаблонов представляет собой специализацию класса basic_string для типа wchar_t.
typedef basic_string
typedef basic_string
Слайд 17Операции со строками
s1 = s2 Присвоение строки s2 строке s1; строка s2
Операции со строками
s1 = s2 Присвоение строки s2 строке s1; строка s2
класса string или строкой к стиле языка С
s += s1 Добавление объекта s1 в конец строки; объект s1 может быть символом, объектом класса string или строкой в стиле языка С
s[i] Индексация ( как у обычного массива)
s = s1+s2 Конкатенация; символы в целевом объекте класса string будут копиями символов их строки s1, за которыми следуют копии символов из строки s2
s1==s2 Сравнение объектов класса string; либо s1, либо s2, но не оба
объекта могут быть строкой в стиле языка С.
s1 != s2 Проверка неравенства объектов s1 и s2
s1 < s2 Лексикографическое сравнение объектов класса string; либо s1, либо s2, но не оба объекта могут быть строкой в стиле языка С.
. <=, > и >= Аналогично.
Слайд 18Потоки строк
Объект класса string можно использовать в качестве источника ввода для
Потоки строк
Объект класса string можно использовать в качестве источника ввода для
Например, поток istringstream полезен для извлечения числовых значений из строк:
stringstream ss;
ss << "22.84";
float k = 0;
ss >> k;
Слайд 19Итераторы
Итераторы — это клей, скрепляющий алгоритмы стандартной библиотеки с их данными. Итераторы
Итераторы
Итераторы — это клей, скрепляющий алгоритмы стандартной библиотеки с их данными. Итераторы
Итератор — это аналог указателя, в котором реализованы операции косвенного доступа (например, оператор * для разыменования) и перехода к новому элементу (например, оператор ++ для перехода к следующему элементу).
Последовательность элементов определяется парой итераторов, задающих полуоткрытый диапазон [begin , end).
Здесь итератор begin указывает на первый элемент последовательности, а итератор end — на элемент, следующий за последним элементом последовательности.
Никогда не считывайте и не записывайте значение *end. Для пустой последовательности всегда выполняется условие begin == end. Другими словами, для любого итератора р последовательность [р:р) является пустой
Слайд 20Категории итераторов
input iterator - Можем перемещаться вперед с помощью оператора ++
Категории итераторов
input iterator - Можем перемещаться вперед с помощью оператора ++
output iterator - Можем перемещаться вперед с помощью оператора ++ и записывать каждый элемент только один раз с помощью оператора *. Этот вид итераторов реализован в классе ostream
forward iterator - Можем перемещаться вперед, применяя оператор ++ повторно, а также считывать и записывать элементы (если они не константные), с помощью оператора *. Если итератор указывает на объект класса, то для доступа к его члену можно использовать оператор ->
bidirectional iterator - Можем перемещаться вперед ( используя оператор ++) и назад (используя оператор - - ), а также считывать и записывать элементы (если они не константные) с помощью оператора *. Этот вид итераторов реализован в классах list, map и set
randomaccess iterator - Можем перемещаться вперед (с помощью операторов ++ и +=) и назад (с помощью операторов - - и -=), а также считывать и записывать элементы (если они не константные) с помощью оператора * или [ ]. Мы можем применять индексацию, добавлять к итератору произвольного доступа целое число с помощью оператора +, а также вычитать из него целое число с помощью итератора -. Можем вычислить расстояние между двумя итераторами произвольного доступа, установленными на одну и ту же последовательность, вычитая один из другого. Итераторы произвольного доступа можно сравнивать с помощью операторов <, <=, > и >=. Этот вид итераторов реализован в классе vector.
Слайд 21Потоковые классы
Поток — это общее название потока данных. В C++ поток представляет
Потоковые классы
Поток — это общее название потока данных. В C++ поток представляет
Одним из аргументов в пользу потоков является простота использования.
Если вам приходилось когда-нибудь использовать символ управления форматом %d при форматировании вывода с помощью %d в printf(), вы оцените это. Ничего подобного в потоках вы не встретите, ибо каждый объект сам знает, как он должен выглядеть на экране или в файле. Это избавляет программиста от одного из основных источников ошибок.
Другим арументом является то, что можно перегружать стандартные операторы и функции вставки (<<) и извлечения (>>) для работы с создаваемыми классами. Это позволяет работать с собственными классами как со стандартными типами, что, опять же, делает программирование проще и избавляет от множества ошибок, не говоря уж об эстетическом удовлетворении.
Поток данных - лучший способ записывать данные в файл, лучший способ организации данных в памяти для последующего использования при вводе/выводе текста в окошках и других элементах графического интерфейса пользователя ( GUI)
Слайд 22Общая структура потоковых классов
Общая структура потоковых классов
Слайд 23Файлы
Обычно мы имеем намного больше данных, чем способна вместить основная память нашего
Файлы
Обычно мы имеем намного больше данных, чем способна вместить основная память нашего
На самом нижнем уровне файл просто представляет собой последовательность байтов, пронумерованных начиная с нуля.
Файл имеет формат; иначе говоря, набор правил, определяющих смысл байтов. Например, если файл является текстовым, то первые четыре байта представляют собой первые четыре символа.
С другой стороны, если файл хранит бинарное представление целых чисел, то первые четыре байта используются для бинарного представления первого целого числа. Формат по отношению к файлам на диске играет ту же роль, что и типы по отношению к объектам в основной памяти. Мы можем приписать битам, записанным в файле, определенный смысл тогда и только тогда, когда известен его формат. При работе с файлами поток ostream преобразует объекты, хранящиеся в основной памяти, в потоки байтов и записывает их на диск. Поток istream действует наоборот: он считывает поток байтов с диска и составляет из них объект.
Слайд 24Работа с файлами
Для того чтобы прочитать файл, мы должны
• знать его имя;
• открыть его
Работа с файлами
Для того чтобы прочитать файл, мы должны
• знать его имя;
• открыть его
• считать символы;
• закрыть файл (хотя это обычно выполняется неявно).
Для того чтобы записать файл, мы должны
• назвать его;
• открыть файл (для записи) или создать новый файл с таким именем;
• записать наши объекты;
• закрыть файл (хотя это обычно выполняется неявно).
Слайд 25std::vector
представляют собой контейнеры последовательностей, представляющие массивы, которые могут меняться по размеру.
Подобно массивам,
std::vector
представляют собой контейнеры последовательностей, представляющие массивы, которые могут меняться по размеру.
Подобно массивам,
Внутри векторы используют динамически выделенный массив для хранения своих элементов. Этот массив, возможно, потребуется перераспределить для увеличения размера при вставке новых элементов, что подразумевает выделение нового массива и перемещение в него всех элементов. Это относительно дорогостоящая задача с точки зрения времени обработки, и, следовательно, векторы не перераспределяются каждый раз, когда элемент добавляется в контейнер.
Слайд 26std::list
template < class T, class Alloc = allocator > class list;
Списки представляют
std::list
template < class T, class Alloc = allocator
Списки представляют
По сравнению с другими базовыми стандартными контейнерами последовательности (array, vector и deque) list действуют лучше при вставке, извлечении и перемещении элементов в любом положении внутри контейнера, для которого уже был получен итератор.
Основным недостатком list и forward_lists по сравнению с этими другими контейнерами последовательности является то, что они не имеют прямого доступа к элементам по их положению; Например, чтобы получить доступ к шестому элементу в списке, нужно выполнить итерацию из известной позиции (например, начало или конец) в эту позицию, которая занимает линейное время на расстоянии между ними. Они также потребляют некоторую дополнительную память, чтобы связать информацию привязки к каждому элементу (что может быть важным фактором для больших списков малогабаритных элементов).
Слайд 27Глубокое копирование
Копирование называется глубоким, по причине того, что в классе могут находится
Глубокое копирование
Копирование называется глубоким, по причине того, что в классе могут находится
Такие члены данных имеют указательный тип и соответственно являются динамическими, память которых выделяется в «куче» через оператор new, а освобождается - delete.
Правильное, глубокое копирование означает, что будет не просто скопирована память, где лежит указательный объект члена-данных класса, а еще и скопировано содержимое этой памяти.
Слайд 28int _tmain ( int argc, _TCHAR* argv[ ]) {
vector data1; ///
int _tmain ( int argc, _TCHAR* argv[ ]) {
vector
list
data1.insert( data1.end(), new base);
data1.insert( data1.end(), new der);
// первая попытка скопировать: попытки не всегда правильные!
for ( vector
// казалось бы, легко взять и просто по элементам скопировать
// из первой БД во вторую.
data2.insert( data2.end(), *it);
}
// Тогда во второй БД будут лежать те же указатели, что и в первой
// И при удалении первой базы эти указатели станут недействительными
// Поэтому этот вариант копирования отпадает.
return 0;
}
Слайд 29 // Описываем базовый и производный классы
class base {
int *i;
public:
base(){ i=new int; *i=11;}
base(
// Описываем базовый и производный классы
class base {
int *i;
public:
base(){ i=new int; *i=11;}
base(
// конструктор копирования
base( const base& obj_for_copy): i( new int) { *I = *obj_for_copy.i; }
virtual ~base(){ delete i;}
};
class der : public base{
public:
der() { }
der(int x):base(x){ }
~ der(){ }
// конструктор копирования
der (const der& obj_for_copy):base( obj_for_copy){ }
};
Слайд 30int _tmain ( int argc, _TCHAR* argv[ ]) {
vector data1; ///
int _tmain ( int argc, _TCHAR* argv[ ]) {
vector
list
data1.insert( data1.end(), new base);
data1.insert( data1.end(), new der);
// следующая попытка: попытка непосредственного копирования
for ( vector
// В этом случае мы должны явно вызвать конструктор копирования, но мы не знаем какого именно класса конструктор надо вызвать – пробуем вызвать конструктор базового класса:
base* new_obj = new base( *( *it) );
data2.insert( data2.end(), new_obj);
}
// Увы , во второй БД будут лежать лишь объекты базового типа.
return 0;
}
Слайд 31Последняя попытка: предположим, что у нас есть специальная функция (не конструктор) для
Последняя попытка: предположим, что у нас есть специальная функция (не конструктор) для
for ( vector
base* new_obj= (*it)->copy();
data2.insert( data2.end(), new_obj);
}
// Определим функцию-член copy() в обоих классах, сделав ее виртуальной:
base* base::copy(){ return new base(*this); }
base* der:: copy(){ return new der(*this); }
// А эта функция как раз сможет вызвать конструктор копирования нужного класса и создать новый объект:
base:: base( const base& obj_for_copy): i( new int) { *i = *obj_for_copy.i; }
der:: der (const der& obj_for_copy):base( obj_for_copy){ }
Слайд 32Домашнее задание
Проект 27
Представьте, что вы – управляющая компания и у вас
Домашнее задание
Проект 27
Представьте, что вы – управляющая компания и у вас
В главной функции программы создайте хранилище объектов ваших типов и положите в него 5 объектов-домов. Любых на свой вкус.
Ниже создайте еще одно хранилище, используя std::vector.
Затем скопируйте данные из первого хранилища во второе.
Затем очистите 1 хранилище, а затем тоже самое сделайте со 2-ым.
Проверьте, нет ли утечек памяти. Если есть – исправьте программу.