通訊錄整包讀寫(延伸) 題解


簡潔題意

照規格定義結構體 student(三個成員),並在結構體外面多載 >>(讀一位同學)與 <<(印一位同學、不換行)。評測程式用 while (cin >> s) 讀到 EOF,再倒序 cout << v[i] << '\n'。

解法

這是 14.8 運算子多載:輸入與輸出 的延伸題——14.6 的 operator < 寫成成員函式,因為 x < y 的左邊是自己的結構體;但 cin >> s 的左邊是 cin,型態是 istream,那是標準函式庫的東西,你塞不進去。所以 >>、<< 要寫成結構體外面的普通函式,參數就是運算子的左右兩邊:

istream& operator >> (istream &is, student &s) {   // cin >> s  就是  operator >> (cin, s)
    is >> s.name >> s.seat >> s.phone;
    return is;
}

四個細節,每一個評測程式都會檢查:

  • is 傳參考:讀取會改變 stream 的內部狀態(讀到哪了、有沒有失敗)。
  • s 傳參考、不加 const:>> 要把讀到的值寫進 s——傳值的話寫進去的是複本,呼叫端的 s 什麼都拿不到。<< 只讀不改,所以那邊寫 const student &s。
  • 回傳 stream 本人:return is;。評測程式的 while (cin >> s) 靠回傳的 stream 判斷「上一次讀成功了沒」,讀到 EOF 時 stream 變成失敗狀態、迴圈才停得下來;cout << v[i] << '\n' 也是靠 << 回傳的 cout 接力印出換行。
  • << 不換行:規格說換行由呼叫端補,你的 << 只印 座號 姓名 電話——這也是 14.8 範例的寫法,印完之後要不要換行、要不要接著印別的,交給呼叫的人決定。

順帶一提,這題的輸入沒有行數,正是上冊 4.8 的 EOF 輸入模式——多載好 >>,while (cin >> s) 對你的型態就自動成立。

常犯錯誤

  • 把 >> 寫成成員函式 istream& operator >> (istream &is)(CE):[檢查] 還沒有多載 operator >>:要寫在結構體外面 …
  • istream& operator >> (istream &is, student s)——s 傳值(CE):[檢查] operator >> 的第二個參數要是 `student &s`
  • void operator >> (…)——沒回傳 stream(CE):[檢查] operator >> 必須回傳 istream&
  • ostream& operator << (ostream &os, student &s)——少了 const(CE):[檢查] operator << 的第二個參數要是 const student &
  • void operator << (…)(CE):[檢查] operator << 必須回傳 ostream&
  • << 裡自己印了 '\n'(WA):每兩筆之間多一個空行
  • 回傳型態寫對了,但函式最後忘了 return is;(編譯只給警告):這是未定義行為,評測程式擋不到;在 OJ 的 -O2 下多半直接當掉(RE)或迴圈停不下來

最後一種最陰險:本機不開最佳化時可能碰巧看起來正常。寫 >>/<< 的固定動作——最後一行永遠是 return is;/return os;。

參考程式碼

struct student {
    string name;    // 姓名
    int seat;       // 座號
    string phone;   // 電話:可能以 0 開頭,所以是字串不是整數
};

istream& operator >> (istream &is, student &s) {          // 教 cin 怎麼讀一位同學:s 要傳參考,讀到的值才回得去
    is >> s.name >> s.seat >> s.phone;
    return is;                                            // 把 stream 還回去,while (cin >> s) 才知道何時該停
}

ostream& operator << (ostream &os, const student &s) {    // 教 cout 怎麼印一位同學:只讀不改,加 const
    os << s.seat << ' ' << s.name << ' ' << s.phone;      // 不換行,換行由呼叫端決定
    return os;
}