三、C++對象模型和this指針
3.1?成員變量和成員函數分開存儲
在C++中,類內的成員變量和成員函數分開存儲,只有非靜態成員變量才屬于類的對象上
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;class Person {int m_A;//非靜態成員變量屬于類的對象上static int m_B;//靜態成員變量不屬于類的對象上void func() {}//非靜態成員函數不屬于類的對象上static void func2() {}//靜態成員函數不屬于類的對象上
};int Person::m_B = 10;void test01() {Person p;//空對象占用內存空間為 1 個字節//C++編譯器會給每個空對象也分配一個字節空間,是為了區分空對象占內存的位置//每個空對象也有一個獨一無二的內存地址cout << "size of p = " << sizeof(p) << endl;
}
void test02() {Person p;//占用內存空間為 4 個字節cout << "size of p = " << sizeof(p) << endl;
}int main() {//test01();test02();system("pause");return 0;
}
3.2 this指針概念
通過上面我們知道在C++中成員變量和成員函數是分開存儲的,每一個非靜態成員函數只會誕生一份函數實例,也就是說多個同類型的對象會共用一塊代碼那么問題是:這—塊代碼是如何區分那個對象調用自己的呢?
C++通過提供特殊的對象指針, this指針,解決上述問題的。this指針指向被調用的成員函數所屬的對象
this指針是隱含每一個非靜態成員函數內的一種指針
this指針不需要定義,直接使用即可
this指針的用途:
1.當形參和成員變量同名時,可用this指針來區分
2.·當形參和成員變量同名時,可用this指針來區分
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;class Person {
public:Person(int age) {//age = age;這樣寫無法區分,達到所需要求//this指針指向的是 被調用的成員函數所屬的對象 即p1this->age = age;}Person& PersonAddAge(Person &p) {this->age += p.age;//this指向p2的指針,而*this指向的就是p2,返回本體用&//如果去掉&,就是返回值,答案為20,出來的是p2'一個新的對象return *this;}//解決名稱沖突int age;};void test01() {Person p1(18);cout << "p1的年齡為:" << p1.age << endl;}
3.3 空指針訪問成員函數
C++中空指針也是可以調用成員函數的,但是也要注意有沒有用到this指針
如果用到this指針,需要加以判斷保證代碼的健壯性
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;class Person {
public:void showClassName() {cout << "this is Person class" << endl;}void showPersonAge() {if (this == NULL) {return;}cout << "age = " << this->m_Age << endl;}int m_Age;};void test01() {Person* p = NULL;p->showClassName();//在新版VS里雖然能運行,但是仍存在問題,傳入的指針為空p->showPersonAge();
}int main() {test01();system("pause");return 0;
}
3.4 const修飾成員函數
常函數:
·成員函數后加const后我們稱為這個函數為常函數
·常函數內不可以修改成員屬性
·成員屬性聲明時加關鍵字mutable后,在常函數中依然可以修改
常對象:
·聲明對象前加const稱該對象為常對象
·常對象只能調用常函數
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;class Person {
public://常函數//this指針的本質 是指針常量 指針的指向是不可修改的//const Person * const this;//Person * const this 就是this在成員函數后面加const,修飾的是this指向,讓指針指向的值也不可以修改void showPerson() const{//加上const則值也不可修改//this->m_A = 100; 報錯//this = NULL; 已經指向 p,不可修改this->m_B = 100;}void func(){}int m_A;mutable int m_B;//特殊變量,即使在常函數中,也可以修改這個值
};void test01() {Person p;p.showPerson();
}//常對象
void test02() {const Person p;//在對象前加上const//p.m_A = 100;不可修改p.m_B = 100;//m_B是特殊的值,在常對象下也可修改//常對象只能調用常函數//p.func();不可調用普通的成員函數,因為普通的成員函數可以修改
}int main() {test01();test02();system("pause");return 0;
}
四、友元
生活中你的家有客廳(Public),有你的臥室(Private。客廳所有來的客人都可以進去,但是你的臥室是私有的,也就是說只有你能進去但是呢,你也可以允許你的好閨蜜好基友進去。
在程序里,有些私有屬性也想讓類外特殊的一些函數或者類進行訪問,就需要用到友元.
友元的目的就是讓—個函數或者類訪問另—個類中私有成員.
友元的關鍵字為friend
友元的三種實現:
·全局函數做友元
·類做友元
·成員函數做友元
4.1?全局函數做友元
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;class Buliding {//這樣就可以使其函數訪問私有成員friend void goodGay(Buliding* bulidding);
public:Buliding() {m_SittingRoom = "客廳";m_BedRoom = "臥室";}public:string m_SittingRoom;//客廳
private:string m_BedRoom;//臥室};void goodGay(Buliding *bulidding) {cout << "好基友的全局函數 正在訪問:" << bulidding->m_SittingRoom << endl;cout << "好基友的全局函數 正在訪問:" << bulidding->m_BedRoom << endl;
}void test01() {Buliding bulidding;goodGay(&bulidding);}int main() {test01();system("pause");return 0;
}
4.2 類做友元
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;class Building;
class GoodGay {
public:GoodGay();void visit();Building * building;
};class Building {//這樣GoodGay類的成員就可以訪問本類的私有成員friend class GoodGay;
public:Building();public:string m_SittingRoom;//客廳
private:string m_BedRoom;//臥室};Building::Building() {m_SittingRoom = "客廳";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.3 成員函數做友元
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;class Building;
class GoodGay {
public:GoodGay();void visit();//讓visit函數可以訪問Building中私有成員void visit2();//讓visit2函數不可以訪問Building中私有成員Building * building;
};class Building {//告訴編譯器GoodGay類下的visit成員函數作為本類的好朋友,可以訪問私有成員friend void GoodGay::visit();
public:Building();public:string m_SittingRoom;//客廳
private:string m_BedRoom;//臥室};Building::Building() {m_SittingRoom = "客廳";m_BedRoom = "臥室";
}GoodGay::GoodGay() {building = new Building;
}void GoodGay::visit() {cout << "visit 函數正在訪問:" << building->m_SittingRoom << endl;cout << "visit 函數正在訪問:" << building->m_BedRoom << endl;
}void GoodGay::visit2() {cout << "visit2 函數正在訪問:" << building->m_SittingRoom << endl;//會報錯//cout << "visit2 函數正在訪問:" << building->m_BedRoom << endl;
}void test01() {GoodGay gg;gg.visit();gg.visit2();
}int main() {test01();system("pause");return 0;
}
五、運算符重載
運算符重載概念:對已有的運算符重新進行定義,賦予其另一種功能,以適應不同的數據類型
5.1 加號運算符重載
作用:實現兩個自定義數據類型相加的運算
eg.
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;//加號運算符重載
class Person {public://1、成員函數重載 + 號成員函數重載本質調用//Person p3 = p1.operator+(p2);/*Person operator+(Person& p) {Person temp;temp.m_A = this->m_A + p.m_A;temp.m_B = this->m_B + p.m_B;return temp;}*/int m_A;int m_B;
};//2、全局函數重載 + 號
//全局函數重載本質調用
// Person p3 = operator+(p1, p2);
Person operator+(Person& p1, Person& p2) {Person temp;temp.m_A = p1.m_A + p2.m_A;temp.m_B = p1.m_B + p2.m_B;return temp;
}//函數重載的版本
Person operator+(Person& p1, int num) {Person temp;temp.m_A = p1.m_A + num;temp.m_B = p1.m_B + num;return temp;
}void test01() {Person p1;p1.m_A = 10;p1.m_B = 10;Person p2;p2.m_A = 10;p2.m_B = 10;Person p3;p3 = p1 + p2;//運算符重載`也可以發生函數重載Person p4 = p1 + 100;cout << "p3.m_A = " << p3.m_A << endl;cout << "p3.m_B = " << p3.m_B << endl;cout << "p4.m_A = " << p4.m_A << endl;cout << "p4.m_B = " << p4.m_B << endl;
}int main() {test01();system("pause");return 0;
}
注意:
1: 對于內置的數據類型的表達式的的運算符是不可能改變的
2: 不要濫用運算符重載
5.2 左移運算符<<重載(較難)
作用:可以輸出自定義數據類型
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;//左移運算符重載
class Person {public://利用成員函數重載左移運算符//不會利用成員函數重載<<運算符,因為無法實現cout在左側//void operator<<(Person &p) {// //兩個對象了// //改為p.operator<<(cout) 簡化版本 p << cout//}int m_A;int m_B;
};///只能利用全局函數重載左移運算符ostream& operator<<(ostream &cout,Person p)//本質operator<< ( cout ,p )簡化 cout<< p
{//全局只能有1個,所以用&coutcout << "m_A = " << p.m_A << " m_B = " << p.m_B;return cout;
}void test01() {Person p;p.m_A = 10;p.m_B = 10;cout << p << "hello,word" << endl;
}int main() {test01();system("pause");return 0;
}
5.3 遞增運算符重載
作用:通過重載遞增運算符,實現自己的整型數據
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;//重載遞增運算符//自定義整型
class MyInteger {friend ostream& operator<<(ostream& cout, MyInteger myint);
public:MyInteger() {m_Num = 0;}//重載前置++運算符,返回引用為了一直對一個數據講行遞增操作MyInteger& operator++() {m_Num++;return *this;}//重載后置++運算符//void operator++(int) int代表占位參數,可以用于區分前置和后置MyInteger operator++(int) {//先記錄當時結果MyInteger temp = *this;//后遞增m_Num++;//最后將記錄結果做返回return temp;}
private:int m_Num;};ostream& operator<<(ostream& cout, MyInteger myint) {cout << myint.m_Num;return cout;
}void test01() {MyInteger myint;cout << ++(++myint) << endl;
}
void test02() {MyInteger myint;cout << myint++ << endl;cout << myint << endl;
}int main() {test01();test02();system("pause");return 0;
}
5.4 賦值運算符重載
C++編譯器至少給一個類添加4個函數
1.默認構造函數(無參,函數體為空)
2.默認析構函數(無參,函數體為空)
3.默認拷貝構造函數,對屬性進行值拷貝
4.賦值運算符operator=,對屬性進行值拷貝
如果類中有屬性指向堆區,做賦值操作時也會出現深淺拷貝問題
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;//重載賦值運算符
class Person {
public:Person(int age) {m_Age = new int(age);}~Person() {if (m_Age != NULL) {delete(m_Age);m_Age = NULL;}}Person& operator=(Person& p) {//應該先判斷是否有屬性在堆區,如果有先釋放干凈,然后再深拷貝if (m_Age != NULL) {delete m_Age;m_Age = NULL;}this->m_Age = new int(*p.m_Age);return *this;}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();system("pause");return 0;
}
5.5 關系運算符重載
作用:重載關系運算符,可以讓兩個自定義類型對象進行對比操作
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;//重載關系運算符class Person {
public:Person(string name,int age) {m_Name = name;m_Age = age;}//重載==號bool operator==(Person &p) {if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {return true;}return false;}bool operator!=(Person& p) {if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {return false;}return true;}string m_Name;int m_Age;
};void test01() {Person p1("Tom", 18);Person p2("Tom", 8);if (p1 == p2) {cout << "p1和p2是相等的!" << endl;}else {cout << "p1和p2是不相等的!" << endl;}if (p1 != p2) {cout << "p1和p2是不相等的!" << endl;}else {cout << "p1和p2是相等的!" << endl;}}int main() {test01();system("pause");return 0;
}
5.6 函數調用運算符()重載
·函數調用運算符()也可以重載
·由于重載后使用的方式非常像函數的調用,因此稱為仿函數
·仿函數沒有固定寫法,非常靈活
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <string.h>
using namespace std;//重載函數調用運算符
class MyPrint {
public://重載函數調用運算符void operator()(string test) {cout << test << endl;}
};void test02(string test) {cout << test << endl;}void test01() {MyPrint myPrint;myPrint("hello world!");//由于使用起來非常類似于函數調用,因此稱為仿函數test02("hello world!");
}//仿函數沒有固定寫法,非常靈活
//加法類class MyAdd {
public:int operator()(int num1, int num2) {return num1 + num2;}};void test03() {MyAdd myAdd;int ret = myAdd(100, 100);cout << "ret = " << ret << endl;//匿名函數對象cout << MyAdd()(100, 100) << endl;
}int main() {test01();test03();system("pause");return 0;
}