БлогNot. Можно ли в C++... или каверзные вопросы по языку

Можно ли в C++... или каверзные вопросы по языку

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

Ответы даны сразу же по ссылкам оглавления, но лучше сначала подумать.

Вероятно, список будет пополняться и послужит хорошим дополнением к "Типичным ошибкам начинающего разработчика на C++".

Можно из этого сделать и какой-нибудь тест - на сколько вопросов вы смогли ответить или хотя бы рассуждали в правильном направлении?

Все коды проверялись в консоли Visual Studio 2015 или близких версий.

Оглавление

Вычисления, арифметика

Можно ли в C++ сравнить значения 2 переменных без использования оператора сравнения (==)?

Можно ли в C++ поменять местами значения 2 целочисленных переменных без использования третьей переменной или арифметических операций?

Можно ли в C++ сложить 2 числа, не используя операцию сложения?

Можно ли в C++ умножать или делить целое значение на степени двойки, не используя операций "*" или "/"?

Можно ли умножить на вещественное значение, например, на 3.5, без использования операций умножения и деления?

Можно ли в C++ найти модуль (абсолютное значение) целого числа без применения каких-либо разветвлений?

Можно ли в C++ найти максимум или минимум из двух целочисленных значений, не используя условий или циклов?

Можно ли в C++ найти наименьшее из трёх целых чисел, не используя операторов сравнения?

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

Можно ли в C++ выполнить обе ветви условного оператора "если-то-иначе"?

Можно ли в C++ найти знак числа без использования любого (бинарного или тернарного) условного оператора?

Можно ли в C++ напечатать натуральный ряд чисел, не используя цикла или рекурсии?

Можно ли в C++ напечатать ряд натуральных чисел 1,2,...,N, не используя ни одной точки с запятой?

Можно ли в C++ найти сумму всех цифр числа одним оператором?

Можно ли в C++ прибавить к целому числу единицу, не используя арифметических операций сложения, вычитания или инкремента и декремента?

Можно ли в C++ заполнить двумерную матрицу размерности n x m последовательно идущими числами 1,2,3,..., применяя только один цикл?

Как эффективно (без применения операции умножения) умножить целое число на 7?

Функции, namespace

Можно ли в C++ вызвать необъявленную функцию?

Можно ли в C++ одновременно использовать одноимённые локальную и глобальную переменные?

Можно ли в C++ вызвать функцию слева от знака "="?

Можно ли в C++ из пустой функции main что-нибудь вывести в консоль?

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

Классы

Можно ли в C++ получить доступ к приватным свойствам класса без использования методов-членов или функций-друзей класса?

Можно ли в C++ сделать класс, объекты которого можно будет создавать только динамически?

Можно ли в C++ сделать класс, объекты которого наоборот можно будет создавать только в стеке, но не динамически в куче?

У класса есть конструктор с одним целочисленным аргументом. Покажите три синтаксически различных способа создать экземпляр класса с помощью этого конструктора

Как запретить автоматическое преобразование типов в конструкторе (не создавать объекты способом 3 из предыдущего вопроса)?

Всегда ли при выполнении операции вида a = b, где a и b - объекты одного класса, вызывается конструктор копирования?

Почему переопределённый оператор присваивания возвращает ссылку на объект?

Можно ли без friend перегрузить бинарный оператор "в обе стороны"?

Как определить для объектов класса сложение с числом в виде Obj+i и i+Obj?

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

Как классу "выдать себя" за своего предка, то есть, использовать его виртуальный метод?

Обязательно ли указывать override и virtual при переопределении виртуального метода предка?

Как "заставить" класс-потомок определить виртуальный метод?

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

Как инициализировать члены класса в конструкторе, не обращаясь к ним из тела функции-конструктора?

Как разрешить инициализацию нескольких членов класса в виде X x{ 1, 2 } ?

Когда уместно писать код в файле .h, а в .cpp не стоит?

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

Разное

Можно ли в C++ проверить порядок байтов (big-endian/little-endian) компьютера?

Можно ли в C++ писать URL-адреса прямо в тексте программы?

Можно ли в C++ из пользовательской программы завершить работу системы?

Почему здесь выведется 2 единицы?

Ответы на вопросы
Можно ли в C++ вызвать не объявленную функцию?

Только если скомпилировать файл типа .c, но не .cpp, например, делать так было можно в "чистом" C:

#include <cstdio>

void f(); /* функция описана без аргументов! */

int main() {
 f(2); /* компилируется в файле .c, но не .cpp */
 getchar();
 return 0;
}

void f(int x) { /* реализация функции имеет аргумент */
 printf ("%d", x);
}
Можно ли в C++ одновременно использовать одноимённые локальную и глобальную переменные?

Да. Оператор области видимости ::

#include <iostream>
using namespace std;

int x = 5;  //глобальная

int main() {
 int x = 10; //локальная
 cout << "global x = " << ::x << endl;
 cout << "local x = " << x;
 cin.get(); return 0;
}
Можно ли в C++ вызвать функцию слева от знака "="?

Да, например, так:

#include <iostream>
using namespace std;

int &fun() {
 static int x; //с не-статической переменной м.б. небезопасно
 return x;
}

int main() {
 fun() = 10;
 cout << fun() << endl; //10
 cin.get(); return 0;
}
Можно ли в C++ получить доступ к приватным свойствам класса без использования методов-членов или функций-друзей класса?

Ага :) Указатели-то на что?

#include <iostream>
using namespace std;

class Test {
private:
 int data;
public:
 Test() { data = 0; }
 int getData() { return data; }
};

int main() {
 Test t;
 int *ptr = (int*)&t;
 *ptr = 10; //косвенно пишем в свойство data... приём опасный, но работает не хуже лома
 cout << t.getData(); //приватное свойство было изменено. Вот и вся инкапсуляция :)
 cin.get(); return 0;
}
Можно ли в C++ сделать класс, объекты которого можно будет создавать только динамически?

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

А для удаления динамических объектов кучи можно предусмотреть отдельную функцию-друг класса. Это будет платой за "защиту от стека".

#include <iostream>
using namespace std;

class Test {
private:
 ~Test() { cout << "Destroying Object\n"; }
public:
 Test() { cout << "Object Created\n"; }
 friend void destructTest (Test *);
};

void destructTest(Test* ptr) {
 delete ptr;
 cout << "Object Destroyed\n";
}

int main() {
 //Test t1; //теперь это вызовет ошибку компиляции

 Test *t2 = new Test(); //Всё хорошо
 destructTest (t2);
 
 cin.get(); return 0;
}
Можно ли в C++ сделать класс, объекты которого наоборот можно будет создавать только в стеке, но не динамически в куче?

Да. В противоположность предыдущему пункту, можно определить в классе приватный оператор new.

#include <iostream>
using namespace std;

class Test {
 void* operator new (size_t size){};
 int x;
public:
 Test(int x) { this->x = x; }
 void show() { cout << x << endl; }
 ~Test() { }
};

int main() {
 //Test* t1= new Test(1); //А теперь нельзя вот так
 
 Test t2(2); t2.show(); //Всё работает
 cin.get(); return 0;
}
Можно ли в C++ из пустой функции main что-нибудь вывести в консоль?

Да. Скажем, создав глобальный объект класса, который выводит что-то в консоль из конструктора.

#include <iostream>
using namespace std;

class Test {
public:
 Test() { cout << "Hello, world!"; }
} test;

void main() {}

Также класс можно заменить структурой или просто глобальной переменной:

#include <cstdio>

int var = printf ("Hello, world!");
void main() {}
Можно ли в C++ напечатать натуральный ряд чисел, не используя цикла или рекурсии?

Да. Можно использовать шаблоны класса и статические функции.

#include <iostream>
using namespace std;

template <int N> class Printer {
public:
 static void print()  {
  Printer<N - 1>::print();  //Это - не рекурсия, а вызов статического метода!
  cout << N << endl;
 }
};

template <> class Printer<1> {
public:
 static void print()  {
  cout << 1 << endl;
 }
};

int main() {
 const int N = 1000;
 Printer<N>::print();
 cin.get(); return 0;
}

...или просто массив объектов класса и статическую переменную.

#include<iostream>
using namespace std;

class Printer {
public:
 static int a;
 Printer() {
  cout << a++ << endl;
 }
};

int Printer::a = 1;

int main() {
 const int N = 1000;
 Printer obj[N];
 cin.get(); return 0;
}
Можно ли в C++ найти сумму всех цифр числа одним оператором?

Да, если это оператор цикла for

#include <iostream>
using namespace std;

int main() {
 int n = 6789, sum;
 for (sum = 0; n > 0; sum += n % 10, n /= 10); //один оператор
 cout << sum;
 cin.get(); return 0;
}

Можно ли в C++ писать URL-адреса прямо в тексте программы?

Да. Полную ссылку, которая начинается с названия протокола, можно вставить в текст программы без каких-либо символов-ограничителей.

#include <iostream>
using namespace std;

int main() {
http://www.disney.com

cout << "Hello, world!";
cin.get(); return 0;
}

Просто http: воспринимается как метка, а всё, что за символами // - как комментарий :)

Можно ли в C++ поменять местами значения 2 целочисленных переменных без использования третьей переменной или арифметических операций?

Да. Есть ещё побитовые операции и операция следования (запятая).

#include <iostream>
using namespace std;

int main() {
 int x = -5, y = 10;
 (x ^= y), (y ^= x), (x ^= y);
 cout << x << " " << y;
 cin.get(); return 0;
}
Можно ли в C++ из пользовательской программы завершить работу системы?

Да, поскольку доступна функция system. Наверное, не буду выполнять этот код :)

#include <cstdlib>
using namespace std;

int main() {
 char *cmd = "c:\\windows\\system32\\shutdown /i"; //В кавычках - команда для Windows
 //или "shutdown -P now" для Linux
 system(cmd);
 return 0;
}

На самом деле, современные ОС попросят подтвердить действие.

Можно ли в C++ сравнить значения 2 переменных без использования оператора сравнения (==)?

Да. Побитовое исключающее "или".

#include <iostream>
using namespace std;

int main() {
 int x = 10, y = 10;
 cout << x << (!(x ^ y) ? "==" : "!=") << y;
 cin.get(); return 0;
}
Можно ли в C++ напечатать ряд натуральных чисел 1,2,...,N, не используя ни одной точки с запятой?

Рекурсивный вызов функции main позволяет решить эту задачу, например, так:

#include <iostream>
using namespace std;
int N = 10;

int main() {
 static int x = 1;
 if (cout << x << " " && x++ < N && main()) {} //точки с запятой при решении задачи нет!
 //cin.get(); //иначе придётся нажить Enter 10 раз, рекурсия же :)
 return 0;
}
Можно ли в C++ найти максимум или минимум из двух целочисленных значений, не используя условий или циклов?

Да.

#include <iostream>
using namespace std;

int main() {
 int a = -10, b = 10;
 cout << endl << "Max=" << ((a + b) + abs(a - b)) / 2;
 cout << endl << "Min=" << ((a + b) - abs(a - b)) / 2;
 cin.get(); return 0;
}
Можно ли в C++ сложить 2 числа, не используя операцию сложения?

Да. Есть же вычитание :)

#include <iostream>
using namespace std;

int main() {
 double a = -3.5;
 double b = 2.8;
 double sum = -(-a - b);
 cout << endl << "Sum=" << sum;
 cin.get(); return 0;
}
Можно ли в C++ выполнить обе ветви условного оператора "если-то-иначе"?

Да.

#include <iostream>
using namespace std;

int main() {
 if (!(cout << " 1 ")) { 
  cout << " 1 "; //Проверьте в отладчике, это тоже выполняется
 }
 else { cout << " 2 "; }
 cin.get(); return 0;
}
Можно ли в C++ умножать или делить целое значение на степени двойки, не используя операций "*" или "/"?

Это можно делать в любом языке, где есть побитовый сдвиг. Умножению значения x на 2, например, соответствует операция x << 1, а делению y на 4 - действие y >> 4. Не путайте операторы побитового арифметического сдвига с переопределёнными в некоторых классах (например, в классах поточного ввода-вывода) операторами вставки << и извлечения >>.

#include<iostream>
using namespace std;

int main() {
 int x = 4, y = -8;
 x = x << 1; //4*2==8
 y = y >> 2; //-8/4==-2
 cout << x << " " << y;
 cin.get(); return 0;
}
Можно ли в C++ проверить порядок байтов (big-endian/little-endian) компьютера?

Для многобайтовых величин имеет значение, в каком порядке хранятся байты числа.

Порядок байтов бывает big-endian (от старшего к младшему) и little-endian (от младшего к старшему).

Например, десятичное число 10000 равно 0x2710 в 16-ричном виде и "естественном" представлении big-endian, а в little-endian это будет 0x1027. Именно так может храниться значение на линейке процессоров x86 :)

#include <iostream>
using namespace std;

int main() {
 unsigned int n = 1;
 char *c = (char*)&n;
 if (*c) cout << "little-endian";
 else cout << "big-endian";
 cin.get(); return 0;
}
Как узнать максимальное значение для беззнакового целого типа?

Можно так, если не хочется искать константу, содержащую это значение:

#include <iostream>
using namespace std;

int main() {
 unsigned int max = 0;
 max = ~max;
 cout << endl << "unsigned int: " << max;
 unsigned long long int max2 = 0;
 max2 = ~max2;
 cout << endl << "unsigned long long int: " << max2;
 cin.get();
 return 0;
}
Можно ли в C++ найти знак числа без использования любого (бинарного или тернарного) условного оператора?

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

#include <iostream>
using namespace std;

int sign (int n) { 
 return (n>0) - (n<0); //Скобки нужны из-за приоритетов!
}

int main() {
 cout << endl << sign(3); //1
 cout << endl << sign(-1); //-1
 cout << endl << sign(0); //0
 cin.get(); return 0;
}
Можно ли в C++ заполнить двумерную матрицу размерности n x m последовательно идущими числами 1,2,3,..., применяя только один цикл?

Остаток от деления и целочисленное деление C++ (целое поделить на целое дают целое) рулят.

Программка есть вот здесь.

У класса есть конструктор с одним целочисленным аргументом. Покажите три синтаксически различных способа создать экземпляр класса с помощью этого конструктора

Поможет "редкий" способ 3 для конструктора с одним аргументом.

#include <iostream>
using namespace std;

class Class {
 int n;
 public:
  Class (int n=0) { this->n= n; }
  void show() { cout << n << endl; }
};

int main() {
 Class object1(1); //Способ 1, обычный
 object1.show();
 Class object2 = Class (2); //Способ 2, так тоже можно
 object2.show();
 Class object3 = 3; //Способ 3, для конструктора с одним аргументом можно и так
 object3.show();
 cin.get(); return 0;
}
Как запретить автоматическое преобразование типов в конкструкторе (не создавать объекты способом 3 из предыдущего вопроса)?

Для того и нужно ключевое слово explicit

#include <iostream>
using namespace std;

class Class {
 int n;
 public:
  explicit Class (int n=0) { this->n= n; }
  void show() { cout << n << endl; }
};

int main() {
 Class object1(1); //Способ 1, обычный
 object1.show();
 Class object2 = Class (2); //Способ 2, так тоже можно
 object2.show();
 cin.get(); return 0;
}
Всегда ли при выполнении операции вида a = b, где a и b - объекты одного класса, вызывается конструктор копирования?

Неа, не всегда. Конструктор копирования применяется только к инициализациям, но не к присваиваниям.

/*
 Конструктор копирования применяется только к инициализациям, но не к присваиваниям
*/
#include <iostream>
using namespace std;

class Class {
 int n;
 public:
  Class (int n=0) { this->n= n; }
  Class (Class &that) {
   cout << "Constructor called" << endl;
   this->n = that.n;
  }
  void show() { cout << n << endl; }
  Class operator = (Class &that) {
   cout << "Operator = called" << endl;
   this->n = that.n;
  }
};

int main() {
 Class a(1);
 Class b = a; //Конструктор копирования будет вызван
 a.show(); b.show();
 Class c(2);
 c = a; //Конструктор копирования не вызывается, а переопределённый оператор "=" - да
 c.show();
 cin.get(); return 0;
}
Как определить для объектов класса сложение с числом в виде Obj+i и i+Obj?

Для решения задачи можно использовать, например, пару функций-"друзей" класса.

#include <iostream>
using namespace std;

class Class {
 int n;
 public:
  Class (int n=0) { this->n= n; }
  void show() { cout << n << endl; }
  friend Class operator + (Class & obj, int n);
  friend Class operator + (int n, Class & obj);
};

Class operator + (Class &obj, int n) {
 Class temp;
 temp.n = obj.n + n;
 return temp;
}

Class operator + (int n, Class & obj) {
 return obj+n;
}

int main() {
 Class a(1),b(2);
 Class c = a + 1; 
 c.show(); //2
 Class d = 1 + b; 
 d.show(); //3
 cin.get(); return 0;
}
Как получить доступ к элементам, определенным дочерним классом через указатель на базовый класс?

Даже не знаю, почему этот вопрос здесь... обычное, в общем, приведение типов :)

#include <iostream>
#include <string>
using namespace std;

class Base {
 string data;
 public:
  Base (string data="") { this->data= data; }
  virtual void show() { cout << endl << data; }
};

class Derived : public Base {
 int n;
 public:
  Derived (string data="", int n = 0) : Base(data) { this->n = n; }
  void show () { Base::show(); cout << ", " << n; }
  int get_n () { return n; }
};

int main() {
 Base a("Base");
 Base *ptr = &a; //Указатель на базовый класс
 ptr->show();
 Derived b ("Derived",1);
 ptr = &b; //Работает и присвоение адреса объекта дочернего класса
 ptr->show();
 cout << endl << ((Derived *)ptr)->get_n();
  //Доступ к элементам, определенным дочерним классом через указатель на базовый класс
 cin.get(); return 0;
}
Как классу "выдать себя" за своего предка, то есть, использовать его виртуальный метод?

Так как атрибут virtual передается по наследству через все уровни наследования, можно просто не переопределять на нужном уровне наследования метод, объявленный в предке виртуальным (show)

#include <iostream>
using namespace std;

class Base {
 public:
  virtual void show() { cout << endl << "Base"; }
};

class Derived1 : public Base {
 public:
  void show() { cout << endl << "Derived1"; }
};

class Derived2 : public Derived1 {
};

int main() {
 Base a;     a.show(); //Base
 Derived1 b; b.show(); //Derived1
 Derived2 c; c.show(); //Derived1, так как show не определён в Derived2
 cin.get(); return 0;
}
Как "заставить" класс-потомок определить виртуальный метод?

Определить в предке чисто виртуальную функцию (show). Иначе это называется "сделать класс абстрактным"

#include <iostream>
using namespace std;

class Base {
 public:
  virtual void show() =0;
};

class Derived1 : public Base {
 public:
  void show() { cout << endl << "Derived1"; }
};

class Derived2 : public Base {
};

int main() {
 Derived1 b; b.show(); //Derived1
// Derived2 c; //ошибка компиляции!
//Base a; // Теперь также нельзя создать объект класса Base
 Base *ptr = &b; ptr->show(); //но можно указатель на него
 cin.get(); return 0;
}
Как заставить шаблон функции с двумя аргументами обрабатывать по-другому один скалярный тип данных, чем другие сочетания типов?

Вариант 1. Написать для этого типа альтернативный шаблон функции

#include <iostream>
using namespace std;

template <class T1, class T2>
T1 divs (T1 a, T2 b) { return a/(T1)b; }

template <class T>
T divs (T a, T b) { return a/b; }

int main() {
 double a = 3.5, b = 2.;
 int c = 3, d = 2;
 cout << divs(a,b) << endl; //1.75
 cout << divs(a,c) << endl; //1.16667
 cout << divs((double)c,d) << endl; //1.5
 cout << divs(c,d) << endl; //1
 cin.get(); return 0;
}

Вариант 2. Написать для этого типа явно заданную функцию

#include <iostream>
using namespace std;

template <class T>
T divs (T a, T b) { return a/b; }

double divs (int a, int b) { return ((double)a)/b; }

int main() {
 double a = 3.5, b = 2.;
 cout << divs(a,b) << endl; //1.75
 int c = 3, d = 2;
 cout << divs(c,d) << endl; //1.5
 cin.get(); return 0;
}
Как инициализировать члены класса в конструкторе, не обращаясь к ним из тела функции-конструктора?

Список инициализации в конструкторе...

#include <iostream>
using namespace std;

class Class {
 int n,m;
 public:
  Class (int n, int m) : n(n), m(m) {}
  void show() { cout << n << " " << m << endl; }
};

int main() {
 Class obj (1,2);
 obj.show(); //1 2
 cin.get(); return 0;
}
Почему здесь выведется 2 единицы?
#include <iostream>
using namespace std;

int main() {
 int a = 0;
 decltype((a)) b = a;
 b++;
 cout << a << b; //11
 cin.get();
 return 0;
}

Если пропустить связанный с decltype нюанс, кажется, что должны вывестись 0 и 1 - b получила значение a, равного нулю, потом b увеличили на единицу.

На самом деле, примерно вот так (не уверен, что моя интерпретация на 100% верна):

1. decltype(var), когда var — это объявленная переменная (например в функции или как член класса). В этом случае decltype(var) будет иметь в точности тот тип, с которым объявлена переменная.

2. decltype(expr), expr — выражение. В этом случае типом decltype(expr) будет тип, которое могло бы вернуть это выражение, с той оговоркой, что decltype(expr) будет иметь тип T& (const T&), если expr возвращает lvalue, T, если expr возвращает rvalue типа Т (const T) и T&& (const T&&), если expr возвращает xvalue (rvalue reference).

То есть, в данном случае b будет ссылкой на a.

Штука на самом деле кошмарная:

int i;
decltype(i); // int
decltype(i + 1); // int
decltype((i)); // int&
decltype(i = 4); //int&
const int foo();
decltype(foo()) ;// int
int&& foo1();
decltype(foo1()) ;// int&&
Почему переопределённый оператор присваивания возвращает ссылку на объект?

Потому что должны работать операторы не только вида

x = y;

но и

if ((x = y)) {

и особенно

x = y = z;

(присваивание "по цепочке").

Вот пример, кроме того, ещё раз показываем, когда вызывается конструктор копирования, а когда оператор "="

#include <iostream>
using namespace std;

class Fraction {
 int n, d; //числитель и знаменатель
 int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } //Наибольший общий делитель
 
public:
 Fraction(int n = 1, int d = 1) : n(n / gcd(n, d)), d(d / gcd(n, d)) { } //Конструктор сразу сокращает дробь
 int num() const { return n; }
 int den() const { return d; }
 Fraction & operator *= (const Fraction& rhs) { //Домножение дроби на дробь
  int new_n = n * rhs.n / gcd(n * rhs.n, d * rhs.d);
  d = d * rhs.d / gcd(n * rhs.n, d * rhs.d);
  n = new_n;
  return *this;
 }
 Fraction (Fraction& rhs) { //Конструктор копирования создает новый объект копированием
  n = rhs.n; d = rhs.d;
 } 
 Fraction& operator = (const Fraction& rhs); //Прототип функции для "=", работает при инициализации
};

ostream & operator << (ostream & out, const Fraction & f) { //Вывод дроби в ostream
 return out << f.num() << '/' << f.den();
}
bool operator == (const Fraction & lhs, const Fraction & rhs) { //Сравнение дробей
 Fraction _lhs(lhs.num(), lhs.den());
 Fraction _rhs(rhs.num(), rhs.den()); // 1/2 == 4/8 !
 return _lhs.num() == _rhs.num() && _lhs.den() == _rhs.den();
}
bool operator != (const Fraction& lhs, const Fraction& rhs) {
 return !(lhs == rhs);
}
Fraction operator * (Fraction lhs, const Fraction & rhs) { //Умножение дробей
 return lhs *= rhs;
}

Fraction & Fraction::operator = (const Fraction & rhs) { //Присваивание дробей
 int gcd = this->gcd(rhs.n, rhs.d);
 n = rhs.n / gcd;
 d = rhs.d / gcd;
 return *this;
}

int main() {
 Fraction f1(4, 8), f2(1, 2), f3(10, 2);
 cout << f1 << " * " << f2 << " = " << f1 * f2 << endl // 1/4
  << f2 << " * " << f3 << " = " << f2 * f3 << endl // 5/2
  << 2 << " * " << f1 << " = " << 2 * f1 << endl; // 1/1
 cout << (f1==f2 ? "f1==f2" : "f1!=f2") << endl; // f1==f2
 Fraction f4 = f2; //Конструктор копирования
 cout << "f4=" << f4 << endl; // 1/2
 Fraction f5;
 f5 = f4 = f3; //Оператор "=" работает по цепочке!
 cout << "f3=" << f3 << endl // 5/1
  << "f4=" << f4 << endl // 5/1
  << "f5=" << f5 << endl; // 5/1
	cin.get(); return 0;
}

По сути, если переопределённый "=" не будет возвращать ссылку на объект, то компилятор будет запускать конструктор копирования и операция будет соответствовать не присваиванию, а инициализации. А вот когда мы возвращаем ссылку на объект, тогда конструктор копирования не запускается и получается операция присваивания.

Не забываем также, что операторы =, (), [], -> должны быть функциями-членами класса.

Как разрешить инициализацию нескольких членов класса в виде X x{ 1, 2 } ?

Например, оставив класс тривиальным.

Наличие конструктора по умолчанию с пустым телом автоматически делает класс нетривиальным, отсюда вытекает то, что тип становится не POD и нельзя использовать агрегатную инициализацию.

При определении конструктора с ключевым словом = default тривиальность класса сохраняется, если она была до этого. Это равносильно отсутствию явного упоминания конструктора в определении класса.

Если конструктор по умолчанию определен как = default вне определения класса, всё равно будет считаться, что конструктор предоставлен пользователем, и это тоже делает класс нетривиальным.

Пример кода:

#include <iostream>

class X {
 public:
 //X() {}
 X() = default;
 int prop1, prop2;
};

int main() {
 X x{ 1, 2 }; // ошибка, если раскомментарить дефолтный конструктор и убрать X() = default;
}
Когда уместно писать код в файле .h, а в .cpp не стоит?

Когда пишем шаблонный класс.

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

Проще говоря, если используется шаблон функции/класса, указывайте реализацию в том же файле, что и объявление.

Вот этот код синтаксически вполне правилен.

Файл main.cpp
#include "myclass.h"
using namespace std;

int main() {
 myclass <int> i(3); i.show();
 myclass <char> c('c'); c.show();
 return 0;
}
Файл myclass.h
#include <iostream>

template <class T> class myclass {
 T bar;
public:
 myclass (T);
 T foo();
 void show();
};
Файл myclass.cpp
#include "myclass.h"

template <class T> myclass <T>::myclass(T bar) {
 this->bar = bar;
}

template <class T> T myclass <T>::foo() {
 return this->bar;
}

template <class T> void myclass <T>::show() {
 std::cout << this->bar << std::endl;
}

Но в Studio получим пресловутую "error LNK2019", а в QT "undefined reference".

Если убрать myclass.cpp, а myclass.h превратить в

#include <iostream>

template <class T> class myclass {
 T bar;
public:
 myclass (T bar) {
  this->bar = bar;
 }
 T foo() {
  return this->bar;
 }
 void show() {
  std::cout << this->bar << std::endl;
 }
};

то всё везде работает.

Можно ли в C++ прибавить к целому числу единицу, не используя арифметических операций сложения, вычитания или инкремента и декремента?

Побитовые операции никто не отменял.

#include <iostream>
using namespace std;
int main() {
 int x = 12;
 int y = (-(~x));
 cout << y;
 cin.get(); return 0;
}
Можно ли умножить на вещественное значение, например, на 3.5, без использования операций умножения и деления?

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

#include <iostream>
using namespace std;

double multiplyWith3Point5(int x) { //умножение на 3.5
 return (double)(x << 1) + x + (double)(x >> 1);
}

int main() {
 cout << multiplyWith3Point5(4);
 cin.get(); return 0;
}
Можно ли не вызывать конструктор родительского класса при создании экземпляра дочернего класса?

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

#include <iostream>
using namespace std;

class Dad {
 int A;
public:
 Dad(int A = 0) {
  this->A = A;
  cout << endl << "Dad at " << this;
 }
 int getA() { return A; }
};

class Mom {
 int A;
public:
 Mom(int A = 0) {
  this->A = A;
  cout << endl << "Mom at " << this;
 }
 int getA() { return A; }
};

class Child : public Dad, public Mom {
 int A; //как не "клонировать" папу с мамой - есть ли способ?
public:
 Child(Dad* D, Mom* M) : Dad(D->getA()), Mom(M->getA()) {
  A = D->getA() + M->getA();
  cout << endl << "Child at " << this;
 }
 int getA() { return A; }
 void show() {
  cout << endl << Dad::getA() << "+" << Mom::getA() << "=" << getA();
 }
};

int main() {
 Dad D(2); Mom M(3);
 Child *C = new Child(&D, &M); //Будут вызваны конструкторы и Dad, и Mom
 C->show();
 delete C;
 cin.get();
 return 0;
}

Важно, что для Child не создается отдельный экземпляр Dad, но Child содержит в себе унаследованные от Dad свойства. Обратите внимание на адреса this, печатаемые конструкторами:

Dad at 003BF8C0
Mom at 003BF8B4
Dad at 00780780
Mom at 00780784
Child at 00780780
2+3=5

Теперь попытаемся "избежать" вызова родительских конструкторов, дублируя в Child нужные свойства (что само по себе некрасиво и избыточно!). Но конструкторы родителей всё равно будут вызываться.

#include <iostream>
using namespace std;

class Dad {
 int A;
public:
 Dad(int A = 0) {
  this->A = A;
  cout << endl << "Dad at " << this;
 }
 int getA() { return A; }
};

class Mom {
 int A;
public:
 Mom(int A = 0) {
  this->A = A;
  cout << endl << "Mom at " << this;
 }
 int getA() { return A; }
};

class Child : public Dad, public Mom { //...прямо вот здесь!
 int dA, mA, A;                        //^
                                       //Пытаемся просто продублировать нужные свойства...
                                       //но конструкторы родителей всё равно будут вызываться
public:
 Child(int dA, int mA) {
  this->dA = dA; this->mA = mA; A = dA + mA;
  cout << endl << "Child at " << this;
 }
 int getA() { return A; }
 void show() {
  cout << endl << dA << "+" << mA << "=" << getA();
 }
};

int main() {
 Dad D(2); Mom M(3);
 Child* C = new Child(D.getA(), M.getA());
 C->show();
 delete C;
 cin.get();
 return 0;
}

Связанный вопрос - а что будет, если конструктор по умолчанию не определён в родительском классе?

#include <iostream>
using namespace std;

class Dad {
 int A;
public:
 Dad (int A) { 
  /* Если дать аргументу A значение по умолчанию, конструктор сможет "работать"
  и конструктором по умолчанию */
  this->A = A;
  cout << endl << "Dad at " << this;
 }
 int getA() { return A; }
};

class Child : public Dad {
 int A;
public:
 int getA() { return A; }
 void show() {
  cout << endl << Dad::getA() << ", " << getA();
 }
};

int main() {
 Dad D(2); 
 Child C; 
 /* Для C вызовется конструктор по умолчанию. 
 Он вызовет конструктор по умолчанию родительского класса (который явно не задан). 
 В результате возникнет ошибка C2280
 https://docs.microsoft.com/ru-ru/cpp/error-messages/compiler-errors-1/compiler-error-c2280?view=msvc-160
 */
 C.show();
 cin.get();
 return 0;
}

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

 Dad (int A = 0) { 

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

 public:
  Child() { this->A = 0; }

Итак, при создании экземпляров потомков вызываются конструкторы предков, в том порядке, в котором они перечислены в списке базовых для данного классов.

Обязательно ли указывать override и virtual при переопределении виртуального метода предка?

Ключевое слово virtual указывает, что дочерний класс может переопределить метод. Это совсем не означает, что в дочернем классе будет override. Но он может быть.

В дочернем классе к переопределённым методам также часто повторно раз добавляют virtual к методу, чтобы указать, что его потомки тоже могут переопределить его.

Что с override, что без него метод дочернего класса перекроет метод базового. Но написав override, мы явно это указываем. Полезно, например, если вы с кодом работаете не один.

#include <iostream>
using namespace std;

class Parent {
public:
 virtual void a() { cout << endl << "A"; }
 void b() { cout << endl << "B"; }
};
class Child : public Parent {
public:
 void a() { cout << endl << "CA"; }
 void b() { cout << endl << "CB"; }
};

int main() {
 Parent P; 
 P.a(); //A
 P.b(); //B
 Child C; 
 C.a(); //CA
 C.b(); //CB, пока разницы нет, c virtual или без него
 Parent *P2 = new Child();
 P2->a(); //CA, вызовется виртуальный метод потомка
 P2->b(); //B, то есть, вызовется метод родителя класса, не виртуальный!
 Child *C2 = (Child *) new Parent(); //сделали явное приведение типа
 C2->a(); //A, вызовется метод предка, что с void a() override в потомке, что без него
 C2->b(); //CB, как как b() - не virtual, вызовется метод потомка
 cin.get(); return 0;
}

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

Компилятор не выдает ошибки при использовании этого кода.

/* Задумано как пример случайного поведения наследования в коде */
#include <iostream>
#include <iomanip>
#include <string>
#include <cmath>
using namespace std;

class BaseClass {
public:
 virtual string funcA() { return "A"; };
 virtual string funcB() const { return "B"; };
 virtual string funcC(int = 0) { return "C"; };
 string  funcD() { return "D"; };
};

class DerivedClass : public BaseClass {
public:
 virtual string funcA() { return "DA"; }; //работает как подразумевается
 virtual string funcB() { return "DB"; };
 // DerivedClass::funcB() не const, так что он не перекрывает
 // BaseClass::funcB() const и является новым методом
 virtual string funcC(double = 0.0) { return "DC"; };
 // DerivedClass::funcC(double) имеет другой
 // тип параметра, чем BaseClass::funcC(int), так что
 // DerivedClass::funcC(double) тоже новый метод
};

int main() {
 BaseClass B;
 cout << endl << B.funcA();
 cout << endl << B.funcB();
 cout << endl << B.funcC();
 DerivedClass D;
 cout << endl << D.funcA();
 cout << endl << D.funcB();
 cout << endl << D.funcC(); 
  //До этого места всё выполнится нормально
 BaseClass *B2 = new DerivedClass();
 try {
 cout << endl << B2->funcA(); //
 }
 catch (...) { cout << endl << "Error in B2->funcA()"; }
 try {
  cout << endl << B2->funcB(); //
 }
 catch (...) { cout << endl << "Error in B2->funcB()"; }
 try {
  cout << endl << B2->funcC(); //
 }
 catch (...) { cout << endl << "Error in B2->funcC()"; }
 DerivedClass* D2 = (DerivedClass *)new BaseClass();
 try {
  cout << endl << D2->funcA();
 }
 catch (...) { cout << endl << "Error in D2->funcA()"; }
 try {
  cout << endl << D2->funcB();
 }
 catch (...) { cout << endl << "Error in D2->funcB()"; }
 try {
  cout << endl << D2->funcC();
 }
 catch (...) { cout << endl << "Error in D2->funcC()"; }
	cin.get();
 return 0;
}
Можно ли без friend перегрузить бинарный оператор "в обе стороны"?

...то есть, чтобы работало, например, не только C * 2, но и 2 * C для класса C.

Вот типичная реализация с friend:

#include <iostream>
using namespace std;
 
class Class {
 double prop;
public:
 Class (double _prop = 0.) { prop = _prop; }
 Class & operator * (double src) { //C * 2
  Class *dest = new Class ();
  dest->prop = this->prop * src;
  return *dest;
 }
 friend Class & operator * (double src, Class &second) { //2 * C
  return second * src;
 }
 friend ostream& operator << (ostream& s, Class &src) {
  s << endl << src.prop; return s;
 };
};
 
int main() { 
 Class A(5); cout << A; //5
 Class B = A * 2; cout << B; //10
 Class C = 3 * A; cout << C; //15
 cin.get(); return 0;
}

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

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

Но такую функцию ("2 * С") нельзя сделать членом класса, так как тогда слева от операции должен стоять экземпляр класса.

Вот пример реализации:

#include <iostream>
using namespace std;

class Class {
 double prop;
public:
 Class(double _prop = 0.) { prop = _prop; }
 double getProp() { return prop; }
 Class operator * (double src) { //C * 2
  Class dest(this->prop * src);
  return dest;
 }
 friend ostream& operator << (ostream& s, Class& src) {
  s << endl << src.prop; return s;
 };
};

Class operator * (double src, Class& second) { //2 * C
 return Class(src * second.getProp());
}

int main() {
 Class A(5); cout << A; //5
 Class B = A * 2; cout << B; //10
 Class C = 3 * A; cout << C; //15
 cin.get(); return 0;
}

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

Class *dest = new Class ();
//...
return *dest;

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

Переменная стека как раз удалится по выходе из своей области видимости, а переменную кучи, созданную через new, Вам, при правильном подходе, придётся удалять вручную через delete.

Вот набросок небольшого класса по теме.

#include <iostream>
#include <iomanip>
#include <string>
using namespace std;

class Point { //Просто точка в трёхмерном пространстве
 string name; //Свойство только для вывода, ставится отдельно
 double x, y, z; //Координаты точки
public:
 Point(double _x = 0., double _y = 0., double _z = 0.) : x(_x), y(_y), z(_z) {};
 //Основной конструктор
 Point(const Point &src) { //Конструктор копирования
  x = src.x; y = src.y; z = src.z;
 }
 Point & operator = (const Point &src) { //Оператор "присвоить"
  if (this != &src) { //Самоприсваивание - излишество
   x = src.x; y = src.y; z = src.z; 
  }
  return *this;
 }
 Point operator + (Point src) {
  //Бинарное сложение точек с получением новой точки
  Point dest (*this);
  dest.x += src.x; dest.y += src.y; dest.z += src.z;
  return dest;
 }
 Point operator - (Point src) {
  //Бинарное вычитание точек с получением новой точки
  Point dest (*this);
  dest.x -= src.x; dest.y -= src.y; dest.z -= src.z;
  return dest;
 }
 Point operator * (double src) { //Бинарное умножение точки на число справа (P * 2)
  Point dest(*this);
  dest *= src;
  return dest;
 }
 Point operator + (double src) { //Бинарное сложение точки и числа справа (P + 2)
  Point dest (*this);
  dest += src;
  return dest;
 }
 Point operator - (double src) { //Бинарное вычитание точки и числа справа (P - 2)
  Point dest (*this);
  dest -= src;
  return dest;
 }
 Point & operator += (Point& src) { //Добавление к точке координат другой точки
  x += src.x; y += src.y; z += src.z;
  return *this;
 };
 Point & operator -= (Point& src) { //Вычитание из точки координат другой точки
  x -= src.x; y -= src.y; z -= src.z;
  return *this;
 };
 Point & operator += (double src) { //Сдвиг координат точки на указанное значение
  x += src; y += src; z += src;
  return *this;
 };
 Point & operator -= (double src) { //Сдвиг координат точки на указанное значение
  x -= src; y -= src; z -= src;
  return *this;
 };
 void operator += (string _name) { //Установка названия точки для операторов вывода
  name = _name;
 }
 Point operator - () { //Смена знака у всех проекций
  Point dest (-x, -y, -z); return dest;
 }
 Point & operator *= (double src) { //Умножение координат точки на число
  x *= src; y *= src; z *= src; return *this;
 }
 friend ostream& operator << (ostream& s, Point& p) { //Оператор-друг для вывода точки в консоль
  s << endl << (p.name.length() == 0 ? "" : p.name + "= ") <<
   "(" << fixed << setprecision(2) << p.x << "," <<
   fixed << setprecision(2) << p.y << "," <<
   fixed << setprecision(2) << p.z << ")";
  return s;
 }
};

Point operator * (double src, Point& op2) { //Бинарное умножение точки на число слева (2 * P)
 Point dest(op2);
 dest *= src;
 return dest;
}

Point operator + (double src, Point& op2) { //Бинарное сложение точки и числа слева (2 + P)
 Point dest(op2);
 dest += src;
 return dest;
};

Point operator - (double src, Point& op2) { //Бинарное вычитание точки и числа слева (2 - P)
 Point dest (src, src, src);
 dest -= op2;
 return dest;
}

int main() {
 Point a(1, 1, 1); a += "A";
 Point b(1, 2, 3); b += "B";
 a += b;
 cout << a; //(2,3,4)
 a -= b;
 cout << a; //(1,1,1)
 cout << b;  //(1,2,3)
 Point d = a * 2 + 2 * b; d += "D";
 cout << d; //(4,6,8)
 d = b - -a;
 cout << d; //(2,3,4)
 Point e = a + 1; e += "E"; cout << e; //(2,2,2)
 Point f = 1 + a; f += "F"; cout << f; //(2,2,2)
 Point g = 1 - a; g += "G"; cout << g; //(0,0,0)
 Point h = a - 1 + 2; h += "H"; cout << h; //(2,2,2)

 cin.get();
 return 0;
}
Можно ли в C++ найти модуль (абсолютное значение) целого числа без применения каких-либо разветвлений?

Помогут, как это часто бывает, побитовые операции.

#include <iostream>
#include <climits>
using namespace std;

unsigned int getAbs(int n) {
 int const mask = n >> (sizeof(int) * CHAR_BIT - 1);
 return ((n + mask) ^ mask);
}

int main() {
 cout << getAbs(0); //0
 cout << endl << getAbs(-10); //10
 cout << endl << getAbs(10); //10
 cin.get();
 return 0;
}
Можно ли в C++ найти наименьшее из трёх целых чисел, не используя операторов сравнения?

Если числа положительные, можно так:

int smallest(int x, int y, int z) {
 int c = 0;
 while (x && y && z) {
  x--;
  y--;
  z--;
  c++;
 }
 return c;
}

Если все числа ненулевые, можно и красивей, например, так:

int smallest(int x, int y, int z) {
 return !(y / x) ? (!(y / z) ? y : z) : (!(x / z) ? x : z);
}

Наконец, с любыми целыми должен сработать такой способ:

#include <iostream>
#include <climits>
using namespace std;

int min(int x, int y) {
 return y + ((x - y) & ((x - y) >> (sizeof(int) * CHAR_BIT - 1)));
}

int smallest(int x, int y, int z) {
 return min(x, min(y, z));
}

int main() {
 cout << smallest(0,8,-4); //-4
 cout << endl << smallest(-3, 0, 2); //-3
 cout << endl << smallest(3, 12, 0); //0
 cin.get();
 return 0;
}
Когда уместней использовать в аргументах функции ссылку, а когда указатель?

То есть,

#include <iostream>

template <typename T> void swap(T* a, T* b) {
 T c = *a; *a = *b; *b = c;
}

int main() {
 int a = 3, b = 5;
 swap (&a, &b);
 std::cout << a << ", " << b;
	return 0;
}

или же

#include <iostream>

template <typename T> void swap(T& a, T& b) {
 T c = a; a = b; b = c;
}

int main() {
 int a = 3, b = 5;
 swap (a, b);
 std::cout << a << ", " << b;
 return 0;
}

следует предпочесть?

Разумеется, сказать однозначно, что какой-то способ "лучше", а какой-то "хуже", нельзя. Они могут быть только более или менее уместны.

Уместно, например, использовать указатели, если предполагается, что можно передавать nullptr.

Уместно использовать ссылки, если предполагается гарантия того, что объект точно существует.

Как эффективно (без применения операции умножения) умножить целое число на 7?

Вычислить значение (n<<3) - n

06.03.2018, 17:06 [3240 просмотров]


теги: c++ список программирование тест