C++學習
引入
根據菜鳥教程學習,供自用
打印hello world
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World" << "\n";
return 0;
}
現象
PS C:\Users\86177> cd "d:\DeskTop\cppstudy\" ; if ($?) { g++ -std=c++11 hello.cpp -o a.exe } ; if ($?) { ./a.exe }
Hello World
PS D:\DeskTop\cppstudy>
簡介
面向對象開發的四大特性
- 封裝(Encapsulation):封裝是將數據和方法組合在一起,對外部隱藏實現細節,只公開對外提供的接口。這樣可以提高安全性、可靠性和靈活性。
- 繼承(Inheritance):繼承是從已有類中派生出新類,新類具有已有類的屬性和方法,并且可以擴展或修改這些屬性和方法。這樣可以提高代碼的復用性和可擴展性。
- 多態(Polymorphism):多態是指同一種操作作用于不同的對象,可以有不同的解釋和實現。它可以通過接口或繼承實現,可以提高代碼的靈活性和可讀性。
- 抽象(Abstraction):抽象是從具體的實例中提取共同的特征,形成抽象類或接口,以便于代碼的復用和擴展。抽象類和接口可以讓程序員專注于高層次的設計和業務邏輯,而不必關注底層的實現細節。
基本語法
C++ 程序可以定義為對象的集合,這些對象通過調用彼此的方法進行交互。
- 對象 - 對象具有狀態和行為。例如:一只狗的狀態 - 顏色、名稱、品種,行為 - 搖動、叫喚、吃。對象是類的實例。
- 類 - 類可以定義為描述對象行為/狀態的模板/藍圖。
- 方法 - 從基本上說,一個方法表示一種行為。一個類可以包含多個方法。可以在方法中寫入邏輯、操作數據以及執行所有的動作。
- 即時變量 - 每個對象都有其獨特的即時變量。對象的狀態是由這些即時變量的值創建的。
標識符
下表列出了 C++ 中的保留字。這些保留字不能作為常量名、變量名或其他標識符名稱。
| asm | else | new | this |
|---|---|---|---|
| auto | enum | operator | throw |
| bool | explicit | private | true |
| break | export | protected | try |
| case | extern | public | typedef |
| catch | false | register | typeid |
| char | float | reinterpret_cast | typename |
| class | for | return | union |
| const | friend | short | unsigned |
| const_cast | goto | signed | using |
| continue | if | sizeof | virtual |
| default | inline | static | void |
| delete | int | static_cast | volatile |
| do | long | struct | wchar_t |
| double | mutable | switch | while |
| dynamic_cast | namespace | template |
注釋
塊注釋符(/.../)是不可以嵌套使用的。
#if 0 ... #endif 屬于條件編譯,0 即為參數。
此外,我們還可以使用 #if 0 ... #endif 來實現注釋,且可以實現嵌套,格式為:
#if 0
code
#endif
你可以把 #if 0 改成 #if 1 來執行 code 的代碼。
這種形式對程序調試也可以幫助,測試時使用 #if 1 來執行測試代碼,發布后使用 #if 0 來屏蔽測試代碼。
#if 后可以是任意的條件語句。
下面的代碼如果 condition 條件為 true 執行 code1 ,否則執行 code2。
#if condition
code1
#else
code2
#endif
數據類型
| 類型 | 關鍵字 |
|---|---|
| 布爾型 | bool |
| 字符型 | char |
| 整型 | int |
| 浮點型 | float |
| 雙浮點型 | double |
| 無類型 | void |
| 寬字符型 | wchar_t (typedef short int wchar_t;) |
在每一行后插入一個換行符,<< 運算符用于向屏幕傳多個值,
| 類型 | 位 | 范圍 |
|---|---|---|
| char | 1 個字節 | -128 到 127 或者 0 到 255 |
| unsigned char | 1 個字節 | 0 到 255 |
| signed char | 1 個字節 | -128 到 127 |
| int | 4 個字節 | -2147483648 到 2147483647 |
| unsigned int | 4 個字節 | 0 到 4294967295 |
| signed int | 4 個字節 | -2147483648 到 2147483647 |
| short int | 2 個字節 | -32768 到 32767 |
| unsigned short int | 2 個字節 | 0 到 65,535 |
| signed short int | 2 個字節 | -32768 到 32767 |
| long int | 8 個字節 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
| signed long int | 8 個字節 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
| unsigned long int | 8 個字節 | 0 到 18,446,744,073,709,551,615 |
| float | 4 個字節 | 精度型占4個字節(32位)內存空間,+/- 3.4e +/- 38 (~7 個數字) |
| double | 8 個字節 | 雙精度型占8 個字節(64位)內存空間,+/- 1.7e +/- 308 (~15 個數字) |
| long long | 8 個字節 | 雙精度型占8 個字節(64位)內存空間,表示 -9,223,372,036,854,775,807 到 9,223,372,036,854,775,807 的范圍 |
| long double | 16 個字節 | 長雙精度型 16 個字節(128位)內存空間,可提供18-19位有效數字。 |
| wchar_t | 2 或 4 個字節 | 1 個寬字符 |
typedef聲明
typedef int feet;
枚舉類型
enum color{red,blue,green}c;
c=blue;
//這里red=0,blue=1,green=2;c被賦值為1;
enum color2{red1,blue1=5,green1};
//這里red1=0,blue1=5,green1=6;(默認情況下,每個名稱都會比它前面一名稱大1,但red的值依然是0)
類型轉換
四種:靜態轉換、動態轉換、常量轉換、重新解釋轉換
- 靜態轉換(static_cast)
強轉,不檢查
int a=10;
float b=static_cast<float>(a);//將a轉換為float類型
- 動態轉換(dynamic_cast)
將一個基類指針或引用轉換為派生類指針或引用。
檢查,不能轉換返回空指針或者引發異常。
class Base{};
class Derived:public Base{};
Base* ptr_base=new Derived;
Derived* ptr_derived=dynamic_cast<Derived*>(ptr_base);
//將基類指針ptr_base轉換為派生類指針ptr_derived
- 常量轉換(const_cast)
將const類型的對象轉換為非const類型
const int a=10;
int& r=const_cast<int&>(a);
//將const int轉換為int
- 重新解釋轉換(reinterpret_cast)
將一個數據類型的值重新解釋為另一個數據類型的值,不檢查(同靜態轉換?)
int i=10;
float f=reineterpret_cast<float&>(i);
//重新解釋將int類型轉換為float類型
靜態轉換用于在兼容類型之間進行安全轉換,編譯時會進行檢查。
重新類型轉換用于在不同類型之間進行底層位模式的重新解釋,不進行任何類型檢查,風險較高。
變量類型
| 類型 | 描述 |
|---|---|
| bool | 1個字節 |
| char | 1個字節 |
| int | 4個字節 |
| float | 4個字節(1位符號,8位指數,23位小數) |
| double | 8個字節(1位符號,11位指數,52位小數) |
| void | 表示類型的缺失 |
| wchar_t | 2或4個字節(寬字符) |
整數類型
- int 4字節
- short 2字節
- long 4字節
- long long 8字節
浮點類型
- float 4字節
- double 8字節
- long double (占用字節數隨實現而變化)
字符類型
- char 1字節
- wchar_t 2或4字節
- char16_t 2字節
- char32_t 4字節
布爾類型
- bool 1字節
枚舉類型
- enum 用于定義一組命名的整數常量
指針類型
- type* 指向type類型的指針
數組類型
- 類型[常量表達式]
結構體類型
- struct 關鍵字定義,可以包含不同類型的成員
類類型
- class 用于定義具有數據成員和函數成員的復合類型
共用體類型
- union 定義一種特殊的數據類型,允許在相同的內存位置存儲不同的數據類型
不帶初始化的定義:帶有靜態存儲持續時間的變量會被隱式初始化為 NULL(所有字節的值都是 0),其他所有變量的初始值是未定義的。
左值和右值
左值:指向內存位置的表達式被稱為左值(lvalue)表達式。左值可以出現在賦值號的左邊或右邊。
右值:術語右值(rvalue)指的是存儲在內存中某些地址的數值。右值是不能對其進行賦值的表達式,也就是說我們不可以把一個右值賦值給一個變量。
(變量是左值,因此可以出現在賦值號的左邊。數值型的字面值是右值,因此不能被賦值,不能出現在賦值號的左邊。下面是一個有效的語句)
變量作用域
- 局部變量
- 全局變量
- 形式參量
- 局部作用域
- 全局作用域
- 塊作用域
- 類作用域:在類內部聲明的變量具有類作用域,它們可以被類的所有成員函數訪問。類作用域變量的生命周期與類的生命周期相同。
(如果在內部作用域中聲明的變量與外部作用域中的變量同名,則內部作用域中的變量將覆蓋外部作用域中的變量。)
當局部變量被定義時,系統不會對其初始化,您必須自行對其初始化。定義全局變量時,系統會自動初始化為0,'\0',NULL
塊作用域舉例
#include <iostream>
int main()
{
int a=10;
{
int a=20;
std::cout<<"塊變量:"<<a<<std::endl;
}
std::cout<<"塊變量:"<<a<<std::endl;
return 0;
}
類作用域舉例
class Myclass
{
public:
static int class_var;//類作用域變量
};
int Myclass::class_var=10;
int main()
{
std::cout<<"類變量:"<<Myclass::class_var<<std::endl;
return 0;
}
Myclass類中聲明了一個名為class_var的類作用域變量,可以使用類名和作用域解析運算符::來訪問這個變量。
常量
:字面量
整數常量
212 // 合法的 不帶前綴則默認表示十進制
215u // 合法的 U表示無符號整數(unsigned)
0xFeeL // 合法的 L表示長整數(long)
078 // 非法的:8不是八進制的數字 前綴0表示八進制
032UU // 非法的:不能重復后綴
85 // 十進制 不帶前綴則默認表示十進制
0213 // 八進制 前綴0表示八進制
0x4b // 十六進制 前綴0x或0X表示十六進制
30 // 整數 不帶前綴則默認表示十進制
30u // 無符號整數
30l // 長整數
30ul // 無符號長整數
浮點常量
3.14159 // 合法的
314159E-5L // 合法的
510E // 非法的:不完整的指數
210f // 非法的:沒有小數或指數
.e55 // 非法的:缺少整數或分數
布爾常量
布爾常量
布爾常量共有兩個,它們都是標準的 C++ 關鍵字:
true 值代表真。
false 值代表假。
我們不應把 true 的值看成 1,把 false 的值看成 0。
字符常量
如果常量以 L(僅當大寫時)開頭,則表示它是一個寬字符常量(例如 L'x'),此時它必須存儲在 wchar_t 類型的變量中
否則,它就是一個窄字符常量(例如 'x'),此時它可以存儲在 char 類型的簡單變量中
字符常量可以是一個普通的字符(例如 'x')、一個轉義序列(例如 '\t'),或一個通用的字符(例如 '\u02C0')。
C++ 中,有一些特定的字符,當它們前面有反斜杠時,它們就具有特殊的含義,被用來表示如換行符(\n)或制表符(\t)等。
| 轉義序列 | 含義 |
|---|---|
| \ | \ 字符 |
| ' | ' 字符 |
| " | " 字符 |
| ? | ? 字符 |
| \a | 警報鈴聲 |
| \b | 退格鍵 |
| \f | 換頁符 |
| \n | 換行符 |
| \r | 回車 |
| \t | 水平制表符 |
| \v | 垂直制表符 |
| \ooo | 一到三位的八進制數 |
| \xhh . . . | 一個或多個數字的十六進制數v |
字符串常量
字符串字面值或常量是括在雙引號 "" 中的。
您可以使用 \ 做分隔符,把一個很長的字符串常量進行分行。(只是編寫分行,輸出沒有分行)
定義常量
- 使用#define預處理
#define identifier value - 使用const關鍵字
const type variable = value;const int LENGTH = 10;const char NEWLINE = '\n';
請注意,把常量定義為大寫字母形式,是一個很好的編程實踐。
修飾符類型
下面列出了數據類型修飾符:
signed:表示變量可以存儲負數。對于整型變量來說,signed 可以省略,因為整型變量默認為有符號類型。
unsigned:表示變量不能存儲負數。對于整型變量來說,unsigned 可以將變量范圍擴大一倍。
short:表示變量的范圍比 int 更小。short int 可以縮寫為 short。
long:表示變量的范圍比 int 更大。long int 可以縮寫為 long。
long long:表示變量的范圍比 long 更大。C++11 中新增的數據類型修飾符。
float:表示單精度浮點數。
double:表示雙精度浮點數。
bool:表示布爾類型,只有 true 和 false 兩個值。
char:表示字符類型。
wchar_t:表示寬字符類型,可以存儲 Unicode 字符。
signed int num1 = -10; // 定義有符號整型變量 num1,初始值為 -10
unsigned int num2 = 20; // 定義無符號整型變量 num2,初始值為 20
short int num1 = 10; // 定義短整型變量 num1,初始值為 10
long int num2 = 100000; // 定義長整型變量 num2,初始值為 100000
long long int num1 = 10000000000; // 定義長長整型變量 num1,初始值為 10000000000
float num1 = 3.14f; // 定義單精度浮點數變量 num1,初始值為 3.14
double num2 = 2.71828; // 定義雙精度浮點數變量 num2,初始值為 2.71828
bool flag = true; // 定義布爾類型變量 flag,初始值為 true
char ch1 = 'a'; // 定義字符類型變量 ch1,初始值為 'a'
wchar_t ch2 = L'你'; // 定義寬字符類型變量 ch2,初始值為 '你'
類型限定符
| 限定符 | 含義 |
|---|---|
| const | const 定義常量,表示該變量的值不能被修改。 |
| volatile | 修飾符 volatile 告訴該變量的值可能會被程序以外的因素改變,如硬件或其他線程。 |
| restrict | 由 restrict 修飾的指針是唯一一種訪問它所指向的對象的方式。只有 C99 增加了新的類型限定符 restrict。 |
| mutable | mutable 用于修飾類的成員變量。被 mutable 修飾的成員變量可以被修改,即使它們所在的對象是 const 的。 |
| static | 用于定義靜態變量,表示該變量的作用域僅限于當前文件或當前函數內,不會被其他文件或函數訪問。 |
| register | 用于定義寄存器變量,表示該變量被頻繁使用,可以存儲在CPU的寄存器中,以提高程序的運行效率。 |
volatile實例
volatile int i = 10; // 定義一個 volatile 整型變量 i,表示其值可能會被程序以外的因素改變。
const實例
const int NUM = 10; // 定義常量 NUM,其值不可修改
const int* ptr = &NUM; // 定義指向常量的指針,指針所指的值不可修改
int const* ptr2 = &NUM; // 和上面一行等價
mutable實例
class Example {
public:
int get_value() const {
return value_; // const 關鍵字表示該成員函數不會修改對象中的數據成員
}
void set_value(int value) const {
value_ = value; // mutable 關鍵字允許在 const 成員函數中修改成員變量
}
private:
mutable int value_;
};
register實例
void example_function(register int num) {
// register 關鍵字建議編譯器將變量 num 存儲在寄存器中
// 以提高程序執行速度
// 但是實際上是否會存儲在寄存器中由編譯器決定
}
static實例
void example_function() {
static int count = 0; // static 關鍵字使變量 count 存儲在程序生命周期內都存在
count++;
}
存儲類
auto:這是默認的存儲類說明符,通常可以省略不寫。auto 指定的變量具有自動存儲期,即它們的生命周期僅限于定義它們的塊(block)。auto 變量通常在棧上分配。
register:用于建議編譯器將變量存儲在CPU寄存器中以提高訪問速度。在 C++11 及以后的版本中,register 已經是一個廢棄的特性,不再具有實際作用。
static:用于定義具有靜態存儲期的變量或函數,它們的生命周期貫穿整個程序的運行期。在函數內部,static變量的值在函數調用之間保持不變。在文件內部或全局作用域,static變量具有內部鏈接,只能在定義它們的文件中訪問。
extern:用于聲明具有外部鏈接的變量或函數,它們可以在多個文件之間共享。默認情況下,全局變量和函數具有 extern 存儲類。在一個文件中使用extern聲明另一個文件中定義的全局變量或函數,可以實現跨文件共享。
mutable (C++11):用于修飾類中的成員變量,允許在const成員函數中修改這些變量的值。通常用于緩存或計數器等需要在const上下文中修改的數據。
thread_local (C++11):用于定義具有線程局部存儲期的變量,每個線程都有自己的獨立副本。線程局部變量的生命周期與線程的生命周期相同。
#include <iostream>
// 全局變量,具有外部鏈接,默認存儲類為extern
int globalVar;
void function() {
// 局部變量,具有自動存儲期,默認存儲類為auto
auto int localVar = 10;
// 靜態變量,具有靜態存儲期,生命周期貫穿整個程序
static int staticVar = 20;
const int constVar = 30; // const變量默認具有static存儲期
// 嘗試修改const變量,編譯錯誤
// constVar = 40;
// mutable成員變量,可以在const成員函數中修改
class MyClass {
public:
mutable int mutableVar;
void constMemberFunc() const {
mutableVar = 50; // 允許修改mutable成員變量
}
};
// 線程局部變量,每個線程有自己的獨立副本
thread_local int threadVar = 60;
}
int main() {
extern int externalVar; // 聲明具有外部鏈接的變量
function();
return 0;
}
auto存儲類
自 C++ 11 以來,auto 關鍵字用于兩種情況:聲明變量時根據初始化表達式自動推斷該變量的類型、聲明函數時函數返回值的占位符。
C++98 標準中 auto 關鍵字用于自動變量的聲明,但由于使用極少且多余,在 C++17 中已刪除這一用法。
根據初始化表達式自動推斷被聲明的變量的類型,如:
auto f=3.14; //double
auto s("hello"); //const char*
auto z = new auto(9); // int*
auto x1 = 5, x2 = 5.0, x3='r';//錯誤,必須是初始化為同一類型
thread_local存儲類
thread_local 是 C++11 引入的一種存儲類,用于在多線程環境中管理線程特有的變量。
使用 thread_local 修飾的變量在每個線程中都有獨立的實例,因此每個線程對該變量的操作不會影響其他線程。
獨立性:每個線程都有自己獨立的變量副本,不同線程之間的讀寫操作互不干擾。
生命周期:thread_local 變量在其線程結束時自動銷毀。
初始化:thread_local 變量可以進行靜態初始化或動態初始化,支持在聲明時初始化。
thread_local 適合用于需要存儲線程狀態、緩存或者避免數據競爭的場景,如線程池、請求上下文等。
以下演示了可以被聲明為 thread_local 的變量:
#include <iostream>
#include <thread>
thread_local int threadSpecificVar = 0; // 每個線程都有自己的 threadSpecificVar
void threadFunction(int id) {
threadSpecificVar = id; // 設置線程特有的變量
std::cout << "Thread " << id << ": threadSpecificVar = " << threadSpecificVar << std::endl;
}
int main() {
std::thread t1(threadFunction, 1);
std::thread t2(threadFunction, 2);
t1.join();
t2.join();
return 0;
}
注意事項:
性能:由于每個線程都有獨立的副本,thread_local 變量的訪問速度可能比全局或靜態變量稍慢。
靜態存儲:thread_local 變量的存儲類型為靜態存儲持續時間,因此在程序整個運行期間會一直存在。
運算符
同C
循環
同C
一般情況下,C++ 程序員偏向于使用 for(;;) 結構來表示一個無限循環。
注意:您可以按 Ctrl + C 鍵終止一個無限循環。
判斷
同C
函數
同C
函數還有很多叫法,比如方法、子例程或程序,等等。
lamba表達式
C++11 提供了對匿名函數的支持,稱為 Lambda 函數(也叫 Lambda 表達式)。
Lambda 表達式把函數看作對象。Lambda 表達式可以像對象一樣使用,比如可以將它們賦給變量和作為參數傳遞,還可以像函數一樣對其求值。
Lambda 表達式本質上與函數聲明非常類似。
Lambda 表達式具體形式如下: [capture](parameters)->return-type{body}
eg:[](int x, int y){ return x < y ; }
如果沒有返回值可以表示為:[capture](parameters){body}
eg:[]{ ++global_x; }
在一個更為復雜的例子中,返回類型可以被明確的指定如下:[](int x, int y) -> int { int z = x + y; return z + x; }
本例中,一個臨時的參數 z 被創建用來存儲中間結果。如同一般的函數,z 的值不會保留到下一次該不具名函數再次被調用時。
如果 lambda 函數沒有傳回值(例如 void),其返回類型可被完全忽略。
在Lambda表達式內可以訪問當前作用域的變量,這是Lambda表達式的閉包(Closure)行為。 與JavaScript閉包不同,C++變量傳遞有傳值和傳引用的區別。可以通過前面的[]來指定:
[] // 沒有定義任何變量。使用未定義變量會引發錯誤。
[x, &y] // x以傳值方式傳入(默認),y以引用方式傳入。
[&] // 任何被使用到的外部變量都隱式地以引用方式加以引用。
[=] // 任何被使用到的外部變量都隱式地以傳值方式加以引用。
[&, x] // x顯式地以傳值方式加以引用。其余變量以引用方式加以引用。
[=, &z] // z顯式地以引用方式加以引用。其余變量以傳值方式加以引用。
另外有一點需要注意。對于[=]或[&]的形式,lambda 表達式可以直接使用 this 指針。但是,對于[]的形式,如果要使用 this 指針,必須顯式傳入:
[this]() { this->someFunc(); }();
數字
同C
數組
同C
#include <iostream>
using namespace std;
#include <iomanip>
using std::setw;
int main ()
{
int n[ 10 ]; // n 是一個包含 10 個整數的數組
// 初始化數組元素
for ( int i = 0; i < 10; i++ )
{
n[ i ] = i + 100; // 設置元素 i 為 i + 100
}
cout << "Element" << setw( 13 ) << "Value" << endl;
// 輸出數組中每個元素的值
for ( int j = 0; j < 10; j++ )
{
cout << setw( 7 )<< j << setw( 13 ) << n[ j ] << endl;
}
return 0;
}
上面的程序使用了 setw() 函數 來格式化輸出。當上面的代碼被編譯和執行時,它會產生下列結果:
Element Value
0 100
1 101
2 102
3 103
4 104
5 105
6 106
7 107
8 108
9 109
| 概念 | 描述 |
|---|---|
| 多維數組 C++ | 支持多維數組。多維數組最簡單的形式是二維數組。 |
| 指向數組的指針 | 您可以通過指定不帶索引的數組名稱來生成一個指向數組中第一個元素的指針。 |
| 傳遞數組給函數 | 您可以通過指定不帶索引的數組名稱來給函數傳遞一個指向數組的指針。 |
| 從函數返回數組 | C++ 允許從函數返回數組。 |
字符串
同C
字符串實際上是使用 null 字符 \0 終止的一維字符數組。因此,一個以 null 結尾的字符串,包含了組成字符串的字符。
其實,您不需要把 null 字符放在字符串常量的末尾。C++ 編譯器會在初始化數組時,自動把 \0 放在字符串的末尾。
String類
C++ 標準庫提供了 string 類類型,支持上述所有的操作,另外還增加了其他更多的功能。
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string str1 = "runoob";
string str2 = "google";
string str3;
int len ;
// 復制 str1 到 str3
str3 = str1;
cout << "str3 : " << str3 << endl;
// 連接 str1 和 str2
str3 = str1 + str2;
cout << "str1 + str2 : " << str3 << endl;
// 連接后,str3 的總長度
len = str3.size();
cout << "str3.size() : " << len << endl;
return 0;
}
result:
str3 : runoob
str1 + str2 : runoobgoogle
str3.size() : 12
指針
同C
| 概念 | 描述 |
|---|---|
| C++ Null 指針 | C++ 支持空指針。NULL 指針是一個定義在標準庫中的值為零的常量。 |
| C++ 指針的算術運算 | 可以對指針進行四種算術運算:++、--、+、- |
| C++ 指針 vs 數組 | 指針和數組之間有著密切的關系。 |
| C++ 指針數組 | 可以定義用來存儲指針的數組。 |
| C++ 指向指針的指針 | C++ 允許指向指針的指針。 |
| C++ 傳遞指針給函數 | 通過引用或地址傳遞參數,使傳遞的參數在調用函數中被改變。 |
| C++ 從函數返回指針 | C++ 允許函數返回指針到局部變量、靜態變量和動態內存分配。 |
引用
引用變量是一個別名,也就是說,它是某個已存在變量的另一個名字。一旦把引用初始化為某個變量,就可以使用該引用名稱或變量名稱來指向變量。
引用vs指針
- 不存在空引用。引用必須連接到一塊合法的內存。
- 一旦引用被初始化為一個對象,就不能被指向到另一個對象。指針可以在任何時候指向到另一個對象。
- 引用必須在創建時被初始化。指針可以在任何時間被初始化。
#include <iostream>
using namespace std;
int main ()
{
// 聲明簡單的變量
int i;
double d;
// 聲明引用變量
int& r = i; //r 是一個初始化為 i 的整型引用
double& s = d; //s 是一個初始化為 d 的 double 型引用
i = 5;
cout << "Value of i : " << i << endl;
cout << "Value of i reference : " << r << endl;
d = 11.7;
cout << "Value of d : " << d << endl;
cout << "Value of d reference : " << s << endl;
return 0;
}
result:
Value of i : 5
Value of i reference : 5
Value of d : 11.7
Value of d reference : 11.7
| 概念 | 描述 |
|---|---|
| 把引用作為參數 | C++ 支持把引用作為參數傳給函數,這比傳一般的參數更安全。 |
| 把引用作為返回值 | 可以從 C++ 函數中返回引用,就像返回其他數據類型一樣。 |
日期和時間
需要在 C++ 程序中引用<ctime> 頭文件。
有四個與時間相關的類型:clock_t、time_t、size_t 和 tm。類型 clock_t、size_t 和 time_t 能夠把系統時間和日期表示為某種整數。
結構類型 tm 把日期和時間以 C 結構的形式保存
struct tm {
int tm_sec; // 秒,正常范圍從 0 到 59,但允許至 61
int tm_min; // 分,范圍從 0 到 59
int tm_hour; // 小時,范圍從 0 到 23
int tm_mday; // 一月中的第幾天,范圍從 1 到 31
int tm_mon; // 月,范圍從 0 到 11
int tm_year; // 自 1900 年起的年數
int tm_wday; // 一周中的第幾天,范圍從 0 到 6,從星期日算起
int tm_yday; // 一年中的第幾天,范圍從 0 到 365,從 1 月 1 日算起
int tm_isdst; // 夏令時
};
| 序號 | 函數 & 描述 |
|---|---|
| 1 | time_t time(time_t *time); |
| 該函數返回系統的當前日歷時間,自 1970 年 1 月 1 日以來經過的秒數。如果系統沒有時間,則返回 -1。 | |
| 2 | char *ctime(const time_t *time); |
| 該返回一個表示當地時間的字符串指針,字符串形式 day month year hours:minutes:seconds year\n\0。 | |
| 3 | struct tm *localtime(const time_t *time); |
| 該函數返回一個指向表示本地時間的 tm 結構的指針。 | |
| 4 | clock_t clock(void); |
| 該函數返回程序執行起(一般為程序的開頭),處理器時鐘所使用的時間。如果時間不可用,則返回 -1。 | |
| 5 | char * asctime ( const struct tm * time ); |
| 該函數返回一個指向字符串的指針,字符串包含了 time 所指向結構中存儲的信息,返回形式為:day month date hours:minutes:seconds year\n\0。 | |
| 6 | struct tm *gmtime(const time_t *time); |
| 該函數返回一個指向 time 的指針,time 為 tm 結構,用協調世界時(UTC)也被稱為格林尼治標準時間(GMT)表示。 | |
| 7 | time_t mktime(struct tm *time); |
| 該函數返回日歷時間,相當于 time 所指向結構中存儲的時間。 | |
| 8 | double difftime ( time_t time2, time_t time1 ); |
| 該函數返回 time1 和 time2 之間相差的秒數。 | |
| 9 | size_t strftime(); |
| 該函數可用于格式化日期和時間為指定的格式。 |
#include <iostream>
#include <ctime>
using namespace std;
int main( )
{
// 基于當前系統的當前日期/時間
time_t now = time(0);
// 把 now 轉換為字符串形式
char* dt = ctime(&now);
cout << "本地日期和時間:" << dt << endl;
// 把 now 轉換為 tm 結構
tm *gmtm = gmtime(&now);
dt = asctime(gmtm);
cout << "UTC 日期和時間:"<< dt << endl;
}
result:
本地日期和時間:Sat Jan 8 20:07:41 2011
UTC 日期和時間:Sun Jan 9 03:07:41 2011
使用tm結構格式化時間
#include <iostream>
#include <ctime>
using namespace std;
int main( )
{
// 基于當前系統的當前日期/時間
time_t now = time(0);
cout << "1970 到目前經過秒數:" << now << endl;
tm *ltm = localtime(&now);
// 輸出 tm 結構的各個組成部分
cout << "年: "<< 1900 + ltm->tm_year << endl;
cout << "月: "<< 1 + ltm->tm_mon<< endl;
cout << "日: "<< ltm->tm_mday << endl;
cout << "時間: "<< ltm->tm_hour << ":";
cout << ltm->tm_min << ":";
cout << ltm->tm_sec << endl;
}
result:
1970 到目前時間:1503564157
年: 2017
月: 8
日: 24
時間: 16:42:37
基本的輸入輸出
C++ 的 I/O 發生在流中,流是字節序列。如果字節流是從設備(如鍵盤、磁盤驅動器、網絡連接等)流向內存,這叫做輸入操作。如果字節流是從內存流向設備(如顯示屏、打印機、磁盤驅動器、網絡連接等),這叫做輸出操作。
| 頭文件 | 函數和描述 |
|---|---|
<iostream> |
該文件定義了 cin、cout、cerr 和 clog 對象,分別對應于標準輸入流、標準輸出流、非緩沖標準錯誤流和緩沖標準錯誤流。 |
<iomanip> |
該文件通過所謂的參數化的流操縱器(比如 setw 和 setprecision),來聲明對執行標準化 I/O 有用的服務。 |
<fstream> |
該文件為用戶控制的文件處理聲明服務。我們將在文件和流的相關章節討論它的細節。 |
標準輸出流 cout
預定義的對象 cout 是 iostream 類的一個實例。cout 對象"連接"到標準輸出設備,通常是顯示屏。
cout 是與流插入運算符 << 結合使用的,如下所示:
#include <iostream>
using namespace std;
int main( )
{
char str[] = "Hello C++";
cout << "Value of str is : " << str << endl;
}
reslut:
Value of str is : Hello C++
流插入運算符 << 在一個語句中可以多次使用,如上面實例中所示,endl 用于在行末添加一個換行符。
標準輸入流 cin
預定義的對象 cin 是 iostream 類的一個實例。cin 對象附屬到標準輸入設備,通常是鍵盤。
cin 是與流提取運算符 >> 結合使用的,如下所示:
#include <iostream>
using namespace std;
int main( )
{
char name[50];
cout << "請輸入您的名稱: ";
cin >> name;
cout << "您的名稱是: " << name << endl;
}
result:
請輸入您的名稱: cplusplus
您的名稱是: cplusplus
cin >> name >> age;相當于cin >> name;cin >> age;
標準錯誤流 cerr
預定義的對象 cerr 是 iostream 類的一個實例。cerr 對象附屬到標準輸出設備,通常也是顯示屏,但是 cerr 對象是非緩沖的,且每個流插入到 cerr 都會立即輸出。
cerr 也是與流插入運算符 << 結合使用的,如下所示:
#include <iostream>
using namespace std;
int main( )
{
char str[] = "Unable to read....";
cerr << "Error message : " << str << endl;
}
result:
Error message : Unable to read....
標準日志流 clog
預定義的對象 clog 是 iostream 類的一個實例。clog 對象附屬到標準輸出設備,通常也是顯示屏,但是 clog 對象是緩沖的。這意味著每個流插入到 clog 都會先存儲在緩沖區,直到緩沖填滿或者緩沖區刷新時才會輸出。
clog 也是與流插入運算符 << 結合使用的,如下所示:
#include <iostream>
using namespace std;
int main( )
{
char str[] = "Unable to read....";
clog << "Error message : " << str << endl;
}
result:
Error message : Unable to read....
良好的編程實踐告訴我們,使用 cerr 流來顯示錯誤消息,而其他的日志消息則使用 clog 流來輸出。
結構體
同C
使用成員訪問運算符(.)
#include <iostream>
#include <cstring>
using namespace std;
// 聲明一個結構體類型 Books
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
Books Book1; // 定義結構體類型 Books 的變量 Book1
Books Book2; // 定義結構體類型 Books 的變量 Book2
// Book1 詳述
strcpy( Book1.title, "C++ 教程");
strcpy( Book1.author, "Runoob");
strcpy( Book1.subject, "編程語言");
Book1.book_id = 12345;
// Book2 詳述
strcpy( Book2.title, "CSS 教程");
strcpy( Book2.author, "Runoob");
strcpy( Book2.subject, "前端技術");
Book2.book_id = 12346;
// 輸出 Book1 信息
cout << "第一本書標題 : " << Book1.title <<endl;
cout << "第一本書作者 : " << Book1.author <<endl;
cout << "第一本書類目 : " << Book1.subject <<endl;
cout << "第一本書 ID : " << Book1.book_id <<endl;
// 輸出 Book2 信息
cout << "第二本書標題 : " << Book2.title <<endl;
cout << "第二本書作者 : " << Book2.author <<endl;
cout << "第二本書類目 : " << Book2.subject <<endl;
cout << "第二本書 ID : " << Book2.book_id <<endl;
return 0;
}
result:
第一本書標題 : C++ 教程
第一本書作者 : Runoob
第一本書類目 : 編程語言
第一本書 ID : 12345
第二本書標題 : CSS 教程
第二本書作者 : Runoob
第二本書類目 : 前端技術
第二本書 ID : 12346
結構體作為函數參數
#include <iostream>
#include <cstring>
using namespace std;
void printBook( struct Books book );
// 聲明一個結構體類型 Books
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
Books Book1; // 定義結構體類型 Books 的變量 Book1
Books Book2; // 定義結構體類型 Books 的變量 Book2
// Book1 詳述
strcpy( Book1.title, "C++ 教程");
strcpy( Book1.author, "Runoob");
strcpy( Book1.subject, "編程語言");
Book1.book_id = 12345;
// Book2 詳述
strcpy( Book2.title, "CSS 教程");
strcpy( Book2.author, "Runoob");
strcpy( Book2.subject, "前端技術");
Book2.book_id = 12346;
// 輸出 Book1 信息
printBook( Book1 );
// 輸出 Book2 信息
printBook( Book2 );
return 0;
}
void printBook( struct Books book )
{
cout << "書標題 : " << book.title <<endl;
cout << "書作者 : " << book.author <<endl;
cout << "書類目 : " << book.subject <<endl;
cout << "書 ID : " << book.book_id <<endl;
}
result:
書標題 : C++ 教程
書作者 : Runoob
書類目 : 編程語言
書 ID : 12345
書標題 : CSS 教程
書作者 : Runoob
書類目 : 前端技術
書 ID : 12346
結構體中各個部分詳解
struct 關鍵字:用于定義結構體,它告訴編譯器后面要定義的是一個自定義類型。
成員變量:成員變量是結構體中定義的數據項,它們可以是任何基本類型或其他自定義類型。在 struct 中,這些成員默認是 public,可以直接訪問。
成員函數:結構體中也可以包含成員函數,這使得結構體在功能上類似于類。成員函數可以操作結構體的成員變量,提供對數據的封裝和操作。
訪問權限:與 class 類似,你可以在 struct 中使用 public、private 和 protected 來定義成員的訪問權限。在 struct 中,默認所有成員都是 public,而 class 中默認是 private。
指向結構的指針
定義指向結構的指針struct Books *struct_pointer;
在上述定義的指針變量中存儲結構變量的地址struct_pointer = &Book1;
使用指向該結構的指針訪問結構的成員,您必須使用 -> 運算符struct_pointer->title;
#include <iostream>
#include <string>
using namespace std;
// 聲明一個結構體類型 Books
struct Books
{
string title;
string author;
string subject;
int book_id;
// 構造函數
Books(string t, string a, string s, int id)
: title(t), author(a), subject(s), book_id(id) {}
};
// 打印書籍信息的函數
void printBookInfo(const Books& book) {
cout << "書籍標題: " << book.title << endl;
cout << "書籍作者: " << book.author << endl;
cout << "書籍類目: " << book.subject << endl;
cout << "書籍 ID: " << book.book_id << endl;
}
int main()
{
// 創建兩本書的對象
Books Book1("C++ 教程", "Runoob", "編程語言", 12345);
Books Book2("CSS 教程", "Runoob", "前端技術", 12346);
// 輸出書籍信息
printBookInfo(Book1);
printBookInfo(Book2);
return 0;
}
reuslt:
書標題 : C++ 教程
書作者 : Runoob
書類目 : 編程語言
書 ID : 12345
書標題 : CSS 教程
書作者 : Runoob
書類目 : 前端技術
書 ID : 12346
typedef關鍵字
更簡單的定義結構的方式,您可以為創建的類型取一個"別名"
typedef struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
}Books;
現在可以Books Book1, Book2;這樣定義結構體變量了
結構體與類的區別
在 C++ 中,struct 和 class 本質上非常相似,唯一的區別在于默認的訪問權限:
struct 默認的成員和繼承是 public。
class 默認的成員和繼承是 private。
在實踐中,大多數程序員傾向于使用 struct 來表示數據結構(例如,一個點或矩形),而使用 class 來實現面向對象編程的特性。
結構體與函數的結合
可以通過構造函數初始化結構體,還可以通過引用傳遞結構體來避免不必要的拷貝。
struct Books {
string title;
string author;
string subject;
int book_id;
// 構造函數
Books(string t, string a, string s, int id)
: title(t), author(a), subject(s), book_id(id) {}
void printInfo() const {
cout << "書籍標題: " << title << endl;
cout << "書籍作者: " << author << endl;
cout << "書籍類目: " << subject << endl;
cout << "書籍 ID: " << book_id << endl;
}
};
void printBookByRef(const Books& book) {
book.printInfo();
}
vector容器
需要#include <vector>
一種序列容器,它允許你在運行時動態地插入和刪除元素。
vector 是基于數組的數據結構,但它可以自動管理內存,這意味著你不需要手動分配和釋放內存。
基本特性
動態大小:vector 的大小可以根據需要自動增長和縮小。
連續存儲:vector 中的元素在內存中是連續存儲的,這使得訪問元素非常快速。
可迭代:vector 可以被迭代,你可以使用循環(如 for 循環)來訪問它的元素。
元素類型:vector 可以存儲任何類型的元素,包括內置類型、對象、指針等。
使用場景:
當你需要一個可以動態增長和縮小的數組時。
當你需要頻繁地在序列的末尾添加或移除元素時。
當你需要一個可以高效隨機訪問元素的容器時。
//創建一個 vector 可以像創建其他變量一樣簡單:
std::vector<int> myVector; // 創建一個存儲整數的空 vector
//這將創建一個空的整數向量,也可以在創建時指定初始大小和初始值:
std::vector<int> myVector(5); // 創建一個包含 5 個整數的 vector,每個值都為默認值(0)
std::vector<int> myVector(5, 10); // 創建一個包含 5 個整數的 vector,每個值都為 10
//或
std::vector<int> vec; // 默認初始化一個空的 vector
std::vector<int> vec2 = {1, 2, 3, 4}; // 初始化一個包含元素的 vector
//添加元素
//可以使用 push_back 方法向 vector 中添加元素:
myVector.push_back(7); // 將整數 7 添加到 vector 的末尾
//訪問元素
//可以使用下標操作符 [] 或 at() 方法訪問 vector 中的元素:
int x = myVector[0]; // 獲取第一個元素
int y = myVector.at(1); // 獲取第二個元素
//獲取大小
//可以使用 size() 方法獲取 vector 中元素的數量:
int size = myVector.size(); // 獲取 vector 中的元素數量
//迭代訪問
//可以使用迭代器遍歷 vector 中的元素:
for (auto it = myVector.begin(); it != myVector.end(); ++it) {
std::cout << *it << " ";
}
//或使用范圍 for 循環
for (int element : myVector) {
std::cout << element << " ";
}
//刪除元素
//可以使用 erase() 方法刪除 vector 中的元素:
myVector.erase(myVector.begin() + 2); // 刪除第三個元素
//清空
//可以使用 clear() 方法清空 vector 中的所有元素:
myVector.clear(); // 清空 vector
- 示例
#include <iostream>
#include <vector>
int main() {
// 創建一個空的整數向量
std::vector<int> myVector;
// 添加元素到向量中
myVector.push_back(3);
myVector.push_back(7);
myVector.push_back(11);
myVector.push_back(5);
// 訪問向量中的元素并輸出
std::cout << "Elements in the vector: ";
for (int element : myVector) {
std::cout << element << " ";
}
std::cout << std::endl;
// 訪問向量中的第一個元素并輸出
std::cout << "First element: " << myVector[0] << std::endl;
// 訪問向量中的第二個元素并輸出
std::cout << "Second element: " << myVector.at(1) << std::endl;
// 獲取向量的大小并輸出
std::cout << "Size of the vector: " << myVector.size() << std::endl;
// 刪除向量中的第三個元素
myVector.erase(myVector.begin() + 2);
// 輸出刪除元素后的向量
std::cout << "Elements in the vector after erasing: ";
for (int element : myVector) {
std::cout << element << " ";
}
std::cout << std::endl;
// 清空向量并輸出
myVector.clear();
std::cout << "Size of the vector after clearing: " << myVector.size() << std::endl;
return 0;
}
result
Elements in the vector: 3 7 11 5
First element: 3
Second element: 7
Size of the vector: 4
Elements in the vector after erasing: 3 7 5
Size of the vector after clearing: 0
- 實例2
#include <bits/stdc++.h>
using namespace std;
int main()
{
// 定義一個整數向量 inner,用于存儲乘法表的每一項
vector<int> inner;
// 計數器 count,用于追蹤輸出向量中的元素位置
int count = 0;
// 生成 1 到 9 的乘法表
for (int i = 1; i < 10; i++) {
// 內層循環,用于輸出每一行的乘法結果
for (int j = 1; j <= i; j++) {
inner.push_back(i * j); // 將乘法結果存儲到 inner 向量中
cout << setw(4) << inner[count++]; // 輸出當前存儲的乘法結果,格式化為寬度為 4 的字段
}
cout << endl; // 換行,開始輸出下一行的乘法結果
}
system("pause"); // 暫停程序,等待用戶按鍵
return 0;
}
數據結構
同C
- 數組
- 結構體
- 類
類是 C++ 中用于面向對象編程的核心結構,允許定義成員變量和成員函數。與 struct 類似,但功能更強大,支持繼承、封裝、多態等特性。
特點:
可以包含成員變量、成員函數、構造函數、析構函數。
支持面向對象特性,如封裝、繼承、多態。
class Person {
private:
string name;
int age;
public:
Person(string n, int a) : name(n), age(a) {}
void printInfo() {
cout << "Name: " << name << ", Age: " << age << endl;
}
};
Person p("Bob", 30);
p.printInfo(); // 輸出: Name: Bob, Age: 30
- 鏈表
鏈表是一種動態數據結構,由一系列節點組成,每個節點包含數據和指向下一個節點的指針。
特點:
動態調整大小,不需要提前定義容量。
插入和刪除操作效率高,時間復雜度為 O(1)(在鏈表頭部或尾部操作)。
線性查找,時間復雜度為 O(n)。
struct Node {
int data;
Node* next;
};
Node* head = nullptr;
Node* newNode = new Node{10, nullptr};
head = newNode; // 插入新節點
- 棧
棧是一種后進先出(LIFO, Last In First Out)的數據結構,常用于遞歸、深度優先搜索等場景。
特點:
只允許在棧頂進行插入和刪除操作。
時間復雜度為 O(1)。
stack<int> s;
s.push(1);
s.push(2);
cout << s.top(); // 輸出 2
s.pop();
- 隊列
隊列是一種先進先出(FIFO, First In First Out)的數據結構,常用于廣度優先搜索、任務調度等場景。
特點:
插入操作在隊尾進行,刪除操作在隊頭進行。
時間復雜度為 O(1)。
queue<int> q;
q.push(1);
q.push(2);
cout << q.front(); // 輸出 1
q.pop();
- 雙端隊列
雙端隊列允許在兩端進行插入和刪除操作,是棧和隊列的結合體。
特點:
允許在兩端進行插入和刪除。
時間復雜度為 O(1)。
deque<int> dq;
dq.push_back(1);
dq.push_front(2);
cout << dq.front(); // 輸出 2
dq.pop_front();
- 哈希表
哈希表是一種通過鍵值對存儲數據的數據結構,支持快速查找、插入和刪除操作。C++ 中的 unordered_map 是哈希表的實現。
特點:
使用哈希函數快速定位元素,時間復雜度為 O(1)。
不保證元素的順序。
unordered_map<string, int> hashTable;
hashTable["apple"] = 10;
cout << hashTable["apple"]; // 輸出 10
- 映射 map
map 是一種有序的鍵值對容器,底層實現是紅黑樹。與 unordered_map 不同,它保證鍵的順序,查找、插入和刪除的時間復雜度為 O(log n)。
特點:
保證元素按鍵的順序排列。
使用二叉搜索樹實現。
map<string, int> myMap;
myMap["apple"] = 10;
cout << myMap["apple"]; // 輸出 10
- 集合 set
set 是一種用于存儲唯一元素的有序集合,底層同樣使用紅黑樹實現。它保證元素不重復且有序。
特點:
保證元素的唯一性。
元素自動按升序排列。
時間復雜度為 O(log n)。
set<int> s;
s.insert(1);
s.insert(2);
cout << *s.begin(); // 輸出 1
- 動態數組 vector
vector 是 C++ 標準庫提供的動態數組實現,可以動態擴展容量,支持隨機訪問。
特點:
動態調整大小。
支持隨機訪問,時間復雜度為 O(1)。
當容量不足時,動態擴展,時間復雜度為攤銷 O(1)。
vector<int> v;
v.push_back(1);
v.push_back(2);
cout << v[0]; // 輸出 1
類和對象
類定義
定義一個類需要使用關鍵字class,然后指定類的名稱,并類的主體是包含在一對花括號中,主體包含類的成員變量和成員函數。
定義一個類,本質上是定義一個數據類型的藍圖,它定義了類的對象包括了什么,以及可以在這個對象上執行哪些操作。

以下實例我們使用關鍵字 class 定義 Box 數據類型,包含了三個成員變量 length、breadth 和 height:
class Box
{
public:
double length; // 盒子的長度
double breadth; // 盒子的寬度
double height; // 盒子的高度
};
關鍵字 public 確定了類成員的訪問屬性。在類對象作用域內,公共成員在類的外部是可訪問的。您也可以指定類的成員為 private 或 protected,這個我們稍后會進行講解。
Box Box1; // 聲明 Box1,類型為 Box
Box Box2; // 聲明 Box2,類型為 Box
訪問數據成員
#include <iostream>
using namespace std;//這行代碼的作用是指示編譯器,在當前作用域中直接使用 std 命名空間中的所有標識符,而不需要在每次引用時都加上 std:: 前綴。
class Box
{
public:
double length; // 長度
double breadth; // 寬度
double height; // 高度
// 成員函數聲明
double get(void);
void set( double len, double bre, double hei );
};
// 成員函數定義
double Box::get(void)
{
return length * breadth * height;
}
void Box::set( double len, double bre, double hei)
{
length = len;
breadth = bre;
height = hei;
}
int main( )
{
Box Box1; // 聲明 Box1,類型為 Box
Box Box2; // 聲明 Box2,類型為 Box
Box Box3; // 聲明 Box3,類型為 Box
double volume = 0.0; // 用于存儲體積
// box 1 詳述
Box1.height = 5.0;
Box1.length = 6.0;
Box1.breadth = 7.0;
// box 2 詳述
Box2.height = 10.0;
Box2.length = 12.0;
Box2.breadth = 13.0;
// box 1 的體積
volume = Box1.height * Box1.length * Box1.breadth;
cout << "Box1 的體積:" << volume <<endl;
// box 2 的體積
volume = Box2.height * Box2.length * Box2.breadth;
cout << "Box2 的體積:" << volume <<endl;
// box 3 詳述
Box3.set(16.0, 8.0, 12.0);
volume = Box3.get();
cout << "Box3 的體積:" << volume <<endl;
return 0;
}
result:
Box1 的體積:210
Box2 的體積:1560
Box3 的體積:1536
類&對象詳解
| 概念 | 描述 |
|---|---|
| 類成員函數 | 類的成員函數是指那些把定義和原型寫在類定義內部的函數,就像類定義中的其他變量一樣。 |
| 類訪問修飾符 | 類成員可以被定義為 public、private 或 protected。默認情況下是定義為 private。 |
| 構造函數 & 析構函數 | 類的構造函數是一種特殊的函數,在創建一個新的對象時調用。類的析構函數也是一種特殊的函數,在刪除所創建的對象時調用。 |
| C++ 拷貝構造函數 | 拷貝構造函數,是一種特殊的構造函數,它在創建對象時,是使用同一類中之前創建的對象來初始化新創建的對象。 |
| C++ 友元函數 | 友元函數可以訪問類的 private 和 protected 成員。 |
| C++ 內聯函數 | 通過內聯函數,編譯器試圖在調用函數的地方擴展函數體中的代碼。 |
| C++ 中的 this 指針 | 每個對象都有一個特殊的指針 this,它指向對象本身。 |
| C++ 中指向類的指針 | 指向類的指針方式如同指向結構的指針。實際上,類可以看成是一個帶有函數的結構。 |
| C++ 類的靜態成員 | 類的數據成員和函數成員都可以被聲明為靜態的。 |
繼承
繼承允許我們依據另一個類來定義一個類
創建和維護一個應用程序變得更加容易
達到了重用代碼功能和提高執行效率的效果
當創建新類時,只需指定新類(派生類)繼承一個已有的類(基類)的成員即可

//基類
class Animal
{
//eat()函數
//sleep()函數
};
//派生類
clss Dog : public Animal
{
// bark()函數
// run()函數
}
基類和派生類
一個類可以派生自多個類,這意味著,它可以從多個基類繼承數據和函數。定義一個派生類,我們使用一個類派生列表來指定基類。類派生列表以一個或多個基類命名,形式如下:
class derived-class: access-specifier base-class
訪問修飾符
access-specifier是public、protected或private中的一個。 (如果未使用訪問修飾符 access-specifier,則默認為 private。)
base-class 是之前定義過的某個類的名稱。
//假設有一個基類 Shape,Rectangle 是它的派生類,
#include <iostream>
using namespace std;
// 基類
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);
}
};
int main(void)
{
Rectangle Rect;
Rect.setWidth(5);
Rect.setHeight(7);
// 輸出對象的面積
cout << "Total area: " << Rect.getArea() << endl;
return 0;
}
result:
Total area: 35
訪問控制和繼承
派生類可以訪問基類中的所有的非私有成員。因此基類成員如果不想被派生類的成員函數訪問,則應在基類中將這些成員聲明為 private。
- 訪問權限總結
| 訪問 | public | protected | private |
|---|---|---|---|
| 同一個類 | yes | yes | yes |
| 派生類 | yes | yes | no |
| 外部的類 | yes | no | no |
- 一個派生類繼承了所有的基類方法,但下列情況除外:
基類的構造函數、析構函數和拷貝構造函數。
基類的重載運算符。
基類的友元函數。
繼承類型
當一個類派生自基類,該基類可以被繼承為 public、protected 或 private 幾種類型。繼承類型是通過上面講解的訪問修飾符 access-specifier 來指定的。
幾乎不使用 protected 或 private 繼承,通常使用 public 繼承。
遵循以下幾個規則
公有繼承(public):當一個類派生自公有基類時,基類的公有成員也是派生類的公有成員,基類的保護成員也是派生類的保護成員,基類的私有成員不能直接被派生類訪問,但是可以通過調用基類的公有和保護成員來訪問。
保護繼承(protected): 當一個類派生自保護基類時,基類的公有和保護成員將成為派生類的保護成員。
私有繼承(private):當一個類派生自私有基類時,基類的公有和保護成員將成為派生類的私有成員。
多繼承
多繼承即一個子類可以有多個父類,它繼承了多個父類的特性。
C++ 類可以從多個類繼承成員,語法如下:
class <派生類名>:<繼承方式1><基類名1>,<繼承方式2><基類名2>,…
{
<派生類類體>
};
eg:
#include <iostream>
using namespace std;
// 基類 Shape
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 基類 PaintCost
class PaintCost
{
public:
int getCost(int area)
{
return area * 70;
}
};
// 派生類
class Rectangle: public Shape, public PaintCost
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea();
// 輸出對象的面積
cout << "Total area: " << Rect.getArea() << endl;
// 輸出總花費
cout << "Total paint cost: $" << Rect.getCost(area) << endl;
return 0;
}
result:
Total area: 35
Total paint cost: $2450
重載運算符和重載函數
C++ 允許在同一作用域中的某個函數和運算符指定多個定義,分別稱為函數重載和運算符重載。
重載聲明是指一個與之前已經在該作用域內聲明過的函數或方法具有相同名稱的聲明,但是它們的參數列表和定義(實現)不相同。
當您調用一個重載函數或重載運算符時,編譯器通過把您所使用的參數類型與定義中的參數類型進行比較,決定選用最合適的定義。選擇最合適的重載函數或重載運算符的過程,稱為重載決策。
函數重載
同名函數的形式參數(指參數的個數、類型或者順序)必須不同。您不能僅通過返回類型的不同來重載函數。
eg:
#include <iostream>
using namespace std;
class printData
{
public:
void print(int i) {
cout << "整數為: " << i << endl;
}
void print(double f) {
cout << "浮點數為: " << f << endl;
}
void print(char c[]) {
cout << "字符串為: " << c << endl;
}
};
int main(void)
{
printData pd;
// 輸出整數
pd.print(5);
// 輸出浮點數
pd.print(500.263);
// 輸出字符串
char c[] = "Hello C++";
pd.print(c);
return 0;
}
result:
整數為: 5
浮點數為: 500.263
字符串為: Hello C++
運算符重載
重載的運算符是帶有特殊名稱的函數,函數名是由關鍵字 operator 和其后要重載的運算符符號構成的。與其他函數一樣,重載運算符有一個返回類型和一個參數列表。
Box operator+(const Box&);
聲明加法運算符用于把兩個 Box 對象相加,返回最終的 Box 對象。大多數的重載運算符可被定義為普通的非成員函數或者被定義為類成員函數。如果我們定義上面的函數為類的非成員函數,那么我們需要為每次操作傳遞兩個參數,如下所示:
Box operator+(const Box&, const Box&);
下面的實例使用成員函數演示了運算符重載的概念。在這里,對象作為參數進行傳遞,對象的屬性使用 this 運算符進行訪問,如下所示:
#include <iostream>
using namespace std;
class Box
{
public:
double getVolume(void)
{
return length * breadth * height;
}
void setLength( double len )
{
length = len;
}
void setBreadth( double bre )
{
breadth = bre;
}
void setHeight( double hei )
{
height = hei;
}
// 重載 + 運算符,用于把兩個 Box 對象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
private:
double length; // 長度
double breadth; // 寬度
double height; // 高度
};
// 程序的主函數
int main( )
{
Box Box1; // 聲明 Box1,類型為 Box
Box Box2; // 聲明 Box2,類型為 Box
Box Box3; // 聲明 Box3,類型為 Box
double volume = 0.0; // 把體積存儲在該變量中
// Box1 詳述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// Box2 詳述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// Box1 的體積
volume = Box1.getVolume();
cout << "Volume of Box1 : " << volume <<endl;
// Box2 的體積
volume = Box2.getVolume();
cout << "Volume of Box2 : " << volume <<endl;
// 把兩個對象相加,得到 Box3
Box3 = Box1 + Box2;
// Box3 的體積
volume = Box3.getVolume();
cout << "Volume of Box3 : " << volume <<endl;
return 0;
}
Box operator+(const Box& b)。這個函數聲明了+運算符被重載以接受兩個Box對象作為參數。第一個參數是隱式的,通過this指針指向調用該運算符的Box對象。第二個參數是顯式的,即+運算符右側的Box對象。
可重載運算符/不可重載運算符
可重載運算符
| 運算符 | 詳細 |
|---|---|
| 雙目算術運算符 | + (加),-(減),*(乘),/(除),% (取模) |
| 關系運算符 | ==(等于),!= (不等于),< (小于),> (大于),<=(小于等于),>=(大于等于) |
| 邏輯運算符 | |(邏輯或),&&(邏輯與),!(邏輯非) |
| 單目運算符 | + (正),-(負),*(指針),&(取地址) |
| 自增自減運算符 | ++(自增),--(自減) |
| 位運算符 | |(按位或),& (按位與),~(按位取反),^(按位異或),,<< (左移),>>(右移) |
| 賦值運算符 | =, +=, -=, *=, /= , % = , &=, |
| 空間申請與釋放 | new, delete, new[ ] , delete[] |
| 其他運算符 | ()(函數調用),->(成員訪問),,(逗號),[](下標) |
不可重載的運算符
| 運算符 | 詳細 |
|---|---|
| . | 成員訪問運算符 |
| .*,->* | 成員指針訪問運算符 |
| :: | 域運算符 |
| sizeof | 長度運算符 |
| ?: | 條件運算符 |
| # | 預處理運算符 |
重載一元減運算符( - )
#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;
}
二元運算符重載
二元運算符需要兩個參數,下面是二元運算符的實例。我們平常使用的加運算符( + )、減運算符( - )、乘運算符( * )和除運算符( / )都屬于二元運算符。就像加(+)運算符。
#include <iostream>
using namespace std;
class Box
{
double length; // 長度
double breadth; // 寬度
double height; // 高度
public:
double getVolume(void)
{
return length * breadth * height;
}
void setLength( double len )
{
length = len;
}
void setBreadth( double bre )
{
breadth = bre;
}
void setHeight( double hei )
{
height = hei;
}
// 重載 + 運算符,用于把兩個 Box 對象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
};
// 程序的主函數
int main( )
{
Box Box1; // 聲明 Box1,類型為 Box
Box Box2; // 聲明 Box2,類型為 Box
Box Box3; // 聲明 Box3,類型為 Box
double volume = 0.0; // 把體積存儲在該變量中
// Box1 詳述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// Box2 詳述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// Box1 的體積
volume = Box1.getVolume();
cout << "Volume of Box1 : " << volume <<endl;
// Box2 的體積
volume = Box2.getVolume();
cout << "Volume of Box2 : " << volume <<endl;
// 把兩個對象相加,得到 Box3
Box3 = Box1 + Box2;
// Box3 的體積
volume = Box3.getVolume();
cout << "Volume of Box3 : " << volume <<endl;
return 0;
}
當 2 個對象相加時是沒有順序要求的,但要重載 + 讓其與一個數字相加則有順序要求,可以通過加一個友元函數使另一個順序的輸入合法。
#include<iostream>
using namespace std;
class A
{
private:
int a;
public:
A();
A(int n);
A operator+(const A & obj);
A operator+(const int b);
friend A operator+(const int b, A obj);
void display();
} ;
A::A()
{
a=0;
}
A::A(int n)//構造函數
{
a=n;
}
A A::operator +(const A& obj)//重載+號用于 對象相加
{
return this->a+obj.a;
}
A A::operator+(const int b)//重載+號用于 對象與數相加
{
return A(a+b);
}
A operator+(const int b, A obj)
{
return obj+b;//友元函數調用第二個重載+的成員函數 相當于 obj.operator+(b);
}
void A::display()
{
cout<<a<<endl;
}
int main ()
{
A a1(1);
A a2(2);
A a3,a4,a5;
a1.display();
a2.display();
int m=1;
a3=a1+a2;//可以交換順序,相當月a3=a1.operator+(a2);
a3.display();
a4=a1+m;//因為加了個友元函數所以也可以交換順序了。
a4.display();
a5=m+a1;
a5.display();
}
result
1
2
3
2
2
對實例進行改寫,以非成員函數的方式重載運算符 +:
#include <iostream>
using namespace std;
class Box
{
double length; // 長度
double breadth; // 寬度
double height; // 高度
public:
double getVolume(void)
{
return length * breadth * height;
}
void setLength( double len )
{
length = len;
}
void setBreadth( double bre )
{
breadth = bre;
}
void setHeight( double hei )
{
height = hei;
}
/**
* 改寫部分 2018.09.05
* 重載 + 運算符,用于把兩個 Box 對象相加
* 因為其是全局函數,對應的參數個數為2。
* 當重載的運算符函數是全局函數時,需要在類中將該函數聲明為友員。
*/
friend Box operator+(const Box& a, const Box& b);
};
Box operator+(const Box& a, const Box& b)
{
Box box;
box.length = a.length + b.length;
box.breadth = a.breadth + b.breadth;
box.height = a.height + b.height;
// cout << box.length << "--" << box.breadth << "--" << box.height << endl;
return box;
}
// 程序的主函數
int main( )
{
Box Box1; // 聲明 Box1,類型為 Box
Box Box2; // 聲明 Box2,類型為 Box
Box Box3; // 聲明 Box3,類型為 Box
double volume = 0.0; // 把體積存儲在該變量中
// Box1 詳述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// Box2 詳述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// Box1 的體積
volume = Box1.getVolume();
cout << "Volume of Box1 : " << volume <<endl;
// Box2 的體積
volume = Box2.getVolume();
cout << "Volume of Box2 : " << volume <<endl;
// 把兩個對象相加,得到 Box3
Box3 = Box1 + Box2;
// Box3 的體積
volume = Box3.getVolume();
cout << "Volume of Box3 : " << volume <<endl;
return 0;
}
關系運算符重載
C++ 語言支持各種關系運算符( < 、 > 、 <= 、 >= 、 == 等等),它們可用于比較 C++ 內置的數據類型。
您可以重載任何一個關系運算符,重載后的關系運算符可用于比較類的對象。
#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);
}
// 重載小于運算符( < )
bool operator <(const Distance& d)
{
if(feet < d.feet)
{
return true;
}
if(feet == d.feet && inches < d.inches)
{
return true;
}
return false;
}
};
int main()
{
Distance D1(11, 10), D2(5, 11);
if( D1 < D2 )
{
cout << "D1 is less than D2 " << endl;
}
else
{
cout << "D2 is less than D1 " << endl;
}
return 0;
}
result:
D2 is less than D1
輸入輸出運算符重載
++與--運算符重載
賦值運算符重載
函數調用符重載
下標運算符[]重載
類成員訪問運算符->重載
多態
多態按字面的意思就是多種形態。
當類之間存在層次結構,并且類之間是通過繼承關聯時,就會用到多態。
C++ 多態允許使用基類指針或引用來調用子類的重寫方法,從而使得同一接口可以表現不同的行為。
多態使得代碼更加靈活和通用,程序可以通過基類指針或引用來操作不同類型的對象,而不需要顯式區分對象類型。這樣可以使代碼更具擴展性,在增加新的形狀類時不需要修改主程序。
虛函數(Virtual Functions):
在基類中聲明一個函數為虛函數,使用關鍵字virtual。
派生類可以重寫(override)這個虛函數。
調用虛函數時,會根據對象的實際類型來決定調用哪個版本的函數。
動態綁定(Dynamic Binding):
也稱為晚期綁定(Late Binding),在運行時確定函數調用的具體實現。
需要使用指向基類的指針或引用來調用虛函數,編譯器在運行時根據對象的實際類型來決定調用哪個函數。
純虛函數(Pure Virtual Functions):
一個包含純虛函數的類被稱為抽象類(Abstract Class),它不能被直接實例化。
純虛函數沒有函數體,聲明時使用= 0。
它強制派生類提供具體的實現。
多態的實現機制:
虛函數表(V-Table):C++運行時使用虛函數表來實現多態。每個包含虛函數的類都有一個虛函數表,表中存儲了指向類中所有虛函數的指針。
虛函數指針(V-Ptr):對象中包含一個指向該類虛函數表的指針。
使用多態的優勢:
代碼復用:通過基類指針或引用,可以操作不同類型的派生類對象,實現代碼的復用。
擴展性:新增派生類時,不需要修改依賴于基類的代碼,只需要確保新類正確重寫了虛函數。
解耦:多態允許程序設計更加模塊化,降低類之間的耦合度。
注意事項:
只有通過基類的指針或引用調用虛函數時,才會發生多態。
如果直接使用派生類的對象調用函數,那么調用的是派生類中的版本,而不是基類中的版本。
多態性需要運行時類型信息(RTTI),這可能會增加程序的開銷。
示例代碼1:
#include <iostream>
using namespace std;
// 基類 Animal
class Animal {
public:
// 虛函數 sound,為不同的動物發聲提供接口
virtual void sound() const {
cout << "Animal makes a sound" << endl;
}
// 虛析構函數確保子類對象被正確析構
virtual ~Animal() {
cout << "Animal destroyed" << endl;
}
};
// 派生類 Dog,繼承自 Animal
class Dog : public Animal {
public:
// 重寫 sound 方法
void sound() const override {
cout << "Dog barks" << endl;
}
~Dog() {
cout << "Dog destroyed" << endl;
}
};
// 派生類 Cat,繼承自 Animal
class Cat : public Animal {
public:
// 重寫 sound 方法
void sound() const override {
cout << "Cat meows" << endl;
}
~Cat() {
cout << "Cat destroyed" << endl;
}
};
// 測試多態
int main() {
Animal* animalPtr; // 基類指針
// 創建 Dog 對象,并指向 Animal 指針
animalPtr = new Dog();
animalPtr->sound(); // 調用 Dog 的 sound 方法
delete animalPtr; // 釋放內存,調用 Dog 和 Animal 的析構函數
// 創建 Cat 對象,并指向 Animal 指針
animalPtr = new Cat();
animalPtr->sound(); // 調用 Cat 的 sound 方法
delete animalPtr; // 釋放內存,調用 Cat 和 Animal 的析構函數
return 0;
}
result:
Dog barks
Dog destroyed
Animal destroyed
Cat meows
Cat destroyed
Animal destroyed
//////////////////////////////////
Animal 類定義了一個虛函數 sound(),這是一個虛函數(virtual),用于表示動物發聲的行為。
~Animal() 為虛析構函數,確保在釋放基類指針指向的派生類對象時能夠正確調用派生類的析構函數,防止內存泄漏。
Dog 和 Cat 類都從 Animal 類派生,并各自實現了 sound() 方法。
Dog 的 sound() 輸出"Dog barks";Cat 的 sound() 輸出"Cat meows"。這使得同一個方法(sound())在不同的類中表現不同的行為。
創建一個基類指針 animalPtr。
使用 new Dog() 創建 Dog 對象,將其地址賦給 animalPtr。此時,調用 animalPtr->sound() 會輸出"Dog barks",因為 animalPtr 實際指向的是 Dog 對象。
釋放 Dog 對象時,先調用 Dog 的析構函數,再調用 Animal 的析構函數。
使用 new Cat() 創建 Cat 對象并賦給 animalPtr,再調用 animalPtr->sound(),輸出"Cat meows",顯示多態行為。
虛函數:通過在基類中使用 virtual 關鍵字聲明虛函數,派生類可以重寫這個函數,從而使得在運行時根據對象類型調用正確的函數。
動態綁定:C++ 的多態通過動態綁定實現。在運行時,基類指針 animalPtr 會根據它實際指向的對象類型(Dog 或 Cat)調用對應的 sound() 方法。
虛析構函數:在具有多態行為的基類中,析構函數應該聲明為 virtual,以確保在刪除派生類對象時調用派生類的析構函數,防止資源泄漏。
示例代碼2:
通過基類指針調用不同的派生類方法,展示了多態的動態綁定特性
#include <iostream>
using namespace std;
// 基類 Shape,表示形狀
class Shape {
protected:
int width, height; // 寬度和高度
public:
// 構造函數,帶有默認參數
Shape(int a = 0, int b = 0) : width(a), height(b) { }
// 虛函數 area,用于計算面積
// 使用 virtual 關鍵字,實現多態
virtual int area() {
cout << "Shape class area: " << endl;
return 0;
}
};
// 派生類 Rectangle,表示矩形
class Rectangle : public Shape {
public:
// 構造函數,使用基類構造函數初始化 width 和 height
Rectangle(int a = 0, int b = 0) : Shape(a, b) { }
// 重寫 area 函數,計算矩形面積
int area() override {
cout << "Rectangle class area: " << endl;
return width * height;
}
};
// 派生類 Triangle,表示三角形
class Triangle : public Shape {
public:
// 構造函數,使用基類構造函數初始化 width 和 height
Triangle(int a = 0, int b = 0) : Shape(a, b) { }
// 重寫 area 函數,計算三角形面積
int area() override {
cout << "Triangle class area: " << endl;
return (width * height / 2);
}
};
// 主函數
int main() {
Shape *shape; // 基類指針
Rectangle rec(10, 7); // 矩形對象
Triangle tri(10, 5); // 三角形對象
// 將基類指針指向矩形對象,并調用 area 函數
shape = &rec;
cout << "Rectangle Area: " << shape->area() << endl;
// 將基類指針指向三角形對象,并調用 area 函數
shape = &tri;
cout << "Triangle Area: " << shape->area() << endl;
return 0;
}
result:
Rectangle Area: Rectangle class area:
70
Triangle Area: Triangle class area:
25
/////////////////////////////////////
Shape 是一個抽象基類,定義了一個虛函數 area()。area() 是用來計算面積的虛函數,并使用了 virtual 關鍵字,這樣在派生類中可以重寫該函數,進而實現多態。
width 和 height 是 protected 屬性,只能在 Shape 類及其派生類中訪問。
Rectangle 繼承了 Shape 類,并重寫了 area() 方法,計算矩形的面積。
area() 方法使用了 override 關鍵字,表示這是對基類 Shape 的 area() 方法的重寫。
Rectangle::area() 返回 width * height,即矩形的面積。
Triangle 類也繼承自 Shape,并重寫了 area() 方法,用于計算三角形的面積。
Triangle::area() 返回 width * height / 2,這是三角形面積的公式。
定義了一個基類指針 shape,這個指針可以指向任何 Shape 類的對象或其派生類的對象。
首先將 shape 指針指向 Rectangle 對象 rec,然后調用 shape->area()。由于 area() 是虛函數,此時會動態綁定到 Rectangle::area(),輸出矩形的面積。
接著,將 shape 指針指向 Triangle 對象 tri,調用 shape->area() 時會動態綁定到 Triangle::area(),輸出三角形的面積。
虛函數:在基類 Shape 中定義了虛函數 area()。虛函數的作用是讓派生類可以重寫此函數,并在運行時根據指針的實際對象類型調用適當的函數實現。
動態綁定:因為 area() 是虛函數,shape->area() 調用時會在運行時根據 shape 實際指向的對象類型(Rectangle 或 Triangle)來調用相應的 area() 實現。這種在運行時決定調用哪個函數的機制稱為動態綁定,是多態的核心。
基類指針的多態性:基類指針 shape 可以指向任何派生自 Shape 的對象。當 shape 指向不同的派生類對象時,調用 shape->area() 會產生不同的行為,這體現了多態的特性。
純虛函數
純虛函數是沒有實現的虛函數,在基類中用 = 0 來聲明。
純虛函數表示基類定義了一個接口,但具體實現由派生類負責。
數據抽象
只向外界提供關鍵信息,并隱藏其后臺的實現細節,即只表現必要的信息而不呈現細節。
一臺電視機,您可以打開和關閉、切換頻道、調整音量、添加外部組件(如喇叭、錄像機、DVD 播放器),但是您不知道它的內部實現細節
C++ 中,我們使用訪問標簽來定義類的抽象接口。一個類可以包含零個或多個訪問標簽:
eg
#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 " << a.getTotal() <<endl;
return 0;
}
result:上面的類把數字相加,并返回總和。公有成員 addNum 和 getTotal 是對外的接口,用戶需要知道它們以便使用類。私有成員 total 是用戶不需要了解的,但又是類能正常工作所必需的。
構造函數是C++中一個特殊的成員函數,它在創建對象時自動調用,用于初始化對象的成員變量。構造函數的名字必須與類名完全相同,且沒有返回類型(連void也沒有)
封裝
通過將數據和操作數據的函數封裝在一個類中來實現。這種封裝確保了數據的私有性和完整性,防止了外部代碼對其直接訪問和修改。
程序語句(代碼):這是程序中執行動作的部分,它們被稱為函數。
程序數據:數據是程序的信息,會受到程序函數的影響。
面向對象編程(OOP)
重要的 OOP 概念,即數據隱藏。
數據封裝是一種把數據和操作數據的函數捆綁在一起的機制,數據抽象是一種僅向用戶暴露接口而把具體的實現細節隱藏起來的機制。
數據封裝示例:
任何帶有公有和私有成員的類都可以作為數據封裝和數據抽象的實例。請看下面的實例:
#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 " << a.getTotal() <<endl;
return 0;
}
數據封裝通過類和訪問修飾符(public, private, protected)來實現,以下是一個簡單的例子:
#include <iostream>
using namespace std;
class Student {
private:
string name;
int age;
public:
// 構造函數
Student(string studentName, int studentAge) {
name = studentName;
age = studentAge;
}
// 訪問器函數(getter)
string getName() {
return name;
}
int getAge() {
return age;
}
// 修改器函數(setter)
void setName(string studentName) {
name = studentName;
}
void setAge(int studentAge) {
if (studentAge > 0) {
age = studentAge;
} else {
cout << "Invalid age!" << endl;
}
}
// 打印學生信息
void printInfo() {
cout << "Name: " << name << ", Age: " << age << endl;
}
};
int main() {
// 創建一個 Student 對象
Student student1("Alice", 20);
// 訪問和修改數據
student1.printInfo();
student1.setName("Bob");
student1.setAge(22);
student1.printInfo();
return 0;
}
接口
C++ 接口是使用抽象類來實現的,抽象類與數據抽象互不混淆,數據抽象是一個把實現細節與相關的數據分離開的概念。
如果類中至少有一個函數被聲明為純虛函數,則這個類就是抽象類。純虛函數是通過在聲明中使用 "= 0" 來指定的,如下所示:
class Box
{
public:
// 純虛函數
virtual double getVolume() = 0;
private:
double length; // 長度
double breadth; // 寬度
double height; // 高度
};
設計抽象類(通常稱為 ABC)的目的,是為了給其他類提供一個可以繼承的適當的基類。抽象類不能被用于實例化對象,它只能作為接口使用。如果試圖實例化一個抽象類的對象,會導致編譯錯誤。
因此,如果一個 ABC 的子類需要被實例化,則必須實現每個純虛函數,這也意味著 C++ 支持使用 ABC 聲明接口。如果沒有在派生類中重寫純虛函數,就嘗試實例化該類的對象,會導致編譯錯誤。
可用于實例化對象的類被稱為具體類。
設計抽象類(通常稱為 ABC)的目的,是為了給其他類提供一個可以繼承的適當的基類。抽象類不能被用于實例化對象,它只能作為接口使用。如果試圖實例化一個抽象類的對象,會導致編譯錯誤。
因此,如果一個 ABC 的子類需要被實例化,則必須實現每個純虛函數,這也意味著 C++ 支持使用 ABC 聲明接口。如果沒有在派生類中重寫純虛函數,就嘗試實例化該類的對象,會導致編譯錯誤。
可用于實例化對象的類被稱為具體類。
eg
基類 Shape 提供了一個接口 getArea(),在兩個派生類 Rectangle 和 Triangle 中分別實現了 getArea():
#include <iostream>
using namespace std;
// 基類
class Shape
{
public:
// 提供接口框架的純虛函數
virtual int getArea() = 0;
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);
}
};
class Triangle: public Shape
{
public:
int getArea()
{
return (width * height)/2;
}
};
int main(void)
{
Rectangle Rect;
Triangle Tri;
Rect.setWidth(5);
Rect.setHeight(7);
// 輸出對象的面積
cout << "Total Rectangle area: " << Rect.getArea() << endl;
Tri.setWidth(5);
Tri.setHeight(7);
// 輸出對象的面積
cout << "Total Triangle area: " << Tri.getArea() << endl;
return 0;
}
從上面的實例中,我們可以看到一個抽象類是如何定義一個接口 getArea(),兩個派生類是如何通過不同的計算面積的算法來實現這個相同的函數。
設計策略
面向對象的系統可能會使用一個抽象基類為所有的外部應用程序提供一個適當的、通用的、標準化的接口。然后,派生類通過繼承抽象基類,就把所有類似的操作都繼承下來。
外部應用程序提供的功能(即公有函數)在抽象基類中是以純虛函數的形式存在的。這些純虛函數在相應的派生類中被實現。
這個架構也使得新的應用程序可以很容易地被添加到系統中,即使是在系統被定義之后依然可以如此。

浙公網安備 33010602011771號