1、动态绑定
c++中当使用基类的引用或指针调用一个虚函数时将发生动态绑定。
2、虚函数继承
如果基类吧一个函数声明成虚函数,则该函数再派生类中隐式地也是虚函数。
3、派生类构造函数
尽管再派生类对象中含有从基类 继承而来的成员,但是派生类并不能直接初始化这些成员。和其他创建了基类对象的代码一样,派生类也必须使用基类的构造函数来初始化它的基类部分。每个类控制它自己的额成员初始化过程。
class A
{
public:
A() :a(0) { cout << "A cons" << endl; }
private:
int a;
};
class B:public A
{
public:
B() :b(1) { cout << "B cons" << endl; }
private:
int b;
};
void test()
{
B b; //B继承了A的成员a,a由A的构造函数初始化 所以这里会先调用A() 再调用B()
}
可以在派生类构造函数中基类构造可以看下面代码
class A
{
public:
A(int x = 0) :a(x) { cout << "A cons" << endl; }
private:
int a;
};
class B:public A
{
public:
B(int x = 0, int y = 1) :A(x), b(y) { cout << "B cons 2" << endl; }
private:
int b;
};
?4、存在进程关系的类型之间的转换规则
- 从派生类想基类的类型转换只对指针或引用类型有效
- 基类向派生类不存在隐式类型转换
- 派生类向基类的类型转换可能会由于访问受限而变得不可行,虽然可以将一个派生类对象拷贝、移动或赋值给一个基类对象,不过需要注意的是,这种操作只处理派生类对象中基类部分
5、override
派生类如果定义了一个函数与基类中虚函数的名字相同但是参数列表不同,如果没有override的话,编译器将认为新定义的函数与基类中原有的函数是独立的,合法。但如果加了override后,编译器在基类中找不到对应的虚函数就会报错了
6、虚函数与默认实参
如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致,否则将以基类中的为准。即使实际运行的是派生类中的函数版本。
class B
{
public:
virtual void print(int x = 0)
{
cout << x << endl;
}
};
class D:public B
{
public:
void print(int x = 1)
{
cout << x << endl;
}
private:
};
void test()
{
B* b = new D();
b->print(); //输出0
D* d = new D();
d->print(); //输出1
}
7、回避虚函数的机制
有时候,我们洗碗该队虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本。使用作用域运算符可以实现这一目的。而通常只有成员函数或友元中的代码才需要使用作用域运算符来回避虚函数机制。
void test()
{
B* b = new D();
b->print(); //输出0
b->D::print(); //错误 限定名不是类B 或其基类
D* d = new D();
d->print(); //输出1
d->B::print();//输出0
}
8、纯虚函数
- 可以为纯虚函数提供定义,但是必须定义在类外
- 含有纯虚函数的类是抽象基类,不能创建抽象基类的对象
- 派生类必须覆盖抽象基类的纯虚函数
9、protected访问控制
- 和私有成员类似,受保护的成员对于类的用户来说不可访问
- 和公有成员类似,受保护的成员对于派生类的成员和友元来说是可访问的
- 派生类的成员或友元只能通过派生类对象(而不是基类对象)来访问基类的受保护成员,即派生类的成员和友元之恩呢访问派生类对象中基类部分的受保护成员
class B
{
protected:
int b;
};
class D:public B
{
friend void printD(D&); //能访问D::d 和 D从B继承来的b
friend void printB(B&); //不饿能访问 B中的b
public:
void init() { b = 5; }
private:
int d;
};
void printD(D& rd) { rd.d = 1; rd.b = 2; } //正确
void printB(B& rb) { rb.b = 3; } //错误 不能访问B中的protected成员
10、公有、私有和受保护的继承
派生访问说明符对于派生类的成员以及友元能否访问其直接基类的成员没什么影响。对基类成员的访问权限只与基类中的访问说明符有关。派生访问说明符的目的是控制派生类用户对于基类成员的访问控制。还可以控制继承自派生类的新类的访问权限
class Bass
{
public:
void pub_mem();
protected:
int prot_mem;
protected:
int priv_mem;
};
struct Pub_Derv:public Bass
{
int f() { return prot_mem; } //
int g() { return priv_mem; }//错误 不能访问基类的private成员
};
struct Priv_Derv:private Bass
{
int f1() { return prot_mem; } //正确 派生访问说明符对访问直接基类的成员没影响
};
struct Derived_from_Public:public Pub_Derv
{
int use_base() { return prot_mem; } // 正确
};
struct Derived_from_Private1 :public Priv_Derv
{
int use_base() { return prot_mem; } // 错误 Base::prot_mem在Priv_derv中是private的
};
struct Derived_from_Private1 :private Priv_Derv
{
int use_base() { return prot_mem; } // 错误 Base::prot_mem在Priv_derv中是private的
};
void test()
{
Pub_Derv d1; //继承自Bass的成员是public的
Priv_Derv d2; //继承自Bass的成员是private的
d1.pub_mem(); //正确
d2.pub_mem(); //错误
}
11、友元和继承
正如友元关系不能传递一样,友元关系同样不能继承。
12、改变个别成员的可访问性
通过在类的内部使用using声明语句,可以将类的直接或间接基类中的任何可访问成员标记出来。
13、struct和class?
struct的默认成员访问说明符和默认派生访问说明符都是public,class正好相反。除此之外 struct和class再无不同
14、在编译时进行名字查找
一个对象、引用或指针的静态类型决定了该对象的哪些成员是可见的。即使静态类型与动态类型可能不一致,但是我们能使用哪些成员仍然是静态类型决定的。
class A
{
public:
virtual void printa(){ cout << "A" << endl; }
};
class B:public A
{
public:
virtual void printa(){ cout << "B" << endl; }
void printb(){ cout << "BB" << endl; }
};
void test()
{
A* a = new B();
a->printa(); // 正确 输出B
a->printb(); //错误 a的静态类型是A A中没有printb()
}
15、名字查找优于类型检查
定义派生类中的函数不会重载其基类中的成员。如果派生类的成员与基类的某个成员同名,则派生类将在其作用域内隐藏该基类成员,即使他们的形参列表不一样。
class A
{
public:
void printa(){ cout << "A" << endl; }
};
class B:public A
{
public:
void printa(int a){ cout << "B" << endl; }
};
void test()
{
A a; B b;
a.printa();
b.printa(3);
b.printa(); //错误 A中的printa被隐藏
b.A::printa();//正确 显示调用A的成员
}
16、覆盖重载的函数
派生类想定义一个与基类中某函数同名的新的版本,但又不想隐藏基类中的同名函数,可以使用using声明语句
class A
{
public:
void printa() { cout << "a" << endl; }
void printa(int x) { cout << "ax" << endl; }
};
class B:public A
{
public:
using A::printa;
void printa(int x, int y){ cout << "Bxy" << endl; }
};
void test()
{
B b;
b.printa();
b.printa(1);
b.printa(1, 2);
}
17、派生类中删除的拷贝控制与基类的关系
- 如果基类中的默认构造函数、拷贝构造函数、拷贝赋值运算符或析构函数是被删除的或不可访问,则派生类中对应的成员将是被删除的,原因是编译器不能使用基类成员来执行派生类对象中基类部分的构造、赋值或销毁动作。
- 如果在基类中有一个不可访问或删除掉的析构函数,则派生类中合成的默认和拷贝构造函数是被删除的,因为编译器无法销毁派生类对象的基类部分。
- 和过去一样,编译器将不会合成一个删除掉的移动操作。当我们使用=default请求一个移动操作时,如果基类中的对应操作时删除的或不可访问的,那么派生类中该函数将是被删除的,原因时派生类对象的基类部分不可移动,同样,如果基类的析构函数是删除的或不可访问,则派生类的移动构造函数也将是被删除的。
18、默认情况下,基类默认构造函数初始化派生类对象的基类部分,如果我们想拷贝或移动基类部分,则必须在派生类的构造函数初始值列表中显示地是哟那个积累的拷贝或移动构造函数。派生类的赋值运算符也必须显示地为其基类部分赋值
class A{};
class B:public A
{
public:
B(const B& b):A(b) //拷贝基类成员
/*B的成员的初始值*/ {}
B(B&& b):A(std::move(b)) //移动基类成员
/*B的成员的初始值*/ {}
};
B& B::operator=(const B &rhs)
{
A::operator=(rhs); //为基类部分赋值
/* ... */
return *this;
}
19、完整的文本查询程序
TextQuery.h
#include <fstream>
#include <memory>
#include <vector>
#include <map>
#include <string>
#include <set>
#include <algorithm>
class QueryResult;
class TextQuery
{
public:
using line_no = std::vector<std::string>::size_type;
TextQuery(std::ifstream&);
QueryResult query(const std::string&)const;
private:
std::shared_ptr<std::vector<std::string>> file;
std::map<std::string, std::shared_ptr<std::set<line_no>>> wm;
};
class QueryResult
{
public:
using Iter = std::set<TextQuery::line_no>::iterator;
friend std::ostream& print(std::ostream&, const QueryResult&);
QueryResult(const std::string s, std::shared_ptr<std::set<TextQuery::line_no>> l, std::shared_ptr<std::vector<std::string>> f):
sought(s), lines(l), file(f){}
Iter begin()const { return lines->begin(); }
Iter end()const { return lines->end(); }
std::shared_ptr<std::vector<std::string>> get_file()const
{
return std::make_shared<std::vector<std::string>>(file);
}
private:
std::string sought;
std::shared_ptr<std::set<TextQuery::line_no>> lines;
std::shared_ptr<std::vector<std::string>> file;
};
std::ostream& print(std::ostream&, const QueryResult&);
class Query_base
{
friend class Query;
protected:
using line_no = TextQuery::line_no;
virtual ~Query_base() = default;
private:
//eval返回与当前Query匹配的QueryResult
virtual QueryResult eval(const TextQuery&) const = 0;
//rep是表示查询的一个string
virtual std::string rep() const = 0;
};
class WordQuery;
//Query是管理Query_base继承体系的接口类
class Query
{
//下面三个运算符需要访问接受shared_ptr的构造函数,而该函数是私有的
friend Query operator~(const Query&);
friend Query operator|(const Query&, const Query&);
friend Query operator&(const Query&, const Query&);
public:
//构建一个新的WordQuery对象
Query(const std::string&);
//接口函数 调用对应的Query_base操作
QueryResult eval(const TextQuery& t)const { return q->eval(t); }
std::string rep()const { return q->rep(); }
private:
Query(std::shared_ptr<Query_base> query):q(query){}
std::shared_ptr<Query_base> q;
};
class WordQuery:public Query_base
{
friend class Query;
WordQuery(const std::string& s):query_word(s){}
QueryResult eval(const TextQuery& t)const { return t.query(query_word); }
std::string rep()const { return query_word; }
std::string query_word;
};
class NotQuery:public Query_base
{
friend Query operator~(const Query&);
NotQuery(const Query& q):query(q){}
QueryResult eval(const TextQuery&) const;
std::string rep()const { return "~(" + query.rep() + ")"; }
Query query;
};
inline Query operator~(const Query& operand)
{
//隐式调用Query的private构造函数 构造Query对象
return std::shared_ptr<Query_base>(new NotQuery(operand));
}
class BinaryQuery:public Query_base
{
protected:
BinaryQuery(const Query& l, const Query& r, std::string s) :
lhs(l), rhs(r), opSym(s) {}
std::string rep() const{
return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")";
}
//BinaryQuery也是抽象类 不需要定义eval
Query lhs, rhs;
std::string opSym;
};
class AndQuery:public BinaryQuery
{
friend Query operator&(const Query&, const Query&);
AndQuery(const Query& l, const Query& r) :BinaryQuery(l, r, "&") {};
QueryResult eval(const TextQuery&)const;
};
class OrQuery:public BinaryQuery
{
friend Query operator|(const Query&, const Query&);
OrQuery(const Query& l, const Query& r):BinaryQuery(l, r, "|"){}
QueryResult eval(const TextQuery&)const;
};
TextQuery.cpp
#include "TextQuery.h"
#include <sstream>
using namespace std;
TextQuery::TextQuery(std::ifstream& ifs):file(new vector<string>)
{
string text;
while(getline(ifs, text))
{
file->push_back(text);
int n = file->size() - 1;
istringstream line(text);
string word;
while(line >> word)
{
auto& lines = wm[word];
if (!lines)
lines.reset(new set<line_no>);
lines->insert(n);
}
}
}
QueryResult TextQuery::query(const std::string& word) const
{
static shared_ptr<set<line_no>> nodata(new set<line_no>);
auto itr = wm.find(word);
if (itr == wm.end())
return QueryResult(word, nodata, file);
return QueryResult(word, itr->second, file);
}
std::ostream& print(std::ostream& os, const QueryResult& qr)
{
os << qr.sought << " occurs " << qr.lines->size() << "\n";
for (auto& line : *qr.lines)
os << "\tline:" << line + 1 << "\t" << *(qr.file->begin() + line) << endl;
return os;
}
inline Query::Query(const std::string& s) :q(new WordQuery(s)) {}
std::ostream& operator<<(std::ostream& os, const Query& query)
{
return os << query.rep();
}
Query operator|(const Query& l, const Query& r)
{
return std::shared_ptr<Query_base>(new OrQuery(l, r));
}
inline Query operator&(const Query& l, const Query& r)
{
return std::shared_ptr<Query_base>(new AndQuery(l, r));
}
QueryResult NotQuery::eval(const TextQuery& t)const
{
auto result = query.eval(t);
auto ret_lines = std::make_shared<set<TextQuery::line_no>>();
auto beg = result.begin(), end = result.end();
auto sz = result.get_file()->size();
for (size_t n = 0; n != sz; ++n)
{
if (beg == end || *beg != n)
ret_lines->insert(n);
else if (beg != end)
++beg;
}
return QueryResult(rep(), ret_lines, result.get_file());
}
QueryResult OrQuery::eval(const TextQuery& t)const
{
auto right = rhs.eval(t), left = lhs.eval(t);
auto ret_lines = std::make_shared<set<TextQuery::line_no>>(right.begin(), right.end());
ret_lines->insert(left.begin(), left.end());
return QueryResult(rep(), ret_lines, left.get_file());
}
QueryResult AndQuery::eval(const TextQuery& t)const
{
auto right = rhs.eval(t), left = lhs.eval(t);
auto ret_lines = std::make_shared<set<TextQuery::line_no>>();
std::set_intersection(left.begin(), left.end(), right.begin(), right.end(), std::inserter(*ret_lines, ret_lines->begin()));
return QueryResult(rep(), ret_lines, left.get_file());
}
|