ファイルシステム | Programming Place Plus 新C++編

トップページ新C++編

先頭へ戻る

このページの概要

このページでは、ファイルに対して行うさまざまな操作について取り上げます。具体的には、ファイルの存在確認、名前の変更、コピー、削除といった操作があります。また、画像のファイルフォーマットの1つである BMP(拡張子 .bmp のファイル)を読み書きする(かなり割り切った実装の)プログラムを取り上げます。

以下は目次です。要点だけをさっと確認したい方は、「まとめ」をご覧ください。



.bmp ファイルを書き出す

目標としているペイントスクリプトでは、できあがった画像を、ビットマップ画像(BMP)のファイルとして書き出せる必要があります。

しかし、C++ の標準機能には画像を取り扱う機能がないので、BMP のデータフォーマットに合うように、自力でバイナリ形式で書き出すコードを記述します。現在のキャンバスの中身を BMP のフォーマットに合うように調整して、.bmpファイルとして書き出します。

BMP のデータフォーマットは少し調べればすぐに情報が見つかりますが1、なかなかに複雑です。BMP を深く理解することが主題ではないので、ここでは深入りせずに、ペイントスクリプトを実装するという目的を果たすために必要なことに集中します。たとえば次のような関数を用意すれば、キャンバスの内容を出力するには十分です。

この実装ではたとえば、1ピクセル当たりのビット数が 24(RGB で 8ビットずつ)が前提になっています。こうした割り切りをすることで簡略化しています。

#include <cstdint>
#include <fstream>
#include <string>

// .bmpファイルに書き出す
//
// path: ファイルパス
// width: 横方向のピクセル数
// height: 縦方向のピクセル数
// pixels: ピクセル情報
// 戻り値: 成否
bool save_bmp(const std::string& path, unsigned int width, unsigned int height, const std::vector<std::vector<Color>>& pixels)
{
    std::ofstream ofs(path, std::ios_base::out | std::ios_base::binary);
    if (!ofs) {
        return false;
    }

    // ----- ファイルヘッダ部 -----
    // Windows API の BITMAPFILEHEADER構造体にあたる。
    // https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapfileheader

    // ファイルタイプ
    // 必ず 0x4d42 ("BM" のこと)
    constexpr std::uint16_t file_type {0x4d42};
    ofs.write(reinterpret_cast<const char*>(&file_type), sizeof(file_type));

    // ファイルサイズ
    // 縦横のピクセル数 * 1ピクセル当たりのバイト数(PaintScript では 4バイト固定) + ヘッダ部のバイト数。
    // ヘッダ部の大きさは、sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) より。
    const std::uint32_t file_size {width * height * 4 + 54};
    ofs.write(reinterpret_cast<const char*>(&file_size), sizeof(file_size));

    // 予約領域
    const std::uint32_t reserved {0};
    ofs.write(reinterpret_cast<const char*>(&reserved), sizeof(reserved));

    // ファイル先頭から、ピクセル情報までの距離
    const std::uint32_t offset_to_pixels {54};
    ofs.write(reinterpret_cast<const char*>(&offset_to_pixels), sizeof(offset_to_pixels));


    // ----- ビットマップ情報ヘッダ部 -----
    // Windows API の _BITMAPINFOHEADER構造体にあたる。
    // https://learn.microsoft.com/ja-jp/windows/win32/wmdm/-bitmapinfoheader

    // ビットマップ情報ヘッダ部のサイズ
    const std::uint32_t bitmap_info_header_size {40};
    ofs.write(reinterpret_cast<const char*>(&bitmap_info_header_size), sizeof(bitmap_info_header_size));

    // 横方向のピクセル数
    const std::int32_t w {static_cast<std::int32_t>(width)};
    ofs.write(reinterpret_cast<const char*>(&w), sizeof(w));

    // 縦方向のピクセル数
    const std::int32_t h {static_cast<std::int32_t>(height)};
    ofs.write(reinterpret_cast<const char*>(&h), sizeof(h));

    // プレーン数。必ず 1
    const std::uint16_t planes {1};
    ofs.write(reinterpret_cast<const char*>(&planes), sizeof(planes));

    // 1ピクセル当たりのビット数。PaintScript では 24 に固定
    const std::uint16_t bit_count {24};
    ofs.write(reinterpret_cast<const char*>(&bit_count), sizeof(bit_count));

    // 圧縮形式。無圧縮は 0
    const std::uint32_t compression {0};
    ofs.write(reinterpret_cast<const char*>(&compression), sizeof(compression));

    // 画像サイズ。無圧縮であれば 0 で構わない
    const std::uint32_t image_size {0};
    ofs.write(reinterpret_cast<const char*>(&image_size), sizeof(image_size));

    // メートル当たりの横方向のピクセル数の指示。不要なら 0 にできる
    const std::int32_t x_pixels_per_meter {0};
    ofs.write(reinterpret_cast<const char*>(&x_pixels_per_meter), sizeof(x_pixels_per_meter));

    // メートル当たりの縦方向のピクセル数の指示。不要なら 0 にできる
    const std::int32_t y_pixels_per_meter {0};
    ofs.write(reinterpret_cast<const char*>(&y_pixels_per_meter), sizeof(y_pixels_per_meter));

    // カラーテーブル内の色のうち、実際に使用している個数。パレット形式でなければ無関係
    const std::uint32_t clr_used {0};
    ofs.write(reinterpret_cast<const char*>(&clr_used), sizeof(clr_used));

    // カラーテーブル内の色のうち、重要色である色の個数。パレット形式でなければ無関係
    const std::uint32_t clr_important {0};
    ofs.write(reinterpret_cast<const char*>(&clr_important), sizeof(clr_important));


    // ----- ピクセル情報 -----
    // 各ピクセルは RGB の各8bit である想定。
    for (std::int32_t y {h - 1}; y >= 0; --y) {
        for (std::int32_t x {0}; x < w; ++x) {
            const Color pixel {pixels.at(y).at(x)};
            ofs.write(reinterpret_cast<const char*>(&pixel.blue), sizeof(pixel.blue));
            ofs.write(reinterpret_cast<const char*>(&pixel.green), sizeof(pixel.green));
            ofs.write(reinterpret_cast<const char*>(&pixel.red), sizeof(pixel.red));
        }
    }

    return true;
}

適当にキャンバスを作って、.bmpファイルとして書き出してみます。

#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <vector>

struct Color {
    unsigned char red;          // 赤成分
    unsigned char green;        // 緑成分
    unsigned char blue;         // 青成分
};

// キャンバスの型
using canvas_t = std::vector<std::vector<Color>>;

// キャンバスの大きさを変更する
void resize_canvas(canvas_t& canvas, std::size_t width, std::size_t height)
{
    canvas.resize(height);
    for (auto& row : canvas) {
        row.resize(width);
    }
}

// キャンバス内の座標かどうか判定する
bool is_canvas_inside(const canvas_t& canvas, int x, int y)
{
    if (x < 0 || static_cast<int>(canvas[0].size()) <= x) {
        return false;
    }
    if (y < 0 || static_cast<int>(canvas.size()) <= y) {
        return false;
    }
    return true;
}

// キャンバスに点を描く
void paint_dot(canvas_t& canvas, int x, int y, Color color)
{
    if (!is_canvas_inside(canvas, x, y)) {
        return;
    }

    canvas.at(y).at(x) = color;
}

// キャンバスに四角形を描く
void paint_rect(canvas_t& canvas, int left, int top, int right, int bottom, Color color)
{
    for (int y {top}; y <= bottom; ++y) {
        for (int x {left}; x <= right; ++x) {
            paint_dot(canvas, x, y, color);
        }
    }
}

int main()
{
    std::size_t width {500};
    std::size_t height {500};
    canvas_t canvas {};
    resize_canvas(canvas, width, height);

    paint_rect(canvas, 100, 150, 400, 300, {0, 255, 0});

    if (!save_bmp("test.bmp", width, height, canvas)) {
        std::cerr << ".bmpファイルの保存に失敗しました。\n";
        std::quick_exit(0);
    }
}

実行結果(test.bmp):

.bmpファイルに出力した結果

.bmp ファイルを読み込む

.bmpファイルを読み込むほうも作っておきましょう。これがあれば、ペイントスクリプトに loadコマンドを実装して、既存の .bmpファイルの内容でキャンバスを初期化することもできるようになります。読み込もうとしている .bmp の縦横のピクセル数が、現在のキャンバスの大きさと合わない可能性がありますが、その場合は .bmp の大きさに合わせて resize することにします。。

書き出しのほうがそうであったように、この実装では1ピクセル当たりのビット数が 24(RGB で 8ビットずつ)を前提とするといった割り切りをすることで簡略化しています。

#include <cassert>
#include <cstdint>
#include <fstream>

// .bmpファイルを読み込む
//
// path: ファイルパス
// width: 横方向のピクセル数を受け取るポインタ。ヌルポインタ不可
// height: 縦方向のピクセル数を受け取るポインタ。ヌルポインタ不可
// pixels: ピクセル情報を受け取るポインタ。ヌルポインタ不可
// 戻り値: 成否
bool load_bmp(const std::string& path, unsigned int* width, unsigned int* height, std::vector<std::vector<Color>>* pixels)
{
    assert(width);
    assert(height);
    assert(pixels);

    std::ifstream ifs(path, std::ios_base::in | std::ios_base::binary);
    if (!ifs) {
        return false;
    }

    // 不要なところを読み飛ばす
    ifs.seekg(18, std::ios_base::beg);

    // 横方向のピクセル数
    std::int32_t w {};
    ifs.read(reinterpret_cast<char*>(&w), sizeof(w));
    if (w < 1) {
        return false;
    }
    *width = static_cast<unsigned int>(w);
    
    // 縦方向のピクセル数
    std::int32_t h {};
    ifs.read(reinterpret_cast<char*>(&h), sizeof(h));
    if (h < 1) {
        return false;
    }
    *height = static_cast<unsigned int>(h);

    // 不要なところを読み飛ばす
    ifs.seekg(28, std::ios_base::cur);

    // ピクセル情報
    // vector は、ビットマップの大きさに合わせて resize する。
    pixels->resize(h);
    for (auto& row : *pixels) {
        row.resize(w);
    }
    for (std::int32_t y {h - 1}; y >= 0; --y) {
        for (std::int32_t x {0}; x < w; ++x) {
            std::uint8_t b {};
            ifs.read(reinterpret_cast<char*>(&b), sizeof(b));
            std::uint8_t g {};
            ifs.read(reinterpret_cast<char*>(&g), sizeof(g));
            std::uint8_t r {};
            ifs.read(reinterpret_cast<char*>(&r), sizeof(r));

            pixels->at(y).at(x) = Color{r, g, b};
        }
    }

    return true;
}

ファイルの存在を確認する

ペイントスクリプトの話題から脱線しますが、ここからは、ファイルに対して行う各種の操作について触れておきます。

ファイルの内容の読み書き以外の、ファイルに対する各種の操作(名前の変更、コピーや削除など)を行う機能は、C++14 にはあまり用意されていません。この状況は C++17 で一気に解消されており、<filesystem> にまとめられています。C++17 が使える環境なのであれば、ここにある機能を使うのが簡単です。新C++編は C++14 がターゲットなので、ここから先の解説は C++17 が使えない前提で書かれています。


まず、ファイルが存在しているかどうかを確認する方法です。

C++14 の標準ライブラリには、ファイルの存在を判定するものはありませんが、代わりの手段がいくつか知られています。

  1. 読み込み用にファイルをオープンしようとしてみて、失敗するかどうかで判断する(正確な判定にならない)
  2. stat関数を使う
  3. OS が提供する API を使う
  4. (C++17 から) std::filesystem::exist関数2を使う

1番目の方法は昔からよく使われていますが、そもそも正確な判定ではありません。C++14 の時点で妥当な方法は2番目か3番目です。C++17 が使えるのなら4番を選びましょう。ここでは1~3番の方法について説明します。

どの方法を使うにしても、ファイルの存在を判定した直後に状況が変わる可能性を意識しなければなりません。たとえば、「ファイルが存在しなければ新規で作成する」という処理を次のように実装したとします。

if (test.txt が存在するか? == false) {
    test.txt を作成する
}

test.txt をオープンして使う

しかし、「test.txt を作成する」の直前で、同時に実行していたほかのプログラムなどが、test.txt を作成するかもしれません。そうすると、「test.txt を作成する」によって、ほかのプログラムが作った test.txt を上書きして消してしまう結果になる恐れがあります。ここでいう「ほかのプログラム」は、悪意を持った攻撃者がしかけたものかもしれず、これはセキュリティ上の問題になるかもしれません。

問題のポイントは、「ファイルの状態を調べること (check)」と「そのファイルを実際に使うこと (use)」が分かれていることにあります。この間に状態が変化すると、無視できない不具合に発展する可能性があります。この問題には、TOCTOU (Time Of Check to Time Of Use) という名前が付いています。この問題は、check と use という関係性があればファイルでなくても起こり得るものです。

新C++編の本題は C++ の機能を解説することですので、TOCTOU に対する詳細な議論は省きます。詳しい解説や対策を求めるなら、セキュアプログラミングの分野に当たってみてください。

読み込み用にオープンしてみる

1番目の方法は、std::ifstream を使ってファイルのオープンを試みる方法です。読み込みを目的としたオープンでは、指定したファイルが存在しないときにはエラーになるため(「ファイル処理」のページを参照)、これで判断しようというわけです。

#include <fstream>
#include <iostream>
#include <string>

bool is_file_exist(const std::string& path)
{
    std::ifstream ifs(path);
    if (ifs.fail()) {
        return false;
    }
    return true;
}

int main()
{
    if (is_file_exist("test.txt")) {
        std::cout << "ファイルが存在します。\n";
    }
    else {
        std::cout << "ファイルが存在しません。\n";
    }
}

実行結果:

ファイルが存在します。

昔からよく使われている方法ですが、ファイルのオープンがエラーになる原因は、ファイルが存在しないこと以外にもあるので(権限がない。一度にオープンできるファイルの個数の上限に達しているなど)、この判定方法は正確さに欠けています。

なお、ファイルが存在した場合には実際にオープンされた状態になるので、存在の判定以外に用がないのなら、すぐにクローズしたほうがいいです。このサンプルプログラムでは、is_file_exist関数を抜け出すときにクローズされています。

stat関数を使う

C++ の標準ではないですが、POSIX (Portable Operating System Interface)3 と呼ばれる規格で定義されている、stat関数4を使って判定することもできます。POSIX に対応した処理系であれば使用できます。

stat関数は、ファイルやディレクトリなどに関するさまざまな情報を取得する関数です。<sys/stat.h> に、次のように宣言されています。

int stat(const char* pathname, struct stat* buf);

第1引数に指定したファイルパスにあるファイルなどの情報を、第2引数で指定したポインタが指し示す stat構造体に取得します(関数名と構造体名が同じになっています)。戻り値は、成功すると 0、エラー発生時には -1 です。

stat関数が成功するかどうかに加えて、stat構造体のメンバ st_mode を確認することで、取得した情報がファイルのものであることを確認します。実際のプログラムは次のようになります。

#include <iostream>
#include <string>
#include <sys/stat.h>

bool is_file_exist(const std::string& path)
{
    struct stat st {};

    if (stat(path.c_str(), &st) != 0) {
        return 0;
    }

    // ファイルかどうか
    // S_ISREG(st.st_mode); の方がシンプルだが、Visual Studio では使えない。
    return (st.st_mode & S_IFMT) == S_IFREG;
}

int main()
{
    if (is_file_exist("test.txt")) {
        std::cout << "ファイルが存在します。\n";
    }
    else {
        std::cout << "ファイルが存在しません。\n";
    }
}

実行結果:

ファイルが存在します。

OS が提供する API を使う

OS が提供する関数を使う方法もあります。もちろん、OS によって異なりますが、Windows では PathFileExistsA関数5が使えます。

【上級】PathFileExists というマクロがあって、UNICODEマクロの定義の有無によって ANSI版(char型)の PathFileExistsA と Unicode版(wchar_t型) の PathFileExistsW のいずれかに置換されます。ここでは文字列を const char* で扱っているため、PathFileExistsA の方を直接呼び出すようにしています。

PathFileExistsA関数は、<shlwapi.h> に、次のように宣言されています。

BOOL PathFileExistsA(LPCSTR pszPath);

BOOL は int の別名ですが、成否をあらわすための型です。LPCSTR は、const char* の別名です。

引数で指定したファイルパスのファイルが存在すれば、戻り値が真 (0以外) になり、存在しなければ偽 (0) になります。

また、この関数を使うために、Shlwapi.lib というライブラリをリンクする必要があります。ここでは具体的な方法の解説は省きます。

Visual Studio でライブラリをリンクする方法については、Visual Studio編>「スタティックライブラリ」を参照してください。

実際のプログラムは次のようになります。

#include <iostream>
#include <Shlwapi.h>
#include <string>

#pragma comment(lib, "Shlwapi.lib")

bool is_file_exist(const std::string& path)
{
    return PathFileExistsA(path.c_str()) != 0;
}

int main()
{
    if (is_file_exist("test.txt")) {
        std::cout << "ファイルが存在します。\n";
    }
    else {
        std::cout << "ファイルが存在しません。\n";
    }
}

実行結果:

ファイルが存在します。

ファイルの名前を変更する

ファイルの名前は std::rename関数6で変更(リネーム)できます。<cstdio> で次のように宣言されています。

int rename(const char* old_filename, const char* new_filename);

【C言語プログラマー】C言語の rename関数と同等のものです。

第1引数が変更前の名前(ファイルパス)、第2引数が変更後の名前(ファイルパス)です。戻り値は、成功したら 0、失敗したら 0以外が返されます。

変更後の名前が、すでに存在しているファイルと同じだった場合の動作は処理系定義です7

【C++17】std::filesystem::rename関数8が追加されています。

使用例は次のとおりです。

#include <cstdio>
#include <iostream>
#include <string>

int main()
{
    const std::string old_path {"test.txt"};
    const std::string new_path {"new_test.txt"};
    if (std::rename(old_path.c_str(), new_path.c_str()) == 0) {
        std::cout << "ファイル名を変更しました。\n";
    }
    else {
        std::cerr << old_path << "のリネームに失敗しました。\n";
    }
}

実行結果:

ファイル名を変更しました。

ファイルを移動する

C++ の標準ライブラリには、ファイルの移動を行う専用関数は用意されていませんが、std::rename関数を流用することで実現できます。第1引数に移動させたいファイルを指定し、第2引数に移動先のファイルパスを指定します。

#include <cstdio>
#include <iostream>
#include <string>

int main()
{
    const std::string filename {"test.txt"};
    const std::string new_dir {"data/"};
    if (std::rename(filename.c_str(), (new_dir + filename).c_str()) == 0) {
        std::cout << "ファイルを移動しました。\n";
    }
    else {
        std::cerr << filename << "の移動に失敗しました。\n";
    }
}

実行結果:

ファイルを移動しました。

ファイルをコピーする

C++14 の標準ライブラリには、ファイルのコピーを作るものはありませんが、代わりの手段がいくつか知られています。

  1. 手動でコピー先のファイルを作り、コピー元のデータを自力でコピーするコードを書く
  2. std::system関数を使う
  3. OS が提供する API を使う
  4. (C++17 から) std::filesystem::copy_file関数9を使う

1番目の方法は、処理速度の面では問題があります。また、処理の途中で問題が起きたときにどうなるべきなのか、といった細かい懸念もあります。

2番目と3番目の方法は、関数を1つ呼び出す程度のコードで済み簡単ですし、OS の側で用意している機能を使うため、細かい問題を引き受けてもらえる利点もあります。うまく最適化してくれていることが期待できて、効率的でもあります。ただし、環境に強く依存するため、環境ごとに使うコマンドや関数を変えなければなりません。

4番目は C++17以降でなければ使えませんが、最も簡単です。

手動でコピーするコードを書く

1番目の方法として、2つのイテレータを使ったコピーによる実装を「バイナリ形式での読み書き」のページの練習問題で紹介しました。

元のファイルの読み取りに使う内部的なバッファへの参照を取得して、ごっそり出力側のストリームに流し込む方法もあります。内部バッファへの参照は rdbufメンバ関数10で取得できます。

#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>

int main()
{
    const std::string filename {"test.bmp"};
    const std::string copy_filename {"copy_test.bmp"};

    std::ifstream ifs(filename, std::ios_base::in | std::ios_base::binary);
    if (!ifs) {
        std::cerr << filename << "のオープンに失敗しました。\n";
        std::quick_exit(0);
    }

    std::ofstream ofs(copy_filename, std::ios_base::out | std::ios_base::binary);
    if (!ofs) {
        std::cerr << copy_filename << "のオープンに失敗しました。\n";
        std::quick_exit(0);
    }
    
    // ifs の内部バッファから、ofs へ送り込む
    ofs << ifs.rdbuf();
}

実行結果:

ofs << ifs.rdbuf() が何をしているのかを詳しく知ろうとすると大変なのでここでは割愛しますが、ifs の内部バッファから文字を取り出しては ofs へ出力するという操作を、終端に到達するまで行ってくれます。そのため、ファイルが非常に巨大な場合、膨大な時間を必要としてしまう欠点があります。

【上級】rdbufメンバ関数は std::basic_filebuf のポインタを返しますが、これは内部バッファをあらわすオブジェクトへのポインタです。なお、std::basic_filebuf は std::basic_streambuf から派生したクラステンプレートです。一方、std::basic_ostream::operator<< には、引数(つまり << の右辺)に std::basic_streambuf のポインタを指定できるオーバーロードがあるので、ofs << ifs.rdbuf() のような記述が可能になります。このオーバーロードでは、右辺のストリームバッファからの入力を、出力側のストリームに出力します。11

std::system関数を使う

2番目の方法は、C++ の標準ライブラリにある std::system関数12を用います。

std::system関数を使うと、コマンドプロセッサ (command processor) にコマンドを渡して、処理を行わせることができます。コマンドプロセッサとは、Windows での cmd.exe(コマンドプロンプトと呼ばれていることが多い)のように、ユーザーが入力したコマンドを、実行するプログラムのことです。コマンドプロセッサで実行できるコマンドは環境によって異なっており、Windows の場合は copyコマンド13がファイルのコピーを行います。


std::system関数は、<cstdlib> に次のように宣言されています。

int system(const char* command);

引数で指定した文字列がそのままコマンドプロセッサに渡されて実行されます。戻り値がどんな値であるかは、処理系定義です。

引数にヌルポインタを渡すと、コマンドプロセッサが使用可能かどうかを判定するという意味になります。この場合、コマンドプロセッサが使用可能なら 0以外の値が返されます。

std::system関数は、処理をコマンドプロセッサ側に任せるという性質上、使い方には注意が必要です。特に、ユーザーの入力や、ファイルの内容といった外部から来る文字列を、そのまま std::system関数に渡す行為は、そこにどんな内容が含まれているか分からないため、非常に危険といえます。


Windows において、std::system関数を使ってファイルコピーを行うプログラムは次のようになります。

#include <cstdlib>
#include <string>

int main()
{
    const std::string filename {"test.bmp"};
    const std::string copy_filename {"copy_test.bmp"};

    std::system(("copy /B " + filename + " " + copy_filename).c_str());
}

実行結果:

        1 個のファイルをコピーしました。

copyコマンドは、copy /B コピー元ファイルパス コピー先ファイルパス という使い方をしています。/B はバイナリ形式で取り扱うことを意味するオプションです。実行結果にあるメッセージは、copyコマンドが出力したものです。

オプションの指定に / を使っていることから分かるように、ファイルパスの指定で / を使うとオプションの指示であると誤認されます。フォルダの区切りには ¥ を使わなければなりません。

copyコマンド自身は、コピーが成功したときに 0、失敗したときに 0以外のエラーコードを返すようです(ただし、明確な資料は見つかりませんでした)。これらの値が、std::system関数の戻り値として返されてくるため、戻り値を調べることでコピーの成否を判別できるかもしれません。

OS が提供する API を使う

3番目は、OS が提供している関数を使う方法です。この方法も環境ごとにプログラムを書く必要がありますが、OS の事情に合わせて正確に実装されていることが期待できるので、信頼性が高く、効率的でもあります。

Windows の場合なら、CopyFileA関数14を使います。

【上級】CopyFile というマクロがあって、UNICODEマクロの定義の有無によって ANSI版(char型)の CopyFileA と Unicode版(wchar_t型) の CopyFileW のいずれかに置換されます。ここでは文字列を const char* で扱っているため、 CopyFileA の方を直接呼び出すようにしています。

CopyFileA関数は <winbase.h> に次のように宣言されています。ただし実際にインクルードするファイルは <Windows.h> にします。

BOOL CopyFileA(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, BOOL bFailIfExists);

第1引数にコピー元のファイルパス、第2引数にコピー先のファイルパスを指定します。第3引数はコピー先のファイルがすでに存在していた場合に、エラーとみなすか、上書きするかを選択するものです。TRUE を指定するとエラーになり、FALSE を指定すると上書きされます。戻り値は成功 or 失敗を表します。

プログラムは次のようになります。

#include <iostream>
#include <string>
#include <Windows.h>

int main()
{
    const std::string filename {"test.bmp"};
    const std::string copy_filename {"copy_test.bmp"};

    if (CopyFileA(filename.c_str(), copy_filename.c_str(), TRUE)) {
        std::cout << "ファイルをコピーしました。\n";
    }
    else {
        std::cout << "ファイルのコピーに失敗しました。\n";
    }
}

実行結果:

ファイルをコピーしました。

ファイルを削除する

ファイルを削除するには、C++ の標準ライブラリの std::remove関数15を使います。std::remove関数は、<cstdio> で、次のように宣言されています。

int remove(const char* filename);

要素を取り除く関数も remove という名前ですが(「要素を取り除く」のページを参照)、そちらとは別物です。

【C言語プログラマー】C言語の remove関数と同等のものです。

【C++17】std::filesystem::remove関数16が追加されています。

引数に、削除したいファイルのパスを指定します。戻り値は、成功したら 0、失敗したら 0以外が返されます。指定したファイルがオープンされた状態のときの動作は処理系定義です17

次のように使用します。

#include <cstdio>
#include <iostream>

int main()
{
    if (std::remove("test.bmp") == 0) {
        std::cout << "ファイルを削除しました。\n";
    }
    else {
        std::cout << "ファイルの削除に失敗しました。\n";
    }
}

実行結果:

ファイルを削除しました。

ファイルサイズを取得する

ファイルのサイズを取得する方法は、以前に「ランダムアクセス」のページで取り上げました。ここでは割愛します。

まとめ


新C++編の【本編】の各ページには、末尾に練習問題があります。ページ内で学んだ知識を確認する簡単な問題から、これまでに学んだ知識を組み合わせなければならない問題、あるいは更なる自力での調査や模索が必要になるような高難易度な問題をいくつか掲載しています。


参考リンク


練習問題

問題の難易度について。

★は、すべての方が取り組める入門レベルの問題です。
★★は、自力でプログラミングができるようなるために、入門者の方であっても取り組んでほしい問題です。
★★★は、本格的にプログラマーを目指す人のための問題です。

問題1 (基本★)

.bmp の読み込みと書き出しを行う関数を、bmp.cpp と bmp.h に整理してください。

解答・解説

問題2 (応用★★)

.bmpファイルを読み込み、ネガポジ変換を行った結果を、新たな .bmpファイルとして書き出すプログラムを作成してください。

ネガポジ変換とは、RGB のそれぞれの強弱を逆転させる色変換です(ネガポジは、ネガティブとポジティブのことです)。たとえば、R(赤) が 255、G(緑) が 200、B(青) が 50 のピクセルが、R = 0、G = 55、B = 205 となるように変換します。

解答・解説


解答・解説ページの先頭



更新履歴




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る