Разработка → man!( C => D )

перевод
vintage 31 января 2016 в 12:32 20,3k
Оригинал: D Community

Каждый С-программист с опытом накапливает привычный багаж техник и идиом. Зачастую бывает сложно понять, как сделать то же самое в новом языке. Так вот, вашему вниманию предлагается коллекция распространенных паттернов на C и их эквивалентов на D. Если вы собираетесь перевести свою программу с C на D или ещё сомневаетесь стоит ли это делать, то эта статья для вас.


Получаем размер типа в байтах


В C используем специальный оператор:


sizeof( int )
sizeof( char * )
sizeof( double )
sizeof( struct Foo )

В D у каждого типа есть специальное свойство:


int.sizeof
(char*).sizeof
double.sizeof
Foo.sizeof

Получаем максимальное и минимальное значение типа


Было на C:


#include <limits.h>
#include <math.h>

CHAR_MAX
CHAR_MIN
ULONG_MAX
DBL_MIN

Стало на D:


char.max
char.min
ulong.max
double.min_normal

Таблица соответствия типов C => D


bool               =>        bool
char               =>        char
signed char        =>        byte
unsigned char      =>        ubyte
short              =>        short
unsigned short     =>        ushort
wchar_t            =>        wchar
int                =>        int
unsigned           =>        uint
long               =>        int
unsigned long      =>        uint
long long          =>        long
unsigned long long =>        ulong
float              =>        float
double             =>        double
long double        =>        real
_Imaginary long double =>    ireal
_Complex long double   =>    creal

Особые значения чисел с плавающей точкой


Было на C:


#include <fp.h>

NAN
INFINITY

#include <float.h>

DBL_DIG
DBL_EPSILON
DBL_MANT_DIG
DBL_MAX_10_EXP
DBL_MAX_EXP
DBL_MIN_10_EXP
DBL_MIN_EXP

Стало на D:


double.nan
double.infinity
double.dig
double.epsilon
double.mant_dig
double.max_10_exp
double.max_exp
double.min_10_exp
double.min_exp

Остаток от деления вещественных чисел


В C используем специальную функцию:


#include <math.h>

float f = fmodf( x , y );
double d = fmod( x , y );
long double r = fmodl( x , y );

D имеет специальный оператор для этой операции:


float f = x % y;
double d = x % y;
real r = x % y;

Обработка NaN значений


В C сравнение с NaN является неопределённым поведением и разные компиляторы по-разному реагируют (от игнорирования до возбуждения исключения), поэтому приходится использовать специальные функции:


#include <math.h>

if( isnan( x ) || isnan( y ) ) {
    result = FALSE;
} else {
    result = ( x < y );
}

В D сравнение с NaN — всегда возвращает false:


result = ( x < y );        // false if x or y is nan

Асерты — полезный механизм выявления ошибок


В C нет встроенного механизма асертов, но он поддерживает псевдоконстанты FILE, LINE и макросы, с помощью которых можно реализовать асерты (по факту, у этих констант нет другого практического применения):


#include <assert.h>

assert( e == 0 );

D поддерживает асерты на уровне языка:


assert( e == 0 );

Итерирование по массиву


На C в задаёте длину массива константой, а потом пробегаетесь по массиву громоздким for-циклом:


#define ARRAY_LENGTH 17
int array[ ARRAY_LENGTH ];
for( i = 0 ; i < ARRAY_LENGTH ; i++ ) {
    func( array[i] );
}

Вы также можете использовать неуклюжее выражение с sizeof(), но это не сильно меняет дело:


int array[17];
for( i = 0 ; i < sizeof( array ) / sizeof( array[0] ) ; i++ ) {
    func( array[i] );
}

В D у массивов есть свойство length:


int array[17];
foreach( i ; 0 .. array.length ) {
     func( array[i] );
}

Но, если есть возможность, лучше использовать итерирование по коллекции:


int array[17];
foreach( value ; array ) {
    func( value );
}

Инициализация элементов массива


На C вы вынуждены были пробегаться по массиву в цикле (или опять же использовать макрос):


#define ARRAY_LENGTH 17
int array[ ARRAY_LENGTH ];
for( i = 0 ; i < ARRAY_LENGTH ; i++ ) {
    array[i] = value;
}

D имеет специальную простую нотацию для этого частого случая:


int array[17];
array[] = value;

Создание массивов переменной длины


C не поддерживает такие массивы, поэтому приходится заводить отдельную переменную для длины и вручную управлять выделением памяти:


#include <stdlib.h>

int array_length;
int *array;
int *newarray;

newarray = (int *) realloc( array , ( array_length + 1 ) * sizeof( int ) );
if( !newarray ) error( "out of memory" );
array = newarray;
array[ array_length++ ] = x;

D имеет встроенную поддержку массивов переменной длины и сам обеспечивает правильную работу с памятью:


int[] array;
int x;
array.length = array.length + 1;
array[ array.length - 1 ] = x;

Соединение строк


На C приходится решать множество проблем типа «когда память может быть освобождена», «как обрабатывать нулевые указатели», «как узнать длину строки», «сколько памяти выделить» и другие:


#include <string.h>

char *s1;
char *s2;
char *s;

// Concatenate s1 and s2, and put result in s
free(s);
s = malloc( ( s1 ? strlen( s1 ) : 0 ) + ( s2 ? strlen( s2 ) : 0 ) + 1 );
if( !s ) error( "out of memory" );
if( s1 ) {
    strcpy( s, s1 );
} else {
    *s = 0;
}
if( s2 ) {
    strcpy( s + strlen( s ) , s2 );
}

// Append "hello" to s
char hello[] = "hello";
char *news;
size_t lens = s ? strlen( s ) : 0;
news = realloc( s , ( lens + strlen( hello ) + 1 ) * sizeof( char ) );
if( !news ) error( "out of memory" );
s = news;
memcpy( s + lens , hello , sizeof( hello ) );

В D есть специальные перегружаемые операторы ~ и ~= предназначенные для соединения списков:


char[] s1;
char[] s2;
char[] s;

s = s1 ~ s2;
s ~= "hello";

Форматированный вывод


В C основной способ форматированного вывода — это функция printf():


#include <stdio.h>

printf( "Calling all cars %d times!\n" , ntimes );

Что мы напишем в D? Да почти то же самое:


import std.stdio;

writefln( "Calling all cars %s times!" , ntimes );

Но в отличие от printf, writef типобезопасен, то есть компилятор проверит соответствие типов переданных параметров типам в шаблоне.


Обращение к функциям до объявления


В C компилятор не позволяет обращаться к функции до того, как встретил её объявление, поэтому приходится либо переносить саму функцию, либо, если перенос не возможен, то вставлять специальную декларацию, говорящую компилятору, что функция будет объявлена позже:


void forwardfunc();

void myfunc() {
    forwardfunc();
}

void forwardfunc() {
    ...
}

Компилятор D анализирует файл целиком, при этом игнорирует порядок следования объявлений в исходниках:


void myfunc() {
    forwardfunc();
}

void forwardfunc() {
    ...
}

Функции без аргументов


Было на C:


void foo( void );

Стало на D:


void foo() {
    ...
}

Выход из нескольких блоков кода


В C операторы break и continue позволяют выйти лишь на один уровень вверх. Чтобы выйти сразу из нескольких блоков кода, приходится использовать goto:


for( i = 0 ; i < 10 ; i++ ) {
    for( j = 0 ; j < 10 ; j++ ) {
        if( j == 3 ) goto Louter;
        if( j == 4 ) goto L2;
    }
    L2:;
}
Louter:;

В D вы можете пометить блок кода и затем выйти из него с любой глубины вложенности:


Louter: for( i = 0 ; i < 10 ; i++ ) {
    for( j = 0 ; j < 10 ; j++ ) {
        if (j == 3) break Louter;
        if (j == 4) continue Louter;
    }
}
// break Louter goes here

Пространство имён структур


В C несколько напрягает, что у структур отдельное пространство имён, из-за чего каждый раз перед именем структуры приходится указывать ключевое слово struct. Поэтому, типичный способ объявления структур выглядит так:


typedef struct ABC { ... } ABC;

В D ключевое слово struct используется для объявления структур в том же пространстве имён, что и все остальные объявления, так что достаточно писать просто:


struct ABC { ... }

Ветвление по строковым значениям (например, обработка аргументов командной строки)


На C вы вынуждены заводить для этого массив строк, синхронный с ним список констант, последовательно итерироваться по массиву в поисках нужной строки, а потом делать switch-case по этим константам:


#include <string.h>
void dostring( char *s ) {
    enum Strings { Hello, Goodbye, Maybe, Max };
    static char *table[] = { "hello", "goodbye", "maybe" };
    int i;

    for( i = 0 ; i < Max ; i++ ) {
        if( strcmp( s , table[i] ) == 0 ) break;
    }
    switch( i ) {
        case Hello:   ...
        case Goodbye: ...
        case Maybe:   ...
        default:      ...
    }
}

При большом числе вариантов становится сложно поддерживать синхронность этих трёх структур данных, что ведёт к ошибкам. Кроме того, последовательный перебор вариантов — не слишком эффективен при большом их числе, а значит требуется ещё более сложный код, чтобы искать не линейно, а, например, двоичным поиском или через хеш таблицу.


D же расширяет функционал switch в том числе и на строки, что упрощает исходный код и позволяет компилятору сгенерировать наиболее оптимальный машинный код:


void dostring( string s ) {
    switch( s ) {
        case "hello":   ...
        case "goodbye": ...
        case "maybe":   ...
        default:        ...
    }
}

Выравнивание полей структур


В C управление выравниванием происходит через аргументы компилятора и влияет сразу на всю программу и боже упаси вас не перекомпилировать какой-нибудь модуль или библиотеку. Для решения этой проблемы используются директивы препроцессора #pragma pack, но директивы эти не портабельны и сильно зависят от используемого компилятора:


#pragma pack(1)
struct ABC {
    ...
};
#pragma pack()

В D есть специальный синтаксис, с помощью которого вы можете детально настроить как выравнивать те или иные поля (По умолчанию поля выравниваются в совместимой с C манере):


struct ABC {
    int z;              // z is aligned to the default

    align(1) int x;    // x is byte aligned
    align(4) {
        ...             // declarations in {} are dword aligned
    }
    align(2):          // switch to word alignment from here on

    int y;              // y is word aligned
}

Однако в C11 уже появилось ключевое слово alignas:


#include <stdalign.h>

struct data {
  char x;
  alignas(128) char cacheline[128]; // over-aligned array of char, not array of over-aligned chars
};

Анонимные структуры и объединения


C до версии 2011 года требует всем структурам давать имена, даже если они излишни:


struct Foo {
    int i;
    union Bar {
        struct Abc { int x; long y; } _abc;
        char *p;
    } _bar;
};

#define x _bar._abc.x
#define y _bar._abc.y
#define p _bar.p

struct Foo f;

f.i;
f.x;
f.y;
f.p;

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


D (а также C11) поддерживает анонимные структуры, что позволяет выражать вложенные сущности более естественным образом, сохраняя плоский внешний интерфейс:


struct Foo {
    int i;
    union {
        struct { int x; long y; }
        char* p;
    }
}

Foo f;

f.i;
f.x;
f.y;
f.p;

Определение структур и переменных


На C вы можете объявить и структуру и переменную одним выражением:


struct Foo { int x; int y; } foo;

Или по отдельности:


struct Foo { int x; int y; };   // note terminating ;
struct Foo foo;

В D всегда используются отдельные выражения:


struct Foo { int x; int y; }    // note there is no terminating ;
Foo foo;

Получение смещения поля структуры


В C, опять же, используются макросы:


#include <stddef>
struct Foo { int x; int y; };

off = offsetof( Foo , y );

В D у каждого поля есть специальное свойство:


struct Foo { int x; int y; }

off = Foo.y.offsetof;

Инициализация объединений


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


union U { int a; long b; };
union U x = { 5 };                // initialize member 'a' to 5

В D вам необходимо явно указать какому полю вы присваиваете значение:


union U { int a; long b; }
U x = { a : 5 };

Инициализация структур


В C (до версии 1999 года) поля инициализируются в порядке их объявления, что не является проблемой для маленьких структур, но становится настоящей головной болью в случае структур больших, а также в случаях, когда необходимо изменить порядок следования и состав полей:


struct S { int a; int b; int d; int d; };
struct S x = { 5 , 3 , 2 , 10 };

В D (и в C99) вы тоже можете инициализировать поля по порядку, но лучше всё же явно указывать имена инициализируемых полей:


struct S { int a; int b; int c; int d; }
S x = { b : 3 , a : 5 , c : 2 , d : 10 };

Инициализация массивов


В C массивы инициализируются по порядку следования элементов:


int a[3] = { 3 , 2 , 2 };

Вложенные массивы в C могут не окружаться фигурными скобками:


int b[3][2] = { 2,3 , { 6 , 5 } , 3,4 };

В D, разумеется, элементы инициализируются также по порядку, но вы можете и явно указывать смещения. Следующие объявления приводят к одному и тому же результату:


int[3] a = [ 3, 2, 0 ];
int[3] a = [ 3, 2 ];            // unsupplied initializers are 0, just like in C
int[3] a = [ 2 : 0, 0 : 3, 1 : 2 ];
int[3] a = [ 2 : 0, 0 : 3, 2 ];     // if not supplied, the index is the previous one plus one.

Явное указание индексов очень полезно, когда в качестве смещений необходимо иметь значение из какого-либо набора:


enum color { black, red, green }
int[3] c = [ black : 3, green : 2, red : 5 ];

Скобки для вложенных массивов обязательны:


int[2][3] b = [ [ 2 , 3 ] , [ 6 , 5 ] , [ 3 , 4 ] ];

int[2][3] b = [ [ 2 , 6 , 3 ] , [ 3 , 5 , 4 ] ];            // error

Экранирование спецсимволов в строках


В C проблемно использовать символ обратной косой черты, так как он означает начало специальной последовательности, поэтому его необходимо дублировать:


char file[] = "c:\\root\\file.c"; // c:\root\file.c
char quoteString[] = "\"[^\\\\]*(\\\\.[^\\\\]*)*\""; // /"[^\\]*(\\.[^\\]*)*"/

В D в дополнение к обычным строкам с экранированием в стиле C, есть и так называемые «сырые строки», где экранирование не работает, и вы получаете ровно то, что ввели:


string file = r"c:\root\file.c";  // c:\root\file.c
string quotedString = `"[^\\]*(\\.[^\\]*)*"`;  // "[^\\]*(\\.[^\\]*)*"

ASCII против многобайтных кодировок


В C используется отдельный тип символов wchar_t и специальный префикс L у строковых литералов с «широкими символами»:


#include <wchar.h>
char foo_ascii[] = "hello";
wchar_t foo_wchar[] = L"hello";

Но из-за этого есть проблема с написанием универсального кода, совместимого с разными типами символов, что решается специальными макросами, добавляющими необходимые конвертации:


#include <tchar.h>
tchar string[] = TEXT( "hello" );

Компилятор D выводит типы констант из контекста использования, снимая с программиста бремя указывать типы символов вручную:


string  utf8  = "hello";     // UTF-8 string
wstring utf16 = "hello";     // UTF-16 string
dstring utf32 = "hello";     // UTF-32 string

Однако, есть и специальные суффиксы, указывающие тип символов строковых констант:


auto str    = "hello";       // UTF-8 string
auto _utf8  = "hello"c;      // UTF-8 string
auto _utf16 = "hello"w;      // UTF-16 string
auto _utf32 = "hello"d;      // UTF-32 string

Отображение перечисления на массив


В C вы отдельно объявляете перечисление, отдельно массив, что довольно сложно поддерживать, когда число элементов разрастается:


enum COLORS { red , blue , green , max };
char *cstring[ max ] = { "red" , "blue" , "green" };

В D такое отображение задаётся парами ключ-значение, что гораздо проще в поддержке:


enum COLORS { red, blue, green }

string[ COLORS.max + 1 ] cstring = [
    COLORS.red : "red",
    COLORS.blue : "blue",
    COLORS.green : "green",
];

Создание новых типов


В C оператор typedef на самом деле создаёт не новый тип, а всего лишь псевдоним:


typedef void *Handle;
void foo( void * );
void bar( Handle );

Handle h;
foo( h ); // coding bug not caught
bar( h ); // ok

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


#define HANDLE_INIT ( (Handle) -1 )

Handle h = HANDLE_INIT;
h = func();
if( h != HANDLE_INIT ) {
    ...
}

Чтобы в C реально создать новый тип, с которым будет работать как проверка типов, так и перегрузка функций, необходимо создать создать структуру:


struct Handle__ { void *value; }
typedef struct Handle__ *Handle;
void foo( void * );
void bar( Handle );

Handle h;
foo( h ); // syntax error
bar( h ); // ok
``
А работа со значениями по умолчанию превращается в чёрную магию:
```c
struct Handle__ HANDLE_INIT;

// call this function upon startup
void init_handle() {
    HANDLE_INIT.value = (void *)-1;
}

Handle h = HANDLE_INIT;
h = func();
if( memcmp( &h , &HANDLE_INIT , sizeof( Handle ) ) != 0 ) {
    ...
}

D же обладает мощными возможностями метапрограммирования, что позволяет реализовать typedef самостоятельно и подключать из библиотеки:


import std.typecons;

alias Handle = Typedef!( void* );
void foo( void* );
void bar( Handle );

Handle h;
foo( h ); // syntax error
bar( h ); // ok

Вторым параметром шаблона Typedef можно указать значение по умолчанию, которое и попадёт в стандартное свойство всех типов — init:


alias Handle = Typedef!( void* , cast( void* ) -1 );
Handle h;
h = func();
if( h != Handle.init ) {
    ...
}

Сравнение структур


В C нет простого способа сравнить две структуры, поэтому приходится использовать сравнение диапазонов памяти:


#include <string.h>

struct A x , y;
...
if( memcmp( &x , &y , sizeof( struct A ) ) == 0 ) {
    ...
}

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


В D вы просто сравниваете значения, а компилятор обо всём позаботится (в D память всегда инициализируется нулями по умолчанию, так что под капотом просто используется быстрое сравнение диапазонов памяти):


A x , y;
...
if( x == y ) {
    ...
}

Сравнение строк


В C используется специальная функция, которая последовательно сравнивает байты до нулевого байта, которым заканчиваются все строки:


char str[] = "hello";

if( strcmp( str , "betty" ) == 0 ) {  // do strings match?
    ...
}

В D же вы просто используете стандартный оператор сравнения:


string str = "hello";

if( str == "betty" ) {
    ...
}

Строка в D являются не более чем массивом символов, перед которым сохранена его длина, что позволяет сравнивать строки с гораздо большей эффективностью, посредством сравнения диапазонов памяти. Более того D поддерживает и операции отношения в отношении строк:


string str = "hello";

if( str < "betty" ) {
    ...
}

Сортировка массивов


Хоть многие C программисты и велосипедят из раза в раз пузырьковые сортировки, правильный путь — это использовать библиотечную функцию qsort():


int compare( const void *p1 , const void *p2 ) {
    type *t1 = (type *) p1;
    type *t2 = (type *) p2;

    return *t1 - *t2;
}

type array[10];
...
qsort( array , sizeof( array ) / sizeof( array[0] ), sizeof( array[0] ), compare );

К сожалению, функция compare() должна быть объявлена явно и подходить к сортируемым типам.


D имеет мощную библиотеку алгоритмов, работающую как со встроенными, так и с пользовательскими типами:


import std.algorithm;
type[] array;
...
sort( array ); // sort array in-place
array.sort!"a>b" // using custom compare function
array.sort!( ( a , b ) => ( a > b ) )  // same as above

Строковые литералы


C не поддерживает многострочные строковые константы, однако с помощью экранирования перевода строки можно добиться их подобия:


"This text \"spans\"\n\
multiple\n\
lines\n"

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


"This text \"spans\"
multiple
lines
"

Обход структур данных


Рассмотрим простую функцию поиска строки в бинарном дереве. В C мы вынуждены создать вспомогательную функцию membersearchx, которая используется для непосредственно обхода дерева. Чтобы она не просто ходила, но и делала что-то полезное мы передаём ей ссылку на контекст в виде специальной структуры Paramblock:


struct Symbol {
    char *id;
    struct Symbol *left;
    struct Symbol *right;
};

struct Paramblock {
    char *id;
    struct Symbol *sm;
};

static void membersearchx( struct Paramblock *p , struct Symbol *s ) {
    while( s ) {
        if( strcmp( p->id , s->id ) == 0 ) {
            if( p->sm ) error( "ambiguous member %s\n" , p->id );
            p->sm = s;
        }

        if( s->left ) {
            membersearchx(p,s->left);
        }
        s = s->right;
    }
}

struct Symbol *symbol_membersearch( Symbol *table[] , int tablemax , char *id ) {
    struct Paramblock pb;
    int i;

    pb.id = id;
    pb.sm = NULL;
    for( i = 0 ; i < tablemax ; i++ ) {
        membersearchx( pb , table[i] );
    }
    return pb.sm;
}

В D всё гораздо проще — достаточно объявить вспомогательную функцию внутри реализуемой, и первая получит доступ к переменным второй, так что нам не приходится прокидывать в неё дополнительный контекст через параметры:


class Symbol {
    char[] id;
    Symbol left;
    Symbol right;
}

Symbol symbol_membersearch( Symbol[] table , char[] id ) {
    Symbol sm;

    void membersearchx( Symbol s ) {
        while( s ) {
            if( id == s.id ) {
                if( sm ) error( "ambiguous member %s\n" , id );
                sm = s;
            }

            if( s.left ) {
                membersearchx(s.left);
            }
            s = s.right;
        }
    }

    for( int i = 0 ; i < table.length ; i++ ) {
        membersearchx( table[i] );
    }

    return sm;
}

Динамические замыкания


Рассмотрим простой контейнерный тип. Чтобы быть реиспользуемым, ему необходимо уметь применять некоторый сторонний код к каждому элементу. В C это реализуется посредством передачи ссылки на функцию, которая и вызывается с каждым элементом в качестве параметра. В большинстве случаев дополнительно ей нужно передавать и некоторый контекст с состоянием. Для примера, передадим функцию вычисляющую максимальное значение чисел из списка:


void apply( void *p , int *array , int dim , void (*fp) ( void* , int ) ) {
    for( int i = 0 ; i < dim ; i++ ) {
        fp( p , array[i] );
    }
}

struct Collection {
    int array[10];
};

void comp_max( void *p , int i ) {
    int *pmax = (int *) p;

    if( i > *pmax ) {
        *pmax = i;
    }
}

void func( struct Collection *c ) {
    int max = INT_MIN;

    apply( &max , c->array , sizeof( c->array ) / sizeof( c->array[0] ) , comp_max );
}

В D вы можете передать так называемый делегат — функцию, привязанную к некоторому контексту. Когда вы передаёте куда-либо ссылку на функцию, которая зависит от контекста, в котором она объявлена, то на самом деле передаётся именно делегат.


class Collection {
    int[10] array;

    void apply( void delegate( int ) fp ) {
        for( int i = 0 ; i < array.length ; i++ ) {
            fp( array[i] );
        }
    }
}

void func( Collection c ) {
    int max = int.min;

    void comp_max( int i ) {
        if( i > max ) max = i;
    }

    c.apply( &comp_max );
}

Или вариант по проще, с анонимным делегатом:


void func( Collection c ) {
    int max = int.min;

    c.apply( ( int i ) {
        if( i > max ) max = i;
    } );
}

Переменное число аргументов


Простой пример, как на C написать функцию, суммирующую все переданные ей аргументы, сколько бы их ни было:


#include <stdio.h>
#include <stdarg.h>

int sum( int dim , ... ) {
    int i;
    int s = 0;
    va_list ap;

    va_start( ap , dim );
    for( i = 0 ; i < dim ; i++) {
        s += va_arg( ap , int );
    }
    va_end( ap );
    return s;
}

int main() {
    int i;

    i = sum(3, 8 , 7 , 6 );
    printf( "sum = %d\n" , i );

    return 0;
}

Как видим, нам пришлось явно указать при вызове сколько параметров мы собираемся передать в функцию, что не просто избыточно с точки зрения программиста, но и является потенциальным источником трудноуловимых багов. Ну и куда же без традиционной проблемы — проверка передаваемых в функцию типов лежит целиком и полностью на совести программиста.


В D же есть специальная конструкция "..." позволяющая принять несколько параметров в качестве одного типизированного массива:


import std.stdio;

int sum( int[] values ... ) {
    int s = 0;

    foreach( int x ; values ) {
        s += x;
    }
    return s;
}

int main() {
    int i = sum( 8 , 7 , 6 );

    writefln( "sum = %d", i );

    return 0;
}

И наоборот, вы можете передать массив в функцию, которая принимает переменное число параметров:


int main() {
    int[] ints = [ 8 , 7 , 6 ];

    int i = sum( ints );

    writefln( "sum = %d", i );

    return 0;
}

Заключение


В этой статье мы рассмотрели преимущественно низкоуровневые возможности языка D, во многом являющиеся небольшим эволюционным шагом относительно языка C. В следующих статьях мы рассмотрим вопрос перехода с более мощного языка C++ и более простого Go. Оставайтесь на связи.

Проголосовать:
+28
Сохранить: