Swift 5.1 温故而知新笔记系列之第二天

1.结构体

在 Swift 标准库中,绝大多数的公开类型都是结构体,而枚举和类只占很小一部分
比如BoolIntDoubleStringArrayDictionary等都是结构体

struct Point {
	var x: Int = 0
	var y: Int = 0
	var origin: Bool = false
}
var point = Point(x: 10, y:10, origin:false)
print(MemoryLayout<Point>.size) // 17
print(MemoryLayout<Point>.stride) // 24
print(MemoryLayout<Point>.alignment) // 8

所有结构体都有一个编译器自动生成可传入成员值的初始化器(initializer,初始化方法,构造器,构造方法)
自动生成的初始化器会保证存储属性都被赋值

2.类

类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器

class Point {
	var x: Int = 0
	var y: Int = 0
}
let p1 = Point()
let p2 = Point(x: 10, y: 20) // 报错
let p3 = Point(x: 10)  // 报错
let p4 = Point(y: 20)  // 报错

如果类的所有成员都在定义的时候指定了初始值,编译器会为类生成无参的初始化器 p成员的

class Point {
    var x: Int = 0
    var y: Int = 0
}
var p = Point()

class Point {
    var x: Int
    var y: Int
    init() {
        x = 0
        y = 0
    }
}
var p = Point()

类和结构体的本质区别

  • 1.结构体是值类型,类是引用类型
struct Size {
    var width = 3
    var height = 4
}

class Point {
    var x: Int = 1
    var y: Int = 2
    init(x: Int, y: Int) {
        self.x = x
        self.y = y
    }
}

func test() {
    // 0x00007ffeefbff048
    var point = Point(x: 100, y: 200)
    
    // 0x00007ffeefbff030
    // 03 00 00 00 00 00 00 00
    // 04 00 00 00 00 00 00 00
    var size = Size()

    withUnsafePointer(to: &point) { ptr in
        print("point \(ptr)")
    }

    withUnsafePointer(to: &size) { ptr in
        print("size \(ptr)")
    }

    // 引用类型,指针都占用8
    print(MemoryLayout.size(ofValue: point)) // 8
    print(MemoryLayout.stride(ofValue: point)) // 8
    print(MemoryLayout.alignment(ofValue: point)) // 8
    
    // 0x0000000102800150
    // 68 77 00 00 01 00 00 00  0x10007768
    // 02 00 00 00 00 00 00 00  0x00000002
    // 64 00 00 00 00 00 00 00  0x00000064
    // C8 00 00 00 00 00 00 00  0x000000C8
    print(Unmanaged.passUnretained(point).toOpaque())
    
}
test()

在这里插入图片描述
可以看到,我们是在函数体内,栈空间创建的,如果是在外部,那就会在全局区,如果是在class里面创建,就会跟随class在堆空间,这边就举个例子。

  • 2.结构体值类型给var、let或者给函数传参,是直接深copy了一份
//全局变量,程序运行过程中只有一份内存 内存地址不变 
struct Space {
    var x: Int
    var y: Int
}

var space1 = Space(x: 10, y: 20)
var space2 = space1

space2.x = 100
space2.y = 200

//withUnsafePointer(to: &space1) {  ptr in
//    print("space1 \(ptr)")
//}
//
//withUnsafePointer(to: &space2) {  ptr in
//    print("space2 \(ptr)")
//}

这边用全局变量来做例子,你也可以放到函数体局部变量来做例子。
先看全局变量上面最简单代码的赋值汇编

0x1000013ab <+11>:  movl   $0xa, %eax			// 10 给到 eax  = rax
0x1000013b0 <+16>:  movl   %edi, -0x7c(%rbp)
0x1000013b3 <+19>:  movq   %rax, %rdi			// rax 给到 rdi = 10
0x1000013b6 <+22>:  movl   $0x14, %eax			// 20 给到 eax  = rax
0x1000013bb <+27>:  movq   %rsi, -0x88(%rbp)
0x1000013c2 <+34>:  movq   %rax, %rsi			// rax 给到 rsi = 20
0x1000013c5 <+37>:  callq  0x100001740               ; Swift01.Space.init(x: Swift.Int, y: Swift.Int) -> Swift01.Space at main.swift:16

si 进去callq对应的init函数


Swift01`Space.init(x:y:):
->  0x100001740 <+0>:  pushq  %rbp
    0x100001741 <+1>:  movq   %rsp, %rbp
    0x100001744 <+4>:  movq   %rdi, %rax		rdi = 10 给到 rax
    0x100001747 <+7>:  movq   %rsi, %rdx		rsi = 20 给到 rdx
    0x10000174a <+10>: popq   %rbp
    0x10000174b <+11>: retq  


finish 跳出

0x1000013ca <+42>:  leaq   0xe47(%rip), %rsi         0x100002a8f + 0xe47 = 0x100002218 	space1的内存地址
0x1000013d3 <+51>:  movq   %rax, 0xe3e(%rip)         rax = 10 赋值给右边内存地址 0x1000013da + 0xe3e = 0x100002218  space1的内存地址
0x1000013da <+58>:  movq   %rdx, 0xe3f(%rip)         rdx = 20 赋值给右边内存地址 0x1000013e1 + 0xe3f = 0x100002220  
0x1000013e1 <+65>:  movq   %rsi, %rdi

var space1 = Space(x: 10, y: 20)
到这里,space1对象初始化后,内存赋值完毕

0x1000013f2 <+82>:  movq   0xe1f(%rip), %rax         0x1000013f9 + 0xe1f = 0x100002218  把space1里面的前8个字节 (q类型) 10给到rax 
0x1000013f9 <+89>:  movq   %rax, 0xe28(%rip)         0x100001400 + 0xe28 = 0x100002228	然后把rax的值给到 0x100002228	    space2的内存地址
0x100001400 <+96>:  movq   0xe19(%rip), %rax         0x100001407 + 0xe19 = 0x100002220  把space1里面的后8个字节 (q类型) 20给到rax 
0x100001407 <+103>: movq   %rax, 0xe22(%rip)         0x10000140e + 0xe22 = 0x100002230  然后把rax的值给到 0x100002230
0x10000140e <+110>: leaq   -0x18(%rbp), %rdi

到这里 var space2 = space1 赋值操作的汇编完成

0x100001430 <+144>: callq  0x100001d1c               ; symbol stub for: swift_beginAccess
0x100001435 <+149>: movq   $0x64, 0xde8(%rip)        // 100 赋值给 0x100001440 + 0xde8 = 0x100002228  space2的内存地址的 (q类型) 前八个字节
0x100001440 <+160>: leaq   -0x30(%rbp), %rdi
0x100001444 <+164>: callq  0x100001d28               ; symbol stub for: swift_endAccess
0x100001449 <+169>: leaq   0xdd8(%rip), %rax         
0x100001450 <+176>: xorl   %r8d, %r8d
0x100001450 <+176>: xorl   %r8d, %r8d
0x100001453 <+179>: movl   %r8d, %ecx				
0x100001456 <+182>: movq   %rax, %rdi
0x100001459 <+185>: leaq   -0x48(%rbp), %rsi
0x10000145d <+189>: movl   $0x21, %edx
0x100001462 <+194>: callq  0x100001d1c               ; symbol stub for: swift_beginAccess
0x100001467 <+199>: movq   $0xc8, 0xdbe(%rip)        // 200 赋值给 0x100001472 + 0xdbe = 0x100002230  space2的内存地址的 (q类型) 后八个字节
0x100001472 <+210>: leaq   -0x48(%rbp), %rdi

space2.x = 100
space2.y = 200
到这里 space2的赋值操作完成

0x100002228 地址二进制如下:

64 00 00 00 00 00 00 00 
C8 00 00 00 00 00 00 00

可以简单看下这个局部变量对应的汇编,很简单

0x1000017b3 <+19>: movl   $0xa, %edi   		// 10赋值给edi = rdi
0x1000017b8 <+24>: movl   $0x14, %esi		// 20赋值给esi = rsi
0x1000017bd <+29>: callq  0x100001790		// si 进去  rdi 赋值给rax  rsi 赋值给rdx	               
0x1000017c2 <+34>: movq   %rax, -0x10(%rbp)	
0x1000017c6 <+38>: movq   %rdx, -0x8(%rbp)
0x1000017ca <+42>: movq   %rax, -0x20(%rbp)
0x1000017ce <+46>: movq   %rdx, -0x18(%rbp)
0x1000017d2 <+50>: movq   $0x64, -0x20(%rbp)
0x1000017da <+58>: movq   $0xc8, -0x18(%rbp)

寄存器经验:
如果类似0xde8(%rip)一般是全局区
如果类似-0x20(%rbp)一般是局部变量

3.值类型的赋值操作

var s1 = "Jack"
var s2 = s1
s2.append("_Rose")
print(s1) // Jack
print(s2) // Jack_Rose

var a1 = [1, 2, 3]
var a2 = a1
a2.append(4)
a1[0] = 2
print(a1) // [2, 2, 3]
print(a2) // [1, 2, 3, 4]

var d1 = ["max" : 10, "min" : 2]
var d2 = d1
d1["other"] = 7
d2["max"] = 12
print(d1) // ["other": 7, "max": 10, "min": 2]
print(d2) // ["max": 12, "min": 2]
  • 在Swift标准库中,为了提升性能,String、Array、Dictionary、Set采取了Copy On Write的技术,比如仅当有“写”操作时,才会真正执行拷贝操作
  • 对于标准库值类型的赋值操作,Swift 能确保最佳性能,所有没必要为了保证最佳性能来避免赋值

引用类型

这个简单,就是我们普遍认为的内存引用,举个例子看下汇编

func test1(){
    class People {
        var width: Int
        var height: Int
        init(width: Int, height: Int) {
            self.width = width
            self.height = height
        }

    }
    
    var p1 = People(width: 10, height: 20)
    
    // register read rax
    // 98 22 00 00 01 00 00 00  ->meta info
    // 02 00 00 00 00 00 00 00  ->ref
    // 0A 00 00 00 00 00 00 00  ->10
    // 14 00 00 00 00 00 00 00  ->20
    // 0x1000010e0 <+64>:  movq   %rax, -0x10(%rbp)
    
     
    
    // 0x1000010e7 <+71>:  movq   %rax, -0x60(%rbp)
    var p2 = p1
    
    //  0x100001127 <+135>: movq   -0x60(%rbp), %rax
    //  0x10000112b <+139>: movq   $0xb, 0x10(%rax)
    // rax是对象的地址,0x10是16个字节 也就是 0A 00 00 00 00 00 00 00 赋值11
    p2.width = 11
    // 0x100001162 <+194>: movq   -0x60(%rbp), %rax
    // 0x100001166 <+198>: movq   $0xc, 0x18(%rax)
    // rax是对象的地址,0x18是24个字节 也就是 14 00 00 00 00 00 00 00  赋值12
    p2.height = 12
}

test1()

根据上述的汇编分析,也可以看到

1.内存地址格式为 0x4bdc(%rip),一般是全局变量,全局区(数据段)
2.内存地址格式为 -0x78(%rbp),一般是局部变量,栈空间
3.内存地址格式为 0x10(%rax),一般是堆空间   

4.闭包表达式

var clouse = {
	(v1: Int, v2: Int) -> Int in 
	return v1 + v2
}
clouse()

{
	(参数列表) -> 返回值类型 in
	函数体代码
}

func exec(v1: Int, v2: Int, fn:(Int, Int) -> Int) {
    print(fn(v1, v2))
}


exec(v1: 10, v2: 20) {
    (v1: Int, v2: Int) -> Int in
    return v1 + v2
}

exec(v1: 10, v2: 20) {
    v1, v2 in
    return v1 - v2
}

exec(v1: 10, v2: 20) {
    v1, v2 in v1 * v2
}


exec(v1: 10, v2: 20) { $0 + $1}

exec(v1: 100, v2: 200, fn:+)

5.闭包

一个函数和它所捕获的变量/常量环境组合在一起

  • 一般指定义在函数内部的函数
  • 一般它捕获的是外层函数的局部变量、常量
  • 返回出去的函数 + 捕获堆空间的东西
5.1 概述

实例一

//typealias FNC = (Int) -> Int
//
//func getFn() -> FNC {
//    var num = 0
//    return {
//        (i: Int) -> Int in
//        num += i
//        return num
//    }
//}
//
//var x = getFn()
//print(x(1))
//print(x(2))
//print(x(3))
typealias KJFunc = (Int) -> Int

func getFn() -> KJFunc {
    var num = 1
    func plus(_ i: Int) -> Int{
        num += i
        return num
    }
    return plus
}

var fn = getFn()

print(fn(1))
print(fn(2))
print(fn(3))
print(fn(4))
print(fn(5))

这个闭包应该很简单,是个人都知道结果,但是有没有想过,var num这个是局部变量,在getFn函数之后,栈空间就被回收了,那么除了全局静态区变量,就只可能被分配到堆空间了。
我们在两个return的时候打断点,看下汇编

Swift01`getFn():
    0x1000018f0 <+0>:  pushq  %rbp
    0x1000018f1 <+1>:  movq   %rsp, %rbp
    0x1000018f4 <+4>:  subq   $0x20, %rsp
    0x1000018f8 <+8>:  leaq   0x7f9(%rip), %rdi
    0x1000018ff <+15>: movl   $0x18, %esi
    0x100001904 <+20>: movl   $0x7, %edx
    0x100001909 <+25>: callq  0x100001e1c               ; symbol stub for: swift_allocObject
->  0x10000190e <+30>: movq   %rax, %rdx
    0x100001911 <+33>: addq   $0x10, %rdx
    0x100001915 <+37>: movq   %rdx, %rsi
    0x100001918 <+40>: movq   $0x1, 0x10(%rax)
    0x100001920 <+48>: movq   %rax, %rdi
    0x100001923 <+51>: movq   %rax, -0x8(%rbp)
    0x100001927 <+55>: movq   %rdx, -0x10(%rbp)

可以看到,闭包是分配了堆空间的swift_allocObject,之前 有提到过,callq函数返回值是存储在rax寄存器中,看下内存

(lldb) register read rax
     rax = 0x000000010067fc80
     
(lldb) x/5xg 0x000000010067fc80
0x10067fc80: 0x00000001000020f8 0x0000000000000002
0x10067fc90: 0x000000010067000f 0x0000000000000000
0x10067fca0: 0x0000000000000000

读出rax内存地址,然后打印该地址下5组数据,可以猜测前32个字节,有点像对象的内存分布,但是可以看到0x10067fc90: 0x000000010067000f是脏数据,因为还没有被初始化,断点继续往下0x100001918 <+40>: movq $0x1, 0x10(%rax),执行完这句,内存就被初始化为1了

(lldb) x/5xg 0x000000010067fc80
0x10067fc80: 0x00000001000020f8 0x0000000000000002
0x10067fc90: 0x0000000000000001 0x0000000000000000
0x10067fca0: 0x0000000100075380

继续断到闭包内的return里面的断点
在这里插入图片描述
再次读取对象的地址,可以看到第十六个字节被赋值了2,一次类推,可以看到闭包捕获的变量,变量本身是局部变量,栈空间的,被捕获到了堆空间,而且内存分布看上去和对象基本一致,前八个字节元数据,8-16是引用计数,在后面就存储捕获的变量,那么又个问题,怎么知道捕获的变量实际分配了多少内存呢。
先简单看下普通类实例化的时候,init函数之后,实际分配了多少内存

示例二

class Test {
    var test1: Int = 0
    var test2: Int = 0
}
var test = Test()
0x1000013d4 <+36>: callq  0x100001810               ; Swift01.Test.__allocating_init() -> Swift01.Test at main.swift:38

si进去

在这里插入图片描述
可以看到alloc的时候,movl $0x20, %esi分配了32个字节的内存

那么我们再回过头来看闭包的汇编
在这里插入图片描述
可以看到,发送了24个字节的指令去申请内存,实际上根据架构,实际上获取到了32个字节,因此上面x/5xg打印出来的内存地址,其实只要x/4xg就是完整的闭包捕获的对象了。

小结:

  • 1.闭包就是定义在函数内部的函数,捕获外层函数的局部变量/常量
  • 2.内存捕获到堆空间,捕获的局部变量、常量就是对象的成员,组成的闭包函数,就是类内部定义的方法
  • 3.捕获的变量原本在栈空间,出了作用域被回收,copy了值到堆空间变成对象供函数使用

阶段一概述:
从汇编来看
1.变量捕获会在堆空间开辟内存
2.例子中开辟的内存是32个字节,前16个字节类似类的数据结构,后16个字节开始存放捕获的变量值

5.2 详述

1.先来看一下简单的函数内存分布

func sum(v1: Int, v2: Int) -> Int{
    return v1 + v2
}
var fn = sum
print("1")

(lldb) p fn
() -> () $R0 = 0x0000000100000aa0 Swift01`Swift01.sum(v1: Swift.Int, v2: Swift.Int) -> Swift.Int at main.swift:11

展开的汇编如下

    0x1000009e3 <+19>:  leaq   0xb6(%rip), %rcx          ; Swift01.sum(v1: Swift.Int, v2: Swift.Int) -> Swift.Int at main.swift:11
    0x1000009ea <+26>:  movq   %rcx, 0x7a7(%rip)         ; Swift01.fn : (Swift.Int, Swift.Int) -> Swift.Int
    0x1000009f1 <+33>:  movq   $0x0, 0x7a4(%rip)         ; Swift01.fn : (Swift.Int, Swift.Int) -> Swift.Int + 4
    0x1000009fc <+44>:  movl   $0x1, %ecx

根据经验来看,0x10(%rip)这种就是全局变量,也就是这边的var fn,那么根据右侧的提示,可以看到leaq对应的地址赋值是 rip + 0xb6 = 0x100000AA0,和上面的po打印出来的吻合,而且下面两个movq分别是对 0x1000011A0``0x100001198进行8个字节的赋值,因此,函数分配给fn变量的内存是16个字节,前8个字节是函数地址,后八个字节是0,暂时没用到。

2.看一下没捕获变量的闭包(这种也不叫闭包)

typealias KJFunc = (Int) -> Int

func getFn() -> KJFunc {
//    var num = 0
    func plus(_ i: Int) -> Int{
//        num += i
//        return num
        return i
    }
    return plus
}

var fn = getFn()
print("1")

断点在getFn()

0x1000013c0 <+0>:    pushq  %rbp
    0x1000013c1 <+1>:    movq   %rsp, %rbp
    0x1000013c4 <+4>:    pushq  %r13
    0x1000013c6 <+6>:    subq   $0x218, %rsp              ; imm = 0x218 
    0x1000013cd <+13>:   movl   %edi, -0x84(%rbp)
    0x1000013d3 <+19>:   movq   %rsi, -0x90(%rbp)
    0x1000013da <+26>:   callq  0x100001aa0               ; Swift01.getFn() -> (Swift.Int) -> Swift.Int at main.swift:47
si 进去
Swift01`getFn():
->  0x100001aa0 <+0>:  pushq  %rbp
    0x100001aa1 <+1>:  movq   %rsp, %rbp
    0x100001aa4 <+4>:  leaq   0x15(%rip), %rax          ; plus #1 (Swift.Int) -> Swift.Int in Swift01.getFn() -> (Swift.Int) -> Swift.Int at main.swift:49
    0x100001aab <+11>: xorl   %ecx, %ecx
    0x100001aad <+13>: movl   %ecx, %edx
    0x100001aaf <+15>: popq   %rbp
    0x100001ab0 <+16>: retq   

可以看到函数plus赋值给了rax,xorl %ecx, %ecx清0操作,然后把0赋值给edx也就是rdx,然后看外部

0x1000013df <+31>:   movq   0xc32(%rip), %rsi         ; (void *)0x00007fff9abdeb18: type metadata for Any
    0x1000013e6 <+38>:   addq   $0x8, %rsi
    0x1000013ea <+42>:   movq   %rax, 0xdc7(%rip)         ; Swift01.fn : (Swift.Int) -> Swift.Int
    0x1000013f1 <+49>:   movq   %rdx, 0xdc8(%rip)         ; Swift01.fn : (Swift.Int) -> Swift.Int + 8
    0x1000013f8 <+56>:   movl   $0x1, %edi

这里可以看到把rax、rdx里面的值赋值给了对应的地址,

(lldb) p fn
() -> () $R4 = 0x0000000100001ac0 Swift01`plus #1 (Swift.Int) -> Swift.Int in Swift01.getFn() -> (Swift.Int) -> Swift.Int at main.swift:49
(lldb) register read rax
     rax = 0x0000000100001ac0  Swift01`plus #1 (Swift.Int) -> Swift.Int in Swift01.getFn() -> (Swift.Int) -> Swift.Int at main.swift:49

在没有捕获变量的情况下,和普通函数没什么区别,给外部变量前八个字节就是函数地址,后八个字节是0

3.看下完整的闭包是怎么样的

typealias KJFunc = (Int) -> Int

func getFn() -> KJFunc {
    var num = 0
    func plus(_ i: Int) -> Int{
        num += i
        return num
    }
    return plus
}

var fn = getFn()


print(fn(1))
print(fn(2))
print(fn(3))
print(fn(4))
print(fn(5))

0x01

先在return plus那打上断点,然后进入汇编
在这里插入图片描述
看到闭包的返回值,rax中存储着函数地址,rdx中存储着堆对象的地址

0x02

return plus后,回到外部调用,这边的函数地址存储在rax中,因此调用就会变成callq *%rax
在这里插入图片描述
callq上面,fn(1)的1存入rdi-0xb0(%rbp)追溯到上面的rcx(变量的后8个字节,堆空间地址)地址存入r13-0xa8(%rbp)追溯到上面的rax(变量的前8个字节),函数地址,所以调用callq的时候,参数1,参数self,也就是堆空间变量的地址,函数指针都有了,

  • rdi = 1
  • rax = 函数地址
  • r13 = 堆空间地址

0x03

si进去验证下

Swift01`partial apply for plus #1 (_:) in getFn():
->  0x100001a90 <+0>: pushq  %rbp
    0x100001a91 <+1>: movq   %rsp, %rbp
    0x100001a94 <+4>: movq   %r13, %rsi
    0x100001a97 <+7>: popq   %rbp
    0x100001a98 <+8>: jmp    0x100001930               ; plus #1 (Swift.Int) -> Swift.Int in Swift01.getFn() -> (Swift.Int) -> Swift.Int at main.swift:51

进入闭包函数地址,可以看到r13赋值给了rsi,然后jmp到 plus函数

  • rdi = 1
  • rax = 函数地址
  • rsi = 堆空间地址

0x04

plus函数
在这里插入图片描述
首先红色部分,把对应的rdx对象0x10后字节的数字 加上 rcx中存储的值,然后存储在rcx中。蓝色部分就是把存储在寄存器中计算好的值,写入rax对应的地址,可以看到rax对应的地址就是上面分析出来,堆对象地址后移16个字节所处的地址,写入寄存器中的值,完成一次fn(1)的调用

6.自动闭包

func getNumber() -> Int {
    let a = 100
    let b = 200
    return a + b
}
func getFirstPositive(_ v1: Int, _ v2: Int) -> Int {
    return v1 > 0 ? v1 : v2
}
print(getFirstPositive(10, 20)) // 10
print(getFirstPositive(-2, 20)) // 20
print(getFirstPositive(0, -4))  // -4

print(getFirstPositive(0, getNumber()))  // 300
print(getFirstPositive(10, getNumber())) // 10

正常写法,参数如果是函数的话,虽然有时候不需要,也会执行

func getNumber() -> Int {
    let a = 100
    let b = 200
    return a + b
}


func getFirstPositive(_ v1: Int, _ v2: () -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}

print(getFirstPositive(10, {
    let a = 1000
    let b = 2000
    print("111")
    return a + b
}))  // 10

print(getFirstPositive(0, {
    let a = 1000
    let b = 2000
    print("222")
    return a + b
})) // 222    3000


print(getFirstPositive(0, getNumber)) // 300
print(getFirstPositive(0, {100})) // 100
print(getFirstPositive(0){200}) // 200

这种写法就可以让函数晚点执行,但是如果是() -> Int类型,还有个@autoclosure让调用更加优雅一点


func getNumber() -> Int {
    let a = 100
    let b = 200
    print("333")
    return a + b
}
func getFirstPositive(_ v1: Int, _ v2: @autoclosure () -> Int) -> Int {
    return v1 > 0 ? v1 : v2()
}
print(getFirstPositive(100, getNumber()))
print(getFirstPositive(0, getNumber()))
print(getFirstPositive(0, 99))
  • @autoclosure会自动将 99 封装成闭包 { 99 }
  • @autoclosure只支持 () -> T格式的参数
  • @autoclosure 并非只支持最后一个参数
  • 空合运算符 ??使用了 @autoclosure 技术
  • @autoclosure@autoclosure 构成函数重载



第二天结束0.0
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 1 University students can understand innovation through learning from the past. 2. Students can better review by breaking down complex concepts into smaller components and studying the material in an organized way. 3. When learning from the past to understand innovation, it is important to focus on understanding the big picture and to not get bogged down in the details. ### 回答2: 1. 大学生如何理解故而故而是一种学习方法,它要求我们在学习识之前先回顾和巩固已经学过的识。大学生理解故而意味着要在学习识之前,先回顾和复习以前学过的相关识或基础识。通过故,我们能够加深对已有识的理解和记忆,从而更好地理解和掌握识。 2. 学生如何更好地去复习? 学生要更好地复习,可以采取以下策略: 首先,制定一个合理的复习计划,将要复习的内容分配到不同的时间段,确保每个科目都有足够的时间。 其次,采用多种复习方法,如阅读教材、做练习题、参加讨论等,以帮助加深理解和牢固记忆。 另外,与同学或老师一起讨论复习内容,通过讲解和互动来加深理解。 此外,保持良好的学习习惯,比如及时复习、做好笔记等,能够帮助学生更好地掌握和复习识。 3. 故而的过程需要注意什么? 在故而的过程中,需要注意以下几点: 首先,要有针对性,根据自己的学习需求和复习目标,选择性地回顾和复习相关识点。 其次,要有系统性,将复习内容进行分类整理,形成一个清晰的识框架,有助于加深理解和记忆。 另外,要关注重难点,重点复习那些相对较难或容易遗忘的识点,加强对这些内容的学习和理解。 还要有耐心和恒心,故而是一个持续的过程,需要长期坚持和不断巩固。 最后,要善于总结和归纳,通过整理和回顾复习过程中的笔记和练习,提炼出关键概念和思维模式,便于记忆和应用。 ### 回答3: 1. 大学生如何理解故而? 大学生可以理解为通过回顾过去的识和经验,来获取的见解和理解。故是指回顾已经学过的识,了解其中的原理、概念和重要点。而则是指通过对识的学习,扩展和更自己的识体系。故而相辅相成,是一个持续学习和发展的过程。 2. 学生如何更好地去复习? 学生可以通过以下方式更好地进行复习: - 制定合理的复习计划:根据时间安排和课程难度,合理分配复习时间,确保每个学科都有足够的复习时间。 - 多种复习方法结合:采用不同的学习方式,如阅读教材、做练习题、参与讨论、制作思维导图等,帮助巩固记忆和理解识。 - 主动参与课堂:积极参与讨论和提问,与同学和老师交流,加深对识的理解和记忆。 - 不断反思和总结:及时检查自己的复习情况,发现不足和问题,并及时调整学习方法和计划。 3. 故而的过程需要注意什么? 在故而的过程中,学生需要注意以下几点: - 有目的性地故:针对具体的识点或者问题进行回顾,明确自己的学习目标和重点。 - 理解和记忆结合:不仅要理解概念和原理,还要通过多次的复习和记忆,帮助信息在大脑中形成长期记忆。 - 理论联系实际:将学到的识应用到实际情境中,加深对识的理解和记忆。 - 及时巩固复习成果:通过做练习题、整理笔记、与同学讨论等方式,巩固复习的成果,确保识掌握得更牢固。 - 长期持续学习:故而是一个持续的过程,要保持学习的热情和动力,不断更自己的识体系。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值