C++ 在 C 語言的基礎上增加了面向對象編程,C++ 支持面向對象程序設計。類是 C++ 的核心特性,通常被稱爲用戶定義的類型。
1.面向對象:
- 類&對象:類用於指定對象的形式,它包含了數據表示法和用於處理數據的方法。類中的數據和方法稱爲類的成員。函數在一個類中被稱爲類的成員。
類的定義:類定義是以關鍵字class開頭,後跟類的名稱。
class Box
{
public:
double length; // 盒子的長度
double breadth; // 盒子的寬度
double height; // 盒子的高度
};
定義類的對象:類提供了對象的藍圖,所以基本上,對象是根據類來創建的。
Box Box1; // 聲明 Box1,類型爲 Box
Box Box2; // 聲明 Box2,類型爲 Box
訪問數據成員:類的對象的公共數據成員可以使用直接成員訪問運算符 (.) 來訪問。
Box1.height = 5.0;
類的成員函數:類的成員函數是指那些把定義和原型寫在類定義內部的函數,就像類定義中的其他變量一樣。類成員函數是類的一個成員,它可以操作類的任意對象,可以訪問對象中的所有成員。
class Box
{
public:
double length; // 長度
double breadth; // 寬度
double height; // 高度
double getVolume(void);// 返回體積
};
類的訪問修飾符:public/private/protected。
構造函數:類的構造函數是類的一種特殊的成員函數,它會在每次創建類的新對象時執行。構造函數的名稱與類的名稱是完全相同的,並且不會返回任何類型,也不會返回 void。構造函數可用於爲某些成員變量設置初始值。
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 這是構造函數
private:
double length;
};
// 成員函數定義,包括構造函數
Line::Line(void)
{
cout << "Object is being created" << endl;
}
void Line::setLength( double len )
{
length = len;
}
使用初始化列表來初始化字段:
Line::Line( double len): length(len)
{
cout << "Object is being created, length = " << len << endl;
}
//類同與如上語法
Line::Line( double len)
{
length = len;
cout << "Object is being created, length = " << len << endl;
}
類的析構函數:類的析構函數是類的一種特殊的成員函數,它會在每次刪除所創建的對象時執行。析構函數的名稱與類的名稱是完全相同的,只是在前面加了個波浪號(~)作爲前綴,它不會返回任何值,也不能帶有任何參數。析構函數有助於在跳出程序(比如關閉文件、釋放內存等)前釋放資源。
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 這是構造函數聲明
~Line(); // 這是析構函數聲明
private:
double length;
};
// 成員函數定義,包括構造函數
Line::Line(void)
{
cout << "Object is being created" << endl;
}
Line::~Line(void)
{
cout << "Object is being deleted" << endl;
}
C++類的拷貝函數:一種特殊的構造函數,它在創建對象時,是使用同一類中之前創建的對象來初始化新創建的對象。通常用於:通過使用另一個同類型的對象來初始化新創建的對象;複製對象把它作爲參數傳遞給函數;複製對象,並從函數返回這個對象。
如果在類中沒有定義拷貝構造函數,編譯器會自行定義一個。如果類帶有指針變量,並有動態內存分配,則它必須有一個拷貝構造函數。拷貝構造函數的最常見形式如下:
class Line
{
public:
int getLength( void );
Line( int len ); // 簡單的構造函數
Line( const Line &obj); // 拷貝構造函數
~Line(); // 析構函數
private:
int *ptr;
};
Line::Line(const Line &obj)
{
cout << "調用拷貝構造函數併爲指針 ptr 分配內存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷貝值
}
C++類友元函數:類的友元函數是定義在類外部,但有權訪問類的所有私有(private)成員和保護(protected)成員。儘管友元函數的原型有在類的定義中出現過,但是友元函數並不是成員函數。友元可以是一個函數,該函數被稱爲友元函數;友元也可以是一個類,該類被稱爲友元類,在這種情況下,整個類及其所有成員都是友元。如果要聲明函數爲一個類的友元,需要在類定義中該函數原型前使用關鍵字friend。
class Box
{
double width;
public:
double length;
friend void printWidth( Box box );
void setWidth( double wid );
};
C++內聯函數:通常與類一起使用。如果一個函數是內聯的,那麼在編譯時,編譯器會把該函數的代碼副本放置在每個調用該函數的地方。對內聯函數進行任何修改,都需要重新編譯函數的所有客戶端,因爲編譯器需要重新更換一次所有的代碼,否則將會繼續使用舊的函數。如果想把一個函數定義爲內聯函數,則需要在函數名前面放置關鍵字 inline,在調用函數之前需要對函數進行定義。如果已定義的函數多於一行,編譯器會忽略 inline 限定符。在類定義中的定義的函數都是內聯函數,即使沒有使用 inline 說明符。
C++中的this指針:每一個對象都能通過 this 指針來訪問自己的地址。this 指針是所有成員函數的隱含參數。因此,在成員函數內部,它可以用來指向調用對象。友元函數沒有 this 指針,因爲友元不是類的成員。只有成員函數纔有 this 指針。
C++中指向類的指針:一個指向 C++ 類的指針與指向結構的指針類似,訪問指向類的指針的成員,需要使用成員訪問運算符 ->,就像訪問指向結構的指針一樣。與所有的指針一樣,必須在使用指針之前,對指針進行初始化。
C++類的靜態成員:可以使用 static 關鍵字來把類成員定義爲靜態的。當我們聲明類的成員爲靜態時,這意味着無論創建多少個類的對象,靜態成員都只有一個副本。
2.繼承:
繼承允許我們依據另一個類來定義一個類,這使得創建和維護一個應用程序變得更容易。這樣做,也達到了重用代碼功能和提高執行效率的效果。當創建一個類時,您不需要重新編寫新的數據成員和成員函數,只需指定新建的類繼承了一個已有的類的成員即可。這個已有的類稱爲基類,新建的類稱爲派生類。
- 基類&派生類:一個類可以派生自多個類,這意味着,它可以從多個基類繼承數據和函數;
// 基類
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 派生類
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
- 訪問控制&繼承:
派生類可以訪問基類中所有的非私有成員。因此基類成員如果不想被派生類的成員函數訪問,則應在基類中聲明爲 private。
一個派生類繼承了所有的基類方法,除了:基類的構造函數/析構函數/拷貝函數;基類的重載運算符;基類的友元函數。
- 繼承類型:當一個類派生自基類,該基類可以被繼承爲 public、protected 或 private 幾種類型。
- 多繼承:多繼承即一個子類可以有多個父類,它繼承了多個父類的特性。
// 基類 PaintCost
class PaintCost
{
public:
int getCost(int area)
{
return area * 70;
}
};
// 派生類
class Rectangle: public Shape, public PaintCost
{
public:
int getArea()
{
return (width * height);
}
};
3.重載運算符&重載函數:
C++允許在同一個作用域中某個運算符和函數指定多重定義,稱之爲運算符重載和函數重載。重載聲明是指一個與之前已經在該作用域內聲明過的函數或方法具有相同名稱的聲明,但是它們的參數列表和定義(實現)不相同。
- 函數重載:在同一個作用域內,可以聲明幾個功能類似的同名函數,但是這些同名函數的形式參數(指參數的個數、類型或者順序)必須不同。
- 運算符重載:重載的運算符是帶有特殊名稱的函數,函數名是由關鍵字 operator 和其後要重載的運算符符號構成的。與其他函數一樣,重載運算符有一個返回類型和一個參數列表。
#include <iostream>
using namespace std;
class Distance
{
private:
int feet; // 0 到無窮
int inches; // 0 到 12
public:
// 所需的構造函數
Distance(){
feet = 0;
inches = 0;
}
Distance(int f, int i){
feet = f;
inches = i;
}
// 顯示距離的方法
void displayDistance()
{
cout << "F: " << feet << " I:" << inches <<endl;
}
// 重載負運算符( - )
Distance operator- ()
{
feet = -feet;
inches = -inches;
return Distance(feet, inches);
}
};
int main()
{
Distance D1(11, 10), D2(-5, 11);
-D1; // 取相反數
D1.displayDistance(); // 距離 D1
-D2; // 取相反數
D2.displayDistance(); // 距離 D2
return 0;
}
不能重載的運算符:成員訪問運算符(.)/成員指針訪問運算符(.*/->*)/域運算符(::)/長度運算符(sizeof)/條件運算符(?:)/預處理運算符(#)。
4.C++多態:
C++ 多態意味着調用成員函數時,會根據調用函數的對象的類型來執行不同的函數。
- 虛函數:是在基類中使用關鍵字virtual聲明的函數。在派生類中重新定義基類中定義的虛函數時,會告訴編譯器不要靜態鏈接到該函數。想要的是在程序中任意點可以根據所調用的對象類型來選擇調用的函數,這種操作被稱爲動態連接,或後期綁定。
- 純虛函數:想要在基類中定義虛函數,以便在派生類中重新定義該函數更好地適用於對象,但是您在基類中又不能對虛函數給出有意義的實現,這個時候就會用到純虛函數。
5.數據抽象:
只向外界提供關鍵信息,並隱藏其後臺的實現細節,即只表現必要的信息而不呈現細節。
6.數據封裝:
封裝是面向對象編程中的把數據和操作數據的函數綁定在一起的一個概念,這樣能避免受到外界的干擾和誤用,從而確保了安全。數據封裝引申出了另一個重要的 OOP 概念,稱數據隱藏。數據封裝是一種把數據和操作數據的函數捆綁在一起的機制,數據抽象一種僅向用戶暴露接口而把具體的實現細節隱藏起來的機制。
7.C++接口(抽象類):
接口描述了類的行爲和功能,而不需要完成類的特定實現。C++ 接口是使用抽象類來實現的,抽象類與數據抽象互不混淆,數據抽象是一個把實現細節與相關的數據分離開的概念。
8.C++文件和流:
C++ 中標準庫fstream定義的三種數據類型:
數據類型 | 描述 |
ofstream | 該數據類型表示輸出文件流,用於創建文件並向文件寫入信息。 |
ifstream | 該數據類型表示輸入文件流,用於從文件讀取信息。 |
fstream | 該數據類型通常表示文件流,且同時具有 ofstream 和 ifstream 兩種功能,這意味着它可以創建文件,向文件寫入信息,從文件讀取信息。 |
- 打開文件:在從文件讀取信息或者向文件寫入信息之前,必須先打開文件。
open() 函數的標準語法,open() 函數是 fstream、ifstream 和 ofstream 對象的一個成員:
void open(const char *filename, ios::openmode mode);
open() 成員函數的第一參數指定要打開的文件的名稱和位置,第二個參數定義文件被打開的模式。
模式標誌 | 描述 |
ios::app | 追加模式。所有寫入都追加到文件末尾。 |
ios::ate | 文件打開後定位到文件末尾。 |
ios::in | 打開文件用於讀取。 |
ios::out | 打開文件用於寫入。 |
打開文件用於寫入。 | 如果該文件已經存在,其內容將在打開文件之前被截斷,即把文件長度設爲 0。 |
- 關閉文件:當 C++ 程序終止時,它會自動關閉刷新所有流,釋放所有分配的內存,並關閉所有打開的文件。
void close();
- 寫入文件:使用流插入運算符( << )向文件寫入信息,就像使用該運算符輸出信息到屏幕上一樣。唯一不同的是,在這裏您使用的是ofstream 或fstream對象,而不是cout對象。
- 讀取文件:使用流提取運算符( >> )從文件讀取信息,就像使用該運算符從鍵盤輸入信息一樣。唯一不同的是,在這裏您使用的是ifstream或fstream對象,而不是cin對象。
- 文件位置指針:istream和ostram都提供了用於重新定位文件位置指針的成員函數。這些成員函數包括關於 istream 的seekg("seek get")和關於 ostream 的seekp"seek put")。
// 定位到 fileObject 的第 n 個字節(假設是 ios::beg)
fileObject.seekg( n );
// 把文件的讀指針從 fileObject 當前位置向後移 n 個字節
fileObject.seekg( n, ios::cur );
// 把文件的讀指針從 fileObject 末尾往回移 n 個字節
fileObject.seekg( n, ios::end );
// 定位到 fileObject 的末尾
fileObject.seekg( 0, ios::end );
9.C++異常處理:
異常是程序在執行期間產生的問題。C++ 異常是指在程序運行時發生的特殊情況,比如嘗試除以零的操作。
throw:當問題出現時,程序會拋出一個異常。這是通過使用throw關鍵字來完成的;
try:try塊中的代碼標識將被激活的特定異常。它後面通常跟着一個或多個 catch 塊;
catch:在您想要處理問題的地方,通過異常處理程序捕獲異常。
- 拋出異常:
double division(int a, int b)
{
if( b == 0 )
{
throw "Division by zero condition!";
}
return (a/b);
}
- 捕獲異常:
try
{
// 保護代碼
}catch( ExceptionName e )
{
// 處理 ExceptionName 異常的代碼
}
- C++標準異常:C++ 提供了一系列標準的異常,定義在<exception>中,我們可以在程序中使用這些標準的異常。
- 定義新的異常:通過繼承和重載exception類來定義新的異常。
struct MyException : public exception
{
const char * what () const throw ()
{
return "C++ Exception";
}
};
10.C++動態內存:
- 棧:在函數內部聲明的所有變量都將佔用棧內存;
- 堆:這是程序中未使用的內存,在程序運行時可用於動態分配內存。
- new&delete運算符:
使用new運算符來爲任意的數據類型動態分配內存,malloc()函數在 C 語言中就出現了,在 C++ 中仍然存在,但建議儘量不要使用 malloc() 函數。new 與 malloc() 函數相比,其主要的優點是,new 不只是分配了內存,它還創建了對象.
使用 delete 操作符釋放它所佔用的內存
double* pvalue = NULL; // 初始化爲 null 的指針
pvalue = new double; // 爲變量請求內存
double* pvalue1 = NULL;
if( !(pvalue1 = new double ))
{
cout << "Error: out of memory." <<endl;
exit(1);
}
delete pvalue; // 釋放 pvalue 所指向的內存
- 數據的動態內存分配:
// 動態分配,數組長度爲 m
int *array=new int [m];
//釋放內存
delete [] array;
int **array
// 假定數組第一維長度爲 m, 第二維長度爲 n
// 動態分配空間
array = new int *[m];
for( int i=0; i<m; i++ )
{
array[i] = new int [n] ;
}
//釋放
for( int i=0; i<m; i++ )
{
delete [] array[i];
}
delete [] array;
- 對象的動態內存分配:對象與簡單的數據類型一樣。
11.命名空間:
- 定義命名空間:命名空間的定義使用關鍵字namespace,後跟命名空間的名稱。
namespace namespace_name {
// 代碼聲明
}
- 調用:爲了調用帶有命名空間的函數或變量,需要在前面加上命名空間的名稱。
name::code; // code 可以是變量或函數
- using指令:可以使用using namespace指令,這樣在使用命名空間時就可以不用在前面加上命名空間的名稱。這個指令會告訴編譯器,後續的代碼將使用指定的命名空間中的名稱。
- 不連續的命名空間:命名空間可以定義在幾個不同的部分中,因此命名空間是由幾個單獨定義的部分組成的。一個命名空間的各個組成部分可以分散在多個文件中。所以,如果命名空間中的某個組成部分需要請求定義在另一個文件中的名稱,則仍然需要聲明該名稱。
- 嵌套的命名空間:命名空間可以嵌套,您可以在一個命名空間中定義另一個命名空間。
namespace namespace_name1 {
// 代碼聲明
namespace namespace_name2 {
// 代碼聲明
}
}
// 訪問 namespace_name2 中的成員
using namespace namespace_name1::namespace_name2;
// 訪問 namespace:name1 中的成員
using namespace namespace_name1;
12.C++模版:
模板是泛型編程的基礎,泛型編程即以一種獨立於任何特定類型的方式編寫代碼。
- 函數模版:
#include <iostream>
#include <string>
using namespace std;
template <typename T>
inline T const& Max (T const& a, T const& b)
{
return a < b ? b:a;
}
int main ()
{
int i = 39;
int j = 20;
cout << "Max(i, j): " << Max(i, j) << endl;
double f1 = 13.5;
double f2 = 20.7;
cout << "Max(f1, f2): " << Max(f1, f2) << endl;
string s1 = "Hello";
string s2 = "World";
cout << "Max(s1, s2): " << Max(s1, s2) << endl;
return 0;
}
- 類模版:
13.C++預處理器:
預處理器是一些指令,指示編譯器在實際編譯之前所需完成的預處理。
所有的預處理器指令都是以井號(#)開頭,只有空格字符可以出現在預處理指令之前。預處理指令不是 C++ 語句,所以它們不會以分號(;)結尾。
- #define預處理:
#define PI 3.14159
- 參數宏:
#define MIN(a,b) (a<b ? a : b)
- 條件編譯:
#ifdef NULL
#define NULL 0
#endif
#ifdef DEBUG
cerr <<"Variable x = " << x << endl;
#endif
- #和##運算符:# 和 ## 預處理運算符在 C++ 和 ANSI/ISO C 中都是可用的。# 運算符會把 replacement-text 令牌轉換爲用引號引起來的字符串;## 運算符用於連接兩個令牌。
- C++中的預定義宏:
宏 | 描述 |
__LINE__ | 這會在程序編譯時包含當前行號。 |
__FILE__ | 這會在程序編譯時包含當前文件名。 |
__DATE__ | 這會包含一個形式爲 month/day/year 的字符串,它表示把源文件轉換爲目標代碼的日期。 |
__TIME__ | 這會包含一個形式爲 hour:minute:second 的字符串,它表示程序被編譯的時間。 |
14.C++信號處理:
信號是由操作系統傳給進程的中斷,會提早終止一個程序。有些信號不能被程序捕獲,但是下表所列信號可以在程序中捕獲,並可以基於信號採取適當的動作。這些信號是定義在 C++ 頭文件 <csignal> 中:
信號 | 描述 |
SIGABRT | 程序的異常終止,如調用 abort。 |
SIGFPE | 錯誤的算術運算,比如除以零或導致溢出的操作。 |
SIGILL | 檢測非法指令。 |
SIGINT | 程序終止(interrupt)信號。 |
SIGSEGV | 非法訪問內存。 |
SIGTERM | 發送到程序的終止請求。 |
- signal()函數:C++ 信號處理庫提供了signal函數,用來捕獲突發事件。這個函數接收兩個參數:第一個參數是一個整數,代表了信號的編號;第二個參數是一個指向信號處理函數的指針。
void (*signal (int sig, void (*func)(int)))(int);
void signalHandler( int signum )
{
cout << "Interrupt signal (" << signum << ") received.\n";
// 清理並關閉
// 終止程序
exit(signum);
}
int main ()
{
// 註冊信號 SIGINT 和信號處理程序
signal(SIGINT, signalHandler);
while(1){
cout << "Going to sleep...." << endl;
sleep(1);
}
return 0;
}
- raise()函數:使用函數raise()生成信號,該函數帶有一個整數信號編號作爲參數。sig是要發送的信號的編號,這些信號包括:SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM、SIGHUP。
int raise (signal sig);
15.C++多線程:
多線程是多任務處理的一種特殊形式,多任務處理允許讓電腦同時運行兩個或兩個以上的程序。一般情況下,兩種類型的多任務處理。
基於進程的多任務處理是程序的併發執行;
基於線程的多任務處理是同一程序的片段的併發執行。
多線程程序包含可以同時運行的兩個或多個部分。這樣的程序中的每個部分稱爲一個線程,每個線程定義了一個單獨的執行路徑。
- 創建線程:
#include <pthread.h>
pthread_create (thread, attr, start_routine, arg)
參數 | 描述 |
thread | 指向線程標識符指針。 |
attr | 一個不透明的屬性對象,可以被用來設置線程屬性。您可以指定線程屬性對象,也可以使用默認值 NULL。 |
start_routine | 線程運行函數起始地址,一旦線程被創建就會執行。 |
arg | 運行函數的參數。它必須通過把引用作爲指針強制轉換爲 void 類型進行傳遞。如果沒有傳遞參數,則使用 NULL。 |
- 終止線程:
#include <pthread.h>
pthread_exit (status)
#include <iostream>
// 必須的頭文件
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5
// 線程的運行函數
void* say_hello(void* args)
{
cout << "Hello Runoob!" << endl;
return 0;
}
int main()
{
// 定義線程的 id 變量,多個變量使用數組
pthread_t tids[NUM_THREADS];
for(int i = 0; i < NUM_THREADS; ++i)
{
//參數依次是:創建的線程id,線程參數,調用的函數,傳入的函數參數
int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
if (ret != 0)
{
cout << "pthread_create error: error_code=" << ret << endl;
}
}
//等各個線程退出後,進程才結束,否則進程強制結束了,線程可能還沒反應過來;
pthread_exit(NULL);
}
- 向線程傳遞參數:
- 連接和分離線程:pthread_join() 子程序阻礙調用程序,直到指定的 threadid 線程終止爲止。當創建一個線程時,它的某個屬性會定義它是否是可連接的(joinable)或可分離的(detached)。只有創建時定義爲可連接的線程纔可以被連接。如果線程創建時被定義爲可分離的,則它永遠也不能被連接。
pthread_join (threadid, status)
pthread_detach (threadid)