隨筆-204  評論-149  文章-0  trackbacks-0
           

          從Java到C++ — 對比Java與C++編程的不同

          14th August 2007, 11:40 pm

          原作:Cay Horstmann 英文原文

          翻譯:Aqua  prglab.com

          注:很多程序員包括本人在內都是先學會的Java,然后才學的C++,其實C++與Java有很多相似和互通之處,有相當的Java知識可以對應轉化到C++概念,從而幫助我們快速上手。這篇文章介紹的內容就對從Java向C++轉變很有幫助,所以翻譯推薦給有同樣需要的朋友。翻譯中加入了本人的理解,不完全是全文照搬。有不明或異議,請參考原文,或留言討論。還是那句話,推薦多讀原版資料。

          學習怎樣從一種編程語言轉移到另一種編程語言是今日的程序員必須面對的現實,還好,C++和Java有很多共同的特點,所以從Java轉到C++就容易許多。C++比Java復雜很多,本文并沒打算涵蓋所有C++的功能。但是如果你能夠熟練掌握本文中的所有知識點,也足夠有效的使用C++了。

          這里我們只講Java與C++的不同之處。像流程控制(if, while, for)這些在C++與Java中完全一樣的內容這里就不講了。

          本文是基于ANSI C++標準的,一些老的C++ 編譯器可能不支持這里講到的一些重要功能。要使用這些編譯器,你需要更多學習C++中從C繼承來的部分。那些都超出了本文的范疇,也就不介紹了。

          1. 數據類型和變量

          C++ 中的變量類型與Java很相似。像Java一樣,C++ 有intdouble類型。但是這些數字類型的取值范圍是依賴于機器的。比如在16位系統上,例如運行DOS 或Windows 3.x的PC機上,int是雙字節(2-byte)的,取值范圍比Java的4-byte的int要小很多。在這些機器上,如果 int 不夠用的話,你需要使用長整型long。

          C++ 有 shortunsigned類型來更有效的存儲數字。(我認為所謂有效是指更高的空間利用率。) 最好是盡量避免使用這些類型除非是空間利用的有效性對你的系統真的非常重要。

          在C++中布爾型用 bool表示,而不像在Java中用boolean。

          C++ 中字符串類型用 string表示。它與Java中的 String類型非常相似,但是,還是要逐一以下幾點不同之處:

          1. C++ 字符串存儲ASCII 碼字符,而不是標準碼Unicode 字符

          2. C++ 字符串是可以被修改的,而Java字符串的內容是不可修改的(immutable)。

          3. 取子字符串的操作在 C++ 中叫做 substr,這個命令s.substr(i, n)從字符串s中取得從位置 i 開始長度為n的子字符串。

          4. 在C++中,你只能夠將字符串與其它字符串對象相串聯(concatenate),而不能夠與任意的對象相串聯。

          5. C++中可以直接使用關系操作符 ==、 !=、 <、 <=、 >、 >= 來進行字符串比較,其中后面四個操作符是按字母順序進行比較的。這比Java中使用函數equals和compareTo來比較要方便很多。

          2. 變量和常量

          在C++中,本地變量的定義看起來與Java中相同,例如:

          int n = 5;

          實際上這正是C++和Java的一個重要不同之處。C++編譯器不對本地變量進行初始化檢驗,所以在C++中很容易忘記初始化一個變量,這種情況下,變量的值該變量所占內存區域中剛好當前存在隨機值。這顯然是很容易產生程序出錯的地方。

          與Java一樣, C++中類可以有數據域和靜態變量。不同的是,C++中變量可以在函數甚至是類的外面定義,這些所謂的全局變量可以在程序的任何函數中被訪問,因而不易被很好的管理。所C++中應該盡量避免使用全局變量。

          在C++中,常量可以在任何地方被定義(記得在Java中,常量必須是類的靜態數據static data)。 C++ 使用關鍵字 const來定義常量,而Java中是 final。例如:

          const int DAYS_PER_YEAR = 365;

          3. 類

          C++ 中對類的定義與Java有些不同,這里是一個例子:一個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;

          };

          這里幾點重要的不同是:

          1. C++的類定義中分為公共和私有部分,分別以關鍵字 publicprivate開始。而在Java中,每一個元素都必須標明 publicprivate

          2. C++中類的定義只包含函數的聲明,真正的實現另外單獨列出。

          3. 訪問函數(accessor methods)標有關鍵字 const,表明這個函數不會改變本對象的元素值。

          4. 類定義的結尾處有分號

          類中函數的實現跟在類的定義之后。因為函數是在類外面定義的,所以每一個函數的名字前面要加類名稱作為前綴,并使用操作符雙冒號::來分割類的名稱和函數的名稱。不改變隱含參數值(即當前對象的值)的訪問函數用 const標明。如下所示是上面類定義中的函數的實現:

          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. 對象

          Java 與 C++ 最主要的不同在于對象變量的使用。

          在 C++中,對象變量存儲的是真正的對象的值,而不是對象引用(reference)。注意在C++中構造一個對象的時候是不使用關鍵字new的,只需要在變量的名字后面直接賦予構造函數的參數就可以了,例如:

          Point p(1, 2); /* 構造對象 p */

          如果不跟參數賦值,則使用默認構造函數,例如:

          Time now; /* 默認使用構造函數 Time::Time() */

          這一點與Java很不同。在Java中,這個命令僅僅生成一個沒有初始化的對象reference,要創建對象還得使用new在堆中創建一個實際的對象.  

          而在C++中,它生成一個實際的對象。

          當一個對象被賦給另一個對象變量的時候,實際的值將被拷貝。而在Java中,拷貝一個對象變量只不過是建立了另外一個指向對象的reference。拷貝一個C++的對象就像在Java中調用clone這個函數一樣,而修改拷貝的值不會改變原對象的值。例如:

          Point q = p; /* 拷貝p到q ,q是另外一個新的對象,有自己的內存空間*/

          q.move(1, 1); /* 移動q而p不動,即q的值變了,而p的不變*/

          多數情況下,C++中這種對象直接對值操作的特性使用起來很方便,但是也有些時候不盡如人意:

          C++中對象的指針非常類似于Java中的引用

          1. 當需要一個函數中修改一個對象的值,必須記住要使用按引用調用call by reference (參見下面函數部分),或者將參數定義為指針類型,在函數中通過指針來操作其實際指向的對象

          2. 兩個對象變量不能指向同一個對象實體。如果你要在C++中實現這種效果,必須使用指針pointer(參見下面指針部分)

          3. 一個對象變量只能存儲一種特定的類型的值,如果你想要使用一個變量來存儲不同子類的對象的值(多態ploymorphism),則需要使用指針。

          4. 如果你想在C++中使用一個變量來或者指向null或者指向一個實際的對象,則需要使用指針

          5. 函數

          在Java中,每一個函數必須或者是對象函數(instance method),或者是靜態函數(static function)或稱類函數。C++同樣支持對象函數和靜態函數(類函數),但同時C++也允許定義不屬于任何類的函數,這些函數叫做全局函數(global functions)

          特別的是,每一個C++ 程序都從一個叫做 main的全局函數開始執行:

          int main()

          { . . .

          }

          還有另外一個格式的main函數可以用來捕捉命令行參數,類似于Java的main函數,但是它要求關于C格式的數組和字符串的知識,這里就不介紹了。

          按照習慣,通常如果程序執行成功, main函數返回0,否則返回非零整數。

          同Java一樣,函數參數是通過值傳遞的(passed by value)。在Java中,函數無論如何都是可以修改對象(類對象)的值的(因為java中傳遞的是java中的引用,相當與C++中的指針。Java函數返回時也是返回的引用,將此引用拷貝到外面的某個引用變量中)。然而在C++中,因為對象直接存儲的是實際的值而不是指向值的reference,也就是說傳入函數的是一個實際值的拷貝,因此也就無法修改原來對象的值。(C++中函數返回時也是要拷貝到外面的變量中的,是指針就拷貝指針,是對象就進行對象的拷貝)

          所以,C++ 有兩種參數傳遞機制,同Java一樣的按值調用(call by value) ,以及按地址調用(call by reference)。當一個參數是按reference傳遞時,函數可以修改其原始值。Call by reference 的參數前面有一個地址號 &跟在參數類型的后面,例如:

          void raiseSalary(Employee& e, double by)

          { . . .

          }

          下面是一個典型的利用call by reference的函數,在Java中是無法實現這樣的功能的

          void swap(int& a, int& b)

          { int temp = a;

          a = b;

          b = temp;

          }

          如果使用 swap(x, y)來調用這個函數,則reference參數 ab指向原實際參數xy的位置,而不是它們的值的拷貝,因此這個函數可以實現實際交換這兩個參數的值。

          在 C++中,每當需要實現修改原參數的值時你就可以使用按地址調用 call by reference 。

           

           

           

          6. 向量Vector

          C++ 的向量結構結合了Java中數組和向量兩者的優點。一個C++ 的向量可以方便的被訪問,其容量又可以動態的增長。如果 T是任意類型,則 vector<T>是一個元素為 T類型的動態數組。下面的語句

          vector<int> a;

          產生一個初始為空的向量。而語句

          vector<int> a(100);

          生成一個初始有100個元素的向量。你可以使用push_back函數來添加元素:

          a.push_back(n);

          調用 a.pop_back()a中取出最后一個元素(操作后這個元素被從a中刪掉), 使用函數size可以得到當前a中的元素個數。

          你還可以通過我們熟悉的 []操作符來訪問向量中元素,例如:

          for (i = 0; i < a.size(); i++) {

          sum = sum + a[i];

          }

          同Java中一樣,數組索引必須為 0 和a.size() - 1之間的值。但是與Java不同的是,C++中沒有runtime的索引號合法性檢驗。試圖訪問非法的索引位置可能造成非常嚴重的出錯。

          就像所有其它 C++ 對象一樣,向量也是值。如果你將一個向量賦值給另外一個向量變量,所有的元素都會被拷貝過去。

          vector<int> b = a; /* 所有的元素都被拷貝了 */

          對比Java中的情況,在Java中,一個數組變量是一個指向數組的reference。拷貝這個變量僅僅產生另外一個指向同一數組的reference,而不會拷貝每一個元素的值。

          正因如此,如果一個C++函數要實現修改向量的值,必須使用reference參數:

          void sort(vector<int>& a)

          { . . .

          }

          7. 輸入和輸出

          在C++中,標準的輸入輸出流用對象 cincout表示。我們使用 <<操作符寫輸出,例如:

          cout << “Hello, World!”;

          也可以連著輸出多項內容,例如:

          cout << “The answer is ” << x << “"n”;

          我們使用 >>操作符來讀入一個數字或單詞,例如:

          double x;

          cout << “Please enter x: “;

          cin >> x;

          string fname;

          cout << “Please enter your first name: “;

          cin >> fname;

          函數getline可以讀入整行的輸入,例如:

          string inputLine;

          getline(cin, inputLine);

          如果到達輸入的結尾,或者一個數字無法被正確的讀入,這個流對象會被設置為 failed 狀態,我們可以使用函數 fail來檢驗這個狀態,例如:

          int n;

          cin >> n;

          if (cin.fail()) cout << “Bad input”;

          一旦一個流的狀態被設為failed,我們是很難重置它的狀態的,所以如果你的程序需要處理錯誤輸入的情況,應該使用函數 getline然后人工處理得到的輸入數據。

          8. 指針pointer

          我們已經知道在C++中,對象變量直接存儲的是對象的值。這是與Java不同的,在Java中對象變量存儲的是一個地址,該地址指向對象值實際存儲的地方。有時在C++中也需要實現這樣的布置,這就用到了指針pointer。在 C++中,一個指向對象的變量叫做指針。如果T是一種數據類型,則 T*是指向這種數據類型的指針。

          就像 Java中一樣,一個指針變量可以被初始化為空值 NULL,另外一個指針變量的值,或者一個調用new生成的新對象:

          Employee* p = NULL;

          Employee* q = new Employee(”Hacker, Harry”, 35000);

          Employee* r = q;

          實際上在C++中還有第四種可能,那就是指針可以被初始化為另外一個對象的地址,這需要使用地址操作符 &

          Employee boss(”Morris, Melinda”, 83000);

          Employee* s = &boss;

          這實際上并不是什么好主意。保險的做法還是應該直接讓指針指向使用 new生成的新對象。

          到目前為止,C++ 指針看起來非常像 Java 的對象引用變量。然而,這里有一個很重要的語法的不同。我們必須使用星號操作符 *來訪問指針指向的對象。如果 p是一個指向Employee對象的指針,則 *p才代表了這個對象:

          Employee* p = . . .;

          Employee boss = *p;

          當我們需要執行對象的函數或訪問對象的一個數據域時,也需要使用 *p

          (*p).setSalary(91000);

          *p外面的括號是必需的,因為 .操作符比 * 操作符有更高的優先級。C的設計者覺得這種寫法很難看,所以他們提供了另外一種替代的寫法,使用 -> 操作符來實現 *.操作符的組合功能。表達式

          p->setSalary(91000);

          可以調用對象*p的函數 setSalary。你可以簡單的記住.操作符是在對象上使用的,-> 操作符是在指針上使用的。

          如果你不初始化一個指針,或者如果一個指針為空值 NULL 或指向的對象不再存在,則在它上面使用 *->操作符就會出錯。 不幸的是 C++ runtime 系統并不檢查這個出錯。如果你范了這個錯誤,你的程序可能會行為古怪或死機。

           

          而在Java中,這些錯誤是不會發生的。所有的reference都必須初始化,所有的對象只要仍有reference指向它就不會被從內存中清除,因此你也不會有一個指向已被刪除的對象的reference。Java的runtime 系統會檢查reference是否為空,并在遇到空指針時拋出一個null pointer的例外(exception)。

          C++ 和 Java還有一個顯著的不同,就是 Java 有垃圾回收功能,能夠自動回收被廢棄的對象。而在C++中,需要程序員自己管理內存分配回收。

          C++中當對象變量超出范圍時可以自動被回收。但是使用new生成的對象必須用delete操作符手動刪除,例如:

          Employee* p = new Employee(”Hacker, Harry”, 38000);

          . . .

          delete p; /* 不在需要這個對象 */

          如果你忘記刪除一個對象,那么你的程序有可能最終用光所有內存。這就是我們常說的內存泄漏 (memory leak)。更重要的是,如果你如果刪除了一個對象,然后又繼續使用它,你可能覆蓋不屬于你的數據。如果你剛巧覆蓋了用于處理內存回收的數據域,那么內存分配機制就可能運轉失常而造成更嚴重的錯誤,而且很難診斷和修復。因此,在C++中最好盡量少用指針。

          9. 繼承

          C++和Java中繼承的基本語法是很相似的。在C++中,使用 : public代替Java中的extends來表示繼承關系 。 (C++ 也支持私有繼承的概念,但是不太有用。)

          默認情況下,C++中的函數不是動態綁定的。如果你需要某個函數實現動態綁定,需要使用virtual聲明它為虛函數,例如:

          class Manager : public Employee

          {

          public:

          Manager(string name, double salary, string dept);

          virtual void print() const;

          private:

          string department;

          };

          同Java一樣,構造函數中調用父類的構造函數有特殊的語法。 Java使用關鍵字 super。C++中必須在子類的構造函數體外調用父類的構造函數。下面是一個例子:

          Manager::Manager(string name, double salary, string dept)

          : Employee(name, salary) /* 調用父類的構造函數 */

          { department = dept;

          }

          Java 中在子類函數中調用父類的函數時也使用關鍵字 super。而在C++中是使用父類的名稱加上操作符 ::表示,例如:

          void Manager::print() const

          { Employee::print(); /* 調用父類的函數 */

          cout << department << “"n”;

          }

          一個 C++ 對象變量只能存儲特定類型的對象值。要想在C++中實現多態(polymorphism),必須使用指針。一個 T*指針可以指向類型為 TT的任意子類的對象,例如:

          Employee* e = new Manager(”Morris, Melinda”, 83000, “Finance”);

          你可以將父類和不同子類的對象混合收集到一個元素均為指針的向量中,然后調用動態綁定的函數,如下所示:

          vector<Employee*> staff;

          . . .

          for (i = 0; i < staff.size(); i++)

          staff[i]->print();

          posted on 2009-05-11 14:58 Frank_Fang 閱讀(500) 評論(0)  編輯  收藏 所屬分類: C++編程
          主站蜘蛛池模板: 西林县| 吉水县| 色达县| 比如县| 大方县| 甘谷县| 龙门县| 连江县| 会同县| 潞西市| 河津市| 屯留县| 利辛县| 塔河县| 岱山县| 和龙市| 武乡县| 佛学| 石阡县| 德化县| 大安市| 武邑县| 乡城县| 呼和浩特市| 长宁区| 宣威市| 宁波市| 炉霍县| 阜新市| 咸宁市| 涿州市| 威海市| 黔南| 平陆县| 临沂市| 渑池县| 淄博市| 洛阳市| 会宁县| 军事| 双江|