关键词:
第二章 序
在计算机底层,一切都是比特位。然而计算机一般都操作固定大小的数,称之为字(word)。字会被解释为整数、浮点数、比特位数组、内存地址等,这些字又可以进一步聚合成数据包(packet)、像素点、作品集、是个或者其它任何对象。Go语言提供了多样化的数据组织方式,这些数据类型能提供硬件层面的兼容性,也能让程序员方便的组合成更复杂的数据类型。
Go语言的数据类型分为四大类:基本类型,复合类型,引用类型及接口类型。本章将介绍基本类型:数字,字符串,布尔值。
一、整数
Go语言的数值数据类型包括以下几种:整数,浮点数,复数,每一种都包含了大小(size)不同的数值类型,例如int8,int16,int32,int64,int。每一种数值类型都会决定值的大小和符号(正负),我们首先从整数类型开始。
Go提供了有符号和无符号整数运算。有符号整数类型分为8bit,16bit,32bit,64bit四种:int8,int16,int32,int64,还有对应的无符号整数:uint8,uint16,uint32,uint64。 还有两种整数类型,它们的大小是取决于机器平台的CPU字长的:int和uint,其中int是使用最广的类型。两种类型在不同平台上可能有不同的大小,32或64bit,但是我们在使用过程中不能有任何的假设,因为即使是在同一个平台,不同的编译器也可能采用不同的字长,因此如果确实需要64位长度,那就使用int64,如果32位长度足够,就使用int,因为int是性能最高的整数类型。
Unicode的字符都是rune类型的,rune是int32的同义词,事实上rune的底层类型就是int32,每个rune代表一个Unicode码点,这两个类型是可以互换使用的。同样的,byte也是uint8的同义词,byte用来强调数值是字符流的一个点而不是一个小整数。
最后,还有一种无符号整数类型uintptr,它的长度等同于机器的字长,是不固定的,但是足够容纳一个指针值。uintptr只在底层编程时才需要,例如,需要指针计算,需要和C语言交互等等。
总之,这些类型都是完全不同的类型,例如int和int32,虽然int在某个平台上也可能是32bits,但是把int值当作int32使用时,必须要显式的类型转换,反之亦然。
有符号整数采用2的补码形式表示,最高bit位表示符号位。一个n-bit的有符号数,它的值的范围是??
-1。无符号数使用所有的bit位来表示非负值,因此值的范围是0到 。例如int8的范围是-128到127,uint8是0到255。
下面列举了Go的算数运算、逻辑运算、比较运算中的二元操作符,按照优先级递减顺序排列:
* / % << >> & &^ + - | ^ == != < <= > >= && ||
二元操作符有五层优先级,在同一个层级使用左优先的计算原则,因此可能需要括号来提升可读性和保证正确的计算顺序 :mask & (1 << 28) 。
前两行的每个操作符都有对应的简略的赋值语句,例如 + 对应的赋值语句 +=, ^对应的赋值语句 ^=。
其中,整数运算符+,-,*,/还可以用在浮点数、复数类型上,但是求余运算符只能用在整数上。对于不同的语言来说,%可能有不同的行为,在Go中,余数的符号和被除数的符号是相同的。所以 -5%3和-5%-3的结果都是-2。 /(除法)的行为依赖于操作数的类型,如果两个操作数都是整数,那么结果也是整数: 5 / 4 的结果是1;如果至少有一个操作数是浮点数,那么结果就是浮点数, 5.0 / 4.0 的结果是1.25。
如果算数运算的结果,无论是有符号还是无符号,如果需要更多的bit才能正确表示,那么我们就说计算溢出了,这时,超过高位的bit位将被丢弃。如果原始数值是有符号的且最左边的bit是1,那么最终结果可能是负的,例如下面的int8示例:
var u uint8 = 255 fmt.Println(u, u+1, u*u) // "255 0 1" var i int8 = 127 fmt.Println(i, i+1, i*i) // "127 -128 1"两个同样类型整数可以通过比较运算符进行比较,比较表达式的类型是一个布尔值:
== 等于 != 不等于 < 小于 <= 小于等于 > 大于 >= 大于等于事实上,所有的基本类型:布尔,数值,字符串都是可以比较的,这些类型至少支持== 和 != 运算符。其中,整数、浮点数、字符串可以用所有的比较运算符。有很多类型是不可以比较的,因此是不可以排序的。当后续章节遇到这些类型时,我们会详细讲解这些规则。
下面这些也是一元运算符:
+ 正号 - 负号对于整数,+x 是 0 + x表达式的缩写,-x是0 - x表达式的缩写;对于浮点数和复数,+x就是x, -x就是x的负数。
Go也提供了下面这些bit位操作符,其中前4个不区分操作数的符号:
& 位运算 AND | 位运算 OR ^ 位运算 XOR &^ 位清空 (AND NOT) << 左移 >> 右移
位操作符^作为二元运算符时是按位异或(XOR),但是作为一元操作符时是按位取反或补位:返回一个每个位都取反的数,10100111 -> 01011000。$^操作符是位清空(AND NOT):在表达式z = x &^ y 中,如果y的某个位是1,那么z的对应位就是0,z剩余的位对应于x的相应位。
下面的代码利用按位操作把uint8的值作为8个独立的位来使用。这里使用了Printf的 %b参数打印了一个数字的位表示;08修饰%b,可以让结果准确的显示为8bit,不足位补0:
var x uint8 = 1<<1 | 1<<5 var y uint8 = 1<<1 | 1<<2 fmt.Printf("%08b ", x) // "00100010" fmt.Printf("%08b ", y) // "00000110" fmt.Printf("%08b ", x&y) // "00000010" fmt.Printf("%08b ", x|y) // "00100110" fmt.Printf("%08b ", x^y) // "00100100" fmt.Printf("%08b ", x&^y) // "00100000" for i := uint(0); i < 8; i++ { if x&(1<<i) != 0 { fmt.Println(i) // "1", "5" } } fmt.Printf("%08b ", x<<1) // "01000100" fmt.Printf("%08b ", x>>1) // "00010001"(章节5.5会给出一个远大于byte整数集的实现)
在移位操作x<<n和x>>n中,操作数n决定了移位的bit个数,n必须是无符号的。x操作数可以是无符号或有符号的。从算数上来说,x<<n等价于x * 2n
,x>>n等价于x / 2n
。
左移操作会在右边新增的空缺bit位填充0;无符号数右移是在左边新增的空缺bit位填充0,但是有符号数的右移会在左边新增的空缺bit位填充它的符号位。因此当你要将一个整数按位模式操作时,最好使用无符号运算,例如给待运算的数指定类型uint8。
虽然Go提供无符号数和运算,但是我们更倾向于使用int类型,即使在需要非负数的场景,例如数组的长度,虽然这里看上去使用uint更合适。事实上len函数返回的就是有符号的int类型:
medals := []string{"gold", "silver", "bronze"} for i := len(medals) - 1; i >= 0; i-- { fmt.Println(medals[i]) // "bronze", "silver", "gold" }这里如果len返回的是uint类型,那将是致命的,因为i将是一个uint类型,i >= 0 将永远为真。i永远不会变为-1,当i == 0是,i--语句会将i变成uint的MAX值,这个时候程序就出现致命的问题了:medals[i]会去访问slice边界外的元素。
因此,无符号数一般只在按位操作或者特定的运算场景时使用,例如:实现bit集合,解析二进制文件,哈希算法或者其它加密算法中。我们不能仅仅因为需要非负数,就去使用无符号数。
一般来说把一个值转换成另外一个类型需要显示类型转换,且二元操作符需要两个操作数的类型都相同,因此有时候会导致较长的表达式,但是这也是值得的,这样就可以消除类型带来的问题,提供更好的可读性。
下面这个例子在很多场景都会出现:
var apples int32 = 1 var oranges int16 = 2 var compote int = apples + oranges // compile error编译时会报错:
invalid operation: apples + oranges (mismatched types int32 and int16)有几种办法可以解决这个问题,其中一个是把所有类型统一成int:
var compote = int(apples) + int(oranges)许多整形之间的转换都不会改变具体的值,它们只是告诉编译器该怎么解释这个值。但是从范围大的值转换为范围小的值,例如从整数转为浮点数(反之亦然),可能会改变值的大小或者丢失精度:
f := 3.141 // a float64 i := int(f) fmt.Println(f, i) // "3.141 3" f = 1.99 fmt.Println(int(f)) // "1"浮点数转为整数会丢弃掉小数部分,你应该避免这种操作数范围不一致的转换:
f := 1e100 // a float64 i := int(f) // result is implementation-dependent整型可以表示为普通的十进制数,也可以在头部加一个0表示为8进制:0666,加一个0x或0X表示为16进制:0xdeadbeef。16进制的数字可以用大小写字母。在当前社会,8进制貌似只有一个用途了:POSIX系统下的文件权限控制,但是16进制的使用是非常广泛的,因为16进制强调的是一个数值的位模式。
当使用fmt包打印数值时,我们可以通过%d,%o,%x参数来控制进制的基数或格式:
o := 0666 fmt.Printf("%d %[1]o %#[1]o ", o) // "438 666 0666" x := int64(0xdeadbeef) fmt.Printf("%d %[1]x %#[1]x %#[1]X ", x) // Output: // 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF
注意这里有两个fmt的技巧。首先,一般来说,打印参数%x 的数目和要打印的操作数的数目是相同的,但是这里使用了%[1]告诉Printf重复使用第一个打印的操作数。其次%o,%x,%X和#结合,告诉Printf打印的时间分别添加0,0x,0X。#的用途是非常广的,可以打印数据的详细格式,例如打印struct的时候使用%#v,建议读者亲自尝试!
Rune的表现形式是字符两边加上单引号,最简单的例子就是ASCII字符 ‘a‘。我们既可以直接写Unicode码点,也可以通过数值逃逸的方式。
fmt使用%c或者%q(加上引号)来打印runes:
ascii := ‘a‘ unicode := ‘Image‘ newline := ‘ ‘ fmt.Printf("%d %[1]c %[1]q ", ascii) // "97 a ‘a‘" fmt.Printf("%d %[1]c %[1]q ", unicode) // "22269 Image ‘Image‘" fmt.Printf("%d %[1]q ", newline) // "10 ‘ ‘"
java核心技术卷一笔记8
第十四章并发多进程和多线程的区别:每个进程拥有自己的一套变量,而线程共享数据。14.1什么是线程不要调用Thread类或Runnable对象的run方法。直接调用run方法只会执行同一个线程中的任务,而不会启动新线程。应该调用start方... 查看详情
关于java核心技术(卷一)读后的思考(回调,clone的讨论)
回调回调是一种常见的程序设计模式。这种模式中,可以指出某个特定事件发生时应该采取的动作。直接给上代码packagecom.java.timer;importjava.awt.event.ActionListener;importjavax.swing.JOptionPane;importjavax.swing.Timer;publicclassTimerTest{publicstaticvoidm 查看详情
java核心技术卷一笔记7
第九章集合9.1Java集合框架在Java类库中,集合类的基本接口是Collection接口。访问集合元素时,使用“foreach”循环编译器会将其翻译成带有迭代器的循环。元素被访问的数据取决于集合类型,对于ArrayList进行迭代就会按顺序得到... 查看详情
java核心技术卷一笔记六date类
在Java核心技术卷就行了一前期 date类出现的频率很高 所以就对date类进行了小小的整合Date类有两个date类表示特定时间的类 这个构造函数分配一个Date对象并初始化它代表指定的毫秒数,因为被称为“纪元”,即1970年1... 查看详情
《java核心技术(卷一)》读书笔记——第六章:内部类
1. 内部类的概念?类中类2. 为什么要用内部类?内部类的方法可以访问外部类的实例域内部类对外部类的同一个包中的类实现了隐藏匿名内部类在“想要定义一个回调函数却... 查看详情
go语言进阶之路
1.1Go语言基础环境配置1.2VScode中Go的相关插件的安装1.3Go语言基础之1--标识符、关键字、变量和常量、数据类型、Go的基本程序结构、Golang的特性2.1Go语言基础之2--字符串详解2.2Go语言基础之3--时间和日期序列2.3Go语言基础之4--流程... 查看详情
关于java核心技术(卷一)读后的思考(内部类的讨论)
内部类内部类是定义在另一个类中的类。定义内部类的原因有:1)内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。2)内部类可以对同一个包中的其他类隐藏起来。3)当想要定义一个回调函数且不想编写... 查看详情
关于java核心技术(卷一)读后的思考(接口的基本知识的讨论)
接口接口技术:这种技术主要用来描述类具有什么功能,而并不给出每个功能的具体实现。一个类可以实现一个或多个接口,并在需要接口的地方,随时使用实现了相应接口对象。接口概念:接口不是类,而是对类的一组需求的... 查看详情
go语言之nil
在go语言中,nil是一个零值,如果我们在声明整数的时候没有为变量赋值,那么该整数的值默认是0,如果是一个指针没有明确的指向,它的值就是nil,除了指针,nil还标识切片、映射和接口的零值。如果对一个指针进行解引用程... 查看详情
关于java核心技术(卷一)读后的思考(equals方法的讨论)
这是我反复看了两遍才理解的部分。其中也不乏参考了他人的微博内容,才大致对这个方法有所理解。首先我们从Object类开始说起,书中已经说了Object类是Java中所有类的始祖,在Java中的每个类都是由他扩展而来的,但在现实使... 查看详情
关于java核心技术(卷一)读后的思考(泛型数组列表的讨论)
在C++中编译时是要确定数组大小的,而Java有所不同,它允许在运行时确定数组的大小。但是如果仅通过数组是无法改变运行时无法动态更改数组的问题。一旦确定了数组大小,就很难改变他了数组的大小了,要解决这个问题,... 查看详情
关于java核心技术(卷一)读后的思考(对象与类,日历的构造)
关于这本书,前三张都是基本内容,我觉得个人掌握的还可以,所以从第四章开始整理每日所学。第四章主要说的是对象和类。第一部分是面向对象程序设计的概述。其中面向对象程序设计简写OOP,接下来写的是类,即构造对象... 查看详情
关于java核心技术(卷一)读后的思考(lambda表达式的讨论)
lambda表达式lambda表达式是一个可传递的代码块。可以以后执行一次或者多次。在程序运行中我们经常会遇到传递代码段的情况,但是我们一般都是先构造一个对象,然后通过对象调用相应的代码块来实现传递代码块的目的,这个... 查看详情
关于java核心技术(卷一)读后的思考(用户自定义类,静态域和静态方法的思考以及方法参数)
用户自定义类:这部分并没有太过于困难的部分,借由代码进行复习:Employee类的定义:packagecom.java.EmployeeTest;importjava.time.*;publicclassEmployee{ privateStringname; privatedoublesalary; &nb 查看详情
go语言之类型转换
go语言的类型不能混合使用fmt.Println("abc"+1)会报字符串和整数不能这么操作se:=12.0wu:=5fmt.Println(se*wu)(mismatchedtypesfloat64andint)一个是float一个是int不允许操作例如上面,我们需要把wu这个变量类型转换为float64.fmt.Println(se*float64(wu))通过... 查看详情
读书列表
记录读书列表,促进阅读~蓝血十杰2017-04-20百年孤独(英文版)ThinkinginC++(英文版)卷一2017-04-26ThinkinginC++(英文版)卷二ReadingCodeJava核心技术卷一Java核心技术卷二重构 查看详情
面霸篇:高频java基础问题(核心卷一)(代码片段)
从面试题作为切入点提升大家的Java内功,所谓根基不牢,地动山摇。只有扎实的基础,才是写出写好代码。拒绝知识碎片化码哥在《Redis系列》的开篇Redis为什么这么快中说过:学习一个技术,通常只接触了零... 查看详情
go----go语言快速体验之开发环境搭建及第一个项目helloworld
文章目录一、Go开发环境搭建1.1下载安装Go1.2下载安装配置Goland编辑器二、使用Goland创建第一个项目2.1配置Go的版本号2.2创建HelloWorld项目一、Go开发环境搭建1.1下载安装Go(1)从... 查看详情