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