红魔咖啡馆

头发越掉越多,头发越掉越少

0%

Interface & Inheritance

重载

Java允许多个方法拥有相同名称,只要他们的参数不同,这就是Java中的重载

重载的优缺点:

  • 代码基本一样
  • 对其余猎豹不适用,需要增加重载
  • 难维护

Hypernym and Hyponym

上位词与下位词

如Dog与poodle、malamute等狗的品种,前者为后者的上位词,后者为前者的下位词

Java中也有类似的概念,如我们可以把List作为SLList与ArrayList的上位词

在Java中表示这种属性:

  1. 给上位词定义一个引用属性
  2. 指定某些词为该上位词的下位词
  • 使用关键词interface来定义上位词
  • 使用关键词implements来定位下位词

接口内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package lec8;

public interface List61B<T> {
    public void addFirst(T x);

    public void addLast(T x);

    public T getFirst();

    public T getLast();

    public T get(int i);

    public T removeLast();

    public int size();
}

下位词部分修改:

1
2
3
4
public class ArrayList<T> implements List61B<T>
    ...
public class SLList<T> implements List61B<T>
    ...

这样在使用时我们只需要以该接口作为类型定义即可

重写

若你有一个子类实现接口,其方法与超类的签名相同,则可以在子类中重写来覆盖该方法

有相同名称而不同签名的方法叫做重写

在要重写的方法上面添加@override标签,当该方法不是一个重写方法时,该代码不会被编译

使用该标签的优点:

  • 主要优点:防止拼写错误,当标记了@override,但方法并没有被重写,就会爆CE
  • 提醒程序员方法来自于更高级的继承

Interface Inheritance

使用implements关键字来指定子类所具备的能力叫作接口继承

  • 接口:所有方法签名的列表
  • 继承:子类继承了来自超类的接口
  • 接口继承指定了子类可以做什么,而没有指定怎么做
  • 子类必须重写所有方法,否则会CE

Implementation Inheritance

接口继承只继承了签名,而没有继承实现

Java允许另一种继承:实现继承,使得子类可以继承签名与实现

使用default关键字来指定一个子类可以从接口继承的方法

1
2
3
4
5
6
7
// implementation inheritance
    default public void print(){
        for (int i = 0;i<size();i++){
            System.out.print(get(i)+" ");
        }
        System.out.println();
    }

(在接口文件中写默认的print方法,子类可以继承该方法)

1
2
3
4
5
6
7
8
9
// 重写接口中的print方法
   @Override
   public void print(){
       IntNode p = sentinel.next;
       while(p!=null){
           System.out.print(p.item+" ");
           p = p.next;
       }
   }

(针对特定子类,可以对default方法进行重写)

Abstract Data Type

抽象数据类型(ADT)仅由其操作定义,而不由其实现定义

ArrayList

Array中的随机访问

根据指定位置获取a数组中的元素是很快的,原因如下:

  • 与数组大小无关
  • 内存空间其实都是相同的大小

实现

添加删除元素

观察发现:

  • 元素个数可以用size表示
  • 在结尾添加元素时,添加的位置其实是第size位
  • 而获得结尾元素时,结尾元素位于size-1的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void addLast(int x){
        items[size] = x;
}

public int getLast(){
    return items[size-1];
}

public int get(int i){
    return items[i];
}

public int removeLast(){
    int itemToReturn = getLast();
    size -=1;
    return itemToReturn;
}

扩容

当列表满了以后,还想扩容并且增加容量,需要创建一个新数组,并调整size,将新元素加入数组

1
2
3
4
5
6
7
8
9
10
11
12
13
private void resize(int capacity){
        int[] a = new int[capacity];
        System.arraycopy(items, 0, a, 0, size);
        items = a;
}

public void addLast(int x){
    if (items.length == size){
        resize(size+1);
    }
    items[size] = x;
    size++;
}

这里将resize作为一个private方法,即调用addLast时,若发现空间不够,则会自动调用resize扩容,而不用手动调用

但每次扩容都会复制一份数组,扩容次数多了,创建的数组空间就会变大,导致性能下降

我们可以在每次扩容时在原大小基础上乘一个常量以一次性扩大大量空间,可以有效解决性能问题(Python的列表就是这样的原理)

但有时候不需要那么多空间,会造成空间浪费,故在发现利用率不够时,我们需要缩小数组尺寸:

  • 定义一个使用比率:\(R = \frac{\text{size}}{\text{items.length}}\)
  • 常用方案位当\(R<0.25\)时,将size减半

泛型化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package lec7;

public class ArrayList<T> {
    private T[] items;
    private int size;
    public ArrayList(){
        size = 0;
        items = (T[]) new Object[100];
    }

    private void resize(int capacity){
            T[] a = (T[])new Object[capacity];
            System.arraycopy(items, 0, a, 0, size);
            items = a;
    }

    public void addLast(T x){
        if (items.length == size){
            resize(size+1);
        }
        items[size] = x;
        size++;
    }

    public T getLast(){
        return items[size-1];
    }

    public T get(int i){
        return items[i];
    }

    public T removeLast(){
        T itemToReturn = getLast();
        items[size-1] = null;
        size -=1;
        return itemToReturn;
    }
}

有一个问题:在创建泛型T的数组引用时,会用到(T[]) new Object[100]这样的创建方法,即对类型进行了强制类型转换,IDEA会报warning:未检查的转换,此时可以忽略

在泛型的情况下,对于删除元素最好在减小size的同时将数组对应位置设置为null,这样垃圾回收器可以发现并回收内存,因为泛型导致了存入数组的数据类型大小可能会很大,从而浪费空间

Testing

Unit Tests

测试库

可以使用一些库中的语句进行测试,如:Truth库、JUnits库等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package lec6;
import static com.google.common.truth.Truth.assertThat;

public class TestSort {
    public static void testSort(){
        String[] input = {"bob", "luka", "C++"};
        String[] expected = {"bob", "C++", "luka"};
        Sort.sort(input);

        assertThat(input).isEqualTo(expected);
    }
    public static void main(String[] args){
        testSort();
    }
}

@test

使用标识符@test可以实现在文件中直接写入测试,而不需要在main函数中调用

IDEA中,使用该测试可以使用其自带的单元测试框架,可观列出测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package lec6;
import org.junit.jupiter.api.Test;

import static com.google.common.truth.Truth.assertThat;

public class TestSort {
    @Test
    public void testSort(){
        String[] input = {"bob", "luka", "C++"};
        String[] expected = {"bob", "C++", "luka"};
        Sort.sort(input);

        assertThat(input).isEqualTo(expected);
    }
}

Example: Building Selection Sort

原理:

  • 选择最小的元素
  • 移至表头
  • 对剩下n-1个元素进行选择排序

思路:

代码:测试部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package lec6;
import org.junit.jupiter.api.Test;

import static com.google.common.truth.Truth.assertThat;

public class TestSort {
    @Test
    public void testFindSmallest(){
        String[] input = {"bob", "luka", "c++"};
        int expected = 0;
        int actual = Sort.findSmallest(input, 0);
        assertThat(actual).isEqualTo(expected);
    }

    @Test
    public void testSwap(){
        String[] input = {"bob", "luka", "c++"};
        String[] expected = {"luka", "bob", "c++"};

        Sort.swap(input, 0, 1);
        assertThat(input).isEqualTo(expected);
    }
}

实现部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package lec6;

public class Sort {
    public static void sort(String[] x){
        sort(x, 0);
    }
    public static void sort(String[] x, int s){
        if (s == x.length){
            return ;
        }
        int smallest = findSmallest(x, s);
        swap(x, s, smallest);
        sort(x,s+1);

    }
    public static int findSmallest(String[] x, int s){
        int smallestIndex = s;
        for (int i = s; i<x.length; i++){
            int cmp = x[i].compareTo(x[smallestIndex]);
            if (cmp<0){
                smallestIndex = i;
            }
        }
        return smallestIndex;

    }
    public static void swap(String[] x, int a, int b){
        String temp = x[a];
        x[a] = x[b];
        x[b] = temp;
    }
}

综上,测试可以增强代码的鲁棒性

  • 在单元测试中增加信心
  • 确保之后的改变不会破坏代码
  • 辅助代码的重构

DLList

DLList

为了让remove等操作更加高效,我们在SLList的基础上进行改进,变为双向链表,这样有利于提升操作效率

同时,对于头节点的操作,我们可以用两个头节点,但更好的方法是使用循环链表

具体实现详见Project1

Generic Lists

我们设计的SLList目前只接受int类型,通过修改可以实现接受各种类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package lec5;

// 数字列表
public class GenericList<T> {
    private class TNode {
        public T item;
        public TNode next;

        public TNode(T i, TNode n){
            item = i;
            next = n;

        }
    }

    //列表的第一项
    private TNode sentinel;
    private int size;

    public GenericList(){
        sentinel = new TNode(null, null);
        size = 0;
    }
    public void addFirst(T x){
        size++;
        sentinel.next = new TNode(x, sentinel.next);
    }

    public T getFirst(){
        return sentinel.next.item;
    }

    public void addLast(T x){
        size++;
        TNode p = sentinel;
        while(p.next!=null){
            p = p.next;
        }
        p.next = new TNode(x, null);
    }

    public int size(){
        return size;
    }

    public static void main(String[] args){
        GenericList<Integer> L = new GenericList<>();
        L.addFirst(10);
        L.addFirst(5);
        L.addLast(20);
        System.out.println(L.getFirst());
        System.out.println(L.size());
    }

}

泛型的规则如下:

  • .java文件中实现数据结构时,需要在文件开头设定泛型且只能设定一次

  • .java文件中使用数据结构时,设定需要的数据类型

    类名<引用类型> 实例名 = new 类名<>();

  • 声明和实例化时,需要使用引用类型

    • int:Integer

    • double:Double

    • char:Character

    • boolean:Boolean

    • long:Long

Arrays

数组是一种拥有指定长度内存的对象,组成如下:

  • 固定整型长度

  • N=length个内存空间

    • 所有内存空间有着相同数据类型
    • 索引从0到length-1

初始化方法

1
2
3
4
x = new int[3];
y = new int[]{1,2,3,4,5};

int[] z = {9, 10, 12, 13};

数组复制

  • 使用循环逐个复制
  • 使用System.arraycopy(),包括五个参数
    • 源数组
    • 源数组起始位置
    • 目标数组
    • 目标数组起始位置
    • 复制元素个数

二维数组

二维数组

SLList

SLList

我们将对前面创建的链表进行封装与优化

对上节课的IntList的修改:

1
2
3
4
5
6
7
8
9
10
11
12
package lec4;

public class IntNode {
    public int item;
    public IntNode next;

    public IntNode(int i, IntNode n){
        item = i;
        next = n;

    }
}

重构为IntNode

接下来创建类SLList并进行封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package lec4;

// 数字列表
public class SLList {
    //列表的第一项
    public IntNode first;

    public SLList(int x){
        first = new IntNode(x,null);
    }
    public void addFirst(int x){
        first = new IntNode(x, first);
    }

    public int getFirst(){
        return first.item;
    }

    public static void main(String[] args){
        SLList L = new SLList(15);
        L.addFirst(10);
        L.addFirst(5);
        System.out.println(L.getFirst());
    }

}

但接下来有一个问题:有些用户可能会不通过给定的方法,而是直接操纵内部属性,这样可能会导致一些致命性的错误,如L.first = null,这时我们需要加强抽象屏障

我们需要修改一些实例的权限,防止可以直接被操纵,具体操作为将public改为private

private关键字使得其他类中的代码调用该类中的成员

隐藏实现细节:

  • 让用户理解更少
  • 修改方法时是安全的(因为没有人可以调用)

嵌套类

Java中可以将类进行嵌套,如我们可以将IntNode类放入SLList类中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package lec4;

// 数字列表
public class SLList {
    private static class IntNode {
        public int item;
        public IntNode next;

        public IntNode(int i, IntNode n){
            item = i;
            next = n;

        }
    }

    //列表的第一项
    private IntNode first;

    public SLList(int x){
        first = new IntNode(x, null);
    }
    public void addFirst(int x){
        first = new IntNode(x, first);
    }

    public int getFirst(){
        return first.item;
    }

    public static void main(String[] args){
        SLList L = new SLList(15);
        L.addFirst(10);
        L.addFirst(5);
        System.out.println(L.getFirst());
    }

}

当一个类无法独立存在(明显从属于另一个类)时,设为嵌套类是合适的。

对于第5行嵌套类是否设为静态:若嵌套类不使用外部类的任何内容,则可以设为static

增加功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void addLast(int x){
    IntNode p = first;
    while(p.next!=null){
        p = p.next;
    }
    p.next = new IntNode(x, null);
}

public int size(){
    return size(first);
}
private int size(IntNode p){
    if (p==null){
        return 0;
    }
    return 1 +size(p.next);
}

其中,计算size时使用了递归,是通过在一个非递归类里调用了递归方法实现的:

  • 创建一个私有的递归方法,以那些裸递归对象作为参数
  • 在一个公共方法中直接调用该递归方法

但这种方式很低效,我们需要优化为\(O(1)\)复杂度

优化递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package lec4;

// 数字列表
public class SLList {
    private static class IntNode {
        public int item;
        public IntNode next;

        public IntNode(int i, IntNode n){
            item = i;
            next = n;

        }
    }

    //列表的第一项
    private IntNode first;
    private int size;

    public SLList(int x){
        first = new IntNode(x, null);
        size = 1;
    }
    public void addFirst(int x){
        size++;
        first = new IntNode(x, first);
    }

    public int getFirst(){
        return first.item;
    }

    public void addLast(int x){
        size++;
        IntNode p = first;
        while(p.next!=null){
            p = p.next;
        }
        p.next = new IntNode(x, null);
    }

    public int size(){
        return size;
    }

    public static void main(String[] args){
        SLList L = new SLList(15);
        L.addFirst(10);
        L.addFirst(5);
        L.addLast(20);
        System.out.println(L.getFirst());
        System.out.println(L.size());
    }
}

我们创建了一个size变量,存储数据来方便检索,在add的时候同时更新该值

创建空链表

修改为空链表后,addLast方法会出现问题,因为此时的first为null,无法访问first.next

解决方法1:

1
2
3
4
5
6
7
8
9
10
11
12
public void addLast(int x){
        size++;
        if (first == null){
            first = new IntNode(x, null);
            return ;
        }
        IntNode p = first;
        while(p.next!=null){
            p = p.next;
        }
        p.next = new IntNode(x, null);
    }

这样需要一个特殊情况,使得代码更加复杂以及更难追踪

解决方法2:

在空链表初始化时加入头节点(sentinel)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package lec4;

// 数字列表
public class SLList {
    private static class IntNode {
        public int item;
        public IntNode next;

        public IntNode(int i, IntNode n){
            item = i;
            next = n;

        }
    }

    //列表的第一项
    private IntNode sentinel;
    private int size;

    public SLList(int x){
        sentinel = new IntNode(0, null);
        sentinel.next = new IntNode(x, null);
        size = 1;
    }
    public SLList(){
        sentinel = new IntNode(0, null);
        size = 0;
    }
    public void addFirst(int x){
        size++;
        sentinel.next = new IntNode(x, sentinel.next);
    }

    public int getFirst(){
        return sentinel.next.item;
    }

    public void addLast(int x){
        size++;
        IntNode p = sentinel;
        while(p.next!=null){
            p = p.next;
        }
        p.next = new IntNode(x, null);
    }

    public int size(){
        return size;
    }

    public static void main(String[] args){
        SLList L = new SLList();
        L.addFirst(10);
        L.addFirst(5);
        L.addLast(20);
        System.out.println(L.getFirst());
        System.out.println(L.size());
    }

}

sentinal在这里的用处为占位,该节点永远不为空,且其值随意

不变式(Invariants)

不变式就是代码运行中保证成立的条件

存在头节点的SLList有以下不变式:

  • 头节点的引用总指向头节点
  • 第一个节点总存在sentinal.next
  • size变量总是表示添加节点的数目

Java Basic 2

声明变量的原理

  • 计算机留出对应变量类型的内存空间
  • Java创建一个内部表用来将变量名与各个位进行对应
  • 首次声明时,Java不会在这些空间里写入任何东西,但你不可以使用一个未定义的变量,对变量赋值后,才会向里面填充内容

类的实例化

当实例化一个对象时

  • Java对类中的每个实例变量分配空间并初始化值
  • 构造函数用其他一些值填充这些空间

其中,new关键字的作用类似于寻找并返回新创建的对象的地址

引用类型

不属于Java的八种基本类型的类型统称为引用类型

当声明一个引用类型时:

  • Java会分配64bits空间,无论是哪一种对象
  • 这些空间会被设置为
    • Null(二进制位全0)
    • 该类特定实例的地址

参数传递

The Golden Rule of Equals:

给予变量a与b,a=b的意义为把b的bits值复制给a

参数传递的方式按照此规律:即按值传递(有时候值为地址),而不能使用引用(如C++中的&)

数组的比较

Java中,==运算符仅仅是比较两个数组的地址是否相同

如果需要比较内容是否已知,可以使用Arrays.equals(x,y);方法

实例:递归实现链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package lec3;

// 无限长的整型链表
// 使用递归写
public class IntList {
    public int first;
    public IntList rest;

    public IntList(int f, IntList r) {
        first = f;
        rest = r;

    }

    public int size() {
        if (rest == null) {
            return 1;
        }
        return 1 + rest.size();
    }

    public int get(int x) {
        if (x == 0) return this.first;
        return this.rest.get(x - 1);
    }

    public static void main(String[] args) {
        IntList L = new IntList(15, null);
        L = new IntList(10, L);
        L = new IntList(5, L);

        System.out.println(L.size());
        System.out.println(L.get(2));
    }
}

Java Basic 1

关于类

  • 每个方法(method)都与某些类相关
  • 执行一个类需要主(main)方法,但不是所有类都有主方法

e.g. 创建一个狗类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Dog {
    public int size;
    public static String binmen = "Canis";
    // 构造函数
    public Dog(int s){
        size = s;
    }
    public void makeNoise(){
        if (size >20){
            System.out.println("bark");
        }
        else if (size <10){
            System.out.println("bark!");
        }
        else{
            System.out.println("bark!!");
        }
    }
    public Dog maxDog(Dog otherDog){
        System.out.println("Being called");
        if (this.size > otherDog.size){
            return this;
        }
        return otherDog;
    }

}

(Dog.java)

1
2
3
4
5
6
7
8
9
10
public class DogLauncher {
    public static void main(String[] args){
        Dog maya = new Dog(9);
        maya.makeNoise();

        Dog hugeDog = new Dog(100);
        Dog largerDog = maya.maxDog(hugeDog);

    }
}

DogLauncher,java文件

(将dog类放入Dog.java文件,主方法放入DogLauncher.java文件,调用主方法时从Dog类调用makeNoise方法)

该Dog类包含如下:

  • 实例变量:定义一个示例的多个属性
  • 构造函数:定义如何实例化一个类
  • 非静态方法(实例方法):由特定实例调用的方法,不需要加static关键字

Array

一种ADT的受限版本

  • 创建数组时需要声明大大小
  • 大小不能更改
  • 所有元素类型需要相同
  • 没有方法

Map

map是键值对的集合,每个键都保证是唯一的

Lab3-Debugging2

给予项目Adventure来进行调试并修改代码

学会阅读堆栈追踪

运行主方法后,会在某个位置抛出异常:

一般作物类型会在报错第一行出现,如该处出现错误:NullPointerException

对于这一类的异常,java也会抛出解释,这里指的是this.inputnull,因此我们不能调用方法

下方表示程序引发错误时的方法调用序列:列表中的第一行是错误发生的位置,其下一行表示调用抛出错误方法的代码行,依此类推。

可以点击蓝色文本追踪到对应文件和方法行处

注意:按照执行顺序,堆栈追踪第一行错误显示的应为发生错误时最后调用的方法,可以根据此来判断bug的位置等

Debug BeeCountingStage

根据错误信息发现,错误应该出现在BeeCountingStage中,我们跳入该类进行检查

此处抛出空指针的原因是this.input还是空指针,未被初始化,因此我们需要对input初始化

可以发现声明变量处代码为private List<String> input;,再看构造函数发现这两处均为对input初始化,故我们在构造函数中添加初始化代码this.input = new ArrayList<>(0);即可解决问题

第二个问题还是出现在该类中,错误类型为IndexOutOfBoundsError

经检查,是sumInput函数中,for循环的终止条件多了等号,导致多访问了一位没有定义的位置

Debug SpeciesListStage

错误类型为/ by zero,跟踪到函数arraySimilarity

发现在return处计算相似度时,若询问结束,还会传入一次空字符串,即listOne的size为0,导致除以0的错误发生

所以若传入的是空字符串,我们需要特定的返回0

Debug PalindromeStage

错误类型:Index 3 out of bounds for length 3

我们进入类PalindromeStage,根据IDEA的warning提示,来到函数digitsToIntList

容易发现第一个bug出在for循环中,对计数器i的操作有误,此处为反向遍历,i应该自减

第二个bug也在for循环中,对于s的下标,最后一位应该是s.length()-1,第一位应该是0,这样避免charAt方法出现异常

Debug MachineStage

该机器的作用是将输入的两个等长数列的每位进行比较,选出每个下标中更大的那一位,并组成一个新数组并求和,但目前结果有误

第一个问题出在mysteryMax处,这里对b-a获得了两个掩码

  • b - a >= 0 时,w 为 0,
  • b - a < 0 时,w 为 -1(即全 1)

然后z为w的取反,获得的是相反结果,max的计算等价于

  • b >= aw = 0z = -1,结果为 (b & 0) | (a & -1),返回 a
  • b < aw = -1z = 0,结果为 (b & -1) | (a & 0),返回 b

因此这里返回的是最小值,而非最大值,应该将z与w对调

第二个问题出在arraySum处,这里要求数组最大值之和

但代码行sum = sum + mysteryAdd(sum, x[i]);明显多加了一个sum,因为mysteryAdd中已经执行了相加操作并返回和,应该删除一个

Lab2-Debugging

我们需要学会在idea中使用交互式的debug工具

在行号上点击以设置断点,当debug时,运行到断点处便会停止

设置断点后,点击debug按钮开始进行debug

此时在控制台处会有debugger窗口,在左侧可以看到当前的所有方法调用,在右侧可以看到程序当前点的实例变量值(它们也会在编辑器中以灰色文本显示)。对于类的实例,可以点击下拉菜单来展开它们并查看它们的字段。

有一些按钮可以进行操作:

Lambda表达式

用于快速定义一个匿名函数对象(闭包)

语法:

1
2
3
[捕获变量](参数列表) 可选限定符->返回类型{
    代码
}

原理

实际上是函数对象的一种快速定义方式

1
2
3
4
5
6
7
8
9
int x = 7;
float y = 3.0;
struct {
    int _x;
    float y;
    float operator()(int a,int b) const{
        return _x *_ y+a*b;
    }
}p{x,y};

变量捕获

按值捕获

lambda中的成员变量是外部变量的拷贝

外部更改不会改变内部的变量

此时lambda对应的函数调用运算符默认是const函数,函数内部不能修改按值捕获的变量,也不能调用非常量函数(或者可以在lambda函数后加上mutable限制符)

按引用捕获

在中括号中变量前添加引用运算符

外部更改会改变内部的变量

若所引用的变量已经失效,则会报错或访问无效数据

默认捕获方式

  • [=]:默认按值捕获

  • [&]:默认按引用捕获

  • [this]/[*this]:在类中按引用/按值捕获,可以访问类成员

此时也可以添加特例,添加显式捕获的变量

但默认捕获和显式捕获不能相同

其中[=], [&], [&, this], [=, *this], [=, this]这几种捕获方式,this都是按引用捕获的

变量使用

除了可以使用捕获变量,还可以使用自定义变量,外层中的静态变量与全局变量

泛型lambda

在参数类型中使用auto即可实现泛型lambda,相当于套了类模板