#c #c 14
Вопрос:
Я не могу понять вывод этого кода. Я ожидаю 10
, что меня напечатают, но результат есть 85
. Может кто-нибудь объяснить, что происходит?
#include <iostream>
using namespace std;
class A
{
public:
int x=3;
A(int a) : x(a) {}
};
class B: public A
{
public:
int y = 10;
B() : A(y) {}
};
int main()
{
B b;
cout << b.x << endl;
return 0;
}
Но, изменив его на:
class B: public A
{
public:
int y = 10;
B(int s) : A(s) {}
};
int main()
{
B b(4);
cout << b.x << endl;
return 0;
}
Он работает так, как ожидалось (печать 4).
Комментарии:
1. Порядок инициализации не такой, как вы ожидаете.
2. предупреждения-ваш друг: предупреждение: ‘*<неизвестно>.B::y’ используется неинициализированным в этой функции-Wuninitialized<неизвестно>
3. @user1810087 я не получил никакого предупреждения, когда запускал код на терминале
4. @Someprogrammerdude спасибо за ссылку! Это все четко объясняло.
5. @spab обратите внимание на флаги » — Wall-Wextra -педантичный`. Я думаю, что большинство разработчиков согласятся включить предупреждения как можно более экстремальные и отключать их только в случае необходимости. Для VS это будет /W4.
Ответ №1:
Это называется «неопределенное поведение».
Сначала создаются базовые классы, прежде чем будут созданы члены класса.
В этом случае вы передаете содержимое неинициализированного члена класса конструктору базового класса.
Тот факт, что у вас есть инструкция инициализации для члена класса, которая отображается в предыдущей строке .cpp
файла, не означает, что это порядок инициализации. C не всегда работает таким образом. Вы передаете неинициализированный y
конструктор базового класса, а затем, когда базовый класс возвращает, создается подкласс, устанавливающий y
значение, для которого он инициализирован.
Комментарии:
1. Спасибо за ответ. Но я не уверен, что это неопределенное поведение. Ответ @Someprogrammerdude прояснил это для меня. Я принимаю этот ответ.
2. @spab Я не уверен, что вы имеете в виду — ни один из людей, которые опубликовали ответы, не назван Someprogrammerdude.
3. Вы сами наблюдали неопределенное поведение: бессмысленный результат.
4. @DavidZ 1 — й комментарий к вопросу от Someprogrammerdude
5. @SamVarshavchik конечно. Но документация, направленная каким-то программистом, дала понимание на более высоком уровне. Некоторые части вашего ответа показались мне расплывчатыми/вводящими в заблуждение: 1) «Базовые классы создаются первыми, до создания членов класса» — возможно, вы имели в виду «члены класса инициализируются» 2) «Когда базовый класс возвращает, подкласс создается» — это явно не указывает, когда члены класса инициализируются. Ответ какой-то программы полностью прояснил мой вопрос. Спасибо!
Ответ №2:
Из-за правил порядка инициализации в первом случае вы вызываете конструктор() до того, как y было присвоено значение 10, поэтому значение y не определено и зависит от текущего значения этих байтов sizeof(int) в стеке. Поэтому вы инициализируете x этим неопределенным полуслучайным значением и печатаете его позже. Во втором случае вы вызываете B(int) с s = 4, и он успешно инициализирует x с помощью 4.
Ответ №3:
Как следует из ссылки в комментарии @Someprogrammerdude:
Порядок инициализаторов элементов в списке не имеет значения: фактический порядок инициализации выглядит следующим образом:
- Если конструктор предназначен для наиболее производного класса, виртуальные базы инициализируются в том порядке, в котором они отображаются при первом обходе деклараций базового класса слева направо (слева направо относится к появлению в списках базовых спецификаторов).
- Затем прямые базы инициализируются в порядке слева направо, как они отображаются в списке базовых спецификаторов этого класса
- Затем нестатические элементы данных инициализируются в порядке объявления в определении класса.
- Наконец, выполняется тело конструктора