变量知识点的补充

本文颜色 普通文本448aca,高亮文本ae5da1

(本教程存在着明显的知识跨度,下面的介绍只是进行知识点复习,会存在很多的知识点缺漏,,因为这只是对该知识点的补充)

一、变量

0x00 引言

 

变量是贯穿编程学习以及实际运用的一个关键内容,在编程当中我们几乎都得使用变量,所谓的变量就是保存我们处理数据的最终结果或临时结果的容器。由我个人的开发经验总结得到如下几点事实:

其一、编程实际上就是处理数据和逻辑。

其二、编程的最终目的是为生产生活提供便利,甚至更直接的说,提高生产力水平。

由上面两条大家应该可以知道,变量对于编程而言到底是有多重要了吧?

 

在内容开始之前我们先举个栗子,假设你要做一个计算器程序,那么你需要做什么事情呢?

(编者注:虽然计算器这种东西在应用商店里面已经是漫天漫地,但是新手的第一课一般都是学做计算器起步的) 

正常的思路就是我们需要做一个界面,上面画几个按钮,然后单击他们的时候向屏幕输入按钮上面对应的数字,然后保存起来,以备下次使用的时候能够方便的取出。

但是现在这里我要指出里面没有一个错误,是的,所以对于编程初学者而言可以自己动手试试,而至于由其他编程环境迁移到C#的朋友可以自己动手实现复杂的计算了。

 

我这里因为教程需要所以暂时不贴Winform/WPF/UWP的代码:

static void Main(string[] args)
        {
            Console.WriteLine("请输入一个数");
            //提示输入一个数
            int num1 = int.Parse(Console.ReadLine());
            //请输入你要执行的操作
            Console.WriteLine("请输入你要执行的操作,以下操作可供选择/、*、-、+");
            char oper = Console.ReadLine()[0];
            Console.WriteLine("请输入另一个数");
            int num2 = int.Parse(Console.ReadLine());
            //现在开始处理逻辑了
            int result = 0;
            if (oper == '*')
            {
                result = num1 * num2;
                Console.WriteLine(result.ToString());
            }
            else if (oper == '/')
            {
                result = num1 / num2;
                Console.WriteLine(result.ToString());
            }
            else if (oper == '-')
            {
                result = num1 - num2;
                Console.WriteLine(result.ToString());
            }
            else if (oper == '+')
            {
                result = num1 + num2;
                Console.WriteLine(result.ToString());
            }
            else
            {
                Console.WriteLine("输入的符号不正确");
            }
            
        }
View Code

 

上面代码实现了最基本的四则运算,但是很遗憾的是他并不完善。该程序首先需要用户输入一个数并将其保存在num1变量里边,然后输入一个操作符,操作符是[+,-,*,/]之一,

最后再让用户输入最后一个数num2里边。最后程序将通过判断输入的符号类型判断该执行哪类操作,到底是+还是-;

 

(编者注:细心的同学应该会注意到我这里用了int.Parse,对于新手可能满满的疑问,而对于C#初学者(有过C语言基础但是没学过其他高级语言)的同学就会有点疑惑,而对于平台迁移的大鸟那就不要关注这个问题了。

这个问题会在后面几节的内容里面逐个强调。 )

  

0x01变量类型

 

变量作为存储数据的容器,本应该不分界限,但是实际上计算机对于数据是格外的敏感,不同的数据有不同的组织形式,在数据操作上也有不同的操作方式,如果不严格检查数据类型,轻则结果错误,重则引发宕机。

因此,很多编程语言从一开始就是强类型的编程语言,至于什么是强类型的编程语言,如果感兴趣的可以翻阅下面的编者注。

(编者注:强类型编程语言是针对数据类型的检查上面的,像C语言,他是不能够将string赋值给int,因为数据类型是不一样的,但是也有着类似于VB6这样的弱类型语言,这类语言不会要求检查数据类型,也就是出现了string<->integer之间互转但是

不会直接出现错误这样的情况,但实际上如果两者的数据无法直接进行转换或者是存储的数据操作之后不符合它声明的变量类型的组织形式的时候它就会触发错误。总结起来就是,强类型不允许两个不同类型变量的直接复制,而弱类型允许直接复制,但是

需要自己承担后果)

 

C#作为一个既具有C++能力又具有Visual Basic那样易读性的编程语言,在变量类型方面可谓下了很大功夫,因为一个编程语言变量类型越多,越方便构造复杂的系统。因此C#在C语言的基础上为我们丰富了许多变量类型,其中包括布尔类型等:

变量类型 变量类型说明
byte 字节类型
string 字符串型
bool 布尔类型
int 整数型
Uint 无符号整数型
double 双精度型
decimal 保存位数为32的整数型
DateTime 保存时间的数据类型
float 单精度型
char 字符型
IntPtr 托管的用于表示指针或句柄的平台特定类型
Object 对象类型

 

 

 

 

 

 

 

 

 

 

 如果嫌上面的变量类型比较多,难于记忆,那么我也可以简单的把变量类型分成以下几大类:

1、字符类

专门用于存储文字的类型,字符串型用于存储文字组,对文字类型以及数量没有限定,而字符则只能用于存储一个标点符号以及大小写英文还有数字。

2、整数类

专门用于存储整数型数据,包括字节型在内很高

 

前面我们说到,变量是结果或临时数据的保存容器,既然是容器自然具有容量以及体积等属性。在计算机系统中,一个变量所占的内存空间表示该容器的体积,而一个变量所占的内存空间同样等于该容器的容量。

不同的变量类型具有不同的容量大小,但是注意,在C#之中只有值类型能够获得准确的变量大小和数据范围,而引用类型的数据范围没有讨论的意义。

(编者注:对于什么是值类型什么是引用类型,初学者暂时不用深究,但学到一定程度的时候一定要回头复习,该知识点的内容会在后面的章节里面逐个强调)

 那么问题来了,什么时候我们应该使用哪些类型呢?

因为变量类型的使用十分的灵活,我也不好说很绝对,但是我要告诉大家的是,如果是文字(字母,汉子,符号等)就使用string,而带有小数点的数字则保存在float或者double当中,而整数则放在int类型之中,

对于C语言迁移过来的同学,因为使用char数组比较多,可能使用string比较少,所以我还是得指出,多使用string少用一些char。bool作为一个补充内容,是为了方便操作结果的保存,在C语言当中,我们一般使用一个int

变量保存某次操作的最终结果,0代表false,非0代表true。到了C#,我们可以直接用Bool变量直接保存true或者false结果。

 

0x02变量定义

 

上面的几个小节里面絮絮叨叨的说了一堆混乱的东西,接下来我就要和大家介绍在C#里面如何定义变量。单个变量的定义方式与C语言没有区别,依旧需要在变量名之前声明变量类型,即:

变量类型 变量名

实际效果如下:

object  C;
int i;
double d;

在C#里面,语句的结束都需要以分号结束,这与C语言没有任何区别。

但是在定义数组上面就有很大的区别了。一般的,在C语言里面如果我们需要定义一个变量数组,那么我们需要这样定义: 变量类型 变量名[],[]运算符不可省略,他告诉编译器这个变量名的变量要定义成数组,而C#则要定义成这样:

变量类型[] 变量名,同样的[]运算符不能省略。C语言与C#最大的不同就是同一语句里面可以同时声明变量和变量数组,但是在C#里面因为他把声明变量数组的运算符提前,所以在声明的时候只能声明多个数组而不能同时声明数组和变量。

(编者注:至于这个设定是不是脑残,我觉得还是由大家自己评判吧)

int i[];
int k[][];

int[] i;
int[,] k;

特别要注意的就是C#里面定义二维数组的方法,也许有人以为int[]是定义变量数组,int[][]就是定义二维数组了,但是我只想说 图样!微软爸爸绝对不会做这种合乎常理的事情呢,在C#中定义多维数组是需要在[]方括号里面加,逗号的,一个逗号加一维。

这个声明方式需要大家适应。

int[] i = new int[16]; //声明变量数组
i[12];

int[,] k = new int[2,12]; //声明二维变量数组
k[1,4];

(编者注:还有一个需要强调的事情,C#与C语言不同,在定义数组的时候我们可以暂时不指定他的长度,这是有别于C语言的。)

0x03 变量的赋值与访问

 既然说了变量的定义了,那么我们也应该讨论一下变量的赋值问题。大部分的访问方法与C语言没有什么区别,char用''赋值,即char i='a';里面只能保存一个Ascii码声明的字符(不能是汉字或英文之外的文字),后面我们会讲到原因。

既然说到了赋值,那么我得指出,C#的变量在使用之前必须赋值,但是这一点与C语言不同,C语言在定义之后是可以直接使用整型变量的,但是在C#这里是全面禁止,这是出于安全性的考虑。

 

在变量访问方面我着重强调数组,因为C#有了很多的改进,特别是对于数组元素的访问,在C语言里面我们是使用()运算符访问,但是在C#里面一律改成了[]。

C#的数组后面跟着的[]符号可以叫做索引器,索引器里面的数字可以是非数字,但是一般是数字,比如:

int[] i;

i[0];//访问第一个数组元素

这里还要说明一个东西,那就是位序。我们习惯把1当做所有东西的其实,但是在计算机里面我们更习惯将0当做开始,逻辑位序是从1-10,而物理位序是从0-9,其中0是数组的下标,而9是数组的上标。但这里强调的内容并不是说数组最大只有10个元素,

实际上数组的上限是int,也就是2^31次方。

 

C#在数组方面也增加了许多的内容,比如交叉数组,就是说多维数组各个维数的数组长度可以不相同,但是在C语言里面多维数组默认必须为矩阵。同时C#在数组的初始化上面添加了一些改进,其中第一个就是比较好用的:

 new 关键字创建数组:

int[] i = new int[12];

使用 new 关键字初始化数组的时候需要在new关键字后面指定一个变量类型,但是这个变量类型必须与前面定义声明的时候的变量类型所一致,否则编译器将会报错。

第二种方法是直接使用值初始化:

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

这种方法不需要指定变量类型,但是一定要注意数据格式(在编程里面,数据格式也会决定数据类型,比如1.1虽然是数字,但是他不是整数,这样的初始化在编译阶段就能被发现,需要注意)

第三种方法比较类似于Java:

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

首先他要在new关键字里面指定一个变量类型及元素个数,之后用值集合创建数组,但是实验表明,就算不加元素个数改成int[] i = new int[] {1,2,3,4,5,6}也能通过编译。

(编者注:至于原理感兴趣的同学可以看一下,不过在原理方面我个人觉得都是使用值来初始化的,编译器会忽略new int[]部分,然后直接计算集合里面的元素个数来初始化,至于类型检查,他会将元素第一个数据格式Parse之后比对是否符合声明的变量,然后再逐个检查其他元素是否符合定义的变量类型)


最后我要强调一个很好用的东西,叫做foreach遍历,以往在C语言的遍历我们都是需要使用for循环进行的,但是在C语言之后的编程语言里面已经默认提供一个foreach遍历语句,使用foreach遍历的一个好处就是他不会出现数组上标或者下标越界等遍历问题。

语法是这样的:

foreach(数组的基础类型 临时变量 in 数组)
{
} 
//比如string[]数组,他的基础类型是string
//那么写成这样
string[] s; 

foreach(string var in s)
{
}

 

 0x04类型转换

 之后就是变量的类型转换了,类型转化是变量之中最需要掌握的知识,在实际的项目当中,数据很多时候需要从一个数据类型转换到另一个变量类型,就比如一开始我觉得例子:

 

 static void Main(string[] args)
        {
            Console.WriteLine("请输入一个数");
            //提示输入一个数
            int num1 = int.Parse(Console.ReadLine());
            //请输入你要执行的操作
            Console.WriteLine("请输入你要执行的操作,以下操作可供选择/、*、-、+");
            char oper = Console.ReadLine()[0];
            Console.WriteLine("请输入另一个数");
            int num2 = int.Parse(Console.ReadLine());
            //现在开始处理逻辑了
            int result = 0;
            if (oper == '*')
            {
                result = num1 * num2;
                Console.WriteLine(result.ToString());
            }
            else if (oper == '/')
            {
                result = num1 / num2;
                Console.WriteLine(result.ToString());
            }
            else if (oper == '-')
            {
                result = num1 - num2;
                Console.WriteLine(result.ToString());
            }
            else if (oper == '+')
            {
                result = num1 + num2;
                Console.WriteLine(result.ToString());
            }
            else
            {
                Console.WriteLine("输入的符号不正确");
            }
            
        }
View Code

 

在这里例子里面大家应该能看到许多类型转化的场景,就比如简单四则计算器例子,一开始,我就用Console.ReadLine()方法得到用户在控制台输入的一行文字,但是我需要得到的不是文字,而是数字(在编程语言里面即便是文本里面存在着数字也不能让他直接赋值给整型变量),所以我使用int.Parse()方法包装一下Console.ReadLine()方法,这样子我就能把文本里面的数字提取出来(仅限文本里面只有数字的情况),该方法实际上就是把文本的数字转换成计算机能识别的数字。这里使用的类型转换实际上是下面我要说的内容。

 

在实际编程当中很多时候我们需要改变数据保存形式,以适应需求,为了适应这些需要因此我们需要系统提供一些必要的转换。这些转换大概分成三种,第一种是隐式转换,第二种是强制转换,第三种是自定义转换。

 

1、隐式转换

隐式转换使用的情况有以下几种:

第一,取值范围比较小的变量类型一般在使用上很有局限,为了长远考虑有时候需要直接进行向取值范围较大的变量类型的转换。

比如:

    byte -> int

  int -> long

    short ->int

    int -> Uint

其次是第二类,强制转换.

 

2、强制转换

在满足一定条件下,不同类型的变量是可以强制转换成某个类型的,该操作实际上不会做人和数据处理,但是它依旧有性能损耗,就好比你你朋友给你一个蓝色的番茄,实际上是可以吃的,但是我们心理会让我

下意识认为这不可以吃,强制转换实际上就是起到安慰作用,就比如你的朋友告诉你这东西没事,可以吃,只是长得不一样而已。

但是这里要强调一件事情,那就是强制转换的风险,因为不同类型之间本来是不可以互相转换的,但是强制转换实际上是强迫编译器不去检查变量类型,万一强制转换失败,这就会导致程序错误。

强制转换失败的情况大概是一下几种:

第一,两者的数据组织形式根本不同,转换之后会使计算机识别目标类型的时候出现错误。

比如object的拆箱操作

第二,是转换之前,被转换的对象实际为空,也就是null值

 第三类,则是需要对数据进行特殊处理

 

3、第三方方法强制转换

这些方法包括ToString(),Parse()等等,在这里我要统一的介绍一下该怎么使用:

首先是Convert类,该类提供了许多方法能够帮助我们进行转换。它的使用方法实际上是使用对应的方法,比如转换成string类型,那么我们只需要使用GetString方法即可,而其他的比如转换成整数,我们调用ToInt32等等就可以了。

由于Convert类是静态类,所以在使用的时候不需要实例化,直接可以Convert.ToInt32()即可。

 

除此之外就是使用相应数据的基类型提供的方法,比如int,因为C#的数据类型实际上也是面向对象的,每个类型都建立与之相对应的基类,比如int它的基类是,ValueType,结构体是ValueType实现的,所以int类型实际上是一个结构体,

于是我们就可调用int结构体的方法来帮助我们进行数据转换。

对于数字类的变量类型,一般会提供一个Parse方法帮助我们实现数据类型的转换,而对于String类型,因为它派生自Object,所以每个对象都会存在Tostring的方法,这个方法可以帮助我们把数据转换成文本。

 

1x01 强制转换的性能开销

这里我们讨论的是()强制转换的方法,至于第三方类以及第三方方法转换不打算讨论,因为那种方法实际上既存在CPU开销,又存在IO开销,不用讨论实际上都知道不应该多使用。

第一个测试时执行类->接口的转换,因为找不到什么测试样例,所以就拿这个充数吧。代码如下

   class Program
    {
        static void Main(string[] args)
        {
            odeTimer Timer = new odeTimer();
            Inum l;
            
            num[] obj = new num[1024];
            for (int i = 0; i < 1024; i++)
            {
                obj[i] = new num(i);
            }
            Console.ReadLine();//为了降低影响,我选择在直接在转换阶段计时
            Timer.Start();//计时开始
            foreach (object var in obj)
            {
                l = (Inum)var;
            }
            Console.WriteLine($"总共耗时:{Timer.Tick()}ms");
            Console.ReadLine();
        }
    }
    interface Inum
    {
        int Data { get; set; }
    }
    class num : Inum
    {
        int n;
        public num(int num)
        {
            n = num;
        }
        public int Data { get { return n; } set { n = value; } }
    }

结果:最终CPU消耗始终不大于1%,而耗时始终在100ms-120ms之间,这是引用类型的转换,可以看出相当耗费时间

接下来是值类型,依旧是CPU消耗很低,但是在执行1048576个值转换的时候,他的耗时也仅仅是4ms

 

测试机器

内存是DDR3双通道,在测试的过程中,进程的CPU开销始终小于2%,也就是说()强制转换是不需要消耗CPU的,毕竟没有对数据进行处理,只是在数据的存储位置上进行了一番功夫(详见Idasm应用的反编译IL指令集)

所以在强制转换上面,结构体的转换比类的强制转换速度更快,再有,类的强制转换实际上不消耗CPU而是消耗IO,实际上这么多的耗时也只是等待IO操作而浪费的,毕竟低于1%的CPU消耗,由此看出,他只存在IO等待浪费了CPU周期而已。

(编者注:实际上我讨论的情况比较少,也不严谨,但是通过上面的例子其实大家已经可以窥见,引用类型的强制转换消耗特别大,而值类型的强制转换则没有什么影响)。

 

[参考书籍]

《C#入门经典 第五版》     作者:美沃森     译者:齐立波,黄俊伟   清华大学出版社出版

posted @ 2016-08-01 12:57  丹麦的鱼  阅读(734)  评论(0编辑  收藏  举报