c#学习<二>:数据类型

时间:2023-03-09 16:13:06
c#学习<二>:数据类型

基元类型

编译器直接支持的数据类型称为基元类型(primitive type)。基元类型直接映射到Framework类库(FCL)中存在的类型(BCL是FCL的子集)。

C#中的基元类型

BCL类型

是否与CLS兼容

描述

sbyte

System.SByte

N

有符号8位值

byte

System.Byte

Y

无符号8位值

short

System.Int16

Y

有符号16位值

ushort

System.UInt16

N

无符号16位值

int

System.Int32

Y

有符号32位值

uint

System.UInt32

N

无符号32位值

long

System.Int64

Y

有符号64位值

ulong

System.UInt64

N

无符号64位值

char

System.Char

Y

16位Unicode字符

float

System.Single

Y

IEEE32位浮点数

double

System.Double

Y

IEEE 64位浮点数

bool

System.Boolean

Y

一个True或者Flase值

decimal

System.Decimal

Y

128位高精度浮点值,通常用于不容许有摄入误差的金融计算场合。

string

System.String

Y

一个字符数组

object

System.Object

Y

所有类型的基类型

dynamic

System.Object

Y

对于CLR, dynamic 和 object 完全一致。然而,C#编译器允许使用一个简单的语法,让dynamic变量参与动态调度。

8种整型

8位、16位、32位、64位,每种都有有符号和无符号两种类型。

c#学习<二>:数据类型

  1. BCL:基类库(Base Class Library),被包含在CLI(Commin Language Infrastructure,公共语言基础结构)规范中。
  2. 在c语言中short是 short int的缩写,而在c#中,short本身就是一种实际的数据类型
  3. 对比在java中没有无符号整数,仅4种整型byte、short、int、long,但有无符号右移位>>>
  4. 整型字面量解析顺序:int->uint->long->ulong
  5. 16进制:字面量用做0x前缀,将数值格式化为16进制:System.Console.WriteLine("0x{0:x}",42);
  6. c#没有8进制的字面量表示,java,c/c++,javascript 中8进制用0做前缀,010表示8,而C#中010依然是10。 swift中二进制:0b,八进制:0o,十六进制:0x

3种浮点

double、float

c#学习<二>:数据类型

  1. 取值范围和c语言,java不一样(依IEEE 754标准,指数部分单精度为8位(0~255),双精度为11位(0~2047),其中最大值和最小值表示无穷,实际使用时加上偏差值,单精度是-127,双精度是-1023,也就是说正负两侧的取值区间是对称的,但C#这里不对称,因为c#中指数部分的取值区间变了:-149~104,-1045~970),有效数字位数都是一致的
  2. 默认情况下,浮点数转换为字符串时,无效位会被丢弃。使用round-trip格式化的字符串再转换回的数值能获得原始值。
    static void Main(string[] args)
    {
    const double number = 1.618033988749895;
    Console.WriteLine("\n16个有效数字的浮点数转化为字符串时最后一位被舍弃");
    Console.WriteLine("number 16位 :1.618033988749895");
    Console.WriteLine("number.ToString() :" + number);
    const double number2 = 1618033.9887498951;
    Console.WriteLine("\n17个有效数字的浮点数转化为字符串时最后两位被舍弃");
    Console.WriteLine("number2 17位 :1618033.9887498951");
    Console.WriteLine("number2.ToString() :" + number2);
    const double number5 = 1.6180339887498751;
    Console.WriteLine("number5 17位 :1.6180339887498751");
    Console.WriteLine("number5.ToString() :" + number5);
    Console.WriteLine("\n15个有效数字的浮点数转化为字符串时位数都保留了");
    const double number3 = 1.61803398874969;
    Console.WriteLine("number3 15位 :1.61803398874969");
    Console.WriteLine("number3.ToString() :" + number3);
    const double number4 = 1.61803398874962;
    Console.WriteLine("number4 15位 :1.61803398874962");
    Console.WriteLine("number4.ToString() :" + number4);
    const double number6 = 1.618033988749623398874962;
    Console.WriteLine("\n25个有效数字的浮点数转化为字符串时最后10位被舍弃");
    Console.WriteLine("number6 25位 :1.618033988749627398874962");
    Console.WriteLine("number6.ToString() :" + number6); double result;
    string text; Console.WriteLine("\n浮点数位数太多时,格式为字符串再转换回来,是可能不等于原值的");
    text = string.Format("{0}", number);
    Console.WriteLine("number 16位 :1.618033988749895");
    Console.WriteLine("text = string.Format(\"{0}\", number) :" + text);
    result = double.Parse(text);
    Console.WriteLine("result = double.Parse(text) :" + result);
    Console.WriteLine("{0}:result!=number", result != number);//true Console.WriteLine("\n使用round-trip格式化的字符串再转换回的数值能获得原始值");
    text = string.Format("{0:r}", number);
    Console.WriteLine("text = string.Format(\"{0:r}\", number) :" + text);
    result = double.Parse(text);
    Console.WriteLine("result = double.Parse(text) :" + result);
    Console.WriteLine("{0}:result==number", result == number);//true Console.WriteLine("\n但这也只是相对的");
    text = string.Format("{0:r}", number6);
    Console.WriteLine("number6 25位 :1.618033988749627398874962");
    Console.WriteLine("text = string.Format(\"{0:r}\", number6) :" + text);
    result = double.Parse(text);
    Console.WriteLine("result = double.Parse(text) :" + result);
    Console.WriteLine("{0}:result==number", result == number);//true Console.ReadKey();
    }

    c#学习<二>:数据类型

  3. c#学习<二>:数据类型

decimal类型

128位精度的十进制浮点类型,适合大而精确的计算,尤其是金融计算。

在这128位中,1位表示浮点值的符号,96位表示浮点值本身(一个整数值,小数点位置由下面8个位来确定),8位表示用96位值除以浮点值所得结果的10的幂次(0~28)。其余的位尚未使用

缺点:取值范围较小,从浮点类型转换为decimal类型时可能发生溢出错误,此外计算速度稍慢。

c#学习<二>:数据类型

bool类型

对应的BLC名称是 System.Boolean。bool类型的大小是一个字节,虽然只有0,1两个值,但计算机的基本存储单位是字节。在c#,java中最小操作单位是字节。

c#不支持从数值类型到布尔类型的转换。

字符类型

对应的BLC名称是 System.Char。取值范围对应unicode字符集,占16个bit,取值范围 0 ~ 65536,可以当整数使用。

转义字符

c#学习<二>:数据类型

字符串

对应的BLC名称是 System.String。

逐字前缀字符@

指明转义序列不被处理。不仅将反斜杠当普通字符处理,还将逐字解释所有空白字符。

而在逐字前缀字符@修饰的字符串中双引号的字符采用转义序列""表示,用两个双引号代表一个双引号字符。

        static void Main(string[] args)
{
Console.Write(
@"begion ""
*
* *
* * *
end");
Console.ReadKey();
}

c#学习<二>:数据类型

System.Environment.NewLine

消除平台间换行符的不一致。

String是不可变的

字符串的长度是只读的,因为字符串不可修改,这一点与java一样。

同样的,java中JVM会使用一个字符串驻留池来缓存字符串字面。c#也类似的功能,在程序集中多次出现的同一个字符串字面量都指向同一个实例。

当需要经历多个步骤来构建一个长字符串的时候,一般使用StringBuilder。

字符串中的字符操作

可以直接通过索引读取字符串中的字符,但因为字符串是不可修改的,所以字符串中的字符也是只读的。

使用“asb”.ToCharArray()可以将整个字符串作为字符数组返回

总结

  1. java中的的8种基本数据类型(byte,short,int, long, float, double ,char , boolean)和c语言一样仅表示单个值。但java中没有无符号整数。
  2. c#中这些类型在BCL中定义,整型、浮点、字符、布尔类型全部采用结构的形式实现。
  3. c#中有无符号整数:byte,ushort,uint,ulong,但它们不是CLS-Compliant的。
  4. c#中浮点类型有3种: float,double, decimel 。 java中有java.math.BigDecimal

java中没有struct,如果使用类实现基本数据类型有两不便: 1、运行速度。2、类是引用类型。

所以java中使用类似C,C++的基本数据类型。而C#,swift则使用struct来构建基本数据类型,从而使基本数据类型在一定程度上也有了面向对象的特性。

java中的基本数据类型某些情况下不能满足程序的需求(如对泛型编程的支持),因此它们又分别有对应的类封装形式,并提供自动装箱、拆箱的功能。C#中的结构体,同样有需要将它们转换为object类型,或者它所实现的接口类型的时候。C#也提供了自动装箱、拆箱的功能。

java出于简约的设计理念,舍弃了无符号整数,并增加了无符号右移的操作符以满足特殊运算的需求。

数组

在java中的数组声明和c,c++类似

// 申请内存与c++相同,但无需delete操作
int x[] = new int[12]; // 也可以直接初始化,java会自动创建足够大的数组,以容纳数组初始化器里的元素
int days[] = {31,28,31,30,31,30,31}; // 多维数组
int twoD[][] = new int[4][5]; // 手动分配第二维
int twoD2[][] = new int[4][];
twoD2[0] = new int[1];
twoD2[1] = new int[2];
twoD2[2] = new int[3];
twoD2[3] = new int[4]; // 初始化器
int twoD3[][] = {
{1,2},
{1,2,3},
{1,2,3,4}
}; // 另一种数组声明语法
int[] y = new int[12]; //int x[] = new int[12];
int[][] twoD4 = new int[4][5];//twoD[][] = new int[4][5];

而在C#中

作为数组申明的一部分[]必须跟着数据类型后面。

int[] a = new int[3];
int[] b = new int[] { 1,2};
int[] d = new [] { 1, 2 }; //c# 3.0
int[] e = { 1,2 };

c#中的多维数组

int[,] c = new int[,]
{
{1,2 },
// {1 }, 数组长度必须与第一个一致
{2,4 }
};
Console.WriteLine($"c.Length:{c.Length}"); //Length属性返回的是所有维数中元素的总数
Console.ReadKey();
用方括号中的逗号定义附加的维,维数等于逗号数+1,内里的子数组的长度也要对齐。 //创建一个四行两列的二维数组:
int[,] array = new int[4, 2]; //创建一个三维数组:
int[,,] array1 = new int[4, 2, 3]; //数组初始化
// 1. 在声明数组时将其初始化
int[,] array2D = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
int[,,] array3D = new int[,,] { { { 1, 2, 3 } }, { { 4, 5, 6 } } };
// 2. 不使用new
int[,] array4 = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
// 3. 初始化延后
int[,] array5;
array5 = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } }; // OK
//array5 = {{1,2}, {3,4}, {5,6}, {7,8}}; // Error
array5[2, 1] = 25; Console.WriteLine(array5.Rank); //2(维数) //for遍历
for (int i = 0,rowNum = array5.GetLength(0); i < rowNum; i++)
{
for (int j = 0, colNum = array5.GetLength(1); j < colNum; j++)
{
if (j != 0)
{
Console.Write(", ");
}
Console.Write(array5[i, j]);
}
Console.Write(Environment.NewLine);
}
//foreach遍历
bool isfirst = true;
foreach (var item in array5)
{
if (!isfirst)
{
Console.Write(", ");
}
Console.Write(item);
isfirst = false;
}

c#学习<二>:数据类型

C#中的不规则数组(jagged array )

C#中的不规则数组才是对应的其他语言中的多维数组的结构,但是在C#中,它的声明方式有点特别。

int[][] cells =
{
new int[] {1,0,2,0},
new int[] {1,2,0},
new int[] {1,2},
new int[] {1}
};

调整数组的大小

因为数组在内存中都是连续存储的,所以它们的长度也都是固定的,无法动态扩展。改变数组的大小需要重新申请内存(实例化一个新的数组),然后把原数组的成员拷贝过去。

  1. .net 2.0提供了Resize()方法来重新创建新的数组,并把所有元素复制到新数组中。
  2. 也因为数组的大小的不可更改,所以数组的Clear方法不会删除数组中的元素,而是将将每个元素设为其默认值。

js中的数组:

var a = [[1,2],[3]];
a[2]= [4];
alert(a[0]); //1,2
alert(a[0][1]); //2
alert(a[2]); //4
alert(a[7]); //undefined

swift中的数组:

var a = [1,2]
print(a[1]) //2 var b = [[1,2],[2,3,3]]
print(b[1]) //[2, 3, 3] var array = [Int]()
array.append(1)
array += [3,3]
print(array) // [1,3,3] var array2 = [[Int]]()
array2.append([1,2])
array2.append([2,3,3])
print(array2) //[[1, 2], [2, 3, 3]]

数组的常用方法

//仅支持一维数组
int[] arr = new []{ 5,3,1,7};
System.Array.Sort(arr);
arr.Print(); int index = System.Array.BinarySearch(arr, 5);
Console.WriteLine(index);
int index2 = System.Array.BinarySearch(arr, 4);
Console.WriteLine(index2);
Console.WriteLine(~index2);
System.Array.Reverse(arr);
arr.Print(); System.Array.Clear(arr,1,2);
arr.Print(); System.Array.Resize<int>(ref arr, 7);//.net 2.0
arr.Print();
Console.ReadKey(); public static void Print(this System.Array array)
{
bool isfirst = true;
Console.Write("[");
foreach (var item in array)
{
if (!isfirst)
{
Console.Write(", ");
}
Console.Write(item);
isfirst = false;
}
Console.Write("]"+Environment.NewLine);
}

c#学习<二>:数据类型

  • 使用BinarySearch前要先调用Sort对数组排序,如果数组不是升序排列的,就会返回错误的索引。如果搜索的元素不存在,会返还一个负数,对其取反~index,得到的是大于搜索值的第一个索引(如果有的话)

DateTime

结构实现

void

void表示没有类型或没有任何值。

1. 在返回类型的位置使用void标记函数不返回任何数据

2. 表示指针指向未知类型的存储空间 void**

与null都表示空,但一个是类型,一个是值。

null

null 表示空指针

大多数计算机语言,都有一个表示"无"的值。

  1. 在c/c++中表示空指针的NULL,它是一个宏定义,相当于0,类似的还有BOOL等。
  2. java,c#中的null在功能上虽然与NULL一样,但却是一个关键字符号,而不是宏定义。在C#,java中对象、可空值类型都是可能为空的,每次读取操作都需要先判别其是否是null。
  3. 在Swift语言中这个关键字写作nil, 表示可选类型的值缺失,和null并不完全相同,swift中的可选类型类似可空值类型,不同在于,对于引用类型只有其在定义时被明确申明为可选类型,它才可能出现值缺失的情况。
  4. 在javascript语言中则有两个表示空的关键字,除了null, 又额外分出一个undefined

JavaScript诞生时,最初像Java一样,只设置了null作为表示"无"的值。

根据C语言的传统,null被设计成可以自动转为0。JavaScript的设计者Brendan Eich认为,仅有一个null还不够,有两个原因。

首先,null像在Java里一样,被当成一个对象。但是,JavaScript的数据类型分成原始类型(primitive)和合成类型(complex)两大类,Brendan Eich觉得表示"无"的值最好不是对象。

其次,JavaScript的最初版本没有包括错误处理机制,发生数据类型不匹配时,往往是自动转换类型或者默默地失败。Brendan Eich觉得,如果null自动转为0,很不容易发现错误。

因此,Brendan Eich又设计了一个undefined。

  • null表示"空,没有对象",但“空”本身也是一个取值,可以给一个引用变量赋值为空,表示空指向,但不可能给一个值类型赋值为null,可空值类型本身也是一种封装,所以typeof null === object
  • undefined表示"缺少值",就是此处应该有一个值,但是还没有定义

在静态类型的语言里不会碰到需要undefined的情况,因为值缺失编译压根通不过。但js是一门动态类型的脚本语言。值存不存在只有在运行期中才知道。这时候需要一个特定的值来表示取值失败。

可空修饰符?

值类型直接保存数据,引用类型则保存一个地址,这个地址指向一个保存在堆中的对象。

当引用类型没有指向时,可赋值为null。但值类型有的时候也需要表示空的概念(特别是数据库编程中)。于是在c#2.0中引入了可空修饰符。

int? count = null;

类型转换

数据类型间的类型转换

可能造成大小变小或引发异常的转换都需要显示转型,反正则属于隐式转型。

显式转型可能丢失精度和数据,如果不能成功转型,则运行时会引发异常。需要程序员确保数据能成功转换,或者提供错误处理机制。

checked和unchecked

下列代码默认是不检测溢出的

int maxInt = int.MaxValue;
Console.WriteLine($"int.MaxValue:{maxInt}");
Console.WriteLine($"int.MaxValue+1:{maxInt + 1}");

c#学习<二>:数据类型 

将上面的代码放入checked块中

checked
{
int maxInt = int.MaxValue;
Console.WriteLine($"int.MaxValue:{maxInt}");
Console.WriteLine($"int.MaxValue+1:{maxInt + 1}");
}

就会引发一个未处理的异常:“System.OverflowException”类型的未经处理的异常在 String.exe 中发生

编译器默认是不检测溢出的,但可以通过配置改变编译器的这一默认选项。

而将代码放入unchecked块中,即便运行时打开了checked选项,在执行期间,unchecked关键字也会阻止运行时引发异常。

unchecked
{
int maxInt = int.MaxValue;
Console.WriteLine($"int.MaxValue:{maxInt}");
Console.WriteLine($"int.MaxValue+1:{maxInt + 1}");
}

对decimal类型来说,checked和uncheked操作符、语句和编译器都是无效的,decimal溢出时是一定会抛出异常的。

Parse

前面的整型,浮点,小数,布尔,字符除string外的所有基元类型都提供了从字符串转换的方法Parse。

2.0后又增加了TryParse(),转换识别时不会引发异常。

System.Convert类型

容许从任何基元类型转换到其他基元类型。

隐式类型var

c#3.0中增加的上下文关键字var,用来声明隐式类型的局部变量。需要在声明类型的同时用确定类型的表达式来初始化它。

一般在类型已经的情况下,除非右侧的数据类型是非常明显的,否则,尽量不要使用var, 这样代码更易理解的同时也能检测右侧的表达式是否返回了预期的类型。

关键字var的添加是为了支持匿名类型。

var a = new { count = 14, date = "2016-5-29" };
Console.WriteLine($"count:{a.count},date:{a.date}");

动态类型dynamic

C#4.0引入了动态类型,动态类型的实质是变量中含有值,但那些值并不限于特定的类型,所以编译器不能执行相同形式的检查。相反,执行环境会视图采取一种合适的方式来理解引用值的给定表达式。动态类型的绑定操作可以发生在执行时而不是编译时。可用于简化COM互操作、与动态语言互操作等。

C#是面向对象的语言,通过类来实现面向对象的封装。封装对象不一定用类,但类是最普遍的。

类的成员

字段、属性、方法、构造函数等

如果把类理解为一组相关的变量和函数的集合。那么类中的核心成员就是字段和方法(行为)。

当然这里的变量不仅可以是基本数据类型,也可以是枚举、结构、或其他的类封装。

类的访问权限

public , internal 或无, abstract, seal, internal abstract, internal seal

成员的访问权限

public , private ,protected, internal , protected internal

字段

字段在类中的形式就是一个申明的变量。不过是前面一般要加上一些修饰词。

对象都有状态,如人有性别,身份,年龄,衣着等……。

这些状态在类结构中对应字段,类似数据库中数据表的每一列,是用来存储数据的。可以说是一个对象的核心部分。

设计模式中备忘录模式,就是通过保存对象的状态来实现备份还原。

public class Person
{
public static string Earth;
public string Name;
}

修饰词

static , readonly, const, volatile,public, private, protected

方法

方法由方法签名和一系列语句的代码块组成。形式上就是一个带了修饰词的函数。

public class Person
{
public static string Earth = "地球";
public string Name = "张三"; public void Run(string somePlace)
{
Console.WriteLine($"{Name}跑步去{somePlace}");
}
}

如上例的Run方法,它定义了对象Person的一种行为:跑。

Run是方法名。返回值void,参数列表 (string)。public是访问权限修饰词。

重载

通过改变参数列表可以对方法实现重载。但函数的类型是有由参数列表和返回值联合构成的,为什么仅改变返回值类型不能实现重载呢。

public class Person
{
public static string Earth = "地球";
public string Name; public Person(string Name)
{
this.Name = Name;
} public void Run(string somePlace)
{
Console.WriteLine($"{Name}跑步去{somePlace}");
} public void Run(string somePlace, Person somebody)
{
Console.WriteLine($"{Name}和{somebody.Name}跑步去{somePlace}");
}
//public int Run (string somePlace)
//{ //}
}  

  

Person A =  new Person("张三");
Person B = new Person("李四");
A.Run("操场");
A.Run("操场",B);
A.Run(somePlace: "操场", somebody: B);

上例,为对象Person增添了一个构造方法,及一个Run的重载方法,另一个尝试只改变返回类型的重载则以报错而结束。

在下面的调用中可以看到,如果仅改变返回值就能重载的话,A.Run("操场"); 这句就不知道该执行谁了。

c#4.0后可以使用命名实参(A.Run(somePlace: "操场", somebody: B)),使代码含义更加清楚。

修饰词

static , virtual,abstract,sealed,public, private, protected

重写

  1. C#中只有主动声明为virtual或abstract的方法才能在子类中对其重写。
  2. 重写时必须使用override关键字。
  3. 如果子类中还想继续调用基类中定义的方法,则通过base关键字来实现。
  4. sealed关键字修饰的类不能被继承。sealed修饰的方法将不能再被重写,类似java中的final
  5. 使用new关键字可以在子类中定义一个和基类同名的成员,但它和基类没有关系,只是子类中新增的一个成员。
public class Person
{
// …… public void Run(string somePlace)
{
Console.WriteLine($"{Name}跑步去{somePlace}");
} //虚方法,可以重写
public virtual void Run(string somePlace, Person somebody)
{
Console.WriteLine($"{Name}和{somebody.Name}跑步去{somePlace}");
}
}

  

public class Chinese : Person
{
public Chinese(string Name) : base(Name, 30)
{ }
public new int Age { get; set; } // 同名成员
public new void Run(string somePlace)
{
Console.Write($"子类调用,{Age},{base.Age}:");
base.Run(somePlace);
} //重载
public void Run(object somePlace)
{
Console.Write($"子类调用:");
base.Run(somePlace.ToString());
} //重写
public sealed override void Run(string somePlace, Person somebody)
{
Console.Write($"子类调用:");
base.Run(somePlace, somebody);
}
}

  

Person A = new Person("张三");
Person B = new Chinese("李四");
A.Run("操场");
A.Run("操场", B);
B.Run("操场");
((Chinese)B).Run("操场");
B.Run(somePlace: "操场", somebody: A);

c#学习<二>:数据类型

调整:

public class Chinese : Person
{
public Chinese(string Name) : base(Name, 30)
{ }
public new int Age { get; set; } //重载版本1
public void Run(IEnumerable<char> somePlace)
{
Console.Write($"子类调用:");
base.Run(somePlace.ToString());
} //重载版本2
public void Run(object somePlace)
{
Console.Write($"子类调用2:");
base.Run(somePlace.ToString());
} //重写
public sealed override void Run(string somePlace, Person somebody)
{
Console.Write($"子类调用:");
base.Run(somePlace, somebody);
}
}

c#学习<二>:数据类型

总结:

虽然使用new关键字可以在子类中定义一个和基类同名的成员。但它的调用规则却和正常的重写是不一致的。反而更像重载(参数逆变或返回类型协变)的调用规则。并没有覆盖掉基类的字段和方法。只是并存了。

 以 Person B = new Chinese("李四");的方式实例化对象。

B.Run(somePlace: "操场", somebody: A); 顺利调用了子类中的重写。

B.Run("操场"); 却依旧调用的基类的方法,只有对其强制类型转换后才会调用子类的方法。

  而在子类中同时存在两个可匹配的方法时,自动匹配了范围最贴近的那个(第一次调用了void Run(string somePlace),在这个方法被移除后,调用了void Run(IEnumerable<char> somePlace)

构造函数

构造函数的存在价值是为了初始化类的成员(字段)。

对比:

1. C#中有静态构造函数,可以初始化静态成员。在创建第一个实例或引用任何静态成员前执行。

2. C#,java中的字段都有默认值,如对象类型的默认值是null,数值类型的默认值是0,布尔类型的默认值是false。

所以在C#,java中,子类实例化时会先调用父类的构造函数,再调用自身的。

但在swift中,因为可选类型的引入,类中的字段是不会再默认为null,必须要执行初始化操作。所以子类实例化时要先将自身新增的字段都初始化了之后才能调用父类的构造函数,否则编译器会报错。

ps:

  1. 构造函数的方法名必须与类名一致且没有返回值。
  2. 实例构造函数可以重载。
  3. 实例构造函数可以指定访问级别。
  4. 实例构造函数可以省略,编译器会自动添加一个函数体为空的默认无参的实例构造函数。
  5. 静态构造函数不能带任何参数
  6. 静态构造函数不能使用任何访问修饰符
  7. 静态构造函数只会执行一次
  8. 不能直接调用静态构造函数
  9. 程序员无法在程序中控制静态构造函数的执行时机。
public class Person
{
public static string Earth = "地球";
public string Name;
public int Age; public Person(string Name):this(Name,20)
{ }
public Person(string Name,int Age)
{
this.Name = Name;
this.Age = Age;
} public void Run(string somePlace)
{
Console.WriteLine($"{Name}跑步去{somePlace}");
} public void Run(string somePlace, Person somebody)
{
Console.WriteLine($"{Name}和{somebody.Name}跑步去{somePlace}");
}
} public class Chinese : Person
{
public Chinese(string Name) : base(Name, 30)
{ }
}

  

Person A =  new Person("张三");
Person B = new Chinese("李四");
A.Run("操场");
A.Run("操场",B);

  

对比Java中的实现方式

public class Person
{
public static String Earth = "地球";
public String Name;
public int Age; public Person(String Name)
{
this(Name,20);
}
public Person(String Name,int Age)
{
this.Name = Name;
this.Age = Age;
} public void Run(String somePlace)
{
System.out.println(Name+"跑步去"+somePlace);
} public void Run(String somePlace, Person somebody)
{
System.out.println(Name + "和"+somebody.Name+"跑步去"+somePlace);
}
} public class Chinese extends Person
{
public Chinese(String Name)
{
super(Name, 30);
}
}

属性

属性是对字段的扩展。 根据面向对象的封装思想。字段最好应该设为private。这样可以防止直接对字段进行篡改,保证内部成员的完整性。

为了访问私有字段,提供了属性这种机制:

选中上例中要封装的字段

c#学习<二>:数据类型

c#学习<二>:数据类型

public class Person
{
public static string Earth = "地球";
private string name;
private int age; public static string Earth
{
get
{
return earth;
} set
{
earth = value;
}
}
public string Name
{
get
{
return name;
} private set
{
name = value;
}
}
public int GetAge()
{
return age;
}
private void SetAge(int value)
{
age = value;
}
//......
}

PS:

  1. 属性定义由get访问器和set访问器组成。隐式参数value表示用户传入的值。可以理解为两个方法(上例中对age属性的属性实现方式)
  2. 可以按照需要分别设置不同的访问权限(C# 2.0)。
  3. 可以为静态字段设置静态属性。
  4. 属性的访问器就是两个简写的方法,所以可以在其中根据需要加入更多的逻辑控制代码。

自动实现的属性和简化的初始化

在C#3.0中提供了自动实现的属性和简化的初始化

public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(){}
} Person A = new Person { Name ="张三",Age = 20};

  

 不只是简化了代码,由于不再有name和age变量可供访问,我们必须在类中处处使用属性,这增强了一致性

索引器

索引器类似于属性,也提供get访问器和set访问器。通过索引器可以实现对集合类型的字段的限制性访问。

public class Person
{
private List<string> identity = new List<string>(); public string this[int index]
{
get
{
return identity[index];
}
private set
{
identity[index] = value;
}
}
}

析构函数

析构函数用于在类销毁前释放类实例所使用的托管和非托管资源。

对封装了非托管资源的对象,在应用程序使用完这些非托管资源之后,垃圾回收站(GC)将运行对象的析构函数来释放这些资源。

~Person()
{
Console.WriteLine("析构函数被调用了");
}

ps:

  1. 一个类只能有一个析构函数
  2. 无法继承或者重载析构函数
  3. 无法显示地调用析构函数,只能由垃圾回收器自动调用
  4. 析构函数既没有修饰词也没有参数

上面的析构函数会被隐式地转换为如下代码

protected override void Finalize()
{
try
{
Console.WriteLine("析构函数被调用了");
}
finally
{
base.Finalize();
}
}

c#学习<二>:数据类型

c#学习<二>:数据类型

执行结果:

Person A = new Person("张三");
Person B = new Chinese("李四");
A.Run("操场");
A.Run("操场", B);
A.Run(somePlace: "操场", somebody: B); A = null;
B = null;
GC.Collect();

c#学习<二>:数据类型

枚举类型

c#中的枚举是值类型,默认情况下,每个元素都是整数。用户可以用冒号指定一种全新的整数值类型

enum Gender : byte
{
Female,
Male
}

与C语言中的枚举区别不大。

对比: java中的枚举是通过类实现。 自动继承超类 java.lang.Enum,使用非常灵活。

swift中的枚举的元素类型则不再局限于整数,且拥有部分面向对象的特性(封装、多态、实现接口)。

结构类型

C#中的结构体也是一种值类型。

C#中的结构体有着和类几乎相同的语法,可以用来实现一些轻量级的对象封装。

PS:

  1. 类中如果没有显式地定义一个构造函数,则编译器会自动生成一个无参数的实例构造函数,称之为隐式构造函数。而一旦我们主动定义了一个构造函数后,编译器就不会再自动生成隐式构造函数了。 但在结构体中,隐式构造函数始终存在。所以也就不能显式地定义一个无参的构造函数。
  2. 结构体中的字段不可以在声明时直接初始化,所以在构造函数中必须为所有字段赋值。
  3. 结构体只能实现接口,不能被继承。也不能继承类。
  4. 不能有析构函数
  5. 不能用abstract,seal 修饰。
public struct Rect
{
int width;
int height;
public Rect(int width,int height)
{
this.width = width;
this.height = height;
}
public Rect(int width) : this(width, 20)
{ }
public int GetArea()
{
return width * height;
}
}

  

Console.WriteLine(new Rect().GetArea());
Console.WriteLine(new Rect(2,3).GetArea());
Console.WriteLine(new Rect(width: 2,height: 3).GetArea());

  

对比:

java中没有结构体

swift中的结构体与c#中的类似,同样可以封装,多态。同样可以实现接口。但都不能被继承,也不能继承类。