`
为心中梦想挥剑的那一刹那
  • 浏览: 6871 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

c++程序设计梳理(谭浩强)11章-12章

 
阅读更多

 从前天开始,这两天调整了下生物钟,毕竟半夜4点的作息是不可取的。并在昨天凌晨0:05分的时候去看了诺兰新上映的电影《星际穿越》。感受我就不多说了,一个字:神!是当今世界最主流的广义相对论的一个实现体,打算在看一遍imax的。生活方面,可能比较影响的,就是在昨天知道了自己一直比较倾心的女生对自己评价很低的事实。怎么说呢,这个问题多少以前自己早都有感觉,只不过一切摆在面前的时候,还是比较残酷的,哪有人说是完全不介意的呢?今天想了下,自己的努力在于技术与工作与学习,吃过情感的苦肯定不会再让它来影响自己的追去与梦想,不是主流的内心思想,何苦自取不快呢?再来,自己是有动力有激情去灌注当下一切的技术的,我想当我走到底,证明给他们看,即使不成熟,即使不懂爱,一切都可以实现,我的生活我的未来比你们成熟懂爱的人更精彩!走着瞧~
 傍晚接到了京东深圳招聘的电话,进行了下电话面试。心想,你这个京东让人等还不说,竟然还加试!哎真心不容易啊!我还是认认真真的学我的c++,专研linux,真心被你们党务不起。最后真的能行了,再说吧!(说实话,自己还是想走当下这个道路,毕竟当初本科是学这些的,研究生无奈接触了java,可去京东是要java的啊,这个着实伤脑筋,不发也算是老天爷的一种指引吧!)
 今天我来总结第11章到第12章。之所以要在面向对象的梳理过程中合并这两张一起写,是因为,这两章有非常大的连续性和关联性,必须要一气呵成的看完且梳理完,这样才能更好的掌握这两章所涉及的知识。
Chapter11~Chapter12
->继承与派生
1、继承:这个东西,成为当今程序设计与项目开发的主流概念!“无数英雄竞折腰”!哈哈!概念是死板切枯燥的。如
果您要考个什么计算机证或者是等级或者是期末考试或者是……可以去记一下。其他的时候,例如很关键的找个工作,
千万不要死记硬背!因为没有公司会傻到会问你概念!最好的回答永远是结合实际的实践,来说一说你对继承的感想
,说一说具体在项目的什么地方体现了继承的这个理念,这样才是最佳!故在此我也写概念了!


2、类的继承方式有三种:私有继承、公有继承、保护继承。下面两张表能总结一切问题。
表一:

基类中的成员 在公用继承的派生类中的访问属性 在私有继承的派生类中的访问属性 在保护继承的派生类中的访问属性
私有成员 不可访问 不可访问 不可访问
公用成员 公用 私有 保护
保护成员 保护 私有 保护

表二:

派生类中的成员 在派生类中 在派生类外 在下层公用派生类中
派生类中的访问属性为公用的成员 可以 可以 可以
派生类中的访问属性为受保护的成员 可以 不可以 可以
派生类中的访问属性为私有的成员 可以 不可以 不可以
在派生类中的不可访问的成员 不可以 不可以 不可以

p.s:class继承默认是private继承,而struct继承默认是public继承!


3、当一个派生类具有公用继承的基类和其他类的子对象(对象为派生类的成员属性)的时候,初始化的顺序如下:
(1)对基类的数据成员初始化(基类数据成员中有子对象的话,先初始化基类的子对象);
(2)对子对象数据成员初始化;
(3)对派生类的数据成员初始化。
这种模型要牢记,2015年的各大公司的笔试当中,考到了几次!另外,在当今的程序设计中并不推荐使用继承这个模式,因为他在整体的类一多起来之后,会显得非常的繁琐与麻烦,较难维护和调整,有时候可能牵一发而动全身,所以最推荐的还是这种子对象的模式,这种在java中,叫做组合!下面是一个简单的继承测试的代码:

/*
header_file.h文件
*/
#include<iostream>
using namespace std;

class MyClass1
{
public:
	MyClass1();
	~MyClass1();
};
class MyClass2:public MyClass1
{
public:
	static int flag;
	MyClass2(int a) :c(a){}
	~MyClass2();
private:
	MyClass1 mc;
	int c;

};
/*
header_file.cpp文件
*/
#include "header_file.h"
#include<iostream>
using namespace std;

int MyClass2::flag = 0;
MyClass1::MyClass1()
{
	cout << "这是基类的默认构造函数!" << (MyClass2::flag)++<<endl;
}

MyClass1::~MyClass1()
{
	cout << "这是基类的默认析构函数!" << --(MyClass2::flag) << endl;
}
MyClass2::~MyClass2()
{
	cout << "这是派生类的默认析构函数!" << endl;
}
/*
主文件
*/
#include "header_file.h"
#include<iostream>
int main(){
	MyClass2 mc2(2);
	return 0;
}

 

4、还有一种情况要单独拿出来说明下,具体在下面代码中给出了注释:

class MyClass2:public MyClass1
{
public:
	static int flag;
	MyClass2() :b(3),c(b){//这样的初始化顺序是按照下面成员属性的顺序初始化的,就是先初始化c后初始化b,并不是按照我这种列表的顺序,所以在这种情况下,c初始化时b并没有初始化,所以是一个随机值,然后再把b初始化成3
		cout << c << "," << b << endl;
	}
	~MyClass2();
private:
	MyClass1 mc;
	int c;
	int b;

};

 

5、多重继承(这个问题是c++的面向对象程序设计中很烦的一个部分,会出现二义性的问题,java的开发者也是被此所烦恼才开发了java这们语言!我在这要一步步来写)
①多重继承的构造顺序与析构顺序。根据继承时候的声明顺序进行构造,析构的话是和构造相反。具体的可以执行下面代码尝试。

/*
header_file.h文件
*/
#include<iostream>
using namespace std;

class MyClass1
{
public:
	MyClass1();
	~MyClass1();
};

class MyClass3
{
public:
	MyClass3();
	~MyClass3();
};

class MyClass2:public MyClass1,public MyClass3
{
public:
	static int flag;
	MyClass2() :b(3),c(4){
		cout << c << "," << b << endl;
	}
	~MyClass2();
private:
	MyClass1 mc;
	int c;
	int b;

};
/*
header_file.cpp文件
*/
#include "header_file.h"
#include<iostream>
using namespace std;

int MyClass2::flag = 0;
MyClass1::MyClass1()
{
	cout << "这是基类的默认构造函数1!" <<endl;
}

MyClass1::~MyClass1()
{
	cout << "这是基类的默认析构函数1!"<< endl;
}
MyClass2::~MyClass2()
{
	cout << "这是派生类的默认析构函数!" << endl;
}
MyClass3::MyClass3(){
	cout << "这是基类的默认构造函数3!" << endl;
}
MyClass3::~MyClass3(){
	cout << "这是基类的默认析构函数3!" << endl;
}
/*
主文件
*/
#include "header_file.h"
#include<iostream>
int main(){
	MyClass2 mc2;
	return 0;
}

②解决二义性的问题。这个其实也简单,就是要访问哪个类中的具体函数或者是成员只要用这样:类名::函数、类名::成员。如果是在派生类中出现了和无论哪个基类一样的成员或者是函数(同名、参数个数一样、参数类型一样、类型的顺序也一样),那如果我申请的是派生类的对象,我们访问这些成员或者是函数,我们将用到的是派生类的这个成员或者是函数,就是说派生类中的这些将基类中的屏蔽了!
③虚基类的出现。这个的出现,主要是保证继承间接的共同基类时只保留一份成员。情况如下。

class A{
public:
	int a;
	void display(){}
};
class B : public A{

};
class C : public A{

};
class D : public B, public C{
//此种情况下的D具备了两个a和两个void display(),同时是B::a,C::a,B::display(),C::display()。如果要具体对a赋值,要在前面加上C或者是B类名的声明,如果加上A声明的话,编译器并不知道我到底是用从B继承来的还是从C继承来的。
};
要避免这种情况的话必须要虚基类的使用:
class A{
public:
	int a;
	void display(){}
};
class B :virtual public A{

};
class C :virtual public A{

};
class D : public B, public C{
//这种情况下,D类中只保留了一份的a的属性空间与一个dispaly()函数,这样能防止对基类的多重继承的问题
};

注意的是,在用了虚基类之后,我要是在终端的子类中运用带参数的构造函数的时候,我要保证同时初始化基类与间接基类,因为如果我只初始化了间接基类的话,然后由间接基类去初始化的话,这样这几个间接基类可能会给出同一个属性不同值的情况,这样会产生矛盾。所以要在终端的派生类中一并给出基类与间接基类的属性值。
④基类与派生类的对象转换。这个是难点与重点,具体的每次笔试面试过程中都会出这种问题来考察我们对C++这门语言的掌握程度与深度!具体的我们心中要牢记的一个核心本质就行:在没有虚基类与虚函数的情况下(下一章会具体介绍这两个),在基类与派生类之间,只能从派生类到基类的赋值与转换,指针也是派生类的地址赋给基类申请的指针,可访问的情况仅仅是基类的部分,或者是派生类从基类继承过来的部门,不能进行派生类新加部分的访问!

class A{
public:
	int a;
	void display(){}
private:
	int b;
};
class B : public A{

};
int main(){
	A a;
	B b;
	a=b;//赋值
	A aa = b;//复制
	A& aa = b;//访问b对象中基类A中的部分
	A *p = new B();//指针的指向B类中A基类中的部分
	return 0;
}

⑤组合的实现基本代码:

class A{
public:
	int a;
	void display(){
		cout << "jicheng" << endl;
	}
private:
	int b;
};
class D {
public:
	A a;//组合
};
int main(){
	D d;
	d.a.display();//调用
	return 0;
}

 


->多态性与虚函数
1、多态啊多态!这又是一个难点与重点!此处是深入c++程序设计的关键之关键!做了两年的JavaEE项目,深知道多态对代码的复用与解耦和是多么的重要!在spring的IOC中,正是从根本上用到了这种多态的理念,才使得一个接口与多个实现相匹配的模型得以实现!所以,当今软件开发,在代码的复用,开发的效率方面尤为重视,面向对象的这些个特性也孕育而生!我一直认为c++是始祖型的语言,他带来了并开创了许多当今软件界的先河,才有后面的java等等一系列的面向对象语言。也许这里我说的不准确吧,但是我觉得,c++作为一个很关键性重要性的面向对象的程序设计语言,终归脱离不开曾经我接触过的一系列的软件设计理念的。多态如此,虚函数就是类似java的接口功能。

 

 

(插播一条刚刚自己的生活小常识的物理原理:水是反常膨胀,热缩冷涨,密度随温度的升高而增加 ,所以等体积水
越热,质量越大。下午泡的茶,刚刚煮了热水加到里面,过了会儿,上面是热的下面是凉的。⊙﹏⊙b汗)

 

 

2、虚函数的引用。这个是对上一章第5条里面的第④点的一个扩展:上面基类的对象或者是指针,赋值成了派生类的对象或者是地址,并不能访问派生类中的新加入的属性和方法的,只能进行基类的属性与方法的访问。这个虚函数就使得这种访问成为现实。实例代码如下:

#include<iostream>
using namespace std;
class A{
public:
	int a;
	virtual void display(){
		cout << "jicheng" << endl;
	}
private:
	int b;
};
class D : public A {
public:
	A a;
	void display(){
		cout << "jicheng1" << endl;
	}
};
int main(){
	A a;
	D d;
	a = d;
	a.display();//此处调用的还是A类中的display(),因为虚函数的多态性得用指针或者引用来实现
	A *p = &d;
	p->display();//此处调用的是D类中display()函数
	p = &a;
	p->display();//此处调用的是A类中display()函数
	D dd;
	A& aa = dd;//引用来调用虚函数
	aa.display();
	return 0;
}

 

3、虚函数实现原理。具体的是在有虚函数的类中保存一个指针,指向具体的这个虚函数的入口。(详尽的内存模型可以看看《程序员面试宝典》中对这里的解释)

#include<iostream>
using namespace std;
class A{
public:
	virtual void display(){
		cout << "jicheng" << endl;
	}
	virtual void display_again(){
		cout << "jicheng2" << endl;
	}
	virtual void display_tree(){
		cout << "jicheng3" << endl;
	}
};
class D : public A {
public:
	/*void display(){
		cout << "jicheng1" << endl;
	}*/
};
int main(){
	cout << sizeof(A) <<","<<sizeof(D)<< endl;//运行结果是:4,4。表明在类中还有个指针的大小,指针都是整型的
	return 0;
}

 

4、析构函数尽量保证是虚函数。因为当我们在整个继承体系中,我在基类的析构函数不是虚函数的话,那我将基类对象的指针指向派生类时候,我们释放指针指向的空间,这时候我们发现我们只调用了基类对象的析构函数,而并没有调用派生类的,这并不是我们所希望的,因为如果不调用派生类的析构函数,我们并没有释放派生类的资源所占用的空间,这回造成内存溢出。代码如下:

#include<iostream>
using namespace std;
class A{
public:
	/*virtual void display(){
		cout << "jicheng" << endl;
	}
	virtual void display_again(){
		cout << "jicheng2" << endl;
	}
	virtual void display_tree(){
		cout << "jicheng3" << endl;
	}*/
	virtual ~A(){
		cout << "这是基类的析构函数!" << endl;
	}
};
class D : public A {
public:
	/*void display(){
		cout << "jicheng1" << endl;
	}*/
	~D(){
		cout << "这是派生类的析构函数!" << endl;
	}
};
class E{

};
int main(){
	A *p = new D();
	delete p;//这样会先释放派生类的,最后释放基类的!
	return 0;
}

 


5、纯虚函数与抽象类。拥有纯虚函数的类称之为抽象类。这里其他的没什么讲的主要就是要注意一点:在同时有用纯虚函数与虚函数的同时,这样的基类是也是抽象基类,不能申请对象!

class A{
public:
	virtual void display(){
		cout << "jicheng" << endl;
	}
	virtual void display_again() = 0;//这个是纯虚函数声明形式,所以A类是抽象类!
	virtual ~A(){
		cout << "这是基类的析构函数!" << endl;
	}
};
class D : public A {//测试中发现,此时如果在D类中并不实现A类中的纯虚函数,这时候D类也不能申请对象,D其实也是抽象类!这个和书中说的有点出入!是不是编译器的原因,这个不得而知!
public:
	/*void display(){
		cout << "jicheng1" << endl;
	}*/
	~D(){
		cout << "这是派生类的析构函数!" << endl;
	}
};

 

p.s:这一部分,我要多说一下。因为具体的c++笔试面试过程中,这一部分经常会考,简单的点考的繁琐,让人容易出错;复杂的点考的深入,让人不懂。例如在最后的这个虚函数与派生类的点,我记得印象中有道题是指针来回乱指的一道(具体的我忘了,当我找到了我会在这补上给出!),最后考你具体他调用的是哪个类里面的这个同名的函数,输出是什么?答案是指针指在基类中的这个函数的地址空间,但是要按照派生类的这个函数模型来调用,所以实际会执行基类这个函数的行数。很奇葩,考的也千奇百样!所以这里不会完,在之后我会将找到的好题都一点点的补上!

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics