Developer

競技プログラミング風 標準C++ライブラリ入門【第1回】
2020.11.18
Lv3

競技プログラミング風 標準C++ライブラリ入門【第1回】

競技プログラミング風 標準C++ライブラリ入門【第1回】

標準C++ライブラリとは、C++で標準的に用意されているクラス・関数ライブラリのことだ。 ヘッダをインクルードするだけですぐに使える便利なクラス・関数が多数用意されている。 同じようなものを自作しようとすると、そこそこの工数も必要だし、高品質(バグがなく高速)にするのは大変だ。 なので、これを使わないC++erは人生をずいぶん損していることになる。

合計4回の本連載では、標準C++ライブラリの競技プログラミング風問題を全部で12問用意した。 問題難易度は比較的簡単なものなので、気軽にどんどん解けるものばかりだ。 正確に言えば、標準C++ライブラリを使用すれば簡単という問題なので、 標準C++ライブラリをマスターしようという動機づけになることを意図している。 競技プログラミング風の問題を多数解くことで、標準C++ライブラリの基本を少しでも知ってもらいたいというわけだ。

各問題は、「問題文」「テストコード」「ヒント」「解答例」「解説」から構成される。
まず、問題を読んで正しく理解し、テストコード部分をIDEやテキストエディタまたはweb上のコンパイラ (ideonなど)で保存・ビルド・実行し、 NGが出ないようにテストコードの「ToDo:」部分のコードを完成させてほしい。

「ヒント」「解答例」「解説」はデフォルトでは非表示になっており、中身を読みたい場合は【show】ボタンを押せば表示される。 これらをすぐに読むのではなく、ちゃんと解答を考え、使用する標準ライブラりのクラス・関数をweb検索して勉強し、 コード記述・ビルド・実行してからそれらを読むのを強く推奨する。なにごとも自分で考え、いろいろ試した上で、解答などを見るようにするのが肝要だ。 その方が解答が印象に残り問題解決方法が記憶に定着しやすいのだ。

目次

ハローワールド(難易度:★☆☆☆☆)

問題

文字列 “Hello, World.” を返す関数 string hello() を実装しなさい。

#include <iostream>       //  標準入出力ライブラリ
#include <string>
using namespace std;    //  std 名前空間使用
#define DO_TEST(exp)    do_test(exp, __LINE__)
void do_test(bool b, int line) {
    if( b ) return;     //  OK
    cout << "\nNG at line " << line << "\n";
    exit(1);
}
string hello() {
    return "";      //  ToDo: ここを書き換えて "Hello, World." を返す
}
int main() {
    DO_TEST( hello() == "Hello, World." );
    cout << "\nGood Job!\n";
    return 0;
}
ヒント

・string 型の関数であれば、単に「return “文字列”;」と書けば、その文字列を返してくれるぞ。
・string オブジェクトを文字列リテラルで初期化したい場合は「string(“文字列”)」と書くんだぞ。

解答例
string hello() {
    return "Hello, World.";       //  "Hello, World." を返す
    //return string("Hello, World.");   //  明示的に string オブジェクトを返す
}
解説

string 型関数で、特定の文字列を返したい場合は、単に「return “文字列”;」と記述すればよい。
string 型を強調するために「return string(“文字列”);」と書いてもいいのだが、
単に文字列リテラルを書いても、暗黙的な型変換により自動的に string 型に変換される。

なお、念のために解説しておくと、std::string のように毎回 std:: を書くのは個人的に面倒なので、
テストコードのように「using namespace std;」と書いておき、「std::」の記述を省略するようにしている。
これは好みの問題だと思うので、標準クラス名・関数名には必ず「std::」を前置してもよい。お好きにどうぞ。

動的配列生成(難易度:★☆☆☆☆)

問題

参照引数で渡された動的配列に、{3, 1, 4, 1, 5} の要素を設定する関数 void setPI(vector<int>& v) を実装しなさい。

#include <iostream>       //  標準入出力ライブラリ
#include <vector>
using namespace std;    //  std 名前空間使用
#define DO_TEST(exp)    do_test(exp, __LINE__)
void do_test(bool b, int line) {
    if( b ) return;     //  OK
    cout << "\nNG at line " << line << "\n";
    exit(1);
}
void setPI(vector<int>& v) {
    //  ToDo: ここにコードを追加し v の要素を {3, 1, 4, 1, 5} に設定する
}
int main() {
    vector<int> v;
    setPI(v);
    DO_TEST( v.size() == 5 );
    DO_TEST( v[0] == 3 );
    DO_TEST( v[1] == 1 );
    DO_TEST( v[2] == 4 );
    DO_TEST( v[3] == 1 );
    DO_TEST( v[4] == 5 );
    cout << "\nGood Job!\n";
    return 0;
}
ヒント

・動的配列クラスは vector<型> を使うんだぞ。
・vector オブジェクトの要素を全部消去したい場合は clear() を使う。
・vector オブジェクトにデータを追加するには push_back(値) を使う。
・vector オブジェクトは代入することが可能なので v = vector<型>{初期化リスト} という記述も可能だぞ。

解答例
void setPI(vector<int>& v) {
    //  v の要素を {3, 1, 4, 1, 5} に設定
    v = {3, 1, 4, 1, 5};
    //v = vector<int>{3, 1, 4, 1, 5};
}
解説

「vector<int>& v」の要素全体を設定するには「v = vector<int>{3, 1, 4, 1, 5};」と記述すればよい。 または、解答例のように単に「v = {3, 1, 4, 1, 5};」と記述してもよい。

あえて各要素を順に設定したい場合は以下のように記述する。

    v.clear();          //  v の要素をクリア
    v.push_back(3);     //  末尾に 3 を追加
    v.push_back(1);
    v.push_back(4);
    v.push_back(1);
    v.push_back(5);

別の方法として、以下のように記述してもよい。

    v.resize(5);
    v[0] = 3;
    v[1] = 1;
    v[2] = 4;
    v[3] = 1;
    v[4] = 5;

vector は最もよく使われるコンテナクラスだ。高速で使い勝手がよく大変便利なので、 「C++ 動的配列クラス std::vector 入門」 などを読んで、 ぜひともマスターしていただきたい。;^p

下駄を履かせる(難易度:★★☆☆☆)

問題

引数で渡された vector<int>型への参照の各要素に第2引数を加える関数 void geta(vector<int>& v, int a) を実装しなさい。

例えば、v に {1, 2, 3}, a に 1 が渡された場合、v の内容は {2, 3, 4} となる。

※ オーバーフローは考慮しなくてよいものとする。

#include <iostream>
#include <vector>
using namespace std;
#define DO_TEST(exp)    do_test(exp, __LINE__)
void do_test(bool b, int line) {
    if( b ) return;     //  OK
    cout << "\nNG at line " << line << "\n";
    exit(1);
}
void geta(vector<int>& v, int a) {
    //  ToDo: ここにコードを追加し v の各要素に a を加える
}
int main()
{
    vector<int> v1 = {1, 2, 3};
    geta(v1, 1);
    DO_TEST( v1 == vector<int>({2, 3, 4}) );
    vector<int> v2 = {1, 2, 3, 4};
    geta(v2, -2);
    DO_TEST( v2 == vector<int>({-1, 0, 1, 2}) );
    cout << "\nGood Job!\n";
    return 0;
}
//  ※ vector<int>({1,2,3}) という書き方は まみむめも氏(https://twitter.com/hu_123456)にご教授いただきました。さんくす
ヒント

・vector オブジェクトの各要素を参照する場合は、通常配列同様に [式] を使うんだぞ。
・vector オブジェクトの全要素を参照する場合は、for文を使う。
・vector オブジェクトの要素数は size() で取得可能だぞ。
・拡張for文を使うと、全要素にアクセスすることができるぞ。

解答例
void geta(vector<int>& v, int a) {
    //  v の各要素に a を加える
    for(int i = 0; i != v.size(); ++i)
        v[i] += a;
}
解説

「for(int i = 0; i != v.size(); ++i)」で要素数分ループを行い、 「v[i] += a;」で各要素に a を加える。
for分部分は「for(auto& x: v) x += a;」と拡張for文を使って簡潔に記述してもよい。
または、イテレータを使って「for(auto itr = v.begin(); itr != v.end(); ++itr) *itr += a;」と記述してもよい。
イテレータとは抽象化されたポインタのことだ。詳しくは自分でぐぐって勉強してほしい。なにごとも自分で調べる癖をつけることが大事だぞ。

関連記事

投稿が見つかりません。

筆者:津田伸秀
プロフィール:テニス・オセロ・ボードゲーム・パズル類が趣味の年齢不詳のおじさん。 自宅研究員(主席)。vi と C++が好き。迷走中・・・ ボードゲーム・パズル系アプリ開発・リリースしてます。