C++特性
C++特性:
1、封装:实现代码模块化
2、继承:实现代码扩展
3、多态:
3.1静态多态:函数重载,泛型编程
3.2动态多态:虚函数
封装
封装:类是某个对象的定义,包含了对象动作方式的信息,例如对象的名称、方法、属性和事件
封装的类的访问类型:
1、public(公有):类中成员可以在类外访问
2、private(私有):类中成员只能被**该类**的成员函数访问
3、protected(保护):成员只能被**该类**的成员函数或者**派生类**的成员函数访问
数据成员通常是私有的,成员函数通常有一部分是公有的,一部分是私有的。
类的接口:类的公有的函数可以在类外被访问,也被称之为类的接口
封装的优点:
1、程序更模块化
2、更容易读
3、提升代码重用到更高的层次
4、保密性和跨平台性
继承
继承:允许依据另一个类来定义一个类(已有的类为基类,新建的类为派生类,)
继承的优点:
1、使得创建和维护一个应用程序变得更容易
2、使达到**重用代码功能**和**提高执行时间**的效果
多态
多态:调用成员函数时,会根据调用函数的对象的类型来执行不同的函数
下面通过几部分代码来说明问题
using namespace std;
class Base{
public:
void Test(){
cout<<"对基类中的函数调用"<<endl;
}
};
class Derived : public Base{
public:
// void Test(){
// cout<<"对派生类中的函数调用"<<endl;
// }
};
int main(){
Base b;
Derived d;
b.Test();
d.Test();
return 0;
}
当把派生类中的方法注释时,输出为
派生类中的方法不注释时,输出为
using namespace std;
class Base{
public:
void Test(){
cout<<"对基类中的函数调用"<<endl;
}
};
class Derived : public Base{
public:
void Test(){
cout<<"对派生类中的函数调用"<<endl;
}
};
int main(){
Base b;
Derived d;
b.Test();
d.Test();
d.Base::Test(); //输出->基类函数调用
return 0;
}
根据上诉输出可知,派生类虽然继承了基类中的Test()函数,但是由于派生类中本身就含有Test()函数,此时构成了重定义(相当于隐藏基类中的Test()函数)
上诉的派生类可以通过d.Base::Test()来获取基类中的成员函数
指针方法
引用方法
上诉代码,如果我们在基类中的Test()函数前面加上virtual,就可以实现**动态绑定**
using namespace std;
class Base{
public:
virtual void Test(){//注意在这里声明虚函数
cout<<"对基类中的函数调用"<<endl;
}
};
class Derived : public Base{
public:
void Test(){
cout<<"对派生类中的函数调用"<<endl;
}
};
int main(){
Base b;
Derived d;
// b.Test();
// d.Test();
// d.Base::Test(); //输出->基类函数调用
Base &pb=b;
Derived &pd=d;
pb.Test();
pd.Test();
Base &pb2=d;
pb2.Test();
return 0;
}
由输出可知,加上virtual之后,Base &pb2=d; pb2.Test();输出的就不是基类中的成员函数,而是派生类中的成员函数
上诉的代码测试中,我们可能会发现一个问题,当基类指针指向子类时(C++继承中有赋值兼容),为什么还会出现调用基类自己的Test()函数**???**
原因:静态联编(绑定),在**编译时期**就将函数实现和函数调用关联起来,不管是引用还是指针在编译时期都是基类,所以自然调用的是基类中的成员函数
为了解决上诉问题,引入动态联编(绑定),即通过**继承+虚函数**来实现,只有在**程序运行期间**(非编译期间)才能判断所引用对象的实际类型,并根据实际类型来调用相应的方法
具体操作就是用virtual关键字修饰类的成员函数,指明该函数为虚函数,并且派生类需要重新实现该成员函数,编译器将实现动态绑定。
静态绑定:速度快、效率高,但是灵活性不够
动态绑定:灵活性强,但是效率低
对于同一个对象的引用,采用不同的联编方式将会被联编到不同类的对象上。即不同联编可以选择不同的实现,这便是**多态性**
可以再看看下面两段代码的对比
using namespace std;
class Base{
public:
// virtual void Test(){
void Test(){
cout<<"对基类中的函数调用"<<endl;
}
};
class Derived : public Base{
public:
void Test(){
cout<<"对派生类中的函数调用"<<endl;
}
};
int main(){
Base *t = new Derived();
t->Test();
return 0;
}
using namespace std;
class Base{
public:
virtual void Test(){
// void Test(){
cout<<"对基类中的函数调用"<<endl;
}
};
class Derived : public Base{
public:
void Test(){
cout<<"对派生类中的函数调用"<<endl;
}
};
int main(){
Base *t = new Derived();
t->Test();
return 0;
}
using namespace std;
class Base{
public:
// virtual void Test1(){
void Test1(){
cout<<"对基类中的函数调用"<<endl;
}
};
class Derived : public Base{
public:
void Test2(){
cout<<"对派生类中的函数调用"<<endl;
}
};
int main(){
// Base *t = new Derived();
// t->Test1();
Base b;
Derived d;
Base *t = &d;
t->Test2();
return 0;
}
注意上诉的报错,当基类和派生类中的成员函数名不同时,像这种用父类的指针指向派生类,就会触发静态绑定
简单总结
1、若一个基类指针指向派生类对象,那么该指针只能访问基类中的函数(静态绑定)
2、若派生类指针指向基类对象,需要做强制转换(explicit cast)
3、若基类和派生类定义了相同名称的成员函数,想要通过指针调用成员函数就是看动态绑定还是静态绑定
几个重要概念
**重载**:在**同一作用域**中允许有多个**同名函数**,这些同名函数的参数列表不同,包括的参数个数不同、类型不同、次序不同,另外也不需要返回值相同
重写(覆盖):(比如C++中虚函数允许子类重新定义父类中的成员函数,子类重新定义父类的做法就是覆盖)(实现过程前面代码)
重定义(**同名**隐藏):当子类继承父类的函数时,若子类没有函数,依然可以调用父类的函数;若子类已经重定义,则会调用自己的函数。(实现过程前面代码)**注意函数名相同**
重写和重定义类似,区别是写重写的函数是否为**虚函数**,只有重写了虚函数才算体现出C++的多态性,否则为重定义
**虚函数**:在基类中使用virtual关键字声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要**静态链接**到该函数
**动态链接**:在程序中任意点可以根据所调用的**对象类型**来**选择调用的函数**
纯虚函数:纯虚函数是在基类中声明的虚函数,它可以在基类中有定义,而且派生类必须定义自己的实现方法。
**基类不能生成对象**,可以使用指针或者引用派生类对象。基类不在基类中实现纯虚函数的方法是在函数原型后加“=0”
引入纯虚函数的原因
1、为了方便使用多台特性
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
为了解决上述两个问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。
注意:纯虚函数不能实例化(不能生成对象)