☰
Go语言学习day1
2026/10/24 4:07:59 网站建设 项目流程

1变量的声明

1.1go的变量声明是放在变量名后面

var 是 Go 里声明变量的关键字,var就是“我要声明一个变量”的标记。

1.完整声明 var port int var host string var timeout time.Duration //time包里的类型,单位为ns 2.有初始值,可以推断其类型 var port = 8080 var host = "127.0.0.1" 3.短声明(只能在函数内部用) port := 8080 host := "127.0.0.1" time := 500*time.Millisecond //ms

1.2再补充一下time包

1.3零值

Go 的变量声明后一定有零值,不存在“未初始化”的悬空状态:

类型零值
int0
string""
boolfalse
指针、切片、map、channel、接口nil

C++ 里int port;是未定义值,Go 里var port int一定是0。这意味着你不需要手动初始化,但也意味着忘记赋值时不会报错,而是拿到零值,逻辑错误更隐蔽。

1.4多变量声明

1.声明多个变量 var a, b, c int 2.同时赋值 d, e := 1, 2 3.变量交换值(需要中间的临时值) d, e = e, d

2.打括号需要跟在同一行

错误写法

func main() { // 编译错误! fmt.Println("hi") }

编译器报错信息通常是syntax error: unexpected semicolon or newline before {。

原因:自动分号插入

Go 编译器在每一行末尾自动插入分号,规则是:如果一行以标识符、数字、字符串、)、]、}、++、--等结尾,就在行尾加;。所以上面的代码被编译器看成:

func main(); // 编译器自动加了分号 { // 这里就变成孤立的代码块了 ... }

正确写法

func main() { fmt.Println("hi") } if port > 0 { fmt.Println("open") } for i := 0; i < 10; i++ { fmt.Println(i) }

唯一例外:else必须紧跟}同一行

// 正确 if err != nil { return } else { fmt.Println("ok") } // 错误:else 不能另起一行 if err != nil { return } else { // 编译错误! ... }

3. 没有 while,只有 for

Go 的for有三种形式,覆盖 C++ 的for、while、do-while(没有 do-while,用for模拟)。

形式一:经典三段式(等同 C++ for)

形式一:经典三段式(等同 C++ for) go// C++: for (int i = 0; i < 10; i++) for i := 0; i < 10; i++ { fmt.Println(i)//打印 } // 你的扫描器里: for port := start; port <= end; port++ { scanPort(target, port) }

形式二:只有条件(等同 C++ while)

// C++: while (port < 100) { port++ } port := 1 for port < 100 { port++ } // 无限循环,等同 while(true) for { // 需要 break 退出 if done { break } }

形式三:for range(遍历切片、map、channel)

ports := []int{22, 80, 443, 8080} //数组 // 只要值 for _, p := range ports { fmt.Println(p) } // 要索引和值 for i, p := range ports { fmt.Printf("index=%d port=%d\n", i, p) } // 遍历 channel(你收扫描结果时用)//result是一个map for openPort := range results { fmt.Println(openPort) }

range对切片返回(索引, 值),对 map 返回(键, 值),对 channel 只返回值(没有索引,所以for v := range ch)。_是空白标识符,用来丢弃不需要的返回值,因为 Go 要求所有声明的变量必须被使用,否则编译报错。

4. 数组 vs 切片

1.数组:固定长度,值语义

var arr [3]int // 三个元素,都是 0 arr[0] = 1 arr[1] = 2 arr[2] = 3 fmt.Println(len(arr)) // 3,长度是类型的一部分

数组是值类型,赋值和传参会复制整个数组:

a := [3]int{1, 2, 3} b := a // b 是 a 的副本 b[0] = 100 fmt.Println(a[0]) // 1,a 不受影响

2.切片:可变长度,引用语义

切片是日常使用的“动态数组”,底层是一个指向数组的指针 + 长度 + 容量。

// 创建方式一:字面量 ports := []int{22, 80, 443} // 创建方式二:make(类型, 长度, 容量) s := make([]int, 0) // 长度 0,容量 0 s := make([]int, 0, 10) // 长度 0,容量 10(预留空间,避免频繁扩容) s := make([]int, 5) // 长度 5,容量 5,五个元素都是 0 // 创建方式三:从数组切 arr := [5]int{1, 2, 3, 4, 5} s := arr[1:4] // [2, 3, 4],左闭右开

日常最常用:var s []int(收集结果)和s := []int{...}(有初始值)。make([]int, 0, N)只在明确知道大概要存多少、想避免扩容时用。

注意make([]int, 5)和make([]int, 0, 5)的区别:

a := make([]int, 5) // [0 0 0 0 0],len=5,可以直接 a[0] 访问 b := make([]int, 0, 5) // [],len=0,不能 b[0](panic),只能 append

make([]int, 5)是“已经存在 5 个零值元素”,不是“预留 5 个空位”。收集结果时用var s []int或make([]int, 0),不要用make([]int, N),否则 append 后会多出一堆你没想要的 0。

2.1len 和 cap 的区别

s := make([]int, 0, 5) fmt.Println(len(s)) // 0,当前有几个元素 fmt.Println(cap(s)) // 5,底层数组能装几个 s = append(s, 22) fmt.Println(len(s)) // 1 fmt.Println(cap(s)) // 5,没超过容量,底层数组没换 s = append(s, 80, 443, 8080, 3306, 21) fmt.Println(len(s)) // 6 fmt.Println(cap(s)) // 10(扩容了,通常翻倍)

len决定你能访问哪些索引(s[0]到s[len-1]),cap决定 append 时要不要分配新数组。len <= cap永远成立。

2.2append 的两种情形

append是内置函数,签名大致是func append(s []T, elems ...T) []T,必须接收返回值。

情形一:容量够(len < cap)——原地写,不换数组

s := make([]int, 0, 5) // {array: A, len: 0, cap: 5} s = append(s, 22) // 写 A[0],返回 {A, 1, 5} s = append(s, 80) // 写 A[1],返回 {A, 2, 5}

底层数组始终是 A,append写的永远是A[len]这个新位置,已有元素不动。但len变了,len在切片头里,所以必须把返回的新切片头赋回去。

情形二:容量不够(len == cap)——分配新数组

s := []int{22, 80, 443} // {array: A, len: 3, cap: 3} s = append(s, 8080) // 分配新数组 B(cap 通常 6),拷贝 22 80 443,写 B[3]=8080,返回 {B, 4, 6}

地址从 A 变成 B,旧数组 A 被丢弃(如果没有其他切片共享它)。不赋值的话,s还指向 A,8080在 B 里,完全看不到。

为什么必须赋值

append返回的是新的切片头(len至少变了,扩容时array也变了)。切片头是值类型,append拿不到你变量的地址,只能返回新值让你赋回去。不赋值,底层数据可能写进去了,但len没更新,你访问不到;扩容时新数组直接丢。

切片是一个结构体,无法直接修改它的原结构体,append(s,8080)是一个临时变量,需要把值赋值回去

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询