跳到主要内容

go的语法层面

引入规则

Go语言引入规则总结

1. 同一个包(package)内

// 在 pkg/interfaces/ 包内
package interfaces

// ✅ 直接使用,无需import
func DemoInterfaces() {
memoryStore := NewMemoryStore() // 同包内直接使用
consoleLogger := &ConsoleLogger{} // 同包内直接使用
}

2. 不同包需要import

// 在 cmd/demo/main.go 中
package main

import (
"my-go-app/pkg/interfaces" // ✅ 需要import
"fmt" // ✅ 标准库也需要import
)

func main() {
interfaces.DemoInterfaces() // 使用包名.函数名
}

3. 其他重要规则

包名规则

  • 包名通常与目录名相同
  • 包名应该是小写字母
  • 一个目录只能有一个包

可见性规则

// 大写字母开头 = 公开(可被其他包访问)
type PublicStruct struct {
PublicField string // 公开字段
}

func PublicFunction() {} // 公开函数

// 小写字母开头 = 私有(只能包内访问)
type privateStruct struct {
privateField string // 私有字段
}

func privateFunction() {} // 私有函数

特殊import方式

import (
// 1. 标准库
"fmt"
"net/http"

// 2. 第三方库
"github.com/gin-gonic/gin"

// 3. 本地包
"my-go-app/internal/handler"

// 4. 别名导入
myhandler "my-go-app/internal/handler"

// 5. 点导入(不推荐)
. "fmt" // 可以直接使用Println而不需要fmt.Println

// 6. 匿名导入(只执行init函数)
_ "database/sql/driver"
)

init函数

// 每个包可以有init函数
func init() {
fmt.Println("包初始化")
}

// 执行顺序:
// 1. 导入包的init函数
// 2. 当前包的init函数
// 3. main函数

循环导入限制

// ❌ 不允许循环导入
// package A 导入 package B
// package B 导入 package A

让我创建一个完整的示例来演示这些规则:

现在让我总结一下Go语言的完整引入规则:

Go语言引入规则完整总结

1. 包内规则

  • ✅ 同一个包内的所有内容可以直接使用
  • ✅ 不需要import
  • ✅ 包括结构体、函数、变量、常量等

2. 跨包规则

  • ✅ 不同包需要import
  • ✅ 只能访问公开内容(大写字母开头)
  • ✅ 使用 包名.内容名 的方式访问

3. 可见性规则

  • 大写字母开头 = 公开(可被其他包访问)
  • 小写字母开头 = 私有(只能包内访问)
  • ✅ 这个规则适用于:结构体、函数、方法、字段、常量、变量

4. 特殊规则

  • init函数:每个包可以有init函数,在包被导入时自动执行
  • 循环导入:不允许包A导入包B,包B又导入包A
  • 包名:通常与目录名相同,应该是小写字母
  • 一个目录一个包:一个目录只能有一个包

5. Import方式

import (
"标准库" // 标准库
"第三方库" // 第三方库
"本地包" // 本地包
alias "包名" // 别名导入
. "包名" // 点导入(不推荐)
_ "包名" // 匿名导入
)

6. 执行顺序

  1. 导入包的init函数
  2. 当前包的init函数
  3. main函数

这些规则让Go语言既保持了简洁性,又确保了代码的模块化和封装性。

特殊引入形式

这是一个很好的问题!这里使用 _ 是Go语言的匿名导入(blank import),用于只执行包的init函数,而不使用包中的任何标识符

为什么使用 _ 导入

1. MySQL驱动的init函数

_ "github.com/go-sql-driver/mysql" // MySQL驱动

这个导入的目的是注册MySQL驱动,而不是直接使用驱动中的函数。

2. 驱动注册机制

MySQL驱动在 init() 函数中会自动注册自己:

// 在 github.com/go-sql-driver/mysql 包中
func init() {
sql.Register("mysql", &MySQLDriver{}) // 注册驱动
}

3. 间接使用

虽然你没有直接调用驱动函数,但Ent在连接数据库时会使用:

// initializer/data.go
client, err := ent.Open(dialect.MySQL, dsn)
// ↑
// 这里会查找已注册的"mysql"驱动

完整的调用链

1. 导入驱动: _ "github.com/go-sql-driver/mysql"

2. 执行init(): sql.Register("mysql", &MySQLDriver{})

3. Ent连接: ent.Open(dialect.MySQL, dsn)

4. 查找驱动: sql.Open("mysql", dsn)

5. 使用已注册的MySQL驱动

验证驱动注册

让我查看Ent是如何使用这个驱动的:

其他例子

// 1. 数据库驱动
_ "github.com/go-sql-driver/mysql" // MySQL
_ "github.com/lib/pq" // PostgreSQL
_ "github.com/mattn/go-sqlite3" // SQLite

// 2. 图像格式支持
_ "image/gif" // GIF格式
_ "image/png" // PNG格式

// 3. 编码支持
_ "golang.org/x/text/encoding/charmap" // 字符编码

总结

  • _ 导入:只执行包的 init() 函数
  • 驱动注册:MySQL驱动在 init() 中注册自己
  • 间接使用:Ent通过 dialect.MySQL 使用已注册的驱动
  • 必须导入:不导入驱动就无法连接MySQL数据库

所以这个 _ 导入是必需的,它确保了MySQL驱动被正确注册,让Ent能够连接MySQL数据库!

模块命名

这个路径前缀是项目创建时自定义的模块名,它来自于 go.mod 文件的第一行。

模块名的来源

1. go.mod文件定义

// go.mod 第1行
module cambridge-hit.com/gin-base/activateserver

2. 创建方式

这个模块名通常通过以下方式创建:

# 方式1:初始化Go模块时指定
go mod init cambridge-hit.com/gin-base/activateserver

# 方式2:在现有项目中修改go.mod文件

3. 命名规则

Go模块名通常遵循以下格式:

  • 域名格式cambridge-hit.com (组织域名)
  • 路径结构/gin-base/activateserver (项目路径)

常见的模块命名模式

// 1. GitHub项目
module github.com/gin-gonic/gin

// 2. 公司内部项目
module company.com/project-name

// 3. 个人项目
module github.com/username/project

// 4. 你的项目
module cambridge-hit.com/gin-base/activateserver

为什么选择这个名称

从名称可以推测:

  • cambridge-hit.com - 可能是公司或组织的域名
  • gin-base - 基于Gin框架的项目模板
  • activateserver - 激活服务器项目

如何修改

如果你想修改模块名:

# 1. 修改go.mod文件
module your-new-module-name

# 2. 更新所有导入路径
# 将所有的 cambridge-hit.com/gin-base/activateserver 替换为新名称

# 3. 重新下载依赖
go mod tidy

总结

这个路径前缀是项目初始化时自定义的模块标识符,用于:

  • 标识项目的唯一性
  • 作为所有内部导入的基础路径
  • 管理Go模块的依赖关系

它完全由开发者决定,没有特殊含义,只是一个标识符!

go中的定义和赋值方式

Go语言变量定义和赋值方式

1. 声明后赋值

// 先声明,后赋值
var memoryStore DataStore
memoryStore = NewMemoryStore()

2. 声明时直接赋值

// 声明时直接赋值
var memoryStore DataStore = NewMemoryStore()

3. 类型推断(最常用)

// 类型推断,Go自动推断类型
memoryStore := NewMemoryStore()

4. 多变量声明

// 多个变量同时声明
var (
memoryStore DataStore
fileStore DataStore
logger Logger
)

// 或者
var memoryStore, fileStore DataStore
var logger Logger

5. 多变量同时赋值

// 多个变量同时赋值
memoryStore, fileStore := NewMemoryStore(), NewFileStore("data.txt")

go中有哪些类型 复杂类型怎么定义

Go语言有丰富的类型系统,让我详细介绍一下Go中的各种类型以及复杂类型的定义方式。

Go语言类型系统

1. 基本类型(Primitive Types)

// 数值类型
var (
// 整数类型
int8 int8 = 127
int16 int16 = 32767
int32 int32 = 2147483647
int64 int64 = 9223372036854775807
int int = 2147483647 // 平台相关,32位或64位

// 无符号整数类型
uint8 uint8 = 255
uint16 uint16 = 65535
uint32 uint32 = 4294967295
uint64 uint64 = 18446744073709551615
uint uint = 4294967295

// 浮点类型
float32 float32 = 3.14
float64 float64 = 3.141592653589793

// 复数类型
complex64 complex64 = 1 + 2i
complex128 complex128 = 1 + 2i

// 字节类型
byte byte = 255 // uint8的别名
rune rune = '中' // int32的别名,表示Unicode码点
)

// 布尔类型
var flag bool = true

// 字符串类型
var str string = "Hello, 世界"

2. 复合类型(Composite Types)

现在让我总结一下Go语言中的类型系统:

Go语言类型系统完整总结

1. 基本类型(Primitive Types)

  • 整数类型int8, int16, int32, int64, int, uint8, uint16, uint32, uint64, uint
  • 浮点类型float32, float64
  • 复数类型complex64, complex128
  • 布尔类型bool
  • 字符串类型string
  • 字节类型byte(uint8别名), rune(int32别名)

2. 复合类型(Composite Types)

结构体(Struct)

type Person struct {
Name string
Age int
}

接口(Interface)

type Writer interface {
Write([]byte) (int, error)
}

切片(Slice)

type StringSlice []string

映射(Map)

type StringMap map[string]string

数组(Array)

type StringArray [5]string

通道(Channel)

type DataChannel chan string

函数(Function)

type HandlerFunc func(string) string

指针(Pointer)

type PersonPointer *Person

3. 高级类型

类型别名

type UserID int
type UserName string

自定义类型

type Status int
const (
StatusPending Status = iota
StatusProcessing
StatusCompleted
)

泛型类型(Go 1.18+)

type GenericSlice[T any] []T
type GenericMap[K comparable, V any] map[K]V

嵌套结构体

type Company struct {
Name string
CEO Person
Employees []Person
}

嵌入结构体

type Employee struct {
Person // 嵌入Person结构体
ID int
Salary float64
}

4. 类型操作

类型转换

var i int = 42
var f float64 = float64(i)
var s string = fmt.Sprintf("%d", i)

类型断言

var value interface{} = "Hello"
if str, ok := value.(string); ok {
// 使用str
}

类型开关

switch v := value.(type) {
case string:
// 处理字符串
case int:
// 处理整数
default:
// 处理其他类型
}

5. 类型特点

  • 静态类型:编译时确定类型
  • 类型安全:编译时检查类型错误
  • 零值:每种类型都有零值
  • 类型推断:支持自动类型推断
  • 接口实现:隐式实现接口
  • 类型转换:显式类型转换
  • 类型断言:运行时类型检查

Go的类型系统既强大又简洁,提供了丰富的类型定义方式,同时保持了类型安全和性能。

go中指针的使用

会大量使用指针,对于函数传参

  • 90%的情况:结构体用指针传参

  • 10%的情况:小结构体用值传参

原因(考虑内存使用和读写):

  • 避免大对象复制

  • 可以修改原对象

  • 内存效率高

  • 符合Go的惯用法

所以你的观察很准确:大多数函数传参都是指针传结构体,然后通过指针访问属性!

你的理解完全正确!这确实是Go中结构体初始化和返回指针的经典模式。

代码分析

func NewRedisCache() *RedisCache {
rdb := redis.NewClient(&redis.Options{...})
return &RedisCache{ // 返回指针
client: rdb,
ctx: context.Background(),
}
}

两种写法的对比

方法1:返回指针(当前代码)

func NewRedisCache() *RedisCache {
rdb := redis.NewClient(&redis.Options{...})
return &RedisCache{ // 返回指针
client: rdb,
ctx: context.Background(),
}
}

// 使用
cache := NewRedisCache() // cache是 *RedisCache 类型

方法2:返回值(不加&)

func NewRedisCache() RedisCache {  // 返回值类型
rdb := redis.NewClient(&redis.Options{...})
return RedisCache{ // 返回值
client: rdb,
ctx: context.Background(),
}
}

// 使用
cache := NewRedisCache() // cache是 RedisCache 类型

为什么返回指针更好

1. 内存效率

// 指针:8字节(64位系统)
var cache *RedisCache // 8字节

// 值:结构体大小
var cache RedisCache // 16字节(client指针8字节 + ctx 8字节)

2. 避免复制

// 指针:传递8字节地址
func processCache(cache *RedisCache) { ... }

// 值:复制整个结构体
func processCache(cache RedisCache) { ... } // 复制16字节

3. 修改原对象

// 指针:可以修改原对象
func (c *RedisCache) SetClient(client *redis.Client) {
c.client = client // 修改原对象
}

// 值:无法修改原对象
func (c RedisCache) SetClient(client *redis.Client) {
c.client = client // 只修改副本
}

实际使用场景

工厂模式

// 创建Redis缓存实例
func NewRedisCache() *RedisCache {
return &RedisCache{
client: redis.NewClient(...),
ctx: context.Background(),
}
}

// 创建文件缓存实例
func NewFileCache() *FileCache {
return &FileCache{
path: "./cache",
}
}

单例模式

var MyRedis *RedisCache  // 全局单例

func init() {
MyRedis = NewRedisCache() // 只创建一次
}

总结

你的理解完全正确:

  • 组装对象RedisCache{...} 创建结构体
  • 返回地址&RedisCache{...} 返回指针
  • 节省空间:指针只占8字节,避免大结构体复制
  • 提高性能:传递指针比传递值更高效
  • 支持修改:指针可以修改原对象

这是Go中构造函数的标准模式,既节省内存又提高性能!

对go的编译

1. 包级编译 (go build ./app/entity/...)

  • ✅ 只检查语法错误

  • ✅ 验证依赖关系

  • ✅ 检查类型匹配

  • ❌ 不执行main函数

  • ❌ 不进行完整的程序流程编译

2. 程序级编译 (go build cmd/my-go-app/main.go)

  • ✅ 完整的程序编译

  • ✅ 从main函数开始

  • ✅ 包含所有依赖

  • ✅ 生成可执行文件

ent-orm

完成实体类

ent generate ./app/entity/ent/schema

太好了!现在让我总结一下Ent的关系定义:

✅ Ent关系实体自动生成:生成一个文章的实体,要包含文章名称、作者(多对一)、标签(多对多)、更新时间

支持的关系类型:

  1. 一对一 (One-to-One)
edge.To("profile", Profile.Type).Unique()
  1. 一对多 (One-to-Many)
// 在Author中
edge.To("articles", Article.Type)

// 在Article中
edge.From("author", Author.Type).Ref("articles").Unique().Required()
  1. 多对多 (Many-to-Many)
// 在Article中
edge.To("tags", Tag.Type)

// 在Tag中
edge.From("articles", Article.Type).Ref("tags")

自动生成的功能:

  1. CRUD操作 - 自动生成Create、Read、Update、Delete方法
  2. 关系查询 - 自动生成WithAuthor()、WithTags()等方法
  3. 复杂查询 - 支持Where、Order、Limit等查询条件
  4. 类型安全 - 编译时检查,避免运行时错误
  5. 数据库迁移 - 自动生成SQL迁移脚本

使用示例:

// 创建带关系的文章
article, err := client.Article.
Create().
SetTitle("文章标题").
SetContent("文章内容").
SetAuthor(author). // 设置作者(多对一)
AddTags(tag1, tag2). // 添加标签(多对多)
Save(ctx)

// 查询文章及其关系
articles, err := client.Article.
Query().
WithAuthor(). // 预加载作者
WithTags(). // 预加载标签
All(ctx)

运行演示:

go run cmd/blog-demo/main.go

Ent确实可以自动生成复杂的关系实体,包括你需要的:

  • ✅ 文章名称
  • ✅ 作者(多对一关系)
  • ✅ 标签(多对多关系)
  • ✅ 更新时间
  • ✅ 完整的CRUD操作
  • ✅ 类型安全的查询方法

这就是Ent的强大之处 - 只需要定义schema,就能自动生成所有必要的代码!

entity/ent中的user 和internal/model中的user 前者是数据模型,后者是vo对吗

给我一个数据链

Loading Comments...

0%