[空白_录音] 嗨,你好
下面一起来学习变量及其传递 变量呢,分为
域变量或者叫字段变量,以及呢局部变量 另外一类呢就是比较特殊的就是 static
变量 那么 static 呢,它相当于全局变量,因为它 跟任何一个对象没关,它只是一个类相关
实际上呢它是放到全局的,所以这个这是比较特殊的 那我们常用的一个是字段变量或者称为叫域变量
它相当于对象当中的变量,实际上呢相当于这个对象的一个属性
嗯,就是相当于个广义的这个属性了,就是表示事物的某种状态 比如姓名啊、 年龄啊,就是这是一种。
然后另外一种呢,就是 局部变量,在函数体以及呢这一对
{ } 里面包括呢 我们的匿名函数,拉姆达表达式里面,这种变量呢 嗯,只具备局部变量。
呃,在 一定意义上呢,函数的参数里的变量也是局部变量
这些局部变量呢在栈中分配,自动消失 也就是说,我一进入到这个函数里头来
呃这个函数里头的时候呢,这些参数 变量以及这些局部变量呢,它就在栈中就分配空间了
然后再自动就消失掉,仅程序这个函数呢调用结束 就自动消失掉了。
另外一个最大的一个区别呢 就在于域变量自动就有初始值。
也就是说 一个对象它一 new 一分配了以后呢 它里面呢比如说姓名啊、
年龄啊,这些它就自动地就有一个默认的初始值,也就是 0 或者是
null 那么局部变量呢,你必须赋值,这个原因是什么呢?我们可以画一个示意图
我们知道呢,一个对象它的这个域变量或者是
字段里面它有,比如说这个对象里面 里面有姓名、
有年龄等等啊 所以有这些值。
啊,那这个地方呢一旦把这个对象分配好
了,那这些值呢它就必须把它确定,否则这个对象呢处于一种不确定状态
所以这就是域变量自动有初始值的一个原因 那么局部变量呢它就不一样了,局部变量是主要是函数里面的变量
包括参数和函数体里头的那种变量,那这种变量它是放到 栈空间里头的。
一旦我们调用一个函数,调用这个函数呢
它是怎么样分配这些变量呢?实际上它就是在栈空间里面,比如说我现在要调一个函数了
进到这个函数的时候,开始呢这个栈指针呢,这个栈它有指针表示我现在变量用到哪个地方去了 这个指针。
然后呢,我们一旦 进到这个函数,那它马上呢就要分配更多
的空间,这个栈指针呢就发生变化,我这里呢 比如说我现在要分配一个参数,这要占一点 要占空间。
然后呢,又有一个局部变量,它又要占一点空间 那我就把这个栈指针呢就移动到这,这样的话在我们看来呢最底下的
这个内存呢是被用掉的,呃,然后这些变量呢我们就可以使用了
当这个函数结束以后,这个局部变量它就和参数变量呢都没有意义了,所以这个栈指针呢又
栈指针呢它又回到这个地方来,呃,回到,回到这个地方来了
所以它又回退过来,那回退过来这里面的值呢就没有意义了 在我们看来呢就是用到的内存呢就是最下面这一部分
呃,所以这两个它其实是有空间,空间在这,但是已经不用它了 就是相当于自动消失,释放掉了。
所以那么这个我们可以想象 那局部变量它是这个栈指针马上发生了一个变化就分配了它空间到这
所以这里面的值呢是没有人帮我们赋值的 所以这就是为什么说局部变量
它是必须要你赋值 不赋值呢它就是错的。
我们下面看一个示例 请看这个示例,那这里呢
我们在一个函数体里头呢,这个变量呢就是局部变量
那在这个类里面呢是一个字段变量,只不过这里为了简单呢,我们用的是个域, static
变量 那这个变量一旦进到这个函数里面来,它就 分配了这个空间
b ,呃,这个 b 呢很显然,分配的这个空间呢 如果我们不对它进行赋值的话,你要直接使用的话,编译是不能通过的
那这里面呢数组,呃,数组呢它也是一种引用类型 当我们引用了,这个
c 这个变量呢,它是局部变量,但是 我们 new 的这个 int
的数组呢,这 5 个整数呢它的值呢默认 就是 0。
这里面的内容和 我们这里的局部变量它在初始化呢有这样的一个差别。
变量 除了我们声明以外呢,另外我们比较关注的就是变量在函数之间进行传递的这个问题
那么这个传递呢,一般情况就是按值传递 也就相当于是 C
语言里面就是把一个 这个值给传进去,但是呢由于我们这里的值呢
类型呢有两种,一种呢是这个值类型 一种是引用类型。
那值类型跟引用类型在传递的时候呢 它跟我们赋值一样,那就说如果是我赋值的是值类型,它是把整个那个
值里面的那些字段呢都给赋值过来
那引用类型呢,它只是赋值了一个引用,所以它们在传递的时候呢跟赋值一样就有所差别 我们下面看一个例子。
请看这个例子, int a = 0 ; modify (a),呃,注意
这里的这个 a 呢由于它是一个局部变量,呃,当我们这个局部变量跟这个
字段变量呢重名的时候呢,局部变量是优先的,所以这里的这个 a 呢 它是相当于这个 a。
那我们这里的 a 呢调用是 a++ 那我们这里再显示这个
a ,请大家思考一下,这个 a 它是 0 还是 1
呢?那这个 a 呢 它显示的呢是 0 ,那原因在于什么呢?
原因在于就是我们在赋值的时候是把这个 a 赋值到这个
a ,呃,也就是把我们这里那个参数呢传递到这的时候呢
它是赋值到这个局部变量,当然现在这个局部变量是参数变量 那我们把这个
a++ ,但我们这里的 a 呢并没有发生改变 我们下面再看下面一个数组 变量 b
= new int ,一个数,一个数组 然后我们同样地调
modify(b),然后我们再看看这个 b[0] 会怎么发生改变。
因为这个 b 呢 我们把引用传递到最下面这个里面去了
所以 b[0] ++ 呢实际上就是相当于我们的引用 ++
,也就是它是这样的 这个数组呢它是指向了一个
引用的,当然这里引用呢里面这个对象里面 也就一个,一个数据,那个数据呢初始是
0 那我们把这个 b 传到这个 b 的时候呢,实际上是传递的
这两个呢它都引用的这个地方,所以我们实际上把这个引用传过去了 引用传过去以后,
b[0] ++ 呢实际上就是把这里面引用的这个地方呢 变成了,变成了 1。
呃,这个地方呢 变成了 1。
然后我们下面又做了一件事情 这个 b 呢又指向了一个,这个
b 呢它 又指向了一个什么地方呢?又指向了 5 个 int 的数组 5 个 int 的数组。
所以这个 b 呢等于,但是我们 在这里显示的这个 b[0] 呢,注意它这个
b[0] 呢它还,这个地方已经变成 1 了 所以也就是说,我们虽然赋值的是这个引用,并且这里也把引用变了,但是我们是
变的引用所指向的这个内容,所以在这里呢输出的是 1
就是这是两个它们在内存里面的一种关系来决定的
那一般情况下就是我们刚才提到的,就是这种普通的把那个引用或者值呢赋值过去
那有的时候为了方便呢,我们还有一种叫做 refrence 的一种传递,就是
ref 参数,这个就是传引用 传引用呢也就是好像把这个变量 本身给传过去了,这就叫传引用。
就好像把那个 就像我们说,我们传,普通的传递呢就像 iii 框一样,赋值一个 iii 框,那个
iii 它是没变的 那个引用呢就把这个变量本身给传进去了,当然在计算机内部呢它是用内存里面的指针来实现的
但对我们使用者感觉是把这个变量给传过去了 这就叫引用传递。
另外呢有的时候还用 out 这种方式,那么 out
呢主要是把那个值呢 在那个函数里面赋值出来,所以它的一个好处,就是说
我这个外面这个变量可以不用赋值,这个函数 在函数里面进行赋值,所以
ref 和 out 呢 这两个修饰语呢它实际上对参数进行限制了
当然由于这两个呢它本身是传递这个变量本身,所以 对一个表达式或者对象的属性呢是不能用
ref 及 out 的,因为它是一个表达,属性实际上是一个
方法体了,本质上 property,所以它是不能用 ref 及
out,我们只能用普通的变量 才能用这个 ref 和 out。
好,下面我们来看看这几个例子 请看这里,我们这里有一个
struct,有一个 class 然后呢我们在程序里头 new
了一个 struct,然后我们就 modify (ref a)。
这个 ref 这个修饰语呢它表示我们是传的是引用 那传的引用呢就相当于传这个变量本身
如果我们不加那个,大家就知道呢就是它相当于把这个值呢 复制到这儿去。
但是如果传变量本身呢,就是说改变的就是这个里面的 就是改变了它。
所以这里 a.x++ 呢 实际上就是这里的 a.x++。
如果我们没有这个 ref 呢 这个效果就不一样了。
所以同样地,我们看看下面如果是我 ref 也可以用到引用类型的修饰语上
同样地,我们在声明这个函数的时候,也要写个 ref 修饰,调用的时候呢要 跟它一样。
那在用的时候呢,我们 b.x++,就是b+ 了
然后呢 b = new,那个 b 呢又等于 new 一个
class,那在这里呢,由于这个 b
new 了一个这个对象,实际上就相当于这里 new 了一个对象
所以 ref 和我们前面的普通的不加 ref 呢这个感觉呢它是不一样的
就相当于这个,这里的 b = new 了一个对象,所以这个 b.x
呢是输出为 0 而不是原先那个 b.x。
我们 下面再看一个例子呢,TransByOut,跟刚才一样,有一个 struct
class 那么这个 Out 呢就是输出,传递出来,那这个传递
出来呢就是要求呢这个变量必须在里面赋了值,因为它是 Out 嘛
所以才能够 return,所以在这里面呢就是我们 new 了一个对象,然后
a.x++ 当然这个 a.x++ 呢,所以这里就输出的是
1 那对于这个 class 呢我们类似的有一个 Out,然后 new 了一个对象,然后 b.x++
所以这里呢由于它输出出来,所以这个我们也可以认为这个 b 呢也是 直接就是这个变量本身。
所以 ref 和 out 实际上在 C#
的底层呢它是一样的 只是我们使用者看来呢,它这两个有点差别
一个是为了传递出来,一个是呢,一个是既传进去,也能传出来 在
C# 里面呢使用 ref 和 out 呢,使得我们在传递的时候呢
就能够解决变量本身这个传递 下面一个呢我们看看
params 参数 称为呢数组参数,或者叫可变参数
这个数组参数和可变参数呢,前面加一个修饰语,叫 params parameters 的意思,params。
params 呢它本质上 是一个数组,它是本质是一个数组,但是由于呢它的个数呢 是可变的,数组的元素个数是可变的,所以
这参数呢必须放到最后,因为你的参数是可变的嘛,所以前面一些固定的参数呢是在前头
所以它这个呢必须放到,必须放到最后。
在调用的 时候呢,它实际上方便了我们的书写了,比如说我们有个 Multi
多个元素的乘积,那你可以是 0 个元素,也可以是 1
个,或者是多个元素 然后我们也可以呢使用
double 这种数组这种方式,是一个数组
也就是它本质呢是一个数组,但是它给我们的一种,这种书写方式呢给我们感觉好像是
个数呢是可变的,在一定意义上呢,这种参数呢是方便书写的一种手段 我们看这个例子
Multi 它是多个元素 的数组来存,但是我们加了个
params 修饰 所以我们可以把它用 foreach 来遍历这样一个数组,foreach (double in,然后 我们来遍历它。
所以,然后在使用的时候呢,既可以当成多个元素的数组
来使用,也可以直接写多个元素,所以这样呢写起来就方便了
在参数传递当中呢还有一个叫默认参数 那么这个在参变量当中使用的时候就直接我们在这个
变量后面写个等于多少,那它的好处呢就是说我们可以呢
节省一个,就是我们就不用重载这种方法了,实际上在本质上呢 它也是重载了一个这个元素,就是可以省略这个参数
你比如说我们这里假设要放大缩小,我们围绕着某一点进行放大缩小,这个 k
呢是放大的系数 那我们这样写,就是等于 1,在参数的时候等于 1,
那就是说我们可以有两种方式调用,一种是普通的 p,然后逗号,然后 1.5,这个是那个系数
那我们也可以把这个参数给省略,这个省略呢就相当于这个默认的 1 所以这种呢称为默认参数