C++中的继承

可能你做题时会遇到这么一个问题:
子类将继承父类的全部内容。
这不肯定是错的嘛,子类只能继承父类的public和protected成员,而不能继承private成员。然后有的教材却说这句话是正确的。

其实,确实不能说这句话有错。

在C++中,有三种继承方式,分别是公有继承、私有继承、保护继承。

公有继承是父类的公有属性变成子类公有属性,父类的保护属性变成子类的保护属性,父类的私有属性并不变成子类的属性,它只能由父类访问,但它却实实在在并子类给“继承”下去了,只能无法直接访问而已。但是可以通过父类的函数来访问,父类的成员函数只能访问父类成员。看下面例子:

#include <iostream>
using namespace std;
class Father{
private:
int a
};
class Son:public{
public:
int a;
};
int main(){
cout<<sizeof(Father)<<endl;
cout<<sizeof(Son)<<endl;
return 0;
}

输出4和8

可见,子类中确实“继承”了父类的private成员。

公有继承、私有继承和保护继承对父类的私有成员的继承方员都一样。对于父类的非私有成员,保护继承把它当成了子类的保护成员,私有继承把它当成了子类私有成员,公有继承保留原样。

然后讲讲可能出现的同名问题。采用同名覆盖的原则,可以用父类名::父类成员名来访问父类的成员。在类外还可以这样访问:

Son a;
a.Father::var=3;//假设父类有一个var变量,子类也有一个var变量,要访问父类的var变量。

再来说说构造函数和析构函数。

若子类的构造函数没有显示调用父类的构造函数,则默认调用父类的无参构造函数。若父类定义了带参构造函数且没有无参构造函数,则子类必须显示调用父类的带参构造函数,否则会出错。

C++中显示调用父类的构造函数是采取构造函数初始化表的形式,如:

Son():Father(){}

最后说一下子对象,看下面例子:

class A{
public:
A(int a){}
};
class B{
public:
A a;
};
int main(){
B object;
return 0;
}

该程序实例化了一个B类对象object,它有一个类型为A的子对象a,但是类A没有无参的构造函数,而在类声明成员时又不能显示指明参数,也就是在类声明时像A a(1);这样的写法是错误的,无法通过编译,这时就必须在A的构造函数初始化表中调用子对象的构造函数,就像这样:

B():a(1){}

若有多个子对象,用逗号隔开就行。

构造函数初始化表先于构造函数体执行。

在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数。对基类和子对象进行清理。调用的顺序与构造函数正好相反:先执行派生类自己的析构函数,对派生类新增加的成员进行清理,然后调用子对象的析构函数,对子对象进行清理,最后调用基类的析构函数,对基类进行清理。

关于C++的一些问题二

引用不需开辟新的内存,只是给一个变量取一个别名。

生成随机数:

cstdlib提供的rand函数用来生成伪随机数序列,产生的值介于0-RAND_MAX。

cstdlib提供的srand(unsigned int)函数将影响rand函数产生的随机数序列,若传入srand函数的参数相同,则rand函数产生的随机数序列是相同的,若没调用srand函数,相当于调用srand(0)。所以,为了实现比较真实的效果,一般会用ctime提供的time函数,把它传给srand函数。如:

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main(){
srand(time(0));
for(int i=0;i<10;i++){
cout<<rand()%11<<” “<<endl;//输出[0,10]之间的随机数。
}
cout<<endl;
}

关于异常处理:

下面引用一段话:

在一个小的程序中,可以用比较简单的方法处理异常。但是在一个大的系统中,如果在每一个函数中都设置处理异常的程序段,会使程序过于复杂和庞大,因此,C++采取的办法是:如果在执行一个函数过程中出现异常,可以不在本函数中立即处理,而是发出一个信息,传给它的上一级,即调用它的函数(本级函数也可以处理自身throw出的异常,不过这就跟设置程序段差不多了),它的上级捕捉到这个信息后进行处理。如果上一级的函数也不能处理,就再传给其上一级,由其上一级处理。如此逐级上送,如果到最高一级还无法处理,最后只好异常终止程序的执行。

这样做使异常的发现与处理不由同一函数来完成。好处是使底层的函数专门用于解决实际任务,而不必再承担处理异常的任务,以减轻底层函数的负担,而把处理异常的任务上移到某一层去处理。这样可以提高效率。

引用结束。

异常若没有被处理,则会中断程序的执行。

catch块比如紧跟在try块之间,且不能单独使用。课件上说可以只有try块而没有catch块,怎么我自己试就不行呢?

try和catch之间的语句必须用花括号括起来,即使只有一条语句。

throw语句:

当仅仅出现throw;这个语句必须出现在catch语句块中,表示把异常传递给上级也就是调用它的函数处理。出现在其它任何位置没有任何意义,虽然编译通过,但是没有任何一个catch能够捕捉到它,包括catch(…),所以异常没有处理,程序中止。

看下面一段代码:

#include <iostream>
using namespace std;
void fun(){
int a=10;
try{
throw a;
}catch(float){
cout<<”fun catch”<<endl;
}
cout<<”fun end”<<endl;
}
int main(){
int a=3;
try{
fun();
}catch(int){
cout<<”main catch”<<endl;
}
cout<<”main end”<<endl;
return 0;
}

输出:
main catch
main end

因为fun函数抛出的异常a没有并本身的catch语句捕获,所以流程转到调用它的函数,本函数中止。若是被本身的catch语句块捕获,则流程转到本函数的catch语句,并且些函数后面的语句也会被执行。如下面一段程序:

#include <iostream>
using namespace std;
void fun(){
int a=10;
try{
throw a;
}catch(float){
cout<<”fun catch”<<endl;
}
cout<<”fun end”<<endl;
}
int main(){
int a=3;
try{
fun();
}catch(int){
cout<<”main catch”<<endl;
}
cout<<”main end”<<endl;
return 0;
}

输出:
fun catch
fun end
main end

 

关于C++的一些问题

C++中是没有abstract这个关键字的,所以定义抽象类也不是用abstract class来定义。那么怎么定义抽象类呢?其实只要类中有纯虚函数,该类就是一个抽象类,不能实例化。抽象类并不要求所有函数都是纯虚函数,只要存在一个或一个以上的纯虚函数,该类就是抽象类。其实这很好理解,抽象类不能被实例化,然后再看看什么是纯虚函数:

virtual void function()=0;

纯虚函数是没有被实现的函数,所以该类当然不能被实例化。

再看看虚函数:

virtual void function();

这个虚函数没有实现(如果也没有在类外实现的话,或者说定义,该句只是声明),所以该类也不能被实例化。那么算不算是抽象类呢?其实我觉得倒底算不算这个问题倒是不重要了。

如果说虚函数有被实现的话,比如:

virtual void function(){}

那么该类就可以被实例化(不考虑其它函数)。

那么虚函数(自然包括纯虚函数),是用来干嘛的呢?是用来实现多态的。

请看下面的代码:

#include <iostream>
using namespace std;
class A{
public:
virtual void fun(){cout<<”A”<<endl;}
void fun2(){cout<<”A”<<endl;}
};
class B:public A{
public:
virtual void fun(){cout<<”B”<<endl;}
void fun2(){cout<<”B”<<endl;}
};

int main(){
A *a=new B();
a->fun();
a->fun2();
return 0;
}
先说明一下,C++里也没有override这个关键词,函数重载直接重新定义就可以了。

这段代码定义了一个指向基类的指针,但它指向的并不一定是基类,还可以是它的任何一个子类。但是用该指针来调用成员函数时,如果该成员函数不是虚函数,则该指针定义时是什么类型,它就调用那个类的成员函数。比如该段代码中a指向的虽然是B的类型,但它定义时是指向基类A的类型,而fun2()函数并不是虚函数,所以a->fun2();调用的是定义它时的类型A的fun函数。

显然这样不能实现多态。

而当成员函数为虚函数时,调用的是则是实际类型的成员函数。比如:

a->fun();

fun()函数是虚函数,a指向的是B类型,所以它调用的是B类型的成员函数。

再说下接口,C++中也没有interface关键字,所以接口类型还是得用抽象类来实现,接口没有数据成员。

还有就是const的问题,const会因为它存在的位置不同而有不同情况,const起是的修饰的作用,到底哪一部分是常量,常量必须初始化并且不能改变。

主要讲一下const的二个部分:

1.修饰一般常量、常数组、常对象

const int a=10;或int const a=10;(好像有人说后面一种是错误的?我编译倒是没错,不过尽量用第一种吧,基本上书上的也是第一种写法)

const int a[3]={1};或int const a[3]={1};此时每个数组元素的值都不能被改变。

2.修饰指针比如有下面一个类A:

class A{
public:
A(int a=0){m=a}
int m;
};

const A *a;或A const *a;

此时a不是常量而是变量。对,这不是我打错,const修饰的是A或者是*a,而a是指针。即a可变,而a指向的对象不可变。也就是说,a->m=0;是非法的,而a=new A();是合法的。

A * const a=new A();(必须初始化,而上一种情况a不是常量,所以不必初始化)

此时a是常量,而a指向的对象则不是常量。所以a->m=0;合法

const A * const a=new A();或
A const * const a=new A();

此时a和a指向的对象都是常量,都不能改变。

到底是指针本身是常量还是它所指向的对象是常量,有这么句口诀:前固值,后固址。

其实只要真正理解了,什么口诀都是浮云。

ubuntu的上网问题

今天终于解决了ubuntu下的上网问题。在windows系统下,需要用中兴认证客户端才能有线上网,问了下电信客服,说我的上网方式是没有用猫的,2M,不是ADSL。所以网络上比较多的关于Linux下ADSL上网的资料根本不能解决我的问题。所以得找个Linux系统下的中兴认证客户端,搜索一下就可以找到,安装方法也包括在了下载文件中:

打开终端,sudo ./client 上网帐号 上网密码

但这时可能会出现command not found,这是因为client没有执行的权限,输入ls -l会发现client只有rw(读写)的权限,并不是rwx,这时改下权限即可:
chmod 700 client

如果成功的话,打开浏览器,就可以浏览网页了。

然后无线上网就比较简单了,只要打开系统设置,点击附加驱动,选择无线网卡的驱动进行激活,重启后就可以搜索到无线网络了。

A星算法

这次要做个程序,涉及到A星算法。

先记录一下似乎不符合主题的东西:
对于graphics绘图,不再需要图像时就调用clear 函数来清除。特别是你想要覆盖的时候,你在原来的图像上绘制,虽然覆盖了原来的图像,清不清除原来的图像表面上看上去效果是一样的,但实际是不一样的。如果你一直在原来的图像上绘图,而不清除原来的图像,随着程序的进行,你的程序就会变得很卡。

还有一个是关于Array类的sort和sornOn函数。sorn函数可以自己写比较函数,sornOn函数可以对数组中的对象按对象的某一个属性值来进行排序。
比如这次A星算法用到的,我对开启列表里的结点按f值的大小来进行排序:
openList.sornOn(“f”,Array.NUMERIC);
默认是按升序来排序,Array.NUMERIC不能少,表示是数值类型。

还有一个是迟迟没花时间去考虑的问题是对数组的深复制、浅复制,还有对象的拷贝构造函数。AS3里好像没有拷贝构造函数这个概念。

下面到A星算法。其实这个算法不难,也就那么几步。但是做任何一个程序都得细心,要不然就会出现各种问题。把你所想的用代码正确的表示出来,这也是一个难点。
比如openList.sornOn(“f”,Array.NUMERIC)起初我就少了Array.NUMERIC,以至于程序出错。这也是经验问题吧,对sornOn函数很陌生。先来看下效果吧:

算法其实不难,步骤也不多,就是需要细心和认真。
把起始点加入开启列表,然后循环遍历开启列表,选取f值最低的结点作为当前结点,然后对当前结点的周围8个结点操作,若是可以直接到达当前结点 ,则把它加入开启列表,并计算它的f值 ,g值,h值,把当前结点作为它的父结点。当把目标结点加入开启列表中,说明找到了最短路径,此时退出循环。或者开启列表为空,表明没有找到通路,也退出循环。

只要把上面的算法描述正确地用代码表示出来了,A星算法也就写出来了。但是在编程过程中还是可能会出现各种错误,比如在不能一步到达对角线结点的情况下把它加入开启列表,此时可能也就会导致一系列接下来的错误了,其它结点的父结点错误,f值,g值,h值错误等等,也都可能是由于前一个错误引起了,而本身没错。

接下来是对A星算法的一个变形,这是这次任务需要的。先来看下效果:

这个跟普通的二维矩阵地图不同,但显然,它也是二维矩阵地图。只是在显示时,有的结点让它偏移一下。周围结点也不是8个,从一个结点到另一个结点的移动耗费相同,不是像前一个的对角线的移动耗费比水平和垂直移动耗费多。核心思想还是一样的。

接着用这个改进后的A星算法做了一个这次任务的原型,再来看下效果:

算是一个回合制游戏吧,你的任务是用鼠标堵住敌人(一个灰色的方块)的去路,当敌人逃离这个地区时,你失败,重新开始,当你让敌人无路可走了,你就胜利了,游戏重新开始。来试试吧。

电脑组装

昨天晚上终于解决了最后一个问题,组装机正常工作了。这次主要碰到了两个问题,一个是开机时会不停地响或者响一声警报。这个可能是内存和显卡的问题,比如没插好之类的。还有一个是CPU风扇不转或者要碰一下才转,这个可能是由于像我这样的新手,没有把扇叶往下压的缘故。网上说,这是风扇的电动机松动,你得稍微用点力往下压,会听到卡的一声。