Go语言基础指南+流程控制
Go语言常用的百分号参数
-
%v 值的默认格式
-
%T 查看变量类型
-
%t 单词true或者false
-
%b 表示为二进制
-
%c 该值对应的unicode码值
-
%d 表示十进制
-
%o 表示八进制
-
%f 有小数部分但无指数部分
-
%q 双引号输出
-
%e 科学计数法
-
%g 在%e和%f之间做一个最优选择
-
%s 以字符串方式打印
-
%p 十六进制表示前面加0x
原码反码和补码
对于有符号而言:
-
二进制的最高位是符号位:0表示正数,1表示负数
1===>[0000 00001] -1===>[1000 0001]
-
正数的原码、反码、补码都一样
1===>原码[0000 0001]反码[0000 0001]补码[0000 0001]
-
负数的反码=他的原码符号位不变其他位取反(0->1 ,1->0)
-1===>原码[1000 0001]反码[1111 1110]补码[1111 1111]
-
负数的补码=他的反码+1
-
0的反码、补码都是0
-
在计算机运行的时候,都是以补码的方式来运算的
Go语言运算符
算数运算符
运算符 | 描述 |
---|---|
+ | 相加 |
- | 相减 |
* | 相乘 |
/ | 相除 |
% | 求余 |
关系运算符
运算符 | 描述 |
---|---|
== | 检查两个值是否相等,如果相等返回ture 否则返回false |
!= | 检查两个值是否不相等,如果不相等返回ture 否则返回false |
> | 检查左边的值是否大于右边的值,如果是返回ture,否则返回false |
>= | 检查左边的值是否大于等于右边的值,如果是返回ture,否则返回false |
< | 检查左边的值是否小于右边的值,如果是返回ture,否则返回false |
<= | 检查左边的值是否小于等于右边的值,如果是返回ture,否则返回false |
逻辑运算符
运算符 | 描述 |
---|---|
&& | 逻辑AND运算符。如果两边的操作数都是Ture,则为Ture,否则为False |
|| | 逻辑OR运算符。如果两边操作数有一个为Ture,则为Ture,否则为False |
! | 逻辑NOT运算符。如果条件为Ture,则为False,否则为Ture |
位运算符
别是"按位与&"、按位或|、按位异或^,他们的运算规则是:
- 按位与& :两位全为1,结果为1,否则为0
- 按位或| :两位有一个为1,结果为1,否则为0
- 按位异或^ :两位一个为0一个为1,结果为1,否则为0
求2&3,2|3,23,-22
分析推导
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-44jbWr8f-1690279161318)(图片/image-20230406113027134.png)]
案例演示验证
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-soSCLwRd-1690279161320)(图片/image-20230406115636418.png)]
位移运算符
Golang中有两个位移运算符:
>>,<<右移和左移,运算规则:
右移运算符>>:低位溢出,符号位不变,并用符号位补溢出的高位
左移运算符<<:符号位不变,低位补0
先算出来补码然后右移或左移
a := 1>>2
c := 1<<2
案例演示:
注释:因为正数的原码、反码、补码都一样所以可以直接算
a := 1>>2 //0000 0001=>0000 0000=0
c := 1 <<2 //0000 0001=>0000 0100=4
如果是负数先求原码、然后反码、最后补码算结果
原码 反码 补码 结果后移 转反码-1 转原码
d := -1<<2 //1000 0001=>1111 1110=>1111 1111=>1111 1100=>1111 1011=>1000 0100= -4
d := -1>>2 //1000 0001=>1111 1110=>1111 1111=>1111 1111=>1111 1110=>1000 0001= -1
结果验证
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CD4uo4OL-1690279161320)(图片/image-20230406140743481.png)]
GO变量、常量、字面量
变量类型
类型 | go变量类型 | fmt输出 |
---|---|---|
整型 | int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 | %d |
浮点型 | float32 float64 | %f %e %g |
复数 | complex128 complex64 | %v |
布尔型 | bool | %t |
指针 | uintptr | %d |
引用 | map slice channel | %v |
字节 | byte(byte和runc的区别 byte只占用一个字节 而runc等价于init32占4字节) | %d |
任意字符 | runc(打印runc类型%c可以直接输出字符 %s需要进行强制转换 string()) | %c |
字符串 | string | %s |
错误 | error | %v |
变量初始化
如果声明后未显示初始化,数值型初始化0,字符串初始化为空字符串,布尔型初始化为false,引用类型、函数、指针、接口初始化为nil
函数内部的变量(非全局变量)可以通过 := 去声明并初始化
下划线表示匿名变量
匿名变量不占命名空间,不会分配内存,因此可以重复使用
常量
常量在定义时必须赋值,且程序运行期间其值不能变
常量的三种写法
第一种
const PI float32 = 3.14
第二种
const (
PI = 3.14
E = 2.71
)
第三种
const (
a = 100
b //100跟上一行的值相同
c //100跟上一行的值相同
)
iota是go语言里面一个迭代数简单用法
const (
a = iota //a = 0
b //b = 1
c //c = 2
)
也可以简单理解为变量名在第几行 变量值就是几
const (
a = iota //a = 0
b = 3
c //c = 2
d //c = 4
)
字面量
字面量:没有出现变量名,直接出现了值。基础类型字面量相当于常量
变量和作用域
全局变量不能使用 :=
的方式来进行声明必须通过var/const
的方式来声明变量
同包不同go文件小写的变量名也可以调用
注释:再次提醒常量一旦被定义无法再次赋值只能修改原常量
var (
A = 3 //全局变量,大写字母开头,所有地方都可以访问,跨package访问时需要导包,带上package名称
b = 4 //全局变量,小写字母开头,本package都可以访问
)
func foo() {
b := 5 //局部变量,仅本函数内可以访问。内部声明的变量可以跟外部声明的变量有冲突,以内部为准
{
b := 6 //仅{}圈定的作用域可以访问可以跟外部变量有冲突以圈内为准
}
}
指针
基本介绍
- 基本数据类型,变量存得就是值,也叫值类型
- 获取变量的地址,用&,比如:var num int,获取num的地址:&num
- 指针类型,变量存的是一个地址,这个地址指向的空间存的才是值,比如:var prt *int = &num
- 获取指针类型所指向的值,使用:*,比如:var *prt, 使用 *prt获取prt指向的值
举例说明
var num int = 1
var i = 999i
var prt *int=i
程序流程控制
程序流程控制介绍
在程序中,程序运行的流程控制是决定程序是如何执行的,是我们必须掌握的,主要有三大流程控制语句。
- 顺序控制
- 分支控制
- 循环控制
顺序控制
从上到下逐行执行,中间没有任何的判断和跳转
语句1==>语句2==>语句3……语句n
Go定义变量时采用合法的向前引用
正确演示
package main
import "fmt"
func main() {
a := 10
b = a + 20
fmt.Println(a)
}
错误演示
package main
import "fmt"
func main() {
b = a + 20
a := 10
fmt.Println(a)
}
分支控制
让程序有选择的执行,分支控制有三种
- 单分支
- 双分支
- 多分支
1.单分支控制
基本语法
if 条件表达式 {
执行代码块
}
说明:当条件表达式为true时就会执行{}里面的代码。注意{}必须要有,就算你只写一行代码
案例说明
编写程序,可以输入人的年龄,如果大于18岁,则输入“你的年龄大于18,要对自己的行为负责”
需求分析
1.定义一个年龄的变量
2.变量值从控制台接受
3.对变量值进行判单
package main
import "fmt"
func main() {
var age int
fmt.Println("请输入你的年龄:")
fmt.Scanln(&age)
if age > 18 {
fmt.Println("你的年龄大于18,要对自己的行为负责")
}
}
单分支的流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wK5355KJ-1690279161321)(图片/image-20230406163144086.png)]
细节说明
Go的if还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了
案例说明
package main
import "fmt"
func main() {
if age := 20 ; age > 18 {
fmt.Println("你的年龄大于18,要对自己的行为负责")
}
}
2.双分支
基本语法
package main
import "fmt"
func main() {
if 条件判断 {
执行代码块1
} else {
执行代码块2
}
}
说明:当条件表达式成立执行代码块1,否则执行代码块2。{}也是必须要有的。
案例演示
编写程序,如果大于18岁,则输入“你的年龄大于18,要对自己的行为负责”。否则,输出“你年龄不大,这次先放过你”
package main
import "fmt"
func main() {
var age int
fmt.Println("请输入年龄:")
fmt.Scanln(&age)
if age > 18 {
fmt.Println("你的年龄大于18,要对自己的行为负责")
} else {
fmt.Println("你年龄不大,这次先放过你")
}
}
双分支流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vYcEdUc0-1690279161321)(图片/image-20230406165647788.png)]
单分支和双分支练习题
1.下列代码,若有输出,请指出输出结果,若有错误请指出原因
package main
import "fmt"
func main() {
x := 4
y := 1
if x > 2 {
if y > 2 {
fmt.Println(x+y)
}
fmt.Println("songyuxiang")
} else{
fmt.Println("x is=",x)
}
}
2.下列代码,若有输出,请指出输出结果,若有错误请指出原因
package main
import "fmt"
func main() {
x := 4
y := 1
if x > 2
fmt.Println("ok")
else
fmt.Println("hello")
3.下列代码,若有输出,请指出输出结果,若有错误请指出原因
package main
import "fmt"
func main() {
x := 4
if x > 2 {
fmt.Println("ok")
}
else{
fmt.Println("hello")
}
4.下列代码,若有输出,请指出输出结果,若有错误请指出原因
package main
import "fmt"
func main() {
x := 4
if x > 2 {
fmt.Println("ok")
} else {
fmt.Println("hello")
}
3.多分支
基本语法
package main
import "fmt"
func main() {
if 条件判断 {
执行代码块1
} else if {
执行代码块2
}
......
else {
执行代码块n
}
}
多分支的判断流程如下:
- 先判断条件表达式1是否成立,如果为真,就执行代码块1
- 如果条件表达式1为假,就去判断条件表达式2是否成立,如果条件表达式2为真,就执行代码块2
- 依次类推
- 如果所有的条件表达式不成立,则实行else的语句块
- else不是必须的
- 多分支只能有一个执行入口
多分支流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9R8dsAah-1690279161322)(图片/image-20230407141403167.png)]
应用案例
package main
import "fmt"
func main() {
var cj int
fmt.Println("请输入小鹏的成绩:")
fmt.Scanln(&cj)
if cj == 100 {
fmt.Println("奖励一台奔驰迈巴赫")
} else if cj >= 80 && cj <= 99 {
fmt.Println("奖励一部iphone 14promax")
} else if cj >= 60 && cj <= 80 {
fmt.Println("奖励一个ipad")
}else {
fmt.Println("请再接再厉")
}
}
4.嵌套分支
基本介绍
在一个分支结构中又完整嵌套了另一个完成的分支结构,里面的分支结构称为内分支外面的分支结构称为外分支。
基本语法
if 条件表达式 {
if 条件表达式 {
} else {
}
} else {
}
}
说明:嵌套分支不宜过多,最多我们建议控制在3层
应用案例1
参加百米运动会,如果用时8秒以内可以进入决赛,否则提示淘汰,并根据性别提示进入男子组或者女子组,输入成绩和性别,进行判断。
package main
import "fmt"
func main() {
var second float64
fmt.Println("清输入学生成绩:")
fmt.Scanln(&second)
if second < 8.0 {
var gender string
fmt.Println("请输入你的性别")
fmt.Scanln(&gender)
if gender == "男" {
fmt.Println("恭喜你进入男子组决赛")
} else {
fmt.Println("恭喜你进入女子组决赛")
}
} else {
fmt.Println("很遗憾你被淘汰出局 请下次再接再厉")
}
}
5.switch分支结构
基本介绍
- switch语句基于不同条件执行不同动作,每一个case分支都是唯一的,从上到下注意测试,直到匹配成功为止
- 匹配项后也不需要加break
基本语法
package main
import "fmt"
func main(){
switch 表达式 {
case 表达式1,表达式2:
执行语句块1
case 表达式3,表达式4:
执行语句块2
这里可以有多个case语句
default:
语句块
}
}
switch流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W34ROWAD-1690279161323)(图片/image-20230407150639451.png)]
对上图的说明总结:
- switch的执行过程是,先执行表达式,得到值,然后和case的表达式进行比较,如果相等,就匹配到,然后执行对应的case语句块,然后退出switch控制
- 如果switch的表达式的值没有和任何的case表达式匹配成功,则执行default语句块,执行完退出
- case后可以有多个表达式,使用 逗号 隔开
- case语句块不需要写break,因为默认会有,即在默认情况下,当程序执行完case语句块,就直接退出switch结构
应用案例
请编写一个程序,该程序可接受一个字符,比如:a,b,c,d,e,f,g 其中a表示星期一,b表示星期二……以此类推根据用户的输入显示相关信息
要求使用swich语句完成
package main
import "fmt"
func main() {
var key byte
fmt.Println("请输入一个字符: a,b,c,d,e,f,g")
fmt.Scanf("%c",&key)
switch key {
case 'a':
fmt.Println("周一")
case 'b':
fmt.Println("周二")
case 'c':
fmt.Println("周三")
case 'd':
fmt.Println("周四")
case 'e':
fmt.Println("周五")
case 'f':
fmt.Println("周六")
case 'g':
fmt.Println("周日")
default:
fmt.Println("你的输入有误")
}
}
switch分支结构细节讨论
-
case后面是一个表达式(即:常量、变量、一个有返回值的函数等都可以)
-
case后的各个表达式的值的数据类型必须和switch的类型一样
-
case后可以有多个表达式,使用 逗号 隔开。比如case 表达式1,表达式2……
-
case后面如果是常量值,则要求不能重复
-
case后面不需要带break,程序匹配到一个case后就会执行对应的代码块,然后退出switch,如果一个都匹配不到,则执行default
-
default语句不是必须的
-
switch后也可以不带表达式,类似if----else分支来使用
-
switch后也可以直接声明/定义一个变量,分号结束不推荐
-
switch穿透fallthrough,如果case语句块后增加fallthrough,则会继续执行下一个case,也叫switch穿透
-
穿透案例演示 package main import "fmt" func main() { var num int = 10 switch num { case 10 : fmt.Println("ok1") fallthrough case 20 : fmt.Println("ok2") case 30 : fmt.Println("ok3") default: fmt.Println("没有匹配成功") } } 注释:switch会穿透到下一层不会进行判断直接输出
-
Type switch:switch 语句还可以用于type-switch来判断某个interface变量中实际指向的 变量类型【还没有学interface 可以先体验一把】
package main
import "fmt"
func main() {
var x interface{}
var y = 10.0
x = y
switch i := x.(type) {
case nil:
fmt.Printf("x 的类型是: %T ",i)
case int:
fmt.Printf("x 的类型是:int ")
case float32:
fmt.Printf("x 的类型是: float32 ")
case float64:
fmt.Printf("x 的类型是: float64 ")
case bool,string:
fmt.Printf("x 的类型是: bool/string ")
default:
fmt.Printf("x 的类型是: 未知 ")
}
}
switch和if的比较
- 如果判断的具体数值不多,而且符合整数,浮点数,字符,字符串这几个类型。建议使用switch语句,简洁高效
- 其他情况:对区间的判断和结果为bool类型的判断,使用if,if的使用范围更广。
6.for循环语句
基本介绍
听其名知起意,就是让你的代码可以循环的执行。
for基本语法
package main
import "fmt"
func main() {
for 循环变量初始化; 循环条件; 循环变量迭代 {
循环操作(语句)
}
}
for语言的快速入门
package main
import "fmt"
func main() {
for i := 1; i <= 10; i++ {
fmt.Println("你好 Golang",i)
}
}
for循环执行说明
- 首先执行循环变量初始化 比如:i := 1
- 其次执行循环条件 比如:i <= 10
- 然后执行循环操作 比如:fmt.Println(“你好 Golang”,i)
- 最后执行循环迭代, 比如 i++
- 然后反复执行2,3,4步直到循环条件为false就退出循环
for循环流程分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rfCib4SM-1690279161323)(图片/image-20230410104128160.png)]
for循环遍历
Golang提供for-range的方式,可以方便便利字符串和数组
第一种方式传统方式
package main
import "fmt"
func main() {
var str string = "Hello,world!"
for i := 0; i < len(str);i ++ {
fmt.Printf("%c \n",str[i])
}
}
第二种方式
package main
import "fmt"
func main() {
var str string = "Hello,world!"
for index,val := range str {
fmt.Printf("index= %d,val=%c \n",index,val)
}
}
上面代码细节讨论
如果我们的字符串含有中文,那么传统的遍历字符串方式,就是错误,会出现乱码。原因是传统的对字符串的遍历是按照字节来遍历,而一个汉字在utf8编码对应的是三个字节。
解决方法:需要将str转成[]rune切片 体验一把
1.传统方式
import "fmt"
func main() {
var str string = "Hello,world!北京"
t := []rune(str) //就是把str转成了[]rune类型然后把值给t
for i := 0; i < len(t);i ++ {
fmt.Printf("%c \n",t[i])
}
}
注释:此时再次运行代码没有问题
2.对于for-range遍历方式
package main
import "fmt"
func main() {
var str string = "Hello,world!上海"
for index,val := range str {
fmt.Printf("index= %d,val=%c \n",index,val)
}
}
注释:他是按照字符的方式遍历。因此字符串有中文也是可以的
8.for循环练习题
打印1~100之间所有是9的倍数的整数的个数和总和
package main
import "fmt"
func main() {
var max int = 100
var count int = 0
var sum int = 0
for i := 1 ; i <= max ; i++{
if i % 9 == 0 {
count++
sum += i
}
}
fmt.Printf("count=%v sum=%v",count,sum)
}
请获得以下效果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dfHJpTZw-1690279161324)(图片/image-20230410140335320.png)]
package main
import "fmt"
func main() {
var n int = 6
for i := 0; i <= n; i++ {
fmt.Printf("%v + %v = %v\n",i,n-i,n)
}
}
9.多重循环控制
介绍
-
将一个循环放在另一个循环体内,就形成了嵌套循环。在外面的for称为外层循环在里面的for称之为内层循环【一般建议使用两层,最多不要超过三层】
-
实质上,嵌套循环就是把内层循环当成外层循环的循环体。当只有内层循环的循环条件为flase时,才会完全跳出内层循环,才可结束外层的档次循环,开始下一次的循环。
-
设外层循环为m次,内存循环为n次,则内层循环实际需要执行m*n次。
应用案例
统计三个班成绩情况,每个班有五名同学,求出各个班的平均分和所有班级
思路分析
可以先求出来一个班五个学生的所有平均分
学生数量就是五
声明一个sum 统计班级的总分
思路二
在求出来三个班的成绩情况,每个班五个同学求出每个班的平均分
j表示第几个班级
package main
import "fmt"
func main() {
for j := 1; j <=3 ; j++{
sum := 0.0
for i := 1 ; i <= 5 ; i++ {
var score float64
fmt.Printf("请输入第%d班 第%d个学生的成绩 \n",j,i)
fmt.Scanln(&score)
sum += score / 5
}
fmt.Printf("班级的平均分是%v \n",sum)
}
}
打印空心金字塔
package main
import "fmt"
func main() {
for i := 1; i <= 3; i++ {
for k := 1 ; k <= 3 - i ; k++ {
fmt.Print(" ")
}
for j := 1; j <= 2*i - 1 ; j++ {
fmt.Print("*")
}
fmt.Println()
}
}
打印99乘法表
package main
import "fmt"
func main() {
for i := 1 ; i <=9 ; i++ {
for j := 1 ; j <= i ; j++ {
fmt.Printf("%v * %v = %v \t",j,i,i * j)
}
fmt.Println()
}
}
Break和Continue
Break应用和快速入门
看下面一个需求:
随机生成一个1-100的一个数,直道生成了99次这个数,你看看一共用了几次
提示使用
//在go中,需要生成一个随机种子,否则返回的数总是固定的
//time.now().unix():返回一个从1970 1-1 0:0:0到现在的一个数
rand.Seed(time.Now().Unix())
fmt.Println("n",rand.Intn(100)+1)
通过该需求可以说明其他流程控制数据的必要性,比如break
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
var count int = 0
for {
rand.Seed(time.Now().UnixNano())
n := rand.Intn(100)+1
count++
if n == 99 {
break
}
}
fmt.Println(count)
}
基本介绍
break语句用于终止某个语句块的执行,用于终端当前for循环或者跳出swich语句
基本语法
{ ........
break
........
}
以for循环使用break为例,画出示意图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OL6LWVrZ-1690279161325)(图片/image-20230411134922569.png)]
break的注意事项和使用细节
break语句出现在多层嵌套循环语句时,可以通过标签指明要终止的那一层语句块
看一个案例
案例1
package main
import (
"fmt"
)
func main() {
for i := 0; i <= 4; i++ {
for j :=0 ; j <=10 ; j++ {
if j == 2 {
break //默认跳出当前循环
}
fmt.Println("j = ",j)
}
}
}
案例2
package main
import (
"fmt"
)
func main() {
label1 :
for i := 0; i <= 4; i++ {
for j :=0 ; j <=10 ; j++ {
if j == 2 {
break label1 //跳出指定循环注意如果你定义了多个标签一定要全部使用到否则会报错
}
fmt.Println("j = ",j)
}
}
}
Continue快速介绍
基本介绍
continue语句用于结束本次循环,继续执行下一次循环。
continue语句出现在多层嵌套循环语句体中时,可以通过标签指明要跳过的时那一层循环,这个和前面的标签规则使用一样。
流程示意图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J3vNE86o-1690279161325)(图片/image-20230411143344702.png)]
goto和return
goto介绍
基本介绍
- Go语言中的goto语句可以无条件跳转到程序指定的行
- goto语句通常与条件语句配合使用
- 在go语言中一般不主张使用goto语句,以免造成程序流程混乱,使理解和调试程序都产生困难
return介绍
基本介绍
return使用在方法或函数中,表示跳出所在的方法或者函数,在讲解函数的时候会详细介绍。
说明
- 如果return是在普通函数,则表示跳出函数,即不再执行函数中return后面的代码,也可以理解成终止函数
- 如果函数是在main()函数,表示终止main函数,也就是说终止整个程序
出现在多层嵌套循环语句时,可以通过标签指明要终止的那一层语句块
看一个案例
案例1
package main
import (
"fmt"
)
func main() {
for i := 0; i <= 4; i++ {
for j :=0 ; j <=10 ; j++ {
if j == 2 {
break //默认跳出当前循环
}
fmt.Println("j = ",j)
}
}
}
案例2
package main
import (
"fmt"
)
func main() {
label1 :
for i := 0; i <= 4; i++ {
for j :=0 ; j <=10 ; j++ {
if j == 2 {
break label1 //跳出指定循环注意如果你定义了多个标签一定要全部使用到否则会报错
}
fmt.Println("j = ",j)
}
}
}
Continue快速介绍
基本介绍
continue语句用于结束本次循环,继续执行下一次循环。
continue语句出现在多层嵌套循环语句体中时,可以通过标签指明要跳过的时那一层循环,这个和前面的标签规则使用一样。
goto和return
goto介绍
基本介绍
- Go语言中的goto语句可以无条件跳转到程序指定的行
- goto语句通常与条件语句配合使用
- 在go语言中一般不主张使用goto语句,以免造成程序流程混乱,使理解和调试程序都产生困难
return介绍
基本介绍
return使用在方法或函数中,表示跳出所在的方法或者函数,在讲解函数的时候会详细介绍。
说明
- 如果return是在普通函数,则表示跳出函数,即不再执行函数中return后面的代码,也可以理解成终止函数
- 如果函数是在main()函数,表示终止main函数,也就是说终止整个程序