在C++中,final是一个关键字,用于修饰类的成员变量和成员函数。
1.final修饰成员变量:当一个类中的成员变量被声明为final时,它就变成了常量,即它的值不能再被修改。final修饰的成员变量必须在类定义中进行初始化,且只能初始化一次。
假设我们有一个名为Person的类,其中包含一个成员变量name,我们想将其声明为final:
class Person
{
public:
final string name; // 将name声明为final
Person(string n) : name(n) {} // 初始化name
};
现在,我们可以创建一个Person对象并初始化其name属性:
int main()
{
Person p("Alice"); // 正确使用final修饰的成员变量
p.name = "Bob"; // 错误:无法修改final修饰的成员变量
return 0;
}
在这个例子中,我们将name变量声明为final,这意味着它不能被修改。当我们尝试将其值更改为"Bob"时,编译器会报错,因为这是不允许的。只有在类定义中进行初始化后,我们才能访问和修改name变量的值。
2.final修饰成员函数:当一个类中的成员函数被声明为final时,它就变成了不可调用的虚函数,即它的指针不能被指向派生类的函数重载覆盖。final修饰的成员函数必须在类定义中进行声明,且只能声明一次。
class MyClass
{
public:
void foo() const final; // foo是final修饰的成员函数
};
class MyDerivedClass : public MyClass
{
public:
void foo() const override final; //error: cannot override 'const' final function 'MyClass::foo() const'
};
3.final修饰构造函数:当一个类中的构造函数被声明为final时,它就变成了不可调用的构造函数,即它的指针不能被指向派生类的构造函数重载覆盖。final修饰的构造函数必须在类定义中进行声明,且只能声明一次。
当一个类中的构造函数被声明为final时,它就不能被派生类的构造函数重载覆盖,也就是说,派生类不能再定义与该构造函数相同的构造函数。这样可以保证该类的对象在创建时一定会调用该构造函数,从而保证了该类的一些重要属性或状态的初始化。
举个例子,假设有一个基类Animal,其中有一个构造函数Animal(string name),用于初始化动物的名字。现在我们希望这个构造函数不能被派生类重载覆盖,可以将其声明为final:
class Animal
{
public:
final Animal(string name) : m_name(name) {}
// ...
private:
string m_name;
};
class Dog : public Animal
{
public:
// 这里无法定义与Animal(string name)相同的构造函数
// ...
};
这样,无论是Dog还是其他派生类,都不能再定义与Animal(string name)相同的构造函数,从而保证了Animal类的对象在创建时一定会调用该构造函数。
如果将Animal类中的构造函数声明从final改为不final,那么在Dog类中就可以定义与Animal(string name)相同的构造函数了。这将导致一个问题:当创建一个Dog对象时,会调用哪个构造函数?
在这种情况下,编译器将选择调用Dog类中的构造函数,而不是Animal类中的构造函数。这意味着,Dog类的构造函数将覆盖Animal类的构造函数,并且Dog对象的属性将被初始化为默认值。
因此,如果您希望确保在创建Animal对象时始终调用其构造函数,则应将其构造函数声明为final。如果您希望允许派生类定义与其基类构造函数相同的构造函数,则应将其构造函数声明为非final。
下面是一个例子,展示了将Animal类中的构造函数声明从final改为非final后,Dog类可以定义与Animal(string name)相同的构造函数的情况:
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
Animal(string name) : m_name(name) {}
void setName(string name) { m_name = name; }
private:
string m_name;
};
class Dog : public Animal
{
public:
Dog(string name) : Animal(name) {} // 这里可以定义与Animal(string name)相同的构造函数了
void bark() { cout << "Woof!" << endl; }
};
int main()
{
Dog dog("Buddy");
dog.setName("Rover");
dog.bark();
return 0;
}
在上面的例子中,我们创建了一个名为Dog
的派生类,它继承自Animal
类。在Dog
类中,我们定义了一个与Animal
类中的构造函数相同的构造函数,并将其声明为非final。然后,我们在主函数中创建了一个Dog
对象,并调用了它的方法来输出一条消息和设置其名称。由于Dog
类中的构造函数与Animal
类中的构造函数具有相同的参数列表和返回类型,因此编译器会选择调用Dog
类中的构造函数来初始化其基类Animal
对象的属性。