在C#中使用指針的示例代碼
.Net平臺(tái)定義了兩種主要數(shù)據(jù)類型:值類型和引用類型,其實(shí)還有第三種數(shù)據(jù)類型:指針類型。使用指針,可以繞開CLR的內(nèi)存管理機(jī)制。(說明:在C#中使用指針,需要有相關(guān)C/C++指針操作基礎(chǔ))
1、C#中指針相關(guān)的操作符和關(guān)鍵字
操作符/關(guān)鍵字 | 作用 |
* | 該操作符用于創(chuàng)建一個(gè)指針變量,和在C/C++中一樣。也可用于指針間接尋址(解除引用) |
& | 該操作符用于獲取內(nèi)存中變量的地址 |
-> | 該操作符用于訪問一個(gè)由指針表示的類型的字段,和在C++中一樣 |
[] | 在不安全的上下文中,[]操作符允許我們索引由指針變量指向的位置 |
++,-- | 在不安全的上下文中,遞增和遞減操作符可用于指針類型 |
+,- | 在不安全的上下文中,加減操作符可用于指針類型 |
==, !=, <, >, <=, >= | 在不安全的上下文中,比較和相等操作符可用于指針類型 |
stackalloc | 在不安全的上下文中,stackalloc關(guān)鍵字可用于直接在棧上分配C#數(shù)組,類似CRT中的_alloca函數(shù) |
fixed | 在不安全的上下文中,fixed關(guān)鍵字可用于臨時(shí)固定一個(gè)變量以使它的地址可被找到 |
2、在C#中使用指針,需要啟用“允許不安全代碼”設(shè)置
選擇項(xiàng)目屬性->生成,鉤上“允許不安全代碼”
3、unsafe關(guān)鍵字
只有在unsafe所包含的代碼區(qū)塊中,才能使用指針。類似lock關(guān)鍵字的語法結(jié)構(gòu)
除了聲明代碼塊為不安全代碼外,也可以直接構(gòu)建“不安全的”結(jié)構(gòu)、類型成員和函數(shù)。
unsafe struct Point { public int x; public int y; public Point* next; public Point* previous; } unsafe static void CalcPoint(Point* point) { // }
也可以在導(dǎo)入非托管 DLL 的函數(shù)聲明中使用unsafe
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)] private static extern unsafe int memcpy(void* dest, void* src, int count);
注意:
指針不能指向引用或包含引用的結(jié)構(gòu),因?yàn)闊o法對(duì)對(duì)象引用進(jìn)行垃圾回收,即使有指針指向它也是如此。 垃圾回收器并不跟蹤是否有任何類型的指針指向?qū)ο蟆?/strong>
下面的示例代碼可以說明:
/// <summary> /// 聲明一個(gè)Point結(jié)構(gòu)體 /// </summary> struct Point { public int x; public int y; } static void Main(string[] args) { unsafe { //編譯正常 Point p = new Point(); Point* pp = &p; } }
//換成類 class Point { public int x; public int y; }
4、*和&操作符
在不安全的上下文中,可以使用 * 操作符構(gòu)建數(shù)據(jù)類型相對(duì)應(yīng)的指針類型(指針類型、值類型和引用類型,示例代碼中的type),使用 & 操作符獲取被指向的內(nèi)存地址。
type* identifier; void* identifier; //允許但不推薦
下面是使用*操作符進(jìn)行指針類型聲明
int* p | p 是指向整數(shù)的指針。 |
int** p | p 是指向整數(shù)的指針的指針。 |
int*[] p | p 是指向整數(shù)的指針的一維數(shù)組。 |
char* p | p 是指向字符的指針。 |
void* p | p 是指向未知類型的指針。 |
注意:
1、無法對(duì) void* 類型的指針應(yīng)用間接尋址運(yùn)算符。 但是,你可以使用強(qiáng)制轉(zhuǎn)換將 void 指針轉(zhuǎn)換為任何其他指針類型,反過來也是可以的。
2、指針類型不從object類繼承,并且指針類型與 object 之間不存在轉(zhuǎn)換。 此外,裝箱和取消裝箱不支持指針。
下面的代碼演示了如何聲明指針類型:
static void Main(string[] args) { int []a = { 1, 2, 3, 4, 4 }; unsafe { //臨時(shí)固定一個(gè)變量以使它的地址可被找到 fixed (int* p = &a[0]) { int* p2 = p; Console.WriteLine(*p2); p2++; Console.WriteLine(*p2); p2++; Console.WriteLine(*p2); } } }
輸出結(jié)果如下:
1
2
3
下面的代碼演示了如何使用指針類型進(jìn)行數(shù)據(jù)交換:
static void Main(string[] args) { int a = 1; int b = 2; unsafe { UnsafeSwap(&a, &b); } Console.WriteLine(a); Console.WriteLine(b); } /// <summary> /// 使用指針 /// </summary> /// <param name="a"></param> /// <param name="b"></param> static unsafe void UnsafeSwap(int* a,int *b) { int temp = *a; *a = *b; *b = temp; } /// <summary> /// 不使用指針的安全版本 /// </summary> /// <param name="a"></param> /// <param name="b"></param> static void SafeSwap(ref int a,ref int b) { int temp = a; a = b; b = temp; }
輸出結(jié)果如下:
2
1
5、通過指針訪問字段
定義如下結(jié)構(gòu)體
struct Point { public int x; public int y; public override string ToString() { return $"x:{x},y:{y}"; } }
如果聲明一個(gè)Point類型的指針,就需要使用指針字段訪問操作符(->)來訪問公共成員(和C++一樣),也可以使用指針間接尋址操作符(*)來解除指針的引用,使其也可以使用 (.)操作符訪問字段(和C++一樣)。
static unsafe void Main(string[] args) { //通過指針訪問成員 Point point = new Point(); Point* p = &point; p->x = 10; p->y = 5; Console.WriteLine(p->ToString()); //通過指針間接尋址訪問成員 Point point2; //不使用 new 運(yùn)算符的情況下對(duì)其進(jìn)行實(shí)例化,需要在首次使用實(shí)例之前必須初始化所有實(shí)例字段。 Point* p2 = &point2; (*p2).x = 128; (*p2).y = 256; Console.WriteLine((*p2).ToString()); }
運(yùn)行結(jié)果如下:
x:10,y:5
x:128,y:256
6、stackalloc關(guān)鍵字
在不安全上下文中,可能需要聲明一個(gè)直接從調(diào)用棧分配內(nèi)存的本地變量(不受.Net垃圾回收器控制)。C#提供了與CRT函數(shù)_alloca等效的stackalloc關(guān)鍵字來滿足這個(gè)需求。
static unsafe void Main(string[] args) { char* p = stackalloc char[3]; for (int i = 0; i < 3; i++) { p[i] = (char)(i+65); //A-C } Console.WriteLine(*p); Console.WriteLine(p[0]); Console.WriteLine(*(++p)); Console.WriteLine(p[0]); Console.WriteLine(*(++p)); Console.WriteLine(p[0]); }
輸出結(jié)果如下:
A
A
B
B
C
C
7、fixed關(guān)鍵字
在上面的示例中,我們可以看到,通過stackalloc關(guān)鍵字,在不安全上下文中分配一大塊內(nèi)存非常方便。但這塊內(nèi)存是在棧上的,當(dāng)分配方法返回的時(shí)候,被分配的內(nèi)存立即被清理。
假設(shè)有如下情況:
聲明一個(gè)引用類型PointRef和一個(gè)值類型Point
class PointRef { public int x; public int y; public override string ToString() { return $"x:{x},y:{y}"; } } struct Point { public int x; public int y; public override string ToString() { return $"x:{x},y:{y}"; } }
調(diào)用者聲明了一個(gè)PointRef類型的變量,內(nèi)存將被分配在垃圾回收器堆上。如果一個(gè)不安全的上下文要與這個(gè)對(duì)象(或這個(gè)堆上的任何對(duì)象)交互,就可能會(huì)出現(xiàn)問題,因?yàn)槔厥湛呻S時(shí)發(fā)生。設(shè)想一下,恰好在清理堆的時(shí)候訪問Point成員,這就很
為了將不安全上下文中的引用類型變量固定,C#提供了fixed關(guān)鍵字,fixed語句設(shè)置指向托管類型的指針并在代碼執(zhí)行過程中固定該變量。換句說話:fixed關(guān)鍵字可以鎖定內(nèi)存中的引用變量。這樣在語句的執(zhí)行過程中,該變量地址保持不變。
事實(shí)上,也只有使用fixed關(guān)鍵字,C#編譯器才允許指針指向托管變量。
static unsafe void Main(string[] args) { PointRef pointRef = new PointRef(); Point point = new Point(); int a = &pointRef.x; //編譯不通過 int *b = &point.x; //編譯通過 fixed(int *c = &pointRef.x) { //編譯通過 } }
說明:
在fixed中初始化多個(gè)變量也是可以的
//同時(shí)聲明多個(gè)指針變量的語法跟C++中的不一樣,需要注意 fixed(int *e = &(pointRef.x) , f = &(pointRef.y) ) { }
8、sizeof關(guān)鍵字
在不安全上下文中,sizeof關(guān)鍵字用于獲取值類型(不是引用類型)的字節(jié)大小。sizeof可計(jì)算任何由System.ValueType派生實(shí)體的字節(jié)數(shù)。
static void Main(string[] args) { unsafe { //不安全版本 Console.WriteLine(sizeof(int)); Console.WriteLine(sizeof(float)); Console.WriteLine(sizeof(Point)); } //安全版本 Console.WriteLine(Marshal.SizeOf(typeof(int))); Console.WriteLine(Marshal.SizeOf(typeof(float))); Console.WriteLine(Marshal.SizeOf(typeof(Point))); }
9、避免使用指針
事實(shí)上在C#中,指針并不是新東西。因?yàn)樵诖a中可以自由使用引用 ,而引用就是一個(gè)類型安全的指針。指針只是一個(gè)存儲(chǔ)地址的變量,這和引用其實(shí)是一個(gè)原理。引用的主要作用是使C#更易于使用,防止用戶無意中執(zhí)行某些破壞內(nèi)存中內(nèi)容的操作。
使用指針后,可以進(jìn)行低級(jí)的內(nèi)存訪問,但這是有代價(jià)的,使用指針的語法比引用類型的語法復(fù)雜得多,而且指針使用起來也比較困難,需要較高的編程技巧和強(qiáng)力。如果不仔細(xì),就容易在程序中引入細(xì)微的,難以查找的錯(cuò)誤。另外,如果使用指針,就必須授予代碼運(yùn)行庫的代碼訪問安全機(jī)制的高級(jí)別信任,否則就不能執(zhí)行它。
MSDN上有如下關(guān)于指針的說明:
在公共語言運(yùn)行時(shí) (CLR) 中,不安全代碼是指無法驗(yàn)證的代碼。 C# 中的不安全代碼不一定是危險(xiǎn)的;只是 CLR 無法驗(yàn)證該代碼的安全性。 因此,CLR 將僅執(zhí)行完全信任的程序集中的不安全代碼。 如果你使用不安全代碼,你應(yīng)該負(fù)責(zé)確保代碼不會(huì)引發(fā)安全風(fēng)險(xiǎn)或指針錯(cuò)誤。
大多數(shù)情況下,可以使用System.Intptr或ref關(guān)鍵字來替代指針完成我們想要的操作。
下面使用示例代碼說明一下:(僅供演示)
這里還是以memcpy函數(shù)為例,假設(shè)我有一個(gè)Point結(jié)構(gòu)的實(shí)例,要對(duì)這個(gè)Point進(jìn)行拷貝。
聲明Point結(jié)構(gòu)
struct Point { public int x; public int y; }
使用System.IntPtr:
/// <summary> /// 使用IntPtr /// </summary> /// <param name="pDst"></param> /// <param name="pSrc"></param> /// <param name="count"></param> /// <returns></returns> [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl)] private static extern unsafe int memcpyi(IntPtr pDst, IntPtr pSrc, int count);
static void MemCpyIntPtr() { var p = new Point() { x = 200,y = 10}; Console.WriteLine(p.x + " " + p.y); var size = Marshal.SizeOf(p); IntPtr ptrSrc = Marshal.AllocHGlobal(size); IntPtr ptrDest = Marshal.AllocHGlobal(size); //將結(jié)構(gòu)體Point轉(zhuǎn)換成ptrSrc Marshal.StructureToPtr(p, ptrSrc, false); //memcpy memcpyi(ptrDest, ptrSrc, size); //再轉(zhuǎn)換成結(jié)構(gòu)體 Point p2 = new Point(); //先輸出一次進(jìn)行對(duì)比 Console.WriteLine(p2.x + " " + p2.y); p2 = (Point)Marshal.PtrToStructure(ptrDest, typeof(Point)); Console.WriteLine(p2.x + " " + p2.y); }
運(yùn)行結(jié)果如下:
200 10
0 0
200 10
使用指針:
/// <summary> /// 使用指針 /// </summary> /// <param name="pDst"></param> /// <param name="pSrc"></param> /// <param name="count"></param> /// <returns></returns> [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl)] private static extern unsafe int memcpyp(void* pDst, void* pSrc, int count);
static unsafe void MemCpyPointer() { Point p = new Point() { x = 200, y = 10 }; Point p2 = new Point(); Console.WriteLine(p.x + " " + p.y); Console.WriteLine(p2.x + " " + p2.y); Point* pSrc = &p; Point* pDest = &p2; memcpyp((void*)pDest, (void*)pSrc, sizeof(Point)); p2 = *pDest; Console.WriteLine(p2.x + " " + p2.y); }
運(yùn)行結(jié)果如下:
200 10
0 0
200 10
下面介紹使用指針傳遞時(shí)的另外一種情況,這種情況我們可以使用ref來代替指針完成操作。
先用C++封裝一個(gè)庫,導(dǎo)出如下函數(shù),用來打印一個(gè)整形數(shù)組
extern "C" __declspec(dllexport) void PrintArray(int* pa,int size); extern "C" __declspec(dllexport) void PrintArray(int* pa,int size) { for (size_t i = 0; i < size; i++) { std::cout << *pa << std::endl; pa++; } }
使用ref:
[DllImport("demo_lib.dll",EntryPoint = "PrintArray")] private static extern void PrintArrayRef(ref int pa,int size);
static void PrintArrayRef() { int[] array = new int[] { 1,2,3}; //使用ref關(guān)鍵字傳的是引用,ref[0]其實(shí)就是傳的首地址 PrintArrayRef(ref array[0], array.Length); }
運(yùn)行結(jié)果:
1
2
3
使用指針:
[DllImport("demo_lib.dll", EntryPoint = "PrintArray")] private static extern unsafe void PrintArrayPointer(int* pa, int size);
static unsafe void PrintArrayPointer() { int size = 3; int* array = stackalloc int[3]; for (int i = 0; i < size; i++) { array[i] = i+1; } PrintArrayPointer(array, size); }
運(yùn)行結(jié)果:
1
2
3
以上就是在C#中使用指針的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于C#使用指針的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C# WebService發(fā)布以及IIS發(fā)布
這篇文章主要介紹了C# WebService發(fā)布以及IIS發(fā)布的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-07-07C# PictureBox圖片控件實(shí)現(xiàn)圖片交換
在c#中可以使用PictureBox控件來呈現(xiàn)圖像,本文主要介紹了C# PictureBox實(shí)現(xiàn)圖片交換,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06C#動(dòng)態(tài)代碼生成控件后其他事件不能獲取該控件值的解決方法
這篇文章主要給大家介紹了關(guān)于C#動(dòng)態(tài)代碼生成控件后其他事件不能獲取該控件值的解決方法,文中通過圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07C#運(yùn)行程序時(shí)阻止關(guān)閉顯示器和系統(tǒng)待機(jī)
這篇文章介紹了C#運(yùn)行程序時(shí)阻止關(guān)閉顯示器和系統(tǒng)待機(jī)的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06