Counter в c что это

от admin

How to implement a constant-expression counter in C++

Disclaimer: The technique described in this post is primarily meant as "just another clever hack, diving into the dark corners of C++". I do not recommend anyone to incorporate the contents of this post into production code without considering its caveats.

Note: I have not had time to review the contents of this post as much as I have been wanting to; please use the comment section if you find anything that is hard to understand, or misleading.

Introduction

In the previous post we went through the basic idea of adding state to the world of constant-expressions, and with that; adding state to the phase of translation.

The technique described introduced something similar to the intrinsic type bool , only having the additional property of being able to change its value during the compilation of our program (instead of just during runtime).

We were presented with, and solved, the following challenge:

Note: One could make the above compile using #define f() __LINE__ , but that is quite obviously not the sentiment of the post (even though it is a somewhat clever way to circumvent the wording of the problem).

So, what now?

In this post we will take the concept even further, effectively making the following snippet compile with the desired semantics (not firing the static_assert ):

We will also deal with the somewhat hidden caveats of using the technique described, and how to circumvent them in order to write code that follows the guarantees of the ISO C++ Standard (N3290, aka. C++11).

Table of Contents

Prerequisites

Besides the contents of the previous post in this series, one must understand the following concepts in order to fully grasp the upcoming implementation:

  • Argument-Dependent Lookup (ADL)
  • The evaluation of default arguments
  • The gist of SFINAE
  • The fundamental concept of value representation

Note: The contents of this section is meant to be used as an introduction to the technical aspects related to the solution at the end of this post, as such it could potentially be skipped by more experienced (or eager) readers.

If you are only here for the cake, please, skip to the solution.

The semantics of Argument-Dependent Lookup

When a compiler sees an unqualified-id, a name that does not contain the scope operator ( :: ), as the postfix-expression in a function call; the rules of C++ says that the compiler shall potentially search for additional functions that might not be available in the immediate context of said function call.

The search for additional functions is governed by the types of the arguments involved in the function call itself, where each and every argument potentially gives birth to additional scopes to search in order to find the best viable function.

Consider the following example:

Note: (func) is not a name, but an expression, as such argument dependent lookup doesn’t apply.

What are the additional scopes associated with an argument of type T ?

Note: A complete list is available at [basic.lookup.argdep]p2 in the ISO C++ Standard, but the below can be used as a reference when dealing with ADL in the most common cases.

If the argument type, T , is;

  • an intrinsic type, there will be no additional scopes to search.
  • a pointer to U or an array of U , the additional scopes are those associated with U .
  • a function type, the additional scopes include:
    • the scopes associated with its parameter types, and;
    • the scopes associated with its return type.
    • the namespace in which it is declared, and;
    • the class in which it is a member (if any).
    • in case it is a template specialization;
      • the scopes associated with each template parameter of the specialization,

      The evaluation of default arguments

      C++ has two tightly coupled types of default arguments, one associated with template parameters, and the other with function parameters. Even though vastly different in their use case, they have many rules in common.

      A default argument is:

      • an expression (not a value),
      • evaluated each time it is required,
      • cannot be specified for a template parameter pack, and;
      • in case it refers to a dependent name;
        • such name is not instantiated unless used.

        The fact that default-arguments are evaluated each time they are used may not come as a shock to anyone. Having code such as the below, we expect "hello world\n" to be printed three times:

        Going further, it is not overly surprising that the below is legal C++ even though it may look ludicrous to the untrained eye:

        Note: The default-argument for arg is never evaluated nor instantiated, which inherently means that there is no issue with the non-existing instantiation of A<123> (where A<123>::arr would have a negative size) being ill-formed.

        References: [dcl.fct.default]p1-9 , [temp.deduct]p5 , [temp.point]p2 , [temp.inst]p10

        The gist of SFINAE

        Having a set of specializations, for class- or function templates, it is sometimes desired to have different behavior depending on whether some quality of an argument passed into the template has been met.

        Let us consider a minor, contrived, example:

        There are two important concepts dealt with in the above example;

        Overload Resolution

        Disregarding everything that has to do with templates, we see that we have two functions named is_big in namespace detail , where one accepts an int as its argument, and the other a float .

        The basics of Overload Resolution says that a compiler shall find the best candidate function (considering its parameters) whenever it encounters a call to a function name that is associated with more than one overload.

        In simple terms, the best candidate is the one that would require the least amount of work (conversions, type deduction, etc.), in regard to the arguments passed, in order to be called.

        If there is more than one function that is an equally good fit (ie. requires the same amount of work in order to be called), the program is ill-formed.

        Substitution Failure Is Not An Error

        When working with templates we might stumble into cases where a certain specialization can only be instantiated with a certain set of arguments.

        If a failure emerges during deduction or substitution of the template parameters involved would mean that the entire compilation process comes to a halt, we would not be able to express the following:

        Even though there is no way to form a pointer-to-const- T when looking at (2) having print(x) in main , the ISO C++ Standard does not consider the application to be ill-formed – instead it states that such error simply discards the specialization in favor of other viable alternatives.

        Moving on, the standard explicitly states that an array type cannot have a negative size, which further means that detail::is_big<T> (int) can only be instantiated if sizeof(T) > 4 :

        When the implementation is looking for the function to call in detail::is_big<T> (0) , it will try to instantiate every possible alternative, and discard alternatives that are not viable.

        There are two potential outcomes having detail::is_big<T> (0) (depending on type T ):

        If both templates are viable after template argument substitution, the one taking an int is a better match for 0 than the one taking a float and as such; overload resolution will pick the former.

        If only the latter template can be instantiated, overload resolution simply picks the function taking a float (even though it would require a conversion of the argument passed) — as there are no other viable alternatives.

        References: [over.match] , [dcl.array]p1 , [temp.deduct]p1-9 , [temp.deduct.call]p1-6

        The fundamental concept of value representation

        Note: This section is meant as a light introduction to representing values that cannot fit in one given entity (by using several).

        The usage of "value representation" is not to be confused with what the ISO C++ Standard says about value representation (even though the two terms share some similarities).

        An object of the intrinsic type bool can be toggled between two different states, it’s either false or true , and this is similar to the "variable" we introduced in the previous post.

        There is however one important difference; a bool can switch between the two states in whatever order we want, whereas our "variable" can only go from one state to the other — but not back.

        If we want to represent values beyond those that can be stored in these entities, we will need to combine several of such to get where we want — and because of their differences, the approach differs.

        Representing N states using bools

        If we were to represent N different states (where N > 0 ) using a set of bools (or bits), we would need that set to have a size of at least log2(N) .

        Representing N states using constexpr functions

        The previous sub-section shows how the value of B toggles from one state back to the other, but as previously stated, our "variable" does not support this concept.

        In order to represent N different states by conditionally defining constexpr functions (abbreviated as cexprf in the below), we would need that set of functions to have a size of at least N-1 .

        Solution

        Implementation

        Note: Because of bugs in vc++ (Visual C++, Microsoft), it will not yield the desired behavior when fed the above snippet; see the appendix for a vc++ specific workaround.

        Elaboration

        The "variables"

        Following what was stated in The fundamental concept of value representation, we quickly realize that in order to represent N different states using the technique described in the previous post, we need variables — plural.

        It would be tedious if we, as developers, had to explicitly write out N different names to act as "variables" when implementing our counter. Instead we will use a class template so that the compiler can generate the different entities for us — saving us a tremendous amount of typing.

        When a specialization of the above primary class template is instantiated, the compiler will effectively introduce a function named int adl_flag (flag<N>) that can only be found through Argument Dependent-Lookup.

        The Writers

        Having more than one "variable", we of course need more than one "writer", and following the same logic as applied in the previous section; we use a class template to create a definition for adl_flag (flag<N>) .

        Since we need a simple way to force instantiation of writer<N> , each specialization of writer<N> has a static constexpr data-member that simply has the value of the passed non-type template-parameter N .

        The Readers

        This is by far the trickiest part of the implementation, but it is not as complicated as it may seem at first glance.

        In order to query which functions has been given a definition, so that we can get the current count, we will have to walk through the different overloads of adl_flag and check their status; an easy task if we use the rules of overload resolution.

        The different overloads of reader will always be invoked with two arguments, the first being int ( 0 ), and the second a specialization of template<int> struct flag , as in the below:

        The Match

        Without explicitly passing template-arguments to the above template, a specialization will only be viable if adl_flag (flag<N> <>) is usable where a constant-expression is required, in other words; it can only be called if the appropriate overload of adl_flag has been defined.

        The Searcher

        The Matcher accepts an int as its first argument, whereas The Seacher takes a float . Knowing the rules of overload resolution we quickly see that passing 0 as the first argument will pick the former, if it is viable, otherwise it will pick the latter.

        In case a Searcher is picked, the default-argument specified for R will be evaluated. This will effectively walk down our different overloads until it finds a function that will stop the recursion — yielding the current count.

        The Base

        Since the searcher walks down the path by looking at the next overload matching a value less than N , we must have a base-case so that we do not run into infinite recursion (in case reader (int, flag<N>) can never be instantiated).

        The Helper

        The readers has granted us the power to get the current count, the only thing left is to implement a function that will, depending on the current count, instantiate the next writer<N> — effectively increasing the count.

        We will have the readers start searching at 32 , and then work themselves down towards 0 , this in turn means that 32 will be the maximum value of our counter.

        Note: The implementation could be reversed so that we start searching at 0 , and walk ourselves up towards inf – making it possible to have an "infinite" counter.

        Since such implementation is somewhat more complex, I will leave it as an exercise for the reader.

        Caveats

        It is of utter importance that one remembers that what we are currently dealing with involves some quite complex rules of the language. To top it off; some of these rules were not intended to be used as we are (ab)using them.

        As always when dealing with complex rules, there are caveats that are easily triggered unless we thoroughly think about what the standard does, and does not, guarantee.

        The order of template instantiation

        In the previous post we spent a fair share of time talking about the points of instantiation when it comes to templates, and how function templates have more than one point of instantiation.

        What we did not explicitly address is the fact that since a function template may have several points of instantation, the relative order of code generation for two different entities are not guaranteed.

        Note: Even though (2) appears after (1) , a compiler is free to generate code for the bodies of the specializations in any order it feels appropriate.

        The order of code generation is normally not observable, but when we are dealing with changing the global state of constant-expressions, it suddenly matters a lot, mainly because we rely on the fact that some change happens before another.

        Basically there is no guarantee that a < b yields true in the previous snippet because the order of code generation is implementation-defined.

        How to circumvent the issue

        The solution is quite simple; don’t rely on changes of the global state inside the body of a function template, only use them in contexts where the order of evaluation is guaranteed, such as:

        • As the default-argument for template- and function parameters,
        • directly inside class template specializations (these have at most one point of instantiation),
        • in the declaration of a function template, or;
        • in the declaration or definition of a non-template function.

        The order of template argument substitution

        In C++11 the order of template argument substitution (between non-dependent expressions) is implementation-defined, this means that we can run into cases where we logically think something is guaranteed to happen in some order — even though it is not.

        The wording in C++14 changed to make the order defined across implementations, and I have written a Q&A that explains why the order of template argument substitution matters (not only to the technique described in this post):

        Translation Units

        It is very easy to run into cases where using the technique breaks the One Definition Rule (ODR), if used across different translation units (TU).

        The problem boils down to that the technique relies on conditionally providing a definition of a function, by checking if it has already been defined within the current TU.

        Since a function can have at most one definition (ODR), and there is no way to check whether a function has a definition in some other TU — one needs to properly consider this when using the technique described.

        What about using inline functions?

        An alternative to using an anonymous namespace is to rely on the wording that an inline function may have more than one definition, as long as it is the same across different translation units.

        3.2p3 One definition rule [basic.def.odr]p3

        Every program shall contain exactly one definition of every non- inline function or variable that is odr-used in that program; no diagnostic required. The definition can appear explicitly in the program, it can be found in the standard or a user-defined library, or (when appropriate) it is implicitly defined (see 12.1, 12.4 and 12.8). An inline function shall be defined in every translation unit in which it is odr-used.

        Even though this is a suitable workaround, it is an extra mental barrier that — according to me — is best to be avoided; the easier it is to reason about a program, the better.

        How to circumvent the issue

        Only use the technique in contexts that are local to the current translation unit, such as by wrapping the boiler plate inside an unanonmyous namespace to prevent the definition(s) from causing conflicts with some other definition for the same name, in some other translation unit.

        • stackoverflow.com — c++ — Inline Function Linkage
        • msdn.com — Unnamed Namespaces

        Conclusion

        This post has explained the technique involved to implement a counter that is usable where a constant-expression is required, and the somewhat hidden caveats of using the implementation (if one doesn’t properly consider its side-effects (pun intended)).

        Together with the previous post, it effectively shows that constant-expressions are not "stateless", and that one can rely on the changing state to solve "impossible" problems.

        What’s next?

        We can view our counter implementation as a way to increment a counter in the global scope. The next post will show how to also associate an arbitrary value associated with each state — effectively making it possible to write something as the below.

        Further Down The Road

        In upcoming posts we will also dive deep into the ISO C++ Standard to discuss whether the behavior we are (ab)using is something worth keeping around — and how to potentially form wording to prevent the black magic this technique is using.

        We will also discuss other techniques that boils down to effectively solving the same sort of problems (word of warning; they are quite complex).

        Appendix

        Workaround for vc++

        vc++ has trouble with the original solution due to the following:

        • vc++ does not have proper support for expression SFINAE
        • vc++ does not support aggregate-initialization in constant-expressions
        • vc++ does not re-evaluate expressions used as template-arguments in function parameter default-arguments

        Additional Credit

        First and foremost, I would like to take the opportunity to thank those who have donated to Project Keep-Alive, effectively helping me from going home- — and with that — internet-less.

        All donors have asked to remain anonymous; but you know who you are, and I am very thankful for the aid provided! When struggling to put food on the table it is hard to find strength to write posts such as this.

        In other words; the contents of this blog would not have been possible without you guys, and once again; thanks!

        Shout-outs

        Mara Bos , for:
        • Helping me with the wording of this post.
        • Keeping my spirit up when I sometimes have felt like rm -rf -ing the whole thing (because of Writer’s Block).
        Didrik Nordström , for:
        • Asking very interesting questions regarding the contents of the post.
        • Keeping me company during late nights at The Royal Insitutite of Technology.
        "Columbu" , for:
        • In a way not quoting the standard and throwing standardese in my face, though this post is not as techinical as the previous one, I appreciate his review of the contents.
        Author Filip Roséen
        Contact filip.roseen@gmail.com

        Note: If you appreciate the contents of this blog, please feel free to make a donation (paypal) to show your support.

        Name already in use

        If nothing happens, download GitHub Desktop and try again.

        Launching GitHub Desktop

        If nothing happens, download GitHub Desktop and try again.

        Launching Xcode

        If nothing happens, download Xcode and try again.

        Launching Visual Studio Code

        Your codespace will open once ready.

        There was a problem preparing your codespace, please try again.

        Latest commit

        Git stats

        Files

        Failed to load latest commit information.

        README.md

        C++ wrapper for counters that can roll-over (e.g. timestamps/ack-ids)

        Represents an unsigned counter that can roll over from its maximum value back to zero. A common example is a 32-bit timestamp from GetTickCount() on Windows, which can roll-over and cause software bugs despite testing.

        This class provides:

        • Counters of 2-64 bits e.g. 24-bit counters
        • Increment/decrement by 1 or a constant
        • Safe comparison operator overloads
        • Compression/decompression via truncation
        • Unit tested software

        What problems does this solve?

        (Solution 1): It solves wrap-around issues commonly seen in code involving timestamps.

        Example buggy code:

        This will not wait the full 300 ms, because the (t < kTimeout) will be false: (0xffffff00 < 0x0000002c) . Instead the exact same code can be written with Counter32 and it will work:

        (Solution 2): When transporting counters (timestamps, ack-ids) across the Internet using UDP/IP packets, timestamps do not change much between messages so they can be compressed.

        The compression works so long as the timestamp only wraps around once and not twice.

        Implement Counter in C#

        In this guide, we’ll see how to make an increment and decrement counter in C# using two buttons. For instance, if there are two buttons, A and B.

        Pressing A and B should increment the counter, and pressing A and B should decrement the counter, respectively. Let’s dive into this guide and learn ourselves.

        Implement Counter in C#

        In this problem, we require two variables. First, we need a variable for the counter, and secondly, we need one to check the last button pressed.

        Let’s see how we are going to do that. At first, we’ll need to create event delegates, so a certain event should occur whenever a button is clicked.

        In the above code, we made two buttons and assigned event delegates. The function Button_A_Click will run whenever a buttonA is clicked.

        Next up, we need to have two variables. Take a look.

        The LastPressed will track which button was pressed last, and the counter will track the incrementation and decrementation. The next thing that we need is to have two functions tracking all this.

        If buttonA is clicked, the program will check for the value in the LastPressed . If it’s buttonB , the counter will decrement; if it’s nothing, buttonA will be assigned to the LastPressed .

        The same goes for buttonB ; if the LastPressed were A, it would increment the counter; otherwise, buttonB will be assigned. That’s how you make a program of incrementing and decrementing a counter in C#.

        The following is the full code.

        Haider specializes in technical writing. He has a solid background in computer science that allows him to create engaging, original, and compelling technical tutorials. In his free time, he enjoys adding new skills to his repertoire and watching Netflix.

        Управление потоком кода.

        Освоить средства создания циклов различных типов в программах на C++ и операторов управления потоком команд.

        2. Теоретические сведения

        Оператор цикла while

        Для организации повторения, управляемого счетчиком, требуется задать:

        Имя управляющей переменной (или счетчика циклов).

        Приращение (или уменьшение), на которое изменяется управляющая переменная в каждом цикле.

        Условие проверки, не достигнуто ли конечное значение управляющей переменной (т.е. надо ли продолжать циклы).

        Рассмотрим простую программу, которая выводит на консоль числа от 1 до 10:

        // повторение, управляемое счетчиком

        #include <iostream.h>

        int counter = 1; // задание начального значения

        count << counter << endl;

        ++counter; // увеличение

        1 2 3 4 5 6 7 8 9 10

        определяет имя управляющей переменной (counter), объявляет переменную как целую, резервируя для нее соответствующее место в памяти, и задает ее начальное значение 1. Объявления, в которых задается начальное значение, являются фактически выполняемыми операторами.

        Это объявление и задание начального значения переменной counter можно было бы сделать операторами

        int counter; counter = 1;

        Здесь само объявление не является выполняемым оператором, а присва­ивание осуществляется отдельным оператором. Можно использовать оба эти метода для задания начальных значений переменных.

        увеличивает счетчик на 1 при каждом выполнении цикла. Условие продолже­ния цикла в структуре while проверяет, меньше или равно значение управляю­щей переменной числа 10 (последнего значения, при котором условие истинно). Заметьте, что тело этой структуры while выполняется и при значении управ­ляющей переменной, равном 10. Выполнение цикла заканчивается, когда значение управляющей переменной превысит 10 (т.е. переменная counter станет равной 11).

        Программа может быть сделана более компактной, если пере­менной counter задать начальное значение 0 и заменить структуру while следующей:

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

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

        #define delta 0.00001

        If(abs(a – b) < delta)

        Count << “Значения равны” << endl;

        Оператор цикла for

        Структура повторения for содержит все элементы, необходимые для по­вторения, управляемого счетчиком. Чтобы проиллюстрировать мощь струк­туры for, перепишем программу.

        // Повторение, управляемое счетчиком, со структурой for

        // задание начального значения,условие повторения и

        // приращение – включено в заголовок структуры for

        for(int counter = 1; counter <= 10; counter++)

        Когда структура for начинает выполняться, управляющей переменной counter задается начальное значение 1. Затем проверяется условие продол­жения цикла counter <= 10. Поскольку начальное значение counter равно 1, это условие удовлетворяется, так что оператор тела структуры печатает значение counter, равное 1. Затем управляющая переменная counter увеличивается на единицу в выражении counter++ и цикл опять начинается с про­верки условия его продолжения. Поскольку значение counter теперь 2, предельная величина не превышена, так что программа снова выполняет тело цикла. Этот процесс продолжается, пока управляющая переменная counter не увеличится до 11 — это приведет к тому, что условие продолжения цикла нарушится и повторение прекратится. Выполнение программы про­должится с первого оператора, расположенного после структуры for (в данном случае с оператора return в конце программы).

        Структура for «делает все» — она определяет каждый элемент, необходимый для повторения, управляемого счетчиком с управляю­щей переменной. Если в теле for имеется более одного оператора, то для определения тела цикла требуются фигурные скобки.

        Отметим, что используется условие продолжения цикла counter <= 10. Если программист некорректно напишет counter < 10, то цикл выполнится всего 9 раз. Это типичная логическая ошибка, называемая ошибкой занижения (или завышения) на единицу.

        Использование конечного значения управляющей переменной в условиях структур while и for и использование операции отношения <= поможет избежать ошибок занижения на единицу. Например, для цикла, используемого при печати чисел от 1 до 10 условие продолжения цикла следует записать counter <= 10, а не counter < 10 (что является ошибкой занижения на единицу) или counter < 11 (что тем не менее корректно).

        Общая форма структуры for:

        for (выражение1; выражение2; выражениеЗ) оператор

        где выражение1 задает начальное значение переменной, управляющей циклом, выражение2 является условием продолжения цикла, а выражениеЗ изменяет управляющую переменную. В большинстве случаев структуру for можно представить эквивалентной ей структурой while следующим образом:

        Иногда выражение1 и выражение3 представляются как списки выражений, разделенных запятой. В данном случае запятая используется как опе­рация запятая или операция последования, гарантирующая, что список вы­ражений будет вычисляться слева направо. Операция последования имеет самый низкий приоритет среди всех операций C++. Значение и тип списка выражений, разделенных запятыми, равны значению и типу самого правого выражения в списке. Операция последования наиболее часто используется в структуре for. Ее основное назначение — помочь программисту использо­вать несколько выражений задания начальных значений и (или) несколько выражений приращения переменных. Например, в одной структуре for может быть несколько управляющих переменных, которым надо задавать начальное значение и которые надо изменять.

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

        Выражения в структуре for являются необязательными. Если выраже-ние2 отсутствует, C++ предполагает, что условие продолжения цикла всегда истинно и таким образом создается бесконечно повторяющийся цикл. Иногда может отсутствовать выражение1 если начальное значение управляющей переменной задано где-то в другом месте программы. Может отсутствовать и выражение3, если приращение переменной осуществляется операторами в теле структуры for или если приращение не требуется. Выражение для при­ращения переменной в структуре for действует так же, как автономный опе­ратор в конце тела for. Следовательно, все выражения

        counter = counter + 1

        эквивалентны в той части структуры for, которая определяет приращение. Многие программисты предпочитают форму counter++, поскольку прираще­ние срабатывает после выполнения тела цикла. Поэтому постфиксная форма представляется более естественной. Поскольку изменяемая переменная здесь не входит в какое-то выражение, обе формы инкремента равноправны. В структуре for должны применяться точки с запятой.

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

        Если условие продолжения цикла с самого начала не удовлетворяется, то операторы тела структуры for не выполняются и управление передается оператору, следующему за for.

        Управляющая переменная иногда печатается или используется в вычис­лениях в теле структуры for, но обычно это делается без изменений ее ве­личины. Чаще управляющая переменная используется только для контроля числа повторений и никогда не упоминается в теле структуры.

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

        Оператор цикла do/while

        Структура повторения do/while похожа на структуру while. В структуре while условие продолжения циклов проверяется в начале цикла, до того, как выполняется тело цикла. В структуре do/while проверка условия про­должения циклов производится после того, как тело цикла выполнено, сле­довательно, тело цикла будет выполнено по крайней мере один раз. Когда do/while завершается, выполнение программы продолжается с оператора, сле­дующего за предложением while. Отметим, что в структуре do/while нет не­обходимости использовать фигурные скобки, если тело состоит только из одного оператора. Но фигурные скобки обычно все же ставят, чтобы избежать путаницы между структурами while и do/while. Например,

        обычно рассматривается как заголовок структуры while. Структура do/while без фигурных скобок и с единственным оператором в теле имеет вид

        что может привести к путанице. Последняя строка — while (условие); может ошибочно интерпретироваться как заголовок структуры while, содержащий пустой оператор. Таким образом, чтобы избежать путаницы, структура do/while даже с одним оператором часто записывается в виде

        оператор > while (условие);

        Некоторые программисты всегда включают фигурные скобки в структуру do/while, даже если в них нет необходимости. Это помогает устранить двусмысленность, про­истекающую из совпадения предложений структуры while и структуры do/while, содержащей один оператор.

        Следующая программа использует структуру do/while, чтобы напечатать числа от 1 до 10. Обратите внимание, что к управляющей переменной counter в проверке окончания цикла применяется инкремент в префиксной форме. Отметьте также использование фигурных скобок, заключающих единствен­ный оператор в теле do/while.

        // Применение структуры повторения do/while

        Операторы break и continue

        Операторы break и continue изменяют поток управления. Когда оператор break выполняется в структурах while, for, do/while или switch, происходит немедленный выход из структуры. Программа продолжает выполнение с пер­вого оператора после структуры. Обычное назначение оператора break — досрочно прерывать цикл или пропустить оставшуюся часть структуры switch.

        Оператор continue в структурах while, for или do/while вызывает про­пуск оставшейся части тела структуры и начинается выполнение следующей итерации цикла. В структурах while и do/while немедленно после выпол­нения оператора continue производится проверка условия продолжения цикла. В структуре for выполняется выражение приращения, а затем осу­ществляется проверка условия продолжения. Ранее мы установили, что в большинстве случаев структура while может использоваться вместо for. Единственным исключением является случай, когда выражение приращения в структуре while следует за оператором continue. В этом случае приращение не выполняется до проверки условия продолжения цикла и структура while работает не так, как for.

        Оператор выбора if else

        Читать:
        Cache cleaner как пользоваться

Похожие статьи