1. 數(shù)據(jù)類型和變量
C++ 中的變量類型與Java很相似。像Java一樣,C++ 有int 和 double 類型。但是這些數(shù)字類型的取值范圍是依賴于機(jī)器的。比如在16位系統(tǒng)上,例如運(yùn)行DOS 或Windows 3.x的PC機(jī)上,int 是雙字節(jié)(2-byte)的,取值范圍比Java的4-byte的int 要小很多。在這些機(jī)器上,如果 int 不夠用的話,你需要使用長(zhǎng)整型long。
C++ 有 short 和 unsigned 類型來更有效的存儲(chǔ)數(shù)字。(我認(rèn)為所謂有效是指更高的空間利用率。) 最好是盡量避免使用這些類型除非是空間利用的有效性對(duì)你的系統(tǒng)真的非常重要。
在C++中布爾型用 bool 表示,而不像在Java中用boolean。
C++ 中字符串類型用 string 表示。它與Java中的 String 類型非常相似,但是,還是要逐一以下幾點(diǎn)不同之處:
1. C++ 字符串存儲(chǔ)ASCII 碼字符,而不是標(biāo)準(zhǔn)碼Unicode 字符
2. C++ 字符串是可以被修改的,而Java字符串的內(nèi)容是不可修改的(immutable)。
3. 取子字符串的操作在 C++ 中叫做 substr,這個(gè)命令s.substr(i, n) 從字符串s中取得從位置 i 開始長(zhǎng)度為n的子字符串。
4. 在C++中,你只能夠?qū)⒆址c其它字符串對(duì)象相串聯(lián)(concatenate),而不能夠與任意的對(duì)象相串聯(lián)。
5. C++中可以直接使用關(guān)系操作符 ==、 !=、 <、 <=、 >、 >= 來進(jìn)行字符串比較,其中后面四個(gè)操作符是按字母順序進(jìn)行比較的。 這比Java中使用函數(shù)equals和compareTo來比較要方便很多。
2. 變量和常量
在C++中,本地變量的定義看起來與Java中相同,例如:
int n = 5;
實(shí)際上這正是C++和Java的一個(gè)重要不同之處。C++編譯器不對(duì)本地變量進(jìn)行初始化檢驗(yàn),所以在C++中很容易忘記初始化一個(gè)變量,這種情況下,變量的值該變量所占內(nèi)存區(qū)域中剛好當(dāng)前存在隨機(jī)值。這顯然是很容易產(chǎn)生程序出錯(cuò)的地方。
與Java一樣, C++中類可以有數(shù)據(jù)域和靜態(tài)變量。不同的是,C++中變量可以在函數(shù)甚至是類的外面定義,這些所謂的全局變量可以在程序的任何函數(shù)中被訪問,因而不易被很好的管理。所C++中應(yīng)該盡量避免使用全局變量。
在C++中,常量可以在任何地方被定義(記得在Java中,常量必須是類的靜態(tài)數(shù)據(jù)static data)。 C++ 使用關(guān)鍵字 const 來定義常量,而Java中是 final。例如:
const int DAYS_PER_YEAR = 365;
3. 類
C++ 中對(duì)類的定義與Java有些不同,這里是一個(gè)例子:一個(gè)C++ 版本的 Point 類:
class Point /* C++ */
{
public:
Point();
Point(double xval, double yval);
void move(double dx, double dy);
double getX() const;
double getY() const;
private:
double x;
double y;
};
這里幾點(diǎn)重要的不同是:
1. C++的類定義中分為公共和私有部分,分別以關(guān)鍵字 public 和 private開始。而在Java中,每一個(gè)元素都必須標(biāo)明 public 或 private。
2. C++中類的定義只包含函數(shù)的聲明,真正的實(shí)現(xiàn)另外單獨(dú)列出。
3. 訪問函數(shù)(accessor methods)標(biāo)有關(guān)鍵字 const ,表明這個(gè)函數(shù)不會(huì)改變本對(duì)象的元素值。
4. 類定義的結(jié)尾處有分號(hào)
類中函數(shù)的實(shí)現(xiàn)跟在類的定義之后。因?yàn)楹瘮?shù)是在類外面定義的,所以每一個(gè)函數(shù)的名字前面要加類名稱作為前綴,并使用操作符雙冒號(hào)::來分割類的名稱和函數(shù)的名稱。不改變隱含參數(shù)值(即當(dāng)前對(duì)象的值)的訪問函數(shù)用 const標(biāo)明。如下所示是上面類定義中的函數(shù)的實(shí)現(xiàn):
Point::Point() { x = 0; y = 0; }
void Point::move(double dx, double dy)
{
x = x + dx;
y = y + dy;
}
double Point::getX() const
{
return x;
}
4. 對(duì)象
Java 與 C++ 最主要的不同在于對(duì)象變量的使用。在 C++中,對(duì)象變量存儲(chǔ)的是真正的對(duì)象的值,而不是對(duì)象引用(reference)。注意在C++中構(gòu)造一個(gè)對(duì)象的時(shí)候是不使用關(guān)鍵字new的,只需要在變量的名字后面直接賦予構(gòu)造函數(shù)的參數(shù)就可以了,例如:
Point p(1, 2); /* 構(gòu)造對(duì)象 p */
如果不跟參數(shù)賦值,則使用默認(rèn)構(gòu)造函數(shù),例如:
Time now; /* 默認(rèn)使用構(gòu)造函數(shù) Time::Time() */
這一點(diǎn)與Java很不同。在Java中,這個(gè)命令僅僅生成一個(gè)沒有初始化的reference,而在C++中,它生成一個(gè)實(shí)際的對(duì)象。
當(dāng)一個(gè)對(duì)象被賦給另一個(gè)對(duì)象變量的時(shí)候,實(shí)際的值將被拷貝。而在Java中,拷貝一個(gè)對(duì)象變量只不過是建立了另外一個(gè)指向?qū)ο蟮膔eference。拷貝一個(gè)C++的對(duì)象就像在Java中調(diào)用clone這個(gè)函數(shù)一樣,而修改拷貝的值不會(huì)改變?cè)瓕?duì)象的值。例如:
Point q = p; /* 拷貝p到q */
q.move(1, 1); /* 移動(dòng)q而p不動(dòng),即q的值變了,而p的不變*/
多數(shù)情況下,C++中這種對(duì)象直接對(duì)值操作的特性使用起來很方便,但是也有些時(shí)候不盡如人意:
1. 當(dāng)需要一個(gè)函數(shù)中修改一個(gè)對(duì)象的值,必須記住要使用按引用調(diào)用call by reference (參見下面函數(shù)部分)
2. 兩個(gè)對(duì)象變量不能指向同一個(gè)對(duì)象實(shí)體。如果你要在C++中實(shí)現(xiàn)這種效果,必須使用指針pointer(參見下面指針部分)
3. 一個(gè)對(duì)象變量只能存儲(chǔ)一種特定的類型的值,如果你想要使用一個(gè)變量來存儲(chǔ)不同子類的對(duì)象的值(多態(tài)ploymorphism),則需要使用指針。
4. 如果你想在C++中使用一個(gè)變量來或者指向null或者指向一個(gè)實(shí)際的對(duì)象,則需要使用指針
5. 函數(shù)
在Java中,每一個(gè)函數(shù)必須或者是對(duì)象函數(shù)(instance method),或者是靜態(tài)函數(shù)(static function)或稱類函數(shù)。C++同樣支持對(duì)象函數(shù)和靜態(tài)函數(shù)(類函數(shù)),但同時(shí)C++也允許定義不屬于任何類的函數(shù),這些函數(shù)叫做全局函數(shù)(global functions)。
特別的是,每一個(gè)C++ 程序都從一個(gè)叫做 main的全局函數(shù)開始執(zhí)行:
int main()
{ . . .
}
還有另外一個(gè)格式的main函數(shù)可以用來捕捉命令行參數(shù),類似于Java的main函數(shù),但是它要求關(guān)于C格式的數(shù)組和字符串的知識(shí),這里就不介紹了。
按照習(xí)慣,通常如果程序執(zhí)行成功, main 函數(shù)返回0,否則返回非零整數(shù)。
同Java一樣,函數(shù)參數(shù)是通過值傳遞的(passed by value)。在Java中,函數(shù)無論如何都是可以修改對(duì)象的值的。然而在C++中,因?yàn)閷?duì)象直接存儲(chǔ)的是實(shí)際的值,而不是指向值的reference,也就是說傳入函數(shù)的是一個(gè)實(shí)際值的拷貝,因此也就無法修改原來對(duì)象的值。
所以,C++ 有兩種參數(shù)傳遞機(jī)制,同Java一樣的按值調(diào)用(call by value) ,以及按地址調(diào)用(call by reference)。當(dāng)一個(gè)參數(shù)是按reference傳遞時(shí),函數(shù)可以修改其原始值。Call by reference 的參數(shù)前面有一個(gè)地址號(hào) & 跟在參數(shù)類型的后面,例如:
void raiseSalary(Employee& e, double by)
{ . . .
}
下面是一個(gè)典型的利用call by reference的函數(shù),在Java中是無法實(shí)現(xiàn)這樣的功能的。
void swap(int& a, int& b)
{ int temp = a;
a = b;
b = temp;
}
如果使用 swap(x, y)來調(diào)用這個(gè)函數(shù),則reference參數(shù) a 和 b 指向原實(shí)際參數(shù)x 和 y的位置,而不是它們的值的拷貝,因此這個(gè)函數(shù)可以實(shí)現(xiàn)實(shí)際交換這兩個(gè)參數(shù)的值。
在 C++中,每當(dāng)需要實(shí)現(xiàn)修改原參數(shù)的值時(shí)你就可以使用按地址調(diào)用 call by reference
6. 向量Vector
C++ 的向量結(jié)構(gòu)結(jié)合了Java中數(shù)組和向量?jī)烧叩膬?yōu)點(diǎn)。一個(gè)C++ 的向量可以方便的被訪問,其容量又可以動(dòng)態(tài)的增長(zhǎng)。如果 T 是任意類型,則 vector<T> 是一個(gè)元素為 T 類型的動(dòng)態(tài)數(shù)組。下面的語句
vector<int> a;
產(chǎn)生一個(gè)初始為空的向量。而語句
vector<int> a(100);
生成一個(gè)初始有100個(gè)元素的向量。你可以使用push_back 函數(shù)來添加元素:
a.push_back(n);
調(diào)用 a.pop_back() 從a中取出最后一個(gè)元素(操作后這個(gè)元素被從a中刪掉), 使用函數(shù)size 可以得到當(dāng)前a中的元素個(gè)數(shù)。
你還可以通過我們熟悉的 [] 操作符來訪問向量中元素,例如:
for (i = 0; i < a.size(); i++) {
sum = sum + a[i];
}
同Java中一樣,數(shù)組索引必須為 0 和 a.size() - 1之間的值。但是與Java不同的是,C++中沒有runtime的索引號(hào)合法性檢驗(yàn)。試圖訪問非法的索引位置可能造成非常嚴(yán)重的出錯(cuò)。
就像所有其它 C++ 對(duì)象一樣,向量也是值。如果你將一個(gè)向量賦值給另外一個(gè)向量變量,所有的元素都會(huì)被拷貝過去。
vector<int> b = a; /* 所有的元素都被拷貝了 */
對(duì)比Java中的情況,在Java中,一個(gè)數(shù)組變量是一個(gè)指向數(shù)組的reference。拷貝這個(gè)變量?jī)H僅產(chǎn)生另外一個(gè)指向同一數(shù)組的reference,而不會(huì)拷貝每一個(gè)元素的值。
正因如此,如果一個(gè)C++函數(shù)要實(shí)現(xiàn)修改向量的值,必須使用reference參數(shù):
void sort(vector<int>& a)
{ . . .
}
7. 輸入和輸出
在C++中,標(biāo)準(zhǔn)的輸入輸出流用對(duì)象 cin 和 cout 表示。我們使用 << 操作符寫輸出,例如:
cout << “Hello, World!”;
也可以連著輸出多項(xiàng)內(nèi)容,例如:
cout << “The answer is ” << x << “\n”;
我們使用 >> 操作符來讀入一個(gè)數(shù)字或單詞,例如:
double x;
cout << “Please enter x: “;
cin >> x;
string fname;
cout << “Please enter your first name: “;
cin >> fname;
函數(shù)getline 可以讀入整行的輸入,例如:
string inputLine;
getline(cin, inputLine);
如果到達(dá)輸入的結(jié)尾,或者一個(gè)數(shù)字無法被正確的讀入,這個(gè)流對(duì)象會(huì)被設(shè)置為 failed 狀態(tài),我們可以使用函數(shù) fail 來檢驗(yàn)這個(gè)狀態(tài),例如:
int n;
cin >> n;
if (cin.fail()) cout << “Bad input”;
一旦一個(gè)流的狀態(tài)被設(shè)為failed,我們是很難重置它的狀態(tài)的,所以如果你的程序需要處理錯(cuò)誤輸入的情況,應(yīng)該使用函數(shù) getline 然后人工處理得到的輸入數(shù)據(jù)。
8. 指針pointer
我們已經(jīng)知道在C++中,對(duì)象變量直接存儲(chǔ)的是對(duì)象的值。這是與Java不同的,在Java中對(duì)象變量存儲(chǔ)的是一個(gè)地址,該地址指向?qū)ο笾祵?shí)際存儲(chǔ)的地方。有時(shí)在C++中也需要實(shí)現(xiàn)這樣的布置,這就用到了指針pointer。在 C++中,一個(gè)指向?qū)ο蟮淖兞拷凶鲋羔槨H绻鸗是一種數(shù)據(jù)類型,則 T* 是指向這種數(shù)據(jù)類型的指針。
就像 Java中一樣,一個(gè)指針變量可以被初始化為空值 NULL,另外一個(gè)指針變量的值,或者一個(gè)調(diào)用new生成的新對(duì)象:
Employee* p = NULL;
Employee* q = new Employee(”Hacker, Harry”, 35000);
Employee* r = q;
實(shí)際上在C++中還有第四種可能,那就是指針可以被初始化為另外一個(gè)對(duì)象的地址,這需要使用地址操作符 & :
Employee boss(”Morris, Melinda”, 83000);
Employee* s = &boss;
這實(shí)際上并不是什么好主意。保險(xiǎn)的做法還是應(yīng)該直接讓指針指向使用 new生成的新對(duì)象。
到目前為止,C++ 指針看起來非常像 Java 的對(duì)象變量。然而,這里有一個(gè)很重要的語法的不同。我們必須使用星號(hào)操作符 * 來訪問指針指向的對(duì)象。如果 p 是一個(gè)指向Employee對(duì)象的指針,則 *p 才代表了這個(gè)對(duì)象:
Employee* p = . . .;
Employee boss = *p;
當(dāng)我們需要執(zhí)行對(duì)象的函數(shù)或訪問對(duì)象的一個(gè)數(shù)據(jù)域時(shí),也需要使用 *p :
(*p).setSalary(91000);
*p外面的括號(hào)是必需的,因?yàn)?. 操作符比 * 操作符有更高的優(yōu)先級(jí)。C的設(shè)計(jì)者覺得這種寫法很難看,所以他們提供了另外一種替代的寫法,使用 -> 操作符來實(shí)現(xiàn) * 和 . 操作符的組合功能。表達(dá)式
p->setSalary(91000);
可以調(diào)用對(duì)象*p的函數(shù) setSalary 。你可以簡(jiǎn)單的記住 . 操作符是在對(duì)象上使用的,-> 操作符是在指針上使用的。
如果你不初始化一個(gè)指針,或者如果一個(gè)指針為空值 NULL 或指向的對(duì)象不再存在,則在它上面使用 * 或 -> 操作符就會(huì)出錯(cuò)。 不幸的是 C++ runtime 系統(tǒng)并不檢查這個(gè)出錯(cuò)。如果你范了這個(gè)錯(cuò)誤,你的程序可能會(huì)行為古怪或死機(jī)。
而在Java中,這些錯(cuò)誤是不會(huì)發(fā)生的。所有的reference都必須初始化,所有的對(duì)象只要仍有reference指向它就不會(huì)被從內(nèi)存中清除,因此你也不會(huì)有一個(gè)指向已被刪除的對(duì)象的reference。Java的runtime 系統(tǒng)會(huì)檢查reference是否為空,并在遇到空指針時(shí)拋出一個(gè)null pointer的例外(exception)。
C++ 和 Java還有一個(gè)顯著的不同,就是 Java 有垃圾回收功能,能夠自動(dòng)回收被廢棄的對(duì)象。而在C++中,需要程序員自己管理內(nèi)存分配回收。
C++中當(dāng)對(duì)象變量超出范圍時(shí)可以自動(dòng)被回收。但是使用new生成的對(duì)象必須用delete操作符手動(dòng)刪除,例如:
Employee* p = new Employee(”Hacker, Harry”, 38000);
. . .
delete p; /* 不在需要這個(gè)對(duì)象 */
如果你忘記刪除一個(gè)對(duì)象,那么你的程序有可能最終用光所有內(nèi)存。這就是我們常說的內(nèi)存泄漏 (memory leak)。更重要的是,如果你如果刪除了一個(gè)對(duì)象,然后又繼續(xù)使用它,你可能覆蓋不屬于你的數(shù)據(jù)。如果你剛巧覆蓋了用于處理內(nèi)存回收的數(shù)據(jù)域,那么內(nèi)存分配機(jī)制就可能運(yùn)轉(zhuǎn)失常而造成更嚴(yán)重的錯(cuò)誤,而且很難診斷和修復(fù)。因此,在C++中最好盡量少用指針
9. 繼承
C++和Java中繼承的基本語法是很相似的。在C++中,使用 : public 代替Java中的extends 來表示繼承關(guān)系 。 (C++ 也支持私有繼承的概念,但是不太有用。)
默認(rèn)情況下,C++中的函數(shù)不是動(dòng)態(tài)綁定的。如果你需要某個(gè)函數(shù)實(shí)現(xiàn)動(dòng)態(tài)綁定,需要使用virtual聲明它為虛函數(shù),例如:
class Manager : public Employee
{
public:
Manager(string name, double salary, string dept);
virtual void print() const;
private:
string department;
};
同Java一樣,構(gòu)造函數(shù)中調(diào)用父類的構(gòu)造函數(shù)有特殊的語法。 Java使用關(guān)鍵字 super。C++中必須在子類的構(gòu)造函數(shù)體外調(diào)用父類的構(gòu)造函數(shù)。下面是一個(gè)例子:
Manager::Manager(string name, double salary, string dept)
: Employee(name, salary) /* 調(diào)用父類的構(gòu)造函數(shù) */
{ department = dept;
}
Java 中在子類函數(shù)中調(diào)用父類的函數(shù)時(shí)也使用關(guān)鍵字 super 。而在C++中是使用父類的名稱加上操作符 ::表示,例如:
void Manager::print() const
{ Employee::print(); /* 調(diào)用父類的函數(shù) */
cout << department << “\n”;
}
一個(gè) C++ 對(duì)象變量只能存儲(chǔ)特定類型的對(duì)象值。要想在C++中實(shí)現(xiàn)多態(tài)(polymorphism),必須使用指針。一個(gè) T* 指針可以指向類型為 T 或 T 的任意子類的對(duì)象,例如:
Employee* e = new Manager(”Morris, Melinda”, 83000, “Finance”);
你可以將父類和不同子類的對(duì)象混合收集到一個(gè)元素均為指針的向量中,然后調(diào)用動(dòng)態(tài)綁定的函數(shù),如下所示:
vector<Employee*> staff;
. . .
for (i = 0; i < staff.size(); i++)
staff[i]->print();