C++でソースコードからコメントを削除する方法【実装例付き】
C++のプログラムが入力として与えられたとき、そこからコメントをすべて取り除くことを考えます。「source」は文字列型ベクター(vector<string>)で、ソースコードのi行目がsource[i]に格納されています。これは、ソースコード全体の文字列を改行文字「\n」で分割した結果に相当します。
C++では、コメントとして次の2種類を記述できます。
- 行コメント: 文字列「//」で始まるコメント。同じ行の「//」以降の記述はプログラム上無視されます。
- ブロックコメント(複数行コメント): 文字列「/*」で始まり「*/」で終わるコメント。この間に書かれた内容は、複数行にまたがっていてもすべて無視されます。
コメントの優先順位にも注意が必要です。ブロックコメント内に「//」が現れても、それは単なる文字列として扱われます。同様に、行コメントやブロックコメントの中に「/*」が現れた場合も無視されます。さらに、コメントを除去した結果ある行が空になった場合は、その行を出力してはいけません。つまり、出力リスト内の各文字列は必ず非空である必要があります。
入力例と出力例
入力:
source = ["/*Test program */", "int main()", "{ ",
" // variable declaration ", "int a, b, c;",
"/* This is a test", " multiline ", " comment for ",
" testing */", "a = b + c;", "}"]
1行ずつ展開すると以下のようになります:
/*Test program */
int main()
{
// variable declaration
int a, b, c;
/* This is a test multiline comment for testing */
a = b + c;
}
出力:
["int main()","{ "," ","int a, b, c;","a = b + c;","}"]
1行ずつ展開すると以下のようになります:
int main()
{
int a, b, c;
a = b + c;
}
解説: 1行目の「/*Test program */」と、6〜9行目にまたがる「/* ... */」が複数行コメントとして除去され、4行目の「// variable declaration」が行コメントとして除去されています。
解法のアプローチ
理想的なコンパイラと同じように、ソースコードを1文字ずつ解析していくのが基本方針です。コメント開始の記号に遭遇したら、それ以降の該当部分をすべて読み飛ばします。具体的な手順は以下の通りです。
- 関数removeComments(vector<string>& source)は、ソースコードを引数として受け取り、コメントを除去したコードを返します。
- ブール変数commentをfalseで初期化します。この変数は、現在解析中の位置がブロックコメントの内部かどうかを追跡するために使用します。
- ブロックコメントの開始「/*」を検出し、かつまだブロック内にいない場合は、2文字分を読み飛ばして状態を「ブロック内」に切り替えます。
- ブロックコメントの終端「*/」を検出し、かつブロック内にいる場合は、2文字分を読み飛ばして状態を「ブロック外」に戻します。
- 行コメントの開始「//」を検出し、かつブロック内にいない場合は、その行の残りをすべて無視します。
- ブロックコメント内ではなく、かつコメントの開始記号でもない場合、その文字を結果バッファに追加します。
- 各行の終端でブロックコメント内にいない場合、バッファの内容を1行として結果に記録します。
- このアルゴリズムの計算量はO(N)です(Nは入力文字列の総文字数)。各文字を一度だけ走査すればよいため、非常に効率的です。
C++での実装例
#include <bits/stdc++.h>
using namespace std;
vector<string> removeComments(vector<string>& source) {
vector<string> ans;
string s;
bool comment = false;
for (int i = 0; i < source.size(); i++) {
for (int j = 0; j < source[i].size(); j++) {
// 行コメントの開始を検出 → 残りの行を無視
if (!comment && j + 1 < source[i].size() &&
source[i][j] == '/' && source[i][j+1] == '/')
break;
// ブロックコメントの開始を検出
else if (!comment && j + 1 < source[i].size() &&
source[i][j] == '/' && source[i][j+1] == '*') {
comment = true;
j++;
}
// ブロックコメントの終端を検出
else if (comment && j + 1 < source[i].size() &&
source[i][j] == '*' && source[i][j+1] == '/') {
comment = false;
j++;
}
// コメント外の文字は結果に追加
else if (!comment)
s.push_back(source[i][j]);
}
// ブロック内でなく、バッファが空でなければ1行として記録
if (!comment && s.size()) {
ans.push_back(s);
s.clear();
}
}
return ans;
}
int main() {
vector<string> source = {
"/*Test program */", "int main()", "{ ",
" // variable declaration ", "int a, b, c;",
"/* This is a test", " multiline ", " comment for ",
" testing */", "a = b + c;", "}"
};
vector<string> res = removeComments(source);
for (auto x : res) {
cout << x << endl;
}
return 0;
}
実行結果
["int main()","{ "," ","int a, b, c;","a = b + c;","}"]
このように、状態を管理するブール変数を1つ用意するだけで、行コメントとブロックコメントの両方を正確に判定しながら除去できることがわかります。実際のコンパイラが行う字句解析(トークン化)でも、同様の考え方が応用されています。
-
C++を使って文字列から特定の単語を削除する方法
本記事では、与えられた文字列から指定した単語を削除する問題を解説します。まず、具体的な例を見てみましょう。入力 : str = remove a given word, word = remove 出力 : a given word 入力 : str = god is everywhere, word = is 出力 : god everywhere解決のためのアプローチ文字列から特定の単語を削除するには、シンプルな手法を用いることができます。手順は以下の通りです。まず、与えられた文字列を2次元配列(マトリックス)形式に変換し、各行に1つの単語を格納します。マトリックス内から対象の単語を検索
-
C++で文字列の部分文字列の総数を求める方法を解説
この記事では、与えられた文字列から作成できる空でない部分文字列の個数を求める方法について解説します。入力 : string = "moon" 出力 : 10 説明 : 部分文字列は m、o、o、n、mo、oo、on、moo、oon、moon の 10 個です。 入力 : string = "yellow" 出力 : 21解法のアプローチ文字列の長さを n とします。上の例からも分かるように、考えられるすべての部分文字列の個数を求めるには、長さ n、(n-1)、(n-2)、(n-3)、……2、1 の部分文字列の個数を順に加算していく必要があります。部分文