Какой идиоматичный эквивалент тернарного оператора C в Go?
В языках C/C++ (и во многих других языках, принадлежащих к этой семье) распространенным приемом при объявлении и инициализации переменной в зависимости от условия является использование тернарного оператора:
int index = val > 0 ? val : -val
В языке Go нет тернарного оператора. Как наиболее идиоматично реализовать тот же кусок кода, что и выше? Я пришел к следующему решению, но оно кажется слишком многословным:
var index int
if val > 0 {
index = val
} else {
index = -val
}
Есть ли что-то лучше?
5 ответ(ов)
В языке C тернарный оператор позволяет компактно записывать условные выражения. Например, следующий код:
int a = test ? 1 : 2;
в Go не имеет аналогичного тернарного оператора, поэтому вместо него часто используют if
блок:
var a int
if test {
a = 1
} else {
a = 2
}
Однако в некоторых случаях такая запись может оказаться неудобной, например, если необходимо использовать инлайн-выражение в шаблоне генерации кода. В этом случае я нашел решение, использовав немедленно вызываемую анонимную функцию:
a := func() int { if test { return 1 } return 2 }()
Такой способ гарантирует, что ни одна из веток не будет выполнена, что может быть важно для оптимизации или специфики вашего кода.
В вашем примере кода используется карта, и с помощью тернарного оператора вы выбираете значение в зависимости от условия.
Код выглядит следующим образом:
c := map[bool]int{true: 1, false: 0}[5 > 4]
Здесь 5 > 4
возвращает true
, поэтому в переменную c
будет записано значение 1
. Такой подход действительно удобен для чтения, так как позволяет избежать использования дополнительных скобок.
Вместо стандартной конструкции if
, этот метод делает ваш код более лаконичным и выразительным. Если у вас есть дополнительные вопросы по этому коду или его применению, не стесняйтесь спрашивать!
Ваш код демонстрирует использование тернарной функции для вычисления абсолютного значения числа. Хотя это решение действительно работает, оно имеет несколько недостатков.
Во-первых, тернарная функция Ternary
возвращает интерфейс, что требует явного приведения типа (.(int)
) в функции Abs
. Это добавляет накладные расходы времени выполнения и усложняет код.
Во-вторых, как показали бенчмарки, использование конструкции if/else значительно более производительно. В вашем примере, производительность Abs
с использованием тернарной функции составляет 18.8 нс на операцию, тогда как обычное условие if/else достигает 0.27 нс на операцию. Это означает, что вариант с if/else выполнится примерно в 70 раз быстрее.
Если требуется максимальная производительность, рекомендуется использовать стандартную конструкцию if/else:
func Abs(n int) int {
if n >= 0 {
return n
}
return -n
}
Таким образом, можно улучшить как читаемость, так и производительность кода.
Как было отмечено другими участниками, язык Go не имеет тернарного оператора или его эквивалента. Это осознанное решение, направленное на улучшение читаемости кода.
На днях я столкнулся с ситуацией, когда создание битовой маски стало сложным для восприятия при написании в идиоматичном стиле, а также очень неэффективным при инкапсуляции в функции - или тем и другим, так как код генерировал ветвления:
package lib
func maskIfTrue(mask uint64, predicate bool) uint64 {
if predicate {
return mask
}
return 0
}
При компиляции это приводит к:
text "".maskIfTrue(SB), NOSPLIT|ABIInternal, $0-24
funcdata $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
funcdata $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
movblzx "".predicate+16(SP), AX
testb AL, AL
jeq maskIfTrue_pc20
movq "".mask+8(SP), AX
movq AX, "".~r2+24(SP)
ret
maskIfTrue_pc20:
movq $0, "".~r2+24(SP)
ret
Из этого я извлек для себя урок использовать немного больше возможностей Go. Использование именованного результата в функции (result int)
позволяет сэкономить строку на его объявление внутри функции (то же самое можно сделать с захватами), но компилятор также распознает этот идиом (использовать присвоение только при условии IF) и, если возможно, заменяет его на условную инструкцию.
func zeroOrOne(predicate bool) (result int) {
if predicate {
result = 1
}
return
}
Код без ветвлений выглядит так:
movblzx "".predicate+8(SP), AX
movq AX, "".result+16(SP)
ret
Что в свою очередь позволяет Go свободно инлайнить эту функцию.
package lib
func zeroOrOne(predicate bool) (result int) {
if predicate {
result = 1
}
return
}
type Vendor1 struct {
Property1 int
Property2 float32
Property3 bool
}
// Позиции битов Vendor2.
const (
Property1Bit = 2
Property2Bit = 3
Property3Bit = 5
)
func Convert1To2(v1 Vendor1) (result int) {
result |= zeroOrOne(v1.Property1 == 1) << Property1Bit
result |= zeroOrOne(v1.Property2 < 0.0) << Property2Bit
result |= zeroOrOne(v1.Property3) << Property3Bit
return
}
Код генерирует https://go.godbolt.org/z/eKbK17:
movq "".v1+8(SP), AX
cmpq AX, $1
seteq AL
xorps X0, X0
movss "".v1+16(SP), X1
ucomiss X1, X0
sethi CL
movblzx AL, AX
shlq $2, AX
movblzx CL, CX
shlq $3, CX
orq CX, AX
movblzx "".v1+20(SP), CX
shlq $5, CX
orq AX, CX
movq CX, "".result+24(SP)
ret
Таким образом, использование именованных результатов помогает делать код более чистым и при этом позволяет компилятору оптимизировать выходные данные.
Ответ eold интересен и креативен, возможно, даже умный.
Тем не менее, рекомендуется сделать следующее:
var index int
if val > 0 {
index = printPositiveAndReturn(val)
} else {
index = slowlyReturn(-val) // или slowlyNegate(val)
}
Да, оба варианта в конечном счете компилируются в примерно одинаковый ассемблерный код, однако этот код гораздо более понятен, чем вызов анонимной функции просто для возвращения значения, которое могло бы быть записано в переменную изначально.
В общем, простой и ясный код лучше, чем креативный.
Кроме того, использование литералов для карт (maps) не является хорошей идеей, так как карты в Go совсем не легковесны. С версии Go 1.3 гарантируется случайный порядок итерации для небольших карт, и для обеспечения этого их память стала менее эффективной для маленьких карт.
В результате создание и удаление множества небольших карт занимает как место, так и время. У меня был фрагмент кода, который использовал небольшую карту (две или три ключа, вероятно, но общий случай - это всего одна запись). Однако код работал очень медленно. Мы говорим как минимум о трех порядках величины медленнее, чем тот же код, переписанный с использованием двустороннего среза key[index] => data[index]
. И, вероятно, даже больше. Некоторые операции, которые раньше занимали несколько минут, теперь выполнялись за миллисекунды.
Существует ли цикл foreach в Go?
Как сопоставить любой символ на нескольких строках в регулярном выражении?
Отформатировать строку в Go без вывода?
Как присвоить строку массиву байтов
Есть ли способ получить доступ к приватным полям структуры из другого пакета?