二. 基础知识(2)
三大语句:
1.顺序语句
默认情况下,Python代码的执行顺序,是从上到下依次执行的
而选择语句和循环语句,则为更多了选择提供了可能
2.条件语句:
(1) if
(2) if - else
(3) if - elif - else
ps : Python中条件语句的写法和多数编程语言都不太一样
①if 后面的条件表达式,没有(),使用:作为结尾
②if / else 命中后面要执行的语句块,使用缩进,而不是{}
③对于多条件分支,不是写作else if,而是elif(合体了)
直接上代码
#让用户输一个数字,如果输入1,表示愿意认真学习,输入2表示躺平摆烂
choice = input("输入 1 表示愿意认真学习,输入 2 表示躺平摆烂:")
if choice == '1':
print('能够找到好工作')
else:
print('你可能毕业就失业了')
但是上述代码仍然存在漏洞.在实际开发中,用户的输入很有可能出乎意料,因此当用户输入其他数时,我们应该给以提示
#让用户输一个数字,如果输入1,表示愿意认真学习,输入2表示躺平摆烂
choice = input("输入 1 表示愿意认真学习,输入 2 表示躺平摆烂:")
if choice == '1':
print('能够找到好工作')
elif choice == '2':
print('你可能毕业就失业了')
else:
print('您的输入有误')
----------------------Python中的缩进---------------------
a = input("请输入一个整数")
if a == '1':
print('aaa')
print('bbb')
输入1,打印 aaa 和 bbb
输入其他,什么都不打印
a = input("请输入一个整数")
if a == '1':
print('aaa')
print('bbb')
输入 1,打印 aaa 和 bbb
输入其他,打印 bbb
Python中,if/else/elif/while/for 等等需要跟上代码块的部分,都是用缩进表示的
在C++/Java中,代码缩进没有强制要求,但是会影响代码风格
而Python则是做了硬性规定,没按要求缩进则会报错
Python中的代码还是可以嵌套的
a = input("请输入第一个整数")
b = input("请输入第二个整数")
if a == '1':
if b == '2':
print('aaa')
print('bbb')
print('ccc')
这段代码大家自行理解即可,体会Python中缩进决定了当前语句属于哪个代码块
ps: 嵌套层数不宜太多,否则语句对应哪一代码块是不容易观察的,容易出错
····························条件语句练习题:
1.判断奇数/偶数
n = int(input())
if n % 2 == 0:
print('偶数')
else:
print('奇数')
n = int(input())
if n % 2 == 1:
print('奇数')
else:
print('偶数')
这两段代码都是OK的
但是要注意的是在C++/Java中,第二段代码是存在问题的,如下图所示
因为在Python中 -5 % 2 =>1
而在C++/Java中,-5 % 2 =>-1
2.判断正负数
n = int(input())
if n > 0:
print('正数')
elif n < 0:
print('负数')
else:
print('0')
注意考虑输入为0的情况
3.判断闰年
闰年的判定:
# 每隔 4 年 闰一次
# 如果年份仍能够被100整除,这是世纪闰年,得看能否被400整除!
写法一:
year = int(input('请输入一个年份'))
if year % 100 == 0:
#世纪闰年的判定
if year % 400 == 0:
print('闰年')
else:
print('平年')
else:
if year % 4 == 0:
print('闰年')
else:
print('平年')
#写法二:
n = int(input('请输入一个年份'))
if (n % 4 == 0 and n % 100 != 0) or n % 400 == 0:
print('是闰年')
else:
print('平年')
虽然代码二比较短,但是中间夹杂了一些更复杂党的条件判定,因此代码一可读性还是较高的,不能只根据代码的长短来评定代码的好坏
下面再看一个例子
#输入1打印 hello ,否则什么都不打印
n = input()
if n == '1':
print('hello')
这段代码非常简单,但是有些同学想着把条件反着写,如果n != 1 就什么也不打印,否则打印 hello
可以看到,这样的写法是有问题的,不符合Python的语法要求,怎么解决这个问题呢?
这个时候便要用到pass语句,pass语句被称为空语句,没有实际的意义,但是可以来占位
n = input()
if n != '1':
pass
else:
print('hello')
2.循环语句:
①while语句
while 条件:
代码块
eg:
1.打印1-10
num = 1
while num <= 10:
print(num)
num += 1
while 循环三要素 : 循环变量的初始值;循环的判定条件;循环变量的语句更新
ps:若忘记 num += 1 这条语句,则为造成死循环.但是要说明的是死循环很多时候是bug,但有些时候不是bug(服务器要一直处于运行状态因为不知道客户什么时候需要连接过来)
2.计算1到100的和
num = 1
sum = 0
while num<=100:
sum += num
num += 1
print(f'sum = {sum}')
3.5的阶乘
num = 1
ret = 1
while num <= 5:
ret *= num
num += 1
print(f'ret = {ret}')
4.求1! +2! +3! +4! +5!
法一:
num = 1
sum = 0
while num<=5:
ret = 1
i = 1
while i <= num:
ret *= i
i += 1
sum += ret
num += 1
print(f'sum = {sum}')
法二:
num = 1
ret = 1
sum = 0
while num <= 5:
ret *= num
sum += ret
num += 1
print(f'sum = {sum}')
②for 循环
基本格式 :
for 循环变量 in 可迭代对象
循环体
ps:
1.Python中的for循环和其他语言不同,没没有初识化语句,没有循环条件判定语句,也没有循环变量更新语句,而是更加简单
2.所谓的可迭代对象,指的是"内部包含多个元素,能一个一个把元素取出来的特殊变量"
例题:
1.打印1到10
for i in range(1,10):
print(i)
range 是 Python 的一个内建函数,起到的效果就是得到了一个"可迭代对象",这个可迭代对象就包含了一系列的整数. (range可以生成一个包含整数序列的可迭代对象~) range(begin,end) => [begin,end)前闭后开
那如果是打印 2,4,6,8 呢?
range 还提供了第三个参数~表示步长,默认的步长是1
for i in range(2,10,2):
print(i)
那如果想倒着打印(1-10)呢? ------步长设为-1
for i in range(10, 0, -1):
print(i)
2.求1 + 2 + ····+100
theSum = 0
for i in range(1, 101):
theSum += i
print(f'theSum = {theSum}')
开始定义存和变量 sum,Python解释器会有提示,表示和内建函数名冲突了,后续就无法再使用内建函数sum了,因为我们要重命名
ps : 快捷键:shift + F6 ( + fn),会智能分析代码,自动把所有需要修改的名字都统一替换了
------------------continue 与 break
continue: 立即结束当前这次循环,进入下一次循环
break: 立即结束整个循环
案例:
# 计算用户输入任意个数字的平均数,以用户输入分号作为结束标志
theSum = 0
count = 0
while True:
num = input("请输入一个数字(分号表示输入结束):")
if num == ';':
break
num = float(num)
theSum += num
count += 1
print(f'平均值为:{theSum / count}')
函数
一段可以被重复使用的代码
ctrl + c ,ctrl + v 不就可以直接复制嘛?
1.复制代码,必须经过非常仔细的进行细节调整,尤其是数据不同的情况下
2.一旦复制过代码,需要调整,复制了几份就得调整几次~(因为我们也不知道这个代码被复制了几份)
因为,尽量还是不要复制代码,尽量做好代码"复用"
eg1:求 1- 100的和(上文已经介绍过)
eg2:求 300 - 400 的和
eg3:求1 - 1000 的和
可以发现,除了range里面的begin与end不同之外,其余都是一样的
因此可以定义一个函数,来实现代码的复用
#定义一个求和函数
def calSum(beg, end):
theSum = 0
for i in range(beg, end + 1):
theSum += i
print(theSum)
#调用函数
#求 1-100的和
calSum(1,100)
#求 300-400的和
calSum(300, 400)
#求 1-1000的和
calSum(1, 1000)
函数具体语法:
1. 函数定义:创建函数(分配任务)
def 函数名(形参列表):
函数体
return 返回值
ps:
ps1:函数名与变量命名规则类似,软性规则与硬性规则~
ps2:形参列表:有多个形参,多个形参之间采用逗号分隔
ps3:函数返回值:函数执行到此就结束了,不过return 语句并不是必须有的
2. 函数调用:使用函数(开始完成任务)
实际参数简称形参,形参个数要和实参个数匹配,但是Python中,形参类型和实参可以不匹配(上文提到过的Python动态类型特点)(C++/Java必须要匹配),但是也不是说,随便传什么参数都行,只要保证传入的参数在函数体内能够支持对应的运算操作即可(比如下面第二段代码中字符串和数字本身就不能相加,这样传肯定报错)
def test(a):
print(a)
test(10)
test('hello')
test(True)
def add(x, y):
return x + y
print(add(10, 20))
print(add(1.5, 2.5))
print(add('hello', 'world'))
上面两段代码充分说明了Python中函数的形参和实参类型可以不匹配这一特点
函数的输入和输出
eg:求beg, end 这个范围内的整数之和(上文已经写过代码)
#求 beg, end这个范围的整数之和
def calSum(beg, end):
theSum = 0
for i in range(beg, end + 1):
theSum += i
print(theSum)
calSum(1, 100)
这段代码我们把打印功能放在了求和函数里面,而我们还有第二种写法
#求 beg, end这个范围的整数之和
def calSum(beg, end):
theSum = 0
for i in range(beg, end + 1):
theSum += i
return theSum
result = calSum(1, 100)
print(result)
···········拓展:
这段代码中,在calSum内部,只是进行了计算,而把打印的逻辑放在了函数外面,calSum把计算结果当做返回值,返回给"函数调用者"
在实际开发中,一般都倾向第二种写法,因为有一个通用的编程原则:一个函数只做一件事情
第一个版本既做了计算,又做了打印(和用户进行交互)
第二个版本,只做了计算,不关心如何和用户进行交互
一旦后续需要改变和用户交互的方式,第二种写法就更具有优势(不必修改代码了),当前是通过控制台和用户进行交互的~
让逻辑和交互/界面分离
进一步的好处:解耦合
一个稍微复杂一些的程序中,经常会涉及到多个模块,模块之间可能要进行交互,交互就会带来耦合,当然希望通过良好的设计让耦合尽量低
ps:
1.如果只是定义函数,而不去调用,则函数体里面的代码不会被执行,函数调用才会真正执行函数体里面的代码
2.函数定义一次后,可以多次调用
3. Python中要求,函数定义必须写在函数调用前面,否则就会报错
3.函数返回值
一个函数可以有多个 return 语句
def test():
return 1
return 2
这种情况显然不算有多个return语句的情况,因为执行到一个return 语句函数就结束了
多个return语句常见于函数里面有分支语句的情况
eg:判断奇数
def isOdd(num):
if num % 2 == 0:
return False
else:
return True
print(isOdd(10))
print(isOdd(9))
def isOdd(num):
if num % 2 == 0:
return False
return True
print(isOdd(10))
print(isOdd(9))
仔细体会,发现上面两段代码是等价的
第二段代码本来 意思是无论if 条件是否满足,都会执行return True,但是由于if条件满足后,就会执行到return语句,没有机会执行return True了。
Python中一个函数可以返回多个值(C++/Java无法直接做到,但也有其他手段)
(C++ :要想返回多个值,可以通过输出型参数(指针/引用))
(Java要想返回多个值,需要把多个值给包装成一个对象,返回这个对象)
eg:
#写一个函数,返回平面上的一个点:
def getPoint():
x = 10
y = 20
return x,y
a, b = getPoint()
虽然现在返回了多个值,但是我只想用其中的一部分,不关注其他的,可以使用 _ 来占位
例如上述代码中,我只想要y,不想要x,此时最后一句代码便可以写成 _, b = getPoint()
4.变量的作用域:
def getPoint():
x = 10
y = 20
return x, y
x, y = getPoint()
print(x, y)
函数里面的x 和 y 和函数外面的x y 是同一组变量吗?
是不同的变量!只不过名字恰好相同!
一个变量名的有效范围是一定的!只在一个固定的区域内生效,如下图所示
函数内部的变量名只能在函数内部使用,出了函数就无效了
在这个函数中,出现了两个名字一样的变量,函数内部的是局部变量,外面的是全局变量。
但是函数内部也是可以使用全局变量的~
x = 10
def test():
print(f'x = {x}')
test()
在函数中尝试访问某个变量的时候,会先尝试在局部变量里面查找,如果找到就访问局部变量,如果没找到,就会往上一级查找(该段代码中上一级就是全局了)
读取操作容易,但是修改就没那么容易了
x = 10
def test():
x = 20
test()
print(x)
发现x仍然是10,因为在函数内部 x = 10 相当于创建出了一个新的变量,并没有修改全局x
此处可以使用 global 关键字,相当于声明了x是全局变量
x = 10
def test():
global x
x = 20
test()
print(x)
这样便达到了在函数内部修改全局变量的目的
ps: if/else/while/for 这些关键字也会引入“代码块”,但是这些代码块不会对变量的作用域产生影响!!在上述语句代码块内部定义的变量也可以在外面被访问.
这点Python和Java/C++是不一样的,在Python中,只有函数和类才会影响作用域,而在C++/Java中,只要有{},就会影响作用域
for i in range(1, 11):
pass
print(i)
if True:
x = 10
print(x)
这些代码都是OK的,等等······
函数执行的过程就很简单了,就是当执行到函数调用的时候,就会调转到函数体内部来执行,当函数执行完毕就是回到之前调用的位置,继续往下执行~
如何观察函数执行的逻辑呢?---调试!!!(程序员的必备技能)
在某一条语句打上断点,然后鼠标右击'debug'选项,就可以进行调试执行,然后再下面选项中点击step into(F7)就可以让代码"单步运行"
相比于正常的运行,调试执行的最大的区别就是可以随时停下来,方便程序员观察程序的中间过程
5.函数链式调用:一个函数的返回值作为另外一个函数的参数
eg1:
在判断一个数是否为奇数的函数中
def is_odd(num):
if num % 2 == 0:
return False
return True
result = is_odd(9)
print(result)
上面的最后两行完全可以写成 print(is_odd(9)),这便是函数链式调用
当然链式调用也可以有多个层级
eg2:
def is_odd(num):
if num % 2 == 0:
return False
return True
def add(x, y):
return x + y
print(is_odd(add(3, 5)))
链式调用中,是先执行()的函数,后执行外面的函数,换句话说,调用一个函数,需要先对它的参数求值
ps:链式调用的层次太深!否则会影响可读性
6. 函数嵌套调用:
eg1:
# 最简单的嵌套调用
def test():
print('hello')
test()
eg2:
# 多层嵌套调用
def a():
print('函数a')
def b():
print('函数b')
a()
def c():
print('函数c')
b()
def d():
print('函数d')
c()
d()
结果:
ps:稍加修改,代码的调用逻辑就截然不同(借助画图/调试理解)
变式:(深刻理解函数执行的逻辑)---从哪里来到哪里去
# 多层嵌套调用
def a():
print('函数a')
def b():
a()
print('函数b')
def c():
b()
print('函数c')
def d():
c()
print('函数d')
d()
结果:
7. 函数栈帧:
def a():
num2 = 20
print('函数a')
def b():
a()
num3 = 30
print('函数b')
def c():
num3 = 30
b()
print('函数c')
def d():
num4 = 40
c()
print('函数d')
d()
调试起来之后,在调试器的坐下方就会看到函数之间的"调用栈",调用栈里面描述了当前这个代码的函数之间的调用关系是啥,每一层这个调用关系就被成为"函数的栈帧",每个函数的局部变量就在这个栈帧中体现的~
而每一层栈帧,选中之后都能看见其中的局部变量,每个函数的局部变量就保存在对应的函数栈帧中
调用函数,则生成对应的函数栈帧,函数结束,则对应的函数栈帧消亡(局部变量也随之销毁)
如果把上面的变量名全部都改为 num,他们仍然是属于不同的变量,变量就是一块内存空间,每个变量的都是保存在各自的栈帧之上的,而每个栈帧也是位于不同的内存空间上
8. 函数递归:
函数自己调用自己
eg:
# 写一个函数采用递归方式求n的阶乘(n是正整数)
def factor(n):
if n == 1:
return 1
else:
return n * factor(n - 1)
print(factor(4))
递归的代码往往看着写法很简单,但是实际的执行过程可能会很复杂的(画图可以很好理解递归函数执行的逻辑/调试)
递归代码两要素:
--------递归结束条件
--------递归的递推公式
递归缺点:
1.执行过程非常复杂,难以理解
2.递归代码容易出现"栈溢出"的情况(每调用一次函数,就会在栈区上为函数开辟空间(函数栈帧),而栈区空间是有限的,总有被用完的时候)
①没有递归跳出条件/递归的过程中没有接近递归跳出条件
tip1:无递归条件
死递归
tip2:没有接近跳出条件
②递归层次太深
求第10个斐波拉契数时没有任何问题
当求第100个斐波拉契数的时候光标一直在闪烁,计算机可不会偷懒,它一直在卖力的运行,不过求第100个斐波拉契实在太费劲了,调用的函数次数已经太多了
当求第1000个斐波拉契数的时候程序直接崩溃了,因为栈溢出了
其实求斐波拉契数这一问题涉及到了时间复杂度的问题,学过时间复杂度还可以对其理解更加深刻,后面我会介绍数据结构中的时间复杂度,大家可以回头再看这一代码定会有更深刻理解
3.递归代码一般是可以转化为等价的循环代码的,并且循环版本通常运行速度要比递归版本更有优势
(函数调用也是有开销的)
递归的优点:
代码非常简洁,尤其是处理一些"问题本身就是按递归定义的"(如二叉树,后面文章进行讲解)
函数里面的参数默认值
前面讲函数形参是根据传入的实参确定的
但是我们也可以直接给函数形参一个默认值,传参的时候就不必传对应实参了
经典案例:
def add(x, y):
return x + y
result = add(10, 20)
print(result)
希望在函数运行时方便参数传了什么,于是我们加上了打印信息
def add(x, y):
print(f'x = {x}, y = {y}')
return x + y
result = add(10, 20)
print(result)
在函数内部加上打印信息,可以方便我们进行调试
但是像这种调试信息,希望在正式发布的时候不要有,只是在调试阶段才有
def add(x, y, debug):
if debug:
print(f'x = {x}, y = {y}')
return x + y
result = add(10, 20, False)
print(result)
于是我们给函数多加了一个参数,通过debug参数决定是否要打印
但是每次都要传第三个参数(False/True,看着比较别扭)
def add(x, y, debug=False):
if debug:
print(f'x = {x}, y = {y}')
return x + y
result = add(10, 20)
print(result)
这个时候就可以用到默认值参数
带有默认值的形参,就可以在调用函数的时候不必传参,不传参就使用默认值,传参就使用传递的实参
通过这样的默认值,就可以让函数的设计更灵活----有的函数需要给调用者提供多种功能,要提供很多参数,但是参数越多使用成本就越高,调用函数的人就得研究每个参数是什么意思,传不同参数会有什么效果~~~
所有我们把一些参数设计为默认值,如果调用者只是简单使用一下,那么很多参数就不用关注,如果想深度影响函数行为,就可以添加一些更多的选项,手动指定参数来影响函数的内部行为
但是默认值这样的语法,在编程界存在争议
C++也支持默认参数,但Java就不支持
ps:要求带有默认值的形参,得在形参列表的后面,而不能在后面/中间,多个带有默认参数的形参,这些都得放在后面
关键字参数
前面讲的函数传参主要是按照位置先后顺序对应传参的(位置传参),这也是各个编程语言中最普遍的方式~~~
关键字传参---按照形参的名字进行传参
eg:
def test(x, y):
print(f'x = {x}')
print(f'y = {y}')
test(10, 20)
这是之前的写法
def test(x, y):
print(f'x = {x}')
print(f'y = {y}')
test(x=10, y=20)
现在我们在传参的时候非常明显的告诉了程序员,你的参数要传递给谁,另外可以无视形参和实参的顺序
def test(x, y):
print(f'x = {x}')
print(f'y = {y}')
test(y=10, x=20)
这样依旧可以照常打印x与y
位置参数和关键字参数还能混着用,只不过混着用的时候要求位置参数在前,关键字参数在后~~~
关键字参数也就是搭配默认参数来使用的
一个函数可以提供很多的参数,来实现对这个函数的内部功能做出一些调整设定,为了降低调用者的使用成本,就可以把大部分参数设定出默认值~~~
当调用者需要调整其中一部分参数的时候,就可以搭配关键字参数来进行操作~~~