👻類
👾語法格式
class className{Access specifiers: // 訪問權限DataType variable; // 變量returnType functions() { } // 方法
};
👾訪問權限
class className {public:// 公有成員protected:// 受保護成員private:// 私有成員
};
👽公有成員 public
公有成員在類外部可訪問,可不使用任何成員函數來設置和獲取公有變量的值
class Line {public:double length;void setLength(double len);double getLength(void);
};
double Line::getLength(void) { return length ; }
void Line::setLength(double len) { length = len; }...
Line line;
// 使用成員變量(正確,因為 length 公有)
line.length = 10.0;
cout << line.length <<endl;
// 使用成員函數
line.setLength(6.0);
cout << line.getLength() <<endl;
👽私有成員 private
私有成員在類外部不可訪問,不可查看,只有類和友元函數可以訪問私有成員。沒有使用任何訪問修飾符默認私有。
class Box {public:double length;void setWidth(double wid);double getWidth(void);private:double width;
};
double Box::getWidth(void) { return width; }
void Box::setWidth( double wid ) { width = wid; }...
Box box;
// box.width = 10.0; // [Error] 'double Box::width' is private
box.setWidth(10.0); // 使用成員函數設置寬度
cout << box.getWidth() <<endl;
👽受保護成員 protected
受保護成員與私有成員相似,但有一點不同,受保護成員在**派生類(即子類)**中是可訪問的。
/* 基類 */
class Box {protected:double width;
};/* 派生類 */
class SmallBox: Box { public:void setSmallWidth(double wid);double getSmallWidth();
};
double SmallBox::getSmallWidth() { return width; }
void SmallBox::setSmallWidth(double wid) { width = wid; }...
SmallBox box;
box.setSmallWidth(5.0);
cout << box.getSmallWidth() << endl;
👻類成員函數
👾語法格式
-
定義在類定義內部
class className{returnType functions(parameter list) { ... } };
-
單獨使用范圍解析運算符
::
定義class className{returnType functions(parameter list); }; returnType class_name::functions(parameter list){ ... }
👾示例代碼
-
定義在類定義內部
class Box {public:double length; // 長度double breadth; // 寬度double height; // 高度double getVolume() { return length * breadth * height; } };
-
單獨使用范圍解析運算符
::
定義class Box {public:double length; // 長度double breadth; // 寬度double height; // 高度double getVolume(); };double Box::getVolume() { return length * breadth * height; }
👻類構造函數
- 類構造函數在每次創建類的新對象時執行。
- 構造函數的名稱與類的名稱是完全相同的,不返回任何類型。
- 構造函數可用于為某些成員變量設置初始值。
👾無參構造函數
👽語法格式
class className{className() { ... }
};
👽示例代碼
class Line {public:Line() { cout << "Object is being created" << endl; }private:double length;
};...
Line line; // 輸出:Object is being created
👾帶參構造函數
👽語法格式
class className{className(parameter list) { ... }
};
👽示例代碼
class Line {public:Line(double len) {length = len;cout << "Object is being created" << endl;}private:double length;
};...
Line line; // 輸出:Object is being created
👾初始化列表構造函數
👽語法格式
類有多個字段 A、B、C 等需要進行初始化:
class className{className(int a,int b,int c): A(a), B(b), C(c) { ... }
};
👽示例代碼
class Line {public:Line(double len): length(len) { cout << "Object is being created" << endl; }private:double length;
};...
Line line; // 輸出:Object is being created
等同語法:
Line(double len) {length = len;cout << "Object is being created" << endl;
}
注意:初始化類成員時,是按聲明的順序初始化,而不是按初始化列表的順序初始化,即:
class Base {public:// 按照 A -> B -> C 的順序初始化int A;int B;int C;// 而不是按照 C -> B -> A 的順序初始化Base(int a, int b, int c): C(c), B(b), A(a) {} };
👻類析構函數
- 類析構函數在每次刪除所創建的對象時執行。
- 析構函數的名稱與類的名稱是完全相同的,在前面加
波浪號(~)
作為前綴,不返回任何值,不能帶有任何參數。 - 析構函數有助于在跳出程序(比如關閉文件、釋放內存等)前釋放資源。
👾語法格式
類有多個字段 A、B、C 等需要進行初始化:
class className{~className() { ... }
};
👾示例代碼
class Line {public:Line() { cout << "Object created" << endl; } // 構造函數聲明~Line() { cout << "Object deleted" << endl; } // 析構函數聲明
};...
Line line;
一個類內可以有多個構造函數(相當于重載構造函數),只有一個析構函數
class Matrix {public:Matrix(int row, int col); //普通構造函數Matrix(const Matrix& matrix); //拷貝構造函數Matrix(); //構造空矩陣的構造函數~Matrix(); };
👻類拷貝構造函數
👾語法結構
class className{// obj是對象引用,用于初始化另一個對象className (const className &obj) { }
};
-
參數
obj
—— 本類的引用-
const
類型引用 —— 既能以常量對象(初始化后值不改變的對象)作為參數,也能以非常量對象作為參數初始化其他對象 -
非
const
類型引用 —— 一般不使用
-
👾被調用情況
-
情況1:用一個對象初始化同類的另一個對象時,調用被初始化的對象的拷貝構造函數
Base a2(a1); Base a2 = a1;
class Base {public:int num;// 一般構造函數Base(int n) {num = n;cout << "General Constructor called" << endl;}// 拷貝構造函數Base(const Base& a) {num = a.num;cout << "Copy Constructor called" << endl;} };...// 只調用a1的一般構造函數,輸出 General Constructor called Base a1(1); // 只調用a2的拷貝構造函數,輸出 Copy Constructor called Base a2(a1); Base a2 = a1; // 不調用 a2 = a1;
注意,
Base a2 = a1;
是初始化語句,不是賦值語句。賦值語句的等號左邊是一個早已有定義的變量,不會引發復制構造函數的調用,
Base a1(1); // 調用a1的一般構造函數 Base a2(a1); // 調用a2的拷貝構造函數 a2 = a1; // 不調用,因為 a2 早已生成且初始化,不會引發拷貝構造函數
只會調用被初始化的對象的拷貝構造函數,不會調用其一般構造函數
-
情況2:函數參數是類的對象,當函數調用時,調用對象的拷貝構造函數
class Base {public:// 一般構造函數Base() { cout << "General Constructor called" << endl; };// 拷貝構造函數Base(Base& a) { cout << "Copy constructor called" << endl; } }; void Function(Base a) {}...Base a; // 調用a的一般構造函數,輸出 General Constructor called Function(a); // 調用a的拷貝構造函數,輸出 Copy constructor called
- 以對象作為形參,函數被調用時,生成形參要用拷貝構造函數,會帶來時間開銷
- 用對象的引用作為形參,無時間開銷,但有一定的風險,因為如果形參的值發生改變,實參值也會改變
如果要確保實參值不改變,又希望避免拷貝構造函數的開銷,解決辦法是將形參聲明為對象的
const
引用:void Function(const Base &a){... }
-
情況3:函數返回值是類的對象,當函數返回時,調用對象的拷貝構造函數
class Base {public:int num;// 一般構造函數Base(int n) {num = n;cout << "General Constructor called" << endl;}// 拷貝構造函數Base(const Base& a) {num = a.num;cout << "Copy constructor called" << endl;} };Base Func() {Base a(4); // 調用一般構造函數,輸出 General Constructor calledreturn a; }...int a = Func().num; // 調用拷貝構造函數,輸出 Copy constructor called
有些編譯器出于程序執行效率的考慮,編譯時進行優化,函數返回值對象不用拷貝構造函數,不符合C++標準
👾深拷貝和淺拷貝
👽淺拷貝
- 使用情況:基本類型數據、簡單對象
- 原理:按位復制內存
// 基本類型數據
int a = 10;
int b = a;
// 簡單對象
class Base {public:Base(): m_a(0), m_b(0) { }Base(int a, int b): m_a(a), m_b(b) { }private:int m_a;int m_b;
};Base obj1(10, 20);
Base obj2 = obj1;
👽深拷貝
- 使用情況:
- 持有其它資源的類(如動態分配的內存、指向其他數據的指針)
- 標準模板庫(STL)中 string、vector、stack 等
- 創建對象時進行一些預處理工作,比如統計創建過的對象的數目、記錄對象創建的時間等
- 原理:顯式定義拷貝構造函數
- 過程:
- 會將原有對象的所有成員變量拷貝給新對象
- 會為新對象再分配一塊內存,并將原有對象所持有的內存也拷貝過來
- 結果:原有對象和新對象所持有的動態內存相互獨立,更改一個對象的數據不影響另一個對象
// 持有其它資源的類(如動態分配的內存、指向其他數據的指針)
#include <cstdlib>
#include <iostream>
using namespace std;class Array {public:Array(const int len) {m_len = len;m_p = (int*)calloc(m_len, sizeof(int));}Array(const Array& arr) {this->m_len = arr.m_len;this->m_p = (int*)calloc(this->m_len, sizeof(int));memcpy(this->m_p, arr.m_p, m_len * sizeof(int));}~Array() { free(m_p); }int operator[](int i) const { return m_p[i]; }int& operator[](int i) { return m_p[i]; }int length() const { return m_len; }private:int m_len;int* m_p;
};void printArray(const Array& arr) {int len = arr.length();for(int i = 0; i < len; i++) { if(i == len - 1) { cout << arr[i] << endl; } else { cout << arr[i] << ", "; } }
}...int main() {Array arr1(10);for(int i = 0; i < 10; i++)arr1[i] = i;Array arr2 = arr1;arr2[5] = 100;arr2[3] = 29;printArray(arr1); // 輸出:0, 1, 2, 3, 4, 5, 6, 7, 8, 9printArray(arr2); // 輸出:0, 1, 2, 29, 4, 100, 6, 7, 8, 9
}
// 創建對象時進行一些預處理工作
#include <iostream>
#include <ctime>
#include <windows.h>
using namespace std;class Base {public:Base(int a = 0, int b = 0) {m_a = a;m_b = b;m_count++;m_time = time(nullptr);}Base(const Base& obj) {this->m_a = obj.m_a;this->m_b = obj.m_b;this->m_count++;this->m_time = time(nullptr);}static int getCount() { return m_count; }time_t getTime() const { return m_time; }private:int m_a;int m_b;time_t m_time; //對象創建時間static int m_count; //創建過的對象的數目
};int Base::m_count = 0;...int main() {Base obj1(10, 20);cout << "obj1: count = " << obj1.getCount() << ", time = " << obj1.getTime() << endl;// 輸出:obj1: count = 1, time = 1740055359Sleep(3000);Base obj2 = obj1;cout << "obj2: count = " << obj2.getCount() << ", time = " << obj2.getTime() << endl;// 輸出:obj2: count = 2, time = 1740055362return 0;
}
👻類靜態成員
👾類靜態成員變量
👽聲明
使用 關鍵字 static
把類成員聲明為靜態
class Box {public:static int objectCount; // 聲明靜態變量Box() { objectCount++; } // 每次創建對象時調用構造函數,靜態變量值加 1
};
無論創建多少個類的對象,靜態成員只有一個副本,在類的所有對象中是共享的。
👽定義
在類外部通過 范圍解析運算符 ::
定義靜態變量
class Box {public:static int objectCount; // 聲明靜態變量Box() { objectCount++; }
};
// 僅定義卻不初始化 類靜態成員
int Box::objectCount;
無論是否初始化,必須定義,否則報錯:
(.rdata$.refptr._ZN3Box11objectCountE[.refptr._ZN3Box11objectCountE]+0x0): undefined reference to `Box::objectCount'
👽初始化
// 定義+初始化 類靜態成員
int Box::objectCount = 0;
若不初始化,則在創建第一個對象時所有靜態數據初始化為零。
👽訪問
- 使用 范圍解析運算符
::
訪問類靜態變量
Box box1(); // 聲明 box1
Box box2(); // 聲明 box2
cout << "創建對象總數: " << Box::objectCount << endl;
👾類靜態成員函數
👽聲明定義
- 使用 關鍵字
static
把類成員聲明為靜態
class Box {public:static int objectCount; // 聲明靜態變量Box() { objectCount++; }static int getCount() { // 聲明定義靜態函數return objectCount;}
};
- 普通成員函數有
this
指針,可以訪問類中的任意成員- 靜態成員函數沒有
this
指針,只能訪問靜態成員變量、其他靜態成員函數、類外部的其他函數
👽使用
- 使用 范圍解析運算符
::
訪問類靜態變量
cout << "創建對象總數: " << Box::getCount() << endl; // 對象不存在時 也能被調用
Box box1(); // 聲明 box1
Box box2(); // 聲明 box2
cout << "創建對象總數: " << Box::getCount() << endl;
靜態成員函數即使 類對象不存在 也能被調用
👻友元
👾友元函數
- 類的友元函數定義在類外部,但有權訪問類的私有成員、保護成員
- 盡管友元函數的原型在類的定義中出現,但友元函數不是成員函數
👽聲明
使用 關鍵字 friend
把函數聲明為友元函數
class Box {double width;public:Box(double w): width(w) {}friend void printWidth(Box box); // 在函數前使用關鍵字 friend
};
👽定義
class Box {double width;public:Box(double w): width(w) {}friend void printWidth(Box box);
};/* printWidth() 不是任何類的成員函數,但因為它是 Box 的友元,可直接訪問該類的任何成員 */
void printWidth(Box box) { cout << "Width of box : " << box.width <<endl;}
👽使用
Box box;
box.setWidth(10.0);
printWidth(box); // 使用友元函數訪問Box中的私有成員
👾友元類
👽聲明定義
class Box {public:Box(double w): width(w) {}friend class BigBox;private:double width;
};/* BigBox是Box的友元類,它可以直接訪問Box類的任何成員 */
class BigBox {public :void printWidth(Box& box) { cout << "Width of box : " << box.width << endl; }
};
👽使用
Box box(10.0);
BigBox big;
big.printWidth(box); // 使用友元類中的方法訪問Box中的私有成員
👻 this 指針
this
指針是一個特殊的指針,它指向當前對象的實例,每一個對象都能通過this
指針來訪問自己的地址。- 當一個對象的成員函數被調用時,編譯器會隱式地傳遞該對象的地址作為
this
指針。 - 友元函數沒有
this
指針,因為友元不是類的成員
class MyClass {private:int value;public:// 可以明確地告訴編譯器想訪問當前對象的成員變量,而不是函數參數或局部變量,避免命名沖突void setValue(int value) { this->value = value; } void getValue() { return this->value; }
};...MyClass obj;
obj.setValue(42);
int value = obj.getValue();