c# 值类型
目录
- 1、c#类型
- 2、值类型
- 2.1 结构体
- 2.2 枚举
1、c#类型
类型(Type)又叫数据类型(Data Type)。
A data type is a homogeneous collection of values,effectively prensented,equipped with a set of operations which manipulate these values.
- 数据类型是由相同类型的值组成的集合。比如int[]是整数的集合。
- 数据类型配备有专门针对自己的值的一组运算操作,比如int类型的数据可以进行加法、减法、乘法、除法操作。深层的意思是,这一组操作是专门为这一种数据类型准备的,我们不能拿数据类型A的操作去对数据类型B进行操作。
- 数据类型的“类型”二字包含有“型号”的意思,也就是说,一个数据类型代表着这种数据类型的值在内存中存储时需要占多少的内存,比如对于int来说,它可存储-2,147,483,648 到 2,147,483,647范围内的值,需要占4个字节。我们在存储数据时,应该选择合适的数据类型,比如我们想要存储100这个数据,如果使用int的话显得太浪费了,使用byte就够用了,byte只占一个字节;而如果我们想要存储256这个数据,使用byte就不行了,因为byte只能存储0~255的整数。所以说,大内存存储小尺寸的数据会导致浪费,小内存存储大尺寸的数据会导致丢失精度。举一个现实中的例子,有一个盒子,如果我们拿一个橡皮丢进去,空间完全足够而且还有很多空出来的空间没有得到利用,而如果我们拿一把椅子丢进去就不行了,这时候如果想强制丢进去,只能把椅子的一部分放进去,椅子就会被损坏了。
- 数据类型会被有效地表示,包括存储在内存中的位置、占内存的大小、类型包含的成员(方法、字段、事件等)、类型所继承的基类型。
- c#是一种强类型的语言,这意味着每个变量和常量都必须有一个明确的数据类型。这样编译器就能保证代码中执行的所有运算都是类型安全的。例如,如果定义了一个 int 类型的变量,则编译器允许在加法和减法运算中使用此变量, 如果尝试在一个 string 类型的变量上执行相同的运算,则编译器会产生错误。
// c#代码
int a = 10;
string str = "Hello, world!";
int b = a + str;
//输出结果为:
//无法将类型“string”隐式转换为“int”
# python代码
a = 10
a = "Hello, world!"
c#类型分为值类型和引用类型,值类型有结构体和枚举,引用类型有类、接口、委托。
struct MyStruct // 定义结构体
{
}
Type type = typeof(int); //使用typeof关键字获取int的类型
Console.WriteLine(type.BaseType); //打印int的基类型
Console.WriteLine(type.BaseType.BaseType); //打印int的基类型的基类型
Console.WriteLine("---------");
type = typeof(MyStruct); //使用typeof关键字获取MyStruct的类型
Console.WriteLine(type.BaseType); //打印MyStruct的基类型
Console.WriteLine(type.BaseType.BaseType); //打印MyStruct的基类型的基类型
//输出结果为:
//System.ValueType
//System.Object
//---------
//System.ValueType
//System.Object
2、值类型
值类型的变量存储数据,而引用类型的变量存储对实际数据的引用。详见引用变量与实例。
2.1 结构体
结构体和类很相似,结构体通常用来封装小型相关变量组。
与类相比,结构体有一些限制,例如它不能声明为抽象的或密封的,它也不能声明默认构造函数(没有参数的构造函数)和析构函数。结构体通常用于小型、不可变的数据结构,而类更适合用于需要更复杂行为的对象。
结构体在C#中是实现轻量级数据结构的强大工具,它在性能上通常优于类,因为它避免了垃圾回收的开销。然而,它也有一定的限制,比如不能被声明为可空的,并且当结构体包含引用类型字段时,可能会引入垃圾回收的开销。
结构体可以包含构造函数、 常量、 字段、 方法、 属性、 索引器、 运算符、 事件和嵌套类型,但如果同时需要上述几种成员,则应当考虑改为使用类作为类型。
struct Student
{
public int age;
public int height;
public double weight;
public string name;
}
我们不能在结构体中初始化实例字段,可以在结构体中初始化静态字段以及常量。
struct Student
{
public static int avgAge = 10; //可以在结构体中初始化静态字段
public const int height = 100; //可以在结构体中初始化常量
public int age; //不能在结构体中初始化实例字段
}
要想初始化实例字段,有两种方法:一是使用参数化构造函数,二是在声明结构后分别访问成员。
struct Student
{
public Student(int x)
{
age = x;
}
//public static int avgAge = 10;
public int age;
}
Student stu = new Student(10); //使用参数化构造函数初始化实例字段
Console.WriteLine(stu.age);
stu.age = 20; //声明结构后访问实例字段
Console.WriteLine(stu.age);
//输出结果为:
//10
//20
与类不同,结构的实例化可以使用new运算符,也可以不使用。如果使用的话,会创建该结构的对象,并调用构造函数,构造函数不传入参数的话,调用的是默认构造函数,默认构造函数会对结构体的成员进行初始化;如果不使用的话,就不会调用构造函数,在初始化所有字段之前,字段将保持未赋值状态且对象不可用。
struct Student
{
public int age;
public int height;
}
Student stu1; //不使用new创建对象
Student stu2 = new Student(); //使用new创建对象,并调用构造函数
struct Student
{
public int age;
public int height;
}
Student stu1; //不使用new创建对象
Console.WriteLine(stu1.age);
//输出结果为:
//使用了可能未赋值的字段"age"
所以正确的做法应该是:
struct Student
{
public int age;
public int height;
}
Student stu1;
stu1.age = 10;
stu1.height = 130;
Console.WriteLine(stu1.age);
Console.WriteLine(stu1.height);
struct Student
{
public int age;
public int height;
}
Student stu2 = new Student();
Console.WriteLine(stu1.age);
Console.WriteLine(stu1.height);
2.2 枚举
枚举类型用enum关键字进行声明,它是一种由一组称为枚举数列表的命名常量组成的独特类型。
通常情况下,最好是在命名空间内直接定义枚举,以便该命名空间中的所有类都能够同样方便地访问它。 但是,还可以将枚举嵌套在类或结构中。
默认情况下,第一个枚举数的值为 0,后面每个枚举数的值依次递增 1。
namespace ConsoleApp1
{
enum Days { Mon, Tue, Wed, Thu, Fri, Sat, Sun };
class Program
{
static void Main(string[] args)
{
Console.WriteLine((int)Days.Mon);
Console.WriteLine((int)Days.Tue);
Console.WriteLine((int)Days.Wed);
}
}
}
//输出结果为:
//0
//1
//2
当然,也可以强制元素序列从1开始。
namespace ConsoleApp1
{
enum Days { Mon=1, Tue, Wed, Thu, Fri, Sat, Sun };
class Program
{
static void Main(string[] args)
{
Console.WriteLine((int)Days.Mon);
Console.WriteLine((int)Days.Tue);
Console.WriteLine((int)Days.Wed);
}
}
}
//输出结果为:
//1
//2
//3
枚举类型的默认基础类型是int,所以,上述代码中定义枚举类型变量的完整表达为:
enum Days:int { Mon=1, Tue, Wed, Thu, Fri, Sat, Sun };
枚举类型变量可赋以基础类型范围内的任何值,准许使用的枚举类型有 byte、 sbyte、 short、 ushort、 int、 uint、 long 或 ulong。
namespace ConsoleApp1
{
enum Days:byte { Mon=1, Tue=2, Wed=10, Thu=20, Fri=30, Sat=100, Sun=255 };
class Program
{
static void Main(string[] args)
{
Console.WriteLine((byte)Days.Mon);
Console.WriteLine((byte)Days.Tue);
Console.WriteLine((byte)Days.Wed);
Console.WriteLine((byte)Days.Thu);
Console.WriteLine((byte)Days.Fri);
Console.WriteLine((byte)Days.Sat);
Console.WriteLine((byte)Days.Sun);
}
}
}
//输出结果为:
//1
//2
//10
//20
//30
//100
//255
在switch语句中使用枚举值。
namespace ConsoleApp1
{
enum Days { Mon, Tue, Wed, Thu, Fri, Sat, Sun };
class Program
{
static void Main(string[] args)
{
Days day = (Days)1;
switch (day)
{
case Days.Mon:
Console.WriteLine("Today is Mon");
break;
case Days.Tue:
Console.WriteLine("Today is Tue");
break;
case Days.Wed:
Console.WriteLine("Today is Wed");
break;
case Days.Thu:
Console.WriteLine("Today is Thu");
break;
case Days.Fri:
Console.WriteLine("Today is Fri");
break;
case Days.Sat:
Console.WriteLine("Today is Sat");
break;
case Days.Sun:
Console.WriteLine("Today is Sun");
break;
}
}
}
}
使用枚举类型的好处:
- 明确变量可以存储的值。
enum Days:byte { Mon=1, Tue=2, Wed=10, Thu=20, Fri=30, Sat=100, Sun=255 };
Days day = Days.Mon;
在这个程序中,一个星期只能包含从星期一到星期日的7天,所以只能取枚举中的值。
我们可以使用扩展方法为枚举类型添加功能。
namespace ConsoleApp1
{
// 在非泛型静态类中定义扩展方法
public static class Extensions
{
public static Grades minPassing = Grades.D;
// this关键字在方法定义中用于指定这是一个扩展方法
//this关键字后面跟着的是类型参数,表示这个扩展方法可以被任何Grade类型的实例调用
public static bool Passing(this Grades grade)
{
return grade >= minPassing;
}
}
public enum Grades { F = 0, D=1, C=2, B=3, A=4 };
class Program
{
static void Main(string[] args)
{
Grades g1 = Grades.D;
Grades g2 = Grades.F;
Console.WriteLine("First {0} a passing grade.", g1.Passing() ? "is" : "is not");
Console.WriteLine("Second {0} a passing grade.", g2.Passing() ? "is" : "is not");
Extensions.minPassing = Grades.C;
Console.WriteLine("\r\nRaising the bar!\r\n");
Console.WriteLine("First {0} a passing grade.", g1.Passing() ? "is" : "is not");
Console.WriteLine("Second {0} a passing grade.", g2.Passing() ? "is" : "is not");
}
}
}
}
/* 输出结果为:
First is a passing grade.
Second is not a passing grade.
Raising the bar!
First is not a passing grade.
Second is not a passing grade.
*/
实际上,通过枚举类型实例对扩展方法的调用,等效于调用普通非扩展方法的方式。也就是说,调用扩展方法与调用在类型中实际定义的方法之间没有明显的差异。
Console.WriteLine("First {0} a passing grade.", g1.Passing() ? "is" : "is not");
Console.WriteLine("Second {0} a passing grade.", g2.Passing() ? "is" : "is not");
// 等效于
Console.WriteLine("First {0} a passing grade.", Extensions.Passing(g1) ? "is" : "is not");
Console.WriteLine("First {0} a passing grade.", Extensions.Passing(g2) ? "is" : "is not");