【Go语言圣经2.5】

server/2025/3/19 5:05:16/

目标

了解类型定义不仅告诉编译器如何在内存中存储和处理数据,还对程序设计产生深远影响:

  • 内存结构:类型决定了变量的底层存储(比如占用多少字节、内存布局等)。
  • 操作符与方法集:类型决定了哪些内置运算符适用于它,以及能否为该类型定义方法,从而扩展它的行为。
  • 语义区分:通过定义新类型,即使它们的底层类型相同,也能明确区分不同概念,避免混用(例如温度单位、索引、文件描述符等)。
  • 可读性与安全性:使用命名类型可以让代码语义更明确,从而降低出错的风险。

概念

  1. 每个变量或表达式都有一个类型,类型描述了值的属性,如数据占用的内存大小、内部结构、支持哪些操作以及关联的方法集等。
  2. 一个 int 类型的变量可能用于表示索引、时间戳或月份;一个 float64 类型的变量可能表示速度或温度。尽管底层都是数字,但语义上却截然不同。
  3. 内存与数据表示:每个数据类型在内存中的表示方式(如整数占用 32 位或 64 位)由其底层类型决定。命名类型会沿用相同的存储方式,但在类型系统中赋予它新的含义。
  4. 方法与接收者
    • **方法的定义:**在 Go 中,可以为任意命名类型定义方法,使得该类型的值具有特定行为(如格式化输出)。
    • 调用示例:fmt.Printf 等函数会自动调用类型的 String 方法,从而定制输出格式。
    • 静态分派:fmt.Printf 等函数会自动调用类型的 String 方法,从而定制输出格式。

要点

类型声明语句

type 新类型名称 底层类型

这条语句创建一个新的类型名称,其底层结构与现有类型相同,但在类型系统中被认为是不同的。例如:

type Celsius float64    // 摄氏温度类型
type Fahrenheit float64 // 华氏温度类型

即使 Celsius 和 Fahrenheit 都基于 float64,它们是两个不同的类型,防止你无意中混用不同温度单位的数据。

  • 命名规则与导出
    • 如果新类型名称的首字符大写,则表示该类型对包外可见(导出)。
    • Go 语言中关于 Unicode 字母的规则也使得中文命名默认不能导出(Go 1 的规则),但在将来的 Go 2 中可能会调整。
  • 用途
    • 语义区分:通过定义不同的命名类型(即使底层相同)可以让程序更安全,不会将代表不同概念的值误用在一起。
    • 简化代码:对于复杂或冗长的类型,用一个简单的名称可以使代码更清晰易读。

类型转换操作

  1. 操作形式

    类型转换写作 T(x),它不会改变 x 的实际值,只是将它“贴上”新的类型标签

    var f Fahrenheit = CToF(BoilingC)
    fmt.Println(Celsius(f)) // 将 f 转换为 Celsius 类型
    
    • 限制:只有当两个类型的底层类型完全相同时,或者都是指向相同底层结构的指针时,才能进行转换。
  2. 作用

    通过类型转换,可以将同样底层数据但语义不同的值显式转换,使得运算和比较变得类型安全。例如

    var c Celsius
    var f Fahrenheit
    // c == f // 编译错误:类型不匹配
    fmt.Println(c == Celsius(f)) // 显式转换后可以比较
    

类型与方法

  1. 方法集

    命名类型不仅是一个新的数据类型,还可以为该类型定义方法,这使得它拥有特定的行为。

    func (c Celsius) String() string {return fmt.Sprintf("%g°C", c)
    }
    

    这段代码为 Celsius 类型定义了一个 String 方法,在调用 fmt.Printf 时会自动调用该方法来格式化输出。(因为 fmt 包会自动调用 String 方法)(具体一点说:当一个类型实现了 String() string 方法后,它就满足了 fmt 包定义的 Stringer 接口。fmt 包在格式化输出时,会检查传入的值是否实现了该接口,如果实现了,就自动调用 String() 方法来获取该值的字符串表示。)

    c := FToC(212.0)
    fmt.Println(c.String()) // 输出 "100°C"
    fmt.Printf("%v\n", c)   // 也输出 "100°C",因为 fmt 包会自动调用 String 方法

    为类型定义方法不仅增加了代码的可读性,也让类型更具语义。

如何定义 Celsius 与 Fahrenheit 两个命名类型及它们之间的转换和方法

// Package tempconv performs Celsius and Fahrenheit temperature computations.
package tempconvimport "fmt"type Celsius float64    // 定义摄氏温度类型
type Fahrenheit float64 // 定义华氏温度类型// 常量声明
const (AbsoluteZeroC Celsius = -273.15 // 绝对零度(摄氏)FreezingC     Celsius = 0       // 冰点(摄氏)BoilingC      Celsius = 100     // 沸点(摄氏)
)// CToF 将摄氏温度转换为华氏温度
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) 
}// FToC 将华氏温度转换为摄氏温度
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) 
}// Celsius 的 String 方法,使其输出更友好
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) 
}
  • Celsius 类型的 String 方法为其提供了定制格式,当使用 fmt 包打印时,会自动调用此方法。

类型比较与运算

  • 支持内置运算符

    因为 Celsius 和 Fahrenheit 的底层类型都是 float64,所以它们可以使用底层类型的算术和比较运算符。例如:

    
    fmt.Printf("%g\n", BoilingC-FreezingC) // 输出 "100"
    boilingF := CToF(BoilingC)
    fmt.Printf("%g\n", boilingF-CToF(FreezingC)) // 输出 "180"
    

    注意,直接将 Fahrenheit 与 Celsius 进行运算会导致编译错误,因为它们是不同的命名类型。

  • 比较运算

    只有相同类型之间才能进行直接比较:

    var c Celsius
    var f Fahrenheit
    fmt.Println(c == 0)          // "true",0 会被视为 Celsius 类型的零值
    fmt.Println(f >= 0)          // "true",同理,0 对于 Fahrenheit 也是零值
    // fmt.Println(c == f)       // 编译错误:类型不匹配
    fmt.Println(c == Celsius(f)) // 正确:通过类型转换后比较
    

总结

  1. 类型定义描述了变量的内存表示、支持的运算符和方法集,同时区分不同的语义。
  2. 将不同的概念定义为不同的命名类型,即使它们底层相同,也能在编译期防止错误的混用。(例如,不小心将温度与文件描述符混用可能引发错误,通过定义不同类型就可以避免这种情况。)
  3. 类型转换 T(x) 只改变 x 的类型标签而不改变底层数据。转换要求两个类型有相同的底层类型或兼容的结构。
  4. 方法与类型行为:为命名类型定义方法(如 String 方法),使得在打印和其它操作时可以自动调用,增强了类型的表现力。
  5. 新类型继承了底层类型的算术与比较操作,但必须保证操作数类型一致。

http://www.ppmy.cn/server/176155.html

相关文章

Java 学习,查看端口使用与否

Java查看端口是否已被使用,通常涉及尝试绑定一个 ServerSocket 到指定的端口,并捕获可能抛出的 IOException 异常。如果绑定成功,则说明端口未被使用;如果抛出异常,则说明端口已被占用。 基本概念: 端口&…

SpringBoot 和vue前后端配合开发网页拼图10关游戏源码技术分享

今天分享一个 前后端结合 的网页游戏 开发项目源码技术。 这也是我第一次写游戏类的程序,虽然不是特别复杂的游戏,但是是第一次写,肯定要记录一下了,哈哈。 游戏的内容 就是 我们显示中玩的那个 拼图碎片的 游戏,类似下…

网络安全证书培训机构有哪些

一、前言少叙 记得刚入行的时候,想考一个证书来装装门面,结果发现费用太高了,比当时一个月的工资都高,感叹网络安全这帮人真舍得花钱,遂放弃。后来入职网络安全公司,考了一个CISP,在工作中逐渐…

从零开始 | C语言基础刷题DAY3

❤个人主页&#xff1a;折枝寄北的博客 目录 1.打印3的倍数的数2.从大到小输出3. 打印素数4.打印闰年5.最大公约数 1.打印3的倍数的数 题目&#xff1a; 写一个代码打印1-100之间所有3的倍数的数字 代码&#xff1a; int main(){int i 0;for (i 1; i < 100; i){if (i % …

设计模式 二、创建型设计模式

GoF是 “Gang of Four”&#xff08;四人帮&#xff09;的简称&#xff0c;它们是指4位著名的计算机科学家&#xff1a;Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides。他们合作编写了一本非常著名的关于设计模式的书籍《Design Patterns: Elements of Reusable…

人工智能辅助 3D 建模:Claude + Blender MCP 体验

作者提供的图片 大约六年前&#xff0c;我曾把玩Blender作为一项业余爱好。虽然我热爱它带来的创意可能性&#xff0c;但我总觉得学习曲线陡峭且耗费时间。最近&#xff0c;我发现了Blender MCP&#xff0c;它通过模型上下文协议&#xff08;Model Context Protocol&#xff0…

C#-委托delegate

一.C#-委托delegate C#中委托即C中函数指针,通过delegate关键字可声明一个代理.代理可像指针一样作为参数传递和调用. <1.声明一个代理类型 class Test{public delegate string CreateNativeString(); }<2.创建代理变量 class Main{public Test.CreateNativeString poi…

JAVA(8)-数组

一.数组&#xff1a;可以存多种数据的容器 二.数组初始化 &#xff08;1&#xff09; 静态初始化 三.数组元素访问 数组名【索引】 把数据存储到数组中 四.数组遍历&#xff1a;取出数据 调用方式&#xff1a;数组名.length 自动快速生成&#xff1a;数组名.fori 五.数…