问题引入 : 为什么 局部变量无法在 函数体外部 访问??
1 age = 10
2 name = 'james'
3
4 def info():
5 global age
6 gender = '男'
7 b = 20 # 只是为了方便下面 讲解,定义一个局部变量 内存(对于本代码没啥用)
8 age += 1
9 print("球星的名字是 : ", name)
10 print("球星的年龄是 : ", age)
11 print("球星的性别是 : ", gender)
12
13 print(age)
14 print(name)
15
16
17 info()
18 print(age)
展示结果 :
球星的名字是 : james
球星的年龄是 : 11
球星的性别是 : 男
11
james
11
需要用到上面的代码 协助 我们讲解 ( 不需要 代码 本身 )
现在换个角度,从内存的角度来分析整个程序
( 因为所有代码本质都是在内存中跑起来的! 所以我们从内存角度来分析下上示问题!)
( 当然了,在这里讲的内存 是指 运行内存 !!)
内存是分为好几个区的,在这里,我们不多说,我们摘几个和本题相关的区来讲 :
堆 : stack
# 它是内存中最大的区域; 它的结构是树形结构!
# 函数的本质其实就是 堆内存,它就存储在 堆内存中!!
栈 : heap
# 每个程序开始运行的时候,就会分配一个 运行栈 让程序去执行
它的内存空间不会太大( 运行代码! )
# 整个运行栈就可以简单的理解为 整个程序的运行轨迹 !
# 先进后出
函数调用 :
从内存角度来说,函数调用 其实本质就是 压栈操作( push )
即 : 把数据推入到 栈 中去 !!
首先,强调上图几点 :
# 在上图 是把 基本数据类型放到了 栈里,这是为了更方便理解,其实,基本数据
类型是在小整形缓冲区里放着,不想引入,但不妨碍我们理解内存分析!!
# 栈 真正的结构中 恰好是 栈底在上面,栈顶在下面,但为了方便理解(先进后
出)我们就反着画了下( 把 age 画在了 底部----我们意识里的底部,其实不是
栈真正意义上的底部)
你看,按照现在画的,就很好理解 先进后出(你想象成一个瓶子)了, age 是
先放进去的,你要想把它取出来就得先把 name 取出来,这就是先进后出(尽
管 真正的栈的结构不是这样!)
解释下,上图 ( 解释下 最上面 代码 的执行逻辑 )
# 当 程序(最上面的代码) 开始运行时,就会 往栈里压数据,所以就有 整个运
行栈就是 整个程序的运行轨迹这么一说!
# 首先,程序执行第一行代码,有一个全局变量 age ,所以就会把 age 存到
(压) 栈里去; 接着 运行 第二行代码,即 把 name 也存到(压栈) 栈里面
去; 接着程序继续运行,注意, 4 到 11 行代码运行后,堆里是会有一个函数
的引用,但并不会自动就执行了,因为这一串并没有到栈里去,因为函数是要
调用的,调用才会执行属于函数体的代码,因此,当程序来到 17 行时,开始
进行函数调用!! 所以,也就有了 函数调用的本质其实就是 压栈操作!当
压栈操作完成后,在 栈内存 中 便有了一块 属于 函数的内存( 为了方便区
分,我用 红色框框 圈起来,因此在 栈内存中的 函数内存 便 执行 函数体里的
代码( 这里也自然时 局部变量 被声明,被执行的地方 )
当 函数体里的代码 一行一行的执行完,便会回到 17 行,你函数 从那里调用
的最后就回到那里去。
至此,整个 函数调用彻底结束!!( 这也叫做 出栈 / 弹栈 )
当 函数调用结束 (也就是 函数执行完毕) 其本质 即 : 弹栈/出栈操作( pop )
即 : 函数在调用完成后,会退出内存(也就是 栈里不会有函数)!!
而 函数里的局部变量就是在刚才 释放的内存里存储的,你现在,函数执行完
成释放了,出栈了; 因此,在 栈里必然再找不到 局部变量了 ( 你局部变量被
存储的内存都没了,怎么可能还能看到 局部变量呢??)
因此 ,函数在调用完成后,内存是会被回收的!!
拓展 :
内存溢出 : 内存不够用了
比如,我分配了一个大小,结果数据太大存不下,比如我分配了一个
10 个字节的大小,结果你数据是 11 个字节,这就是数据太大存不下
了,那怎么办? 只能存 10 个 剩余那 1 个 就会被溢出,那么,这
样就有可能导致 数据出问题!!
内存泄漏 : 垃圾内存没有得到回收