复习
有了对象后思维方式的改变。
昨天构造和析构函数
类的两种自动调用的成员函数,这样的函数,只能自动调用,不需要也不能主动调用。
对象创建和销毁的时候调用,内容上没有要求。
创建对象Person p; Person *p=new Person;这个语句是自己写的,所以可以传参数。
传的参数传给了相应的构造函数。
通过不同参数表可以把他们分开来。
拷贝构造,也是构造函数,调不调完全根据参数表决定。
如果没有参数要传就不要跟空括号()了,跟了会误认为是函数声明 A aa();
而有参的就可以这样写,因为和函数声明的差别在于,参数是否有类型。
无名对象:如果创建对象,用类名直接跟参数。只能被用一次。
析构函数,可以主动调用,对象销毁的时候
如果在动态内存中建立一对象数组,如果释放的时候只不带[]那就能看到只释放一个。
继承
以继承方式使的类的重用和维护都很方便
有了继承后 父类 子类 外部 访问限制 public protected private
私有成员只能在本类的成员函数中访问,子类也不能访问。继承是一定要做的。
以前私有和保护可以看作相同的。
不管那种方式继承,子类总可以访问父类的保护和公共成员
而外部是不可以访问保护成员的。
继承方式,对父类的成员访问限制在子类的表现,影响的是对外的访问限制。
用的时候几乎总是使用public 了解一下就可以。
后面的重点:IO 连表 摸板 相比而言其他都是简单讲。
每个子类会把父类成员继承一份。只是能不能访问。
如何不使用父类的成员 覆盖
覆盖
在子类里写一个和父类同名的成员。自己改写父类的成员,就叫做覆盖
如果再调用同名的成员就调用的是子类成员,如果要使用父类的,就要特别说明用父类的成员。
c++中父子类之间不发生重载,覆盖只要求同名就可以完成。
overhide 父子类中的同名函数,没有重载,只有覆盖。两个不同的类之间没有重载只有覆盖。
调用父类的方法还是要指明是那个类的函数。
定义新类的时候说明一下它继承谁。
全部把父类成员继承过来,再扩展子类自己的新成员
可以认为一子类包含了一个父类对象,加上自己扩展的东西
子类只维护自己扩展的东西,父类呢只负责维护自己的成员,叫做各斯其值。
内存模式
数据和函数共同组成,数据在内存的数据区,函数的代码在代码区,
类的成员的存放顺序不一定连续。
数组的各个元素一定是连续存放的。//前面学的基本的东西都是对的。
sizeof()只会计算数据成员,
构造和析构不能被继承。
数据扩展数据,扩展的函数在代码区,和把父类的对象的存储方式放在一起,就是子类的成员。
调用子类的构造的时候就要调用父类构造,因为在子类无权去初始化父类的数据成员,只能通过父类构造去调用,这个调用也是自动的,所以在子类构造函数第一个语句执行之前,会自动调用父类的构造函数。子类析构执行完之后,再调用父类析构。
如果父类有两个构造函数,那么要在子类调用需要调用的构造,根据传参数决定那个构造函数。
在子类构造函数的初始化列表中用父类类名后面跟上(参数),调用匹配的父类构造。默认没写就不传。传不传怎么传,都是有参数列表来决定。
子类析构执行到最后的一句会调用父类的析构函数,如果主动调子类析构,父类也会被调用。
创建新类,自动重用旧类的东西,对一个已经有的类继承,如果是原始数据类型,一定不可以继承。
结构也可以继承。
多态引入
类和继承都是知识点,现实中的表现:
车辆 自行车 火车 汽车 控制 红灯调用各种车辆的停车方法。
车辆类,永不停息的车俩。希望能统一管理,这时就要以父类身份访问子类函数,这种需求就用多态来实现。
必须是父类的子类,才能用父类来调用子类的函数。
把派生类对象的地址赋给父类指针。从生活逻辑上是可以的,把汽车叫车。从程序逻辑上也是可以的。
它们依然是“is a”的关系,所以继承是多态的
通过指针访问成员函数,通过基类指针调用成员函数,调用的是派生类的函数,这就是多态的本质, 当发生多态的时候,只能把子类当作父类来看。也就是说只能调用父类的成员,而子类所扩展的东西是不能被调用。(动物,汽车);
多态:
在c++里面不是直接使用的。必须要在函数前面加上viatrul关键字,表示虚函数。
只有对虚函数的调用才能自动调用子类的成员函数。
在子类里的函数可加可不加。
多态使用:
1,调用父类的虚函数,子类中的函数也成为虚函数。
2,必须是要有继承关系。
3,要识别这个对象的真实类型,必须是真实的对象,可以用引用或指针。(保证访问的是原始对象)。
多态要求这么三点。
class Mammal{
string type;
public:
Mammal(string type):type(type)//这里是不会混淆的:后只可能是数据成员
{}
virtual ~Mammal(){//虚析构函数
cout<<"~Mammal"<<endl;
}
virtual void eat(){
cout<<type<<" eats"<<endl;
}
void sleep(){
cout<<type<<" sleeps"<<endl;
}
virtual void shout(){
cout<<type<<" shouts"<<endl;
}
};
class Dog:Mammal{
public:
Dog(string type="dog"):Mammal(type)//这里是不会混淆的:后只可能是数据成员
{}
void eat(){
cout<<" eats bone"<<endl;
}
void shout(){
cout<<" wang!"<<endl;
}
~Dog(){
cout<<"~dog "<<endl;
}
};
class Cat:Mammal{
public:
Cat(string type="cat"):Mammal(type)//这里是不会混淆的:后只可能是数据成员
{}
void eat(){
cout<<" eats mouse"<<endl;
}
void shout(){
cout<<" miao!!!"<<endl;
}
};
class Player{
Mammal *p;
public:
Player(Mammal *pm):p(pm){
}
void play(){
p->play();
p->sleep();
p->shout();
}
};
void feed(Mammal *p){
p->eat();
}
void knock(Mammal &p){
m.shout();
}
int main(){
Mammal* p=NULL;
Dog od;
Cat oc;
p=&od;//利用指针实现多态
p->eat();
p->sleep();
p->shout();
p=&oc;//利用指针实现多态
p->eat();
p->sleep();
p->shout();
Player p1(od);
Player p2(oc);
p1.play();
p2.play();
feed(oc);
feed(od);
knock(oc);//用引用来实现
knock(od);
return 0;
}
前一天作业要求:
设备,
大数类,1,定义字符数组。1000位的数据。
2,输入字符串。
3,strlen知道长度。
4,申请空间。
5,两个都这么做。
6,把大数放入对象里。
7,逐位相加。
8,不够的在左边布空格。
9,把短的也延长,把原来的数据复制过去靠右边放
继续多态,
原则:以父类身份管理子类对象。
多态和函数覆盖,他们有一定的关系。
虚函数:
有virtual关键字的函数,就是虚函数,
对同样一个调用,会根据不同的实际类型,调用不同实现。
用一张表保存所有虚函数,如果一个类有虚函数,那么这个类就会保存一分指向虚函数的指针。
class A{
int data;
public:
virtual void disp(){}
};
sizeof(A); //结果是8;
识别对象真实类型。由于识别,调用虚函数比调用普通函数慢。
c++要求效率,所以并没有把虚函数设成默认。
java的每个函数都是虚函数。
如果不加virtual调用始终是父类自己的函数。
构造函数不能是virtual函数,c++创建对象必须有明确的类型,所以它不可能有不确定类型,而析构函数确可以是虚函数。
如果希望操作是根据对象的真实类型来造作,那就把父类的函数写成虚函数。
调用的时候总会调用到子类所在的对象。
对象指针转换
讲义28页,强制类型转换算子。
类型转换(类型)这个转换运算符并没有表明类型转换的目的;
格式
static<类型>(数据)
4个类型转换算子:
static_cast,就是强制类型转换,在数值类型之间转换.
应用于合理的类型转换,它不保证转换的正确性.
情况
普通指针和void指针之间的转换是合理的.
父子类指针之间转换也认为是合理的.
临时对象也被认为是合理的
(A(20)可以认为把20转换成了A也是合理的).
例子
cout<<static_cast<int>(3.5)<<endl;
输出为3.
const_cast,在void和普通指针之间转换,不合理的转换之一.
常量类型转换,把常量变成变量。
const int a=100;
const_cast<int&>(a)=200;
就使用a这个旧变量.
常量变量如果直接的转换成int&
编译器有自动的优化,对常量直接使用a的地方直接用100代替不用内存中的数据.实际上内存中的数据已经改变了.
合理应用的例子:
class Array{
public:
const int length;
Array(int len):length(len){}
void resize(int len){
const_cast<int&>(length)=len;
}
void show(){
cout<<"length="<<len<<endl;
}
}
dynamic_cast,父子类之间的类型转换。有识别类型的功能.
要检查对象的真实类型,转换失败返回一个NULL,如果指针指向对象的真实类型。
把父类指针转换成子类指针,转换的时候会根据类型转换,如果是子类那么就转换成子类,如果不成功就返回null,根据对象的真实类型返回不同的指针.
class A{
public:
virtual void func(){}
};
class B:public A(){};
int main(){
A* p1=new A();
A* p2=new B();
cout<<dynamic_cast<B*>(p1)<<endl;
cout<<dynamic_cast<B*>(p2)<<endl;
delete p1;
delete p2;
}
reinterpret_cast,常量类型转换,把常
评论0