C#核心 面向對象--封裝 用程式來抽象現實世界,(萬物皆對象)來編程實現功能。 三大特性:封裝、繼承、多態。 類與對象 聲明位置:namespace中 樣式:class 類名{} 命名:帕斯卡命名法(首字母大寫) 實例化對象:根據類來新建一個對象。Person p=new Person(); 成員 ...
C#核心
面向對象--封裝
用程式來抽象現實世界,(萬物皆對象)來編程實現功能。
三大特性:封裝、繼承、多態。
類與對象
聲明位置:namespace
中
樣式:class 類名{}
命名:帕斯卡命名法(首字母大寫)
實例化對象:根據類來新建一個對象。Person p=new Person();
成員變數
- 聲明在類語句塊中
- 用來描述對象的特征
- 可以是任意變數類型
- 數量不做限制
- 是否賦值根據需求決定
enum E_SexType
{
Man,
Woman
}
struct Position{}//位置結構體
class Pet{}//寵物類
//類中的成員變數
class Person
{
public string name="TonyChang";//區別於結構體--可以預設賦初始值
public int age=21;
public E_SexType sex;
public Person bestFriend;//區別於結構體---類中可以有同類的成員類型(本質是因為類屬於引用類型,但不可以實例化,防止反覆new,陷入死迴圈)
public Position pos;
public Pet pet;
}
成員類型的預設值:
值類型的:數字的為0,bool
類型的false
引用類型:null
查看(int類型)預設值:default(int)
補充:class屬於引用類型,其中的值類型也放置在堆中。
成員方法
- 聲明在類語句塊中
- 用來描述對象行為
- 其返回值參數不做限制
- 數量不做限制
- 帕斯卡命名法(首字母大寫)
成員方法只有在實例化之後才可以使用調用。具體的一個對象的行為(方法),必須具體的對象調用。
//成員方法
class Person
{
public string name;
public int age;
public void Speak()
{
Console.WriteLine("你好!");
}
}
//成員方法的使用
Person p=new Person;
p.Speak();
構造函數和析構函數
預設有一個無參構造函數,而類中可以允許自己聲明無參構造函數,而結構體不行。
一旦有自定義的構造函數,預設的無參構造函數則失效!
構造函數:
- public修飾
- 無返回值,名字和類名相同
class Person
{
public string name;
public int age;
//構造函數
public Person()
{
name="TonyChang";
age=21;
}
//此時先調用age參數的構造函數 然後再調用兩個參數的構造函數
public Person(string name,int age):this(age)
{
this.name=name;
this.age=age;
}
public Person(string name)
{
this.name=name;
}
public Person(int age)
{
this.age=age;
}
}
特殊的構造函數,在調用該函數之前先調用this的無參構造函數。
public Person(int age):this()
{
this.age=age;
}
析構函數:
由於C#中有自動的垃圾回收機制,一般不使用析構函數。
析構函數是當垃圾真正被回收時候才會調用。
~Person(){}
//析構函數
成員屬性:
用於保護成員變數,為成員屬性的獲取和賦值添加邏輯處理。
//成員屬性 帕斯卡命名法
class Person
{
private string name;
public string Name
{
get{
return name;
}
set{
name=value;
}
}
private int age;
public int Age
{
//不可以獲得年齡(或者刪除set設置則可表明也無法獲取age)
private get=>age;
//可以設置年齡
set
{
age=value;
}
}
//額外:
//自動成員屬性 (對於沒有特殊需要的成員)
public float Height
{
get;
set;
}
}
索引器
可以讓對象像數組一樣通過索引來訪問其中的元素。
註意:結構體中也支持索引器。
//索引器
class Person
{
private string name;
private int age;
private Person[] friends;
private int[,] arry;
//索引器
public Person this[int index]
{
get
{
return friends[index];
}
set
{
//此處可以寫一些控制邏輯
friends[index]=value;
}
}
//索引器的重載
public int this[int i,int j]
{
get
{
return array[i,j];
}
set
{
array[i,j]=value;
}
}
}
//使用
Person p=new Person();
p[0]=new Person();//可以像數組一樣進行訪問
靜態成員
static
修飾的成員變數/方法為靜態成員。
靜態成員歸屬為類,即不用初始化就可以使用的類的成員。
靜態成員函數中不可以使用非靜態成員,非靜態成員函數中可以使用靜態成員函數。
本質:在程式運行開始時,檢查到靜態成員,會再特定的區域為其開闢記憶體空間來存儲。所以說靜態成員與程式共生死。因為生命周期不同,所以靜態成員函數中不可以使用非靜態成員。
使用:全局性,穩定不變的常量。例如固定的數值 Π,重力加速度g等包括固定的計算方法,可以供全局成員來訪問使用。但是靜態過多會占用記憶體,引發GC。
常量與靜態成員
相同點:都可以通過類名點來使用
不同點:
const
必須初始化,不能修改,而static可以const
只能修飾變數,而static可以修飾很多const
一定是寫在訪問修飾符的後面,static則無此要求
靜態類與靜態構造函數
用 static修飾的類為靜態類,往往來作為工具類。例如System中的Console類。只能包含靜態成員,不能實例化。
靜態構造函數 :在靜態構造函數中初始化靜態成員變數。
- 靜態類和普通類中均可以有
- 不能使用訪問修飾符
- 不能有參數
- 只會自動調用一次
//靜態構造函數
static class Test
{
public static int testInt=100;
public static float testFloat=20.5f;
static Test()
{
//靜態構造函數
Console.WriteLine("自動調用了!");
}
}
class NormalTest
{
public static int i=5;
//首次使用靜態成員時候 自動調用一次
//靜態成員函數
static NormalTest()
{
Console.WriteLine("靜態構造函數");
}
public NormalTest()
{
Console.WriteLine("非靜態成員函數");
}
}
拓展方法
拓展方法為現有的非靜態變數類型添加新方法。
作用:
- 提升程式的拓展性
- 不需要再對對象中重新寫方法
- 不需要繼承來添加方法
- 為別人封裝的類型添加額外的方法
特點:
- 一定寫在靜態類中
- 一定是個靜態函數
- 第一個參數為拓展目標(為誰而拓展)
- 第一個參數用this修飾
//拓展方法
static class expendTool
{
//為int添加拓展方法
public static void SpeakValue(this int value)
{
Console.WriteLine("這是int的拓展方法,int的數值為{0}",value);
}
//為string拓展方法
public static void SpeakStringInfo(this string str,string name,string info)
{
Console.WriteLine("這是string的拓展方法,string的數值為{0},該拓展方法由{1}編寫,拓展內容為{2}",str,name,info);
}
}
class Program
{
static void Main(string[] args)
{
int i = 5;
i.SpeakValue();
string ss = "原始字元串";
ss.SpeakStringInfo("TonyChang", "附加字元串");
}
}
註意:如果拓展方法名稱與自身現有的方法名稱相同,則只會調用自身的方法,不會調用拓展的方法。
運算符重載
關鍵字 operator
特點:1. 一定是一個公共的靜態成員方法
- 返回值寫在operator前
- 邏輯處理自定義
作用:可以使自定義的數據類型來完成後相同意義的運算。
註意:
- 條件運算符要成對實現(有>必須有<)
- 一個符號可以有多個重載
- 不能使用ref與out
//運算符重載
class Point
{
public int x;
public int y;
public Point(int x,int y)
{
this.x=x;
this.y=y;
}
//重載+運算符
//參數列表中必須要有自己的類別出現
public static Point operator +(Point p1,Point p2)
{
Point sum=new Point();
sum.x=p1.x+p2.x;
sum.y=p1.y+p2.y;
return sum;
}
}
class Program
{
static void Main(string[] args)
{
Point p1=new Point(1,1);
Point p2=new Point(2,2);
Point p3=P1+p2;
Console.WriteLine(p3.x);
}
}
補充:大部運算符可以重載,邏輯運算符中只可以允許重載 邏輯非!
不能重載的運算符有:
&& || 索引符[] 強制轉換符號() 特殊運算符 點. 三目運算符?:
*內部類和分部類(瞭解)
//內部類
class Person
{
public class Body
{
class Arm
{
}
}
}
class Program
{
static void Main(string[] args)
{
Person.Body body=new Person.Body();
}
}
//分佈類
//分佈類可以分佈在不同的腳本文件中
//分佈類的訪問修飾符要一致
partial class Student
{
public string name;
public bool sex;
partial void Speak();
}
partial class Student
{
public int age;
public string stuId;
partial void Speak()
{
Console.WriteLine("分佈方法的具體實現");
}
}
垃圾回收機制
垃圾回收,英文簡稱GC(Garbage Collector)
垃圾回收過程:遍歷堆(Heap)上的動態分配的所有對象
通過識別它們是否被引用來確定其是否是垃圾。垃圾是指沒有引用所指引的對象、變數,需要被回收釋放掉占用的記憶體空間。
垃圾回收演算法:
引用計數、標記清除、標記整理、複製集合。
註意:垃圾回收只回收heap堆上的 棧中的記憶體由系統管理
回收機制: 三代記憶體
0代記憶體 1代記憶體 2代記憶體
-
每一代記憶體滿掉之後便會清理垃圾
-
高代連鎖:1代清理會連帶0代清理,2代清理連帶0代和1代
-
清理完垃圾之後,非垃圾內容搬遷到下一代中(0代將非垃圾轉移到1代記憶體,
1代記憶體將非垃圾轉移到2代記憶體)所以2代記憶體存儲的較為老的對象實例,還包括大的對象
一般是85kb以上的對象
-
0代1代的讀取速度要高於1代,分配記憶體位置優先0代>1代>2代
手動GC:
GC.Collect();
一般在場景載入時候進行GC。
面向對象--繼承
繼承者(子類)繼承父類(基類、超類)的特性,同時也可以有自己獨特的方法性質。
只能單繼承。子類只能由一個父類。
繼承特性:單根性、傳遞性。
//繼承
//老師類
class Teacher
{
//姓名
public string name;
//職工號
protected int number;
//介紹名字
public void SpeakName()
{
number = 10;
Console.WriteLine(name);
}
}
//教學老師繼承老師類
class TeachingTeacher : Teacher
{
//科目
public string subject;
//介紹科目
public void SpeakSubject()
{
number = 11;
Console.WriteLine(subject + "老師");
}
}
//語文老師繼承教學老師類
class ChineseTeacher:TeachingTeacher
{
public void Skill()
{
Console.WriteLine("一行白鷺上青天");
}
}
class Program
{
static void Main(string[] args)
{
TeachingTeacher tt = new TeachingTeacher();
tt.name = "汪老師";
//tt.number = 1;
tt.SpeakName();
tt.subject = "Unity";
tt.SpeakSubject();
ChineseTeacher ct = new ChineseTeacher();
ct.name = "張老師";
//ct.number = 2;
ct.subject = "語文";
ct.SpeakName();
ct.SpeakSubject();
ct.Skill();
}
}
里氏替換原則
父類容器裝在子類對象。(任何父類出現的地方,子類都可以替代)
class GameObject
{
}
class Player:GameObject
{
public void PlayerAtk()
{
Console.WriteLine("玩家攻擊");
}
}
class Monster:GameObject
{
public void MonsterAtk()
{
Console.WriteLine("怪物攻擊");
}
}
class Boss:GameObject
{
public void BossAtk()
{
Console.WriteLine("Boss攻擊");
}
}
class Program
{
static void Main(string[] args)
{
//里氏替換原則
Gameobjet player=new Player();
Gameobjet monster=new Monster();
Gameobjet boss=new Boss();
//is 和 as
if(player is Player)
{
(player as Player).PlayerAtk();
}
}
}
is和as
is是判斷一個對象是否為指定類型對象,返回值為true則為真,不是則為false
as用來將一個對象轉換為指定類型對象,返回值為指定類型對象,若轉換失敗則返回null
繼承中的構造函數
子類構造函數調用之前,先執行父類的構造函數。(爺爺-->父親-->子類)
所以要保證父類的構造函數(尤其為無參構造函數)
- 保證父類的無參構造函數
- 通過base調用指定的有參構造函數
//繼承中的構造函數
class Father
{
//父類的無參構造函數很重要!
public Father()
{
}
public Father(int i)
{
Console.WriteLine("Father的有參構造");
}
}
class Son:Father
{
public Son(int i):base(i)
{
//構造函數
}
}
萬物之父--裝箱和拆箱
object 是所有類型的基類,
作用:
- 可以利用里氏替換原則,用父類裝子類
- 可以用來表示不確定類型,作為函數的參數類型
裝箱:
用object存值類型。本該在棧中數值轉移到堆上
拆箱
將object轉換為值類型,將堆上的值類型轉移到棧上(配合 is和as 使用)
優點:統一對象類型(里氏替換原則),方便對不同類型對象數值的管理
缺點:消耗性能,
//裝箱拆箱
int i=5;
object obj=i;//裝箱
i=(int)obj;//拆箱
*密封類(瞭解)
使用sealed關鍵字修飾的類,不可以被派生。(結扎了!)
面向對象--多態
V: virtual(虛函數)
O: override(重寫)
B: base(父類)
讓繼承同一父類的子類們在執行相同方法有不同的表現與狀態。
就是說,繼承是一脈相承父類的品質,而多態是由自己的個性,儘管做的和父輩的事情相同。
解決的問題:
class Father
{
public void SpeakName()
{
Console.WriteLine("Father的方法");
}
}
class Son:Father
{
public new void SpeakName()
{
Console.WriteLine("Son的方法");
}
}
class Program
{
static void Main(string[] args)
{
#region 解決的問題
Father f = new Son();
f.SpeakName();//調用的是父親的方法
(f as Son).SpeakName();//調用的是兒子的方法
#endregion
}
}
使用多態來保證(繼承類)一個類方法的獨立性
class GameObject
{
public string name;
public GameObject(string name)
{
this.name = name;
}
//虛函數 可以被子類重寫
public virtual void Atk()
{
Console.WriteLine("游戲對象進行攻擊");
}
}
class Player:GameObject
{
public Player(string name):base(name)
{
}
//重寫虛函數
public override void Atk()
{
//base的作用
//代表父類 可以通過base來保留父類的行為
base.Atk();
Console.WriteLine("玩家對象進行攻擊");
}
}
抽象類與抽象方法
抽象類不可以被實例化
abstract class Thing{
public string name;
}
抽象方法:沒有方法體的純虛方法,繼承的子類必須實現純虛方法。(子類必須重寫該方法,子類的子類不必強制實行,但也可以繼續重寫。)
抽象方法與virtual(虛函數)方法區別:
- 抽象方法只能在抽象類中出現,沒有方法體,子類必須重寫實現
- 虛函數則有方法體,可在普通類中出現,由子類選擇性的實現
abstract class Fruits
{
public string name;
//抽象方法 是一定不能有函數體的
public abstract void Bad();
public virtual void Test()
{
//可以選擇是否寫邏輯
}
}
class Apple : Fruits
{
public override void Bad()
{
}
//虛方法是可以由我們子類選擇性來實現的
//抽象方法必須要實現
}
介面(重要)
概念:介面是行為的抽象規範
關鍵字:interface
聲明規範:
- 不能包含成員變數
- 只能包含方法、屬性、索引器、事件
- 成員不能被實現
- 成員可以不用寫訪問修飾符,預設為public,不能是private
- 介面不能繼承類,但是可以繼承另一個介面
使用規範:
- 類可以繼承多個介面
- 類繼承介面,必須實現介面中所有成員
特點:
- 和類的聲明相似
- 介面就是用來繼承的
- 介面不能被實例化,可以作為容器存儲對象(里氏替換原則,父類裝子類)
介面是抽象行為的”基類“
//介面的聲明
//命名規範 I+帕斯卡命名法
interface IFly
{
void Fly();//方法
string Name//屬性
{
get;
set;
}
int this[int index]//索引器
{
get;
set;
}
event Action doSomthing;//事件委托
}
介面的使用---類的繼承
- 一個類只能繼承一個基類,但是可以繼承多個介面
- 繼承介面之後,必須實現其中的內容
//介面的使用
class Animal
{
}
class Person:Animal,IFly
{
//實現介面方法也可以加virtual來實現
public virtual void Fly()
{
}
public string Name
{
set;
get;
}
public int this[int index]
{
get
{
return 0;
}
set
{
}
}
public event Action doSomething;
}
介面的使用---介面的繼承
介面繼承基類介面之後,不需要實現介面中的內容(抽象繼承抽象,還是抽象)
等到最後類來具體實現
//介面繼承介面
interface IWalk
{
void Walk();
}
interface IMove:IFly,IMove
{
}
//必須實現所有相關的
//繼承來的抽象內容(介面,介面的父介面中的成員)
class Test:IMove
{
public int this[int index] {
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
public string Name {
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
public event Action doSomthing;
public void Fly()
{
throw new NotImplementedException();
}
public void Walk()
{
throw new NotImplementedException();
}
}
顯示實現介面
//介面的使用--當作容器 父類裝子類
interface IAtk
{
void Atk();
}
interface ISuperAtk
{
void Atk();
}
//顯示實現介面
class Player : IAtk, ISuperAtk
{
//遇到相同方法名字
//顯示實現介面 就是用 介面名.行為名 去實現
void IAtk.Atk()
{
}
void ISuperAtk.Atk()
{
}
public void Atk()
{
}
}
class Progarm
{
static void Main(string[] args)
{
IFly f = new Person();
//里氏替換原則
IMove im = new Test();
IFly ifly = new Test();
IWalk iw = new Test();
IAtk ia = new Player();
ISuperAtk isa = new Player();
ia.Atk();
isa.Atk();
Player p = new Player();
(p as IAtk).Atk();//IAtk的
(p as ISuperAtk).Atk();//ISuperAtk的Atk
p.Atk();//自己的Atk
}
}
*密封方法(瞭解)
sealed 修飾的重寫方法,子類不會被重寫方法
其它關聯知識點
命名空間namespace
- 命名空間是個工具包,用來管理類
- 不同命名空間中允許由同名類
- 不同命名空間相互使用時,using引用命名空間 或者指明出處
- 命名空間可以包裹命名空間
萬物之父Object中的方法
string
-
string 本質是char[]數組 可以有
string ss="Tony",char[0]='T'
-
字元串的拼接
-
正向查找字元的位置 IndexOf()
-
反向查找字元串的位置 LastIndexOf()
-
移除指定位置後的字元 Remove(index)//註意接受返回值
-
字元串的替換
-
大小寫轉換
-
字元串的截取
-
字元串的切割 str.Split(',');按照,切割
StringBuilder
字元串頻繁拼接使用StringBuilder,不會再次頻繁的創建新的對象,減少垃圾產生。
容量問題:初始時候本身有一定容量,在容量允許範圍內,直接存儲。
超過容量之後,會以2倍大小擴容。相對於string每一次更改便會新建對象,可減少垃圾產生。
//StringBuilder
StringBuilder str=new StringBuilder("My Name is Tony");
//獲取容量
str.Capacity;
//增加
str.Append("Chang");
str.AppendFormat("{0}{1}",123,456);
//插入
str.Insert(0,"Hello");
//刪除
str.Remove(0,10);
//清空
str.Clear();
//查
str[1];
//替換
str.Replace("Name"."name");
//重新賦值
str.Clear();
str.Append("Hello World");
//equals
if(str.Equals("123456"))
{
}
String 還是StringBuilder?
String的方法種類較多,使用更加方便和靈活,但性能上不如StringBuilder,不如StringBuilder產生垃圾少
需要頻繁修改的字元串選用StringBuilder更好些。
如何優化記憶體?
- 節約記憶體
- 少new對象 少產生垃圾
- 合理使用static
- 合理使用String與StringBuilder
- 減少GC產生
結構體與類的區別
- 存儲位置 結構體是值類型,存儲在棧中 類是引用類型,存儲在堆中
- 結構體中的成員變數不可以賦初始值,類中可以
- 結構體具備封裝特性但是不具備繼承和多態 而類都具有
- 結構體不具備繼承特性,所以不可以使用protected保護修飾符修飾
- 結構體聲明有參構造之後,無參構造不會被頂掉
- 結構體不能聲明析構函數,而類可以
- 結構體需要在構造函數中初始化所有成員變數,而類隨意
- 結構體不能被static修飾,不存在靜態的結構體,而類隨意
- 結構體不能在內部聲明與自己一樣的結構體變數(會無限創建...棧溢出),類可以(因為是引用)
- 結構體可以繼承介面(不可以繼承類、結構體)
如何選擇結構體和類:
- 如果想要用繼承和多態時,直接淘汰結構體,如玩家、怪物
- 存儲的對象是數據的集合時候,優先考慮結構體,如向量、坐標等
- 從本質上考慮,如果經常要改變賦值對象,原有的數值不想跟著改變的,選用結構體(值類型,複製拷貝,不影響本身),如坐標、向量、旋轉角等
抽象類與介面的區別
相同點:
- 都可以被繼承
- 都不可以直接實例化
- 都可以包含方法的聲明
- 子類必須實現未實現的方法
- 都遵循里氏替換原則(父類裝子類)
區別:
- 抽象類中可以有構造函數,而介面不可以
- 抽象類只能被單一繼承,介面可以被繼承多個
- 抽象類中可以有成員變數,介面中不能
- 抽象類中可以有聲明成員方法,虛方法,抽象方法,靜態方法而介面中只能聲明沒有實現的抽象方法
- 抽象類方法可以使用訪問修飾符;介面中建議不寫,預設為public
如何選擇抽象類與介面
表示對象的選用抽象類,表示行為拓展的用介面。不同對象的相同行為,我們可以抽象出行為,用介面來實現。
面向對象的七大原則
總目標:高內聚、低耦合
減少類內部對其它類的調用,減少模塊與模塊之間的交互複雜度。
- 單一職責原則(一個類專註於一個功能)
- 里氏替換原則(父類可以裝子類)
- 開閉原則(對拓展開放,對修改關閉,要保持開放和擴展,減少修改)
- 依賴倒轉原則(依賴於抽象,不依賴於抽象具體)
- 迪米特原則(最少知識原則,不要和陌生人說話)
- 介面隔離原則(一個介面不應該提供過多的功能,)
- 合成復用原則(儘量使用組合復用實現功能,減少繼承高耦合行為)