Побайтовое копирование файлов c

Обновлено: 02.07.2024

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

Копирует существующий файл в новый файл.

Перегрузки

Копирует существующий файл в новый файл. Перезапись файла с тем же именем не разрешена.

Копирует существующий файл в новый файл. Перезапись файла с тем же именем разрешена.

Copy(String, String)

Копирует существующий файл в новый файл. Перезапись файла с тем же именем не разрешена.

Параметры

Имя целевого файла. Это не может быть имя каталога или имя существующего файла.

Исключения

У вызывающего объекта отсутствует необходимое разрешение.

-или- Параметр sourceFileName или destFileName определяет каталог.

Параметр sourceFileName или destFileName имеет значение null .

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

В sourceFileName или destFileName указан недопустимый путь (например, ведущий на несопоставленный диск).

Не удалось найти sourceFileName .

-или- Произошла ошибка ввода-вывода.

Параметр sourceFileName или destFileName имеет недопустимый формат.

Примеры

В следующем примере файлы копируются в папку резервного копирования C:\archives\2008. В нем используются две перегрузки Copy метода следующим образом:

Сначала используется File.Copy(String, String) перегрузка метода для копирования текстовых файлов (.txt). В коде показано, что эта перегрузка не допускает перезаписи уже скопированных файлов.

Затем она использует File.Copy(String, String, Boolean) перегрузку метода для копирования изображений (.jpg файлов). В коде показано, что эта перегрузка разрешает перезапись уже скопированных файлов.

Комментарии

Этот метод эквивалентен Copy(String, String, Boolean) перегрузке метода с overwrite параметром, для которого задано значение false .


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

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

Поверхностное (неглубокое) копирование – простой и дешевый способ, который можно реализовать просто копируя каждый бит объекта. Такой способ известен и как побитовое копирование.

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

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

Результат main

Функция main Результат main

Внесенные в rect1 изменения не отражаются на rect2 . Чтобы увидеть проблемы неглубокого копирования, изменим класс Rectangle так, чтобы он содержал указатели:

Измененный класс Rectangle

Выполнение той же функции main выдает другой результат:

Новый результат main

Новый результат main

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

<span><span><span>Диаграмма, иллюстрирующая поверхностное копирование</span></span></span>

Диаграмма, иллюстрирующая поверхностное копирование

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

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

Конструктор копирования и оператор присваивания

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

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

Напомним, что автоматически созданный конструктор копирования выполняет неглубокое копирование. Допустим, класс называется ClassName и имеет поля m 1 , m 2 , m 3 , …, mN . Тогда определение созданного компилятором конструктора выглядит следующим образом:

Конструктор копирования вызывается многократно в разных ситуациях. Самый очевидный случай – когда мы явно создаем новый объект на основе другого экземпляра класса:

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

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

Мы должны помнить, что конструктор копирования по-прежнему является конструктором и используется только для инициализации нового объекта. Но как быть, если мы хотим присвоить значение экземпляра существующему объекту?

Оператор присваивания – это метод, который используется для выполнения присваивания. Как и в случае с конструктором копирования, C++ предоставляет оператор присваивания по умолчанию.

Пример объявления оператора присваивания:

Оператор присваивания и конструктор копирования реализованы аналогично, хотя есть некоторые заметные различия. Во-первых, мы видим, что оператор присваивания возвращает ссылку на экземпляр, потому что в C ++ разрешены объединенные в цепочку присваивания:

В приведённом выше примере оператор присваивания вызывается для rectangle2 с rectangle3 в качестве аргумента. Затем оператор для rectangle1 вызывается со ссылкой, возвращенной из предыдущего вызова в качестве аргумента. Также необходимо учитывать возможность самоприсваивания:

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

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

  • оператор присваивания;
  • конструктор копирования;
  • деструктор.
Если мы определили деструктор, но не определен конструктор копирования, то деструктор будет вызван дважды для копий: один раз для содержащих копию объектов и во второй раз –для объектов, из которых копируются элементы данных. Поскольку копии не являются независимыми, деструктор дважды освобождает один и тот же участок памяти, что приводит к неопределенному поведению программы.

Реализация глубокого копирования

Ознакомившись с конструктором копирования и оператором присваивания, мы готовы реализовать глубокое копирование для класса Rectangle .

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

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

Новый результат main Диаграмма, иллюстрирующая глубокое копирование

Выводы

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

Создайте новый проект. Форму сразу же переименуйте в fMain , в свойстве Caption напишите "Работа с файлами" а проект сохраните в новую папку под именем "FileManager". Стиль формы сделайте bsDialog , а положение формы – poDesktopCenter .

Далее, установите на форму компонент Label , в свойстве Caption которого напишите "Что:". Рядом установите Edit , и удалите из него текст. В этот Edit мы будем заносить исходный файл , или что мы собираемся копировать или переносить.

Ниже установите еще по одному Label и Edit . У Label напишите "Куда:", а у Edit удалите текст. Оба Edit немного расширьте, чтобы могли уместиться имена файлов с длинными адресами.

Ниже установите еще один Label , и в нем напишите: " Размер файла :".

Ниже будут две кнопки – "Копировать" и "Перенести".

Еще ниже установим индикатор копирования. Обычно для таких целей устанавливают индикатор ProgressBar с вкладки Win32 . У него за показ процентного отношения отвечает свойство Position . Установите этот компонент и попробуйте этому свойству присваивать разные значения от 0 до 100, чтобы посмотреть, как изменяется внешний вид компонента. Однако мы для этих целей воспользуемся другим компонентом, поэтому удалите ProgressBar и перейдите на вкладку Samples . Там выберите компонент Gauge , и установите его на форму. Как видите, по умолчанию Gauge имеет квадратную форму. Свойству Height присвойте значение 30, а свойству Width – 270. Таким образом, мы придали Gauge горизонтальную направленность. Этот компонент не только выглядит привлекательней предыдущего, но также показывает процент выполненного действия! За этот процент отвечает свойство Progress , попробуйте присвоить ему разные значения от 0 до 100. Каким именно компонентом пользоваться в таких случаях – дело вкуса каждого программиста. Но вы должны знать, что в стандартной поставке Delphi есть два компонента, которые выполняют это действие. Верните Progress в ноль. Вы, наверное, уже успели заметить, что компонент по умолчанию окрашивается в черный цвет. Это немного мрачно, поэтому в свойстве ForeColor выберите темно-синий цвет.

Для того, чтобы пользователь не вводил много лишнего текста, облегчим ему работу и установим два диалога – OpenDialog и SaveDialog . А для их вызова справа от компонентов Edit установите по одной кнопочке, ширину и высоту которых сделайте такой же, как высота Edit , то есть, 21, чтобы кнопочки получились квадратными. В результате, компонент Edit и стоящая справа от него квадратная кнопочка кажутся одним элементом интерфейса. У вас должна получиться форма примерно с таким интерфейсом:

Внешний вид приложения

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

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

Для того, чтобы не путаться при написании кода, переименуйте верхний Edit в Otkuda , а нижний – в Kuda (свойство Name ). Так мы точно будем знать, где и что у нас находится.

Теперь займемся написанием кода программы. Для начала объявим процедуру, которая будет побайтово копировать файл . Ранее мы уже вводили в программы собственные функции и процедуры. Мы их нигде не объявляли, а просто описывали выше того места, откуда в дальнейшем вызывали. Этот способ имел один минус – такая процедура не являлась частью объекта Форма, и для того, чтобы обратиться к какому-нибудь компоненту, нам приходилось вначале обращаться к форме, например:

Однако собственную процедуру или функцию можно сделать частью формы, если вначале ее объявить. Делается это в разделе private , сразу под комментарием:

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

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

Итак, что нового для нас в этом коде? Во-первых, директивы компилятору. Директива отключает обработку компилятором ошибок ввода-вывода (Input-Output), нам это необходимо, чтобы получить результат – была ошибка или нет. Ниже мы вновь включаем эту обработку: .

Создайте новый проект. Форму сразу же переименуйте в fMain, в свойстве Caption напишите "Работа с файлами" а проект сохраните в новую папку под именем "FileManager". Стиль формы сделайте bsDialog, а положение формы – poDesktopCenter.

Далее, установите на форму компонент Label, в свойстве Caption которого напишите "Что:". Рядом установите Edit, и удалите из него текст. В этот Edit мы будем заносить исходный файл, или что мы собираемся копировать или переносить.

Ниже установите еще по одному Label и Edit. У Label напишите "Куда:", а у Edit удалите текст. Оба Edit немного расширьте, чтобы могли уместиться имена файлов с длинными адресами.

Ниже установите еще один Label, и в нем напишите: "Размер файла:".

Ниже будут две кнопки – "Копировать" и "Перенести".

Еще ниже установим индикатор копирования. Обычно для таких целей устанавливают индикатор ProgressBar с вкладки Win32. У него за показ процентного отношения отвечает свойство Position. Установите этот компонент и попробуйте этому свойству присваивать разные значения от 0 до 100, чтобы посмотреть, как изменяется внешний вид компонента. Однако мы для этих целей воспользуемся другим компонентом, поэтому удалите ProgressBar и перейдите на вкладку Samples. Там выберите компонент Gauge, и установите его на форму. Как видите, по умолчанию Gauge имеет квадратную форму. Свойству Height присвойте значение 30, а свойству Width – 270. Таким образом, мы придали Gauge горизонтальную направленность. Этот компонент не только выглядит привлекательней предыдущего, но также показывает процент выполненного действия! За этот процент отвечает свойство Progress, попробуйте присвоить ему разные значения от 0 до 100. Каким именно компонентом пользоваться в таких случаях – дело вкуса каждого программиста. Но вы должны знать, что в стандартной поставке Delphi есть два компонента, которые выполняют это действие. Верните Progress в ноль. Вы, наверное, уже успели заметить, что компонент по умолчанию окрашивается в черный цвет. Это немного мрачно, поэтому в свойстве ForeColor выберите темно-синий цвет.

Для того, чтобы пользователь не вводил много лишнего текста, облегчим ему работу и установим два диалога – OpenDialog и SaveDialog. А для их вызова справа от компонентов Edit установите по одной кнопочке, ширину и высоту которых сделайте такой же, как высота Edit, то есть, 21, чтобы кнопочки получились квадратными. В результате, компонент Edit и стоящая справа от него квадратная кнопочка кажутся одним элементом интерфейса. У вас должна получиться форма примерно с таким интерфейсом:


Рис. 26.1. Внешний вид приложения

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

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

Для того, чтобы не путаться при написании кода, переименуйте верхний Edit в Otkuda, а нижний – в Kuda (свойство Name). Так мы точно будем знать, где и что у нас находится.

Теперь займемся написанием кода программы. Для начала объявим процедуру, которая будет побайтово копировать файл. Ранее мы уже вводили в программы собственные функции и процедуры. Мы их нигде не объявляли, а просто описывали выше того места, откуда в дальнейшем вызывали. Этот способ имел один минус – такая процедура не являлась частью объекта Форма, и для того, чтобы обратиться к какому-нибудь компоненту, нам приходилось вначале обращаться к форме, например:

Однако собственную процедуру или функцию можно сделать частью формы, если вначале ее объявить. Делается это в разделе private, сразу под комментарием:

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

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

f1, f2: File; // первый и второй файл

cop: array [1..2048] of Char; //буфер копирования

sizefile, sizeread: Int64; //размер файла и размер прочитанного

colRead, colWrite : Integer; //прочитано и записано

fOtkuda, fKuda : String; //адреса и имена файлов

//даем компилятору директиву, чтобы не отслеживал ошибки ввода-вывода:

//проверяем, указаны ли файлы. если нет - выходим

if (Otkuda.Text='') or (Kuda.Text='') then begin

ShowMessage('Укажите какой файл копировать/переносить, и куда');

//связываем файловые переменные:

//открываем первый файл для чтения:

//определяем его размер в переменную:

//отображаем размер файла в килобайтах:

Label3.Caption := 'Размер файла: '+IntToStr(Round(sizefile / 1024)) + ' Кб.';

//создаем или перезаписываем второй файл:

//делаем, пока не достигнут конец исходного файла

Screen.Cursor := crHourGlass; //песочные часы

while colRead = colWrite do begin

BlockRead(f1, cop, SizeOf(cop), colRead);

if colRead = 0 then break;

//двигаем индикатор копирования

BlockWrite(f2, cop, colRead, colWrite);

sizeread := sizeread + colRead;

Screen.Cursor := crDefault; //обычный вид курсора

if IOResult <> 0 then

Application.MessageBox('Ошибка при копировании файла!', 'Внимание. ',

else ShowMessage('Копирование успешно завершено!');

//включаем обработчик компилятором ошибок

Итак, что нового для нас в этом коде? Во-первых, директивы компилятору. Директива отключает обработку компилятором ошибок ввода-вывода (Input-Output), нам это необходимо, чтобы получить результат – была ошибка или нет. Ниже мы вновь включаем эту обработку: .

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

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

Screen.Cursor := crHourGlass; //песочные часы

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

Screen.Cursor := crDefault; //обычный вид курсора

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

sizeread := sizeread + colRead;

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

К сожалению, при копировании обычными средствами Delphi файл успешно копируется и переносится, однако при этом дата и время создания копии будут текущими – ведь для операционной системы мы просто создали новый файл! Для исправления даты мы вызвали процедуру PravkaDate, куда в качестве параметров передали адреса и имена исходного файла и копии. Теперь нам нужно описать эту процедуру, причем сделать это выше той процедуры, из которой мы ее вызываем.

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

Данная тема имеет общие толкования со следующими темами:

Содержание

  • 1. Понятие побитового копирования
  • 2. В каких случаях вызывается побитовое копирование объектов?
  • 3. Какие функции класса используют побитовое копирование объектов?
  • 4. Для каких классов обязательно реализовывать собственные явным образом определенные конструктор копирования и оператор копирования?
  • 5. Проблемы которые возникают в случае, если в классе нет явным образом заданного конструктора копирования и оператора копирования
  • 6. Пример, который демонстрирует недостатки побитового копирования
    • 6.1. Версия класса Copy , который содержит ошибочный код
    • 6.2. Версия класса Copy , которая содержит корректный код (исправленная версия)

    Поиск на других ресурсах:

    1. Понятие побитового копирования

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

    2. В каких случаях вызывается побитовое копирование объектов?

    Побитовое копирование объектов вызывается в следующих случаях:

    • если в классе нет явным образом заданного конструктора копирования, который обеспечивает создание нового объекта из существующего. В этом случае компилятор автоматически генерирует собственный неявный конструктор копирования. Этот конструктор копирования реализует создание копии объекта путем побитового копирования;
    • в операторной функции operator=() , которая осуществляет копирование одного объекта в другой. В этом случае компилятор также автоматически генерирует собственную неявную операторную функцию, которая делает полную копию объекта.

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

    3. Какие функции класса используют побитовое копирование объектов?

    Побитовое копирование объектов используют две функции класса:

    • неявный конструктор копирования который генерируется компилятором автоматически в случае отсутствия явно заданного конструктора копирования. Более подробно о конструкторе копирования описывается здесь ;
    • неявная операторная функция operator=() , которая генерируется компилятором автоматически в случае отсутствия явно заданной операторной функции. Подробный пример использования операторной функции operator=() описывается здесь .
    4. Для каких классов обязательно реализовывать собственные явным образом определенные конструктор копирования и оператор копирования?

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

    Если в классе память не выделяется динамически (нет указателей), то явным образом заданный конструктор копирования и оператор копирования operator=() реализовывать необязательно.

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

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

    1. При копировании типа

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

    2. Класс, в котором выделяется память для указателя, обязательно должен ее освободить в деструкторе. Освобождение памяти для указателя в деструкторе класса есть обычной практикой. После завершения программы не остается выделенной памяти, которая по ошибке не была освобождена. Деструктор вызывается при уничтожении объекта. Если два объекта obj1 и obj2 указывают на общую область памяти, то эта область памяти будет освобождаться два раза (для объекта obj1 и объекта obj2 ). А это есть ошибкой: выделенная один раз память может быть освобождена только один раз и не более. В результате система сгенерирует исключительную ситуацию.

    6. Пример, который демонстрирует недостатки побитового копирования
    6.1. Версия класса Copy , который содержит ошибочный код

    В примере объявляется класс Copy , который содержит ошибочный код. В классе Copy объявляется указатель на тип int . Класс содержит следующие элементы:

    • переменная-указатель на тип int ;
    • конструктор с 1 параметром. В этом конструкторе память для указателя p выделяется динамически;
    • метод доступа Get() , возвращающий значение по указателю *p ;
    • метод доступа Set() , устанавливающий новое значение по указателю p . Если память предварительно не выделена, то она выделяется снова.

    Текст класса Copy следующий:

    Функция main() остается в предыдущем варианте. После запуска на выполнение, программа выдаст следующий результат:

    Как видно из результата, после добавления конструктора копирования

    и добавления операторной функции

    программа работает корректно. Также работает корректно освобождение памяти для объектов obj1 , obj2 , obj3 в деструкторе класса

    в результате чего критическая ситуация (exception) не генерируется. Таким образом, недостатки побитового копирования были устранены.

    Читайте также: