Идиома для обработки недостаточного размера size_t в состоянии l oop - PullRequest
3 голосов
/ 15 февраля 2020

В C и C ++ size_t - это тип без знака, который используется для выражения размера. Он выражает намерение и несколько упрощает утверждения диапазона (len < upper_bound против len >= 0 && len < upper_bound для целых чисел со знаком).

(Во всех приведенных ниже примерах len означает длину массива a).

Идиома для l oop: for (i = 0; i < len; i++). Идиома отсталого для l oop - for (i = len-1; i >= 0; i--). Но наличие беззнаковых индексов l oop приводит к тонким ошибкам, и время от времени я путаю крайние случаи.

Во-первых, обратная сторона для l oop. Этот код ниже для len = 0.

for (size_t i = len-1; i >= 0; i--) { // Bad: Underflows for len=0
    use(a[i]);
}

Есть трюк с оператором -->, который выглядит странно, если вы к нему не привыкли.

for (size_t i = len; i--> 0;) {
    use(a[i]);
}

Вы можете используйте тип со знаком для индексной переменной l oop, но он переполняется, если len > INT_MAX. Многие люди и организации считают этот риск настолько минимальным, что они просто придерживаются int.

for (int i = len-1; i >= 0; i--) {  // BAD: overflows for len < INT_MAX
    use(a[i]);
}

Так что я согласился на эту конструкцию, так как она наиболее близка к канонической для-l oop форме и имеет самые простые выражения.

for (size_t i = len; i > 0; i--) {
    size_t pos = i-1;
    use(a[pos]);
}

Моя проблема с итерацией от 0 до len-1

То есть с циклическим изменением диапазона [0, len-1). Это l oop понижается, когда len=0.

for (size_t i = 0; i < len-1; i++) {   // BAD: Underflows for len=0.
    use(a[i]);
}

. Что касается случая итерации в обратном направлении, вы можете использовать целые числа со знаком, но это может вызвать переполнение.

for (int i = 0; i < len-1; i++) {    // BAD: Will overflow if len > INT_MAX
    use(a[i]);
}

Я склонен к добавьте еще одно выражение к условию l oop, проверяя на len > 0, но это кажется неуклюжим.

for (size_t i = 0; len > 0 && i < len-1; i++) {
    use(a[i]);
}

Я могу добавить оператор if перед l oop, но это также кажется неуклюжим.

Есть ли менее громоздкий способ записи для l oop с индексными переменными без знака, циклически изменяющимися от 0 до len-1?

Ответы [ 5 ]

2 голосов
/ 15 февраля 2020

Здесь есть два случая.

Итерация вперед от 0 до len - 2 включительно

for (size_t i = 0; i + 1 < len; ++i) {
    size_t index = i;
    // use index here
}

Итерация назад от len - 2 до 0 включительно

for (size_t i = len; i > 1; --i) {
    size_t index = i - 2;
    // use index here
}
1 голос
/ 15 февраля 2020

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

Одна вещь, которая помогает здесь, - сделать эти два значения явными. В простейшем случае, итерируя вперёд весь диапазон, конечное значение просто равно len:

for (size_t i=0, end=len; i!=end; ++i) {
    ///...
}

Это следует общим советам, которые даются людям, использующим итераторы. В частности, обратите внимание на сравнение, которое работает здесь и которое фактически требуется для некоторых итераторов.

Теперь, итерация в обратном направлении:

for (size_t i=len-1, end=-1; i!=end; --i) {
    ///...
}

Наконец, итерация подмножества, исключая последний n элементов диапазона в обратном направлении:

if (len > n) {
    for (size_t i=len-n-1, end=-1; i!=end; --i) {
        ///...
    }
}

На самом деле, вы боролись с вашей попыткой поместить слишком много вещей в l oop logi c. Просто укажите, что для этого нужно больше, чем n элементов. Да, вы могли бы поставить len > n в состояние l oop, но это не дало бы вам ясного и простого кода, намерение которого любой понимает.

1 голос
/ 15 февраля 2020

Как насчет

for (size_t i = 0; i+1 < len; i++) {
    use(a[i]);
}
0 голосов
/ 15 февраля 2020

Есть много возможных ответов на этот вопрос. Что лучше всего - это мнение. Я предложу несколько вариантов.

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

for (size_t index = 0; index < len; ++index)
{
     size_t i = len - 1 - index;
     use(a[i]);
}

или (проще)

for (size_t i = 0; i < len; ++i)
{
     use(a[len - 1 - i]);
}

В обоих случаях, если len равно нулю, тело l oop не выполняется. Хотя циклы увеличиваются, а не уменьшаются, оба элемента доступа обращаются в обратном порядке. Если вы часто пишете такие циклы, также нетрудно написать небольшую встроенную функцию вида

size_t index_in_reverse(size_t index, size_t len)
{
     return len - 1 - index;
}

и выполнить

for (size_t i = 0; i < len; ++i)
{
     use(index_in_reverse(i, len));
}

Чтобы выполнить итерацию вперед по всем элементам l oop в прямом порядке, кроме последнего, я бы сделал это явно, а не пытался бы сделать это в условии l oop.

if (len > 0)
{
     size_t  shortened_len = len - 1;
     for (size_t i = 0; i < shortened_len; ++i)
         use(a[i]);
}

Причина, по которой я ввел переменную shortened_len это сделать код самодокументированным о том факте, что он не повторяется по всему массиву. Я видел слишком много случаев, когда последующее разработчик «исправляло» условие формы i < len - 1, чтобы удалить - 1, потому что они считают, что это опечатка.

Это может показаться "неуклюжим" в ОП, но я полагаю, что

for (size_t i = 0; len > 0 && i < len-1; i++) {
   use(a[i]);
} 

человеку труднее понять (введение нескольких тестов в состояние al oop заставляет человека, поддерживающего код, фактически работать выяснить, что делают оба условия и как они взаимодействуют). Учитывая выбор между «кратким» кодом или «менее лаконичным» кодом, но потребляющим меньше умственных способностей незнакомого человека для понимания «я ВСЕГДА выберу последнее. Имейте в виду, что пользователь, поддерживающий код через шесть месяцев, может быть самим собой, и лишь немногие мыслительные процессы более унизительны, чем «Какой идиот написал это? О, это был я!».

0 голосов
/ 15 февраля 2020

Я склонен добавлять другое выражение к условию l oop, проверяя len> 0, но это кажется неуклюжим.

Ваш код должен следовать логике c. Это совсем не коряво. И это намного более читабельно для людей.

Поскольку l oop не имеет смысла, если len == 0 и я обычно используем оператор if. Это облегчает понимание и сопровождение кода.

if(len)
{
    for (size_t i = 0; i < len-1; i++) { /*...*/ }       
}

или вы также можете добавить чек в l oop. Кстати, вы только проверить, если это не ноль.

 for (size_t i = 0; len && i < len-1; i++) { /*...*/ }       
...