宮商定時制計算部

計算部日記180330

計算部では、現在C++を学習中です。
顧問にも部員にも経験者がいませんので、頼りになるのは参考書とネットのみ、毎日が試行錯誤の連続ですが、大人(というか、顧問はもうすぐ引退のロートルで、しかも担当教科は国語です)と青少年が対等に、「これはこうじゃない?」「それはこういうことだと思います」「あ、そうか」などと意見を交換し合いながら進んでいくのは、これはこれで楽しいものがあります。
なお、この日記自体は顧問が書いていますが、記事内容は顧問と部員の体験がごっちゃになっています。

Qt Creatorでcinが使えない!
最近は、プログラミングにMacintoshを使う人が多いようですね。
私たちもunix系OSの操作に慣れておいた方がよさそうなのですが、やや高価なMacをそう簡単に購入するというわけにもいきませんので、自宅ではそれぞれが好きなLinuxのディストリビューションを使うことにしよう、ということになりました。
現在のところ、部員A君はManjaro、顧問はミーハーにUbuntuを使っています。
さて、この前、LinuxにインストールしたQtで練習問題を解いていたところ、基本的な操作方法でつまずく事態が起こりました。
分かってしまえば何てことはなかったのですが、Qtについてはきちんとした日本語のチュートリアルがない状態ですので、ごく小さな疑問を解くのも一苦労です。
Qtとは、要するにアプリケーションを開発するためのツールの集合体です。
プロの使用にも耐えますし、素人のプログラミング学習にも使えます。
商用に使わなければ無料ですし、WindowsにもMacにもLinuxにもインストールできます。
Qtのインストールは、Ubuntuなら
sudo apt-get install qt-sdk
一発でOKでした(時間はけっこうかかります)。
C++の練習問題程度であれば、プロジェクトはコンソールで十分です。
「Application」→「Qt Console Application」
問題自体は、「最大公約数を求めるプログラムを再帰関数を使って書け」という、ユークリッドの互除法を利用した実によくあるもので、それほど苦もなく書くことができました。
#include <iostream>

using namespace std;

int gcd(int x, int y)
{
    if (x % y != 0){
        return gcd(y, x % y);
    }else{
        return y;
    }
}

int main()
{
    int a, b;

    cout << "整数1:";
    cin >> a;
    cout << "整数2:";
    cin >> b;

    cout << a << " と " << b << " の最大公約数は " 
         << gcd(a, b) << " です。" << endl;
}
★変数aとbは、a > bになるように調整してから関数gcdに渡すのが筋なのでしょうけれど、実際はどちらが大きくても大丈夫です。
紙に書いて計算してみれば分かります。
ところがこれ、いざ実行してみると、cinが入力できません。
ご覧のように、実行結果がQtの出力ペインに表示されていますが、この枠(ペイン)は「出力」ペインと言うくらいですから、どうやらそこからの入力はできないようです。
対話型にしたければターミナルで実行する設定にしておく必要があるわけで、その設定は、
ご覧の場所にありました。
デフォルトではたぶんチェックが付いているのではないかと思うのですが、何かの拍子にチェックが外れてしまったのかもしれません。
分かってしまえば拍子抜けするほど簡単なことなのに、こんなのを解決するのにも余計な時間がかかってしまいます。
Qtについては、こういう小さなことも網羅的に教えてくれるような日本語の参考書や解説サイトが、一刻も早く欲しいところです。
ま、どうしても必要なら、英語サイトや洋書を読めばいいのですけれどもね(辞書と首っ引きになりそう……)。
★上記のチェックを入れてもコンソールが出ない場合は、
ツール→オプション→環境→概要→システム→ターミナル:
で、ターミナルを選び直してみるといいかもしれません。
ちなみに、Ubuntuでの選択肢は三つで、
/usr/bin/gnome-terminal -x
/usr/bin/x-terminal-emulator -e ←これがデフォルト
/usr/bin/xterm -e
のいずれでもターミナルが起動しましたが、環境によっては出ない場合もあるようです。

int型以外の入力を拒否するには?
さて、この練習問題は「再帰関数」に主眼があるので、入力部分の処理は、上記のように最低限のコードで済ませてしまってかまわないと思います。
しかし、このままでは、int型の数値以外の、実数やら数字以外の文字列やらが誤入力されると、ちゃんと動いてくれません(たいていそこで止まってしまいます)。
そういえば、C言語の参考書には、「学習中は入力にscanfを使ってよいけれども、実際の開発では危険なので使ってはいけません」みたいなことが書いてありました。
C++はC言語に比べるといろいろ安全になっていると言われますが、この手の処理には、C言語同様、きっちりエラー回避の工夫を施しておくべきなのは同様なのでしょう。
入力処理については、前々から気になっていたことでもあるので、今回、それについても考えてみました。
とりあえず、まずは先人の知恵を拝借ということで、手元にある参考書やらネット上の情報やらに当たってみました。
簡単なのは次のようなコードです。
#include <iostream>

using namespace std;

int main(){
    int i;
    cout << "整数を入力してください。:";
    if(cin >> i){
        cout <<  "入力された値は " << i << " でした。" << endl;
    }else{
        cout << "不正な値です。" << endl;
    }
}
cinを条件式に指定すると、エラーの場合はfalseが返るのだそうで、それを利用したプログラムですね。
これにいろいろな値を入れて試してみました。
下記の、○は正しく判定されたもの、×は誤って判定されたものです。
  //↓普通の整数:○
整数を入力してください。:123
入力された値は 123 でした。

 //↓符号付き正の整数:○
整数を入力してください。:+999
入力された値は 999 でした。

 //↓符号付き負の整数:○
整数を入力してください。:-888
入力された値は -888 でした。

 //↓余計な0付き正の整数:○
整数を入力してください。:00000000000777
入力された値は 777 でした。

 //↓余計な0付き負の整数:○
整数を入力してください。:-00000000000666
入力された値は -666 でした。

 //↓int型の上限を超える整数:○
整数を入力してください。:12345678901234567890
不正な値です。

 //↓int型の下限を超える整数:○
整数を入力してください。:-12345678901234567890
不正な値です。

 //↓不正文字混合&数字始まり:× 
整数を入力してください。:12ab34
入力された値は 12 でした。

 //↓不正文字混合&文字始まり:○
整数を入力してください。:zy98xw
不正な値です。

 //↓実数:× 
整数を入力してください。:15.69
入力された値は 15 でした。

 //↓最初にスペース混入:○
整数を入力してください。: 99
入力された値は 99 でした。

 //↓途中にスペース混入:× 
整数を入力してください。:1 23 45
入力された値は 1 でした。

 //↓最後にスペース混入:○
整数を入力してください。:678  /*(←ここにスペース)*/
入力された値は 678 でした。
これを見ると、エラーを吐いてほしいのに吐いてくれなかったパターンが三つあります。
どうやら、数字以外の文字が出てくると、その直前までしか読み込まないようです。
いずれにしても、これらも「不正入力」と判断されないと困りますので、やはり処理を自作するしかなさそうです。
ま、入門レベルの練習にはなるでしょうから、やってみようと思います。
1. 入力処理
まずは入力部分を作ります。
数値を二回入力してもらうので、同じことを二度繰り返します。
#include <iostream>

using namespace std;

int main(){
    int s, t;
    cout << "数値1:";
    cin >> s;
    cout << "入力されたのは " << s << " でした。" << endl;
    cout << "数値2:";
    cin >> t;
    cout << "入力されたのは " << t << " でした。" << endl;
}
これは、上にも書いたように、int型の数値が正しく入力されていれば問題ありませんが、int型の範囲を超える数値や、数字以外の文字や記号などが入力されると、不本意な値を返すか、あるいは止まってしまいます。
また、cinは、スペースを入力すると、それを区切りと判断して、スペースの前までを変数に格納し、残りをバッファに残したままにするのだそうです。
で、バッファにデータが残っていると、cinは「二度目もすでに入力済みだな!」と判断して、次の変数にそれを格納してしまいます。
例えば、ここに間にスペースを挟んだ数字「1 2」を入力すると、実行結果は
数値1:1 2
       //↑1と2の間に半角スペース。 
入力されたのは 1 でした。
              //↑1だけが入出力される。 
数値2:入力されたのは 2 でした。
      //↑「数値2:」で入力待ちにならず、2が入出力されてしまう。 
と、まあこんな感じになってしまうわけですね。
では、スペース交じり数字の代わりに、文字交じりの数字を入力してみるとどうなるかというと、
数値1:123abc
         //↑末尾に文字(列)。 
入力されたのは 123 でした。
              //↑末尾の文字(列)は無視。 
数値2:入力されたのは 0 でした。
   //↑「数値2:」で入力待ちにならず、わけのわからない値が入出力されてしまう。 

数値1:789@65
         //↑間に文字(列)。 
入力されたのは 789 でした。
              //↑間の文字(列)は無視。 
数値2:入力されたのは 0 でした。
   //↑「数値2:」で入力待ちにならず、わけのわからない値が入出力されてしまう。 

数値1:bnh908
         //↑頭に文字(列)。 
入力されたのは 0 でした。
              //↑頭の文字(列)は0に。 
数値2:入力されたのは 1629917344 でした。
   //↑「数値2:」で入力待ちにならず、わけのわからない値が入出力されてしまう。 
と、やはり数値2が入力待ちになってくれず、それどころか、今度はわけの分からない値が返ってきます。
スペースや不正な文字が入ってきても余計な動作をしないように、入力時点では、どんなデータが入力されてもひとまずすべて受け入れられるようにしておく必要がありそうです。
それには「文字列型」の変数がいいでしょう。
文字列型なら、数字以外の文字はもちろん、int型の範囲を超える数、例えば「9999999999999999」などという値の入力にも対応できます。
入力時点ではとりあえず文字列型変数に格納しておいて、それが計算可能なデータであることが検査できたら、その時点でatoi関数なりstoi関数なりで数値に直せばよさそうですね。
数字だろうとアルファベットだろうと日本語文字だろうとスペースだろうとタブ文字だろうと、文字列をすべて読み込むには、「getline関数」が使えます。
#include <iostream>

using namespace std;

int main(){

    string s;

    cout << "数値1:";
    getline(cin, s);
    cout << "入力されたのは " << s << " でした。" << endl;

    cout << "数値2:";
    getline(cin, s);
    cout << "入力されたのは " << s << " でした。" << endl;
}
数値1:12345678901234567890
     //↑int型上限をはるかに超える数。
入力されたのは 12345678901234567890 でした。
         //↑そのまま入出力される。

数値2:987 abc6 54@:;
     //↑スペース・文字・記号が混在する文字列。
入力されたのは 987 abc6 54@:; でした。
         //↑そのまま入出力される。
入力処理の基本形はこんなものでよいでしょう。
次は、入力されたデータが演算可能なものかどうかを検査する処理を考えます。
2. 数字かどうか?
まずは「数字かどうか」を調べましょう。
この処理、最初は、ループを回して、入力された文字列を一文字ずつisdigit関数で調べる方法、
//sは調べる文字列。
for(size_t i = 0; i < s.size(); i++) {
    if(!isdigit(s[i])) exit(1);
}
みたいなコードを書いてみたのですが、調べてみると、それよりもfind_first_not_of()を使った方が簡単に書けそうでした。
これは、指定した文字列に「含まれない」文字を探して、見つかれば位置(番号)を返し、見つからなければnposを返す、という関数です。
これを使えば、
if(s.find_first_not_of("0123456789", 0) != string::npos)
と、かなり短く書くことができます。
「s.find_first_not_of("0123456789", 0) 」で、「文字列sを位置番号0(=先頭)から調べ、"0123456789"『以外』の文字が見つかった場合は位置番号を返し、見つからなかった場合はnposを返す」という処理ですから、右辺に「!= string::npos」と書けば、「nposが返されなかった場合は真」=「"0123456789"『以外』の文字が見つかった場合は真」という判定になるわけです。
#include <iostream>
#include <string>

using namespace std;

int main(){
    string s;
    cout << "整数を入力してください。" << endl;
    cout << "整数:";
    getline(cin, s);
    if(s.find_first_not_of("0123456789", 0) != string::npos){
        cout << "×数字以外の文字が混入しました。" << endl;
    }else{
        cout << "○入力された文字はすべて数字です。" << endl;
    }
}
何度か実行して試してみます。
整数を入力してください。
整数:123
○入力された文字はすべて数字です。

整数を入力してください。
整数:12h54
×数字以外の文字が混入しました。

整数を入力してください。
整数:asd801
×数字以外の文字が混入しました。

整数を入力してください。
整数:0.52
×数字以外の文字が混入しました。

整数を入力してください。
整数:45 654
×数字以外の文字が混入しました。

整数を入力してください。
整数:くぁwせdrftgyふじこlp
×数字以外の文字が混入しました。

整数を入力してください。
整数:-
×数字以外の文字が混入しました。

整数を入力してください。
整数:/*(何も入力しない)*/
○入力された文字はすべて数字です。
    //↑問題1 

整数を入力してください。
整数:-800
×数字以外の文字が混入しました。
    //↑問題2 
ご覧のように、ほぼちゃんと動きますが、最後から二番目の、何も入力しない場合に数字と判定されてしまうのは問題です(問題1)。
また、いちばん下の「−800」はint型の範囲内ですので、これは通してくれないと困ります(問題2)。
同じように、正の符号が付いた「+800」も通したいところ。
問題1は、empty関数で空の文字列かどうかを判定すればよいでしょう。
問題2の解決方法としては、
  1. 一文字目が「−」「+」だった場合は、二文字目から数字かどうか調べる。
  2. 一文字目が「−」「+」だった場合は、それを削除してから数字かどうか調べる。
の二通りの処理が考えられます。
どちらもそれほど難しくなさそうですが、1.は元データに変更を加えない「非破壊型処理」であるのに対し、2.は元データに変更を加える「破壊型処理」になるという違いがあります。
この処理の後、「int型の範囲内に収まっているかどうか」を検査しなければいけませんので、「+」はともかく、「−」は残しておく必要があるでしょう。
なら必ず1.でなければダメかというと、そんなこともなくて、2.の場合は、元データを別の変数にコピーして、それに変更を加えれば、元データはそのまま温存されますから、それで問題は解決です。
というわけで、この両方を書いてみました。
下記のコードの上のハイライト部分が問題1の解消コード、下のハイライト部分が問題2の解消コード(二通り)です。
#include <iostream>
#include <string>

using namespace std;

int main(){
    string s;
    cout << "整数を入力してください。" << endl;
    cout << "整数:";
    getline(cin, s);
    if(s.empty()){
        cout << "×何も入力されていません。" << endl;
        return 1;
    }

    string::size_type p;
    (s[0] == '+' || s[0] == '-') ? (p = 1) : (p = 0);
    if(s.find_first_not_of("0123456789", p) != string::npos){
        cout << "×数字以外の文字が混入しました。" << endl;
    }else{
        cout << "○入力された文字はすべて数字です。" << endl;
    }
}
★find_first_not_of関数の第2引数(上記ではp)は、「何番目の文字から検査するか」を示すのだからint型に見えるけれど、正しくはstring::size_type型なのだそうです。
まあ「string::size_type p;」の代わりに「int p;」と書いても、コンパイラはほとんど警告を吐きませんけれどね。
こちらの環境では、Clangに-Weverythingまたは-Wconversionオプションを付けたときだけ、「符号付き型と符号なし型の間で暗黙の型変換をやっとるぞ!」みたいな警告が出ましたが、GCCでは全然出ませんでした。
★Clangの「-Weverything」オプションは、実に簡単で便利です。
これを一つくっつければ「本当に全部の」警告オプションが有効になり、最も厳しいチェックをしてくれるようになるのだとか。
GCCにはこれと同じオプションはなく、「-Wall -Wextra」とやっても、「-Weverything」が包含するチェック項目数には全然及ばないそうです。
#include <iostream>
#include <string>

using namespace std;

int main(){
    string s, st;
    cout << "整数を入力してください。" << endl;
    cout << "整数:";
    getline(cin, s);
    if(s.empty()){
        cout << "×何も入力されていません。" << endl;
        return 1;
    }

    st = s;
    if(st[0] == '+' || st[0] == '-') st.erase(0, 1);
    if(st.find_first_not_of("0123456789", 0) != string::npos){
        cout << "×数字以外の文字が混入しました。" << endl;
    }else{
        cout << "○入力された文字はすべて数字です。" << endl;
    }
}
整数を入力してください。
整数:/*(何も入力しない)*/
×何も入力されていません。

整数を入力してください。
整数:-800
○入力された文字はすべて数字です。

整数を入力してください。
整数:+9654
○入力された文字はすべて数字です。

整数を入力してください。
整数:+-45
×数字以外の文字が混入しました。

整数を入力してください。
整数:-
○入力された文字はすべて数字です。
        //↑新たな問題 

整数を入力してください。
整数:+
○入力された文字はすべて数字です。
        //↑新たな問題 
2-1も2-2も実行結果はまったく同じでしたので、まとめて書いてあります。
上記の二つの問題は、これでほぼ解消できたようです。
問題2の解決方法は、どちらを採用しても大丈夫そうですが、「非破壊型」の方が何となく安全な気もするので、今回は2-1の方を採用することにします。
さて、実行結果に赤字で書いたように、このままだと「+」または「−」が一つだけ入力された場合でも「すべて数字です」と表示されてしまいます。
【すべて数字か?1】ではこの問題は生じなかったけれども、今は、最初の文字が符号であったらそれは飛ばして検査しているので、符号が一つだけの場合は空の文字列と同じ判定になってしまうのです。
繰り返しになりますが、数字かどうかの判定部分は、「nposが返らなければ真」ですので、
となっています。
空の文字列は「"0123456789"以外の文字がなかった場合」の方に該当するため("0123456789"以外の文字どころか、文字が一つもない状態)、偽と判断されて、「○入力された文字はすべて数字です。」が表示されてしまうというわけです。
この問題を回避するため、符号しか入力されていない場合は、別の警告を出すようにしておきたいと思います(下のハイライト部分)。
#include <iostream>
#include <string>

using namespace std;

int main(){
    string s;
    cout << "整数を入力してください。" << endl;
    cout << "整数:";
    getline(cin, s);
    if(s.empty()){
        cout << "×何も入力されていません。" << endl;
        return 1;
    }
    if(s.find_first_not_of("+-", 0) == string::npos){
        cout << "×符号しか入力されていません。" << endl;
        return 1;
    }
    string::size_type p;
    (s[0] == '+' || s[0] == '-') ? (p = 1) : (p = 0);
    if(s.find_first_not_of("0123456789", p) != string::npos){
        cout << "×数字以外の文字が混入しました。" << endl;
    }else{
        cout << "○入力された文字はすべて数字です。" << endl;
    }
}
整数を入力してください。
整数:-800
○入力された文字はすべて数字です。

整数を入力してください。
整数:+9654
○入力された文字はすべて数字です。

整数を入力してください。
整数:-
×符号しか入力されていません。
    //↑OK。

整数を入力してください。
整数:+--+++-
×符号しか入力されていません。
    //↑OK。
すべて数字かどうかを検査する処理は、こんなものでしょうか。
では、これを部品化して、main関数から呼ぶようにしてみます。
#include <iostream>
#include <string>
#include <cstdlib>	//for exit

using namespace std;

void noStr(string&);

void noStr(string& s){
    if(s.empty()){
        cout << "×何も入力されていません。" << endl;
        exit(1);        //←入力に失敗すると終了してしまう。
    }
    if(s.find_first_not_of("+-", 0) == string::npos){
        cout << "×符号しか入力されていません。" << endl;
        exit(1);        //←入力に失敗すると終了してしまう。
    }
    string::size_type p;
    (s[0] == '+' || s[0] == '-') ? (p = 1) : (p = 0);
    if(s.find_first_not_of("0123456789", p) != string::npos){
        cout << "×数字以外の文字が混入しました。" << endl;
		exit(1);        //←入力に失敗すると終了してしまう。
    }else{
        cout << "○入力された文字はすべて数字です。" << endl;
    }
}

int main(){
	string numa, numb;
	cout << "一つ目の整数を入力してください。" << endl;
	cout << "整数1:";
	getline(cin, numa);
	noStr(numa);
	
	cout << "二つ目の整数を入力してください。" << endl;
	cout << "整数2:";
	getline(cin, numb);
	noStr(numb);
}
う~ん、これでは、実行して実験してみるまでもなく、不正な入力があったらそこでプログラムが終了してしまうのが明白ですね(ハイライト箇所)。
やはり、不正入力があった場合は、何度でも入力し直してもらうループを入れておく必要がありそうです。
それと、main関数内の繰り返しも、ループで書いておいた方が、後々の修正が楽になるかもしれません。
それらを反映させて、書き直してみましょう。
【変更1】noStr関数は、bool型として、検査結果をtrueかfalseで返してもらうことにします。
【変更2】main関数は、同じことを二度繰り返すので、要素数2の文字列配列を使ってループすればよいでしょう。
#include <iostream>
#include <string>

using namespace std;

bool noStr(const string&);

bool noStr(const string& s){    //文字列sには変更を加えないのでconstを付ける。
    if(s.empty()){
        cout << "×何も入力されていません。\n" << endl;
        return false;
    }
    if(s.find_first_not_of("+-", 0) == string::npos){
        cout << "×符号しか入力されていません。\n" << endl;
        return false;
    }
    string::size_type p;
    (s[0] == '+' || s[0] == '-') ? (p = 1) : (p = 0);
    if(s.find_first_not_of("0123456789", p) != string::npos){
        cout << "×数字以外の文字が混入しました。\n" << endl;
        return false;
    }else{
        cout << "○入力された文字はすべて数字です。\n" << endl;
    }
    return true;
}

int main(){
    string num[2];
    for(int i = 0; i < 2; i++){
        cout << "★整数" << i + 1 << " を入力してください。" << endl;
        while(1){
            cout << "整数" << i + 1 << ":";
            getline(cin, num[i]);
            if(noStr(num[i])) break;
        }
    }
}
こんな感じでしょうか。
★整数1を入力してください。
整数1:1g56
×数字以外の文字が混入しました。

整数1:lkp456@
×数字以外の文字が混入しました。

整数1:99 787
×数字以外の文字が混入しました。

整数1:77.54
×数字以外の文字が混入しました。

整数1:+
×符号しか入力されていません。

整数1:-78
○入力された文字はすべて数字です。

★整数2を入力してください。
整数2:ふじこlp
×数字以外の文字が混入しました。

整数2:/*(何も入力しない)*/
×何も入力されていません。

整数2:+-123456789012345678901234567890
×数字以外の文字が混入しました。

整数2:+123456789012345678901234567890
○入力された文字はすべて数字です。
大丈夫そうですね。
3. int型の範囲内かどうか?
では次に、int型の範囲に収まっているかどうかを検査する処理を作りましょう。
例えば、上記の実行結果の「整数2」の値「+123456789012345678901234567890」は、確かにちゃんとした整数になってはいるのですが、このように30桁もある数値では、普通の処理系のint型変数にはとても格納できないでしょう。
文字列から数値に変換してint型変数に格納する前に、int型の範囲内なのかどうかを検査しておく必要があります。
関数gcdは剰余算しか使いませんので、演算結果が入力値よりも大きな値になることはありません。
従って、入力値の範囲は、int型の最小値から最大値までフルに許容しても大丈夫でしょう。
処理の流れとしては、
  1. その処理系のint型の上限値と下限値を求める。
  2. それらを文字列化して、入力された文字列と比較する。
  3. int型上限値と下限値の中に収まっていればOK。
こんな感じで、この検査の結果、int型に変換しても大丈夫そうなら、main関数に「大丈夫だよ!」という戻り値を送り、その後でint型に変換すればよさそうです。
文字列で表されたの数値の大小を比較する場合、単純に比較演算子やcompare関数を使ったのでは比較できません。
例えば、文字列「123」と文字列「4」を比較した場合、「右の方が大きい」という結果が返ってしまいます。
(string("123") > string("4")) ? (cout << "左大" << endl) : (cout << "右大" << endl);
        //……実行結果:「右大」
これは、文字列の比較では、「左端から順に」「同じ位置の文字どうしが」比較されていくことが原因です。
上の例で言うと、左の「123」の「1」と、右の「4」とを比較して、「4」の方が大きいから「右大」という結果が返るわけですね(右には一つの文字しかありませんから、比較は一回行われるだけです)。
では、これがもし「123」と「124」だったらどうなるでしょうか。
この場合の実行結果は、正しく「右大」が返ります。
同じように、「123」と「122」の場合も、正しく「左大」が返ってきます。
「123」と「124」の比較の場合は、「左の左端1と右の左端1」を比較:〈同じ〉→「左の二番目2と右の二番目2」を比較:〈同じ〉→「左の三番目3と右の三番目4」を比較:〈右の方が大きい〉という順で比較が行われます。
つまり、「ある位置の文字どうしが同じであるときに限り、次の文字の比較へ進む」ということになっているのです。
ですから、文字数というか桁数が同じであれば、文字列の数値であっても比較演算子で正しい比較ができるわけですね。
それなら、「1」と「10」との比較はどうなるかというと、左端は両方とも「1」で〈同じ〉、次はもう比較することができませんが、この場合は文字数が多い方が大きいという結果になります(よって「右大」が返る)。
まあ早い話が、string型の大小の比較はC言語のstrcmp関数と同じなわけで、いずれにしても「文字列の比較は『辞書順』になる」と覚えておけばよいでしょう。
そういうわけで、文字列で表された数値の大小を比較するためには、それなりの工夫が必要になります。
普通の(文字列でない)二つの数値は、正の数どうしでは桁数が大きい方が大きな値になり、負の数どうしでは逆に桁数が大きい方が小さな値になります(例:100 > 10、−100 < −10)。
また、文字列で表された数値の比較は、桁数が同じ場合に限り、比較演算子で正しく大小が返ります(ただし、「−」符号もただの文字扱いになるので、負の数かどうかは問題にされない → 例:101 > 100、−101 > −100 …… 要するに、「絶対値での比較になる」と思っておけばよさそう)。
以上のことから、手順としては、
  1. int型の上限値と下限値を求め、それらの桁数も求める。
  2. int型上限値または下限値と、入力された値とを、桁数(=文字数)で比較する。
  3. 桁数(=文字数)が同じなら、比較演算子で比較する。
でいけると思います。
まずはint型の上限下限を求めてみます。
#include <iostream>
#include <string>
#include <sstream>   //for stringstream
#include <limits>    //for numeric_limits

using namespace std;

int main(){

    //int型の上限値と下限値を求める。
    stringstream sp, sm;
    sp << numeric_limits<int>::max();
    sm << numeric_limits<int>::min();
    cout << "int型上限値:" << sp.str() << endl;
    cout << "int型下限値:" << sm.str() << endl;

    //それぞれの桁数を求める。
    size_t pdig = sp.str().size();
    size_t mdig = sm.str().size();
    cout << "int型上限値桁数:" << pdig << endl;
    cout << "int型下限値桁数:" << mdig << endl;
}
int型上限値:2147483647
int型下限値:-2147483648
int型上限値桁数:10
int型下限値桁数:11
正しく求められたようです。
ここで、int型の上限値・下限値を格納するのに、int型変数を使わずにstringstream型変数を使ったのは、簡単に文字列に直せる方がこの後の処理が楽だからです。
stringstream型なら「.str()」を付けるだけで文字列型に変換できますからね。
まあ、ビルドするときにC++11オプションを付けるのが面倒でなければ、普通にint型に格納してからto_string関数(C++11以降で使用可能)を使えばいいだけなのですが。
例えば下記のように。
#include <iostream>
#include <string>
#include <limits>    //for numeric_limits

using namespace std;

int main(){
    
    //int型の上限値と下限値を求める。
    int ip = numeric_limits<int>::max();
    int im = numeric_limits<int>::min();
    cout << "int型上限値:" << ip << endl;
    cout << "int型下限値:" << im << endl;

    //それぞれの桁数を求める。
    size_t pdig = to_string(ip).size();
    size_t mdig = to_string(im).size();
    cout << "int型上限値桁数:" << pdig << endl;
    cout << "int型下限値桁数:" << mdig << endl;
}
これでもまったく同じ結果が得られます。
コンパイラは、新しいバージョンほどC++11オプションが不要になりつつあるようです(GCCなら6.1以降、Clangなら6.0以降)。
新しいコンパイラを使っているなら、オプションを打ち込む手間もかかりませんから、C++11などの新しい規格は積極的に使った方がいいのかもしれません。
しかし、ちょっとしたテストなどで、Windowsの古いBCCを使う機会もまだありますので、一応古い書き方でも動くようなコードを考えるようにしています。
さて、int型の範囲が求められたので、次は入力された文字列の数値がその範囲内かどうかを調べる処理に進みます。
次のようにすればよいでしょう。
実際に比較をする前の準備として、まず「符号の有無を揃えておく」必要があります。
負の数の場合は、int型下限値も入力値も、ともに先頭に「−」が必ず付いてるので、そのままでいいでしょう。
ところが正の数の場合は、入力値の方に「+」が付いていたり付いていなかったりします。
numeric_limits<int>::max()で求めた数字には、上記のように「+」は付いていませんので、それに合わせるのなら、入力値の方にもし「+」が付いているようなら削除しておく必要があります。
文字列の一部を削除するためには、erase関数が使えます。
s.erase(0, 1)と書けば、「文字列sの0番目(=先頭)から1文字分を削除する」処理となります。
#include <iostream>
#include <string>

using namespace std;

int main(){
    string s;
    cout << "整数:";
    cin >> s;
    cout << s << " が入力されました。" << endl;

    if(s[0] == '+'){
        s.erase(0, 1);
        cout << "+ 符号は削除しました。→ " << s << endl;
    }
}
整数:+600
+600 が入力されました。
+ 符号は削除しました。→ 600

整数:1022
1022 が入力されました。

整数:-78
-78 が入力されました。
比較前の準備その二。
もし入力時に余計な0が入力された場合、それも削除しておかなければなりません。
例えば、今回の処理ではまず桁数で大小を比較しますが、そうすると、「00100」と「200」では、「00100」の方が大きくなってしまいます。
余計な0を削り、「100」と「200」で比較できるように整えておく必要があります。
例:000045 → 45、-00012000 → -12000、+000006 → 6
削除処理ですから、もう一度erase関数を使います。
ただし、今度は削除開始位置と削除文字数が変化しますので、それらを求める必要があります。
例:string s == 
000045     → 0番目から4文字文削除 → s.erase(0, 4)
-00012000  → 1番目から3文字分削除 → s.erase(1, 3)
000006     → 0番目から5文字分削除 → s.erase(0, 5)
この段階では、先頭の「+」はすでに削除されていますので、削除開始位置は、最初の文字を調べて「−」であれば「1」、そうでなければ「0」になります。
下のコードのハイライト部分が余計な0の削除処理です。
#include <iostream>
#include <string>

using namespace std;

int main(){
    string s;
    cout << "整数:";
    cin >> s;

    if(s[0] == '+'){
        s.erase(0, 1);
        cout << "+ 符号は削除しました。→ " << s << endl;
    }

    string::size_type bgn = 0, cnt = 0;

    for(size_t i = 0; i < s.size(); i++){
        if((i == 0) && (s[i] == '-')){
            bgn++;
            continue;
        }
        if(s[i] == '0') {
            cnt++;
        }else{
            break;
        }
    }
    s.erase(bgn, cnt);
    
    cout << "先頭の0の連続は削除しました。→ " << s << endl;
}
整数:+000060060
+ 符号は削除しました。→ 000060060
先頭の0の連続は削除しました。→ 60060

整数:-000000000000000000000000001230000000
先頭の0の連続は削除しました。→ -1230000000

整数:+0000000000000
+ 符号は削除しました。→ 0000000000000
先頭の0の連続は削除しました。→
                          //↑数字がなくなってしまった! 

整数:-000000000000000
先頭の0の連続は削除しました。→ -
                           //↑数字がなくなってしまった! 
ご覧のように、上の二つは期待どおりの結果が得られましたが、符号と0だけしか入力されなかった場合の下の二つは、このままではまずいですね。
いろいろ削除した結果、「空の文字列になってしまった場合」と「マイナス符号だけしか残らなかった場合」は、入力値の変数に0を入れておきたいと思います(下のハイライト部分)。
#include <iostream>
#include <string>

using namespace std;

int main(){
    string s;
    cout << "整数:";
    cin >> s;
    
    if(s[0] == '+'){
        s.erase(0, 1);
        cout << "+ 符号は削除しました。→ " << s << endl;
    }
    
    string::size_type bgn = 0, cnt = 0;

    for(size_t i = 0; i < s.size(); i++){
        if((i == 0) && (s[i] == '-')){
            bgn++;
            continue;
        }
        if(s[i] == '0') {
            cnt++;
        }else{
            break;
        }
    }
    s.erase(bgn, cnt);
    if((s.empty()) || (s == "-")) s = "0";
        
    cout << "先頭の0の連続は削除しました。→ " << s << endl;
}
整数:000000
先頭の0の連続は削除しました。→ 0

整数:-000000
先頭の0の連続は削除しました。→ 0

整数:+000000
+ 符号は削除しました。→ 000000
先頭の0の連続は削除しました。→ 0
いずれも0になりましたので、これで大丈夫そうです。
これで比較すべき値が整いましたので、やっとint型範囲内かどうかを検査する処理に進みます。
比較の手順をもう一度おさらいしておくと、
  1. 桁数で比較 →
  2. 桁数が同じなら、そのまま比較演算子で比較
で、比較対象は、
でしたね。
文字列での比較なので、負の数の場合、「aの方が大きい」という結果なら、実際には「aの方が小さい」ということになります。
→ 例1【桁数比較】文字列「−200」の桁数と文字列「−20」の桁数では「−200」の方が大きいが、実際の数値では逆に「−20」の方が大きい。
→ 例2【文字比較】(桁数が同じである場合)文字列「−200」と文字列「−100」では「−200」の方が大きいが、実際の数値では逆に「−100」の方が大きい。
では書いてみます。
#include <iostream>
#include <string>
#include <sstream>    //for stringstream
#include <limits>     //for numeric_limits
#include <cstdlib>    //for exit

using namespace std;

int main(){
    string s;
    cout << "整数:";
    cin >> s;                           //入力。

    stringstream sp, sm;
    sp << numeric_limits<int>::max();   //int型最大値。
    sm << numeric_limits<int>::min();   //int型最小値。
    
    size_t sdig = s.size();             //入力値の桁数。
    size_t pdig = sp.str().size();      //int型最大値の桁数。
    size_t mdig = sm.str().size();      //int型最小値の桁数。

    //sの正負によって使用する値を変える。
    //dig:桁数(文字数)、cs:比較対象文字列、msg:エラーメッセージ。
    size_t dig;
    string cs, msg;
    if(s[0] == '-'){
        dig = mdig;
        cs = sm.str();
        msg = " は小さすぎます。";
    }else{
        dig = pdig;
        cs = sp.str();
        msg = " は大きすぎます。";
    }

    //大小の比較
    if(sdig > dig){               //まず桁数を比較する。
        cout << s << msg << endl;
        exit(1);
    }else if(sdig == dig){        //桁数が同じなら文字列どうしをそのまま比較する。
        if(s > cs){
            cout << s << msg << endl;
            exit(1);
        }
    }
    cout << s << " はint型の範囲内です。" << endl;
}
いろいろな整数を入力して試してみます。
ここには整数かどうかのチェック機能がありませんので、正しい整数を慎重に入力します。
整数:654321
654321 はint型の範囲内です。

整数:1111111111111111
1111111111111111 は大きすぎます。

整数:-48765
-48765 はint型の範囲内です。

整数:-1111111111111111
-1111111111111111 は小さすぎます。

整数:2147483647
2147483647 はint型の範囲内です。
     //↑int型上限値。

整数:2147483648
2147483648 は大きすぎます。

整数:-2147483648
-2147483648 はint型の範囲内です。
     //↑int型下限値。

整数:-2147483649
-2147483649 は小さすぎます。

整数:0
0 はint型の範囲内です。
うまくいったようです。
4. プログラム構築
それでは、これらすべてを部品化して、main関数でコントロールしてみましょう。
上の【すべて数字か?5】において、《noStr関数》を部品化したプログラムをすでに書きましたので、それを利用して進めていきたいと思います。
ではあらためて、まずは数字以外の文字が混入していないかどうかを検査する《noStr関数》から。
これはbool型とし、不正入力ならfalseを、全部数字ならtrueを返すことにします。
また、検査するだけで元データに加工はしませんので、仮引数にconstを付けておきます。
#include <iostream>
#include <string>

using namespace std;

bool noStr(const string&);

bool noStr(const string& s){
    if(s.empty()){
        cout << "×何も入力されていません。\n" << endl;
        return false;
    }
    if(s.find_first_not_of("+-", 0) == string::npos){
        cout << "×符号しか入力されていません。\n" << endl;
        return false;
    }
    string::size_type p;
    (s[0] == '+' || s[0] == '-') ? (p = 1) : (p = 0);
    if(s.find_first_not_of("0123456789", p) != string::npos){
        cout << "×数字以外の文字が混入しました。\n" << endl;
        return false;
    }
    return true;
}
/*****************************************************************************/
int main(){
    string num[2];
    for(int i = 0; i < 2; i++){
        cout << "★整数" << i + 1 << " を入力してください。" << endl;
        while(1){
            cout << "整数" << i + 1 << ":";
            getline(cin, num[i]);
            if(!noStr(num[i])) continue;
            break;
        }
        cout << "○ " << num[i] << " は演算可能です。\n" << endl;
    }
}
★整数1 を入力してください。
整数1:1a2
×数字以外の文字が混入しました。
    //↑(数字の中に文字)

整数1:b4d
×数字以外の文字が混入しました。
    //↑(文字の中に数字)

整数1:   
×何も入力されていません。
    //↑(何も入力しない)

整数1:+  
×符号しか入力されていません。
    //↑(符号のみ)

整数1:+654
○ +654 は演算可能です。
    //↑(正の整数符号付き)

★整数2 を入力してください。
整数2:2 54
×数字以外の文字が混入しました。
    //↑(数字の中にスペース)

整数2:  444
×数字以外の文字が混入しました。
    //↑(スペースから始まる数字)

整数2:567
×数字以外の文字が混入しました。
    //↑(数字の後にスペース)

整数2:0.4569
×数字以外の文字が混入しました。
    //↑(実数)

整数2:-327
○ -327 は演算可能です。
    //↑(負の整数符号付き)
大丈夫そうですね。
次は、頭の「+」や頭の無駄な0を削除する《adjNum関数》です。
これは元データを加工する関数ですので、仮引数にconstは付けず、戻り値不要のvoid関数とします。
#include <iostream>
#include <string>

using namespace std;

bool noStr(const string&);
void adjNum(string&);

void adjNum(string& s){
    if(s[0] == '+') s.erase(0, 1);
    string::size_type bgn = 0, cnt = 0;
    for(size_t i = 0; i < s.size(); i++){
        if((i == 0) && (s[i] == '-')){
            bgn++;
            continue;
        }
        if(s[i] == '0') {
            cnt++;
        }else{
            break;
        }
    }
    s.erase(bgn, cnt);
    if((s.empty()) || (s == "-")) s = "0";
}
/*****************************************************************************/
bool noStr(const string& s){
    if(s.empty()){
        cout << "×何も入力されていません。\n" << endl;
        return false;
    }
    if(s.find_first_not_of("+-", 0) == string::npos){
        cout << "×符号しか入力されていません。\n" << endl;
        return false;
    }
    string::size_type p;
    (s[0] == '+' || s[0] == '-') ? (p = 1) : (p = 0);
    if(s.find_first_not_of("0123456789", p) != string::npos){
        cout << "×数字以外の文字が混入しました。\n" << endl;
        return false;
    }
    return true;
}
/*****************************************************************************/
int main(){
    string num[2];
    for(int i = 0; i < 2; i++){
        cout << "★整数" << i + 1 << " を入力してください。" << endl;
        while(1){
            cout << "整数" << i + 1 << ":";
            getline(cin, num[i]);
            if(!noStr(num[i])) continue;
            adjNum(num[i]);
            break;
        }
        cout << "○ " << num[i] << " は演算可能です。\n" << endl;
    }
}
★整数1 を入力してください
整数1:+000000000045
○ 45 は演算可能です。

★整数2 を入力してください
整数2:-0000001200
○ -1200 は演算可能です。
これも大丈夫そうです。
次は、int型の範囲に収まっているかどうかを検査する《inRng関数》です。
再び検査の関数ですからbool型とし、仮引数にconstを付けます。
#include <iostream>
#include <string>
#include <sstream>  //for stringstream
#include <limits>   //for numeric_limits

using namespace std;

bool noStr(const string&);
void adjNum(string&);
bool inRng(const string&);

bool inRng(const string& s){
    stringstream sp, sm;
    sp << numeric_limits<int>::max();
    sm << numeric_limits<int>::min();
    
    size_t sdig = s.size();
    size_t pdig = sp.str().size();
    size_t mdig = sm.str().size();

    size_t dig;
    string cs, msg;
    if(s[0] == '-'){
        dig = mdig;
        cs = sm.str();
        msg = "×小さすぎます。\n";
    }else{
        dig = pdig;
        cs = sp.str();
        msg = "×大きすぎます。\n";
    }

    if(sdig > dig){
        cout << msg << endl;
        return false;
    }else if(sdig == dig){
        if(s > cs){
            cout << msg << endl;
            return false;
        }
    }
    return true;
}
/*****************************************************************************/
void adjNum(string& s){
    if(s[0] == '+') s.erase(0, 1);
    string::size_type bgn = 0, cnt = 0;
    for(size_t i = 0; i < s.size(); i++){
        if((i == 0) && (s[i] == '-')){
            bgn++;
            continue;
        }
        if(s[i] == '0') {
            cnt++;
        }else{
            break;
        }
    }
    s.erase(bgn, cnt);
    if((s.empty()) || (s == "-")) s = "0";
}
/*****************************************************************************/
bool noStr(const string& s){
    if(s.empty()){
        cout << "×何も入力されていません。\n" << endl;
        return false;
    }
    if(s.find_first_not_of("+-", 0) == string::npos){
        cout << "×符号しか入力されていません。\n" << endl;
        return false;
    }
    string::size_type p;
    (s[0] == '+' || s[0] == '-') ? (p = 1) : (p = 0);
    if(s.find_first_not_of("0123456789", p) != string::npos){
        cout << "×数字以外の文字が混入しました。\n" << endl;
        return false;
    }
    return true;
}
/*****************************************************************************/
int main(){
    string num[2];
    for(int i = 0; i < 2; i++){
        cout << "★整数" << i + 1 << " を入力してください。" << endl;
        while(1){
            cout << "整数" << i + 1 << ":";
            getline(cin, num[i]);
            if(!noStr(num[i])) continue;
            adjNum(num[i]);
            if(!inRng(num[i])) continue;
            break;
        }
        cout << "○ " << num[i] << " は演算可能です。\n" << endl;
    }
}
★整数1 を入力してください。
整数1:12345678901234567890
×大きすぎます。

整数1:-12345678901234567890
×小さすぎます。

整数1:2147483648
×大きすぎます。

整数1:+2147483647
○ 2147483647 は演算可能です。

★整数2 を入力してください。
整数2:0000000000000999999999999999999
×大きすぎます。

整数2:-0000000000000888888888888888888
×小さすぎます。

整数2:-2147483649
×小さすぎます。

整数2:-2147483648
○ -2147483648 は演算可能です。
大丈夫ですね。
では最後に、最大公約数を求める《gcd関数》です。
検査を通過した二つの数字文字列num[0]とnum[1]を、atoi関数で数値に変換し、gcd関数に渡して最大公約数を求めます。
#include <iostream>
#include <string>
#include <sstream>  //for stringstream
#include <limits>   //for numeric_limits
#include <cstdlib>  //for atoi

using namespace std;

bool noStr(const string&);
void adjNum(string&);
bool inRng(const string&);
int gcd(int, int);

int gcd(int x, int y){
    if (x % y != 0){
        return gcd(y, x % y);
    }else{
        return y;
    }
}
/*****************************************************************************/
bool inRng(const string& s){
    stringstream sp, sm;
    sp << numeric_limits<int>::max();
    sm << numeric_limits<int>::min();
    
    size_t sdig = s.size();
    size_t pdig = sp.str().size();
    size_t mdig = sm.str().size();

    size_t dig;
    string cs, msg;
    if(s[0] == '-'){
        dig = mdig;
        cs = sm.str();
        msg = "×小さすぎます。\n";
    }else{
        dig = pdig;
        cs = sp.str();
        msg = "×大きすぎます。\n";
    }

    if(sdig > dig){
        cout << msg << endl;
        return false;
    }else if(sdig == dig){
        if(s > cs){
            cout << msg << endl;
            return false;
        }
    }
    return true;
}
/*****************************************************************************/
void adjNum(string& s){
    if(s[0] == '+') s.erase(0, 1);
    string::size_type bgn = 0, cnt = 0;
    for(size_t i = 0; i < s.size(); i++){
        if((i == 0) && (s[i] == '-')){
            bgn++;
            continue;
        }
        if(s[i] == '0') {
            cnt++;
        }else{
            break;
        }
    }
    s.erase(bgn, cnt);
    if((s.empty()) || (s == "-")) s = "0";
}
/*****************************************************************************/
bool noStr(const string& s){
    if(s.empty()){
        cout << "×何も入力されていません。\n" << endl;
        return false;
    }
    if(s.find_first_not_of("+-", 0) == string::npos){
        cout << "×符号しか入力されていません。\n" << endl;
        return false;
    }
    string::size_type p;
    (s[0] == '+' || s[0] == '-') ? (p = 1) : (p = 0);
    if(s.find_first_not_of("0123456789", p) != string::npos){
        cout << "×数字以外の文字が混入しました。\n" << endl;
        return false;
    }
    return true;
}
/*****************************************************************************/
int main(){
    string num[2];
    for(int i = 0; i < 2; i++){
        cout << "★整数" << i + 1 << " を入力してください。" << endl;
        while(1){
            cout << "整数" << i + 1 << ":";
            getline(cin, num[i]);
            if(!noStr(num[i])) continue;
            adjNum(num[i]);
            if(!inRng(num[i])) continue;
            break;
        }
        cout << "○ " << num[i] << " は演算可能です。\n" << endl;
    }
    cout << num[0] << " と " << num[1] << " の最大公約数は " 
       << gcd(atoi(num[0].c_str()), atoi(num[1].c_str())) << " です。\n" << endl;
}
★整数1 を入力してください。
整数1:
×何も入力されていません。

整数1:-
×符号しか入力されていません。

整数1:+-
×符号しか入力されていません。

整数1:5h99
×数字以外の文字が混入しました。

整数1:grt@*444ゆ
×数字以外の文字が混入しました。

整数1:78 694
×数字以外の文字が混入しました。

整数1:91.812
×数字以外の文字が混入しました。

整数1:+000000000788
○ 788 は演算可能です。

★整数2 を入力してください。
整数2:    9
×数字以外の文字が混入しました。

整数2:999999999999999999999
×大きすぎます。

整数2:-111111111111111111111
×小さすぎます。

整数2:246
○ 246 は演算可能です。

788 と 246 の最大公約数は 2 です。

    //OK! エラー回避も完璧。
………………………………………………………………………………

★整数1 を入力してください。
整数1:174053
○ 174053 は演算可能です。

★整数2 を入力してください。
整数2:2057322283
○ 2057322283 は演算可能です。

174053 と 2057322283 の最大公約数は 15823 です。

    //OK! 手計算で検算しても正解。
………………………………………………………………………………

★整数1 を入力してください。
整数1:104114085
○ 104114085 は演算可能です。

★整数2 を入力してください。
整数2:2144750151
○ 2144750151 は演算可能です。

104114085 と 2144750151 の最大公約数は 20822817 です。

    //OK! 手計算で検算しても正解。
………………………………………………………………………………

★整数1 を入力してください。
整数1:0
○ 0 は演算可能です。

★整数2 を入力してください。
整数2:100
○ 100 は演算可能です。

0 と 100 の最大公約数は 100 です。

    //OK! 0と100の最大公約数は確かに100。
………………………………………………………………………………

★整数1 を入力してください。
整数1:100
○ 100 は演算可能です。

★整数2 を入力してください。
整数2:0
○ 0 は演算可能です。

    //NG! 答えが出ず止まってしまった。 
………………………………………………………………………………

★整数1 を入力してください。
整数1:0
○ 0 は演算可能です。

★整数2 を入力してください。
整数2:0
○ 0 は演算可能です。

    //NG! 答えが出ず止まってしまった。 
………………………………………………………………………………

★整数1 を入力してください。
整数1:-24
○ -24 は演算可能です。

★整数2 を入力してください。
整数2:8
○ 8 は演算可能です。

-24 と 8 の最大公約数は 8 です。

    //? 一方が負の数の最大公約数は正の数でいいのか? 
………………………………………………………………………………

★整数1 を入力してください。
整数1:0000100
○ 100 は演算可能です。

★整数2 を入力してください。
整数2:-0010000
○ -10000 は演算可能です。

100 と -10000 の最大公約数は 100 です。

    //? 余計な0は削除できているが、上と同じ疑問が残る。 
………………………………………………………………………………

★整数1 を入力してください。
整数1:-120
○ -120 は演算可能です。

★整数2 を入力してください。
整数2:-20
○ -20 は演算可能です。

-120 と -20 の最大公約数は -20 です。

    //? 両方とも負の数の最大公約数は負の数でいいのか? 
「これで完成!」と思ったのですが、まだダメですね……_| ̄|○。
演算可能な整数かどうかの検査はできているけれど、最大公約数を計算する処理の方に問題があるようです。
まず赤字の、止まってしまうケースについて。
これはよくある「ゼロ除算エラー」ですから、回避するのは簡単です。
ただ、ありきたりの方法、すなわち「第2オペランドが0かどうかを調べ、0ならエラー処理をする」でもまあいいのだけれど、これは最大公約数を求めるプログラムですので、「0でないnと0の最大公約数はn」という定義(?)を使う方が、コードが短くて済みそうです。
「0でないnと0の最大公約数はn」の例:gcd(4, 0) = 4、gcd(0, 100) = 100 等
また、「0と0の最大公約数」は「定義されていない」らしいので、「なし」とするのが正解なようです。
もう一つ、「負の数を含む最大公約数」は、どう考えればいいのでしょうか?
負の数まで広げた場合を単純に考えれば、2の約数は「2、1、−1、−2」、−4の約数は「4、2、1、−1、−2、−4」ですので、これらの公約数は「2、1、−1、−2」となり、最大公約数は「2」ということになります。
これは「−2と4」でも「−2と−4」でも同じことです。
従って、もし二つの整数のどちらかまたは両方に負の数が含まれていたとしても、普通にgcd関数で計算して、解答はその「絶対値」を取ればよい、ということになりそうです。
それでは、0や負の数が含まれていても適切な結果が出るようにgcd関数とmain関数を書き直してみますが(「★」の部分)、ついでに、特に難しい処理でもないので、以下のコードも加えておきたいと思います。
int gcd(int x, int y){
    if (x % y != 0){
        return gcd(y, x % y);
    }else{
        return abs(y);                                            //★
    }
}
/*****************************************************************************/
/*****************************************************************************/
int main(){
    cout << "最大公約数を求めます。\n二つの整数を入力してください。\n"  //★★
         << "整数入力の途中でやめる場合は poipoi と入力してください。\n" << endl;
    string num[2], cont;                                          //★★★
    do{                                                           //★★★
        for(int i = 0; i < 2; i++){
            cout << "★整数" << i + 1 << " を入力してください。" << endl;
            while(1){
                cout << "整数" << i + 1 << ":";
                getline(cin, num[i]);
                    if(num[i] == "poipoi"){                       //★★↓
                        cout << "\n途中終了します。\n" << endl;
                        exit(1);
                    }                                             //★★↑
                if(!noStr(num[i])) continue;
                adjNum(num[i]);
                if(!inRng(num[i])) continue;
                break;
            }
            cout << "○ " << num[i] << " は演算可能です。\n" << endl;
        }
        int a = atoi(num[0].c_str()), b = atoi(num[1].c_str());    //★↓
        if(a == 0 && b == 0){
            cout << "0 どうしの最大公約数はありません。\n" << endl;
        }else if(a == 0 || b == 0){
            cout << a << " と " << b << " の最大公約数は " 
                 << ((a == 0) ? b : a) << " です。\n" << endl;
        }else{
            cout << a << " と " << b << " の最大公約数は " 
                 << gcd(a, b) << " です。\n" << endl;
        }                                                           //★↑
        cout << "続ける場合は y を一つだけ、"                          //★★★↓
             << "終了する場合はそれ以外の文字を入力してください。\n"
             << "続けますか?:";
        getline(cin, cont);
        cout << "\n";
    }while(cont == "y");
    cout << "終了します。\n" << endl;                                 //★★★↑
}
中断コマンド「poipoi」は、「ポイポイ」ではなく、どうかフランス語で「プワプワ」と発音してください(……特に意味はありませんw)。
最大公約数を求めます。
二つの整数を入力してください。
整数入力の途中でやめる場合は poipoi と入力してください。

★整数1 を入力してください。
整数1:
×何も入力されていません。

整数1:-
×符号しか入力されていません。

整数1:-+
×符号しか入力されていません。

整数1:456h50
×数字以外の文字が混入しました。

整数1:ghgh9078rrr
×数字以外の文字が混入しました。

整数1:998 11246 54
×数字以外の文字が混入しました。

整数1:0.00356
×数字以外の文字が混入しました。

整数1:+0000000000000000000000002260
○ 2260 は演算可能です。

★整数2 を入力してください。
整数2:    63
×数字以外の文字が混入しました。

整数2:98765432109876543210
×大きすぎます。

整数2:-98765432109876543210
×小さすぎます。

整数2:565
○ 565 は演算可能です。

2260 と 565 の最大公約数は 565 です。

続ける場合は y を一つだけ、終了する場合はそれ以外の文字を入力してください。
続けますか?:y

★整数1 を入力してください。
整数1:0
○ 0 は演算可能です。

★整数2 を入力してください。
整数2:2020
○ 2020 は演算可能です。

0 と 2020 の最大公約数は 2020 です。

続ける場合は y を一つだけ、終了する場合はそれ以外の文字を入力してください。
続けますか?:y

★整数1 を入力してください。
整数1:3030
○ 3030 は演算可能です。

★整数2 を入力してください。
整数2:0
○ 0 は演算可能です。

3030 と 0 の最大公約数は 3030 です。

続ける場合は y を一つだけ、終了する場合はそれ以外の文字を入力してください。
続けますか?:y

★整数1 を入力してください。
整数1:0
○ 0 は演算可能です。

★整数2 を入力してください。
整数2:0
○ 0 は演算可能です。

0 どうしの最大公約数はありません。

続ける場合は y を一つだけ、終了する場合はそれ以外の文字を入力してください。
続けますか?:y

★整数1 を入力してください。
整数1:0000000000000000
○ 0 は演算可能です。

★整数2 を入力してください。
整数2:-000000000000000000
○ 0 は演算可能です。

0 どうしの最大公約数はありません。

続ける場合は y を一つだけ、終了する場合はそれ以外の文字を入力してください。
続けますか?:y

★整数1 を入力してください。
整数1:-880
○ -880 は演算可能です。

★整数2 を入力してください。
整数2:110
○ 110 は演算可能です。

-880 と 110 の最大公約数は 110 です。

続ける場合は y を一つだけ、終了する場合はそれ以外の文字を入力してください。
続けますか?:y

★整数1 を入力してください。
整数1:-365
○ -365 は演算可能です。

★整数2 を入力してください。
整数2:-5
○ -5 は演算可能です。

-365 と -5 の最大公約数は 5 です。

続ける場合は y を一つだけ、終了する場合はそれ以外の文字を入力してください。
続けますか?:e

終了します。
………………………………………………………………………………

最大公約数を求めます。
二つの整数を入力してください。
整数入力の途中でやめる場合は poipoi と入力してください。

★整数1 を入力してください。
整数1:-2091431567
○ -2091431567 は演算可能です。

★整数2 を入力してください。
整数2:22227104
○ 22227104 は演算可能です。

-2091431567 と 22227104 の最大公約数は 694597 です。

続ける場合は y を一つだけ、終了する場合はそれ以外の文字を入力してください。
続けますか?:y

★整数1 を入力してください。
整数1:poipoi

途中終了します。
………………………………………………………………………………

最大公約数を求めます。
二つの整数を入力してください。
整数入力の途中でやめる場合は poipoi と入力してください。

★整数1 を入力してください。
整数1:2147483648
×大きすぎます。

整数1:2147483647
○ 2147483647 は演算可能です。

★整数2 を入力してください。
整数2:poipoi

途中終了します。
OK、これでやっとこさ、すべて期待どおりの結果となりました!
「clang++ -Weverything gcd.cpp」でビルドしても、
「g++ -Wall -Wextra gcd.cpp」でビルドしても、特に警告は出ません。
意外と大変でしたが、当初の目的どおりに動いてくれるとうれしいものです。
「最大公約数を求めるプログラムを再帰関数を使って書け」という練習問題に対して、これくらいの解答が示せれば十分合格点がもらえると思うのですが、どうでしょうか?
最後に、このプログラムの全体を掲載しておきます。
#include <iostream>
#include <string>
#include <sstream>    //for stringstream
#include <limits>     //for numeric_limits
#include <cstdlib>    //for atoi, abs, exit

using namespace std;

bool noStr(const string&);
void adjNum(string&);
bool inRng(const string&);
int gcd(int, int);

int gcd(int x, int y){                     /*最大公約数を求める関数。*/
    if (x % y != 0){
        return gcd(y, x % y);
    }else{
        return abs(y);                     //結果は絶対値で返す。
    }
}
/*****************************************************************************/
bool inRng(const string& s){               /*int型範囲内かどうかを検査する関数。*/
    stringstream sp, sm;
    sp << numeric_limits<int>::max();
    sm << numeric_limits<int>::min();
    
    size_t sdig = s.size();                //入力値の桁数。
    size_t pdig = sp.str().size();         //int型最大値の桁数。
    size_t mdig = sm.str().size();         //int型最小値の桁数。
    
    //sの正負によって使用する値を変える。
    //dig:桁数(文字数)、cs:比較対象文字列、msg:エラーメッセージ。
    size_t dig;
    string cs, msg;
    if(s[0] == '-'){
        dig = mdig;
        cs = sm.str();
        msg = "×小さすぎます。\n";
    }else{
        dig = pdig;
        cs = sp.str();
        msg = "×大きすぎます。\n";
    }

    if(sdig > dig){                        //以下、大小の比較。まず桁数を比較。
        cout << msg << endl;
        return false;
    }else if(sdig == dig){                 //桁数が同じなら文字列をそのまま比較。
        if(s > cs){
            cout << msg << endl;
            return false;
        }
    }
    return true;
}
/*****************************************************************************/
void adjNum(string& s){                    /*先頭の+符号と無駄な0を削除する関数。*/
    if(s[0] == '+') s.erase(0, 1);         //先頭の+符号を削除。
    string::size_type bgn = 0, cnt = 0;    //erase関数の引数を格納する変数を宣言。
    for(size_t i = 0; i < s.size(); i++){  //先頭の無駄な0を削除する処理。
        if((i == 0) && (s[i] == '-')){
            bgn++;
            continue;
        }
        if(s[i] == '0') {
            cnt++;
        }else{
            break;
        }
    }
    s.erase(bgn, cnt);
    if((s.empty()) || (s == "-")) s = "0"; //数字がなくなってしまった場合は0とする。
}
/*****************************************************************************/
bool noStr(const string& s){               /*数字かどうかを検査する関数。*/
    if(s.empty()){                         //何も入力されていなければ再入力。
        cout << "×何も入力されていません。\n" << endl;
        return false;
    }
    if(s.find_first_not_of("+-", 0) == string::npos){    //符号しかなければ再入力。
        cout << "×符号しか入力されていません。\n" << endl;
        return false;
    }
    string::size_type p;                   //検査開始位置を格納する変数を宣言。
    (s[0] == '+' || s[0] == '-') ? (p = 1) : (p = 0);  //先頭が符号なら二字目から。
    if(s.find_first_not_of("0123456789", p) != string::npos){
        cout << "×数字以外の文字が混入しました。\n" << endl;
        return false;
    }
    return true;
}
/*****************************************************************************/
int main(){
    cout << "最大公約数を求めます。\n二つの整数を入力してください。\n"
         << "整数入力の途中でやめる場合は poipoi と入力してください。\n" << endl;
    string num[2], cont;
    do{
        for(int i = 0; i < 2; i++){
            cout << "★整数" << i + 1 << " を入力してください。" << endl;
            while(1){
                cout << "整数" << i + 1 << ":";
                getline(cin, num[i]);
                    if(num[i] == "poipoi"){
                        cout << "\n途中終了します。\n" << endl;
                        exit(1);
                    }
                if(!noStr(num[i])) continue;
                adjNum(num[i]);
                if(!inRng(num[i])) continue;
                break;
            }
            cout << "○ " << num[i] << " は演算可能です。\n" << endl;
        }
        int a = atoi(num[0].c_str()), b = atoi(num[1].c_str());
        if(a == 0 && b == 0){
            cout << "0 どうしの最大公約数はありません。\n" << endl;
        }else if(a == 0 || b == 0){
            cout << a << " と " << b << " の最大公約数は " 
                 << ((a == 0) ? b : a) << " です。\n" << endl;
        }else{
            cout << a << " と " << b << " の最大公約数は " 
                 << gcd(a, b) << " です。\n" << endl;
        }
        cout << "続ける場合は y を一つだけ、"
             << "終了する場合はそれ以外の文字を入力してください。\n"
             << "続けますか?:";
        getline(cin, cont);
        cout << "\n";
    }while(cont == "y");
    cout << "終了します。\n" << endl;
}