C++三大特性之多态

简述

  • C++有三大特性,封装,继承和多态。本文章就主要介绍下多态。

多态

  • 多态性是面向对象程序设计的重要特性。利用多态性可以设计和实现一个易于扩展的系统。在C++中,多态性是指具有不同功能的函数用同一个函数名,即用同一函数名调用不同内容的函数。
  • 从系统实现的角度看,多态性分为两类 : 静态多态和动态多态
    • 静态多态 : 系统在编译的时候就能知道要调用的是哪个函数。也叫做编译时的多态性。
    • 动态多态 : 程序在运行过程中才动态地确定操作所针对的对象。

静态多态

  • 静态多态主要通过函数重载和运算符重载来实现。
  • 函数重载
    • 函数重载指完成不同功能的函数可以具有相同的函数名,通过函数的参数类型和参数个数来决定具体调用哪一个函数。
    •   #include <iostream>
        #include <functional>
      
        int myAdd(int data1, int data2, int data3) {
        	std::cout << "myAdd 123" << std::endl;
        	return data1 + data2 + data3;
        }
      
        int myAdd(int data1, int data2) {
        	std::cout << "myAdd 12" << std::endl;
        	return data1 + data2;
        }
      
      
      
        int main(){
      
        	myAdd(12, 22);
        	myAdd(12, 22, 33);
      
         	system("pause");
        	return 0;
        }
      
    • 运行结果
    •   myAdd 12
        myAdd 123
        请按任意键继续. . .
      
    • 上面这个例子,两个函数名是一样的,但参数个数不相同,我们就可以通过传入不同的参数个数来调用不同的函数,实现函数重载。也可以通过不同的参数类型来实现函数重载,这个就不举例了。需要注意的是,仅函数返回值不同,不能定义为重载函数。
  • 运算符重载
    • 格式
      •   <返回类型说明符> operator <运算符符号>(<参数表>)
          {
          	函数体
          }
        
    • 示例
    •   #include <stdio.h>
        #include <iostream>
        #include <string.h>
        #include <stdlib.h>
      
        using namespace std;
      
        class String
        {
        private:         //私有成员属性
        	char *ptr;
        	int len;
      
        public:         //共有成员方法
        	String()    //构造方法,分配空间,初始化成员属性
        	{
        		ptr = new char(1);  //指针初始化 
        		*ptr = '\0';
        		len = 0;
        	}
        	String(const char *str) //构造方法
        	{
        		this->len = strlen(str);
        		this->ptr = new char[this->len + 1];
        		memset(this->ptr, 0, this->len + 1);
        		strcpy(this->ptr, str);
        	}
      
        	//析构方法不能重载
        	~String()  //析构方法,动态内存(malloc),动态对象(new)的回收
        	{
        		delete[]this->ptr;  //[]ptr
        	}
      
      
        	void show()
        	{
        		cout << this->ptr << endl;
        	}
      
        	bool operator==(String &str)   //运算符 == 的重载方法
        	{
        		if (strcmp(this->ptr, str.ptr) == 0)
        		{
        			return true;
        		}
        		else
        		{
        			return false;
        		}
        	}
      
        	bool operator>(String &str)   //运算符 > 的重载方法
        	{
        		if (strcmp(this->ptr, str.ptr) > 0)
        		{
        			return true;
        		}
        		else
        		{
        			return false;
        		}
        	}
      
      
        	bool operator<(String &str)  //运算符 < 的重载方法
        	{
        		if (strcmp(this->ptr, str.ptr) < 0)
        		{
        			return true;
        		}
        		else
        		{
        			return false;
        		}
        	}
      
        	String &operator=(const String &str)  //运算符 = 的重载方法
        	{
        		if (this != &str)    //如果赋值运算符左右不相等
        		{
        			delete[]this->ptr;
        			this->len = str.len;
        			this->ptr = new char[this->len + 1];
        			memset(this->ptr, 0, this->len + 1);
        			strcpy(this->ptr, str.ptr);
        		}
        		return *this;
        	}
      
        	String &operator+(const String &str)  //运算符 + 的重载方法
        	{
        		String *p = new String;
        		p->len = this->len + str.len;
        		p->ptr = new char[p->len + 1];
        		memset(p->ptr, 0, p->len + 1);
        		strcpy(p->ptr, this->ptr);
        		strcat(p->ptr, str.ptr);
        		return *p;
        	}
      
        	char operator[](int tmp)  //运算符 [] 的重载方法
        	{
        		return this->ptr[tmp];
        	}
        };
      
      
        int main()
        {
        	String str1("hello word");
        	String str2("welcome to beijing!");
        	String str3;
      
        	str3 = str1 + str2;
        	str1.show();
        	str2.show();
        	str3.show();
      
      
        	system("pause");
        	return 0;
        }
      
      
    • 运行结果
    •   hello word
        welcome to beijing!
        hello wordwelcome to beijing!
        请按任意键继续. . .
      
    • 上面程序实现了一个string类,对常用运算符进行了重载。
    • 需要注意的是,以下运算符不能被重载
    •   .   *    ::   ?:    sizeof
      

动态多态

  • 动态多态实现过程
    • 程序编译时
      • 为每一个有虚函数的类设置一个虚函数表v_table,一个指针数组,存放每个虚函数的入口地址。
      • 在函数调用处插入一个隐藏的,指向虚函数表的指针v_pointer
    • 程序运行时
      • 根据对象的v_pointer,在相应的虚函数表中获得函数入口,来调用正确的函数。
  • 虚函数
    • 动态多态要通过虚函数来实现。虚函数是在基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数。
  • 虚函数使用
    • 虚函数格式
      • virtual <函数返回类型> <函数名>(<参数表>)
    • 虚函数使用
      • 在基类中用virtual声明成员函数为虚函数
      • 在派生类中重新定义此函数
      • 定义一个指向基类的指针(或引用)
      • 通过对该指针(或引用)作同类族对象赋值,调用该类对象同名虚函数
    • 注意
      • 派生类中同名函数必须与基类虚函数完全一致(即函数名、参数个数与类型、返回类型都相同)
      • 派生类必须以公用方式继承
      • 当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数
  • 示例
    •   #include <iostream>
      
        class Base
        {
        public:
        	virtual void show()       //虚方法(函数)
        	{
        		std::cout << "I am base!" << std::endl;
        	}
        };
      
        class Desive1 :public Base
        {
        public:
        	void show()
        	{
        		std::cout << "I am desive1" << std::endl;
        	}
        };
        
        int main()
        {
        	Base *b = new Desive1;
        	b->show();   //通过虚方法,调用派生类方法(实现:通过虚函数表)
        	system("pause");
        	return 0;
        }
      
  • 执行结果
    •   I am desive1
        请按任意键继续. . .
      
    • 从这个例子可以看出,通过虚函数,我们new了一个派生类对象,赋值给基类指针,通过基类指针就可以直接调用派生类方法。
  • 虚析构函数
    • 程序中最好把析构函数声明为虚函数!即使基类不需要析构函数,也显式地定义一个函数体为空的虚析构函数。下面通过一个例子说明以下为什么
    • 示例
      •   #include <iostream>
        
          class Base
          {
          public:
          	virtual void show()       //虚方法(函数)
          	{
          		std::cout << "I am base!" << std::endl;
          	}
          	~Base()
          	{
          		std::cout << "~Base" << std::endl;
          	}
          };
        
          class Desive1 :public Base
          {
          public:
          	void show()
          	{
          		std::cout << "I am desive1" << std::endl;
          	}
          	~Desive1()
          	{
          		std::cout << "~Desive1" << std::endl;
          	}
          };
        
          int main()
          {
          	Base *b = new Desive1;
        
          	b->show();   //通过虚方法,调用派生类方法(实现:通过虚函数表)
        
          	delete b;
        
          	system("pause");
          	return 0;
          }
        
    • 运行结果
      •   I am desive1
          ~Base
          请按任意键继续. . .
        
      • 有没有发现问题,我们去delete基类指针的时候,由于析构函数不是虚函数,没有实现动态多态,所以调用的还是基类的析构函数,这就会导致无法释放派生类的资源,造成内存泄漏。
    • 我们对上面程序进行下修改
      •   #include <iostream>
        
          class Base
          {
          public:
          	virtual void show()       //虚方法(函数)
          	{
          		std::cout << "I am base!" << std::endl;
          	}
          	virtual ~Base()
          	{
          		std::cout << "~Base" << std::endl;
          	}
          };
        
          class Desive1 :public Base
          {
          public:
          	void show()
          	{
          		std::cout << "I am desive1" << std::endl;
          	}
          	~Desive1()
          	{
          		std::cout << "~Desive1" << std::endl;
          	}
          };
        
          int main()
          {
          	Base *b = new Desive1;
        
          	b->show();   //通过虚方法,调用派生类方法(实现:通过虚函数表)
        
          	delete b;
        
          	system("pause");
          	return 0;
          }
        
    • 运行结果
      •   I am desive1
          ~Desive1
          ~Base
          请按任意键继续. . .
        
      • 只需要将基类的析构函数声明为虚函数,再去释放基类指针,基类的析构函数和派生类的析构函数都可以调用到。因此我们一般将基类的析构函数都声明为虚析构函数。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值