Đặc tả Ngôn ngữ Lập trình Go
Phiên bản ngôn ngữ go1.26 (12 tháng 1, 2026)
Giới thiệu
Đây là tài liệu tham chiếu cho ngôn ngữ lập trình Go. Để biết thêm thông tin và các tài liệu khác, xem go.dev.
Go là ngôn ngữ đa dụng được thiết kế với lập trình hệ thống trong tâm trí. Ngôn ngữ này có kiểu tĩnh mạnh, được quản lý bộ nhớ tự động (bộ gom rác) và hỗ trợ tường minh cho lập trình đồng thời. Các chương trình được xây dựng từ các gói, với các thuộc tính cho phép quản lý hiệu quả các phụ thuộc.
Cú pháp gọn gàng và dễ phân tích cú pháp, cho phép phân tích dễ dàng bởi các công cụ tự động như môi trường phát triển tích hợp.
Ký hiệu
Cú pháp được đặc tả bằng cách sử dụng một biến thể của Extended Backus-Naur Form (EBNF):
Syntax = { Production } .
Production = production_name "=" [ Expression ] "." .
Expression = Term { "|" Term } .
Term = Factor { Factor } .
Factor = production_name | token [ "…" token ] | Group | Option | Repetition .
Group = "(" Expression ")" .
Option = "[" Expression "]" .
Repetition = "{" Expression "}" .
Các production là các biểu thức được xây dựng từ các term và các toán tử sau đây, theo thứ tự độ ưu tiên tăng dần:
| alternation
() grouping
[] option (0 or 1 times)
{} repetition (0 to n times)
Tên production viết thường được dùng để định danh các token từ vựng (terminal).
Các non-terminal được viết theo dạng CamelCase. Các token từ vựng được đặt trong
dấu ngoặc kép "" hoặc dấu backtick ``.
Dạng a … b biểu diễn tập hợp các ký tự từ
a đến b như các lựa chọn thay thế. Dấu
chấm lửng ngang … cũng được sử dụng ở nơi khác trong đặc tả để biểu thị không chính thức các
liệt kê hoặc đoạn mã khác nhau mà không được chỉ định thêm. Ký tự …
(khác với ba ký tự ...) không phải là token của ngôn ngữ
Go.
Một liên kết có dạng [Go 1.xx] cho biết rằng một tính năng ngôn ngữ được mô tả (hoặc một khía cạnh nào đó của nó) đã được thay đổi hoặc thêm vào với phiên bản ngôn ngữ 1.xx và do đó yêu cầu tối thiểu phiên bản ngôn ngữ đó để biên dịch. Để biết chi tiết, xem phần được liên kết trong phụ lục.
Biểu diễn mã nguồn
Mã nguồn là văn bản Unicode được mã hóa bằng UTF-8. Văn bản không được chuẩn hóa, vì vậy một code point có dấu đơn lẻ khác với cùng ký tự đó được xây dựng từ việc kết hợp dấu và chữ cái; những ký tự đó được xử lý như hai code point. Để đơn giản, tài liệu này sẽ sử dụng thuật ngữ không có định ngữ ký tự để chỉ một Unicode code point trong văn bản nguồn.
Mỗi code point là riêng biệt; ví dụ, chữ hoa và chữ thường là các ký tự khác nhau.
Hạn chế cài đặt: Để tương thích với các công cụ khác, một trình biên dịch có thể không cho phép ký tự NUL (U+0000) trong văn bản nguồn.
Hạn chế cài đặt: Để tương thích với các công cụ khác, một trình biên dịch có thể bỏ qua dấu thứ tự byte được mã hóa UTF-8 (U+FEFF) nếu nó là Unicode code point đầu tiên trong văn bản nguồn. Dấu thứ tự byte có thể không được phép ở bất kỳ nơi nào khác trong nguồn.
Ký tự
Các thuật ngữ sau đây được sử dụng để biểu thị các danh mục ký tự Unicode cụ thể:
newline = /* the Unicode code point U+000A */ . unicode_char = /* an arbitrary Unicode code point except newline */ . unicode_letter = /* a Unicode code point categorized as "Letter" */ . unicode_digit = /* a Unicode code point categorized as "Number, decimal digit" */ .
Trong Tiêu chuẩn Unicode 8.0, Mục 4.5 "General Category" định nghĩa một tập hợp các danh mục ký tự. Go xử lý tất cả các ký tự trong bất kỳ danh mục Letter nào Lu, Ll, Lt, Lm, hoặc Lo như các chữ cái Unicode, và những ký tự trong danh mục Number Nd như các chữ số Unicode.
Chữ cái và chữ số
Ký tự gạch dưới _ (U+005F) được coi là chữ cái thường.
letter = unicode_letter | "_" . decimal_digit = "0" … "9" . binary_digit = "0" | "1" . octal_digit = "0" … "7" . hex_digit = "0" … "9" | "A" … "F" | "a" … "f" .
Các phần tử từ vựng
Chú thích
Chú thích đóng vai trò là tài liệu của chương trình. Có hai dạng:
-
Chú thích dòng bắt đầu bằng chuỗi ký tự
//và kết thúc ở cuối dòng. -
Chú thích tổng quát bắt đầu bằng chuỗi ký tự
/*và kết thúc tại chuỗi ký tự đầu tiên tiếp theo là*/.
Một chú thích không thể bắt đầu bên trong một rune hoặc chuỗi literal, hay bên trong một chú thích khác. Một chú thích tổng quát không chứa dòng mới nào sẽ hoạt động như một khoảng trắng. Bất kỳ chú thích nào khác đều hoạt động như một dòng mới.
Token
Token tạo nên từ vựng của ngôn ngữ Go. Có bốn lớp: định danh, từ khóa, toán tử và dấu câu / ký tự đặc biệt, và literal. Khoảng trắng, được tạo từ các dấu cách (U+0020), tab ngang (U+0009), ký tự xuống dòng (U+000D), và dòng mới (U+000A), bị bỏ qua trừ khi nó phân tách các token mà nếu không sẽ được kết hợp thành một token duy nhất. Ngoài ra, một dòng mới hoặc kết thúc tệp có thể kích hoạt việc chèn một dấu chấm phẩy. Khi phân tách đầu vào thành các token, token tiếp theo là chuỗi ký tự dài nhất tạo thành một token hợp lệ.
Dấu chấm phẩy
Cú pháp hình thức sử dụng dấu chấm phẩy ";" làm ký tự kết thúc trong
một số quy tắc sản xuất. Các chương trình Go có thể bỏ qua phần lớn các dấu chấm phẩy này
theo hai quy tắc sau:
-
Khi đầu vào được phân tách thành các token, một dấu chấm phẩy sẽ được tự động chèn
vào luồng token ngay sau token cuối cùng của một dòng nếu token đó là
- một định danh
- một literal số nguyên, số thực dấu phẩy động, ảo (phần ảo), rune, hoặc chuỗi
- một trong các từ khóa
break,continue,fallthrough, hoặcreturn - một trong các toán tử và dấu câu / ký tự đặc biệt
++,--,),], hoặc}
-
Để cho phép các câu lệnh phức tạp chiếm một dòng duy nhất, một dấu chấm phẩy
có thể được bỏ qua trước dấu đóng
")"hoặc"}".
Để phản ánh cách dùng thông thường, các ví dụ mã trong tài liệu này lược bỏ dấu chấm phẩy theo các quy tắc trên.
Định danh
Định danh đặt tên cho các thực thể trong chương trình như biến và kiểu dữ liệu. Một định danh là một chuỗi gồm một hoặc nhiều chữ cái và chữ số. Ký tự đầu tiên trong một định danh phải là một chữ cái.
identifier = letter { letter | unicode_digit } .
a _x9 ThisVariableIsExported αβ
Một số định danh được khai báo trước.
Từ khóa
Các từ khóa sau đây được đặt dành riêng và không thể dùng làm định danh.
break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var
Toán tử và dấu câu / ký tự đặc biệt
Các chuỗi ký tự sau đây biểu diễn toán tử (bao gồm toán tử gán) và dấu câu / ký tự đặc biệt [Go 1.18]:
+ & += &= && == != ( )
- | -= |= || < <= [ ]
* ^ *= ^= <- > >= { }
/ << /= <<= ++ = := , ;
% >> %= >>= -- ! ... . :
&^ &^= ~
Literal số nguyên
Một literal số nguyên là một chuỗi chữ số biểu diễn một
hằng số nguyên.
Một tiền tố tùy chọn đặt cơ số không phải thập phân: 0b hoặc 0B
cho nhị phân, 0, 0o, hoặc 0O cho bát phân,
và 0x hoặc 0X cho thập lục phân
[Go 1.13].
Một số 0 đơn lẻ được xem là số không thập phân.
Trong các literal thập lục phân, các chữ cái a đến f
và A đến F biểu diễn các giá trị từ 10 đến 15.
Để dễ đọc, một ký tự gạch dưới _ có thể xuất hiện sau
tiền tố cơ số hoặc giữa các chữ số liên tiếp; các dấu gạch dưới như vậy không thay đổi
giá trị của literal.
int_lit = decimal_lit | binary_lit | octal_lit | hex_lit .
decimal_lit = "0" | ( "1" … "9" ) [ [ "_" ] decimal_digits ] .
binary_lit = "0" ( "b" | "B" ) [ "_" ] binary_digits .
octal_lit = "0" [ "o" | "O" ] [ "_" ] octal_digits .
hex_lit = "0" ( "x" | "X" ) [ "_" ] hex_digits .
decimal_digits = decimal_digit { [ "_" ] decimal_digit } .
binary_digits = binary_digit { [ "_" ] binary_digit } .
octal_digits = octal_digit { [ "_" ] octal_digit } .
hex_digits = hex_digit { [ "_" ] hex_digit } .
42 4_2 0600 0_600 0o600 0O600 // second character is capital letter 'O' 0xBadFace 0xBad_Face 0x_67_7a_2f_cc_40_c6 170141183460469231731687303715884105727 170_141183_460469_231731_687303_715884_105727 _42 // an identifier, not an integer literal 42_ // invalid: _ must separate successive digits 4__2 // invalid: only one _ at a time 0_xBadFace // invalid: _ must separate successive digits
Literal số thực dấu phẩy động
Một literal số thực dấu phẩy động là biểu diễn thập phân hoặc thập lục phân của một hằng số thực dấu phẩy động.
Một literal số thực dấu phẩy động thập phân bao gồm phần nguyên (các chữ số thập phân),
dấu chấm thập phân, phần thập phân (các chữ số thập phân), và phần số mũ
(e hoặc E theo sau bởi dấu tùy chọn và các chữ số thập phân).
Phần nguyên hoặc phần thập phân có thể được lược bỏ; dấu chấm thập phân
hoặc phần số mũ có thể được lược bỏ.
Một giá trị số mũ exp chia tỷ lệ phần định trị (phần nguyên và phần thập phân) cho 10exp.
Một literal số thực dấu phẩy động thập lục phân bao gồm tiền tố 0x hoặc 0X,
phần nguyên (các chữ số thập lục phân), dấu chấm cơ số, phần thập phân (các chữ số thập lục phân),
và phần số mũ (p hoặc P theo sau bởi dấu tùy chọn và các chữ số thập phân).
Phần nguyên hoặc phần thập phân có thể được lược bỏ; dấu chấm cơ số cũng có thể được lược bỏ,
nhưng phần số mũ là bắt buộc. (Cú pháp này tương khớp với cú pháp trong IEEE 754-2008 §5.12.3.)
Một giá trị số mũ exp chia tỷ lệ phần định trị (phần nguyên và phần thập phân) cho 2exp
[Go 1.13].
Để dễ đọc, một ký tự gạch dưới _ có thể xuất hiện sau
tiền tố cơ số hoặc giữa các chữ số liên tiếp; các dấu gạch dưới như vậy không thay đổi
giá trị của literal.
float_lit = decimal_float_lit | hex_float_lit .
decimal_float_lit = decimal_digits "." [ decimal_digits ] [ decimal_exponent ] |
decimal_digits decimal_exponent |
"." decimal_digits [ decimal_exponent ] .
decimal_exponent = ( "e" | "E" ) [ "+" | "-" ] decimal_digits .
hex_float_lit = "0" ( "x" | "X" ) hex_mantissa hex_exponent .
hex_mantissa = [ "_" ] hex_digits "." [ hex_digits ] |
[ "_" ] hex_digits |
"." hex_digits .
hex_exponent = ( "p" | "P" ) [ "+" | "-" ] decimal_digits .
0. 72.40 072.40 // == 72.40 2.71828 1.e+0 6.67428e-11 1E6 .25 .12345E+5 1_5. // == 15.0 0.15e+0_2 // == 15.0 0x1p-2 // == 0.25 0x2.p10 // == 2048.0 0x1.Fp+0 // == 1.9375 0X.8p-0 // == 0.5 0X_1FFFP-16 // == 0.1249847412109375 0x15e-2 // == 0x15e - 2 (integer subtraction) 0x.p1 // invalid: mantissa has no digits 1p-2 // invalid: p exponent requires hexadecimal mantissa 0x1.5e-2 // invalid: hexadecimal mantissa requires p exponent 1_.5 // invalid: _ must separate successive digits 1._5 // invalid: _ must separate successive digits 1.5_e1 // invalid: _ must separate successive digits 1.5e_1 // invalid: _ must separate successive digits 1.5e1_ // invalid: _ must separate successive digits
Literal ảo (phần ảo)
Một literal ảo (phần ảo) biểu diễn phần ảo của một
hằng số phức.
Nó bao gồm một literal số nguyên hoặc
số thực dấu phẩy động
theo sau bởi chữ cái thường i.
Giá trị của một literal ảo (phần ảo) là giá trị của literal số nguyên hoặc số thực dấu phẩy động tương ứng nhân với đơn vị ảo i
[Go 1.13]
imaginary_lit = (decimal_digits | int_lit | float_lit) "i" .
Để tương thích ngược, phần nguyên của một literal ảo (phần ảo) chỉ gồm
các chữ số thập phân (và có thể có các dấu gạch dưới) được xem là số nguyên thập phân,
ngay cả khi nó bắt đầu bằng số 0 đứng đầu.
0i 0123i // == 123i for backward-compatibility 0o123i // == 0o123 * 1i == 83i 0xabci // == 0xabc * 1i == 2748i 0.i 2.71828i 1.e+0i 6.67428e-11i 1E6i .25i .12345E+5i 0x1p-2i // == 0x1p-2 * 1i == 0.25i
Literal rune
Một literal rune biểu diễn một hằng rune,
một giá trị số nguyên xác định một điểm mã Unicode.
Một literal rune được biểu diễn dưới dạng một hoặc nhiều ký tự được đặt trong dấu ngoặc đơn,
như trong 'x' hoặc '\n'.
Bên trong dấu ngoặc, bất kỳ ký tự nào cũng có thể xuất hiện ngoại trừ dòng mới và dấu ngoặc đơn không được thoát. Một ký tự trong ngoặc đơn biểu diễn giá trị Unicode
của chính ký tự đó,
trong khi các chuỗi nhiều ký tự bắt đầu bằng dấu gạch chéo ngược mã hóa
các giá trị theo nhiều định dạng khác nhau.
Dạng đơn giản nhất biểu diễn ký tự đơn trong ngoặc;
vì văn bản nguồn Go là các ký tự Unicode được mã hóa theo UTF-8, nhiều
byte được mã hóa UTF-8 có thể biểu diễn một giá trị số nguyên duy nhất. Chẳng hạn,
literal 'a' chứa một byte đơn biểu diễn
literal a, Unicode U+0061, giá trị 0x61, trong khi
'ä' chứa hai byte (0xc3 0xa4) biểu diễn
literal a-dieresis, U+00E4, giá trị 0xe4.
Một số chuỗi thoát dấu gạch chéo ngược cho phép các giá trị tùy ý được mã hóa dưới dạng
văn bản ASCII. Có bốn cách để biểu diễn giá trị số nguyên
dưới dạng hằng số: \x theo sau bởi chính xác hai chữ số thập lục phân;
\u theo sau bởi chính xác bốn chữ số thập lục phân;
\U theo sau bởi chính xác tám chữ số thập lục phân, và một
dấu gạch chéo ngược đơn \ theo sau bởi chính xác ba chữ số bát phân.
Trong mỗi trường hợp, giá trị của literal là giá trị được biểu diễn bởi
các chữ số trong cơ số tương ứng.
Mặc dù tất cả các cách biểu diễn này đều cho ra một số nguyên, chúng có
các phạm vi hợp lệ khác nhau. Chuỗi thoát bát phân phải biểu diễn giá trị từ
0 đến 255 bao gồm hai đầu. Chuỗi thoát thập lục phân thỏa mãn điều kiện này
theo cấu trúc. Các chuỗi thoát \u và \U
biểu diễn các điểm mã Unicode nên bên trong chúng một số giá trị là bất hợp lệ,
đặc biệt là những giá trị trên 0x10FFFF và các nửa surrogate.
Sau một dấu gạch chéo ngược, một số chuỗi thoát ký tự đơn biểu diễn các giá trị đặc biệt:
\a U+0007 alert or bell \b U+0008 backspace \f U+000C form feed \n U+000A line feed or newline \r U+000D carriage return \t U+0009 horizontal tab \v U+000B vertical tab \\ U+005C backslash \' U+0027 single quote (valid escape only within rune literals) \" U+0022 double quote (valid escape only within string literals)
Một ký tự không được nhận dạng theo sau dấu gạch chéo ngược trong một literal rune là bất hợp lệ.
rune_lit = "'" ( unicode_value | byte_value ) "'" .
unicode_value = unicode_char | little_u_value | big_u_value | escaped_char .
byte_value = octal_byte_value | hex_byte_value .
octal_byte_value = `\` octal_digit octal_digit octal_digit .
hex_byte_value = `\` "x" hex_digit hex_digit .
little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit .
big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit
hex_digit hex_digit hex_digit hex_digit .
escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) .
'a' 'ä' '本' '\t' '\000' '\007' '\377' '\x07' '\xff' '\u12e4' '\U00101234' '\'' // rune literal containing single quote character 'aa' // illegal: too many characters '\k' // illegal: k is not recognized after a backslash '\xa' // illegal: too few hexadecimal digits '\0' // illegal: too few octal digits '\400' // illegal: octal value over 255 '\uDFFF' // illegal: surrogate half '\U00110000' // illegal: invalid Unicode code point
Literal chuỗi
Một literal chuỗi biểu diễn một hằng chuỗi thu được từ việc nối một chuỗi các ký tự. Có hai dạng: chuỗi thô literal và chuỗi có diễn giải literal.
Chuỗi thô literal là các chuỗi ký tự nằm giữa các dấu ngoặc ngược, như trong
`foo`. Bên trong dấu ngoặc, bất kỳ ký tự nào cũng có thể xuất hiện ngoại trừ
dấu ngoặc ngược. Giá trị của một chuỗi thô literal là
chuỗi bao gồm các ký tự không được thông dịch (được mã hóa ngầm định theo UTF-8)
nằm giữa các dấu ngoặc;
đặc biệt, dấu gạch chéo ngược không có ý nghĩa đặc biệt và chuỗi có thể
chứa các dòng mới.
Các ký tự xuống dòng ('\r') bên trong chuỗi thô literal
bị loại bỏ khỏi giá trị chuỗi thô.
Chuỗi có diễn giải literal là các chuỗi ký tự nằm giữa các dấu ngoặc kép,
như trong "bar".
Bên trong dấu ngoặc, bất kỳ ký tự nào cũng có thể xuất hiện ngoại trừ dòng mới và dấu ngoặc kép không được thoát.
Văn bản giữa các dấu ngoặc tạo thành
giá trị của literal, với các chuỗi thoát dấu gạch chéo ngược được thông dịch như chúng
được trong literal rune (ngoại trừ \' là bất hợp lệ và
\" là hợp lệ), với các hạn chế tương tự.
Chuỗi thoát bát phân ba chữ số (\nnn)
và chuỗi thoát thập lục phân hai chữ số (\xnn) biểu diễn các
byte riêng lẻ của chuỗi kết quả; tất cả các chuỗi thoát khác biểu diễn
mã hóa UTF-8 (có thể nhiều byte) của các ký tự riêng lẻ.
Vì vậy bên trong một literal chuỗi \377 và \xFF biểu diễn
một byte đơn với giá trị 0xFF=255, trong khi ÿ,
\u00FF, \U000000FF và \xc3\xbf biểu diễn
hai byte 0xc3 0xbf của mã hóa UTF-8 của ký tự
U+00FF.
string_lit = raw_string_lit | interpreted_string_lit .
raw_string_lit = "`" { unicode_char | newline } "`" .
interpreted_string_lit = `"` { unicode_value | byte_value } `"` .
`abc` // same as "abc" `\n \n` // same as "\\n\n\\n" "\n" "\"" // same as `"` "Hello, world!\n" "日本語" "\u65e5本\U00008a9e" "\xff\u00FF" "\uD800" // illegal: surrogate half "\U00110000" // illegal: invalid Unicode code point
Tất cả các ví dụ này đều biểu diễn cùng một chuỗi:
"日本語" // UTF-8 input text `日本語` // UTF-8 input text as a raw literal "\u65e5\u672c\u8a9e" // the explicit Unicode code points "\U000065e5\U0000672c\U00008a9e" // the explicit Unicode code points "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e" // the explicit UTF-8 bytes
Nếu mã nguồn biểu diễn một ký tự dưới dạng hai điểm mã, chẳng hạn như một dạng kết hợp bao gồm dấu trọng âm và một chữ cái, kết quả sẽ là lỗi nếu được đặt trong một literal rune (nó không phải là một điểm mã duy nhất), và sẽ xuất hiện dưới dạng hai điểm mã nếu được đặt trong một literal chuỗi.
Hằng số
Có các hằng số boolean, hằng số rune, hằng số số nguyên, hằng số số thực dấu phẩy động, hằng số số phức, và hằng số chuỗi. Hằng số rune, số nguyên, số thực dấu phẩy động, và số phức được gọi chung là hằng số số học.
Một giá trị hằng số được biểu diễn bởi một literal
rune,
số nguyên,
số thực dấu phẩy động,
số ảo,
hoặc
chuỗi,
một định danh biểu thị một hằng số,
một biểu thức hằng số,
một phép chuyển đổi kiểu có kết quả là hằng số, hoặc
giá trị kết quả của một số hàm dựng sẵn như
min hoặc max được áp dụng với các đối số hằng số,
unsafe.Sizeof được áp dụng với một số giá trị nhất định,
cap hoặc len được áp dụng với
một số biểu thức,
real và imag được áp dụng với hằng số số phức
và complex được áp dụng với các hằng số số học.
Các giá trị boolean true (đúng) và false (sai) được biểu diễn bởi các hằng số được khai báo trước
true và false. Định danh được khai báo trước
iota biểu thị một hằng số số nguyên.
Nhìn chung, các hằng số số phức là một dạng biểu thức hằng số và được thảo luận trong phần đó.
Các hằng số số học biểu diễn các giá trị chính xác với độ chính xác tùy ý và không bị tràn số. Do đó, không có hằng số nào biểu thị giá trị số không âm IEEE 754, vô cực, và các giá trị không phải số (NaN).
Hằng số có thể có kiểu tường minh hoặc không có kiểu tường minh.
Các literal hằng số, true, false, iota,
và một số biểu thức hằng số
chỉ chứa các toán hạng hằng số không có kiểu tường minh đều không có kiểu tường minh.
Một hằng số có thể được gán kiểu tường minh thông qua một khai báo hằng số hoặc phép chuyển đổi kiểu, hoặc ngầm định khi được sử dụng trong khai báo biến hoặc một câu lệnh gán hoặc làm toán hạng trong một biểu thức. Đây là lỗi nếu giá trị hằng số không thể được biểu diễn như một giá trị của kiểu tương ứng. Nếu kiểu là một tham số kiểu, hằng số được chuyển đổi thành một giá trị không phải hằng số của tham số kiểu.
Một hằng số không có kiểu tường minh có một kiểu mặc định, đó là kiểu mà
hằng số được ngầm chuyển đổi sang trong các ngữ cảnh yêu cầu một giá trị có kiểu tường minh,
ví dụ như trong một khai báo biến ngắn
như i := 0 khi không có kiểu tường minh nào được chỉ định.
Kiểu mặc định của một hằng số không có kiểu tường minh là bool, rune,
int, float64, complex128, hoặc string
tương ứng, tùy thuộc vào việc đó là hằng số boolean, rune, số nguyên, số thực dấu phẩy động,
số phức, hay chuỗi.
Giới hạn triển khai: Mặc dù các hằng số số học có độ chính xác tùy ý trong ngôn ngữ, một trình biên dịch có thể triển khai chúng bằng cách sử dụng biểu diễn nội bộ với độ chính xác hạn chế. Tuy nhiên, mọi triển khai phải:
- Biểu diễn các hằng số số nguyên với ít nhất 256 bit.
- Biểu diễn các hằng số số thực dấu phẩy động, bao gồm các phần của một hằng số số phức, với phần định trị ít nhất 256 bit và số mũ nhị phân có dấu ít nhất 16 bit.
- Báo lỗi nếu không thể biểu diễn chính xác một hằng số số nguyên.
- Báo lỗi nếu không thể biểu diễn một hằng số số thực dấu phẩy động hoặc số phức do tràn số.
- Làm tròn đến hằng số có thể biểu diễn gần nhất nếu không thể biểu diễn một hằng số số thực dấu phẩy động hoặc số phức do giới hạn về độ chính xác.
Các yêu cầu này áp dụng cho cả literal hằng số và kết quả của việc đánh giá biểu thức hằng số.
Biến
Một biến là một vị trí lưu trữ để chứa một giá trị. Tập hợp các giá trị được phép được xác định bởi kiểu của biến.
Một khai báo biến
hoặc, đối với các tham số và kết quả của hàm, chữ ký
của một khai báo hàm
hoặc literal hàm dành riêng
vùng lưu trữ cho một biến được đặt tên.
Gọi hàm dựng sẵn new
hoặc lấy địa chỉ của một composite literal
sẽ cấp phát vùng lưu trữ cho một biến tại thời điểm chạy.
Biến ẩn danh như vậy được tham chiếu thông qua một
phép hướng dẫn con trỏ (có thể ngầm định).
Các biến có cấu trúc của các kiểu mảng, slice, và struct có các phần tử và trường có thể được lấy địa chỉ riêng lẻ. Mỗi phần tử như vậy hoạt động như một biến.
Kiểu tĩnh (hay chỉ là kiểu) của một biến là
kiểu được khai báo trong khai báo của nó, kiểu được cung cấp trong lời gọi
new hoặc composite literal, hoặc kiểu của
một phần tử của biến có cấu trúc.
Các biến thuộc kiểu interface cũng có một kiểu động riêng biệt,
đó là kiểu (không phải interface) của giá trị được gán cho biến tại thời điểm chạy
(trừ khi giá trị là định danh được khai báo trước nil,
không có kiểu).
Kiểu động có thể thay đổi trong quá trình thực thi nhưng các giá trị được lưu trữ trong
các biến interface luôn có thể gán
cho kiểu tĩnh của biến.
var x interface{} // x is nil and has static type interface{}
var v *T // v has value nil, static type *T
x = 42 // x has value 42 and dynamic type int
x = v // x has value (*T)(nil) and dynamic type *T
Giá trị của một biến được lấy ra bằng cách tham chiếu biến đó trong một biểu thức; đó là giá trị gần nhất được gán cho biến. Nếu một biến chưa được gán giá trị, giá trị của nó là giá trị không (zero value) cho kiểu của nó.
Kiểu
Một kiểu xác định một tập hợp các giá trị cùng với các phép toán và phương thức đặc thù cho những giá trị đó. Một kiểu có thể được biểu thị bằng một tên kiểu, nếu có, và phải được theo sau bởi đối số kiểu nếu kiểu đó là generic. Một kiểu cũng có thể được chỉ định bằng một literal kiểu, kết hợp một kiểu từ các kiểu hiện có.
Type = TypeName [ TypeArgs ] | TypeLit | "(" Type ")" .
TypeName = identifier | QualifiedIdent .
TypeArgs = "[" TypeList [ "," ] "]" .
TypeList = Type { "," Type } .
TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType |
SliceType | MapType | ChannelType .
Ngôn ngữ khai báo trước một số tên kiểu nhất định. Các tên khác được giới thiệu qua khai báo kiểu hoặc danh sách tham số kiểu. Kiểu tổng hợp—mảng, struct, con trỏ, hàm, interface, slice, map, và channel—có thể được xây dựng bằng cách sử dụng các literal kiểu.
Các kiểu được khai báo trước, kiểu đã định nghĩa, và tham số kiểu được gọi là kiểu được đặt tên. Một bí danh (alias) biểu thị một kiểu được đặt tên nếu kiểu được khai báo trong khai báo bí danh đó là một kiểu được đặt tên.
Kiểu boolean
Một kiểu boolean biểu diễn tập hợp các giá trị boolean true (đúng) và false (sai)
được biểu thị bởi các hằng số được khai báo trước true
và false. Kiểu boolean được khai báo trước là bool;
đây là một kiểu đã định nghĩa.
Kiểu số học
Một kiểu số nguyên, số thực dấu phẩy động, hoặc số phức biểu diễn tập hợp các giá trị số nguyên, số thực dấu phẩy động, hoặc số phức tương ứng. Chúng được gọi chung là kiểu số học. Các kiểu số học được khai báo trước không phụ thuộc vào kiến trúc là:
uint8 the set of all unsigned 8-bit integers (0 to 255) uint16 the set of all unsigned 16-bit integers (0 to 65535) uint32 the set of all unsigned 32-bit integers (0 to 4294967295) uint64 the set of all unsigned 64-bit integers (0 to 18446744073709551615) int8 the set of all signed 8-bit integers (-128 to 127) int16 the set of all signed 16-bit integers (-32768 to 32767) int32 the set of all signed 32-bit integers (-2147483648 to 2147483647) int64 the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807) float32 the set of all IEEE 754 32-bit floating-point numbers float64 the set of all IEEE 754 64-bit floating-point numbers complex64 the set of all complex numbers with float32 real and imaginary parts complex128 the set of all complex numbers with float64 real and imaginary parts byte alias for uint8 rune alias for int32
Giá trị của một số nguyên n-bit có độ rộng n bit và được biểu diễn bằng cách sử dụng số học bù hai.
Ngoài ra còn có một tập hợp các kiểu số nguyên được khai báo trước với kích thước phụ thuộc vào triển khai:
uint either 32 or 64 bits int same size as uint uintptr an unsigned integer large enough to store the uninterpreted bits of a pointer value
Để tránh các vấn đề về tính di động, tất cả các kiểu số học đều là kiểu
đã định nghĩa và do đó phân biệt với nhau, ngoại trừ
byte, là một bí danh cho uint8, và
rune, là một bí danh cho int32.
Cần có các phép chuyển đổi tường minh
khi các kiểu số học khác nhau được trộn lẫn trong một biểu thức
hoặc phép gán. Ví dụ, int32 và int
không phải là cùng một kiểu mặc dù chúng có thể có cùng kích thước trên một
kiến trúc cụ thể.
Kiểu chuỗi
Một kiểu chuỗi biểu diễn tập hợp các giá trị chuỗi.
Một giá trị chuỗi là một dãy (có thể rỗng) các byte.
Số byte được gọi là độ dài của chuỗi và không bao giờ âm.
Chuỗi là bất biến: một khi đã được tạo,
không thể thay đổi nội dung của một chuỗi.
Kiểu chuỗi được khai báo trước là string;
đây là một kiểu đã định nghĩa.
Độ dài của một chuỗi s có thể được tìm ra bằng cách sử dụng
hàm dựng sẵn len.
Độ dài là hằng số tại thời điểm biên dịch nếu chuỗi là một hằng số.
Các byte của chuỗi có thể được truy cập bằng chỉ số số nguyên
từ 0 đến len(s)-1.
Không được phép lấy địa chỉ của phần tử như vậy; nếu
s[i] là byte thứ i của một
chuỗi, &s[i] là không hợp lệ.
Kiểu mảng
Một mảng là một dãy các phần tử được đánh số của một kiểu duy nhất, gọi là kiểu phần tử. Số phần tử được gọi là độ dài của mảng và không bao giờ âm.
ArrayType = "[" ArrayLength "]" ElementType . ArrayLength = Expression . ElementType = Type .
Độ dài là một phần của kiểu mảng; nó phải ước tính ra một
hằng số không âm
có thể biểu diễn bằng một giá trị
của kiểu int.
Độ dài của mảng a có thể được tìm ra
bằng cách sử dụng hàm dựng sẵn len.
Các phần tử có thể được lấy địa chỉ bằng chỉ số số nguyên
từ 0 đến len(a)-1.
Kiểu mảng luôn là một chiều nhưng có thể được kết hợp để tạo thành
các kiểu đa chiều.
[32]byte
[2*N] struct { x, y int32 }
[1000]*float64
[3][5]int
[2][2][2]float64 // same as [2]([2]([2]float64))
Một kiểu mảng T không được có phần tử thuộc kiểu T,
hoặc thuộc kiểu chứa T như một thành phần, trực tiếp hay gián tiếp,
nếu các kiểu chứa đó chỉ là kiểu mảng hoặc struct.
// invalid array types
type (
T1 [10]T1 // element type of T1 is T1
T2 [10]struct{ f T2 } // T2 contains T2 as component of a struct
T3 [10]T4 // T3 contains T3 as component of a struct in T4
T4 struct{ f T3 } // T4 contains T4 as component of array T3 in a struct
)
// valid array types
type (
T5 [10]*T5 // T5 contains T5 as component of a pointer
T6 [10]func() T6 // T6 contains T6 as component of a function type
T7 [10]struct{ f []T7 } // T7 contains T7 as component of a slice in a struct
)
Kiểu slice
Một slice là một bộ mô tả cho một đoạn liên tiếp của một mảng nền và
cung cấp quyền truy cập vào một dãy các phần tử được đánh số từ mảng đó.
Một kiểu slice biểu thị tập hợp tất cả các slice của các mảng thuộc kiểu phần tử của nó.
Số phần tử được gọi là độ dài của slice và không bao giờ âm.
Giá trị của một slice chưa được khởi tạo là nil.
SliceType = "[" "]" ElementType .
Độ dài của một slice s có thể được tìm ra bằng hàm dựng sẵn
len; khác với mảng, nó có thể thay đổi trong quá trình
thực thi. Các phần tử có thể được lấy địa chỉ bằng chỉ số số nguyên
từ 0 đến len(s)-1. Chỉ số slice của một
phần tử nhất định có thể nhỏ hơn chỉ số của cùng phần tử đó trong
mảng nền.
Một slice, sau khi được khởi tạo, luôn liên kết với một mảng nền chứa các phần tử của nó. Do đó, một slice chia sẻ vùng lưu trữ với mảng của nó và với các slice khác của cùng mảng; ngược lại, các mảng khác nhau luôn biểu diễn các vùng lưu trữ khác nhau.
Mảng nền của một slice có thể mở rộng ra ngoài phần cuối của slice.
Dung lượng là số đo cho mức độ mở rộng đó: đó là tổng của
độ dài của slice và độ dài của mảng vượt ra ngoài slice;
một slice có độ dài tối đa bằng dung lượng đó có thể được tạo bằng cách
slicing một slice mới từ slice gốc.
Dung lượng của một slice a có thể được tìm ra bằng cách sử dụng
hàm dựng sẵn cap(a).
Một giá trị slice mới, đã được khởi tạo cho một kiểu phần tử nhất định T có thể được
tạo bằng hàm dựng sẵn
make,
nhận vào một kiểu slice
và các tham số chỉ định độ dài và tùy chọn dung lượng.
Một slice được tạo bằng make luôn cấp phát một mảng mới, ẩn
mà giá trị slice trả về tham chiếu đến. Tức là, việc thực thi
make([]T, length, capacity)
tạo ra cùng một slice như việc cấp phát một mảng và slicing nó, vì vậy hai biểu thức này là tương đương:
make([]int, 50, 100) new([100]int)[0:50]
Giống như mảng, slice luôn là một chiều nhưng có thể được kết hợp để xây dựng các đối tượng nhiều chiều. Với các mảng của mảng, các mảng bên trong, theo cấu trúc, luôn có cùng độ dài; tuy nhiên với các slice của slice (hoặc mảng của slice), các độ dài bên trong có thể thay đổi động. Hơn nữa, các slice bên trong phải được khởi tạo riêng lẻ.
Kiểu struct
Một struct là một dãy các phần tử được đặt tên, gọi là các trường, mỗi trường có tên và một kiểu. Tên trường có thể được chỉ định tường minh (IdentifierList) hoặc ngầm định (EmbeddedField). Trong một struct, các tên trường không phải blank phải duy nhất.
StructType = "struct" "{" { FieldDecl ";" } "}" .
FieldDecl = (IdentifierList Type | EmbeddedField) [ Tag ] .
EmbeddedField = [ "*" ] TypeName [ TypeArgs ] .
Tag = string_lit .
// An empty struct.
struct {}
// A struct with 6 fields.
struct {
x, y int
u float32
_ float32 // padding
A *[]int
F func()
}
Một trường được khai báo với kiểu nhưng không có tên trường tường minh được gọi là trường nhúng.
Một trường nhúng phải được chỉ định là
một tên kiểu T hoặc là con trỏ đến một tên kiểu không phải interface *T,
và T bản thân không được là
kiểu con trỏ hoặc tham số kiểu. Tên kiểu không đủ điều kiện đóng vai trò là tên trường.
// A struct with four embedded fields of types T1, *T2, P.T3 and *P.T4
struct {
T1 // field name is T1
*T2 // field name is T2
P.T3 // field name is T3
*P.T4 // field name is T4
x, y int // field names are x and y
}
Khai báo sau đây là bất hợp lệ vì tên trường phải là duy nhất trong một kiểu struct:
struct {
T // conflicts with embedded field *T and *P.T
*T // conflicts with embedded field T and *P.T
*P.T // conflicts with embedded field T and *T
}
Một trường hoặc phương thức f của một
trường nhúng trong một struct x được gọi là được thăng cấp nếu
x.f là một bộ chọn hợp lệ biểu thị
trường hoặc phương thức f đó.
Các trường được thăng cấp hoạt động như các trường thông thường của một struct ngoại trừ chúng không thể được sử dụng như tên trường trong composite literal của struct.
Cho một kiểu struct S và một tên kiểu
T, các phương thức được thăng cấp được bao gồm trong tập phương thức của struct như sau:
-
Nếu
Schứa một trường nhúngT, tập phương thức củaSvà*Sđều bao gồm các phương thức được thăng cấp với receiverT. Tập phương thức của*Scũng bao gồm các phương thức được thăng cấp với receiver*T. -
Nếu
Schứa một trường nhúng*T, tập phương thức củaSvà*Sđều bao gồm các phương thức được thăng cấp với receiverThoặc*T.
Một khai báo trường có thể được theo sau bởi một string literal tag tùy chọn, trở thành một thuộc tính cho tất cả các trường trong khai báo trường tương ứng. Một chuỗi tag rỗng tương đương với không có tag. Các tag được hiển thị thông qua một giao diện reflection và tham gia vào nhận dạng kiểu cho các struct nhưng bị bỏ qua trong các trường hợp khác.
struct {
x, y float64 "" // an empty tag string is like an absent tag
name string "any string is permitted as a tag"
_ [4]byte "ceci n'est pas un champ de structure"
}
// A struct corresponding to a TimeStamp protocol buffer.
// The tag strings define the protocol buffer field numbers;
// they follow the convention outlined by the reflect package.
struct {
microsec uint64 `protobuf:"1"`
serverIP6 uint64 `protobuf:"2"`
}
Một kiểu struct T không được chứa một trường thuộc kiểu T,
hoặc thuộc kiểu chứa T như một thành phần, trực tiếp hay gián tiếp,
nếu các kiểu chứa đó chỉ là kiểu mảng hoặc struct.
// invalid struct types
type (
T1 struct{ T1 } // T1 contains a field of T1
T2 struct{ f [10]T2 } // T2 contains T2 as component of an array
T3 struct{ T4 } // T3 contains T3 as component of an array in struct T4
T4 struct{ f [10]T3 } // T4 contains T4 as component of struct T3 in an array
)
// valid struct types
type (
T5 struct{ f *T5 } // T5 contains T5 as component of a pointer
T6 struct{ f func() T6 } // T6 contains T6 as component of a function type
T7 struct{ f [10][]T7 } // T7 contains T7 as component of a slice in an array
)
Kiểu con trỏ
Một kiểu con trỏ biểu thị tập hợp tất cả các con trỏ đến biến của một
kiểu nhất định, gọi là kiểu cơ sở của con trỏ.
Giá trị của một con trỏ chưa được khởi tạo là nil.
PointerType = "*" BaseType . BaseType = Type .
*Point *[4]int
Kiểu hàm
Một kiểu hàm biểu thị tập hợp tất cả các hàm với cùng kiểu tham số và kết quả.
Giá trị của một biến chưa được khởi tạo thuộc kiểu hàm
là nil.
FunctionType = "func" Signature .
Signature = Parameters [ Result ] .
Result = Parameters | Type .
Parameters = "(" [ ParameterList [ "," ] ] ")" .
ParameterList = ParameterDecl { "," ParameterDecl } .
ParameterDecl = [ IdentifierList ] [ "..." ] Type .
Trong một danh sách các tham số hoặc kết quả, các tên (IdentifierList) phải đều có hoặc đều vắng mặt. Nếu có, mỗi tên đại diện cho một mục (tham số hoặc kết quả) của kiểu được chỉ định và tất cả các tên không phải blank trong chữ ký phải duy nhất. Nếu vắng mặt, mỗi kiểu đại diện cho một mục của kiểu đó. Danh sách tham số và kết quả luôn được đặt trong dấu ngoặc đơn ngoại trừ khi có chính xác một kết quả không được đặt tên, nó có thể được viết dưới dạng một kiểu không có dấu ngoặc đơn.
Tham số cuối cùng trong chữ ký hàm có thể có
kiểu có tiền tố ....
Một hàm với tham số như vậy được gọi là variadic và
có thể được gọi với không hoặc nhiều đối số cho tham số đó.
func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefix string, values ...int)
func(a, b int, z float64, opt ...interface{}) (success bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)
Kiểu interface
Một kiểu interface định nghĩa một tập hợp kiểu.
Một biến thuộc kiểu interface có thể lưu trữ một giá trị của bất kỳ kiểu nào nằm trong tập hợp kiểu
của interface. Kiểu như vậy được gọi là
triển khai interface.
Giá trị của một biến chưa được khởi tạo thuộc kiểu
interface là nil.
InterfaceType = "interface" "{" { InterfaceElem ";" } "}" .
InterfaceElem = MethodElem | TypeElem .
MethodElem = MethodName Signature .
MethodName = identifier .
TypeElem = TypeTerm { "|" TypeTerm } .
TypeTerm = Type | UnderlyingType .
UnderlyingType = "~" Type .
Một kiểu interface được chỉ định bởi một danh sách các phần tử interface. Một phần tử interface là một phương thức hoặc một phần tử kiểu, trong đó một phần tử kiểu là hợp của một hoặc nhiều phần tử kiểu (type terms). Một phần tử kiểu (type term) là một kiểu đơn hoặc một kiểu nền đơn.
Interface cơ bản
Ở dạng cơ bản nhất, một interface chỉ định một danh sách (có thể rỗng) các phương thức. Tập hợp kiểu được định nghĩa bởi interface như vậy là tập hợp các kiểu triển khai tất cả các phương thức đó, và tập phương thức tương ứng bao gồm chính xác các phương thức được chỉ định bởi interface. Các interface có tập hợp kiểu có thể được định nghĩa hoàn toàn bằng danh sách phương thức được gọi là interface cơ bản.
// A simple File interface.
interface {
Read([]byte) (int, error)
Write([]byte) (int, error)
Close() error
}
Tên của mỗi phương thức được chỉ định tường minh phải duy nhất và không phải blank.
interface {
String() string
String() string // illegal: String not unique
_(x int) // illegal: method must have non-blank name
}
Nhiều hơn một kiểu có thể triển khai một interface.
Ví dụ, nếu hai kiểu S1 và S2
có tập phương thức
func (p T) Read(p []byte) (n int, err error) func (p T) Write(p []byte) (n int, err error) func (p T) Close() error
(trong đó T đứng cho S1 hoặc S2)
thì interface File được triển khai bởi cả S1 và
S2, bất kể S1 và S2 có hoặc chia sẻ
những phương thức nào khác.
Mọi kiểu là thành viên của tập hợp kiểu của một interface đều triển khai interface đó. Bất kỳ kiểu nhất định nào cũng có thể triển khai nhiều interface khác nhau. Ví dụ, tất cả các kiểu đều triển khai interface rỗng biểu diễn tập hợp tất cả các kiểu (không phải interface):
interface{}
Để tiện lợi, kiểu được khai báo trước any là một bí danh cho interface rỗng.
[Go 1.18]
Tương tự, xem xét đặc tả interface này,
xuất hiện trong một khai báo kiểu
để định nghĩa một interface có tên Locker:
type Locker interface {
Lock()
Unlock()
}
Nếu S1 và S2 cũng triển khai
func (p T) Lock() { … }
func (p T) Unlock() { … }
chúng triển khai interface Locker cũng như
interface File.
Interface nhúng
Ở dạng tổng quát hơn một chút,
một interface T có thể sử dụng một tên kiểu interface (có thể đủ điều kiện)
E như một phần tử interface. Điều này được gọi là
nhúng interface E vào T
[Go 1.14].
Tập hợp kiểu của T là giao của các tập hợp kiểu
được định nghĩa bởi các phương thức được khai báo tường minh của T và các tập hợp kiểu
của các interface được nhúng trong T.
Nói cách khác, tập hợp kiểu của T là tập hợp tất cả các kiểu triển khai tất cả các
phương thức được khai báo tường minh của T và cũng tất cả các phương thức của
E
[Go 1.18].
type Reader interface {
Read(p []byte) (n int, err error)
Close() error
}
type Writer interface {
Write(p []byte) (n int, err error)
Close() error
}
// ReadWriter's methods are Read, Write, and Close.
type ReadWriter interface {
Reader // includes methods of Reader in ReadWriter's method set
Writer // includes methods of Writer in ReadWriter's method set
}
Khi nhúng các interface, các phương thức có cùng tên phải có chữ ký giống hệt nhau.
type ReadCloser interface {
Reader // includes methods of Reader in ReadCloser's method set
Close() // illegal: signatures of Reader.Close and Close are different
}
Interface tổng quát
Ở dạng tổng quát nhất, một phần tử interface cũng có thể là một phần tử kiểu tùy ý
T, hoặc một phần tử có dạng ~T chỉ định kiểu nền T,
hoặc một hợp của các phần tử t1|t2|…|tn
[Go 1.18].
Cùng với các đặc tả phương thức, các phần tử này cho phép định nghĩa chính xác
tập hợp kiểu của một interface như sau:
- Tập hợp kiểu của interface rỗng là tập hợp tất cả các kiểu không phải interface.
- Tập hợp kiểu của một interface không rỗng là giao của các tập hợp kiểu của các phần tử interface của nó.
- Tập hợp kiểu của một đặc tả phương thức là tập hợp tất cả các kiểu không phải interface có tập phương thức bao gồm phương thức đó.
- Tập hợp kiểu của một phần tử kiểu không phải interface là tập hợp chỉ bao gồm chính kiểu đó.
- Tập hợp kiểu của một phần tử có dạng
~Tlà tập hợp tất cả các kiểu có kiểu nền làT. - Tập hợp kiểu của một hợp các phần tử
t1|t2|…|tnlà hợp của các tập hợp kiểu của các phần tử.
Cách diễn đạt "tập hợp tất cả các kiểu không phải interface" đề cập không chỉ đến tất cả các kiểu (không phải interface) được khai báo trong chương trình hiện tại, mà đến tất cả các kiểu có thể có trong tất cả các chương trình có thể có, và do đó là vô hạn. Tương tự, cho tập hợp tất cả các kiểu không phải interface triển khai một phương thức cụ thể, giao của các tập phương thức của những kiểu đó sẽ chứa chính xác phương thức đó, ngay cả khi tất cả các kiểu trong chương trình hiện tại luôn ghép phương thức đó với một phương thức khác.
Theo cấu trúc, tập hợp kiểu của một interface không bao giờ chứa kiểu interface.
// An interface representing only the type int.
interface {
int
}
// An interface representing all types with underlying type int.
interface {
~int
}
// An interface representing all types with underlying type int that implement the String method.
interface {
~int
String() string
}
// An interface representing an empty type set: there is no type that is both an int and a string.
interface {
int
string
}
Trong một phần tử có dạng ~T, kiểu nền của T
phải chính là nó, và T không được là một interface.
type MyInt int
interface {
~[]byte // the underlying type of []byte is itself
~MyInt // illegal: the underlying type of MyInt is not MyInt
~error // illegal: error is an interface
}
Các phần tử hợp biểu thị hợp của các tập hợp kiểu:
// The Float interface represents all floating-point types
// (including any named types whose underlying types are
// either float32 or float64).
type Float interface {
~float32 | ~float64
}
Kiểu T trong một phần tử có dạng T hoặc ~T không được
là một tham số kiểu, và các tập hợp kiểu của tất cả
các phần tử không phải interface phải từng đôi một không giao nhau (giao từng đôi của các tập hợp kiểu phải rỗng).
Cho một tham số kiểu P:
interface {
P // illegal: P is a type parameter
int | ~P // illegal: P is a type parameter
~int | MyInt // illegal: the type sets for ~int and MyInt are not disjoint (~int includes MyInt)
float32 | Float // overlapping type sets but Float is an interface
}
Giới hạn triển khai:
Một hợp (với nhiều hơn một phần tử) không được chứa
định danh được khai báo trước comparable
hoặc các interface chỉ định phương thức, hoặc nhúng comparable hoặc các interface
chỉ định phương thức.
Các interface không phải cơ bản chỉ có thể được dùng làm ràng buộc kiểu, hoặc làm phần tử của các interface khác được dùng làm ràng buộc. Chúng không thể là kiểu của các giá trị hoặc biến, hoặc là các thành phần của các kiểu không phải interface khác.
var x Float // illegal: Float is not a basic interface
var x interface{} = Float(nil) // illegal
type Floatish struct {
f Float // illegal
}
Một kiểu interface T không được nhúng một phần tử kiểu
mà là, chứa, hoặc nhúng T, trực tiếp hay gián tiếp.
// illegal: Bad may not embed itself
type Bad interface {
Bad
}
// illegal: Bad1 may not embed itself using Bad2
type Bad1 interface {
Bad2
}
type Bad2 interface {
Bad1
}
// illegal: Bad3 may not embed a union containing Bad3
type Bad3 interface {
~int | ~string | Bad3
}
// illegal: Bad4 may not embed an array containing Bad4 as element type
type Bad4 interface {
[10]Bad4
}
Triển khai một interface
Một kiểu T triển khai một interface I nếu
-
Tkhông phải là interface và là một phần tử của tập hợp kiểu củaI; hoặc -
Tlà một interface và tập hợp kiểu củaTlà tập con của tập hợp kiểu củaI.
Một giá trị thuộc kiểu T triển khai một interface nếu T
triển khai interface đó.
Kiểu map
Một map là một nhóm các phần tử không có thứ tự của một kiểu, gọi là
kiểu phần tử, được đánh chỉ mục bằng một tập hợp các khóa duy nhất của một kiểu khác,
gọi là kiểu khóa.
Giá trị của một map chưa được khởi tạo là nil.
MapType = "map" "[" KeyType "]" ElementType . KeyType = Type .
Các toán tử so sánh
== và != phải được định nghĩa đầy đủ
cho các toán hạng của kiểu khóa; do đó kiểu khóa không được là hàm, map, hoặc
slice.
Nếu kiểu khóa là kiểu interface, các
toán tử so sánh này phải được định nghĩa cho các giá trị khóa động;
thất bại sẽ gây ra một panic tại thời điểm chạy.
map[string]int
map[*T]struct{ x, y float64 }
map[string]interface{}
Số phần tử map được gọi là độ dài của nó.
Đối với một map m, nó có thể được tìm ra bằng cách sử dụng
hàm dựng sẵn len
và có thể thay đổi trong quá trình thực thi. Các phần tử có thể được thêm vào trong quá trình thực thi
bằng cách sử dụng phép gán và được lấy ra bằng
biểu thức chỉ mục; chúng có thể được xóa bằng hàm dựng sẵn
delete và
clear.
Một giá trị map mới, rỗng được tạo bằng hàm dựng sẵn
make,
nhận vào kiểu map và một gợi ý dung lượng tùy chọn làm đối số:
make(map[string]int) make(map[string]int, 100)
Dung lượng ban đầu không giới hạn kích thước của nó:
map phát triển để chứa số lượng mục
được lưu trữ trong chúng, ngoại trừ các map nil.
Một map nil tương đương với một map rỗng ngoại trừ không có phần tử nào
có thể được thêm vào.
Kiểu channel
Một channel cung cấp cơ chế cho
các hàm thực thi đồng thời
để giao tiếp bằng cách
gửi và
nhận
các giá trị của một kiểu phần tử được chỉ định.
Giá trị của một channel chưa được khởi tạo là nil.
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
Toán tử <- tùy chọn chỉ định hướng của channel,
gửi hoặc nhận. Nếu hướng được chỉ định, channel là có hướng,
ngược lại thì hai chiều.
Một channel có thể bị ràng buộc chỉ để gửi hoặc chỉ để nhận bằng
phép gán hoặc
phép chuyển đổi kiểu tường minh.
chan T // can be used to send and receive values of type T chan<- float64 // can only be used to send float64s <-chan int // can only be used to receive ints
Toán tử <- kết hợp với chan
ngoài cùng bên trái có thể:
chan<- chan int // same as chan<- (chan int) chan<- <-chan int // same as chan<- (<-chan int) <-chan <-chan int // same as <-chan (<-chan int) chan (<-chan int)
Một giá trị channel mới, đã được khởi tạo
có thể được tạo bằng hàm dựng sẵn
make,
nhận vào kiểu channel và một dung lượng tùy chọn làm đối số:
make(chan int, 100)
Dung lượng, tính bằng số phần tử, đặt kích thước của bộ đệm trong channel.
Nếu dung lượng bằng không hoặc vắng mặt, channel không có bộ đệm và việc giao tiếp
chỉ thành công khi cả người gửi và người nhận đều sẵn sàng. Ngược lại, channel
có bộ đệm và việc giao tiếp thành công mà không bị chặn nếu bộ đệm
không đầy (gửi) hoặc không rỗng (nhận).
Một channel nil không bao giờ sẵn sàng để giao tiếp.
Một channel có thể được đóng bằng hàm dựng sẵn
close.
Dạng gán đa giá trị của
toán tử nhận
báo cáo liệu một giá trị nhận được có được gửi trước khi
channel được đóng hay không.
Một channel duy nhất có thể được sử dụng trong
câu lệnh gửi,
phép toán nhận,
và các lời gọi đến các hàm dựng sẵn
cap và
len
bởi bất kỳ số lượng goroutine nào mà không cần đồng bộ hóa thêm.
Channel hoạt động như hàng đợi vào trước ra trước (FIFO).
Ví dụ, nếu một goroutine gửi các giá trị trên một channel
và một goroutine thứ hai nhận chúng, các giá trị được
nhận theo thứ tự được gửi.
Thuộc tính của kiểu và giá trị
Biểu diễn của giá trị
Giá trị của các kiểu được khai báo trước (xem bên dưới đối với các interface any
và error), mảng, và struct là độc lập:
Mỗi giá trị như vậy chứa một bản sao hoàn chỉnh của tất cả dữ liệu của nó,
và biến của các kiểu đó lưu trữ toàn bộ giá trị.
Ví dụ, một biến mảng cung cấp bộ nhớ lưu trữ (các biến)
cho tất cả phần tử của mảng.
Các giá trị không tương ứng là đặc trưng cho
kiểu của giá trị; chúng không bao giờ là nil.
Giá trị con trỏ, hàm, slice, map và channel khác nil chứa các tham chiếu đến dữ liệu nền có thể được chia sẻ bởi nhiều giá trị:
- Một giá trị con trỏ là một tham chiếu đến biến giữ giá trị kiểu cơ sở của con trỏ.
- Một giá trị hàm chứa các tham chiếu đến hàm (có thể là ẩn danh) và các biến được bao bởi nó.
- Một giá trị slice chứa độ dài slice, dung lượng, và một tham chiếu đến mảng nền của nó.
- Một giá trị map hoặc channel là một tham chiếu đến cấu trúc dữ liệu đặc thù của cài đặt cho map hoặc channel.
Một giá trị interface có thể độc lập hoặc chứa các tham chiếu đến dữ liệu nền
tùy thuộc vào kiểu động của interface.
Định danh được khai báo trước nil là giá trị không cho các kiểu mà giá trị
của chúng có thể chứa tham chiếu.
Khi nhiều giá trị chia sẻ dữ liệu nền, thay đổi một giá trị có thể thay đổi giá trị khác. Ví dụ, thay đổi một phần tử của một slice sẽ thay đổi phần tử đó trong mảng nền cho tất cả các slice chia sẻ mảng đó.
Kiểu nền
Mỗi kiểu T có một kiểu nền: Nếu T
là một trong các kiểu boolean, số học, hay chuỗi được khai báo trước, hoặc là một literal kiểu,
thì kiểu nền tương ứng là chính T.
Ngược lại, kiểu nền của T là kiểu nền của kiểu mà
T tham chiếu đến trong khai báo của nó.
Đối với một tham số kiểu thì kiểu nền là
ràng buộc kiểu của nó, luôn luôn là một interface.
type (
A1 = string
A2 = A1
)
type (
B1 string
B2 B1
B3 []B1
B4 B3
)
func f[P any](x P) { … }
Kiểu nền của string, A1, A2, B1,
và B2 là string.
Kiểu nền của []B1, B3, và B4 là []B1.
Kiểu nền của P là interface{}.
Đồng nhất kiểu
Hai kiểu hoặc là đồng nhất ("giống nhau") hoặc là khác nhau.
Một kiểu có tên luôn luôn khác với bất kỳ kiểu nào khác. Ngược lại, hai kiểu là đồng nhất nếu các literal kiểu nền của chúng tương đương về mặt cấu trúc; nghĩa là, chúng có cùng cấu trúc literal và các thành phần tương ứng có các kiểu đồng nhất. Cụ thể:
- Hai kiểu mảng là đồng nhất nếu chúng có kiểu phần tử đồng nhất và cùng độ dài mảng.
- Hai kiểu slice là đồng nhất nếu chúng có kiểu phần tử đồng nhất.
- Hai kiểu struct là đồng nhất nếu chúng có cùng dãy các trường, và nếu các cặp trường tương ứng có cùng tên, kiểu đồng nhất, và tag đồng nhất, đồng thời cả hai đều được nhúng hoặc cả hai đều không được nhúng. Tên trường không được xuất từ các gói khác nhau luôn luôn khác nhau.
- Hai kiểu con trỏ là đồng nhất nếu chúng có kiểu cơ sở đồng nhất.
- Hai kiểu hàm là đồng nhất nếu chúng có cùng số lượng tham số và giá trị kết quả, các kiểu tham số và kết quả tương ứng là đồng nhất, và cả hai hàm đều là variadic hoặc đều không là variadic. Tên tham số và kết quả không cần phải khớp nhau.
- Hai kiểu interface là đồng nhất nếu chúng định nghĩa cùng tập hợp kiểu.
- Hai kiểu map là đồng nhất nếu chúng có kiểu khóa và kiểu phần tử đồng nhất.
- Hai kiểu channel là đồng nhất nếu chúng có kiểu phần tử đồng nhất và cùng hướng.
- Hai kiểu được khởi tạo là đồng nhất nếu các kiểu được định nghĩa và tất cả các tham số kiểu của chúng đều đồng nhất.
Cho các khai báo sau
type (
A0 = []string
A1 = A0
A2 = struct{ a, b int }
A3 = int
A4 = func(A3, float64) *A0
A5 = func(x int, _ float64) *[]string
B0 A0
B1 []string
B2 struct{ a, b int }
B3 struct{ a, c int }
B4 func(int, float64) *B0
B5 func(x int, y float64) *A1
C0 = B0
D0[P1, P2 any] struct{ x P1; y P2 }
E0 = D0[int, string]
)
các kiểu sau là đồng nhất:
A0, A1, and []string
A2 and struct{ a, b int }
A3 and int
A4, func(int, float64) *[]string, and A5
B0 and C0
D0[int, string] and E0
[]int and []int
struct{ a, b *B5 } and struct{ a, b *B5 }
func(x int, y float64) *[]string, func(int, float64) (result *[]string), and A5
B0 và B1 khác nhau vì chúng là các kiểu mới
được tạo ra bởi các định nghĩa kiểu riêng biệt;
func(int, float64) *B0 và func(x int, y float64) *[]string
khác nhau vì B0 khác với []string;
và P1 và P2 khác nhau vì chúng là các
tham số kiểu khác nhau.
D0[int, string] và struct{ x int; y string } là
khác nhau vì cái trước là một kiểu được định nghĩa đã được khởi tạo
trong khi cái sau là một literal kiểu
(nhưng chúng vẫn có thể gán cho nhau).
Khả năng gán
Một giá trị x có kiểu V là có thể gán cho một biến có kiểu T
("x có thể gán cho T") nếu một trong các điều kiện sau được thỏa mãn:
-
VvàTlà đồng nhất. -
VvàTcó kiểu nền đồng nhất nhưng không phải là tham số kiểu và ít nhất một trongVhoặcTkhông phải là kiểu có tên. -
VvàTlà các kiểu channel với kiểu phần tử đồng nhất,Vlà channel hai chiều, và ít nhất một trongVhoặcTkhông phải là kiểu có tên. -
Tlà kiểu interface, nhưng không phải tham số kiểu, vàxhiện thực hóaT. -
xlà định danh được khai báo trướcnilvàTlà kiểu con trỏ, hàm, slice, map, channel, hoặc interface, nhưng không phải tham số kiểu. -
xlà một hằng số không có kiểu có thể biểu diễn bằng một giá trị của kiểuT.
Ngoài ra, nếu kiểu V của x hoặc T là tham số kiểu, x
có thể gán cho một biến của kiểu T nếu một trong các điều kiện sau được thỏa mãn:
-
xlà định danh được khai báo trướcnil,Tlà một tham số kiểu, vàxcó thể gán cho mỗi kiểu trong tập hợp kiểu củaT. -
Vkhông phải là kiểu có tên,Tlà một tham số kiểu, vàxcó thể gán cho mỗi kiểu trong tập hợp kiểu củaT. -
Vlà một tham số kiểu vàTkhông phải là kiểu có tên, và các giá trị của mỗi kiểu trong tập hợp kiểu củaVcó thể gán choT.
Khả năng biểu diễn
Một hằng số x là có thể biểu diễn
bằng một giá trị của kiểu T,
trong đó T không phải là một tham số kiểu,
nếu một trong các điều kiện sau được thỏa mãn:
-
xthuộc tập hợp giá trị được xác định bởiT. -
Tlà một kiểu dấu phẩy động vàxcó thể được làm tròn theo độ chính xác củaTmà không bị tràn số. Việc làm tròn sử dụng quy tắc làm tròn về số chẵn gần nhất của IEEE 754 nhưng với số âm không của IEEE được đơn giản hóa thêm thành số không không dấu. Lưu ý rằng các giá trị hằng số không bao giờ cho kết quả là số âm không của IEEE, NaN, hay vô cực. -
Tlà kiểu số phức, và các thành phầnreal(x)vàimag(x)củaxcó thể biểu diễn bằng các giá trị của kiểu thành phần củaT(float32hoặcfloat64).
Nếu T là một tham số kiểu,
x có thể biểu diễn bằng một giá trị của kiểu T nếu x có thể biểu diễn
bằng một giá trị của mỗi kiểu trong tập hợp kiểu của T.
x T x is representable by a value of T because 'a' byte 97 is in the set of byte values 97 rune rune is an alias for int32, and 97 is in the set of 32-bit integers "foo" string "foo" is in the set of string values 1024 int16 1024 is in the set of 16-bit integers 42.0 byte 42 is in the set of unsigned 8-bit integers 1e10 uint64 10000000000 is in the set of unsigned 64-bit integers 2.718281828459045 float32 2.718281828459045 rounds to 2.7182817 which is in the set of float32 values -1e-1000 float64 -1e-1000 rounds to IEEE -0.0 which is further simplified to 0.0 0i int 0 is an integer value (42 + 0i) float32 42.0 (with zero imaginary part) is in the set of float32 values
x T x is not representable by a value of T because 0 bool 0 is not in the set of boolean values 'a' string 'a' is a rune, it is not in the set of string values 1024 byte 1024 is not in the set of unsigned 8-bit integers -1 uint16 -1 is not in the set of unsigned 16-bit integers 1.1 int 1.1 is not an integer value 42i float32 (0 + 42i) is not in the set of float32 values 1e1000 float64 1e1000 overflows to IEEE +Inf after rounding
Tập hợp phương thức
Tập hợp phương thức của một kiểu xác định các phương thức có thể được gọi trên một toán hạng của kiểu đó. Mỗi kiểu đều có một tập hợp phương thức (có thể rỗng) gắn với nó:
- Tập hợp phương thức của một kiểu được định nghĩa
Tbao gồm tất cả phương thức được khai báo với kiểu receiverT. -
Tập hợp phương thức của một con trỏ đến kiểu được định nghĩa
T(trong đóTkhông phải là con trỏ hay interface) là tập hợp tất cả các phương thức được khai báo với receiver*ThoặcT. - Tập hợp phương thức của một kiểu interface là giao của các tập hợp phương thức của mỗi kiểu trong tập hợp kiểu của interface (tập hợp phương thức kết quả thường chỉ là tập hợp các phương thức được khai báo trong interface).
Các quy tắc bổ sung áp dụng cho struct (và con trỏ đến struct) chứa các trường được nhúng, như được mô tả trong phần về kiểu struct. Bất kỳ kiểu nào khác đều có tập hợp phương thức rỗng.
Trong một tập hợp phương thức, mỗi phương thức phải có tên phương thức duy nhất không phải là định danh rỗng.
Khối
Một khối là một dãy khai báo và câu lệnh (có thể rỗng) nằm trong cặp dấu ngoặc nhọn.
Block = "{" StatementList "}" .
StatementList = { Statement ";" } .
Ngoài các khối tường minh trong mã nguồn, còn có các khối ẩn:
- Khối vũ trụ bao gồm tất cả văn bản nguồn Go.
- Mỗi gói có một khối gói chứa tất cả văn bản nguồn Go của gói đó.
- Mỗi tệp có một khối tệp chứa tất cả văn bản nguồn Go trong tệp đó.
- Mỗi câu lệnh "if", "for", và "switch" được xem như nằm trong khối ẩn riêng của nó.
- Mỗi mệnh đề trong câu lệnh "switch" hoặc "select" hoạt động như một khối ẩn.
Các khối lồng nhau và ảnh hưởng đến phạm vi.
Khai báo và phạm vi
Một khai báo liên kết một định danh không phải định danh rỗng với một hằng số, kiểu, tham số kiểu, biến, hàm, nhãn, hoặc gói. Mỗi định danh trong một chương trình phải được khai báo. Không được khai báo cùng một định danh hai lần trong cùng một khối, và không được khai báo một định danh ở cả khối tệp lẫn khối gói.
Định danh rỗng có thể được dùng như bất kỳ định danh nào khác
trong một khai báo, nhưng nó không tạo ra ràng buộc và do đó không được khai báo.
Trong khối gói, định danh init chỉ có thể được dùng cho
các khai báo hàm init,
và giống như định danh rỗng, nó không tạo ra ràng buộc mới.
Declaration = ConstDecl | TypeDecl | VarDecl . TopLevelDecl = Declaration | FunctionDecl | MethodDecl .
Phạm vi của một định danh được khai báo là phần văn bản nguồn mà trong đó định danh đó biểu thị hằng số, kiểu, biến, hàm, nhãn, hoặc gói được chỉ định.
Go được xác định phạm vi theo từ vựng sử dụng khối:
- Phạm vi của một định danh được khai báo trước là khối vũ trụ.
- Phạm vi của một định danh biểu thị một hằng số, kiểu, biến, hoặc hàm (nhưng không phải phương thức) được khai báo ở cấp cao nhất (bên ngoài bất kỳ hàm nào) là khối gói.
- Phạm vi của tên gói của một gói được import là khối tệp của tệp chứa khai báo import.
- Phạm vi của một định danh biểu thị receiver của phương thức, tham số hàm, hoặc biến kết quả là thân hàm.
- Phạm vi của một định danh biểu thị tham số kiểu của một hàm hoặc được khai báo bởi receiver của phương thức bắt đầu sau tên của hàm và kết thúc ở cuối thân hàm.
- Phạm vi của một định danh biểu thị tham số kiểu của một kiểu bắt đầu sau tên của kiểu và kết thúc ở cuối TypeSpec.
- Phạm vi của một định danh hằng số hoặc biến được khai báo bên trong một hàm bắt đầu ở cuối ConstSpec hoặc VarSpec (ShortVarDecl đối với khai báo biến ngắn) và kết thúc ở cuối khối trong cùng chứa nó.
- Phạm vi của một định danh kiểu được khai báo bên trong một hàm bắt đầu tại định danh trong TypeSpec và kết thúc ở cuối khối trong cùng chứa nó.
Một định danh được khai báo trong một khối có thể được khai báo lại trong một khối bên trong. Khi định danh của khai báo bên trong đang trong phạm vi, nó biểu thị thực thể được khai báo bởi khai báo bên trong.
Mệnh đề package không phải là một khai báo; tên gói không xuất hiện trong bất kỳ phạm vi nào. Mục đích của nó là để xác định các tệp thuộc cùng một gói và để chỉ định tên gói mặc định cho các khai báo import.
Phạm vi nhãn
Nhãn được khai báo bởi các câu lệnh có nhãn và được dùng trong các câu lệnh "break", "continue", và "goto". Việc định nghĩa một nhãn không bao giờ được dùng là bất hợp lệ. Trái với các định danh khác, nhãn không có phạm vi theo khối và không xung đột với các định danh không phải nhãn. Phạm vi của một nhãn là thân của hàm mà trong đó nó được khai báo và không bao gồm thân của bất kỳ hàm lồng nhau nào.
Định danh rỗng
Định danh rỗng được biểu diễn bằng ký tự gạch dưới _.
Nó đóng vai trò là một trình giữ chỗ ẩn danh thay cho một định danh thông thường (không rỗng)
và có ý nghĩa đặc biệt trong khai báo,
như một toán hạng, và trong câu lệnh gán.
Định danh được khai báo trước
Các định danh sau được khai báo ngầm trong khối vũ trụ [Go 1.18] [Go 1.21]:
Types: any bool byte comparable complex64 complex128 error float32 float64 int int8 int16 int32 int64 rune string uint uint8 uint16 uint32 uint64 uintptr Constants: true false iota Zero value: nil Functions: append cap clear close complex copy delete imag len make max min new panic print println real recover
Định danh được xuất
Một định danh có thể được xuất để cho phép truy cập vào nó từ một gói khác. Một định danh được xuất nếu cả hai điều kiện sau đây đều đúng:
- ký tự đầu tiên của tên định danh là một chữ cái viết hoa Unicode (danh mục ký tự Unicode Lu); và
- định danh đó được khai báo trong khối gói hoặc là một tên trường hoặc tên phương thức.
Tất cả các định danh khác đều không được xuất.
Tính duy nhất của định danh
Cho một tập hợp các định danh, một định danh được gọi là duy nhất nếu nó khác với mọi định danh khác trong tập hợp đó. Hai định danh khác nhau nếu chúng được viết khác nhau, hoặc nếu chúng xuất hiện trong các gói khác nhau và không được xuất. Ngược lại, chúng là giống nhau.
Khai báo hằng số
Một khai báo hằng số liên kết một danh sách các định danh (tên của các hằng số) với các giá trị của một danh sách biểu thức hằng. Số lượng định danh phải bằng số lượng biểu thức, và định danh thứ n ở bên trái được liên kết với giá trị của biểu thức thứ n ở bên phải.
ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) .
ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] .
IdentifierList = identifier { "," identifier } .
ExpressionList = Expression { "," Expression } .
Nếu có kiểu, tất cả các hằng số lấy kiểu được chỉ định, và các biểu thức phải có thể gán cho kiểu đó, và kiểu đó không được là tham số kiểu. Nếu kiểu bị bỏ qua, các hằng số lấy kiểu riêng lẻ của các biểu thức tương ứng. Nếu các giá trị biểu thức là các hằng số không có kiểu, các hằng số được khai báo vẫn không có kiểu và các định danh hằng số biểu thị các giá trị hằng số. Ví dụ, nếu biểu thức là một literal dấu phẩy động, định danh hằng số biểu thị một hằng số dấu phẩy động, ngay cả khi phần thập phân của literal bằng không.
const Pi float64 = 3.14159265358979323846 const zero = 0.0 // untyped floating-point constant const ( size int64 = 1024 eof = -1 // untyped integer constant ) const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", untyped integer and string constants const u, v float32 = 0, 3 // u = 0.0, v = 3.0
Trong một danh sách khai báo const có ngoặc đơn, danh sách
biểu thức có thể được bỏ qua ở bất kỳ ConstSpec nào ngoại trừ cái đầu tiên.
Danh sách rỗng như vậy tương đương với việc thay thế văn bản của
danh sách biểu thức không rỗng đầu tiên trước đó và kiểu của nó nếu có.
Do đó, việc bỏ qua danh sách biểu thức tương đương với
việc lặp lại danh sách trước đó. Số lượng định danh phải bằng
số lượng biểu thức trong danh sách trước đó.
Cùng với bộ sinh hằng số iota,
cơ chế này cho phép khai báo nhẹ nhàng các giá trị tuần tự:
const ( Sunday = iota Monday Tuesday Wednesday Thursday Friday Partyday numberOfDays // this constant is not exported )
Iota
Trong một khai báo hằng số, định danh được khai báo trước
iota biểu thị các hằng số nguyên không có kiểu liên tiếp.
Giá trị của nó là chỉ số của ConstSpec tương ứng ConstSpec
trong khai báo hằng số đó, bắt đầu từ không.
Nó có thể được dùng để xây dựng một tập hợp các hằng số liên quan:
const ( c0 = iota // c0 == 0 c1 = iota // c1 == 1 c2 = iota // c2 == 2 ) const ( a = 1 << iota // a == 1 (iota == 0) b = 1 << iota // b == 2 (iota == 1) c = 3 // c == 3 (iota == 2, unused) d = 1 << iota // d == 8 (iota == 3) ) const ( u = iota * 42 // u == 0 (untyped integer constant) v float64 = iota * 42 // v == 42.0 (float64 constant) w = iota * 42 // w == 84 (untyped integer constant) ) const x = iota // x == 0 const y = iota // y == 0
Theo định nghĩa, nhiều lần dùng iota trong cùng một ConstSpec đều có cùng giá trị:
const ( bit0, mask0 = 1 << iota, 1<<iota - 1 // bit0 == 1, mask0 == 0 (iota == 0) bit1, mask1 // bit1 == 2, mask1 == 1 (iota == 1) _, _ // (iota == 2, unused) bit3, mask3 // bit3 == 8, mask3 == 7 (iota == 3) )
Ví dụ cuối này khai thác sự lặp lại ngầm của danh sách biểu thức không rỗng cuối cùng.
Khai báo kiểu
Một khai báo kiểu liên kết một định danh, tên kiểu, với một kiểu. Khai báo kiểu có hai dạng: khai báo alias và định nghĩa kiểu.
TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) .
TypeSpec = AliasDecl | TypeDef .
Khai báo alias
Một khai báo alias liên kết một định danh với kiểu cho trước [Go 1.9].
AliasDecl = identifier [ TypeParameters ] "=" Type .
Trong phạm vi của định danh đó, nó đóng vai trò là một alias cho kiểu cho trước.
type ( nodeList = []*Node // nodeList and []*Node are identical types Polar = polar // Polar and polar denote identical types )
Nếu khai báo alias chỉ định tham số kiểu [Go 1.24], tên kiểu biểu thị một alias chung. Các alias chung phải được khởi tạo khi chúng được sử dụng.
type set[P comparable] = map[P]bool
Trong một khai báo alias, kiểu cho trước không thể là tham số kiểu được khai báo trong cùng khai báo đó.
type A[P any] = P // illegal: P is a type parameter declared in the declaration of A
func f[P any]() {
type A = P // ok: T is a type parameter declared by the enclosing function
}
Định nghĩa kiểu
Một định nghĩa kiểu tạo ra một kiểu mới, khác biệt với cùng kiểu nền và các phép toán như kiểu cho trước và liên kết một định danh, tên kiểu, với nó.
TypeDef = identifier [ TypeParameters ] Type .
Kiểu mới được gọi là kiểu được định nghĩa. Nó khác với bất kỳ kiểu nào khác, kể cả kiểu mà nó được tạo ra từ đó.
type (
Point struct{ x, y float64 } // Point and struct{ x, y float64 } are different types
polar Point // polar and Point denote different types
)
type TreeNode struct {
left, right *TreeNode
value any
}
type Block interface {
BlockSize() int
Encrypt(src, dst []byte)
Decrypt(src, dst []byte)
}
Một kiểu được định nghĩa có thể có các phương thức gắn với nó. Nó không kế thừa bất kỳ phương thức nào được liên kết với kiểu cho trước, nhưng tập hợp phương thức của kiểu interface hoặc của các phần tử của kiểu tổng hợp vẫn không thay đổi:
// A Mutex is a data type with two methods, Lock and Unlock.
type Mutex struct { /* Mutex fields */ }
func (m *Mutex) Lock() { /* Lock implementation */ }
func (m *Mutex) Unlock() { /* Unlock implementation */ }
// NewMutex has the same composition as Mutex but its method set is empty.
type NewMutex Mutex
// The method set of PtrMutex's underlying type *Mutex remains unchanged,
// but the method set of PtrMutex is empty.
type PtrMutex *Mutex
// The method set of *PrintableMutex contains the methods
// Lock and Unlock bound to its embedded field Mutex.
type PrintableMutex struct {
Mutex
}
// MyBlock is an interface type that has the same method set as Block.
type MyBlock Block
Các định nghĩa kiểu có thể được dùng để định nghĩa các kiểu boolean, số học, hoặc chuỗi khác nhau và gắn các phương thức với chúng:
type TimeZone int
const (
EST TimeZone = -(5 + iota)
CST
MST
PST
)
func (tz TimeZone) String() string {
return fmt.Sprintf("GMT%+dh", tz)
}
Nếu định nghĩa kiểu chỉ định tham số kiểu, tên kiểu biểu thị một kiểu chung. Các kiểu chung phải được khởi tạo khi chúng được sử dụng.
type List[T any] struct {
next *List[T]
value T
}
Trong một định nghĩa kiểu, kiểu cho trước không thể là tham số kiểu.
type T[P any] P // illegal: P is a type parameter
func f[P any]() {
type L P // illegal: P is a type parameter declared by the enclosing function
}
Một kiểu chung cũng có thể có các phương thức gắn với nó. Trong trường hợp này, các receiver của phương thức phải khai báo cùng số lượng tham số kiểu như có trong định nghĩa kiểu chung.
// The method Len returns the number of elements in the linked list l.
func (l *List[T]) Len() int { … }
Khai báo tham số kiểu
Một danh sách tham số kiểu khai báo các tham số kiểu của một hàm chung hoặc khai báo kiểu chung. Danh sách tham số kiểu trông giống như một danh sách tham số hàm thông thường ngoại trừ tên tham số kiểu phải đều có mặt và danh sách được bao bởi dấu ngoặc vuông thay vì dấu ngoặc đơn [Go 1.18].
TypeParameters = "[" TypeParamList [ "," ] "]" .
TypeParamList = TypeParamDecl { "," TypeParamDecl } .
TypeParamDecl = IdentifierList TypeConstraint .
Tất cả các tên không rỗng trong danh sách phải là duy nhất. Mỗi tên khai báo một tham số kiểu, là một kiểu có tên mới và khác biệt hoạt động như một trình giữ chỗ cho một kiểu (chưa biết) trong khai báo. Tham số kiểu được thay thế bằng một tham số kiểu thực khi khởi tạo hàm chung hoặc kiểu chung.
[P any]
[S interface{ ~[]byte|string }]
[S ~[]E, E any]
[P Constraint[int]]
[_ any]
Cũng như mỗi tham số hàm thông thường có kiểu tham số, mỗi tham số kiểu có một (meta-)kiểu tương ứng được gọi là ràng buộc kiểu.
Một sự mơ hồ khi phân tích phát sinh khi danh sách tham số kiểu cho một kiểu chung
khai báo một tham số kiểu duy nhất P với ràng buộc C
sao cho văn bản P C tạo thành một biểu thức hợp lệ:
type T[P *C] … type T[P (C)] … type T[P *C|Q] … …
Trong những trường hợp hiếm gặp này, danh sách tham số kiểu không thể phân biệt với một biểu thức và khai báo kiểu được phân tích như một khai báo kiểu mảng. Để giải quyết sự mơ hồ, nhúng ràng buộc vào một interface hoặc dùng dấu phẩy ở cuối:
type T[P interface{*C}] …
type T[P *C,] …
Tham số kiểu cũng có thể được khai báo bởi đặc tả receiver của một khai báo phương thức được liên kết với một kiểu chung.
Ràng buộc kiểu
Một ràng buộc kiểu là một interface định nghĩa tập hợp các tham số kiểu thực được phép cho tham số kiểu tương ứng và kiểm soát các phép toán được hỗ trợ bởi các giá trị của tham số kiểu đó [Go 1.18].
TypeConstraint = TypeElem .
Nếu ràng buộc là một interface literal dạng interface{E} trong đó
E là một phần tử kiểu được nhúng (không phải phương thức), trong danh sách tham số kiểu
phần bao bọc interface{ … } có thể được bỏ qua cho tiện lợi:
[T []P] // = [T interface{[]P}]
[T ~int] // = [T interface{~int}]
[T int|string] // = [T interface{int|string}]
type Constraint ~int // illegal: ~int is not in a type parameter list
Kiểu interface comparable
được khai báo trước
biểu thị tập hợp tất cả các kiểu không phải interface
có thể so sánh nghiêm ngặt
[Go 1.18].
Mặc dù các interface không phải tham số kiểu là có thể so sánh,
chúng không có thể so sánh nghiêm ngặt và do đó chúng không hiện thực hóa comparable.
Tuy nhiên, chúng thỏa mãn comparable.
int // implements comparable (int is strictly comparable)
[]byte // does not implement comparable (slices cannot be compared)
interface{} // does not implement comparable (see above)
interface{ ~int | ~string } // type parameter only: implements comparable (int, string types are strictly comparable)
interface{ comparable } // type parameter only: implements comparable (comparable implements itself)
interface{ ~int | ~[]byte } // type parameter only: does not implement comparable (slices are not comparable)
interface{ ~struct{ any } } // type parameter only: does not implement comparable (field any is not strictly comparable)
Interface comparable và các interface (trực tiếp hoặc gián tiếp) nhúng
comparable chỉ có thể được dùng như các ràng buộc kiểu. Chúng không thể là kiểu của
các giá trị hay biến, hoặc là thành phần của các kiểu không phải interface khác.
Thỏa mãn ràng buộc kiểu
Một tham số kiểu thực T thỏa mãn một ràng buộc kiểu C
nếu T là một phần tử của tập hợp kiểu được định nghĩa bởi C; nói cách khác,
nếu T hiện thực hóa C.
Là một ngoại lệ, một ràng buộc kiểu có thể so sánh nghiêm ngặt
cũng có thể được thỏa mãn bởi một tham số kiểu thực có thể so sánh
(không nhất thiết phải có thể so sánh nghiêm ngặt)
[Go 1.20].
Cụ thể hơn:
Một kiểu T thỏa mãn ràng buộc C nếu
-
Thiện thực hóaC; hoặc -
Ccó thể được viết dưới dạnginterface{ comparable; E }, trong đóElà một interface cơ bản vàTlà có thể so sánh và hiện thực hóaE.
type argument type constraint // constraint satisfaction
int interface{ ~int } // satisfied: int implements interface{ ~int }
string comparable // satisfied: string implements comparable (string is strictly comparable)
[]byte comparable // not satisfied: slices are not comparable
any interface{ comparable; int } // not satisfied: any does not implement interface{ int }
any comparable // satisfied: any is comparable and implements the basic interface any
struct{f any} comparable // satisfied: struct{f any} is comparable and implements the basic interface any
any interface{ comparable; m() } // not satisfied: any does not implement the basic interface interface{ m() }
interface{ m() } interface{ comparable; m() } // satisfied: interface{ m() } is comparable and implements the basic interface interface{ m() }
Do ngoại lệ trong quy tắc thỏa mãn ràng buộc, việc so sánh các toán hạng có kiểu tham số kiểu có thể gây panic lúc chạy (ngay cả khi các tham số kiểu có thể so sánh luôn luôn có thể so sánh nghiêm ngặt).
Khai báo biến
Một khai báo biến tạo ra một hoặc nhiều biến, liên kết các định danh tương ứng với chúng, và gán cho mỗi biến một kiểu và một giá trị khởi tạo.
VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) .
VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
var i int var U, V, W float64 var k = 0 var x, y float32 = -1, -2 var ( i int u, v, s = 2.0, 3.0, "bar" ) var re, im = complexSqrt(-1) var _, found = entries[name] // map lookup; only interested in "found"
Nếu một danh sách biểu thức được cho, các biến được khởi tạo với các biểu thức theo các quy tắc của câu lệnh gán. Ngược lại, mỗi biến được khởi tạo về giá trị không của nó.
Nếu có kiểu, mỗi biến được gán kiểu đó.
Ngược lại, mỗi biến được gán kiểu của giá trị
khởi tạo tương ứng trong phép gán.
Nếu giá trị đó là một hằng số không có kiểu, nó trước tiên được
chuyển đổi ngầm sang kiểu mặc định của nó;
nếu là một giá trị boolean không có kiểu, nó trước tiên được chuyển đổi ngầm sang kiểu bool.
Định danh được khai báo trước nil không thể được dùng để khởi tạo một biến
không có kiểu tường minh.
var d = math.Sin(0.5) // d is float64 var i = 42 // i is int var t, ok = x.(T) // t is T, ok is bool var n = nil // illegal
Giới hạn cài đặt: Một trình biên dịch có thể làm cho việc khai báo một biến bên trong một thân hàm là bất hợp lệ nếu biến đó không bao giờ được dùng.
Khai báo biến ngắn
Một khai báo biến ngắn sử dụng cú pháp:
ShortVarDecl = IdentifierList ":=" ExpressionList .
Đây là cách viết tắt của một khai báo biến thông thường với các biểu thức khởi tạo nhưng không có kiểu:
"var" IdentifierList "=" ExpressionList .
i, j := 0, 10
f := func() int { return 7 }
ch := make(chan int)
r, w, _ := os.Pipe() // os.Pipe() returns a connected pair of Files and an error, if any
_, y, _ := coord(p) // coord() returns three values; only interested in y coordinate
Không giống như khai báo biến thông thường, một khai báo biến ngắn có thể khai báo lại
các biến miễn là chúng đã được khai báo trước đó trong cùng một khối
(hoặc danh sách tham số nếu khối là thân hàm) với cùng kiểu,
và ít nhất một trong các biến không phải định danh rỗng là mới.
Kết quả là, khai báo lại chỉ có thể xuất hiện trong một khai báo ngắn đa biến.
Khai báo lại không tạo ra biến mới; nó chỉ gán một giá trị mới cho biến gốc.
Tên biến không rỗng ở bên trái của :=
phải là duy nhất.
field1, offset := nextField(str, 0) field2, offset := nextField(str, offset) // redeclares offset x, y, x := 1, 2, 3 // illegal: x repeated on left side of :=
Khai báo biến ngắn chỉ có thể xuất hiện bên trong các hàm. Trong một số ngữ cảnh như các bộ khởi tạo cho các câu lệnh "if", "for", hoặc "switch", chúng có thể được dùng để khai báo các biến tạm cục bộ.
Khai báo hàm
Một khai báo hàm liên kết một định danh, tên hàm, với một hàm.
FunctionDecl = "func" FunctionName [ TypeParameters ] Signature [ FunctionBody ] . FunctionName = identifier . FunctionBody = Block .
Nếu chữ ký của hàm khai báo các tham số kết quả, danh sách câu lệnh của thân hàm phải kết thúc bằng một câu lệnh kết thúc.
func IndexRune(s string, r rune) int {
for i, c := range s {
if c == r {
return i
}
}
// invalid: missing return statement
}
Nếu khai báo hàm chỉ định tham số kiểu, tên hàm biểu thị một hàm chung. Một hàm chung phải được khởi tạo trước khi nó có thể được gọi hoặc dùng như một giá trị.
func min[T ~int|~float64](x, y T) T {
if x < y {
return x
}
return y
}
Một khai báo hàm không có tham số kiểu có thể bỏ qua thân hàm. Khai báo như vậy cung cấp chữ ký cho một hàm được cài đặt bên ngoài Go, chẳng hạn như một thủ tục assembly.
func flushICache(begin, end uintptr) // implemented externally
Khai báo phương thức
Một phương thức là một hàm có receiver. Một khai báo phương thức liên kết một định danh, tên phương thức, với một phương thức, và liên kết phương thức với kiểu cơ sở của receiver.
MethodDecl = "func" Receiver MethodName Signature [ FunctionBody ] . Receiver = Parameters .
Receiver được chỉ định thông qua một phần tham số bổ sung đứng trước
tên phương thức. Phần tham số đó phải khai báo một tham số duy nhất không phải variadic, là receiver.
Kiểu của nó phải là một kiểu được định nghĩa T hoặc là
con trỏ đến một kiểu được định nghĩa T, có thể theo sau bởi danh sách
tên tham số kiểu [P1, P2, …] được bao trong dấu ngoặc vuông.
T được gọi là kiểu cơ sở của receiver. Kiểu cơ sở receiver không thể là
kiểu con trỏ hay interface và nó phải được định nghĩa trong cùng gói với phương thức.
Phương thức được gọi là được liên kết với kiểu cơ sở receiver của nó và tên phương thức
chỉ có thể nhìn thấy trong các selector cho kiểu T
hoặc *T.
Một định danh receiver không phải định danh rỗng phải là duy nhất trong chữ ký phương thức. Nếu giá trị của receiver không được tham chiếu bên trong thân phương thức, định danh của nó có thể được bỏ qua trong khai báo. Điều tương tự áp dụng nói chung cho các tham số của hàm và phương thức.
Đối với một kiểu cơ sở, các tên không rỗng của các phương thức được liên kết với nó phải là duy nhất. Nếu kiểu cơ sở là một kiểu struct, các tên phương thức và trường không rỗng phải khác biệt nhau.
Cho kiểu được định nghĩa Point thì các khai báo
func (p *Point) Length() float64 {
return math.Sqrt(p.x * p.x + p.y * p.y)
}
func (p *Point) Scale(factor float64) {
p.x *= factor
p.y *= factor
}
liên kết các phương thức Length và Scale,
với kiểu receiver *Point,
với kiểu cơ sở Point.
Nếu kiểu cơ sở receiver là một kiểu chung, thì đặc tả receiver phải khai báo các tham số kiểu tương ứng để phương thức có thể sử dụng. Điều này làm cho các tham số kiểu của receiver trở nên khả dụng với phương thức. Về mặt cú pháp, khai báo tham số kiểu này trông giống như một khởi tạo của kiểu cơ sở receiver: các tham số kiểu thực phải là các định danh biểu thị các tham số kiểu đang được khai báo, một cho mỗi tham số kiểu của kiểu cơ sở receiver. Tên tham số kiểu không cần phải khớp với tên tham số tương ứng trong định nghĩa kiểu cơ sở receiver, và tất cả các tên tham số không rỗng phải là duy nhất trong phần tham số receiver và chữ ký phương thức. Các ràng buộc tham số kiểu của receiver được ngầm hiểu từ định nghĩa kiểu cơ sở receiver: các tham số kiểu tương ứng có các ràng buộc tương ứng.
type Pair[A, B any] struct {
a A
b B
}
func (p Pair[A, B]) Swap() Pair[B, A] { … } // receiver declares A, B
func (p Pair[First, _]) First() First { … } // receiver declares First, corresponds to A in Pair
Nếu kiểu receiver được biểu thị bằng (con trỏ đến) một alias, alias đó không được là chung và không được biểu thị một kiểu chung đã được khởi tạo, dù trực tiếp hay gián tiếp qua một alias khác, và bất kể các mức độ gián tiếp con trỏ.
type GPoint[P any] = Point
type HPoint = *GPoint[int]
type IPair = Pair[int, int]
func (*GPoint[P]) Draw(P) { … } // illegal: alias must not be generic
func (HPoint) Draw(P) { … } // illegal: alias must not denote instantiated type GPoint[int]
func (*IPair) Second() int { … } // illegal: alias must not denote instantiated type Pair[int, int]
Biểu thức
Một biểu thức xác định phép tính một giá trị bằng cách áp dụng các toán tử và hàm lên các toán hạng.
Toán hạng
Toán hạng biểu thị các giá trị cơ bản trong một biểu thức. Một toán hạng có thể là một literal, một định danh không phải blank (có thể đầy đủ) biểu thị một hằng số, biến, hoặc hàm, hoặc một biểu thức được đặt trong ngoặc đơn.
Operand = Literal | OperandName [ TypeArgs ] | "(" Expression ")" .
Literal = BasicLit | CompositeLit | FunctionLit .
BasicLit = int_lit | float_lit | imaginary_lit | rune_lit | string_lit .
OperandName = identifier | QualifiedIdent .
Tên toán hạng biểu thị một hàm generic có thể được theo sau bởi danh sách các đối số kiểu; kết quả toán hạng là một hàm đã được khởi tạo.
Định danh blank chỉ có thể xuất hiện như một toán hạng ở phía bên trái của một câu lệnh gán.
Giới hạn thực thi: Trình biên dịch không cần báo lỗi nếu kiểu của một toán hạng là một tham số kiểu với một tập hợp kiểu rỗng. Các hàm với tham số kiểu như vậy không thể được khởi tạo; bất kỳ nỗ lực nào cũng sẽ dẫn đến lỗi tại vị trí khởi tạo.
Định danh đầy đủ
Một định danh đầy đủ là một định danh được bổ nghĩa bởi tiền tố tên gói. Cả tên gói lẫn định danh đều không được là blank.
QualifiedIdent = PackageName "." identifier .
Một định danh đầy đủ truy cập một định danh trong một gói khác, gói đó phải được import. Định danh phải được xuất và khai báo trong khối gói của gói đó.
math.Sin // denotes the Sin function in package math
Composite literal
Composite literal tạo ra các giá trị mới cho struct, array, slice và map mỗi lần chúng được đánh giá. Chúng bao gồm kiểu của literal theo sau là một danh sách các phần tử được bao bởi dấu ngoặc nhọn (có thể rỗng). Mỗi phần tử có thể tùy chọn được đứng trước bởi một khóa tương ứng.
CompositeLit = LiteralType LiteralValue .
LiteralType = StructType | ArrayType | "[" "..." "]" ElementType |
SliceType | MapType | TypeName [ TypeArgs ] .
LiteralValue = "{" [ ElementList [ "," ] ] "}" .
ElementList = KeyedElement { "," KeyedElement } .
KeyedElement = [ Key ":" ] Element .
Key = FieldName | Expression | LiteralValue .
FieldName = identifier .
Element = Expression | LiteralValue .
Trừ khi LiteralType là một tham số kiểu, kiểu cơ bản của nó phải là kiểu struct, array, slice hoặc map (cú pháp thực thi ràng buộc này trừ khi kiểu được cho dưới dạng TypeName). Nếu LiteralType là tham số kiểu, tất cả các kiểu trong tập hợp kiểu của nó phải có cùng kiểu cơ bản, và kiểu đó phải là một kiểu composite literal hợp lệ.
Các kiểu của phần tử và khóa phải có thể gán cho các trường, phần tử và kiểu khóa tương ứng của LiteralType; không có chuyển đổi bổ sung nào. Khóa được hiểu là tên trường đối với struct literal, một chỉ số đối với array và slice literal, và một khóa đối với map literal. Đây là lỗi khi chỉ định nhiều phần tử có cùng tên trường hoặc giá trị khóa hằng số. Một literal có thể bỏ qua danh sách phần tử; một literal như vậy được đánh giá theo giá trị zero cho kiểu của nó.
Sự mơ hồ trong phân tích xuất hiện khi một composite literal sử dụng dạng TypeName của LiteralType xuất hiện như một toán hạng giữa từ khóa và dấu ngoặc nhọn mở của khối của câu lệnh "if", "for" hay "switch", và composite literal không được bao trong ngoặc đơn, ngoặc vuông hay dấu ngoặc nhọn. Trong trường hợp hiếm gặp này, dấu ngoặc nhọn mở của literal bị phân tích nhầm là dấu bắt đầu khối câu lệnh. Để giải quyết sự mơ hồ, composite literal phải xuất hiện trong ngoặc đơn.
if x == (T{a,b,c}[i]) { … }
if (x == T{a,b,c}[i]) { … }
Struct literal
Đối với struct literal không có khóa, danh sách phần tử phải chứa một phần tử cho mỗi trường của struct theo thứ tự khai báo của các trường.
Đối với struct literal có khóa, các quy tắc sau áp dụng:
- Mỗi phần tử phải có một khóa.
- Mỗi khóa phải là tên trường được khai báo trong kiểu struct.
- Danh sách phần tử không cần phải có phần tử cho mỗi trường của struct. Các trường bị bỏ qua nhận giá trị zero cho trường đó.
- Đây là lỗi khi chỉ định một phần tử cho trường không xuất của một struct thuộc về một gói khác.
Cho các khai báo
type Point3D struct { x, y, z float64 }
type Line struct { p, q Point3D }
người ta có thể viết
origin := Point3D{} // zero value for Point3D
line := Line{origin, Point3D{y: -4, z: 12.3}} // zero value for line.q.x
Array và slice literal
Đối với array và slice literal, các quy tắc sau áp dụng:
- Mỗi phần tử có một chỉ số nguyên liên kết đánh dấu vị trí của nó trong array.
- Một phần tử có khóa sử dụng khóa đó làm chỉ số của nó. Khóa
phải là một hằng số không âm
có thể biểu diễn bởi
một giá trị của kiểu
int; và nếu nó có kiểu thì phải là kiểu nguyên. - Một phần tử không có khóa sử dụng chỉ số của phần tử trước cộng một. Nếu phần tử đầu tiên không có khóa, chỉ số của nó là không.
Lấy địa chỉ của một composite literal tạo ra một con trỏ đến một biến duy nhất được khởi tạo với giá trị của literal.
var pointer *Point3D = &Point3D{y: 1000}
Lưu ý rằng giá trị zero cho kiểu slice hoặc map không giống với một giá trị đã khởi tạo nhưng rỗng của cùng kiểu đó. Do đó, lấy địa chỉ của một slice hoặc map composite literal rỗng không có hiệu ứng tương tự như cấp phát một giá trị slice hoặc map mới với new.
p1 := &[]int{} // p1 points to an initialized, empty slice with value []int{} and length 0
p2 := new([]int) // p2 points to an uninitialized slice with value nil and length 0
Độ dài của một array literal là độ dài được chỉ định trong kiểu literal.
Nếu ít phần tử hơn độ dài được cung cấp trong literal, các
phần tử còn thiếu được đặt thành giá trị zero cho kiểu phần tử array.
Đây là lỗi khi cung cấp các phần tử với giá trị chỉ số nằm ngoài phạm vi chỉ số
của array. Ký hiệu ... chỉ định độ dài array bằng
chỉ số phần tử lớn nhất cộng một.
buffer := [10]string{} // len(buffer) == 10
intSet := [6]int{1, 2, 3, 5} // len(intSet) == 6
days := [...]string{"Sat", "Sun"} // len(days) == 2
Một slice literal mô tả toàn bộ array literal cơ bản. Do đó độ dài và dung lượng của một slice literal là chỉ số phần tử lớn nhất cộng một. Một slice literal có dạng
[]T{x1, x2, … xn}
và là viết tắt cho một thao tác cắt lát áp dụng lên một array:
tmp := [n]T{x1, x2, … xn}
tmp[0 : n]
Map literal
Đối với map literal, mỗi phần tử phải có một khóa. Đối với các khóa map không phải hằng số, xem phần về thứ tự đánh giá.
Lược bỏ kiểu phần tử
Trong một composite literal của kiểu array, slice hoặc map T,
các phần tử hoặc khóa map mà bản thân chúng là composite literal có thể lược bỏ kiểu literal tương ứng
nếu nó giống với kiểu phần tử hoặc kiểu khóa của T.
Tương tự, các phần tử hoặc khóa là địa chỉ của composite literal có thể lược bỏ
&T khi kiểu phần tử hoặc kiểu khóa là *T.
[...]Point{{1.5, -3.5}, {0, 0}} // same as [...]Point{Point{1.5, -3.5}, Point{0, 0}}
[][]int{{1, 2, 3}, {4, 5}} // same as [][]int{[]int{1, 2, 3}, []int{4, 5}}
[][]Point{{{0, 1}, {1, 2}}} // same as [][]Point{[]Point{Point{0, 1}, Point{1, 2}}}
map[string]Point{"orig": {0, 0}} // same as map[string]Point{"orig": Point{0, 0}}
map[Point]string{{0, 0}: "orig"} // same as map[Point]string{Point{0, 0}: "orig"}
type PPoint *Point
[2]*Point{{1.5, -3.5}, {}} // same as [2]*Point{&Point{1.5, -3.5}, &Point{}}
[2]PPoint{{1.5, -3.5}, {}} // same as [2]PPoint{PPoint(&Point{1.5, -3.5}), PPoint(&Point{})}
Các ví dụ về array, slice và map literal hợp lệ:
// list of prime numbers
primes := []int{2, 3, 5, 7, 9, 2147483647}
// vowels[ch] is true if ch is a vowel
vowels := [128]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true}
// the array [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1}
filter := [10]float32{-1, 4: -0.1, -0.1, 9: -1}
// frequencies in Hz for equal-tempered scale (A4 = 440Hz)
noteFrequency := map[string]float32{
"C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
"G0": 24.50, "A0": 27.50, "B0": 30.87,
}
Function literal
Một function literal đại diện cho một hàm ẩn danh. Function literal không thể khai báo tham số kiểu.
FunctionLit = "func" Signature FunctionBody .
func(a, b int, z float64) bool { return a*b < int(z) }
Một function literal có thể được gán cho một biến hoặc được gọi trực tiếp.
f := func(x, y int) int { return x + y }
func(ch chan int) { ch <- ACK }(replyChan)
Function literal là closure: chúng có thể tham chiếu đến các biến được định nghĩa trong hàm bao quanh. Các biến đó sau đó được chia sẻ giữa hàm bao quanh và function literal, và chúng tồn tại miễn là chúng còn có thể truy cập được.
Biểu thức chính
Biểu thức chính là các toán hạng cho biểu thức một ngôi và hai ngôi.
PrimaryExpr = Operand |
Conversion |
MethodExpr |
PrimaryExpr Selector |
PrimaryExpr Index |
PrimaryExpr Slice |
PrimaryExpr TypeAssertion |
PrimaryExpr Arguments .
Selector = "." identifier .
Index = "[" Expression [ "," ] "]" .
Slice = "[" [ Expression ] ":" [ Expression ] "]" |
"[" [ Expression ] ":" Expression ":" Expression "]" .
TypeAssertion = "." "(" Type ")" .
Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" .
x
2
(s + ".txt")
f(3.1415, true)
Point{1, 2}
m["foo"]
s[i : j + 1]
obj.color
f.p[i].x()
Selector
Đối với một biểu thức chính x
không phải là tên gói, biểu thức
selector
x.f
biểu thị trường hoặc phương thức f của giá trị x
(hoặc đôi khi *x; xem bên dưới).
Định danh f được gọi là selector (trường hoặc phương thức);
nó không được là định danh blank.
Kiểu của biểu thức selector là kiểu của f.
Nếu x là tên gói, xem phần về
định danh đầy đủ.
Một selector f có thể biểu thị một trường hoặc phương thức f của
kiểu T, hoặc nó có thể tham chiếu
đến một trường hoặc phương thức f của một
trường nhúng lồng nhau của T.
Số lượng trường nhúng được đi qua
để đến f được gọi là độ sâu của nó trong T.
Độ sâu của trường hoặc phương thức f
được khai báo trong T là không.
Độ sâu của trường hoặc phương thức f được khai báo trong
một trường nhúng A trong T là
độ sâu của f trong A cộng một.
Các quy tắc sau áp dụng cho selector:
-
Đối với một giá trị
xcủa kiểuThoặc*Ttrong đóTkhông phải là kiểu con trỏ hoặc interface,x.fbiểu thị trường hoặc phương thức ở độ sâu nông nhất trongTmà cófnhư vậy. Nếu không có đúng mộtfvới độ sâu nông nhất, biểu thức selector là bất hợp lệ. -
Đối với một giá trị
xcủa kiểuItrong đóIlà kiểu interface,x.fbiểu thị phương thức thực tế có tênfcủa giá trị động củax. Nếu không có phương thức nào có tênftrong tập hợp phương thức củaI, biểu thức selector là bất hợp lệ. -
Là một ngoại lệ, nếu kiểu của
xlà kiểu con trỏ được định nghĩa và(*x).flà biểu thức selector hợp lệ biểu thị một trường (nhưng không phải phương thức),x.flà viết tắt của(*x).f. -
Trong tất cả các trường hợp khác,
x.flà bất hợp lệ. -
Nếu
xlà kiểu con trỏ và có giá trịnilvàx.fbiểu thị một trường struct, gán cho hoặc đánh giáx.fgây ra một panic lúc chạy. -
Nếu
xlà kiểu interface và có giá trịnil, gọi hoặc đánh giá phương thứcx.fgây ra một panic lúc chạy.
Ví dụ, cho các khai báo:
type T0 struct {
x int
}
func (*T0) M0()
type T1 struct {
y int
}
func (T1) M1()
type T2 struct {
z int
T1
*T0
}
func (*T2) M2()
type Q *T2
var t T2 // with t.T0 != nil
var p *T2 // with p != nil and (*p).T0 != nil
var q Q = p
người ta có thể viết:
t.z // t.z t.y // t.T1.y t.x // (*t.T0).x p.z // (*p).z p.y // (*p).T1.y p.x // (*(*p).T0).x q.x // (*(*q).T0).x (*q).x is a valid field selector p.M0() // ((*p).T0).M0() M0 expects *T0 receiver p.M1() // ((*p).T1).M1() M1 expects T1 receiver p.M2() // p.M2() M2 expects *T2 receiver t.M2() // (&t).M2() M2 expects *T2 receiver, see section on Calls
nhưng những điều sau là không hợp lệ:
q.M0() // (*q).M0 is valid but not a field selector
Biểu thức phương thức
Nếu M thuộc tập hợp phương thức của kiểu T,
T.M là một hàm có thể được gọi như một hàm thông thường
với cùng các đối số như M được thêm vào trước bởi một
đối số bổ sung là receiver của phương thức.
MethodExpr = ReceiverType "." MethodName . ReceiverType = Type .
Xem xét một kiểu struct T với hai phương thức,
Mv, có receiver kiểu T, và
Mp, có receiver kiểu *T.
type T struct {
a int
}
func (tv T) Mv(a int) int { return 0 } // value receiver
func (tp *T) Mp(f float32) float32 { return 1 } // pointer receiver
var t T
Biểu thức
T.Mv
tạo ra một hàm tương đương với Mv nhưng
với một receiver rõ ràng là đối số đầu tiên của nó; nó có chữ ký
func(tv T, a int) int
Hàm đó có thể được gọi bình thường với một receiver rõ ràng, vì vậy năm lần gọi này là tương đương:
t.Mv(7) T.Mv(t, 7) (T).Mv(t, 7) f1 := T.Mv; f1(t, 7) f2 := (T).Mv; f2(t, 7)
Tương tự, biểu thức
(*T).Mp
tạo ra một giá trị hàm đại diện cho Mp với chữ ký
func(tp *T, f float32) float32
Đối với một phương thức với value receiver, người ta có thể dẫn xuất một hàm với một pointer receiver rõ ràng, vì vậy
(*T).Mv
tạo ra một giá trị hàm đại diện cho Mv với chữ ký
func(tv *T, a int) int
Một hàm như vậy gián tiếp qua receiver để tạo ra một giá trị để truyền như receiver cho phương thức cơ bản; phương thức không ghi đè giá trị mà địa chỉ được truyền vào trong lời gọi hàm.
Trường hợp cuối cùng, một hàm value-receiver cho một phương thức pointer-receiver, là bất hợp lệ vì các phương thức pointer-receiver không thuộc tập hợp phương thức của kiểu giá trị.
Các giá trị hàm được dẫn xuất từ phương thức được gọi với cú pháp gọi hàm;
receiver được cung cấp như đối số đầu tiên của lời gọi.
Nghĩa là, cho
Câu lệnh điều khiển việc thực thi.
Một câu lệnh kết thúc làm gián đoạn luồng điều khiển thông thường trong
một khối. Các câu lệnh sau đây là câu lệnh kết thúc:
Tất cả các câu lệnh khác không phải là câu lệnh kết thúc.
Một danh sách câu lệnh kết thúc bằng một câu lệnh kết thúc nếu danh sách
không rỗng và câu lệnh không rỗng cuối cùng của nó là câu lệnh kết thúc.
Câu lệnh rỗng không làm gì cả.
Một câu lệnh có nhãn có thể là đích của câu lệnh
Ngoại trừ một số hàm dựng sẵn nhất định,
các lời gọi hàm và phương thức cũng như
thao tác nhận
có thể xuất hiện trong ngữ cảnh câu lệnh. Các câu lệnh như vậy có thể được đặt trong ngoặc đơn.
Các hàm dựng sẵn sau đây không được phép trong ngữ cảnh câu lệnh:
Một câu lệnh gửi gửi một giá trị trên một channel.
Biểu thức channel phải thuộc kiểu channel,
hướng của channel phải cho phép thao tác gửi,
và kiểu của giá trị được gửi phải có thể gán
cho kiểu phần tử của channel.
Cả biểu thức channel lẫn biểu thức giá trị đều được định lượng trước khi quá trình giao tiếp
bắt đầu. Giao tiếp bị chặn cho đến khi việc gửi có thể tiến hành.
Một lần gửi trên channel không có bộ đệm có thể tiến hành nếu có một bên nhận sẵn sàng.
Một lần gửi trên channel có bộ đệm có thể tiến hành nếu còn chỗ trống trong bộ đệm.
Một lần gửi trên channel đã đóng sẽ gây ra panic lúc chạy.
Một lần gửi trên channel
Nếu kiểu của biểu thức channel là một
tham số kiểu,
tất cả các kiểu trong tập hợp kiểu của nó phải là kiểu channel cho phép thao tác gửi,
tất cả phải có cùng kiểu phần tử,
và kiểu của giá trị được gửi phải có thể gán cho kiểu phần tử đó.
Các câu lệnh "++" và "--" tăng hoặc giảm toán hạng của chúng
theo hằng số không có kiểu
Các câu lệnh gán sau đây tương đương về mặt ngữ nghĩa:
Một phép gán thay thế giá trị hiện tại được lưu trong một biến
bằng một giá trị mới được xác định bởi một biểu thức.
Một câu lệnh gán có thể gán một giá trị đơn cho một biến đơn, hoặc nhiều giá trị cho một
số lượng biến tương ứng.
Mỗi toán hạng ở vế trái phải có địa chỉ,
là một biểu thức chỉ mục map, hoặc (chỉ với phép gán
Một phép gán có toán tử
Một phép gán bộ giá trị gán từng phần tử của một thao tác đa trị
cho một danh sách biến. Có hai dạng. Trong dạng
thứ nhất, toán hạng vế phải là một biểu thức đa trị đơn
chẳng hạn như một lời gọi hàm, một thao tác channel hoặc
map, hoặc một khẳng định kiểu.
Số lượng toán hạng ở vế trái
phải khớp với số lượng giá trị. Ví dụ, nếu
gán giá trị đầu tiên cho
Định danh rỗng cung cấp một cách để
bỏ qua các giá trị vế phải trong một phép gán:
Phép gán được thực hiện qua hai giai đoạn.
Đầu tiên, các toán hạng của biểu thức chỉ mục
và phép giải tham chiếu con trỏ
(bao gồm cả phép giải tham chiếu con trỏ ngầm trong bộ chọn)
ở vế trái và các biểu thức ở vế phải đều được
định lượng theo thứ tự thông thường.
Thứ hai, các phép gán được thực hiện theo thứ tự từ trái sang phải.
Trong các phép gán, mỗi giá trị phải có thể gán
cho kiểu của toán hạng mà nó được gán vào, với các trường hợp đặc biệt sau:
Khi một giá trị được gán cho một biến, chỉ có dữ liệu được lưu trong biến
mới được thay thế. Nếu giá trị chứa một tham chiếu,
phép gán sao chép tham chiếu nhưng không tạo bản sao của dữ liệu được tham chiếu
(chẳng hạn như mảng nền của một slice).
Câu lệnh "if" xác định việc thực thi có điều kiện của hai nhánh
theo giá trị của một biểu thức boolean. Nếu biểu thức
định lượng thành true, nhánh "if" được thực thi, ngược lại, nếu
có mặt, nhánh "else" được thực thi.
Biểu thức có thể được đứng trước bởi một câu lệnh đơn giản,
câu lệnh đó được thực thi trước khi biểu thức được định lượng.
Câu lệnh "switch" cung cấp khả năng thực thi theo nhiều hướng.
Một biểu thức hoặc kiểu được so sánh với các "case"
bên trong "switch" để xác định nhánh nào
sẽ được thực thi.
Có hai dạng: switch biểu thức và switch kiểu.
Trong switch biểu thức, các case chứa các biểu thức được so sánh
với giá trị của biểu thức switch.
Trong switch kiểu, các case chứa các kiểu được so sánh với
kiểu của một biểu thức switch được chú thích đặc biệt.
Biểu thức switch được định lượng đúng một lần trong một câu lệnh switch.
Trong switch biểu thức,
biểu thức switch được định lượng và
các biểu thức case, không nhất thiết phải là hằng số,
được định lượng từ trái sang phải và từ trên xuống dưới; biểu thức đầu tiên bằng với
biểu thức switch
sẽ kích hoạt thực thi các câu lệnh của case tương ứng;
các case khác bị bỏ qua.
Nếu không có case nào khớp và có case "default",
các câu lệnh của nó được thực thi.
Có thể có nhiều nhất một case default và nó có thể xuất hiện ở bất kỳ đâu trong
câu lệnh "switch".
Một biểu thức switch vắng mặt tương đương với giá trị boolean
Nếu biểu thức switch định lượng thành một hằng số không có kiểu, nó trước tiên được
chuyển đổi ngầm sang kiểu mặc định của nó.
Giá trị không có kiểu được khai báo trước
Nếu một biểu thức case không có kiểu, nó trước tiên được chuyển đổi ngầm
sang kiểu của biểu thức switch.
Với mỗi biểu thức case
Nói cách khác, biểu thức switch được xử lý như thể nó được dùng để khai báo và
khởi tạo một biến tạm thời
Trong một mệnh đề case hoặc default, câu lệnh không rỗng cuối cùng
có thể là một câu lệnh "fallthrough" (có thể có nhãn) để
chỉ ra rằng luồng điều khiển nên chuyển từ cuối mệnh đề này sang
câu lệnh đầu tiên của mệnh đề tiếp theo.
Ngược lại, luồng điều khiển chuyển đến cuối câu lệnh "switch".
Câu lệnh "fallthrough" có thể xuất hiện là câu lệnh cuối cùng của tất cả
các mệnh đề ngoại trừ mệnh đề cuối cùng của một switch biểu thức.
Biểu thức switch có thể được đứng trước bởi một câu lệnh đơn giản,
câu lệnh đó được thực thi trước khi biểu thức được định lượng.
Hạn chế triển khai: Trình biên dịch có thể không cho phép nhiều biểu thức case
định lượng thành cùng một hằng số.
Ví dụ, các trình biên dịch hiện tại không cho phép các hằng số nguyên, số thực dấu chấm động,
hoặc chuỗi trùng lặp trong các biểu thức case.
Một switch kiểu so sánh các kiểu thay vì các giá trị. Ngoài ra nó tương tự
như switch biểu thức. Nó được đánh dấu bằng một biểu thức switch đặc biệt có
dạng của một khẳng định kiểu
sử dụng từ khóa
Khi đó các case khớp các kiểu thực tế
TypeSwitchGuard có thể bao gồm một
khai báo biến ngắn.
Khi dạng đó được sử dụng, biến được khai báo ở cuối
TypeSwitchCase trong khối ngầm của mỗi mệnh đề.
Trong các mệnh đề có case liệt kê đúng một kiểu, biến
có kiểu đó; ngược lại, biến có kiểu của biểu thức
trong TypeSwitchGuard.
Thay vì một kiểu, một case có thể sử dụng định danh được khai báo trước
Cho biểu thức
có thể được viết lại thành:
Một tham số kiểu hoặc một kiểu generic
có thể được sử dụng làm kiểu trong một case. Nếu khi khởi tạo, kiểu đó trùng lặp
với một mục khác trong switch, case khớp đầu tiên được chọn.
Bộ bảo vệ switch kiểu có thể được đứng trước bởi một câu lệnh đơn giản,
câu lệnh đó được thực thi trước khi bộ bảo vệ được định lượng.
Câu lệnh "fallthrough" không được phép trong switch kiểu.
Câu lệnh "for" chỉ định việc thực thi lặp lại của một khối. Có ba dạng:
Việc lặp có thể được kiểm soát bởi một điều kiện đơn, một mệnh đề "for", hoặc một mệnh đề "range".
Ở dạng đơn giản nhất, câu lệnh "for" chỉ định việc thực thi lặp lại của
một khối miễn là một điều kiện boolean định lượng thành true.
Điều kiện được định lượng trước mỗi lần lặp.
Nếu điều kiện vắng mặt, nó tương đương với giá trị boolean
Câu lệnh "for" với ForClause cũng được kiểm soát bởi điều kiện của nó, nhưng
ngoài ra nó có thể chỉ định một câu lệnh init
và một câu lệnh post, chẳng hạn như một phép gán,
một câu lệnh tăng hoặc giảm. Câu lệnh init có thể là một
khai báo biến ngắn, nhưng câu lệnh post thì không được.
Nếu không rỗng, câu lệnh init được thực thi một lần trước khi định lượng
điều kiện cho lần lặp đầu tiên;
câu lệnh post được thực thi sau mỗi lần thực thi khối (và
chỉ khi khối đã được thực thi).
Bất kỳ phần tử nào của ForClause có thể rỗng nhưng các
dấu chấm phẩy
là bắt buộc trừ khi chỉ có một điều kiện.
Nếu điều kiện vắng mặt, nó tương đương với giá trị boolean
Mỗi lần lặp có biến (hoặc các biến) được khai báo riêng
[Go 1.22].
Biến được sử dụng trong lần lặp đầu tiên được khai báo bởi câu lệnh init.
Biến được sử dụng trong mỗi lần lặp tiếp theo được khai báo ngầm trước
khi thực thi câu lệnh post và được khởi tạo thành giá trị của biến
của lần lặp trước tại thời điểm đó.
in ra
Trước [Go 1.22], các lần lặp dùng chung một tập hợp biến
thay vì có các biến riêng của chúng.
Trong trường hợp đó, ví dụ trên in ra
Câu lệnh "for" với mệnh đề "range"
lặp qua tất cả các mục của một mảng, slice, chuỗi hoặc map, các giá trị được nhận trên
một channel, các giá trị nguyên từ không đến một giới hạn trên [Go 1.22],
hoặc các giá trị được truyền cho hàm yield của một hàm iterator [Go 1.23].
Với mỗi mục, nó gán các giá trị lặp
cho các biến lặp tương ứng nếu có, rồi thực thi khối.
Biểu thức ở vế phải trong mệnh đề "range" được gọi là biểu thức range,
có thể là một mảng, con trỏ đến mảng, slice, chuỗi, map, channel cho phép
thao tác nhận, một số nguyên, hoặc
một hàm với chữ ký cụ thể (xem bên dưới).
Như với một phép gán, nếu có, các toán hạng ở vế trái phải
có địa chỉ hoặc là biểu thức chỉ mục map; chúng
biểu thị các biến lặp.
Nếu biểu thức range là một hàm, số lượng biến lặp tối đa phụ thuộc vào
chữ ký hàm.
Nếu biểu thức range là một channel hoặc số nguyên, cho phép nhiều nhất một biến lặp;
ngược lại có thể có đến hai biến.
Nếu biến lặp cuối cùng là định danh rỗng,
mệnh đề range tương đương với mệnh đề tương tự không có định danh đó.
Biểu thức range
Các lời gọi hàm ở vế trái được định lượng một lần mỗi lần lặp.
Với mỗi lần lặp, các giá trị lặp được tạo ra như sau
nếu các biến lặp tương ứng có mặt:
Các biến lặp có thể được khai báo bởi mệnh đề "range" bằng dạng
khai báo biến ngắn
(
Nếu các biến lặp không được khai báo tường minh bởi mệnh đề "range",
chúng phải đã tồn tại trước.
Trong trường hợp này, các giá trị lặp được gán cho các biến tương ứng
như trong một câu lệnh gán.
Nếu kiểu của biểu thức range là một tham số kiểu,
tất cả các kiểu trong tập hợp kiểu của nó phải có cùng kiểu nền và biểu thức range phải hợp lệ
với kiểu đó, hoặc, nếu tập hợp kiểu chứa các kiểu channel, nó chỉ được chứa các kiểu channel với
các kiểu phần tử giống nhau, và tất cả các kiểu channel phải cho phép thao tác nhận.
Câu lệnh "go" bắt đầu thực thi một lời gọi hàm
như một luồng điều khiển đồng thời độc lập, hay goroutine,
trong cùng một không gian địa chỉ.
Biểu thức phải là một lời gọi hàm hoặc phương thức; nó không thể được đặt trong ngoặc đơn.
Các lời gọi hàm dựng sẵn bị hạn chế như đối với
câu lệnh biểu thức.
Giá trị hàm và các tham số được
định lượng như thông thường
trong goroutine đang gọi, nhưng
khác với một lời gọi thông thường, việc thực thi chương trình không chờ
hàm được gọi hoàn thành.
Thay vào đó, hàm bắt đầu thực thi độc lập
trong một goroutine mới.
Khi hàm kết thúc, goroutine của nó cũng kết thúc.
Nếu hàm có bất kỳ giá trị trả về nào, chúng bị loại bỏ khi
hàm hoàn thành.
Câu lệnh "select" chọn trong số một tập hợp các thao tác
gửi hoặc
nhận
có thể thực hiện được.
Nó trông tương tự như câu lệnh
"switch" nhưng với tất cả
các case đều tham chiếu đến các thao tác giao tiếp.
Một case với RecvStmt có thể gán kết quả của một RecvExpr cho một hoặc
hai biến, các biến đó có thể được khai báo bằng
khai báo biến ngắn.
RecvExpr phải là một thao tác nhận (có thể được đặt trong ngoặc đơn).
Có thể có nhiều nhất một case default và nó có thể xuất hiện ở bất kỳ đâu
trong danh sách các case.
Việc thực thi một câu lệnh "select" tiến hành qua nhiều bước:
Vì giao tiếp trên các channel
Câu lệnh "return" trong một hàm
Trong một hàm không có kiểu kết quả, câu lệnh "return" không được
chỉ định bất kỳ giá trị kết quả nào.
Có ba cách để trả về giá trị từ một hàm có kiểu kết quả:
Bất kể chúng được khai báo như thế nào, tất cả các giá trị kết quả đều được khởi tạo thành
giá trị không của kiểu của chúng khi vào
hàm. Câu lệnh "return" chỉ định các kết quả sẽ thiết lập các tham số kết quả trước
khi bất kỳ hàm defer nào được thực thi.
Hạn chế triển khai: Trình biên dịch có thể không cho phép một danh sách biểu thức rỗng
trong câu lệnh "return" nếu một thực thể khác (hằng số, kiểu, hoặc biến)
với cùng tên với một tham số kết quả đang nằm trong
phạm vi tại vị trí return.
Câu lệnh "break" kết thúc việc thực thi của câu lệnh
"for",
"switch", hoặc
"select" trong cùng nằm trong cùng
một hàm.
Nếu có một nhãn, nó phải là nhãn của một câu lệnh
"for", "switch", hoặc "select" bao ngoài,
và đó là câu lệnh mà việc thực thi của nó bị kết thúc.
Câu lệnh "continue" bắt đầu lần lặp tiếp theo của
vòng lặp "for" bao nằm trong cùng
bằng cách chuyển điều khiển đến cuối khối vòng lặp.
Vòng lặp "for" phải nằm trong cùng một hàm.
Nếu có một nhãn, nó phải là nhãn của một câu lệnh
"for" bao ngoài, và đó là câu lệnh mà việc thực thi của nó
tiến lên.
Câu lệnh "goto" chuyển điều khiển đến câu lệnh có nhãn tương ứng
trong cùng một hàm.
Thực thi câu lệnh "goto" không được làm bất kỳ biến nào đi vào
phạm vi mà chưa nằm trong phạm vi tại điểm goto.
Ví dụ, ví dụ này:
là sai vì bước nhảy đến nhãn
Câu lệnh "goto" bên ngoài một khối không thể nhảy đến một nhãn bên trong khối đó.
Ví dụ, ví dụ này:
là sai vì nhãn
Câu lệnh "fallthrough" chuyển điều khiển đến câu lệnh đầu tiên của
mệnh đề case tiếp theo trong một câu lệnh "switch" biểu thức.
Nó chỉ có thể được sử dụng là câu lệnh không rỗng cuối cùng trong một mệnh đề như vậy.
Câu lệnh "defer" gọi một hàm mà việc thực thi của nó được hoãn lại
đến thời điểm hàm bao quanh trả về, hoặc vì
hàm bao quanh đã thực thi một câu lệnh return,
đến cuối thân hàm của nó,
hoặc vì goroutine tương ứng đang panic.
Biểu thức phải là một lời gọi hàm hoặc phương thức; nó không thể được đặt trong ngoặc đơn.
Các lời gọi hàm dựng sẵn bị hạn chế như đối với
câu lệnh biểu thức.
Mỗi lần câu lệnh "defer"
thực thi, giá trị hàm và các tham số cho lời gọi được
định lượng như thông thường
và lưu lại mới nhưng hàm thực tế không được gọi.
Thay vào đó, các hàm defer được gọi ngay trước khi
hàm bao quanh trả về, theo thứ tự ngược lại
với thứ tự chúng được defer. Tức là, nếu hàm bao quanh
trả về thông qua một câu lệnh return tường minh,
các hàm defer được thực thi sau khi bất kỳ tham số kết quả nào được thiết lập
bởi câu lệnh return đó nhưng trước khi hàm trả về cho người gọi của nó.
Nếu một giá trị hàm defer định lượng thành
Ví dụ, nếu hàm defer là
một hàm literal và hàm bao quanh
có các tham số kết quả được đặt tên nằm trong
phạm vi trong literal đó, hàm defer có thể truy cập và sửa đổi
các tham số kết quả trước khi chúng được trả về.
Nếu hàm defer có bất kỳ giá trị trả về nào, chúng bị loại bỏ khi
hàm hoàn thành.
(Xem thêm phần về xử lý panic.)
Các hàm dựng sẵn được
khai báo trước.
Chúng được gọi như bất kỳ hàm nào khác nhưng một số trong chúng
chấp nhận một kiểu thay vì một biểu thức làm đối số đầu tiên.
Các hàm dựng sẵn không có kiểu Go chuẩn,
vì vậy chúng chỉ có thể xuất hiện trong biểu thức gọi hàm;
chúng không thể được dùng như các giá trị hàm.
Các hàm dựng sẵn
Hàm biến đổi tham số
Nếu
Nếu dung lượng của
Hàm
Nếu kiểu của một hoặc cả hai đối số là tham số kiểu,
tất cả các kiểu trong tập kiểu tương ứng của chúng phải có cùng kiểu slice cơ sở
Ví dụ:
Hàm dựng sẵn
Nếu kiểu của đối số truyền vào
Nếu map hoặc slice là
Đối với một channel
Nếu kiểu của đối số truyền vào
Ba hàm dùng để lắp ráp và phân tách số phức.
Hàm dựng sẵn
Kiểu của các đối số và giá trị trả về tương ứng nhau.
Đối với
Đối với
Các hàm
Nếu các toán hạng của các hàm này đều là hằng số, giá trị trả về
là một hằng số.
Các đối số có kiểu tham số kiểu không được phép.
Hàm dựng sẵn
Nếu kiểu của
Nếu map
Các hàm dựng sẵn
Nếu kiểu đối số là một tham số kiểu
Dung lượng của một slice là số phần tử mà có
không gian được cấp phát trong mảng cơ sở.
Tại bất kỳ thời điểm nào, mối quan hệ sau đây luôn đúng:
Độ dài của một slice, map hoặc channel
Biểu thức
Hàm dựng sẵn
Nếu đối số đầu tiên là một tham số kiểu,
tất cả các kiểu trong tập kiểu của nó phải có cùng kiểu cơ sở, phải là kiểu slice
hoặc map, hoặc nếu có kiểu channel, chỉ được có kiểu channel,
chúng phải đều có cùng kiểu phần tử, và chiều của channel không được xung đột.
Mỗi đối số kích thước
Gọi
Các hàm dựng sẵn
Các quy tắc kiểu tương tự như đối với toán tử áp dụng:
đối với các đối số có thứ tự
Đối với các đối số số, giả sử tất cả các NaN đều bằng nhau,
Đối với các đối số dấu phẩy động, số không âm, NaN, và vô cực, các quy tắc sau áp dụng:
Đối với các đối số chuỗi, kết quả của
Hàm dựng sẵn
Nếu đối số là một kiểu
Nếu đối số là một biểu thức
Ví dụ,
cấp phát một biến có kiểu Hai hàm dựng sẵn,
Trong khi thực thi một hàm
Hàm
Giá trị trả về của
Hàm
Các bản cài đặt hiện tại cung cấp một số hàm dựng sẵn hữu ích trong quá trình
bootstrapping. Các hàm này được ghi lại để đầy đủ nhưng không
được đảm bảo sẽ ở lại trong ngôn ngữ. Chúng không trả về kết quả.
Hạn chế bản cài đặt:
Các chương trình Go được xây dựng bằng cách liên kết các gói lại với nhau.
Một gói lần lượt được xây dựng từ một hoặc nhiều tệp nguồn
cùng nhau khai báo các hằng số, kiểu, biến và hàm
thuộc gói và có thể truy cập trong tất cả các tệp
của cùng gói đó. Các phần tử đó có thể được
xuất và dùng trong gói khác.
Mỗi tệp nguồn bao gồm một mệnh đề gói định nghĩa gói
mà nó thuộc về, theo sau là một tập hợp có thể rỗng các khai báo import
khai báo các gói mà nó muốn sử dụng,
theo sau là một tập hợp có thể rỗng các khai báo hàm,
kiểu, biến, và hằng số.
Một mệnh đề gói bắt đầu mỗi tệp nguồn và định nghĩa gói
mà tệp đó thuộc về.
PackageName không được là định danh trống.
Một tập hợp các tệp có cùng PackageName tạo thành bản cài đặt của một gói.
Một bản cài đặt có thể yêu cầu tất cả các tệp nguồn của một gói phải nằm trong cùng thư mục.
Một khai báo import cho biết rằng tệp nguồn chứa khai báo đó
phụ thuộc vào chức năng của gói được import
(§Khởi tạo và thực thi chương trình)
và cho phép truy cập vào các định danh được xuất
của gói đó.
Khai báo import đặt tên một định danh (PackageName) để dùng để truy cập và một ImportPath
chỉ định gói cần được import.
PackageName được dùng trong định danh đủ điều kiện
để truy cập các định danh được xuất của gói trong tệp nguồn đang import.
Nó được khai báo trong khối tệp.
Nếu PackageName bị bỏ qua, nó mặc định là định danh được chỉ định trong
mệnh đề gói của gói được import.
Nếu một dấu chấm tường minh (
Cách diễn giải ImportPath phụ thuộc vào bản cài đặt nhưng
thường là một chuỗi con của tên tệp đầy đủ của gói được biên dịch
và có thể liên quan đến một repository của các gói đã cài đặt.
Hạn chế bản cài đặt: Một trình biên dịch có thể hạn chế ImportPath chỉ
là các chuỗi không rỗng chỉ sử dụng các ký tự thuộc về
các danh mục
chung L, M, N, P và S của Unicode (các ký tự đồ họa không có
khoảng trắng) và cũng có thể loại trừ các ký tự
Hãy xét một gói đã biên dịch chứa mệnh đề gói
Một khai báo import khai báo một quan hệ phụ thuộc giữa
gói đang import và gói được import.
Việc một gói tự import chính nó, trực tiếp hay gián tiếp,
hoặc trực tiếp import một gói mà không
tham chiếu đến bất kỳ định danh được xuất nào của nó là bất hợp lệ. Để import một gói chỉ vì
tác dụng phụ của nó (khởi tạo), hãy dùng định danh trống
như tên gói tường minh:
Đây là một gói Go hoàn chỉnh cài đặt một sàng nguyên tố đồng thời.
Khi vùng nhớ được cấp phát cho một biến,
dù thông qua một khai báo hay một lời gọi
Hai khai báo đơn giản này tương đương:
Sau
những điều sau đây đúng:
Tương tự cũng đúng sau
Trong một gói, việc khởi tạo biến cấp gói tiến hành từng bước,
với mỗi bước chọn biến sớm nhất trong thứ tự khai báo
không có phụ thuộc vào các biến chưa được khởi tạo.
Chính xác hơn, một biến cấp gói được coi là sẵn sàng để
khởi tạo nếu nó chưa được khởi tạo và hoặc không có
biểu thức khởi tạo hoặc
biểu thức khởi tạo của nó không có phụ thuộc vào các biến chưa được khởi tạo.
Việc khởi tạo tiến hành bằng cách lặp đi lặp lại khởi tạo biến cấp gói tiếp theo
sớm nhất trong thứ tự khai báo và sẵn sàng để khởi tạo,
cho đến khi không còn biến nào sẵn sàng để khởi tạo.
Nếu bất kỳ biến nào vẫn chưa được khởi tạo khi quá trình này
kết thúc, các biến đó là một phần của một hoặc nhiều vòng lặp khởi tạo,
và chương trình không hợp lệ.
Nhiều biến ở vế trái của một khai báo biến được khởi tạo
bởi một biểu thức đơn (đa giá trị) ở vế phải được khởi tạo
cùng nhau: Nếu bất kỳ biến nào ở vế trái được khởi tạo, tất cả
các biến đó được khởi tạo trong cùng một bước.
Với mục đích khởi tạo gói, các biến trống
được xử lý như bất kỳ biến nào khác trong các khai báo.
Thứ tự khai báo của các biến được khai báo trong nhiều tệp được xác định
bởi thứ tự mà các tệp được trình bày cho trình biên dịch: Các biến
được khai báo trong tệp đầu tiên được khai báo trước bất kỳ biến nào được khai báo
trong tệp thứ hai, và cứ như vậy.
Để đảm bảo hành vi khởi tạo có thể tái lập được, các hệ thống xây dựng được khuyến khích
trình bày nhiều tệp thuộc cùng gói theo thứ tự tên tệp từ điển
cho trình biên dịch.
Phân tích phụ thuộc không dựa vào các giá trị thực tế của
các biến, chỉ dựa vào các tham chiếu từ vựng đến chúng trong nguồn,
được phân tích theo truyền bắc cầu. Ví dụ, nếu biểu thức khởi tạo của một biến
Ví dụ, với các khai báo sau
thứ tự khởi tạo là
Phân tích phụ thuộc được thực hiện theo từng gói; chỉ các tham chiếu đến
các biến, hàm và phương thức (không phải interface) được khai báo trong gói hiện tại
mới được xem xét. Nếu có các phụ thuộc dữ liệu ẩn khác giữa
các biến, thứ tự khởi tạo giữa các biến đó là không được chỉ định.
Ví dụ, với các khai báo sau
biến
Các biến cũng có thể được khởi tạo bằng cách dùng các hàm có tên
Nhiều hàm như vậy có thể được định nghĩa trong mỗi gói, thậm chí trong một
tệp nguồn duy nhất. Trong khối gói, định danh
Toàn bộ gói được khởi tạo bằng cách gán các giá trị ban đầu
cho tất cả các biến cấp gói của nó, theo sau là gọi
tất cả các hàm
Các gói của một chương trình hoàn chỉnh được khởi tạo từng bước, mỗi lần một gói.
Nếu một gói có các import, các gói được import sẽ được khởi tạo
trước khi khởi tạo chính gói đó. Nếu nhiều gói import
một gói, gói được import sẽ chỉ được khởi tạo một lần.
Việc import các gói, theo cấu trúc, đảm bảo rằng
không thể có các phụ thuộc khởi tạo tuần hoàn.
Chính xác hơn:
Với danh sách tất cả các gói, được sắp xếp theo đường dẫn import, trong mỗi bước gói đầu tiên
chưa được khởi tạo trong danh sách mà tất cả các gói được import (nếu có) đã được
khởi tạo sẽ được khởi tạo.
Bước này được lặp lại cho đến khi tất cả các gói được khởi tạo.
Khởi tạo gói—khởi tạo biến và việc gọi
các hàm
Một chương trình hoàn chỉnh được tạo ra bằng cách liên kết một gói duy nhất chưa được import
được gọi là gói main với tất cả các gói mà nó import, theo cách bắc cầu.
Gói main phải
có tên gói
Việc thực thi chương trình bắt đầu bằng cách khởi tạo chương trình
và sau đó gọi hàm
Kiểu được khai báo trước
Đây là interface thông thường để biểu diễn một điều kiện lỗi,
với giá trị nil biểu thị không có lỗi.
Ví dụ, một hàm để đọc dữ liệu từ một tệp có thể được định nghĩa:
Các lỗi thực thi chẳng hạn như cố gắng đánh chỉ số một mảng ngoài
giới hạn kích thước kích hoạt một panic lúc chạy tương đương với một lời gọi của
hàm dựng sẵn
Gói dựng sẵn
Một
Các hàm
Hàm
Các kiến trúc máy tính có thể yêu cầu các địa chỉ bộ nhớ phải được căn chỉnh;
tức là, đối với các địa chỉ của một biến phải là bội số của một hệ số,
căn chỉnh của kiểu biến. Hàm
Một (biến của) kiểu
Hàm
Hàm
ngoại trừ, như một trường hợp đặc biệt, nếu
Đối số
Hàm
Hàm
Hàm
Đối với các kiểu số, các kích thước sau được đảm bảo:
Các thuộc tính căn chỉnh tối thiểu sau được đảm bảo:
Một kiểu struct hoặc mảng có kích thước không nếu nó không chứa các trường (hoặc phần tử, tương ứng) có kích thước lớn hơn không. Hai biến có kích thước không khác biệt có thể có cùng địa chỉ trong bộ nhớ.
Đảm bảo tương thích Go 1 đảm bảo rằng
các chương trình được viết theo đặc tả Go 1 sẽ tiếp tục biên dịch và chạy
đúng, không thay đổi, trong suốt thời gian tồn tại của đặc tả đó.
Tổng quát hơn, khi các điều chỉnh được thực hiện và các tính năng được thêm vào ngôn ngữ,
đảm bảo tương thích đảm bảo rằng một chương trình Go hoạt động với một
phiên bản ngôn ngữ Go cụ thể sẽ tiếp tục hoạt động với bất kỳ phiên bản tiếp theo nào.
Ví dụ, khả năng sử dụng tiền tố
Bảng sau mô tả phiên bản ngôn ngữ tối thiểu cần thiết cho
các tính năng được giới thiệu sau Go 1.
Bản phát hành 1.18 thêm các hàm và kiểu đa hình ("generic") vào ngôn ngữ.
Cụ thể:
Các quy tắc hợp nhất kiểu mô tả liệu và cách hai kiểu hợp nhất với nhau.
Các chi tiết chính xác có liên quan đến các bản cài đặt Go,
ảnh hưởng đến các chi tiết cụ thể của thông báo lỗi (chẳng hạn như việc
trình biên dịch báo cáo lỗi suy luận kiểu hay lỗi khác),
và có thể giải thích lý do tại sao suy luận kiểu thất bại trong các tình huống mã bất thường.
Nhưng nhìn chung các quy tắc này có thể được bỏ qua khi viết mã Go:
suy luận kiểu được thiết kế để phần lớn "hoạt động như mong đợi",
và các quy tắc hợp nhất được tinh chỉnh cho phù hợp.
Hợp nhất kiểu được kiểm soát bởi một chế độ khớp, có thể là
chính xác hoặc lỏng.
Khi hợp nhất đệ quy đi xuống một cấu trúc kiểu tổng hợp,
chế độ khớp được sử dụng cho các phần tử của kiểu, chế độ khớp phần tử,
vẫn giống với chế độ khớp ngoại trừ khi hai kiểu được hợp nhất để
có thể gán (
Hai kiểu không phải tham số kiểu ràng buộc hợp nhất chính xác nếu bất kỳ
điều kiện nào sau đây là đúng:
Nếu cả hai kiểu đều là tham số kiểu ràng buộc, chúng hợp nhất theo các
chế độ khớp đã cho nếu:
Một tham số kiểu ràng buộc đơn
Cuối cùng, hai kiểu không phải tham số kiểu ràng buộc hợp nhất lỏng
(và theo chế độ khớp phần tử) nếu:
f := T.Mv, Câu lệnh
Statement = Declaration | LabeledStmt | SimpleStmt |
GoStmt | ReturnStmt | BreakStmt | ContinueStmt | GotoStmt |
FallthroughStmt | Block | IfStmt | SwitchStmt | SelectStmt | ForStmt |
DeferStmt .
SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl .
Câu lệnh kết thúc
panic.
Câu lệnh rỗng
EmptyStmt = .
Câu lệnh có nhãn
goto,
break hoặc continue.
LabeledStmt = Label ":" Statement .
Label = identifier .
Error: log.Panic("error encountered")
Câu lệnh biểu thức
ExpressionStmt = Expression .
append cap complex imag len make new real
unsafe.Add unsafe.Alignof unsafe.Offsetof unsafe.Sizeof unsafe.Slice unsafe.SliceData unsafe.String unsafe.StringData
h(x+y)
f.Close()
<-ch
(<-ch)
len("foo") // illegal if len is the built-in function
Câu lệnh gửi
SendStmt = Channel "<-" Expression .
Channel = Expression .
nil bị chặn mãi mãi.
ch <- 3 // send value 3 to channel ch
Câu lệnh tăng/giảm
1.
Như với một phép gán, toán hạng phải có địa chỉ
hoặc là một biểu thức chỉ mục map.
IncDecStmt = Expression ( "++" | "--" ) .
IncDec statement Assignment
x++ x += 1
x-- x -= 1
Câu lệnh gán
Assignment = ExpressionList assign_op ExpressionList .
assign_op = [ add_op | mul_op ] "=" .
=)
là định danh rỗng.
Các toán hạng có thể được đặt trong ngoặc đơn.
x = 1
*p = f()
a[i] = 23
(k) = <-ch // same as: k = <-ch
x op=
y trong đó op là một toán tử số học nhị phân
tương đương với x = x op
(y) nhưng chỉ định lượng x
một lần. Cấu trúc op= là một token đơn.
Trong các phép gán có toán tử, cả danh sách biểu thức vế trái lẫn vế phải
đều phải chứa đúng một biểu thức đơn trị, và biểu thức vế trái
không được là định danh rỗng.
a[i] <<= 2
i &^= 1<<n
f là một hàm trả về hai giá trị,
x, y = f()
x và giá trị thứ hai cho y.
Trong dạng thứ hai, số lượng toán hạng vế trái phải bằng số lượng
biểu thức vế phải, mỗi biểu thức phải đơn trị, và biểu thức
thứ n ở vế phải được gán cho toán hạng thứ n
ở vế trái:
one, two, three = '一', '二', '三'
_ = x // evaluate x but ignore it
x, _ = f() // evaluate f() but ignore second result value
a, b = b, a // exchange a and b
x := []int{1, 2, 3}
i := 0
i, x[i] = 1, 2 // set i = 1, x[0] = 2
i = 0
x[i], i = 2, 1 // set x[0] = 2, i = 1
x[0], x[0] = 1, 2 // set x[0] = 1, then x[0] = 2 (so x[0] == 2 at end)
x[1], x[3] = 4, 5 // set x[1] = 4, then panic setting x[3] = 5.
type Point struct { x, y int }
var p *Point
x[2], p.x = 6, 7 // set x[2] = 6, then panic setting p.x = 7
i = 2
x = []int{3, 5, 7}
for i, x[i] = range x { // set i, x[2] = 0, x[0]
break
}
// after this loop, i == 0 and x is []int{3, 5, 3}
bool.
var s1 = []int{1, 2, 3}
var s2 = s1 // s2 stores the slice descriptor of s1
s1 = s1[:1] // s1's length is 1 but it still shares its underlying array with s2
s2[0] = 42 // setting s2[0] changes s1[0] as well
fmt.Println(s1, s2) // prints [42] [42 2 3]
var m1 = make(map[string]int)
var m2 = m1 // m2 stores the map descriptor of m1
m1["foo"] = 42 // setting m1["foo"] changes m2["foo"] as well
fmt.Println(m2["foo"]) // prints 42
Câu lệnh if
IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] .
if x > max {
x = max
}
if x := f(); x < y {
return x
} else if x > z {
return z
} else {
return y
}
Câu lệnh switch
SwitchStmt = ExprSwitchStmt | TypeSwitchStmt .
Switch biểu thức
true.
ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" .
ExprCaseClause = ExprSwitchCase ":" StatementList .
ExprSwitchCase = "case" ExpressionList | "default" .
nil không thể được dùng làm biểu thức switch.
Kiểu của biểu thức switch phải có thể so sánh.
x (có thể đã được chuyển đổi) và giá trị t
của biểu thức switch, x == t phải là một phép so sánh hợp lệ.
t không có kiểu tường minh; chính
giá trị của t đó được kiểm tra bằng với từng biểu thức case x.
switch tag {
default: s3()
case 0, 1, 2, 3: s1()
case 4, 5, 6, 7: s2()
}
switch x := f(); { // missing switch expression means "true"
case x < 0: return -x
default: return x
}
switch {
case x < y: f1()
case x < z: f2()
case x == 4: f3()
}
Switch kiểu
type thay vì một kiểu thực tế:
switch x.(type) {
// cases
}
T với kiểu động của
biểu thức x. Như với các khẳng định kiểu, x phải thuộc
kiểu interface, nhưng không phải là
tham số kiểu, và mỗi kiểu không phải interface
T được liệt kê trong một case phải triển khai kiểu của x.
Các kiểu được liệt kê trong các case của một switch kiểu phải đều
khác nhau.
TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" .
TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" .
TypeCaseClause = TypeSwitchCase ":" StatementList .
TypeSwitchCase = "case" TypeList | "default" .
nil;
case đó được chọn khi biểu thức trong TypeSwitchGuard
là một giá trị interface nil.
Có thể có nhiều nhất một case nil.
x thuộc kiểu interface{},
switch kiểu sau:
switch i := x.(type) {
case nil:
printString("x is nil") // type of i is type of x (interface{})
case int:
printInt(i) // type of i is int
case float64:
printFloat64(i) // type of i is float64
case func(int) float64:
printFunction(i) // type of i is func(int) float64
case bool, string:
printString("type is bool or string") // type of i is type of x (interface{})
default:
printString("don't know the type") // type of i is type of x (interface{})
}
v := x // x is evaluated exactly once
if v == nil {
i := v // type of i is type of x (interface{})
printString("x is nil")
} else if i, isInt := v.(int); isInt {
printInt(i) // type of i is int
} else if i, isFloat64 := v.(float64); isFloat64 {
printFloat64(i) // type of i is float64
} else if i, isFunc := v.(func(int) float64); isFunc {
printFunction(i) // type of i is func(int) float64
} else {
_, isBool := v.(bool)
_, isString := v.(string)
if isBool || isString {
i := v // type of i is type of x (interface{})
printString("type is bool or string")
} else {
i := v // type of i is type of x (interface{})
printString("don't know the type")
}
}
func f[P any](x any) int {
switch x.(type) {
case P:
return 0
case string:
return 1
case []P:
return 2
case []byte:
return 3
default:
return 4
}
}
var v1 = f[string]("foo") // v1 == 0
var v2 = f[byte]([]byte{}) // v2 == 2
Câu lệnh for
ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .
Condition = Expression .
Câu lệnh for với điều kiện đơn
true.
for a < b {
a *= 2
}
Câu lệnh for với mệnh đề
for
ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .
InitStmt = SimpleStmt .
PostStmt = SimpleStmt .
for i := 0; i < 10; i++ {
f(i)
}
true.
for cond { S() } is the same as for ; cond ; { S() }
for { S() } is the same as for true { S() }
var prints []func()
for i := 0; i < 5; i++ {
prints = append(prints, func() { println(i) })
i++
}
for _, p := range prints {
p()
}
1
3
5
6
6
6
Câu lệnh for với mệnh đề
range
RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .
x được định lượng trước khi bắt đầu vòng lặp,
với một ngoại lệ: nếu có nhiều nhất một biến lặp và x hoặc
len(x) là hằng số,
biểu thức range không được định lượng.
Range expression 1st value 2nd value
array or slice a [n]E, *[n]E, or []E index i int a[i] E
string s string type index i int see below rune
map m map[K]V key k K m[k] V
channel c chan E, <-chan E element e E
integer value n integer type, or untyped int value i see below
function, 0 values f func(func() bool)
function, 1 value f func(func(V) bool) value v V
function, 2 values f func(func(K, V) bool) key k K v V
a, các giá trị lặp chỉ mục
được tạo ra theo thứ tự tăng dần, bắt đầu từ chỉ mục phần tử 0.
Nếu có nhiều nhất một biến lặp, vòng lặp range tạo ra
các giá trị lặp từ 0 đến len(a)-1 và không lập chỉ mục vào mảng
hoặc slice đó. Với một slice nil, số lần lặp là 0.
rune, sẽ là giá trị của
điểm mã tương ứng. Nếu khi lặp gặp phải
một chuỗi UTF-8 không hợp lệ, giá trị thứ hai sẽ là 0xFFFD,
ký tự thay thế Unicode, và lần lặp tiếp theo sẽ tiến
một byte trong chuỗi.
nil, số lần lặp là 0.
nil, biểu thức range bị chặn mãi mãi.
n, trong đó n thuộc kiểu nguyên
hoặc là một hằng số nguyên không có kiểu, các giá trị lặp từ 0 đến n-1
được tạo ra theo thứ tự tăng dần.
Nếu n thuộc kiểu nguyên, các giá trị lặp có cùng kiểu đó.
Ngược lại, kiểu của n được xác định như thể nó được gán cho
biến lặp.
Cụ thể:
nếu biến lặp đã tồn tại trước, kiểu của các giá trị lặp là kiểu của biến lặp,
phải thuộc kiểu nguyên.
Ngược lại, nếu biến lặp được khai báo bởi mệnh đề "range" hoặc vắng mặt,
kiểu của các giá trị lặp là kiểu mặc định cho n.
Nếu n <= 0, vòng lặp không chạy bất kỳ lần lặp nào.
f, việc lặp tiến hành bằng cách gọi f
với một hàm yield tổng hợp mới làm đối số của nó.
Nếu yield được gọi trước khi f trả về,
các đối số cho yield trở thành các giá trị lặp
để thực thi thân vòng lặp một lần.
Sau mỗi lần lặp vòng lặp kế tiếp, yield trả về true
và có thể được gọi lại để tiếp tục vòng lặp.
Miễn là thân vòng lặp không kết thúc, mệnh đề "range" sẽ tiếp tục
tạo ra các giá trị lặp theo cách này cho mỗi lời gọi yield cho đến khi
f trả về.
Nếu thân vòng lặp kết thúc (chẳng hạn bởi một câu lệnh break),
yield trả về false và không được gọi lại.
:=).
Trong trường hợp này, phạm vi của chúng là khối của câu lệnh "for"
và mỗi lần lặp có các biến mới riêng [Go 1.22]
(xem thêm câu lệnh "for" với ForClause).
Các biến có kiểu của các giá trị lặp tương ứng của chúng.
var testdata *struct {
a *[7]int
}
for i, _ := range testdata.a {
// testdata.a is never evaluated; len(testdata.a) is constant
// i ranges from 0 to 6
f(i)
}
var a [10]string
for i, s := range a {
// type of i is int
// type of s is string
// s == a[i]
g(i, s)
}
var key string
var val interface{} // element type of m is assignable to val
m := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6}
for key, val = range m {
h(key, val)
}
// key == last map key encountered in iteration
// val == map[key]
var ch chan Work = producer()
for w := range ch {
doWork(w)
}
// empty a channel
for range ch {}
// call f(0), f(1), ... f(9)
for i := range 10 {
// type of i is int (default type for untyped constant 10)
f(i)
}
// invalid: 256 cannot be assigned to uint8
var u uint8
for u = range 256 {
}
// invalid: 1e3 is a floating-point constant
for range 1e3 {
}
// fibo generates the Fibonacci sequence
fibo := func(yield func(x int) bool) {
f0, f1 := 0, 1
for yield(f0) {
f0, f1 = f1, f0+f1
}
}
// print the Fibonacci numbers below 1000:
for x := range fibo {
if x >= 1000 {
break
}
fmt.Printf("%d ", x)
}
// output: 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
// iteration support for a recursive tree data structure
type Tree[K cmp.Ordered, V any] struct {
left, right *Tree[K, V]
key K
value V
}
func (t *Tree[K, V]) walk(yield func(key K, val V) bool) bool {
return t == nil || t.left.walk(yield) && yield(t.key, t.value) && t.right.walk(yield)
}
func (t *Tree[K, V]) Walk(yield func(key K, val V) bool) {
t.walk(yield)
}
// walk tree t in-order
var t Tree[string, int]
for k, v := range t.Walk {
// process k, v
}
Câu lệnh go
GoStmt = "go" Expression .
go Server()
go func(ch chan<- bool) { for { sleep(10); ch <- true }} (c)
Câu lệnh select
SelectStmt = "select" "{" { CommClause } "}" .
CommClause = CommCase ":" StatementList .
CommCase = "case" ( SendStmt | RecvStmt ) | "default" .
RecvStmt = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr .
RecvExpr = Expression .
nil không bao giờ có thể tiến hành,
một select chỉ có các channel nil và không có case default sẽ bị chặn mãi mãi.
var a []int
var c, c1, c2, c3, c4 chan int
var i1, i2 int
select {
case i1 = <-c1:
print("received ", i1, " from c1\n")
case c2 <- i2:
print("sent ", i2, " to c2\n")
case i3, ok := (<-c3): // same as: i3, ok := <-c3
if ok {
print("received ", i3, " from c3\n")
} else {
print("c3 is closed\n")
}
case a[f()] = <-c4:
// same as:
// case t := <-c4
// a[f()] = t
default:
print("no communication\n")
}
for { // send random sequence of bits to c
select {
case c <- 0: // note: no statement, no fallthrough, no folding of cases
case c <- 1:
}
}
select {} // block forever
Câu lệnh return
F kết thúc việc thực thi
của F, và tùy chọn cung cấp một hoặc nhiều giá trị kết quả.
Bất kỳ hàm nào được defer bởi F
đều được thực thi trước khi F trả về cho người gọi của nó.
ReturnStmt = "return" [ ExpressionList ] .
func noResult() {
return
}
func simpleF() int {
return 2
}
func complexF1() (re float64, im float64) {
return -7.0, -4.0
}
func complexF2() (re float64, im float64) {
return complexF1()
}
func complexF3() (re float64, im float64) {
re = 7.0
im = 4.0
return
}
func (devnull) Write(p []byte) (n int, _ error) {
n = len(p)
return
}
func f(n int) (res int, err error) {
if _, err := f(n-1); err != nil {
return // invalid return statement: err is shadowed
}
return
}
Câu lệnh break
BreakStmt = "break" [ Label ] .
OuterLoop:
for i = 0; i < n; i++ {
for j = 0; j < m; j++ {
switch a[i][j] {
case nil:
state = Error
break OuterLoop
case item:
state = Found
break OuterLoop
}
}
}
Câu lệnh continue
ContinueStmt = "continue" [ Label ] .
RowLoop:
for y, row := range rows {
for x, data := range row {
if data == endOfRow {
continue RowLoop
}
row[x] = data + bias(x, y)
}
}
Câu lệnh goto
GotoStmt = "goto" Label .
goto Error
goto L // BAD
v := 3
L:
L bỏ qua
việc tạo v.
if n%2 == 1 {
goto L1
}
for n > 0 {
f()
n--
L1:
f()
n--
}
L1 nằm bên trong
khối của câu lệnh "for" nhưng goto thì không.
Câu lệnh fallthrough
FallthroughStmt = "fallthrough" .
Câu lệnh defer
DeferStmt = "defer" Expression .
nil, việc thực thi sẽ panic
khi hàm được gọi, không phải khi câu lệnh "defer" được thực thi.
lock(l)
defer unlock(l) // unlocking happens before surrounding function returns
// prints 3 2 1 0 before surrounding function returns
for i := 0; i <= 3; i++ {
defer fmt.Print(i)
}
// f returns 42
func f() (result int) {
defer func() {
// result is accessed after it was set to 6 by the return statement
result *= 7
}()
return 6
}
Các hàm dựng sẵn
append vào và copy slice
append và copy hỗ trợ
các thao tác slice thông dụng.
Đối với cả hai hàm, kết quả không phụ thuộc vào việc vùng nhớ được tham chiếu
bởi các đối số có chồng lấp hay không.
append
nối thêm không hoặc nhiều giá trị x vào một slice s có
kiểu S và trả về slice kết quả, cũng có kiểu
S.
Các giá trị x được truyền vào một tham số có kiểu ...E
trong đó E là kiểu phần tử của S
và các quy tắc truyền tham số tương ứng được áp dụng.
Như một trường hợp đặc biệt, append cũng chấp nhận một slice có kiểu có thể gán cho
kiểu []byte với đối số thứ hai có kiểu string theo sau bởi
....
Dạng này nối thêm các byte của chuỗi.
append(s S, x ...E) S // E is the element type of S
S là một tham số kiểu,
tất cả các kiểu trong tập kiểu của nó phải có cùng kiểu slice cơ sở []E.
s không đủ lớn để chứa thêm các
giá trị, append cấp phát một mảng cơ sở mới đủ lớn
để chứa cả các phần tử slice hiện có lẫn các giá trị bổ sung.
Ngược lại, append tái sử dụng mảng cơ sở hiện có.
s0 := []int{0, 0}
s1 := append(s0, 2) // append a single element s1 is []int{0, 0, 2}
s2 := append(s1, 3, 5, 7) // append multiple elements s2 is []int{0, 0, 2, 3, 5, 7}
s3 := append(s2, s0...) // append a slice s3 is []int{0, 0, 2, 3, 5, 7, 0, 0}
s4 := append(s3[3:6], s3[2:]...) // append overlapping slice s4 is []int{3, 5, 7, 2, 3, 5, 7, 0, 0}
var t []interface{}
t = append(t, 42, 3.1415, "foo") // t is []interface{}{42, 3.1415, "foo"}
var b []byte
b = append(b, "bar"...) // append string contents b is []byte{'b', 'a', 'r' }
copy copy các phần tử slice từ
nguồn src đến đích dst và trả về
số lượng phần tử đã được copy.
Cả hai đối số phải có kiểu phần tử giống hệt
E và phải có thể gán cho một slice có kiểu []E.
Số lượng phần tử được copy là giá trị nhỏ nhất trong
len(src) và len(dst).
Như một trường hợp đặc biệt, copy cũng chấp nhận một đối số đích
có thể gán cho kiểu []byte với một đối số nguồn có kiểu
string.
Dạng này copy các byte từ chuỗi vào slice byte.
copy(dst, src []T) int
copy(dst []byte, src string) int
[]E.
var a = [...]int{0, 1, 2, 3, 4, 5, 6, 7}
var s = make([]int, 6)
var b = make([]byte, 5)
n1 := copy(s, a[0:]) // n1 == 6, s is []int{0, 1, 2, 3, 4, 5}
n2 := copy(s, s[2:]) // n2 == 4, s is []int{2, 3, 4, 5, 4, 5}
n3 := copy(b, "Hello, World!") // n3 == 5, b is []byte("Hello")
Clear
clear nhận một đối số có kiểu map,
slice, hoặc tham số kiểu,
và xóa hoặc đặt về không tất cả các phần tử
[Go 1.21].
Call Argument type Result
clear(m) map[K]T deletes all entries, resulting in an
empty map (len(m) == 0)
clear(s) []T sets all elements up to the length of
s to the zero value of T
clear(t) type parameter see below
clear là
tham số kiểu,
tất cả các kiểu trong tập kiểu của nó phải là map hoặc slice, và clear
thực hiện thao tác tương ứng với đối số kiểu thực tế.
nil, clear là một thao tác không làm gì.
Close
ch, hàm dựng sẵn close(ch)
ghi nhận rằng sẽ không còn giá trị nào được gửi trên channel đó nữa.
Đây là lỗi nếu ch là channel chỉ nhận.
Gửi vào hoặc close một channel đã đóng gây ra một panic lúc chạy.
Closing channel nil cũng gây ra một panic lúc chạy.
Sau khi gọi close, và sau khi tất cả các giá trị đã được gửi trước đó được nhận, các thao tác nhận sẽ trả về
giá trị không của kiểu channel mà không bị chặn.
Thao tác nhận đa giá trị
trả về một giá trị đã nhận kèm theo chỉ báo về việc channel có bị đóng hay không.
close là
tham số kiểu,
tất cả các kiểu trong tập kiểu của nó phải là channel.
Đây là lỗi nếu bất kỳ channel nào trong số đó là channel chỉ nhận.
Thao tác với số phức
complex tạo ra một giá trị phức
từ phần thực và phần ảo dấu phẩy động, trong khi
real và imag
trích xuất phần thực và phần ảo của một giá trị phức.
complex(realPart, imaginaryPart floatT) complexT
real(complexT) floatT
imag(complexT) floatT
complex, hai đối số phải có cùng
kiểu dấu phẩy động và kiểu trả về là
kiểu phức
với các thành phần dấu phẩy động tương ứng:
complex64 cho các đối số float32, và
complex128 cho các đối số float64.
Nếu một trong các đối số được đánh giá là một hằng số không có kiểu, nó sẽ trước tiên được
chuyển đổi ngầm định sang kiểu của đối số kia.
Nếu cả hai đối số đều được đánh giá là các hằng số không có kiểu, chúng phải là các số không phức
hoặc phần ảo của chúng phải bằng không, và giá trị trả về của
hàm là một hằng số phức không có kiểu.
real và imag, đối số phải có
kiểu phức, và kiểu trả về là kiểu dấu phẩy động tương ứng:
float32 cho đối số complex64, và
float64 cho đối số complex128.
Nếu đối số được đánh giá là một hằng số không có kiểu, nó phải là một số,
và giá trị trả về của hàm là một hằng số dấu phẩy động không có kiểu.
real và imag cùng nhau tạo thành nghịch đảo của
complex, vì vậy đối với một giá trị z của kiểu phức Z,
z == Z(complex(real(z), imag(z))).
var a = complex(2, -2) // complex128
const b = complex(1.0, -1.4) // untyped complex constant 1 - 1.4i
x := float32(math.Cos(math.Pi/2)) // float32
var c64 = complex(5, -x) // complex64
var s int = complex(1, 0) // untyped complex constant 1 + 0i can be converted to int
_ = complex(1, 2<<s) // illegal: 2 assumes floating-point type, cannot shift
var rl = real(c64) // float32
var im = imag(a) // float64
const c = imag(b) // untyped constant -1.4
_ = imag(3 << s) // illegal: 3 assumes complex type, cannot shift
Xóa phần tử map
delete xóa phần tử có khóa
k khỏi một map m. Giá trị
k phải có thể gán
cho kiểu khóa của m.
delete(m, k) // remove element m[k] from map m
m là một tham số kiểu,
tất cả các kiểu trong tập kiểu đó phải là map, và chúng phải đều có kiểu khóa giống hệt nhau.
m là nil hoặc phần tử m[k]
không tồn tại, delete là một thao tác không làm gì.
Độ dài và dung lượng
len và cap nhận các đối số
có nhiều kiểu khác nhau và trả về kết quả có kiểu int.
Bản cài đặt đảm bảo rằng kết quả luôn vừa trong một int.
Call Argument type Result
len(s) string type string length in bytes
[n]T, *[n]T array length (== n)
[]T slice length
map[K]T map length (number of defined keys)
chan T number of elements queued in channel buffer
type parameter see below
cap(s) [n]T, *[n]T array length (== n)
[]T slice capacity
chan T channel buffer capacity
type parameter see below
P,
lời gọi len(e) (hoặc cap(e) tương ứng) phải hợp lệ với
mỗi kiểu trong tập kiểu của P.
Kết quả là độ dài (hoặc dung lượng, tương ứng) của đối số có kiểu
tương ứng với đối số kiểu mà P được
khởi tạo.
0 <= len(s) <= cap(s)
nil là 0.
Dung lượng của một slice hoặc channel nil là 0.
len(s) là hằng số nếu
s là một hằng số chuỗi. Các biểu thức len(s) và
cap(s) là hằng số nếu kiểu của s là một mảng
hoặc con trỏ đến mảng và biểu thức s không chứa
nhận channel hoặc
lời gọi hàm (không phải hằng số); trong trường hợp này s không được đánh giá.
Ngược lại, các lời gọi len và cap không phải là
hằng số và s được đánh giá.
const (
c1 = imag(2i) // imag(2i) = 2.0 is a constant
c2 = len([10]float64{2}) // [10]float64{2} contains no function calls
c3 = len([10]float64{c1}) // [10]float64{c1} contains no function calls
c4 = len([10]float64{imag(2i)}) // imag(2i) is a constant and no function call is issued
c5 = len([10]float64{imag(z)}) // invalid: imag(z) is a (non-constant) function call
)
var z complex128
Tạo slice, map và channel
make nhận một kiểu T,
phải là kiểu slice, map hoặc channel, hoặc một tham số kiểu,
tùy chọn theo sau bởi một danh sách biểu thức đặc thù theo kiểu.
Nó trả về một giá trị có kiểu T (không phải *T).
Vùng nhớ được khởi tạo như mô tả trong phần về
giá trị ban đầu.
Call Type T Result
make(T, n) slice slice of type T with length n and capacity n
make(T, n, m) slice slice of type T with length n and capacity m
make(T) map map of type T
make(T, n) map map of type T with initial space for approximately n elements
make(T) channel unbuffered channel of type T
make(T, n) channel buffered channel of type T, buffer size n
make(T, n) type parameter see below
make(T, n, m) type parameter see below
n và m phải có kiểu nguyên,
có một tập kiểu chỉ chứa các kiểu nguyên,
hoặc là một hằng số không có kiểu.
Một đối số kích thước hằng số phải không âm và có thể biểu diễn
bởi một giá trị có kiểu int; nếu nó là một hằng số không có kiểu thì được gán kiểu int.
Nếu cả n và m đều được cung cấp và đều là hằng số, thì
n không được lớn hơn m.
Đối với slice và channel, nếu n âm hoặc lớn hơn m lúc chạy,
một panic lúc chạy xảy ra.
s := make([]int, 10, 100) // slice with len(s) == 10, cap(s) == 100
s := make([]int, 1e3) // slice with len(s) == cap(s) == 1000
s := make([]int, 1<<63) // illegal: len(s) is not representable by a value of type int
s := make([]int, 10, 0) // illegal: len(s) > cap(s)
c := make(chan int, 10) // channel with a buffer size of 10
m := make(map[string]int, 100) // map with initial space for approximately 100 elements
make với kiểu map và gợi ý kích thước n sẽ
tạo ra một map với không gian ban đầu để chứa n phần tử map.
Hành vi chính xác phụ thuộc vào bản cài đặt.
Min và max
min và max tính toán
giá trị nhỏ nhất—hoặc lớn nhất, tương ứng—trong số một lượng cố định
các đối số có kiểu có thứ tự.
Phải có ít nhất một đối số
[Go 1.21].
x và
y, min(x, y) hợp lệ nếu x + y hợp lệ,
và kiểu của min(x, y) là kiểu của x + y
(và tương tự cho max).
Nếu tất cả các đối số là hằng số, kết quả là hằng số.
var x, y int
m := min(x) // m == x
m := min(x, y) // m is the smaller of x and y
m := max(x, y, 10) // m is the larger of x and y but at least 10
c := max(1, 2.0, 10) // c == 10.0 (floating-point kind)
f := max(0, float32(x)) // type of f is float32
var s []string
_ = min(s...) // invalid: slice arguments are not permitted
t := max("", "foo", "bar") // t == "foo" (string kind)
min và max có tính
giao hoán và kết hợp:
min(x, y) == min(y, x)
min(x, y, z) == min(min(x, y), z) == min(x, min(y, z))
x y min(x, y) max(x, y)
-0.0 0.0 -0.0 0.0 // negative zero is smaller than (non-negative) zero
-Inf y -Inf y // negative infinity is smaller than any other number
+Inf y y +Inf // positive infinity is larger than any other number
NaN y NaN NaN // if any argument is a NaN, the result is a NaN
min là đối số đầu tiên
có giá trị nhỏ nhất (hoặc đối với max, lớn nhất),
so sánh theo thứ tự từ điển theo từng byte:
min(x, y) == if x <= y then x else y
min(x, y, z) == min(min(x, y), z)
Cấp phát bộ nhớ
new tạo ra một biến mới được khởi tạo
và trả về một con trỏ đến nó.
Nó chấp nhận một đối số duy nhất, có thể là kiểu hoặc biểu thức.
T, thì new(T)
cấp phát một biến có kiểu T được khởi tạo về
giá trị không của nó.
x, thì new(x)
cấp phát một biến có kiểu của x được khởi tạo về giá trị của x.
Nếu giá trị đó là một hằng số không có kiểu, nó trước tiên được chuyển đổi ngầm định
về kiểu mặc định của nó;
nếu là một giá trị boolean không có kiểu, nó được chuyển đổi ngầm định sang kiểu bool.
Định danh được khai báo trước nil không thể được dùng như một đối số cho new.
new(int) và new(123) đều
trả về một con trỏ đến một biến mới có kiểu int.
Giá trị của biến đầu tiên là 0, và giá trị
của biến thứ hai là 123. Tương tự
type S struct { a int; b float64 }
new(S)
S,
khởi tạo nó (a=0, b=0.0),
và trả về một giá trị có kiểu *S chứa địa chỉ
của biến đó.
Xử lý panic
panic và recover,
hỗ trợ trong việc báo cáo và xử lý panic lúc chạy
và các điều kiện lỗi do chương trình định nghĩa.
func panic(interface{})
func recover() interface{}
F,
một lời gọi tường minh đến panic hoặc một panic lúc chạy
sẽ kết thúc việc thực thi F.
Bất kỳ hàm nào được trì hoãn bởi F
sau đó được thực thi như thường.
Tiếp theo, bất kỳ hàm trì hoãn nào được chạy bởi caller của F cũng được chạy,
và cứ như vậy cho đến bất kỳ hàm trì hoãn nào bởi hàm cấp cao nhất trong goroutine đang thực thi.
Tại thời điểm đó, chương trình bị kết thúc và điều kiện lỗi
được báo cáo, bao gồm giá trị của đối số cho panic.
Chuỗi kết thúc này được gọi là panicking.
panic(42)
panic("unreachable")
panic(Error("cannot parse"))
recover cho phép một chương trình quản lý hành vi
của một goroutine đang panic.
Giả sử một hàm G trì hoãn một hàm D gọi
recover và một panic xảy ra trong một hàm trên cùng goroutine mà G
đang thực thi.
Khi việc chạy các hàm trì hoãn đến D,
giá trị trả về của lời gọi recover của D sẽ là giá trị được truyền vào lời gọi của panic.
Nếu D trả về bình thường, mà không bắt đầu một
panic mới, chuỗi panicking dừng lại. Trong trường hợp đó,
trạng thái của các hàm được gọi giữa G và lời gọi đến panic
bị loại bỏ, và việc thực thi bình thường tiếp tục.
Bất kỳ hàm nào được trì hoãn bởi G trước D sau đó được chạy và việc
thực thi G kết thúc bằng cách trả về cho caller của nó.
recover là nil khi
goroutine không đang panic hoặc recover không được gọi trực tiếp bởi một hàm trì hoãn.
Ngược lại, nếu một goroutine đang panic và recover được gọi trực tiếp bởi một hàm trì hoãn,
giá trị trả về của recover được đảm bảo không phải nil.
Để đảm bảo điều này, việc gọi panic với một giá trị interface nil (hoặc một nil không có kiểu)
gây ra một panic lúc chạy.
protect trong ví dụ dưới đây gọi
đối số hàm g và bảo vệ các caller khỏi
các panic lúc chạy gây ra bởi g.
func protect(g func()) {
defer func() {
log.Println("done") // Println executes normally even if there is a panic
if x := recover(); x != nil {
log.Printf("run time panic: %v", x)
}
}()
log.Println("start")
g()
}
Bootstrapping
Function Behavior
print prints all arguments; formatting of arguments is implementation-specific
println like print but prints spaces between arguments and a newline at the end
print và println không nhất thiết
phải chấp nhận các kiểu đối số tùy ý, nhưng việc in các
kiểu boolean, số và chuỗi phải được hỗ trợ.
Các gói
Tổ chức tệp nguồn
SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .
Mệnh đề gói
PackageClause = "package" PackageName .
PackageName = identifier .
package math
Các khai báo import
ImportDecl = "import" ( ImportSpec | "(" { ImportSpec ";" } ")" ) .
ImportSpec = [ "." | PackageName ] ImportPath .
ImportPath = string_lit .
.) xuất hiện thay vì tên, tất cả
các định danh được xuất của gói được khai báo trong
khối gói của gói đó sẽ được khai báo trong khối tệp của tệp nguồn đang import
và phải được truy cập mà không cần bộ định danh.
!"#$%&'()*,:;<=>?[\]^`{|}
và ký tự thay thế Unicode U+FFFD.
package math, xuất hàm Sin, và
đã cài đặt gói được biên dịch trong tệp được xác định bởi
"lib/math".
Bảng này minh họa cách Sin được truy cập trong các tệp
import gói sau các
dạng khai báo import khác nhau.
Import declaration Local name of Sin
import "lib/math" math.Sin
import m "lib/math" m.Sin
import . "lib/math" Sin
import _ "lib/math"
Một gói ví dụ
package main
import "fmt"
// Send the sequence 2, 3, 4, … to channel 'ch'.
func generate(ch chan<- int) {
for i := 2; ; i++ {
ch <- i // Send 'i' to channel 'ch'.
}
}
// Copy the values from channel 'src' to channel 'dst',
// removing those divisible by 'prime'.
func filter(src <-chan int, dst chan<- int, prime int) {
for i := range src { // Loop over values received from 'src'.
if i%prime != 0 {
dst <- i // Send 'i' to channel 'dst'.
}
}
}
// The prime sieve: Daisy-chain filter processes together.
func sieve() {
ch := make(chan int) // Create a new channel.
go generate(ch) // Start generate() as a subprocess.
for {
prime := <-ch
fmt.Print(prime, "\n")
ch1 := make(chan int)
go filter(ch, ch1, prime)
ch = ch1
}
}
func main() {
sieve()
}
Khởi tạo và thực thi chương trình
Giá trị không
new, hoặc khi
một giá trị mới được tạo ra, dù thông qua một literal tổng hợp hay một lời gọi
make,
và không có khởi tạo tường minh nào được cung cấp, biến hoặc giá trị đó được
gán một giá trị mặc định. Mỗi phần tử của một biến hoặc giá trị như vậy được
đặt về giá trị không của kiểu của nó: false cho boolean,
0 cho các kiểu số, ""
cho chuỗi, và nil cho con trỏ, hàm, interface, slice, channel và map.
Quá trình khởi tạo này được thực hiện đệ quy, vì vậy ví dụ mỗi phần tử của một
mảng struct sẽ có các trường của nó được đặt về không nếu không có giá trị nào được chỉ định.
var i int
var i int = 0
type T struct { i int; f float64; next *T }
t := new(T)
t.i == 0
t.f == 0.0
t.next == nil
var t T
Khởi tạo gói
var x = a
var a, b = f() // a and b are initialized together, before x is initialized
x
tham chiếu đến một hàm có thân tham chiếu đến
biến y thì x phụ thuộc vào y.
Cụ thể:
m là một
giá trị phương thức hoặc
biểu thức phương thức có dạng
t.m, trong đó kiểu (tĩnh) của t là
không phải kiểu interface, và phương thức m nằm trong
tập phương thức của t.
Việc giá trị hàm kết quả
t.m có được gọi hay không là không quan trọng.
x phụ thuộc vào một biến
y nếu biểu thức khởi tạo hoặc thân
(đối với hàm và phương thức) của x chứa một tham chiếu đến y
hoặc đến một hàm hay phương thức phụ thuộc vào y.
var (
a = c + b // == 9
b = f() // == 4
c = f() // == 5
d = 3 // == 5 after initialization has finished
)
func f() int {
d++
return d
}
d, b, c, a.
Lưu ý rằng thứ tự của các biểu thức con trong các biểu thức khởi tạo là không liên quan:
a = c + b và a = b + c dẫn đến cùng thứ tự khởi tạo
trong ví dụ này.
var x = I(T{}).ab() // x has an undetected, hidden dependency on a and b
var _ = sideEffect() // unrelated to x, a, or b
var a = b
var b = 42
type I interface { ab() []int }
type T struct{}
func (T) ab() []int { return []int{a, b} }
a sẽ được khởi tạo sau b nhưng
việc x được khởi tạo trước b, giữa
b và a, hay sau a, và
do đó thời điểm sideEffect() được gọi (trước
hay sau khi x được khởi tạo) là không được chỉ định.
init
được khai báo trong khối gói, không có đối số và không có tham số kết quả.
func init() { … }
init chỉ có thể
được dùng để khai báo các hàm init, nhưng định danh
chính nó không được khai báo. Do đó
các hàm init không thể được tham chiếu từ bất cứ đâu
trong một chương trình.
init theo thứ tự chúng xuất hiện
trong nguồn, có thể trong nhiều tệp, như được trình bày
cho trình biên dịch.
Khởi tạo chương trình
init—xảy ra trong một goroutine duy nhất,
tuần tự, mỗi lần một gói.
Một hàm init có thể khởi chạy các goroutine khác, có thể chạy
đồng thời với mã khởi tạo. Tuy nhiên, quá trình khởi tạo
luôn xếp hàng
các hàm init: nó sẽ không gọi hàm tiếp theo
cho đến khi hàm trước đó đã trả về.
Thực thi chương trình
main và
khai báo một hàm main không nhận
đối số và không trả về giá trị.
func main() { … }
main trong gói main.
Khi lời gọi hàm đó trả về, chương trình thoát.
Nó không chờ các goroutine khác (không phải main) hoàn thành.
Lỗi
error được định nghĩa là
type error interface {
Error() string
}
func Read(f *File, b []byte) (n int, err error)
Panic lúc chạy
panic
với một giá trị của kiểu interface do bản cài đặt định nghĩa runtime.Error.
Kiểu đó thỏa mãn kiểu interface được khai báo trước
error.
Các giá trị lỗi chính xác biểu diễn các điều kiện lỗi lúc chạy khác nhau là không được chỉ định.
package runtime
type Error interface {
error
// and perhaps other methods
}
Các lưu ý hệ thống
Gói
unsafeunsafe, được trình biên dịch biết đến
và có thể truy cập thông qua đường dẫn import "unsafe",
cung cấp các tiện ích cho lập trình cấp thấp bao gồm các thao tác
vi phạm hệ thống kiểu. Một gói sử dụng unsafe
phải được kiểm tra thủ công về an toàn kiểu và có thể không mang tính di động.
Gói cung cấp interface sau:
package unsafe
type ArbitraryType int // shorthand for an arbitrary Go type; it is not a real type
type Pointer *ArbitraryType
func Alignof(variable ArbitraryType) uintptr
func Offsetof(selector ArbitraryType) uintptr
func Sizeof(variable ArbitraryType) uintptr
type IntegerType int // shorthand for an integer type; it is not a real type
func Add(ptr Pointer, len IntegerType) Pointer
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
func SliceData(slice []ArbitraryType) *ArbitraryType
func String(ptr *byte, len IntegerType) string
func StringData(str string) *byte
Pointer là một kiểu con trỏ nhưng một giá trị Pointer
không thể được giải tham chiếu.
Bất kỳ con trỏ hay giá trị nào có kiểu cơ sở uintptr đều có thể được
chuyển đổi sang một kiểu có kiểu cơ sở Pointer và ngược lại.
Nếu các kiểu tương ứng là tham số kiểu, tất cả các kiểu trong
các tập kiểu tương ứng của chúng phải có cùng kiểu cơ sở, lần lượt là uintptr và
Pointer.
Hiệu ứng của việc chuyển đổi giữa Pointer và uintptr phụ thuộc vào bản cài đặt.
var f float64
bits = *(*uint64)(unsafe.Pointer(&f))
type ptr unsafe.Pointer
bits = *(*uint64)(ptr(&f))
func f[P ~*B, B any](p P) uintptr {
return uintptr(unsafe.Pointer(p))
}
var p ptr = nil
Alignof và Sizeof nhận một biểu thức x
của bất kỳ kiểu nào và trả về căn chỉnh hoặc kích thước, tương ứng, của một biến giả thuyết v
như thể v được khai báo qua var v = x.
Offsetof nhận một selector (có thể có dấu ngoặc đơn)
s.f, biểu thị một trường f của struct được biểu thị bởi s
hoặc *s, và trả về độ lệch trường tính bằng byte so với địa chỉ của struct.
Nếu f là một trường nhúng, nó phải có thể tiếp cận được
mà không cần đi qua con trỏ gián tiếp qua các trường của struct.
Đối với một struct s có trường f:
uintptr(unsafe.Pointer(&s)) + unsafe.Offsetof(s.f) == uintptr(unsafe.Pointer(&s.f))
Alignof
nhận một biểu thức biểu thị một biến của bất kỳ kiểu nào và trả về
căn chỉnh của (kiểu) biến tính bằng byte. Đối với một biến
x:
uintptr(unsafe.Pointer(&x)) % unsafe.Alignof(x) == 0
T có kích thước biến đổi nếu T
là một tham số kiểu, hoặc nếu nó là một
kiểu mảng hay struct chứa các phần tử
hoặc trường có kích thước biến đổi. Ngược lại kích thước là hằng số.
Các lời gọi đến Alignof, Offsetof, và Sizeof
là các biểu thức hằng số lúc biên dịch có
kiểu uintptr nếu các đối số của chúng (hoặc struct s trong
biểu thức selector s.f đối với Offsetof) là các kiểu
có kích thước hằng số.
Add cộng len vào ptr
và trả về con trỏ đã được cập nhật unsafe.Pointer(uintptr(ptr) + uintptr(len))
[Go 1.17].
Đối số len phải có kiểu nguyên hoặc là một hằng số không có kiểu.
Một đối số len hằng số phải có thể biểu diễn bởi một giá trị có kiểu int;
nếu nó là một hằng số không có kiểu thì được gán kiểu int.
Các quy tắc cho các cách dùng hợp lệ của Pointer vẫn áp dụng.
Slice trả về một slice có mảng cơ sở bắt đầu tại ptr
và có độ dài và dung lượng là len.
Slice(ptr, len) tương đương với
(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]
ptr
là nil và len bằng không,
Slice trả về nil
[Go 1.17].
len phải có kiểu nguyên hoặc là một hằng số không có kiểu.
Một đối số len hằng số phải không âm và có thể biểu diễn bởi một giá trị có kiểu int;
nếu nó là một hằng số không có kiểu thì được gán kiểu int.
Lúc chạy, nếu len âm,
hoặc nếu ptr là nil và len khác không,
một panic lúc chạy xảy ra
[Go 1.17].
SliceData trả về một con trỏ đến mảng cơ sở của đối số slice.
Nếu dung lượng của slice cap(slice) khác không, con trỏ đó là &slice[:1][0].
Nếu slice là nil, kết quả là nil.
Ngược lại nó là một con trỏ không phải nil đến một địa chỉ bộ nhớ không xác định
[Go 1.20].
String trả về một giá trị string có các byte cơ sở bắt đầu tại
ptr và có độ dài là len.
Các yêu cầu tương tự áp dụng cho đối số ptr và len như trong hàm
Slice. Nếu len bằng không, kết quả là chuỗi rỗng "".
Vì các chuỗi Go là bất biến, các byte được truyền vào String không được sửa đổi sau đó.
[Go 1.20]
StringData trả về một con trỏ đến các byte cơ sở của đối số str.
Đối với một chuỗi rỗng, giá trị trả về là không xác định và có thể là nil.
Vì các chuỗi Go là bất biến, các byte được trả về bởi StringData không được sửa đổi
[Go 1.20].
Đảm bảo kích thước và căn chỉnh
type size in bytes
byte, uint8, int8 1
uint16, int16 2
uint32, int32, float32 4
uint64, int64, float64, complex64 8
complex128 16
x của bất kỳ kiểu nào: unsafe.Alignof(x) là ít nhất 1.
x của kiểu struct: unsafe.Alignof(x) là giá trị lớn nhất trong
tất cả các giá trị unsafe.Alignof(x.f) cho mỗi trường f của x, nhưng ít nhất là 1.
x của kiểu mảng: unsafe.Alignof(x) giống với
căn chỉnh của một biến có kiểu phần tử của mảng.
Phụ lục
Các phiên bản ngôn ngữ
0b cho các
literal nguyên nhị phân được giới thiệu với Go 1.13, được chỉ ra
bởi [Go 1.13] trong phần về
literal nguyên.
Mã nguồn chứa một literal nguyên như 0b1011
sẽ bị từ chối nếu phiên bản ngôn ngữ ngầm định hoặc bắt buộc được sử dụng bởi
trình biên dịch cũ hơn Go 1.13.
Go 1.9
Go 1.13
0b, 0B, 0o,
và 0O cho các literal nhị phân và bát phân, tương ứng.
0x và 0X.
i có thể được dùng với bất kỳ
literal nguyên hoặc dấu phẩy động nào (nhị phân, thập phân, thập lục phân), không chỉ các literal thập phân.
_.
Go 1.14
Go 1.17
unsafe bao gồm các hàm mới
Add và Slice.
Go 1.18
~.
~T.
any và comparable.
Go 1.20
unsafe bao gồm các hàm mới
SliceData, String, và StringData.
comparable, ngay cả khi các đối số kiểu không hoàn toàn có thể so sánh.
Go 1.21
min, max, và clear.
Go 1.22
Go 1.23
Go 1.24
Các quy tắc hợp nhất kiểu
≡A):
trong trường hợp này, chế độ khớp là lỏng ở cấp cao nhất nhưng
sau đó thay đổi thành chính xác cho các kiểu phần tử, phản ánh thực tế
rằng các kiểu không cần phải giống hệt nhau để có thể gán được.
≡A
(hợp nhất lỏng ở cấp cao nhất và hợp nhất chính xác
cho các kiểu phần tử).
P và một kiểu khác T hợp nhất
theo các chế độ khớp đã cho nếu:
P không có đối số kiểu đã biết.
Trong trường hợp này, T được suy luận là đối số kiểu cho P.
P có đối số kiểu đã biết A,
A và T hợp nhất theo các chế độ khớp đã cho,
và một trong các điều kiện sau là đúng:
A và T đều là kiểu interface:
Trong trường hợp này, nếu cả A và T đều là
các kiểu đã định nghĩa,
chúng phải giống hệt nhau.
Ngược lại, nếu không có kiểu nào trong số chúng là kiểu đã định nghĩa, chúng phải
có cùng số lượng phương thức
(sự hợp nhất của A và T đã
xác lập rằng các phương thức khớp nhau).
A lẫn T đều không phải kiểu interface:
Trong trường hợp này, nếu T là kiểu đã định nghĩa, T
thay thế A làm đối số kiểu được suy luận cho P.