Go语言 Package 和 Import 学习笔记

查看结论直接跳转到7,8,9即可

本博客主要记录:同一个目录下的go文件都得是一个package里面的,不允许同一个目录的的go文件属于第二个包。也就是一个目录对应一个package,而不同目录下的想要调用当前目录的go文件里面的代码必须使用import才可以进行调用

注:大小写控制的包内和包外是否可以调用,不管大小写,包内都随便调用,如果是大写,那么你在别的包使用import之后可以调用,如果是小写,那么在包外就无法被调用了

一、核心问题:同一个 package 的文件能否互相调用?

1.1 问题场景

假设 hello1.gopackage mainhello2.go 也是 package main,那么在 hello1.go 中能否直接调用 hello2.go 中的函数?这两个文件是否属于同一个作用域?

1.2 答案

是的,可以!

如果两个文件满足以下条件:

  1. 声明相同的包名(如 package main
  2. 同一个目录

那么:

  • ✅ 可以直接调用对方的函数
  • ✅ 属于同一个包,共享同一个命名空间

1.3 代码示例

1
2
3
4
5
6
7
8
9
// hello1.go
package main

import "fmt"

func main() {
SayHello() // ✅ 直接调用 hello2.go 中的函数
fmt.Println("from hello1")
}
1
2
3
4
5
6
7
8
// hello2.go
package main

import "fmt"

func SayHello() {
fmt.Println("Hello from hello2!")
}

1.4 运行方式

1
2
3
4
5
6
7
8
# ❌ 错误:只运行一个文件,会报错 "undefined: SayHello"
go run hello1.go

# ✅ 正确方式1:运行所有相关文件
go run hello1.go hello2.go

# ✅ 正确方式2:运行整个目录
go run .

二、Go 的 Package 规则详解

2.1 核心规则

规则 说明
同目录 = 同包 同一个目录下的所有 .go 文件必须声明相同的包名
包内共享 同一个包内的所有代码共享命名空间,可以互相访问
大小写控制导出 首字母大写 = 可被其他包访问;首字母小写 = 仅包内访问

2.2 作用域示意图

1
2
3
4
5
6
7
8
9
10
11
┌─────────────────────────────────────────┐
package main │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ hello1.go │ │ hello2.go │ │
│ │ │ │ │ │
│ │ func main() │ │ func SayHello()
│ │ SayHello()│──│ │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ ✅ 同一个作用域,可以互相访问 │
└─────────────────────────────────────────┘

2.3 注意事项

1. 同一目录不能有不同包名

1
2
3
4
5
6
// ❌ 错误示例
// hello1.go
package main

// hello2.go
package other // 编译错误!同一目录下包名必须一致

2. 函数不能重复定义

其实就是在同一个作用域里面不能定义两个一样的函数

1
2
3
4
5
6
// ❌ 两个文件都定义了同名函数
// hello1.go
func DoSomething() {}

// hello2.go
func DoSomething() {} // 错误:重复定义

三、与 C++ 头文件机制的对比

3.1 整体对比

特性 C++ Go
代码组织 头文件(.h) + 源文件(.cpp) 包(package)
声明可见性 头文件声明 首字母大小写
引用方式 #include "xxx.h" import "xxx"
编译单位 单个 .cpp 文件 整个包
符号导出 所有声明都可见 大写开头才导出
链接 手动管理 .lib.o 自动处理

3.2 同一个项目内文件共享的对比

C++ 方式:需要头文件声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ========== hello2.h ==========
#ifndef HELLO2_H
#define HELLO2_H

void SayHello(); // 声明

#endif

// ========== hello2.cpp ==========
#include "hello2.h"
#include <iostream>

void SayHello() { // 实现
std::cout << "Hello from hello2!" << std::endl;
}

// ========== hello1.cpp ==========
#include "hello2.h" // 必须include头文件才能用

int main() {
SayHello(); // 调用
return 0;
}

Go 方式:同一个包内直接用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ========== hello2.go ==========
package main

import "fmt"

func SayHello() { // 直接定义,无需头文件
fmt.Println("Hello from hello2!")
}

// ========== hello1.go ==========
package main
// 不需要任何import!同包内直接用

func main() {
SayHello() // 直接调用
}

对比总结

C++ Go
同目录下的 .cpp 文件互不知道 同目录下的 .go 文件(同包)自动共享
必须用 #include 引入头文件 不需要,同包直接用
头文件 = 声明的桥梁 无需桥梁,编译器自动处理

3.3 引用其他目录/模块代码的对比

C++ 方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ========== math_utils/math_utils.h ==========
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

int Add(int a, int b); // 声明
int Multiply(int a, int b);

#endif

// ========== math_utils/math_utils.cpp ==========
#include "math_utils.h"

int Add(int a, int b) { return a + b; }
int Multiply(int a, int b) { return a * b; }

// ========== main.cpp ==========
#include "math_utils/math_utils.h" // 引入其他模块

int main() {
int result = Add(1, 2); // 使用
return 0;
}

Go 方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ========== math_utils/add.go ==========
package math_utils // 包名

func Add(a, b int) int { // 首字母大写 = 导出
return a + b
}

func multiply(a, b int) int { // 首字母小写 = 不导出
return a * b
}

// ========== main.go ==========
package main

import "myproject/math_utils" // 导入其他包

func main() {
result := math_utils.Add(1, 2) // ✅ 可以用
// math_utils.multiply(1, 2) // ❌ 编译错误,小写不可见
}

对比总结

C++ Go
#include "xxx.h" 引入声明 import "xxx" 引入整个包
头文件里声明的都能用 只有大写开头的能用
链接时需要 .lib.o 文件 自动链接,无需手动管理

3.4 完整对比图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
┌─────────────────────────────────────────────────────────────────┐
│ C++ 头文件机制 │
├─────────────────────────────────────────────────────────────────┤
│ hello2.h hello2.cpp hello1.cpp │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 声明 │◄──include│ 实现 │ │ #include│ │
│ │void Say│ │void Say │ │ "hello2"│ │
│ │Hello();│ │Hello(){ │ │ │ │
│ └─────────┘ │ ... │ │ main(){ │ │
│ ▲ └─────────┘ │ SayHello() │
│ │ └─────────┘ │
│ └────────── 必须通过头文件桥接 ──────────────┘ │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│ Go 包机制 │
├─────────────────────────────────────────────────────────────────┤
│ hello2.go hello1.go │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ package main │ │ package main │ │
│ │ │ │ │ │
│ │ func SayHello() { │─────────│ func main() { │ │
│ │ fmt.Println() │ 直接用! │ SayHello() │ │
│ │ } │ │ } │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ 同一个 package = 自动共享,无需头文件 │
└─────────────────────────────────────────────────────────────────┘

四、Package 和 Import 的区别

4.1 核心概念

Go 概念 作用 C++ 类比
package 定义”我是谁”(属于哪个包) namespace + 编译单元
import 引入”别人是谁”(使用其他包) #include + using

4.2 package = 定义身份

Go

1
2
3
4
5
6
// hello.go
package myutils // 我属于 myutils 包

func Add(a, b int) int { // 大写 = 导出
return a + b
}

C++ 类比

1
2
3
4
5
6
// hello.cpp
namespace myutils { // 我属于 myutils 命名空间
int Add(int a, int b) {
return a + b;
}
}
Go C++
package myutils namespace myutils { ... }
定义这个文件属于哪个包 定义这些代码属于哪个命名空间
每个文件必须声明 可选,不是必须的

4.3 import = 引入别人

Go

1
2
3
4
5
6
7
8
// main.go
package main

import "myproject/myutils" // 引入 myutils 包

func main() {
myutils.Add(1, 2) // 通过包名调用
}

C++ 类比

1
2
3
4
5
6
7
8
// main.cpp
#include "myutils/hello.h" // 引入头文件

using namespace myutils; // 使用命名空间

int main() {
myutils::Add(1, 2); // 通过命名空间调用
}
Go C++
import "xxx" #include "xxx.h" + 链接
引入整个包的符号 引入头文件的声明
自动链接 需要手动链接 .lib.o

4.4 核心区别总结

package = 定义身份(我是谁)
• 写在文件开头
• 声明这个文件属于哪个包
• 类似 C++ 的 namespace,但更强(目录必须对应)

import = 引入别人(我要用谁)
• 引入其他目录的包
• 类似 C++ 的 #include + using
• 但只能用大写开头的符号

类比:
package → “我是中国人”(定义身份)
import → “我要用美国货”(引入外部资源)


五、不同目录下的情况分析

5.1 场景 A:三个文件在同一个目录,都是 package main

1
2
3
4
myproject/
├── hello1.go (package main)
├── hello2.go (package main)
└── hello3.go (package main)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// hello1.go
package main
func Func1() { println("hello1") }

// hello2.go
package main
func Func2() { println("hello2") }

// hello3.go
package main
// 不需要任何 import!直接用!
func main() {
Func1() // ✅ 直接调用
Func2() // ✅ 直接调用
}

结论:同目录 + 同包名 = 直接用,不需要 import

5.2 场景 B:hello3.go 在另一个目录(错误示例)

1
2
3
4
5
6
myproject/
├── main/
│ ├── hello1.go (package main)
│ └── hello2.go (package main)
└── other/
└── hello3.go (package other)
1
2
3
4
5
6
7
8
9
10
11
12
13
// main/hello1.go
package main
func Func1() { println("hello1") }

// other/hello3.go
package other

// ❌ 不能 import "main"!main 包不能被导入!
// import "myproject/main" // 编译错误!

func main() { // ❌ 这个 main 函数也不会执行
// 无法调用 Func1 和 Func2
}

问题

  1. main 包是程序入口不能被其他包 import
  2. 如果想让代码被其他包使用,必须放在非 main 包

5.3 场景 C:正确做法 - 抽取公共代码到独立包

1
2
3
4
5
6
myproject/
├── myutils/
│ ├── hello1.go (package myutils)
│ └── hello2.go (package myutils)
└── main/
└── hello3.go (package main)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// myutils/hello1.go
package myutils
func Func1() { println("hello1") } // 大写开头 = 导出

// myutils/hello2.go
package myutils
func Func2() { println("hello2") } // 大写开头 = 导出

// main/hello3.go
package main

import "myproject/myutils" // ✅ 导入 myutils 包

func main() {
myutils.Func1() // ✅ 通过包名调用
myutils.Func2() // ✅ 通过包名调用
}

5.4 场景总结

场景 能否调用 原因
同目录 + 同包名 直接调用 同一个包,共享作用域
同目录 + 不同包名 编译错误 Go 要求同目录必须同包名
不同目录 + import “main” 编译错误 main 包不能被 import
不同目录 + 抽取到独立包 import 后调用 正确做法

六、常见错误示例

6.1 错误:import 自己所在的包

1
2
3
4
// ❌ 错误示例
// hello3.go
package main
import "main" // 编译错误!不能 import 自己所在的包

6.2 错误:import main 包

1
2
3
4
// ❌ 错误示例
// other/hello3.go
package other
import "myproject/main" // 编译错误!main 包不能被 import

6.3 错误:同目录不同包名

1
2
3
4
5
6
// ❌ 错误示例
// hello1.go
package main

// hello2.go (同目录)
package other // 编译错误!同目录必须同包名

七、Go 的核心规则总结

Go 的 package/import 规则:

  1. import 只能引入【其他包】,不能引入自己所在的包

  2. main 包是程序入口,不能被其他包 import

  3. 同目录 = 必须同包名 = 不需要 import = 直接用

  4. 不同目录 = 不同包 = 需要 import = 通过包名调用

  5. 大写开头 = 可被其他包使用(导出)

  6. 小写开头 = 只能包内使用(私有)


八、与 C++ 的核心差异总结

特性 C++ Go
同目录文件共享 需要头文件声明 同包自动共享
使用其他模块 #include "xxx.h" import "xxx"
符号可见性 头文件里写了就能用 大写=导出,小写=私有
编译链接 手动管理 .o .lib 自动处理
循环依赖 头文件可以循环 include 禁止循环 import
main 能否被引用 可以 不能被 import

九、总结

Go Package 机制速查表

• 每个文件必须声明 package

• 同目录必须同包名

• 类似 C++ 的 namespace

【import = 引入别人】

• 只能引入其他目录的包

• 不能 import 自己所在的包

• 不能 import main 包

• 类似 C++ 的 #include + using

【大小写控制可见性

• 大写开头 = 导出(可被其他包使用)

• 小写开头 = 私有(只能包内使用)

这一条不止针对函数,而是针对所有的标识符,比如函数,变量,结构体等等

【同包 = 同作用域】

• 同目录 + 同包名 = 直接用,不需要 import

• 不同目录 = 需要 import + 通过包名调用

同一个目录下的go文件都得是一个package里面的,不允许同一个目录的的go文件属于第二个包。

也就是一个目录对应一个package,而不同目录下的想要调用当前目录的go文件里面的代码必须使用import才可以进行调用