精华内容
下载资源
问答
  • 关于编程中变量的定义

    千次阅读 2020-08-04 22:54:00
    关于编程中变量的定义 (学习过程发现了一种比较好的说法----变量的本质) 变量本质上就是代表一个"可操作性的存储空间",空间位置是确定的,但是里面放置什么值不确定.我们可通过变量名来访问"对应的存储空间",从而...

    关于编程中变量的定义

    (学习过程中发现了一种比较好的说法----变量的本质)

    变量本质上就是代表一个"可操作性的存储空间",空间位置是确定的,但是里面放置什么值不确定.我们可通过变量名来访问"对应的存储空间",从而操纵这个"存储空间"存储的值.

    变量作为程序中最基本的存储单元,其要素包括变量名,变量类型和作用域。变量在使用前必须对其声明,只有在变量声明以后,才能为其分配相应长度的存储空间。

    展开全文
  • 一个变量应该有一个名字,并在内存占据一定的存储单元,在该存储单元存放变量的值。请注意区分变量名和变量值这两个不同的概念,见图 变量名规则 先介绍标识符的概念。和其他高级语言一样,用来标识变量、符号...
  • 一、概念变量:可以 改变 的量 类似于数学里的 x, y x=1 命名规则: 命名只能使用英文字母 数字和下划线 ...1、仅在当前shell实例有效的变量 在脚本或命令定义 2、函数内的变量 需加 local 此时该变量只...

    一、概念:

    变量:可以 改变 的量
    类似于数学里的 x, y

    x=1
    

    命名规则:

    • 命名只能使用英文字母 数字和下划线
    • 首个字符不能以数字开头
    • 中间不能有空格 可以使用下划线(_)
    • 不能使用bash里的关键字(例:help命令 man命令 等)

    二、变量的分类:

    1、局部变量

    概念:

    • 1、仅在当前shell实例中有效的变量
      在脚本或命令中定义
    • 2、函数内的变量
      需加 local 此时该变量只能在内部使用

    定义变量:

    定义普通变量:
    变量的定义和使用

    my_name=piconjo
    echo "1.$my_name"
    

    输出结果:1.piconjo

    另一种定义方式(推荐):

    location="zhejiang"
    echo 2.${location}
    

    输出结果:2.zhejiang


    定义只读变量:
    只读变量无法被删除

    readonly school
    school="zjitc"
    echo "3.${school}"
    

    输出结果:

    ./hello.sh: line 14: school: readonly variable
    			3.
    

    2、环境变量

    概念:

    又叫全局变量
    所有的程序都能访问环境变量
    建议全部大写

    定义变量:

    export关键字

    export KEY="value"
    

    定义一个环境变量
    例:export MY_NAME="piconjo"


    查看当前系统内所有的环境变量:

    env关键字(env:environment)

    env
    

    查找自定义的环境变量:env | grep MY_NAME


    引用其它文件定义的环境变量:

    source 或 .

    source 文件名 或 . 文件名
    

    若不引用 则系统不知道定义的环境变量
    因此echo$全局变量名 此时是查不到的
    只有引用后 才会添加到系统中
    在这里插入图片描述


    3、特殊变量

    种类

    $0:当前脚本的文件名(常用)

    $n:传递给脚本或函数的参数(常用)
    n为一个数字 表示第几个参数
    例:第一个参数为$1 第二个参数为$2

    $#:传递给脚本或函数的参数个数(常用)

    $*:传递给脚本或函数的所有参数
    将所有参数放在一个字符串里输出

    $ @:当前脚本或函数的所有参数(常用)
    被双引号""包含时 与$*稍有不同
    将所有参数以是否有空格分隔进行判断 放在多个字符串里输出

    $?:上个命令的退出状态(常用)
    可用于获取返回值

    $$:当前Shell的进程id
    对于Shell脚本 就是这些脚本所在的进程id


    #!/bin/bash
    echo "当前脚本文件名:$0"
    echo "参数1:$1"
    echo "参数2:$2"
    echo "全部参数以空格判断:$@"
    echo "传入的参数的个数:$#"
    
    ./specialvariable.sh variable1 variable2 variable3
    

    输出结果:

    当前脚本文件名:./specialvariable.sh
    参数1:variable1
    参数2:variable2
    全部参数以空格判断:variable1 variable2 variable3
    传入的参数的个数:3
    

    展开全文
  • 变量对象概念

    2018-07-24 09:17:08
    JavaScript编程的时候总避免不了声明函数和变量,以成功构建我们的系统,但是解释器是如何并且在什么地方去查找这些函数和变量呢?我们引用这些对象的时候究竟发生了什么?
  • 函数式编程中的重要概念

    千次阅读 2019-06-26 11:15:17
    函数式编程中的重要概念函数式编程范式的意义函数类型与高阶函数部分函数柯里化闭包递归记忆化 原文地址 函数式编程范式的意义 在众多的编程范式,大多数开发人员比较熟悉的是面向对象编程范式。一方面是由于面向...


    原文地址

    函数式编程范式的意义

    在众多的编程范式中,大多数开发人员比较熟悉的是面向对象编程范式。一方面是由于面向对象编程语言比较流行,与之相关的资源比较丰富;另外一方面是由于大部分学校和培训机构的课程设置,都选择流行的面向对象编程语言。面向对象编程范式的优点在于其抽象方式与现实中的概念比较相近。比如,学生、课程、汽车和订单等这些现实中的概念,在抽象成相应的类之后,我们很容易就能理解类之间的关联关系。这些类中所包含的属性和方法可以很直观地设计出来。举例来说,学生所对应的类 Student 就应该有姓名、出生日期和性别等基本的属性,有方法可以获取到学生的年龄、所在的班级等信息。使用面向对象的编程思想,可以直观地在程序要处理的问题和程序本身之间,建立直接的对应关系。这种从问题域到解决域的简单对应关系,使得代码的可读性很强。对于初学者来说,这极大地降低了上手的难度。

    函数式编程范式则相对较难理解。这主要是由于函数所代表的是抽象的计算,而不是具体的实体。因此比较难通过类比的方式来理解。举例来说,已知直角三角形的两条直角边的长度,需要通过计算来得到第三条边的长度。这种计算方式可以使用函数来表示。length(a, b)=√a²+b² 就是具体的计算方式。这样的计算方式与现实中的实体没有关联。

    基于计算的抽象方式可以进一步提高代码的复用性。在一个学生信息管理系统中,可能会需要找到一个班级的某门课程的最高分数;在一个电子商务系统中,也可能会需要计算一个订单的总金额。看似风马牛不相及的两件事情,其实都包含了同样的计算在里面。也就是对一个可迭代的对象进行遍历,同时在遍历的过程中执行自定义的操作。在计算最高分数的场景中,在遍历的同时需要保存当前已知最高分数,并在遍历过程中更新该值;在计算订单总金额的场景中,在遍历的同时需要保存当前已累积的金额,并在遍历过程中更新该值。如果用 Java 代码来实现,可以很容易写出如下两段代码。清单 1 计算学生的最高分数。

    清单 1. 计算学生的最高分数的代码

    int maxMark = 0;
    for (Student student : students) {
      if (student.getMark() > maxMark) {
        maxMark = student.getMark();
      }
    }
    

    清单 2. 计算订单的总金额的代码

    BigDecimal total = BigDecimal.ZERO;
    for (LineItem item : order.getLineItems()) {
       total = total.add(item.getPrice().multiply(new BigDecimal(item.getCount())));
    }
    

    在面向对象编程的实现中,这两段代码会分别添加到课程和订单所对应的类的某个方法中。课程对应的类 Course 会有一个方法叫 getMaxMark,而订单对应的类 Order 会有一个方法叫 getTotal。尽管在实现上存在很多相似性和重复代码,由于课程和订单是两个完全不相关的概念,并没有办法通过面向对象中的继承或组合机制来提高代码复用和减少重复。而函数式编程可以很好地解决这个问题。

    我们来进一步看一下清单 1 和清单 2 中的代码,尝试提取其中的计算模式。该计算模式由 3 个部分组成:

    • 保存计算结果的状态,有初始值。
    • 遍历操作。
    • 遍历时进行的计算,更新保存计算结果的状态值。

    把这 3 个元素提取出来,用伪代码表示,就得到了清单 3 中用函数表示的计算模式。iterable 表示被迭代的对象,updateValue 是遍历时进行的计算,initialValue 是初始值。

    清单 3. 计算模式的伪代码

    function(iterable, updateValue, initialValue) {
      value = initialValue
      loop(iterable) {
          value = updateValue(value, currentValue)
      }
      return value
    }
    

    了解函数式编程的读者应该已经看出来了,这就是常用的 reduce 函数。使用 reduce 对清单 1 和清单 2 进行改写,可以得到清单 4 中的两段新的代码。

    清单 4. 使用 reduce 函数改写代码

    reduce(students, (mark, student) -> {
       return Math.max(student.getMark(), mark);
    }, 0);
     
    reduce(order.lineItems, (total, item) -> {
       return total.add(item.getPrice().multiply(new 
    BigDecimal(item.getCount())))
    }, BigDecimal.ZERO);
    

    函数类型与高阶函数

    对函数式编程支持程度高低的一个重要特征是函数是否作为编程语言的一等公民出现,也就是编程语言是否有内置的结构来表示函数。作为面向对象的编程语言,Java 中使用接口来表示函数。直到 Java 8,Java 才提供了内置标准 API 来表示函数,也就是 java.util.function 包。Function<T, R> 表示接受一个参数的函数,输入类型为 T,输出类型为 RFunction 接口只包含一个抽象方法 R apply(T t),也就是在类型为 T 的输入 t 上应用该函数,得到类型为 R 的输出。除了接受一个参数的 Function 之外,还有接受两个参数的接口 BiFunction<T, U, R>TU 分别是两个参数的类型,R 是输出类型。BiFunction 接口的抽象方法为 R apply(T t, U u)。超过 2 个参数的函数在 Java 标准库中并没有定义。如果函数需要 3 个或更多的参数,可以使用第三方库,如 Vavr 中的 Function0Function8

    除了 FunctionBiFunction 之外,Java 标准库还提供了几种特殊类型的函数:

    • Consumer<T>:接受一个输入,没有输出。抽象方法为 void accept(T t)
    • Supplier<T>:没有输入,一个输出。抽象方法为 T get()
    • Predicate<T>:接受一个输入,输出为 boolean 类型。抽象方法为 boolean test(T t)
    • UnaryOperator<T>:接受一个输入,输出的类型与输入相同,相当于 Function<T, T>
    • BinaryOperator<T>:接受两个类型相同的输入,输出的类型与输入相同,相当于BiFunction<T,T,T>
    • BiPredicate<T, U>:接受两个输入,输出为 boolean 类型。抽象方法为 boolean test(T t, U u)

    在本系列的第一篇文章中介绍 λ 演算时,提到了高阶函数的概念。λ 项在定义时就支持以 λ 项进行抽象和应用。具体到实际的函数来说,高阶函数以其他函数作为输入,或产生其他函数作为输出。高阶函数使得函数的组合成为可能,更有利于函数的复用。熟悉面向对象的读者对于对象的组合应该不陌生。在划分对象的职责时,组合被认为是优于继承的一种方式。在使用对象组合时,每个对象所对应的职责单一。多个对象通过组合的方式来完成复杂的行为。函数的组合类似对象的组合。上一节中提到的 reduce 就是一个高阶函数的示例,其参数 updateValue 也是一个函数。通过组合,reduce 把一部分逻辑代理给了作为其输入的函数 updateValue。不同的函数的嵌套层次可以很多,完成复杂的组合。

    在 Java 中,可以使用函数类型来定义高阶函数。上述函数接口都可以作为方法的参数和返回值。Java 标准 API 已经大量使用了这样的方式。比如 IterableforEach 方法就接受一个 Consumer 类型的参数。

    在清单 5 中,notEqual 返回值是一个 Predicate 对象,并使用在 Streamfilter 方法中。代码运行的输出结果为 2 和 3。

    清单 5. 高阶函数示例

    public class HighOrderFunctions {
        private static <T> Predicate<T> notEqual(T t) {
             return (v) -> !Objects.equals(v, t);
        }public static void main(String[] args) {
           List.of(1, 2, 3)
               .stream()
               .filter(notEqual(1))
               .forEach(System.out::println);
        }
    }
    

    部分函数

    部分函数(partial function)是指仅有部分输入参数被绑定了实际值的函数。清单 6 中的函数 f(a, b, c) = a + b +c 有 3 个参数 a、b 和 c。正常情况下调用该函数需要提供全部 3 个参数的值。如果只提供了部分参数的值,如只提供了 a 值,就得到了一个部分函数,其中参数 a 被绑定成了给定值。假设给定的参数 a 的值是 1,那新的部分函数的定义是 fa(b, c) = 1 + b + c。由于 a 的实际值可以有无穷多,也有对应的无穷多种可能的部分函数。除了只对 a 绑定值之外,还可以绑定参数 b 和 c 的值。

    清单 6. 部分函数示例

    function f(a, b, c) {
      return a + b + c;
    }
    ​
    function fa(b, c) {
      return f(1, b, c);
    }
    

    部分函数可以用来为函数提供快捷方式,也就是预先绑定一些常用的参数值。比如函数 add(a, b) = a + b 用来对 2 个参数进行相加操作。可以在 add 基础上创建一个部分函数 increase,把参数 b 的值绑定为 1。increase 相当于进行加 1 操作。同样的,把参数值 b 绑定为 -1 可以得到函数 decrease。

    Java 标准库并没有提供对部分函数的支持,而且由于只提供了 Function 和 BiFunction,部分函数只对 BiFunction 有意义。不过我们可以自己实现部分函数。部分函数在绑定参数时有两种方式:一种是按照从左到右的顺序绑定参数,另外一种是按照从右到左的顺序绑定参数。这两个方式分别对应于 清单 7 中的 partialLeft 和 partialRight 方法。这两个方法把一个 BiFunction 转换成一个 Function。

    清单 7. 部分函数的 Java 实现

    public class PartialFunctions {
      private static  <T, U, R> Function<U, R> partialLeft(BiFunction<T, 
    U, R> biFunction, T t) {
       return (u) -> biFunction.apply(t, u);
      }private static  <T, U, R> Function<T, R> partialRight(BiFunction<T, 
    U, R> biFunction, U u) {
       return (t) -> biFunction.apply(t, u);
      }
    ​
    ​
      public static void main(String[] args) {
        BiFunction<Integer, Integer, Integer> biFunction = (v1, v2) -> v1 
    - v2;
        Function<Integer, Integer> subtractFrom10 = 
    partialLeft(biFunction, 10);
        Function<Integer, Integer> subtractBy10 = partialRight(biFunction, 
    10);
        System.out.println(subtractFrom10.apply(5)); // 5
        System.out.println(subtractBy10.apply(5));   // -5
      }
    }
    

    柯里化

    柯里化(currying)是与λ演算相关的重要概念。通过柯里化,可以把有多个输入的函数转换成只有一个输入的函数,从而可以在λ演算中来表示。柯里化的名称来源于数学家 Haskell Curry。Haskell Curry 是一位传奇性的人物,以他的名字命令了 3 种编程语言,Haskell、Brook 和 Curry。柯里化是把有多个输入参数的求值过程,转换成多个只包含一个参数的函数的求值过程。对于清单 6 的函数 f(a, b, c),在柯里化之后转换成函数 g,则对应的调用方式是 g(a)(b)©。函数 (x, y) -> x + y 经过柯里化之后的结果是 x -> (y -> x + y)。

    柯里化与部分函数存在一定的关联,但两者是有区别的。部分函数的求值结果永远是实际的函数调用结果;而柯里化函数的求值结果则可能是另外一个函数。以清单 6 的部分函数 fa 为例,每次调用 fa 时都必须提供剩余的 2 个参数。求值的结果都是具体的值;而调用柯里化之后的函数 g(a) 得到的是另外的一个函数。只有通过递归的方式依次求值之后,才能得到最终的结果。

    闭包

    闭包(closure)是函数式编程相关的一个重要概念,也是很多开发人员比较难以理解的概念。很多编程语言都有闭包或类似的概念。

    在上一篇文章介绍 λ 演算的时候提到过 λ 项的自由变量和绑定变量,如 λx.x+y 中的 y 就是自由变量。在对λ项求值时,需要一种方式可以获取到自由变量的实际值。由于自由变量不在输入中,其实际值只能来自于执行时的上下文环境。实际上,闭包的概念来源于 1960 年代对 λ 演算中表达式求值方式的研究。

    闭包的概念与高阶函数密切相关。在很多编程语言中,函数都是一等公民,也就是存在语言级别的结构来表示函数。比如 Python 中就有函数类型,JavaScript 中有 function 关键词来创建函数。对于这样的语言,函数可以作为其他函数的参数,也可以作为其他函数的返回值。当一个函数作为返回值,并且该函数内部使用了出现在其所在函数的词法域(lexical scope)的自由变量时,就创建了一个闭包。我们首先通过一段简单的 JavaScript 代码来直观地了解闭包。

    清单 8 中的函数 idGenerator 用来创建简单的递增式的 ID 生成器。参数 initialValue 是递增的初始值。返回值是另外一个函数,在调用时会返回并递增 count 的值。这段代码就用到了闭包。idGenerator 返回的函数中使用了其所在函数的词法域中的自由变量 count。count 不在返回的函数中定义,而是来自包含该函数的词法域。在实际调用中,虽然 idGenerator 函数的执行已经结束,其返回的函数 genId 却仍然可以访问 idGenerator 词法域中的变量 count。这是由闭包的上下文环境提供的。

    清单 8. JavaScript 中的闭包示例

    function idGenerator(initialValue) {
    let count = initialValue;
    return function() {
           return count++;
    };
    }let genId = idGenerator(0);
    genId(); // 0
    genId(); // 1
    

    从上述简单的例子中,可以得出来构成闭包的两个要件:

    • 一个函数
    • 负责绑定自由变量的上下文环境

    函数是闭包对外的呈现部分。在闭包创建之后,闭包的存在与否对函数的使用者是透明的。比如清单 8 中的 genId 函数,使用者只需要调用即可,并不需要了解背后是否有闭包的存在。上下文环境则是闭包背后的实现机制,由编程语言的运行时环境来提供。该上下文环境需要为函数创建一个映射,把函数中的每个自由变量与闭包创建时的对应值关联起来,使得闭包可以继续访问这些值。在 idGenerator 的例子中,上下文环境负责关联变量 count 的值,该变量可以在返回的函数中继续访问和修改。

    从上述两个要件也可以得出闭包这个名字的由来。闭包是用来封闭自由变量的,适合用来实现内部状态。比如清单 8 中的 count 是无法被外部所访问的。一旦 idGenerator 返回之后,唯一的引用就来自于所返回的函数。在 JavaScript 中,闭包可以用来实现真正意义上的私有变量。

    从闭包的使用方式可以得知,闭包的生命周期长于创建它的函数。因此,自由变量不能在堆栈上分配;否则一旦函数退出,自由变量就无法继续访问。因此,闭包所访问的自由变量必须在堆上分配。也正因为如此,支持闭包的编程语言都有垃圾回收机制,来保证闭包所访问的变量可以被正确地释放。同样,不正确地使用闭包可能造成潜在的内存泄漏。

    闭包的一个重要特性是其中的自由变量所绑定的是闭包创建时的值,而不是变量的当前值。清单 9 是一个简单的 HTML 页面的代码,其中有 3 个按钮。用浏览器打开该页面时,点击 3 个按钮会发现,所弹出的值全部都是 3。这是因为当点击按钮时,循环已经执行完成,i 的当前值已经是 3。所以按钮的 click 事件处理函数所得到是 i 的当前值 3。

    清单 9. 闭包绑定值的演示页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
       <title>Test</title>
    </head>
    <body>
       <button>Button 1</button>
       <button>Button 2</button>
       <button>Button 3</button>
    </body>
    <script>
       var buttons = document.getElementsByTagName("button");
       for (var i = 0; i < buttons.length; i++) {          
         buttons[i].addEventListener("click", function() {
           alert(i);              
         });
       }
    </script>
    </html>
    

    如果把 JavaScript 代码改成清单 10 所示,就可以得到所期望的结果。我们创建了一个匿名函数并马上调用该函数来返回真正的事件处理函数。处理函数中访问的变量 i 现在成为了闭包的自由变量,因此 i 的值被绑定到闭包创建时的值,也就是每个循环执行过程中的实际值。

    清单 10. 使用闭包解决绑定值的问题

    var buttons = document.getElementsByTagName("button");
    for (var i = 0; i < buttons.length; i++) {          
       buttons[i].addEventListener("click", function(i) {
          return function() {
            alert(i);              
          }
        }(i));
    }
    

    在 Java 中有与闭包类似的概念,那就是匿名内部类。在匿名内部类中,可以访问词法域中声明为 final 的变量。不是 final 的变量无法被访问,会出现编译错误。匿名内部类提供了一种方式来共享局部变量。不过并不能对该变量的引用进行修改。在清单 11 中,变量 latch 被两个匿名内部类所使用。

    清单 11. Java 中的匿名内部类

    public class InnerClasses {public static void main(String[] args) {
        final CountDownLatch latch = new CountDownLatch(1);final Future<?> task1 = ForkJoinPool.commonPool().submit(() -> {
          try {
            Thread.sleep(ThreadLocalRandom.current().nextInt(2000));
          } catch (InterruptedException e) {
            e.printStackTrace();
          } finally {
            latch.countDown();
          }
        });final Future<?> task2 = ForkJoinPool.commonPool().submit(() -> {
          final long start = System.currentTimeMillis();
          try {
            latch.await();
          } catch (InterruptedException e) {
            e.printStackTrace();
          } finally {
            System.out.println("Done after " + (System.currentTimeMillis() 
    - start) + "ms");
          }
        });try {
          task1.get();
          task2.get();
        } catch (InterruptedException | ExecutionException e) {
          e.printStackTrace();
        }
      }
    }
    

    可以被共享的变量必须声明为 final。这个限制只对变量引用有效。只要对象本身是可变的,仍然可以修改该对象的内容。比如一个 List 类型的变量,虽然对它的引用是 final 的,仍然可以通过其方法来修改 List 内部的值。

    递归

    递归(recursion)是编程中的一个重要概念。很多编程语言,不仅限于函数式编程语言,都有递归这样的结构。从代码上来说,递归允许一个函数在其内部调用该函数自身。有些函数式编程语言并没有循环这样的结构,而是通过递归来实现循环。递归和循环在表达能力上是相同的,只不过命令式编程语言偏向于使用循环,而函数式编程语言偏向于使用递归。递归的优势在于天然适合于那些需要用分治法(divide and conquer)解决的问题,把一个大问题划分成小问题,以递归的方式解决。经典的通过递归解决的问题包括阶乘计算、计算最大公约数的欧几里德算法、汉诺塔、二分查找、树的深度优先遍历和快速排序等。

    递归分为单递归和多递归。单递归只包含一个对自身的引用;而多递归则包含多个对自身的引用。单递归的例子包括列表遍历和计算阶乘等;多递归的例子包括树遍历等。在具体的编程实践中,单递归可以比较容易改写成使用循环的形式,而多递归则一般保持递归的形式。清单 12 给出了 JavaScript 实现的计算阶乘的递归写法。

    清单 12. 递归方式计算阶乘

    int fact(n) {
      if (n === 0) {
          return 1;
      } else {
          return n * fact(n - 1);
      }
    }
    

    而下面的清单 13 则是 JavaScript 实现的使用循环的写法。

    清单 13. 循环方式计算阶乘

    int fact_i(n) {
       let result = 1;
       for (let i = n; i > 0; i--) {
         result = result * i;
       }
       return result;
    }
    

    有一种特殊的递归方式叫尾递归。如果函数中的递归调用都是尾调用,则该函数是尾递归函数。尾递归的特性使得递归调用不需要额外的空间,执行起来也更快。不少编程语言会自动对尾递归进行优化。

    下面我们以欧几里德算法来说明一下尾递归。该算法的 Java 实现比较简单,如清单 14 所示。函数 gcd 的尾调用是递归调用 gcd 本身。

    清单 14. 尾递归的方式实现欧几里德算法

    int gcd(x, y) {
       if (y == 0) {
          return x;
       }
       return gcd(y, x % y);
    }
    

    尾递归的特性在于实现时可以复用函数的当前调用栈的帧。当函数执行到尾调用时,只需要简单的 goto 语句跳转到函数开头并更新参数的值即可。相对于循环,递归的一个大的问题是层次过深的函数调用栈导致占用内存空间过大。对尾递归的优化,使得递归只需要使用与循环相似大小的内存空间。

    记忆化

    记忆化(memoization)也是函数式编程中的重要概念,其核心思想是以空间换时间,提高函数的执行性能,尤其是使用递归来实现的函数。使用记忆化要求函数具有引用透明性,从而可以把函数的调用结果与调用时的参数关联起来。通常是做法是在函数内部维护一个查找表。查找表的键是输入的参数,对应的值是函数的返回结果。在每次调用时,首先检查内部的查找表,如果存在与输入参数对应的值,则直接返回已经记录的值。否则的话,先进行计算,再用得到的值更新查找表。通过这样的方式可以避免重复的计算。

    最典型的展示记忆化应用的例子是计算斐波那契数列 (Fibonacci sequence)。该数列的表达式是 F[n]=F[n-1]+Fn-2。清单 15 是斐波那契数列的一个简单实现,直接体现了斐波那契数列的定义。函数 fib 可以正确完成数列的计算,但是性能极差。当输入 n 的值稍微大一些的时候,计算速度就非常之慢,甚至会出现无法完成的情况。这是因为里面有太多的重复计算。比如在计算 fib(4) 的时候,会计算 fib(3) 和 fib(2)。在计算 fib(3) 的时候,也会计算 fib(2)。由于 fib 函数的返回值仅由参数 n 决定,当第一次得出某个 n 对应的结果之后,就可以使用查找表把结果保存下来。这里需要使用 BigInteger 来表示值,因为 fib 函数的值已经超出了 Long 所能表示的范围。

    清单 15. 计算斐波那契数列的朴素实现

    import java.math.BigInteger;
     
    public class Fib {
     
     public static void main(String[] args) {
       System.out.println(fib(40));
     }
     
     private static BigInteger fib(int n) {
       if (n == 0) {
         return BigInteger.ZERO;
       } else if (n == 1) {
         return BigInteger.ONE;
       }
       return fib(n - 1).add(fib(n - 2));
     }
    }
    

    清单 16 是使用记忆化之后的计算类 FibMemoized。已经计算的值保存在查找表 lookupTable 中。每次计算之前,首先查看查找表中是否有值。改进后的函数的性能有了数量级的提升,即便是 fib(100) 也能很快完成。

    清单 16. 使用记忆化的斐波那契数列计算

    import java.math.BigInteger;
    import java.util.HashMap;
    import java.util.Map;
     
    public class FibMemoized {
     
     public static void main(String[] args) {
       System.out.println(fib(100));
     }
     
     private static Map<Integer, BigInteger> lookupTable = new 
    HashMap<>();
     
     static {
       lookupTable.put(0, BigInteger.ZERO);
       lookupTable.put(1, BigInteger.ONE);
     }
     
     private static BigInteger fib(int n) {
       if (lookupTable.containsKey(n)) {
         return lookupTable.get(n);
       } else {
         BigInteger result = fib(n - 1).add(fib(n - 2));
         lookupTable.put(n, result);
         return result;
       }
     }
    }
    

    很多函数式编程库都提供了对记忆化的支持,会在本系列后续的文章中介绍。

    总结
    本文对函数式编程相关的一些重要概念做了系统性介绍。理解这些概念可以为应用函数式编程实践打下良好的基础。本文首先介绍了函数式编程范式的意义,接着介绍了函数类型与高阶函数、部分函数、柯里化、闭包、递归和记忆化等概念。下一篇文章将介绍 Java 8 所引入的 Lambda 表达式和流处理。

    展开全文
  • shell编程规范与变量

    千次阅读 2020-12-24 03:16:43
    shell编程规范与变量一级目录二级目录三级目录一,shell1.shell脚本概念:2.shell脚本用用场景编写脚本代码重定向与管道操作变量read 从键盘获取变量的值设置变量的作用范围整数的运算特殊的Shell变量预定义变量 ...

    一、shell脚本

    是弱编程语言,用户和内存之间的翻译官,把用户输入的指令翻译成计算机识别的二进制语言

    1.shell脚本概念:

    • 将要执行的命令按照顺序保存到一个文本文件
    • 给该文件可执行权限(wrx)X执行的权限
    • 可结合各种shell控制语句以完成更复杂的操作

    2.shell脚本用用场景

    • 重复性操作:例如写个for循环,批量性任务
    • 交互性任务 :通过免交互性
    • 批量事务处理
    • 服务运行状态监控:例如监控磁盘空间,内存使用率,服务的状态等通过shell脚本定时监控巡检。(运维常用)
    • 定时任务执行

    3.Shell的作用

    命令解释器,内核和用户输入命令的解释器
    linux 默认bin/bash

    • 用户登录shell
      • 登陆后默认使用的shell程序,很一般为/bin/bash环境。
      • 不同shell的内部指令、运行环境会有区别
        在这里插入图片描述

    4.编写脚本代码

    4.1编写脚本

    • 使用vim编辑器
    • 每行linux命令按照从上往下的顺序读取
      在这里插入图片描述

    4.2赋予执行权限

    在这里插入图片描述
    在这里插入图片描述

    4.3 执行脚本文件

    方法一和二必须有执行权限才可以

    • 方法一:脚本文件路径(相对路径与绝对路径)
    ./ fir666.sh
    

    在这里插入图片描述

    • 方法二:sh 脚本文件路径
    sh fir666.sh
    

    在这里插入图片描述
    前两个方法不会真正执行,只会显示结果

    • 方法三: source或者直接用.
      会执行,会切换到/boot目录下
      source fir666.sh
    . fir666.sh
    

    在这里插入图片描述

    在这里插入图片描述

    4.4 更完善的脚本构成

    • 脚本声明
    • 注释信息
    • 可执行语句
      基本规范:
      第一行 #!表示使用哪个编译器(#!后没有空格,#!usr/bin 表示使用python解释器)
      第二行# 注释上此脚本干什么的

    途中的echo用于提示自己命令执行到哪一步出错的,因为脚本在执行过程中,从上往下依次执行,并不知晓执行进度。
    在这里插入图片描述

    二、重定向与管道操作

    1. 重定向

    交互式硬件设备

    • 标准输入:从该设备接受用户输入的数据
    • 标准输出:通过该设备向用户输出数据
    • 标准错误:通过该设备报告执行出错信息
    类型设备文件文件描述编号默认设备
    标准输入/dev/stdin0键盘
    标准输出/dev/stdout1显示器
    标准错误/dev/stderr2显示器
    类型操作符用途
    重定向输入<从指定的文件读取数据,而不是从键盘输入
    重定向输出>将输出结果保存到指定的文件(覆写原有内容)
    同上>>将输出结果追加到指定的文件尾部
    标准错误输出2>将错误信息保存到指定的文件(覆写原内容)
    同上2>>将错误信息追加到指定的文件中
    混合输出&>将标准输出、标准错误的内容保存到同一个文件中
    同上2>&1将标准错误输出重定向到标准输出

    重定向示例:
    在这里插入图片描述
    个人理解:操作符的尖角指向谁,就是往哪里输入数据
    在这里插入图片描述
    从pass.txt文件中取密码放入test66更改的密码中
    “>>”:这个操作符和">"唯一区别就是。它是把结果追加到指定文件尾部的另起一行

    2. 管道符号

    将左侧的命令输出结果,作为右侧命令的处理对象

    
    示例:
    ps aux | wc -l       #统计系统进程的总量
    cat /etc/passwd | grep sicong
    echo "123456" | passwd --stdin sicong   #将用户sicong的密码改为123456
    

    在这里插入图片描述
    在这里插入图片描述

    三、变量

    1. 作用:

    • 用来 存放系统和用户需要使用的特定参数
      变量名:使用固定的名称,由系统预设和用户定义
      变量值:能够根据用户设置,系统环境的变换而变换

    2. 变量的类型

    • 自定义变量:由用户自己定义,修改和使用

    • 特殊变量:环境变量,只读变量,位置变量,预定义变量

    • 定义一个新的变量
      变量名以字母或下划线开头,区分大小写,建议全大写
      变量名=变量值

    3. 查看变量的值

    echo $变量名
    在这里插入图片描述

    4. 赋值时使用引号等

    
    双引号:允许通过$符号引用其他变量值
    
    单引号:禁止引用其他变量值,$视为普通字符
    
    反撇号:命令替换,提取命令执行后的输出结果,`...`和$(...)作用相同
    

    双引号:引用变量值,记得把$也放在引号内

    在这里插入图片描述
    反撇号:把反撇号里面执行的结果值提取出来

    在这里插入图片描述

    5. read 从键盘中获取变量的值

    
    read -p "提示信息" 变量名
    
    

    在这里插入图片描述
    read 基本都是写在脚本当中用来或许键盘输入的值

    6. echo 的常见用法

    
    echo -n    #表示不换行输出
    echo -e    #输出转义字符,将转义后的内容输出到屏幕上
    
    

    在这里插入图片描述

    7. 设置变量的作用范围

    默认情况下,新定义的变量只在当前的Shell环境中有效,因此称为局部变量。当进入子程序或新的子shell环境时,局部变量将无法再使用

    系统环境中的 局部变量可以用export 来把局部提升至全局变量
    提升后重启就失效了,如果想一直使用,则在/etc/profile里输入
    export 变量名=变量值

    
    
    格式1export 变量名   #(赋过值得的)
    
    格式2export 变量名=变量值    # (没赋过值的)
    
    两种格式可以混合使用
    

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    
    
    export rain          #导出为全局变量
    
    bash                 #进入子环境
    
    echo "$rain"     
    

    在这里插入图片描述

    8. 整数的运算(四种书写方式)

    shell中需要通过运算符才能实现运算,这里用整数的运算符

    
    expr 变量1 运算符 变量2 [运算符变量3]...
    

    在这里插入图片描述

    在这里插入图片描述
    四种方式:
    在这里插入图片描述

    四、特殊的Shell变量

    1.环境变量

    由系统提前创建,用来设置用户的工作环境

    配置文件: /etc/profile、~/.bash_profile

    路径名称具体
    PATH可执行程序的默认搜索路径
    USER用户名称
    SHELL当前的 shell,它的值通常是 /bin/bash
    HOME用户的宿主目录
    LANG语言和字符集
    
    echo $PASH           #显示pash路径变量值。(显示某个环境变量值,这里的pash可以是其他路径)
    export               #设置一个新的环境变量
    env                  #显示所有的环境变量
    unset                #清除环境变量
    set                  #显示本地定义的shell变量和环境变量
    
    
    echo $PATH            #查看当前搜索路径
    
    PATH="$PATH:/root"   #将/root目录添加到搜索路径
    
    export PATH="$PATH:/root"    #输出为全局环境变量first.sh
    
    

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    2. 只读变量

    对于一些固定不变且不允许修改的变量。类似与环境变量设置为只读

    在这里插入图片描述

    3. 位置变量

    在执行脚本的同时输入具体的变量参数
    下图中的$1 $2 $3 表示的是位置变量参数,通过执行脚本时直接赋予

    在这里插入图片描述

    在这里插入图片描述
    注意:$0 为此脚本本身的名字
    在这里插入图片描述

    10以后的需要加大括号
    否则$11 为外面第一个数字 $1 +1


    4. 预定义变量

    
    
    
    $*、$@:表示命令或脚本要处理的参数。
    
    $*:把所有参数看成以空格分隔的一个字符串整体(单字符串)返回,代表"$1 $2 $3 $4"
    
    $@:把各个参数加上双引号分隔成n份的参数列表,每个参数作为一个字符串返回,代表"$1" "$2" "$3" "$4"
    
    $0:表示当前执行的脚本或命令的名称。
    
    $#:表示命令或脚本要处理的参数的个数
    
    $?:表示前一条命令或脚本执行后的返回状态码,返回值为0表示执行正确,返回任何非0值均表示执行出现异常。也常被用于Shell脚本中return退出函数并返回的退出值。
    
    
    
    
    $*、$@不加双引号时表现一致;加双引号时,$*会将所有参数作为一个整体。
    vim bianliang.sh
    #!/bin/bash
    test()  {
            echo "未加引号,二者相同"
            echo $*
            echo $@
            echo "加入引号后对比"
            for N in "$*"
            do
                echo $N
                echo " "
            done
            
            echo "----"
            for N in "$@"
            do
               echo $N
               echo " "				
            done					
    }
    test 12 34 56 78
    

    在这里插入图片描述

    展开全文
  • 让你学会C语言编程中volatile变量的使用
  • 转载自:http://www.aboutyun.com/thread-19652-1-1.html问题导读1.spark共享变量的作用是什么?2.什么情况下使用共享变量?3.如何在程序使用共享变量?...不可能在程序声明一个全局变量,在分布式编程中就...
  • Oracle基础--PL/SQL编程--变量

    万次阅读 2021-09-28 13:09:07
    一、PL/SQL变量概念 在Oracle,有两种变量,一为普通变量(如char、varchar2、date、number、long等),二为特殊变量(引用型变量、记录型变量)。 二、普通变量的声明 普通变量声明方式为: 变量名 变量类型...
  • 就像JavaScript和C语言,GLSL ES也有全局变量和局部变量概念。全局变量可以在程序的任意位置使用,而局部变量只能在优先的某一部分代码使用。 在GLSL ES,如果变量声明在函数的外面,那么它就是全局变量...
  • 情景一:建立一个shell脚本var1.sh,其内容为: [rocrocket@rocrocket SHELL]$ cat -n var1.sh 1 #!/bin/bash 2 echo :$myvar: 然后在终端上执行变量赋值语句并用e
  • Linux 系统编程 -进程概念

    万次阅读 多人点赞 2021-06-07 20:16:56
    Linux系统编程-进程篇冯诺依曼体系结构冯诺依曼的两个重要思想当代计算机的三级缓存操作系统操作系统的概念操作系统的组成操作系统作用Linux下的操作系统体系进程进程概念进程特性进程的组成进程与程序区别进程控制...
  • C++编程--原子变量

    千次阅读 2019-06-06 17:38:18
    在新标准C++11,引入了原子操作的概念,并通过这个新的头文件提供了多种原子操作数据类型,例如,atomic_bool,atomic_int等等,如果我们在多个线程对这些类型的共享资源进行操作,编译器将保证这些操作都是原子性...
  • MATLAB面向对象编程:在类实现static成员变量 http://www.matlabsky.com/thread-23077-1-1.html 类(class)封装是C++语言最显著的特征!但是很少听说MATLAB类的概念,这里悄悄的告诉的大家,其实MATLAB也...
  • 并发编程中的问题 在并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题。我们先看具体看一下这三个概念: 1.原子性  原子性:即一个操作或者多个操作 要么全部执行并且执行的过程...
  • Scala核心编程 第二章—Scala变量概述

    万次阅读 2019-09-17 21:08:15
    变量相当于内存一个数据存储空间的表示,你可以把变量看做是一个房间的门 牌号,通过门牌号我们可以找到房间,而通过变量名可以访问到变量(值)。 2.Scala变量使用案例 3.变量声明基本语法 var | val 变量...
  • 函数式编程--变量作用域

    千次阅读 2015-10-26 09:58:42
    通常,我们希望能够在lambda表达式的闭合方法或类访问其他的变量,比如下面的代码: public class Test { public void test(String text, int count) { Runnable a = () -> { for (int i = 0; i ; i++...
  • 函数式编程-闭包概念

    2012-03-09 20:12:45
    “闭包” 一词来源于以下两者的结合:要执行的代码块(由于自由变量被包含在代码块,这些自由变量以及它们引用的对象没有被释放)和为自由变量提供绑定的计算环境(作用域)。在 Scheme、Common Lisp、Smalltalk、...
  • 变量概念与定义格式

    千次阅读 2020-03-12 22:59:48
    变量:常量是固定不变的数据,那么在程序可以变化的量称为变量。 数学,可以使用字母代替数字运算,例如 x=1+5 或者 6=x+5。 程序,可以使用字母保存数字的方式进行运算,提高计算能力,可以解决更多的问题。...
  • 详解Shell编程规范与变量一、Shell脚本概述1、shell脚本的概念2、shell脚本应用场景3、shell的作用——命令翻译器,“翻译官”二、用户的登录shell三、shell脚本的构成四、shell脚本的执行方法一:指定路径的命令,...
  • PL/SQL 编程语言概念 PL/SQL 编程语言是对 SQL 语言的扩展,使得 SQL 语言具有过程化编程的特性,比一般的过程化编程语言更加灵活高效。PL/SQL 编程语言主要用来编写存储过程和存储函数等。 PL/SQL 程序语法: ...
  • Scratch使用变量和列表管理数据。 什么是变量? 还记得我们在数学计算给游戏增加一个记分的score吗? score一开始被设成0,当答对问题后,我们让score增加一个数值。回答问题正确数量越多,score就越大。这里...
  • 你真的理解程序变量

    千次阅读 2016-12-18 21:35:02
    关于变量,很多人肯定觉得这有啥可说的,不就是int a=10这类的吗,这样的太简单了, ...上述概念中说到了内存对象,要想真正理解变量就需要从内存的角度来理解。我们来看下面这张图: int a;定义了一个变量a,
  • Java并发编程:基本概念与volatile关键字解析

    万次阅读 多人点赞 2016-06-03 14:54:58
    在Java 5之前,它是一个备受争议的关键字,因为在程序使用它往往会导致出人意料的结果。在Java 5之后,volatile关键字才得以重获生机。  volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的...
  • Shell编程概念 在Shell中用$变量名,来定义和使用变量。 在Shell,可用echo命令输出常量、变量或者表达式的值。 变量的类型有:自定义变量、环境变量和参数变量三种。
  • 《代码之髓--编程语言核心概念》 [日] 西尾泰和(Nishio Hirokazu) 著 曾一鸣 译 人民邮电出版社 2014年8月第1版 书的示例源代码可以从作者的网站上下载: http://nhiro.org/langbook/ p3: ruby语言false, ...
  • Less变量

    千次阅读 2017-11-20 10:30:47
    Less 变量和其他编程语言一样,可以实现值的复用,同样它也有作用域(scope)。简单的讲,变量作用域就是局部变量和全局变量概念。 Less 变量作用域采用的是就近原则,换句话说,就是先查找自己有没有这个...
  • 一直以为是没有的,今天看官方文档,无意竟然发现了,其实是有的。也怪自己,平时编码,在@interface 里面从来没有试过@private 一下,其实是有的,自动补全的提示也有。 Objective C 有跟 C++一样的成员变量...
  • Java编程那些事儿18——变量和常量

    千次阅读 2008-05-07 10:35:00
    Java编程那些事儿18——变量和常量作者:陈跃峰出自:http://blog.csdn.net/mailbomb 3.6 变量和常量 在程序存在大量的数据来代表程序的状态,其中有些数据在程序的运行过程中值会发生改变,有些数据在程序运行...
  • 问题: 今天同事在写一个STM32上的程序时,总是遇到内存溢出的错误。结果发现是因为使用了一个局部变量导致的。 因为C语言的局部变量被编译器自动放...而且这样的错误在编译的过程不会有任何错误,只有跑起来才出现…

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 378,153
精华内容 151,261
关键字:

编程中变量的概念