目錄
面向過程和面向對象的初步認識
類的引入
類的定義
類的訪問限定符及封裝
訪問限定符
封裝
類的作用域
類的實例化
類對象模型
如何計算類對象大小
結構體內存對齊規則
this指針
this指針的引出
this指針的特性
類的6個默認成員函數
構造函數
概念
特性
析構函數
概念
特性
拷貝構造函數
概念
特性
賦值運算符重載
運算符重載
賦值運算符重載
前置++重載和后置++重載
const成員
取地址及const取地址操作符重載
友元
友元函數
友元類
面向過程和面向對象的初步認識
C語言是面向過程的,其核心在于過程,通常分析出問題的步驟然后進行調用函數進行解決,過程中的每一步都需要進行手動操作實現。
C++是面向對象的,也就是關注的是對象并不關注對象是怎么實現的對應操作的。
以洗衣服為例子,對于面向過程而言整個過程就是用盆子接水->放衣服->放洗衣粉->手搓->倒水->接水->放洗衣粉->...
而對于面向對象而言,相對就沒有這么繁瑣了,對象就是人、洗衣機、衣服、洗衣粉,我將衣服放入洗衣機中放入洗衣粉,然后打開洗衣機就可以了。至于洗衣機是怎么洗衣服的我并不需要關注。
類的引入
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++中以類class實現。
類的定義
class className{// 類體:由成員函數和成員變量組成}; ? // 一定要注意后面的分號
成員函數。
class student
{
public:void print(){cout << name << " " << scores; }
private:string name;double scores;
};
class student
{
public:void print();
private:string name;double scores;
};
?在.cpp文件中需要訪問類域:
void student::print()
{cout << name << " " << scores << endl ;
}
類的訪問限定符及封裝
訪問限定符
C++實現封裝的方式:用類將對象的屬性與方法結合在一塊,讓對象更加完善,通過訪問權限選
封裝
面向對象的三大特性: 封裝、繼承、多態 。在類和對象階段,主要是研究類的封裝特性,那什么是封裝呢?封裝:將數據和操作數據的方法進行有機結合,隱藏對象的屬性和實現細節,僅對外公開接口來和對象進行交互。封裝本質上是一種管理,讓用戶更方便使用類 。比如:對于電腦這樣一個復雜的設備,提供給用戶的就只有開關機鍵、通過鍵盤輸入,顯示器, USB 插孔等,讓用戶和計算機進行交互,完成日常事務。但實際上電腦真正工作的卻是 CPU 、顯卡、內存等一些硬件元件。對于計算機使用者而言,不用關心內部核心部件,比如主板上線路是如何布局的, CPU 內部是如何設計的等,用戶只需要知道,怎么開機、怎么通過鍵盤和鼠標與計算機進行交互即可。因此 計算機廠商在出廠時,在外部套上殼子,將內部實現細節隱藏起來,僅僅對外提供開關機、鼠標以及鍵盤插孔等,讓用戶可以與計算機進行交互即可 。

類的作用域
類的實例化
用類類型創建對象的過程,稱為類的實例化1. 類是對對象進行描述的 ,是一個 模型 一樣的東西,限定了類有哪些成員,定義出一個類 并沒有分配實際的內存空間 來存儲它;比如:入學時填寫的學生信息表,表格就可以看成是一個類,來描述具體學生信息。2. 一個類可以實例化出多個對象, 實例化出的對象 占用實際的物理空間,存儲類成員變量Person 類是沒有空間的,只有 Person 類實例化出的對象才有具體的年齡。3. 做個比方。 類實例化出對象就像現實中使用建筑設計圖建造出房子,類就像是設計圖 ,只設計出需要什么東西,但是并沒有實體的建筑存在,同樣類也只是一個設計,實例化出的對象才能實際存儲數據,占用物理空間。總之,類跟struct一樣都看成是自定義類型即可;
類對象模型
如何計算類對象大小
先定義一個簡單的類;
class A
{
public:
void PrintA()
{cout<<_a<<endl;
}
private:
char _a;
};
類中既有成員函數又有成員變量,那么如何計算類的大小呢?
類的內存大小是指的類成員變量的所占的內存大小,成員函數并不計入在內;其計算方式與結構體的計算大小方式相同;不同的是如果一個類是空的那么他的大小不是0而是1;
結構體內存對齊規則
this指針
this指針的引出
先來定義一個日期類;
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;
}
this指針的特性
class date
{
public:void print(){cout << _year << "-" << _month << "-"<<_day; }
private: int _year;int _month;int _day;
};
class date
{
public:void print(){cout << this->_year << "-" <<this-> _month << "-"<<this->_day; }
private: int _year;int _month;int _day;
};
二者等同。
類的6個默認成員函數

構造函數
概念
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;d1.Init(2022, 7, 5);d1.Print();Date d2;d2.Init(2022, 7, 6);d2.Print();return 0;
}
特性
class Date
{
public:// 1.無參構造函數Date(){}// 2.帶參構造函數Date(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;};void TestDate(){Date d1; // 調用無參構造函數Date d2(2015, 1, 1); // 調用帶參的構造函數// 注意:如果通過無參構造函數創建對象時,對象后面不用跟括號,否則就成了函數聲明// 以下代碼的函數:聲明了d3函數,該函數無參,返回一個日期類型的對象// warning C4930: “Date d3(void)”: 未調用原型函數(是否是有意用變量定義的?)Date d3();}
class Date{public:/*// 如果用戶顯式定義了構造函數,編譯器將不再生成Date(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類中構造函數屏蔽后,代碼可以通過編譯,因為編譯器生成了一個無參的默認構造函數// 將Date類中構造函數放開,代碼編譯失敗,因為一旦顯式定義任何構造函數,編譯器將不再生成// 無參構造函數,放開后報錯:error C2512: “Date”: 沒有合適的默認構造函數可用Date d1;return 0;}
class Time
{
public:Time() {cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}private: int _hour; int _minute; int _second;
};
class Date
{
private:// 基本類型(內置類型)int _year;int _month;int _day;// 自定義類型Time _t;
};
int main()
{Date d;return 0;
}
class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本類型(內置類型)int _year = 1970;int _month = 1;int _day = 1;// 自定義類型Time _t;
};
int main()
{Date d;return 0;
}
class Date
{
public:Date(){_year = 1900;_month = 1;_day = 1;}Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
// 以下測試函數能通過編譯嗎?
void Test()
{Date d1;
}
析構函數
概念
通過前面構造函數的學習,我們知道一個對象是怎么來的,那一個對象又是怎么沒呢的?析構函數:與構造函數功能相反,析構函數不是完成對對象本身的銷毀,局部對象銷毀工作是由編譯器完成的。而 對象在銷毀時會自動調用析構函數,完成對象中資源的清理工作
特性
ypedef int DataType;
class Stack
{
public:Stack(size_t capacity = 3){_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申請空間失敗!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){// CheckCapacity();_array[_size] = data;_size++;}// 其他方法...~Stack(){if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}
private:DataType* _array;int _capacity;int _size;
};
void TestStack()
{Stack s;s.Push(1);s.Push(2);
}
class Time
{
public:~Time(){cout << "~Time()" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本類型(內置類型)int _year = 1970;int _month = 1;int _day = 1;// 自定義類型Time _t;
};
int main()
{Date d;return 0;
}
// 程序運行結束后輸出:~Time()
// 在main方法中根本沒有直接創建Time類的對象,為什么最后會調用Time類的析構函數?
// 因為:main方法中創建了Date對象d,而d中包含4個成員變量,其中_year, _month,
_day三個是
// 內置類型成員,銷毀時不需要資源清理,最后系統直接將其內存回收即可;而_t是Time類對
象,所以在d銷毀時,要將其內部包含的Time類的_t對象銷毀,所以要調用Time類的析構函數。但是:
main函數
// 中不能直接調用Time類的析構函數,實際要釋放的是Date類對象,所以編譯器會調用Date
類的析構函
// 數,而Date沒有顯式提供,則編譯器會給Date類生成一個默認的析構函數,目的是在其內部
調用Time
// 類的析構函數,即當Date對象銷毀時,要保證其內部每個自定義對象都可以正確銷毀
// main函數中并沒有直接調用Time類析構函數,而是顯式調用編譯器為Date類生成的默認析
構函數
// 注意:創建哪個類的對象則調用該類的析構函數,銷毀那個類的對象則調用該類的析構函數
拷貝構造函數
概念
拷貝構造函數 : 只有單個形參 ,該形參是對本 類類型對象的引用 ( 一般常用 const 修飾 ) ,在用 已存在的類類型對象創建新對象時由編譯器自動調用
特性

class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time(const Time& t){_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time::Time(const Time&)" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本類型(內置類型)int _year = 1970;int _month = 1;int _day = 1;// 自定義類型Time _t;
};
int main()
{Date d1;// 用已經存在的d1拷貝構造d2,此處會調用Date類的拷貝構造函數// 但Date類并沒有顯式定義拷貝構造函數,則編譯器會給Date類生成一個默認的拷貝構//造函數Date d2(d1); return 0;
}
// 這里會發現下面的程序會崩潰掉?這里就需要我們以后講的深拷貝去解決。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申請空間失敗");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0;
}
class Date
{
public:Date(int year, int minute, int day){cout << "Date(int,int,int):" << this << endl;}Date(const Date& d){cout << "Date(const Date& d):" << this << endl;}~Date(){cout << "~Date():" << this << endl;}
private:int _year;int _month;int _day;
};
Date Test(Date d)
{Date temp(d);return temp;
}
int main()
{Date d1(2022, 1, 13);Test(d1);return 0;
}
賦值運算符重載
運算符重載
C++ 為了增強代碼的可讀性引入了運算符重載 , 運算符重載是具有特殊函數名的函數 ,也具有其返回值類型,函數名字以及參數列表,其返回值類型與參數列表與普通的函數類似。函數名字為:關鍵字 operator 后面接需要重載的運算符符號 。函數原型: 返回值類型 ?operator 操作符 ( 參數列表 )注意:不能通過連接其他符號來創建新的操作符:比如 operator@重載操作符必須有一個類類型參數用于內置類型的運算符,其含義不能改變,例如:內置的整型 + ,不 能改變其含義作為類成員函數重載時,其形參看起來比操作數數目少 1 ,因為成員函數的第一個參數為隱藏的 this.* :: sizeof ?: . 注意以上 5 個運算符不能重載。這個經常在筆試選擇題中出現。
// 全局的operator==
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private:int _year;int _month;int _day;
};
// 這里會發現運算符重載成全局的就需要成員變量是公有的,那么問題來了,封裝性如何保證?
// 這里其實可以用我們后面學習的友元解決,或者干脆重載成成員函數。
bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}
void Test()
{Date d1(2018, 9, 26);Date d2(2018, 9, 27);cout << (d1 == d2) << endl;
}
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// bool operator==(Date* this, const Date& d2)// 這里需要注意的是,左操作數是this,指向調用函數的對象bool operator==(const Date& d2){return _year == d2._year&& _month == d2._month&& _day == d2._day;}
private:int _year;int _month;int _day;
};
賦值運算符重載
class Date
{
public :Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date (const Date& d){_year = d._year;_month = d._month;_day = d._day;}Date& operator=(const Date& d){if(this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:
int _year ;int _month ;int _day ;
};
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};
// 賦值運算符重載成全局函數,注意重載成全局函數時沒有this指針了,需要給兩個參數
Date& operator=(Date& left, const Date& right)
{if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;
}
// 編譯失敗:
// error C2801: “operator =”必須是非靜態成員
class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time& operator=(const Time& t){if (this != &t){_hour = t._hour;_minute = t._minute;_second = t._second;}return *this;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本類型(內置類型)int _year = 1970;int _month = 1;int _day = 1;// 自定義類型Time _t;
};
int main()
{Date d1;Date d2;d1 = d2;return 0;
}
// 這里會發現下面的程序會崩潰掉?這里就需要我們以后講的深拷貝去解決。
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申請空間失敗");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2;s2 = s1;return 0;
}

前置++重載和后置++重載
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 前置++:返回+1之后的結果// 注意:this指向的對象函數結束后不會銷毀,故以引用方式返回提高效率Date& operator++(){_day += 1;return *this;}// 后置++:// 前置++和后置++都是一元運算符,為了讓前置++與后置++形成能正確重載// C++規定:后置++重載時多增加一個int類型的參數,但調用函數時該參數不用傳遞,編譯器//自動傳遞// 注意:后置++是先使用后+1,因此需要返回+1之前的舊值,故需在實現時需要先將this保存//一份,然后給this + 1// ? ? ? 而temp是臨時對象,因此只能以值的方式返回,不能返回引用Date operator++(int){Date temp(*this);_day += 1;return temp;}
private:int _year;int _month;int _day;
};int main()
{Date d;Date d1(2022, 1, 13);d = d1++; ? ?// d: 2022,1,13 ? d1:2022,1,14d = ++d1; ? ?// d: 2022,1,15 ? d1:2022,1,15return 0;
}
const成員
將 const 修飾的 “ 成員函數 ” 稱之為 const 成員函數 , const 修飾類成員函數,實際修飾該成員函數隱含的 this 指針 ,表明在該成員函數中 不能對類的任何成員進行修改。

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}void Print() const{cout << "Print()const" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};
void Test()
{Date d1(2022, 1, 13); d1.Print(); const Date d2(2022, 1, 13); d2.Print();
}
取地址及const取地址操作符重載
class Date{public:Date* operator&(){return this;}const Date* operator&()const{return this;}private:int _year; // 年int _month; // 月int _day; // 日};
友元
概念
友元提供了一種突破封裝的方式,有時提供了便利。但是友元會增加耦合度,破壞了封裝,所以 友元不宜多用。 友元分為:友元函數和友元類
友元函數
問題:現在嘗試去重載 operator<< ,然后發現沒辦法將 operator<< 重載成成員函數。 因為 cout 的輸出流對象和隱含的 this 指針在搶占第一個參數的位置 。 this 指針默認是第一個參數也就是左操作數了。但是實際使用中 cout 需要是第一個形參對象,才能正常使用。所以要將 operator<< 重載成全局函數。但又會導致類外沒辦法訪問成員,此時就需要友元來解決。 operator>> 同理。
class Date{public:Date(int year, int month, int day): _year(year), _month(month), _day(day){}// d1 << cout; -> d1.operator<<(&d1, cout); 不符合常規調用
// 因為成員函數第一個參數一定是隱藏的this,所以d1必須放在<<的左側ostream& operator<<(ostream& _cout){_cout << _year << "-" << _month << "-" << _day << endl;return _cout;}private:int _year;int _month;int _day;};
class Date
{friend ostream& operator<<(ostream& _cout, const Date& d);friend istream& operator>>(istream& _cin, Date& d);
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{_cin >> d._year;_cin >> d._month;_cin >> d._day;return _cin;
}
int main()
{Date d;cin >> d;cout << d << endl;return 0;
}
說明:
友元函數可訪問類的私有和保護成員,但不是類的成員函數
友元函數不能用const修飾
友元函數可以在類定義的任何地方聲明,不受類訪問限定符限制
一個函數可以是多個類的友元函數
友元函數的調用與普通函數的調用原理相同
友元類
友元類的所有成員函數都可以是另一個類的友元函數,都可以訪問另一個類中的非公有成員。友元關系是單向的,不具有交換性。比如上述 Time 類和 Date 類,在 Time 類中聲明 Date 類為其友元類,那么可以在 Date 類中直接訪問 Time 類的私有成員變量,但想在 Time 類中訪問 Date 類中私有的成員變量則不行。友元關系不能傳遞如果 C 是 B 的友元, B 是 A 的友元,則不能說明 C 時 A 的友元。友元關系不能繼承,在繼承位置再給大家詳細介紹。
class Time{friend class Date; ? // 聲明日期類為時間類的友元類,則在日期類中就直接訪問Time類//中的私有成員變量public:Time(int hour = 0, int minute = 0, int second = 0): _hour(hour), _minute(minute), _second(second){}private:int _hour;int _minute;int _second;};class Date{public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接訪問時間類私有的成員變量_t._hour = hour;_t._minute = minute;_t._second = second;}private:int _year;int _month;int _day;Time _t; };