C++——類和對象1

1.類的定義

1.1 類定義格式

  • class為定義類的關鍵字,Stack為類的名字,{ }中的內容是類的主題為了,注意類定義結束時后面的分號不能省略。類體中的內容稱為類的成員:類中的變量稱為類的屬性或成員變量;類中的函數稱為類的方法或者成員函數。
  • 為了區分成員變量,一般習慣上成員變量會加上一個特殊標識,如成員變量前面或者后面加上_或者是m開頭,注意C++中這個并不是強制的,只是一些慣例,具體看公司的要求。
  • C++中struct也可以定義類,C++兼容C中的struct的用法,同時struct升級成了類,明顯的變化是struct中可以定義函數,一般情況下我們還是推薦用class定義類。
  • 定義在類里面的成員函數默認為inline。
//定義一個類
class Date
{void Init(int year, int month, int day){_year = year;_month = month;_day = day;}//為了好區分到底是成員變量還是參數,一般在成員變量前面加上_或是mint _year;int _month;int _day;};//C++中兼容C語言中的struct的用法
typedef struct ListNodeC
{struct ListNodeC* next;int val;
}ListNode;//在C++中struct升級為類了
//不再需要typedef,ListNodeCPP就可以代表類型struct ListNodeCPP
{void Init(int x){next = nullptr;val = x;}ListNodeCPP* next;int val;
};int main()
{ListNode node1;struct ListNodeC node2;ListNodeCPP node3;return 0;
}//class類如果沒有訪問限定符的修飾,默認是private
//struct類如果沒有訪問限定符的修飾,默認是publicclass Stack 
{//默認在類里面的成員函數都是內聯,但是展不展開是編譯器的事情
public:void Init(int n=4){arr = (int*)malloc(sizeof(int) * n);if (arr == nullptr){perror("realloc fail!\n");return;}capacity = n;top = 0;}void Push(int x){// ...擴容arr[top++] = x;}int Top(){assert(top > 0);return arr[top - 1];}void Destroy(){free(arr);arr = nullptr;top = capacity = 0;}private:int* arr;size_t top;size_t capacity;
};int main()
{Date d1;//error C2248 : “Date::Init” : 無法訪問 private 成員(在“Date”類中聲明)d1.Init(2024, 11, 11);//如果想要Init被訪問在Date類加上public,就可以被訪問了//或者是將class類改為struct類Stack st1;st1.Init(10);st1.Push(1);st1.Push(1);st1.Push(1);//int top = st1->arr[st1->top - 1]; C中實現top方法會更加自由,不能進行更好的管理int top=st1.Top();//C++的模式下會更加規范,會更好的進行管理,因為私有成員不能訪問,只能訪問公有函數st1.Destroy();return 0;
}

1.2 訪問限定符

  • C++一種實現防撞的方式,用類將對象的屬性和方法結合在一塊,讓對象更加完善,通過訪問權限選擇性的將其接口提供給外部的用戶使用。
  • publi修飾的成員在類外可以直接被訪問;protected 和 private 修飾的成員在類外不能直接被訪問,protected 和 private 是一樣的,在之后的繼承章節才能體現出它們的區別。
  • 訪問權限作用域從該訪問限定符出現的位置開始直到下一個訪問限定符出現為止,如果后面沒有訪問限定符,作用域就到 } 即類結束。
  • class定義成員沒有被訪問限定符修飾時默認為private,struct默認為public。
  • 一般成員變量都會被限制為private/protected,需要給別人使用的成員函數會為public。

1.3 類域

  • 類定義一個新的作用域,類的所有成員都在類的作用域中,在類體外定義成員時,需要使用 :: 作用域操作符指明成員屬于哪個類域。
  • 類域影響的是編譯的查找規則,下面程序中Init如果不指定類域Stack,那么編譯器就把Init當成全局函數,那么編譯時,找不到array等成員的聲明/定義在哪里,就會報錯。指定類域Stack,就是知道Init是成員函數,當前域找不到的array等成員,就會到類域中去查找。
//Stack.h#include<iostream>
using namespace std;
class Stack
{
public://成員函數  唯一不同的:只聲明不定義void Init(int n = 4);//缺省參數:(int n = 4)不能在定義和聲明的地方同時給,只能在聲明的地方給
private:int* arr; //聲明——不開空間size_t top;size_t capacity;
};
//Stack.cpp#define _CRT_SECURE_NO_WARNINGS 1#include"Stack.h"//聲明和定義分離,需要指定類域
//加上 Stack:: 類域,就不會報錯,arr會到Stack這個域里面去找,否則arr不知道去哪里找 
//不加上Stack:: 類域,“arr”: 未聲明的標識符//error C2065 : “capacity”: 未聲明的標識符//error C2065 : “top”: 未聲明的標識符
void Stack::Init(int n)  
{//這里的Init還是符合只能在類里面使用,不是在類外面,只是類的聲明和定義分開了而已//還是符合私有的在類里面使用不能在類外面使用arr = (int*)malloc(sizeof(int) * n);if (arr == nullptr){perror("realloc fail!\n");return;}capacity = n;top = 0;
}
//Test.cppint main()
{Stack st;st.Init();return 0;
}

類域的作用:

不同的域中可以定義同樣的函數,隔離出了類和類之間的命名沖突

2. 實例化

2.1 實例化概念

  • 用類型在物理內存中創建對象的過程,稱為類實例化出的對象。
  • 類是對象進行一種抽象描述,是一個模型一樣的東西,限定了類有哪些成員變量,這些成員變量只是聲明,沒有分配空間,用類實例化出對象時,才會分配空間。
  • 一個類可以實例化出多個對象,實例化出的對象占用實際的物理空間,存儲類成員變量。例如:
  • 類實例化處對象就像是現實中使用建筑設計圖建造出房子,類就像是設計圖,設計圖規劃了多少個房間,房間大小功能,但是沒有實體的建筑存在,也不能住人,用設計圖修建出房子,房子才能住人。同樣類就像是設計圖一樣,不能存儲數據,實例化出的對象分配物理內存存儲數據。
//Stack.h#include<iostream>
#include<assert.h>
using namespace std;
class Stack
{
public://成員函數  唯一不同的:只聲明不定義void Init(int n = 4);//缺省參數:(int n = 4)不能在定義和聲明的地方同時給,只能在聲明的地方給
//private:int* arr; //聲明——不開空間size_t top;size_t capacity;
};
//類實例化對象——對象是實實在在需要內存的,在內存上面存儲數據的#include"Stack.h"
int main()
{//類實例化對象// 對象定義,也就是成員變量的定義,因為成員變量是對象的一部分//定義——開空間,定義不等于初始化Stack st1; //這就是類實例化對象Stack st2; //這就是類實例化對象Stack st3; //這就是類實例化對象//st1、st2、st3才有空間//st1.top = 1; (將成員變量改為public,否則不能訪問)雖然這樣是正確的,因為定義之后有空間了,        但是這樣的寫法不規范,因為top是私有的,//要想改變的話,通過公有的函數來進行改變,這就使C++更加規范//Stack::top = 1;//top沒有空間,不能這樣寫//對象不一定有聲明,但是類是必須要有聲明的,如果類沒有聲明的話,怎么能用類去實例化對象呢return 0;
}

2.2 對象大小

對象的大小 —— 只計算成員變量,不計算成員函數,遵循內存對齊原則

內存對齊規則

  • 第一個成員在與結構體偏移量為0的地址處。
  • 其他成員變量要對齊到某個數字(對齊數)的整數倍的地址處。
  • 注意:對齊數 = 編譯器默認的一個對齊數 與 該成員大小的較小值。
  • VS中默認的對齊數是8
  • 結構體總大小為:最大對齊數(所有變量類型最大者與默認對齊參數取最小)的整數倍。
  • 如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是在所有最大對齊數(含嵌套結構體的對齊數)的整數倍。
#include"Stack.h"
int main()
{//定義——開空間,定義不等于初始化Stack st1; //類實例化對象Stack st2; Stack st3; cout << sizeof(st1) << endl;    //12  x86cout << sizeof(Stack) << endl;  //12  x86st1.top = 1;  //(將成員變量改為public,否則不能訪問)st1.Init();  // call 函數地址(不需要存在對象里面)st2.top = 2; //(將成員變量改為public,否則不能訪問)st2.Init();return 0;
}

運行結果:

為什么運行出的結果都是 12 ???

調試 ---> 轉到反匯編

通過調試,轉到反匯編,可以看見st1.Init(); 和 st2.Init();調用的都是相同的地CA104Bh)
而 st1 和 st2 各自開空間存儲自己的成員變量,指向的不是同一塊地址
成員函數的地址不在對象里面,那成員函數的地址又是聲明時候確定的呢??
函數的地址是在編譯的時候確定的

所以:

對象的大小 —— 只計算成員變量,不計算成員函數,遵循內存對齊原則

// 計算一下A/B/C實例化的對象是多大?
class A
{
public:void Print(){cout << _ch << endl;}
private:char _ch;int _i;
};class B
{
public:void Print(){//...}
};class C
{
};int main()
{A a;B b;C c;cout << sizeof(a) << endl;  //8cout << sizeof(b) << endl;  //1cout << sizeof(c) << endl;  //1return 0;
}

運行結果:

分析:

  1. 類A遵循內存對齊原則,所以大小為:8
  2. B和C一樣:成員函數不占空間,為什么是 1 ???

系統設的機制,B和C都是空類,雖然B有成員函數,成員函數不占空間,1 是為了占位,表示對象存在過,如果1個字節都不給的話,怎么能表示這個對象定義出來了呢?如果一個字節都不開,地址用怎樣給呢?這里開一個字節不存儲有效數據,純粹是為了占位,表示這個對象還在,那為什么不是 2、3呢?肯定是越少越好嘛,節省空間

3. this 指針?

  • Date類的d1和d2涉及到的Init和Print函數都是相同的空間,但是打印出來的值為什么是不同的呢??難道是因為 Init 傳了參數,Print 沒有傳參數 ??? —— 答案不是這樣的,引出 this 指針
  • 編譯器編譯后,類的成員函數默認都會在形參第?個位置,增加?個當前類類型的指針,叫做 this?指針。實際上:void Init(const Date* this,int year, int month, int day)
  • 類的成員函數中訪問成員變量,本質都是通過this指針訪問的,如Init函數中給_year賦值,this- >_year = year;
  • C++規定不能在實參和形參的位置顯示的寫this指針(編譯時編譯器會處理),但是不能在實參或形參的位置顯示的加,但是可以在函數里面使用 why???? ?---->之后會提到
class Date
{
public://實際上:void Init(const Date* this,int year, int month, int day)void Init(int year, int month, int day){this->_year = year; //_year訪問的是344行的_year嗎?不是,因為沒空間,編譯語法設計的方面//用一個變量和函數要找到他的出處,出處可以是定義,也可以是聲明,意味著這個變量和和函數是你自己定義的,在編譯的時候找到他的聲明就夠了this->_month = month; //但是在打印和初始化設計的是實實際際的空間this->_day = day;}//實際上:void Print(const Date* this)void Print(){cout << _year << "/" << _month << "/" << _day << endl;}//為了好區分到底是成員變量還是參數,一般在成員變量前面加上_或是m
private:int _year;  //344行int _month;int _day;};int main()
{Date d1;Date d2;d1.Init(2024, 7,3);d2.Init(2024, 4,27);//  d1.Print(&d1);  d1.Print();     //    2024 / 7 / 3//  d2.Print(&d2); d2.Print();     //    2024 / 4 / 27}

分析:? ??

?這里的d1和d2涉及到的Init和Print函數都是相同的空間,但是打印出來的值為什么是不同的呢??
難道是 Init 傳了參數,Print 沒有傳參數 ??? —— 引出 this 指針
隱含的this 指針 —— 隱含的指的是:編譯器會在成員函數的實參和形參的位置加一個this指針的參數
void Init(int year, int month, int day)看見的是3個參數,實際是4個參數
void Print()看見的是0個參數,實際上是1個參數,實際上是編譯器加的
void Init(const Date* this,int year, int month, int day)
void Print(const Date* this)
在變量訪問的地方會在成員變量前面自動加上 this->
所以在調用同一個函數,訪問到了不同的變量
在調用d1.Print(); 和 d2.Print();的時候分別將d1和d2的地址傳過去了
所以用this指針在調用同一個函數,訪問到了不同的變量,但是不能在實參或形參的位置顯示的加,但是可以在函數里面使用 why???? ?---->之后會提到

相應題目:

1.下面程序編譯運行結果是()

A.編譯報錯? ? ? ? ? ? ? ? ? ? ? ? ? ? ?B.運行崩潰? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?C.正常運行

class A
{
public:void Print(){cout << "A::Print()" << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->Print(); return 0;
}

運行結果:

可以看見代碼并沒有問題,正常運行,所以選C

分析:

p->Print(); 

從匯編的角度:call Print(地址),地址是在編譯的時候確定的
雖然這里有一個 -> 但是卻沒有解引用,不是說看見箭頭就是解引用,要去看它實際轉換成的動作是什么,實際的動作是調用函數,在不在對象里面??—不在

p的作用是:
1.編譯器調用成員函數,編譯器要知道Print()成員函數從哪里調的,用對象的指針去調用就知道他是他(類)的成員函數,編譯的時候符合語法找Print的出處,就到類里面去找
2.調用成員函數要傳遞this指針,相當于將p傳給了this,但是this指針是一個空指針,是不會報錯的
所以從始至終都有空指針,但是并沒有進行解引用,所以不會報錯

2.下面程序編譯運行結果是()

A.編譯報錯? ? ? ? ? ? ? ? ? ? ? ? ? ? ?B.運行崩潰? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?C.正常運行

class A
{
public:void Print(){cout << "A::Print()" << endl;cout << _a << endl;  //  cout << this->_a << endl;  空指針的訪問 }
private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}

運行結果:

代碼和上一道題幾乎一樣,但是可以看見運行有問題,只是因為對空指針進行了解引用操作

分析:

this 指針是一個空指針,用代碼來驗證一下:

class A
{
public:void Print(){cout << this << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}

運行結果:

上面的運行結果驗證了 this 確實是空指針,所以下面的代碼會報錯,因為不能對空指針進行進引用操作

cout << _a << endl;

cout << this->_a << endl; ?空指針的訪問?

考點:

1.對象里面沒有存儲成員函數的指針
2.隱含的傳this指針,誰傳過去?對象——就傳對象的地址,指針——直接就是指針傳過去,成員變量前面要加this:this->_a

3.所以這道題選A

上面的兩個例子初不初始化都無所謂,大不了就是隨機值,跟初始化沒有關系

3.this指針存在內存哪個區域中()

A.棧? B.堆? ?C.靜態區? D.常量區? ? E.對象里面

分析:

所以這道題?A.棧

拓展:

4. C實現Stack核心代碼? 和? C++實現Satck核心代碼對比:

面向對象顯著的三大特性:封裝、繼承、多態(當然還有其它的特性),下面是對封裝的初步介紹:

  • C+中的數據和函數都放到了類里面,通過訪問限定符進行了限制,不能再隨意的通過對象直接修改數據,這就是C++封裝的一種體現,這個是最重要的變化。這里的封裝的本質是一種更嚴格規范的管理,避免出現訪問修改的問題。
  • C++中會有一些相對方便的語法,比如Init給的缺省參數會方便很多,成員函數每次不需要傳對象地址,因為this指針隱含的傳遞了,方便了許多。
C語言面向的過程,更加注重過程,更注重如何實現某個函數;而C++面向的是對象,更關注類和類、對象和對象之間的關系,面向對象像是在更關注模擬現實世界

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/97859.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/97859.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/97859.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

動手學Agent:Agent設計模式——構建有效Agent的7種模型

Agent本身的定義也不是絕對的&#xff0c;從LLM到最高等級的Agent&#xff0c;中間是有大量灰度地帶的&#xff0c;在Anthropic看來&#xff0c;Agent可以以多種方式定義&#xff0c;有些人將完全自主系統定義為Agent&#xff0c;而另一些團隊則將預定義的工作流程定義為Agent。…

Windows 下 .venv 激活腳本深度定制:同時注入 PyTorch 調試日志與國內網絡加速通道——從“能跑”到“好調”的完整工程化方案

Windows 下 .venv 激活腳本深度定制&#xff1a;同時注入 PyTorch 調試日志與國內網絡加速通道 ——從“能跑”到“好調”的完整工程化方案 一、為什么非得改激活腳本&#xff1f; 重復勞動最耗時 每次打開終端都要敲四五行 set/export&#xff0c;人腦就是不可靠的剪貼板。 環…

[BX]和loop指令,debug和masm匯編編譯器對指令的不同處理,循環,大小寄存器的包含關系,操作數據長度與寄存器的關系,段前綴

[bx]是什么[bx]這個表達方式和[0]很像&#xff0c;他們倆的功能也很像。之前就提到了&#xff0c;[0]表示一個內存單元&#xff0c;他的偏移地址是0。從這邊我們可以引出內存單元的定義&#xff1a;要有內存單元的地址&#xff0c;要有內存單元的長度&#xff08;類型&#xff…

域格YM310 X09移芯CAT1模組HTTPS連接服務器

HTTPS連接服務器 本文檔介紹了HTTPS連接服務器的大致流程&#xff0c;測試服務器為httpbin.org。 HTTPS連接服務器流程 創建證書文件 創建一個文件 ATFSCREATE<filename>參數&#xff1a;<filename> 文件名 寫入CA證書 ATFSWRITE<filename>,<mode&…

【ManiSkill】常見envs學習筆記

1. StackCube-v1 用于模擬機器人在桌面場景中將紅色立方體&#xff08;cubeA&#xff09;堆疊到綠色立方體&#xff08;cubeB&#xff09;上的操作。該任務強調精確抓取、放置和穩定性控制。成功條件包括紅色立方體穩定堆疊在綠色立方體上且不被機器人抓取。 參數 (Arguments…

Java 網絡編程全解析

前言&#xff1a;網絡編程的意義與價值 前言&#xff1a;網絡編程的意義與價值 在當今互聯網時代&#xff0c;網絡編程是軟件開發的核心技能之一。無論是桌面應用、移動應用還是企業級系統&#xff0c;幾乎都需要與網絡交互。Java 作為一門跨平臺的編程語言&#xff0c;提供了完…

HarmonyOS應用拉起系列(三):如何直接拉起騰訊/百度/高德地圖進行導航

在鴻蒙應用開發中&#xff0c;經常需要跳轉第三方地圖應用&#xff08;如 騰訊地圖、百度地圖、高德地圖&#xff09;進行導航。無論是出行類 App、物流類 App&#xff0c;還是線下活動類應用&#xff0c;都存在“跳轉地圖導航”的實際需求。寫完HarmonyOS應用拉起系列一和二后…

PCGrad解決多任務沖突

論文解讀&#xff1a;"Gradient Surgery for Multi-Task Learning" 1. 論文標題直譯 Gradient Surgery: 梯度手術for Multi-Task Learning: 應用于多任務學習 合在一起就是&#xff1a;為多任務學習量身定制的梯度手術。這個名字非常形象地概括了它的核心思想。 …

Nvidia顯卡架構解析與cuda應用生態淺析

文章目錄 0. Nvidia顯卡簡介 一、主要顯卡系列 二、主要GPU架構與代表產品 1.main 1.1 CUDA 13.0 的重大變化 1.2 V100 的硬件短板已顯現 1.3 這意味著什么? 1.4 寫在后面 彩蛋:V100 0. Nvidia顯卡簡介 一、主要顯卡系列 GeForce 系列(消費級) 用途:游戲、創作、日常圖形…

開發指南:使用 MQTTNet 庫構建 .Net 物聯網 MQTT 應用程序

一、背景介紹 隨著物聯網的興起&#xff0c;.Net 框架在構建物聯網應用程序方面變得越來越流行。微軟的 .Net Core 和 .Net 框架為開發人員提供了一組工具和庫&#xff0c;以構建可以在 Raspberry Pi、HummingBoard、BeagleBoard、Pine A64 等平臺上運行的物聯網應用程序。 MQT…

突破性能瓶頸:基于騰訊云EdgeOne的AI圖片生成器全球加速實踐

1. 項目背景與挑戰 1.1 開發背景 隨著AIGC技術爆發&#xff0c;我們團隊決定開發一款多模型支持的AI圖片生成器&#xff0c;主要解決以下痛點&#xff1a; 不同AI模型的參數規范不統一生成結果難以系統化管理缺乏企業級的安全水印方案全球用戶訪問延遲高&#xff0c;中國用戶…

一、Java 基礎入門:從 0 到 1 認識 Java(詳細筆記)

1.1 Java 語言簡介與發展歷程 Java 是一門面向對象的高級編程語言&#xff0c;以“跨平臺、安全、穩定”為核心特性&#xff0c;自誕生以來長期占據編程語言排行榜前列&#xff0c;廣泛應用于后端開發、移動端開發、大數據等領域。 1.1.1 起源與核心人物 起源背景&#xff1…

uniapp:根據目的地經緯度,名稱,喚起高德/百度地圖來導航,兼容App,H5,小程序

1、需要自行申請高德地圖的key,配置manifest.json 2、MapSelector選擇組件封裝 <template><view><u-action-sheet :list="mapList" v-model="show" @click="changeMap"></u-action-sheet></view> </template&…

我對 WPF 動搖時的選擇:.NET Framework 4.6.2+WPF+Islands+UWP+CompostionApi

目錄 NET Framework 4.6.2的最大亮點 為什么固守462不升級 WPF-開發體驗的巔峰 為什么對WPF動搖了 基于IslandsUWP的濾鏡嘗試 總結 NET Framework 4.6.2的最大亮點 安全性能大提升&#xff1a; 默認啟用TLS1.2協議&#xff0c;更安全&#xff0c;它為后續的版本提供了重…

SpringBoot大文件下載失敗解決方案

SpringBoot大文件下載失敗解決方案 后端以文件流方式給前端接收下載文件,文件過大時出現下載失敗的情況或者打開后提示文件損壞,實際是字節未完全讀取寫入。 針對大文件下載失敗的情況,以下是詳細的解決方案: 大文件下載失敗的主要原因 內存溢出:一次性加載大文件到內存…

torch.gather

torch.gather 介紹 torch.gather(input, dim, index, *, sparse_gradFalse, outNone) → Tensor 沿由 dim 指定的軸收集值。 對于三維張量&#xff0c;輸出按如下方式確定&#xff1a; out[i][j][k] input[index[i][j][k]][j][k] # 如果 dim 0 out[i][j][k] input[i][i…

Golang | http/server Gin框架簡述

http/server http指的是Golang中的net/http包&#xff0c;這里用的是1.23.10。 概覽 http包的作用文檔里寫的很簡明&#xff1a;Package http provides HTTP client and server implementations. 主要是提供http的客戶端和服務端&#xff0c;也就是能作為客戶端發http請求&a…

Vision Transformer (ViT) :Transformer在computer vision領域的應用(三)

Experiment 上來的一段話就概括了整章的內容。 We evaluate the representation learning capabilities of ResNet, Vision Transformer (ViT), and the hybrid. 章節的一開頭就說明了,對比的模型就是 ResNet,CNN領域中的代碼模型。 ViT。 上一篇中提到的Hybrid模型,也就是…

5-12 WPS JS宏 Range數組規范性測試

Range()數組是JS宏中不缺少的組成部分,了解Range()數組的特性必不可少,下面我們一起測試一下各種Range()數組。 1.Range()數組特性 單元格區域:Range("a2:m2")與Range("a2","m2")的類型都是:Range/Object,功能都為單元格區域,功能…

uniapp微信小程序保存海報到手機相冊canvas

在uniapp中實現微信小程序保存海報到手機相冊&#xff0c;主要涉及Canvas繪制和圖片保存。以下是關鍵步驟和代碼示例&#xff1a; 一、關鍵代碼展示&#xff1a; 1. 模板配置&#xff1a;頁面展示該海報&#xff0c;可直接查看&#xff0c;也可下載保存到手機相冊&#xff0c;h…