精华内容
下载资源
问答
  • C++ | 引用变量

    万次阅读 多人点赞 2018-03-22 17:20:14
    导语:为什么要有引用变量 引用的概念 用法示例 引用的属性与特别之处 主要作用 1.引用作参数 用法 引用传参的另一个好处 小结 2.引用作返回值 为何返回引用 返回引用时要注意的问题 小结 何时使用引用...

    目录

    导语:为什么要有引用变量

    拿一个简单的例子来说,要交换两个变量的值,如何交换?注意这里我们说的是,交换的功能我们会封装成一个函数。
    在 C 中我们都是这么调用的:

    void Swap(int* a, int* b) {
        int tmp = *a;
        *a = *b;
        *b = tmp;
    }
    
    int main() {
        int a = 5;
        int b = 3;
        Swap(&a, &b);
        return 0;
    }

    我们采用的是一种传址的方式,而不是传值。原因在于,调用的 Swap 中的 a 、 b 是形参,而不是实参。那有没有更简便的方法?毕竟,我们这里举的只是一个简单的例子,当涉及到的东西更多时(比如之前学的单链表不带头结点删除..传二级指针简直太复杂),在 C++ 中有一个新的复合类型——引用变量。

    引用的概念

    我们说的引用变量,实际上是给另一个变量取一个别名。例如,我们已经有了一个叫做 a ,将一个新的变量 b ,作为变量 a 的引用,相当于给 a 取了一个别名叫做 b 。如下图:
    引用图解ab
    一开始我们给这块空间取了一个名字叫做 a ,后来又给他取了个小名叫做 b ,相当于之后,我们对 b 进行任何操作,也会对 a 进行修改。

    用法示例

    正如上面所举的例子, b 是 a 的引用变量,这就需要在创建 b 的时候,就告诉编译器这个信息,用 & 来声明引用变量:

    int a;
    int& b = a;

    在这里 & 不是取地址操作符,而是类型标识符的一部分。正如 char* , * 也是类型标识符的一部分,表示一个指向 char 类型的指针变量,而我们的 int& 表示一个指向 int 类型的引用变量。
    这里的 ab 指向相同的值和内存单元(我的是64位平台)。可以测试一下:

    int main() {
        int a = 5;
        int& b = a;
        cout << &a << endl; // 取a的地址
        cout << &b << endl; // 取b的地址
        return 0;
    }

    输出:

    0x7fff5fbff80c
    0x7fff5fbff80c

    可以看到,两个变量的地址是相同的。在这里, a 变量的地址是在他定义之时随机分配的,但 b 不是,他是根据自己即将要引用的变量的地址来分配。
    既然如此,在引用变量 b 定义之时,必须初始化,而不能先声明,再赋值。也就是必须定义时就指向另一个已经定义了的变量,否则,引用变量将无法得知自己的地址,也就是说 “int& b;” 这样的写法是错误的。

    注意:
    当引用变量在创建时进行初始化,一旦与某个变量关联起来,就会一起效忠于他。
    即引用变量只能作为一个变量的引用,正如上面的 b ,当他成为 a 的引用之后,在他的生命周期里,就不能成为其他变量的引用

    例如,当 b 成为 a 的引用之后,如果我们试图让他成为c 的引用,并不会成功,只会实现赋值的效果(连 a 的值也相对的改变):

    int main(int argc, const char * argv[]) {
        int a = 5;
        int& b = a;
    
        int c = 20;
        b = c;
        cout << a << endl; // 输出a修改后的值
        cout << b << endl; // 输出b修改后的值
        return 0;
    }

    输出结果:

    20
    20

    总结一下上面所说的:
    引用变量必须在创建的时候初始化,一旦与某个变量关联起来,就只能是这个变量的引用。


    高能预警,以下有些复杂,涉及到 临时变量、引用参数和 const ,看了好几遍 primer 上的讲解才看懂。

    引用的属性与特别之处

    我们不能将一个变量作为右值的引用。如下:

    void test(int& ra) {
        // ...
    }
    
    int main(int argc, const char * argv[]) {
        int a = 5;
        test(a + 3);
        return 0;
    }

    这样编译器将会报错。因为 a + 3 并不是变量。但是早期的编译器会比较温和的发出警告,而不是直接报错,而现在的编译器,只有参数类型为 const引用 才能编译通过。

    这是为什么呢,接下来我们将详细剖析其中的原理。

    以前的编译器之所以允许将表达式传给引用变量是因为,由于 a + 3 并不是 int 类型的变量,于是程序将创建一个临时的无名变量,初始化为 a + 3 的值,然后将 ra 成为该无名变量的引用。

    那什么时候才会生成临时变量呢?有以下两种情况。

    1.实参类型正确,但不是左值
    2.实参类型不正确,但可以转换为正确的类型

    第一种情况:非左值会产生临时变量,哪些是非左值呢?也就是常量或包含多项的表达式。
    而左值就是我们可以赋值的对象。但是,在引入 const 关键字之后,常规变量const 变量 都可视为左值,因为可通过地址访问他们。

    常规变量视为可修改的左值,const变量视为不可修改的左值。

    第二种情况则是类型不匹配。

    回到之前的一个例子:

    void test(int& ra) {
        ++ra;
    }
    
    int main(int argc, const char * argv[]) {
        int a = 5;
        double b = 4;
    
        test(a + 3);
        test(b);
        return 0;
    }

    上面这个例子,a + 3 作为参数属于第一种情况(不是左值), b 属于第二种情况(类型不正确),这在以前的编译器,可以通过,是因为这时编译器产生了临时变量,分别让临时变量等于 a + 3 和 b 的值,而 ra 成为他们的引用,这对于原本的 a + 3 和 b 不并不会产生影响,所以他们的值不变。
    换言之,从前的编译器采取了一种机制——创建临时变量,使得原本的变量的值不会改变。因此,如果我们现在采取一种类似的机制,也能使得编译通过:使用 const 。

    我们将代码修改为:

    void test(const int& ra);

    这样也会使得编译通过。在这样的函数中,我们的目的只是使用传递的值,而不是修改他们。后面还会具体阐述这样使用 const 与引用搭配的好处。

    这就是为什么以前的编译器采用创建临时变量,现在采用加 const 修饰都可以编译通过的原因。

    主要作用

    1.引用作参数

    引用变量的主要作用体现在函数传参。以往我们已经有了两种传参方式:按值传参、按址传参(指针)。现在有了第三种方法:按引用传参。

    用法

    还是之前交换a、b的例子,现在有了一种新的写法:

    void Swap(int& x, int& y) {
        int tmp = x;
        x = y;
        y = tmp;
    }
    
    int main(int argc, const char * argv[]) {
        int a = 5;
        int b = 3;
        Swap(a, b);
        return 0;
    }

    但是有的时候,我们又不希望传入的值会改变,例如以下例子:

    int cube(int& ra) {
        ra *= ra * ra;
        return ra;
    }
    
    int main() {
        int a = 3;
        int ret = cube(a);
        cout << "cube of " << a << " = "<< ret << endl;
        return 0;
    }

    结果:cube of 27 = 27

    我们想要求 a 的立方,并且之后还要用到 a ,这时候并不希望改变 a ,而这时候 a 却被改变了。为了解决这个问题,当我们希望不改变原值时,尽量采用 const 来修饰:

    int cube(const int& ra);

    这时候我们若不小心改变了 a 的值,编译器便会报错。

    但是我相信您肯定要问了,传参时采用引用变量作为参数的原因就是,这样可以很方便的修改原值,这时候又说不改变原值,这不是自相矛盾么?

    引用传参的另一个好处

    这其实是因为,引用变量作为参数还有另外一个好处,便是省时间、省空间。大家都知道,我们平时传参时,都需要将原来的变量拷贝一份至一个临时变量,再将这个临时变量作为形参传入函数,但现在不需要了,因为从始至终,都是原来的那个变量。
    别看一个小小的原生类型占不了多少空间,复制一份也用不了多少时间,但是当我们传的是一个自定义变量,一个十分巨大的结构体或类的时候呢?,我们不需要去占用空间拷贝,这省了很多时间与空间。
    但同时也要注意,如果这时候您不希望改变原值,记得加上 const 来修饰。

    小结

    1. 在您打算修改原变量的值时,尽量使用引用作参数,省时间省空间,更重要的是比采取指针传参的方式简单很多;
    2. 在您并不打算改变变量的值,但是写了一个函数,要使用到您变量的值的时候,可以采用 const + 引用 的方式,表面上看和从前两者都不用的时候效果是一样的,但实际上省了很多的时间与空间(在传参较大时)。

    2.引用作返回值

    为何要返回引用

    来看一个例子:

    double m = sqrt(16.0);

    在这个例子中, sqrt 函数的返回值为 4.0 ,但若是让函数中的临时变量的值直接赋给 m 是不行的,因为 sqrt 函数结束,里面变量的生存周期已结束,再将其值赋给 m 可能会出错。总之,4.0 先被复制到了一个临时位置(实际上在寄存器,出了函数作用于也存在),然后再复制给了 m ,和之前传参时情形类似。

    不懂的可以再看下面:
    临时变量
    例如上面这个程序里,c 是一个临时变量,而 Add 函数的返回值并不是 c ,因为出了这个函数, c 就相当于不在了,所以他会先将 c 的值复制给另一个临时变量(如果较小是寄存器,较大则是提前开辟好的一块空间),这个临时变量的生存周期比较长,能将其值复制给 ret 变量。

    这样就存在一个效率问题,也就是多复制了一步。别看这里只是32位平台上八个字节的 double 变量而已,但如果是一个极大的结构体,就会浪费很多时间和空间。但是返回引用变量能很好地解决这个问题。

    例如以下例子
    引用
    a 自增以后再返回其值,利用引用,这时候并不需要考虑生命周期的问题,因为来来回回都是在堆那一块空间进行修改,一直在 a / ra 的作用域内,省去了再复制一步的时间和空间。

    返回引用时要注意的问题

    引用变量作返回值并不是任何时候都那么好用的:
    下面是一个错误的例子:

    int& Add(int a, int b) {
        int c = a + b;
        return c;
    }
    
    int main() {
        int& ret = Add(1, 2);
        Add(10, 20);
        cout << ret << endl;
        return 0;
    }

    输出结果:30

    我们之前提到了,当函数返回一个临时变量 c ,这时候会在创建一个一般在寄存器的临时变量,我们给这个变量取名为 k ,由于返回的类型是引用,这时候 k 成为了 c 的引用,而接受的类型也是引用,即 ret 是 k 的引用。当我们再调用 Add 时,k 内存放的是新的 c 的值,也就是 30,因此 ret 也跟着变为了 30 。

    也就是说,引用作返回值,也是有前提的:引用的变量需要出了这个函数作用域还在,否则可能会出错。而前面 my_func 的例子可以用的原因是:先传入了一个引用类型的参数,再返回这个变量。从始至终,实参、形参、与返回值,都是同一个变量,这个变量一直都在。

    同时这个时候,我们返回的是一个作为参数传递给函数的引用,正是因为如此,在这过程中这个参数可能会改变。当没有这样的参数传入的时候,我们可以开辟一块存储空间(c++ 用 new, 记得搭配 delete),在堆上就不会被自动分配与销毁了,或者返回一个静态/全局变量。

    小结

    1. 引用作返回值,不能返回一个临时变量的引用,需要变量在这个函数结束后还在,例如静态或全局变量
    2. 如果可以的话,我们尽量用引用作返回值,因为更省时间和空间

    何时使用引用参数

    当然,以下只是建议,而非必须如此。

    1. 传递值而不修改值(尽量 const 修饰)

     1、内置数据类型:由于较小,可直接按值传递;
     2、数组:采用 const 修饰的指针;
     3、较大的结构:使用 const 指针或 const 引用,可提高效率、节省时间空间;
     4、类对象:const 引用。
    

    2. 需要修改原数据

     1、内置数据类型:可使用指针;
     2、数组:只能使用指针;
     3、较大的结构:使用指针或引用;
     4、类对象:const 引用。
    

    实现方法(汇编层来看)

    从汇编层来看,引用的最终实现方法也是借用了指针(先取了地址,再解引用)。这和我们的指针的实现机制是一样的。但从语法层面来讲,引用比指针更省空间。

    引用与指针的区别:

    1. 引用必须初始化,并且从一而终,但指针可以修改指向;
    2. 引用必须指向有效变量,指针可以指向空;
    3. 指针代表着地址,而引用代表着变量。sizeof 指针,是指针的大小,sizeof 变量,是变量的大小;
    4. 引用与指针自增自减的意义不同。
    5. 引用比指针更安全,没有野指针的危险,但同时指针也比引用灵活。

    当然这肯定没把引用写完啦,但是目前就涉及了这么多,看完后面的内容再来补充啦。

    展开全文
  • Python变量赋值引用传递问题

    千次阅读 2018-08-15 17:58:07
    Python变量赋值引用传递问题及可变对象与不可变对象的说明

    变量赋值时的引用传递

      在Python语言中,对象是通过引用传递的。在赋值时,不管这个对象是新创建的,还是一个已经存在的,都是将该对象的引用(并不是值)赋值给变量。

      要保持追踪内存中的对象,Python使用了引用计数这一简单技术。也就是说Python内部记录着所有使用中的对象各有多少引用。一个内部跟踪变量,称为一个引用计数器。每个对象各有多少个引用,简称引用计数。当对象被创建时,就创建了一个引用计数,当这个对象不再需要时,也就是说,这个对象的引用计数变为0时,它被垃圾回收。

      当对象被创建并(将其引用)赋值给变量时,该对象的引用计数就被设置为1。当同一个对象(的引用)又被赋值给其他变量时,或作为参数传递给函数、方法或类实例时,或者被赋值为一个窗口对象的成员时,该对象的一个新的引用,或者称作别名,就被创建(则该对象的引用计数自动加1)。

      当对象的引用被销毁时,引用计数会减少。最明显的例子就是当引用离开其作用范围时,这种情况最经常出现在函数运行结束时,所有局部变量都被自动销毁,对象的引用计数也就随之减少。当变量被赋值给另外一个对象时,原对象的引用计数也会自动减1。其他造成对象的引用计数减少的方式包括使用del语句删除一个变量,或者当一个对象被移出一个窗口对象时(或该容器对象本身的引用计数变成了0时)。

    >>> a = 1
    >>> b = a
    >>> id(a), id(b)
    (1386769424, 1386769424)
    >>> a = 2
    >>> a, b
    (2, 1)
    >>> id(a), id(b)
    (1386769456, 1386769424)

      在上面的例子中,a = 1使变量a指向了整型对象1b = a使变量b也指向了整型对象1,通过语句id(a), id(b)可以看到变量a和变量b所指向的内存地址相同。

      语句a = 2将新的对象2赋值给了变量a,此时变量a指向了对象2,变量b仍然指向原来的对象1。通过语句id(a), id(b)可以看到变量a与变量b所指向的内存地址不同。

    可变对象和不可变对象

      Python语言中的对象分为可变对象和不可变对象。

    • 不可变对象,该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。

    • 可变对象,该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟出新的地址,通俗点说就是原地改变。

      数字、字符串和元组是不可变类型,列表和字典是可变类型。

    >>> a = 1
    >>> id(a)
    1386769424
    >>> a = a + 1
    >>> a
    2
    >>> id(a)
    1386769456

      在上面的例子中,语句a = a + 1改变变量a的值时,由于变量a所指向的对象1是不可变对象,因而创建了新的对象2并使变量a指向了对象2

    >>> a = (1, 2, 3)
    >>> id(a)
    305934860864
    >>> a = a + (4, 5, 6)
    >>> a
    (1, 2, 3, 4, 5, 6)
    >>> id(a)
    305934762152

      可以看到使用元组的结果与整型对象相同。

    >>> a = [1, 2, 3]
    >>> b = a
    >>> id(a), id(b)
    (305934751624, 305934751624)
    >>> b += [4, 5, 6]
    >>> a, b
    ([1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6])
    >>> id(a), id(b)
    (305934751624, 305934751624)

      上面是可变对象的一个例子,变量a和变量b同时指向了列表对象[1, 2, 3],当语句b += [4, 5, 6]改变变量b的值时,并没有创建新的列表对象,而是直接改变了原先变量a和变量b所指向的列表对象。

      在Python中a += ba = a + b是有区别的。a += b直接对变量a进行操作,会改变变量a的值。a = a + b中,a + b会生成一个新的对象,变量a将指向这个新对象。

    >>> a = [1, 2, 3]
    >>> b = a
    >>> id(a), id(b)
    (305934751176, 305934751176)
    >>> b = b + [4, 5, 6]
    >>> a, b
    ([1, 2, 3], [1, 2, 3, 4, 5, 6])
    >>> id(a), id(b)
    (305934751176, 305939124168)

    Python内存分配

      Python中会为每个出现的对象分配内存,哪怕他们的值完全相等(注意是相等不是相同)。如执行a = 2.0b = 2.0这两个语句时会先后为2.0这个Float类型对象分配内存,然后将ab分别指向这两个对象。所以ab指向的不是同一对象:

    >>> a = 2.0
    >>> b = 2.0
    >>> a is b
    False
    >>> a == b
    True

      但是为了提高内存利用效率对于一些简单的对象,如一些数值较小的int对象,Python采取重用对象内存的办法,如执行a = 2b = 2时,由于2作为简单的int类型且数值小,Python不会两次为其分配内存,而是只分配一次,然后将ab同时指向已分配的对象:

    >>> a = 2
    >>> b = 2
    >>> a is b
    True

      如果赋值的不是2而是大的数值,情况就跟前面的一样了:

    >>> a = 5555
    >>> b = 5555
    >>> a is b
    False
    >>> id(a)
    12464372
    >>> id(b)
    12464396

      两个对象即便值相等,但仍然是不同的对象。

    >>> id(12345)
    305935347440
    >>> id(12345)
    305935347152

    参考文献
    [1] Python核心编程(第二版)
    [2] https://www.cnblogs.com/sun-haiyu/p/7096918.html
    [3] https://blog.csdn.net/weixin_36250487/article/details/79620874

    展开全文
  • 在Python中,如果将数字、字符串等传入到函数中,再改变其,我们会发现:当函数结束,该变量不会发生改变。 In [20]: def q(x): ...: x = 2 ...: In [21]: a = 1 In [22]: q(a) In [23]: a Out[23]: 1 ...

    在Python中,如果将数字、字符串等传入到函数中,再改变其值,我们会发现:当函数结束时,该变量不会发生改变。

    In [20]: def q(x):
        ...:     x = 2
        ...:
    
    In [21]: a = 1
    
    In [22]: q(a)
    
    In [23]: a
    Out[23]: 1

    我们先令a=1,再在函数中让a的值变为2,当函数结束时,打印a的值,依旧为1。有人称类似于a这样的变量为不可变对象(数字、字符、字符串、元祖等)

    In [24]: def p(x):
        ...:     x[0] = 2
        ...:
    
    In [25]: b = [1]
    
    In [26]: p(b)
    
    In [27]: b
    Out[27]: [2]

    这里我们令b是只有一个元素,且该元素值为1的列表,在函数中我们修改了b[0]的值,退出函数后发现b的值被修改了。有人称类似于b这样的变量为可变对象(列表、字典等)。

    我们再深入了解一下,利用id函数(用于求变量地址)

    In [31]: def q(x):
        ...:     print(id(x))
        ...:     x = 2
        ...:     print(id(x))
        ...:
        ...:
        ...:
    
    In [32]: a = 1
    
    In [33]: id(a)
    Out[33]: 2002568880
    
    In [34]: q(a)
    2002568880
    2002568912
    
    In [35]: id(a)
    Out[35]: 2002568880

    这里输出了4个地址,分别是调用函数前a的地址,传入函数时x的地址,x改变后的地址,以及调用结束后a的地址。其中,第三个地址与其他几个地址不同。这里你需要知道,在Python中,同样的值对应唯一一个地址,我们可以看看

    In [36]: id(1)
    Out[36]: 2002568880
    
    In [37]: c=1
    
    In [38]: id(c)
    Out[38]: 2002568880

    那么,在函数中发生了什么?我将用下面的图来表示(用画图画的,有点丑):

    有一个内存块,地址是2002568880,该内存块上保存的值是1,我们定义了一个变量,其值为1,也就是让这个变量指向了地址为2002568880的内存块上。

    此时,我们调用函数q,它生成了一个新变量x,这个x指向的地址和a指向的地址一样(这里我又画了一个内存块,储存的值为2)

    在函数中,我们令x的值为2,也就是让x指向了内存地址为2002568912的内存块,但是请注意,a的指向并未发生改变。如下图

    所以我们退出函数时,a的值未发生改变。

    同样的,我们对b进行分析。假设b是一个长度为100的列表,其中

    In [60]: b[0] = 1
    
    In [61]: b[1] = 2
    
    In [62]: b[99] = 2

    我们再看看这三个元素的地址

    In [63]: id(b[0])
    Out[63]: 2002568880
    
    In [64]: id(b[1])
    Out[64]: 2002568912
    
    In [65]: id(b[99])
    Out[65]: 2002568912

    和之前的1、2的地址完全相同!更加说明了对于任意一个数,地址是唯一的。

    也就是说,数组的表示形式应该是如下图所示,b指向的是一个内存块,这个内存块存储了b[0]到b[99]的所有地址。

    此时,定义一个函数p,修改b[99]的值

    In [73]: def p(x):
        ...:     x[99] = 1
        ...:
        ...:
    
    In [74]: b[99]
    Out[74]: 2
    
    In [75]: p(b)
    
    In [76]: b[99]
    Out[76]: 1

    b[99]的值被修改了。

    发生了什么?

    在调用函数的过程中,首先生成x变量,指向了b指向的内存块。

    再对x[99]赋值,指向了1所在的内存块

    退出函数之后,x被丢弃,但b依然指向这个列表,而这个列表的内部元素已经被修改了。所以b[99]变成了1。

     

    所以大家应该明白了,为什么数字、字符、字符串、元祖都是值传递,因为你传递进去生成了一个新的变量x,你修改x的值无非是修改了x指向的内存块,而对原变量没有影响。

    而对于列表,字典这种能增删的变量类型,通过新变量x能修改内部元素指向的地址(但不改变x的指向),因而也会导致原变量被修改。

    所以,抛弃掉可变对象,不可变对象,值传递,引用传递这些概念吧,你需要理解的是函数调用时的机制:有一实际参数a,在调用时产生一个新的变量x(形式参数),x和a指向相同的地址。如果对x赋值,意味着改变了x指向的内存块,而不改变a的值。如果x是列表,对x[0]赋值,则改变了x[0]指向的内存块,而又因为x[0]的地址是存放在列表中,a又指向了这个列表,因此a也被修改了。

    如果你理解了上面,那么下面的情况你也应该理解了。

    In [77]: def t(x):
        ...:     x = [55,66]
        ...:
    
    In [78]: c = [1,2,3]
    
    In [79]: t(c)
    
    In [80]: c
    Out[80]: [1, 2, 3]
    

    你需要记住的是对x赋值,相当于改变了x指向的内存地址。而对c无关。

    展开全文
  • Shell语言中的变量引用

    千次阅读 2019-06-05 21:33:22
    变量就是程序设计语言中的一个可以变化的量,当然,可以变化的是变量变量几乎所有的程序设计语言中都定义,并且其涵义也大同小异。从本质上讲,变量就是在程序中保存用户数据的一块内存空间,而变量名就是...

    变量

    简介

    变量就是程序设计语言中的一个可以变化的量,当然,可以变化的是变量的值。变量几乎所有的程序设计语言中都有定义,并且其涵义也大同小异。从本质上讲,变量就是在程序中保存用户数据的一块内存空间,而变量名就是这块内存空间的地址。
    在程序的执行过程中,保存数据的内存空间的内容可能会不断地发生变化,但是,代表内存地址的变量名却保持不变。

    命名

    在Shell中,变量名可以由字母、数字或者下划线组成,并且只能以字母或者下划线开头。对于变量名的长度,Shell并没有做出明确的规定。因此,用户可以使用任意长度的字符串来作为变量名。但是,为了提高程序的可读性,建议用户使用相对较短的字符串作为变量名。
    在一个设计良好的程序中,变量的命名有着非常大的学问。通常情况下,用户应该尽可能选择有明确意义的英文单词作为变量名,尽量避免使用拼音或者毫无意义的字符串作为变量名。这样的话,用户通过变量名就可以了解该变量的作用。
    例如,下面的变量名都是非常好的选择:

    PATH=/sbin
    UID=100
    JAVA_HOME="/usr/lib/jvm/jre-1.6.0-openjdk.x86_64/bin/../.."
    SSHD=/usr/sbin/sshd
    
    变量的类型

    Shell是一种动态类型语言和弱类型语言,即在Shell中,变量的数据类型毋需显示地声明,变量的数据类型会根据不同的操作有所变化。准确地讲,Shell中的变量是不分数据类型的,统一地按照字符串存储。 但是根据变量的上下文环境,允许程序执行一些不同的操作,例如字符串的比较和整数的加减等等。

    【例1】演示Shell变量的数据类型

    #!/bin/bash
    x=123			#定义变量x,并且赋值为123
    let "x += 1" 	#变量x加1
    echo "x = $x"	#输出变量x的值	124
    echo			#显示空行
    y=${x/1/abc}	#替换x中的1为abc,并且将值赋给变量y		abc24
    echo "y = $y"	#输出变量y的值						abc24
    declare -i y	#声明变量y
    echo "y = $y"	#输出变量y的值
    let "y += 1"	#变量y的值加1  #abc24不是整形,便将y变为0,0自增为1
    echo "y = $y"	#输出变量y的值	1
    echo			#显示空行
    z=abc22			#将字符串赋给变量z
    echo "z = $z"	#输出变量z的值
    m=${z/abc/11}	#替换变量z中的abc为数字11,并且将值赋给变量m
    echo "m = $m"	#输出变量m的值
    let "m += 1"	#变量m加1		#弱类型,自动变为int型
    echo "m = $m"	#输出变量m的值
    echo
    n=""			#将空串赋给变量n
    echo "n = $n"	#输出变量n的值
    let "n += 1"	#变量n加1
    echo "n = $n"
    echo
    echo "p = $p"	#输出空变量p的值	#自动赋值为空
    let "p += 1"	# 变量p加1
    echo "p = $p"
    

    上述代码运行结果

    [root@localhost ~]# ./test.sh
    x = 124
    y = abc24
    y = abc24
    y = 1
    z = abc22
    m = 1122
    m = 1123
    n = 
    n = 1
    p = 
    p = 1
    

    在Shell中,通常情况下用户可以直接使用变量,而毋需先进行定义,当用户第一次使用某个变量名时,实际上就同时定义了这个变量,在变量的作用域内,用户都可以使用该变量。
    【例2】演示通过直接使用变量来定义变量

    #!/bin/bash   
    a=1      			#定义变量a    #类似于 int n=3 这种操作
    b="hello"   		#定义变量b        
    c="hello world"		#定义变量c     
    
    通过declare命令声明变量

    declare attribute variable(声明变量属性)

    -p:显示所有变量的值。
    -i:将变量定义为整数。在之后就可以直接对表达式求值,结果只能是整数。如果求值失败或者不是整数,就设置为0。
    	declare -i t,把t设置为整型
    -r:将变量声明为只读变量。只读变量不允许修改,也不允许删除。readonly,
    -a:变量声明为数组变量。但这没有必要。所有变量都不必显式定义就可以用作数组。事实上,在某种意义上,似乎所有变量
    	都是数组,而且赋值给没有下标的变量与赋值给下标为0的数组元素相同. array,矩阵,变长数组
    -f:显示所有自定义函数,包括名称和函数体。function
    -x:将变量设置成环境变量,这样在随后的脚本和程序中可以使用。
    

    【例3】演示使用不同的方法了声明变量,导致变量在不同的环境下表现出不同的行为

    #!/bin/bash
    x=6/3				#定义变量x,并将一个算术式赋给它
    echo "$x"
    declare -i x		#定义变量x为整数
    echo "$x"			#仍为之前的x
    x=6/3				#将算术式赋给变量x,自动转变为2
    echo "$x"			#
    x=hello				#将字符串赋给变量x
    echo "$x"			#给整型变量赋hello,不是整数,只能给x赋值为0
    x=3.14				#将浮点数赋给变量x
    echo "$x"			
    declare +i x		#取消变量x的整数属性
    x=6/3				#重新将算术式赋给变量x
    echo "$x"			#6/3
    x=$[6/3]			#求表达式的值
    echo "$x"			#2
    x=$((6/3))			#求表达式的值
    echo "$x"			#2
    declare -r x		#声明只读变量x
    echo "$x"			#2
    x=5					#尝试为只读变量赋值
    echo "$x#2
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    6/3
    6/3
    2
    0
    ./test.sh: line 10: 3.14: syntax error: invalid arithmetic operator (error token is ".14")
    0
    6/3
    2
    2
    2
    ./test.sh: line 21: x: readonly variable
    2
    

    引用

    Shell语言中一共有3种引号,分别为
    单引号(’ ')单引号括起来的字符都作为普通字符出现
    双引号(" ")双引号括起来的字符,除“$”、“\”、“ ’ ”和“ " ”这几个字符仍是特殊字符并保留其特殊功能外,其余字符仍作为普通字符对待,
    反引号(` `)。反引号括起来的字串被Shell解释为命令,在执行时,Shell首先执行该命令,并以它的标准输出结果取代整个反引号(包括两个反引号)部分
    【例4】演示反引号使用方法

    #!/bin/bash
    #输出当前目录
    echo "current directory is `pwd`"
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    current directory is /root
    

    【例5】演示单双引号使用方法

    #!/bin/bash
    a= demo
    b='b- $a'
    c="b- $a"
    echo $a
    echo $b
    echo $c
    运行结果如下:
    demo
    b- $a	#单引号,所见即所得
    b- demo
    
    三种引号的理解:

    1、单引号(弱引用)
    所见即所得
    例如:var=123
    var2=’${var}123’
    echo var2 var2结果为${var}123
    2、双引号(强引用)
    输出引号中的内容,若存在命令、变量等,会先执行命令解析出结果再输出
    例如:var=123
    var2="${var}123"
    echo var2 var2结果为123123
    3、反引号(键盘tab键上面一个键)
    命令替换
    例如:root用户登录系统
    var=`whoami`
    echo $var var结果为执行whoami命令的结果 显示root
    备注:反引号和$()作用相同

    对三种引号的建议:
    1、简单变量,简单赋值可以不用""
    2、脚本中声明变量最好用""
    3、原样子输出用’’
    4、执行命令赋值最好用$()或者``

    变量的作用域

    Shell中的变量也分为全局变量和局部变量2种

    1.全局变量

    全局变量可以在脚本中定义,也可以在某个函数中定义。在脚本中定义的变量都是全局变量,其作用域为从被定义的地方开始,一直到Shell脚本结束或者被显式地删除
    【例6】 演示全局变量的使用方法

    #!/bin/bash
    func()					#定义函数
    {
       echo "$v1"			#输出变量x的值
       v1=200				#修改变量x的值
    }
    v1=100					#在脚本中定义变量x
    func					#调用函数
    echo "$v1"				#输出变量x的值
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    100
    200
    

    【例7】演示在函数内部定义全局变量的方法

    #!/bin/bash
    
    func()					#定义函数
    {
       v2=200				#在函数内部定义变量
    }
    func					#调用函数
    echo "$v2"				#输出变量的值
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    200
    
    2、局部变量

    与全局变量相比,局部变量的使用范围较小,通常仅限于某个程序段访问,例如函数内部。在Shell语言中,可以在函数内部通过local关键字定义局部变量,另外,函数的参数也是局部变量。
    【例8】演示使用local关键字定义局部变量

    #!/bin/bash
    func()				#定义函数
    {
       local v2=200		#使用local关键字定义局部变量,仅在函数体内部起作用,函数调用完毕,就完了
    }
    func				#调用函数
    echo "$v2"			#输出变量的值
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    					//NULL
    

    【例9】演示全局变量和局部变量的区别

    #!/bin/bash
    func()									#定义函数
    {
       echo "global variable v1 is $v1"		#输出全局变量v1的值
       local v1=2							#定义局部变量v1
       echo "local variable v1 is $v1"		#输出局部变量v1的值
    }
    v1=1									#定义全局变量v1
    func									#调用函数
    echo "global variable v1 is $v1"		#输出全局变量v1的值
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    global variable v1 is 1
    local variable v1 is 2
    global variable v1 is 1
    

    【例10】演示全局变量和局部变量的区别(二)

    #!/bin/bash
    function test() {
    local a
    a="hello world"
    echo $a
    }
    test
    echo $a
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    hello world
    				//全局变量a无值
    

    对比如下:

    #!/bin/bash
    function test() {
    a="hello world"
    echo $a
    }
    test
    echo $a
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    hello world
    hello world
    

    总结:

    1、全局变量总结
    环境变量即为全局变量,可以在定义它们的shell及其派生出来的任意子进程的shell中使用。局部变量只能在定义它们的函数/脚本中使用。还有一些变量是用户创建的,其他的则是专用的shell变量。环境变量可用于定义shell的运行环境,环境变量可以在配置文件中定义与修改,也可以在命令行中设置,但是命令行中的修改操作在终端重启时就会丢失,因此最好在配置文件中修改(用户家目录的“.bash_profile“文件或者全局配置“/etc/profile”、“/etc/bashrc”文件或者“/etc/profile.d”文件中定义。)将环境变量放在profile文件中,每次用户登录时这些变量值将被初始化。比如HOME、USER、SHELL、UID等再用户登录之前就已经被/bin/login程序设置好了。

    常见的系统环境变量如下:
    TMOUT: 设置自动退出的误操作等待时间
    HOSTTYPE: 系统文件系统类型
    HISTSIZE: 历史命令记录条数
    HOME: 用户登录时进入的目录,家目录
    UID: 当前用户的id
    SHELL: 当前shell解释器
    PWD: 当前所在路径(每改变一次目录,该值就会改变)
    PATH: 可执行文件默认路径
    可以用echo来显示查看全局变量(eg:echo $HOME)。env(或printenv)、set也可以用来查看系统的环境变量,但不是单独查看。而用unset临时取消环境变量(eg:unset USER),要永久生效还是要写到配置文件中

    自定义环境变量(采用export):
    ①export 变量名=value
    ②变量名=value;export 变量名
    ③declare -x 变量名=value
    这里依旧是临时生效,在shell终端关闭后就消失了,写到配置文件中则永久生效(注意:写到配置文件中后需要运行一遍配置文件的脚本才可生效,否则等重启时生效)

    2.局部变量总结

    局部变量即为本地变量,本地变量在用户当前的shell生存期的脚本中使用。在一个函数中将某个变量声明为local,则该变量就是一个局部变量,只在本函数中有效。

    3.关于局部变量的一些其他问题:

    ①用反引号将命令的结果作为变量名是常用的方法,eg:cmd=`date +%F`
    ②用$符号将命令的结果作为变量名也比较常用,eg:cmd=$(date +%F)
    ③变量在大括号上的使用:在以时间、主机名等为包名一部分来打包的时候常用
    【例11】用时间作为文件名的一部分打包:

    #!/bin/bash
    cmd=$(date +%F)    #由于`date +%F`的反引号不容易辨认,就不太使用`date +%F`
    tar -zcf code_$(date +%F)_young1.tar.gz /etc #没有问题
    tar -zcf code_`date +%F`_young2.tar.gz /etc   #没有问题
    tar -zcf code_young3_$cmd.tar.gz /etc	#没有问题 
    tar -zcf code_$cmd_young4.tar.gz /etc	#会有歧义,因为系统会不清楚是应该解析$cmd还是cmd_young
    tar -zcf code_${cmd}_young5.tar.gz /etc  #不会有歧义
    

    运行结果如下:

    [root@localhost ~]# ./test.sh
    tar: Removing leading `/' from member names
    tar: Removing leading `/' from member names
    tar: Removing leading `/' from member names
    tar: Removing leading `/' from member names
    tar: Removing leading `/' from member names
    [root@localhost ~]# ls -l code*
    -rw-r--r--. 1 root root 9166435 Jun  5 03:39 code_2019-06-05_young1.tar.gz
    -rw-r--r--. 1 root root 9166435 Jun  5 03:39 code_2019-06-05_young2.tar.gz
    -rw-r--r--. 1 root root 9166435 Jun  5 03:39 code_2019-06-05_young5.tar.gz
    -rw-r--r--. 1 root root 9166435 Jun  5 03:39 code_.tar.gz	//中间变量不存在,用null替换了
    -rw-r--r--. 1 root root 9166435 Jun  5 03:39 code_young3_2019-06-05.tar.gz
    

    【例12】用主机名与时间打包:

    #!/bin/bash
    cmd=$(date +%F)
    host=$(hostname)
    tar -zcf code_${cmd}_${host}.tar.gz /etc 
    

    运行结果如下:

    [root@localhost ~]# ls -l code*
    -rw-r--r--. 1 root root 9166435 Jun  5 04:10 code_2019-06-05_localhost.localdomain.tar.gz
    因此,养成将字符串括起来使用的习惯防止不易发现的错误
    

    关于$?的补充:

    [root@localhost ~]# vim test.cpp
    #include<iostream>
    using namespace std;
    int  main()
    {
    return 256;	//超过上限,返回值范围为0-255,所以后面echo $?为0
    }
    [root@localhost ~]# rm -rf test
    [root@localhost ~]# make test
    g++     test.cpp   -o test
    [root@localhost ~]# ./test
    [root@localhost ~]# echo $?
    0
    
    [root@localhost ~]# vim test.cpp
    #include<iostream>
    using namespace std;
    int  main()
    {
    return 255;	//此处255在合法范围内,第一个vim中,该值设为256,产生上溢,为0,即返回给父进程的值只能是0-255.
    }
    [root@localhost ~]# make test
    g++     test.cpp   -o test
    [root@localhost ~]# ./test
    [root@localhost ~]# echo $?
    255
    

    关于$?在脚本中的应用:
    常用来判断上一步是否成功(压缩失败打印ERROR压缩成功打印OK):

     [root@foundation0 ~]# cat test.sh 
    #!/bin/bash
    tar profile.tar.gz /etc/profile >& /dev/null
    [ $? -eq 0 ] && echo "tar profile OK" || echo "tar profile ERROR"
    tar -zcf profile.d.tar.gz /etc/profile.d >& /dev/null
    [ $? -eq 0 ] && echo " tar profile.d OK" || echo "tar profile.d ERROR"
    
    [root@localhost ~]# ./test.sh 
    tar profile ERROR
     tar profile.d OK
    

    系统变量

    Shell语言的系统变量主要在对参数判断和命令返回值判断时使用,包括脚本和函数的参数以及脚本和函数的返回值

    变量说明
    $n表示传递给脚本的第n个参数,例如$1表示第1个参数,$2表示第2个参数…
    $#命令行参数的个数
    $0当前脚本的名称
    $?前一个命令或者函数的返回状态码
    $*以“参数1 参数2 参数3…”的形式返回所有参数的值
    $@以“参数1” “参数2” “参数3”…的形式返回所有参数的值
    $_保存之前执行的命令的最后一个参数
    $$返回本程序的进程ID(PID)

    【例13】演示常用系统变量的使用方法

    #!/bin/bash
    echo "the number of parameters is $#"			#输出脚本的参数个数
    echo "the return code of last command is $?"	#输出上一个命令的退出状态码
    echo "the script name is $0"					#输出当前脚本名称
    echo "the parameters are $*"					#输出所有的参数
    echo "\$1=$1;\$2=$2;\$11=${11}"					#输出其中的几个参数
    

    运行以上程序

    [root@localhost ~]# ./test.sh a b c d e f g h i j k l m n
    the number of parameters is 14
    the return code of last command is 0
    the script name is ./test.sh
    the parameters are a b c d e f g h i j k l m n
    $1=a;$2=b;$11=k
    

    环境变量

    Shell的环境变量是所有的Shell程序都可以使用的变量。Shell程序在运行时,都会接收一组变量,这组变量就是环境变量。环境变量会影响到所有的脚本的执行结果。
    在这里插入图片描述除了上表列出的环境变量之外,用户还可以使用set命令列出当前系统的环境变量。
    【例14】通过环境变量来获取与当前Shell有关的一些环境变量的值

    #!/bin/bash
    echo "commands path is $PATH"			#输出命令搜索路径
    echo "current login name is $LOGNAME"	#输出当前的登录名
    echo "current user's home is $HOME"		#输出当前用户的主目录
    echo "current shell is $SHELL"			#输出当前的Shell
    echo "current path is $PWD"				#输出当前工作目录
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    commands path is /usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
    current login is root
    current user's home is /root
    current shell is /bin/bash
    current path is /root
    

    变量赋值和清空

    变量赋值

    在Shell中,变量的赋值使用以下语法:
    variable_name=value
    其中,varibale_name表示变量名,value表示变量的值。

    例如,下面都是有效的赋值语句:
    v1=Linux
    v2='RedHat Linux'
    v3="RedHat Linux $HOSTTYPE"
    v4=12345
    
    引用变量的值

    在Shell中,通过在变量名前面加上“$”,来获取该变量的值。相当于取该地址上的内容
    【例15】演示Shell变量的

    #!/bin/bash
    v1=Linux
    v2='RedHat Linux'
    v3="RedHat Linux $HOSTTYPE"
    v4=12345
    echo "$v1"			#输出变量v1的值
    echo "$v2"			#输出变量
    echo "$v3"			#输出变量v3的值
    echo "$v4"			#输出变量v4的值
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    Linux
    RedHat Linux
    RedHat Linux x86_64
    12345
    
    清除变量

    当Shell变量不再需要时,使用unset语句可以将其清除。当变量被清除后,其所代表的值也会消失。基本语法如下:
    unset variable_name
    其中,参数varibale_name表示要清除的变量的名称。
    【例16】演示Shell变量清除方法,并且观察在清除前后变量值的变化

    #!/bin/bash
    v1="Hello world"						#定义变量v1
    echo "$v1"								#输出v1的值
    unset v1								#清除变量
    echo "the value of v1 has been reset"
    echo "$v1"								#再次输出变量的值
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    Hello world
    the value of v1 has been reset
    			//NULL
    

    引用和替换

    变量的引用和替换是Shell对于变量功能的扩展。

    引用

    引用,是指将字符串用引用符号包括起来,以防止其中的特殊字符被Shell解释为其他涵义。特殊字符是指除了字面意思之外还可以解释为其他意思的字符。例如在Shell中,“$”符号的本身涵义是美元符号,其ASCII码值为十进制36。除了这个涵义之外,前面已经讲过,“$”符号还可以用了获取某个变量的值,即变量替换。星号“*”也是一个特殊的字符,星号可以用来作为通配符使用。
    【例17】演示星号通配符的使用方法

    [root@localhost chapter3]# ll ex*
    -rwxr-xr-x 	1 	root 	root 	179 	Jan  7 11:51 	ex3-10.sh
    -rwxr-xr-x 	1 	root 	root 	114 	Jan  7 15:49 	ex3-11.sh
    -rwxr-xr-x 	1 	root 	root 	100 	Jan  7 16:15 	ex3-12.sh
    
    符号说明
    双引号除美元符号、单引号、反引号和反斜线之外,其他所有的字符都将保持字面意义
    单引号所有的字符都将保持字面意义
    反引号反引号中的字符串将被解释为Shell命令
    反斜线转义字符,屏蔽后的字符的特殊意义
    全引用(’’)

    在Shell语句中,当一个字符串被单引号引用起来之后,其中所有的字符,除单引号本身之外,都将被解释为字面意义,即字符本身的涵义。这意味着被单引号引用起来的所有的字符都将被解释为普通的字符,这称为全引用。

    【例18】演示全引用的使用方法

    #!/bin/bash
    v1="value"			#定义变量v1
    echo 'Hello, $v1'	#输出含有变量名的字符串
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    hello,$v1
    
    部分引用("")

    对于单引号来说, 被其引用起来的所有的字符都将被解释为字面意义。而对于双引号来说,情况会有所不同。如果用户使用双引号将字符串引用起来,则其中所包含的字符除美元符号(KaTeX parse error: Expected 'EOF', got '\)' at position 15: )、反引号(`)以及反斜线(\̲)̲之外的所有的其他的字符,都将被…”、“`”和“\”仍然拥有特殊的涵义。
    【例19】演示部分引用的使用方法

    #!/bin/bash
    v1="world"			#定义变量
    echo "Hello, $v1"	#输出变量的值
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    hello,world
    
    命令替换

    所谓命令替换,是指在Shell程序中,将某个Shell命令的执行结果赋给某个变量。在bash中,有2种语法可以进行命令替换,分别使用反引号和圆括号,如下:
    `command`
    $(command)
    以上2种语法时等价的,用户可以根据自己的习惯来选择使用。

    【例20】演示反引号的使用方法

    #!/bin/bash
    v1=`pwd`		#变量替换
    echo "current working directory is $v1"		#输出变量的值
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    current working directory is /root
    
    转义(\)

    转义的作用是转换某些特殊字符的意义。转义使用反斜线表示,当反斜线后面的一个字符具有特殊的意义时,反斜线将屏蔽该字符的特殊意义,使得Shell按照该字符的字面意义来解释。

    [root@localhost ~]# echo $SHELL
    /bin/bash
    [root@localhost ~]# echo \$SHELL
    $SHELL
    [root@localhost ~]# echo \$$SHELL
    $/bin/bash
    



    例题

    1、变量赋值和替换

    #!/bin/bash 
    # 变量赋值和替换
    a=375
    hello=$a
    #------------------------------------------------------------------------- 
    # 强烈注意,在赋值的前后一定不要有空格. 
    # 如果有空格会发生什么? 
    # 如果"VARIABLE =value", 脚本将尝试运行一个"VARIABLE"的命令,带着一个"=value"参数. 
    # 如果"VARIABLE= value",
    # script tries to run "value" command with 
    #+ 脚本将尝试运行一个"value"的命令,带着the environmental variable "VARIABLE" set to "". 
    #+ 一个被赋成""值的环境变量"VARIABLE". 
    #------------------------------------------------------------------------- 
    echo hello # 没有变量引用,不过是个 hello 字符串
    echo $hello 		#375
    echo ${hello} # 同上
    echo "$hello" 		#375
    echo "${hello}" 	#375
    echo 
    hello="A B  C    D"
    echo $hello # A B C D 
    echo "$hello" # A B  C    D 
    # 就象你看到的 echo $hello 和 echo "$hello" 将给出不同的结果.
    # ^ ^ 
    # Quoting a variable preserves whitespace. 
    # 引用一个变量将保留其中的空白,当然,如果是变量替换就不会保留了. 
    echo 
    echo '$hello' # $hello 
    # ^ ^ 
    # 全引用的作用
    #+ 将导致"$"变成一个单独的字符. 
    # 注意两种引用不同的效果
    hello= # 设置为空值
    echo "\$hello (null value) = $hello" 	#$hello (null value) =
    # 注意设置一个变量为空,与 unset 它,不是一回事,虽然看起来一样,
    #unset,地址都擦除了,设置为空,保留地址
    # -------------------------------------------------------------- 
    # 可以在同一行上设置多个变量. 
    #+ 要以空白分隔
    # 小心,这会降低可读性,和可移植性. 
    var1=21 var2=22 var3=$V3
    echo 
    echo "var1=$var1 var2=$var2 var3=$var3" 
    # 在老版本的"sh"上,可能会有问题. 
    # -------------------------------------------------------------- 
    echo; echo 
    numbers="one two three"
    # ^ ^ 
    other_numbers="1 2 3"
    # ^ ^ 
    # 如果变量中有空白,那么引用就必要了. 
    echo "numbers = $numbers" 
    echo "other_numbers = $other_numbers" # other_numbers = 1 2 3 
    echo 
    echo "uninitialized_variable = $uninitialized_variable" 	# Uninitialized 变量为空值(根本就没赋值). 
    uninitialized_variable= # 声明,但是没被初始化,其实和前边设置为空值得作用是一样的. 
    echo "uninitialized_variable = $uninitialized_variable" 		# 还是一个空值
    uninitialized_variable=23 		# 赋值
    unset uninitialized_variable 	# Unset it. 
    echo "uninitialized_variable = $uninitialized_variable" 		# 还是空值
    echo 
    exit 0
    

    一个空值变量,或者是根本就没声明的变量,在赋值之前使用它可能会引起问题. 但是还是可以用来做算术运算
    2、

    echo "$uninitialized" # (blank line)  #空
    let "uninitialized += 5" # Add 5 to it. #5
    echo "$uninitialized" # 5 				#5
    # 结论: 
    # 对于一个空值变量在做算术操作的时候,就好像它的值为 0 一样. 
    # This is undocumented (and probably non-portable) behavior. 
    # 这并没被文档化(可能是不可移植)的行为.
    

    3、一般变量赋值

    #!/bin/bash 
    # "裸体"变量
    echo 
    # 变量什么时候是"裸体"的,比如前边少了$的时候. 
    # 当它被赋值的时候,而不是被引用的时候. 
    # 赋值
    a=879 
    echo "The value of \"a\" is $a." 	#879
    let a=16+5 # 使用 let 赋值	#21
    echo "The value of \"a\" is now $a." #21
    echo 
    # 在 for 循环中
    echo -n "Values of \"a\" in the loop are: " 
    for a in 7 8 9 11 
    do 
    echo -n "$a " 
    done 
    echo 
    echo 
    # 在 read 命令状态中
    echo -n "Enter \"a\" " 
    read a 		#屏幕输入的a
    echo "The value of \"a\" is now $a." 
    echo 
    exit 0
    

    运行以上程序

    [root@localhost ~]# ./test.sh
    
    The value of "a" is 879.
    The value of "a" is now 21.
    
    Values of "a" in the loop are: 7 8 9 11 
    
    Enter "a" 8
    The value of "a" is now 8.
    
    [root@localhost ~]# 
    

    4、变量赋值,一般的和比较特殊的

    #!/bin/bash 
    a=23 # Simple case 
    echo $a 		#23
    b=$a 
    echo $b 		#23
    # 现在让我们来点小变化
    a=`echo Hello!` # 把 echo 命令的结果传给变量 a 
    echo $a 		#hello!
    # 注意,如果在命令扩展结构中使用一个(!)的话,在命令行中将不能工作
    #+ 因为这触发了 Bash 的"历史机制". 
    # 但是,在校本里边使用的话,历史功能是被关闭的,所以就能够正常运行. 
    a=`ls-l` # 把 ls -l 的结果给 a 
    echo $a # 别忘了,这么引用的话,ls 的结果中的所有空白部分都没了(包括换行) 
    echo 
    echo "$a" # 这么引用就正常了,保留了空白
    # (具体参阅章节"引用") 
    exit 0
    
    展开全文
  • MyBatis面试题(2020最新版)

    万次阅读 多人点赞 2019-09-24 16:40:33
    使用MyBatis的mapper接口调用时有哪些要求? 最佳实践中,通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗 Mybatis的Xml映射文件...
  • c语言结构体学习整理(结构体初始化,结构体指针)

    万次阅读 多人点赞 2018-11-01 20:22:12
    首先我们为什么用到结构体,我们都已经学了很多int char …等类型还学到了同类型元素构成的数组,以及取上述类型的指针,在一些小应用可以灵活使用,然而,在我们实际应用中,每一种变量进行一次声明,再结合起来...
  • 传递 两个变量间赋值,...对于引用类型的(Object,Array…)的,因为传参和赋值,复制的是地址的一个副本,修改新的变量中的属性,是会影响原变量的 var d={f:1}; var e=d; e.f++; console.log(d.f);
  • 原始引用值类型及区别

    千次阅读 2020-02-20 20:52:41
    引用值:存储在堆中的对象,即存储在变量处的是一个指针,只想存储对象的内存处。 包括:object、array、function等 两者的区别: 原始变量及他们的储存在栈中,当把一个原始变量传递给另一个原始...
  • c++中引用变量的使用

    千次阅读 2017-03-29 15:13:09
    c++中引用是已定义的变量的别名,例如,如果把b作为a变量引用,则可以交替使用a和b来表示该变量引用的主要用途是作为函数的形参,通过将引用变量作为参数,函数将使用原始数据,而不是副本(把变量之间传入函数...
  • JS变量引用的坑

    千次阅读 2018-10-11 18:35:23
    说的通俗易懂一点,值引用就是我们平常用变量一下,改这个不会改变原变量,而地址引用恰恰相反,一改全改。 如何解决地址引用? 那么如何解决呢?我起初的时候发现是引用的坑了,因为当时对这块不是很了解,百度...
  • js面试题

    千次阅读 多人点赞 2019-04-09 19:42:32
    JavaScript 的组成 JavaScript 由以下三部分组成: ECMAScript(核心):JavaScript 语言基础 ...JS 的基本数据类型和引用数据类型 基本数据类型:undefined、null、boolean、number、string、s...
  • python变量引用以及在底层存储原理

    千次阅读 多人点赞 2018-08-10 11:38:18
    那么不同变量类型在底层是如何存储的,关系到变量引用,能否正确的掌握变量的相关操作? #下面v1,v2的分别是多少?为什么? v1 =3 v2=v1 print("v2:",v2) v1 += 2 print("v1:",v1) print("v2:",v2) #下面l2的...
  • 学习Python首先我们知道Python变量保存的是值引用 也可以说:变量是对内存及其地址的抽象 Python:一切变量都是对象 变量的存储,采用了引用语义的方式,存储的只是一个变量所在的内存地址,而不是这个变量...
  • 一.Java中什么叫做引用类型变量引用:就是按内存地址查询  比如:String s = new String();这个其实是在栈内存里分配一块内存空间为s,在堆内存里 new了一个String类型的空间,在运行是 栈内存里的...
  • 大型网站都使用,如:腾迅,网易,新浪等等) 缺点:代码多、不少初学者不理解原理,两句代码结合使用才能让主流浏览器都支持 4,父级div定义 overflow:hidden 原理:必须定义width或zoom:1,同时不能定义height...
  • C# 匿名函数引用局部变量解析

    千次阅读 2018-07-03 18:18:48
    必须了解:引用类型、类型、引用、对象、类型的(简称)。 关于引用、对象和在内存的分配如下几点规则: •对象分配在堆中。 •作为字段的引用分配在堆中(内嵌在对象中)。 •作为局部变量(参数也...
  • C++面试题汇总 (一)

    万次阅读 多人点赞 2019-06-27 08:54:39
    C++面试题汇总 (一)一,基础题二,算法题 一,基础题 new、delete、malloc、free关系 delete会调用对象的析构函数,和new对应free只会释放...对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的...
  • Markdown中的变量引用

    千次阅读 2018-04-05 21:34:20
    载地址:https://www.jianchengss.com/2018/04/05/Markdown中的变量引用/ 转载请注明出处! 写东西的时候突发奇想,Markdown支持变量引用吗?这么高大上的工具应该支持的吧?!于是就查阅了一些资料:果然...
  • c语言中变量引用传递和指针

    万次阅读 多人点赞 2017-08-02 19:25:15
    掌握了引用变量和指针,才能深入掌握面向过程的函数调用机制。 引用变量存储的是变量的地址,指针存储的也是变量的地址,所以本质上来说二者是一样的。 使用引用变量,子函数中所有的操作会直接修改主函数中的...
  • 是的,我们是可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点,不要共享可变对象的引用就可以了,如果需要变化,就返回对象的一个拷贝。最常见的例子就是对象中包含一个日期对象的引用。数据类型和 ...
  • R语言按引用(reference)传递变量

    千次阅读 2017-06-06 21:54:19
    稍微研究了一下R语言按引用传递的问题,写过其他程序的人都知道,很多语言传递函数都...表现就是,如果你在函数中修改了传入数据的,按传递方法原本的数据不会变化,但是按引用传递的话,数据就会发生变化。
  • 关于c++的引用变量与命名空间

    千次阅读 2017-03-03 13:41:55
    表示将b定义为一个引用变量引用了a的数值,同时b与a的地址相同,如果改变b的数值,a,b的数值也会改变,但是作为引用变量b的地址不会改变,同时引用变量与指针有着相似性,但是却是不同的,有着不同的特性。...
  • 指针变量引用变量的区别

    千次阅读 2016-03-26 14:01:06
    在栈里创建了一个char型的指针和引用,结果用sizeof计算大小输出1144,而且ri和b的地址一样,ri和b的也一样,看起来引用对象的一个nick name。但问题来了,我在声明了一个引用变量ri,这个变量的地址确是b...
  • Js引用类型按传递的题目:两个变量间赋值,以及作为变量给函数传参,只是将原变量中的复制一个副本给对方变量或形参变量 i. 对于原始类型的,修改新变量,不影响原变量 ii. 对于引用类型的,因为...
  • Cannot refer to a non-final variable mylis inside an inner class ...局部匿名类在源代码编译后也是生成对应的class文件的(一般会是A$1.class这种形式的文件),那么这个二进制文件是独立于其外围类(A.clas
  • 这里我插一点,final修饰符对变量来说,深层次的理解就是保障变量值的一致性。为什么这么说呢?因为引用类型变量其本质是存入的是一个引用地址,说白了还是一个值(可以理解为内存中的地址值)。用final修饰后,这...
  • C++引用 ...引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。引用的声明方法:类型标识符 &引用名=目标变量名; (1)&在此不是求地址运算,而是起标识作...
  • C#基础教程-c#实例教程,适合初学者

    万次阅读 多人点赞 2016-08-22 11:13:24
     版本支持:系统中的组件或动态联接库可能升级,由于这些组件或动态联接库都在注册表中注册,由此可能带来一系列问题,例如,安装新程序自动安装新组件替换旧组件,可能使某些必须使用旧组件才可以运行的...
  • Java传递和引用传递详细说明

    千次阅读 多人点赞 2020-07-14 15:53:17
    学过Java基础的人都知道:传递和引用传递是初次接触Java的一个难点,时候记得了语法却记不得怎么实际运用,时候会的了运用却解释不出原理,而且坊间讨论的话题又是充满争议:的论坛帖子说Java只有传递,...
  • java 方法内修改外部变量不变

    千次阅读 2020-02-15 18:31:47
    优化代码的时候遇到,无法修改变量。 场景: 现有两个java 方法,方法2包含在方法1中,方法1中的变量作为参数传入方法2,在方法2内部对这个传入的参数的进行了修改。 public static void main(String[] args)...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 215,998
精华内容 86,399
关键字:

引用原变量时必须要有值