C++
Функции нестатического пользователя
Поиск…
Синтаксис
// Вызов:
- variable.member_function ();
- variable_pointer-> member_function ();
// Определение:
- ret_type class_name :: member_function () cv-qualifiers {
- тело;
- }
- ret_type class_name :: member_function () cv-qualifiers {
// Прототип:
- class class_name {
- virt-specifier ret_type member_function () cv-qualifiers virt-specifier-seq;
- // virt-specifier: «virtual», если применимо.
- // cv-qualifiers: «const» и / или «volatile», если применимо.
- // virt-specifier-seq: «переопределить» и / или «final», если применимо.
- }
- class class_name {
замечания
Не - static
функция - член является class
/ struct
/ union
функция, которая вызывается на конкретном случае, и действует на указанный экземпляр. В отличие от static
функций-членов, он не может быть вызван без указания экземпляра.
Информацию о классах, структурах и объединениях см. В родительской теме .
Нестатические функции-члены
class
или struct
могут иметь функции-члены, а также переменные-члены. Эти функции имеют синтаксис, в основном похожий на автономные функции, и могут быть определены либо внутри, либо вне определения класса; если определено вне определения класса, имя функции имеет префикс имени класса и оператора области ( ::
:).
class CL {
public:
void definedInside() {}
void definedOutside();
};
void CL::definedOutside() {}
Эти функции вызываются в экземпляре (или ссылке на экземпляр) класса с помощью оператора точки ( .
) Или указателя на экземпляр с помощью оператора стрелки ( ->
), и каждый вызов привязывается к экземпляру, был вызван; когда функция-член вызывается в экземпляре, он имеет доступ ко всем полям этого экземпляра (через this
указатель ), но может обращаться только к полям других экземпляров, если эти экземпляры поставляются в качестве параметров.
struct ST {
ST(const std::string& ss = "Wolf", int ii = 359) : s(ss), i(ii) { }
int get_i() const { return i; }
bool compare_i(const ST& other) const { return (i == other.i); }
private:
std::string s;
int i;
};
ST st1;
ST st2("Species", 8472);
int i = st1.get_i(); // Can access st1.i, but not st2.i.
bool b = st1.compare_i(st2); // Can access st1 & st2.
Этим функциям разрешен доступ к переменным-членам и / или другим функциям-членам, независимо от модификаторов доступа к переменной или функции. Они также могут быть записаны вне порядка, доступа к переменным-членам и / или вызовам функций-членов, объявленных перед ними, поскольку все определение класса должно анализироваться до того, как компилятор сможет начать компилировать класс.
class Access {
public:
Access(int i_ = 8088, int j_ = 8086, int k_ = 6502) : i(i_), j(j_), k(k_) {}
int i;
int get_k() const { return k; }
bool private_no_more() const { return i_be_private(); }
protected:
int j;
int get_i() const { return i; }
private:
int k;
int get_j() const { return j; }
bool i_be_private() const { return ((i > j) && (k < j)); }
};
Инкапсуляция
Обычное использование функций-членов заключается в инкапсуляции, используя аксессуар (обычно известный как геттер) и мутатор (обычно известный как сеттер) вместо прямого доступа к полям.
class Encapsulator {
int encapsulated;
public:
int get_encapsulated() const { return encapsulated; }
void set_encapsulated(int e) { encapsulated = e; }
void some_func() {
do_something_with(encapsulated);
}
};
Внутри класса encapsulated
могут быть свободно доступны любой нестатической функцией-членом; вне класса доступ к нему регулируется функциями-членами, используя get_encapsulated()
для его чтения и set_encapsulated()
для его изменения. Это предотвращает непреднамеренные изменения переменной, поскольку для ее чтения и записи используются отдельные функции. [Есть много дискуссий о том, предоставляют ли геттеры и сеттеры или прерывают инкапсуляцию, с хорошими аргументами для обеих претензий; такие жаркие споры выходят за рамки этого примера.]
Имя Скрытие и импорт
Когда базовый класс предоставляет набор перегруженных функций, а производный класс добавляет еще одну перегрузку в набор, это скрывает все перегрузки, предоставляемые базовым классом.
struct HiddenBase {
void f(int) { std::cout << "int" << std::endl; }
void f(bool) { std::cout << "bool" << std::endl; }
void f(std::string) { std::cout << "std::string" << std::endl; }
};
struct HidingDerived : HiddenBase {
void f(float) { std::cout << "float" << std::endl; }
};
// ...
HiddenBase hb;
HidingDerived hd;
std::string s;
hb.f(1); // Output: int
hb.f(true); // Output: bool
hb.f(s); // Output: std::string;
hd.f(1.f); // Output: float
hd.f(3); // Output: float
hd.f(true); // Output: float
hd.f(s); // Error: Can't convert from std::string to float.
Это связано с правилами разрешения имен: во время поиска имени, как только правильное имя найдено, мы перестаем искать, даже если мы явно не нашли правильную версию объекта с таким именем (например, с hd.f(s)
); из-за этого перегрузка функции в производном классе предотвращает поиск имени из обнаружения перегрузок в базовом классе. Чтобы избежать этого, использование-объявления можно использовать для «импорта» имен из базового класса в производный класс, чтобы они были доступны во время поиска имени.
struct HidingDerived : HiddenBase {
// All members named HiddenBase::f shall be considered members of HidingDerived for lookup.
using HiddenBase::f;
void f(float) { std::cout << "float" << std::endl; }
};
// ...
HidingDerived hd;
hd.f(1.f); // Output: float
hd.f(3); // Output: int
hd.f(true); // Output: bool
hd.f(s); // Output: std::string
Если производный класс импортирует имена с использованием объявления-объявления, но также объявляет функции с той же сигнатурой, что и функции в базовом классе, функции базового класса молча будут переопределены или скрыты.
struct NamesHidden {
virtual void hide_me() {}
virtual void hide_me(float) {}
void hide_me(int) {}
void hide_me(bool) {}
};
struct NameHider : NamesHidden {
using NamesHidden::hide_me;
void hide_me() {} // Overrides NamesHidden::hide_me().
void hide_me(int) {} // Hides NamesHidden::hide_me(int).
};
Использование-декларация также может использоваться для изменения модификаторов доступа, если импортированный объект был public
или protected
в базовом классе.
struct ProMem {
protected:
void func() {}
};
struct BecomesPub : ProMem {
using ProMem::func;
};
// ...
ProMem pm;
BecomesPub bp;
pm.func(); // Error: protected.
bp.func(); // Good.
Аналогично, если мы явно хотим вызвать функцию-член из определенного класса в иерархии наследования, мы можем квалифицировать имя функции при вызове функции, указав этот класс по имени.
struct One {
virtual void f() { std::cout << "One." << std::endl; }
};
struct Two : One {
void f() override {
One::f(); // this->One::f();
std::cout << "Two." << std::endl;
}
};
struct Three : Two {
void f() override {
Two::f(); // this->Two::f();
std::cout << "Three." << std::endl;
}
};
// ...
Three t;
t.f(); // Normal syntax.
t.Two::f(); // Calls version of f() defined in Two.
t.One::f(); // Calls version of f() defined in One.
Виртуальные функции участника
Функции-члены также могут быть объявлены virtual
. В этом случае, если вы вызываете указатель или ссылаетесь на экземпляр, они не будут доступны напрямую; скорее, они будут искать функцию в таблице виртуальных функций (список указателей на vftable
-функции для виртуальных функций, более широко известный как vtable
или vftable
), и использовать это для вызова версии, соответствующей динамике экземпляра (фактического) типа. Если функция вызывается напрямую, из переменной класса не выполняется поиск.
struct Base {
virtual void func() { std::cout << "In Base." << std::endl; }
};
struct Derived : Base {
void func() override { std::cout << "In Derived." << std::endl; }
};
void slicer(Base x) { x.func(); }
// ...
Base b;
Derived d;
Base *pb = &b, *pd = &d; // Pointers.
Base &rb = b, &rd = d; // References.
b.func(); // Output: In Base.
d.func(); // Output: In Derived.
pb->func(); // Output: In Base.
pd->func(); // Output: In Derived.
rb.func(); // Output: In Base.
rd.func(); // Output: In Derived.
slicer(b); // Output: In Base.
slicer(d); // Output: In Base.
Обратите внимание, что в то время как pd
Base*
, а rd
- Base&
, вызывающий func()
для любого из двух вызовов Derived::func()
вместо Base::func()
; это связано с тем, что vtable
для Derived
обновляет запись Base::func()
вместо этого указывает на Derived::func()
. И наоборот, обратите внимание, что передача экземпляра slicer()
всегда приводит к slicer()
Base::func()
, даже когда переданный экземпляр является Derived
; это происходит из-за того, что называется срезом данных , где передача экземпляра Derived
в Base
параметр по значению делает часть экземпляра Derived
которая не является недоступной для Base
экземпляра.
Когда функция-член определена как виртуальная, все функции-члены производного класса с одной и той же сигнатурой переопределяют ее, независимо от того, указана ли функция переопределения как virtual
или нет. Это может сделать производные классы более трудными для программистов, чтобы разобрать, однако, поскольку нет никаких указаний относительно того, какие функции (ы) являются virtual
.
struct B {
virtual void f() {}
};
struct D : B {
void f() {} // Implicitly virtual, overrides B::f.
// You'd have to check B to know that, though.
};
Обратите внимание, однако, что производная функция только переопределяет базовую функцию, если их подписи совпадают; даже если производная функция явно объявлена virtual
, она вместо этого создаст новую виртуальную функцию, если сигнатуры будут несогласованными.
struct BadB {
virtual void f() {}
};
struct BadD : BadB {
virtual void f(int i) {} // Does NOT override BadB::f.
};
На C ++ 11, намерение переопределения может быть явно с контекстно-зависимым ключевым словом override
. Это говорит компилятору, что программист ожидает, что он переопределит функцию базового класса, что заставляет компилятор пропускать ошибку, если она не отменяет ничего.
struct CPP11B {
virtual void f() {}
};
struct CPP11D : CPP11B {
void f() override {}
void f(int i) override {} // Error: Doesn't actually override anything.
};
Это также дает возможность говорить программистам, что функция является виртуальной, а также объявляется, по крайней мере, в одном базовом классе, что упрощает анализ сложных классов.
Когда функция объявляется virtual
и определяется вне определения класса, virtual
спецификатор должен быть включен в объявление функции и не повторяться в определении.
Это также справедливо для override
.
struct VB {
virtual void f(); // "virtual" goes here.
void g();
};
/* virtual */ void VB::f() {} // Not here.
virtual void VB::g() {} // Error.
Если базовый класс перегружает virtual
функцию, то только перегрузки, явно указанные как virtual
будут виртуальными.
struct BOverload {
virtual void func() {}
void func(int) {}
};
struct DOverload : BOverload {
void func() override {}
void func(int) {}
};
// ...
BOverload* bo = new DOverload;
bo->func(); // Calls DOverload::func().
bo->func(1); // Calls BOverload::func(int).
Для получения дополнительной информации см. Соответствующую тему .
Const Correctness
Одним из основных применений для this
CV-классификаторов является const
корректности . Это практика гарантирования того, что только доступ, который должен изменить объект, способен модифицировать объект и что любая (членная или нечлена) функция, которая не нуждается в изменении объекта, не имеет права на запись к этому объекта (прямо или косвенно). Это предотвращает непреднамеренные изменения, делая код менее ошибочным. Он также позволяет любой функции, которая не нуждается в изменении состояния, чтобы иметь возможность принимать объект const
или не- const
, без необходимости переписывать или перегружать функцию.
const
по своей природе начинается снизу вверх: любая функция-член класса, которая не нуждается в изменении состояния, объявляется как const
, поэтому ее можно вызвать в экземплярах const
. Это, в свою очередь, позволяет объявлять const
-by-reference, когда они не нуждаются в модификации, что позволяет функциям принимать либо const
либо не const
объекты без жалобы, а const
-ness может распространяться наружу в этом манера. Из-за этого геттеры часто являются const
, как и любые другие функции, которые не нуждаются в изменении логического состояния.
class ConstIncorrect {
Field fld;
public:
ConstIncorrect(const Field& f) : fld(f) {} // Modifies.
const Field& get_field() { return fld; } // Doesn't modify; should be const.
void set_field(const Field& f) { fld = f; } // Modifies.
void do_something(int i) { // Modifies.
fld.insert_value(i);
}
void do_nothing() { } // Doesn't modify; should be const.
};
class ConstCorrect {
Field fld;
public:
ConstCorrect(const Field& f) : fld(f) {} // Not const: Modifies.
const Field& get_field() const { return fld; } // const: Doesn't modify.
void set_field(const Field& f) { fld = f; } // Not const: Modifies.
void do_something(int i) { // Not const: Modifies.
fld.insert_value(i);
}
void do_nothing() const { } // const: Doesn't modify.
};
// ...
const ConstIncorrect i_cant_do_anything(make_me_a_field());
// Now, let's read it...
Field f = i_cant_do_anything.get_field();
// Error: Loses cv-qualifiers, get_field() isn't const.
i_cant_do_anything.do_nothing();
// Error: Same as above.
// Oops.
const ConstCorrect but_i_can(make_me_a_field());
// Now, let's read it...
Field f = but_i_can.get_field(); // Good.
but_i_can.do_nothing(); // Good.
Как показано в комментариях к ConstIncorrect
и ConstCorrect
, надлежащим образом функции cv-qualifying также служат документацией. Если класс const
корректен, любая функция, которая не является const
можно смело предположить, чтобы изменить состояние, и любая функция, которая является const
можно смело предположить, что она не изменяет состояние.