1、指向class member function的指针
pointer to member function的声明,和pointer to non-member function不同的是需要指定它指向的class对象。
//pm声明为一个指针,指向num_sequence的成员函数,后者的返回了类型必须是void,函数参数为int类型
void (num_sequence::*pm)(int) = 0;
//另一种定义方式
typedef void (num_sequence::*PtrType)(int) = 0;
PtrType pm =0;
取member function地址需要用class scope
pm = &num_sequence::triangular; //pm指向triangular函数
2、面向对象的编程思维
默认情况下,member function的解析都在编译时静态的完成,如果要令在运行时动态进行,在声明前加上关键字virtual;
程序定义的派生对象,基类和派生类的constructor和destructor函数都会被执行。
protected关键字:被声明为protected的成员仅可以被派生类直接访问
特殊转换记号: static_cast
3、定义抽象基类
定义抽象基类的步骤
1)找出所有子类共通的操作行为
2)找出操作行为与哪些类型有关,哪些操作行为必须根据不同的派生类而有不同的实现方式,设置成为虚函数
3)确定访问层级,public、private、protected;
只能在派生类中才能通过派生类对象访问基类的protected成员!!!
static member function不能被声明为虚拟函数!!!
凡基类定义有一个虚函数,应该要将destructor声明为虚函数,但是不要是纯虚函数。
纯虚函数:任何类如果声明有纯虚函数,程序无法为它产生任何对象,这种类只能作为派生类的子对象使用,而且前提是这些派生类必须为所有虚函数提供确切的定义。
这篇文章有更深的讨论;https://blog.csdn.net/hackbuteer1/article/details/7558868
4、定义派生类
类进行继承声明之前,基类的定义必须已经存在。
在类之外对虚函数进行定义时,不必指明关键词virtual。
每当派生类中有某个member与基类的member同名,那么会遮掩基类的那份member,如果要在派生类内使用继承来的那份member,必须使用class scope加以限定。
Data member 如果是个reference,必须在构造函数的memberintialization list中加以初始化。一旦初始化,就再也无法指向另一个对象。如果data member是个pointer,就无此限制:可以在构造哈桑内加以初始化, 也可以先将它初始化为nulll。
5、初始化
如果抽象基类中有实际的data member,那么必须要提供初始化,若将初始化留给派生类,可能有隐患。恰当的设计方法是为基类提供构造函数,利用这个构造函数处理基类所声明的所有data member的初始化操作。
抽象基类无法为它定义任何对象,它扮演的角色是每个派生类对象的子对象,基于此,将抽象基类的构造函数声明为protected而不是public。
派生类的构造函数,不仅要为派生类的data member进行初始化操作,还需要为基类的data member提供适当的值。
//基类有三个data member,派生类有两个data member
inline Fibonacci::Fibonacci(int len,int beg_pos):num_sequence(len,beg_pos,_elems){}
另一种初始化的方法是提供默认构造函数。
6、在派生类中定义一个虚函数
如果要覆盖基类提供的虚函数,那么派生类提供的新定义,函数原型必须完全符合基类所声明的函数原型,包括参数列表、返回类型、常量性。 要么无法发挥虚函数机制,要么无法编译成功。
在派生类中,为了覆盖基类的某个虚函数,而进行声明操作,不一定得加上关键字Virtual。编译器会根据两个函数的原型声明,决定某个函数是否会覆盖基类中的同名函数。
有两种情况,虚函数不会出现预期行为,1)基类的构造和折构函数 2)使用的是基类的对象,而不是针对对象的pointer或reference时。【如果传入的是派生对象,可能没有足够的内存放置派生类的data member】
7、 编程时遇到的问题及总结
1、switch中default的位置:https://www.cnblogs.com/LubinLew/p/default_in_switch.html
结论是:default在case全不匹配的情况下进入;可以放在任意位置;进入后和普通进入点一样,如果没有break则继续执行
2、protected的访问权限 https://blog.csdn.net/luoruiyi2008/article/details/7179788
3、无法实例化抽象类
https://blog.csdn.net/m0_38129013/article/details/78490931
如果抽象类的派生类有任何一个纯虚函数没有实现,那么这个类仍然是一个抽象类
https://blog.csdn.net/wangshubo1989/article/details/49953095
4、有关char*
const char* num_sequence::what_am_i() const {
const char *names[num_seq] = {
"noset","fibonacci","pell","lucas","triangular","square","pentagonal"
};
return names[_isa];
}
之前总觉得 上面的代码有点奇怪,字符串常量的本质表示其实是一个地址。
所以有:
char *s ;
s = "China"; //真正的意义是 s ="China" = 0x3000(地址);
5、一个很低级的错误,找不到“valType”类型的右操作数的运算符,是因为没有包含string头文件
/*ERROR错误说明
调用:
BinaryTree< string > bt;
...
bt.insert("Piglet");
cout << "preorder traversal: \n";
bt.preorder();
// preorder函数体内有display_val函数,错误指示到display_val函数内:
display_val(BTnode *pt, ostream &os) const
{
os << pt->_val;
if (pt->_cnt > 1)
os << "( " << pt->_cnt << " ) ";
else os << ' ';
}
错误:C2679 二进制“ << ”: 没有找到接受“valType”类型的右操作数的运算符(或没有可接受的转换)
原因:没有包含string头文件,哎呀我去!!!!
*/