Подтвердить что ты не робот

Как переписать сложные строки кода С++ (вложенный тернарный оператор)

Я искал код другого пользователя для отладки и нашел это:

!m_seedsfilter ? good=true : m_seedsfilter==1 ? good=newClusters(Sp) : good=newSeed(Sp);  

Что это значит? Есть ли автоматизированный инструмент, который сделает это более понятным if/else заявлениям? Какие-нибудь советы для работы со сложными структурами управления, вроде этого?

Отредактируйте примечание: я изменил это с "излишне сложного" на "сложное" в названии, так как это вопрос мнения. Спасибо за все ваши ответы.

4b9b3361

Ответ 1

Утверждение, написанное в письменном виде, может быть улучшено, если его переписать следующим образом.

good = m_seedsfilter==0 ? true :
       m_seedsfilter==1 ? newClusters(Sp) :
                          newSeed(Sp);

... но в целом вы должны просто стать знакомыми с тройственным выражением. Нет ничего злонамеренного в отношении кода, который был первоначально опубликован, или версии xanatos, или моего. Тернарные заявления не являются злыми, они являются основной чертой языка, и как только вы познакомитесь с ними, вы заметите, что такой код (как я опубликовал, а не как написано в вашем исходном сообщении) на самом деле проще чтобы читать, чем цепочку операторов if-else. Например, в этом коде вы можете просто прочитать этот оператор следующим образом: "Переменная good равна... if m_seedsfilter==0, затем true, в противном случае, если m_seedsfilter==1, тогда newClusters(Sp), в противном случае newSeed(Sp)".

Обратите внимание, что моя версия выше позволяет избежать трех отдельных назначений переменной good и дает понять, что целью оператора является присвоение значения good. Кроме того, написанный таким образом, он дает понять, что, по сути, это конструкция "switch-case", причем по умолчанию используется newSeed(Sp).

Следует, наверное, отметить, что мой переписать выше хорошо, пока operator!() для типа m_seedsfilter не переопределяется. Если это так, то вам придется использовать это, чтобы сохранить поведение вашей оригинальной версии...

good = !m_seedsfilter   ? true :
       m_seedsfilter==1 ? newClusters(Sp) :
                          newSeed(Sp);

... и как поясняется ниже комментарий xanatos, если ваши методы newClusters() и newSeed() возвращают разные типы, чем другие, и если эти типы написаны с помощью тщательно продуманных бессмысленных операторов преобразования, то вы будете иметь чтобы вернуться к самому оригинальному коду (хотя, надеюсь, отформатирован лучше, как и в собственном сообщении xanatos), чтобы точно дублировать то же поведение, что и исходное сообщение. Но в реальном мире никто не собирается этого делать, поэтому моя первая версия выше должна быть в порядке.


ОБНОВЛЕНИЕ, через два с половиной года после первоначального сообщения/ответа: Интересно, что @TimothyShields и я постоянно время от времени набираю обороты, и ответ Тима, похоже, постоянно отслеживает примерно 50% этого ответа upvotes, более или менее (43 против 22 из этого обновления).

Я думал, что добавлю еще один пример ясности, которую может добавить тройной оператор при разумном использовании. Ниже приведены краткие фрагменты кода, который я писал для анализатора использования вызова (инструмент, который анализирует скомпилированный код C, но сам инструмент написан на С#). Все три варианта выполняют точно такую ​​же цель, по крайней мере, насколько внешне видимые эффекты идут.

1. Без тернарного оператора:

Console.Write(new string(' ', backtraceIndentLevel) + fcnName);
if (fcnInfo.callDepth == 0)
{
   Console.Write(" (leaf function");
}
else if (fcnInfo.callDepth == 1)
{
   Console.Write(" (calls 1 level deeper");
}
else
{
   Console.Write(" (calls " + fcnInfo.callDepth + " levels deeper");
}
Console.WriteLine(", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");

2. С помощью тернарного оператора отдельные вызовы Console.Write():

Console.Write(new string(' ', backtraceIndentLevel) + fcnName);
Console.Write((fcnInfo.callDepth == 0) ? (" (leaf function") :
              (fcnInfo.callDepth == 1) ? (" (calls 1 level deeper") :
                                         (" (calls " + fcnInfo.callDepth + " levels deeper"));
Console.WriteLine(", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");

3. С помощью тернарного оператора свернута до одного вызова Console.Write():

Console.WriteLine(
   new string(' ', backtraceIndentLevel) + fcnName +
   ((fcnInfo.callDepth == 0) ? (" (leaf function") :
    (fcnInfo.callDepth == 1) ? (" (calls 1 level deeper") :
                               (" (calls " + fcnInfo.callDepth + " levels deeper")) +
   ", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");

Можно утверждать, что разница между тремя приведенными выше примерами тривиальна, и поскольку это тривиально, почему бы не предпочесть более простой (первый) вариант? Все дело в том, чтобы быть кратким; выражая идею в "как можно меньше слов", чтобы слушатель/читатель все еще мог вспомнить начало идеи к тому времени, когда я дойду до конца идеи. Когда я говорю с маленькими детьми, я использую простые, короткие предложения, и в результате для выражения идеи требуется больше предложений. Когда я говорю со взрослыми, свободно владеющими языком, я использую более длинные и более сложные предложения, которые более кратко выражают идеи.

Эти примеры печатают одну строку текста на стандартный вывод. Хотя выполняемая ими операция проста, ее легко представить как подмножество большей последовательности. Чем короче я могу четко выражать подмножества этой последовательности, тем больше эта последовательность может поместиться на экране редактора. Конечно, я могу легко занять эти усилия слишком далеко, что затрудняет понимание; цель состоит в том, чтобы найти "сладкое пятно между тем, чтобы быть понятным и кратким. Я утверждаю, что как только программист знаком с тройным выражением, понимание кода, использующего их, становится проще, чем понимание кода, который не имеет значения (например, 2 и 3), а 1).

Конечная причина, по которой опытные программисты должны чувствовать себя комфортно, используя тройные заявления, заключается в том, чтобы избежать создания ненужных временных переменных при выполнении вызовов методов. В качестве примера я представляю четвертый вариант вышеприведенных примеров с логикой, сжатой до одного вызова Console.WriteLine(); результат будет меньше понятным и меньше лаконичным:

4. Без тернарного оператора свернута до одного вызова Console.Write():

string tempStr;
if (fcnInfo.callDepth == 0)
{
   tempStr = " (leaf function";
}
else if (fcnInfo.callDepth == 1)
{
   tempStr = " (calls 1 level deeper";
}
else
{
   tempStr = " (calls " + fcnInfo.callDepth + " levels deeper";
}
Console.WriteLine(new string(' ', backtraceIndentLevel) + fcnName + tempStr +
                  ", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");

Прежде чем утверждать, что "конденсирование логики с одним вызовом на Console.WriteLine() не нужно", подумайте, что это всего лишь пример: представьте вызовы на какой-то другой метод, который принимает несколько параметров, для всех из которых требуются временные состояние других переменных. Вы можете создать свои собственные временные ряды и вызвать вызов метода с этими временными или использовать тернарный оператор и позволить компилятору создавать собственные (неназванные) временные ряды. Опять же я утверждаю, что тернарный оператор допускает гораздо более сжатый и понятный код, чем без. Но для того, чтобы это было понятно, вам придется отказаться от любых предвзятых понятий, которые у вас есть, что тернарный оператор злой.

Ответ 2

Эквивалентный код без зла:

if (m_seedsfilter == 0)
{
    good = true;
}
else if (m_seedsfilter == 1)
{
    good = newClusters(Sp);
}
else
{
    good = newSeed(Sp);
}

Связанные тройные операторы, т.е. следующие

condition1 ? A : condition2 ? B : condition3 ? C : D

- отличный способ сделать ваш код нечитаемым.

Я добавлю второе предложение @phonetagger, чтобы вы познакомились с тройными операторами - чтобы вы могли устранить вложенные, когда вы их встретите.

Ответ 3

Это лучше?

!m_seedsfilter ? good=true 
               : m_seedsfilter==1 ? good=newClusters(Sp) 
                                  : good=newSeed(Sp);  

Я добавлю, что, хотя теоретически возможно упростить это выражение (почему? Это так ясно!), получившееся выражение, вероятно, не будет равно 100% во всех возможных случаях... И показывается, если два выражения действительно эквивалентны в С++ - проблема очень очень очень очень сложная...

Вырожденный пример, который я придумал (http://ideone.com/uLpe0L) (обратите внимание, что он не очень вырожден... Он основан только на небольшая ошибка программирования) основана на рассмотрении good a bool, создавая два класса UnixDateTime и SmallUnixDateTime, при этом newClusters() возвращает SmallUnixDateTime и newSeed(), возвращая a UnixDateTime. Оба они должны использоваться, чтобы содержать дату и время Unix в формате количества секунд с полудня полудня от 1970-01-01. SmallUnixDateTime использует int, а UnixDateTime использует long long. Оба они неявно конвертируются в bool (они возвращаются, если их внутреннее значение != 0, что-то "классическое" ), но UnixDateTime даже неявно конвертируется в SmallUnixDateTime (это неправильно, потому что может быть потеря точность... Вот это небольшая ошибка программирования). При отказе преобразования возвращается SmallUnixDateTime, установленная на 0. В коде этого примера всегда будет одно преобразование: от SmallUnixDateTime до bool или от UnixDateTime до bool...

В этом подобном, но другом примере:

good = !m_seedsfilter ? true 
                      : m_seedsfilter==1 ? newClusters(Sp) 
                                         : newSeed(Sp);

существует два возможных пути: SmallUnixDateTime (newClusters(Sp)) преобразуется в bool или UnixDateTime (newSeed(Sp)) сначала преобразуется в SmallUnixDateTime, а затем в bool. Ясно, что два выражения не эквивалентны.

Чтобы заставить его работать (или "не работать" ), newSeed(Sp) возвращает значение, которое не может содержаться в SmallUnixTime (std::numeric_limits<int>::max() + 1LL).

Ответ 4

Чтобы ответить на ваш основной вопрос, это пример условного выражения:

conditional-expression:
    logical-OR-expression
    logical-OR-expression ? expression : conditional-expression

Если логическое-OR-выражение оценивается как true, то результатом выражения является выражение, следующее за ?, иначе это выражение следует за :. Например,

x = y > 0 ? 1 : 0;

назначит 1 для x, если y больше 0, иначе он назначит '0'.

Вы правы, чтобы чувствовать себя тошнотворным в этом примере, потому что это плохо написано. Автор пытается использовать оператор ?: как структуру управления, для которой он не предназначен.

Лучшим способом написать это будет


    good = !m_seedsfilter ? true : 
                            ( m_seedsfilter == 1 ? newClusters(SP) : 
                                                   newSeed(SP) );

Если m_seedsfilter равно 0, то good будет установлено на true. Если m_seedsfilter равно 1, то good будет установлен на результат newClusters(SP). В противном случае good будет установлен в результат newSeed(SP).

Ответ 5

Вложенные тернарные заявления делают код менее читаемым IMHO. Я бы использовал их только тогда, когда они значительно упростили остальную часть кода. Записанный код можно переписать следующим образом:

good = !m_seedsfilter ? true : m_seedsfilter==1 ? newClusters(Sp) : newSeed(Sp); 

или вот так:

if(!m_seedsfilter)
    good = true;
else if(m_seedsfilter==1)
    good = newClusters(Sp);
else
    good = newSeed(Sp);

Первый выбор более краткий, но менее читаемый для новичков и менее отладочный.

Ответ 6

if ( !m_seedsfilter )
  good = true;
else if ( m_seedsfilter == 1 )
  good = newClusters(Sp);
else
  good = newSeed(Sp);

Выражение ? соответствует примерно if ( expression ), а : вводит что-то похожее на предложение else. Обратите внимание, что это выражение, а не выражение, т.е.

<condition> ? <expression-1> : <expression-2>

- выражение, значение которого expression-1, если condition истинно, в противном случае это expression-2.

Ответ 7

!m_seedsfilter ? good=true : m_seedsfilter==1 ? good=newClusters(Sp) : good=newSeed(Sp);

Переведем на

if (!m_seedsfilter)
{
     good = true;
}
else
{
     if (m_seedsfilter == 1)
     {
          good = newClusters(Sp);
     }
     else
     {
          good = new Seed(Sp);
     }
}