昨天和趙崇說了一下工作的事情,說起了性能問題就討論起了數據結果和指針對性能的影響,以前一直沒有想到這方面的事情,這幾天專門抽時間回顧一下這方面的知識,然後一點一點的總結一下,看看數據結構和指針在咱們代碼中是如何實現效率的提升的。
今天咱們先說一下指針,關於指針,在學C++的時候到時接觸過指針,但是當時學的雲裡霧裡,也沒能好好的總結一下,以至於忘的差不多了,如果大家也有對指針不熟悉的地方,我們先來回顧一下C++的指針吧。
在C++中,指針是這樣子定義的:指針是一個特殊的變量,它裡面存儲的數值被解釋成為內存裡的一個地址。要搞清一個指針需要搞清指針的四方面的內容:指針的類型,指針所指向的類型,指針的值或者叫指針所指向的內存區,還有指針本身所占據的內存區。讓我們分別說明。
int *ptr; char *ptr; int **ptr; int (*ptr)[3]; int *(*ptr)[4];
通過上邊的例子,大家把指針的申明語法去了,剩下的就是指針了,所以上邊的指針就是“ptr”。
然後我們看一下指針的類型:
int *ptr; //指針的類型是int * char *ptr; //指針的類型是char * int **ptr; //指針的類型是 int ** int (*ptr)[3]; //指針的類型是 int(*)[3] int *(*ptr)[4]; //指針的類型是 int *(*)[4]
怎麼樣?找出指針的類型的方法是不是很簡單?
指針所指向的類型
當你通過指針來訪問指針所指向的內存區時,指針所指向的類型決定了編譯器將把那片內存區裡的內容當做什麼來看待。
指針所指向的類型
int *ptr; //指針的類型是int char *ptr; //指針的類型是char int **ptr; //指針的類型是 int * int (*ptr)[3]; //指針的類型是 int()[3] int *(*ptr)[4]; //指針的類型是 int *()[4]
指針的值
這個需要看操作系統,如果咱們的系統是32位的,那麼咱們的指針就是32位長度的一個值,因為計算機的內存長度是32位,同理,64位的就是64位長度的值,當然這個64和32,都是用0和1表示的,因為計算機只能知道0和1.
指針所指向的內存區就是從指針的值所代表的那個內存地址開始,長度為sizeof(指針所指向的類型)的一片內存區。以後,我們說一個指針的值是XX,就相當於說該指針指向了以XX為首地址的一片內存區域;我們說一個指針指向了某塊內存區域,就相當於說該指針的值是這塊內存區域的首地址。
運算符&和*
這裡&是取地址運算符,*是...書上叫做“間接運算符”。&a的運算結果是一個指針,指針的類型是a的類型加個*(例如int*),指針所指向的類型是a的類型(int),指針所指向的地址嘛,那就是a的地址。*p的運算結果就五花八門了。總之*p的結果是p所指向的東西,這個東西有這些特點:它的類型是p指向的類型,它所占用的地址是p所指向的地址。
說過來翻過去,就是一個咱們手機導航過程,a就是你家,&a是指向你家的地址,例如p=&a,那麼p就是我家的地址,那麼*p的*就相當於咱們手機的導航過,通過你輸入的地址,來找到你家。
上邊的例子,只是簡單的說了一下什麼叫做地址,那麼如果大家想要更深層次的理解指針的話,給大家推薦一篇博客,寫的非常的基礎http://www.cnblogs.com/basilwang/archive/2010/09/20/1831493.html
那麼為什麼要有指針呢,如不你設計一個函數
struct get(){
.........
}
返回一個結構體對象的函數,你要知道,C++中,這樣的返回值都是復制傳遞的過程,也就是說,你返回這個結構體的時候,程序會復制一個一樣的結構體對象在棧裡面,然後接受的變量在通過拷貝構造函數,復制一個新的變量。最後程序在析構掉這個臨時的。如果結構體很小,沒什麼問題,如果很大呢?這樣一構造,一析構,會非常浪費時間。但是指針就不一樣了,管你怎麼弄,反正就是4字節,和一個int一樣,完全沒區別。
那麼接下來咱們說一下C++到C#的”進化史”吧,平時我們見的代碼好像沒有再像C++的代碼用到了指針,後來人們就說微軟是不是就沒有指針,其實微軟是有的,大家右擊咱們的解決方案下的類庫-à生成à不安全代碼,大家勾選一下啊,一樣可以用,只不過寫類和方法的時候前邊加上個一個unsafe。例如:
public partial unsafe class Demo static unsafe void Copy(byte[] src, int srcIndex, byte[] dst, int dstIndex, int count) { //。。。。。 }
還有一個關鍵字要注意,那就是Fixed,他的作用就是一個釘子,大家看了上邊的介紹會發現指針其實也是計算機中的0和1。指針也占用內存,只是他的大小是固定的。而fixed語句可用於設置指向托管變量的指針並在 statement 執行期間“釘住”該變量。如果沒有 fixed語句,則指向可移動托管變量的指針的地址可變,因為垃圾回收可能不可預知地重定位變量。
C# 編譯器只允許在 fixed語句中分配指向托管變量的指針,但無法修改在 fixed 語句中初始化的指針。
可以用數組或字符串的地址初始化指針:
fixed (int* p = arr) ... // equivalent to p = &arr[0] fixed (char* p = str) ... // equivalent to p = &str[0]
下邊為大家呈現一個完整的例子,一個C#使用指針的例子
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace cursorTest { class Program { // 使用unsafe標注該方法 static unsafe void Copy(byte[] src, int srcIndex, byte[] dst, int dstIndex, int count) { if (src == null || srcIndex < 0 || dst == null || dstIndex < 0 || count < 0) { throw new ArgumentException(); } int srcLen = src.Length; int dstLen = dst.Length; if (srcLen - srcIndex < count || dstLen - dstIndex < count) { throw new ArgumentException(); } // 用fixed釘住指針,不讓他改變 fixed (byte* pSrc = src, pDst = dst) { byte* ps = pSrc; byte* pd = pDst; // 循環復制 for (int n = 0; n < count / 4; n++) { *((int*)pd) = *((int*)ps); pd += 4; ps += 4; } //完成賦值 for (int n = 0; n < count % 4; n++) { *pd = *ps; pd++; ps++; } } } static void Main(string[] args) { byte[] a = new byte[100]; byte[] b = new byte[100]; for (int i = 0; i < 100; ++i) a[i] = (byte)i; Copy(a, 0, b, 0, 100); Console.WriteLine(The first 10 elements are:); for (int i = 0; i < 10; ++i) Console.Write(b[i] + ); Console.WriteLine( ); Console.ReadLine(); } } }
但是為什麼我們的代碼現在都不怎麼用指針呢,因為在公共語言運行庫 (CLR) 中,不安全代碼是指無法驗證的代碼。C# 中的不安全代碼不一定是危險的,只是其安全性無法由 CLR進行驗證的代碼。因此,CLR 只對在完全受信任的程序集中的不安全代碼執行操作。如果使用不安全代碼,由您負責確保您的代碼不會引起安全風險或指針錯誤,所以你如果對你的代碼非常有保證的話,用也是沒問題的。