C#面向對象核心-封裝

来源:https://www.cnblogs.com/tanyuyang/archive/2023/03/28/17264756.html
-Advertisement-
Play Games

封裝 封裝定義為"把一個或多個項目封閉在一個物理的或者邏輯的包中",這個包就是類。在面向對象程式設計方法論中,封裝可以防止對實現細節的訪問。 1 類和對象 1.1 什麼是類 具有相同特征、行為,是一類事物的抽象 類是對象的模板,通過類創建對象 1.2 類聲明語法 //聲明在namespace中 /* ...


封裝

封裝定義為"把一個或多個項目封閉在一個物理的或者邏輯的包中",這個包就是類。在面向對象程式設計方法論中,封裝可以防止對實現細節的訪問。

1 類和對象

1.1 什麼是類

具有相同特征、行為,是一類事物的抽象

類是對象的模板,通過類創建對象

1.2 類聲明語法

//聲明在namespace中
/*class 類名
{
    //成員變數 表示特征
    //成員方法 表示行為
    //成員屬性 保護成員變數
    //構造函數和析構函數 初始化和釋放
    //索引器 像數組一樣使用
    //運算符重載 自定義對象可計算
    //靜態成員 類名.點出成員使用
}*/
class Person
{

}

1.3 類對象

類聲明和類對象聲明是兩個概念:

  • 類聲明相當於定義了一個變數類型;
  • 類對象聲明相當於聲明一個類的變數,這個過程稱為實例化對象;
  • 類對象是引用類型。
//類名 變數名;
//類名 變數名 = null;
//類名 變數名 = new 類名();
Person p1;
Person p2 = null;//空,不分配堆的記憶體空間

//p3 p4雖然來自一個類的實例化對象,但它們的特征行為等都是分別獨有的,不會相互共用
Person p3 = new Person();
Person p4 = new Person();

2 成員變數和訪問修飾符

2.1 成員變數

聲明在類語句塊中,來描述對象的特征,可以是任意的變數類型,數量無限制,是否賦值根據需求確定

 enum E_SexType
{
    Man,
    Woman,
}

struct Position
{

}

class Pet//寵物類
{

}

class Person
{
    //成員變數
    public string name;
    public int age;
    public E_SexType sex;
    //如果在類中聲明和自己類型相同的成員變數時,不能對它實例化:Person grilFriend = new Person();
    public Person grilFriend;//女朋友 類可以用自己,但結構體不能,因為類在初始化時候才會分配記憶體,結構體則會一直死迴圈
    public Person[] friend;//朋友
    public Position pos;//位置
    public Pet pet = new Pet();//寵物
}

2.2 訪問修飾符

  • public 公共的,自己(內部)和別人(外部)都能訪問使用
  • private 私有的,自己才能訪問和使用,不寫預設為私有
  • protected 保護的,自己和子類才能訪問使用
  • internal 內部的,同一個程式集的對象可以訪問,程式集就是命名空間

2.3 成員變數的使用和初始值

Person p = new Person();
/* 成員變數的使用和初始值
 * 值類型、數字類型:預設為0
 * bool類型:預設為false
 * 引用類型:預設為null
 * 用default(變數類型) 關鍵字可以看預設值
 */
Console.WriteLine(default(bool));
p.age = 10;

3 成員方法

3.1 成員方法聲明

  • 聲明在類語句塊中,來描述對象的行為
  • 和函數聲明規則相同,受訪問限制符影響
  • 不需要加static關鍵字
class Person
{
    public string name;
    public int age;
    public Person[] friends;

    //成員方法
    public void Speak(string str)
    {
        Console.WriteLine($"{name}說{str}");
    }

    public bool isAudlt()
    {
        return age >= 18;
    }

    public void AddFriend(Person p)//添加朋友
    {
        if (friends == null) 
        { 
            friends = new Person[] { p }; 
        }
        else
        {
            Person[] newFriends = new Person[friends.Length + 1];
            for(int i = 0; i < friends.Length; i++)//老朋友複製到新數組
            {
                newFriends[i] = friends[i];
            }
            newFriends[newFriends.Length - 1] = p;//新加的朋友
            friends = newFriends;//地址重定向
        }
    }

3.2 成員方法的使用

必須實例化對象,再通過對象來使用,相當於對象執行了某個行為

Person p =  new Person();
p.name = "abc";
p.age = 18;
p.Speak("123");

if (p.isAudlt()) p.Speak("我成年了");//使用

Person p2 = new Person();
p2.name = "def";
p2.age = 24;

p.AddFriend(p2);

foreach(Person f in p.friends)
{
    Console.WriteLine(f.name);
}

4 構造函數、析構函數和垃圾回收

4.1 構造函數

  • 用處:在實例化對象時,會調用的用於初始化的函數,如果沒寫,預設存在一個無參的構造函數
  • 沒有返回值,函數名和類名相同,訪問許可權一般都是public
  • 如果實現了有參構造函數且沒有寫無參構造函數,那麼預設的無參構造函數就沒有了,實例化對象時只能有參實例化
class Person
{
    public string name;
    public int age;

    //類中允許無參構造函數,結構體中不行
    //無參構造函數
    public Person()
    {
        name = "tyy";
        age = 24;
    }

    //構造函數可以被重載,this代表當前調用該函數的對象本身
    public Person(string name, int age)
    {
        this.name = name; 
        this.age = age;
    }

    //構造函數特殊寫法:通過this 復用構造函數代碼
    //訪問修飾符 構造函數名(參數):this(參數1,參數2...)。先調用this()裡面的構造函數重載,再執行後面花括弧內容
    //先調用this()裡面的構造函數重載,再執行後面花括弧內容
    public Person(string name):this()
    {
        Console.WriteLine("兩個構造函數調用");
    }
}

4.2 析構函數

  • 與記憶體垃圾回收有關,C#有自動回收垃圾,幾乎不用析構函數
  • 析構函數只能在類中定義,不能用於結構體;
  • 一個類中只能定義一個析構函數;
  • 析構函數不能繼承或重載;
  • 析構函數沒有返回值;
  • 析構函數是自動調用的,不能手動調用;
  • 析構函數不能使用訪問許可權修飾符修飾,也不能包含參數。
class Person
{
    public Person()
    {
        
    }
	~Person()
    {

    }
}

4.3 垃圾回收

  • 垃圾就是記憶體中沒有被任何變數、對象引用的內容;
  • 垃圾回收(Garbage Collecyor GC)就是遍歷堆(Heap)上動態分配的所有對象,識別它們是否被引用來確定哪些對象是垃圾,哪些對象有用;
  • GC只負責Heap記憶體的垃圾回收,引用類型都是存放在Heap中,因此它們的分配釋放都通過GC來處理;
  • 系統自動管理棧(Stack)記憶體的垃圾回收,值類型通過Stack分配記憶體,它們有自己的生命周期,自動分配釋放;
  • 垃圾回收演算法:引用計數(Reference Counting)、標記清除(Mark Sweep)、標記整理(Mark Compact)、複製集合(Copy Collection)。

GC基本原理:

  • 分代演算法:把Heap記憶體分為 0代記憶體 1代記憶體 2代記憶體
  • 新分配的對象存儲在0代記憶體里,每次分配或(0 1 2代記憶體滿)可能進行垃圾回收釋放記憶體,執行下列兩步:
  • 1、標記對象:從根(靜態欄位、方法參數)開始檢測引用對象,檢測到的為可達對象,未檢測到的為不可達對象(垃圾)
  • 2、搬遷對象壓縮堆:釋放不可達對象,搬遷可達對象到下一代,修改它們的引用地址
  • 1代記憶體滿時會觸發GC釋放0 1代記憶體空間,2代滿了0 1 2代記憶體都釋放
//手動出發垃圾回收的方法,一般在Loading切換場景時才調用
GC.Collect();

5 成員屬性

  • 用於保護成員變數;
  • 為成員屬性的獲取和賦值添加邏輯處理;
  • 解決3P(public、private、protected)的局限性;
  • 成員屬性可以設置成員變數在外部的許可權為只能獲取不能修改 或 只能修改不能獲取。

5.1 成員屬性的基本語法

/*
訪問修飾符 屬性類型 屬性名
{
    get{}   讀,寫了get就一定要有一個返回值
    set{}   寫
}
*/

class Person
{
    private string name;
    private int age;
    private int money;
    private bool sex;

    //屬性的命名用帕斯卡命名法
    public string Name
    {
        get
        {
            //可以在返回之前添加一些邏輯條件
            return name;//雖然name是私有的,但是通過成員屬性也能得到它
        }
        set
        {
            //可以在設置之前添加一些邏輯條件
            //value關鍵字表示外部傳入的值
            name = value;
        }
    }

    public int Money
    {
        get
        {
            //解密處理
            return money - 5;
        }
        set
        {
            //可以進行額外的加密處理 起到安全和保密作用
            if(value < 0)
            {
                value = 0;
                Console.WriteLine("錢不能為負數");
            }
            money = value + 5;
        }
    }
}

5.2 成員屬性的使用

Person p = new Person();
p.Name = "abc";//執行set{}
Console.WriteLine(p.Name);//p.Name執行get{}

p.Money = 1000;
Console.WriteLine(p.Money);//外部看是1000元,但在記憶體裡面是1005元 起到了加密的作用

5.3 get和set的訪問修飾符

  • 預設會使用屬性的訪問修飾符;
  • 如果要加,許可權要低於屬性的訪問許可權;
  • 不能讓get和set的許可權都低於屬性的許可權,要滿足 只讀不寫 或 只寫不讀,解決3P的局限性。

5.4 get和set可以只有一個

class Person
{
    private string name;
    private int age;
    private int money;
    private bool sex;
    
    public bool Sex
    {
        get { return sex; }//創造只讀屬性,private類型的成員也能讀
    }
}

5.5 自動屬性

  • 作用:外部只讀不寫的特征
  • 如果類中有一個特征是只希望外部只讀不寫的,又沒什麼特殊處理,那麼可以直接使用自動屬性
class Person
{
    private string name;
    private int age;
    private int money;
    private bool sex;
    
    public float Height
    {
        get;
        private set;
    }
}

6 索引器

  • 索引器(Indexer) 允許一個對象可以像數組一樣使用下標的方式來訪問;
  • 當為類定義一個索引器時,該類的行為就會像一個 虛擬數組(virtual array) 一樣。可以使用數組訪問運算符 [ ] 來訪問該類的的成員。

6.1 基本語法

/*
 element-type this[int index]
{
    // get 訪問器
    get
    {
        // 返回 index 指定的值
    }

    // set 訪問器
    set
    {
        // 設置 index 指定的值
    }
}
*/
class Person
{
    private string name;
    private int age;
    private Person[] friends;

    public Person this[int index]
    {
        //可以寫邏輯條件來處理
        get
        {
            if(friends == null || friends.Length - 1 < index)
            {
                return null;
            }
            return friends[index];
        }
        set
        {
            //和成員屬性類似,value代表傳入的值
            if(friends == null)
            {
                friends = new Person[] { value };
            }else if(index > friends.Length - 1)
            {
                friends[friends.Length - 1] = value;//舉例 如果索引越界就把最後一個朋友頂掉
            }
            else friends[index] = value;
        }
    }
}

6.2 使用

Person p = new Person();
p[0] = new Person();//調用set 得到1個朋友
Console.WriteLine(p[0]);//調用get

6.3 索引器重載

class Person
{
    public int[,] array;
    
    //函數名相同,但參數類型、個數或順序不同
    public int this[int i, int j]
    {
        get
        {
            return array[i, j];
        }
        set
        {
            array[i, j] = value;
        }
    }
}

7 靜態成員

  • 關鍵字 static
  • 用static修飾的 成員變數、方法、屬性等 稱為靜態成員
  • 特點:直接用類名+點 使用,如Console.XX

7.1 自定義靜態成員

class Test
{
    //靜態成員變數
    public static float PI = 3.121592654f;
    //成員變數
    public int testInt = 100;

    //靜態成員方法
    //靜態方法不能使用非靜態成員變數,與靜態和非靜態的生命周期有關
    public static float CalcCircle(float r)
    {
        //πr²
        return PI * r * r;
    }

    //普通成員方法
    //非靜態方法可以使用靜態成員變數
    public void testFun()
    {
        Console.WriteLine("123");
        Console.WriteLine(PI);
    }
}

7.2 靜態成員使用

Console.WriteLine(Test.PI);
Console.WriteLine(Test.CalcCircle(2));

//普通的只有new了對象之後才能使用那些變數方法等
Test t = new Test();
Console.WriteLine(t.testInt);
t.testFun();

7.3 為什麼能夠不實例化對象直接使用

  • 靜態成員在程式開始運行時就會分配記憶體空間,和程式同生共死,只要使用了靜態成員,直到程式結束了它的記憶體才會被釋放;
  • 每個靜態成員在記憶體里都有唯一的一塊空間,因此類中只有一個該靜態成員的實例,在任何地方使用都是改變那一個。

7.4 常量const和靜態變數static

相同點:都可以使用類名+點使用
不同點:

  • const必須初始化且不能修改;
  • const只修飾變數;
  • const一定寫在訪問修飾符後面,static可前可後。

8 靜態類和靜態構造函數

8.1 靜態類

用 static 修飾的類,只能包含靜態成員,不能被實例化
作用:

  • 將常用的靜態成員寫在靜態類中,方便使用;
  • 靜態類不能被實例化,體現工具類的唯一性 如Console就是一個靜態類,它寫在命名空間System裡面。
static class TestClass
{
    public static int testIndex = 0;
    public static void testFun()
    {

    }
}

8.2 靜態構造函數

  • 在構造函數加上static修飾;
  • 不能使用訪問修飾符,不能有參數,只會自動調用一次;
  • 作用:在靜態構造函數中初始化靜態變數。

8.2.1 靜態類 中的 靜態構造函數

static class StaticClass
{
    //第一次使用類時,類裡面的靜態成員自動調用一次
    public static int testInt = 100;
    public static int testInt2 = 100;

    static StaticClass()
    {
        Console.WriteLine("靜態構造函數");
    }
}

8.2.2 普通類 中的 靜態構造函數

class Test
{
    public static int testInt = 200;
    static Test()
    {
        Console.WriteLine("靜態構造");
    }
    public Test()
    {
        Console.WriteLine("普通構造");
    }
}

8.3 使用

Console.WriteLine(StaticClass.testInt);

//第一次使用類時,類裡面的靜態成員自動調用一次
Console.WriteLine(Test.testInt);
//普通構造在new的時候調用
Test t = new Test();

9 拓展方法

為現有 非靜態 的 <變數類型> 添加新方法

作用:

  • 提升程式的拓展性;
  • 不需要在對象中重寫方法或通過繼承來添加方法;
  • 為別人封裝的類型寫額外的方法。

特點:

  • 一定寫在靜態類中
  • 一定是個靜態函數
  • 第一個參數為拓展目標
  • 第一個參數用this修飾

9.1 基本語法

訪問修飾符 static 返回值 函數名(this 要拓展的類名 參數名,參數類型 參數名,......)

static class Tools
{
    //為int拓展了一個成員方法,int裡面是沒有SpeakValue方法的
    //value 代表使用該方法的 實例化對象
    public static void SpeakValue(this int value)
    {
        //拓展方法的邏輯
        Console.WriteLine("為int拓展的方法" + value);
    }

    public static void SpeakStringInfo(this string str, string str2, string str3)
    {
        Console.WriteLine("為string拓展的方法,調用方法的對象是:" + str);
        Console.WriteLine("傳的參數:" + str2 + str3);
    }

    //為自定義的類型Test拓展方法
    public static void Fun3(this Test t)
    {
        Console.WriteLine("為Test拓展的方法");
    }
}

class Test
{
    public int i = 10;
    public void Fun1()
    {
        Console.WriteLine("123");
    }
    public void Fun2()
    {
        Console.WriteLine("456");
    }
}

9.2 使用

int i = 10;
i.SpeakValue();

string s = "ABC";
s.SpeakStringInfo("a ", "b");

Test t = new Test();
t.Fun3();

10 運算符重載

10.1 基本概念

作用:讓自定義的類和結構體能夠使用運算符進行運算,關鍵字 operator

特點:

  • 一定是一個公共的靜態方法;
  • 返回值寫在operator前;
  • 邏輯處理自定義。

註意:

  • 條件運算符需要成對實現;
  • 一個符號可以多個重載;
  • 不能使用ref和out。

10.2 可重載和不可重載的運算符

註意:運算符需要兩個參數還是一個參數

可重載的:

  • 算數運算符:+ - * / % ++ --
  • 邏輯運算符:!
  • 位運算符:| & ^ ~ << >>
  • 條件運算符:> < >= <= == != ,需要成對出現:(大於,小於)(大於等於,小於等於)(等於,不等於)

不可重載的:

  • 邏輯與(&&) 邏輯或(||)
  • 索引符 []
  • 強轉運算符 ()
  • 特殊運算符
  • 點. 三目運算符

10.3 基本語法

public static 返回類型 operator 運算符(參數列表)

class Point
{
    public int x;
    public int y;

    //重載‘+’成為類Point的加法
    public static Point operator +(Point p1, Point p2)
    {
        Point p = new Point();
        p.x = p1.x + p2.x;
        p.y = p1.y + p2.y;
        return p;
    }

    //可以有多個重載
    public static Point operator +(Point p1, int value)
    {
        Point p = new Point();
        p.x = p1.x + value;
        p.y = p1.y + value;
        return p;
    }
}

10.4 使用

Point p1 = new Point();
p1.x = 1;
p1.y = 1;
Point p2 = new Point();
p2.x = 2;
p2.y = 2;

Point p3 = p1 + p2;

Point p4 = p3 + 2;

11 內部類和分部類

11.1 內部類

  • 在一個類中再申明一個類
  • 使用時要用包裹者點出內部類
  • 實現類之間的親密關係
class Person
{
    public int age;
    public string name;
    public Body body;
    public class Body
    {
        Arm leftArm;
        Arm rightArm;
        class Arm
        {

        }
    }
}

//使用
Person p = new Person();
Person.Body body = new Person.Body();

11.2 分部類

把一個類分成幾部分申明。
作用:分部描述一個類,增加程式的拓展性,關鍵字 partial

11.3 分部方法

將方法的聲明和實現分離。

特點:

  • 不能加訪問修飾符,預設私有
  • 只能在分部類中聲明
  • 返回值只能是void
  • 可以有參數,但不用out關鍵字
//分部類
partial class Student
{
    public string name;
    public bool sex;

    //分部方法
    partial void Move();
}

partial class Student
{
    public int number;

    partial void Move()
    {
        //實現分部方法邏輯
        throw new NotImplementedException();
    }

    public void Speak(string str)
    {

    }
}

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

-Advertisement-
Play Games
更多相關文章
  • PowerPlume是PowerBuilder深度創新的擴展開發框架(免費商用)。 它的三個主要特色是一、原創功能;二、零改動相容(非侵入性);三、極簡介面設計。 ...
  • 記錄人生第一次重裝系統之後的數據恢復過程,包括桌面恢復、常用軟體下載和屬性修改、vscode插件、zotero數據恢復、onenote筆記數據恢復,讓重裝系統的你不用慌。 ...
  • 發文原因 很多初學者都使用 cargo new [project_name] 來創建項目,並直接在 main.rs 文件中實現所有功能。 這樣是不合理的,並不符合我們 cargo 的開發規範。 下麵將簡單的介紹一下 rust project 中的文件結構。 cargo new [project_na ...
  • 其他 1 命名空間 命名空間用來組織和重用代碼的,命名空間就像一個工具包,類就像工具。 1.1 使用 namespace MyGame { class GameObject { } } namespace MyGame//命名空間可以分開寫 { class Player : GameObject { ...
  • 多態 1 認識多態 1.1 基本概念 多態是同一個行為具有多個不同表現形式或形態的能力,意味著有多重形式。在面向對象編程範式中,多態性往往表現為"一個介面,多個功能"。 在 C# 中,每個類型都是多態的,因為包括用戶定義類型在內的所有類型都繼承自 Object。 多態性分為靜態的和動態多態。在靜態多 ...
  • 微軟發佈 C# async/await 非同步語法功能已經好久了,但是目前來看使用並不廣泛。本人經過實踐在開發過程中使用 async/await 一路到底確實很爽,而且也沒有啥問題。但是在面對舊項目變更要使用些功能的時候可能會遇到同步方法調用非同步方法的情況,本人在這種情況就發生調用沒有響應的問題,並作 ...
  • 繼承 繼承主要實現重用代碼,來節省開發時間。 1 繼承基本概念 一個類B繼承一個類A,被繼承的類A稱為 父類、基類、超類,繼承的類B稱為 子類、派生類。 子類會繼承父類的所有成員 子類擁有父類的所有特征和行為 子類可以有自己的特征行為 C#中允許子類和父類存在同名的成員,但不建議使用 特點: 單根性 ...
  • 本文主要介紹了.net7簡單使用NPOI讀取Excel表格。NPOI是指構建在POI 3.x版本之上的一個程式,NPOI可以在沒有安裝Office的情況下對Word或Excel文檔進行讀寫操作。NPOI這個老牌控制項不錯,只需要很少的代碼就可以實現,下麵是一步一步實現,希望對你有參考價值。 一、環境準 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...