文章目錄
前言
學習C++,首先要了解的便是它和C語言的區別所在,很多的新手可能和我一般,學校是先開設了C語言,之後纔開設的C++課程,因此學習C++之前,我們要明白:
- C語言是面向過程的,更多的關注的是過程,分析出求解問題的具體步驟,通過函數調用來逐步解決這個問題。
- C++是基於面向對象的,首先關注的是對象,將一件事情拆分成不同的對象,靠對象之間的交互來完成。
以我自己的理解來看,就是將一個問題所涉及到的所有的東西,元素,函數等全部打包放在一個地方,它內部就能夠解決掉它自身的很多問題,如果使用的時候只需要調用它就完全可以了。
類與對象(上)
01 .類的引入及定義
兩種定義類1.struct 類名 { }
2. class 類名 {}
,這裏我們重點介紹的是後面一種。
class Student
{
public: //公有的成員在類外可見
//成員函數
void display()
{}
void setId(int id)
{
_id = id;
}
void setNumber(int number)
{
_number = number;
}
private: //私有的成員在類外不可見
//成員變量, 屬性
int _number;
int _id;
protected: // 保護的成員在類外不可見
char name[10];
char gender[10];
char major[10];
};
【面試題】C++中的struct和class的區別是什麼呢?
C++需要兼容C語言,所以C++中的struct可以當成結構體去進行使用,也可以用來定義類;它和class定義類是一樣得,區別在於struct的成員默認訪問方式是public,class的成員默認訪問方式是private。
02 .類的訪問限定符及封裝
上面我們所看到的public和protected,private都是類的訪問限定符
對於訪問限定符的說明:
- public修飾的成員在類外可以直接被訪問
- protected和private所修飾的成員在類外不能直接被訪問(protected和private是類似的)
- 訪問權限作用域從該訪問限定符出現的位置開始直到下一個訪問限定符出現爲止
- class的默認訪問權限爲private,struct的默認訪問權限爲public
封裝:用類將成員和函數將合在一起,使得對象更加的完善,通過訪問權限選擇性的將接口提供給外部用戶來進行使用,實質上是一種管理。
03 .類的作用域和類的實例化
如果在類體外定義成員的話,需要使用::
作用域解析符指明成員屬於那個類域
class B
{
private:
int _a;
void fun() {
cout << "class B fun()" << endl;
}
void fun2();
};
void B::fun2() {//類域之中的fun2的定義
cout << "B::fun2()" << endl;
}
類的實例化:
- 創建一個類類型變量的過程稱之爲類的實例化
- 相當於類只是一張設計圖,通過類創建出的變量纔是將其變換成一個真正的實物
- 未實例化前的類不佔據空間
04 .類對象模型
類的大小也遵循內存對齊的規則:並且在類中嵌套類的時候,如果此類沒有創建變量,不計算其內存大小
class G
{
char _c; //1
double _d; // 16
int _a; // 20
char _c2; //21
//24
//嵌套類本身遵循內存對齊的原則,計算大小: H: 24
class H
{
double _d; //8
char _c; //9
int _a; //16
char _c1; //17
//24
};
H _h;//未創建變量時,不計算內存大小
};
05 .this指針
this指針:
- this指針類型爲 類類型
- this指針只存在於成員函數之中,始終指向當前所調用的這個函數,是不能夠改變的,是一個形參,不能夠算作成員對象
- this始終作爲成員函數第一個形參,編譯器會自動傳遞,不需要顯式定義此函數
- this不是類的成員,只是一個函數形參,一般存在於棧中,不會做優化
this解引用:
- this未進行解引用,可能是因爲內部沒有使用this指針所指地址的變量,因此當作一個參數進行傳參,不會發生未定義行爲
- 如果this所指向的變量需要被改變或使用,這個時候this是一個指針被解引用,若此時this爲空會導致異常
類與對象(中)
01.類的6個默認成員函數
如果一個類中什麼成員都沒有,簡稱爲空類。空類中並不是什麼都沒有,任何一個類在外面不寫的情況下,都會自動生成下面的6個默認成員函數。
0.1 構造函數
構造函數是特殊的成員函數,需要注意的是,構造函數雖然名爲構造,但其主要任務並不是開空間創建對象,而是初始化對象
- 函數名與類名相同,無返回值,對象實例化時編譯器會自動調用所對應的構造函數,構造函數可以重載
- 如果類中存在自定義成員,則構造函數會自動調用自定義成員的默認構造完成初始化,如果自定義成員沒有默認構造則會產生編譯錯誤
- 默認構造只能夠存在一個,在聲明一個函數的時候,是不會調用無參構造的
編譯器默認生成的構造函數
Date()
{
}
顯示定義的無參構造
Date()
{
_year = y;
_month = m;
_day = d;
}
全缺省的構造函數
Date(int y = 2020, int m = 5, int d = 20)
{
_year = y;
_month = m;
_day = d;
}
重載構造函數
Date(float f)
{
}
explicit關鍵字
構造函數對於單個參數的構造函數還具有類型轉換的作用(調用構造創建一個匿名對象,通過匿名對象來給所需要創建的對象進行賦值或拷貝構造),而爲了避免這種隱式轉換,可以使用explicit
關鍵字來修飾構造函數,將會禁止單參構造函數的隱式轉換。
構造函數初始化列表
class Time {
public:
Time(int a = 1)
:_a(a)
{
cout << "Time(int)" << endl;
}
private://這裏是成員變量聲明的地方,而引用和const類型變量,這兩者定義時必須初始化
int _a;
};
- 類中必須放在初始化列表中的有引用成員變量,const成員變量,自定義類型成員(沒有默認構造函數),其他成員可以不進行顯示初始化
- 每個成員變量在初始化列表中只能夠出現一次,因爲初始化列表是對象的成員變量定義的地方
- 成員變量在初始化列表中初始化的順序,必須和聲明順序一致,與其在初始化列表中的順序無關(最好保持初始化列表和聲明順序一致)
0.2 析構函數
因爲類的一些資源並不在類中,因此在對象生命週期結束的時候需要對資源進行清理和釋放(時清理資源不是銷燬對象),則會自動調用析構函數,完成資源清理的工作。
- 析構函數名 在類名前加上取反符號
~
,沒有參數也沒有返回值 - 一個類有且只有一個析構函數,若未進行顯式定義,系統會自動生成默認的析構函數
- 在對象生命週期結束時,編譯系統會自動調用析構函數
- 如果沒有資源需要清理,可以不用顯式寫析構函數,直接使用編譯器默認生成的析構函數即可
class A {
public:
~A()
{
cout << "~A()" << endl;
}
int _a;
};
- 全局對象先於局部對象進行構造
- 靜態對象先於普通對象進行構造
0.3 拷貝構造函數
拷貝構造是構造函數的一個重載形式,它是用一個已經存在的對象去創建一個新的對象,創建的新對象和當前所存在的對象內容完全相同;
class Date {
public:
//構造函數
Date(int y = 1, int m = 1, int d = 1) {
_year = y;
_month = m;
_day = d;
}
//拷貝構造函數
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
void Test() {
Date d;
Date d2(2020, 4, 1);
Date& rd = d;
Date copy1(d);
Date copy2(Date(2020, 5, 20));//優化,直接調用構造函數創建copy2
//不優化:調用構造創建匿名對象,+ 拷貝構造
}
若沒有顯式定義拷貝構造函數的話,系統會默認生成拷貝構造函數,但智慧按照內存存儲的字節序完成淺拷貝,拷貝對象模型中的內容,不會拷貝資源,因此如果需要拷貝資源一定要顯式定義拷貝構造函數。
拷貝構造函數的參數只有一個(一般用const修飾),必須使用引用傳參,如果使用傳值方式的話會引發無窮次的遞歸調用。
0.4運算符重載
運算符重載是具有特殊函數名的函數,也具備其返回值的類型,函數名,參數列表,其返回值類型和參數列表與普通的函數類似,其作用旨在增強代碼的可讀性,定義和使用與普通函數一致。
class Date {
public:
bool IsEqual(const Date& d) {
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
bool operator==(const Date& d) {//底層接口 bool operator==(Date* const this, const Date& d)
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
private:
int _year;
int _month;
int _day;
};
//輸出運算符重載函數
ostream& operator<<(ostream& _cout, const Date& date) {
_cout << date._year << " " << date._month << " " << date._day << endl;
return _cout;
}
- 重載操作符必須有一個類類型或枚舉類型的操作數,不能通過連接其他符號來創建新的操作符
- 作爲類成員的重載函數時,成員函數的第一個操作符默認爲形參this
.* ,::,sizeof,?:, .
以上5個勻速那副不能重載,筆試選擇題中較爲熱門
賦值運算符重載:
class Date
{
public:
Date(int y = 1, int m = 1, int d = 1)
{
_year = y;
_month = m;
_day = d;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
cout << "Date(const Date& d)" << endl;
}
//優化 ,避免自己給自己賦值
Date& operator=(const Date& d2)
{
//判斷是否給自己賦值
if (this != &d2)
{
_year = d2._year;
_month = d2._month;
_day = d2._day;
}
cout << "operator=(const Date& d2)" << endl;
//返回當前調用此函數的對象本身
return *this;
}
//private:
int _year;
int _month;
int _day;
};
void Test() {
Date d(2020, 5, 22);
Date d2(2019, 1, 1);
//如果對象都存在,調用賦值運算符重載函數,如果左邊對象不存在,則調用拷貝構造
d2 = d;
d2.operator==(d);// 同上等價
d2 = d2;
Date d3(2018, 10, 1);
//連續賦值:從右向左賦值
d = d2 = decltype;
d.operator=(d2.operator=(d3));//同上面等價
Date d4 = d3;//因爲d4不存在,則調用拷貝構造,用d3創建d4對象
}
- 賦值運算符重載函數
d=d2
,修改已經存在的對象內容,不是去創建新的對象 - 如果當前類中有資源存在,必須顯式定義賦值運算符重載函數完成深拷貝,否則會採用編譯器默認生成的字節拷貝,只能夠淺拷貝
- 如所需賦值的對象存在,則直接調用賦值運算符重載函數,如所需賦值對象不存在,則直接調用拷貝構造
0.5 const成員函數
將const修飾的類成員函數稱之爲const函數,它實際修飾的是該成員函數的隱式this指針,表明在該成員函數中不能夠對類的任何成員進行修改。
const函數和非const函數
void printD() // 等價於 printD(Date* const this)
{
cout << _year << " " << _month << " " << _day << endl;
//可以修改內容
this->_year = 100;
//可以調用const成員函數
fun();
}
void printD() const //等價於 printD(const Date* const this)
{
cout << _year << " " << _month << " " << _day << endl;
//不能修改內容 this->_year = 100;這是錯誤的
//不能調用非const成員函數,讀寫的權限不能被放大 fun()
//不能進行自加操作 ++*this;
}
void fun()const
{
}
0.6取地址
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
這兩個運算符一般不需要重載使用編譯器默認生成的即可,如果想要讓別人獲取到指定的內容的話,才需要進行重載。
02.六大成員函數總結對比
類與對象(下)
01.靜態成員
//int cnt = 0; //定義全局變量的話,安全性較低,容易篡改
class Date
{
public:
Date(int year = 2020, int month = 12, int day = 20)
:_year(year)
, _month(month)
, _day(day)
{
++cnt;
cout << "Date(int ,int ,int)" << endl;
}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
++cnt;
cout << "Date(const Date&)" << endl;
}
//靜態成員函數:函數內部沒有this指針
static int getCount()
{
}
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
getCount();
cout << cnt << endl;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
public:
static int cnt;
};
//靜態成員必須在類外初始化
int Date::cnt = 0;
Date fun(Date d) //拷貝構造
{
cout << &d.cnt << endl;
return d;
}
void Test()
{
Date d;//構造
Date d2 = fun(d);//拷貝函數 fun:進行優化,只有兩次拷貝構造,傳參創建d2
//靜態成員變量/靜態成員函數訪問方式:
// 1. 對象訪問
cout << d.getCount() << endl;
cout << d2.getCount() << endl;
cout << &d.cnt << endl;
cout << &d2.cnt << endl;
// 2. 類名 + 作用域限定符
cout << &Date::cnt << endl;
cout << Date::cnt << endl;
cout << Date::getCount() << endl;
//普通成員只能通過對象訪問,不能通過類名訪問
d.Display();
//Date::Display(); //不支持
}
對於C++11的初始化方式,相對於給一個缺省值
private:
int _year = 1;
int _month = 1;
int _day = 1;
它將是初始化時候的最後一個候選,先優先前面的進行選擇
如果有缺省的構造函數,那麼就先優先使用缺省構造函數的數據
02.友元函數
class Date
{
public:
friend ostream& operator<<(ostream& outputS, Date& d);
friend istream& operator >> (istream& inputS, Date& d);
friend class B;
}
class B
{
public:
//disPlay, fun, fun1都爲Date類的的友元函數
void disPlay(const Date& d)
{
cout << d._year << d._month << d._day << endl;
}
void fun(const Date& d)
{
cout << d._year << d._month << d._day << endl;
}
void fun1(const Date& d)
{
cout << d._year << d._month << d._day << endl;
}
};
輸出流和輸入流
ostream& operator<<(ostream& outputS, Date& d)
{
outputS << d._year << "-" << d._month << "-" << d._day << endl;
return outputS;
}
istream& operator >> (istream& inputS, Date& d)
{
inputS >> d._year >> d._month >> d._day;
return inputS;
}
03.內部類
enum Color
{
BLACK,
WHITE
};
class C
{
public:
class D
{
public:
void fun(const C& c)
{
//可以通過外部類對象訪問外部類的私有成員
cout << c._color << endl;
cout << c._c << endl;
cout << c._sc << endl;
cout << C::_sc << endl;
//可以直接訪問外部類的static成員
cout << _sc << endl;
}
private:
int _d;
};
private:
int _c;
static int _sc;
Color _color;
//內部類可以在類的任何地方定義
class E
{
private:
int _e;
};
};
實踐 日期類的實現
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)//構造函數
{
//在進行一個日期賦值前需要判斷日期是否合法
if (year > 0 && month > 0 && month < 13
&& day > 0 && day <= getMonthDay(year, month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "日期不合法: " << year << "-" << month << "-" << day << endl;
cout << "重置爲默認值: 2000-1-1" << endl;
_year = 2000;
_month = 1;
_day = 1;
}
}
int getMonthDay(int year, int month)//獲得當前月份的天數
{
static int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int day = days[month];
//如果是2月且爲閏年,+1
if (month == 2
&& (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)))
++day;
return day;
}
// a += b
Date& operator+=(int day) //加等操作
{
// a += -b --> a -= b
if (day < 0)
return *this -= -day;
//2020.5.1 + 20 --> 2020.5.21
//2020.5.21 + 20 --> 2020.5.41 --> 進位 --> -31 -->月份進位 --> 2020.6.10
//2020.12.6 + 90 --> 2020.12.96 --> 進位 --> -31 -->月份進位 --> 2020.13.65 -->年進位
// --> 2021.1.65--> --> 進位 --> -31 -->月份進位 --> 2021.2.34 --> --> 進位 --> -28 -->月份進位 --> 2021.3.6
_day += day;
//判斷日期是否溢出,溢出需要進位
while (_day > getMonthDay(_year, _month))
{
//減去當月的天數,月份進位
_day -= getMonthDay(_year, _month);
++_month;
//判斷月份是否溢出
if (_month == 13)
{
//進位到下一年的1月
_month = 1;
_year++;
}
}
return *this;
}
//前置++: ++d: 首先++,返回++之後的值
Date& operator++()
{
return *this += 1;
//return (*this).operator+=(1);
//return *this;
}
//後置++: d++: 本身++, 返回++之前的值
//前置++, 後置++都爲單目運算符
//如果爲成員函數,則本質上不需要顯式傳參,編譯器會自動傳入this指針
//int: 形參不是一個真正的參數,只是一個標記參數,編譯器看到這樣的定義,通過語法樹搜索,可以解釋爲後置++
Date operator++(int)
{
//保存++之前的值
Date ret(*this);
//++
*this += 1;
//返回++之前的值
return ret;
}
Date& operator-=(int day)//減等操作
{
if (day < 0)
return *this += -day;
_day -= day;
//判斷_day是否爲負值或者0, 退位
//2020.5.24 - 30 --> 2020.5.-6 --> 月份退位 --> +30 --> 2020.4.24
while(_day <= 0)
{
//月份退位
--_month;
//月份是否爲負值或者0
if (_month == 0)
{
_month = 12;
//年份退位
--_year;
}
_day += getMonthDay(_year, _month);
}
return *this;
}
Date& operator--()//前置減減操作
{
return *this -= 1;
}
Date operator--(int)//後置減減操作
{
Date ret = *this;
//Date ret(*this);
*this -= 1;
return ret;
}
// +, -運算符:不能修改操作數的內容
// c = a + b
Date operator+(int day)//加運算符
{
Date ret = *this;
ret += day;
return ret;
}
Date operator-(int day)//減運算符
{
Date ret = *this;
ret -= day;
return ret;
}
bool operator==(const Date& date)//比較相等
{
return _year == date._year
&& _month == date._month
&&_day == date._day;
}
bool operator>(const Date& date)//大於
{
if (_year > date._year)
return true;
else if (_year == date._year)
{
if (_month > date._month)
return true;
else if (_month == date._month)
{
if (_day > date._day)
return true;
}
}
return false;
}
bool operator>=(const Date& date)//大於等於
{
return (*this > date) || (*this == date);
}
bool operator<(const Date& date)
{
return !(*this >= date);
}
bool operator<=(const Date& date)
{
return !(*this > date);
}
bool operator!=(const Date& date)
{
return !(*this == date);
}
void printD() // 等價於 printD(Date* const this)
{
cout << _year << " " << _month << " " << _day << endl;
//可以修改內容
//this->_year = 100;
//可以調用const成員函數
fun();
}
//const成員函數中的const修飾的爲第一個參數,即this指針
//const成員函數內部補能修改成員變量的值
void printD() const //等價於 printD(const Date* const this)
{
cout << _year << " " << _month << " " << _day << endl;
//不能修改內容
//this->_year = 100;
//不能調用非const成員函數,讀寫的權限不能被放大
//++*this;
}
void fun()const
{
}
int operator-(Date& date)//兩個日期進行相減
{
Date d1(*this);
Date d2(date);
//d1 - d2
int num = 0;
if (d1 > d2)
{
while (d1 > d2)
{
--d1;
++num;
}
return num;
}
else
{
//d1 <= d2
while (d1 < d2)
{
++d1;
++num;
}
return -num;
}
}
/*Date operator-(Date& date)
{
}*/
//取地址運算符重載函數: operator&
//一般不需要顯示定義,直接用默認即可
Date* operator&()
{
//return (Date*) 0x1234;
return this;
}
const Date* operator&() const
{
//return nullptr;
return this;
}
//private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& date)
{
_cout << date._year << " " << date._month << " " << date._day << endl;
return _cout;
}