C#基础---变量、常量、数据类型


一、变量

一个变量只不过是一个供程序操作的存储区的名字。在 C# 中,每个变量都有一个特定的类型,类型决定了变量的内存大小和布局。范围内的值可以存储在内存中,可以对变量进行一系列操作。

1. 变量的定义

<data_type> <variable_list>;
在这里,data_type 必须是一个有效的 C# 数据类型,可以是 char、int、float、double 或其他用户自定义的数据类型。variable_list 可以由一个或多个用逗号分隔的标识符名称组成。

2. 初始化

变量通过在等号后跟一个常量表达式进行初始化(赋值)。初始化的一般形式为:

variable_name = value;
变量可以在声明时被初始化(指定一个初始值)。初始化由一个等号后跟一个常量表达式组成,如下所示:

<data_type> <variable_name> = value;
一些实例:

int d = 3, f = 5;    /* 初始化 d 和 f. /
byte z = 22;         / 初始化 z. /
double pi = 3.14159; / 声明 pi 的近似值 /
char x = 'x';        / 变量 x 的值为 'x' */

正确地初始化变量是一个良好的编程习惯,否则有时程序会产生意想不到的结果。

请看下面的实例,使用了各种类型的变量:

class Program
{
    static void Main(string[] args)
    {
        short a;
        int b ;
        double c;
        /* 实际初始化 */
        a = 10;
        b = 20;
        c = a + b;
        Console.WriteLine("a = {0}, b = {1}, c = {2}", a, b, c);
        Console.ReadLine();
    }
}

当上面的代码被编译和执行时,它会产生下列结果:
a = 10, b = 20, c = 30

3. 类型推断

类型推断使用var关键字。声明变量的语法有些变化:使用var关键字替代实际的类型。编译器可以根据
变量的初始化值“推断”变量的类型。例如

var someNumber=0;
就变成:
int someNumber=0;

即便someNumber从来没有声明为int,编译器也可以确定,只要someNumber在其作用域内,就是int类

需要遵循以下一些规则:

  • 变量必须初始化。否则,编译器就没有推断变量类型的依据。
  • 初始化器不能为空。
  • 初始化器必须放在表达式中。
  • 不能把初始化器设置为1个对象,除非在初始化器中创建了一个新对象

4. 作用域

变量的作用域是可以访问该变量的代码区域,一般情况下,确定作用域遵循以下规则:

  1. 只要类在某个作用域内,其字段(成员变量)也在该作用域内
  2. 局部变量存在于表示声明该变量的块语句或方法结束的右花括号之前的作用域内
  3. 在for、while或类似语句中声明的变量存在于该循环体内

4.1 局部变量的作用域冲突

大型程序在不同部分为不同的变量使用相同的变量名很常见。只要变量的作用域是程序的不同
部分,就不会有问题,也不会产生多义性。但要注意,同名的局部变量不能在同一作用域内声明两
次。例如,不能使用下面的代码:

class Program
{
    static int Main(string[] args)
    {
        int j = 20;
        for (int i = 0; i < 10; i++)
        {
            int j = 30; 
            Console.WriteLine(j + i);
        }
        return 0;
    }
}

如果试图编译它,就会产生如下错误:

error CS0136:A local variable namedj’cannot be declared in
this scope because that name is used in an enclosing local scope
to define a local or parameter

其原因是:变量j是在for循环开始之前定义的,在执行for循环时仍处于其作用域内,直到MainO
方法结束执行后,变量j才超出作用域。第2个j(不合法)虽然在循环的作用域内,但作用域嵌套
在Main0方法的作用域内。因为编译器无法区分这两个变量,所以不允许声明第二个变量

4.2 字段和局部变量的作用域冲突

某些情况下,可以区分名称相同(尽管其完全限定名不同)、作用域相同的两个标识符。此时编译器允许声明第二个变量。原因是C#在变量之间有一个基本的区分,它把在类型级别声明的变量看成字段,而把在方法中声明的变量看成局部变量

class Program
{
    static int j = 20;
    static int Main(string[] args)
    {
        int j = 30;
        Console.WriteLine(j);
        return 0;
    }
}

虽然在Main0方法的作用域内声明了两个变量j,这段代码也会编译:一个是在类级别上定义的j,在类Program删除前(在本例中,是Main0方法终止,程序结束时)是不会超出作用域的:一个是在Main0中定义的j。这里,在Main0方法中声明的新变量j隐藏了同名的类级别变量,所以在运行这段代码时,会显示数字30。

二、常量

2.1 定义

顾名思义,常量是其值在使用过程(生命周期)中不会发生变化的变量。在声明和初始化变量时,在变量的前面加上关键字const,就可以把该变量指定为一个常量:
constinta=100://This value cannot be changed

2.2 特点

  • 常量必须在声明时初始化。指定了其值后,就不能再改写了。
  • 常量的值必须能在编译时用于计算。因此,不能用从变量中提取的值来初始化常量。如果需要这么做,应使用只读字段
  • 常量总是隐式静态的。

2.3 好处

  • 由于使用易于读取的名称(名称的值易于理解)替代了较难读取的数字和字符串,常量使程序变得更易于阅读。
  • 常量使程序更易于修改。例如,在C#程序中有一个SalesTax常量,该常量的值为6%。如果以后销售税率发生变化,把新值赋给这个常量,就可以修改所有的税款计算结果,而不必查找整个程序去修改税率为0.06的每个项。
  • 常量更容易避免程序出现错误。如果在声明常量的位置以外的某个地方将另一个值赋给常量,编译器就会标记错误

三、数据类型

在 C# 中,变量分为以下几种类型:

  • 值类型(Value types)
  • 引用类型(Reference types)
  • 指针类型(Pointer types)

3.1 值类型(Value types)

值类型变量可以直接分配给一个值。它们是从类 System.ValueType 中派生的。

值类型直接包含数据。比如 int、char、float,它们分别存储数字、字母、浮点数。当您声明一个 int 类型时,系统分配内存来存储值。

下表列出了 C#中可用的值类型:

在这里插入图片描述
如需得到一个类型或一个变量在特定平台上的准确尺寸,可以使用 sizeof 方法。表达式 sizeof(type) 产生以字节为单位存储对象或类型的存储尺寸。下面举例获取任何机器上 int 类型的存储尺寸:

class Program
{
   static void Main(string[] args)
   {
      Console.WriteLine("Size of int: {0}", sizeof(int));
      Console.ReadLine();
   }
}

当上面的代码被编译和执行时,它会产生下列结果:
Size of int: 4

3.2 引用类型(Reference types)

引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用。

换句话说,它们指的是一个内存位置。使用多个变量时,引用类型可以指向一个内存位置。如果内存位置的数据是由一个变量改变的,其他变量会自动反映这种值的变化。内置的 引用类型有:object、dynamic 和 string。

3.3 装箱和拆箱

当一个值类型转换为引用类型时,则被称为 装箱;另一方面,当一个引用类型转换为值类型时,则被称为 拆箱。

3.3.1 装箱

装箱:值类型转化为引用类型的过程。在堆上为新生成的引用对象分配内存,然后将值类型的数据拷贝到分配的内存中,返回堆中新分配对象的地址,这个地址就是指向对象的引用(如下图)
在这里插入图片描述
o 和 i 的改变将互不影响,因为装箱使用的是 i 的一个副本。
对值类型在堆中分配一个对象实例,并将该值复制到新的对象中。按三步进行。

  1. 首先从托管堆中为新生成的引用对象分配内存(大小为值类型实例大小加上一个方法表指针和一个SyncBlockIndex)。
  2. 然后将值类型的数据拷贝到刚刚分配的内存中。
  3. 返回托管堆中新分配对象的地址。这个地址就是一个指向对象的引用了。

可以看出,进行一次装箱要进行分配内存和拷贝数据这两项比较影响性能的操作。

3.3.2 拆箱

拆箱:引用类型转化为值类型的过程。获取引用类型的地址,将用用对象的值拷贝到栈上的值类型实例中(注意 拆箱时可能会引发“转换无效”的异常。要记住,拆箱时强转的值类型,应以装箱时的值类型一致)(如下图)
在这里插入图片描述

  1. 首先获取托管堆中属于值类型那部分字段的地址,这一步是严格意义上的拆箱。
  2. 将引用对象中的值拷贝到位于线程堆栈上的值类型实例中。

经过这2步,可以认为是同boxing是互反操作。严格意义上的拆箱,并不影响性能,但伴随这之后的拷贝数据的操作就会同boxing操作中一样影响性能。

PS:

  • 拆箱和装箱过程正好相反,有发生的先决条件。
  • 拆箱和装箱过程符合里氏转换原则。
  • 装箱操作和拆箱操作,是要额外耗费cpu和内存资源的,在进行大量的计算时,要避免装箱、拆箱,否则严重影响程序的效率。
  • 微软在c# 2.0之后,引入了泛型概念,来减少装箱操作和拆箱操作引起的消耗。

注意:只有装过箱的数据才能拆箱。

3.4 对象(Object)类型

对象(Object)类型 是 C# 通用类型系统(Common Type System - CTS)中所有数据类型的终极基类。Object 是 System.Object 类的别名。所以对象(Object)类型可以被分配任何其他类型(值类型、引用类型、预定义类型或用户自定义类型)的值。但是,在分配值之前,需要先进行类型转换。

3.5 动态(Dynamic)类型

您可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在运行时发生的。

声明动态类型的语法:

dynamic <variable_name> = value;

例如:

dynamic d = 20;

动态类型与对象类型相似,但是对象类型变量的类型检查是在编译时发生的,而动态类型变量的类型检查是在运行时发生的。

3.6 字符串(String)类型

字符串(String)类型 允许您给变量分配任何字符串值。字符串(String)类型是 System.String 类的别名。它是从对象(Object)类型派生的。字符串(String)类型的值可以通过两种形式进行分配:引号和 @引号。

例如:

String str = "qq.com";

一个 @引号字符串:
@“qq.com”;
C# string 字符串的前面可以加 @(称作"逐字字符串")将转义字符(\)当作普通字符对待,比如:

string str = @"C:\Windows";
等价于:
string str = "C:\\Windows";

@ 字符串中可以任意换行,换行符及缩进空格都计算在字符串长度之内。

string str = @"<script type=""text/javascript"">
    <!--     -->
    </script>";

用户自定义引用类型有:class、interface 或 delegate。

3.7 指针类型(Pointer types)

指针类型变量存储另一种类型的内存地址。C# 中的指针与 C 或 C++ 中的指针有相同的功能。声明指针类型的语法:
type* identifier;
例如:

char* cptr;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sufengmarket

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值