上次的总结是类与对象,这次是最近学的对象的初始化、复制和销毁,运算符重载,组合与继承,虚函数与多态,面向对象程序设计的特点:封装、继承、多态就都学完了。
第七章对象的初始化、复制和销毁:
一、对象初始化
不同的初始化形式,对于类类型的对象来说,意味着要调用不同的构造函数。对象初始化有4种:默认初始化、直接初始化、拷贝初始化、列表初始化。
1、默认初始化:
定义对象时没有指定初值,调用类中的默认构造函数,对象被默认初始化。
例如:
Student name; //调用Student()
Student name[2]; //调用两次Student(),初始化每个数组元素
2、直接初始化:
初始值在圆括号“()”中,可以提供多个初始值,根据初始值类型和个数直接调用最匹配的构造函数。
例如:
Student name(mingzi);//调用和mingzi类型匹配的Student(string)
3、拷贝初始化:
用赋值号“=”初始化一个对象时,执行拷贝初始化,编译器用赋值号右边的初始值创建一个对象,复制给新创建的对象。赋值号右边的初始值只能有一个,调用与初始值类型匹配的构造函数。
例如:
Student name = mingzi; //用mingzi调用Student(string),复制给name
4、列表初始化:
用花括号“{}”中的初始值构造对象,调用相应的构造函数,与直接初始化类似。
例如:
Student number{123};
花括号也可以是初始值列表,用来初始化数组的每个元素,此时对每个值调用构造函数,创建数组元素,如果初始值的个数少于数组大小,对后面的元素调用默认构造函数来初始化
例如:
Student number[3] = {1,2}; //用1和2调用两次Student(int)创建nubmer[0]和number[1],number[2]则调用Student()
二、,默认构造函数
通常需要为类定义一个默认构造函数,如果类没有定义任何构造函数,编译器会在需要时自动合成一个默认构造函数,一旦定义了构造函数,即使不是默认构造函数,编译器也不再合成,当对象被默认初始化或值初始化时自动执行默认构造函数。
class X{
int m,n;
public:
X(){m=0;n=0;}//默认构造函数
X(int x,int y){m=x;n=y;}//构造函数
};
int main()
{
X x1;//调用默认构造函数X()
X x2(1,2)//调用构造函数X(int,int)
}
三、析构函数
对象离开作用域时析构函数被自动调用,析构函数包括函数体和隐式的析构部分。首先执行函数体,然后执行析构部分销毁成员,而构造函数却相反,析构函数销毁成员时按初始化的逆序销毁,销毁方式完全依赖于成员类型,销毁类类型的成员需要执行成员自己的析构函数。
四、拷贝构造函数
浅复制:用已有对象去初始化另一个同类型的对象
X one;
X two(one); //用one初始化同类型对象two
用one 初始化two 时需要构造函数X(X&),称为拷贝构造函数
如果在类中没有定义这样的构造函数,编译器会自动合成一个,默认的行为是逐个成员复制
X two(one)就是用one 中的每个成员分别去初始化two 的每个对应的成员
类中包含指针或引用成员,浅复制就不恰当了。
class X {
int m;
int& r;
int* p;
public:
X(int mm=0):m(mm),r(m),p(&m){}
void changep(){*p = 10;}
void changer(){ r = 5;}
};
int main()
{
X a;
X b(a);
b.changep();
b.changer();
}
深复制:
class X {
int m;
int& r;
int* p;
public:
X(int mm=0):m(mm),r(m),p(&m){}
X(const X&a):m(a.m),r(m),p(&m){}
void changep(){*p = 10;}
void changer(){ r = 5;}
};
int main()
{
X a;//X(int):a.m=0;a.r=a.m,a.p=&a.m
X b(a);//X(const X&):b.m=0;b.r=b.m,b.p=&b.m
b.changep();//b.m=10
B.changer();//b.m=5 不会影响a
}
浅复制实现方式是简单赋值,深复制是申请资源以后简单赋值(已知对象创建)
五、拷贝赋值运算符
类可以通过重载赋值运算符控制其对象如何赋值,类X的赋值运算符要定义为类X的成员函数,X& operator=(const X&);
如果类中没有重载operator=(),编译器将在需要时自动合成一个,其行为是按成员赋值,对于复杂的类,尤其是包含指针成员时,应该显式地创建operator=(),编写拷贝赋值运算符时,要切记两点:1、如果一个对象给自身赋值,赋值运算符必须能正确工作。2、大多赋值运算符组合了析构函数和拷贝构造函数的工作。
第八章运算符重载:
重载运算符声明为友元,以便访问私有数据成员
运算符重载的实质:运算符重载仅仅提供了一种语法上的方便,是以另外一种方式调用函数。
不能滥用运算符重载
只有至少一个操作数是用户自定义类型时,才可能调用重载的运算符,只有在能使类的代码更易读、使类对象的操作方式更符合一般习惯时,才重载运算符,运算符重载不会改变内置类型的表达式中运算符的含义。
不建议重载的特殊运算符:逻辑与(&&)、逻辑或(||)、逗号运算符(,)、取地址运算符(&)
可以重载的运算符:
对于类类型的参数,如果仅仅只是读参数的值,而不改变参数,应该作为const引用来传递,当运算符函数是类的成员函数时,就将其定义为const成员函数。
输入输出运算符的函数原型:
istream& operator>>(istream&, type&);
ostream& operator<<(ostream&, const type&);
第九章组合与继承:
一、组合(包含):将一个类的对象作为另一个类的成员
成员对象是组合对象的一部分,随着组合对象的创建而创建,随着组合对象的撤销而撤销,成员对象不作为独立元素对外部展现。
例如:Car和Engine的组合关系:汽车中包含引擎,引擎是汽车的一部分,对外展现的只是汽车的整体功能
class Engine{
public:
void fire(void){...}
void stall(void){...}
};
class Car {
public:
void run() { engine.fire(); }
void stop() { engine.stall();}
private:
Engine engine;
};
int main()
{
Car benz;
benz.run();
benz.stop();
}
创建包含对象成员的组合对象时,会执行成员类的构造函数初始化对象成员,如果成员对象所属的类不存在默认构造函数,会引起编译错误,成员对象的初始化使用初始化列表语法。
当组合对象被撤销时,会执行其析构函数,成员对象的析构函数也会被执行。
析构函数的执行次序和构造函数相反。
二、复用类:当一个类的功能与所需类的功能类似,应用组合,提供新接口
class XCircle {
public:
XCircle(); //构造函数
void Xdraw(); //绘制圆形
double calc_area(); //计算面积
double calc_perimeter();//计算周长
void zoom(double factor); //按比例缩放
};
class Circle {
public:
Circle() : xc() {}
void draw(){xc.Xdraw();}
double area() {return xc.calc_area();}
double perimeter(){return xc.calc_perimeter();}
void scale(double factor) {xc.zoom(factor);}
private:
XCircle xc;
};
三、继承:在已有类的基础上创建新类的过程
class 派生类名 : 基类名表
{
数据成员和成员函数声明
};
基类名表 构成:访问控制 基类名1, 访问控制 基类名2 ,… , 访问控制 基类名n
访问控制 表示派生类对基类的继承方式,使用关键字:
public公有继承
private私有继承
protected 保护继承
派生类不能直接使用基类的私有成员
派生类的生成过程经历了三个步骤:
1、吸收基类成员(全部吸收(构造函数、析构函数除外),但不一定可见)
2、改造基类成员
3、添加派生类新成员
为了帮助我更好的理解继承,我自己打了下面代码进行了调试
class Person{
string name;
int age;
public:
Person(){};
Person(string n, int a):name(n),age(a){};
string getname(){return name;}
int getage(){return age;}
bool operator < (const Person &p) const {
return name<p.name&&age<p.age;
}
bool operator == (const Person &p) const {
return name==p.name&&age==p.age;
}
friend ostream &operator << (ostream &os,const Person &p){
os<<p.name<<" "<<p.age<<endl;
return os;
}
friend istream &operator >> (istream &is,Person &p){
is>>p.name;
if(p.name=="end")
return is;
is>>p.age;
}
};
class Personop{
Person person;
vector<Person> vp;
vector<Person>::iterator pit;
public:
void push(Person p){return vp.push_back(p);}
void addp();
void sendp();
};
void Personop::addp(){
while(1)
{
cin>>person;
if(person.getname()=="end") break;
push(person);
}
};
void Personop::sendp(){
for(pit=vp.begin();pit!=vp.end();pit++)
cout<<*pit;
};
class Student:public Person{
public:
Student(){};
Student(string na,int ag):Person(na,ag){};
};
class Studentop:public Personop{
public:
void adds(){addp();}
void sends(){sendp();}
};
基类与派生类关系:
第十章虚函数与多态:
一、多态:指一个名字,多种语义或界面相同,多种实现。
冠以关键字 virtual 的成员函数称为虚函数,实现运行时多态的关键首先是要说明虚函数,另外,必须用基类指针调用派生类的不同实现版本。
通过基类指针,只能访问从基类继承的成员
注意:
1、一个虚函数,在派生类层界面相同的重载函数都保持虚特性
2、虚函数必须是类的成员函数
3、虚函数可以是另一个类的友元
4、析构函数可以是虚函数,但构造函数不能是虚函数
5、在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同(仅仅返回类型不同,C++认为是错误重载,函数原型不同,仅函数名相同,丢失虚特性 )
二、纯虚函数
纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。
纯虚函数是一个在基类中说明的虚函数,在基类中没有定义,要求任何派生类都定义自己的版本。
纯虚函数为各派生类提供一个公共界面。
纯虚函数说明形式:
virtual 类型 函数名(参数表)= 0 ;
例如:virtual void draw ( ) = 0 ;// 纯虚函数
一个具有纯虚函数的基类称为抽象类。
下面是自己测试的多态代码和截图(加了virtual后输出的不同)
class Base
{
public :
Base(char xx) { x = xx; }
void who() { cout << "Base class: " << x << "\n" ; }
protected:
char x;
};
class First_d:public Base
{
public :
First_d(char xx, char yy):Base(xx){ y = yy; }
void who() { cout << "First derived class: "<< x << ", " << y << "\n" ; }
protected:
char y;
} ;
class Second_d : public First_d
{
public :
Second_d( char xx, char yy, char zz ) : First_d( xx, yy ) { z = zz; }
void who() { cout << "Second derived class: "<< x << ", " << y << ", " << z << "\n" ; }
protected:
char z;
} ;
int main()
{
Base B_obj( 'A' ) ;
First_d F_obj( 'T', 'O' ) ;
Second_d S_obj( 'E', 'N', 'D' ) ;
Base * p ;
p = & B_obj ;
p -> who() ;
p = &F_obj ;
p -> who() ;
p = &S_obj ;
p -> who() ;
}
class Base
{
public :
Base(char xx) { x = xx; }
virtual void who() { cout << "Base class: " << x << "\n" ; }
protected:
char x;
};
class First_d:public Base
{
public :
First_d(char xx, char yy):Base(xx){ y = yy; }
void who() { cout << "First derived class: "<< x << ", " << y << "\n" ; }
protected:
char y;
} ;
class Second_d : public First_d
{
public :
Second_d( char xx, char yy, char zz ) : First_d( xx, yy ) { z = zz; }
void who() { cout << "Second derived class: "<< x << ", " << y << ", " << z << "\n" ; }
protected:
char z;
} ;
int main()
{
Base B_obj( 'A' ) ;
First_d F_obj( 'T', 'O' ) ;
Second_d S_obj( 'E', 'N', 'D' ) ;
Base * p ;
p = & B_obj ;
p -> who() ;
p = &F_obj ;
p -> who() ;
p = &S_obj ;
p -> who() ;
}