Страницы

Поиск по вопросам

Показаны сообщения с ярлыком типы. Показать все сообщения
Показаны сообщения с ярлыком типы. Показать все сообщения

понедельник, 30 марта 2020 г.

Различные типы функций

#cpp #типы


Не очень понимаю, почему следующий код выводит 0 1 0. Какие типы у foo_1, foo_2 и foo_3?

void foo() {}

int main() {
    using foo_1 = decltype(foo);
    using foo_2 = const foo_1&;
    using foo_3 = foo_1&;


    std::cout << std::is_same_v << '\n'
              << std::is_same_v << '\n'
              << std::is_same_v << '\n';
}


Тем более на gcc следующий код даёт одинаковые значения

std::cout << typeid(foo_1).name() << '\n'
          << typeid(foo_2).name() << '\n'
          << typeid(foo_3).name() << '\n';

    


Ответы

Ответ 1



foo_2 и foo_3 являются ссылками на функцию (причём const для foo_2 проигнорирован и эти типы вообще полностью идентичны). В то время как foo_1 тип - функция (не указатель на функцию). Поэтому и получается такой результат в std::is_same_v. Несколько примеров: #include void f() {} int main() { using F = decltype(f); // тип функции using FR = void(&)(); // тип ссылки на функцию using FP = void(*)(); // тип указателя на функцию (using форма) typedef void (*FP2)(); // тип указателя на функцию (typedef форма) std::cout << std::boolalpha; std::cout << std::is_same_v << "\n"; // true (F - функция) std::cout << std::is_same_v << "\n"; // false (F - не ссылка на функцию) std::cout << std::is_same_v << "\n"; // false (F - не указатель на функцию) std::cout << std::is_same_v << "\n"; // true (FP и FP2 одно и то же, заданное разными способами) std::cout << std::is_same_v << "\n"; // true (F& - ссылка на функцию) std::cout << std::is_same_v << "\n"; // true (const не влияет на тип ссылки на функцию) std::cout << std::is_same_v << "\n"; // true (const не влияет на тип функции) std::cout << std::is_same_v << "\n"; // false (const влияет на тип указателя на функцию) } При этом typeid(/* */).name() даёт одинаковые результаты потому, что для ссылок и значений он работает единообразно, можете попробовать например typeid(int&).name() и typeid(int).name(). А учитывая неявные преобразования между типом функции и типом указателя на функцию одинаковость результата здесь тоже не вызывает особых вопросов.

Ответ 2



Тип foo - void (void), foo_2 и foo_3 - void ( & ) (void). const в этом контексте ничего не делает (warning: 'const' qualifier on function type 'foo_1' (aka 'void ()') has no effect [-Wignored-qualifiers]) typeid выдает одинаковые результаты потому что не различает ссылок.

понедельник, 24 февраля 2020 г.

Объекты классов-оболочек в Java

#java #классы #типы


Объект любого из классов-оболочек представляет собой полноценный экземпляр в динамической
памяти, в котором хранится его неизменяемое значение.
Что подразумевается под неизменяемым значением?
    


Ответы

Ответ 1



Неизменяемое значение - это значение примитива, для которого объект является оболочкой. К примеру, для int - оболочка java.lang.Integer для long - оболочка - java.lang.Long. Чтобы убедиться - открываем исходный код класса java.lang.Integer : /** * The value of the {@code Integer}. * * @serial */ private final int value; /** * Constructs a newly allocated {@code Integer} object that * represents the specified {@code int} value. * * @param value the value to be represented by the * {@code Integer} object. */ public Integer(int value) { this.value = value; } Вот это private final int value и есть неизменяемое значение. UPD: То есть объявляя Integer somevar = 1; на самом деле, выполняется нечто вроде: Integer somevar = new Integer(1); далее, если мы захотим поменять значение somevar = 2 - то переменная somevar будет указывать уже на другой объект, а не перезатрет значение предыдущего. а предыдущий объект, будет собран сборщиком сусора, если на него более не имеется ссылок

C++. Чем заменить тип huge компилятора Turbo C в GCC MinGW?

#c #mingw #borland #типы


Есть кусочек кода программы компилируемой в Borland Turbo C.
Там есть такое объявление:

huge x[240001];


При попытке скомпилировать эту строку компилятором GCC (MinGW32) происходит ошибка: 


  huge does not name a type.


Соответственно вопросы:

1) Что за тип такой huge? Каков sizeof() в байтах?

2) Это структура или что?

3) Где объявлен этот тип? В каких заголовочных файлах?

4) Чем его можно заменить в GCC (MinGW32)?

P.S. Желательно точное соответствие замены, т.к. важен размер, порядок байт при считывании/записи
массива x[240001], 10 байт пишется в файл fwrite(x,10,240000,ou);.
    


Ответы

Ответ 1



О какой версии компилятора идет речь? Просто начиная с версии 3.0 компилятор назывался просто Borland C++, без Turbo. Так что я вынужден считать, что речь идет о Turbo C 2.0. Тогда... Тогда это были 16-разрядные программы, которые работали в DOS в реальном режиме работы процессора, так что указатели там состояли из сегмента и смещения... Вобщем, рассказывать долго, почитайте лучше сами - например, тут или тут или где-нибудь еще. Соответственно, у вас просто выделен массив памяти с использованием такого указателя памяти, ну, а пропущенный тип в C всегда воспринимался как int. Так что это должен быть просто int x[240001];. Только учтите, что int тогда представлял собой 16-битное значение. Что до fwrite - то в приведенной у вас записи в файл пишется 240000 раз по 10 байт. Это смущает, потому что такой памяти под DOS просто не было - вся она была ограничена 640КБайтами. Так что, может, поделитесь исходником - выложив его где-то? Просто интересно посмотреть и разобраться, что же это такое программа пытается сделать. "По-моему, так" (с) Пух

воскресенье, 16 февраля 2020 г.

C++. Чем заменить тип huge компилятора Turbo C в GCC MinGW?

#c #mingw #borland #типы


Есть кусочек кода программы компилируемой в Borland Turbo C.
Там есть такое объявление:

huge x[240001];


При попытке скомпилировать эту строку компилятором GCC (MinGW32) происходит ошибка: 


  huge does not name a type.


Соответственно вопросы:

1) Что за тип такой huge? Каков sizeof() в байтах?

2) Это структура или что?

3) Где объявлен этот тип? В каких заголовочных файлах?

4) Чем его можно заменить в GCC (MinGW32)?

P.S. Желательно точное соответствие замены, т.к. важен размер, порядок байт при считывании/записи
массива x[240001], 10 байт пишется в файл fwrite(x,10,240000,ou);.
    


Ответы

Ответ 1



О какой версии компилятора идет речь? Просто начиная с версии 3.0 компилятор назывался просто Borland C++, без Turbo. Так что я вынужден считать, что речь идет о Turbo C 2.0. Тогда... Тогда это были 16-разрядные программы, которые работали в DOS в реальном режиме работы процессора, так что указатели там состояли из сегмента и смещения... Вобщем, рассказывать долго, почитайте лучше сами - например, тут или тут или где-нибудь еще. Соответственно, у вас просто выделен массив памяти с использованием такого указателя памяти, ну, а пропущенный тип в C всегда воспринимался как int. Так что это должен быть просто int x[240001];. Только учтите, что int тогда представлял собой 16-битное значение. Что до fwrite - то в приведенной у вас записи в файл пишется 240000 раз по 10 байт. Это смущает, потому что такой памяти под DOS просто не было - вся она была ограничена 640КБайтами. Так что, может, поделитесь исходником - выложив его где-то? Просто интересно посмотреть и разобраться, что же это такое программа пытается сделать. "По-моему, так" (с) Пух

воскресенье, 9 февраля 2020 г.

Как устроены примитивные типы в Java?

#java #типы #реализация


Типы-обёртки (вроде Integer) это классы, которые вполне можно "пощупать" в исходниках
и понять как они устроены и работают. Как реализованы примитивные типы на уровне языка? 
    


Ответы

Ответ 1



Честно говоря опасаюсь, что не понимаю суть вопроса, но ответить всё же попробую. Возьмём для примера public class Example { public static void test() { int i = 69; } public static void main(String[] args) { test(); } } После компиляции исходный код превратится в class-файл содержащий байткод - инструкции виртуальной машины. В частности int i = 69 превратится в два байткода 0: bipush 69 2: istore_0 Команда java Example запустит виртуальную машину, которая загрузит файл Example.class в память, создаст главный поток и вызовет в нём метод main. При создании главного потока в off-heap памяти виртуальной машины для него будет создан стек. В момент вызова метода test в этом стеке будет создан фрейм. Каждый фрейм содержит массив 32-битных ячеек для хранения локальных переменных и аргументов метода, а также стек операндов. Да-да, стек в стеке. JVM - стековая виртуальная машина, в ней стек операндов служит тем же целям, что и регистры в процессоре. Виртуальная машина начнёт выполнять байткод метода с нулевого смещения, по которому расположен опкод bipush с операндом 69. Этот опкод поместит константу 69 на вершину стека операндов. Следующий опкод istore_0 возьмёт элемент с вершины стека операндов и поместит в 1-й элемент массива локальных переменных. "Пощупать" релизацию виртуальной машины, выполняющей байткод можно так же в исходниках. Правда, это будет уже C++, а не Java. Посмотреть код интерпретатора, выполняющийся для конкретного опкода, можно там же, но он платформозависим и в нём уже будет сплошной ассемблер.

В чем разница в массивах IEnumerable<T> и List<T>?

#c_sharp #linq #типы #ienumerable


Возможно вопрос не совсем корректный.
Допустим у нас имеется следующий код:

  List listValues = new List { 1, 2, 3 };
            var t1 = listValues.ToList();


В данном случае переменная t1 будет ссылаться на новый объект List с аналогичными
значениями.

IEnumerable listValues2 = new List { 1, 2, 3 };
        var t2 = listValues2.ToList();


Здесь, как я понял происходит то, что t2 ссылаться на новый объект IEnumerable с
аналогичными значениями (массив t2 содержит 1,2,3).

Разнится в том, что при добавлении элементов во втором случае надо явно привести
к типу List, так? 

 ((List)listValues2).Add(4);


И/или имеются еще различия в данном случае?
    


Ответы

Ответ 1



t2, как и t1, будет ссылаться на новый объект List. IEnumerable - интерфейс, создать экземпляр интерфейса нельзя. listValues2 - интерфейсная ссылка. Она может указывать на любой экземпляр любого класса, реализующего IEnumerable. Например, на экземпляр класса Queue или Stack. IEnumerable необходим для оператора foreach, так как определяет метод GetEnumerator(). В этом интерфейсе не определяется метод Add(). Этот метод определен в интерфейсе ICollection, который также реализуется классом List. Именно поэтому для добавления элементов по ссылке listValues2 приходится предварительно явно приводить типы. При этом, если ссылка listValues2 указывала на экземпляр класса, отличного от List произойдет исключение времени выполнения.

Ответ 2



Здесь, как я понял происходит то, что t2 ссылаться на новый объект IEnumerable с аналогичными значениями Тип времени выполнения в обоих случаях одинаковый - List. Более того, даже если заменить var ... на IEnumerable ..., ничего не изменится, тип времени выполнения останется тем же, так как метод ToList всегда возвращает объект типа List. (Кроме того, объект просто не может иметь тип IEnumerable, так как экземпляр интерфейса создать нельзя.) Чтобы убедиться на практике, выполните код: List listValues = new List { 1, 2, 3 }; var t1 = listValues.ToList(); Console.WriteLine(t1.GetType()); IEnumerable listValues2 = new List { 1, 2, 3 }; var t2 = listValues2.ToList(); Console.WriteLine(t2.GetType()); IEnumerable listValues3 = new List { 1, 2, 3 }; IEnumerable t3 = listValues3.ToList(); Console.WriteLine(t3.GetType()); Он выведет System.Collections.Generic.List`1[System.Int32] System.Collections.Generic.List`1[System.Int32] System.Collections.Generic.List`1[System.Int32] Чтобы все-таки получить тип времени выполнения, отличный от List, можно реализовать метод-итератор: static IEnumerable Foo(List list) { for (int i = 0; i < list.Count; i++) yield return list[i]; } Тогда var t4 = Foo(listValues); Console.WriteLine(t4.GetType()); Выведет имя автоматически сгенерированного компилятором типа-итератора (у меня ConsoleApplication1.Program+d__0)

Ответ 3



Давайте я попробую немножко объяснить по-своему. Зайду с другого конца: зачем на практике могут понадобиться интерфейсы? Допустим, вам понадобилось выводить на консоль содержимое списка List и вы написали такой метод для этого: static void Print(List list) { foreach (var n in list) Console.Write(n + " "); Console.WriteLine(); } Теперь можно его использовать: List list = new List { 1, 2, 3 }; Print(list); Удобно. Но что, если нам понадобилось вывести массив? Пробуем использовать этот же метод: int[] array = new int[] { 4, 5, 6 }; Print(array); Увы, это невозможно. Как быть? Давайте разберёмся, что именно делается со списком в этом методе: он перечисляется (enumerate). В него ничего не добавляется, не изменяется, не удаляется. Всё, что от него требуется - позволить себя перечислять (проходить по нему). Интерфейс IEnumerable как раз и означает, что тип, реализующий его, является перечислым - по нему можно пробежаться в цикле. Изменим наш метод следующим образом: static void Print(IEnumerable list) { foreach (var n in list) Console.Write(n + " "); Console.WriteLine(); } Теперь код Print(array); успешно компилируется и работает. Теперь мы можем передавать в этот метод любой тип, реализующий интерфейс IEnumerable. Конечно, здесь мы ограничены типом int, но оставим это за рамками обсуждения. Более того, другой программист, использующий нашу библиотеку с этим методом, по одной лишь сигнатуре этого метода скажет, что может произойти с коллекцией, которую он подаст на вход: она будет перечислена и только. А если метод принимает List, то внутри метода вполне может произойти добавление новых данных в список или их удаление, или другие нежелательные действия... Таким образом, интерфейсы позволяют использовать не один жёстко заданный тип, а множество разных. А также интерфейсы дают понять, какие действия разрешены. PS: ещё больше гарантий по неизменности данных дают интерфейсы IReadOnly*.

Ответ 4



Ответ немного припоздал, но всё-же. Важно понимать, что тип переменной и тип её значения - не одно и то же. К примеру: using System; using System.Collections.Generic; namespace CSrharpApplicationTest { internal class Program { private static void Main(string[] args) { IEnumerable x = new List(); Console.WriteLine(x.GetType()); } } } Тип переменной - IEnumerable, тип её значения - List. В обоих случаях в Ваших примерах тип t1 и t2 - List, благодаря вызову метода-расширения ToList(), реализация которого выглядит следующим образом: [__DynamicallyInvokable] public static List ToList(this IEnumerable source) { if (source == null) { throw Error.ArgumentNull("source"); } return new List(source); } , где конструктор List описывается так: [__DynamicallyInvokable] public List(IEnumerable collection) { if (collection == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.collection); } ICollection collection2 = collection as ICollection; if (collection2 != null) { int count = collection2.Count; if (count == 0) { _items = _emptyArray; } else { _items = new T[count]; collection2.CopyTo(_items, 0); _size = count; } } else { _size = 0; _items = _emptyArray; foreach (T item in collection) { Add(item); } } } Разнится в том, что при добавлении элементов во втором случае надо явно привести к типу List, так? Да, во втором случае Вы указываете компилятору, что тип listValues2 - любой тип, реализующий IEnumerable. В интерфейсе IEnumerable, нет методов добавления/изменения/удаления элементов, ведь он описывает минимальный функционал для возможности перечисления по коллекции, соответственно компилятор не может дать гарантий, что то, что лежит внутри listValues2 - коллекция, с методом Add. Поэтому требуется явное приведение типа к List.

среда, 5 февраля 2020 г.

Семантика работы\хранения UpCast“инга \ DownCast”инга в CLR

#c_sharp #память #clr #типы


Начнем с теории.
Допустим,имеется следующие классы:  

class A{}
class B : A{}
class C : B{}


Далее,мы делаем UpCast :  

A a1 = new C();  


Будет ли следующее утверждение верным : объект a1 является объектом типа C,и базовым
классом для него является тип А (то бишь вверх по иерархии) !?  

Далеко не уходя от кассы, представьте что добавили в код следующее: 

class A
{
    public virtual void Method()
    {
        Console.WriteLine("Method A invoked");
    }
}
class B : A
{
    public new virtual void Method()
    {
        Console.WriteLine("Method B invoked");
    }
}
class C : B
{
    public override void Method()
    {
        Console.WriteLine("Method C invoked");
    }
}


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

Method A invoked
Method A invoked
Method С invoked
Method C invoked   



   Но на самом то деле,мы получим : Method A invoked Method A invoked Method A invoked
Method C invoked


Исходя из этой логики,выходит что мое предыдущее высказывание не верное,и это значит,что
все таки базовым классом для C,является B ?  



Теперь перейдем к другой части вопроса.
К примеру имеем код:  

  class Program
    {
        static void Main(string[] args)
        {
            //объект типа класса А
            A a = new A();
            //объект типа класса B
            B b = new B();
            //UpCast, который равен объекту "b"
            A a1 = b;
            //UpCast как отдельный объект
            A a2 = new B();
            //DownCast, который равен объекту "а1"
            B b1 = (B)a1;

            B b2 = a as B; // вернет Null, т.к. DownCast
            //без предварительного UpCast не возможен

            // B b2 = new A(); - невозможно из за безопасности типов

            //сравниваем b с а1,видим что типы идентичны.
            Console.WriteLine(b.GetType() == a1.GetType());
            //сравниваем а2 с а1,видим что типы идентичны.
            Console.WriteLine(a2.GetType() == a1.GetType());
            //сравниваем b1 и а1,видим что типы идентичны
            Console.WriteLine(b1.GetType() == a1.GetType());

            //Проверяем сами обьекты,вернет True
            Console.WriteLine(a1.Equals(b));
            //вернет False,но реализация этих объектов идентична
            Console.WriteLine(a2.Equals(a1));
            //Вернет True
            Console.WriteLine(b1.Equals(a1));

            Console.ReadKey();
        }
    }
    class A
    {
    }
    class B : A
    {
    }  




Так все же,что происходит за кулисами?
Как при UpCast"е \ DownCast"е ,два одинаковых объекта(точнее две ссылки,указывающие
на один и тот же объект),имеют различную реализацию(да,да - это полиморфизм). За счет
чего это достигается(то бишь,как CLR реализует эту модель поведения) и как примерно
выглядит все это чудо-юдо в самой среде CLR ? Как выглядит "наследование" внутри CLR
между типами?
    


Ответы

Ответ 1



По порядку: Будет ли следующее утверждение верным : объект a1 является объектом типа C,и базовым классом для него является тип А (то бишь вверх по иерархии) !? Нет. Корректным утверждением будет следующее: объект a1 является объектом типа C,и базовыми классами для него являются типы B и А. Разница большая, поскольку каждый тип в иерархии наследования может привносить новые аспекты поведения. Теперь дальше: class A { public virtual void Method() { Console.WriteLine("Method A invoked"); } } class B : A { public new virtual void Method() { Console.WriteLine("Method B invoked"); } } class C : B { public override void Method() { Console.WriteLine("Method C invoked"); } } А данном примере сложно сказать, что именно хотел сказать автор этих строк с точки зрения бизнес-логики, но звучит это примерно так: класс B добавляет новый метод Method, но, к сожалению, он использует метод, имя которого уже есть в базовом классе. Но класс B хочет не "подменить" поведение метода из базового класса, а создать свой собстенный метод, который ничего не имеет общего с методом базового класса, кроме имени. Подобная практика приводит к неоднозначному поведению, поскольку теперь выбор метода определяется не только динамическим типом объекта (типом времени исполнения), но и типом переменной (типом, известным компилятору): если используется переменная типа A, то будет вызван метод из класса A. Если же тип переменной - это B или C, то будет использоваться другая ветка методов (ниже будет объяснение, почему это так). Другими словами, с точки зрения метода Method существует две ветки: одна начинается типом А и им же и ограничивается, и есть другая полиморфная ветка, которая начинается в типе B и продолжается в наследнике - типе С. Теперь немного о том, как это устроено в CLR. Для каждого типа CLR хранит табличку с методами (Method Table), где каждая запись описывает отельный метод - его сигнатуру и признак того, переопределяет ли данный слот метод из базового класса. Когда вы объявили метод с приставкой new в классе B, CLR добавила "новый" метод в табличку, при этом пометила этот метод, как новый, не связанный с методом из базового класса. В случае же класса С, метод Method в табличке методово типа C помечен, как полиморфный, т.е. переопределяющий поведение непосредственного базового класса. Теперь стоит сказать, как происходит разрешение метода во время исполнения: в случае вызова a.Method будет вначале определен статический тип переменной a, после чего в таблице методов будет найден метод Method. Если статический тип переменной - это A, то вначале будет просмотрена таблица методов типа A. И в этом случае CLR увидет, что этот метод виртуальный. После чего будет определен реальный тип объекта (например, тип C) и CLR посмотрит, а есть ли у этого типа переопределение метода, объявленного в типе A. CLR получит отрицательный ответ, поскольку тип C не переопределяет метод Method, объявленный в типе A (ведь этот тип переопределяет метод Method типа B). Вот и получается, что результат разрешения имени метода у нас теперь зависит не только от типа времени исполнения, но и от типа переменной времени компиляции.

среда, 29 января 2020 г.

Как преобразовать double? в double на C#

#c_sharp #преобразование #типы


Есть поле класса типа double?, то есть оно может и не содержать значения.

А я в своем классе использую просто double.

Как сделать преобразование?

И как быть с DateTime? -> DateTime
    


Ответы

Ответ 1



Если вы уверены, что значение там есть, вы можете получить его так: double? nd = ...; double v = nd.Value; Если не уверены, вам придётся сначала проверить: double? nd = ...; if (nd == null) { // значения нет, обрабатываем этот случай } else { // значение есть double d = nd.Value; // работаем с ним } Для случая, когда для отсутствующего значения подойдёт, например, 0.0, можно написать просто так: double d = nd ?? 0.0; Но как именно правильно реагировать на отсутствующее значение, решать только вам.

Ответ 2



Ну можно что-нибудь вот такое например: double val = nullableDouble.HasValue ? nullableDouble.Value : 0.0;

вторник, 28 января 2020 г.

Общий вопрос по работе *_cast< >()?

#cpp #типы #преобразование_типов


Привет всем.

Нужно разобраться как работает static_cast, dynamic_cast, да и вообще любой cast
из серии кастов, но особенно эти 2, чтобы потом далее без помощи думать над casts.

На сколько смог вычитать, static_cast занимается тем, что берет и побитово( или все
же побайтово?) копирует данные одного типа в данные другого типа. Вроде просто, но
мне еще интересна реализация. Как найти релизацию? В visual studio я не могу перейти
по goToDeclaration/defenition к описанию тела этой функции. 
*_cast вообще функция или что это? Похоже на какую-то типа шаблонную функцию или
что-то типа generic в C#.

dynamic_cast. Преобразование с проверкой. То есть на сколько понял он позволяет делать
upCast/downCas, но не позволяет делать crossCast.
Тут у меня холиварный большой вопрос: зачем делать upCast - понятно, на этом работает
полиморфизм. А вот downCast зачем в С++ нужен? Я пытался на stackowerflow найти ответ,
читая посты с подобным вопросом на c#, но я пока не могу думать глобальными концепциями
c#, потому мало что понял.

Можно еще приводить один тип объектов к другому (crossCast). Ну это для меня вообще
дремучий лесище. Во-первых: указатели-то может как-то и приведутся, но а вообще как
это будет работать-то??? Для этого нужно знать как вообще работают недра вызова методов
(про раннее и позднее связывание знаю).
Во-вторых: а зачем такое может быть нужно??? Можно пример попроще?
В-третьих: я так понимаю, что после приведения одного объекта к другому, поля перемешаются
и будут содержать мусор. Так как информация о том как приводить не имеется, на сколько
я знаю, в классе нет такой информации. Ну а методы вообще непонятно как будут работать. 

Помогите пожалуйста залатать столь огромную дыру в моих знаниях)
    


Ответы

Ответ 1



На сколько смог вычитать, static_cast занимается тем, что берет и побитово (или все же побайтово?) копирует данные одного типа в данные другого типа. Ни одно из преобразований в С++ не работает "побитово" или "побайтово". Потому они и называются преобразованиями. Преобразования выполняют конвертирование данных из одного формата в другой (зачастую - нетривиальное). В общем случае с "побитовой" точки зрения представление результата такого преобразования не имеет ничего общего с представлением исходного значения. Побитовым/побайтовым копированием занимается функция memcpy, а не преобразования. static_cast - пожалуй самый универсальный каст. Он умеет делать арифметические преобразования, преобразования указателей в/из типа void *, преобразования объектных указателей вверх-вниз по иерархии, преобразования указателей на члены вверх-вниз по иерархии и т.п. Как найти релизацию? Реализацию чего? Все преобразования являются операторами языка и, соответственно, реализуются на уровне ядра языка. dynamic_cast. Преобразование с проверкой. То есть на сколько понял он позволяет делать upCast/downCas, но не позволяет делать crossCast. Тут у вас наблюдается какая-то терминологическая путаница. Наоборот, dynamic_cast - это единственный из кастов, который умеет делать cross-cast. Cross-cast-ом называется прямое преобразование от одного базового подобъекта к другому базовому подобъекту в рамках одного общего содержащего объекта. Т.е. сross-cast может возникать только в условиях множественного наследования #include struct B { virtual ~B() {} }; struct C {}; struct D : B, C {}; int main() { D d; B *b = &d; // <- upcast C *c1 = &d; // <- upcast C *c2 = dynamic_cast(b); // <- cross-cast assert(c1 == c2); } Также dynamic_cast умеет делать downcast сквозь виртуальное наследование (чего не умеет делать static_cast) #include struct A { virtual ~A() {} }; struct B : virtual A {}; struct C : virtual A {}; struct D : B, C {}; int main() { D d; A *a = &d; // <- upcast D *d1 = dynamic_cast(a); // <- downcast, OK assert(d1 == &d); D *d2 = static_cast(a); // <- downcast, ERROR } dynamic_cast предназначен для нетривиальных полиморфных преобразований, но только внутри связной иерархии полиморфных классов. Ну и, разумеется, dynamic_cast умеет проверять корректность downcast-ов и сross-cast-ов. А вот downCast зачем в С++ нужен? Во многих жизненных ситуациях избежать downcast-а не удается, хоть с пьюристско-педантичной токи зрения downcast-ы возможно и идут вразрез с принципами чистого ООП. Во многих случаях код с downcast-ом получается проще, чем та же функциональность, реализованная с упорным избежанием downcast-а. Одним из классических примеров идиоматического применения downcast является Curiously Recurring Template Pattern ("странно повторяющийся шаблон"). Можно еще приводить один тип объектов к другому (crossCast). Приведение типов между посторонними несвязанными типами не является и не называется cross-cast-ом. Делать приведение типов между несвязанными между собой типами может только reinterpret_cast, но по поводу результата такого приведения и его полезности почти никаких гарантий языком не дается. В-третьих: я так понимаю, что после приведения одного объекта к другому, поля перемешаются и будут содержать мусор. Так приводить можно не сами объекты, а только указатели или ссылки на них. Такое преобразования делает именно и только reinterpret_cast. И да, при доступе через такие приведенные указатели или ссылки получится ерунда. Но лазить в данные через такие приведенные указатели язык все равно запрещает - поведение не определено (за редкими исключениями). Так что это функциональность из серии "сам виноват".

Ответ 2



Все эти преобразования - это специальные функции (некоторые их называют "магическими"). Это функции на уровне компилятора. Поэтому, если очень интересно, что там внутри - нужно брать исходники компилятора и изучать. Логично предположить, что в случае студии вряд ли кто то их даст:) Но что же делать? а нужно смотреть в сгенерированный код. В студии есть возможность его посмотреть, но если не ограничивать себя только студией, то можно воспользоваться чудесным сайтом - https://gcc.godbolt.org/ Я немного экспериментировал и обычно весь код сводился просто к копированию указателя с одной переменной в другую или компилятор понимал, что я пытаюсь преобразовать два несовместимых типа и отказывался компилировать. То есть на сколько понял он позволяет делать upCast/downCas, но не позволяет делать crossCast. c upCast Вы разобрались сами. А вот с downCast сложнее. Обычно, его применение - признак плохого дизайна. Но... иногда сроки горят, заказчик хочет уже на вчера и приходиться. Эта ситуация может возникнуть, если в каком то общей функции (которая получае указатель на объект-предок) хочется вызвать специфическую функцию наследника. На сколько смог вычитать, static_cast занимается тем, что берет и побитово( или все же побайтово?) в данном случае все равно. Но я везде слышу "копия побитовая" или "побитово одинаковые". Видимо потому, что бит - как самая минимальная единица измерения информации известна давно. И все привыкли. А байт я думаю появился позже и размер был его не постоянный (хотя в стандарте с++ сказано, что байт - это строго 8 бит). Можно еще приводить один тип объектов к другому (crossCast). Ну это для меня вообще дремучий лесище. Во-первых: указатели-то может как-то и приведутся, но а вообще как это будет работать-то??? А кто его знает. Нужно смотреть на внутреннее устройство обеих объектов и тот код, который згенерировал компилятор. С переменными класса все понятно - в большинстве случаев они адресуются по смещению. С невиртуальными функциями также все просто. А вот с виртуальными... для этого вообще то придумали слово UB - неопределенное поведение. Что произойдет в этом случае - да все что угодно. Может работать, а может и комп взорвать. Во-вторых: а зачем такое может быть нужно??? Можно пример попроще? у программистов бывают страшные фантазии.

Ответ 3



static_cast, по сути, вызывает соответствующий оператор преобразования или конструктор. Для встроеных типов всё может быть немного сложнее, но принцип тот же. Если написать int i = 1; double d = i; то будет неявно вызван оператор преобразования из int в double, то же самое можно сделать явно с помощью double d = static_cast(i). static_cast сработает только в том случае, если соответствующая функция преобразования уже существует (в виде конструктора или специального оператора преобразования), и делает ровно то, что делают эти функции преобразования. Естественно, в простых случаях всё максимально оптимизируется, но простое побитовое копирование встречается редко. Какие преобразования допустимы для встроенных типов можно посмотреть тут. Всё, что делает dynamic_cast - это приводит указатель/ссылку на базовый класс к указателю/ссылке на потомка, или бросает исключение, если объект не является этим потомком. Если объект вообще не является потомком этого базового класса т.е. такое преобразование не может произойти независимо от значения времени выполнения, произойдет ошибка компиляции. Обратное преобразование выполняется неявно, или, при желании, при помощи static_cast. reinterpret_cast интерпретирует указатель на объект одного типа как указатель на объект другого типа, независимо от того, насколько это правильно. Соответственно, в общем случае эта интерпретация некорректна и приводит к неопределенному поведению (то есть к проблемам). Для встроенных типов reinterpret_cast можно воспринимать как побитовое копирование. В отличие от static_cast, которое выполнит корректное преобразование, reinterpret_cast не делает этого, т.е. если double наложить на uint_64, получится неизвестное значение (зависит от аппаратного представления double и uint_64). Но можно, например, наложить значение uint_64 на указатель или наоборот, но даже в этом случае это будет работать только на 64-разрядных системах. UPD: Для таких вещей часто полезно смотреть, во что компилятор в итоге транслирует код, для чего удобно использовать https://gcc.godbolt.org/ (только нужно не забывать про оптимизатор и соответствующие флаги компиляции). Edit: dynamic_cast работает во время выполнения, определяя реальный тип объекта по его таблице виртуальных функций или подобным образом. Для этого тип, к которому приводится указатель должен быть либо унаследован от типа исходного указателя, или совпадать с ним. В других случаях такое преобразование невозможно в принципе, а потому ошибка возникает ещё на этапе компиляции. Например, нельзя привести int* к double* или std::runtume_error* к istream*, независимо тот того, на объекты каких типов они на самом указывают.

Определение типа

#cpp #типы #преобразование_типов


Как определить тип результата арифметического выражения?
    


Ответы

Ответ 1



Операнды арифметических типов в под-выражениях должны быть приведены к общему типу, чтобы можно было выполнить операцию и получить тип результирующего выражения. Процесс получения общего типа для операндов операций и выведения результирующего типа носит название обычных арифметических преобразований - usual arithmetic conversions. Порядок преобразование типов операндов арифметических типов в выражениях определяется следующим образом, как это описано в разделе 5 Expressions стандарта C++: —(10.2) If either operand is of type long double, the other shall be converted to long double. То есть если один из операндов имеет тип long double, то второй операнд приводится к тому типу. —(10.3) Otherwise, if either operand is double, the other shall be converted to double. Иначе, если один из операндов имеет тип double, то второй операнд также должен быть приведен к этому типу. —(10.4) Otherwise, if either operand is float, the other shall be converted to float. Иначе, если один операнд имеет тип float, то второй операнд также должен быть приведен к этому типу. —(10.5) Otherwise, the integral promotions (4.5) shall be performed on both operands.61 Then the following rules shall be applied to the promoted operands: Иначе применяется целочисленное приведение типов. —(10.5.1) If both operands have the same type, no further conversion is needed. Если после этого типы совпадают, то дальнейшие преобразования не производятся. —(10.5.2) Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank shall be converted to the type of the operand with greater rank. Иначе если оба операнда имеют беззнаковый или знаковый тип, то операнд с меньшем рангом преобразуется к типу операнда с большим рангом. —(10.5.3) Otherwise, if the operand that has unsigned integer type has rank greater than or equal to the rank of the type of the other operand, the operand with signed integer type shall be converted to the type of the operand with unsigned integer type. Иначе, если операнд беззнакового целого типа имеет ранг больший или равный рангу знакового целого типа второго операнда, то второй операнд преобразуется к типу беззнакового целого типа первого операнда. —(10.5.4) Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, the operand with unsigned integer type shall be converted to the type of the operand with signed integer type. Иначе если операнд знакового целого типа может представить все значения беззнакового целого типа второго операнда, то второй операнд преобразуется к типу первого операнда. —(10.5.5) Otherwise, both operands shall be converted to the unsigned integer type corresponding to the type of the operand with signed integer type. Иначе оба операнда должны быть преобразованы к беззнаковому целому типу, соответствующему знаковому целому типу первого операнда. Вот простой пример Имеются два объявления long int x = 0; unsigned int y = 0; и оба типа, long int и unsigned int, занимают 4 байта. Спрашивается, какой тип будет у варажения x + y Уверяю вас, что многие программисты, которые не читали этот раздел стандарта, будут не в состоянии ответить на этот вопрос:) Ответ: согласно последнему подпункту цитаты типом выражения будет unsigned long int. Имейте в виду, что ранг типа long int всегда больше ранга int и, соответственно, ранга unsigned int.

Ответ 2



Как std::common_type::type :)

Ответ 3



Почему бы Вам не использовать auto, а затем, когда Вам понадобится тип результата - decltype(...) ? Пример (весьма банальный): auto res = 14.88 + 12; std::vector vec;

пятница, 24 января 2020 г.

Приведение параметризованных типов

#java #generics #типы


В сервлете необходимо получить инстанс DAO который обернут в AtomicReference, но
при приведении типов компилятор волнуется и пишет:

Unchecked cast: 'java.lang.Object' to 'java.util.concurrent.atomic.AtomicReference'
less... (⌘F1) 
Signals places where an unchecked warning is issued by the compiler, for example:

  void f(HashMap map) {
    map.put("key", "value");
  }

Hint: Pass -Xlint:unchecked to javac to get more details.


Сам код выглядит так:

AtomicReference dao = (AtomicReference) req.getServletContext().getAttribute("dao");


Подскажите как можно успокоить компилятор, учитывая что instance of не работает с
дженериками, или если работает то как это написать?

Спасибо.
    


Ответы

Ответ 1



Использовать аннотацию @SuppressWarnings("unchecked").

среда, 22 января 2020 г.

Почему Convert.ChangeType не может конвертировать из char в double (или float)

#c_sharp #типы


У меня есть такой метод:

    public static T To(this char o)
        where T : struct
    {
        return (T)Convert.ChangeType(o, typeof(T));
    }


Если я на месте T стоит double (или float), то выдается InvalidCastException с текстом
вроде System.InvalidCastException: Недопустимое приведение "Char" к "Double".
Ну что не так с double? Тип явно больше char. Сейчас этот метод выглядит так:

public static T To(this char o)
        where T : struct
    {
        var obj = Convert.ChangeType(o, typeof(int));
        return (T)Convert.ChangeType(obj, typeof(T));
    }


Сам вопрос - в заголовке. Это как-то объясняется где-нибудь с точки зрения логики?
Я ответа не нашел.
    


Ответы

Ответ 1



Метод Convert.ChangeType внутри себя пытается привести аргумент к IConvertible и вызвать соответствующий метод. Для случая с Char, будет вызываться его метод .ToDouble /// double IConvertible.ToDouble(IFormatProvider provider) { throw new InvalidCastException(Environment.GetResourceString("InvalidCast_FromTo", "Char", "Double")); } В котором явно бросается исключение. Почему метод .ToDouble не поддерживается? Не только этот метод. .ToBoolean, .ToDateTime, .ToDecimal и .ToSingle тоже не поддерживаются, все они бросают InvalidCastException так же как и .ToDouble. В этом случае дизайн .NET пытается уберечь вас от проблем. Преобразование char в целые типы имеет смысл, вы можете посмотреть на таблицы Unicode и посчитать количество codepoint. Но что должно означать преобразование в Boolean? Какой из Unicode code point будет True? Как символ вообще может быть дробным значением? Нет половинных или четвертных codepoint. перевод ответа @HansPassant Стоит так же отметить, что тип char может быть неявно преобразован в тип ushort, int, uint, long, ulong, float, double или decimal. То есть следующий код отработает без ошибок: char c = 'c'; double a = (double)c; double b = c;

понедельник, 6 января 2020 г.

JS: существует таблица неявного приведения типов?

#javascript #nodejs #веб_программирование #типы


Привет!

Существует таблица неявного приведения типов или где правильные ответы смотреть?

Пример:

[1] + [2] - [3]
= 9


или:

{} + {}
= "[object Object][object Object]"

    


Ответы

Ответ 1



Правильные ответы есть в спецификации. В примерах в вопросе нужно смотреть операции сложения и вычитания. Как видно из алгоритма, в случае сложения сначала операнды приводятся к примитивному типу, с помощью вызова внутренней функции ToPrimitive (алгоритм ToPrimivite можно также увидеть в ответе на вопрос В чем разница между valueOf и toString) Далее, если хотя бы один из операндов строка - производится сложение строк. В противном случае, операнды приводятся к числу и выполняется сложение чисел. В случае с вычитанием - операнды сразу приводятся к числу. Рассмотрим первый пример из вопроса: [1] + [2] - [3] Происходит вычисление [1] + [2]. операнды приводятся к примитивному типу, для массивов - это равносильно вызову ToString, следовательно: [1]+[2] -> "1"+"2" складываются строки: "1"+"2" = "12" Происходит вычисление "12" - [3]. Операнды приводятся к числам: "12" -> 12, [3] -> "3" -> 3 вычитание: 12 - 3 = 9

вторник, 31 декабря 2019 г.

Интегральные типы

#cpp #типы #условия


Почему конструкция switch case работает только с интегральными типами, и что вообще
такое интегральные типы?
    


Ответы

Ответ 1



Термин "integral" ("интегральный") является синонимом термина "integer" ("целочисленный"). Так что интегральные типы - это просто все целочисленные типы. Конструкция switch/case работает только с целочисленными типами потому, что, во-первых, ей нужен тип с четко и однозначно определенным отношением строгого равенства, что, например, исключает использование плавающих типов. В этом отношении switch/case аналогичен нетиповым шаблонным параметрам, которые тоже поддерживают только типы с четко определенным отношением равенства. Во-вторых, эта конструкция изначально задумывалась не просто как "синтаксический сахар", а с задней мыслью о том, что во многих случаях структура переходов в switch/case будет поддаваться очевидным оптимизациям: табличной реализации или реализации через прямое вычисление целевого адреса. Для "сложных" типов, вроде строк, возникновение возможности такой оптимизации выглядит существенно менее вероятным, чем для целочисленных типов. Разумеется, в современном С++ не составило бы, наверное, большого труда расширить диапазон поддерживаемых в switch/case типов. Но, насколько я знаю, таких планов пока нет.

Ответ 2



Сначала представим как работает эта конструкция. Не точно, но логика такая: int val[] = {3, 4, 7}, Cswitch_case =4; for (int i = 0; i < 3; ++i) { if (!(val[i] ^ Cswitch_case)) // так проверяет switch cout << i; Т.е. выполняется битовая операция, результат которого дает команду по какой метке выполнять программу. (правда, еще проверку, что оба нули, я не дописал). А для неинтегральных типов, обычно это невозможно. Например: } string s1("first"), s2 = s1; // if(!(s1 ^ s2)) эта битовая операция невозможна // cout << "...."; Поэтому было введено ограничение...

понедельник, 30 декабря 2019 г.

Почему operator++ должен возвращать const T?

#cpp #операторы #типы


const T T::operator++(int)
{
    T tmp = *this;
    ++(*this);
    return tmp;
}


Рассмотрим такой вариант реализации постфиксного оператора инкрементирования. Почему
требуется возвращать именно const T, а не T? Такой вариант реализуется для типа int,
а нужно ли мне реализовывать этот оператор для своего пользовательского типа аналогично?

Я придерживался негласного правила, что при разработке классов (типов) в случае если
непонятно (или еще не сформулировано) требуемое поведение то следует организовать поведение,
какое предоставляет int. int возвращает const int. А нужно ли и мне так?
    


Ответы

Ответ 1



Нет необходимости делать возвращаемый тип константым, чтобы избежать двойного инкремента ++++. Достаточно объявить оператор инкремента только для lvalue объектов (добавить & в сигнатуру): struct T { T operator++(int) & { return *this; } }; int main() { T t; t++; // ok t++++; // ошибка }

Ответ 2



Постфиксный оператор инкрементирования (оператор декрементирования аналогнично) возвращает const T, чтобы предотвратить потенциально некорректное использование некоторых конструкций, т.к. двойное постфиксное инк-/дек- рементирование вида i++++. В силу особенностей реализации этого оператора возвращается прошлое значение объекта, поэтому второй вызов оператора operator++(int) изменит значение возвращенной первым оператором копии исходного объекта, а не результата с учетом первого инкрементирования. Как правило, клиентский код обычно не требует (вообще говоря, не должен) поддержки таких конструкций, как двойное применение постфиксного оператора и одновременное изменение значения, возвращаемого постфиксным оператором, в одной инструкции, поэтому запрет подобной операции через const T вполне оправдан. Ошибок избегать это помогает точно.

Ответ 3



const T в результате функции запрещает неконстантные операции, в частности перемещение (move-семантика). struct X { X(X&&); const X operator++(int); int& operator*(); }; X a; X b = a++; // тут будет копирование, а не перемещение *a++ = 1; // не скомпилируется

воскресенье, 22 декабря 2019 г.

Возможно ли реализовать тип данных более компактный и оптимальный чем String в Java?

#java #строки #типы_данных #типы #теория_типов


Тип данных String (т.е. public final class String) в Java 1.8 занимает 3168 строк
исходного кода (включая все deprecated-методы).
Зачастую, никто не использует все возможности данного типа в конкретном проекте.

Ради спортивного интереса, хочется реализовать компактный тип данных а-ля public
final class CompactFastString с минимальным набором методов и свойств.

Цель - более компактный с точки зрения используемой памяти и более быстрый для работы.
Цель больше лабораторная, нежели реальная.

Но существует мнение, что сложно реализовать более оптимальное решение, чем стандартная
Java SE библиотека.

Что вы думаете на этот счет?
    


Ответы

Ответ 1



Более быстрый в общем случае не получится. Стандартные типы пакета java.lang очень оптимизированы на уровне JVM. Тот исходный код, который вы видите в этих классах - для информации и дебага, а в реальности он заменяется на исключительно быстрые инструкции с учетом возможностей процессора. Например, сравнение строк будет не посимвольным, а с помощью пакетных инструкций SSE, которые умеют сравнивать по 4 байта за такт процессора. В этом и есть преимущество JIT компиляции. P.S. Архитекторы виртуальной машины тоже не очень довольны занимаемой памятью под строки, поэтому планируется в будущих версиях сделать 2 режима работы строк - utf-16 и только latin-1. Но опять же это будет на уровне JVM.

суббота, 21 декабря 2019 г.

Не понимаю почему for выполняется

#c #типы


Не думал что задам такой вопрос но при переделывании кода с Pascal в C получилась
странность.
Pascal:

for j := 1 to Length(tmp)-1 do begin


и C

for(j = 1; j <= strlen(tmp)-1; j ++) {


Это оказывается разные вещи при Length(tmp)=0... 
    


Ответы

Ответ 1



Оказывается в отладчике и в реальности вещи ведут себя по-разному. Ломал голову понял , в реальности: (strlen(tmp) - 1) не -1 )) Т.к strlen возвращает size_t (implementation этого типа я не нашёл) а это unsigned то C почему-то работает с (strlen(tmp) - 1) как с unsigned и соотв-но результатом будет макс_ПОЛОЖИТЕЛЬНОЕ_значение_size_t - 1 ... А отладчик считал что (strlen(tmp) - 1) типа signed и отображал -1 как результат и более того в отладчике j <= (strlen(tmp) - 1) == 0 а в реальности j <= (strlen(tmp) - 1) == 1 по указанной причине... Вот так вот "весело" программировать на C) Исправляется это кстати так: for(j = 1; j <= (signed)(strlen(tmp) - 1); j ++)

Ответ 2



Опередили)) #include int main(void) { size_t i = 0; int j = 1; if (j < i - 1) printf("OK\n"); else printf("NO\n"); return 0; } Печатает ОК. Аккуратнее с signed и unsigned типами, у меня компилятор выдает ворнинг на сравнение

четверг, 19 декабря 2019 г.

Чем отличается хранение в памяти массивов из величин значимого и ссылочного типа?

#c_sharp #массивы #память #типы #c_sharp_faq


Чем отличается хранение в памяти массивов из величин значимого и ссылочного типа?
    


Ответы

Ответ 1



Массив - это непрерывная область памяти, в которой последовательно, друг за другом, размещены некоторые элементы. У массива есть заголовок, о котором, обычно, никто не говорит (и не знает), и собственно значения. Если речь о значимых типах (структурах), они размещаются непосредственно в области выделенной для массива. В случае ссылочных типов (классов), их экземпляры размещаются в куче, а массив содержит всё те же структуры (IntPtr) - указатели на эти объекты. В отличии от массива значимых типов, массив ссылочных типом может содержать элементы указывающие на один и тот же объект.

Ответ 2



Если отвлечься от специфики именно массивов C# (которые являются ссылочными), то переменная значимого типа хранит непосредственно значение, а переменная ссылочного типа хранит адрес значения. Само значение при этом хранится в динамической памяти (куче).

Ответ 3



Все массивы являются ссылочными типами. Arrays are mechanisms that allow you to treat several items as a single collection. The Microsoft® .NET Common Language Runtime (CLR) supports single-dimensional arrays, multidimensional arrays, and jagged arrays (arrays of arrays). All array types are implicitly derived from System.Array, which itself is derived from System.Object. This means that all arrays are always reference types which are allocated on the managed heap, and your app's variable contains a reference to the array and not the array itself. https://stackoverflow.com/questions/1533757/is-int-a-reference-type-or-a-value-type https://docs.microsoft.com/en-us/previous-versions/dotnet/articles/bb985948(v=msdn.10)

Что в себе хранят примитивные типы до инициализации в Java?

#java #типы


Что в себе хранят примитивные типы до инициализации в Java? 
    


Ответы

Ответ 1



Если это локальные переменные (local variables) - ничего. Вы получите ошибку компиляции при обращении к переменной что не была инициализирована. Eсли поля класса/обьекта (class/instance fields) - значения по умолчанию: ссылочние типы - null; примитивные типы - 0 (false для boolean).

Ответ 2



При загрузке классов в память происходит ряд действий в строго определённом порядке. Когда доходит дело до инициализации полей класса, то они принимают значения по умолчанию (false для boolean типа и 0 для всех остальных примитивов и null для всех непримитивов) или явно присвоенное им значение.

Ответ 3



Примитивные типы и их значения по умолчанию: short = 0 byte = 0 int = 0 double = .0d float = .0f long = 0L char = \u0000 boolean = false Ссылка на документацию: https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html

четверг, 5 декабря 2019 г.

Неявное приведение типов в Java

#java #типы #конвертация


Учу Java, читаю Шилдта. Уже много раз прочитал, что Java — сильно типизированный
язык. Почему следующий код выкидывает ошибку

int i = 10;
i = i / 2.5;


А такой код прекрасно работает и присваивает к i значение 4?

int i = 10;
i /= 2.5;

    


Ответы

Ответ 1



Так работает оператор присваивания, если переменная типа int, а i / 2.5 типа double, то вы не можете присвоить значение типа double к типу int без явного каста. То есть проверка типов осуществляется перед присваиванием переменной значения. Во втором случае утверждение является выражением, в котором значение преобразовывается к типу операнда, то есть чтобы можно было осуществить операцию и обратно к типу переменной, где должно хранится значение. То есть int i = 10; d = i /= 2.5; тоже будет работать, поскольку нначения int преобразовавыются к double перед присваиванием без потери точности, обратное не верно и вызывает ошибку.

Ответ 2



операция типа i /= 2.5; это операция с автоматическим приведением типов, будет записана как i = (int) i/2.5; тоже самое с double d = 1d; int i = 1; i = i * d; // ошибка i *= d; // нет ошибки с операциями инкрементации и декрементации анологичная ситуация.