文章目錄
- 知識回顧
- 一、棧(Stack)和堆(Heap)
- 1、什么是棧和堆
- 2、為什么要分棧和堆
- 3、棧和堆的區別
- 棧
- 堆
- 4、總結
- 二、值類型和引用類型
- 1、那么值類型和引用類型到底有什么區別呢?
- 值類型
- 引用類型
- 2、總結
- 三、特殊的引用類型string
- 1、為什么說string是特殊的引用類型?
- 2、理解字符串(string)引用類型
- 3、如何證明呢?
- 使用 `GetHashCode` 方法
- 通過斷點調試直接查看變量指針內存地址
- 4、總結
- 專欄推薦
- 完結
知識回顧
C# 中的變量類型可以分為 值類型
和 引用類型
兩大類。
值類型
變量類型 | 描述 | 范圍 |
---|---|---|
byte | 無符號8位整數 | 0 到 255 |
sbyte | 有符號8位整數 | -128 到 127 |
short | 有符號16位整數 | -32,768 到 32,767 |
ushort | 無符號16位整數 | 0 到 65,535 |
int | 有符號32位整數 | -2,147,483,648 到 2,147,483,647 |
uint | 無符號32位整數 | 0 到 4,294,967,295 |
long | 有符號64位整數 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
ulong | 無符號64位整數 | 0 到 18,446,744,073,709,551,615 |
float | 32 位單精度浮點數 | ±1.5 × 10^?45 到 ±3.4 × 10^38(精度約7位有效數字) |
double | 64 位雙精度浮點數 | ±5.0 × 10^?324 到 ±1.7 × 10^308(精度約15–16位有效數字) |
decimal | 128 位高精度小數 | ±1.0 × 10^?28 到 ±7.9 × 10^28(精度約28–29位有效數字) |
bool | 8 位布爾型 | true 或 false |
char | 16 位單一字符型 | Unicode字符(0 到 65,535) |
引用類型
變量類型 | 描述 | 范圍 |
---|---|---|
string | 字符串 | 任意長度的字符序列(理論上最多可達到 2GB) |
一、棧(Stack)和堆(Heap)
要了解值類型和引用類型的區別,我們得先得棧和堆的概率有個了解。
1、什么是棧和堆
簡單理解就是,程序運行時,它的數據必須存儲在內存中。棧和堆就是計算機內存中的兩種不同的存儲區域。
2、為什么要分棧和堆
通過分棧和堆,程序可以在性能和內存管理上做出平衡,從而讓程序既高效又靈活。
3、棧和堆的區別
棧
- 棧空間比較小,但是讀取速度快。
- 棧存儲的是一些簡單的數據
- 棧遵循
先進后出
原則,棧就像一個堆疊的盤子。你每次放入一個新盤子(數據),都會把它放在最上面。拿東西的時候,也都是從最上面拿,所以非常快速。 - 棧里的數據只在當前函數或方法運行時有效,一旦方法執行完畢,這些數據就會自動被銷毀。
堆
- 堆空間比較大,但是讀取速度慢。
- 堆存儲的是一些較大的數據。
- 堆就像一個大大的垃圾堆,可以隨意放東西。它不按照順序來存放數據,而是根據需要分配空間,可以存儲更復雜的對象
- 堆中的數據不會像棧那樣自動清理。(但在 C# 中,
垃圾回收
會自動清理不再使用的對象)
4、總結
實際上,我們寫程序并不需要關心內存是如何使用的,C#已經幫我們做好了。這里只是簡單介紹這個概念,有些知識看不懂也沒關系,比如垃圾回收
,后面肯定還會詳細介紹。現在有個印象就行。
二、值類型和引用類型
在 C# 中,數據類型分為兩大類:值類型(Value Types)和 引用類型(Reference Types)。
我們目前學了值類型和引用類型只有變量,但是其實不止
- 值類型其實還有
結構體(Struct)
、枚舉(Enum)
, - 引用類型還有
類(Class)
、數組(Array)
、委托(Delegate)
。
這些我們后面會一一介紹,現在了解一下就行。
1、那么值類型和引用類型到底有什么區別呢?
因為只學了變量,這里就用變量舉例。
值類型
- 直接存儲數據,值類型的變量直接保存它們的數據。值類型直接存儲在
棧
上。 - 值類型賦值時,會
復制值本身
。
比如
int a, b;
a = 100;
b = a;
Console.WriteLine("a的值:" + a);
Console.WriteLine("b的值:" + b);a = 20; // 重新給a賦值,b的值不會改變
Console.WriteLine("a的值:" + a);
Console.WriteLine("b的值:" + b);
打印結果,正如前面所說,重新給a賦值,b的值不會改變
畫圖說明
解釋
:
-
聲明變量 int a, b;
當你聲明兩個整數變量 a 和 b 時,編譯器會在棧上為它們各自分配32位的存儲空間。此時,這兩個存儲空間是空的,沒有初始化任何值。 -
賦值 a = 100;
當你給變量 a 賦值為 20 時,棧上的存儲空間 a 會被寫入值 20。這個操作不會重新分配內存,而是直接在已經分配的內存位置寫入新的值。 -
賦值 b = a;
當你執行 b = a; 時,棧上的存儲空間 b 會被寫入 a 的當前值。此時,a 和 b 都存儲了值 20,但它們是獨立的存儲空間。 -
重新賦值 a = 20;
當你再次給 a 賦值為 20 時,棧上的存儲空間 a 會被更新為新的值 20。這不會重新分配內存,而是直接在已經分配的內存位置寫入新的值。
引用類型
- 間接存儲數據,引用類型的變量保存的是對實際數據所在位置的
引用或地址
(也叫指針),而不是數據本身。引用類型存儲在棧上的引用
(或指針)和堆上的實際數據
。 - 引用類型賦值時,會
復制引用
,但實際的數據不會復制。
畫圖說明,假設a b c都是引用類型
解釋
:
-
聲明引用類型 a 和 b;
這時 a 和 b 都是空引用,它們在棧上分配了空間,但它們指向的堆內存地址尚未確定,二者目前都沒有引用任何實際的對象。
-
給 a 賦值:
此時,a 作為棧上的一個引用變量,指向堆上的值。b 仍然是空引用。
-
b = a; b 也指向 a 的堆值:
此時,a 和 b 都存儲相同的堆內存地址,指向相同的堆對象。
-
a 修改為新值:
這時候a b的值就都變成了新值
-
重新定義 c,并給 c 賦值
此時,c 是一個新的引用類型變量,它在棧上存儲了指向堆上c值的地址,且與 a 和 b 的值互不影響。
ps
:有些小伙伴可能會說了,前面不是說了string不就是引用類型嗎,為什么不用string舉例呢,這樣不是更加直觀?其實是因為string是特殊的引用類型,這個問題我接下來會說。
2、總結
特性 | 值類型 (Value Type) | 引用類型 (Reference Type) |
---|---|---|
存儲方式 | 存儲數據的值本身 | 存儲數據的引用(內存地址) |
賦值行為 | 賦值時會復制數據,原始值和復制值互不影響 | 賦值時會復制引用,兩個變量指向同一個對象 |
存儲位置 | 通常存儲在棧上 (stack),但結構體和其他類型可能存儲在堆上 | 存儲在堆上 (heap),引用存儲在棧上 |
初始化 | 默認值為零或空值(如 0 、false 、null ) | 默認值為 null |
內存管理 | 系統負責管理內存(棧上分配的內存自動釋放) | 由垃圾回收器 (GC) 管理內存 |
常見類型 | 基本數據類型(如 int 、float 等)、結構體、枚舉 | 類、數組、委托、字符串等 |
三、特殊的引用類型string
1、為什么說string是特殊的引用類型?
學了前面引用類型的知識,我們可以拿string測試一下試試。
string str1, str2;
str1 = "名";
str2 = str1;
Console.WriteLine("str1的值:" + str1);
Console.WriteLine("str2的值:" + str2);str1 = "字";//重新賦值str1
Console.WriteLine("str1的值:" + str1);
Console.WriteLine("str2的值:" + str2);
按前面引用類型的概念,可能你想說第二次打印的結果應該是"字" "字"
。
實際上真是這樣嗎?我先來看看執行結果
有人會說,如果是值類型,結果倒還說的過去.但是不是說string是引用類型么?如果是引用類型的話。輸出的結果難道不應該是: "名""名""字" "字"
么?
2、理解字符串(string)引用類型
理解字符串(string)在C#中的行為確實可能有些困惑,因為它們在某種程度上表現出值類型和引用類型的特性。讓我們來詳細解釋一下。
-
字符串是
不可變
的
字符串在C#中是不可變的,這意味著一旦你創建了一個字符串對象,就不能修改它的內容。當你嘗試修改一個字符串時,實際上是創建了一個新的字符串對象。 -
字符串為什么是
引用類型
因為它們在堆上分配內存,并且在棧上存儲對堆上對象的引用。因此,多個變量可以引用同一個字符串對象。
3、如何證明呢?
使用 GetHashCode
方法
雖然這并不返回內存地址,但 GetHashCode 方法會返回一個與字符串內容相關的哈希值。這個值可以作為字符串的“標識符”,有時候在調試中,它能幫助你判斷是否為同一個字符串實例。
string str1 = "xxxx";
string str2 = str1;
Console.WriteLine(str1.GetHashCode());
Console.WriteLine(str2.GetHashCode());str1 = "yyyy";
Console.WriteLine(str1.GetHashCode());
Console.WriteLine(str2.GetHashCode());
結果
通過斷點調試直接查看變量指針內存地址
值類型,一開始內存地址就不一樣
string引用類型,開始地址一樣,重新賦值后地址不一樣了
4、總結
字符串不叫值類型,因為它們確實具有引用類型的基本特性:在堆上分配內存,并且在棧上存儲引用。盡管字符串的不可變性使得它們在某些方面表現得像值類型,但從技術上講,它們仍然是引用類型。
由于字符串的不可變性,即使它們是引用類型,修改一個字符串變量不會影響其他引用相同字符串的變量。這是因為當你修改字符串時,實際上是創建了一個新的字符串對象,并將變量的引用指向了這個新對象。
string雖然方便,但是有一個小缺點就是頻繁的改變string重新賦值會產生內存垃圾
,優化替代方案我們會在后面進行講解
專欄推薦
地址 |
---|
【從零開始入門unity游戲開發之——C#篇】 |
【從零開始入門unity游戲開發之——unity篇】 |
【制作100個Unity游戲】 |
【推薦100個unity插件】 |
【實現100個unity特效】 |
【unity框架開發】 |
完結
贈人玫瑰,手有余香!如果文章內容對你有所幫助,請不要吝嗇你的點贊評論和關注
,你的每一次支持
都是我不斷創作的最大動力。當然如果你發現了文章中存在錯誤
或者有更好的解決方法
,也歡迎評論私信告訴我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奮斗的開發者,閑暇之余,邊學習邊記錄分享,站在巨人的肩膀上,通過學習前輩們的經驗總是會給我很多幫助和啟發!如果你遇到任何問題,也歡迎你評論私信或者加群找我, 雖然有些問題我也不一定會,但是我會查閱各方資料,爭取給出最好的建議,希望可以幫助更多想學編程的人,共勉~