Так что это скорее теоретический вопрос. C ++ и языки (в), непосредственно основанные на нем (Java, C #, PHP), имеют операторы ярлыков для присвоения результата большинства бинарных операторов первому операнду, например

a += 3;   // for a = a + 3
a *= 3;   // for a = a * 3;
a <<= 3;  // for a = a << 3;

Но когда я хочу переключить логическое выражение, я всегда пишу что-то вроде

a = !a;

Что раздражает, когда a длинное выражение типа.

this.dataSource.trackedObject.currentValue.booleanFlag =
    !this.dataSource.trackedObject.currentValue.booleanFlag;

(да, закон Деметры, я знаю).

Поэтому мне было интересно, есть ли язык с унарным логическим оператором переключения , который позволил бы мне сокращать a = !a, не повторяя выражения для a, например

!=a;  
// or
a!!;

Давайте предположим, что наш язык имеет правильный логический тип (например, bool в C ++) и что a относится к этому типу (поэтому нет C-стиля int a = TRUE).

Если вы можете найти документированный источник, мне также было бы интересно узнать, например, дизайнеры C ++ решили добавить такой оператор, когда bool стал встроенным типом, и если да, то почему они решили отказаться от него.


(Примечание: я знаю, что некоторые люди считают, что назначение не должно использовать = и ++ и += не являются полезными операторами, но имеют недостатки в дизайне; давайте просто предположим, что я доволен ими и сосредоточимся на том, почему они не распространяются на bools).

141
CompuChip 20 Авг 2018 в 11:46

12 ответов

Лучший ответ

Этот вопрос действительно интересен с чисто теоретической точки зрения. Отбросив, будет ли полезен унарный, изменяющий логический оператор переключения , или почему многие языки решили не предоставлять его, я решил попробовать, действительно ли он существует.

TL; DR , очевидно, нет, но Swift позволяет реализовать его. Если вы хотите только увидеть, как это делается, вы можете прокрутить до конца этого ответа.


После (быстрого) поиска возможностей различных языков я могу с уверенностью сказать, что ни один язык не реализовал этот оператор как строгую мутирующую операцию на месте (исправьте меня, если найдете такой). Следующим шагом будет посмотреть, есть ли языки, которые позволят вам создать один. Для этого потребуются две вещи:

  1. возможность реализации (унарных) операторов с функциями
  2. позволяет указанным функциям иметь аргументы передачи по ссылке (чтобы они могли напрямую изменять свои аргументы)

Многие языки будут немедленно исключены из-за того, что они не поддерживают одно или оба из этих требований. Java для одного не допускает перегрузки операторов (или пользовательских операторов) и, кроме того, все примитивные типы передаются по значению. Go не поддерживает перегрузку операторов (за исключением хаков) вообще. Rust разрешает перегрузку операторов только для пользовательских типов. Вы можете почти добиться этого в Scala , который позволяет вам использовать очень креативно названные функции, а также опускать скобки, но, к сожалению, нет передачи по ссылке. Fortran очень близок к тому, что он допускает пользовательские операторы, но специально запрещает им иметь параметры inout (которые разрешены в обычных функциях и подпрограммах).


Однако есть по крайней мере один язык, отмечающий все необходимые поля: Swift . Хотя некоторые люди ссылались на предстоящую функцию-член .toggle () , вы также можете написать свой собственный оператор, который действительно поддерживает аргументы inout . И вот:

prefix operator ^

prefix func ^ (b: inout Bool) {
    b = !b
}

var foo = true
print(foo)
// true

^foo

print(foo)
// false
15
Lauri Piispanen 30 Авг 2018 в 12:45

В C ++ можно совершить кардинальный грех переопределения значения операторов. Имея это в виду и немного ADL, все, что нам нужно сделать, чтобы развеять хаос на нашей пользовательской базе, это:

#include <iostream>

namespace notstd
{
    // define a flag type
    struct invert_flag {    };

    // make it available in all translation units at zero cost
    static constexpr auto invert = invert_flag{};

    // for any T, (T << invert) ~= (T = !T)    
    template<class T>
    constexpr T& operator<<(T& x, invert_flag)
    {
        x = !x;
        return x;
    }
}

int main()
{
    // unleash Hell
    using notstd::invert;

    int a = 6;
    std::cout << a << std::endl;

    // let confusion reign amongst our hapless maintainers    
    a << invert;
    std::cout << a << std::endl;

    a << invert;
    std::cout << a << std::endl;

    auto b = false;
    std::cout << b << std::endl;

    b << invert;
    std::cout << b << std::endl;
}

Ожидаемый результат:

6
0
1
0
1
44
Ghost4Man 16 Авг 2019 в 14:11

Пока мы включаем язык ассемблера ...

ВПЕРЕД

INVERT для побитового дополнения.

0= для логического (true / false) дополнения.

37
DrSheldon 20 Авг 2018 в 22:47

Я предполагаю, что вы не собираетесь выбирать язык, основанный исключительно на этом :-) В любом случае вы можете сделать это в C ++ с помощью чего-то вроде:

inline void makenot(bool &b) { b = !b; }

Смотрите следующую полную программу, например:

#include <iostream>

inline void makenot(bool &b) { b = !b; }

inline void outBool(bool b) { std::cout << (b ? "true" : "false") << '\n'; }

int main() {
    bool this_dataSource_trackedObject_currentValue_booleanFlag = false;
    outBool(this_dataSource_trackedObject_currentValue_booleanFlag);

    makenot(this_dataSource_trackedObject_currentValue_booleanFlag);
    outBool(this_dataSource_trackedObject_currentValue_booleanFlag);

    makenot(this_dataSource_trackedObject_currentValue_booleanFlag);
    outBool(this_dataSource_trackedObject_currentValue_booleanFlag);
}

Это выводит, как и ожидалось:

false
true
false
28
paxdiablo 20 Авг 2018 в 09:12

PostScript, являющийся конкатенационный, стековый язык, например В-четвертых, имеет одинарный переключатель, не . Оператор not переключает значение в верхней части стека. Например,

true    % push true onto the stack
not     % invert the top of stack
        % the top of stack is now false

См. справочное руководство по языку PostScript (pdf) , р. 458 .

22
Wayne Conrad 21 Авг 2018 в 20:54

Visual Basic.Net поддерживает это с помощью метода расширения.

Определите метод расширения следующим образом:

<Extension>
Public Sub Flip(ByRef someBool As Boolean)
    someBool = Not someBool
End Sub

А потом назовите это так:

Dim someVariable As Boolean
someVariable = True
someVariable.Flip

Итак, ваш оригинальный пример будет выглядеть примерно так:

me.DataSource.TrackedObject.CurrentValue.BooleanFlag.Flip
21
Reginald Blue 20 Авг 2018 в 17:22

В Rust вы можете создать свою собственную черту для расширения типов, которые реализуют черту Not:

use std::ops::Not;
use std::mem::replace;

trait Flip {
    fn flip(&mut self);
}

impl<T> Flip for T
where
    T: Not<Output = T> + Default,
{
    fn flip(&mut self) {
        *self = replace(self, Default::default()).not();
    }
}

#[test]
fn it_works() {
    let mut b = true;
    b.flip();

    assert_eq!(b, false);
}

Вы также можете использовать ^= true, как предложено, и в случае с Rust для этого нет никакой проблемы, поскольку false не является «замаскированным» целым числом, как в C или C ++:

fn main() {
    let mut b = true;
    b ^= true;
    assert_eq!(b, false);

    let mut b = false;
    b ^= true;
    assert_eq!(b, true);
}
14
Boiethios 4 Сен 2018 в 14:52

В Python

Python поддерживает такую функциональность, если переменная имеет тип bool ( верно или неверно) с помощью exclusive or (^=) оператор:

a = False
a ^= True
print(a)  # --> True
a ^= True
print(a)  # --> False
6
Andriy Ivaneyko 12 Сен 2018 в 11:14

Переключение логического бита

... это позволило бы мне сокращать a = !a , не повторяя выражение для a ...

Этот подход на самом деле не является чистым оператором "перевернутого переворота", но он удовлетворяет вашим критериям выше; правая часть выражения не включает саму переменную.

Любой язык с логическим назначением XOR (например, ^=) позволил бы перевернуть текущее значение переменной, скажем, a, посредством назначения XOR true:

// type of a is bool
a ^= true;  // if a was false, it is now true,
            // if a was true, it is now false

Как указано @cmaster в комментариях ниже, в приведенном выше предполагается, что a имеет тип bool, а не, например, целое число или указатель. Если a на самом деле является чем-то другим (например, чем-то, не являющимся bool, оценивающим в «истинное» или «ложное» значение, с битовым представлением, которое не является 0b1 или {{X5} } соответственно) вышесказанное не имеет места.

Для конкретного примера, Java - это язык, в котором он четко определен и не подлежит никаким тихим преобразованиям. Цитирую @ комментарий Боанна снизу:

В Java ^ и ^= имеют явно определенное поведение для логических значений и для целых (15.22.2. Булевы логические операторы &, ^ и |), где либо обе стороны оператора должно быть логическим, или обе стороны должны быть целыми числами. Там нет тихого преобразования между этими типами. Так что это не собирается тихая неисправность, если a объявлен как целое число, а, скорее, дать ошибку компиляции. Так что a ^= true; безопасен и четко определен в Ява.


Свифт: toggle()

Начиная с Swift 4.2, следующее предложение об эволюции было принято и реализовано:

Это добавляет встроенную функцию toggle() к Bool в Swift.

< Сильный > toggle()

Переключает значение логической переменной.

< EM> Декларация

mutating func toggle()

Обсуждение

Используйте этот метод для переключения логического значения с true на false или от false до true.

var bools = [true, false]

bools[0].toggle() // bools == [false, false]

По сути, это не оператор, но он допускает использование родного языка для логического переключения.

143
André Pena 25 Авг 2018 в 10:34

Уменьшение C99 bool будет иметь желаемый эффект, равно как и увеличение или уменьшение типов bit, поддерживаемых в некоторых крошечных диалектах микроконтроллера (которые, как я наблюдал, рассматривают биты как битовые поля шириной в один бит, поэтому все четные числа усекаются до 0, а все нечетные до 1). Я бы особенно не рекомендовал такое использование, отчасти потому, что я не большой поклонник семантики типа bool [IMHO, тип должен был указать, что bool, к которому любое значение, кроме 0 или 1 хранится может вести себя при чтении, как будто он содержит неопределенное (не обязательно согласованное) целочисленное значение; если программа пытается сохранить целочисленное значение, которое, как известно, не равно 0 или 1, сначала следует использовать !! для него].

31
supercat 20 Авг 2018 в 15:15

в C #:

boolean.variable.down.here ^= true;

Логический оператор ^ - это XOR, а XOR с истинным равен обращению.

3
rakensi 24 Авг 2018 в 06:42

Язык ассемблера

NOT eax

См. https://www.tutorialspoint.com/assembly_programming/assembly_logical_instructions.htm

31
Adrian 20 Авг 2018 в 19:21
51927109