04章-類和對象--C++核心知識學習筆記

来源:https://www.cnblogs.com/ffengeng/archive/2022/07/07/16444234.html
-Advertisement-
Play Games

大家好,我是棧長。 大家都知道,程式員這個職業需要不斷總結,對我印象最深的一件事是,我曾經花了幾天時間解決了一個驚天 bug,而幾個月過後,這個 bug 再次發生了,我知道我之前解決過這個 bug,但再次發生時卻束手無策,怎麼想也想不出當時是怎麼解決的。。 相信大家都有遇到過這樣的場景吧? 所以,自 ...


4 類和對象

C++面向對象的三大特性為:封裝、繼承、多態

C++認為萬事萬物皆為對象,對象上有其屬性和行為

例如

人可以作為對象,屬性有姓名,年齡,身高,體重,行為有走,跑,跳,吃飯,唱歌

車也可以作為對象,屬性有偶輪胎,方向盤,車燈,行為有載人,放音樂,開空調

具有相同性質的對象,我們可以抽象為類,人屬於人類,車屬於車類

4.1 封裝

4.1.1 封裝的意義

封裝是C++面向對象三大特性之一

封裝的意義

  • 將屬性和行為作為一個整體,表現生活中的事務
  • 將屬性和行為加以許可權控制

封裝意義一
在設計類的時候,屬性和行為寫在一起,表現事務

語法class 類名{訪問許可權: 屬性 / 行為};

示例1:設計一個圓類,求圓的周長

示例代碼

#include<iostream>	
using namespace std;

const double PI = 3.1415926;
//設計一個圓類,求圓的周長
//圓求周長公式:2*PI*半徑
class Circle
{
	//訪問許可權
	//公共許可權
public:
	//圓的屬性
	int m_r;
	//圓的行為
	//獲取圓的周長
	double calculateZC()
	{
		return 2 * PI * m_r;
	}
};

int main()
{
	//通過圓類 創建具體的圓(對象)
	Circle c1;
	//給圓對象 的屬性進行賦值
	c1.m_r = 10;

	cout << "圓的周長為:" << c1.calculateZC() << endl;

	system("pause");
	return 0;
}

示例2:設計一個學生類,屬性有姓名和學號,可以給姓名和學號賦值,可以顯示學生的姓名和學號

示例2代碼

#include <iostream>
#include <string>
using namespace std;

//設計一個學生類,屬性有姓名和學號
//可以給個姓名和學號賦值,可以顯示學生的姓名和學號

//設計學生類
class Student
{
public://公共許可權
	//類中的屬性和行為統一稱為成員
	// 屬性 成員屬性 成員變數
	// 行為 成員函數 成員方法
	//屬性
	string m_Name;//姓名
	int m_Id;//學號

	//行為
	//顯示姓名和學號
	void showStudent()
	{
		cout << "姓名:" << m_Name << " 學號:" << m_Id << endl;
	}

	//給姓名賦值
	void setName(string name)
	{
		m_Name = name;
	}
};

int main()
{
	//創建一個具體學生,實例化對象
	Student s1;
	//給s1對象 進行屬性賦值操作
	s1.m_Name = "zhangsan";
	s1.m_Id = 1;
	//顯示學生信息
	s1.setName("zhangsan2");
	s1.showStudent();
	

	Student s2;
	s2.m_Name = "lisi";
	s2.m_Id = 2;
	s2.showStudent();


	system("pause");
	return 0;
}

封裝意義二
類設計時,可以把屬性和行為放在不同的許可權下,加以控制

訪問許可權有三種:

  1. public 公共許可權
  2. protected 保護許可權
  3. private 私有許可權

示例

#include<iostream>
using namespace std;

//訪問許可權
//三種
//公共許可權 public     成員 類內可以訪問 類外可以訪問
//保護許可權 protected  成員 類內可以訪問 類外不可以訪問 子類可以訪問父類中保護的內容
//私有許可權 private    成員 類內可以訪問 類外不可以訪問 子類不可以訪問父類中保護的內容

class Person
{
public:
	//公共許可權內容
	string m_Name; //姓名
protected:
	//保護許可權
	string m_Car;
private:
	//私有許可權
	int m_Password;

public:
	void func()
	{
		m_Name = "zhangsan";
		m_Car = "tuolaji";
		m_Password = 123456;
	}
};

int main()
{
	//實例化具體的對象
	Person p1;

	p1.m_Name = "lisi";
	//p1.m_Car = "benchi";//錯誤,保護許可權的內容,在類外訪問不到
	//p1.Password = 123; //錯誤, 私有許可權的內容,類外訪問不到

	p1.func();//函數也是有許可權的,函數體內部是可以訪問屬性的

	system("pause");
	return 0;
}

4.1.2 struct 和 class 區別

在C++中struct和class唯一區別在於預設的訪問許可權不同

  • struct 預設許可權為共有
  • class 預設許可權為私有

示例

#include<iostream>
using namespace std;

class C1
{
	int m_A;//預設許可權是私有 
};

struct C2
{
	int m_A;
};
int main()
{
	//struct 和class 區別
	C1 c1_class;
	struct C2 c2_struct;
	cl_class.m_A;//錯誤,預設是私有成員
	c2_struct.m_A = 100;//正確,預設是共有成員
	system("pause");
	return 0;
}

4.1.3 成員屬性設置私有

優點

  1. 將所有成員屬性設置為私有,可以自己控制讀寫許可權
  2. 對於寫許可權,我們可以檢測數據的有效性

示例

#include <iostream>
#include <string>
using namespace std;

//成員屬性設置為私有
//優點1:可以自己控制讀寫許可權
//優點2:對於寫可以檢測數據的有效性

//設計人 類
class Person
{
public:
	//寫姓名 設置姓名
	void setName(string name)
	{
		m_Name = name;
	}
	//讀姓名 獲取姓名
	string getName()
	{
		return m_Name;
	}
	//讀年齡 獲取年齡
	int getAge()
	{
		m_Age = -1;//初始化為18歲
		return m_Age;
	}
	//寫年齡 設置年齡
	void setAge(int age)
	{
		if (age < 0 || age > 150)
		{
			cout << "您輸入的年齡有誤!" << endl;
			return;
		}
		m_Age = age;
	}
	//寫情人 設置情人
	void setLover(string lover)
	{
		m_Lover = lover;
	}


private:
	//姓名 可讀可寫許可權
	string m_Name;
	//年齡 只讀許可權
	int m_Age;
	//情人 只寫許可權
	string m_Lover;

};

int main()
{
	Person p;
	//寫姓名
	p.setName("zhangsan");
	//讀姓名
	cout << "姓名為:" << p.getName() << endl;
	//寫年齡
	p.setAge(1200);
	//讀年齡
	cout << "年齡為:" << p.getAge() << endl;
	//寫情人
	p.setLover("lisi");

	system("pause");
	return 0;
}  

練習案例1:

  • 設計立方體類(Cube)
  • 求出立方體的面積和體積
  • 分別用全局函數和成員函數判斷連個立方體是否相等

代碼

#include<iostream>
using namespace std;

//立方體類設計
//1、設計立方體類
//2、設計屬性和行為
//3、設計行為,獲取立方體的面積和體積
//4、分別利用全局函數和成員函數,判斷兩個立方體是否相等

class Cube {
public:
	// 行為
	// 設置獲取長寬高
	// 設置長
	void setL(int l)
	{
		m_L = l;
	}
	// 獲取長
	int getL()
	{
		return m_L;
	}
	// 設置寬
	void setW(int w)
	{
		m_W = w;
	}
	// 獲取寬
	int getW()
	{
		return m_W;
	}
	// 設置高
	void setH(int h)
	{
		m_H = h;
	}
	// 獲取高
	int getH()
	{
		return m_H;
	}
	// 獲取立方體面積
	int calculateS()
	{
		return 2 * m_L * m_W + 2 * m_W * m_H + 2 * m_L * m_H;
	}
	//獲取立方體體積
	int calculateV()
	{
		return m_L * m_W * m_H;
	}
	//利用成員函數判斷兩個立方體是否相等
	bool isSameByClass(Cube &c)
	{
		if (getL() == c.getL() && getW() == c.getW() && getH() == c.getH())
		{
			return true;
		}
		else
		{
			return false;
		}
	}

private:
	//屬性
	int m_L;//長
	int m_W;//寬
	int m_H;//高
};

bool isSame(Cube& c1, Cube& c2)
{
	if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH())
	{
		return true;
	}
	else
	{
		return false;
	}
}
int main()
{
	//創建立方體對象
	Cube c1;
	c1.setL(10);
	c1.setH(10);
	c1.setW(10);

	//600
	cout << "c1 的面積為:" << c1.calculateS() << endl;
	//1000
	cout << "c1 的體積為:" << c1.calculateV() << endl;

	//創建第二個立方體
	Cube c2;
	c2.setH(10);
	c2.setL(10);
	c2.setW(10);
	//利用全局函數判斷
	bool ret = isSame(c1, c2);
	if (ret)
	{
		cout << "c1 和 c2是相等的!" << endl;
	}
	else
	{
		cout << "c1 和 c2是不相等的!" << endl;
	}
	//利用成員函數判斷
	ret = c1.isSameByClass(c2);
	if (ret)
	{
		cout << "c1 和 c2是相等的!" << endl;
	}
	else
	{
		cout << "c1 和 c2是不相等的!" << endl;
	}
	system("pause");
	return 0;
}

練習案例2:

設計一個圓形類(Circle),和一個點類(Point),計算點和圓的關係

點和圓關係判斷

  • 點到圓心的距離 == 半徑,點在圓上
  • 點到圓心的距離 > 半徑,點在圓外
  • 點到圓心的距離 < 半徑,點在源內

代碼

#include<iostream>
using namespace std;

//點和圓的關係案例

// 點類
class Point {
private:
	int m_X;
	int m_Y;
public:
	//設置x
	void setX(int x)
	{
		m_X = x;
	}
	//獲取x
	int getX()
	{
		return m_X;
	}
	//設置y
	void setY(int y)
	{
		m_Y = y;
	}
	//獲取y
	int getY()
	{
		return m_Y;
	}

};
//圓類
class Circle {
public:
	//設置半徑
	void setR(int r)
	{
		m_R = r;
	}
	//獲取半徑
	int getR()
	{
		return m_R;
	}
	
	//設置圓心
	void setCenter(Point center)
	{
		m_Center = center;
	}
	//獲取圓心
	Point getCenter()
	{
		return  m_Center;
	}

private:
	int m_R;//半徑

	//在類中可以讓另一個類,作為本類中的成員
	Point m_Center;//圓心
};

//判斷點和圓關係函數
void isInCircle(Circle& c, Point& p)
{
	//計算兩點之間距離的平方
	int distence =
		(c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
		(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());

	//計算半徑的平方
	int rDistance = c.getR() * c.getR();

	//判斷關係
	if (distence == rDistance)
	{
		cout << "點在圓上" << endl;
	}
	else if(distence > rDistance)
	{
		cout << "點在圓外" << endl;
	}
	else
	{
		cout << "點在園內" << endl;
	}
}

int main()
{
	//創建一個圓
	Circle c;
	c.setR(10);
	Point center;
	center.setX(10);
	center.setY(0);
	c.setCenter(center);
	//創建一個點
	Point p;
	p.setX(10);
	p.setY(10);

	//判斷他們的關係
	isInCircle(c, p);

	system("pause");
	return 0;
}

代碼2:分文件寫

main.cpp:

#include<iostream>
using namespace std;
#include "circle.h"
#include "point.h"


//判斷點和圓關係函數
void isInCircle(Circle& c, Point& p)
{
	//計算兩點之間距離的平方
	int distence =
		(c.getCenter().getX() - p.getX()) * (c.getCenter().getX() - p.getX()) +
		(c.getCenter().getY() - p.getY()) * (c.getCenter().getY() - p.getY());

	//計算半徑的平方
	int rDistance = c.getR() * c.getR();

	//判斷關係
	if (distence == rDistance)
	{
		cout << "點在圓上" << endl;
	}
	else if (distence > rDistance)
	{
		cout << "點在圓外" << endl;
	}
	else
	{
		cout << "點在園內" << endl;
	}
}

int main()
{
	//創建一個圓
	Circle c;
	c.setR(10);
	Point center;
	center.setX(9);
	center.setY(0);
	c.setCenter(center);
	//創建一個點
	Point p;
	p.setX(10);
	p.setY(10);

	//判斷他們的關係
	isInCircle(c, p);

	system("pause");
	return 0;
}

point.cpp:

#include "point.h"

//設置x
void Point::setX(int x)
{
	m_X = x;
}
//獲取x
int Point::getX()
{
	return m_X;
}
//設置y
void Point::setY(int y)
{
	m_Y = y;
}
//獲取y
int Point::getY()
{
	return m_Y;
}


point.h:

#pragma once //防止頭文件重覆包含
#include <iostream>
using namespace std;

// 點類
class Point {
private:
	int m_X;
	int m_Y;
public:
	//設置x
	void setX(int x);

	//獲取x
	int getX();

	//設置y
	void setY(int y);

	//獲取y
	int getY();
};

circle.cpp:

#include "circle.h"

//設置半徑
void Circle::setR(int r)
{
	m_R = r;
}
//獲取半徑
int Circle::getR()
{
	return m_R;
}

//設置圓心
void Circle::setCenter(Point center)
{
	m_Center = center;
}
//獲取圓心
Point Circle::getCenter()
{
	return  m_Center;
}

circle.h:

#pragma once //防止同文件重覆包含
#include <iostream> //標準輸入輸出流
using namespace std;
#include "point.h"

//圓類
class Circle {
public:
	//設置半徑
	void setR(int r);
	//獲取半徑
	int getR();

	//設置圓心
	void setCenter(Point center);
	//獲取圓心
	Point getCenter();

private:
	int m_R;//半徑

	//在類中可以讓另一個類,作為本類中的成員
	Point m_Center;//圓心
};

4.2 對象的初始化清理

  • 生活中我們買的電子產品都基本會有出廠設置,在某一天我們不用時候也會刪除一些自己信息數據保證安全
  • C++中的面向對象來源於生活,每個對象也都會有初始化設置以及對象銷毀前的清理數據設置。

4.2.1 構造函數和析構函數

對象的初始化和清理而言是兩個非常重要的安全問題

  • 一個對象或者變數沒有初始狀態,對其使用後果是未知的
  • 同樣的使用完一個對象或變數,沒有及時清理,也會造成一定的安全問題

C++利用了構造函數和析構函數解決上述問題,這兩個函數將會被編譯器自動調用,完成對象初始化和清理工作。對象的初始化和清理工作是編譯器強制要求我們做的事情,因此如果我們不提供構造和析構,編譯器會提供編譯器提供的構造和函數和析構函數是空實現。

  • 構造函數:主要作用在於創建對象時為對象的成員屬性賦值,構造函數由編譯器自動調用,無需手動調用
  • 析構函數:主要作用在於對象銷毀前系統自動調用,執行一些清理工作

構造函數語法類名(){}

  1. 構造函數,沒有返回值也不寫void
  2. 函數名稱與類名相同
  3. 構造函數可以由有參數,因此可以發生重載
  4. 程式在調用對象時候會自動調用構造,無需手動調用,而且只會調用一次

析構函數語法~類名(){}

  1. 析構函數,沒有返回值也不寫void
  2. 函數名稱與類名相同,在名稱前加上符號~
  3. 析構函數不可以有參數,因此不可以發生重載
  4. 程式在對象銷毀前會自動調用析構函數,無需手動調用,而且只會調用一次

示例

#include <iostream>
using namespace std;

//對象的初始化和清理
//1、構造函數,繼續初始化操作

class Person
{
public:
	//1、構造函數
	//沒有返回值,不用寫void
	//函數名 與類名相同
	//構造函數可以有參數,可以發生重載
	//創建對象的時候,構造函數會自動調用,而且只調用一次
	Person()
	{
		cout << "Person 構造函數的調用" << endl;
	}
	//2、析構函數,進行清理的操作
	//沒有返回值,不寫void
	//函數名和類名相同, 在名稱前加~
	//析構函數不可以有參數的,不可以發生重載
	//對象在銷毀前,會自動調用析構函數,而且只會調用一次
	~Person()
	{
		cout << "Person 析構函數的調用" << endl;
	}
};

//構造和析構都是必須有的實現,如果我們自己不提供,編譯器會提供一個空實現的構造和析構
void test01()
{
	Person p;//在棧上的數據,test01執行完畢後,釋放這個對象
}

int main()
{
	test01();

	Person p;//析構是在對象執行完成後才會執行
	system("pause");
	return 0;
}

4.2.2 構造函數的分類和調用

兩種分類方式:

  • 按參數分類:有參構造和無參構造
  • 按類型分類:普通構造和拷貝構造

三種調用方式:

  • 括弧法
  • 顯示法
  • 隱式轉換法

示例

#include<iostream>
using namespace std;

//1構造函數的分類及調用
//分類
//按照參數分類 無參構造(預設構造) 和 有參構造
//按照類型分類 普通構造 拷貝構造
class Person
{
public:
	//構造函數
	Person()
	{
		cout << "Person 無參構造函數的調用" << endl;
	}
	Person(int a)
	{
		age = a;
		cout << " Person 有參構造函數的調用" << endl;
	}
	//拷貝構造函數
	Person(const Person &p) // 規定寫法
	{
		//將傳入的人身上的所有屬性,拷貝到我身上
		age = p.age;
		cout << " Person 拷貝構造函數的調用" << endl;

	}

	//析構函數
	~Person()
	{
		cout << "Person 析構函數的調用" << endl;
	}

	int age;
};

//調用
void test01() {
	//1、括弧法 推薦這種
	Person p1; //預設構造函數調用
	Person p2(10);//有參構造函數調用
	Person p3(p2);//拷貝構造函數調用

	cout << "p2 的年齡為: " << p2.age << endl;
	cout << "p3 的年齡為: " << p3.age << endl;
	//註意事項1
	//調用預設構造函數的時候,不要加()
	Person p11();//這行代碼編譯器會認為是一個函數的聲明,不會認為在創建對象
	
	//2、顯示法
	Person p4;//無參構造,預設構造
	Person p5 = Person(10);//有參構造
	Person p6 = Person(p5);//拷貝構造
	
	Person(10);//匿名對象,特點:當前行執行結束後,系統會立即回收掉匿名對象
	cout << "aaa" << endl;//是在上一個匿名對象析構函數調用後才列印aaa
	//註意事項2
	//不要利用拷貝構造函數 初始化匿名對象
	//Person(p5);//編譯器會認為Person(p3) == Person P3; 會認為是對象的聲明
	//3、隱式轉換法
	Person p7 = 10; //有參構造,相當於寫了Person p7 = Person(10);
	Person p8 = p4; //拷貝構造

}

int main()
{
	test01();

	system("pause");
	return 0;
}

4.2.3 拷貝構造函數調用時機

C++中拷貝構造函數調用時機通常由三種情況

  • 使用一個已經創建完畢的對象來初始化一個新對象
  • 值傳遞的方式給函數參數傳值
  • 以值方式返回局部對象

示例

#include <iostream>
using namespace std;

//拷貝構造函數調用時機
//1、使用一個已經創建完畢的對象來初始化一個新對象
//2、值傳遞的方式給函數參數傳值
//3、值方式返回局部對象

class Person
{
public:
	Person()
	{
		cout << "Person 預設構造函數調用" << endl;
	}

	Person(int age)
	{
		m_Age = age;
		cout << "Person 有參函數調用" << endl;
	}

	Person(const Person & p)
	{
		cout << "Person 拷貝構造函數調用" << endl;
		m_Age = p.m_Age;
	}

	~Person()
	{
		cout << "Person 析構函數調用" << endl;
	}

	int m_Age;
};

//1、使用一個已經創建完畢的對象來初始化一個新對象
void test01()
{
	Person p1(20);
	Person p2(p1);
	cout << "p2 的年齡為:" << p2.m_Age << endl;
}

//2、值傳遞的方式給函數參數傳值
void doWork(Person p)
{

}

void test02()
{
	Person p;
	doWork(p);
}

//3、值方式返回局部對象
Person doWork2()
{
	Person p1;
	cout << (int*)&p1 << endl;
	return p1;
}
void test03()
{
	Person p = doWork2();
	cout << (int*)&p << endl;
}

int main()
{
	test01();

	test02();

	test03();

	system("pause");
	return 0;
}

4.2.4 構造函數調用規則

預設情況下,C++編譯其至少給一個類添加三個函數

  • 預設構造函數(無參,函數體為空)
  • 預設析構函數(無參,函數體為空)
  • 預設拷貝析構函數,對屬性進行值拷貝

構造函數默調用規則如下:

  • 如果用戶定義有參構造函數,C++不再提供預設無參構造函數,但是會提供預設拷貝構造
  • 如果用戶定義拷貝構造函數,C++不再提供其他構造函數

示例

#include<iostream>
using namespace std;

//構造函數的調用規則
//1、創建一個類,C++編譯器會給每個類都添加至少三個函數
//預設構造 (空實現)
//析構函數 (空實現)
//拷貝構造 (值拷貝)

class Person
{
public:
	Person()
	{
		cout << "Person 的預設構造函數調用" << endl;
	}
	Person(int age)
	{
		cout << "Person 的有參構造函數調用" << endl;
		m_Age = age;
	}
	Person(const Person& p)
	{
		cout << "Person 的拷貝構造函數調用" << endl;
		m_Age = p.m_Age;
	}
	~Person()
	{
		cout << "Person 的析構函數調用" << endl;
	}
	int m_Age;
};

void test01()
{
	Person p;
	p.m_Age = 18;

	Person p2(p);
	cout << "p2 的年齡為:" << p2.m_Age << endl;
}

void test02()
{
	Person p;

}

int main()
{
	//test01();
	test02();

	system("pause");
	return 0;
}

4.2.5 深拷貝與淺拷貝

深淺拷貝是面試經典問題,也是常見的一個坑

  • 深拷貝:在堆區重新申請空間,進行拷貝操作
  • 淺拷貝:簡單的賦值拷貝操作

示例

#include <iostream>
using namespace std;
//深拷貝與淺拷貝

class Person
{
public:
	Person()
	{
		cout << "Person 的預設構造函數調用" << endl;
	}
	Person(int age, int height)
	{
		m_Age = age;
		m_Height = new int(height);//利用new把數據創建在堆區
		cout << "Person 的有參構造函數調用" << endl;
	}
	//自己實現拷貝構造函數 解決淺拷貝帶來的問題
	Person(const Person& p)
	{
		cout << "Person 拷貝構造函數的調用" << endl;
		m_Age = p.m_Age;
		//m_Height = p.m_Height;//編譯器預設實現的就是淺拷貝這行代碼
		//深拷貝操作
		m_Height = new int(*p.m_Height);//在堆區深拷貝,用new
	}
	~Person()
	{
		//析構代碼,將堆區開闢數據做釋放操作
		if (m_Height != NULL)
		{
			delete m_Height;
			m_Height = NULL;
		}
		cout << "Person 的析構函數調用" << endl;
	}
	int m_Age;//年齡
	int* m_Height;//身高

};

void test01()
{
	Person p1(18, 178);
	cout << "p1的年齡為:" << p1.m_Age <<" 身高為:"<< *p1.m_Height<< endl;
	Person p2(p1);
	cout << "p2的年齡為:" << p2.m_Age << " 身高為:" << *p2.m_Height << endl;
}

int main()
{
	test01();
	system("pause");
	return 0;
}

4.2.6 初始化列表

作用
C++提供了初始化列表語法,用來初始化屬性

語法
構造函數():屬性1(值1),屬性2(值2)...{}

示例

#include<iostream>
using namespace std;

//初始化列表
class Person
{
public:
	////傳統初始化操作
	//Person(int a, int b, int c)
	//{
	//	m_A = a;
	//	m_B = b;
	//	m_C = c;
	//}
	//初始化列表初始化屬性
	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c)
	{

	}
	int m_A;
	int m_B;
	int m_C;
};

void test01()
{
	//Person p(10, 20, 30);
	Person p(100,20,30);
	cout << "m_A = " << p.m_A << endl;
	cout << "m_B = " << p.m_B << endl;
	cout << "m_B = " << p.m_C << endl;
}

int main()
{
	test01();
	system("pause"); 
	return 0;
}

4.2.7 類對象作為類成員

C++類中的成員可以是另一個類的對象,我們稱該成員為 對象成員

例如

class A {};
class B
{
	A a;
};

B類中由對象A作為成員,A為對象成員

那麼當創建B對象時,A與B的構造和析構的順序是誰先誰後?

示例

#include<iostream> 
#include<string>
using namespace std;
//類對象作為類成員
//手機類
class Phone
{
public:
	Phone(string pName)
	{
		cout << "Phone 的構造函數調用" << endl;
		m_PName = pName;
	}
	~Phone()
	{
		cout << "Phone 的析構函數調用" << endl;
	}
	//手機品牌名稱
	string m_PName;

};

//人類
class Person
{
public:
	//Phone m_Phone = pName; 隱式轉換法
	Person(string name, string pName) :m_Name(name), m_Phone(pName)
	{
		cout << "Person 的構造函數調用" << endl;
	}
	~Person()
	{
		cout << "Person 的析構函數調用" << endl;
	}
	//姓名
	string m_Name;
	//手機
	Phone m_Phone;
};
//結論:當其他類的對象作為本類成員,先構造類的對象,再構造自身
//析構順序:先釋放自身,再釋放構造類的對象
void test01()
{
	Person p("ZhagnSan", "iPhone13");
	cout << "姓名為:" << p.m_Name << "\t";
	cout << "手機為:" << p.m_Phone.m_PName << endl;
}
int main()
{
	test01();
	system("pause");
	return 0;
}

結論

  • 當其他類的對象作為本類成員,先構造類的對象,再構造自身
  • 析構順序:先釋放自身,再釋放構造類的對象

4.2.8 靜態成員

靜態成員就是再成員變數和成員函數前加上關鍵字static,成為靜態成員

靜態成員分為:

  • 靜態成員變數
    • 所有對象共用同一份數據
    • 在編輯階段分配記憶體
    • 類內聲明,類外初始化
  • 靜態成員函數
    • 所有對象共用同一函數
    • 靜態成員函數只能訪問靜態成員變數

示例1

#include<iostream>
using namespace std;

//靜態成員變數
class Person
{
public:
	//1、所有對象都共用同一份數據
	//2、編譯階段就分配記憶體
	//3、類內聲明,類外初始化操作
	static int m_A;

	//靜態成員變數是有訪問許可權的
private:
	static int m_B;
};

int Person::m_A = 100;
int Person::m_B = 200;

void test01()
{
	Person p;
	cout << p.m_A << endl;

	Person p2;
	p2.m_A = 200;
	//100?200
	cout << p.m_A << endl;
}

void test02()
{
	//靜態成員變數 不屬於某個對象上 所有對象都共用同一份數據
	//因此靜態成員變數有兩種訪問方式

	//1、通過對象進行訪問
	Person p;
	cout << p.m_A << endl;

	//2、通過類名進行訪問
	cout << Person::m_A << endl;

	//cout << Person::m_B << endl; //錯誤。類外訪問私有的。
}
int main()
{
	//test01();
	test02();
	system("pause");
	return 0;
}

示例2

#include <iostream>
using namespace std;

//靜態成員函數
//所有對象共用同一個函數
//靜態成員函數只能訪問靜態成員變數

class Person
{
public:
	//靜態成員函數
	static void func()
	{
		m_A = 100;//靜態成員函數能夠訪問靜態成員變數
		//m_B = 200;//錯誤,靜態成員函數,不可以訪問,非靜態成員變數,無法區分到底是哪個對象的數據
		cout << "static void func 調用" << endl;
	}
	static int m_A;//靜態成員變數
	int m_B;

	//靜態成員函數也是有訪問許可權的
private:
	static void func2()
	{
		cout << "static void func2 調用" << endl;
	}
}; 
int Person::m_A = 10;
void test01()
{
	//1、通過對象訪問
	Person p;
	p.func();
	//2、通過類名訪問
	Person::func();

	//Person::func();//錯誤,類外訪問不到私有靜態成員函數
}

int main()
{
	test01();
	system("pause");
	return 0;
}

4.3 C++對象模型和this指針

4.3.1 成員變數和成員函數分開存儲

在C++中,類內的成員變數和成員函數分開存儲,只有非靜態成員變數才屬於類的對象上。

示例

#include<iostream>
using namespace std;

//成員變數和成員函數是分開存儲的
class Person
{
	int m_A;//非靜態成員變數 //屬於類的對象上

	static int m_B; //靜態成員變數 // 不屬於類的對象上

	void func(){} //非靜態成員函數 //不屬於類的對象上

	static void func2() {}//靜態成員函數 //不屬於類的對象上

};

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;

}

4.3.2 this指針概念

通過4.3.1我們知道C++中成員變數和成員函數是分開存儲的,每個非靜態成員函數只會誕生一份函數示例,也就是說多個同類型的對象共用一塊代碼,那麼問題來:這一塊代碼是如何區分哪個對象調用自己的呢?

C++通過提供特殊的對象指針,this指針,解決上述問題。this指針指向被調用的成員函數所屬的對象

  • this指針是隱含每一個非靜態成員函數內的一種指針
  • this指針不需要定義,直接使用即可

this指針的用途:

  • 當形參和成員變數同名時,可用this指針來區分
  • 在類的非靜態成員函數中返回對象本身,可使用 return *this

示例

#include<iostream>
using namespace std;

class Person {
public:
	Person(int age)
	{
		//this指針 指向 被調用成員函數 所屬的對象
		this->age =age;
	}

	Person& PersonAddAge(Person &p)
	{
		this->age += p.age;
		//this 指向p2的指針,而*this指向的就是p2這個對象本體
		return *this;
	}

	int age;//m is member
};

//1、解決名稱衝突
void test01()
{
	Person p1(18);
	cout << "p1 的年齡是多少:" << p1.age << endl;
}

//2、返回對象本身用*this
void test02()
{
	Person p1(10);

	Person p2(10);
	//鏈式編程思想
	p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
	cout << "p2 的年齡為:" << p2.age << endl;
}

int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

4.3.3 空指針訪問成員函數

C++中空指針也是可以調用成員函數的,但是也要註意沒有用到this指針

如果用到this指針,需要加以判斷保證代碼的健壯性

代碼

#include<iostream>
using namespace std;

class Person {
public:
	void showClassName()
	{
		cout << "this is Person class" << endl;
	}
	void showPersonAge()
	{
		//報錯原因是,傳入的指針為NULL

		if (this == NULL)
		{
			return;
		}
		cout << "age = " << this->m_Age << endl;
	}

	int m_Age;
};

void test01()
{
	Person* p = NULL;//空指針,並沒有確定的對象,訪問不到

	p->showClassName();

	p->showPersonAge();
}

int main()
{
	test01();
	system("pause");
	return 0;
}

4.3.4 const修飾成員函數

常函數

  • 成員函數後加const後我們稱這個函數為常函數
  • 常函數內不可以修改成員屬性
  • 成員屬性聲明時加關鍵字mutable後,在常函數中依然可以修改

常對象

  • 聲明對象前加const稱該對象為常對象
  • 常對象只能調用常函數

示例

#include <iostream>
using namespace std;

//常函數
class Person
{
public:
	//this指針本質,是指針常量,指針的指向是不可以修改的
	//Person * const this; //const修改this所以this不允許修改
	//const Person * const this; //const修改this所以this不允許修改
	//在成員函數後面加const ,修飾的this指針,讓指針指向的值也不可以修改
	void showPerson() const //相當於 const Person *//該為常函數
	{
		//this->m_A = 100;
		//this = NULL;//報錯,this指針不可以修改指針指的指向
		this->m_B = 100;
	}
	int m_A;
	mutable int m_B;//特殊變數,即使在常函數中,也可以修改這個值,加上關鍵字mutable就可以修改了

	void func()
	{

	}
};

void test01()
{
	Person p;
	p.showPerson();
}
//常對象
void test02()
{
	const Person p;//在對象前加const ,變為常對象
	//p.m_A = 100;//報錯,不允許修改的。
	p.m_B = 100;//不報錯,可以修改,因為有mutable修飾,

	//常對象只能調用常函數
	p.showPerson();//不報錯,只能調用常函數
	//p.func();//報錯,不可以調用這個,因為普通函數裡面的值可以修改

}

int main()
{

	system("pause");
	return 0;
}

4.4

生活中你的家有客廳(Public),有你的卧室(Private),客廳所有來的客人都可以進去,但是你的卧室是私有的,也就是說只有你能進去,但是呢,你也可以允許你的好閨蜜基友進去。

在程式里,有些私有屬性也想讓類外特殊的一些函數或者類進行訪問,就需要用到友元的技術,友元的目的就是讓一個函數或者類訪問另一個類中私有成員

友元關鍵字為friend

友元的三種實現:

  • 全局函數做友元
  • 類做友元
  • 成員函數做友元

4.4.1 全局函數做友元

示例

#include <iostream>
#include <string>
using namespace std;
//定義類
class Building
{
	//goodGay全局函數是Building好朋友,可以訪問Building中的私有成員
	friend void goodGay(Building* building);
public:
	Building()
	{
		m_SittingRoom = "Sittingroom";
		m_BedRoom = "Bedroom";
	}
public:
	string m_SittingRoom;//客廳
private:
	string m_BedRoom;//卧室
};

//全局函數
void goodGay(Building *building)
{
	cout << "好基友全局函數 正在訪問:" << building->m_SittingRoom << endl;

	cout << "好基友全局函數 正在訪問:" << building->m_BedRoom << endl;
}

void test01()
{
	Building building;
	goodGay(&building);
}

int main()
{
	test01();
	system("pause");
	return 0;
}

4.4.2 類做友元

示例

#include<iostream>
#include<string>
using namespace std;

//類做友元
class Building;
class GoodGay{
public:
	void visit();//參觀函數訪問Building中的屬性
	GoodGay();
	Building* building;

};

class Building {
	//GoodGay是本類的好朋友,可以訪問該類的私有成員
	friend class GoodGay;
public:
	string m_Sittingroom;
	Building();

private:
	string m_Bedroom;
};

//類外寫成員函數
Building::Building()
{
	m_Sittingroom = "客廳";
	m_Bedroom = "卧室";
}

GoodGay::GoodGay()
{
	//創建一個Building的對象
	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.4.3 全局函數做友元

示例

#include<iostream>
#include<string>
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 = "Sittingroom";
	m_Bedroom = "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 << "visit 函數正在訪問:" << building->m_Bedroom << endl;//報錯,訪問不了
}

void test01()
{
	GoodGay gg;
	gg.visit();
	gg.visit2();
}

int main()
{
	test01();
	system("pause");
	return 0;
}

4.5 運算符重載

運算符重載概念:對已有的運算符重新進行定義,賦予其另一種功能,以適應不同的數據類型

4.5.1 加號運算符重載

作用:實現兩個自定義數據類型相加的運算

對於內置的數據類型:編譯器知道如何運算

示例

#include<iostream>
using namespace std;

//加號運算符重載

class Person
{
public:
	//1、成員函數重載+號
	Person operator+(Person& p)
	{
		Person temp;
		temp.m_A = this->m_A + p.m_A;
		temp.m_B = this->m_A + p.m_B;
		return temp;
	}
	int m_A;
	int m_B;
};

//2、全局函數重載+號
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 = p1.operator+(p2);
	//全局函數重載本質調用
	//Person p3 = operator+(p1,p2);
	Person p3 = p1 + p2;

	//運算符重載,也可以發生函數重載
	Person p4 = p1 + 10;
	cout << "p3.m_A = " << p3.m_A << endl;
	cout << "p3.m_B = " << p3.m_B << endl;

}

int main()
{
	test01();
	system("pause");
	return 0;
}

總結:

  • 對於內置的數據類型的表達式的運算符不可以改變的
  • 不要濫用運算符重載

4.5.2 左移運算符重載

作用:可以輸出自定義的數據類型

示例

#include <iostream> 
using namespace std;

//左移運算符重載技術
class Person {
	friend ostream& operator<<(ostream& cout, Person& p);
private:
	////利用成員函數重載左移運算符
	// 通常不會利用成員函數重載左移運算符,無法實現cout在左側
	//void operator<<(cout)
	//{
	//}
	int m_A;
	int m_B;
public:
	Person(int a, int b)
	{
		m_A = a;
		m_B = b;
	}
};

//利用全局函數重載左移運算符
ostream& operator<<(ostream &cout, Person &p)//本質operator(cout ,p) 簡化 cout <<p
{
	cout << "m_A = " << p.m_A << " m_B = " << p.m_B;
	return cout;
}

void test01()
{
	Person p(10,10);

	cout << p << endl;;
}

int main()
{
	test01();
	system("pause");
	return 0;
}

4.5.3 遞增運算符重載

作用:通過重載遞增運算符,實現自己的整形數據

示例

#include <iostream>
using namespace std;

//重載遞增運算符

//自定義的整型變數
class MyInteger
{
	friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
	MyInteger()
	{
		m_Num = 0;
	}
	//重載前置++運算符
	//返回引用是為了一直對一個數據進行操作
	MyInteger& operator++()//返回引用,而不是返回值,所以加&
	{
		//先進行++運算
		m_Num++;
		//再將自身做返回
		return *this;
	}

	//重載後置++運算符
	//int 代表占位參數,可以用於區分前置和後置遞增
	//此處需要返回一個值,不能返回一個引用,後置遞增返回值
	MyInteger operator++(int)//int 可以不用填 只能寫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;
	cout << myint << endl;
}

void test02()
{
	MyInteger myint;
	cout << myint++ << endl;
	cout << myint << endl;
}

int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

4.5.4 賦值運算符重載

C++編譯器至少給一個類添加4個函數

  1. 預設構造函數(無參,函數體為空)
  2. 預設析構函數(無參,函數體為空)
  3. 預設拷貝構造函數,對屬性進行值拷貝
  4. 賦值運算符operator=對屬性進行值拷貝

如果類中有屬性指向堆區,做賦值操作時出現深淺拷貝問題

示例

#include<iostream>
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)
	{
		////編譯器提供是淺拷貝
		//m_Age = p.m_Age;

		//應該先判斷是否有屬性在堆區,如果有先釋放乾凈
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
		//深拷貝
		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();

	//
	int a = 10;
	int b = 20;
	int c = 30;
	a = b = c;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;

	system("pause");
	return 0;
}

4.5.5 關係運算符重載

作用:重載關係運算符,可以讓兩個自定義類型對象進行對比操作

示例

#include<iostream>
#include<string>
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 true;
		}
		return false;
	}
	string m_Name;
	int m_Age;
};

void test01()
{
	Person p1("Tom", 18);
	Person p2("Tom", 19);
	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;
}

4.5.6 函數調用運算符重載

  • 函數調用運算符()也可以重載
  • 由於重載後使用的方式非常像函數的調用,因此成為仿函數
  • 仿函數沒有固定寫法,非常的靈活

示例

#include <iostream>
#include <string> 
using namespace std;
//函數調用運算符重載

//列印輸出類
class MyPrint {
public:
	//重載函數調用運算符
	void operator()(string test)
	{
		cout << test << endl;
	}
};
//定義函數
void MyPrint02(string test)
{
	cout << test << endl;
}

void test01()
{
	MyPrint myprint;

	myprint("hello world!");//由於使用起來非常類似函數調用,因此稱為仿函數

	MyPrint02("hello world!");
}

//仿函數非常的靈活,沒有固定的寫法
//加法類
class MyAdd
{
public:
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};

void test02()
{
	MyAdd myadd;
	int ret = myadd(100, 100);
	cout << "ret = " << ret << endl;
	
	//匿名函數對象
	cout << MyAdd()(100, 100) << endl;
}

int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

4.6 繼承

繼承是面向對象三大特性之一:有些類與類之間存在特殊的關係,定義這些類時,下級別的成員除了擁有上一級的共性,還有自己的特性,這個時候我們可以考慮利用繼承的技術,減少重覆的代碼

4.6.1 繼承的基本語法

例如我們看到很多網站中,都有公共的頭部,公共的底部,甚至公共的左側列表,只有中心內容不同,接下來我們分別利用普通寫法和繼承的寫法來實現網頁中的內容,看一下繼承存在的意義以及好處

普通實現示例

#include<iostream>
using namespace std;
//普通實現頁面

//Java頁面
class Java
{
public:
	void header()
	{
		cout << "首頁、公開課、登錄、註冊...(公共頭部)" << endl;
	}
	void footer()
	{
		cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++、...(公共分類列表)" << endl;
	}
	void content()
	{
		cout << "Java視頻內容" << endl;
	}
};

//C++頁面
class Cpp
{
public:
	void header()
	{
		cout << "首頁、公開課、登錄、註冊...(公共頭部)" << endl;
	}
	void footer()
	{
		cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++、...(公共分類列表)" << endl;
	}
	void content()
	{
		cout << "C++視頻內容" << endl;
	}
};

//Python頁面
class Python
{
public:
	void header()
	{
		cout << "首頁、公開課、登錄、註冊...(公共頭部)" << endl;
	}
	void footer()
	{
		cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++、...(公共分類列表)" << endl;
	}
	void content()
	{
		cout << "Python視頻內容" << endl;
	}
};

void test01()
{
	cout << "Java下載視頻的頁面如下:" << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "==============" << endl;

	cout << "C++下載視頻的頁面如下:" << endl;
	Cpp cpp;
	cpp.header();
	cpp.footer();
	cpp.left();
	cpp.content();
	cout << "==============" << endl;

	cout << "Python下載視頻的頁面如下:" << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "=============" << endl;
	
}
int main()
{
	test01();

	system("pause");

	return 0;
}

繼承實現示例

#include <iostream>
using namespace std;

class BasePage
{
public:
	void header()
	{
		cout << "首頁、公開課、登錄、註冊...(公共頭部)" << endl;
	}
	void footer()
	{
		cout << "幫助中心、交流合作、站內地圖...(公共底部)" << endl;
	}
	void left()
	{
		cout << "Java、Python、C++、...(公共分類列表)" << endl;
	}
};

//繼承的好處:減少重覆的代碼
// 語法 class 子類:繼承方式 父類
// 子類也稱為 派生類
// 父類也稱為 基類
//Java 頁面
class Java :public BasePage
{
public:
	void content()
	{
		cout << "Java視頻內容" << endl;
	}
};

//Python 頁面
class Python : public BasePage
{
public:
	void content()
	{
		cout << "Python視頻內容" << endl;
	}
};

//C++ 頁面
class Cpp : public BasePage
{
public:
	void content()
	{
		cout << "C++視頻內容" << endl;
	}
};

void test01()
{
	cout << "Java下載視頻的頁面如下:" << endl;
	Java ja;
	ja.header();
	ja.footer();
	ja.left();
	ja.content();
	cout << "==============" << endl;

	cout << "C++下載視頻的頁面如下:" << endl;
	Cpp cpp;
	cpp.header();
	cpp.footer();
	cpp.left();
	cpp.content();
	cout << "==============" << endl;

	cout << "Python下載視頻的頁面如下:" << endl;
	Python py;
	py.header();
	py.footer();
	py.left();
	py.content();
	cout << "=============" << endl;
}


int main()
{
	test01();
	system("pause");
	return 0;
}

4.6.2 繼承方式

繼承的語法:class 子類: 繼承方式 父類

繼承方式一共有三種

  • 公共繼承:不可以訪問private,在public和protected保持一致
  • 保護繼承:不可以訪問private,public和protected變成protected
  • 私有繼承:不可以訪問private,public和protected變成private

示例

#include<iostream>
using namespace std;

//繼承方式

//公共繼承
class Base1
{
public:
	int m_A;
protected:
	int m_B:
private:
	int m_C;
};

class son1 :public Base1
{
public:
	void func()
	{
		m_A = 10;//父類中公共許可權內容到子類中依舊是公共許可權
		m_B = 10;//父類中保護許可權內容到子類總依舊是保護許可權
		//m_C = 10;//報錯,父類中私有許可權,子類訪問不到
	}
};

void test01()
{
	son1 s1;
	s1.m_A = 100;
	//s1.m_B = 100;//報錯,到Son1中m_B是保護許可權,類外訪問不到
}

//保護繼承
class son2 :protected Base1
{
public:
	void func()
	{
		m_A = 100;//父類中公有成員,到子類中變成保護許可權
		m_B = 100;//父類中保護許可權,到子類中變成保護許可權
		//m_C = 100;//報錯,父類中的私有成員,子類訪問不到
	}
};

void test02()
{
	son2 s2;
	//s2.m_A = 100;//報錯,son2中 m_A變成保護許可權,因此類外訪問不到
	//s2.m_B = 100;//報錯,son2中 m_B也是保護許可權,因此類外訪問不到
	//s2.m_C = 100;//報錯,
}

class son3 :private Base1
{
public:
	void func() 
	{
		m_A = 100;//父類中共有成員,到子類中變成私有成員
		m_B = 100;//父類中保護成員,到子類中變成私有成員
		//m_C = 100;//報錯,父類中的私有成員,子類訪問不到
	}
};

void test03()
{
	son3 s3;
	//s3.m_A = 100;//報錯,son2中 m_A變成私有成員,因此類外訪問不到
	//s3.m_B = 100;//報錯,son2中 m_B也是私有成員,因此類外訪問不到
	//s3.m_C = 100;//報錯,
}

class GrandSon3 :public son3
{
public:
	void func()
	{
		//m_A = 100;//報錯,因為父類中私有成員,子類訪問不到
		//m_B = 100//報錯,因為父類中私有成員,子類訪問不到
		//m_C = 100;//報錯,因為父類中私有成員,子類訪問不到
	}
};
int main()
{
	system("pause");
	return 0;
}

4.6.3 繼承中的對象模型

問題:從父類繼承過來的成員,哪些屬於子類對象中?

示例

#include<iostream>
using namespace std;

//繼承中的對象模型

class Base
{
public:
	int m_A;
protected:
	int m_B;
private:
	int m_C;
};

class Son :public Base
{
public:
	int m_D;
};

//利用開發人員命令提示工具查看對象模型
//跳轉盤符
//跳轉文件路徑cd 具體路徑
//c1 /d1 reportSingleClassLayout類名 文件名
void test01()
{
	//16
	//父類中所有非靜態成員屬性都會被子類繼承下去
	//父類中私有成員屬性,是被編譯器隱藏了,因此訪問不到,但是確實被繼承下去了
	cout << "size of Son = " << sizeof(Son) << endl;
}

int main()
{
	test01();
	system("pause");
	return 0;
}

4.6.4 繼承中構造和析構順序

子類繼承父類後,當創建子類對象,也會調用父類的構造函數

問題:父類和子類的構造和析構順序是誰先誰後?

:先有父類構造,再有子類構造,再子類析構,再父類析構

示例

#include<iostream>
using namespace std;

//繼承中的構造和析構的順序
class Base
{
public:
	Base()
	{
		cout << "Base構造函數!" << endl;
	}
	~Base()
	{
		cout << "Base析構函數!" << endl;
	}
};

class Son :public Base
{
public:
	Son()
	{
		cout << "Son構造函數!" << endl;
	}
	~Son()
	{
		cout << "Son析構函數!" << endl;
	}
};

void test01()
{
	//Base b;
	Son s;
}

//先有父類構造,再有子類構造,再子類析構,再父類析構
int main()
{
	test01();
	system("pause");
	return 0;
}

4.6.5 繼承同名成員處理方式

問題:當子類和父類出現同名的成員,如何通過子類對象,訪問到子類或父類中同名的數據呢?

  • 訪問子類同名成員 直接訪問即可
  • 訪問父類同名成員 需要加作用域

示例

#include <iostream>
using namespace std;
//繼承中同名成員處理
class Base
{
public :
	Base()
	{
		m_A = 100;
	}
	int m_A;
	void func()
	{
		cout << "Base - func()函數調用" << endl;
	}
	void func(int a)
	{
		cout << "Base - func(int a)函數調用" << endl;
	}

};

class Son :public Base
{
public:
	Son()
	{
		m_A = 200;
	}
	int m_A;
	void func()
	{
		cout << "Son - func()函數調用" << endl;
	}
};

//同名成員屬性處理
void test01()
{
	Son s;
	//如果出現同名是直接調用就是子類中的
	cout << "Son中 m_A = " << s.m_A << endl;
	//如果通過子類對象訪問到父類中的同名成員,需要加父類的作用域
	cout << "Base 中 m_A = " << s.Base::m_A << endl;
}

//同名成員函數處理
void test02()
{
	Son s;
	//如果出現同名是直接調用就是子類中的
	s.func();
	//如果通過子類對象訪問到父類中的同名成員,需要加父類的作用域
	s.Base::func();
	//如果子類中出現同名函數,子類中會隱藏掉所有的父類同名函數
	//如果想要訪問到父類中被隱藏的同名成員函數,需要加作用域
	s.Base::func(10);

}

int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

總結

  1. 子類對象可以直接訪問子類中同名成員
  2. 子類對象加作用域可以訪問到父類同名成名
  3. 當子類與父類擁有同名的成員函數,子類會隱藏父類中同名成員函數,加作用域可以訪問到父類同名函數

4.6.6 繼承同名靜態成員處理方式

問題:繼承中同名的靜態成員在子類對象上如何進行訪問?

靜態成員和非靜態成員出現同名,處理方式一致

  • 訪問子類同名成員 直接訪問即可
  • 訪問父類同名成員 需要加作用域

示例

#include <iostream>
using namespace std;

//繼承中的同名靜態成員處理方式

class Base
{
public:
	static int m_A;//靜態成員

	static void func()
	{
		cout << "Base -static vodi func()" << endl;
	}

	static void func(int a)
	{
		cout << "Base -static vodi func(int a)" << endl;
	}

};
int Base::m_A = 100;//類內定義,類外初始化

class Son :public Base
{
public:
	static int m_A;//

	static void func()
	{
		cout << "Son -static vodi func()" << endl;
	}
};
int Son::m_A = 200;

void test01()
{
	//1、通過對象訪問
	cout << "通過對象訪問:" << endl;
	Son s;
	cout << "Son 中 m_A = " << s.m_A << endl;
	cout << "Base 中 m_A = " << s.Base::m_A << endl;

	//2、通過類名訪問
	cout << "通過類名訪問:" << endl;
	cout << "Son 中 m_A = " << Son::m_A << endl;
	cout << "Base 中 m_A = " << Son::Base::m_A << endl;
}

//同名靜態成員函數
void test02()
{
	Son s;
	//1、通過對象訪問
	cout << "通過對象訪問:" << endl;
	s.func();
	s.Base::func();

	//2、通過類名訪問
	cout << "通過類名訪問:" << endl;
	Son::func();
	Son::Base::func();
	Son::Base::func(10);
}
int main()
{
	test01();
	test02();
	system("pause");
	return 0;
}

總結:同名靜態成員處理方式和非靜態處理方式一致,只不過有兩種訪問的方式(通過對象和通過類名)

4.6.7 多繼承語法

C++允許一個類繼承多個類

語法:class 子類: 繼承方式 父類1, 繼承方式 父類2...

多繼承可能會引發父類中同名成員出現,需要加作用域區分

C++實際開發中不建議多繼承

示例

#include<iostream>
using namespace std;

//多繼承語法

class Base1 
{
public:
	Base1()
	{
		m_A = 100;
	}
	int m_A = 100;

};
class Base2
{
public:
	Base2()
	{
		m_A = 200;
		m_B = 200;
	}
	int m_B = 100;
	int m_A = 200;
};

//子類 需要繼承Base1 和 Base 2
class Son :public Base1, public Base2
{
public:
	Son()
	{
		m_C = 300;
		m_D = 400;
	}
	int m_C = 300;
	int m_D = 400;
};
void test01()
{
	Son s;
	cout << "size of Son = " << sizeof(s) << endl;
	//當父類中出現同名的成員,需要加作用域
	cout << "Base1 中 m_A = " << s.Base1::m_A << endl;
	cout << "Base2 中 m_A = " << s.Base2::m_A << endl;
}

int main()
{
	test01();
	system("pause");
	return 0;
}

總結:多繼承中如果父類出現了同名情況,子類使用時候要加作用域

4.6.8

菱形繼承

  1. 兩個派生類繼承同一個基類。
  2. 又有某個類同時繼承這兩個派生類。
  3. 這種繼承被稱為菱形繼承,或者鑽石繼承。

問題

  1. 羊繼承了動物的數據,駝繼承了動物的數據,當羊駝使用數據時,就會產生二義性
  2. 羊駝繼自動物的數據繼承了兩份,其實我們應該清楚,這份數據只需要一份就夠可以。

代碼

#include<iostream>
using namespace std;

//動物類

class Animal {
public:
	int m_Age;
};
// 利用虛繼承,解決菱形繼承問題
// 繼承之前 加上關鍵字 virtual 變成虛繼承
// Animal 類稱為虛基類
// 羊類
class Sheep :virtual public Animal {};

//駝類
class Camel :virtual public Animal {};

class SheepCamel : public Sheep, public Camel {};

void test01()
{
	SheepCamel st;
	st.Sheep::m_Age = 18;
	st.Camel::m_Age = 28;
	//當菱形繼承,兩個父類擁有相同數據,需要加以作用域區分
	cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl;
	cout << "st.Camel::m_Age = " << st.Camel::m_Age << endl;
	cout << "st.m_Age = " << st.m_Age << endl;
	//這份數據我們知道,只有有一份就可以,菱形繼承導致數據有兩份,浪費資源

}

int main()
{
	test01();
	system("pause");
	return 0;
}

總結

  • 菱形繼承帶來的問題主要是子類繼承兩份相同的數據,導致資源浪費以及毫無意義
  • 利用虛繼承可以解決菱形繼承問題

4.7多態

4.7.1 多態的基本概念

多態是C++面向對象三大特性之一
多態分為兩類:

  • 靜態多態:函數重載和運算符重載屬於靜態多態,復用函數名
  • 動態多態:派生類和虛函數實現運行時多態

金泰多態和動態多態區別:

  • 靜態多態的函數地址早綁定,編譯階段確定函數地址
  • 動態多態的函數地址晚綁定,運行階段確定函數地址

示例

#include<iostream>	
using namespace std;

//多態

//動物類
class Animal
{
public:
	virtual void speak()//虛函數
	{
		cout << "動物在說話" << endl;
	}

};

//貓類
class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小貓在說話" << endl;
	}
};

//狗類
class Dog :public Animal
{
public:
	void speak()
	{
		cout << "小狗在說話" << endl;
	}
};

//執行說話的函數
//地址早綁定,在編譯階段確定函數地址
//如果想要貓說話,那麼這個函數地址不能提前綁定,需要在函數執行階段進行綁定,實行晚綁定
void doSpeak(Animal &animal)//父類可以直接指向子類
{
	animal.speak();
}

void test01()
{
	Cat cat;
	doSpeak(cat);

	Dog dog;
	doSpeak(dog);
}

int main()
{
	test01();
	system("pause");
	return 0;
}

總結

  1. 有繼承關係
  2. 子類重寫父類的虛函數
  3. 父類指針指向或引用指向子類對象

重寫:函數返回值類型,函數名,參數列表,完全一致

4.7.2 多態案例1-計算器類

案例描述:分別利用普通寫法和多態技術,設計實現兩個操作數進行運算的計算器類

多態的優點

  • 代碼組織結構清晰
  • 可讀性強
  • 利於前期和後期的擴展以及維護

示例

#include<iostream>
#include<string>
using namespace std;

//分別利用普通寫法和多態技術實現計算器

//普通寫法
class Calculator
{
public:
	int getResult(string oper)
	{
		if (oper == "+")
		{
			return m_Num1 + m_Num2;
		}
		else if (oper == "-")
		{
			return m_Num1 - m_Num2;
		}
		else if (oper == "*")
		{
			return m_Num1 * m_Num2;
		}
		//如果擴展新的功能,需要修改源碼
		//在真實的開發中,提倡開閉原則
		//開閉原則:對擴展進行開放,對修改進行關閉
	}
	int m_Num1;
	int m_Num2;
};

void test01()
{
	//創建計算器對象
	Calculator c;
	c.m_Num1 = 10;
	c.m_Num2 = 10;

	cout << c.m_Num1 << "+" << c.m_Num2 << "=" << c.getResult("+") << endl;
}

//利用多態實現計算器

//實現計算器抽象類
class AbstractCalculator
{
public:
	virtual int getResult()

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在SqlSugar的開發框架的後端,我們基於Web API的封裝了統一的返回結果,使得WebAPI的介面返回值更加簡潔,而在前端,我們也需要統一對返回的結果進行解析,並獲取和Web API介面對應的數據進行展示即可,本篇隨筆介紹在Vue3+TypeScript+Vite的項目中,使用基於TypeSc... ...
  • 記錄使用typescript配合webpack打包一個javascript library的配置過程. 目標是構建一個可以同時支持`CommonJs`, `esm`, `amd`這個幾個js模塊系統的javascript庫, 然後還有一個單獨打包出一個css的樣式文件的需求. ...
  • 如何實現表驅動編程用來增強可讀性、擴展性,同時提高系統運行效率 ...
  • 工作中,經常遇到這樣的問題,我明明在MySQL表上面加了索引,為什麼執行SQL查詢的時候卻沒有用到索引? 同一條SQL有時候查詢用到了索引,有時候卻沒用到索引,這是咋回事? 原因可能是索引失效了,失效的原因有以下幾種,看你有沒有踩過類似的坑? ...
  • ReentrantLock 0 關於ReentrantLock的文章其實寫過的,但當時寫的感覺不是太好,就給刪了,那為啥又要再寫一遍呢 最近閑著沒事想自己寫個鎖,然後整了幾天出來後不是跑丟線程就是和沒加鎖一樣,而且五六段就一個cas性能很差,感覺離大師寫的差十萬八千里 於是!我就想重新研究研究看看大 ...
  • 遞歸 遞歸就是自己調用自己! 遞歸的意思就是不停的調用自己,但是我們要知道的是我們的電腦資源是有限的,一般來說遞歸的層數不能太深。 遞歸主要包含兩個部分:遞歸頭和遞歸體 遞歸頭:什麼時候不調用自身方法。如果沒有,將陷入死迴圈。 遞歸體:什麼時候需要調用自身方法。 舉個例子: 我們用階乘來測試一下遞 ...
  • package cn.daheww.demo.juc.reentrylock; import sun.misc.Unsafe; import java.lang.reflect.Field; import java.util.concurrent.locks.LockSupport; /** * @ ...
  • 開始之前,pandas中DataFrame刪除對象可能存在幾種情況 1、刪除具體列 2、刪除具體行 3、刪除包含某些數值的行或者列 4、刪除包含某些字元、文字的行或者列 本文就針對這四種情況探討一下如何操作。 數據準備 模擬了一份股票交割的記錄。 In [1]: import pandas as p ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...