Страницы

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

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

четверг, 9 апреля 2020 г.

Работа перегруженного оператора во время инициализации экземпляра класса [закрыт]

#cpp #классы #указатели #перегрузка_операторов #операторы

                            
             
                
                    
                        
                            Закрыт. Этот вопрос не по теме. Ответы на него в данный
момент не принимаются.
                            
                        
                    
                
            
                    
                
                        
                            
                        
                    
                        
                            Хотите улучшить этот вопрос? Переформулируйте вопрос,
чтобы он соответствовал тематике «Stack Overflow на русском».
                        
                        Закрыт 4 года назад.
                    
                
        

Как перегрузить, например, оператор присваивания таким образом, чтобы он работал
во время инициализации экземпляра класса? 

#include `
using namespace std;`

class Matrix
{
private:
    int **Arr;
    int Size;
public:
    Matrix(int SizeOfMatrix);
Matrix operator = (Matrix &);
};

int main()
{
    Matrix A(4);
    Matrix B(4) = A;
    system("pause");
    return 0;
}
Matrix::Matrix(int SizeOfMatrix)
{
    Size = SizeOfMatrix;
    Arr = new int *[Size];
    for (int i = 0; i < Size; i++)
    {
        Arr[i] = new int[Size];
        for (int j = 0; j < Size; j++)
            Arr[i][j] = rand() % 50;
    }
}
Matrix Matrix :: operator =(Matrix &A)
{
    for (int i = 0; i < Size; i++)
    {
        for (int j = 0; j < Size; j++)
            Arr[i][j] = A.Arr[i][j];
    }
    return *this;
}

    


Ответы

Ответ 1



Прежде всего хотел бы отметить, что данное предложение Matrix B(4) = A; некорректное и не должно компилироваться. Фактически в данном предложении имеется два инициализатора: 4 и A. Но даже если вы правильно напишите Matrix A(4); Matrix B = A; то здесь вызывается не оператор присваивания, как вы думаете, а конструктор копирования, который определен компилятором неявно, и который просто почленно копирует члены данных объектов. Когда вы динамически в классе распределяете память, то есть используете указатели, то вы должны определить явно по крайней мере конструктор копирования, копирующий оператор присваивания и деструктор. В противном случае поведение вашей программы может оказаться неопределенным. Например, в результате использования конструктора копирования, созданного компилятором неявно, может оказаться, что два объекта имеют указатели на одну и ту же область памяти, а потому при удалении этих объектов будет осуществлена попытка удалить одну и ту же память дважды. Ваш класс может выглядеть следующим образом class Matrix { private: int **Arr; size_t Size; public: explicit Matrix( size_t ); Matrix( const Matrix & ); ~Matrix(); Matrix & operator = ( const Matrix & ); }; Matrix::Matrix( size_t SizeOfMatrix ) : Arr( nullptr ), Size ( SizeOfMatrix ) { if ( Size ) { Arr = new int *[Size]; for ( size_t i = 0; i < Size; i++ ) { Arr[i] = new int[Size]; for ( size_t j = 0; j < Size; j++ ) Arr[i][j] = rand() % 50; } } } Matrix::Matrix( const Matrix &rhs ) : Arr( nullptr ), Size ( rhs.Size ) { if ( Size ) { Arr = new int *[Size]; for ( size_t i = 0; i < Size; i++ ) { Arr[i] = new int[Size]; for ( size_t j = 0; j < Size; j++ ) Arr[i][j] = rhs.Arr[i][j]; } } } Matrix::~Matrix() { for ( size_t i = 0; i < Size; i++ ) delete [] Arr[i]; delete [] Arr; } Matrix & Matrix::operator =( const Matrix &rhs ) { if ( this != &rhs ) { int **tmp = nullptr; if ( rhs.Size != 0 ) { if ( Size != rhs.Size ) { tmp = new int *[rhs.Size]; for ( size_t i = 0; i < rhs.Size; i++ ) { tmp[i] = new int[rhs.Size]; } } else { tmp = Arr; } for ( size_t i = 0; i < rhs.Size; i++ ) { for ( size_t j = 0; j < rhs.Size; j++ ) tmp[i][j] = rhs.Arr[i][j]; } } if ( Size != rhs.Size ) { for ( size_t i = 0; i < Size; i++ ) delete [] Arr[i]; delete [] Arr; } Arr = tmp; Size = rhs.Size; } return *this; }

воскресенье, 8 марта 2020 г.

Логический оператор с бесконечным количеством итераций

#java #циклы #операторы


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


Ответы

Ответ 1



Начать исполнять и продолжать пока условие верно: boolean someBoolean = true; while(someBoolean) { //тут можно присвоить false для someBoolean для завершения бесконечного цикла } Либо можно и через цикл for вот так: boolean someBoolean = true; //в цикле for сперва идёт инициализация переменной-чсётчика, в конце действие с ней (инкрементация, например), //а по середине - условие - пока оно верно, цикл будет продолжаться for (;someBoolean;) { //тут можно присвоить false для someBoolean для завершения бесконечного цикла } Если же хотя б одну итерацию надо сделать вне зависимости от начального значения someBoolean, то можно написать так: boolean someBoolean = false; do { //хотя бы раз, но выполнится } while(someBoolean);

среда, 4 марта 2020 г.

Возможно ли реализовать перегрузку операции () в Java?

#java #перегрузка_операторов #операторы


Создать несколько объектов  (например, a и b) разработанного класса.
Класс – вектор (одномерный массив). Реализовать для объектов данного
класса перегрузку операции  ():   (a(i)=b(j)).
    


Ответы

Ответ 1



В vanilla/plain Java нет перегрузки операторов. Да, но если очень сильно постараться, то можно изобразить - через плагины к Java компилятору. Есть например решение по перегрузке арифметических операторов - со скобками конечно будет сложнее. Но если хорошо подумать, наверное можно выкрутиться.

Ответ 2



Нет, переопределить поведение операторов в Java нельзя. В таких случаях обычно берут пример с List и создают методы get и set: a.set(i, b.get(j)); В принципе для данного класса будет логично реализовать интерфейс List или воспользоваться одной из готовых реализаций (если задание допускает использование стандартных классов). Также можете посмотреть в сторону языка Kotlin, он работает под JVM и предоставляет много синтаксического сахара, в том числе переопределение оператора ().

четверг, 27 февраля 2020 г.

Алгоритм присваивания отрицательных значений какой-либо переменной в памяти

#java #операторы #cpu #ram #оператор_присваивания


К примеру, мне известно о том, что все отрицательные числа представляются в памяти
посредством дополнительного кода. Как это происходит? Русскоязычная терминология немного
отличается от той, которая была принята на Западе, поэтому было бы правильнее придерживаться
таких понятий, как первое (обратный код) и второе дополнения, чтобы не нарушать канонов.
С положительными значениями особых вопросов не возникает, так как прямой, обратный
и дополнительный код для этих значений абсолютно не отличается. А вот теперь мы и подошли
к первому вопросу. Меня интересует сам алгоритм присваивания отрицательных значений
какой-либо переменной. Поскольку я изучаю язык программирования Java, то хотелось бы
получить ответ именно в данном контексте (хотя я думаю, что для других языков разницы
также не будет, так как все операции физически осуществляются на уровне микропроцессора).
Правильно ли я понимаю, что за данное действие отвечает АЛУ (арифметико-логическое
устройство)? Или же какой-то другой блок процессора?

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

byte b = -5;


Изначально значение записывается в прямом коде, где старший бит является фиксированным
знаковым битом, который используется для кодирования знака числового значения в знаковых
типах данных. Получаем следующее двоичное представление: 

1000 0101


Затем мы получаем первое дополнение (обратный код). Для этого мы применяем инверсию
для каждого значащего разряда, которые участвуют в формировании самого числового значения.
Значение старшего разряда мы оставляем прежним. В результате инвертирования битов получаем
такой результат:

1111 1010


После чего мы добавляем к нашему результату 1, что и является вторым и результирующим
дополнением. В конечном итоге, в память будет помещено двоичное число, которое соответствует
числу -5 в десятичной системе счисления: 

1111 1011 


Но это всё лишь в моём представлении... Скажите, пожалуйста, насколько я прав? И
действительно ли обработкой всех этих операций занимается именно АЛУ? Если же я прав,
то значит ли это, что современные процессоры фактически не используют операцию вычитания
при арифметических расчётах? 

И еще вопрос, у меня уже давно возникла небольшая путаница с пониманием термина машинное
слово. В той же Википедии написано, что данная величина зависит от разрядности регистров
процессора. А от чего в свою очередь зависит эта разрядность? От типа данных в конкретном
языке программирования? Правильно ли я понимаю, что величина машинного слова типа byte
в языке программирования Java составляет 8 бит, short и char по 16 бит, а int 32 бита?
    


Ответы

Ответ 1



Да, смена знака в дополнительном коде производится инверсией битов и прибавлением единицы. как я понимаю алгоритм присваивания значения переменной Нет, не совсем так. Присваивание byte b = -5; не генерирует арифметических команд, только непосредственно присваивание значения 0xFB (-5). Любые арифметические выражения с константами сворачиваются на этапе компиляции. И действительно ли обработкой всех этих операций занимается именно АЛУ? Да, всей арифметикой занимается АЛУ, кто же еще. За исключением вычислений на этапе компиляции (которые в конечном итоге тоже выполняются АЛУ). значит ли это, что современные процессоры фактически не используют операцию вычитания при арифметических расчётах? Конечно, используют. Хотя вычитание можно выполнять в две команды - инверсия знака + сложение, одна команда вычитания выполняется быстрее. Но вот на уровне микропрограммы процессор может вычитание и не делать. И еще вопрос, у меня уже давно возникла небольшая путаница с пониманием термина машинное слово. В той же Википедии написано, что данная величина зависит от разрядности регистров процессора. А от чего в свою очередь зависит эта разрядность? Разрядность зависит от архитектуры процессора. У 32-разрядных процессоров машинное слово длиной 32 бита. У 64-разрядных соответственно 64 бита. Язык программирования тут не при чем, это аппаратная характеристика процессора.

Ответ 2



Главное преимущество дополнительного кода в том что сложение и вычитание работают одинаково как с положительными так и с отрицательными числами. -1 = 1111 1111 1 = 0000 0001 1 + 1 = 0000 0010 -1 + 1 = 0000 0000 -- из-за переполнения. -1 + -1 = 1111 1110

пятница, 14 февраля 2020 г.

Какой порядок выполнения операций и почему?

#c_sharp #битовые_операции #операторы


static class Program
{
  static void Main()
  {
   var x=3;
   var y=(++x)*(x++)|4/2^2;
   Consoley.Write(y);
 }
} 

    


Ответы

Ответ 1



В соответствии с приоритетами слева направо: (++x) * (x++) | 4 / 2 ^ 2 ^ ^^^^^ +--------------------- x=4, returns 4 | ^^^^^--------------- x=5, returns 4 `--------------------- 4*4=16 ^ | ^ | ^-+--------- 4 | | ^------- 2 | `--------- 4/2=2 | ^----- 2^2=0 | | ^--- 2^2=0 | `----- 2^2=0 `------------- 16|0=16 PS: Надеюсь, не ошибся.

C# разница между & и &&

#c_sharp #операторы


Имеется следующий блок кода 

static void main()
{
int a = 10;
int b = 20;
if(a==10 && b == 20)
{
 //что-то
}
else
{
//что - то
}
}


Но следующий пример работает аналогично

if(a==10 & b==20)
{
 //что-то
}
else
{
//что - то
}


Так же существуют подобдные операторы : |, ||, &,&&.
   
   В чём разница между ними? 
    


Ответы

Ответ 1



a == 10 && b == 20 - если результатом левой части выражения будет false, то b == 20 вычисляться не будет a == 10 & b == 20 - будет вычислять оба выражения вне зависимости от результата левой части выражения. Допустим a и b - строки, тогда: a != null && a.Equals(b) - выполнится нормально a != null & a.Equals(b) - получим NullReferenceException если a == null

четверг, 13 февраля 2020 г.

C++, глобальные перегруженные операторы и операторы-члены

#cpp #перегрузка_операторов #операторы


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

Буду очень благодарен, если кто-нибудь поможет мне разобраться в этом вопросе.
    


Ответы

Ответ 1



Во-первых, есть несколько групп ситуаций, когда у вас просто нет возможности выбирать между этими двумя вариантами: Некоторые операторы разрешается перегружать только методами класса: =, (), [], ->. Ситуации, когда первый (или единственный) операнд оператора не является class-типом. Тогда перегрузка возможна только самостоятельной функцией. Например, оператор сложения int + myclass или инкремента myenum++. Ситуации, когда изменять определение класса первого операнда у вас нет возможности. Тогда перегрузка возможна только самостоятельной функцией. Всем нам знакомый пример: операторы ввода-вывода << и >> для стандартных потоков. Во-вторых, для остальных ситуаций жестких правил нет. Но можно порекомендовать использовать самостоятельные функции для таких семантически-симметричных бинарных операторов как арифметические операторы, логические операторы, побитовые операторы, операторы относительного сравнения и операторы сравнения на равенство. Для унарных операторов зачастую уместнее реализация методом класса. Для операторов составного присваивания (+= и т.п.) тоже интуитивно уместнее реализация методом класса (они сродни копирующему оператору присваивания =). Операторы сдвига << и >> - в зависимости от степени симметричности той семантики, которой вы их наделяете. Обратите также внимание, что в стандартной библиотеке низкоуровневые фундаментальные операторы форматированного ввода-вывода реализованы методами классов, в то время как более высокоуровневые "составные" операторы - самостоятельными функциями. То есть можно предложить и такой вариант разделения подходов к перегрузке операторов.

Ответ 2



Все очень просто. Если оператор модифицирует обьект, то его стоит делать членом класса, а когда оператор выполняет действие с другим обьектом и возвращает другой обьект(или ничего не возвращает), то лучше написать его как независимая функция, дабы избежать разбухания кода. Например: class Some_class { //... public: Some_class& operator+=(const Some_class&); Some_class& operator/=(const Some_class&); //... }; inline Some_class operator +(const Some_class&, const Some_class&); inline Some_class operator *(const Some_class&, const Some_class&); //... Одним словом вы сами создаете абстракцию. Вы можете написать так, как представляете(одни смотрят на собак как на друга, который умеет то то, то то, доугие могут смотреть на них как на зверей, которых нужно размножать для корма зверей в зоопарке). Если абстракция выражает только то, что умеет делать обьект или что можно выражать этим классом, то это хороший код(стиль), понятный читательям и пользовательям. Не нужно туда пихать все то, что могут делать пользователи с экземпляром, не то можно туда пихнуть сотни и тысяча функции и сущности....

Перегрузка математических операторов

#операторы #перегрузка_операторов #cpp


Пишу класс для работы с матричной алгеброй. Матрица может быть произвольного размера,
поэтому память выделяю в конструкторе (а в деструкторе уничтожаю).
Переопределил оператор +. Возникает проблема при возврате объекта из функции-оператора:
деструктор срабатывает раньше, чем вызывающая программа успеет прочитать данные из
объекта. Если деструктор убрать - все работает, но память уплывает.
Что я сделал не так? Спасибо.
Вот код:
class Matrice{

float ** items;

float & v(uint8_t i, uint8_t j){
    return *(float *)(items + i * w + j);
}

public:

int h, w;

Matrice(uint8_t height, uint8_t width){
    h = width;
    w = width;
    items = (float **)malloc(h * w * sizeof(float));
}

~Matrice(){
    free((void *)items);
}

float * operator[](uint8_t i){
    return (float *)(items + i * w);
}

const Matrice operator +(Matrice M){

    Matrice A(M.h, M.w);

    for (int i = 0; i < h; i++){
        for (int j = 0; j < w; j++){
            A[i][j] = v(i, j) + M[i][j];
        }
    }

    return A;

}

};    


Ответы

Ответ 1



Вам нужен правильный конструктор копирования. Без него работает автоматически сгенерированный конструктор, который просто копирует указатель на элементы (items) из одной матрицы в другую. В результате, когда исходная матрица уничтожается, данные в скопированной матрице становятся недоступны. Да, и если уж пишете на C++, переходите с malloc/free на new[]/delete[].

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

Перегрузка оператора сложения для дробей

#cpp #перегрузка_операторов #операторы


Пишу класс рациональных чисел. Хочу перегрузить оператор сложения, чтобы можно было
складывать с дробями целые числа. Всё получилось, но проблема в том что возможна запись 
<мой класс> = <мой класс> + <целое число> 
Если же я пишу так:
<мой класс> = <целое число> + <мой класс>
всё крашится: no match for 'operator+' (operand types are 'int' and 'Number')

Вoт код в .h:

class Number {
public:
    Number(int numerator = 0, int denominator = 1);

    int getNumerator() {return this->numerator;}
    int getDenominator() {return this->denominator;}
    string toString();

    // определяем арифметические действия дробей с дробями
    Number operator+ (Number &other);
    Number operator- (Number &other);
    Number operator* (Number &other);
    Number operator/ (Number &other);

    // определяем арифметические действия дробей с целыми числами
    Number operator+ (int i);
protected:
    int numerator; // числитель
    int denominator; // знаменатель
};


Код в .cpp:

Number Number::operator+(int i) {
    Number res(i * this->denominator() + this->numerator(), this->denominator());
    return res;
}

    


Ответы

Ответ 1



Вот в том числе и по этой причине такие операторы рекомендуется реализовывать не методами класса, как у вас, а отдельными (дружественными) функциями. Вы реализовали именно оператор сложения, в котором целое является правым операндом. Реализовать оператор сложения для целого числа в качестве левого операнда методом класса невозможно в принципе. Его в любом случае придется реализовывать обычной функцией. Но тут на самом деле стоит сделать шаг назад и посмотреть на общий дизайн. Вам на самом деле вообще не нужно реализовывать операторы для сложения с целыми числами. Ваш класс Number уже обладает конвертирующим конструктором, который умеет преобразовывать целые числа в ваш Number. После этого ваши готовые операторы для операндов типа Number уже сами сделают все правильно. Для этого, однако необходимо 1) реализовать их обычными функциями, 2) не забыть о const в параметрах-ссылках, который у вас почему-то отсутствует. class Number { public: Number(int numerator = 0, int denominator = 1); // определяем арифметические действия дробей с дробями friend Number operator +(const Number &lhs, const Number &rhs); friend Number operator -(const Number &lhs, const Number &rhs); friend Number operator *(const Number &lhs, const Number &rhs); friend Number operator /(const Number &lhs, const Number &rhs); protected: // <- ??? int numerator; // числитель int denominator; // знаменатель }; Такой класс уже сам по себе будет поддерживать Number a(2, 3); Number b = a + 1; Number c = 25 + a; и ничего для этого дополнительно писать не надо. Если вам при этом захочется написать отдельные реализации для операций с обычными числами (например, в целях оптимизации) - вы можете это сделать. Но лучше это делать обычными функциями с двумя параметрами, как показано выше, а не методами класса.

Ответ 2



#include using namespace std; struct Number { int numerator; // числитель int denominator; // знаменатель Number(int num = 0, int denom = 1) : numerator(num), denominator(denom) {} //... // определяем арифметические действия дробей с целыми числами Number operator+ (int i) { return Number(i * denominator + numerator, denominator); } friend Number operator +(const int i, const Number& N) { return Number(i * N.denominator + N.numerator, N.denominator); } friend ostream& operator <<(ostream& os, const Number& N) { os << N.numerator << " / " << N.denominator; return os; } }; int main() { Number test(4, 2); std::cout << 5 + test; return 0; // теперь все нормально! } просто оператор определите и еще можете при возвращении Number производить еще и сокращение дроби... и не нужно делать данные закрытыми, если вы всеравно обеспечиваете доступ ко всем данным! Если вам удобно, то вы можете не определить int + Number, но в кодах написать не 5 + Number, а Number(5) + Number...

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

Перегрузка оператор С++

#cpp #перегрузка_операторов #операторы


Такой вопрос. Я перегружаю операторы для работы с вектором, который состоит из трек
точек. Есть несколько задач:


Найти скалярное произведения векторов; (возвращает double)
Найти векторное произведения векторов; (возвращает вектор)


И получается такая проблема, что компилятор не знает какой из двух методов вызвать
(хотя они отличаются возвращающимися параметрами). И как все это решить?

using namespace std;
class Vec3D
{
private:
    double x, y, z;
public:
    Vec3D(double xx = 0, double yy = 0, double zz =0);
    Vec3D operator+ (const Vec3D & V);
//  double operator* (const Vec3D & V); // проблема тут
    friend ostream& operator<< (ostream& cout, Vec3D & V);
    friend Vec3D operator* (const double c, const Vec3D & V);   
    friend Vec3D operator* (const Vec3D & V1,const Vec3D & V2); // и тут
};


P.S. То что нужно реализовать семеричные\бинарные операции через friend функции я знаю
    


Ответы

Ответ 1



Компилятор в общем случае не может определить, какую вызывать перегруженную функцию только по возвращаемому значению. Например, рассмотрите следующий фрагмент кода void f() {} int f() { return 0; } f(); Как определить, какая функция из двух вызывается? Поэтому функции не перегружаются по возвращаемому значению. Перегрузите этот оператор следующим образом, как показано в демонстрационной программе ниже. #include class Vec3D { private: double x, y, z; public: Vec3D( double x = 0, double y = 0, double z =0) : x( x ), y( y ), z( z ) { } friend std::ostream & operator <<( std::ostream &os, const Vec3D &v ); friend Vec3D operator *( const Vec3D &V, double d ); friend Vec3D operator *( double d, const Vec3D &v ); friend Vec3D operator *( const Vec3D &V1, const Vec3D &V2 ); // и тут }; std::ostream & operator <<( std::ostream &os, const Vec3D &v ) { return os << "( " << v.x << ", " << v.y << ", " << v.z << " )"; } Vec3D operator *( const Vec3D &v, double d ) { return Vec3D( d * v.x, d * v.y, d * v.z ); } Vec3D operator *( double d, const Vec3D &v ) { return v * d; } Vec3D operator *( const Vec3D &v1, const Vec3D &v2 ) { return Vec3D( v1.x * v2.x, v1.y * v2.y, v1.z * v2.z ); } int main() { Vec3D v1( 1, 2, 3 ); Vec3D v2( 1, 2, 3 ); std::cout << ( 2 * v1 ) * ( v2 * 3 ) << std::endl; return 0; } Ее вывод на консоль ( 6, 24, 54 ) Нельзя перегрузить оператор по возвращаемому типу. Но вы могли бы взять еще один оператор, как, например, operator ^, чтобы использовать его для одного из видов произведения векторов. В итоге у вас может получиться два оператора operator * и operator ^ Объявление перегруженного оператора operator ^ может выглядеть аналогично объявлению оператора operator * за исключением типа возвращаемого значения.

Ответ 2



Понятие сигнатуры функции не включает возвращаемый тип, поэтому использовать перегрузку для одинаковых типов параметров, но разных возвращаемых типов в C++ нельзя. Увы, решайте проблему иначе... P.S. Недаром они даже в математике обозначаются по-разному :)

Ответ 3



Тут можно постараться и сделать, например, такое (но это не означает, что так делать надо): http://ideone.com/8wAlaK #include struct v3d { double x, y, z; }; struct v3d_mr // multiplication result { double scalar; v3d vector; operator double () { return this->scalar; } operator v3d () { return this->vector; } }; v3d_mr operator * (const v3d &a, const v3d &b) { return { a.x*b.x + a.y*b.y + a.z*b.z, { a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - b.x*a.y } }; } int main() { v3d a = {1, 2, 3}, b = {4, 5, 6}; double s = a * b; v3d v = a * b; printf("%lf\n", s); printf("%lf %lf %lf\n", v.x, v.y, v.z); return 0; }

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

Что означает оператор “стрелочка” в контексте объявления свойства

#c_sharp #net #классы #операторы


Столкнулся с непонятным фрагментом кода

public EndPoint LocalEndPoint => _remote.LocalEndPoint;


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

В microsoft.docs => лямбда оператор, однако впервые вижу что бы его так использовали.
    


Ответы

Ответ 1



Это не лямбда, это появившаяся в C# 6 укороченная запись для вычисляемого свойства public EndPoint LocalEndPoint { get { return _remote.LocalEndPoint; } }

Ответ 2



Это свойство, сжатое до выражения. Начиная с версии C# 6, свойство, допускающее только чтение можно объявлять более кратко как свойство, сжатое до выражения (expression-bodied property). Все фигурные скобки, а также ключевые слова get и return заменяются комбинацией =>

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

Зачем использовать “|=”, “&=” и т.п. в C++ вместо “=”?

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


Програмлю Arduino и читаю в интернете про PORT. Говорят, что какая-то там стабильность
будет, если писать вместо = - |=, или &=.
Так я так и не понял, для чего так писать и чем это отличается от обычного "равно"?
Вот пример рабочего кода:

int main() {

DDRB = B00100000;

while(1) {
    PORTB |= B00100000;
    delay(1000);
    PORTB &= B00000000;
    delay(1000);
    }
}


Если заменить все эти |=, и &= на =, то всё будет работать точно также (как мне кажется)
и даже не изменится размер прошивки (скетча). Так возвращаемся к вопросу: Зачем "|=",
"&=" и т.д. в c++?
    


Ответы

Ответ 1



Если вопрос касается именно c++ - то это операторы. |= - побитовое или с присвоением. &= - побитовое и с присвоением. Подробнее про операторы тут По сути, это упрощенный синтаксис для подобной записи: a = a | b; // для |= a = a & b; // для &= Думаю, стало немного яснее, при чем тут равно. Далее попробуем разобраться, для чего же сами операторы | и &. Они называются побитовое или и побитовое и соответственно. Оператор | берет побитовое представление операндов, и в результате выполнения этого оператора вы получите число, побитовое представление которого будет содержать единицы на тех позициях, на которых хоть в одном из двух побитовых представлений операндов стояла единица. Например: 4 | 5 = 5; // или (в побитовом представлении) 100 | 101 = 101; Заметим, что крайняя слева и справа единицы есть хоть в одном из двоичных представлений чисел, и поэтому после выполнения побитового или у нас есть число 101 (в бинарном представлении) или 5 (в десятичной системе счисления). Оператор & берет побитовое представления операндов, и в результате выполнения этого оператора вы получите число, побитовое представление которого будет содержать единицы на тех позициях, на которых в обоих побитовых представлениях операндов стояли единицы. Например: 4 & 5 = 4; // или (в побитовом представлении) 100 | 101 = 100; Заметим, что осталась лишь одна единица, которая есть и в первом, и в правом операнде. Ну и совсем вплотную подойдя к вашей задаче - | и & применяются обычно для работы с побитовыми флагами. Это сделано для экономии места. К примеру, у вас есть переменная размером 4 байта или 32 бита. В таком случае в этой переменной можно хранить аж 32 флага. Чтобы добавить флаг, нужно применить |: int flags = 0; // Добавим флаг на третьей справа позиции, ведь бинарное представление 4 - 100 flags |= 0x4; Чтобы проверить, есть ли флаг, нужно применить &: // Бинарное и - по сути маска. И если на этой позиции была 1, // то результат будет отличен от 0 и условие будет верным if (flags & 0x4) { // сделать что-то } Как-то так. Почему же работает с равно? Все просто. С равно вы по сути стираете предыдущее состояние переменной с флагами, и передаете ей только 1 флаг вместе с равно. Надеюсь, понятно объяснил... UPD кстати, в вашем случае PORTB |= B00100000; добавляет флаг, а потом PORTB &= B00000000; убирает его (что логично, ведь на той позиции, на которой мы поставили флаг, в B00000000 единицы нет, и применив и мы по сути убрали флажок). UPD2 кстати, в чистом c++ нет литералов вида B00000000, это фишка компилятора arduino. В c++ начиная с версии 14 можно использовать запись вида 0b00000000, чтобы представить бинарный литерал.

Ответ 2



В конкретном вашем коде - да, это все равно. Потому что вам нужо, чтобы вся переменная получала то или иное значение. Но при программировании всяких - особенно вот таких - вещей это очень нужные операции. Представьте себе, что ваш PORTB отвечает сразу за 8 светодиодов. И вам нужно, не меняя остальные - которые светятся, пусть светятся, которые нет - так и остаются - помигать третьим. Пусть PORTB был, ну, скажем, B10011010 - четыре светодиода включены В вашем варианте при присвоении PORTB = B00000100 вы погасите их все, включив третий. Затем все выключите PORTB = B00000000 А вот если сделать так: PORTB = PORTB | B00000100 а затем PORTB = PORTB & ~B00000100 переменная получит значения B10011110, а затем опять станет B10011010. Т.е. вы таким образом способны работать с отдельными битами. Ну, а запись PORTB = PORTB | B00000100 немного короче записывается как PORTB |= B00000100 а PORTB = PORTB & ~B00000100 как PORTB &= ~B00000100 Если и это непонятно, то уж не знаю, что и делать...

Ответ 3



Если заменить все эти |=, и &= на =, то всё будет работать точно также Ничего подобного: auto x{0x0E}; x &= 0x01; assert(0 == x); auto y{0x0E}; y = 0x01; assert(0x01 == y); Заменить на равно можно, но с применением битового оператора: auto z{0x0E}; z = z & 0x01; assert(0x00 == z);

Ответ 4



Я хотел бы добавить к ранее сказанным простой пример. Есть массив чисел, но не числа мне нужны, а нужен результат: В какой позиции нечетные числа? int n[] = {1, 12, 33, 44, 5}; for (int i = 0; i < 5; ++i) { n[i] &= 1; // нечетным элементам присваиваем 1 } Тогда нечетным присвоится 1, а четным !1, т.е. 0 А если вместо оператора &= использовать |=, тогда всем нечетным числам прибавится 1, и будет массив выглядеть так 1, 13, 33, 45, 5 Если же просто присвоить, тогда все элементы будут единицами (естественно)...

воскресенье, 5 января 2020 г.

Как перегрузить оператор << у cout?

#операторы #перекодировка #cpp #библиотеки


В связи с этим вопросом решил задать другой, как оказалось, более важный вопрос.
В общем я недавно начал изучать C++ и поимел проблемы с кодировками, помогите мне
перегрузить << у std::cout чтобы он принимал строки в utf-8 а выводил в windows-866. 
    


Ответы

Ответ 1



Собственно, перегрузить сможете, только если будете передавать какой-то тип, отличный от string, char* и пр., поскольку << у cout уже перегружен для них. Можно просто создать функцию с такой вот примерно сигнатурой void printunicode (ostream&, const string&) В ней собственно переводите юникодное содержимое строки в нужную кодировку, записываете его в буфер, а его уже передаете потоку. Или даже проще. Создаете функцию, которая принимает строку с юникодом, а возвращает в нужной кодировке. Тогда можете ее использовать в череде операторов <<.

Ответ 2



Помню как заминусовали когда в прошлый раз ответил на свой вопрос но всё таки отвечу :) Понял что легче всего сменить кодировку файла на целевую (хорошо что я не японец :) ). В общем для Code::Blocks сделал так: 1) Скачал uniconv.exe (давно качал счас не помню где) 1a) Т.к. uniconv не поддерживает utf-8 поставил по умолчанию в code::blocks windows-1251 2) Добавил в Tools такой Tool: 3) И если работаю с D:\ProjectsC++\C++ Learning\Chapter 2\Homework\Ch2_Ex1.cpp: #include int main(){ using namespace std; cout << "Привет! " << endl << "Моё имя: Иван." << endl << "Моя фамилия: Черновалов."; cin.get(); return 0; } и вызываю tools=>reCHARSET WD то получаю D:\ProjectsC++\C++ Learning\Chapter 2\Homework\Ch2_Ex1.cpp.cpp в windows 866 затем вручную :( открываю его и вижу: #include int main(){ using namespace std; cout << "ЏаЁўҐв! " << endl << "Њ®с Ё¬п: €ў ­." << endl << "Њ®п д ¬Ё«Ёп: —Ґа­®ў «®ў."; cin.get(); return 0; } 4) Строю&Запускаю и ву-аля: Может кому поможет... PS: Как видите не могу получить (не создав Project) отдельно имя файла отдельно директорию поэтому приходится использовать двойное расширение чтобы отличать файлы (т.к ели изменять редактируемый файл то ошибка приложения) и приходится вручную открывать файл. Ещё в code::blocks есть Tools+ и Scripts но я не умею ими пользоваться если кто может помогите усовершенствовать мой "костыль" :) чтобы создавался временный файл компилировался с именем текущего и удалялся...

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

Как работает оператор умножения с присваиванием в Java? [дубликат]

#java #операторы


        
             
                
                    
                        
                            This question already has answers here:
                            
                        
                    
                
                        
                            Сложение 2 чисел типа short в Java
                                
                                    (2 ответа)
                                
                        
                                Закрыт 12 месяцев назад.
            
                    
Имеется тестовый кусок кода.    

public class Test {
    public static void main(String[] args) {
            int i = 2;
            char c = '1';
            c = (char) c * i; //ошибка компиляции
            System.out.println(c);
    }
}


Но

...
c *= i; //выводит b
...


Почему?
    


Ответы

Ответ 1



Если посмотреть в спецификацию то можно увидеть что E1 op= E2 эквивалентно E1 = (T) ((E1) op (E2)) где T это тип E1. Поэтому c *= i; из вашего примера эквивалентно c = (char)(c * i); В случае c = (char)c * i; проблема в том что оно парсится как c = ((char)c) * i; и когда мы умножаем ((char)c) на i результат расширяется до int.

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

Приоритеты операций в языке программирования Java

#java #переменные #операторы


Речь о приоритете операторов в Java.

Итак, уже не раз наталкиваюсь на такие вот интересные таблички, в которых операторы
выставлены в приоритете их выполнения в программе. Типичный пример: https://introcs.cs.princeton.edu/java/11precedence/ 
Можно поискать и другие варианты, но все они более-менее схожи. Во всех таблицах,
которые попадались мне на глаза, постфиксные унарные операторы инкремента и декремента
имеют явный приоритет над своими префиксными аналогами. Следовательно, стоило бы ожидать,
что в составном выражении, которое содержит, как постфиксный инкремент/декремент, так
и префиксный, изначально должен вычисляться именно тот инкремент/декремент, который
был записан в постфиксной форме. Хорошо, давайте тогда рассмотрим небольшой пример.
Возьмём следующий кусок кода: 

 int y = 10;
 int z = ++y * y--;
 System.out.println(z);


Что мы здесь имеем? Я вижу целых 4 операции в данном составном выражении. Ключевой
операций здесь является операция присваивания, но она будет выполнена в самую последнюю
очередь, так как имеет наименьший приоритет. В таком случае нам необходимо отдельно
рассмотреть правую часть данной операции. Очевидно, что изначально необходимо выполнить
унарные операции, а уже потом переходить к бинарной. Но какую операцию делать в первую
очередь? Это достаточно принципиально, так как от этого зависит результат выполнения
программы. Если верить таблице, то сначала я должен вычислить инкремент/декремент,
а потом уже переходить к их перемножению. Поскольку постфиксная форма имеет более высокий
приоритет, то сначала я провожу операцию y--, а уже потом ++y. В общем, если всё делать
так, как я это понимаю, то в результате должно получиться 100. Пишу данный код в своей
IDE и вывожу его на консоль. Результат: 121. Почему так? Неправильно расписана таблица
приоритетов? Или же я чего-то не понимаю?
    


Ответы

Ответ 1



Операция ++y выполняется непосредственно перед тем как значение y будет подставлено в выражение. Операция y-- сразу после. Пример 1: y= 10 z = ++y * y-- ---------------------------- y = y + 1 z = 11 * 11 // 11*11 = 121 y = y - 1 Пример 2: z = (y++) * (++y) ---------------------------- z = 10 * (++y) y = y + 1 y = y + 1 z = 10 * 12 // 10*12 = 120 UPD: JLS утверждает что существуют первичные выражения Первичные выражения включают в себя большинство простейших видов выражений, из которых строятся другие: литералы, создание объектов, обращения к полям, вызовы методов, ссылки на методы и обращения к массиву. Выражение в скобках также рассматривается синтаксически как первичное выражение. постфиксные выражения Постфиксные выражения включают использование операторов postfix ++ и -. Они не считаются первичными выражениями (§15.8), но обрабатываются отдельно в грамматике, чтобы избежать определенных неоднозначностей. И становятся взаимозаменяемыми только здесь, на уровне приоритета постфиксных выражений. и унарные операторы Оператор +, -, ++, --, ~, !, и оператор приведения типа (§15.16) называются унарными операторами. Видно что постфиксные операции занимают отдельную позицию в структуре выражений Java, где-то между вызовами методов и унарными операторами. Соответственно им должен быть назначен определенный уровень приоритета. JLS не объясняет конкретно, почему это было сделано, оправдываясь "определенными неоднозначностями". Можно предположить, что это относиться к удобству парсинга выражений или определенной реализации его механизма. Как выяснили в соседних ответах, столкнуться с конфликтом префиксных и постфиксных операторов или неоднозначностью выражения с их использованием достаточно сложно, и на практику написания кода такое разделение уровней приоритетов сильно не влияет.

Ответ 2



Чтобы разобраться, достаточно открыть .class файл и посмотреть как java его декомпилировала: int y = 10; int y = y + 1; // 11 int z = y * y--; // 11 * 11, потом y = y - 1, но это уже не важно, т.к. `y` больше не используется System.out.println("z = " + z);

Ответ 3



Значение подставляется сразу после выполнения операции. То есть, выполнив ++y оно вернет значение 11 на место операции, а в месте y-- сначала вернет 11, а потом уменьшит y на единицу. И выходит что 11*11 Я как-то даже не обращал внимание, что у постфиксных унарных операторов приоритет выше. Выполняются они все в порядке очереди. Интересно Почему оно так работает, вроде бы понятно. Приоритет нужен в случае решения конфликта, когда два оператора разных приоритетов сталкиваются на одном уровне. То есть (a + b*b) сразу ясно что выполнять первым необходимо умножение, а в случае (a+a == b*b) смысла выполнять первым именно операцию умножение нет, идя в порядке слева направо получим тот же результат. Приоритет операторов ++/-- помог бы решить ситуацию вроде (++a--), указывающий, что сначала нужно выполнить именно уменьшение переменной, но такая конструкция запрещена, да и смыла в ней мало. В случае с (++a * a--) выполнятся будет слева направо. А из=за того, что унарные операции изменяют саму переменную, мы можем проследить какая операция в действительности выполняется первая, хотя по сути это не должно иметь значения. Их нужно использовать с осторожностью

Ответ 4



Друзья, спасибо всем за помощь! Мозговой штурм действительно работает, убедился в этом на своей собственной шкуре. Знаю, что данная тема вводит в ступор многих начинающих программистов, поэтому решил расписать свои соображения на данный счёт. Надеюсь, что судить строго не будете. Итак, давайте начнём разбираться в этом вопросе. В чём основная проблема людей (в том числе и моя) в понимании данной темы? А проблема в том, что мы путаем «приоритет» с обычным порядком выполнения операторов. В каком порядке JVM выполняет инструкции? Очевидно, что все инструкции (а также операторы составных выражений) выполняются в привычном нам левоассоциативном направлении (слева направо), а когда мы доходим до конца строки (как правило в конце каждой строки ставится специальный Unicode-символ (или их комбинация), который является ограничителем строк (в основном это символы, которые имеют мнемоническое обозначение CR+LF)), то мы переходим на нижестоящую строчку и выполняем код с самого начала в том же левостороннем направлении (можно представить это как возврат каретки в самое начало строки в пишущей машинке, а затем перевод той же каретки в начало следующей строки). Когда же начинаются основные сложности? Все неурядицы возникают ровным счётом тогда, когда мы сталкиваемся с некоторой неопределённостью. Что я имею в виду? Давайте рассмотрим классический пример применения на практике таблицы приоритетов операторов. Возьмём следующее арифметическое выражение: /* * Some code */ int z = a + b * c; Ещё с начальной школы мы знаем, что сперва выполняются мультипликативные операции, а затем уже аддитивные, так как первые имеют явный приоритет над вторыми. Как это реализовано в самой Java'е и какая здесь может возникнуть коллизия? Мы видим в правой части операции присваивания составное выражение, которое состоит из двух бинарных арифметических операторов и 3-х операндов. Очевидно, что у нас есть два возможных пути, которые будут пересекаться в одном месте. Мы можем сделать так: (a + b) * с; Или же так: a + (b * c); Коллизия заключается в том, что в обеих случаях мы захватываем переменную b, которая одновременно является одним из операндов относительно обеих операторов. Понятное дело, что конечный результат будет разниться в зависимости от выбранного пути. Вот тут и вступают в дело приоритеты операторов! Мы прекрасно знаем, что операция умножения будет выполняться первой, ведь для нас это весьма очевидно и мы даже не обращаем на это внимания (хотя всё это изначально прописано в "мозгах" самой JVM). А теперь хотелось бы перейти к более сложному примеру, с которого всё и начиналось. Почему же инкремент/декремент записанный в постфиксной нотации имеет приоритет над префиксной формой той же записи? Настолько я понял, то в обеих случаях можно привести лишь по одному примеру, где может возникнуть явная неопределённость. Приведём эти примеры: a---b; a+++b; Такая форма записи допускает всего 2 возможных варианта без ошибки времени компиляции. Здесь можно выделить 2 оператора, один из которых будет бинарным оператором (сложения или вычитания), а также унарный инкремент/декремент (о форме записи пока ничего не говорим). Здесь уже возникает немного другого рода неопределённость, которая отличается от первого рассмотренного нами случая. Если в первом случае возникла коллизия на уровне совместно используемого операнда, то здесь возникает двусмысленность в вопросе унарного оператора, который может быть постфиксным/префиксным инкрементом/декрементом, как для переменной a, так и для переменной b. Имеем следующие варианты раскрытия скобок: (a--)-b; a-(--b); (a++)+b; a+(++b); Положение бинарного оператора имеет принципиальное значение только в первом случае, так как вычитание является антикоммутативной операций, а сложение, напротив, коммутативной. Хотелось бы добавить, что такой приоритет появился совсем не случайно. Чтобы понять это, необходимо обратиться к JLS: The longest possible translation is used at each step, even if the result does not ultimately make a correct program while another lexical translation would. There is one exception: if lexical translation occurs in a type context (§4.11) and the input stream has two or more consecutive > characters that are followed by a non-> character, then each > character must be translated to the token for the numerical comparison operator >. The input characters a--b are tokenized (§3.5) as a, --, b, which is not part of any grammatically correct program, even though the tokenization a, -, -, b could be part of a grammatically correct program. Without the rule for > characters, two consecutive > brackets in a type such as List> would be tokenized as the signed right shift operator >>, while three consecutive > brackets in a type such as List>> would be tokenized as the unsigned right shift operator >>>. Worse, the tokenization of four or more consecutive > brackets in a type such as List>>> would be ambiguous, as various combinations of >, >>, and >>> tokens could represent the >>>> characters. Как мы можем понять, на каждом этапе используется наиболее длинная трансляция, даже если она приводит к некорректной программе. Скорее всего именно по этой причине постфиксная форма унарных операторов имеет некоторый приоритет над префиксной, это вписывается в общую логику лексической транляции, тем более других примеров в которых могла бы возникнуть коллизия между такого рода операторами придумать не то чтобы трудно, а скорее всего невозможно (во всяком случае, лично я не вижу других возможных вариантов написания корректной программы, где можно было бы смоделировать данную ситуацию). Напоследок можно рассмотреть пример с логическими операторами. boolean bool = a ^ b & c | d; где a, b, c, d — переменные логического типа. Чтобы понять, какая операция будет выполняться первой, было бы неплохо заглянуть вот сюда. Зная приоритет каждого из операторов находим правильное решение: boolean bool = ((a ^ (b & c)) | d); Что хотелось бы сказать напоследок? В глобальной сети можно найти очень много ложных таблиц, которые могут ввести в заблуждение. Авторы добавляют туда всякую "отсебятину", по типу разделителей, оператора new, операторы приведения типов и прочую чепуху. Считаю, что есть единственный источник к которому можно обратиться в данном вопросе, это официальный сайт корпорации Oracle. И да, соглашусь с тем, что лучше использовать обычные скобки для коррекции приоритетов! :) Благодарю всех за внимание! ;)

Почему 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; // не скомпилируется

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

Причина ограничения использования формы оператора “System.out.print(ln)”?

#java #print #операторы


Почему компилятор позволяет использовать в коде:  

System.out.println("");
System.out.print("");
System.out.println();, 


Но не позволяет использовать:

System.out.print(); ?
    


Ответы

Ответ 1



System.out.println(""); System.out.println(); Эти два оператора можно использовать в качестве перевода каретки. System.out.print(""); Этот оператор можно использовать, поскольку один из вариантов переменной на вход - String, согласитесь, глупо было бы делать проверку на string.equals(""). Тем не менее, действительно, этот оператор в таком виде ничего не делает. А в этом методе System.out.print(); Просто нет смысла- он ничего не делает, поэтому его и не стали создавать

Ответ 2



Метода с такой сигнатурой нет в PrintStream. System.out.println(); переносит на новую строку, System.out.print(); являлся бы просто пустым оператором. Вы уверены, что он необходим?

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

Оператор is и кортежи

#python #python_3x #операторы #tuple


Возник вопрос, почему происходит именно так:

>>> (1, 2) is (1, 2)
False
>>> () is ()
True


Ведь, кортеж - это неизменяемый тип данных, и два одинаковых кортежа должны быть
одним объектом в оперативной памяти.

Так например:

>>> 1 is 1
True
>>> 'str' is 'str'
True

    


Ответы

Ответ 1



Самое главное: Два одинаковых объекта не обязаны иметь одинаковый адрес в памяти, но это возможно. В Python 3.7, благодаря новой оптимизации AST, иногда так и происходит, в версиях ниже - нет. Полагаться на это поведение ни в коем случае нельзя. Теперь подробнее... Для начала стоит сказать, что это зависит от конкретной версии Python (точнее CPython). Python 3.7: print((1, 2) is (1, 2)) # True Python 3.6: print((1, 2) is (1, 2)) # False Для того, чтобы понять некоторые различия в версиях, предлагаю дизассемблировать этот код. Начнём со старой версии (3.6): print(dis.dis('(1, 2) is (1, 2)')) 1 0 LOAD_CONST 2 ((1, 2)) 2 LOAD_CONST 3 ((1, 2)) 4 COMPARE_OP 8 (is) 6 RETURN_VALUE 4-ая колонка с числами (2, 3, 8) - это opargs (аргументы операции). Обратите внимание на то, что команда одна и та же (LOAD_CONST), данные одни и те же ((1, 2)), но эти данные являются разными аргументами при операции. Следовательно, они будут разными объектами в памяти (но я немного сомневаюсь насчёт этого утверждения) Давайте взглянем на список констант скомпилированного кода, чтобы точно в этом убедиться: code = compile('(1, 2) is (1, 2)', '', 'single') print(code.co_consts) (1, 2, None, (1, 2), (1, 2)) Кортеж (1, 2) встречается 2 раза, то есть это разные объекты. Новая версия (3.7): print(dis.dis('(1, 2) is (1, 2)')) 1 0 LOAD_CONST 0 ((1, 2)) 2 LOAD_CONST 0 ((1, 2)) 4 COMPARE_OP 8 (is) 6 RETURN_VALUE Уже на этом этапе видно, что наш кортеж является одним и тем же аргументом в операции. Подтвердим наши догадки, взглянув на константы: code = compile('(1, 2) is (1, 2)', '', 'single') print(code.co_consts) ((1, 2), None) Как видим, объект один, поэтому и адрес в памяти один. Результат в новой версии отличается из-за перемещения некоторых оптимизаций байт-кода в новый оптимизатор AST. Выдержка из нововведений: Constant folding has been moved from the peephole optimizer to the new AST optimizer, which is able perform optimizations more consistently. (Contributed by Eugene Toder and INADA Naoki in bpo-29469 and bpo-11549.) Поэтому скомпилированный код очень сильно отличается от интерактивного сеанса: Интерактивный режим: >>> x = (1, 2) >>> y = (1, 2) >>> print(x is y) False >>> print((3, 4) is (3, 4)) True Скомпилированная версия: x = (1, 2) y = (1, 2) print(x is y) # True print((3, 4) is (3, 4)) # True Подобные оптимизации касаются также других объектов. Также в разных версиях отличается результат следующих инструкций: x, y = 257, 257 x is y -6 is -6 Давние особенности Тестирую Python 3.7 в интерактивном режиме. Сначала немного примеров: №1: >>> x = 257 >>> y = 257 >>> x is y False №2: >>> x = 256 >>> y = 256 >>> x is y True №3: >>> x = -6 >>> y = -6 >>> x is y False №4: >>> x = -5 >>> y = -5 >>> x is y True №5 >>> x = 'Loremipsumdolorsitametconsecteturadipiscingelit' >>> y = 'Loremipsumdolorsitametconsecteturadipiscingelit' >>> x is y True №6 >>> x = 'Hi!' >>> y = 'Hi!' >>> x is y False Из этих примеров видно, что Python кэширует числа в промежутке [-5; 256]. Пруф Также он кэширует простые строки. Пруф Но если запускать скомпилированный код, то в Python 3.7 все примеры вернут True. На основании примеров выше, хочу предупредить, что сравнивать любые данные оператором is ни в коем случае нельзя. Используйте для этого только оператор ==.

Ответ 2



Оператор is используется для проверки того, указывают ли сравниваемые переменные на один и тот же объект в памяти. Пустые кортежи, вероятно, указывают на NULL или же нечто подобное, потому идентичны. Два заполненных кортежа – самостоятельные объекты в памяти. Это то же самое, что объявить две переменных в C++ и сравнить их адреса. Очевидно, что результат будет разным. Вы говорили: кортеж - это неизменяемый тип данных и два одинаковых кортежа должны быть одним объектом в оперативной памяти однако в этом высказывании первое утверждение отнюдь не влечёт второе. Вернитесь к аналогии с переменными C++, приведённой выше, если причина всё ещё не понятна.

пятница, 13 декабря 2019 г.

Не работает унарный минус на INT_MIN

#c #операторы


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

Собственно сабж:

#include 
#include 

int main (void)
{
    int a = INT_MIN;

    printf("a = %d\n", a);
    a = -a;
    printf("a = %d\n", a);

    return 0;
}


Результат работы (компилятор - Clang):

a = -2147483648
a = -2147483648


В чем проблема?

P.S. стандарт c99 никакие ограничения (вроде) не накладывает:


  6.5.3.3 Unary arithmetic operators
  ...
  The result of the unary - operator is the negative of its (promoted) operand. The
integer
  promotions are performed on the operand, and the result has the
  promoted type.

    


Ответы

Ответ 1



-INT_MIN значение ведёт к неопределённому поведению (undefined behavior) в Си из-за переполнения (integer overflow). Это не только теоретическая проблема, к примеру: #define abs(x) ((x) > 0 ? (x) : -(x)) int foo(int x){ return abs(x) >= 0; } может быть скомпилировано в: mov eax, 1 ret то есть даже для INT_MIN возвращается 1 вне зависимости от того как int представлен, то есть даже на реализациях где дополнительный код используется, где -n может вернуть INT_MIN. Переполнение возникает так как INT_MIN по модулю математически может быть больше INT_MAX. n == -n получается в дополнительном коде, так как унарный минус можно реализовать как: обратить биты, прибавить один: -n == ~n+1. К примеру, для 8-битного числа: n=-128 1000 0000 ~n 0111 1111 ~n+1 1000 0000

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

Что означает “оператор auto?”

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


template 
struct A {
    operator auto() {
        return T{};
    }
};


Что означает "оператор auto"? В чём его отличие от explicit operator auto()? Когда
они применяются?
    


Ответы

Ответ 1



// 1 struct widget { operator int() { return 10; } }; // 2 struct widget2 { operator auto() { return std::string{"hello"}; } }; // 3 struct widget3 { explicit operator int() { return 10; } }; // пример из вопроса template struct A { // метод для преобразования объектов типа A // в тип T (выведено) operator auto() { return T{}; } }; int main() { widget w; int wint = w; // ok widget2 w2; std::string wstr = w2; // ok widget3 w3; int w3int = w3; // error int w3inte = static_cast(w3); // ok A as; std::string asstr = as; // ok int asint = as; // error } Определенные пользователем преобразования. Есть возможность определить метод преобразования пользовательского типа в другой тип. Вывод типа с помощью auto. Как к любой другой функции, auto применим и к преобразователям. Explicit-спецификатор. (для функций преобразования) указывает копилятору, что функцию преобразования не нужно рассматривать при неявных приведениях типа. Начиная со стандарта C++14, ключевое слово auto можно использовать для типа возвращаемого значения функции, указывая, что тип возвращаемого значения должен быть выведен. Вместе с этим, auto может быть использован в операторах преобразования (также начиная с C++14). Существует одно исключение, auto нельзя использовать в шаблонных операторах преобразования. template operator auto() { return 1; } - не будет компилироваться. В примере из вопроса operator auto() { return T{}; }. Оператор преобразования не является шаблонным, требования к выведению типов соблюдены (все return-ы имеют одинаковый тип) - можно выводить тип. Тип будет выведен как T и данный оператор преобразования можно рассматривать как operator T{ return T{}; }