пятница, 29 октября 2010 г.

30. Перечислите особенности перегрузки операций как методов класса. Чем отличается перегрузка внешним образом от перегрузки как метода класса?

  • Первый параметр оператора опускается, им становится экземпляр класса (this).
  • Его можно сделать константным методом.
  • У него имеется доступ к не-public членам.
  • Оператор можно сделать виртуальным.
Этим и отличается.

среда, 27 октября 2010 г.

29. Можно ли определить новую операцию?

Нельзя. Технически это было бы несложно, но создало бы больше проблем, чем решило. Допустим, мы решили добавить операцию **, означающую в некоторых языках возведение в степень. Посмотрим, какие возникают трудности.
  • Неоднозначность. Как интерпретировать a**b? (a)**(b) или a*(*b). Заметьте, оба варианта синтаксически корректны, но имеют разный смысл. Придется создавать правила для разрешения этой ситуации.
  • Ассоциативность. Чему эквивалентно a**b**c? (a**b)**c или a**(b**c)?
  • Приоритет. Чему эквивалентно a+b**c? (a+b)**c или a+(b**c)?
Напоминаю, с точки зрения разработчика языка, проблемы не являются неразрешимыми. В некоторых языках есть такая возможность.

вторник, 26 октября 2010 г.

28. Можно ли при перегрузке изменить приоритет операции?

К счастью, нет :) Это создало бы слишком много проблем.

понедельник, 25 октября 2010 г.

27. Можно ли перегружать операции для встроенных типов данных?

Нет. Вообще, хотя бы одно из значений оператора должно являться пользовательским типом.

воскресенье, 24 октября 2010 г.

26. Какие операции нельзя перегружать? Как вы думаете, почему?

По-моему, чаще используется слово "оператор", я так и буду их называть.
Перегружать можно следующие операторы:
+ - * / % ^ & | ~ ! = < >
+= -= *= /= %= ^= &= |=
<< >> >>= <<= == != <= >=
&& || ++ -- ->* , -> [] ()
new new[] delete delete[]


А нельзя (подсмотрел у Страуструпа):
  • :: (разрешение области видимости)
  • (выбор члена)
  • .* (выбор члена через указатель на член)
  • ?: тернарный оператор
  • sizeof, typeid
У первых трех в правой части имя, а не значение. У тернарного оператора аж 3 параметра, к тому же его возвращаемое значение является l-value. Переопределять sizeof, typeid, думаю, просто нет смысла.
Также нельзя определить новые лексемы.

суббота, 23 октября 2010 г.

Размер объекта (класса). Что на него влияет.

Раз уж начались вопросы о размере объекта, то перечислим все факторы, влияющие на размер.
  1. Нестатические члены
  2. Выравнивание
  3. Наличие виртуальных методов (+размер указателя на таблицу виртуальных методов)
  4. Количество виртуально пронаследованных классов в иерархии наследования (+размер указателя на таблицу виртуальных методов каждого такого класса)
Вот и все.

25. Одинаков ли размер класса и аналогичной структуры?

Да. У структуры всего одно отличие: доступ к членам по умолчанию открыт (public)

см. также:
6. Является ли структура классом? Чем класс отличается от структуры?
10. Для чего используются ключевые слова public и private?

пятница, 22 октября 2010 г.

24. Влияют ли методы на размер объекта?

Нет. Метод класса един для всех объектов класса, и разумеется, не создается каждый раз при создании объекта.

см. также:
14. Что такое метод? Как вызывается метод?

четверг, 21 октября 2010 г.

23. Каков размер «пустого» объекта?

class A{
}
В теории не меньше одного байта. На практике ровно один байт. Не ноль потому, что, согласно спецификации, каждый объект должен иметь свой адрес. Объекты с нулевым размером запросто могли иметь один и тот же адрес.

вторник, 19 октября 2010 г.

21. Объясните принцип полиморфизма.

Неинтересный вопрос. В том смысле, что в двух словах не рассказать, а расписывать подробно не хочу. О полиморфизме и так много информации в книгах и интернете. Понравилось в Википедии: Кратко смысл полиморфизма можно выразить фразой: «Один интерфейс, множество реализаций». И от себя неплохой, как мне кажется, примерчик.
class Animal{
public:
virtual string voice() = 0;
};

class Cat: public Animal {
public:
string voice(){return "Мяу!";}
};

class Dog: public Animal {
public:
string voice(){return "Гав!";}
};

void printVoice(Animal* animal){
cout << animal->voice(); //Здесь происходит полиморфизм.
}

int main()
{
Animal* animal1 = new Cat;
Animal* animal2 = new Dog;

printVoice(animal1);
printVoice(animal2);

delete animal1;
delete animal2;
}

понедельник, 18 октября 2010 г.

20. Может ли константный метод вызываться для объектов-переменных? А обычный метод — для объектов-констант?

Проще всего посмотреть самому:
class A{
public:
void f1() {}
void f2() const {}
};

int main()
{
A a;
const A& ac = a;

a.f1();
a.f2();
ac.f1(); //ошибка!
ac.f2();
}
Вывод: нельзя вызывать неконстантные функции константных объектов. Но деструктор можно.

воскресенье, 17 октября 2010 г.

19. Зачем нужны константные методы? Чем отличается определение константного метода от обычного?

a. Для отмечания факта, что метод не изменяет члены класса. Компилятор пресечет попытки такого случайного вмешательства. Следующий вопрос посвящен вызову константных методов.
b. void A::f() const {/*...*/}

суббота, 16 октября 2010 г.

18. Что обозначается ключевым словом this?

В каждой нестатической функции-члене класса this является указателем на объект, для которого вызвана функция. Страуструп пишет, что это не совсем обычная переменная и невозможно получить ее адрес. Мне не очень понятно это ограничение. Тем более, программа, откомпилированная MinGW, прекрасно вывела этот адрес.

пятница, 15 октября 2010 г.

17. Можно в методах присваивать параметрам значения по умолчанию?

Так же, как и в обычных функциях.

Это можно сделать либо в объявлении класса:
class A { 
  int f(float c = 3.14);
}
Либо при определении функции:
int A::f(float c = 3.14);

Но не там и там одновременно.

Inline. Встроенные функции.

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

inline int plusOne(int n){
  return n+1;
}
/*...*/
int k=5;
cout << plusOne(k);
 

Компилятор заменит plusOne(k) на (k+1), подставив k вместо n в определении функции. Рассмотрим более интересный пример:

//рекурсивное вычисление факториала:
inline int fact(int n){
  return n!=1 ? n*fact(n-1) : 1;
}
/*...*/
cout << fact(3);
 

Действия компилятора с этим выражением по шагам:
  1. cout << (3!=1 ? 3*fact(3-1) : 1); //заменил вызов определением
  2. cout << 3*fact(2); //наш компилятор не дурак - упростил выражение
  3. cout << 3*(2!=1 ? 2*fact(2-1) : 1); //заменил
  4. cout << 6*fact(1); //упростил
  5. cout << 6*(1!=1 ? 1*fact(1-1) : 1); //вызов fact уже не нужен, так как условие не выполняется.
  6. cout << 6;
Теперь такое выражение:
cout << fact(100);
 

Что будет делать компилятор? Заменять вызов 100 раз? А если написать fact(1000)? А если так:
int k;
cin >> k;
cout << fact(k);
 

Опять рассмотрим по шагам:
  1. cout << (k!=1 ? k*fact(k-1) : 1); //Это уже не упростить. Ладно, заменим fact(k-1)
  2. cout << (k!=1 ? k*(((k-1)!=1 ? (k-1)*fact(k-2) : 1)) : 1); //Нда... Продолжаем?
  3. cout << (k!=1 ? k*(((k-1)!=1 ? (k-1)*(((k-2)!=1 ? (k-2)*fact(k-3) : 1)) : 1)) : 1);

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

четверг, 14 октября 2010 г.

16. Как определить метод непосредственно внутри класса? А вне класса? Чем эти определения отличаются?

a.
class A {
  int f(){return 0;}
}


b.
class A {
  int f();
}

int A::f(){return 0;}


c. Эти классы практически идентичны, но есть небольшое отличие. Определение первого класса эквивалентено:
class A {
  int f();
}

inline int A::f(){return 0;}


То есть, оно отличается от второго наличием ключевого слова inline. Оно означает, что компилятор постарается(так как это не всегда возможно) заменить вызов этой функции его определением. Рекомендуется не использовать вариант a (желательно использовать эквивалентную запись), так как смешиваются "что делает класс" и "как это делает".

среда, 13 октября 2010 г.

15. Может ли метод быть приватный?

Конечно.
Представим функцию:
void A::save(){
  if(check()){
    commit();
  }
}

Перед принятием изменений происодит проверка, все хорошо и логично. Если бы нельзя было сделать commit() приватной, то пользователь данного класса мог бы напрямую вызвать commit() без проверки, что может быть опасно.

вторник, 12 октября 2010 г.

14. Что такое метод? Как вызывается метод?

а. Функция, принадлежащая классу. Методу доступны все поля класса (которому он принадлежит).
б.
class A{
  public:
  void f(int p);
};
1. Допустим, есть объявление: A a;
a.f(5); //вызов

2. Если есть указатель на A (A* pa), то:
2а. (*pa).f(5); //вызов
2б. pa->f(5); //синтаксически более удобный и рекомендуемый вызов.

Дополнение:
При инициализации класса A, в памяти создается метод f. На низком уровне он представляет собой обычную функцию с дополнительным параметром this (указатель на объект, вызвавший функцию). Поэтому a.f(5) заменяется компилятором на что-то вроде:
A__f(&a, 5);

понедельник, 11 октября 2010 г.

13. Обязательно ли делать поля класса приватными?

Конечно нет (Обычно, если все поля класса открытые, класс объявляют с помощью struct, но это только по желанию)

воскресенье, 10 октября 2010 г.

12. Существуют ли ограничения на использование public и private в классе? А в структуре?

Нет. Эти модификаторы(а также protected) могут следовать в любом порядке, и встречаться в определении класса более одного раза. Влияние модификатора продолжается до следующего модификатора или до конца класса.

пятница, 8 октября 2010 г.

10. Для чего используются ключевые слова public и private?

public и private (а так же protected) - модификаторы доступа.
Public – доступ открыт всем, кто видит определение данного класса.
Private – доступ открыт только для самого класса и друзьям (friend) данного класса.
Следует отметить, что public, private, protected могут означать и тип наследования.

четверг, 7 октября 2010 г.

9. Что такое композиция?

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

class Car {
  Engine engine;
  Wheel wheels[4];
}


В таком случае составной объект является хозяином своих объектов-частей и ответственнен за их создание и удаление.

среда, 6 октября 2010 г.

8. Объясните принцип инкапсуляции.

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

QtCreator. Задаем опции компилятора.

В .pro файле нужно добавить один из параметров (-O2 только для примера):

QMAKE_CXXFLAGS += -O2 #опция будет включена во всех режимах компиляции.
QMAKE_CXXFLAGS_DEBUG += -O2 #только в режиме DEBUG
QMAKE_CXXFLAGS_RELEASE += -O2 #только в режиме RELEASE

Еще можно написать так:

CONFIG(debug, debug|release){
  # Debug
  QMAKE_CXXFLAGS += -Os
}else{
  # Release
  QMAKE_CXXFLAGS += -O2
}

А вообще все параметры можно посмотреть здесь

вторник, 5 октября 2010 г.

7. Какие ключевые слова в С++ обозначают класс?

Ключевые слова - это зарезервированные идентификаторы.
and and_eq asm auto bitand bitor bool break case catch char class compl const const_cast continue default delete do double dynamic_cast else enum explicit export extern false float for friend goto if inline int long mutable namespace new not not_eq operator or or_eq private protected public register reinterpret_cast return short signed sizeof static static_cast struct switch template this throw true try typedef typeid typename union unsigned using virtual void volatile wchar_t while xor xor_eq

Из них class и struct обозначают класс.

QtCreator. Не удалось сохранить сессию: /Users/%username%/AppData/Roaming/Nokia/qtcreator/default.qws

Такая ошибка появлялась у меня на Windows 7. Мой %username% - кириллический, и QtCreator, из-за проблем с кодировкой, не смог найти соответствующую папку в C:/Users. Желания заводить другого пользователя у меня не было. Пощелкав мышкой, по-кривому, но проблему решил. В QtCreator открываем: инструменты - параметры - вставка кода - основное. Видим поле "имя пользователя". У меня там крякозябры. "А-а-а", - в первый раз сказал я, и написал его нормально, по-русски. Облом, не сохраняет. Тогда я скопировал крякозябры, и создал папку с таким же именем в users. Облом. Ей еще понадобилось дать права всем на чтение и запись. Сейчас все хорошо.

понедельник, 4 октября 2010 г.

6. Является ли структура классом? Чем класс отличается от структуры?

а) Да
б)
класс обявленный как class имеет по умолчанию доступ private,
класс обявленный как struct имеет по умолчанию доступ public.

То есть, такие определения эквиваленты:

class A {
/* */
}

struct A {
private:
/* */
}


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

воскресенье, 3 октября 2010 г.

5. Как называется использование объекта одного класса в качестве поля другого класса?

Агрегация. Часто используется ее частный случай - композиция (B - составная часть A, и, в таком случае, А отвечает за создание и уничтожение B)

суббота, 2 октября 2010 г.

4. Допускается ли передавать объекты в качестве параметров, и какими способами? А возвращать как результат?

Так же, как и переменные основных типов - по ссылке(1) и по значению(2).
1. С передачей по ссылке все в принципе ясно: передали ссылку и продолжаем работать с тем же объектом как ни в чем не бывало. Но мне не очень нравится такой способ. Если в коде мы видим:

int x = 1;
f(x);

То мы не можем предугадать, как передан x и может ли измениться его значение в теле функции, так как определение может быть void f(int a) или void f(int &a). Та же неопределенность и при передаче объекта.
Отмечу, что возможности передачи по ссылке не было в C.

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

void f(A* a){/* разыменовываем: *a */}

A a;
f(&a);

При передаче объекта по значению будет передано содержимое объекта

void f(A a){/* разыменовываем: *a */}

A a;
f(a);

Будет вызван конструктор копирования.

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

пятница, 1 октября 2010 г.

3. Разрешается ли объявлять указатель на объект? А указатель на класс?

Я много писал об указателях. Ясно, что объявлять указатель на объект конечно же можно.
Вообще говоря, на физическом уровне указатель представляет собой 4 байта, хранящие некоторый адрес. Теоретически можно объявить указатель на все что угодно, что занимает место в оперативной памяти. Но класс не хранится в явном виде в памяти.

Но ради интереса можно подумать что представляет собой класс на низком уровне.

class A{
int x;
int f(){return 1};
}

Если класс не используется в программе, то компилятор, скорее всего пропустит это объявление. Но допустим мы создали объект: A a; но где лежит информация, что член x имеет тип int, и занимает 4 (на других платформах может и не 4) байта? В исходном коде. Именно оттуда компилятор берет эту информацию и генерирует соответствующий код.
А вот код функции f() действительно загружается в память. Она скорее всего будет иметь свой адрес. А может и не будет, например, если компилятор или автор решит сделать ее встроенной (inline).
Как видим, в явном виде класс не хранится.