前言

简单记录一下关键字virtual的相关内容,涉及虚函数虚析构函数纯虚函数等,记录于此,方便自己查阅。

正文

在介绍虚函数前需要了解一下多态

多态是指调用相同函数(接口),但不同对象调用会产生不同的行为(特性)。

多态分为两类:

  1. 静态多态:静态多态在编译期就能确定要调用哪个函数,包含重载和模板两种方式。

  2. 动态多态:在程序运行阶段根据实际情况来确定调用哪个函数,主要通过虚函数来实现。

这里主要介绍虚函数,也就是动态多态。

如果基类与派生类中有同名成员函数,根据类型兼容规则,当使用基类指针或基类引用操作派生类对象时,只能调用基类的同名函数。如果想要使用基类指针或基类引用调用派生类中的成员函数,就需要虚函数解决,虚函数是实现多态的基础。

动态多态的实现需要满足3个条件:

  1. 基类声明虚函数。

  2. 派生类重写基类的虚函数。

  3. 将基类指针指向派生类对象,通过基类指针访问虚函数。

虚函数

虚函数的声明方式是在成员函数的返回值类型前添加virtual关键字,格式如下所示:

class 类名
{
    权限控制符:
    virtual 返回类型 函数名(参数列表)
    ....//略
}

虚函数有如下注意事项:

  1. 只有类的成员函数才能成为虚函数。

  2. 静态成员函数不能是虚函数,因为静态成员函数不受限于某个对象。

  3. 内联函数不能是虚函数,因为内联函数不能在运行中动态确定其位置。

  4. 构造函数不能是虚函数,析构函数通常是虚函数。

重载和覆盖(重写)的区别是:

  1. 重载是同一层次内多个函数名相同;

  2. 覆盖(重写)则是继承层次中多个成员函数的函数原型完全相同。

例子

下面例子有两个,一个有虚函数,一个没有,可以对比看效果。

不使用virtual
#include <iostream>
class Book {
protected:
    int page;
    float value;
public:
    Book(int page, float value) {
        std::cout << "Book(int page, float value)" << std::endl;
        this->page = page;
        this->value = value;
    }
    void printfInfo() {
        std::cout << "Book.printfInfo page : " << page << std::endl;
        std::cout << "Book.printfInfo value : " << value << std::endl;
    }
};

class LuXunBook :public Book {
public:
    LuXunBook(int page, float value) :Book(page, value) {
        std::cout << "LuXunBook(int page, float value)" << std::endl;
        this->page = page;
        this->value = value;
    }
    void printfInfo() {
        std::cout << "LuXunBook.printfInfo page : " << page << std::endl;
        std::cout << "LuXunBook.printfInfo value : " << value << std::endl;
    }
};

没有使用virtual修饰printfInfo(),如果按照如下创建对象

int main() {
    Book* book_2 = new LuXunBook(300, 28.0f);
    book_2->printfInfo();
    delete book_2;
    book_2 = NULL;
    return 0;
}

会发现调用的是父类的方法。

Book(int page, float value)
LuXunBook(int page, float value)
Book.printfInfo page : 300
Book.printfInfo value : 28

按照我们设想,new了子类对象,应该调用子类的方法才对,但并不是!

使用virtual
#include <iostream>
class Book {
protected:
    int page;
    float value;
public:
    Book(int page, float value) {
        std::cout << "Book(int page, float value)" << std::endl;
        this->page = page;
        this->value = value;
    }
    virtual void printfInfo() {
        std::cout << "Book.printfInfo page : " << page << std::endl;
        std::cout << "Book.printfInfo value : " << value << std::endl;
    }
};

class LuXunBook :public Book {
public:
    LuXunBook(int page, float value) :Book(page, value) {
        std::cout << "LuXunBook(int page, float value)" << std::endl;
        this->page = page;
        this->value = value;
    }
    void printfInfo() {
        std::cout << "LuXunBook.printfInfo page : " << page << std::endl;
        std::cout << "LuXunBook.printfInfo value : " << value << std::endl;
    }
};
int main() {
    Book* book_2 = new LuXunBook(300, 28.0f);
    book_2->printfInfo();
    delete book_2;
    book_2 = NULL;
    return 0;
}

使用virtual修饰printfInfo(),那么printfInfo就是虚函数了。

int main() {
    Book* book_2 = new LuXunBook(300, 28.0f);
    book_2->printfInfo();
    delete book_2;
    book_2 = NULL;
    return 0;
}

这次会发现,book_2调用的是子类的重写的方法。

Book(int page, float value)
LuXunBook(int page, float value)
LuXunBook.printfInfo page : 300
LuXunBook.printfInfo value : 28
小结

若类中声明了虚函数,并且派生类重新定义了虚函数,当使用基类指针或基类引用操作派生类对象调用函数时,系统会自动调用派生类中的虚函数代替基类虚函数。

虚析构函数

C++中不能声明虚构造函数,因为构造函数执行时,对象还没有创建,不能按照虚函数方式调用。

但是,在C++中可以声明虚析构函数,格式如下:

virtual ~ 析构函数(){
    //do something
}
例子

下面还是根据上面是否使用virtual修饰对比查询。

不使用virtual
#include <iostream>
class Book {
protected:
	int page;
	float value;
public:
	Book(int page, float value) {
		std::cout << "Book(int page, float value)" << std::endl;
		this->page = page;
		this->value = value;
	}
	~Book() {
		std::cout << "~Book()" << std::endl;
	}
};

class LuXunBook :public Book {
public:
	LuXunBook(int page, float value) :Book(page, value) {
		std::cout << "LuXunBook(int page, float value)" << std::endl;
		this->page = page;
		this->value = value;
	}
	~LuXunBook() {
		std::cout << "~LuXunBook()" << std::endl;
	}
};
int main() {
	Book* book_2 = new LuXunBook(300, 28.0f);
	delete book_2;
	book_2 = NULL;
	return 0;
}

输出

Book(int page, float value)
LuXunBook(int page, float value)
~Book()

从打印信息看,LuXunBook的构造函数和析构函数没有成对出现

使用virtual
#include <iostream>
class Book {
protected:
	int page;
	float value;
public:
	Book(int page, float value) {
		std::cout << "Book(int page, float value)" << std::endl;
		this->page = page;
		this->value = value;
	}
	virtual ~Book() {
		std::cout << "~Book()" << std::endl;
	}
};

class LuXunBook :public Book {
public:
	LuXunBook(int page, float value) :Book(page, value) {
		std::cout << "LuXunBook(int page, float value)" << std::endl;
		this->page = page;
		this->value = value;
	}
	~LuXunBook() {
		std::cout << "~LuXunBook()" << std::endl;
	}
};
int main() {
	Book* book_2 = new LuXunBook(300, 28.0f);
	delete book_2;
	book_2 = NULL;
	return 0;
}

输出

Book(int page, float value)
LuXunBook(int page, float value)
~LuXunBook()
~Book()

这里LuXunBook的构造函数和析构函数成对出现了。

小结

最好把基类的析构函数声明为虚析构函数,即使基类不需要析构函数,也要显式定义一个函数体为空的虚析构函数,这样所有派生类的析构函数都会自动成为虚析构函数。

如果程序中通过基类指针释放派生类对象,编译器能够调用派生类的析构函数完成派生类对象的释放。

纯虚函数

有些函数只需要子类实现,父类只需要声明即可。例如,动物都有叫声,但不同的动物叫声不同,因此基类(动物类)=只需要声明即可,函数的具体实现在各派生类中完成。

在基类中,这样的函数可以声明为纯虚函数。

格式
virtual 函数返回值类型 函数名(参数列表) = 0;

比如

virtual void printfInfo() = 0; //纯虚函数

纯虚函数也通过virtual关键字声明,但是纯虚函数没有函数体。纯虚函数在声明时,需要在后面加上“=0”。

纯虚函数的作用是在基类中为派生类保留一个接口,方便派生类根据需要完成定义,实现多态。派生类都应该实现基类的纯虚函数,如果派生类没有实现基类的纯虚函数,则该函数在派生类中仍然是纯虚函数。

抽象类

如果一个类中包含纯虚函数,这样的类称为抽象类。

抽象类的作用主要是通过它为一个类群建立一个公共接口(纯虚函数),而接口的完整实现由派生类定义,使它们能够更有效地发挥多态性。

注意
  1. 抽象类只能作为基类派生新类,不能创建抽象类的对象,但可以定义抽象类的指针或引用,通过指针或引用操作派生类对象。

    class Book {
    protected:
    	int page;
    	float value;
    public:
    	Book(int page, float value) {
    		std::cout << "Book(int page, float value)" << std::endl;
    		this->page = page;
    		this->value = value;
    	}
    	virtual void printfInfo() = 0;
    };

    Book中的printfInfo()是纯虚函数,此时Book是抽象类。

    int main() {
    	//Book book; //错误,抽象类不允许创建对象。
    	return 0;
    }

    提示:C++ 不允许使用抽象类类型Book的对象。

    虽然不可以创建抽象类对象,但可以定义抽象类的指针或引用

    #include <iostream>
    class Book {
    protected:
    	int page;
    	float value;
    public:
    	Book(int page, float value) {
    		std::cout << "Book(int page, float value)" << std::endl;
    		this->page = page;
    		this->value = value;
    	}
    	virtual ~Book() {
    		std::cout << "~Book()" << std::endl;
    	}
    	virtual void printfInfo() = 0;
    };
    
    class LuXunBook :public Book {
    public:
    	LuXunBook(int page, float value) :Book(page, value) {
    		std::cout << "LuXunBook(int page, float value)" << std::endl;
    		this->page = page;
    		this->value = value;
    	}
    	~LuXunBook() {
    		std::cout << "~LuXunBook()" << std::endl;
    	}
    	void printfInfo() {
    		std::cout << "LuXunBook.printfInfo page : " << page << std::endl;
    		std::cout << "LuXunBook.printfInfo value : " << value << std::endl;
    	}
    };
    int main() {
    	Book* book_1 = new LuXunBook(300, 28.0f);
    	book_1->printfInfo();
    	delete book_1;
    	book_1 = NULL;
    	std::cout << std::endl;
    	LuXunBook luXunBook(180, 18.0f);
    	Book& book_2 = luXunBook;
    	book_2.printfInfo();
    	return 0;
    }

    输出结果

    Book(int page, float value)
    LuXunBook(int page, float value)
    LuXunBook.printfInfo page : 300
    LuXunBook.printfInfo value : 28
    ~LuXunBook()
    ~Book()
    
    Book(int page, float value)
    LuXunBook(int page, float value)
    LuXunBook.printfInfo page : 180
    LuXunBook.printfInfo value : 18
    ~LuXunBook()
    ~Book()
  2. 抽象类可以有多个纯虚函数,如果派生类需要实例化对象,则在派生类中需要全部实现基类的纯虚函数。如果派生类没有全部实现基类的纯虚函数,未实现的纯虚函数在派生类中仍然是纯虚函数,则派生类也是抽象类。

    #include <iostream>
    class Book {
    protected:
    	int page;
    	float value;
    public:
    	Book(int page, float value) {
    		std::cout << "Book(int page, float value)" << std::endl;
    		this->page = page;
    		this->value = value;
    	}
    	virtual ~Book() {
    		std::cout << "~Book()" << std::endl;
    	}
    	virtual void printfInfo() = 0; //打印书籍信息
    	virtual bool reprint() = 0; //书籍是否重印
    };
    class NovalBook : public Book {
    public:
    	NovalBook(int page, float value) :Book(page, value) {
    		std::cout << "NovalBook(int page, float value)" << std::endl;
    		this->page = page;
    		this->value = value;
    	}
    	~NovalBook() {
    		std::cout << "~NovalBook()" << std::endl;
    	}
    	void printfInfo() {
    		std::cout << "NovalBook.printfInfo page : " << page << std::endl;
    		std::cout << "NovalBook.printfInfo value : " << value << std::endl;
    	}
    };
    int main() {
    	//Book book; //错误,Book为抽象类
    	//NovalBook novalBook; // 错误,NovalBook也是抽象类
    	return 0;
    }

    NovalBook只实现了一个纯虚函数,还有一个没有实现,所以依旧是抽象类。

参考文章

  1. 《C++程序设计教程(第2版)》

相关文章

暂无评论

none
暂无评论...