一. 值類型和引用類型
1. 前言
(1). 分類
值類型包括:布爾類型、浮點類型(float、double、decimal、byte)、字元類型(char)、整型(int、long、short等)、枚舉(entum)、結構體(struct)。
引用類型:數組、字元串(string)、類、接口、委托(delegate)。
(2).記憶體存儲
值類型資料存放在棧stack中, 引用類型位址存放棧stack中,資料存放在堆heap中。
值類型變量聲明後,不管是否指派,都會在在棧中配置設定記憶體空間。引用類型聲明時,隻在棧中配置設定記憶體,用于存放位址,并沒有在堆上配置設定記憶體空間。
2. 對象的傳遞
(1). 将值類型的變量指派給另一個變量,會執行一次指派,指派變量包含的值。
(2). 将引用類型的變量指派給另一個引用類型變量,它複制的是引用對象的記憶體位址,在指派後就會多個變量指向同一個引用對象執行個體。
代碼分享:
Console.WriteLine("------------------------下面是值類型和引用類型指派-----------------------------");
//值類型指派
int a = 0;
int b = a;
Console.WriteLine($"預設值: a={a},b={b}");
a = 1;
Console.WriteLine($"修改a的值後: a={a},b={b}");
b = 2;
Console.WriteLine($"修改b的值後: a={a},b={b}");
//引用類型指派
Student stu1 = new Student();
stu1.age = 20;
Student stu2 = stu1;
Console.WriteLine($"預設值: stu1.age={ stu1.age}, stu2.age={stu2.age}");
stu1.age = 30;
Console.WriteLine($"修改stu1.age的值後:stu1.age={ stu1.age}, stu2.age={stu2.age}");
stu2.age = 40;
Console.WriteLine($"修改stu2.age的值後: stu1.age={ stu1.age}, stu2.age={stu2.age}");
運作結果:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiInVGcq5iM3UGNwM2MhJWNkZGOlNTZyU2M5MDMmdDZxUWZ4UjZk9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpeg)
3. 參數按值傳遞
(1). 對于值類型(age),傳遞的是該值類型執行個體的一個副本,是以原本的值age并沒有改變。
(2). 對于引用類型(Student stu),傳遞是變量stu的引用位址(即stu對象執行個體的記憶體位址)拷貝副本,是以他們操作都是同一個stu對象執行個體。
代碼分享:
{
Console.WriteLine("------------------------下面是參數按值傳遞-----------------------------");
//值類型按值傳遞
int age1 = 60;
Utils.AddAge1(age1);
Console.WriteLine($"age={age1}");
//引用類型按值傳遞
Student stu2 = new Student();
stu2.age = 100;
Utils.ReduceAge1(stu2);
Console.WriteLine($"age={stu2.age}");
}
public class Utils
{
public static void ReduceAge1(Student stu)
{
stu.age -= 10;
Console.WriteLine($"ReduceAge age={stu.age}");
}
public static void AddAge1(int age)
{
age += 10;
Console.WriteLine($"AddAge age ={age}");
}
public static void ReduceAge2(ref Student stu)
{
stu.age -= 10;
Console.WriteLine($"ReduceAge age={stu.age}");
}
public static void AddAge2(ref int age)
{
age += 10;
Console.WriteLine($"AddAge age ={age}");
}
}
運作結果:
4. 參數按引用類型傳遞
不管是值類型還是引用類型,可以使用ref或out關鍵字來實作參數的按引用傳遞。ref或out關鍵字告訴編譯器,方法傳遞的是參數位址,而非參數本身。在按引用傳遞時,方法的定義和調用都必須顯式的使用ref或out關鍵字,不可以省略,否則會引起編譯錯誤。
代碼分享:
{
Console.WriteLine("------------------------下面是參數按引用傳遞-----------------------------");
//值類型按值傳遞
int age1 = 60;
Utils.AddAge2(ref age1);
Console.WriteLine($"age={age1}");
//引用類型按值傳遞
Student stu2 = new Student();
stu2.age = 100;
Utils.ReduceAge2(ref stu2);
Console.WriteLine($"age={stu2.age}");
}
public class Utils
{
public static void ReduceAge1(Student stu)
{
stu.age -= 10;
Console.WriteLine($"ReduceAge age={stu.age}");
}
public static void AddAge1(int age)
{
age += 10;
Console.WriteLine($"AddAge age ={age}");
}
public static void ReduceAge2(ref Student stu)
{
stu.age -= 10;
Console.WriteLine($"ReduceAge age={stu.age}");
}
public static void AddAge2(ref int age)
{
age += 10;
Console.WriteLine($"AddAge age ={age}");
}
}
運作結果:
更多C++背景開發技術點知識内容包括C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,流媒體,音視訊開發,Linux核心,TCP/IP,協程,DPDK多個進階知識點。
C/C++Linux伺服器開發進階架構師/C++背景開發架構師免費學習位址
【文章福利】另外還整理一些C++背景開發架構師 相關學習資料,面試題,教學視訊,以及學習路線圖,免費分享有需要的可以點選領取
5. string和其它引用類型的差別
(1). 在string字元串,一開始s1位址指向是ypf,因為s2=s1,是以s2位址也同樣指向ypf;當s1再次指派lmr時,堆中就會開辟出資料lmr,而且ypf沒有消失,沒有被覆寫。s1位址就指向lmr,s2位址還是原來的ypf。
(2). 在引用類型數組上,一開始arry1和arry2的位址都指向{1,2,3},當給arry1進行資料更改時,由于是引用類型,是以在{1,2,3}上面進行更改,就會對arry2進行覆寫。
代碼如下:
{
Console.WriteLine("------------------------下面是string和其它引用類型的差別-----------------------------");
//string類型的測試
string s1 = "ypf";
string s2 = s1;
Console.WriteLine($"s1={s1},s2={s2}");
//修改s1的值
s1 = "lmr";
Console.WriteLine($"s1={s1},s2={s2}");
//其它引用類型的測試
int[] arry1 = new int[] { 1, 2, 3 };
int[] arry2 = arry1;
Console.WriteLine($"預設值:arry1[0]={arry1[0]},arry1[1]={arry1[1]},arry1[2]={arry1[2]}");
Console.WriteLine($"預設值:arry2[0]={arry2[0]},arry2[1]={arry2[1]},arry2[2]={arry2[2]}");
arry1[2] = 0;
Console.WriteLine($"修改後:arry1[0]={arry1[0]},arry1[1]={arry1[1]},arry1[2]={arry1[2]}");
Console.WriteLine($"修改後:arry2[0]={arry2[0]},arry2[1]={arry2[1]},arry2[2]={arry2[2]}");
}
運作效果:
string變化圖如下:
Array類型變化圖如下:
6. 拆箱和裝箱
裝箱是值類型向引用類型轉換時發生的,拆箱是引用類型向值類型轉換時發生的。裝箱是隐式的,拆箱是顯式的
代碼分享:
{
Console.WriteLine("------------------------下面是拆箱和裝箱-----------------------------");
int a = 123;
object obj = a; //裝箱(隐式)
Console.WriteLine($"裝箱後的結果obj={obj}");
int b = (int)obj; //拆箱(顯式)
Console.WriteLine($"拆箱後的結果b={b}");
}
運作效果
7. 總結
(1). 值類型有更好的效率,但不支援多态,适合用作存儲資料的載體。而引用類型支援多态,适合用于定義程式的行為。
(2). 引用類型可以派生新的類型,而值類型不能。
二. 深拷貝和淺拷貝
1. 淺拷貝
建立一個新對象,這個對象有着原始對象屬性值的一份精确拷貝。如果屬性是值類型和string類型,拷貝的就是基本類型的值;如果屬性是引用類型,拷貝的就是記憶體位址(string類型除外),是以修改其中一個對象,就會影響到另一個對象。
2. 深拷貝
将一個對象從記憶體中完整的拷貝一份出來,從堆記憶體中開辟一個新的區域存放新對象,且修改新對象和原對象的修改不會互相影響.
3. 二者差別
最根本的差別在于是否真正擷取一個對象的複制實體,而不是引用,假設B複制了A,修改A的時候,看B是否發生變化:
(1).如果B跟着也變了,說明是淺拷貝,拿人手短!(修改堆記憶體中的同一個值)
(2).如果B沒有改變,說明是深拷貝,自食其力!(修改堆記憶體中的不同的值)
簡單的來說:
如果拷貝的時候共享被引用的對象就是淺拷貝,如果被引用的對象也拷貝一份出來就是深拷貝。(深拷貝就是說重新new一個對象,然後把之前的那個對象的屬性值在重新指派給這個使用者)
4. .Net中實作
(1).淺拷貝通過MemberwiseClone()方法實作.
(2).深拷貝可以通過流的方式和反射的方式來實作,其中流的方式類前必須加 [Serializable], 反射的方式需要考慮的問題很多,嵌套以及各種類型, 此處提供的方法并不完善.
代碼分享:
/// <summary>
/// 克隆方法(基于淺拷貝)
/// 用于實作ICloneable裡方法(當然你用來實作深拷貝也可以)
/// </summary>
/// <returns></returns>
public object Clone()
{
return this.MemberwiseClone();
}
/// <summary>
/// 克隆方法(基于深拷貝)
/// 類前面必須加可序列化标志[Serializable]
/// </summary>
/// <returns></returns>
public object DeepClone1()
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter bFormatter = new BinaryFormatter();
bFormatter.Serialize(stream, this);
stream.Seek(0, SeekOrigin.Begin);
return bFormatter.Deserialize(stream);
}
}
基于反射的深拷貝:(僅供參考,不是很完善)
public class Utils
{
/// <summary>
/// 基于反射的深拷貝
/// (存在問題,不是很好用)
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static object DeepClone(Object obj)
{
Type type = obj.GetType();
//對于沒有公共無參構造函數的類型此處會報錯
object returnObj = Activator.CreateInstance(type);
FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
for (int i = 0; i < fields.Length; i++)
{
FieldInfo field = fields[i];
var fieldValue = field.GetValue(obj);
///值類型,字元串,枚舉類型直接把值複制,不存在淺拷貝
if (fieldValue.GetType().IsValueType || fieldValue.GetType().Equals(typeof(System.String)) || fieldValue.GetType().IsEnum)
{
field.SetValue(returnObj, fieldValue);
}
else
{
field.SetValue(returnObj, DeepClone(fieldValue));
}
}
//屬性
PropertyInfo[] properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
for (int i = 0; i < properties.Length; i++)
{
PropertyInfo property = properties[i];
var propertyValue = property.GetValue(obj);
if (propertyValue.GetType().IsValueType || propertyValue.GetType().Equals(typeof(System.String)) || propertyValue.GetType().IsEnum)
{
property.SetValue(returnObj, propertyValue);
}
else
{
property.SetValue(returnObj, DeepClone(propertyValue));
}
}
return returnObj;
}
}
5. 經過測試得出來一個結論
(1).對于淺拷貝:所有值類型和string這個引用類型修改其中一個對象的值,不互相影響; 除了string以外的引用類型都互相影響; 類屬于引用類型,修改類中的一個屬性值,被拷貝的另一個對象的屬性值也會發生變化(與類中的屬性值是什麼類型沒有關系).
(2).對于深拷貝:無論是值類型還是引用類型, 修改其中一個對象的值都不會互相影響。
代碼分享:
/// <summary>
/// Video視訊類
/// </summary>
[Serializable]
public class Video : ICloneable
{
public string Id { set; get; } // 視訊編号
public string Content { set; get; } // 視訊内容
public List<int> ageList { set; get; }
public VideoDetails vDetails { get; set; }
/// <summary>
/// 克隆方法(基于淺拷貝)
/// 用于實作ICloneable裡方法(當然你用來實作深拷貝也可以)
/// </summary>
/// <returns></returns>
public object Clone()
{
return this.MemberwiseClone();
}
/// <summary>
/// 克隆方法(基于深拷貝)
/// 類前面必須加可序列化标志[Serializable]
/// </summary>
/// <returns></returns>
public object DeepClone1()
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter bFormatter = new BinaryFormatter();
bFormatter.Serialize(stream, this);
stream.Seek(0, SeekOrigin.Begin);
return bFormatter.Deserialize(stream);
}
}
}
/// <summary>
/// 視訊詳情類
/// </summary>
[Serializable]
public class VideoDetails
{
public string videoUrl { get; set; }
public int videoPic { get; set; }
public VideoDetails(string myVideoUrl, int myVideoPic)
{
this.videoUrl = myVideoUrl;
this.videoPic = myVideoPic;
}
}
測試代碼
#region 淺拷貝
{
Console.WriteLine("-------------------------------下面是基于淺拷貝------------------------------------");
Video v1 = new Video()
{
Id = "001",
Content = "西遊記",
ageList = new List<int>() { 000 }, //List是引用類型
vDetails = new VideoDetails(@"H:\DesignMode", 1111) //類也是引用類型
};
Video v2 = (Video)v1.Clone();
Console.WriteLine($"v2: Id={v2.Id},Content={v2.Content},ageList[0]={ v1.ageList[0]},Url={v2.vDetails.videoUrl},videoPic={v2.vDetails.videoPic}");
//修改v1的值,v2中的屬性是否變化要分情況讨論的
v1.Content = "水浒傳";
v1.ageList[0] = 111;
v1.vDetails.videoUrl = @"H:\XXXXXXX";
v1.vDetails.videoPic = 22222;
Console.WriteLine($"v2: Id={v2.Id},Content={v2.Content},ageList[0]={ v2.ageList[0]},Url={v2.vDetails.videoUrl},videoPic={v2.vDetails.videoPic}");
}
#endregion
#region 深拷貝1(流的模式)
{
Console.WriteLine("-------------------------------下面是基于深拷貝1------------------------------------");
Video v1 = new Video()
{
Id = "001",
Content = "西遊記",
ageList = new List<int>() { 000 },
vDetails = new VideoDetails(@"H:\DesignMode", 1111)
};
Video v2 = (Video)v1.DeepClone1();
Console.WriteLine($"v2: Id={v2.Id},Content={v2.Content},ageList[0]={ v1.ageList[0]},Url={v2.vDetails.videoUrl},videoPic={v2.vDetails.videoPic}");
//修改v1的值,v2不變
v1.Content = "水浒傳";
v1.vDetails.videoUrl = @"H:\XXXXXXX";
v1.vDetails.videoPic = 22222;
Console.WriteLine($"v2: Id={v2.Id},Content={v2.Content},ageList[0]={ v2.ageList[0]},Url={v2.vDetails.videoUrl},videoPic={v2.vDetails.videoPic}");
}
#endregion
運作結果:
三. 原型模式詳解
1. 背景
在有些系統中,存在大量相同或相似對象的建立問題,如果用傳統的構造函數來建立對象,會比較複雜且耗時耗資源,用原型模式生成對象就很高效,就像孫悟空拔下猴毛輕輕一吹就變出很多孫悟空一樣簡單。
2. 定義和特點
定義如下:用一個已經建立的執行個體作為原型,通過複制該原型對象來建立一個和原型相同或相似的新對象。在這裡,原型執行個體指定了要建立的對象的種類。用這種方式建立對象非常高效,根本無須知道對象建立的細節。例如,Windows 作業系統的安裝通常較耗時,如果複制就快了很多。
3. 具體實作
(1). 模式結構
A. 抽象原型類:規定了具體原型對象必須實作的接口,eg:.Net 中的ICloneable。
B. 具體原型類:實作抽象原型類的 clone() 方法,它是可被複制的對象。
C. 通路類:使用具體原型類中的 clone() 方法來複制新的對象。
結構圖如下:
(2). 使用場景
有一塊視訊,我需要1個一模一樣的,并且我還需要1個除了路徑不同其它都相同的視訊,這個時候可以使用原型模式哦。
(3). 代碼實操
相關類:
/// <summary>
/// Video視訊類
/// </summary>
[Serializable]
public class Video : ICloneable
{
public string Id { set; get; } // 視訊編号
public string Content { set; get; } // 視訊内容
public List<int> ageList { set; get; }
public VideoDetails vDetails { get; set; }
/// <summary>
/// 克隆方法(基于淺拷貝)
/// 用于實作ICloneable裡方法(當然你用來實作深拷貝也可以)
/// </summary>
/// <returns></returns>
public object Clone()
{
return this.MemberwiseClone();
}
/// <summary>
/// 克隆方法(基于深拷貝)
/// 類前面必須加可序列化标志[Serializable]
/// </summary>
/// <returns></returns>
public object DeepClone1()
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter bFormatter = new BinaryFormatter();
bFormatter.Serialize(stream, this);
stream.Seek(0, SeekOrigin.Begin);
return bFormatter.Deserialize(stream);
}
}
}
/// <summary>
/// 視訊詳情類
/// </summary>
[Serializable]
public class VideoDetails
{
public string videoUrl { get; set; }
public int videoPic { get; set; }
public VideoDetails(string myVideoUrl, int myVideoPic)
{
this.videoUrl = myVideoUrl;
this.videoPic = myVideoPic;
}
}
測試代碼:
#region 01-完全相同視訊的拷貝
{
Console.WriteLine("------------------------------- 01-完全相同視訊的拷貝------------------------------------");
Video v1 = new Video()
{
Id = "001",
Content = "西遊記",
ageList = new List<int>() { 000 },
vDetails = new VideoDetails(@"H:\DesignMode", 1111)
};
Video v2 = (Video)v1.DeepClone1(); //深拷貝
Console.WriteLine($"v1: Id={v1.Id},Content={v1.Content},ageList[0]={ v1.ageList[0]},Url={v1.vDetails.videoUrl},videoPic={v1.vDetails.videoPic}");
Console.WriteLine($"v2: Id={v2.Id},Content={v2.Content},ageList[0]={ v2.ageList[0]},Url={v2.vDetails.videoUrl},videoPic={v2.vDetails.videoPic}");
}
#endregion
#region 02-相似視訊的拷貝
{
Console.WriteLine("-------------------------------02-相似視訊的拷貝------------------------------------");
Video v1 = new Video()
{
Id = "001",
Content = "西遊記",
ageList = new List<int>() { 000 },
vDetails = new VideoDetails(@"H:\DesignMode", 1111)
};
Video v2 = (Video)v1.DeepClone1(); //深拷貝
Console.WriteLine($"v1: Id={v1.Id},Content={v1.Content},ageList[0]={ v1.ageList[0]},Url={v1.vDetails.videoUrl},videoPic={v1.vDetails.videoPic}");
//相似視訊的拷貝,隻需要簡單修改即可
v2.vDetails.videoUrl = @"F:\newVideo";
Console.WriteLine($"v2: Id={v2.Id},Content={v2.Content},ageList[0]={ v2.ageList[0]},Url={v2.vDetails.videoUrl},videoPic={v2.vDetails.videoPic}");
}
#endregion
運作結果:
4. 使用場景
(1). 對象之間相同或相似,即隻是個别的幾個屬性不同的時候。
(2). 對象的建立過程比較麻煩,但複制比較簡單的時候。
原文連結:第三節: 值類型與引用類型、深拷貝與淺拷貝、原型模式詳解 - Yaopengfei - 部落格園