前言 用戶定義的數據類型(data type)或類(class),是C++區別於傳統過程型語言的地方。 通常將創建好的類庫存放在庫(library)中。 本篇會使用幾個C++類庫(class libraries),如:一個很重要的標準庫是輸入/輸出流庫,可以用它從文件或鍵盤讀取數據,並且將數據寫入文 ...
前言
用戶定義的數據類型(data type)或類(class),是C++區別於傳統過程型語言的地方。
通常將創建好的類庫存放在庫(library)中。
本篇會使用幾個C++類庫(class libraries),如:一個很重要的標準庫是輸入/輸出流庫,可以用它從文件或鍵盤讀取數據,並且將數據寫入文件和顯示出來;還有string類和vector類。
語言的翻譯過程
任何一種電腦語言都要從某種人們容易理解的形式(源代碼)轉化為電腦能執行的形式(機器指令)。
Q: 翻譯的分類?
A: 通常,翻譯器(translators)分為兩類:解釋器(interpreters)和編譯器(compilers)
Q: 解釋器?
A: 解釋器將源代碼轉化成一些動作(它可由多組機器指令組成)並立即執行這些動作。
A: 如Shell、Basic語言的解釋器。傳統的Basic解釋器一次翻譯和執行一次,然後將這一行的解釋丟掉,因為解釋器必須重新翻譯任何重覆的代碼,程式執行就變慢了。
A: 而諸如Python語言的解釋器,先把整個程式轉化成某種中間語言,然後由執行速度更快的解釋器來執行。
Q: 解釋器的優缺點?
A: 使用解釋器有很多好處,從寫代碼到執行代碼的轉換幾乎能立即完成,並且源代碼總是現存的,所以一旦出現錯誤,解釋器能很容易地指出。
A: 對於解釋器,較好的交互性和適於快速程式開發(不必要求可執行程式)也是常被提到的兩個優點。
A: 當做大項目時解釋性語言就有某些局限性,但似乎Python是一個例外。解釋器必須駐留記憶體以執行程式,而這樣一來,即使是最快的解釋器其速度也會變得讓人難以接受。
A: 大部分的解釋器要求一次輸入整個源代碼,這不僅造成記憶體空間的限制,而且如果語言不提供設施來隔離不同代碼段之前的影響,一旦出現錯誤,就很難調試。
Q: 編譯器?
A: 編譯器直接把源代碼轉化成彙編語言或機器指令,最終的結果是一個或多個機器代碼的文件,這是一個複雜的過程,通常分幾步完成。
A: 仰仗編譯器設計者的聰明才智,編譯器生成的程式往往只需要少的運行空間,並且執行速度更快。雖然編譯後的程式較小、運行速度快是人們應當使用編譯器的理由,但在許多時候這卻不是最重要的。
A: 某些語言(如C語言)可以分別編譯各段程式,最後使用連接器(linker)把各段程式連接成一個完成的可執行程式,這個過程稱為分段編譯(separate compilcation)。
Q: 分段編譯有什麼好處?
A: 由於編譯器或編譯環境的限制,不能一次完成編譯的整個程式,可以分段編譯。
A: 每次創建和測試程式的一部分,當這部分程式能正常運行後,就把它作為程式塊保存起來。人們把測試通過並能正常運行的程式塊收集起來加入庫(libraray)中,供其他程式員使用。
A: 由於獨立創建每一段程式,其他各段程式的複雜性便被隱藏起來。
Q: 編譯器的調試功能?
A: 編譯器的調試功能不斷地得以改進,早起的編譯器只能產生機器代碼,要知道程式的運行狀態,程式員需要插入列印語句,但這樣做並不總是有效的。
A: 現代編譯器能在可執行程式中插入與源代碼有關的信息,這個信息由一些強大的源代碼層的調試器(source-level debugger)使用,以便通過跟蹤程式經過源代碼的進展來顯示程式的執行情況。
Q: 記憶體中編譯?
A: 為了提高編譯速度,一些編譯器採用記憶體中編譯(in-memory compilcation)。
A: 大多數編譯器,編譯時每一步都要讀寫文件。記憶體中編譯器就是將編譯器程式存放在RAM中。對於小程式來說,記憶體中編譯器幾乎能和解釋器一樣響應。
Q: C++編譯過程?
A: 為了用C/C++編程,應該瞭解編譯過程的步驟和所需工具。
A: 編譯C/C++時,首先要對源代碼執行預處理。預處理器(preprocessor)是一個簡單的程式,它用程式員定義好的模式代替源代碼中的模式
A: 預處理器指令用來節省輸入,增加代碼的可讀性。
A: C++程式設計並不鼓勵多使用預處理指令,因為它可能會引起一些不易發現的錯誤。
A: 預處理過程通常存放在一個中間文件中。
A: 編譯一般分兩遍進行:
-
對預處理過的代碼進行語法分析,編譯器把源代碼分解成小的單元並把它們按樹結構組織起來。如表達式A+B中的A,+,B就是語法分析樹的葉子節點;
-
由代碼生成器(code generator)遍歷語法分析樹,把樹的每個節點轉化為彙編語言或機器代碼。如果代碼生成器生成的是彙編語言,那麼還必須用彙編器對其彙編。
A: 有時候會在編譯的第一遍和第二遍之間使用全局優化器(global optimizer)來生成更短、更快的代碼。
A: 兩種情況的最後結果都是生成目標模塊,通常是以一個.o或.obj為擴展名的文件。
A: 有時也會在編譯的第二遍中使用窺孔優化器(peephole optimizer)從相鄰一段代碼中查找冗餘彙編語句。
A: 連接器(linker)把一組目標模塊連接成為一個可執行程式,操作系統可以裝載和運行它。
A: 當某個目標模塊中的函數要引用另一個目標模塊中的函數或變數時,由連接器來處理這些引用。
A: 連接器能搜索成為“庫”的特殊文件來處理它的所有引用。庫將一組目標模塊包含在一個文件中。庫由一個被稱為庫管理器(librarian)的程式來創建和維護。
A: 更多參考:C/C++程式編譯過程詳解
Q: 靜態類型檢查?
A: 類型檢查(type checking)是編譯器在第一遍中完成的,類型檢查是檢查函數參數是否正確使用,以防止許多程式設計錯誤。
A: 由於類型檢查是在編譯期階段而不是程式運行階段進行的,所以稱之為靜態類型檢查(Static type checking)。
A: 某些面向對象的語言,如Java,也可以在程式運行時部分類型檢查,這個叫做動態類型檢查(dynamic type checking)。
A: 動態類型檢查和靜態類型檢查結合使用,比僅僅使用靜態類型檢查更有效,但它也增加了程式執行的開銷。
A: C++使用靜態類型檢查,因為C++語言不採用任何特殊的運行時支持來處理錯誤操作。
A: 靜態類型檢查在編譯時被告知程式員類型被誤用,從而加快了執行時的速度。
A: 在C++里可以不使用靜態類型檢查,我們可以自己做動態類型檢查,不過這需要寫一些代碼。
分段編譯工具(Tools for separate compilation)
Q: 為什麼要分段編譯?
A: 針對大型的項目,分段編譯尤為重要,在C/C++中可以將大程式構造成許多小程式塊,而這些小程式塊容易管理,可獨立測試。
A: 在C/C++中,以函數(function)作為子程式塊,函數是一段代碼段,可以將這些函數放在不同的文件中,並能分別編譯。
A: 另一種解釋是,函數是程式的基本單位,因為不能把一個函數分開,讓其不同的部分放在不同的文件中,因此整個函數必須完整地放在一個文件里。
A: 當調用函數時,通常要傳給它一些參數(argument),這些參數是我們希望函數執行時使用的值。當函數執行完後,可得到一個返回值(return value),返回值是函數作為執行結果返回的一個值。當然也可以編寫不帶參數且沒有返回值的函數。
Q: 什麼是聲明?
A: 程式可由多個文件構成,一個文件中的函數很可能要訪問另一些文件中的函數和數據。編譯一個文件時,C/C++編譯器必須知道在另一些文件中的函數和數據,特別是它的名字和基本用法。
A: 編譯器就是要確保函數和數據被正確地使用,“告知編譯器”外部函數和數據的名稱以及它們的模樣,這一個過程就叫聲明(declaration)。
A: 一旦聲明瞭一個函數或變數,編譯器知道怎麼檢查對它們的引用,以確保引用正確。
Q: 聲明和定義的區別?
A: 聲明(declaration)和定義(definition)這兩個術語在C/C++的學習中必須要準確地區分和理解。
A: 聲明是向編譯器介紹名字,即標識符,它告訴編譯器“這個函數或者這個變數在某處可找到,它的模樣像什麼”,而定義是說“在這裡建立變數”或“在這裡建立函數”,它為名字分配了存儲空間。
A: 對於變數,編譯器確定變數的大小,然後在記憶體中開闢空間來確保變數的數據;對於函數,編譯器會生成代碼,這些代碼最終也要占用一定的記憶體。因此,無論定義的是函數或者是變數,編譯器都要為它們在定義點分配存儲空間。
A: 在C/C++中,可以在不同的地方聲明相同的變數或函數,但只能有一個定義,有時這被稱為ODR(one-definition rule,單一定義規則)。當連接器連接所有的目標模塊時,如果發現一個函數或變數有多個定義,連接器就會報錯。
A: 定義也可以是聲明,如果定義int x;
之前,編譯器沒有發現標識符x,編譯器則把這一標識符看成是聲明並立即為它分配存儲空間。
Q: 函數聲明的語法?
A: C/C++的函數聲明就是給函數取名、指定函數的參數類型和返回值。
A: 例如,下麵是一個叫func1()的函數聲明,它帶了兩個整數類型的參數並返回一個整數:
int func1(int, int);
分號說明聲明結束,在這種情況下,它告訴編譯器“就這些,這裡沒有函數定義”。
C/C++儘量使聲明形式和使用形式一致。上面的語句可以如下方式使用:
int a;
...
a = func1(2, 3);
因為func1()返回的是一個整數,C/C++編譯器要檢查func1()的使用情況,以確保a能接收返回值,並且還要檢查函數參數的類型匹配情況。
A: 在函數聲明時,可以給參數命名,編譯器會忽略這些參數名稱,但對程式員來說它們可以幫助記憶。例如下麵的形式聲明func1():
int func1(int length, int width);
Q: C和C++對空參的理解?
A: 對於帶空參數表的函數,C和C++有很大的不同,在C語言中,聲明
int func2();
表示“一個可帶任意參數(任意數目,任意類型)的函數”,這就妨礙了類型檢查。
A: 而在C++語言中它意味著“不帶參數的函數”
Q: 函數的定義?
A: 函數定義看起來像函數聲明,但它還有函數體。
A: 函數體是一個用大括弧括起來的語句集,大括弧表示這段代碼的開始和結束。下麵是定義了一個空函數體的函數func1()
int func1(int length, int width) { }
A: 註意,在函數定義中,大括弧代替了分號的作用,因為大括弧括起來了一條或一組語句,所以就不需要分號了。
A: 另外也要註意,如果要在函數體中使用參數的話,函數定義中的參數必須要有名稱。
Q: 變數聲明的語法?
A: 對“變數聲明”的解釋向來很模糊且自相矛盾,而理解它的準確含義對於正確地理解程式十分重要。
A: 變數聲明告知編譯器變數的外表特征,這好像是對編譯器說:“我知道你以前沒有看到過這個名字,但我保證它一定在某個地方,它是X類型的變數”。
A: 函數聲明包括函數返回值類型、函數名稱、參數列表和一個分號,這些信息使得編譯器足以認出它是一個函數聲明並可識別出它的外部特征,由此推斷,變數聲明應該是數據類型後面跟一個標識符,例如:
int a;
但這也產生了一個矛盾:這段代碼有足夠的信息讓編譯器為整數a分配空間,而且編譯器也確實給整數a分配了空間。要解決這個矛盾,對於C/C++需要使用一個關鍵字extern來說明“這隻是一個聲明,它的定義在別的地方”,extern表示變數是在文件以外定義的,或在文件後面部分才定義。
A: 在變數定義前加extern關鍵字表示聲明一個變數但不定義它,例如
extern int a;
A: extern也可用於函數聲明,例如:
extern int func1(int length, int width);
這種聲明方式和先前的func1()聲明方式一樣,因為沒有函數體,編譯器必定把它作為聲明而不是函數定義。因此extern關鍵字對函數來說是多餘的、可選的。C語言的設計者並不要求函數聲明使用extern。
A: 如果函數聲明也要求使用extern,那麼在形式上與變數聲明更加一致,從而減少混亂,但這就需要更多的輸入,這也許能解釋為什麼不要求函數使用extern的原因。
A: 示例:declare.cpp
A: 函數聲明時參數標識符是可選的,函數定義時則要求要有標識符
Q: 包含頭文件?
A: 頭文件(header file)是一個含有某個庫的外部聲明函數和變數的文件,它通常是擴展名為".h"的文件,可能還會看到一些比較老的程式使用其他的擴展名,如".hxx", ".hpp"
A: 包含頭文件,需使用#include預處理器命令,它告訴預處理器打開指定的頭文件併在#include語句所在的地方插入頭文件。
A: #include有兩種方式指定文件:尖括弧和雙引號。
Q: 尖括弧和雙引號的區別?
A: 用尖括弧指定頭文件,如下示例:
#include <header>
用尖括弧來指定該文件時,預處理器是以特定的方式來尋找文件,一般是環境中或編譯器命令行指定的某種尋找路徑。這種設置尋找路徑的機制隨機器、操作系統、C++實現的不同而不同,要視具體情況而定。
用雙引號指定頭文件,如下示例:
#include "local.h"
用雙引號時,預處理器以“由實現定義的方式”來尋找文件,它通常是從當前目錄開始尋找,如果文件沒有找到,那麼include命令就按與尖括弧同樣的方式重新開始尋找。
Q: 標準C++ include語句格式?
A: 隨著C++的不斷演化,不同的編譯器廠商選用了不同的文件擴展名,而且不同的操作系統對文件名有不同的限制,特別是對文件名長度限制,結果引起了對源代碼的可移植性的限制。
A: 為了消除這些差別,標準使用的格式允許文件名長度可以大於眾所周知的8個字元,去除了擴展名。例如,代替老式的包含iostream.h的語句#include <iostream.h>
,現在可以寫成#include <iostream>
了。
A: 如果需要截斷文件名和加上擴展名,翻譯器會按照一定的方式來實現包含語句,以適應特定的編譯器和操作系統。
A: 如果想使用這種沒有擴展名的風格,但編譯器廠商沒有提供這種支持,也可以將廠商提供的頭文件拷貝成沒有擴展名的文件。
A: 從C繼承下來的帶有傳統".h"擴展名的庫仍然可用,然後,也可以用更現代的C++風格使用它們,即在文件名簽名加一個字母"C"。這樣
#include <stdio.h>
#include <stdlib.h>
就變成了
#include <cstdio>
#include <cstdlib>
這就為讀者提供一個區分標誌,說明所使用的是C還是C++庫。
A: 新的包含格式和老的效果是不一樣的:使用.h的文件是老的、非模板化的版本,而沒有.h的文件是新的模板化版本。如果在同一個程式中混用這兩種形式,會遇到某些問題。
Q: 連接目標模塊?
A: 連接器(linker)把由編譯器生成的目標模塊,一般是帶".o"或".obj"擴展名的文件,連接成為操作系統可以載入和執行的程式,它是編譯過程的最後階段。
A: 連接器的特性隨系統不同而不同。
A: 通常,只需告訴連接器目標模塊和要連接的庫的名稱,及可執行程式的名稱,連接器就可以開始執行連接任務了,一些系統要求用戶自己調用連接器,很多C++軟體包可以讓用戶通過C++編譯器來調用連接器。
A: 某些早期的連接器對目標文件和庫文件只查找一次,這些連接器從左到右查找一遍所給的目標文件和庫文件列表,因此目標文件和庫文件的順序就特別重要。如果連接的時候遇到一些莫名其妙的問題,那有可能跟給定的連接器的文件順序有關。
Q: 使用庫文件?
A: 使用庫文件必須:
- 包含庫的頭文件
- 使用庫中的函數和變數
- 把庫連接進可執行程式
目標模塊沒有進入庫時,也可執行上面的步驟,對於C/C++的分段編譯,包含頭文件和連接目標模塊是基本步驟。
Q: 連接器如何查找庫?
A: 當C/C++要對函數和變數進行外部引用時,如果還未遇到過這個函數或變數的定義時,連接器就會把它的標識符加到“未解決的引用”列表中。如果連接器遇到過,則這就是已解決的引用。
A: 如果連接器在目標模塊列表中不能找到函數或變數的定義,它將去查找庫,庫有某種索引方式,連接器不必到庫里查找所有的目標模塊,而只需瀏覽索引。當連接器在庫中找到定義後,就將整個目標模塊而不僅僅是函數定義連接到可執行程式。
A: 註意,僅僅是庫中包含所需定義的目標模塊加入連接,而不是整個庫參加連接,否則程式會變得毫無意義的龐大。
A: 如果想儘量減少程式的長度,當構建自己的庫時,可以考慮一個源代碼只放一個函數。
A: 因為連接器按照指定的順序查找文件,假設用戶定義的函數與標準的庫函數同名,且把帶有這種函數的文件插到庫文件名列表之前,就能用他自己的函數取代庫函數。由於在找到庫文件之前,連接器已先用用戶所給定的函數來解釋引用,因此被使用的是用戶的函數而不是庫函數。註意,這可能是一個bug,並且C++名字空間禁止這麼做。
Q: 秘密地附加模塊?
A: 當創建C/C++可執行程式時,連接器會秘密連接某些模塊,其中之一就是啟動模塊,它包含了對程式的初始化常式(initialization routines)。
A: 初始化常式是開始執行C/C++程式時必須首先執行一段程式,它創建了堆棧,並初始化程式中的某些變數。
A: 連接器總是從標準庫中查找程式中調用的經過編譯的“標準”函數。由於標準庫總可以被找到,所以只要在程式中包含所需的頭文件,就可以使用庫中的任何模塊,並且不必告訴連接器去找標準庫。例如標準的C++庫由iostream()函數,只需包含<iostream>
頭文件即可。
Q: 使用簡單的C語言庫?
A: 用C++來編寫代碼,並不禁止用C的庫函數,事實上,整個C的庫以預設方式包含在標準的C++庫中,這些函數代替用戶做了大量的工作,因此使用標準C++庫,可以節約許多時間。
A: 在某些情況下必須使用非標準C++庫函數的地方,我們也將儘量使用符合POSIX標準的函數。POSIX是基於UNIX上的一個標準,POSIX包括的函數是C++庫沒有的。
編寫第一個C++程式
Q: 使用iostream類?
A: 為了聲明iostream類中的函數和外部數據,要用如下語句包含頭文件:
#include <iostream>
A: iostream包自動定義一個名叫cout的變數(對象),它接受所有與標準輸出綁定的數據。
A: C++允許操作符重載,操作符重載後與某種特殊類型的對象一起使用,它就有了新的含義。和iostream對象在一起,操作符"<<"意思就是"發送到"。
例如:
cout << "hello, fireway";
意思就是把字元串發送到cout對象,cout是控制台輸出(console output)的簡寫。
A: cout對象按從左到有的順序將參數列印出來。
A: 字元串和常數混合出現在cout語句中,使用cout語句是,操作符"<<"根據所帶的參數以不同的含義重載,所以當向cout發送不同的參數時,它能“識別應該對這個參數作何處理”。
A: 註意endl是一個輸入輸出流函數,它表示一行結束併在行末加上一個換行符。endl是一個函數指針。它的函數定義如下:
template<typename _CharT, typename _Traits>
inline basic_ostream<_CharT, _Traits>&
endl(basic_ostream<_CharT, _Traits>& __os)
{ return flush(__os.put(__os.widen('\n'))); }
Q: 名字空間?
A: 在C語言中,當程式達到一定規模之後,會遇到的一個問題是我們“用完了”函數名和標識符。程式通常分成許多塊,每一個塊由不同的人或小組來構造和連接,由於所有的函數名和標識符都在同一個程式里,這就要求所有的開發人員都必須非常小心,不要碰巧使用了相同的函數名和標識符,導致衝突。
A: 標準的C++有預防這種衝突的機制:namespace關鍵字。庫或程式中的每一個C++定義集被封裝在一個名字空間中。
A: 如果其他的定義有相同的名字,但它們在不同的名字空間,就不會產生衝突。
A: 名字空間是十分方便和有用的工具,但名字空間的出現意味著在寫程式之前,必須知道它們,可以用一個關鍵字來聲明:“我要使用這個名字空間中的聲明或定義”,這個關鍵字就是"using"。
A: 所有的標準C++庫都封裝在一個名字空間std中,std即standard。使用語句如下:
using namespace std;
這意味著打開std名字空間,使它的所有名字都可用。有了這條語句,就不用擔心特殊的庫組件是在一個名字空間中,因為在使用using指令的地方,它使名字空間在整個文件中都是可用的。
A: 在人們費盡心機把名字空間的名字隱藏起來之後,再暴露名字空間的所有名字,這看起來是矛盾的,而事實上,應該對這樣的輕率做法倍加小心。但是,using指令僅暴露當前文件的名字,所以它並不像起初聽起來那樣嚴重,但是,如果再想一想在頭文件中這樣做,就是魯莽的舉動。
A: 名字空間和包含頭文件的方法之前存在著相互關係。現代頭文件的包含命令已標準化了,如不帶擴展名“.h”,過去典型包含頭文件的方式是帶上“.h”,如<iostream.h>
。那時名字空間不是語言的一部分,所以,對已經存在的代碼要提供向後相容,如果給出
#include <iostream.h>
它相當於
#include <iostream>
using namespace std;
Q: 程式的基本結構?
A: C/C++程式時變數、函數定義、函數調用的集合。程式開始運行時,它執行初始化代碼並調用一個特殊的函數“main()”。
A: C++語言中,main()總是返回int類型。
關於輸入輸出流
Q: 輸出的另外一些格式?
A: 前面看到的僅僅是輸入輸出流類最基本的用法,它的輸出還有另外的一些格式,比如,對於數值的輸出格式有十進位、八進位、十六進位。
示例:stream.cpp,運行結果link
A: 浮點數的格式由編譯器自動確定。
A: 通過顯示類型轉換(cast),任何字元都能轉換成char類型,發送到流對象。顯示類型轉換看起來像函數調用:char()帶上字元的ASCII值。本示例中,char(27)就是把“escape”發送到cout。
Q: 字元數組的拼接?
A: C預處理器的一個重要功能就是可以進行字元數組的拼接(character array concatenation)。如果兩個加引號的字元數組鄰接,並且它們之前沒有標點,編譯器就會把這些字元數組連接成單個字元數組。
A: 當代碼列表寬度有限制時,字元數組的拼接就特別有用。
示例:concat.cpp,運行結果link
A: 初看,這個程式好像是錯的,因為在每行結束沒有分號。請記住C/C++是自由格式語言,雖然一般情況下看到在每行的末尾帶有一個分號,但實際要求是在每個語句結束時才加分號,而一個語句很可能要寫好幾行。
Q: 讀取輸入數據?
A: 用來完成標準輸入功能的對象是cin(代表console input),cin通常是指從控制台輸入,但這種輸入可以重定向來自其他輸入源。
A: 和cin一起使用的輸入操作符時“>>”,該操作符接受與參數類型相同的輸入。
A: 示例:numconv.cpp,運行結果link
Q: 調用其他程式?
A: 用標準的C語言system()函數,C/C++程式可調用任何程式。system()函數在頭文件中已聲明。
A: 示例:callhello.cpp,運行結果link
A: 從這個程式可以看出,在C++中使用普通的C庫函數是一件很容易的事,只要包含頭文件和調用所需的庫函數就行了。如果已經學習過C語言,那麼C和C++向上相容的特性,會為學習C++帶來很大的幫助。
字元串簡介
Q: string類
A: 雖然字元數組很有用,但它有一定的限制,簡單地說它就是記憶體中的一組字元,如果要用它做什麼事情,必須處理所有的細節,例如,引號內字元數組的大小在編譯期就確定了,如果想在這樣的字元數組中添增字元,需要瞭解很多有關的知識,包括動態記憶體管理,字元數組的拷貝、連接等,才能完成添加任務。這正是我們所希望的有一種對象能替我們完成的事。
A: 標準C++的string類就是設計用來處理並隱藏對字元數組的低級操作,而這些操作早期是由C程式員來完成的。從有C語言以來這些操作就一直是一個編程費時、容易產生錯誤的地方。
A: 為使用string類,需要包含C++頭文件<string>
。string類在名字空間std中,由於操作符重載,string類的使用很直觀。
A: 示例:hellostrings.cpp,運行結果link
A: 字元串s1和s2開始時是空的。
A: s3和s4的兩種不同初始化方法效果是相同的(用一個string對象來初始化另一個string對象)
A: 可以用“=”來給string對象賦值,“=”用其右邊的內容代替string對象先前的內容,不必為先前的內容費心,它會被自動處理。
A: 連接string對象,只需用“+”操作符,“+”也可將string連接到字元數組中。
A: 如果想將string加到一個string或字元數組之後,可以用“+=”操作符完成這一操作。
A: 輸入輸出流知道如何處理string類,所以可直接向cout發送string(或能產生string的表達式,如示例中的s1 + s2 + "!")來列印它。
文件的讀寫
Q: 拷貝文件示例
A: 在C語言中,完成打開和處理文件這樣複雜的操作,需要對C語言有較深的瞭解。然而,C++語言中的iostream庫提供了一種簡單的方法來處理文件。
A: 為了打開文件進行讀寫操作,必須包含<fstream>
。雖然<fstream>
會自動包含<iostream>
,但如果打算使用cin,cout,最好還是顯示地包含<iostream>
。
A: 示例:scopy.cpp
A: 在string庫中,一個十分有用的函數是istream& getline (istream& is, string& str);
,用它可以把一行讀入到string對象中,以換行符結束。getline()第一個形參是ifstream對象,從中讀取內容,第二個形參是stream對象,函數調用完成之後,string對象就裝載了一行的內容。
Q: 文件拷貝到單獨的string?
A: 示例:fillstring.cpp,把整個文件拷貝成單獨的一個string對象。
A: string具有動態特性,不必擔心string的記憶體分配,只管添加新內容進去就行了,string會自動擴展以保存新的輸入。
vector簡介
Q: 什麼是STL?
A: 人們經常會把標準C++庫的容器與演算法,和被稱為STL的東西相混淆。STL(標準模板類庫,Standard Template Library)是94年春天Alex Stepanov(他當時在惠普工作)在加州San Diego的會議上把他的C++庫提交給C++標準委員會時使用的名稱,這個名稱一直沿用下來,特別是惠普決定允許這個庫功公開下載後,使用的人就更多了。
A: C++標準委員會對STL做了大量的修改,將它整合進標準的C++類庫。SGI公司不斷對STL進行改進,SGI的STL與標準的C++庫在許多細節上是不同的,雖然人們經常產生誤解,但實際上C++標準是不包括STL的。由於標準C++的容器與演算法,與SGI的STL有相同的來源,通常是同名,因此容易引起誤會。所以,本書中,將使用“標準C++庫”或“標準庫容器”或其他類似的說法,避免使用“STL”這個術語。
A: vector示例一:fillvector.cpp,運行結果link
A: vector示例二:getwords.cpp,運行結果link
小結
A: 本章主要說明,如果有人已經定義了我們所需要的類,則面向對象編程是很容易的事,這時,只需要簡單地包含一個頭文件,創建對象,並向對象發送消息。如果所用的類功能很強而且設計完善,那麼我們不需要花費很多的力氣就能編寫出很好的程式。
A: 本章介紹了標準C++庫中一些最基本和十分有用的類型:一系列的輸入輸出流、string類和vector模板,可以看到使用這些類庫是多麼簡單。儘管面向對象編程儘可能使編程工作在較高的層次上進行,但C語言的某些基本知識是不能不知道的。
A: 更多C++的學習,可以到http://www.cplusplus.com/