前言
前段時間有粉絲問我,大一結束c++剛學完,不知道自己目前學得怎么樣?要掌握的知識點有沒有都弄懂了?是否基礎入門了?
前幾天已經整理過C++基礎入門知識點,沒看過的可以看看喔!熬夜爆肝!C++基礎入門大合集【萬字干貨預警 建議收藏】
今天繼續整理C++進階篇知識點,一起來看看吧~

目錄
- 前言
- C++核心編程
- 1 記憶體磁區模型
- 1.1 程式運行前
- 1.2 程式運行后
- 1.3 new運算子
- 2 參考
- 2.1 參考的基本使用
- 2.2 參考注意事項
- 2.3 參考做函式引數
- 2.4 參考做函式回傳值
- 2.5 參考的本質
- 2.6 常量參考
- 3 函式提高
- 3.1 函式默認引數
- 3.2 函式占位引數
- 3.3 函式多載
- 3.3.1 函式多載概述
- 4 類和物件
- 4.1 封裝
- 4.1.1 封裝的意義
- 4.1.2 struct和class區別
- 4.1.3 成員屬性設定為私有
- 4.2 物件的初始化和清理
- 4.2.1 建構式和解構式
- 4.2.2 建構式的分類及呼叫
- 4.2.3 拷貝建構式呼叫時機
- 4.2.4 建構式呼叫規則
- 4.2.5 深拷貝與淺拷貝
- 4.2.6 初始化串列
- 4.2.7 類物件作為類成員
- 4.2.8 靜態成員
- 4.3 C++物件模型和this指標
- 4.3.1 成員變數和成員函式分開存盤
- 4.3.2 this指標概念
- 4.3.3 空指標訪問成員函式
- 4.3.4 const修飾成員函式
- 4.4 友元
- 4.4.1 全域函式做友元
- 4.4.2 類做友元
- 4.4.3 成員函式做友元
- 4.6 繼承
- 4.6.1 繼承
- 4.6.2 繼承方式
- 4.6.3 繼承中構造和析構順序
- 4.6.4 繼承同名成員處理方式
- 4.6.5 繼承同名靜態成員處理方式
- 4.6.6 多繼承語法
- 4.7 多型
- 4.7.1 多型的基本概念
- 4.7.2 純虛函式和抽象類
- 4.7.3 虛析構和純虛析構
- 5 檔案操作
- 5.1文本檔案
- 5.1.1寫檔案
- 5.1.2讀檔案
- 5.2 二進制檔案
- 5.2.1 寫檔案
- 5.2.2 讀檔案
C++核心編程
主要針對C++面向物件編程技術做詳細講解,探討C++的核心和精髓,
1 記憶體磁區模型
C++程式在執行時,將記憶體大方向劃分為4個區域
- 代碼區:存放函式體的二進制代碼,由作業系統進行管理的
- 全域區:存放全域變數和靜態變數以及常量
- 堆疊區:由編譯器自動分配釋放, 存放函式的引數值,區域變數等
- 堆區:由程式員分配和釋放,若程式員不釋放,程式結束時由作業系統回收
記憶體四區意義:
不同區域存放的資料,賦予不同的生命周期, 給我們更大的靈活編程
1.1 程式運行前
? 在程式編譯后,生成了exe可執行程式,未執行該程式前分為兩個區域
? 代碼區:
? 存放 CPU 執行的機器指令
? 代碼區是共享的,共享的目的是對于頻繁被執行的程式,只需要在記憶體中有一份代碼即可
? 代碼區是只讀的,使其只讀的原因是防止程式意外地修改了它的指令
? 全域區:
? 全域變數和靜態變數存放在此.
? 全域區還包含了常量區, 字串常量和其他常量也存放在此.
? 該區域的資料在程式結束后由作業系統釋放.
示例:
//全域變數
int g_a = 10;
int g_b = 10;
//全域常量
const int c_g_a = 10;
const int c_g_b = 10;
int main() {
//區域變數
int a = 10;
int b = 10;
//列印地址
cout << "區域變數a地址為: " << (int)&a << endl;
cout << "區域變數b地址為: " << (int)&b << endl;
cout << "全域變數g_a地址為: " << (int)&g_a << endl;
cout << "全域變數g_b地址為: " << (int)&g_b << endl;
//靜態變數
static int s_a = 10;
static int s_b = 10;
cout << "靜態變數s_a地址為: " << (int)&s_a << endl;
cout << "靜態變數s_b地址為: " << (int)&s_b << endl;
cout << "字串常量地址為: " << (int)&"hello world" << endl;
cout << "字串常量地址為: " << (int)&"hello world1" << endl;
cout << "全域常量c_g_a地址為: " << (int)&c_g_a << endl;
cout << "全域常量c_g_b地址為: " << (int)&c_g_b << endl;
const int c_l_a = 10;
const int c_l_b = 10;
cout << "區域常量c_l_a地址為: " << (int)&c_l_a << endl;
cout << "區域常量c_l_b地址為: " << (int)&c_l_b << endl;
system("pause");
return 0;
}
列印結果:

- C++中在程式運行前分為全域區和代碼區
- 代碼區特點是共享和只讀
- 全域區中存放全域變數、靜態變數、常量
- 常量區中存放 const修飾的全域常量 和 字串常量
1.2 程式運行后
? 堆疊區:
? 由編譯器自動分配釋放, 存放函式的引數值,區域變數等
? 注意事項:不要回傳區域變數的地址,堆疊區開辟的資料由編譯器自動釋放
示例:
int * func()
{
int a = 10;
return &a;
}
int main() {
int *p = func();
cout << *p << endl;
cout << *p << endl;
system("pause");
return 0;
}
? 堆區:
? 由程式員分配釋放,若程式員不釋放,程式結束時由作業系統回收
? 在C++中主要利用new在堆區開辟記憶體
示例:
int* func()
{
int* a = new int(10);
return a;
}
int main() {
int *p = func();
cout << *p << endl;
cout << *p << endl;
system("pause");
return 0;
}
總結:
堆區資料由程式員管理開辟和釋放
堆區資料利用new關鍵字進行開辟記憶體
1.3 new運算子
? C++中利用new運算子在堆區開辟資料
? 堆區開辟的資料,由程式員手動開辟,手動釋放,釋放利用運算子 delete
? 語法:new 資料型別
? 利用new創建的資料,會回傳該資料對應的型別的指標
示例1: 基本語法
int* func()
{
int* a = new int(10);
return a;
}
int main() {
int *p = func();
cout << *p << endl;
cout << *p << endl;
//利用delete釋放堆區資料
delete p;
//cout << *p << endl; //報錯,釋放的空間不可訪問
system("pause");
return 0;
}
示例2:開辟陣列
//堆區開辟陣列
int main() {
int* arr = new int[10];
for (int i = 0; i < 10; i++)
{
arr[i] = i + 100;
}
for (int i = 0; i < 10; i++)
{
cout << arr[i] << endl;
}
//釋放陣列 delete 后加 []
delete[] arr;
system("pause");
return 0;
}
2 參考
2.1 參考的基本使用
**作用: **給變數起別名
語法: 資料型別 &別名 = 原名
示例:
int main() {
int a = 10;
int &b = a;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
b = 100;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause");
return 0;
}
2.2 參考注意事項
- 參考必須初始化
- 參考在初始化后,不可以改變
示例:
int main() {
int a = 10;
int b = 20;
//int &c; //錯誤,參考必須初始化
int &c = a; //一旦初始化后,就不可以更改
c = b; //這是賦值操作,不是更改參考
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
system("pause");
return 0;
}
2.3 參考做函式引數
**作用:**函式傳參時,可以利用參考的技術讓形參修飾實參
**優點:**可以簡化指標修改實參
示例:
//1. 值傳遞
void mySwap01(int a, int b) {
int temp = a;
a = b;
b = temp;
}
//2. 地址傳遞
void mySwap02(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
//3. 參考傳遞
void mySwap03(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 20;
mySwap01(a, b);
cout << "a:" << a << " b:" << b << endl;
mySwap02(&a, &b);
cout << "a:" << a << " b:" << b << endl;
mySwap03(a, b);
cout << "a:" << a << " b:" << b << endl;
system("pause");
return 0;
}
2.4 參考做函式回傳值
參考是可以作為函式的回傳值存在的,不要回傳區域變數參考,
用法:函式呼叫作為左值
//回傳區域變數參考
int& test01() {
int a = 10; //區域變數
return a;
}
//回傳靜態變數參考
int& test02() {
static int a = 20;
return a;
}
int main() {
//不能回傳區域變數的參考
int& ref = test01();
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
//如果函式做左值,那么必須回傳參考
int& ref2 = test02();
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
test02() = 1000;
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
system("pause");
return 0;
}
2.5 參考的本質
本質:參考的本質在c++內部實作是一個指標常量.
C++推薦用參考技術,因為語法方便,參考本質是指標常量,但是所有的指標操作編譯器都幫我們做了
//發現是參考,轉換為 int* const ref = &a;
void func(int& ref){
ref = 100; // ref是參考,轉換為*ref = 100
}
int main(){
int a = 10;
//自動轉換為 int* const ref = &a; 指標常量是指標指向不可改,也說明為什么參考不可更改
int& ref = a;
ref = 20; //內部發現ref是參考,自動幫我們轉換為: *ref = 20;
cout << "a:" << a << endl;
cout << "ref:" << ref << endl;
func(a);
return 0;
}
2.6 常量參考
常量參考主要用來修飾形參,防止誤操作
在函式形參串列中,可以加const修飾形參,防止形參改變實參
//參考使用的場景,通常用來修飾形參
void showValue(const int& v) {
//v += 10;
cout << v << endl;
}
int main() {
//int& ref = 10; 參考本身需要一個合法的記憶體空間,因此這行錯誤
//加入const就可以了,編譯器優化代碼,int temp = 10; const int& ref = temp;
const int& ref = 10;
//ref = 100; //加入const后不可以修改變數
cout << ref << endl;
//函式中利用常量參考防止誤操作修改實參
int a = 10;
showValue(a);
system("pause");
return 0;
}
3 函式提高
3.1 函式默認引數
在C++中,函式的形參串列中的形參是可以有默認值的,
語法:回傳值型別 函式名 (引數= 默認值){}
int func(int a, int b = 10, int c = 10) {
return a + b + c;
}
//1. 如果某個位置引數有默認值,那么從這個位置往后,從左向右,必須都要有默認值
//2. 如果函式宣告有默認值,函式實作的時候就不能有默認引數
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
return a + b;
}
int main() {
cout << "ret = " << func(20, 20) << endl;
cout << "ret = " << func(100) << endl;
system("pause");
return 0;
}
3.2 函式占位引數
C++中函式的形參串列里可以有占位引數,用來做占位,呼叫函式時必須填補該位置
回傳值型別 函式名 (資料型別){}
//函式占位引數 ,占位引數也可以有默認引數
void func(int a, int) {
cout << "this is func" << endl;
}
int main() {
func(10,10); //占位引數必須填補
system("pause");
return 0;
}
3.3 函式多載
3.3.1 函式多載概述
函式名可以相同,提高復用性
函式多載滿足條件:
- 同一個作用域下
- 函式名稱相同
- 函式引數型別不同 或者 個數不同 或者 順序不同
函式的回傳值不可以作為函式多載的條件
//函式多載需要函式都在同一個作用域下
void func()
{
cout << "func 的呼叫!" << endl;
}
void func(int a)
{
cout << "func (int a) 的呼叫!" << endl;
}
void func(double a)
{
cout << "func (double a)的呼叫!" << endl;
}
void func(int a ,double b)
{
cout << "func (int a ,double b) 的呼叫!" << endl;
}
void func(double a ,int b)
{
cout << "func (double a ,int b)的呼叫!" << endl;
}
//函式回傳值不可以作為函式多載條件
//int func(double a, int b)
//{
// cout << "func (double a ,int b)的呼叫!" << endl;
//}
int main() {
func();
func(10);
func(3.14);
func(10,3.14);
func(3.14 , 10);
system("pause");
return 0;
}
4 類和物件
C++面向物件的三大特性為:封裝、繼承、多型
4.1 封裝
4.1.1 封裝的意義
封裝是C++面向物件三大特性之一
封裝的意義:
- 將屬性和行為作為一個整體,表現生活中的事物
- 將屬性和行為加以權限控制
封裝意義一:
? 在設計類的時候,屬性和行為寫在一起,表現事物
語法: class 類名{ 訪問權限: 屬性 / 行為 };
**示例1:**設計一個圓類,求圓的周長
示例代碼:
//圓周率
const double PI = 3.14;
//1、封裝的意義
//將屬性和行為作為一個整體,用來表現生活中的事物
//封裝一個圓類,求圓的周長
//class代表設計一個類,后面跟著的是類名
class Circle
{
public: //訪問權限 公共的權限
//屬性
int m_r;//半徑
//行為
//獲取到圓的周長
double calculateZC()
{
//2 * pi * r
//獲取圓的周長
return 2 * PI * m_r;
}
};
int main() {
//通過圓類,創建圓的物件
// c1就是一個具體的圓
Circle c1;
c1.m_r = 10; //給圓物件的半徑 進行賦值操作
//2 * pi * 10 = = 62.8
cout << "圓的周長為: " << c1.calculateZC() << endl;
system("pause");
return 0;
}
封裝意義二:
類在設計時,可以把屬性和行為放在不同的權限下,加以控制
訪問權限有三種:
- public 公共權限
- protected 保護權限
- private 私有權限
//三種權限
//公共權限 public 類內可以訪問 類外可以訪問
//保護權限 protected 類內可以訪問 類外不可以訪問
//私有權限 private 類內可以訪問 類外不可以訪問
class Person
{
//姓名 公共權限
public:
string m_Name;
//汽車 保護權限
protected:
string m_Car;
//銀行卡密碼 私有權限
private:
int m_Password;
public:
void func()
{
m_Name = "張三";
m_Car = "拖拉機";
m_Password = 123456;
}
};
int main() {
Person p;
p.m_Name = "李四";
//p.m_Car = "奔馳"; //保護權限類外訪問不到
//p.m_Password = 123; //私有權限類外訪問不到
system("pause");
return 0;
}
4.1.2 struct和class區別
在C++中 struct和class唯一的區別就在于 默認的訪問權限不同
區別:
- struct 默認權限為公共
- class 默認權限為私有
class C1
{
int m_A; //默認是私有權限
};
struct C2
{
int m_A; //默認是公共權限
};
int main() {
C1 c1;
c1.m_A = 10; //錯誤,訪問權限是私有
C2 c2;
c2.m_A = 10; //正確,訪問權限是公共
system("pause");
return 0;
}
4.1.3 成員屬性設定為私有
- 將所有成員屬性設定為私有,可以自己控制讀寫權限
- 對于寫權限,我們可以檢測資料的有效性
class Person {
public:
//姓名設定可讀可寫
void setName(string name) {
m_Name = name;
}
string getName()
{
return m_Name;
}
//獲取年齡
int getAge() {
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("張三");
cout << "姓名: " << p.getName() << endl;
//年齡設定
p.setAge(50);
cout << "年齡: " << p.getAge() << endl;
//情人設定
p.setLover("蒼井");
//cout << "情人: " << p.m_Lover << endl; //只寫屬性,不可以讀取
system("pause");
return 0;
}
4.2 物件的初始化和清理
4.2.1 建構式和解構式
物件的初始化和清理也是兩個非常重要的安全問題
? 一個物件或者變數沒有初始狀態,對其使用后果是未知
? 同樣的使用完一個物件或變數,沒有及時清理,也會造成一定的安全問題
c++利用了建構式和解構式解決上述問題,這兩個函式將會被編譯器自動呼叫,完成物件初始化和清理作業,
物件的初始化和清理作業是編譯器強制要我們做的事情,因此如果我們不提供構造和析構,編譯器會提供
編譯器提供的建構式和解構式是空實作,
- 建構式:主要作用在于創建物件時為物件的成員屬性賦值,建構式由編譯器自動呼叫,無須手動呼叫,
- 解構式:主要作用在于物件銷毀前系統自動呼叫,執行一些清理作業,
建構式語法:類名(){}
- 建構式,沒有回傳值也不寫void
- 函式名稱與類名相同
- 建構式可以有引數,因此可以發生多載
- 程式在呼叫物件時候會自動呼叫構造,無須手動呼叫,而且只會呼叫一次
解構式語法: ~類名(){}
- 解構式,沒有回傳值也不寫void
- 函式名稱與類名相同,在名稱前加上符號 ~
- 解構式不可以有引數,因此不可以發生多載
- 程式在物件銷毀前會自動呼叫析構,無須手動呼叫,而且只會呼叫一次
4.2.2 建構式的分類及呼叫
兩種分類方式:
? 按引數分為: 有參構造和無參構造
? 按型別分為: 普通構造和拷貝構造
三種呼叫方式:
? 括號法
? 顯示法
? 隱式轉換法
//1、建構式分類
// 按照引數分類分為 有參和無參構造 無參又稱為默認建構式
// 按照型別分類分為 普通構造和拷貝構造
class Person {
public:
//無參(默認)建構式
Person() {
cout << "無參建構式!" << endl;
}
//有參建構式
Person(int a) {
age = a;
cout << "有參建構式!" << endl;
}
//拷貝建構式
Person(const Person& p) {
age = p.age;
cout << "拷貝建構式!" << endl;
}
//解構式
~Person() {
cout << "解構式!" << endl;
}
public:
int age;
};
//2、建構式的呼叫
//呼叫無參建構式
void test01() {
Person p; //呼叫無參建構式
}
//呼叫有參的建構式
void test02() {
//2.1 括號法,常用
Person p1(10);
//注意1:呼叫無參建構式不能加括號,如果加了編譯器認為這是一個函式宣告
//Person p2();
//2.2 顯式法
Person p2 = Person(10);
Person p3 = Person(p2);
//Person(10)單獨寫就是匿名物件 當前行結束之后,馬上析構
//2.3 隱式轉換法
Person p4 = 10; // Person p4 = Person(10);
Person p5 = p4; // Person p5 = Person(p4);
//注意2:不能利用 拷貝建構式 初始化匿名物件 編譯器認為是物件宣告
//Person p5(p4);
}
int main() {
test01();
//test02();
system("pause");
return 0;
}
4.2.3 拷貝建構式呼叫時機
C++中拷貝建構式呼叫時機通常有三種情況
- 使用一個已經創建完畢的物件來初始化一個新物件
- 值傳遞的方式給函式引數傳值
- 以值方式回傳區域物件
class Person {
public:
Person() {
cout << "無參建構式!" << endl;
mAge = 0;
}
Person(int age) {
cout << "有參建構式!" << endl;
mAge = age;
}
Person(const Person& p) {
cout << "拷貝建構式!" << endl;
mAge = p.mAge;
}
//解構式在釋放記憶體之前呼叫
~Person() {
cout << "解構式!" << endl;
}
public:
int mAge;
};
//1. 使用一個已經創建完畢的物件來初始化一個新物件
void test01() {
Person man(100); //p物件已經創建完畢
Person newman(man); //呼叫拷貝建構式
Person newman2 = man; //拷貝構造
//Person newman3;
//newman3 = man; //不是呼叫拷貝建構式,賦值操作
}
//2. 值傳遞的方式給函式引數傳值
//相當于Person p1 = p;
void doWork(Person p1) {}
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++編譯器至少給一個類添加3個函式
1.默認建構式(無參,函式體為空)
2.默認解構式(無參,函式體為空)
3.默認拷貝建構式,對屬性進行值拷貝
建構式呼叫規則如下:
-
如果用戶定義有參建構式,c++不在提供默認無參構造,但是會提供默認拷貝構造
-
如果用戶定義拷貝建構式,c++不會再提供其他建構式
4.2.5 深拷貝與淺拷貝
淺拷貝:簡單的賦值拷貝操作
深拷貝:在堆區重新申請空間,進行拷貝操作
如果屬性有在堆區開辟的,一定要自己提供拷貝建構式,防止淺拷貝帶來的問題
class Person {
public:
//無參(默認)建構式
Person() {
cout << "無參建構式!" << endl;
}
//有參建構式
Person(int age ,int height) {
cout << "有參建構式!" << endl;
m_age = age;
m_height = new int(height);
}
//拷貝建構式
Person(const Person& p) {
cout << "拷貝建構式!" << endl;
//如果不利用深拷貝在堆區創建新記憶體,會導致淺拷貝帶來的重復釋放堆區問題
m_age = p.m_age;
m_height = new int(*p.m_height);
}
//解構式
~Person() {
cout << "解構式!" << endl;
if (m_height != NULL)
{
delete m_height;
}
}
public:
int m_age;
int* m_height;
};
void test01()
{
Person p1(18, 180);
Person p2(p1);
cout << "p1的年齡: " << p1.m_age << " 身高: " << *p1.m_height << endl;
cout << "p2的年齡: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}
int main() {
test01();
system("pause");
return 0;
}
4.2.6 初始化串列
C++提供了初始化串列語法,用來初始化屬性
建構式():屬性1(值1),屬性2(值2)… {}
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) {}
void PrintPerson() {
cout << "mA:" << m_A << endl;
cout << "mB:" << m_B << endl;
cout << "mC:" << m_C << endl;
}
private:
int m_A;
int m_B;
int m_C;
};
int main() {
Person p(1, 2, 3);
p.PrintPerson();
system("pause");
return 0;
}
4.2.7 類物件作為類成員
C++類中的成員可以是另一個類的物件,我們稱該成員為 物件成員
例如:
class A {}class B{ A a;}
B類中有物件A作為成員,A為物件成員
class Phone
{
public:
Phone(string name)
{
m_PhoneName = name;
cout << "Phone構造" << endl;
}
~Phone()
{
cout << "Phone析構" << endl;
}
string m_PhoneName;
};
class Person
{
public:
//初始化串列可以告訴編譯器呼叫哪一個建構式
Person(string name, string pName) :m_Name(name), m_Phone(pName)
{
cout << "Person構造" << endl;
}
~Person()
{
cout << "Person析構" << endl;
}
void playGame()
{
cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手機! " << endl;
}
string m_Name;
Phone m_Phone;
};
void test01()
{
//當類中成員是其他類物件時,我們稱該成員為 物件成員
//構造的順序是 :先呼叫物件成員的構造,再呼叫本類構造
//析構順序與構造相反
Person p("張三" , "蘋果X");
p.playGame();
}
int main() {
test01();
system("pause");
return 0;
}
4.2.8 靜態成員
靜態成員就是在成員變數和成員函式前加上關鍵字static,稱為靜態成員
靜態成員分為:
- 靜態成員變數
- 所有物件共享同一份資料
- 在編譯階段分配記憶體
- 類內宣告,類外初始化
- 靜態成員函式
- 所有物件共享同一個函式
- 靜態成員函式只能訪問靜態成員變數
靜態成員變數:
class Person
{
public:
static int m_A; //靜態成員變數
//靜態成員變數特點:
//1 在編譯階段分配記憶體
//2 類內宣告,類外初始化
//3 所有物件共享同一份資料
private:
static int m_B; //靜態成員變數也是有訪問權限的
};
int Person::m_A = 10;
int Person::m_B = 10;
void test01()
{
//靜態成員變數兩種訪問方式
//1、通過物件
Person p1;
p1.m_A = 100;
cout << "p1.m_A = " << p1.m_A << endl;
Person p2;
p2.m_A = 200;
cout << "p1.m_A = " << p1.m_A << endl; //共享同一份資料
cout << "p2.m_A = " << p2.m_A << endl;
//2、通過類名
cout << "m_A = " << Person::m_A << endl;
//cout << "m_B = " << Person::m_B << endl; //私有權限訪問不到
}
int main() {
test01();
system("pause");
return 0;
}
靜態成員函式:
class Person
{
public:
//靜態成員函式特點:
//1 程式共享一個函式
//2 靜態成員函式只能訪問靜態成員變數
static void func()
{
cout << "func呼叫" << endl;
m_A = 100;
//m_B = 100; //錯誤,不可以訪問非靜態成員變數
}
static int m_A; //靜態成員變數
int m_B; //
private:
//靜態成員函式也是有訪問權限的
static void func2()
{
cout << "func2呼叫" << endl;
}
};
int Person::m_A = 10;
void test01()
{
//靜態成員變數兩種訪問方式
//1、通過物件
Person p1;
p1.func();
//2、通過類名
Person::func();
//Person::func2(); //私有權限訪問不到
}
int main() {
test01();
system("pause");
return 0;
}
4.3 C++物件模型和this指標
4.3.1 成員變數和成員函式分開存盤
在C++中,類內的成員變數和成員函式分開存盤
只有非靜態成員變數才屬于類的物件上
class Person {
public:
Person() {
mA = 0;
}
//非靜態成員變數占物件空間
int mA;
//靜態成員變數不占物件空間
static int mB;
//函式也不占物件空間,所有函式共享一個函式實體
void func() {
cout << "mA:" << this->mA << endl;
}
//靜態成員函式也不占物件空間
static void sfunc() {
}
};
int main() {
cout << sizeof(Person) << endl;
system("pause");
return 0;
}
4.3.2 this指標概念
通過上面我們知道在C++中成員變數和成員函式是分開存盤的
每一個非靜態成員函式只會誕生一份函式實體,也就是說多個同型別的物件會共用一塊代碼
c++通過提供特殊的物件指標,this指標,解決上述問題,this指標指向被呼叫的成員函式所屬的物件
this指標是隱含每一個非靜態成員函式內的一種指標
this指標不需要定義,直接使用即可
this指標的用途:
- 當形參和成員變數同名時,可用this指標來區分
- 在類的非靜態成員函式中回傳物件本身,可使用return *this
4.3.3 空指標訪問成員函式
C++中空指標也是可以呼叫成員函式的,但是也要注意有沒有用到this指標
如果用到this指標,需要加以判斷保證代碼的健壯性
4.3.4 const修飾成員函式
常函式:
- 成員函式后加const后我們稱為這個函式為常函式
- 常函式內不可以修改成員屬性
- 成員屬性宣告時加關鍵字mutable后,在常函式中依然可以修改
常物件:
- 宣告物件前加const稱該物件為常物件
- 常物件只能呼叫常函式
class Person {
public:
Person() {
m_A = 0;
m_B = 0;
}
//this指標的本質是一個指標常量,指標的指向不可修改
//如果想讓指標指向的值也不可以修改,需要宣告常函式
void ShowPerson() const {
//const Type* const pointer;
//this = NULL; //不能修改指標的指向 Person* const this;
//this->mA = 100; //但是this指標指向的物件的資料是可以修改的
//const修飾成員函式,表示指標指向的記憶體空間的資料不能修改,除了mutable修飾的變數
this->m_B = 100;
}
void MyFunc() const {
//mA = 10000;
}
public:
int m_A;
mutable int m_B; //可修改 可變的
};
//const修飾物件 常物件
void test01() {
const Person person; //常量物件
cout << person.m_A << endl;
//person.mA = 100; //常物件不能修改成員變數的值,但是可以訪問
person.m_B = 100; //但是常物件可以修改mutable修飾成員變數
//常物件訪問成員函式
person.MyFunc(); //常物件不能呼叫const的函式
}
int main() {
test01();
system("pause");
return 0;
}
4.4 友元
在程式里,有些私有屬性 也想讓類外特殊的一些函式或者類進行訪問,就需要用到友元的技術
友元的目的就是讓一個函式或者類 訪問另一個類中私有成員
友元的關鍵字為 friend
友元的三種實作
- 全域函式做友元
- 類做友元
- 成員函式做友元
4.4.1 全域函式做友元
class Building
{
//告訴編譯器 goodGay全域函式 是 Building類的好朋友,可以訪問類中的私有內容
friend void goodGay(Building * building);
public:
Building()
{
this->m_SittingRoom = "客廳";
this->m_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 b;
goodGay(&b);
}
int main(){
test01();
system("pause");
return 0;
}
4.4.2 類做友元
class Building;
class goodGay
{
public:
goodGay();
void visit();
private:
Building *building;
};
class Building
{
//告訴編譯器 goodGay類是Building類的好朋友,可以訪問到Building類中私有內容
friend class goodGay;
public:
Building();
public:
string m_SittingRoom; //客廳
private:
string m_BedRoom;//臥室
};
Building::Building()
{
this->m_SittingRoom = "客廳";
this->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.4.3 成員函式做友元
class Building;
class goodGay
{
public:
goodGay();
void visit(); //只讓visit函式作為Building的好朋友,可以發訪問Building中私有內容
void visit2();
private:
Building *building;
};
class Building
{
//告訴編譯器 goodGay類中的visit成員函式 是Building好朋友,可以訪問私有內容
friend void goodGay::visit();
public:
Building();
public:
string m_SittingRoom; //客廳
private:
string m_BedRoom;//臥室
};
Building::Building()
{
this->m_SittingRoom = "客廳";
this->m_BedRoom = "臥室";
}
goodGay::goodGay()
{
building = new Building;
}
void goodGay::visit()
{
cout << "好基友正在訪問" << building->m_SittingRoom << endl;
cout << "好基友正在訪問" << building->m_BedRoom << endl;
}
void goodGay::visit2()
{
cout << "好基友正在訪問" << building->m_SittingRoom << endl;
//cout << "好基友正在訪問" << building->m_BedRoom << endl;
}
void test01()
{
goodGay gg;
gg.visit();
}
int main(){
test01();
system("pause");
return 0;
}
4.6 繼承
繼承是面向物件三大特性之一
我們發現,定義這些類時,下級別的成員除了擁有上一級的共性,還有自己的特性,
這個時候我們就可以考慮利用繼承的技術,減少重復代碼
4.6.1 繼承
繼承的好處:可以減少重復的代碼
class A : public B;
A 類稱為子類 或 派生類
B 類稱為父類 或 基類
派生類中的成員,包含兩大部分:
一類是從基類繼承過來的,一類是自己增加的成員,
從基類繼承過過來的表現其共性,而新增的成員體現了其個性,
4.6.2 繼承方式
繼承的語法:class 子類 : 繼承方式 父類
繼承方式一共有三種:
- 公共繼承
- 保護繼承
- 私有繼承
class Base1
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
//公共繼承
class Son1 :public Base1
{
public:
void func()
{
m_A; //可訪問 public權限
m_B; //可訪問 protected權限
//m_C; //不可訪問
}
};
void myClass()
{
Son1 s1;
s1.m_A; //其他類只能訪問到公共權限
}
//保護繼承
class Base2
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son2:protected Base2
{
public:
void func()
{
m_A; //可訪問 protected權限
m_B; //可訪問 protected權限
//m_C; //不可訪問
}
};
void myClass2()
{
Son2 s;
//s.m_A; //不可訪問
}
//私有繼承
class Base3
{
public:
int m_A;
protected:
int m_B;
private:
int m_C;
};
class Son3:private Base3
{
public:
void func()
{
m_A; //可訪問 private權限
m_B; //可訪問 private權限
//m_C; //不可訪問
}
};
class GrandSon3 :public Son3
{
public:
void func()
{
//Son3是私有繼承,所以繼承Son3的屬性在GrandSon3中都無法訪問到
//m_A;
//m_B;
//m_C;
}
};
4.6.3 繼承中構造和析構順序
子類繼承父類后,當創建子類物件,也會呼叫父類的建構式
繼承中 先呼叫父類建構式,再呼叫子類建構式,析構順序與構造相反
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()
{
//繼承中 先呼叫父類建構式,再呼叫子類建構式,析構順序與構造相反
Son s;
}
int main() {
test01();
system("pause");
return 0;
}
4.6.4 繼承同名成員處理方式
- 子類物件可以直接訪問到子類中同名成員
- 子類物件加作用域可以訪問到父類同名成員
- 當子類與父類擁有同名的成員函式,子類會隱藏父類中同名成員函式,加作用域可以訪問到父類中同名函式
class Base {
public:
Base()
{
m_A = 100;
}
void func()
{
cout << "Base - func()呼叫" << endl;
}
void func(int a)
{
cout << "Base - func(int a)呼叫" << endl;
}
public:
int m_A;
};
class Son : public Base {
public:
Son()
{
m_A = 200;
}
//當子類與父類擁有同名的成員函式,子類會隱藏父類中所有版本的同名成員函式
//如果想訪問父類中被隱藏的同名成員函式,需要加父類的作用域
void func()
{
cout << "Son - func()呼叫" << endl;
}
public:
int m_A;
};
void test01()
{
Son s;
cout << "Son下的m_A = " << s.m_A << endl;
cout << "Base下的m_A = " << s.Base::m_A << endl;
s.func();
s.Base::func();
s.Base::func(10);
}
int main() {
test01();
system("pause");
return EXIT_SUCCESS;
}
4.6.5 繼承同名靜態成員處理方式
靜態成員和非靜態成員出現同名,處理方式一致
- 訪問子類同名成員 直接訪問即可
- 訪問父類同名成員 需要加作用域
同名靜態成員處理方式和非靜態處理方式一樣,只不過有兩種訪問的方式(通過物件 和 通過類名)
4.6.6 多繼承語法
C++允許一個類繼承多個類
語法:class 子類 :繼承方式 父類1 , 繼承方式 父類2...
多繼承可能會引發父類中有同名成員出現,需要加作用域區分
C++實際開發中不建議用多繼承
class Base1 {
public:
Base1()
{
m_A = 100;
}
public:
int m_A;
};
class Base2 {
public:
Base2()
{
m_A = 200; //開始是m_B 不會出問題,但是改為mA就會出現不明確
}
public:
int m_A;
};
//語法:class 子類:繼承方式 父類1 ,繼承方式 父類2
class Son : public Base2, public Base1
{
public:
Son()
{
m_C = 300;
m_D = 400;
}
public:
int m_C;
int m_D;
};
//多繼承容易產生成員同名的情況
//通過使用類名作用域可以區分呼叫哪一個基類的成員
void test01()
{
Son s;
cout << "sizeof Son = " << sizeof(s) << endl;
cout << s.Base1::m_A << endl;
cout << s.Base2::m_A << endl;
}
int main() {
test01();
system("pause");
return 0;
}
4.7 多型
4.7.1 多型的基本概念
多型是C++面向物件三大特性之一
多型分為兩類
- 靜態多型: 函式多載 和 運算子多載屬于靜態多型,復用函式名
- 動態多型: 派生類和虛函式實作運行時多型
靜態多型和動態多型區別:
- 靜態多型的函式地址早系結 - 編譯階段確定函式地址
- 動態多型的函式地址晚系結 - 運行階段確定函式地址
class Animal
{
public:
//Speak函式就是虛函式
//函式前面加上virtual關鍵字,變成虛函式,那么編譯器在編譯的時候就不能確定函式呼叫了,
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();
}
//
//多型滿足條件:
//1、有繼承關系
//2、子類重寫父類中的虛函式
//多型使用:
//父類指標或參考指向子類物件
void test01()
{
Cat cat;
DoSpeak(cat);
Dog dog;
DoSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
總結:
多型滿足條件
- 有繼承關系
- 子類重寫父類中的虛函式
多型使用條件
- 父類指標或參考指向子類物件
重寫:函式回傳值型別 函式名 引數串列 完全一致稱為重寫
4.7.2 純虛函式和抽象類
在多型中,通常父類中虛函式的實作是毫無意義的,主要都是呼叫子類重寫的內容
因此可以將虛函式改為純虛函式
純虛函式語法:virtual 回傳值型別 函式名 (引數串列)= 0 ;
當類中有了純虛函式,這個類也稱為抽象類
抽象類特點:
- 無法實體化物件
- 子類必須重寫抽象類中的純虛函式,否則也屬于抽象類
class Base
{
public:
//純虛函式
//類中只要有一個純虛函式就稱為抽象類
//抽象類無法實體化物件
//子類必須重寫父類中的純虛函式,否則也屬于抽象類
virtual void func() = 0;
};
class Son :public Base
{
public:
virtual void func()
{
cout << "func呼叫" << endl;
};
};
void test01()
{
Base * base = NULL;
//base = new Base; // 錯誤,抽象類無法實體化物件
base = new Son;
base->func();
delete base;//記得銷毀
}
int main() {
test01();
system("pause");
return 0;
}
4.7.3 虛析構和純虛析構
多型使用時,如果子類中有屬性開辟到堆區,那么父類指標在釋放時無法呼叫到子類的析構代碼
解決方式:將父類中的解構式改為虛析構或者純虛析構
虛析構和純虛析構共性:
- 可以解決父類指標釋放子類物件
- 都需要有具體的函式實作
虛析構和純虛析構區別:
- 如果是純虛析構,該類屬于抽象類,無法實體化物件
虛析構語法:
virtual ~類名(){}
純虛析構語法:
virtual ~類名() = 0;
類名::~類名(){}
? 1. 虛析構或純虛析構就是用來解決通過父類指標釋放子類物件
? 2. 如果子類中沒有堆區資料,可以不寫為虛析構或純虛析構
? 3. 擁有純虛解構式的類也屬于抽象類
class Animal {
public:
Animal()
{
cout << "Animal 建構式呼叫!" << endl;
}
virtual void Speak() = 0;
//解構式加上virtual關鍵字,變成虛解構式
//virtual ~Animal()
//{
// cout << "Animal虛解構式呼叫!" << endl;
//}
virtual ~Animal() = 0;
};
Animal::~Animal()
{
cout << "Animal 純虛解構式呼叫!" << endl;
}
//和包含普通純虛函式的類一樣,包含了純虛解構式的類也是一個抽象類,不能夠被實體化,
class Cat : public Animal {
public:
Cat(string name)
{
cout << "Cat建構式呼叫!" << endl;
m_Name = new string(name);
}
virtual void Speak()
{
cout << *m_Name << "小貓在說話!" << endl;
}
~Cat()
{
cout << "Cat解構式呼叫!" << endl;
if (this->m_Name != NULL) {
delete m_Name;
m_Name = NULL;
}
}
public:
string *m_Name;
};
void test01()
{
Animal *animal = new Cat("Tom");
animal->Speak();
//通過父類指標去釋放,會導致子類物件可能清理不干凈,造成記憶體泄漏
//怎么解決?給基類增加一個虛解構式
//虛解構式就是用來解決通過父類指標釋放子類物件
delete animal;
}
int main() {
test01();
system("pause");
return 0;
}
5 檔案操作
程式運行時產生的資料都屬于臨時資料,程式一旦運行結束都會被釋放
通過檔案可以將資料持久化
C++中對檔案操作需要包含頭檔案 < fstream >
檔案型別分為兩種:
- 文本檔案 - 檔案以文本的ASCII碼形式存盤在計算機中
- 二進制檔案 - 檔案以文本的二進制形式存盤在計算機中,用戶一般不能直接讀懂它們
操作檔案的三大類:
- ofstream:寫操作
- ifstream: 讀操作
- fstream : 讀寫操作
5.1文本檔案
5.1.1寫檔案
寫檔案步驟如下:
-
包含頭檔案
#include <fstream>
-
創建流物件
ofstream ofs;
-
打開檔案
ofs.open(“檔案路徑”,打開方式);
-
寫資料
ofs << “寫入的資料”;
-
關閉檔案
ofs.close();
檔案打開方式:
| 打開方式 | 解釋 |
|---|---|
| ios::in | 為讀檔案而打開檔案 |
| ios::out | 為寫檔案而打開檔案 |
| ios::ate | 初始位置:檔案尾 |
| ios::app | 追加方式寫檔案 |
| ios::trunc | 如果檔案存在先洗掉,再創建 |
| ios::binary | 二進制方式 |
注意: 檔案打開方式可以配合使用,利用|運算子
**例如:**用二進制方式寫檔案 ios::binary | ios:: out
#include <fstream>
void test01()
{
ofstream ofs;
ofs.open("test.txt", ios::out);
ofs << "姓名:張三" << endl;
ofs << "性別:男" << endl;
ofs << "年齡:18" << endl;
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
總結:
- 檔案操作必須包含頭檔案 fstream
- 讀檔案可以利用 ofstream ,或者fstream類
- 打開檔案時候需要指定操作檔案的路徑,以及打開方式
- 利用<<可以向檔案中寫資料
- 操作完畢,要關閉檔案
5.1.2讀檔案
讀檔案與寫檔案步驟相似,但是讀取方式相對于比較多
讀檔案步驟如下:
-
包含頭檔案
#include <fstream>
-
創建流物件
ifstream ifs;
-
打開檔案并判斷檔案是否打開成功
ifs.open(“檔案路徑”,打開方式);
-
讀資料
四種方式讀取
-
關閉檔案
ifs.close();
#include <fstream>
#include <string>
void test01()
{
ifstream ifs;
ifs.open("test.txt", ios::in);
if (!ifs.is_open())
{
cout << "檔案打開失敗" << endl;
return;
}
//第一種方式
//char buf[1024] = { 0 };
//while (ifs >> buf)
//{
// cout << buf << endl;
//}
//第二種
//char buf[1024] = { 0 };
//while (ifs.getline(buf,sizeof(buf)))
//{
// cout << buf << endl;
//}
//第三種
//string buf;
//while (getline(ifs, buf))
//{
// cout << buf << endl;
//}
char c;
while ((c = ifs.get()) != EOF)
{
cout << c;
}
ifs.close();
}
int main() {
test01();
system("pause");
return 0;
}
總結:
- 讀檔案可以利用 ifstream ,或者fstream類
- 利用is_open函式可以判斷檔案是否打開成功
- close 關閉檔案
5.2 二進制檔案
以二進制的方式對檔案進行讀寫操作
打開方式要指定為 ios::binary
5.2.1 寫檔案
二進制方式寫檔案主要利用流物件呼叫成員函式write
函式原型 :ostream& write(const char * buffer,int len);
字符指標buffer指向記憶體中一段存盤空間,len是讀寫的位元組數
#include <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
//二進制檔案 寫檔案
void test01()
{
//1、包含頭檔案
//2、創建輸出流物件
ofstream ofs("person.txt", ios::out | ios::binary);
//3、打開檔案
//ofs.open("person.txt", ios::out | ios::binary);
Person p = {"張三" , 18};
//4、寫檔案
ofs.write((const char *)&p, sizeof(p));
//5、關閉檔案
ofs.close();
}
int main() {
test01();
system("pause");
return 0;
}
5.2.2 讀檔案
二進制方式讀檔案主要利用流物件呼叫成員函式read
函式原型:istream& read(char *buffer,int len);
字符指標buffer指向記憶體中一段存盤空間,len是讀寫的位元組數
#include <fstream>
#include <string>
class Person
{
public:
char m_Name[64];
int m_Age;
};
void test01()
{
ifstream ifs("person.txt", ios::in | ios::binary);
if (!ifs.is_open())
{
cout << "檔案打開失敗" << endl;
}
Person p;
ifs.read((char *)&p, sizeof(p));
cout << "姓名: " << p.m_Name << " 年齡: " << p.m_Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
以上內容,根據網上資料整理匯總,今天我們就到這里,明天繼續努力!

若本篇內容對您有所幫助,請三連點贊,關注,收藏支持下,
創作不易,白嫖不好,各位的支持和認可,就是我創作的最大動力,我們下篇文章見!
Dragon少年 | 文
如果本篇博客有任何錯誤,請批評指教,不勝感激 !
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/290328.html
標籤:其他
