[Перевод] man!( C => D )

Каждый С-программист с опытом накапливает привычный багаж техник и идиом. Зачастую бывает сложно понять, как сделать то же самое в новом языке. Так вот, вашему вниманию предлагается коллекция распространенных паттернов на 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 
#include 

CHAR_MAX
CHAR_MIN
ULONG_MAX
DBL_MIN


Стало на D:

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

Таблица соответствия типов 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 

NAN
INFINITY

#include 

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 

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 

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( 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 

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 

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

// Concatenate s1 and s2, and put result in s
free(s);
s = (char *) 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 = (char *) realloc( s , ( lens + sizeof( 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 

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 
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
}

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


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

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 поддерживает анонимные структуры, что позволяет выражать вложенные сущности более естественным образом, сохраняя плоский внешний интерфейс:

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 
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 поля инициализируются в порядке их объявления, что не является проблемой для маленьких структур, но становится настоящей головной болью в случае структур больших, а также в случаях, когда необходимо изменить порядок следования и состав полей:

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


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

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 
char foo_ascii[] = "hello";
wchar_t foo_wchar[] = L"hello";


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

#include 
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


А работа со значениями по умолчанию превращается в чёрную магию:

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 

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 
#include 

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. Оставайтесь на связи.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

© Habrahabr.ru