C++核心程式設計筆記
C++核心程式設計
本階段主要針對C++物件導向程式設計技術做詳細講解,探討C++中的核心和精髓。
1 記憶體分割槽模型
C++程式在執行時,將記憶體大方向劃分為4個區域
- 程式碼區:存放函式體的二進位制程式碼,由作業系統進行管理的
- 全域性區:存放全域性變數和靜態變數以及常量
- 棧區:由編譯器自動分配釋放, 存放函式的引數值,區域性變數等
- 堆區:由程式設計師分配和釋放,若程式設計師不釋放,程式結束時由作業系統回收
記憶體四區意義:
不同區域存放的資料,賦予不同的生命週期, 給我們更大的靈活程式設計
1.1 程式執行前
在程式編譯後,生成了exe可執行程式,未執行該程式前分為兩個區域
程式碼區:
存放 CPU 執行的機器指令
程式碼區是共享的,共享的目的是對於頻繁被執行的程式,只需要在記憶體中有一份程式碼即可
程式碼區是只讀的,使其只讀的原因是防止程式意外地修改了它的指令
全域性區:
全域性變數和靜態變數存放在此.
全域性區還包含了常量區, 字串常量和其他常量也存放在此.
該區域的資料在程式結束後由作業系統釋放.
示例:
//全域性變數
int g_a = 10;
int g_b = 10;
//全域性常量
const int c_g_a = 10;
const int c_g_b = 10;
int main() {
//區域性變數
int a = 10;
int b = 10;
//列印地址
cout << "區域性變數a地址為: " << (int)&a << endl;
cout << "區域性變數b地址為: " << (int)&b << endl;
cout << "全域性變數g_a地址為: " << (int)&g_a << endl;
cout << "全域性變數g_b地址為: " << (int)&g_b << endl;
//靜態變數
static int s_a = 10;
static int s_b = 10;
cout << "靜態變數s_a地址為: " << (int)&s_a << endl;
cout << "靜態變數s_b地址為: " << (int)&s_b << endl;
cout << "字串常量地址為: " << (int)&"hello world" << endl;
cout << "字串常量地址為: " << (int)&"hello world1" << endl;
cout << "全域性常量c_g_a地址為: " << (int)&c_g_a << endl;
cout << "全域性常量c_g_b地址為: " << (int)&c_g_b << endl;
const int c_l_a = 10;
const int c_l_b = 10;
cout << "區域性常量c_l_a地址為: " << (int)&c_l_a << endl;
cout << "區域性常量c_l_b地址為: " << (int)&c_l_b << endl;
system("pause");
return 0;
}
列印結果:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-WwMjqOfv-1603073902170)(assets/1545017602518.png)]
總結:
- C++中在程式執行前分為全域性區和程式碼區
- 程式碼區特點是共享和只讀
- 全域性區中存放全域性變數、靜態變數、常量
- 常量區中存放 const修飾的全域性常量 和 字串常量
1.2 程式執行後
棧區:
由編譯器自動分配釋放, 存放函式的引數值,區域性變數等
注意事項:不要返回區域性變數的地址,棧區開闢的資料由編譯器自動釋放
示例:
int * func()
{
int a = 10;
return &a;
}
int main() {
int *p = func();
cout << *p << endl;
cout << *p << endl;
system("pause");
return 0;
}
堆區:
由程式設計師分配釋放,若程式設計師不釋放,程式結束時由作業系統回收
在C++中主要利用new在堆區開闢記憶體
示例:
int* func()
{
int* a = new int(10);
return a;
}
int main() {
int *p = func();
cout << *p << endl;
cout << *p << endl;
system("pause");
return 0;
}
總結:
堆區資料由程式設計師管理開闢和釋放
堆區資料利用new關鍵字進行開闢記憶體
1.3 new操作符
C++中利用new操作符在堆區開闢資料
堆區開闢的資料,由程式設計師手動開闢,手動釋放,釋放利用操作符 delete
語法:new 資料型別
利用new建立的資料,會返回該資料對應的型別的指標
示例1: 基本語法
int* func()
{
int* a = new int(10);
return a;
}
int main() {
int *p = func();
cout << *p << endl;
cout << *p << endl;
//利用delete釋放堆區資料
delete p;
//cout << *p << endl; //報錯,釋放的空間不可訪問
system("pause");
return 0;
}
示例2:開闢陣列
//堆區開闢陣列
int main() {
int* arr = new int[10];
for (int i = 0; i < 10; i++)
{
arr[i] = i + 100;
}
for (int i = 0; i < 10; i++)
{
cout << arr[i] << endl;
}
//釋放陣列 delete 後加 []
delete[] arr;
system("pause");
return 0;
}
2 引用
2.1 引用的基本使用
**作用: **給變數起別名
語法: 資料型別 &別名 = 原名
示例:
int main() {
int a = 10;
int &b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
b = 100;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause");
return 0;
}
2.2 引用注意事項
- 引用必須初始化
- 引用在初始化後,不可以改變
示例:
int main() {
int a = 10;
int b = 20;
//int &c; //錯誤,引用必須初始化
int &c = a; //一旦初始化後,就不可以更改
c = b; //這是賦值操作,不是更改引用
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
system("pause");
return 0;
}
2.3 引用做函式引數
**作用:**函式傳參時,可以利用引用的技術讓形參修飾實參
**優點:**可以簡化指標修改實參
示例:
//1. 值傳遞
void mySwap01(int a, int b) {
int temp = a;
a = b;
b = temp;
}
//2. 地址傳遞
void mySwap02(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
//3. 引用傳遞
void mySwap03(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 20;
mySwap01(a, b);
cout << "a:" << a << " b:" << b << endl;
mySwap02(&a, &b);
cout << "a:" << a << " b:" << b << endl;
mySwap03(a, b);
cout << "a:" << a << " b:" << b << endl;
system("pause");
return 0;
}
總結:通過引用引數產生的效果同按地址傳遞是一樣的。引用的語法更清楚簡單
2.4 引用做函式返回值
作用:引用是可以作為函式的返回值存在的
注意:不要返回區域性變數引用
用法:函式呼叫作為左值
示例:
//返回區域性變數引用
int& test01() {
int a = 10; //區域性變數
return a;
}
//返回靜態變數引用
int& test02() {
static int a = 20;
return a;
}
int main() {
//不能返回區域性變數的引用
int& ref = test01();
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
//如果函式做左值,那麼必須返回引用
int& ref2 = test02();
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
test02() = 1000;
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
system("pause");
return 0;
}
2.5 引用的本質
本質:引用的本質在c++內部實現是一個指標常量.
講解示例:
//發現是引用,轉換為 int* const ref = &a;
void func(int& ref){
ref = 100; // ref是引用,轉換為*ref = 100
}
int main(){
int a = 10;
//自動轉換為 int* const ref = &a; 指標常量是指標指向不可改,也說明為什麼引用不可更改
int& ref = a;
ref = 20; //內部發現ref是引用,自動幫我們轉換為: *ref = 20;
cout << "a:" << a << endl;
cout << "ref:" << ref << endl;
func(a);
return 0;
}
結論:C++推薦用引用技術,因為語法方便,引用本質是指標常量,但是所有的指標操作編譯器都幫我們做了
2.6 常量引用
**作用:**常量引用主要用來修飾形參,防止誤操作
在函式形參列表中,可以加const修飾形參,防止形參改變實參
示例:
//引用使用的場景,通常用來修飾形參
void showValue(const int& v) {
//v += 10;
cout << v << endl;
}
int main() {
//int& ref = 10; 引用本身需要一個合法的記憶體空間,因此這行錯誤
//加入const就可以了,編譯器優化程式碼,int temp = 10; const int& ref = temp;
const int& ref = 10;
//ref = 100; //加入const後不可以修改變數
cout << ref << endl;
//函式中利用常量引用防止誤操作修改實參
int a = 10;
showValue(a);
system("pause");
return 0;
}
3 函式提高
3.1 函式預設引數
在C++中,函式的形參列表中的形參是可以有預設值的。
語法:返回值型別 函式名 (引數= 預設值){}
示例:
int func(int a, int b = 10, int c = 10) {
return a + b + c;
}
//1. 如果某個位置引數有預設值,那麼從這個位置往後,從左向右,必須都要有預設值
//2. 如果函式宣告有預設值,函式實現的時候就不能有預設引數
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
return a + b;
}
int main() {
cout << "ret = " << func(20, 20) << endl;
cout << "ret = " << func(100) << endl;
system("pause");
return 0;
}
3.2 函式佔位引數
C++中函式的形參列表裡可以有佔位引數,用來做佔位,呼叫函式時必須填補該位置
語法: 返回值型別 函式名 (資料型別){}
在現階段函式的佔位引數存在意義不大,但是後面的課程中會用到該技術
示例:
//函式佔位引數 ,佔位引數也可以有預設引數
void func(int a, int) {
cout << "this is func" << endl;
}
int main() {
func(10,10); //佔位引數必須填補
system("pause");
return 0;
}
3.3 函式過載
3.3.1 函式過載概述
**作用:**函式名可以相同,提高複用性
函式過載滿足條件:
- 同一個作用域下
- 函式名稱相同
- 函式引數型別不同 或者 個數不同 或者 順序不同
注意: 函式的返回值不可以作為函式過載的條件
示例:
//函式過載需要函式都在同一個作用域下
void func()
{
cout << "func 的呼叫!" << endl;
}
void func(int a)
{
cout << "func (int a) 的呼叫!" << endl;
}
void func(double a)
{
cout << "func (double a)的呼叫!" << endl;
}
void func(int a ,double b)
{
cout << "func (int a ,double b) 的呼叫!" << endl;
}
void func(double a ,int b)
{
cout << "func (double a ,int b)的呼叫!" << endl;
}
//函式返回值不可以作為函式過載條件
//int func(double a, int b)
//{
// cout << "func (double a ,int b)的呼叫!" << endl;
//}
int main() {
func();
func(10);
func(3.14);
func(10,3.14);
func(3.14 , 10);
system("pause");
return 0;
}
3.3.2 函式過載注意事項
- 引用作為過載條件
- 函式過載碰到函式預設引數
示例:
//函式過載注意事項
//1、引用作為過載條件
void func(int &a)
{
cout << "func (int &a) 呼叫 " << endl;
}
void func(const int &a)
{
cout << "func (const int &a) 呼叫 " << endl;
}
//2、函式過載碰到函式預設引數
void func2(int a, int b = 10)
{
cout << "func2(int a, int b = 10) 呼叫" << endl;
}
void func2(int a)
{
cout << "func2(int a) 呼叫" << endl;
}
int main() {
int a = 10;
func(a); //呼叫無const
func(10);//呼叫有const
//func2(10); //碰到預設引數產生歧義,需要避免
system("pause");
return 0;
}
4 類和物件
C++物件導向的三大特性為:封裝、繼承、多型
C++認為萬事萬物都皆為物件,物件上有其屬性和行為
例如:
人可以作為物件,屬性有姓名、年齡、身高、體重…,行為有走、跑、跳、吃飯、唱歌…
車也可以作為物件,屬性有輪胎、方向盤、車燈…,行為有載人、放音樂、放空調…
具有相同性質的物件,我們可以抽象稱為類,人屬於人類,車屬於車類
4.1 封裝
4.1.1 封裝的意義
封裝是C++物件導向三大特性之一
封裝的意義:
- 將屬性和行為作為一個整體,表現生活中的事物
- 將屬性和行為加以許可權控制
封裝意義一:
在設計類的時候,屬性和行為寫在一起,表現事物
語法: class 類名{ 訪問許可權: 屬性 / 行為 };
**示例1:**設計一個圓類,求圓的周長
示例程式碼:
//圓周率
const double PI = 3.14;
//1、封裝的意義
//將屬性和行為作為一個整體,用來表現生活中的事物
//封裝一個圓類,求圓的周長
//class代表設計一個類,後面跟著的是類名
class Circle
{
public: //訪問許可權 公共的許可權
//屬性
int m_r;//半徑
//行為
//獲取到圓的周長
double calculateZC()
{
//2 * pi * r
//獲取圓的周長
return 2 * PI * m_r;
}
};
int main() {
//通過圓類,建立圓的物件
// c1就是一個具體的圓
Circle c1;
c1.m_r = 10; //給圓物件的半徑 進行賦值操作
//2 * pi * 10 = = 62.8
cout << "圓的周長為: " << c1.calculateZC() << endl;
system("pause");
return 0;
}
**示例2:**設計一個學生類,屬性有姓名和學號,可以給姓名和學號賦值,可以顯示學生的姓名和學號
示例2程式碼:
//學生類
class Student {
public:
void setName(string name) {
m_name = name;
}
void setID(int id) {
m_id = id;
}
void showStudent() {
cout << "name:" << m_name << " ID:" << m_id << endl;
}
public:
string m_name;
int m_id;
};
int main() {
Student stu;
stu.setName("德瑪西亞");
stu.setID(250);
stu.showStudent();
system("pause");
return 0;
}
封裝意義二:
類在設計時,可以把屬性和行為放在不同的許可權下,加以控制
訪問許可權有三種:
- public 公共許可權
- protected 保護許可權
- private 私有許可權
示例:
//三種許可權
//公共許可權 public 類內可以訪問 類外可以訪問
//保護許可權 protected 類內可以訪問 類外不可以訪問
//私有許可權 private 類內可以訪問 類外不可以訪問
class Person
{
//姓名 公共許可權
public:
string m_Name;
//汽車 保護許可權
protected:
string m_Car;
//銀行卡密碼 私有許可權
private:
int m_Password;
public:
void func()
{
m_Name = "張三";
m_Car = "拖拉機";
m_Password = 123456;
}
};
int main() {
Person p;
p.m_Name = "李四";
//p.m_Car = "賓士"; //保護許可權類外訪問不到
//p.m_Password = 123; //私有許可權類外訪問不到
system("pause");
return 0;
}
4.1.2 struct和class區別
在C++中 struct和class唯一的區別就在於 預設的訪問許可權不同
區別:
- struct 預設許可權為公共
- class 預設許可權為私有
class C1
{
int m_A; //預設是私有許可權
};
struct C2
{
int m_A; //預設是公共許可權
};
int main() {
C1 c1;
c1.m_A = 10; //錯誤,訪問許可權是私有
C2 c2;
c2.m_A = 10; //正確,訪問許可權是公共
system("pause");
return 0;
}
4.1.3 成員屬性設定為私有
**優點1:**將所有成員屬性設定為私有,可以自己控制讀寫許可權
**優點2:**對於寫許可權,我們可以檢測資料的有效性
示例:
class Person {
public:
//姓名設定可讀可寫
void setName(string name) {
m_Name = name;
}
string getName()
{
return m_Name;
}
//獲取年齡
int getAge() {
return m_Age;
}
//設定年齡
void setAge(int age) {
if (age < 0 || age > 150) {
cout << "你個老妖精!" << endl;
return;
}
m_Age = age;
}
//情人設定為只寫
void setLover(string lover) {
m_Lover = lover;
}
private:
string m_Name; //可讀可寫 姓名
int m_Age; //只讀 年齡
string m_Lover; //只寫 情人
};
int main() {
Person p;
//姓名設定
p.setName("張三");
cout << "姓名: " << p.getName() << endl;
//年齡設定
p.setAge(50);
cout << "年齡: " << p.getAge() << endl;
//情人設定
p.setLover("蒼井");
//cout << "情人: " << p.m_Lover << endl; //只寫屬性,不可以讀取
system("pause");
return 0;
}
4.2 物件的初始化和清理
- 生活中我們買的電子產品都基本會有出廠設定,在某一天我們不用時候也會刪除一些自己資訊資料保證安全
- C++中的物件導向來源於生活,每個物件也都會有初始設定以及 物件銷燬前的清理資料的設定。
4.2.1 建構函式和解構函式
物件的初始化和清理也是兩個非常重要的安全問題
一個物件或者變數沒有初始狀態,對其使用後果是未知
同樣的使用完一個物件或變數,沒有及時清理,也會造成一定的安全問題
c++利用了建構函式和解構函式解決上述問題,這兩個函式將會被編譯器自動呼叫,完成物件初始化和清理工作。
物件的初始化和清理工作是編譯器強制要我們做的事情,因此如果我們不提供構造和析構,編譯器會提供
編譯器提供的建構函式和解構函式是空實現。
- 建構函式:主要作用在於建立物件時為物件的成員屬性賦值,建構函式由編譯器自動呼叫,無須手動呼叫。
- 解構函式:主要作用在於物件銷燬前系統自動呼叫,執行一些清理工作。
建構函式語法:類名(){}
- 建構函式,沒有返回值也不寫void
- 函式名稱與類名相同
- 建構函式可以有引數,因此可以發生過載
- 程式在呼叫物件時候會自動呼叫構造,無須手動呼叫,而且只會呼叫一次
解構函式語法: ~類名(){}
- 解構函式,沒有返回值也不寫void
- 函式名稱與類名相同,在名稱前加上符號 ~
- 解構函式不可以有引數,因此不可以發生過載
- 程式在物件銷燬前會自動呼叫析構,無須手動呼叫,而且只會呼叫一次
class Person
{
public:
//建構函式
Person()
{
cout << "Person的建構函式呼叫" << endl;
}
//解構函式
~Person()
{
cout << "Person的解構函式呼叫" << endl;
}
};
void test01()
{
Person p;
}
int main() {
test01();
system("pause");
return 0;
}
4.2.2 建構函式的分類及呼叫
兩種分類方式:
按引數分為: 有參構造和無參構造
按型別分為: 普通構造和拷貝構造
三種呼叫方式:
括號法
顯示法
隱式轉換法
示例:
//1、建構函式分類
// 按照引數分類分為 有參和無參構造 無參又稱為預設建構函式
// 按照型別分類分為 普通構造和拷貝構造
class Person {
public:
//無參(預設)建構函式
Person() {
cout << "無參建構函式!" << endl;
}
//有參建構函式
Person(int a) {
age = a;
cout << "有參建構函式!" << endl;
}
//拷貝建構函式
Person(const Person& p) {
age = p.age;
cout << "拷貝建構函式!" << endl;
}
//解構函式
~Person() {
cout << "解構函式!" << endl;
}
public:
int age;
};
//2、建構函式的呼叫
//呼叫無參建構函式
void test01() {
Person p; //呼叫無參建構函式
}
//呼叫有參的建構函式
void test02() {
//2.1 括號法,常用
Person p1(10);
//注意1:呼叫無參建構函式不能加括號,如果加了編譯器認為這是一個函式宣告
//Person p2();
//2.2 顯式法
Person p2 = Person(10);
Person p3 = Person(p2);
//Person(10)單獨寫就是匿名物件 當前行結束之後,馬上析構
//2.3 隱式轉換法
Person p4 = 10; // Person p4 = Person(10);
Person p5 = p4; // Person p5 = Person(p4);
//注意2:不能利用 拷貝建構函式 初始化匿名物件 編譯器認為是物件宣告
//Person p5(p4);
}
int main() {
test01();
//test02();
system("pause");
return 0;
}
4.2.3 拷貝建構函式呼叫時機
C++中拷貝建構函式呼叫時機通常有三種情況
- 使用一個已經建立完畢的物件來初始化一個新物件
- 值傳遞的方式給函式引數傳值
- 以值方式返回區域性物件
示例:
class Person {
public:
Person() {
cout << "無參建構函式!" << endl;
mAge = 0;
}
Person(int age) {
cout << "有參建構函式!" << endl;
mAge = age;
}
Person(const Person& p) {
cout << "拷貝建構函式!" << endl;
mAge = p.mAge;
}
//解構函式在釋放記憶體之前呼叫
~Person() {
cout << "解構函式!" << endl;
}
public:
int mAge;
};
//1. 使用一個已經建立完畢的物件來初始化一個新物件
void test01() {
Person man(100); //p物件已經建立完畢
Person newman(man); //呼叫拷貝建構函式
Person newman2 = man; //拷貝構造
//Person newman3;
//newman3 = man; //不是呼叫拷貝建構函式,賦值操作
}
//2. 值傳遞的方式給函式引數傳值
//相當於Person p1 = p;
void doWork(Person p1) {}
void test02() {
Person p; //無參建構函式
doWork(p);
}
//3. 以值方式返回區域性物件
Person doWork2()
{
Person p1;
cout << (int *)&p1 << endl;
return p1;
}
void test03()
{
Person p = doWork2();
cout << (int *)&p << endl;
}
int main() {
//test01();
//test02();
test03();
system("pause");
return 0;
}
4.2.4 建構函式呼叫規則
預設情況下,c++編譯器至少給一個類新增3個函式
1.預設建構函式(無參,函式體為空)
2.預設解構函式(無參,函式體為空)
3.預設拷貝建構函式,對屬性進行值拷貝
建構函式呼叫規則如下:
-
如果使用者定義有參建構函式,c++不在提供預設無參構造,但是會提供預設拷貝構造
-
如果使用者定義拷貝建構函式,c++不會再提供其他建構函式
示例:
class Person {
public:
//無參(預設)建構函式
Person() {
cout << "無參建構函式!" << endl;
}
//有參建構函式
Person(int a) {
age = a;
cout << "有參建構函式!" << endl;
}
//拷貝建構函式
Person(const Person& p) {
age = p.age;
cout << "拷貝建構函式!" << endl;
}
//解構函式
~Person() {
cout << "解構函式!" << endl;
}
public:
int age;
};
void test01()
{
Person p1(18);
//如果不寫拷貝構造,編譯器會自動新增拷貝構造,並且做淺拷貝操作
Person p2(p1);
cout << "p2的年齡為: " << p2.age << endl;
}
void test02()
{
//如果使用者提供有參構造,編譯器不會提供預設構造,會提供拷貝構造
Person p1; //此時如果使用者自己沒有提供預設構造,會出錯
Person p2(10); //使用者提供的有參
Person p3(p2); //此時如果使用者沒有提供拷貝構造,編譯器會提供
//如果使用者提供拷貝構造,編譯器不會提供其他建構函式
Person p4; //此時如果使用者自己沒有提供預設構造,會出錯
Person p5(10); //此時如果使用者自己沒有提供有參,會出錯
Person p6(p5); //使用者自己提供拷貝構造
}
int main() {
test01();
system("pause");
return 0;
}
4.2.5 深拷貝與淺拷貝
深淺拷貝是面試經典問題,也是常見的一個坑
淺拷貝:簡單的賦值拷貝操作
深拷貝:在堆區重新申請空間,進行拷貝操作
示例:
class Person {
public:
//無參(預設)建構函式
Person() {
cout << "無參建構函式!" << endl;
}
//有參建構函式
Person(int age ,int height) {
cout << "有參建構函式!" << endl;
m_age = age;
m_height = new int(height);
}
//拷貝建構函式
Person(const Person& p) {
cout << "拷貝建構函式!" << endl;
//如果不利用深拷貝在堆區建立新記憶體,會導致淺拷貝帶來的重複釋放堆區問題
m_age = p.m_age;
m_height = new int(*p.m_height);
}
//解構函式
~Person() {
cout << "解構函式!" << endl;
if (m_height != NULL)
{
delete m_height;
}
}
public:
int m_age;
int* m_height;
};
void test01()
{
Person p1(18, 180);
Person p2(p1);
cout << "p1的年齡: " << p1.m_age << " 身高: " << *p1.m_height << endl;
cout << "p2的年齡: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}
int main() {
test01();
system("pause");
return 0;
}
總結:如果屬性有在堆區開闢的,一定要自己提供拷貝建構函式,防止淺拷貝帶來的問題
4.2.6 初始化列表
作用:
C++提供了初始化列表語法,用來初始化屬性
語法:建構函式():屬性1(值1),屬性2(值2)... {}
示例:
class Person {
public:
傳統方式初始化
//Person(int a, int b, int c) {
// m_A = a;
// m_B = b;
// m_C = c;
//}
//初始化列表方式初始化
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
void PrintPerson() {
cout << "mA:" << m_A << endl;
cout << "mB:" << m_B << endl;
cout << "mC:" << m_C << endl;
}
private:
int m_A;
int m_B;
int m_C;
};
int main() {
Person p(1, 2, 3);
p.PrintPerson();
system("pause");
return 0;
}
4.2.7 類物件作為類成員
C++類中的成員可以是另一個類的物件,我們稱該成員為 物件成員
例如:
class A {}
class B
{
A a;
}
B類中有物件A作為成員,A為物件成員
那麼當建立B物件時,A與B的構造和析構的順序是誰先誰後?
示例:
class Phone
{
public:
Phone(string name)
{
m_PhoneName = name;
cout << "Phone構造" << endl;
}
~Phone()
{
cout << "Phone析構" << endl;
}
string m_PhoneName;
};
class Person
{
public:
//初始化列表可以告訴編譯器呼叫哪一個建構函式
Person(string name, string pName) :m_Name(name), m_Phone(pName)
{
cout << "Person構造" << endl;
}
~Person()
{
cout << "Person析構" << endl;
}
void playGame()
{
cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手機! " << endl;
}
string m_Name;
Phone m_Phone;
};
void test01()
{
//當類中成員是其他類物件時,我們稱該成員為 物件成員
//構造的順序是 :先呼叫物件成員的構造,再呼叫本類構造
//析構順序與構造相反
Person p("張三" , "蘋果X");
p.playGame();
}
int main() {
test01();
system("pause");
return 0;
}
4.2.8 靜態成員
靜態成員就是在成員變數和成員函式前加上關鍵字static,稱為靜態成員
靜態成員分為:
- 靜態成員變數
- 所有物件共享同一份資料
- 在編譯階段分配記憶體
- 類內宣告,類外初始化
- 靜態成員函式
- 所有物件共享同一個函式
- 靜態成員函式只能訪問靜態成員變數
**示例1 :**靜態成員變數
class Person
{
public:
static int m_A; //靜態成員變數
//靜態成員變數特點:
//1 在編譯階段分配記憶體
//2 類內宣告,類外初始化
//3 所有物件共享同一份資料
private:
static int m_B; //靜態成員變數也是有訪問許可權的
};
int Person::m_A = 10;
int Person::m_B = 10;
void test01()
{
//靜態成員變數兩種訪問方式
//1、通過物件
Person p1;
p1.m_A = 100;
cout << "p1.m_A = " << p1.m_A << endl;
Person p2;
p2.m_A = 200;
cout << "p1.m_A = " << p1.m_A << endl; //共享同一份資料
cout << "p2.m_A = " << p2.m_A << endl;
//2、通過類名
cout << "m_A = " << Person::m_A << endl;
//cout << "m_B = " << Person::m_B << endl; //私有許可權訪問不到
}
int main() {
test01();
system("pause");
return 0;
}
**示例2:**靜態成員函式
class Person
{
public:
//靜態成員函式特點:
//1 程式共享一個函式
//2 靜態成員函式只能訪問靜態成員變數
static void func()
{
cout << "func呼叫" << endl;
m_A = 100;
//m_B = 100; //錯誤,不可以訪問非靜態成員變數
}
static int m_A; //靜態成員變數
int m_B; //
private:
//靜態成員函式也是有訪問許可權的
static void func2()
{
cout << "func2呼叫" << endl;
}
};
int Person::m_A = 10;
void test01()
{
//靜態成員變數兩種訪問方式
//1、通過物件
Person p1;
p1.func();
//2、通過類名
Person::func();
//Person::func2(); //私有許可權訪問不到
}
int main() {
test01();
system("pause");
return 0;
}
4.3 C++物件模型和this指標
4.3.1 成員變數和成員函式分開儲存
在C++中,類內的成員變數和成員函式分開儲存
只有非靜態成員變數才屬於類的物件上
class Person {
public:
Person() {
mA = 0;
}
//非靜態成員變數佔物件空間
int mA;
//靜態成員變數不佔物件空間
static int mB;
//函式也不佔物件空間,所有函式共享一個函式例項
void func() {
cout << "mA:" << this->mA << endl;
}
//靜態成員函式也不佔物件空間
static void sfunc() {
}
};
int main() {
cout << sizeof(Person) << endl;
system("pause");
return 0;
}
4.3.2 this指標概念
通過4.3.1我們知道在C++中成員變數和成員函式是分開儲存的
每一個非靜態成員函式只會誕生一份函式例項,也就是說多個同型別的物件會共用一塊程式碼
那麼問題是:這一塊程式碼是如何區分那個物件呼叫自己的呢?
c++通過提供特殊的物件指標,this指標,解決上述問題。this指標指向被呼叫的成員函式所屬的物件
this指標是隱含每一個非靜態成員函式內的一種指標
this指標不需要定義,直接使用即可
this指標的用途:
- 當形參和成員變數同名時,可用this指標來區分
- 在類的非靜態成員函式中返回物件本身,可使用return *this
class Person
{
public:
Person(int age)
{
//1、當形參和成員變數同名時,可用this指標來區分
this->age = age;
}
Person& PersonAddPerson(Person p)
{
this->age += p.age;
//返回物件本身
return *this;
}
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
Person p2(10);
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
cout << "p2.age = " << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
4.3.3 空指標訪問成員函式
C++中空指標也是可以呼叫成員函式的,但是也要注意有沒有用到this指標
如果用到this指標,需要加以判斷保證程式碼的健壯性
示例:
//空指標訪問成員函式
class Person {
public:
void ShowClassName() {
cout << "我是Person類!" << endl;
}
void ShowPerson() {
if (this == NULL) {
return;
}
cout << mAge << endl;
}
public:
int mAge;
};
void test01()
{
Person * p = NULL;
p->ShowClassName(); //空指標,可以呼叫成員函式
p->ShowPerson(); //但是如果成員函式中用到了this指標,就不可以了
}
int main() {
test01();
system("pause");
return 0;
}
4.3.4 const修飾成員函式
常函式:
- 成員函式後加const後我們稱為這個函式為常函式
- 常函式內不可以修改成員屬性
- 成員屬性宣告時加關鍵字mutable後,在常函式中依然可以修改
常物件:
- 宣告物件前加const稱該物件為常物件
- 常物件只能呼叫常函式
示例:
class Person {
public:
Person() {
m_A = 0;
m_B = 0;
}
//this指標的本質是一個指標常量,指標的指向不可修改
//如果想讓指標指向的值也不可以修改,需要宣告常函式
void ShowPerson() const {
//const Type* const pointer;
//this = NULL; //不能修改指標的指向 Person* const this;
//this->mA = 100; //但是this指標指向的物件的資料是可以修改的
//const修飾成員函式,表示指標指向的記憶體空間的資料不能修改,除了mutable修飾的變數
this->m_B = 100;
}
void MyFunc() const {
//mA = 10000;
}
public:
int m_A;
mutable int m_B; //可修改 可變的
};
//const修飾物件 常物件
void test01() {
const Person person; //常量物件
cout << person.m_A << endl;
//person.mA = 100; //常物件不能修改成員變數的值,但是可以訪問
person.m_B = 100; //但是常物件可以修改mutable修飾成員變數
//常物件訪問成員函式
person.MyFunc(); //常物件不能呼叫const的函式
}
int main() {
test01();
system("pause");
return 0;
}
4.4 友元
生活中你的家有客廳(Public),有你的臥室(Private)
客廳所有來的客人都可以進去,但是你的臥室是私有的,也就是說只有你能進去
但是呢,你也可以允許你的好閨蜜好基友進去。
在程式裡,有些私有屬性 也想讓類外特殊的一些函式或者類進行訪問,就需要用到友元的技術
友元的目的就是讓一個函式或者類 訪問另一個類中私有成員
友元的關鍵字為 friend
友元的三種實現
- 全域性函式做友元
- 類做友元
- 成員函式做友元
4.4.1 全域性函式做友元
class Building
{
//告訴編譯器 goodGay全域性函式 是 Building類的好朋友,可以訪問類中的私有內容
friend void goodGay(Building * building);
public:
Building()
{
this->m_SittingRoom = "客廳";
this->m_BedRoom = "臥室";
}
public:
string m_SittingRoom; //客廳
private:
string m_BedRoom; //臥室
};
void goodGay(Building * building)
{
cout << "好基友正在訪問: " << building->m_SittingRoom << endl;
cout << "好基友正在訪問: " << building->m_BedRoom << endl;
}
void test01()
{
Building b;
goodGay(&b);
}
int main(){
test01();
system("pause");
return 0;
}
4.4.2 類做友元
class Building;
class goodGay
{
public:
goodGay();
void visit();
private:
Building *building;
};
class Building
{
//告訴編譯器 goodGay類是Building類的好朋友,可以訪問到Building類中私有內容
friend class goodGay;
public:
Building();
public:
string m_SittingRoom; //客廳
private:
string m_BedRoom;//臥室
};
Building::Building()
{
this->m_SittingRoom = "客廳";
this->m_BedRoom = "臥室";
}
goodGay::goodGay()
{
building = new Building;
}
void goodGay::visit()
{
cout << "好基友正在訪問" << building->m_SittingRoom << endl;
cout << "好基友正在訪問" << building->m_BedRoom << endl;
}
void test01()
{
goodGay gg;
gg.visit();
}
int main(){
test01();
system("pause");
return 0;
}
4.4.3 成員函式做友元
class Building;
class goodGay
{
public:
goodGay();
void visit(); //只讓visit函式作為Building的好朋友,可以發訪問Building中私有內容
void visit2();
private:
Building *building;
};
class Building
{
//告訴編譯器 goodGay類中的visit成員函式 是Building好朋友,可以訪問私有內容
friend void goodGay::visit();
public:
Building();
public:
string m_SittingRoom; //客廳
private:
string m_BedRoom;//臥室
};
Building::Building()
{
this->m_SittingRoom = "客廳";
this->m_BedRoom = "臥室";
}
goodGay::goodGay()
{
building = new Building;
}
void goodGay::visit()
{
cout << "好基友正在訪問" << building->m_SittingRoom << endl;
cout << "好基友正在訪問" << building->m_BedRoom << endl;
}
void goodGay::visit2()
{
cout << "好基友正在訪問" << building->m_SittingRoom << endl;
//cout << "好基友正在訪問" << building->m_BedRoom << endl;
}
void test01()
{
goodGay gg;
gg.visit();
}
int main(){
test01();
system("pause");
return 0;
}
4.5 運算子過載
運算子過載概念:對已有的運算子重新進行定義,賦予其另一種功能,以適應不同的資料型別
4.5.1 加號運算子過載
作用:實現兩個自定義資料型別相加的運算
class Person {
public:
Person() {};
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
//成員函式實現 + 號運算子過載
Person operator+(const Person& p) {
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
public:
int m_A;
int m_B;
};
//全域性函式實現 + 號運算子過載
//Person operator+(const Person& p1, const Person& p2) {
// Person temp(0, 0);
// temp.m_A = p1.m_A + p2.m_A;
// temp.m_B = p1.m_B + p2.m_B;
// return temp;
//}
//運算子過載 可以發生函式過載
Person operator+(const Person& p2, int val)
{
Person temp;
temp.m_A = p2.m_A + val;
temp.m_B = p2.m_B + val;
return temp;
}
void test() {
Person p1(10, 10);
Person p2(20, 20);
//成員函式方式
Person p3 = p2 + p1; //相當於 p2.operaor+(p1)
cout << "mA:" << p3.m_A << " mB:" << p3.m_B << endl;
Person p4 = p3 + 10; //相當於 operator+(p3,10)
cout << "mA:" << p4.m_A << " mB:" << p4.m_B << endl;
}
int main() {
test();
system("pause");
return 0;
}
總結1:對於內建的資料型別的表示式的的運算子是不可能改變的
總結2:不要濫用運算子過載
4.5.2 左移運算子過載
作用:可以輸出自定義資料型別
class Person {
friend ostream& operator<<(ostream& out, Person& p);
public:
Person(int a, int b)
{
this->m_A = a;
this->m_B = b;
}
//成員函式 實現不了 p << cout 不是我們想要的效果
//void operator<<(Person& p){
//}
private:
int m_A;
int m_B;
};
//全域性函式實現左移過載
//ostream物件只能有一個
ostream& operator<<(ostream& out, Person& p) {
out << "a:" << p.m_A << " b:" << p.m_B;
return out;
}
void test() {
Person p1(10, 20);
cout << p1 << "hello world" << endl; //鏈式程式設計
}
int main() {
test();
system("pause");
return 0;
}
總結:過載左移運算子配合友元可以實現輸出自定義資料型別
4.5.3 遞增運算子過載
作用: 通過過載遞增運算子,實現自己的整型資料
class MyInteger {
friend ostream& operator<<(ostream& out, MyInteger myint);
public:
MyInteger() {
m_Num = 0;
}
//前置++
MyInteger& operator++() {
//先++
m_Num++;
//再返回
return *this;
}
//後置++
MyInteger operator++(int) {
//先返回
MyInteger temp = *this; //記錄當前本身的值,然後讓本身的值加1,但是返回的是以前的值,達到先返回後++;
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream& operator<<(ostream& out, MyInteger myint) {
out << myint.m_Num;
return out;
}
//前置++ 先++ 再返回
void test01() {
MyInteger myInt;
cout << ++myInt << endl;
cout << myInt << endl;
}
//後置++ 先返回 再++
void test02() {
MyInteger myInt;
cout << myInt++ << endl;
cout << myInt << endl;
}
int main() {
test01();
//test02();
system("pause");
return 0;
}
總結: 前置遞增返回引用,後置遞增返回值
4.5.4 賦值運算子過載
c++編譯器至少給一個類新增4個函式
- 預設建構函式(無參,函式體為空)
- 預設解構函式(無參,函式體為空)
- 預設拷貝建構函式,對屬性進行值拷貝
- 賦值運算子 operator=, 對屬性進行值拷貝
如果類中有屬性指向堆區,做賦值操作時也會出現深淺拷貝問題
示例:
class Person
{
public:
Person(int age)
{
//將年齡資料開闢到堆區
m_Age = new int(age);
}
//過載賦值運算子
Person& operator=(Person &p)
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
//編譯器提供的程式碼是淺拷貝
//m_Age = p.m_Age;
//提供深拷貝 解決淺拷貝的問題
m_Age = new int(*p.m_Age);
//返回自身
return *this;
}
~Person()
{
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
}
//年齡的指標
int *m_Age;
};
void test01()
{
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1; //賦值操作
cout << "p1的年齡為:" << *p1.m_Age << endl;
cout << "p2的年齡為:" << *p2.m_Age << endl;
cout << "p3的年齡為:" << *p3.m_Age << endl;
}
int main() {
test01();
//int a = 10;
//int b = 20;
//int c = 30;
//c = b = a;
//cout << "a = " << a << endl;
//cout << "b = " << b << endl;
//cout << "c = " << c << endl;
system("pause");
return 0;
}
4.5.5 關係運算子過載
**作用:**過載關係運算子,可以讓兩個自定義型別物件進行對比操作
示例:
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
};
bool operator==(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
else
{
return false;
}
}
bool operator!=(Person & p)
{
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age)
{
return false;
}
else
{
return true;
}
}
string m_Name;
int m_Age;
};
void test01()
{
//int a = 0;
//int b = 0;
Person a("孫悟空", 18);
Person b("孫悟空", 18);
if (a == b)
{
cout << "a和b相等" << endl;
}
else
{
cout << "a和b不相等" << endl;
}
if (a != b)
{
cout << "a和b不相等" << endl;
}
else
{
cout << "a和b相等" << endl;
}
}
int main() {
test01();
system("pause");
return 0;
}
4.5.6 函式呼叫運算子過載
- 函式呼叫運算子 () 也可以過載
- 由於過載後使用的方式非常像函式的呼叫,因此稱為仿函式
- 仿函式沒有固定寫法,非常靈活
示例:
class MyPrint
{
public:
void operator()(string text)
{
cout << text << endl;
}
};
void test01()
{
//過載的()操作符 也稱為仿函式
MyPrint myFunc;
myFunc("hello world");
}
class MyAdd
{
public:
int operator()(int v1, int v2)
{
return v1 + v2;
}
};
void test02()
{
MyAdd add;
int ret = add(10, 10);
cout << "ret = " << ret << endl;
//匿名物件呼叫
cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}
int main() {
test01();
test02();
system("pause");
return 0;
}
4.6 繼承
繼承是物件導向三大特性之一
下級別的成員除了擁有上一級的共性,還有自己的特性。
這個時候我們就可以考慮利用繼承的技術,減少重複程式碼
4.6.1 繼承的基本語法
例如我們看到很多網站中,都有公共的頭部,公共的底部,甚至公共的左側列表,只有中心內容不同
接下來我們分別利用普通寫法和繼承的寫法來實現網頁中的內容,看一下繼承存在的意義以及好處
普通實現:
//Java頁面
class Java
{
public:
void header()
{
cout << "首頁、公開課、登入、註冊...(公共頭部)" << endl;
}
void footer()
{
cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分類列表)" << endl;
}
void content()
{
cout << "JAVA學科視訊" << endl;
}
};
//Python頁面
class Python
{
public:
void header()
{
cout << "首頁、公開課、登入、註冊...(公共頭部)" << endl;
}
void footer()
{
cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分類列表)" << endl;
}
void content()
{
cout << "Python學科視訊" << endl;
}
};
//C++頁面
class CPP
{
public:
void header()
{
cout << "首頁、公開課、登入、註冊...(公共頭部)" << endl;
}
void footer()
{
cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分類列表)" << endl;
}
void content()
{
cout << "C++學科視訊" << endl;
}
};
void test01()
{
//Java頁面
cout << "Java下載視訊頁面如下: " << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "--------------------" << endl;
//Python頁面
cout << "Python下載視訊頁面如下: " << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "--------------------" << endl;
//C++頁面
cout << "C++下載視訊頁面如下: " << endl;
CPP cp;
cp.header();
cp.footer();
cp.left();
cp.content();
}
int main() {
test01();
system("pause");
return 0;
}
繼承實現:
//公共頁面
class BasePage
{
public:
void header()
{
cout << "首頁、公開課、登入、註冊...(公共頭部)" << endl;
}
void footer()
{
cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
}
void left()
{
cout << "Java,Python,C++...(公共分類列表)" << endl;
}
};
//Java頁面
class Java : public BasePage
{
public:
void content()
{
cout << "JAVA學科視訊" << endl;
}
};
//Python頁面
class Python : public BasePage
{
public:
void content()
{
cout << "Python學科視訊" << endl;
}
};
//C++頁面
class CPP : public BasePage
{
public:
void content()
{
cout << "C++學科視訊" << endl;
}
};
void test01()
{
//Java頁面
cout << "Java下載視訊頁面如下: " << endl;
Java ja;
ja.header();
ja.footer();
ja.left();
ja.content();
cout << "--------------------" << endl;
//Python頁面
cout << "Python下載視訊頁面如下: " << endl;
Python py;
py.header();
py.footer();
py.left();
py.content();
cout << "--------------------" << endl;
//C++頁面
cout << "C++下載視訊頁面如下: " << endl;
CPP cp;
cp.header();
cp.footer();
cp.left();
cp.content();
}
int main() {
test01();
system("pause");
return 0;
}
總結:
繼承的好處:可以減少重複的程式碼
class A : public B;
A 類稱為子類 或 派生類
B 類稱為父類 或 基類
派生類中的成員,包含兩大部分:
一類是從基類繼承過來的,一類是自己增加的成員。
從基類繼承過過來的表現其共性,而新增的成員體現了其個性。
4.6.2 繼承方式
繼承的語法:class 子類 : 繼承方式 父類
繼承方式一共有三種:
- 公共繼承
- 保護繼承
- 私有繼承
示例:
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//公共繼承
class Son1 :public Base1
{
public:
void func()
{
m_A; //可訪問 public許可權
m_B; //可訪問 protected許可權
//m_C; //不可訪問
}
};
void myClass()
{
Son1 s1;
s1.m_A; //其他類只能訪問到公共許可權
}
//保護繼承
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2:protected Base2
{
public:
void func()
{
m_A; //可訪問 protected許可權
m_B; //可訪問 protected許可權
//m_C; //不可訪問
}
};
void myClass2()
{
Son2 s;
//s.m_A; //不可訪問
}
//私有繼承
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3:private Base3
{
public:
void func()
{
m_A; //可訪問 private許可權
m_B; //可訪問 private許可權
//m_C; //不可訪問
}
};
class GrandSon3 :public Son3
{
public:
void func()
{
//Son3是私有繼承,所以繼承Son3的屬性在GrandSon3中都無法訪問到
//m_A;
//m_B;
//m_C;
}
};
4.6.3 繼承中的物件模型
**問題:**從父類繼承過來的成員,哪些屬於子類物件中?
示例:
class Base
{
public:
int m_A;
protected:
int m_B;
private:
int m_C; //私有成員只是被隱藏了,但是還是會繼承下去
};
//公共繼承
class Son :public Base
{
public:
int m_D;
};
void test01()
{
cout << "sizeof Son = " << sizeof(Son) << endl;
}
int main() {
test01();
system("pause");
return 0;
}
4.6.4 繼承中構造和析構順序
子類繼承父類後,當建立子類物件,也會呼叫父類的建構函式
問題:父類和子類的構造和析構順序是誰先誰後?
示例:
class Base
{
public:
Base()
{
cout << "Base建構函式!" << endl;
}
~Base()
{
cout << "Base解構函式!" << endl;
}
};
class Son : public Base
{
public:
Son()
{
cout << "Son建構函式!" << endl;
}
~Son()
{
cout << "Son解構函式!" << endl;
}
};
void test01()
{
//繼承中 先呼叫父類建構函式,再呼叫子類建構函式,析構順序與構造相反
Son s;
}
int main() {
test01();
system("pause");
return 0;
}
總結:繼承中 先呼叫父類建構函式,再呼叫子類建構函式,析構順序與構造相反
4.6.5 繼承同名成員處理方式
問題:當子類與父類出現同名的成員,如何通過子類物件,訪問到子類或父類中同名的資料呢?
- 訪問子類同名成員 直接訪問即可
- 訪問父類同名成員 需要加作用域
示例:
class Base {
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base - func()呼叫" << endl;
}
void func(int a)
{
cout << "Base - func(int a)呼叫" << endl;
}
public:
int m_A;
};
class Son : public Base {
public:
Son()
{
m_A = 200;
}
//當子類與父類擁有同名的成員函式,子類會隱藏父類中所有版本的同名成員函式
//如果想訪問父類中被隱藏的同名成員函式,需要加父類的作用域
void func()
{
cout << "Son - func()呼叫" << endl;
}
public:
int m_A;
};
void test01()
{
Son s;
cout << "Son下的m_A = " << s.m_A << endl;
cout << "Base下的m_A = " << s.Base::m_A << endl;
s.func();
s.Base::func();
s.Base::func(10);
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}
總結:
- 子類物件可以直接訪問到子類中同名成員
- 子類物件加作用域可以訪問到父類同名成員
- 當子類與父類擁有同名的成員函式,子類會隱藏父類中同名成員函式,加作用域可以訪問到父類中同名函式
4.6.6 繼承同名靜態成員處理方式
問題:繼承中同名的靜態成員在子類物件上如何進行訪問?
靜態成員和非靜態成員出現同名,處理方式一致
- 訪問子類同名成員 直接訪問即可
- 訪問父類同名成員 需要加作用域
示例:
class Base {
public:
static void func()
{
cout << "Base - static void func()" << endl;
}
static void func(int a)
{
cout << "Base - static void func(int a)" << endl;
}
static int m_A;
};
int Base::m_A = 100;
class Son : public Base {
public:
static void func()
{
cout << "Son - static void func()" << endl;
}
static int m_A;
};
int Son::m_A = 200;
//同名成員屬性
void test01()
{
//通過物件訪問
cout << "通過物件訪問: " << endl;
Son s;
cout << "Son 下 m_A = " << s.m_A << endl;
cout << "Base 下 m_A = " << s.Base::m_A << endl;
//通過類名訪問
cout << "通過類名訪問: " << endl;
cout << "Son 下 m_A = " << Son::m_A << endl;
cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}
//同名成員函式
void test02()
{
//通過物件訪問
cout << "通過物件訪問: " << endl;
Son s;
s.func();
s.Base::func();
cout << "通過類名訪問: " << endl;
Son::func();
Son::Base::func();
//出現同名,子類會隱藏掉父類中所有同名成員函式,需要加作作用域訪問
Son::Base::func(100);
}
int main() {
//test01();
test02();
system("pause");
return 0;
}
總結:同名靜態成員處理方式和非靜態處理方式一樣,只不過有兩種訪問的方式(通過物件 和 通過類名)
4.6.7 多繼承語法
C++允許一個類繼承多個類
語法:class 子類 :繼承方式 父類1 , 繼承方式 父類2...
多繼承可能會引發父類中有同名成員出現,需要加作用域區分
C++實際開發中不建議用多繼承
示例:
class Base1 {
public:
Base1()
{
m_A = 100;
}
public:
int m_A;
};
class Base2 {
public:
Base2()
{
m_A = 200; //開始是m_B 不會出問題,但是改為mA就會出現不明確
}
public:
int m_A;
};
//語法:class 子類:繼承方式 父類1 ,繼承方式 父類2
class Son : public Base2, public Base1
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
public:
int m_C;
int m_D;
};
//多繼承容易產生成員同名的情況
//通過使用類名作用域可以區分呼叫哪一個基類的成員
void test01()
{
Son s;
cout << "sizeof Son = " << sizeof(s) << endl;
cout << s.Base1::m_A << endl;
cout << s.Base2::m_A << endl;
}
int main() {
test01();
system("pause");
return 0;
}
總結: 多繼承中如果父類中出現了同名情況,子類使用時候要加作用域
4.6.8 菱形繼承
菱形繼承概念:
兩個派生類繼承同一個基類
又有某個類同時繼承者兩個派生類
這種繼承被稱為菱形繼承,或者鑽石繼承
菱形繼承問題:
-
羊繼承了動物的資料,駝同樣繼承了動物的資料,當草泥馬使用資料時,就會產生二義性。
-
草泥馬繼承自動物的資料繼承了兩份,其實我們應該清楚,這份資料我們只需要一份就可以。
示例:
class Animal
{
public:
int m_Age;
};
//繼承前加virtual關鍵字後,變為虛繼承
//此時公共的父類Animal稱為虛基類
class Sheep : virtual public Animal {};
class Tuo : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};
void test01()
{
SheepTuo st;
st.Sheep::m_Age = 100;
st.Tuo::m_Age = 200;
cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl;
cout << "st.m_Age = " << st.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
總結:
- 菱形繼承帶來的主要問題是子類繼承兩份相同的資料,導致資源浪費以及毫無意義
- 利用虛繼承可以解決菱形繼承問題
4.7 多型
4.7.1 多型的基本概念
多型是C++物件導向三大特性之一
多型分為兩類
- 靜態多型: 函式過載 和 運算子過載屬於靜態多型,複用函式名
- 動態多型: 派生類和虛擬函式實現執行時多型
靜態多型和動態多型區別:
- 靜態多型的函式地址早繫結 - 編譯階段確定函式地址
- 動態多型的函式地址晚繫結 - 執行階段確定函式地址
下面通過案例進行講解多型
class Animal
{
public:
//Speak函式就是虛擬函式
//函式前面加上virtual關鍵字,變成虛擬函式,那麼編譯器在編譯的時候就不能確定函式呼叫了。
virtual void speak()
{
cout << "動物在說話" << endl;
}
};
class Cat :public Animal
{
public:
void speak()
{
cout << "小貓在說話" << endl;
}
};
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在說話" << endl;
}
};
//我們希望傳入什麼物件,那麼就呼叫什麼物件的函式
//如果函式地址在編譯階段就能確定,那麼靜態聯編
//如果函式地址在執行階段才能確定,就是動態聯編
void DoSpeak(Animal & animal)
{
animal.speak();
}
//
//多型滿足條件:
//1、有繼承關係
//2、子類重寫父類中的虛擬函式
//多型使用:
//父類指標或引用指向子類物件
void test01()
{
Cat cat;
DoSpeak(cat);
Dog dog;
DoSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
總結:
多型滿足條件
- 有繼承關係
- 子類重寫父類中的虛擬函式
多型使用條件
- 父類指標或引用指向子類物件
重寫:函式返回值型別 函式名 引數列表 完全一致稱為重寫
4.7.2 多型案例一-計算器類
案例描述:
分別利用普通寫法和多型技術,設計實現兩個運算元進行運算的計算器類
多型的優點:
- 程式碼組織結構清晰
- 可讀性強
- 利於前期和後期的擴充套件以及維護
示例:
//普通實現
class Calculator {
public:
int getResult(string oper)
{
if (oper == "+") {
return m_Num1 + m_Num2;
}
else if (oper == "-") {
return m_Num1 - m_Num2;
}
else if (oper == "*") {
return m_Num1 * m_Num2;
}
//如果要提供新的運算,需要修改原始碼
}
public:
int m_Num1;
int m_Num2;
};
void test01()
{
//普通實現測試
Calculator c;
c.m_Num1 = 10;
c.m_Num2 = 10;
cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;
cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;
cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}
//多型實現
//抽象計算器類
//多型優點:程式碼組織結構清晰,可讀性強,利於前期和後期的擴充套件以及維護
class AbstractCalculator
{
public :
virtual int getResult()
{
return 0;
}
int m_Num1;
int m_Num2;
};
//加法計算器
class AddCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 + m_Num2;
}
};
//減法計算器
class SubCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 - m_Num2;
}
};
//乘法計算器
class MulCalculator :public AbstractCalculator
{
public:
int getResult()
{
return m_Num1 * m_Num2;
}
};
void test02()
{
//建立加法計算器
AbstractCalculator *abc = new AddCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc; //用完了記得銷燬
//建立減法計算器
abc = new SubCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
//建立乘法計算器
abc = new MulCalculator;
abc->m_Num1 = 10;
abc->m_Num2 = 10;
cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
delete abc;
}
int main() {
//test01();
test02();
system("pause");
return 0;
}
總結:C++開發提倡利用多型設計程式架構,因為多型優點很多
4.7.3 純虛擬函式和抽象類
在多型中,通常父類中虛擬函式的實現是毫無意義的,主要都是呼叫子類重寫的內容
因此可以將虛擬函式改為純虛擬函式
純虛擬函式語法:virtual 返回值型別 函式名 (引數列表)= 0 ;
當類中有了純虛擬函式,這個類也稱為抽象類
抽象類特點:
- 無法例項化物件
- 子類必須重寫抽象類中的純虛擬函式,否則也屬於抽象類
示例:
class Base
{
public:
//純虛擬函式
//類中只要有一個純虛擬函式就稱為抽象類
//抽象類無法例項化物件
//子類必須重寫父類中的純虛擬函式,否則也屬於抽象類
virtual void func() = 0;
};
class Son :public Base
{
public:
virtual void func()
{
cout << "func呼叫" << endl;
};
};
void test01()
{
Base * base = NULL;
//base = new Base; // 錯誤,抽象類無法例項化物件
base = new Son;
base->func();
delete base;//記得銷燬
}
int main() {
test01();
system("pause");
return 0;
}
4.7.5 虛析構和純虛析構
多型使用時,如果子類中有屬性開闢到堆區,那麼父類指標在釋放時無法呼叫到子類的析構程式碼
解決方式:將父類中的解構函式改為虛析構或者純虛析構
虛析構和純虛析構共性:
- 可以解決父類指標釋放子類物件
- 都需要有具體的函式實現
虛析構和純虛析構區別:
- 如果是純虛析構,該類屬於抽象類,無法例項化物件
虛析構語法:
virtual ~類名(){}
純虛析構語法:
virtual ~類名() = 0;
類名::~類名(){}
示例:
class Animal {
public:
Animal()
{
cout << "Animal 建構函式呼叫!" << endl;
}
virtual void Speak() = 0;
//解構函式加上virtual關鍵字,變成虛解構函式
//virtual ~Animal()
//{
// cout << "Animal虛解構函式呼叫!" << endl;
//}
virtual ~Animal() = 0;
};
Animal::~Animal()
{
cout << "Animal 純虛解構函式呼叫!" << endl;
}
//和包含普通純虛擬函式的類一樣,包含了純虛解構函式的類也是一個抽象類。不能夠被例項化。
class Cat : public Animal {
public:
Cat(string name)
{
cout << "Cat建構函式呼叫!" << endl;
m_Name = new string(name);
}
virtual void Speak()
{
cout << *m_Name << "小貓在說話!" << endl;
}
~Cat()
{
cout << "Cat解構函式呼叫!" << endl;
if (this->m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}
public:
string *m_Name;
};
void test01()
{
Animal *animal = new Cat("Tom");
animal->Speak();
//通過父類指標去釋放,會導致子類物件可能清理不乾淨,造成記憶體洩漏
//怎麼解決?給基類增加一個虛解構函式
//虛解構函式就是用來解決通過父類指標釋放子類物件
delete animal;
}
int main() {
test01();
system("pause");
return 0;
}
總結:
1. 虛析構或純虛析構就是用來解決通過父類指標釋放子類物件
2. 如果子類中沒有堆區資料,可以不寫為虛析構或純虛析構
3. 擁有純虛解構函式的類也屬於抽象類
5 檔案操作
程式執行時產生的資料都屬於臨時資料,程式一旦執行結束都會被釋放
通過檔案可以將資料持久化
C++中對檔案操作需要包含標頭檔案 < fstream >
檔案型別分為兩種:
- 文字檔案 - 檔案以文字的ASCII碼形式儲存在計算機中
- 二進位制檔案 - 檔案以文字的二進位制形式儲存在計算機中,使用者一般不能直接讀懂它們
操作檔案的三大類:
- ofstream:寫操作
- ifstream: 讀操作
- fstream : 讀寫操作
5.1文字檔案
5.1.1寫檔案
寫檔案步驟如下:
-
包含標頭檔案
#include <fstream>
-
建立流物件
ofstream ofs;
-
開啟檔案
ofs.open(“檔案路徑”,開啟方式);
-
寫資料
ofs << “寫入的資料”;
-
關閉檔案
ofs.close();
檔案開啟方式:
開啟方式 | 解釋 |
---|---|
ios::in | 為讀檔案而開啟檔案 |
ios::out | 為寫檔案而開啟檔案 |
ios::ate | 初始位置:檔案尾 |
ios::app | 追加方式寫檔案 |
ios::trunc | 如果檔案存在先刪除,再建立 |
ios::binary | 二進位制方式 |
注意: 檔案開啟方式可以配合使用,利用|操作符
**例如:**用二進位制方式寫檔案 ios::binary | ios:: out
示例:
#include <fstream>
void test01()
{
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "姓名:張三" << endl;
ofs << "性別:男" << endl;
ofs << "年齡:18" << endl;
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
總結:
- 檔案操作必須包含標頭檔案 fstream
- 讀檔案可以利用 ofstream ,或者fstream類
- 開啟檔案時候需要指定操作檔案的路徑,以及開啟方式
- 利用<<可以向檔案中寫資料
- 操作完畢,要關閉檔案
5.1.2讀檔案
讀檔案與寫檔案步驟相似,但是讀取方式相對於比較多
讀檔案步驟如下:
-
包含標頭檔案
#include <fstream>
-
建立流物件
ifstream ifs;
-
開啟檔案並判斷檔案是否開啟成功
ifs.open(“檔案路徑”,開啟方式);
-
讀資料
四種方式讀取
-
關閉檔案
ifs.close();
示例:
#include <fstream>
#include <string>
void test01()
{
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "檔案開啟失敗" << endl;
return;
}
//第一種方式
//char buf[1024] = { 0 };
//while (ifs >> buf)
//{
// cout << buf << endl;
//}
//第二種
//char buf[1024] = { 0 };
//while (ifs.getline(buf,sizeof(buf)))
//{
// cout << buf << endl;
//}
//第三種
//string buf;
//while (getline(ifs, buf))
//{
// cout << buf << endl;
//}
char c;
while ((c = ifs.get()) != EOF)
{
cout << c;
}
ifs.close();
}
int main() {
test01();
system("pause");
return 0;
}
總結:
- 讀檔案可以利用 ifstream ,或者fstream類
- 利用is_open函式可以判斷檔案是否開啟成功
- close 關閉檔案
5.2 二進位制檔案
以二進位制的方式對檔案進行讀寫操作
開啟方式要指定為 ios::binary
5.2.1 寫檔案
二進位制方式寫檔案主要利用流物件呼叫成員函式write
函式原型 :ostream& write(const char * buffer,int len);
引數解釋:字元指標buffer指向記憶體中一段儲存空間。len是讀寫的位元組數
示例:
#include <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
//二進位制檔案 寫檔案
void test01()
{
//1、包含標頭檔案
//2、建立輸出流物件
ofstream ofs("person.txt", ios::out | ios::binary);
//3、開啟檔案
//ofs.open("person.txt", ios::out | ios::binary);
Person p = {"張三" , 18};
//4、寫檔案
ofs.write((const char *)&p, sizeof(p));
//5、關閉檔案
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
總結:
- 檔案輸出流物件 可以通過write函式,以二進位制方式寫資料
5.2.2 讀檔案
二進位制方式讀檔案主要利用流物件呼叫成員函式read
函式原型:istream& read(char *buffer,int len);
引數解釋:字元指標buffer指向記憶體中一段儲存空間。len是讀寫的位元組數
示例:
#include <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
void test01()
{
ifstream ifs("person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "檔案開啟失敗" << endl;
}
Person p;
ifs.read((char *)&p, sizeof(p));
cout << "姓名: " << p.m_Name << " 年齡: " << p.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
- 檔案輸入流物件 可以通過read函式,以二進位制方式讀資料
cout << “Animal 純虛解構函式呼叫!” << endl;
}
//和包含普通純虛擬函式的類一樣,包含了純虛解構函式的類也是一個抽象類。不能夠被例項化。
class Cat : public Animal {
public:
Cat(string name)
{
cout << “Cat建構函式呼叫!” << endl;
m_Name = new string(name);
}
virtual void Speak()
{
cout << *m_Name << “小貓在說話!” << endl;
}
~Cat()
{
cout << “Cat解構函式呼叫!” << endl;
if (this->m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}
public:
string *m_Name;
};
void test01()
{
Animal *animal = new Cat(“Tom”);
animal->Speak();
//通過父類指標去釋放,會導致子類物件可能清理不乾淨,造成記憶體洩漏
//怎麼解決?給基類增加一個虛解構函式
//虛解構函式就是用來解決通過父類指標釋放子類物件
delete animal;
}
int main() {
test01();
system("pause");
return 0;
}
總結:
1. 虛析構或純虛析構就是用來解決通過父類指標釋放子類物件
2. 如果子類中沒有堆區資料,可以不寫為虛析構或純虛析構
3. 擁有純虛解構函式的類也屬於抽象類
## 5 檔案操作
程式執行時產生的資料都屬於臨時資料,程式一旦執行結束都會被釋放
通過**檔案可以將資料持久化**
C++中對檔案操作需要包含標頭檔案 ==< fstream >==
檔案型別分為兩種:
1. **文字檔案** - 檔案以文字的**ASCII碼**形式儲存在計算機中
2. **二進位制檔案** - 檔案以文字的**二進位制**形式儲存在計算機中,使用者一般不能直接讀懂它們
操作檔案的三大類:
1. ofstream:寫操作
2. ifstream: 讀操作
3. fstream : 讀寫操作
### 5.1文字檔案
#### 5.1.1寫檔案
寫檔案步驟如下:
1. 包含標頭檔案
\#include <fstream\>
2. 建立流物件
ofstream ofs;
3. 開啟檔案
ofs.open("檔案路徑",開啟方式);
4. 寫資料
ofs << "寫入的資料";
5. 關閉檔案
ofs.close();
檔案開啟方式:
| 開啟方式 | 解釋 |
| ----------- | -------------------------- |
| ios::in | 為讀檔案而開啟檔案 |
| ios::out | 為寫檔案而開啟檔案 |
| ios::ate | 初始位置:檔案尾 |
| ios::app | 追加方式寫檔案 |
| ios::trunc | 如果檔案存在先刪除,再建立 |
| ios::binary | 二進位制方式 |
**注意:** 檔案開啟方式可以配合使用,利用|操作符
**例如:**用二進位制方式寫檔案 `ios::binary | ios:: out`
**示例:**
```C++
#include <fstream>
void test01()
{
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "姓名:張三" << endl;
ofs << "性別:男" << endl;
ofs << "年齡:18" << endl;
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
總結:
- 檔案操作必須包含標頭檔案 fstream
- 讀檔案可以利用 ofstream ,或者fstream類
- 開啟檔案時候需要指定操作檔案的路徑,以及開啟方式
- 利用<<可以向檔案中寫資料
- 操作完畢,要關閉檔案
5.1.2讀檔案
讀檔案與寫檔案步驟相似,但是讀取方式相對於比較多
讀檔案步驟如下:
-
包含標頭檔案
#include <fstream>
-
建立流物件
ifstream ifs;
-
開啟檔案並判斷檔案是否開啟成功
ifs.open(“檔案路徑”,開啟方式);
-
讀資料
四種方式讀取
-
關閉檔案
ifs.close();
示例:
#include <fstream>
#include <string>
void test01()
{
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "檔案開啟失敗" << endl;
return;
}
//第一種方式
//char buf[1024] = { 0 };
//while (ifs >> buf)
//{
// cout << buf << endl;
//}
//第二種
//char buf[1024] = { 0 };
//while (ifs.getline(buf,sizeof(buf)))
//{
// cout << buf << endl;
//}
//第三種
//string buf;
//while (getline(ifs, buf))
//{
// cout << buf << endl;
//}
char c;
while ((c = ifs.get()) != EOF)
{
cout << c;
}
ifs.close();
}
int main() {
test01();
system("pause");
return 0;
}
總結:
- 讀檔案可以利用 ifstream ,或者fstream類
- 利用is_open函式可以判斷檔案是否開啟成功
- close 關閉檔案
5.2 二進位制檔案
以二進位制的方式對檔案進行讀寫操作
開啟方式要指定為 ios::binary
5.2.1 寫檔案
二進位制方式寫檔案主要利用流物件呼叫成員函式write
函式原型 :ostream& write(const char * buffer,int len);
引數解釋:字元指標buffer指向記憶體中一段儲存空間。len是讀寫的位元組數
示例:
#include <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
//二進位制檔案 寫檔案
void test01()
{
//1、包含標頭檔案
//2、建立輸出流物件
ofstream ofs("person.txt", ios::out | ios::binary);
//3、開啟檔案
//ofs.open("person.txt", ios::out | ios::binary);
Person p = {"張三" , 18};
//4、寫檔案
ofs.write((const char *)&p, sizeof(p));
//5、關閉檔案
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
總結:
- 檔案輸出流物件 可以通過write函式,以二進位制方式寫資料
5.2.2 讀檔案
二進位制方式讀檔案主要利用流物件呼叫成員函式read
函式原型:istream& read(char *buffer,int len);
引數解釋:字元指標buffer指向記憶體中一段儲存空間。len是讀寫的位元組數
示例:
#include <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
void test01()
{
ifstream ifs("person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "檔案開啟失敗" << endl;
}
Person p;
ifs.read((char *)&p, sizeof(p));
cout << "姓名: " << p.m_Name << " 年齡: " << p.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
- 檔案輸入流物件 可以通過read函式,以二進位制方式讀資料
相關文章
- 《Windows核心程式設計》筆記(一)Windows程式設計筆記
- C++核心程式設計C++程式設計
- freeRTOS核心學習筆記(1)-程式設計標準筆記程式設計
- Coursera課程筆記----C++程式設計----Week3筆記C++程式設計
- 精通Visual C++圖象程式設計------讀書筆記8C++程式設計筆記
- 精通Visual C++圖象程式設計---讀書筆記6.2C++程式設計筆記
- 精通Visual C++圖象程式設計---讀書筆記6.1C++程式設計筆記
- 精通Visual C++圖象程式設計---讀書筆記5C++程式設計筆記
- Python核心程式設計筆記第二章----快速入門Python程式設計筆記
- 精通C#學習筆記---C#核心程式設計結構C#筆記程式設計
- 【Python核心程式設計筆記】一、Python中一切皆物件Python程式設計筆記物件
- 非同步程式設計筆記非同步程式設計筆記
- Windows sdk程式設計筆記Windows程式設計筆記
- 網路程式設計筆記程式設計筆記
- C++語言程式設計筆記 - 第12章 - 異常處理C++程式設計筆記
- 《Java核心技術(卷1)》筆記:第8章 泛型程式設計Java筆記泛型程式設計
- windows核心程式設計--記憶體堆疊Windows程式設計記憶體
- go併發程式設計筆記Go程式設計筆記
- JavaScript非同步程式設計筆記JavaScript非同步程式設計筆記
- shell指令碼程式設計筆記指令碼程式設計筆記
- nodejs筆記-非同步程式設計NodeJS筆記非同步程式設計
- Java 基礎程式設計筆記Java程式設計筆記
- JavaScript高階程式設計筆記JavaScript程式設計筆記
- windows核心程式設計--程式Windows程式設計
- 《C++ Primer》學習筆記(六):C++模組設計——函式C++筆記函式
- C++語言程式設計筆記 - 第6章 - 陣列、指標與字串C++程式設計筆記陣列指標字串
- 四. 文字程式設計--Windows程式設計課程學習筆記程式設計Windows筆記
- windows核心程式設計--核心物件Windows程式設計物件
- [筆記]物件導向的程式設計筆記物件程式設計
- Python之——網路程式設計筆記Python程式設計筆記
- Java 程式設計思想筆記:Learn 10Java程式設計筆記
- 高效能javascript程式設計筆記JavaScript程式設計筆記
- 網路程式設計學習筆記程式設計筆記
- 《Go 語言程式設計》讀書筆記(十一)底層程式設計Go程式設計筆記
- Java高階程式設計筆記 • 【第4章 網路程式設計】Java程式設計筆記
- windows核心程式設計--記憶體對映檔案Windows程式設計記憶體
- Windows核心程式設計_HookWindows程式設計Hook
- 大二上 C++高階程式設計筆記(1) 棧和c++對c的補充 20240908C++程式設計筆記