Go语言 Package 和 Import 学习笔记
查看结论直接跳转到7,8,9即可
本博客主要记录:同一个目录下的go文件都得是一个package里面的,不允许同一个目录的的go文件属于第二个包。也就是一个目录对应一个package,而不同目录下的想要调用当前目录的go文件里面的代码必须使用import才可以进行调用
注:大小写控制的包内和包外是否可以调用,不管大小写,包内都随便调用,如果是大写,那么你在别的包使用import之后可以调用,如果是小写,那么在包外就无法被调用了
文章目录
- [Go语言 Package 和 Import 学习笔记](#Go语言 Package 和 Import 学习笔记)
-
- [一、核心问题:同一个 package 的文件能否互相调用?](#一、核心问题:同一个 package 的文件能否互相调用?)
-
- [1.1 问题场景](#1.1 问题场景)
- [1.2 答案](#1.2 答案)
- [1.3 代码示例](#1.3 代码示例)
- [1.4 运行方式](#1.4 运行方式)
- [二、Go 的 Package 规则详解](#二、Go 的 Package 规则详解)
-
- [2.1 核心规则](#2.1 核心规则)
- [2.2 作用域示意图](#2.2 作用域示意图)
- [2.3 注意事项](#2.3 注意事项)
-
- [1. 同一目录不能有不同包名](#1. 同一目录不能有不同包名)
- [2. 函数不能重复定义](#2. 函数不能重复定义)
- [三、与 C++ 头文件机制的对比](#三、与 C++ 头文件机制的对比)
-
- [3.1 整体对比](#3.1 整体对比)
- [3.2 同一个项目内文件共享的对比](#3.2 同一个项目内文件共享的对比)
- [3.3 引用其他目录/模块代码的对比](#3.3 引用其他目录/模块代码的对比)
- [3.4 完整对比图](#3.4 完整对比图)
- [四、Package 和 Import 的区别](#四、Package 和 Import 的区别)
-
- [4.1 核心概念](#4.1 核心概念)
- [4.2 package = 定义身份](#4.2 package = 定义身份)
- [4.3 import = 引入别人](#4.3 import = 引入别人)
- [4.4 核心区别总结](#4.4 核心区别总结)
- 五、不同目录下的情况分析
-
- [5.1 场景 A:三个文件在同一个目录,都是 `package main`](#5.1 场景 A:三个文件在同一个目录,都是
package main) - [5.2 场景 B:hello3.go 在另一个目录(错误示例)](#5.2 场景 B:hello3.go 在另一个目录(错误示例))
- [5.3 场景 C:正确做法 - 抽取公共代码到独立包](#5.3 场景 C:正确做法 - 抽取公共代码到独立包)
- [5.4 场景总结](#5.4 场景总结)
- [5.1 场景 A:三个文件在同一个目录,都是 `package main`](#5.1 场景 A:三个文件在同一个目录,都是
- 六、常见错误示例
-
- [6.1 错误:import 自己所在的包](#6.1 错误:import 自己所在的包)
- [6.2 错误:import main 包](#6.2 错误:import main 包)
- [6.3 错误:同目录不同包名](#6.3 错误:同目录不同包名)
- [七、Go 的核心规则总结](#七、Go 的核心规则总结)
- [八、与 C++ 的核心差异总结](#八、与 C++ 的核心差异总结)
- 九、总结
一、核心问题:同一个 package 的文件能否互相调用?
1.1 问题场景
假设
hello1.go是package main,hello2.go也是package main,那么在hello1.go中能否直接调用hello2.go中的函数?这两个文件是否属于同一个作用域?
1.2 答案
是的,可以!
如果两个文件满足以下条件:
- 声明相同的包名(如
package main) - 在同一个目录下
那么:
- ✅ 可以直接调用对方的函数
- ✅ 属于同一个包,共享同一个命名空间
1.3 代码示例
go
// hello1.go
package main
import "fmt"
func main() {
SayHello() // ✅ 直接调用 hello2.go 中的函数
fmt.Println("from hello1")
}
go
// hello2.go
package main
import "fmt"
func SayHello() {
fmt.Println("Hello from hello2!")
}
1.4 运行方式
bash
# ❌ 错误:只运行一个文件,会报错 "undefined: SayHello"
go run hello1.go
# ✅ 正确方式1:运行所有相关文件
go run hello1.go hello2.go
# ✅ 正确方式2:运行整个目录
go run .
二、Go 的 Package 规则详解
2.1 核心规则
| 规则 | 说明 |
|---|---|
| 同目录 = 同包 | 同一个目录下的所有 .go 文件必须声明相同的包名 |
| 包内共享 | 同一个包内的所有代码共享命名空间,可以互相访问 |
| 大小写控制导出 | 首字母大写 = 可被其他包访问;首字母小写 = 仅包内访问 |
2.2 作用域示意图
go
┌─────────────────────────────────────────┐
│ package main │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ hello1.go │ │ hello2.go │ │
│ │ │ │ │ │
│ │ func main() │ │ func SayHello() │
│ │ SayHello()│──│ │ │
│ └─────────────┘ └─────────────┘ │
│ │
│ ✅ 同一个作用域,可以互相访问 │
└─────────────────────────────────────────┘
2.3 注意事项
1. 同一目录不能有不同包名
go
// ❌ 错误示例
// hello1.go
package main
// hello2.go
package other // 编译错误!同一目录下包名必须一致
2. 函数不能重复定义
其实就是在同一个作用域里面不能定义两个一样的函数
go
// ❌ 两个文件都定义了同名函数
// 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++ 方式:需要头文件声明
cpp
// ========== 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 方式:同一个包内直接用
go
// ========== 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++ 方式:
cpp
// ========== 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 方式:
go
// ========== 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 完整对比图
┌─────────────────────────────────────────────────────────────────┐
│ 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:
go
// hello.go
package myutils // 我属于 myutils 包
func Add(a, b int) int { // 大写 = 导出
return a + b
}
C++ 类比:
cpp
// hello.cpp
namespace myutils { // 我属于 myutils 命名空间
int Add(int a, int b) {
return a + b;
}
}
| Go | C++ |
|---|---|
package myutils |
namespace myutils { ... } |
| 定义这个文件属于哪个包 | 定义这些代码属于哪个命名空间 |
| 每个文件必须声明 | 可选,不是必须的 |
4.3 import = 引入别人
Go:
go
// main.go
package main
import "myproject/myutils" // 引入 myutils 包
func main() {
myutils.Add(1, 2) // 通过包名调用
}
C++ 类比:
cpp
// 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
myproject/
├── hello1.go (package main)
├── hello2.go (package main)
└── hello3.go (package main)
go
// 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 在另一个目录(错误示例)
myproject/
├── main/
│ ├── hello1.go (package main)
│ └── hello2.go (package main)
└── other/
└── hello3.go (package other)
go
// 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
}
问题:
main包是程序入口 ,不能被其他包 import- 如果想让代码被其他包使用,必须放在非 main 包里
5.3 场景 C:正确做法 - 抽取公共代码到独立包
myproject/
├── myutils/
│ ├── hello1.go (package myutils)
│ └── hello2.go (package myutils)
└── main/
└── hello3.go (package main)
go
// 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 自己所在的包
go
// ❌ 错误示例
// hello3.go
package main
import "main" // 编译错误!不能 import 自己所在的包
6.2 错误:import main 包
go
// ❌ 错误示例
// other/hello3.go
package other
import "myproject/main" // 编译错误!main 包不能被 import
6.3 错误:同目录不同包名
go
// ❌ 错误示例
// hello1.go
package main
// hello2.go (同目录)
package other // 编译错误!同目录必须同包名
七、Go 的核心规则总结
Go 的 package/import 规则:
-
import 只能引入【其他包】,不能引入自己所在的包
-
main 包是程序入口,不能被其他包 import
-
同目录 = 必须同包名 = 不需要 import = 直接用
-
不同目录 = 不同包 = 需要 import = 通过包名调用
-
大写开头 = 可被其他包使用(导出)
-
小写开头 = 只能包内使用(私有)
八、与 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才可以进行调用