首页 > 编程语言 >C#变量和常量

C#变量和常量

时间:2025-01-13 18:31:40浏览次数:7  
标签:int 常量 C# 局部变量 作用域 编译器 变量

本文将介绍变量和常量(之前介绍过标识符,变量和常量就是标识符的用途之一)。好了,我们开始吧!

1. 变量

什么是变量呢?顾名思义,变量中的“变”字表示“变化”,说明它所代表的值不是恒定不变的;和变量相对应的就是常量,常量意味着一旦赋值就不再变化,关于常量我们会在第2节详述。因此,变量其实是一个占位符,它引用了一块内存地址,但它存储的值是可以变化的。举个例子说明,假设有一系列的盒子,每个盒子上贴有一个标签。那么,盒子本身相当于变量,而标签相当于变量名,而盒子里的东西则相当于该变量所代表的值。当我们给一个盒子贴了一个标签后,标签名就是相对固定了的,但盒子里的东西却是经常变的,比如我们拿走一支笔,放进去一个墨水瓶。如图4-1所示。

1.1 给变量命名

就像给盒子贴标签一样,标签上的文字一定要言简意赅,让人一眼就能知道盒子中的内容。另外,如果盒子有很多,那么相应的标签也会很多,这时就需要为标签的命名制定一个规则,比如“01-生活用品-化妆品”、“02-生活用品-洗漱类”就是一种不错的规则;反之,诸如“物品类1”、“物品类2”则是不好的命名规则,“物品”这个词本身过于宽泛,不能起到好的说明和描述作用。

和标签命名一样,变量名也要含义清晰,不易混淆,要能够描述变量所代表的用途,要避免词不达意。

下面是一些好的命名:

  1. TaskItem

  2. ItemName

  3. PersonName

下面是一些不好的命名:

  1. var1

  2. someVar

1.2 声明变量

声明一个变量的语法为:数据类型 变量名;

要注意不要漏掉最后的分号(;)。

例如,声明3个变量,分别是stringintbool类型,如下所示:

string yourName;
int booksNumber;
bool isRegister;

声明多个同类型的变量的语法如下:

string x, y, z;

如果不是同类型,则不能一起声明,下面的代码就是错误的:

string x, int y, double z;

编译时编译器会报如下错误:

应输入标识符;\"double\"是关键字
应输入标识符;\"int\"是关键字

因为编译器认为intdoublex一样,都是标识符,且都被声明为string类型,但编译器很快就发现问题了,因为intdouble都是关键字,因此编译器不允许编译通过。

要改正这个问题也很简单,每一个都使用单独的语句即可,如下所示:

string name = "Tom";
bool success = true;
int height = 10;

声明变量的目的是使用变量存储数据,那如何给变量赋值呢?和很多其他语言一样,在C#中使用的是=运算符为变量赋值。也可以声明变量后立即为它赋值,如下所示:

bool isSuccess;
isSuccess = true;
string name = "Tom";

也可以在同时声明多个同变量时有选择的赋值,如下所示:

int a = 1, b, c = 0;

1.3 变量的初始化

基于安全性考虑,C#对变量的初始化有一定要求:

  1. 所有的局部变量在被显式地初始化之前,都会被编译器当作未初始化,然后抛出编译期异常;

  2. 所有的字段级变量被编译器初始化为所属类型中等价于0的值。如布尔型的被初始化为false,数值型的被初始化为0或者0.0,所有的引用类型都被初始化为null。举例说明,如代码清单4-1所示。

代码清单4-1 未赋值的局部变量

using System;

namespace ProgrammingCSharp4
{
    class Person
    {
        private int Age;

        public void SayHello()
        {
            string message = "Hello!";
            Console.WriteLine(message);
        }
    }
}

上述代码中有两个变量,一个字段级变量Age,一个局部变量message,当我们编译这段代码的时候会产生如下错误:

使用了未赋值的局部变量\"message\"

这是因为我们没有给局部变量message赋值初始化,而Age变量是字段级变量,可以被编译器自动初始化为0。知道了错误的原因,我们对上述代码进行修改,如代码清单4-2所示。

代码清单4-2 已赋值的局部变量

using System;

namespace ProgrammingCSharp4
{
    class Person
    {
        private int Age;

        public void SayHello()
        {
            string message = "My age is {0}";
            Console.WriteLine(message, Age);
        }

        static void Main(string[] args)
        {
            Person person = new Person();
            person.SayHello();
        }
    }
}

在上述代码中,我们使用字符串"My age is {0}"初始化了局部变量message,然后把message的值和Age变量的值一起输出到了控制台,这里的{0}是个占位符,意味着它的位置会被Age的值所取代。代码清单4-2运行的结果如下:

My age is 0

这也印证了字段级的变量会被编译器自动初始化,它被初始化为该类型的初始值0

提示:可以使用int myInt = new int();来初始化局部变量,它等同于int myInt = 0;

表4-1显示了各种数据类型的默认值。

1.4 类型推断

从C# 3.0开始,C#引入了一个新的关键字var,它表示一种隐式类型推断,编译器可以通过它的初始值来判断变量的具体类型。尤其需要注意的是,var只能用于局部变量的声明,不能用于字段级变量的声明,并且使用var声明的变量必须有初始值,这样编译器有判断其是否是真实变量的依据。

如代码清单4-3所示,如下两种功能是一样的。

代码清单4-3 使用var关键字

var i = 10; // 隐式类型
int i = 10; // 显式类型

我们将代码清单4-2使用var关键字进行改写,如代码清单4-4所示。

代码清单4-4 使用var关键字改写代码清单4-2

using System;

namespace ProgrammingCSharp4
{
    class Person
    {
        private int Age;

        public void SayHello()
        {
            var message = "My age is {0}";
            Console.WriteLine(message, Age);
        }

        static void Main(string[] args)
        {
            var person = new Person();
            person.SayHello();
        }
    }
}

message变量的初始值为一个字符串类型,因此编译器可以推断其类型为string类型;person变量的初始值为Person类型,同理编译器可以断定person变量为Person类型。

1.5 变量的作用域

我们从中国移动的一句经典广告语开始:我的地盘我做主。变量的作用域即广告语中所说的“地盘”,出了变量的作用域便相当于离开了它的地盘,也就意味着该变量将变得不再“可见”,当然也就无法对它执行读取、赋值等操作了,实际上,从栈的角度来看,变量离开了作用域后,该变量将被弹出栈。我们来研究下这个“地盘”是如何确定的,确定“地盘”的规则如下:

  1. 类的字段所处的作用域等同于该字段所属类所在的作用域;

  2. 局部变量的作用域仅限于声明它的方法或循环体内部,以大括号{}为界。

变量的作用域如下图所示。

下面,我们来看一个例子,如代码清单4-5所示。

代码清单4-5 变量的作用域

using System;

namespace ProgrammingCSharp4
{
    class AboutVariableScope
    {
        public string fieldVar; // 作用域和它所属的类的作用域相同

        public void DoSomething()
        {
            // someVar 变量作用域仅限在 DoSomething() 方法内部,以 {} 为界
            var someVar = "some value";

            // someVars 变量作用域同 someVar 变量
            int[] someVars = { 1, 2, 3, 4, 5, 6 };

            // item 的作用域仅限 foreach 循环体内部,以 {} 为界
            foreach (var item in someVars)
            {
                Console.WriteLine(item);
            }

            // 这里将无法访问 foreach 循环体内部定义的 item 变量,编译器会报错
            Console.WriteLine(item); // 编译错误

            // sampleObject 对象的 sampleValue 字段的作用域与 sampleObject 对象的作用域相同
            SampleObject sampleObject = new SampleObject();
            sampleObject.sampleValue = "sampleValue's scope";
        }
    }

    class SampleObject
    {
        public string sampleValue;
    }
}

代码中以//开始的语句称为注释,主要是起说明和备注作用,注释语句会被编译器忽略,并不会被编译。

代码清单4-5中各变量的作用域情况已经在注释中说明了,建议大家亲自动手实践一下。

接下来,我们探讨作用域的重叠问题。从层次的角度来看C#代码,就是一层层的{}嵌套,那么必然会存在变量作用域重叠的问题,这时候我们就说两者的作用域发生了冲突。冲突主要分为两种情况:

  1. 同一个作用域内,存在两个同名的变量,这里不关心变量的类型是否相同。如果存在这种情况,编译器将会报错,编译将无法继续;

  2. 局部变量和字段级变量同名,那么局部变量会将同名的字段级变量隐藏,就是说在局部变量的作用域内,局部变量的值覆盖了字段级变量的值。

针对以上两种情况,我们来看一下示例代码,如代码清单4-6所示。

代码清单4-6 作用域冲突

using System;

namespace ProgrammingCSharp4
{
    class AboutVariableScope
    {
        public string fieldVar = "你好,世界!";

        public void DoSomething()
        {
            var someVar = "some value";
            var fieldVar = "我是 DoSomething() 方法内的局部变量";

            int[] someVars = { 1, 2, 3, 4, 5, 6 };

            foreach (var item in someVars)
            {
                string item = "作用域冲突了!";
                Console.WriteLine(item);
            }

            Console.WriteLine(fieldVar);
            Console.WriteLine(this.fieldVar);
        }
    }
}

我们来分析上面这段代码,第17行的item变量位于foreach循环体内,位于第15行的foreach循环中已定义了变量item,并且它和第17行的item作用域相同,这符合前面说的第一种冲突条件,属于语法错误,编译器会及时产生错误异常,如下所示:

不能在此范围内声明名为\"item\"的局部变量,因为这样会使\"item\"具有不同的含义,而它已在"父级或当前"范围中表示其他内容了。

这类冲突编译器可以协助我们检查出来,但接下来的一种情况,则属于逻辑错误,并且很难发现。我们来看第7行,这里定义了一个字段级别的变量fieldVar,它的值为:“你好,世界!”,但在另一个地方——DoSomething()方法内部(即第11行),我们又定义了一个相同名称的局部变量fieldVar,其值为:“我是DoSomething()方法内的局部变量”,那么我们来看第21行——输出fieldVar的值,它会输出字段变量fieldVar还是局部变量fieldVar的值呢?由于局部变量隐藏了字段变量,因此这里输出的是:“我是DoSomething()方法内的局部变量”。如果要使用字段变量fieldVar的值,则要像第22行那样使用this关键字。this关键字代表当前类的实例,可以看作当前类实例的别名,比如这里的this.fieldVar代表的是字段变量fieldVar,其值为:“你好,世界!”。

关于变量,我们就介绍到这里,其他章节还会对它做相应的补充,接下来我们一起看看变量的“对立面”:常量。

2. 常量

前面我们讲了变量,那么相对的就是常量了。顾名思义,常量就是一旦声明并且初始化就不再改变的数据。我们使用了一把锁来代表盒子里一旦放了东西就无法再改变了,如图4-3所示。

2.1 常量的特征及其作用

下面我们先来看看常量的特征及其作用。常量一般具有如下的特征:

  1. 常量必须在声明的时候就立即初始化,其值在初始化后将无法再进行更改;

  2. 常量必须使用显式类型声明,不能使用类型推断关键字var

  3. 常量可以在类、结构、接口中进行声明;

  4. 常量可以作为类、结构以及接口的字段,也可以是定义在类、结构中的方法内部的局部变量,事实上常量永远是静态的,虽然并没有使用static关键字(也不允许);

  5. 常量无法接受变量的赋值,哪怕该变量是static并且是readonly也不行,常量在初始化时只能使用另一个常量为它赋值,当然直接赋予一个具体的值更好;

为什么要有常量呢?它的作用如何?下面我们就来探讨这些问题。

  1. 常量一般用作某个具体值的替代物,只需修改常量的值,那么所有用到该常量的地方都不需要修改;

  2. 常量让一个值具有了具体意义,提高了代码的可读性和可维护性。

2.2 常量的声明

在C#中,常量是一个在程序运行时不可更改的值。常量在声明时必须初始化,并且一旦初始化后,不能再改变其值。常量通常使用const关键字进行声明。

代码清单4-7是一个C#常量的例子:

代码清单4-7 常量的用法

using System;

class Program
{
    // 声明常量
    const double Pi = 3.14159;
    const int MaxUsers = 100;

    static void Main()
    {
        // 使用常量
        Console.WriteLine("Pi的值是: " + Pi);
        Console.WriteLine("系统最大用户数: " + MaxUsers);

        // 你不能修改常量的值,例如:
        // Pi = 3.14; // 编译错误
        // MaxUsers = 200; // 编译错误
    }
}

解释:

  1. const double Pi = 3.14159;:声明了一个常量Pi,它的值是3.14159。常量的值是不可修改的。

  2. const int MaxUsers = 100;:声明了一个常量MaxUsers,其值为100,表示系统允许的最大用户数。

  3. Main方法中,常量被用来输出其值。

如果尝试修改常量的值(如Pi = 3.14;),编译器会报错,因为常量值是不可变的。常量通常用于存储那些在程序执行过程中不会改变的值,例如数学常数、配置设置、固定参数等。

标签:int,常量,C#,局部变量,作用域,编译器,变量
From: https://blog.csdn.net/weixin_44643352/article/details/145047174

相关文章

  • C#中的数据类型
    C#是一种强类型语言,无论是变量、常量,还是方法的参数、返回值,都需要指定相应的数据类型。从某种意义上来说,数据类型就像数据结构的模板,它包含了很多信息:一种数据类型所需要的内存空间;该数据类型的取值范围,即它可以表示的最大值和最小值;它所继承的基类信息;运行时它在内存......
  • 利用 LangChain 与 Eden AI 模型进行交互的完整指南
    利用LangChain与EdenAI模型进行交互的完整指南EdenAI是一个颠覆性的AI平台,通过统一多个提供商的优秀AI模型,简化了开发者的工作流。凭借单一API,开发者可以快速将强大的AI功能整合到生产环境中,轻松实现多样化的AI能力。本文将介绍如何使用LangChain与Eden......
  • CKA | Docker容器技术概述
    往期文章推荐【新版】容器&Kubernetes认证管理员(CKA)课程介绍k8s-CKS认证课程介绍【K8s】Kubernetes词汇表什么是Docker容器?3个管理多k8s集群实用工具K8S-CKA课程试听:Container概述CKA课程|Docker容器技术概述 今日分享内容CKA第一节课  目录1......
  • mysql、oracle、sqlserver的区别
    一、保存数据的持久性:        MySQL:是在数据库更新或者重启,则会丢失数据。                Oracle:把提交的sql操作线写入了在线联机日志文件中,保持到了磁盘上,可以随时恢复。                SqlServer:2014之后,也拥有了完全持久和延......
  • React源码解析(1): JSX语法与react项目渲染过程
    好家伙 0.前言由于工作的需要,我不得不入手了react的全家桶,曾经我的主要技术栈是vue。从vue转到react,一开始我感到非常不适应,jsx的语法的不了解,reacthooks的使用方式,react路由的配置。。。这一度让我十分难受但在熟悉一段时间后,我逐渐领略到react的魅力,灵活的状态管理,渲染速......
  • 长脸了!国产机器人闪耀CES 2025
    在CES2025展会上,众多国内机器人企业积极参展,展示各种各样的机器人产品,如智元远征A2、星动纪元Star1、银河通用G1、宇树H1及小鹏Iron等,展现了中国机器人产业在国际舞台上令人瞩目的发展成果。丰富的国产机器人类型展示人形机器人展示伟景智能发布的晓唯人形机器人,搭载了ViEy......
  • 《浪漫沙加2:七英雄的复仇》游戏启动时报错提示“找不到facesdk.dll”文件的原因和处理
    对于众多游戏爱好者而言,《浪漫沙加2:七英雄的复仇》无疑是一款备受期待、渴望沉浸其中的佳作。然而,不少玩家在满心欢喜准备启动游戏时,却遭遇了令人沮丧的报错提示——“找不到facesdk.dll”文件,这一状况瞬间打破了游戏的美好憧憬,探寻背后原因与处理方案势在必行。报错提示......
  • 如何贡献开源项目LangChain:完整指南
    LangChain是一个快速发展的开源项目,旨在构建强大的AI应用程序框架。作为一名开发者或技术爱好者,你或许希望为这个项目贡献力量,无论是开发新功能、修复bug、改进文档,还是参与讨论和设计。这篇文章将详细介绍如何高效地加入LangChain的开发与贡献,帮助你事半功倍。1.......
  • JavaScript与服务器端框架Flask
    JavaScript与服务器端框架Flask基本概念和作用说明示例一:设置基本的Flask环境示例二:使用JavaScript发起请求功能使用思路及代码示例示例三:处理POST请求示例四:表单验证与反馈开发经验分享在现代Web开发中,前端和后端的紧密结合对于创建高效、响应迅速的应用程序至关......
  • keycloak~巧用client-scope实现token字段和userinfo接口的授权
    keycloak中的client-scope允许你为每个客户端分配scope,而scope就是授权范围,它直接影响了token中的内容,及userinfo端点可以获取到的用户信息,这块我们可以通过自定义scope/mapper,来实现粒度的控制,并且这个mapper可以控制添加到token,或者添加到userinfo端点,这两块配置也是独立的,下面......