大家好,我是棧長。 大家都知道,程式員這個職業需要不斷總結,對我印象最深的一件事是,我曾經花了幾天時間解決了一個驚天 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;
}
封裝意義二:
類設計時,可以把屬性和行為放在不同的許可權下,加以控制
訪問許可權有三種:
- public 公共許可權
- protected 保護許可權
- 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 成員屬性設置私有
優點:
- 將所有成員屬性設置為私有,可以自己控制讀寫許可權
- 對於寫許可權,我們可以檢測數據的有效性
示例:
#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++利用了構造函數和析構函數解決上述問題,這兩個函數將會被編譯器自動調用,完成對象初始化和清理工作。對象的初始化和清理工作是編譯器強制要求我們做的事情,因此如果我們不提供構造和析構,編譯器會提供編譯器提供的構造和函數和析構函數是空實現。
- 構造函數:主要作用在於創建對象時為對象的成員屬性賦值,構造函數由編譯器自動調用,無需手動調用
- 析構函數:主要作用在於對象銷毀前系統自動調用,執行一些清理工作
構造函數語法:類名(){}
- 構造函數,沒有返回值也不寫void
- 函數名稱與類名相同
- 構造函數可以由有參數,因此可以發生重載
- 程式在調用對象時候會自動調用構造,無需手動調用,而且只會調用一次
析構函數語法:~類名(){}
- 析構函數,沒有返回值也不寫void
- 函數名稱與類名相同,在名稱前加上符號~
- 析構函數不可以有參數,因此不可以發生重載
- 程式在對象銷毀前會自動調用析構函數,無需手動調用,而且只會調用一次
示例:
#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個函數
- 預設構造函數(無參,函數體為空)
- 預設析構函數(無參,函數體為空)
- 預設拷貝構造函數,對屬性進行值拷貝
- 賦值運算符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;
}
總結
- 子類對象可以直接訪問子類中同名成員
- 子類對象加作用域可以訪問到父類同名成名
- 當子類與父類擁有同名的成員函數,子類會隱藏父類中同名成員函數,加作用域可以訪問到父類同名函數
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
菱形繼承:
- 兩個派生類繼承同一個基類。
- 又有某個類同時繼承這兩個派生類。
- 這種繼承被稱為菱形繼承,或者鑽石繼承。
問題:
- 羊繼承了動物的數據,駝繼承了動物的數據,當羊駝使用數據時,就會產生二義性
- 羊駝繼自動物的數據繼承了兩份,其實我們應該清楚,這份數據只需要一份就夠可以。
代碼:
#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;
}
總結:
- 有繼承關係
- 子類重寫父類的虛函數
- 父類指針指向或引用指向子類對象
重寫:函數返回值類型,函數名,參數列表,完全一致
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()