????昨天和趙崇說了一下工作的事情,說起了性能問題就討論起了數據結果和指針對性能的影響。曾經一直沒有想到這方面的事情,這幾天專門抽時間回想一下這方面的知識,然后一點一點的總結一下,看看數據結構和指針在咱們代碼中是怎樣實現效率的提升的。
今天咱們先說一下指針。關于指針,在學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]
???? 怎么樣?找出指針的類型的方法是不是非常easy?
指針所指向的類型
????? 當你通過指針來訪問指針所指向的內存區時,指針所指向的類型決定了編譯器將把那片內存區里的內容當做什么來看待。
????? 指針所指向的類型
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 Demostatic unsafe void Copy(byte[] src, int srcIndex,byte[] dst, int dstIndex, int count){//。。。。。
}
????? 另一個keyword要注意,那就是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("\n");Console.ReadLine();}}
}
????? 可是為什么我們的代碼如今都不怎么用指針呢,由于在公共語言執行庫 (CLR) 中,不安全代碼是指無法驗證的代碼。C# 中的不安全代碼不一定是危急的,僅僅是其安全性無法由 CLR進行驗證的代碼。
因此。CLR 僅僅對在全然受信任的程序集中的不安全代碼運行操作。假設使用不安全代碼。由您負責確保您的代碼不會引起安全風險或指針錯誤。所以你假設對你的代碼很有保證的話,用也是沒問題的。