Go语言学习day1

1变量的声明

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

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

Go 复制代码
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 的变量声明后一定有零值,不存在"未初始化"的悬空状态:

类型 零值
int 0
string ""
bool false
指针、切片、map、channel、接口 nil

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

1.4多变量声明

Go 复制代码
1.声明多个变量
var a, b, c int

2.同时赋值
d, e := 1, 2

3.变量交换值(需要中间的临时值)
d, e = e, d

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

错误写法

Go 复制代码
func main()
{                       // 编译错误!
    fmt.Println("hi")
}

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

原因:自动分号插入

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

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

正确写法

Go 复制代码
func main() {
    fmt.Println("hi")
}

if port > 0 {
    fmt.Println("open")
}

for i := 0; i < 10; i++ {
    fmt.Println(i)
}

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

Go 复制代码
// 正确
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)

Go 复制代码
形式一:经典三段式(等同 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)

Go 复制代码
// C++: while (port < 100) { port++ }
port := 1
for port < 100 {
    port++
}

// 无限循环,等同 while(true)
for {
    // 需要 break 退出
    if done {
        break
    }
}

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

Go 复制代码
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.数组:固定长度,值语义

Go 复制代码
var arr [3]int          // 三个元素,都是 0
arr[0] = 1
arr[1] = 2
arr[2] = 3

fmt.Println(len(arr))   // 3,长度是类型的一部分

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

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

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

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

Go 复制代码
// 创建方式一:字面量
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) 的区别:

Go 复制代码
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 的区别

Go 复制代码
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)------原地写,不换数组

Go 复制代码
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)------分配新数组

Go 复制代码
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)是一个临时变量,需要把值赋值回去

相关推荐
FungLeo10 小时前
成为全栈·Go 后端篇·契约一致性与差分测试:两套实现如何互相校验
golang·node.js·go·合约测试·差分测试·成为全栈·api兼容
vipxieliang13 小时前
Go 1.25 Green Tea GC 深度解析:以页为单位的垃圾回收新范式
go
孔令飞14 小时前
AI 时代岗位调研分析:调研了近3000份AI岗位描述,分享下调研结果
云原生·go·ai编程
我不会起名字32214 小时前
Redis 从 0 到 1(十二):先删缓存还是先更新库?4 种方案的 Go 并发实测
redis·go·并发·延迟双删·缓存一致性
FungLeo1 天前
成为全栈·Go 后端篇·用 Go 重写分层架构:领域包、装配根与最小抽象
golang·go·领域驱动设计·gorm·分层架构·成为全栈
我不会起名字3222 天前
Go 服务升级 Go 1.2x 后 P99 变差:GMP 调度相关的 3 个变化
golang·go·gmp·调度·gomaxprocs
孔令飞2 天前
知己知彼,百战不殆:AI SKILL 实现原理剖析
云原生·go·ai编程
孔令飞3 天前
工具分享:OneX AI 工具安装、配置及功能介绍
云原生·go·ai编程
喵了几个咪3 天前
GoWind Admin 拆解:一个 Sender 接口接 39 个通知渠道
微服务·golang·go·1024程序员节·admin·gowind