重载
潜在二义性: 如果我们为一个类定义了转化为目标类型的类类型转化(operator type() const
) 又定义了对应类型的算术类型重载时,会有二义性
class Base {
public:
int v;
Base(int i) : v(i) {}
operator int() const {
return v;
}
Base &operator+(const Base &b) {
v += b.v;
return *this;
}
};
int main() {
// 调用了初始构造函数
Base a = 3;
Base c = 4;
// 重载了+号
a + c;
// error,有二义性:
// 1 将a转化为int,然后与int相加
// 2 将3隐式转化为Base然后两者相加
a + 3;
}
我们可以在构造函数前加explicit。禁止隐式转化为Base类,就解决了二义性, a + 3 将为int类型(a转化为int)
动态绑定
通过虚函数机制可以实现动态绑定,当使用引用或者指针调用时,函数在执行时会选择适合的版本执行。
class Base {
public:
virtual void show() const {
printf("Base, %d\n", -1);
}
};
class Ba : public Base {
public:
virtual void show() const override {
printf("Ba, %d\n", 1);
}
};
void show(Base &base) {
base.show();
}
int main() {
Base base;
Ba ba;
show(ba);
show(base);
}
/*
Ba, 1
Base, -1
*/
基类和派生类
类定义
#ifndef UNTITLED_QUOTE_H
#define UNTITLED_QUOTE_H
#include<string>
class Quote {
private:
std::string bookNo;
protected:
double price = 0;
public:
Quote() = default;
Quote(const std::string &s, double p) : bookNo(s), price(p) {}
virtual double net_price(std::size_t n) const { return n * price; }
virtual ~Quote() = default;
inline static void sticOfQuote() { printf("static of Quote\n"); }
};
class Bulk_quote : public Quote {
public:
virtual double net_price(std::size_t n) const override {
return n > min_qty ? n * (1 - discount) * price : n * price;
}
Bulk_quote() = default;
// 在派生类中调用基类的构造函数,先构造基类,然后再初始化派生类成员,然后再执行函数体
Bulk_quote(const std::string bookno, double pri, std::size_t n, double dis) : Quote(bookno, pri), min_qty(n),
discount(dis) {}
void func() { printf("func of Bulk_quote\n"); }
inline static void sticOfBulk_quote() { printf("static of Bulk_quote\n"); }
private:
double discount = 0.0; // 折扣率
std::size_t min_qty = 0; // 折扣个数
};
#endif //UNTITLED_QUOTE_H
测试
int main() {
Quote a("Harry", 100);
Bulk_quote b("Potter", 100, 5, 0.2);
Quote &r = b;
b.func();
cout << b.net_price(10) << endl;
cout << a.net_price(10) << endl;
// 动态绑定
cout << r.net_price(10) << endl;
// r.func(); // r只能访问Quote中有的函数
// 静态成员只存在一份,派生类可以访问基类的
b.sticOfBulk_quote();
b.sticOfQuote();
Bulk_quote::sticOfBulk_quote();
a.sticOfQuote();
// a.sticOfBulk_quote(); error
// 基类不能转化为派生类
// Bulk_quote &rr = a;
}
/*
func of Bulk_quote
800
1000
800
static of Bulk_quote
static of Quote
static of Bulk_quote
static of Quote
*/
虚函数
虚函数必须有定义,不能只被声明
动态绑定只有在通过指针或者引用调用虚函数时才发生
若一个函数被声明为虚函数,则在所有派生类中其都是虚函数
派生类中的虚函数
派生类如果覆盖某个虚函数,其形参类型与返回类型需要与虚函数原型保持一致。
特例 虚函数返回类型是类本身的指针或引用时
class B {
public:
int v;
B(int i = 0) : v(i) {}
virtual B f1() { return *this; }
virtual B &f2() { return *this; }
virtual B *f3() { return this; }
};
class D : public B {
public:
D(int i = 0) : B(i) {}
// D f1() override { return *this; }
D &f2() override { return *this; }
D *f3() override { return this; }
};
int main() {
D d(3);
}
前提是D向B的返回类型转换是可访问的
派生类中,在重写虚函数时,一般加上关键字override
,使编译器检查是否对该虚函数进行了覆写,来避免潜在错误。
在虚函数后加final
将禁止后续再次覆写该虚函数
class B {
public:
int v;
B(int i = 0) : v(i) {}
virtual void show(int i = 1) const { printf("B %d\n", i + v); }
};
class D : public B {
public:
D(int i = 0) : B(i) {}
void show(int i = 2) const override { printf("D %d\n", i + v); }
};
int main() {
B b(3);
D d(3);
d.show();
b.show();
B &r = d;
// 虚函数的默认实参只会按基类中的来确定
r.show();
// 强制执行某版本的虚函数
r.B::show();
}
/*
D 5
B 4
D 4
B 4
*/
抽象基类
含纯虚函数的类是抽象基类
不能够创建抽象基类的对象,派生类覆写了纯虚函数后可以创建该对象
class B {
public:
int v;
B() = default;
B(int i = 0) : v(i) {}
virtual void show(int i = 1) = 0;
};
class D : public B {
public:
D() : B(1) {}
D(int i) : B(i) {}
void show(int i = 1) override { printf("D: %d\n", v); }
};
访问控制与继承
protected
派生类的成员或者友元只能通过派生类对象来访问基类的受保护成员
class Base {
friend void func3(Base &);
protected:
int a;
};
class P : public Base {
friend void func(P &);
friend void func2(Base &);
void show() {
printf("%d\n", a);
}
};
void func(P &p) {
printf("%d", p.a);
}
void func2(Base &b) {
// printf("%d\n",b.a); // error 不能访问protected成员
}
void func3(Base &b) {
printf("%d\n", b.a);
}
只有派生类public继承基类时,派生类对象才能向积累转换
class P : protected Base{}
///
P a;
Base b = a; // 错误
Base &b = a; // 错误
Base *b = &a; // 错误
友元关系不能继承
改变成员的可访问性
继承中的类作用域
名字检查先于类型检查
定义派生类中的函数 不会 重载 其基类中的成员。如果派生类中的成员与基类同名,那么就会隐藏该基类成员。即使其形参列表不一致,也会隐藏掉
class Base {
public:
void f() { printf("Base\n"); }
virtual void f(int i) { printf("Base %d", i); }
};
class P : public Base {
public:
void f(int i) override { printf("P %d", i); }
};
int main() {
Base base;
P p;
base.f();
p.f(1);
// p.f(); // error, 基类中的函数被隐藏了
p.Base::f(1); // 显式调用
}
虚函数与作用域
class Base {
public:
virtual void f() { printf("Base f()\n"); }
};
class D1 : public Base {
public:
void f(int) { printf("D1 f(int)\n"); }
virtual void f1() { printf("D1 f1()\n"); }
};
class D2 : public D1 {
public:
void f(int) { printf("D2 f(int)\n"); }
void f() override { printf("D2 f()\n"); }
void f1() override { printf("D2 f1()\n"); }
};
int main() {
Base base;
D1 d1;
D2 d2;
Base *b = &base, *b1 = &d1, *b2 = &d2;
d1.f(0);
// d1.f(); D1中并没有覆盖虚函数,因为形参不同
// D1将隐藏Base的f(),只能显式调用
d1.Base::f();
// 虚调用
b->f();
b1->f();
b2->f();
D1 *dp1 = &d1;
D2 *dp2 = &d2;
dp1->f(0);
dp1->f1();
dp2->f();
dp2->f(0);
dp2->f1();
}
/*
D1 f(int)
Base f()
Base f()
Base f()
D2 f()
D1 f(int)
D1 f1()
D2 f()
D2 f(int)
D2 f1()
*/
覆盖重载的函数
在类中一个函数可以被重载多个版本。
派生类可以覆盖基类中的重载函数的0个或多个版本。
但是因为名字检查先于参数列表检查,如果派生类希望基类中的所有重载函数都可见,那么只有:
- 覆写所有重载版本
- 一个也不覆盖
只覆盖一个,使其他的可见: using
class Base {
public:
void f() { printf("Base f()\n"); }
void f(int) { printf("Base f(int)\n"); }
void f(double) { printf("Base f(double)\n"); }
};
class D1 : public Base {
public:
void f() { printf("D1 f()\n"); }
};
class D2 : public Base {
public:
void f() { printf("D2 f()\n"); }
// 使其他未被覆盖的函数可用
using Base::f;
};
int main() {
D2 d2;
d2.f();
d2.f(0);
d2.f(1.0);
D1 d1;
d1.f();
// d1.f(1);
// d1.f(1.0);
}
/*
D2 f()
Base f(int)
Base f(double)
D1 f()
*/
构造函数与拷贝控制
虚析构函数
class Base {
public:
virtual ~Base() { printf("Delete Base\n"); }
};
class D1 : public Base {
public:
~D1() { printf("Delete D1\n"); }
};
int main() {
{
Base *p = new D1;
delete p;
}
{
D1 d1;
}
}
/*
Delete D1
Delete Base
Delete D1
Delete Base
*/
对于派生类的析构函数,其不仅负责析构派生类自己的成员,还负责析构其直接基类,以此类推,直到继承链的顶端。
派生类的拷贝控制成员
派生类的拷贝或移动构造函数
与构造函数一样,我们调用基类的拷贝/移动构造函数来操作派生类中的基类部分。
class Base {
public:
int a, b, c;
Base() = default;
Base(const Base &base) : a(base.a), b(base.b), c(base.c) {
printf("call Base copy construct\n");
}
virtual void show() { printf("%d %d %d\n", a, b, c); }
};
class D1 : public Base {
public:
int d;
D1() = default;
D1(const D1 &d1) : Base(d1), d(d1.d) { printf("call d1 copy construct\n"); }
virtual void show() override { printf("%d %d %d %d\n", a, b, c, d); }
};
int main() {
D1 a;
D1 b(a);
}
/*
call Base copy construct
call d1 construct
*/
派生类的赋值运算符
与上面思路类似,调用基类的赋值运算符,但是必须显式调用
class Base {
public:
int a, b, c;
Base &operator=(const Base &base) {
printf("call Base copy =\n");
if (this != &base) {
a = base.a;
b = base.b;
c = base.c;
}
return *this;
}
};
class D1 : public Base {
public:
int d;
D1 &operator=(const D1 &d1) {
printf("call D1 copy =\n");
if (this != &d1) {
Base::operator=(d1);
d = d1.d;
}
return *this;
}
};
int main() {
D1 a;
D1 b;
a = b;
}
/*
call D1 copy =
call Base copy =
*/
在构造函数和析构函数中调用虚函数
Note 如果在基类构造函数中执行虚函数,他可能会用到派生类的成员。而在构造时,是先执行基类的构造函数构造基类部分,然后才构造派生类部分,因此这样有访问未初始化成员的风险。
容器与继承
直接存放基类会导致派生类被切掉
class Base {
public:
int a, b, c;
Base(int i, int i1, int i2) : a(i), b(i1), c(i2) {}
virtual void show() { printf("%d %d %d\n", a, b, c); }
};
class D1 : public Base {
public:
int d;
D1(int a_, int b_, int c_, int d_) : Base(a_, b_, c_), d(d_) {}
D1() = default;
virtual void show() override { printf("%d %d %d %d\n", a, b, c, d); }
};
int main() {
vector<Base> vec;
vec.push_back(D1(1, 2, 3, 4));
vec.back().show();
}
// 1 2 3
在容器中存放智能指针而非对象
int main() {
vector<shared_ptr<Base>> vec;
vec.push_back(make_shared<D1>(1, 2, 3, 4));
vec.push_back(make_shared<Base>(1, 2, 3));
vec[0]->show();
vec[1]->show();
}
/*
1 2 3 4
1 2 3
*/
隐藏智能指针细节
double print_total(std::ostream &os, Quote "e, size_t n) {
double ret = quote.net_price(n);
printf("ISBN %s, sold: %zu, total due: %f\n", quote.isbn().c_str(), n, ret);
return ret;
}
class Basket {
private:
static inline bool cmp(const std::shared_ptr<Quote> &lhs, const std::shared_ptr<Quote> &rhs) {
return lhs->isbn() < rhs->isbn();
}
std::multiset<std::shared_ptr<Quote>, decltype(cmp) *> items{cmp};
public:
void additem(const std::shared_ptr<Quote> &sale) { items.insert(sale); }
// 在类中添加虚函数clone,这样可以直接添加对象,不必 basket.additem(make_shared<Quote>(...));
void additem(const Quote &s) { items.insert(std::shared_ptr<Quote>(s.clone())); }
void additem(Quote &&s) { items.insert(std::shared_ptr<Quote>(std::move(s).clone())); }
double total_receipt(std::ostream &os) const {
double sum = 0;
for (auto iter = items.cbegin(); iter != items.cend(); iter = items.upper_bound(*iter)) {
sum += print_total(os, **iter, items.count(*iter));
}
printf("Total sale %f", sum);
return sum;
}
};
virtual Quote *clone() const &{ return new Quote(*this); }
virtual Quote *clone() &&{ return new Quote(std::move(*this)); }
virtual Bulk_quote *clone() const & override {
return new Bulk_quote(*this);
}
virtual Bulk_quote *clone() && override {
return new Bulk_quote(std::move(*this));
}