Hello World!
在學C++之前,最好先學習一下C語言
讓我們先運行一段簡單的代碼,編譯器可以使用 在線C++編譯器 或 Xcode(蘋果系統) 或Dev C++(Windows系統)。
#include <iostream>
using namespace std; int main() { cout << "Hello World!" << endl; return 0; }
運行結果:
Hello World!
接下來我們講解一下上面這段程序:
(1)C++ 語言定義了一些頭文件,這些頭文件包含了程序中必需的或有用的信息。上面這段程序中,包含了頭文件 <iostream>。
(2)下一行 using namespace std; 告訴編譯器使用 std 命名空間。命名空間是 C++ 中一個相對新的概念。
什么是命名空間呢?
在C++中,名稱(name)可以是符號常量、變量、宏、函數、結構、枚舉、類和對象等等。為了避免,在大規模程序的設計中,以及在程序員使用各種各樣的C++庫時,這些標識符的命名發生沖突,標準C++引入了關鍵字namespace(命名空間/名字空間/名稱空間/名域),可以更好地控制標識符的作用域。
做個比喻,假如三年級(一)班有個小朋友叫Teodoro,三年級(二)班有個小朋友也叫Teodoro。
在(一)班內部說Teodoro時,大家都知道說的是本班的Teodoro。也就是說,(一)班的Teodoro,其作用域是(一)班。
同理,(二)班的Teodoro,其作用域是(二)班。
那如果是校長要找Teodoro呢?因為有兩個叫Teodoro的小朋友,校長需要把班級名稱都說出來,比如“我找(一)班的Teodoro”,這樣才能明確地指出要找的是哪個Teodoro。(一)班或(二)班,就叫做命名空間。
(3)下一行 int main() 是主函數,程序從這里開始執行。
(4)下一行 cout << "Hello World"; 會在屏幕上顯示消息 "Hello World"。
(5)下一行 return 0; 終止 main( )函數,并向調用進程返回值 0。
?
面向過程與面向對象
先用C++編寫一個加法程序
#include <iostream>
using namespace std; int add(int a, int b) { return a + b; } int main() { int x = 5, y = 10; int z = add(5, 10); cout << "x = " << x << endl; cout << "y = " << y << endl; cout << "x + y = " << z << endl; return 0; }
運行結果:
x = 5
y = 10
x + y = 15
從上一節的Hello World程序和本節的加法程序來看,C和C++貌似看起來差不多,只是修改了一些頭文件和語法而已。那么,二者真的高度相似嗎?
答案是否定的!C和C++差別非常大。
C是面向過程的。
C++既可以面向過程,也可以面向對象,并且以面向對象為主。
為何要以面向對象為主呢?因為若只是面向過程,用C語言就行了,不用再勞心勞力再創造一種新的語言。C++是一些聰明的程序員在C的基礎上創造、發展起來的,與C語言最大的區別就是面向對象。
C語言的重點在于算法和數據結構,C程序的設計首要考慮的是如何通過一個過程,對輸入進行運算處理得到輸出。
所以c語言是面向過程語言。
而對于C++,首要考慮的是如何構造一個對象模型,讓這個模型能夠契合與之對應的問題域,這樣就可以通過獲取對象的狀態信息得到輸出或實現過程控制。
從這一點看,C++是面向對象編程(OOP)的。
C++還像C語言一樣面向過程。
所以C++既是面向過程的語言 ,也是面向對象的語言。一般來說,用它的面向對象這方面。
以后會學到Java。Java不能面向過程,是完全面向對象的。
以狗吃屎為例,來說明面向過程和面向過對象的區別。
C語言:吃(狗,屎)
這里“吃”是函數名,“狗”和“屎”是參數。強調的是吃這一過程。
C++:狗.吃屎()
這里狗是對象,吃屎是狗的一個函數。語法是一個完整的主謂賓結構。主語是“狗”,謂語是“吃”,賓語是“屎”。
狗除了吃屎外,還有其他行為,比如汪汪叫,伸舌頭等。語法為
狗.汪汪叫()
狗.伸舌頭()
這一系列行為里,強調的是“誰來做”,這里是狗來做,所以強調的是狗這一對象。
類與對象
(一)類與對象
類是由我們根據客觀事物抽象而成,形成一類事物,然后用類去定義對象,形成這類事物的具體個體。
比如小狗是一個類,你家的“旺財”則是小狗一個具體的對象。
(二)屬性與方法
一般把類的數據成員稱為類的屬性,把類的函數成員稱為方法。
比如小狗這個類吧,它的屬性有身高、體長、體重、名字、年齡、性別等,它的方法有吃,走,跑,呼吸,吠等。
從這里也可以看出,屬性都是靜態的,而方法都是動作。
(三)程序
#include <iostream>
using namespace std; class Dog { public: string name; // 名字 int age; // 年齡 int sex; // 性別,可以定義為,1表示公,0表示母 float height; // 身高 float length; // 體長 float weight; // 體重 void eat() { cout << "eating..." << endl; } void walk() { cout << "walking..." << endl; } void run() { cout << "running..." << endl; } void breathe() { cout << "breathing..." << endl; } void bark() { cout << "wang! wang!" << endl; } }; int main() { Dog dog; dog.name = "Wang Cai"; dog.age = 3; dog.run(); dog.bark(); return 0; }
運行結果:
running...
wang! wang!
(四)程序分析
(1)在類定義結尾處的}后面需要加分號,這是語法要求。否則編程出錯。
(2)public表示公有的,在類的外部可以訪問。main()函數就屬于類的外部。
(3)Dog dog; 這是聲明一個類型為Dog的對象dog。也可以寫為
Dog dog1;
Dog mydog;
Dog myDog;
按照慣例,對象的首字母建議小寫。
(4)dog.name=xxx; 這種賦值的寫法是給對象設置屬性。
(5)dog.run(); 這種寫法是調用對象的方法。
public, protected, private
面向對象有三大特征:封裝、繼承、多態。(具體會在之后的課程里講)
C++用三個關鍵詞來表示訪問范圍:public, protected, private。
public和private作用就是實現封裝。類外的代碼可以訪問public成員而不能訪問private成員;private成員只能由類成員訪問。
protected的作用則是實現繼承。protected成員可以被派生類(也叫子類)對象訪問,不能被用戶代碼類外的代碼訪問。
例1:private修飾屬性和方法
#include <iostream>
using namespace std; class Dog { private: string name; void run() { cout << "running..." << endl; } }; int main() { Dog dog; dog.name = "Wang Cai"; dog.run(); return 0; }
編譯程序,報錯:

這是因為,name和run()都是私有的,類外的main()沒有權限訪問dog.name和dog.run()
例2:將private改為protected
#include <iostream>
using namespace std; class Dog { protected: String name; void run() { cout << "running..." << endl; } }; int main() { Dog dog; dog.name = "Wang Cai"; dog.run(); return 0; }
編譯程序,報錯:

報錯理由跟上面的差不多,因為name和run()是protected, 外部的main()沒有權限訪問。
例3:將protected改為public
#include <iostream>
using namespace std; class Dog { public: string name; void run() { cout << "running..." << endl; } }; int main() { Dog dog; dog.name = "Wang Cai"; dog.run(); return 0; }
程序正常運行。運行結果:
running...
有些人可能會想,我不加任何關鍵字,后果會怎樣?
例4:不加修飾符
#include <iostream>
using namespace std; class Dog { string name; void run() { cout << "running..." << endl; } }; int main() { Dog dog; dog.name = "Wang Cai"; dog.run(); return 0; }
編譯報錯,錯誤與例1相同:

可見如果不加任何關鍵字,無論是屬性還是方法,默認都是private。
構造函數
構造函數,作用是完成對象的初始化工作。
可類比于:int a = 1;這里是給變量a賦初值。
構造函數是一種特殊的函數,首先構造函數名與類名是完全一致的,其次構造函數沒有類型。
構造函數可以不帶參數,也可以帶參數。
#include <iostream>
using namespace std; class Dog { public: string name; // 無參構造函數 Dog() { cout << "Dog's constructor!" << endl; } // 有參構造函數 Dog(string Name) { name = Name; cout << "Dog's constructor with name!" << endl; } void run() { cout << name << " is running..." << endl; } }; int main() { Dog dog1; dog1.name = "Wang Cai"; dog1.run(); Dog dog2("Xiao Bai"); dog2.run(); return 0; }
運行結果:
Dog’s constructor!
Wang Cai is running...
Dog’s constructor with name!
Xiao Bai is running
從運行結果可以看出,構造函數是在生成對象時被調用的,并且不需要顯示調用。
this指針
this指針是一個隱含于類中的特殊指針,指向對象本身。也就是說對象一旦被創建,this指針也就存在了。
就好比你的名字叫做Teodoro,別人說你的時候用的是Teodoro,但是你說你自己的時候,用的是“我”。
這個“我”,在C++和Java中,是用this來表示的。而在Python和Objective-C(蘋果的開發語言)中,則用self來表示。
程序1
#include <iostream>
using namespace std; class Dog { private: string name; public: Dog(string Name) { name = Name; cout << "Constructor method with name!" << endl; } void run() { cout << name << " is running!" << endl; } }; int main() { Dog dog("Wang Cai"); dog.run(); return 0; }
運行結果:
Constructor method with name!
Wang Cai is running!
分析:
在構造函數里,Name為形參,實參為main()函數中的“Wang Cai”。
通過name = Name賦值后,dog的屬性name就有了值“Wang Cai”。
這樣在run()函數中,就可以打印出屬性name的值出來。
對程序1稍作改動,將構造函數的形參改為name,與類的私有屬性name一樣
程序2
#include <iostream>
using namespace std; class Dog { private: string name; public: Dog(string name) { name = name; cout << "Constructor method with name!" << endl; } void run() { cout << name << " is running!" << endl; } }; int main() { Dog dog("Wang Cai"); dog.run(); return 0; }
運行結果:
Constructor method with name!is running!
分析:
類Dog有一個屬性為name,其作用域為整個類,相當于這個類的全局變量。
構造函數的形參為name,其作用域為構造函數內部,是一個局部變量。
name = name; 這個語句發生在構造函數內部,因為局部變量會屏蔽全局變量,兩個name指的都是形參name。所以這個語句就相當于形參name給自己賦值。這樣類的屬性name沒有被賦值,一直為空。
在執行dog.run()時 ,因為name為空,所以打印出來的就是空值。
如果構造函數的參數名稱與類的屬性名稱一樣,可以顯示調用this來加以區分
程序3
#include <iostream>
using namespace std; class Dog { private: string name; public: Dog(string name) { this->name = name; printf("%p\n", &this->name); printf("%p\n", &name); cout << "Constructor method with name!" << endl; } void run() { cout << name << " is running!" << endl; printf("%p\n", &name); } }; int main() { Dog dog("Wang Cai"); dog.run(); return 0; }
運行結果:
000000000022fe20
000000000022fe30
Constructor method with name!
Wang Cai is running! 000000000022fe20
分析:
構造函數里的語句為this->name = name; 從打印的內存地址就可以看出,this->name與name不是同一回事。
等號左邊的this->name為對象的屬性,等號右邊的name則為構造函數的形參,具體由main()函數中的實參“Wang Cai”賦值。
這個語句的效果為this-> name = name = “Wang Cai”
執行run()時,這個name肯定只能是類的屬性了,而不可能是構造函數的形參。這從打印出來的內存地址可以看出來。
實際上,run()中的name完全等價于this->name,只是this不用顯示寫出來罷了。當然要顯示寫出來也是可以的。
最后,看一個更簡單的例子。
程序4
#include <iostream>
using namespace std; class A { public: A() { printf("Memory address of this: %p\n", this); } }; int main() { A a; printf("Memory address of a: %p\n", &a); return 0; }
運行結果:
Memory address of this: 000000000022fe4f
Memory address of a: 000000000022fe4f
分析:
從運行結果可以看出,在類外部運行的對象的內存地址,與類內部運行的this的內存地址,完全一樣。
這也印證了上面說的,別人口中的Teodoro與Teodoro自己口中的“我”,就是同一個人。
封裝
面向對象有三個特征:封裝、繼承和多態。
本節主要講解封裝。
所有的 C++ 程序都有以下兩個基本要素:
函數:這是程序中執行動作的部分,它們被稱為函數或方法。
數據:數據是程序的信息,會受到程序函數的影響,也叫屬性。
封裝是面向對象編程中的把數據和操作數據的函數綁定在一起的一個概念,這樣能避免受到外界的干擾和誤用,從而確保了安全。
我們已經知道,類包含私有成員(private)、保護成員(protected)和公有成員(public)成員。默認情況下,所有的數據成員和函數成員都是私有的。
為了使類中的成員變成公有的(即程序中的類外部的其他部分也能訪問),必須在這些成員前使用 public 關鍵字進行聲明。所有定義在 public 標識符后邊的屬性或函數可以被程序中所有其他的函數訪問。
例子:
#include <iostream>
using namespace std; class Adder{ public: // 構造函數 Adder(int i = 0) { total = i; } // 對外的接口 void addNum(int number) { total += number; } // 對外的接口 int getTotal() { return total; }; private: // 對外隱藏的數據 int total; }; int main( ) { Adder a; a.addNum(10); a.addNum(20); a.addNum(30); cout << "Total is " << a.getTotal() << endl; return 0; }
運行結果:
Total is 60
上面的類把數字相加,并返回總和。公有成員 addNum 和 getTotal 是對外的接口,用戶需要知道它們以便使用類。私有成員 total 是對外隱藏的(即被封裝起來),用戶不需要了解它,但它又是類能正常工作所必需的。
類的設計策略:
通常而言,要把類的數據成員設計成私有(private),類的函數成員則根據實際需要設計成publice, protected或private。
繼承
先編寫程序:
#include <iostream>
using namespace std; class Animal { protected: float weight; public: void setWeight(float w) { weight = w; } float getWeight() { return weight; } void breathe() { cout << "breathing..." << endl; } }; class Dog : public Animal { private: int legs; public: void setLegs(int number) { legs = number; } int getLegs() { return legs; } void bark() { cout << "wang! wang!" << endl; } }; int main(int argc, char** argv) { Dog dog; dog.setWeight(12.5); dog.setLegs(4); cout << "The dog's weight is " << dog.getWeight() << "kg" << endl; cout << "The dog has " << dog.getLegs() << " legs" << endl; dog.breathe(); dog.bark(); return 0; }
運行結果:
The dog’s weight is 12.5kg
The dog has 4 legs
breathing...
wang! wang!
程序分析:
(1)Animal為一個類,Dog為另一個類。
class Dog : public Animal 表示Dog類繼承了 Animal類。此時,Animal就成了Dog的基類或父類。Dog就成了Animal的派生類或子類。
(2)體重和呼吸是所有動物的共性,所以weight和breathe()定義在類Animal中。腿和吠不是所有動物的共性,所以legs和bark()定義在了類Dog中。
(3)class Dog : public Animal , 這里的public表示繼承方式 。
繼承方式有三種:public, protected和private。
① 父類為private的屬性和方法,不能被子類繼承。
② 父類為protected的成員,若被子類public繼承的,仍為子類的protected成員;若被子類protected或private繼承的,變為子類的private成員。
③ 父類為public的成員,若被子類public繼承的,仍為子類的public成員;若被子類protected繼承的,變為子類的protected成員;若被子類private繼承的,變為子類的private成員。
注:這些不用強記,編多了自然就知道
(4)根據(3)中第③條的結論,若程序改為class Dog : protected Animal,則程序無法通過編譯。因為setWeight()和getWeight()被protected繼承后,變為Dog的protected成員,而protected成員,無法在類外部(比如main函數中)訪問。
構造函數的默認參數
?
構造函數可以預先賦一個初值,其作用是:在構造函數被調用時,省略部分或全部參數,這時就會使用默認參數代替實參。
程序:
#include <iostream>
using namespace std; class Rectangle { private: int width; int height; public: Rectangle(int w = 0, int h = 0) { cout << "Constructor method is invoked!" << endl; width = w; height = h; } int area() { return width * height; } }; int main(int argc, char** argv) { Rectangle rec1; cout << "Area of rec1 is : " << rec1.area() << endl; Rectangle rec2(5); cout << "Area of rec2 is : " << rec2.area() << endl; Rectangle rec3(5, 10); cout << "Area of rec3 is : " << rec3.area() << endl; return 0; }
運行結果:
Constructor method is invoked!
Area of rec1 is : 0
Constructor method is invoked! Area of rec2 is : 0 Constructor method is invoked! Area of rec3 is : 50
分析:
生成對象rec1時,沒有傳入拷貝構造函數的實參,則形參w和h取默認值0
w = 0, h = 0
在構造函數中,weight = w = 0, height = h = 0
在area函數中, weight * height = 0
生成對象rec2時,傳入實參5,相當于傳入(5,0),則w = 5, h = 0
在構造函數中,weight = w = 5, height = h = 0
在area函數中,weight * height = 0
生成對象rec3時,傳入實參(5,10),則w = 5, h = 10
在構造函數中, weight = w = 5, height = h = 10
在area函數中,weight * height = 50
子類構造函數調用父類構造函數
從哲學層面來看,子類會繼承父類除private以外的所有成員。
因為構造函數是公有的,所以理所當然地會被子類繼承。
程序1:
#include <iostream>
using namespace std; class Shape { public: Shape() { cout << "Shape's constructor method is invoked!\n"; } }; class Rectangle : public Shape { public: Rectangle() : Shape() { cout << "Rectangle's constructor method is invoked!\n" << endl; } }; int main(int argc, char** argv) { Rectangle rec; return 0; }
運行結果:
Shape's constructor method is invoked!
Rectangle's constructor method is invoked!
分析:
這里構造函數的寫法是
Rectangle() : Shape()
{
子類構造函數本身的語句;
}
這是先調用父類的構造函數,再執行它本身的語句。從運行結果也可以看出這一點。
那么,如果不顯示調用父類的構造函數Shape()呢?父類的構造函數就不被調用了嗎?
咱們可以用下面的程序來驗證。
程序2:
#include <iostream>
using namespace std; class Shape { public: Shape() { cout << "Shape's constructor method is invoked!\n"; } }; class Rectangle : public Shape { public: Rectangle() { cout << "Rectangle's constructor method is invoked!\n" << endl; } }; int main(int argc, char** argv) { Rectangle rec; return 0; }
運行結果:
Shape's constructor method is invoked!
Rectangle's constructor method is invoked!
分析:
從運行結果可以看出,程序1和程序2的運行結果完全一致。也就是說,Shape()即使不顯示調用,實際上也會被調用。并且調用順序優先于子類本身的構造函數。
“箭頭(->)”和“點號(.)”操作符的區別
先看一個程序:
#include <iostream>
using namespace std; class A { public: void play() { cout << "playing..." << endl; } }; int main() { A a; a.play(); A *p = &a; (*p).play(); p->play(); return 0; }
運行結果:
playing...
playing...
playing...
結論:
在C++中,
若是普通對象,使用點號操作符;
若是指針對象,有兩種操作方式:
(*指針).方法() (1)
指針-->方法() (2)
但是(1)不常用,所以(2)中的箭頭操作符用的比較多。