C#数据类型:值类型、引用类型、指针类型
数据类型
在 C# 中,变量分为以下几种类型:
- 值类型(Value types)
- 引用类型(Reference types)
- 指针类型(Poninter types)
值类型
值类型变量可以直接分配给一个值。它们是从类System.ValueType中派生的。
值类型直接包含数据。比如int、char、float,它们分别存储数字、字符、浮点数。当您声明一个int类型时,系统分配内存来存储值,通常这个值会直接存储与栈中,而不是堆中。
值类型不可为空:int x = null (错误写法)。
可空值类型需要加?:int? x = null(正确写法)。
引用类型
引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用。它们指的是一个内存位置。使用多个变量时,引用类型可以指向一个内存位置。如果内存位置的数据是由一个变量改变的,其他变量会自动反映这种值的变化。内置的引用类型有:object、dynamic和string。
-
对象(Object)类型
对象(object)类型是C#通用类型系统(Common Type System - CTS)中所有数据类型的终极基类。Object是System.Object类的别名。所以对象(Object)类型可以被分配任何其它类型(值类型、引用类型、预定义类型或用户自定义类型)的值。但是,在分配值之前,需要先进行类型转换。
当一个值类型转换为对象类型时,则被称为装箱;另一方面,当一个对象类型转换为值类型时,则被称为拆箱。
-
动态(Dynamic)类型
可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在进行时发生的。
声明动态类型的语法:
dynamic <variable_name> = value;
例如:
dynamic a = 9;
动态类型与对象类型相识,但是对象类型变量的类型检查是在编译时发生的,而动态类型变量的类型检查是在运行时发生的。
-
字符串(String)类型
字符串(String)类型 允许您给变量分配任何字符串值。字符串(String)类型是System.String类的别名。它是从对象(Object)类型派生的。字符串(String)类型的值可以通过两种形式进行分配:引号和@引号。
最常见的用法是直接双引号:
string str = "hello world";
C# string字符串的前面可以加@(称作“逐字字符串”)将转义字符串(\)当作普通字符对待,比如
string str = @"c:\directory";
上面代码的意义等价于
string str = "c:\\directory";
@字符串中可以任意换行,换行符及缩进空格计算在字符串长度之内。列如
1 string str = @"<script type=""text/javascript""> 2 <!-- 3 --> 4 </script>";
指针类型
指针是变量的内存地址,涉及到指针的代码必须写到unsafe代码块(或方法中)。
指针变量声明的一般形式为:
type* var-name;
下面是指针类型声明的实例:
1 int* p; //p 是指向整数的指针 2 double* p; //p是指向双精度数的指针 3 float* p; //p是指向浮点数的指针 4 int** p; //p是指向整数的指针的指针 5 int*[] p; //p是指向字符的指针 6 char* p; //p是指向浮点数的指针 7 void* p; //p是指向未知类型的指针
在同一个声明中声明多个指针时,星号 * 仅与基础类型一起写入;而不是用作每个指针名称的前缀。 例如:
1 int* p1, p2, p3; // 正确 2 int *p1, *p2, *p3; // 错误
赋值地址用&,列如
1 int i = 10; 2 int* iptr = &i;
其中 *iptr 是 iptr 的值,而&iptr 是 iptr 的地址。
因为类是托管类型,受GC影响,内存地址不是固定的,引用类型不可声明为指针。而指针分配内存后,内存是固定的,所以如果要使用类中的数据,就需要用fixed关键字临时固定类的指针。列如:
1 // 将peopleObj.Age的地址传给整型指针agePtr 2 fixed (int* agePtr = &peopleObj.Age) 3 { 4 *agePtr += 1; //这时peopleObj.Age的值就跟着变了 5 }
fixed也可以用固定数组,例如:
1 int[] list = {10, 20, 100}; 2 fixed(int* ptr = list)// 将整型数组赋值给ptr指针 3 4 // 如何使用这个指针? 5 // 如何获取地址? 6 (int)(ptr + 0) //内存地址33221102 7 (int)(ptr + 1) //内存地址33221106 8 (int)(ptr + 2) //内存地址33221110 9 10 //如何获取值? 11 *(ptr + 0) //10 12 *(ptr + 1) //20 13 *(ptr + 2) //100
说明:
C# 是一种高级编程语言,通常不鼓励直接使用指针,因为它们可以引起内存管理问题和安全问题。主要用于以下几种情况:
-
与非托管代码交互:在某些情况下,您可能需要与非托管代码(如C/C++库或操作系统API)进行交互。指针可以用于传递数据或引用非托管内存区域。这通常需要使用
unsafe
关键字。 -
性能优化:在某些情况下,使用指针可以提高性能,特别是在处理大型数据结构或执行计算密集型操作时。不过,应该小心使用指针,以避免潜在的错误和不安全的代码。
-
数据结构操作:某些数据结构(如树、图、矩阵等)可能需要直接操作内存以实现高效的算法。指针可以用于这些情况,但必须小心谨慎。
在C#中,要使用指针,您需要在编译时启用unsafe
上下文,然后使用fixed
关键字来标记指针变量。以下是一个简单的示例:
1 unsafe void Example() 2 { 3 int x = 10; 4 int* ptr = &x; //声明一个指向整数的指针 5 6 //使用指针来修改变量的值 7 *ptr = 20; 8 9 Console.WriteLine(x); //输出 20 10 }
要使用指针,您需要在项目属性中启用"允许不安全代码"选项。使用指针时要非常小心,以避免内存泄漏和潜在的安全漏洞。 C# 引入了指针类型的概念,以帮助程序员更容易地进行指针操作,但仍然需要小心谨慎。如果不熟悉指针和内存管理,最好避免使用它们,除非绝对必要。