C語言是面向過程進(jìn)行編程,注重點(diǎn)在過程上,這個(gè)過程指的是解決問題的過程。C++是面向?qū)ο筮M(jìn)行編程,注重點(diǎn)是對象,主要處理的是對象與對象間的關(guān)系。
舉個(gè)例子來了解兩者之間的區(qū)別,例如送外賣這件事情。
(1)面向過程
(2)面向?qū)ο?/p>
過程:外賣員到商家店鋪,商家將外賣提供給外賣員。外賣員到客戶指定點(diǎn),將外賣送給客戶,客戶拿到外賣。
對象:外賣員、外賣、商家、客戶
外賣員:前往商店拿外賣,將外賣送給客戶
外賣:由商家提供,先給外賣員,最后給客戶
商家:提供外賣給外賣員
客戶:拿到外賣員送到的外賣
在面對對象中,客戶不需要知道商家是如何制作外賣的,只需要拿到外賣員送到的外賣,并確認(rèn)是否是自己點(diǎn)的外賣。每個(gè)對象只需要關(guān)注自己有關(guān)的事情,不需要了解整個(gè)國產(chǎn)的實(shí)現(xiàn)。
在C語言中結(jié)構(gòu)體由struct關(guān)鍵字定義。C++中,類可以由class和struct關(guān)鍵字定義。C++是兼容C的,因此struct依舊可以作為結(jié)構(gòu)體使用。
C++和C語言的struct的區(qū)別有哪些?C++的struct用法包含C的struct的用法,也就是說C++在C的struct基礎(chǔ)上增加了一些東西,讓其從結(jié)構(gòu)體變成了類。
第一個(gè)點(diǎn),C++中的struct不僅可以定義成員變量,還可以定義成員函數(shù)。
第二點(diǎn),C++中struct聲明的是類名(用C語言寫結(jié)構(gòu)體可以看做結(jié)構(gòu)體名),在定義變量時(shí),可以不用加struct,可以直接用類名做變量類型。
第三點(diǎn),C++中struct中的成員函數(shù)和成員變量有公有和私有之分,公有就是可以在外部進(jìn)行調(diào)用,私有只能在自己的類域中使用。(struct和class聲明的類被{}圈定的范圍就是類域)
class和struct的定義類的方法相同,都是由于定義類的關(guān)鍵字。在定義上的不同點(diǎn)是struct可以作為結(jié)構(gòu)體使用,class不行。class定義的類成員和函數(shù),在默認(rèn)情況下都是私有的;struct定義的類成員和函數(shù),在默認(rèn)情況下都是公有的。
class和struct在繼承和模板參數(shù)列表位置上也不一樣,在后續(xù)會進(jìn)行講解。
public和private被稱為訪問限定符,public為公有權(quán)限,private為私有權(quán)限。
class Stack
{public://公有
void StackInit()
{arr = nullptr;
capacity = num = 0;
}
private://私有
DataType* arr;
int capacity;
int num;
};
int main()
{Stack st;
st.StackInit();
//st.capacity = 10;//錯(cuò)誤操作,無法訪問私有變量
return 0;
}
在類中,類中的內(nèi)容被稱為類的成員;類中的函數(shù)被稱為成員函數(shù)或者類的方法;類中的變量被稱為成員變量或者成員屬性。
三、類的定義類的定義大致分為兩種,一種是類的成員聲明和定義都放在類中,另一種是類的聲明放在.h文件中,類的定義放在.cpp文件中。
1.聲明和定義放類中:成員函數(shù)在類中定義可能會被編譯器當(dāng)成內(nèi)聯(lián)函數(shù)進(jìn)行處理,也就是調(diào)用該函數(shù)時(shí),不會開辟函數(shù)棧幀,而是會直接在調(diào)用處展開。
#include#includeclass Student
{public:
void Init(const char* name, int age, int score)
{strcpy(_name, name);
_age = age;
_score = score;
}
void Print()
{printf("%s %d %d", _name, _age, _score);
}
private:
char _name[20];
int _age;
int _score;
};
int main()
{Student s;
s.Init("張三", 20, 88);
s.Print();
return 0;
}
2.聲明放在.h文件、定義放在.cpp文件:在函數(shù)成員定義時(shí),需要在函數(shù)成員名前面加作用域限定符,作用域?yàn)樵擃惖念愑?。一般情況下,建議使用第二種方法。
//.h文件
class Student
{public:
void Init(const char* name, int age, int score);
void Print();
private:
char _name[20];
int _age;
int _score;
};
//.cpp文件
void Student::Init(const char* name, int age, int score)
{strcpy(_name, name);
_age = age;
_score = score;
}
void Student::Print()
{printf("%s %d %d", _name, _age, _score);
}
int main()
{Student s;
s.Init("張三", 20, 88);
s.Print();
return 0;
}
為什么要加作用域限定符?因?yàn)槌蓡T函數(shù)都在類域中,出了類域是沒法直接找到的。當(dāng)函數(shù)前面加了作用域限定范圍,就相當(dāng)于可以直接找到該函數(shù)了,換句話講,加完之后,相當(dāng)于你在你家直接操控公司的電腦,那你在使用該電腦的時(shí)候,所有東西也只會在這個(gè)電腦里產(chǎn)生和銷毀。
四、類的實(shí)例化類的實(shí)例化就是用類類型去創(chuàng)建此類對象的過程。
在這里講講類和對象的關(guān)系,類相當(dāng)于一個(gè)模板。比如你要?jiǎng)?chuàng)造一輛車,首先得有車的設(shè)計(jì)圖紙。通過設(shè)計(jì)圖紙才能高效的制造車,一張圖紙就可以批量生成這種型號的車。類就是生產(chǎn)圖紙,對象就是車,通過類可以批量創(chuàng)建對象。
類是不占內(nèi)存的,而對象占內(nèi)存。就像圖紙上畫的東西沒有真正實(shí)現(xiàn)出來,只存在紙上,不額外占空間。生成出來的車子是真實(shí)存在的,占用物理空間。
類對象既然是類的實(shí)例化,那同樣擁有成員函數(shù)和成員變量的,那成員如何計(jì)算其大小呢?需不需要計(jì)算成員函數(shù)的大小?成員函數(shù)的大小是不用計(jì)算的,因?yàn)槌蓡T函數(shù)并不在類對象中,而是在代碼段中。為什么要這樣子?如果每一個(gè)同類型的對象都需要擁有一個(gè)屬于自己的函數(shù),對象數(shù)量多,會造成大量的空間浪費(fèi),不如放在外面,供每個(gè)對象需要時(shí)使用。
按照上述說法,計(jì)算類對象大小只需要計(jì)算成員變量的大小,那是直接將成員變量大小相加就是類對象大???不是的,這個(gè)和成員變量的存儲方式有關(guān)。成員變量的內(nèi)存存儲方式和結(jié)構(gòu)體的相同,不明白的可以看看前面的內(nèi)存對齊。那按照結(jié)構(gòu)體內(nèi)存對齊的方式去計(jì)算大小就一定正確嗎?不一樣,有一種特殊情況,就是沒有成員變量,但是這不能代表這個(gè)類對象大小是0,如果類對象大小是0,就表示這個(gè)對象根本沒有開辟空間,沒有地址如何表示這個(gè)對象?因此在沒有成員變量的情況下,類對象大小為1,需要有一個(gè)地址來表示這個(gè)類對象。
#include#includeclass Student
{public:
void Init(const char* name, int age, int score)
{strcpy(_name, name);
_age = age;
_score = score;
}
void Print()
{printf("%s %d %d", _name, _age, _score);
}
private:
char _name[20];
int _age;
int _score;
};
int main()
{Student s1;
Student s2;
s1.Init("張三", 20, 88);
s1.Print();
s2.Init("李四", 21, 89);
s2.Print();
return 0;
}
為了方便講解,使用第一種定義方法。在上面代碼中,可以看到定義了兩個(gè)類變量分別叫s1和s2,在調(diào)用Student類函數(shù)時(shí),明明沒有傳入任何關(guān)于類變量的信息,那成員函數(shù)是如何區(qū)分并使用對應(yīng)的成員變量?也就是說成員函數(shù)如何判斷是使用s1的成員變量還是s2的?這其實(shí)是C++編譯器給每個(gè)非靜態(tài)成員函數(shù)都多加了一個(gè)指針參數(shù),這個(gè)指針指向的就是調(diào)用這個(gè)函數(shù)的類變量。這個(gè)指針參數(shù)名字就是this,類型是類類型加上* const(如:Student * const this),這是為了防止this改變指向?qū)ο?,出現(xiàn)誤操作。
在成員函數(shù)中,使用成員變量_name在實(shí)現(xiàn)上其實(shí)是this->_name。這就是為什么成員函數(shù)可以隨你的心意去實(shí)現(xiàn)相關(guān)操作,就是因?yàn)橛行┠憧床坏降臇|西,在默默守護(hù)你。那this存在哪里呢?this作為形參,一般都是存在于棧里,但是有些編譯器上會將其放在寄存器中。
構(gòu)造函數(shù)是類中特殊的成員函數(shù),其名字和類名相同,是在創(chuàng)建類對象時(shí)由編譯器進(jìn)行自動(dòng)調(diào)用的函數(shù)。這也就是保證該函數(shù)只會在其生命周期內(nèi)被調(diào)用一次。主要作用就是確保對象成員變量都有初始值。
那構(gòu)造函數(shù)存在哪些特點(diǎn)呢?
1.函數(shù)名與類名相同
2.構(gòu)建類對象時(shí),會被編譯器自動(dòng)調(diào)用默認(rèn)構(gòu)造函數(shù)
3.沒有返回值
4.構(gòu)造函數(shù)可以函數(shù)重載,非默認(rèn)構(gòu)造函數(shù)需要自己調(diào)用
默認(rèn)構(gòu)造函數(shù)包括三種:(1)編譯器自動(dòng)生成的構(gòu)造函數(shù)(2)自己定義的沒有函數(shù)參數(shù)的構(gòu)造函數(shù)(3)自己定義的全缺省構(gòu)造函數(shù)
構(gòu)造函數(shù)是默認(rèn)存在的,也就是說,即使程序員沒有在類中寫構(gòu)造函數(shù),編碼器會自己生成一個(gè)。
編譯器自動(dòng)生成的構(gòu)造函數(shù)會具體實(shí)現(xiàn)哪些功能?為什么還要自己去定義構(gòu)造函數(shù)?
先說明第一個(gè)問題,編譯器自動(dòng)生成的構(gòu)造函數(shù)不會對內(nèi)置類型變量進(jìn)行初始化處理(不做處理),對應(yīng)自定義類型變量會調(diào)用它的默認(rèn)構(gòu)造函數(shù)進(jìn)行初始化處理。內(nèi)置類型就是C++提供的數(shù)據(jù)類型,自定義類型就是自己定義的類型,比如上面用class和struct等等自己定義的類型。
第二個(gè)問題:第一個(gè)問題的結(jié)果說明了自動(dòng)生成的構(gòu)造函數(shù)不會對內(nèi)置類型變量進(jìn)行初始化,那么在存在內(nèi)置類型的類中,往往需要對其進(jìn)行初始化,所以需要自己定義構(gòu)造函數(shù)。
#includeclass Date
{public:
Date(int year = 0, int month = 1, int day = 1)//構(gòu)造函數(shù)
{_year = year;
_month = month;
_day = day;
}
void Print()
{printf("%d-%d-%d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
int main()
{Date d1;
d1.Print();
Date d2(2001,1,1);//構(gòu)造函數(shù)傳參方式比較特殊,直接在對象后面加()進(jìn)行
d2.Print();
return 0;
}
在自己定義構(gòu)造函數(shù)的時(shí)候,需要注意以下幾點(diǎn):(1)函數(shù)參數(shù)能用全缺省參數(shù),盡量用全缺省,即使不行最好也是半缺省參數(shù)(最好用缺省參數(shù))(2)構(gòu)造函數(shù)不需要返回值(3)構(gòu)造函數(shù)名字與類名相同
在C++11中對于自動(dòng)生成的構(gòu)造函數(shù)內(nèi)置類型沒有初始化打了個(gè)補(bǔ)丁,可以在成員變量定義時(shí)給默認(rèn)值作為初始化。
析構(gòu)函數(shù)的作用與構(gòu)造函數(shù)相反,構(gòu)造函數(shù)是進(jìn)行初始化,析構(gòu)函數(shù)就是進(jìn)行清理工作。
首先得知道析構(gòu)函數(shù)什么時(shí)候會被調(diào)用?當(dāng)對象生命周期結(jié)束需要銷毀時(shí),會調(diào)用析構(gòu)函數(shù)先對其進(jìn)行清理。那會清理什么?這和構(gòu)造函數(shù)相似,析構(gòu)函數(shù)對于內(nèi)置類型是不做清理的,對自定義類型會調(diào)用其析構(gòu)函數(shù)做清理。也就是說對于內(nèi)置類型析構(gòu)函數(shù)是不做任何操作的。如果說在一個(gè)管理?xiàng)5念悾镁幾g器自動(dòng)生成的析構(gòu)函數(shù),是會造成資源泄露的。舉個(gè)例子,棧在使用時(shí)不可避免需要開辟空間,空間不進(jìn)行釋放會有什么后果應(yīng)該都清楚(不清楚的可以看看前面的動(dòng)態(tài)內(nèi)存管理),因此在有申請資源時(shí),最好寫析構(gòu)函數(shù)。
這里可能會有人覺得為什么析構(gòu)函數(shù)和構(gòu)造函數(shù)都沒有對內(nèi)置類型進(jìn)行處理,怎么說呢,構(gòu)造函數(shù)那里是存在問題,但是析構(gòu)函數(shù)不對內(nèi)置類型進(jìn)行處理是考慮到有些情況下,不可以對申請的資源進(jìn)行清理。比如說我申請了一塊空間,但是這個(gè)空間我在外面還需要時(shí)候,不可以在對象銷毀時(shí)進(jìn)行清理。
析構(gòu)函數(shù)的特點(diǎn):
(1)函數(shù)名是"~"加上類名
(2)沒有返回值且沒有參數(shù)
(3)在對象生命周期結(jié)束編譯器會自動(dòng)調(diào)用析構(gòu)函數(shù)
(4)析構(gòu)函數(shù)沒有函數(shù)重載,只能存在一個(gè)析構(gòu)函數(shù)
#includeclass Date
{public:
Date(int year = 0, int month = 1, int day = 1)
{_year = year;
_month = month;
_day = day;
}
~Date()//析構(gòu)函數(shù)
{_year = _month = _day = 0;//沒有清理資源可以不寫,因?yàn)檫@些后面會被銷毀掉
}
void Print()
{printf("%d-%d-%d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
int main()
{Date d1(2001,1,1);
d1.Print();
return 0;
}
九、拷貝構(gòu)造函數(shù)拷貝構(gòu)造函數(shù)只有一個(gè)函數(shù)參數(shù),該參數(shù)類型為類的類型的引用,通常會加個(gè)const修飾。在通過已有對象創(chuàng)建新對象時(shí),編譯器會自動(dòng)調(diào)用拷貝構(gòu)造函數(shù)。
沒有定義拷貝構(gòu)造函數(shù),編譯器會自動(dòng)生成一個(gè)。
拷貝構(gòu)造函數(shù)特點(diǎn):
(1)只有一個(gè)形參,并且形參類型必須為類類型的引用。
(2)是構(gòu)造函數(shù)的重載形式
形參類型為什么必須是類類型的引用?如果只是用類的類型,本質(zhì)上是傳值調(diào)用,函數(shù)的傳值本質(zhì)是將該數(shù)值再拷貝一份臨時(shí)變量傳入函數(shù)中。這就會導(dǎo)致對象調(diào)用拷貝函數(shù)時(shí),拷貝函數(shù)會對對象再次調(diào)用拷貝函數(shù),不斷循環(huán)往復(fù)陷入死循環(huán)。使用引用不會對對象進(jìn)行拷貝,而是取了個(gè)別名,使用別名進(jìn)行操作。通常加const是因?yàn)闄?quán)限問題,const修飾的引用權(quán)限最小,其他數(shù)值都可以傳入。
沒有定義拷貝構(gòu)造函數(shù)時(shí),編譯器會自動(dòng)生成默認(rèn)拷貝構(gòu)造函數(shù)。默認(rèn)拷貝構(gòu)造函數(shù)對內(nèi)置類型變量實(shí)現(xiàn)的是淺拷貝(或者值拷貝),就是對象的內(nèi)存存儲按字節(jié)序方式進(jìn)行拷貝。也就是一個(gè)字節(jié)一個(gè)字節(jié)將對象拷到目標(biāo)對象中。對自定義類型變量是通過調(diào)用它的拷貝構(gòu)造函數(shù)進(jìn)行拷貝。
那什么時(shí)候可以使用默認(rèn)拷貝構(gòu)造函數(shù)?在拷貝內(nèi)容存在指針或者引用時(shí),需要注意實(shí)際含義。舉個(gè)例子,被拷貝的對象s1中有兩個(gè)數(shù)值,一個(gè)為變量a,一個(gè)為指針pa,指針pa指向a變量??截悓ο髎2需要拷貝s1中的a和pa,那s2中的a值會與s1的相等,而s2中的pa卻不是指向s2中的a,而s1中的a。這個(gè)情況和預(yù)期是有出入的,不是我們想要的結(jié)果,這時(shí)候需要自定義拷貝構(gòu)造函數(shù)。
#includeclass Date
{public:
Date(int year = 0, int month = 1, int day = 1)
{_year = year;
_month = month;
_day = day;
}
Date(const Date& d)//拷貝構(gòu)造函數(shù)
{_year = d._year;
_month = d._month;
_day = d._day;
}
~Date()
{_year = _month = _day = 0;
}
void Print()
{printf("%d-%d-%d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
int main()
{Date d1(2001, 1, 1);
d1.Print();
Date d2(d1);
d2.Print();
return 0;
}
十、運(yùn)算符重載函數(shù)為什么需要運(yùn)算符重載函數(shù)?用運(yùn)算符做個(gè)類比,我們在使用運(yùn)算符的時(shí)候,可以實(shí)現(xiàn)變量之間的比較和加減乘除等等操作。但是運(yùn)算符并不能對類對象以上的操作,類對象和類對象不能通過運(yùn)算符直接進(jìn)行運(yùn)算,因此我們需要對運(yùn)算符進(jìn)行重載,使其可以對同類型的類對象進(jìn)行運(yùn)算。
在運(yùn)算符重載中,賦值運(yùn)算符重載函數(shù)在沒有定義時(shí),編譯器會自動(dòng)生成。如果類中沒有進(jìn)行資源管理,可以不用自己實(shí)現(xiàn),否則需要自己定義賦值運(yùn)算符重載函數(shù)。
運(yùn)算符重載函數(shù)特點(diǎn):
(1)“.”、“.*”、“::”、“?:”、"sizeof"這五個(gè)是不可以進(jìn)行運(yùn)算符重載的
(2)不可以改變內(nèi)置類型的運(yùn)算符含義
(3)返回類型 operator+“運(yùn)算符”(形參)
(4)形參會比實(shí)際操作數(shù)少一個(gè),因?yàn)榈谝粋€(gè)操作數(shù)會被作為隱藏的this指針進(jìn)行傳入
日期類的實(shí)現(xiàn):
#includeusing std::ostream;
using std::cout;
using std::cin;
using std::endl;
class Date
{friend ostream& operator<<(ostream& out, const Date d);
public:
int Judge_day(int year, int month);
Date(int year = 0, int month = 1, int day = 1);
bool operator>(Date d);
bool operator==(Date d);
bool operator<(Date d);
bool operator>=(Date d);
bool operator<=(Date d);
bool operator!=(Date d);
Date operator+=(int day);
Date operator+(int day);
Date operator-=(int day);
Date operator-(int day);
Date& operator=(const Date d);
// 前置++
Date & operator++();
// 后置++
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
int operator-(const Date& d);
void Print() const;
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date d)
{out<< d._year<< "-"<< d._month<< "-"<< d._day;
return out;
}
int Date::Judge_day(int year, int month)
{int date[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int day = date[month];
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))
{day += 1;
}
return day;
}
Date::Date(int year, int month, int day)
{_year = year;
_month = month;
_day = day;
if (!(year >= 0
&& month >0 && month< 13
&& day >0 && day<= Judge_day(year, month)))
{printf("日期錯(cuò)誤: %d-%d-%d\n", year, month, day);
}
}
bool Date::operator>(Date d)
{if (this->_year >d._year)
{return true;
}
else if (this->_year == d._year && this->_month >d._month)
{return true;
}
else if (this->_year == d._year && this->_month == d._month && this->_day >d._day)
{return true;
}
else
{return false;
}
}
bool Date::operator==(Date d)
{if (this->_year == d._year && this->_month == d._month && this->_day == d._day)
{return true;
}
return false;
}
bool Date::operator<(Date d)
{return !(*this >d || *this == d);
}
bool Date::operator>=(Date d)
{return !(*this< d);
}
bool Date::operator<=(Date d)
{return !(*this >d);
}
bool Date::operator!=(Date d)
{return !(*this == d);
}
Date Date::operator+=(int day)
{if (day< 0)
{return *this -= (-day);
}
int i = 0;
day += this->_day;
while (i = Judge_day(this->_year, this->_month), day >i)
{day -= i;
this->_month++;
if (this->_month >12)
{ this->_month = 1;
this->_year++;
}
}
this->_day = day;
return *this;
}
Date Date::operator+(int day)
{if (day< 0)
{return *this - (-day);
}
int i = 0;
Date ret(*this);
day += ret._day;
while (i = Judge_day(ret._year, ret._month), day >i)
{day -= i;
ret._month++;
if (ret._month >12)
{ ret._month = 1;
ret._year++;
}
}
ret._day = day;
return ret;
}
Date Date::operator-=(int day)
{if (day< 0)
{return *this += (-day);
}
day = this->_day - day;
while (day<= 0)
{this->_month--;
if (this->_month< 1)
{ this->_month = 12;
this->_year--;
}
day += Judge_day(this->_year, this->_month);
}
this->_day = day;
return *this;
}
Date Date::operator-(int day)
{if (day< 0)
{return *this + (-day);
}
Date ret(*this);
day = ret._day - day;
while (day<= 0)
{ret._month--;
if (ret._month< 1)
{ ret._month = 12;
ret._year--;
}
day += Judge_day(ret._year, ret._month);
}
ret._day = day;
return ret;
}
Date& Date::operator=(const Date d)
{this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
return *this;
}
void Date::Print() const
{printf("%d-%d-%d\n", _year, _month, _day);
}
// 前置++
Date& Date::operator++()
{*this += 1;
return *this;
}
// 后置++
Date Date::operator++(int)
{Date ret(*this);
* this += 1;
return ret;
}
// 后置--
Date Date::operator--(int)
{Date ret(*this);
*this -= 1;
return ret;
}
// 前置--
Date& Date::operator--()
{*this -= 1;
return *this;
}
int Date::operator-(const Date& d)
{Date ret(d);
int i = 0;
int day = 0;
while (ret != *this)
{ret += 1;
day++;
}
return day;
}
int main()
{return 0;
}
++和–分前置和后置,如果沒有用于區(qū)分的對象,無法方便是前置還是后置。因此前置被定為默認(rèn)情況,如果要進(jìn)行后置++或–的函數(shù)重載,需要在聲明時(shí),在參數(shù)列表處加上int,作為后置的標(biāo)志,不需要變量名(因?yàn)楹瘮?shù)不會接收這個(gè)值,只是用來判斷)。
如有不當(dāng)之處,請各位看客指正。
你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動(dòng)首月15元起,快前往官網(wǎng)查看詳情吧