W...Y的主頁 ??
代碼倉庫分享 ??
??前言:
?之前我們學(xué)習(xí)了從C語言轉(zhuǎn)到C++后我們需要知道的一些關(guān)鍵改動與變化。今天我們就要學(xué)習(xí)C++獨有的類與對象。在談?wù)擃惻c對象之前我們先說一下什么是面向?qū)ο蟮腃++,什么是面向過程的C語言。
目錄
面向過程和面向?qū)ο蟪醪秸J識
類的引入
類的定義
類的訪問限定符及封裝
訪問限定符
類的作用域
類的實例化
類對象模型
如何計算類對象的大小?
?類對象的存儲方式猜測?
結(jié)構(gòu)體內(nèi)存對齊規(guī)則
this指針(重點)
this指針的引出
this指針的特性
面向過程和面向?qū)ο蟪醪秸J識
C語言是面向過程的,關(guān)注的是過程,分析出求解問題的步驟,通過函數(shù)調(diào)用逐步解決問題。舉個實例來說,比如我們需要進行洗衣服的操作,如果用面向過程的角度去了解:
C++是基于面向?qū)ο蟮模P(guān)注的是對象,將一件事情拆分成不同的對象,靠對象之間的交互完
成。如果用面向?qū)ο蟮姆绞饺タ创匆路?/p>
面向?qū)ο缶幊蹋∣bject-Oriented Programming,OOP)和面向過程編程(Procedural Programming,POP)是兩種不同的編程范式,它們在代碼組織、數(shù)據(jù)處理和解決問題的方式上有明顯的區(qū)別。以下是它們之間的主要區(qū)別:
1. 基本思想:1.面向?qū)ο缶幊蹋∣OP):OOP的核心思想是以對象為基本單位,將數(shù)據(jù)(屬性)和操作(方法)封裝在一起。程序的設(shè)計和實現(xiàn)側(cè)重于對象之間的交互和協(xié)作,強調(diào)模塊化和可重用性。
2.面向過程編程(POP):POP的核心思想是以過程和函數(shù)為基本單位,程序的設(shè)計和實現(xiàn)主要關(guān)注算法和操作步驟的順序。數(shù)據(jù)和函數(shù)是分離的。2. 數(shù)據(jù)處理:
3.面向?qū)ο缶幊蹋∣OP):數(shù)據(jù)和操作在類(class)內(nèi)部封裝在一起,類的實例(對象)通過方法來操作數(shù)據(jù)。這提供了數(shù)據(jù)隱藏和封裝的機制。
4.面向過程編程(POP):數(shù)據(jù)和函數(shù)通常是分開的,函數(shù)對數(shù)據(jù)進行處理,但數(shù)據(jù)的狀態(tài)通常是全局可見的。3. 繼承和多態(tài):
5.面向?qū)ο缶幊蹋∣OP):OOP支持繼承,允許創(chuàng)建新類基于現(xiàn)有類的屬性和方法。多態(tài)是OOP的另一個關(guān)鍵概念,允許對象以不同的方式響應(yīng)相同的消息或方法調(diào)用。
6.面向過程編程(POP):POP通常沒有繼承和多態(tài)的概念。4. 代碼復(fù)用:
7.面向?qū)ο缶幊蹋∣OP):OOP通過繼承和組合等機制促進代碼的復(fù)用,使得可以更輕松地構(gòu)建和維護大型項目。
8.面向過程編程(POP):在POP中,代碼的復(fù)用主要依賴于函數(shù)的抽象和模塊化,但不如OOP中的復(fù)用機制靈活。5. 可擴展性:
9.面向?qū)ο缶幊蹋∣OP):OOP傾向于更好地支持可擴展性,通過添加新類和方法來擴展程序。
10.面向過程編程(POP):在POP中,為了添加新功能,可能需要修改現(xiàn)有代碼或添加新函數(shù),這可能不如OOP那么靈活。6. 程序設(shè)計思維:
11.面向?qū)ο缶幊蹋∣OP):OOP鼓勵程序員思考問題的方式是從對象和其關(guān)系的角度出發(fā),強調(diào)現(xiàn)實世界的建模。
12.面向過程編程(POP):POP鼓勵程序員從任務(wù)的角度出發(fā),思考如何按照一系列步驟來解決問題。綜上所述,面向?qū)ο缶幊毯兔嫦蜻^程編程代表了不同的編程哲學(xué)和方法,適用于不同類型的問題和項目。選擇哪種編程范式取決于問題的復(fù)雜性、團隊的需求以及程序員的偏好。通常,在大型、復(fù)雜的軟件項目中,面向?qū)ο缶幊谈菀拙S護和擴展,而在一些小型、簡單的任務(wù)中,面向過程編程可能更加直觀和高效。
類的引入
C語言結(jié)構(gòu)體中只能定義變量,在C++中,結(jié)構(gòu)體內(nèi)不僅可以定義變量,也可以定義函數(shù)。比如:
之前在數(shù)據(jù)結(jié)構(gòu)初階中,用C語言方式實現(xiàn)的棧,結(jié)構(gòu)體中只能定義變量;現(xiàn)在以C++方式實現(xiàn),
會發(fā)現(xiàn)struct中也可以定義函數(shù)。
當(dāng)我們需要定義一個棧數(shù)據(jù)時,使用C語言時結(jié)構(gòu)體就與其對應(yīng)函數(shù)分開:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;
}
void STDestroy(ST* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
int main(void)
{
ST st;
return 0;
}
而使用C++進行定義時,函數(shù)可以定義到結(jié)構(gòu)體中:
typedef int DataType;
struct Stack
{
void Init(size_t capacity)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _array)
{
perror("malloc申請空間失敗");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(const DataType& data)
{
// 擴容
_array[_size] = data;
++_size;
}
DataType Top()
{
return _array[_size - 1];
}
void Destroy()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
DataType* _array;
size_t _capacity;
size_t _size;
};
int main()
{
Stack s;
s.Init(10);
s.Push(1);
s.Push(2);
s.Push(3);
cout << s.Top() << endl;
s.Destroy();
return 0;
}
?C++兼容C語言struct的所有用法都支持,struct升級成類,類名就是類型,Stack就是類型,不需要加struct。類里面定義的函數(shù)。上面結(jié)構(gòu)體的定義,在C++中更喜歡用class來代替。
int main()
{
struct Stack s1;//可以這樣定義。
Stack s2;//這樣更好
return 0;
}
我們在C++中定義函數(shù)時也可以不用使用stack+作用名,直接作用名即可。因為C語言的函數(shù)都是全局函數(shù),在哪都可以使用,如果在程序中有多個類似函數(shù)就會容易混淆,但是C++我們封裝在一個關(guān)于Stack的類中,所以就可以不用。
C++在使用類中的函數(shù)時,就如C語言中訪問結(jié)構(gòu)體內(nèi)容一樣方便。
C++:
int main() { Stact s1; s1.Init(); s1.push(1); reuturn 0; }
?C語言:
int main() { struct Stact s1; StackInit(&s1); StackPush(&S1, 1); reuturn 0; }
這樣一對比我們就可以直觀的看出,C語言與C++哪一個方便了。
?struct不是C++純正的類,接下來我們來學(xué)習(xí)一下class!
類的定義
class className
{
// 類體:由成員函數(shù)和成員變量組成
}; ?// 一定要注意后面的分號
class為定義類的關(guān)鍵字,ClassName為類的名字,{}中為類的主體,注意類定義結(jié)束時后面分
號不能省略。
類體中內(nèi)容稱為類的成員:類中的變量稱為類的屬性或成員變量; 類中的函數(shù)稱為類的方法或者
成員函數(shù)。
類的兩種定義方式:
1. 聲明和定義全部放在類體中,需注意:成員函數(shù)如果在類中定義,編譯器可能會將其當(dāng)成內(nèi)
聯(lián)函數(shù)處理。
2. 類聲明放在.h文件中,成員函數(shù)定義放在.cpp文件中,注意:成員函數(shù)名前需要加類名::
一般情況下,更期望采用第二種方式。注意:上博客中放一起為了方便演示,大家后序工
作中盡量使用第二種。?
成員變量命名規(guī)則的建議:
// 我們看看這個函數(shù),是不是很僵硬?
class Date
{
public:
void Init(int year)
{
// 這里的year到底是成員變量,還是函數(shù)形參?
year = year;
}
private:
int year;
};
// 所以一般都建議這樣
class Date
{
public:
void Init(int year)
{
_year = year;
}
private:
int _year;
};
// 或者這樣
class Date
{
public:
void Init(int year)
{
mYear = year;
}
private:
int mYear;
};
所以我們進行命名時要區(qū)分參數(shù)與類中聲明變量的區(qū)別。?
其實說直白一點就是將剛剛的struct換成class即可。我們就成功定義了類。那我們將struct換成class后程序有無變化呢?
繼續(xù)是剛才的程序我們換成class進行運行:
?出現(xiàn)了非常之多的問題,這時為什么呢?下面就要引出一個知識。
類的訪問限定符及封裝
訪問限定符
C++實現(xiàn)封裝的方式:用類將對象的屬性與方法結(jié)合在一塊,讓對象更加完善,通過訪問權(quán)限選
擇性的將其接口提供給外部的用戶使用。
【訪問限定符說明】
1. public修飾的成員在類外可以直接被訪問
2. protected和private修飾的成員在類外不能直接被訪問(此處protected和private是類似的)
3. 訪問權(quán)限作用域從該訪問限定符出現(xiàn)的位置開始直到下一個訪問限定符出現(xiàn)時為止
4. 如果后面沒有訪問限定符,作用域就到 } 即類結(jié)束。
5. class的默認訪問權(quán)限為private,struct為public(因為struct要兼容C)
pubic是在類里面外面都可以訪問,而protected與private不能被訪問。
注意:訪問限定符只在編譯時有用,當(dāng)數(shù)據(jù)映射到內(nèi)存后,沒有任何訪問限定符上的區(qū)別?
【面試題】
問題:C++中struct和class的區(qū)別是什么?
解答:C++需要兼容C語言,所以C++中struct可以當(dāng)成結(jié)構(gòu)體使用。另外C++中struct還可以用來定義類。和class定義類是一樣的,區(qū)別是struct定義的類默認訪問權(quán)限是public,class定義的類默認訪問權(quán)限是private。注意:在繼承和模板參數(shù)列表位置,struct和class也有區(qū)別,后序給大家介紹。?
?所以我們可以解決上述問題,為什么將struct換成class后就會報錯了,因為class默認所有元素都是私有的。我們就可以進行解決:
typedef int DataType;
struct Stack
{
public:
void Init(size_t capacity)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _array)
{
perror("malloc申請空間失敗");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(const DataType& data)
{
// 擴容
_array[_size] = data;
++_size;
}
DataType Top()
{
return _array[_size - 1];
}
void Destroy()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _capacity;
size_t _size;
};
int main()
{
Stack s;
s.Init(10);
s.Push(1);
s.Push(2);
s.Push(3);
cout << s.Top() << endl;
s.Destroy();
return 0;
}
訪問限定符的區(qū)間是從一個限定符到另一個限定符或者從限定符到類的結(jié)尾。
那C++為什么要進行這樣的操作呢?它是想優(yōu)化什么問題呢?
在C語言中我們想要取棧頂元素時,會有一個函數(shù)進行封裝然后進行調(diào)用來達到取棧頂元素的效果:
STDataType STTop(ST* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->a[ps->top - 1];
}
?但是其實如果我們清楚棧的實現(xiàn)邏輯,清楚top指向的位置,我們也可以進行直接去訪問棧來獲取棧頂元素:
int num = s1->a[ps->size-1];
但是這非??简炇褂谜叩拇a素養(yǎng),素養(yǎng)高的人會使用函數(shù)素養(yǎng)低的人會一會調(diào)用函數(shù)一會訪問數(shù)據(jù)。而C++有了類有了訪問限定符就只能從函數(shù)下手,一般情況下為了安全數(shù)據(jù)都是私有不能直接訪問的。
類的作用域
類定義了一個新的作用域,類的所有成員都在類的作用域中。在類體外定義成員時,需要使用 ::
作用域操作符指明成員屬于哪個類域。?
class Person
{
public:
void PrintPersonInfo();
private:
char _name[20];
char _gender[3];
int ?_age;
};
// 這里需要指定PrintPersonInfo是屬于Person這個類域
void Person::PrintPersonInfo()
{
cout << _name << " "<< _gender << " " << _age << endl;
}
類的實例化
用類類型創(chuàng)建對象的過程,稱為類的實例化
1. 類是對對象進行描述的,是一個模型一樣的東西,限定了類有哪些成員,定義出一個類并沒
有分配實際的內(nèi)存空間來存儲它;比如:入學(xué)時填寫的學(xué)生信息表,表格就可以看成是一個
類,來描述具體學(xué)生信息。
類就像謎語一樣,對謎底來進行描述,謎底就是謎語的一個實例。
2. 一個類可以實例化出多個對象,實例化出的對象 占用實際的物理空間,存儲類成員變量
int main()
{
Person._age = 100; ?// 編譯失敗:error C2059: 語法錯誤:“.”
return 0;
}
Person類是沒有空間的,只有Person類實例化出的對象才有具體的年齡。
3. 做個比方。類實例化出對象就像現(xiàn)實中使用建筑設(shè)計圖建造出房子,類就像是設(shè)計圖,只設(shè)
計出需要什么東西,但是并沒有實體的建筑存在,同樣類也只是一個設(shè)計,實例化出的對象
才能實際存儲數(shù)據(jù),占用物理空間
?
類對象模型
如何計算類對象的大小?
class A
{
public:
void PrintA()
{
? cout<<_a<<endl;
}
private:
char _a;
};
問題:類中既可以有成員變量,又可以有成員函數(shù),那么一個類的對象中包含了什么?如何計算
一個類的大小?
?類對象的存儲方式猜測?
對象中包含類的各個成員
缺陷:每個對象中成員變量是不同的,但是調(diào)用同一份函數(shù),如果按照此種方式存儲,當(dāng)一
個類創(chuàng)建多個對象時,每個對象中都會保存一份代碼,相同代碼保存多次,浪費空間。那么
如何解決呢?
代碼只保存一份,在對象中保存存放代碼的地址
只保存成員變量,成員函數(shù)存放在公共的代碼段
問題:對于上述三種存儲方式,那計算機到底是按照那種方式來存儲的?
// 類中既有成員變量,又有成員函數(shù)
class A1 {
public:
??void f1(){}
private:
??int _a;
};
// 類中僅有成員函數(shù)
class A2 {
public:
?void f2() {}
};
// 類中什么都沒有---空類
class A3
{};
?sizeof(A1) : 4bite??sizeof(A2) : 1bite??sizeof(A3):1bite
結(jié)論:一個類的大小,實際就是該類中”成員變量”之和,當(dāng)然要注意內(nèi)存對齊
注意空類的大小,空類比較特殊,編譯器給了空類一個字節(jié)來唯一標識這個類的對象。
結(jié)構(gòu)體內(nèi)存對齊規(guī)則
1. 第一個成員在與結(jié)構(gòu)體偏移量為0的地址處。
2. 其他成員變量要對齊到某個數(shù)字(對齊數(shù))的整數(shù)倍的地址處。
注意:對齊數(shù) = 編譯器默認的一個對齊數(shù) 與 該成員大小的較小值。
VS中默認的對齊數(shù)為8
3. 結(jié)構(gòu)體總大小為:最大對齊數(shù)(所有變量類型最大者與默認對齊參數(shù)取最小)的整數(shù)倍。
4. 如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體對齊到自己的最大對齊數(shù)的整數(shù)倍處,結(jié)構(gòu)體的整
體大小就是所有最大對齊數(shù)(含嵌套結(jié)構(gòu)體的對齊數(shù))的整數(shù)倍。
總結(jié):其實與C語言中的結(jié)構(gòu)體計算大小方法相同,不明白的可以自行觀看博主之前博客。
結(jié)構(gòu)體大小計算方法https://blog.csdn.net/m0_74755811/article/details/131757185?spm=1001.2014.3001.5501
this指針(重點)
this指針的引出
我們先來定義一個日期類 Date
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
private:
int _year; ??// 年
int _month; ??// 月
int _day; ???// 日
};
int main()
{
Date d1, d2;
d1.Init(2022,1,11);
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0;
}
對于上述類,有這樣的一個問題:
Date類中有 Init 與 Print 兩個成員函數(shù),函數(shù)體中沒有關(guān)于不同對象的區(qū)分,那當(dāng)d1調(diào)用 Init 函
數(shù)時,該函數(shù)是如何知道應(yīng)該設(shè)置d1對象,而不是設(shè)置d2對象呢?
C++中通過引入this指針解決該問題,即:C++編譯器給每個“非靜態(tài)的成員函數(shù)“增加了一個隱藏
的指針參數(shù),讓該指針指向當(dāng)前對象(函數(shù)運行時調(diào)用該函數(shù)的對象),在函數(shù)體中所有“成員變量”
的操作,都是通過該指針去訪問。只不過所有的操作對用戶是透明的,即用戶不需要來傳遞,編
譯器自動完成。?
我們不能顯示寫this相關(guān)實參和形參?。?!?
this指針的特性
1. this指針的類型:類類型* const,即成員函數(shù)中,不能給this指針賦值。
2. 只能在“成員函數(shù)”的內(nèi)部使用
3. this指針本質(zhì)上是“成員函數(shù)”的形參,當(dāng)對象調(diào)用成員函數(shù)時,將對象地址作為實參傳遞給
this形參。所以對象中不存儲this指針。
4. this指針是“成員函數(shù)”第一個隱含的指針形參,一般情況由編譯器通過ecx寄存器自動傳
遞,不需要用戶傳遞文章來源:http://www.zghlxwxcb.cn/news/detail-716905.html
以上就是本次全部內(nèi)容,感謝大家觀看,一鍵三連支持一下博主吧!??!文章來源地址http://www.zghlxwxcb.cn/news/detail-716905.html
到了這里,關(guān)于掌握C++魔法:深入解析類與對象(上篇)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!