Essential C++浓缩笔记(五)——面向对象的编程风格

 

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 (expression)

 

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头文件,哎呀我去!!!!

*/

 

你可能感兴趣的:(C++)