Что это за странный синтаксис двоеточия (": ") в конструкторе?
Недавно я видел пример, подобный следующему:
#include <iostream>
class Foo {
public:
int bar;
Foo(int num): bar(num) {};
};
int main(void) {
std::cout << Foo(42).bar << std::endl;
return 0;
}
Что это странно : bar(num)
имею в виду? Кажется, что-то инициализирует переменную-член, но я никогда раньше не видел этот синтаксис. Это похоже на вызов функции / конструктора, но для int
? Не имеет смысла для меня. Возможно, кто-то мог бы просветить меня. И, кстати, есть ли какие-либо другие особенности эзотерического языка, подобные этой, которые вы никогда не найдете в обычной книге C++?
14 ответов
Это список инициализации члена. Вы должны найти информацию об этом в любой хорошей книге C++.
В большинстве случаев вам следует инициализировать все объекты-члены в списке инициализации членов (однако, обратите внимание на исключения, перечисленные в конце записи FAQ).
Вывод из часто задаваемых вопросов заключается в том, что
При прочих равных условиях ваш код будет работать быстрее, если вы будете использовать списки инициализации, а не присваивания.
Foo(int num): bar(num)
Эта конструкция называется списком инициализирующих элементов в C++.
Проще говоря, это инициализирует ваш член bar
к стоимости num
,
В чем разница между инициализацией и назначением внутри конструктора?
Инициализация участника:
Foo(int num): bar(num) {};
Назначение участника:
Foo(int num)
{
bar = num;
}
Существует значительная разница между инициализацией элемента с использованием списка инициализатора элемента и присвоением ему значения внутри тела конструктора.
Когда вы инициализируете поля через список инициализатора элемента, конструкторы будут вызываться один раз, а объект будет создан и инициализирован за одну операцию.
Если вы используете присваивание, то поля будут сначала инициализированы конструкторами по умолчанию, а затем переназначены (с помощью оператора присваивания) фактическими значениями.
Как вы видите, в последнем есть дополнительные издержки создания и назначения, которые могут быть значительными для пользовательских классов.
Cost of Member Initialization = Object Construction
Cost of Member Assignment = Object Construction + Assignment
Последнее на самом деле эквивалентно:
Foo(int num) : bar() {bar = num;}
В то время как первое эквивалентно просто:
Foo(int num): bar(num){}
Для встроенных (ваш пример кода) или членов класса POD практических затрат нет.
Когда вы должны использовать список инициализатора участника?
Вы будете вынуждены использовать список инициализаторов участников, если:
- В вашем классе есть референтный член
- В вашем классе есть нестатический член const или
- У вашего ученика нет конструктора по умолчанию или
- Для инициализации членов базового класса или
- Когда имя параметра конструктора совпадает с элементом данных (на самом деле это НЕ ДОЛЖНО)
Пример кода:
class MyClass
{
public:
//Reference member, has to be Initialized in Member Initializer List
int &i;
int b;
//Non static const member, must be Initialized in Member Initializer List
const int k;
//Constructor’s parameter name b is same as class data member
//Other way is to use this->b to refer to data member
MyClass(int a, int b, int c):i(a),b(b),k(c)
{
//Without Member Initializer
//this->b = b;
}
};
class MyClass2:public MyClass
{
public:
int p;
int q;
MyClass2(int x,int y,int z,int l,int m):MyClass(x,y,z),p(l),q(m)
{
}
};
int main()
{
int x = 10;
int y = 20;
int z = 30;
MyClass obj(x,y,z);
int l = 40;
int m = 50;
MyClass2 obj2(x,y,z,l,m);
return 0;
}
MyClass2
не имеет конструктора по умолчанию, поэтому его нужно инициализировать через список инициализаторов членов.- Базовый класс
MyClass
не имеет конструктора по умолчанию, поэтому для инициализации его члена нужно будет использовать Member Initializer List.
Важные моменты, на которые следует обратить внимание при использовании списков инициализаторов элементов:
Переменные-члены класса всегда инициализируются в том порядке, в котором они объявлены в классе.
Они не инициализируются в том порядке, в котором они указаны в списке инициализаторов элементов.
Короче говоря, список инициализации члена не определяет порядок инициализации.
Учитывая вышесказанное, всегда рекомендуется поддерживать тот же порядок членов для инициализации членов, что и порядок, в котором они объявлены в определении класса. Это связано с тем, что компиляторы не предупреждают, если два порядка различаются, но относительно новый пользователь может запутать список инициализаторов членов как порядок инициализации и написать некоторый код, зависящий от этого.
Это инициализация конструктора. Это правильный способ инициализации членов в конструкторе класса, так как он предотвращает вызов конструктора по умолчанию.
Рассмотрим эти два примера:
// Example 1
Foo(Bar b)
{
bar = b;
}
// Example 2
Foo(Bar b)
: bar(b)
{
}
В примере 1:
Bar bar(); // default constructor
bar = b; // assignment
В примере 2:
Bar bar(b) // copy constructor
Все дело в эффективности.
Это называется списком инициализации. Это альтернативный способ инициализации членов класса. Есть преимущества в использовании этого, вместо простого присваивания новых значений членам в теле конструктора, но если у вас есть члены класса, которые являются константами или ссылками, они должны быть инициализированы.
Это неясно, это синтаксис списка инициализации C++
В основном, в вашем случае, x
будет инициализирован с _x
, y
с _y
, z
с _z
,
Другой уже объяснил вам, что синтаксис, который вы наблюдаете, называется "список инициализатора конструктора". Этот синтаксис позволяет вам инициализировать пользовательские базовые подобъекты и дочерние подобъекты класса (вместо того, чтобы позволить им инициализировать по умолчанию или оставаться неинициализированными).
Я просто хочу отметить, что синтаксис, который, как вы сказали, "выглядит как вызов конструктора", не обязательно является вызовом конструктора. На языке C++ ()
синтаксис - это только одна стандартная форма синтаксиса инициализации. Это интерпретируется по-разному для разных типов. Для типов классов с определяемым пользователем конструктором это означает одно (это действительно вызов конструктора), для типов классов без определяемого пользователем конструктора это означает другое (так называемая инициализация значения) для пустых ()
) и для не классовых типов это снова означает что-то другое (так как не классовые типы не имеют конструкторов).
В вашем случае элемент данных имеет тип int
, int
это не тип класса, поэтому он не имеет конструктора. Для типа int
этот синтаксис означает просто "инициализировать bar
со значением num
"И это все. Это делается просто так, напрямую, без участия конструкторов, так как, еще раз, int
это не тип класса, поэтому он не может иметь никаких конструкторов.
Я не знаю, как ты мог пропустить это, это довольно просто. Это синтаксис для инициализации переменных-членов или конструкторов базовых классов. Это работает для простых старых типов данных, а также объектов класса.
Это список инициализации. Он инициализирует члены до запуска тела конструктора. Рассматривать
class Foo {
public:
string str;
Foo(string &p)
{
str = p;
};
};
против
class Foo {
public:
string str;
Foo(string &p): str(p) {};
};
В первом примере str будет инициализирован конструктором без аргументов
string();
перед телом конструктора Foo. Внутри конструктора foo
string& operator=( const string& s );
будет вызываться на 'str', как вы делаете str = p;
Когда во втором примере str будет инициализирован напрямую путем вызова его конструктора
string( const string& s );
с "р" в качестве аргумента.
Вы правы, это действительно способ инициализации переменных-членов. Я не уверен, что в этом есть много пользы, кроме четкого выражения, что это инициализация. Наличие "bar=num" внутри кода может быть гораздо проще перемещено, удалено или неверно истолковано.
Есть еще одна "выгода"
если тип переменной-члена не поддерживает нулевую инициализацию или если это ссылка (которая не может быть инициализирована нулем), у вас нет другого выбора, кроме как предоставить список инициализации
Это список инициализации для конструктора. Вместо построения по умолчанию x
, y
а также z
и затем присваивая им значения, полученные в параметрах, эти члены будут инициализированы с этими значениями сразу. Это может показаться не очень полезным для float
с, но это может быть довольно экономно с пользовательскими классами, которые являются дорогостоящими для создания.
Пока не упоминается в этом потоке: начиная с C++11, список инициализатора членов может использовать инициализацию списка (иначе говоря, "равномерная инициализация", "фигурная инициализация"):
Foo(int num): bar{num} {}
которая имеет ту же семантику, что и инициализация списка в других контекстах.
Хотя это старое обсуждение, я не смог найти упоминания о конструкторе делегирования , который использует странный символ «:» следующим образом.
class Foo
{
public:
Foo(char x, int y)
{}
Foo(int y) : Foo('a', y)
{}
};
Он просто делегирует
Foo(y)
в `` `` Foo ('a', y). Так что
Foo foo(15); //=> foo('a', 15)
При определении делегирующего конструктора у вас не может быть никаких членов в списке инициализаторов, кроме целевого конструктора.
Что такое синтаксис двоеточия () в конструкторе класса и что означает передача целого числа вstd::vector<>
конструктор делать?
Я хотел бы объяснить приведенный ниже пример из этого повторяющегося вопроса :
class UnionFind {
public:
UnionFind(int sz) : root(sz) {
for (int i = 0; i < sz; i++) {
root[i] = i;
}
}
private:
vector<int> root;
};
int main() {
UnionFind uf(10);
}
двоеточие (:
) означает начало «списка инициализации» или «списка инициализаторов» , который инициализирует каждую переменную значением в скобках. Это как если бы вы вызывали конструктор для каждой переменной, при этом значение в скобках передавалось конструктору этой переменной.
Итак, инициализирует переменную с помощью , что похоже на выполнение . Однако выполнение этого таким образом позволяет перейти кUnionFind
конструктор класса.
Инициализация вектора с таким размером — это конструктор №3 здесь ( https://en.cppreference.com/w/cpp/container/vector/vector ):
// Constructor (3) as shown at
// https://en.cppreference.com/w/cpp/container/vector/vector
explicit vector( size_type count,
const T& value = T(),
const Allocator& alloc = Allocator());
Он создает (в приведенном выше примере) количество элементов в векторе, каждый со значениемT()
, что в данном случае означает, чтоvector<int>
. выглядит как вызов функции, но в основном является целочисленным конструктором по умолчанию для нулевого значения (0
). Это называется . Думайте об этом как о вызове «целочисленного конструктора», если целые числа были объектами и имели конструкторы. Смотрите также мой вопрос здесь: Что такое вызов
char()
как функция в С++?
Итак, подытожим:: root(sz)
в конструкторе похоже на построениеvector<int> root(sz);
, что создаетsz
количество элементов вroot
вектор, каждый с начальным значениемint()
, который является синтаксисом для " "инициализация значения"инициализации значения"int
до нуля.
Отметим также, чтоcount
параметр, переданный конструктору № 3, показанному выше, действительно должен бытьsize_type
, что можно записать какstd::vector::size_type
, и обычноsize_t
(см. раздел «Типы участников» здесь ). Так что лучше поменятьint sz
кsize_t sz
вместо. Поэтому измените эту строку конструктора:UnionFind(int sz) : root(sz) {
вместо этого:UnionFind(size_t sz) : root(sz) {
.