C++:函数修饰关键字及应用场景
virtual
场景:在使用多态特性时,将基类中要被子类重写的函数声明为虚函数/纯虚函数。
tips:
对于多态基类,必须声明为virtual否则释放指向派生类对象的基类指针时可能会内存泄漏。
示例:动物基类抽象huntFood方法,子类狗重写对应方法,则需要通过virtual在基类声明。
class Animal
{
private:
/* data */
public:
Animal(/* args */);
~Animal();
virtual void huntFood() = 0;
};
class Dog : public Animal{
public:
void huntFood() override;
int getAge() const;
private:
int age_;
float weight_;
};
const
场景:修饰类的成员函数,声明该成员函数不会修改类的成员变量
tips:
1.如果成员变量需要在const函数中修改,可加mutable修饰
2.如果函数返回指针或引用,常用const保证一致性
示例:getAge的具体实现只涉及到读成员变量,不涉及修改,所以用const修饰。
class Dog : public Animal{
public:
void huntFood() override;
int getAge() const;
private:
int age_;
float weight_;
};
override
场景:显示声明重写基类的虚函数,防止隐式转换或者参数不匹配的情况,要求子类严格重写
tips:
建议所有重写虚函数都在后面加上
示例:使用override关键字后,编译器会检测子类的重载是否重载以及是否格式正确,例如参数以及返回值需一致等
class Animal
{
private:
/* data */
public:
Animal(/* args */);
~Animal();
virtual void huntFood() = 0;
};
class Dog : public Animal{
public:
void huntFood() override;
private:
};
final
场景:
1.禁止被声明的类被继承
2.禁止虚函数被进一步重写
示例:
1.在Shape后面修饰,代表该类无法被继承
class Shape final {
public:
virtual void draw() = 0;
};
class Circle : public Shape { }; // ❌ 编译错误
2.修饰在虚函数后,代表更小的子类无法在重写当前虚函数。
class Base {
public:
virtual void foo();
virtual void bar();
};
class Derived : public Base {
public:
void foo() final; // 允许重写,但禁止 further override
void bar() override;
};
class Further : public Derived {
public:
void foo(); // ❌ 错误!foo 在 Derived 中被标记为 final
void bar(); // ✅ 合法
};
noexcept
场景:声明函数内部不会抛异常,用于类的移动构造和移动赋值构造函数,保证使用vector操作改对象时涉及到扩容支持移动语义扩容,以及部分确定不会抛异常的普通函数中,加快运行速度。
tips:
析构一定noexcept
示例:如果使用std::vector<FileHandle>涉及到扩容时,有下面的noexcept支持就会采取move而非copy
FileHandle(FileHandle &&other) noexcept{
original_file_ = other.original_file_;
other.original_file_ = nullptr;
}
default
场景:对类的构造和析构函数使用,声明使用默认的构造和析构,在存在有参构造时不会有默认的无参构造,这里使用default可保证无参构造仍然存在
示例:在存在有参构造的前提下保证类的无参构造能力
class FileHandle {
public:
// 自定义构造函数
FileHandle(const char* filename) {
// 打开文件...
}
// 显式要求编译器生成默认构造函数
FileHandle() = default;
// 析构函数(可能需要自定义)
~FileHandle() {
if (file_) fclose(file_);
}
private:
FILE* file_ = nullptr;
};
delete
场景:使用类的构造,拷贝构造,拷贝赋值构造等构造函数失效,例如单例模式不允许拷贝和拷贝赋值构造,以及单一资源同样不允许。
示例:封装FILE*因为其不能共享,所以通过delete禁止拷贝和拷贝赋值构造
class FileHandle{
public:
//构造函数,显示构造函数,防止隐式转换,只能通过FileHandle()来进行显示构造,而不是通过字符串来隐式转换构造(形参指定为FileHandle时就会转换)
explicit FileHandle(const std::string &path,const std::string &mode);
//noexcept 析构不能抛异常,默认知道,也可显示声明明确
~FileHandle() noexcept;
//FILE*不可共享,禁止拷贝和赋值
FileHandle(const FileHandle &) = delete;
FileHandle& operator=(const FileHandle&) = delete;
FileHandle(FileHandle &&other) noexcept{
original_file_ = other.original_file_;
other.original_file_ = nullptr;
}
//赋值构造注意规避自赋值情况,
FileHandle& operator=(FileHandle&&other) noexcept{
//赋值的不是自身,就要先关闭自身
if(this != &other){
close();
original_file_ = other.original_file_;
other.original_file_ = nullptr;
}
return *this;
}
private:
FILE* original_file_=nullptr;
};
explicit
场景:主要用于构造函数和类型转换运算符,要求显式类型转换,防止隐式转换
tips:
对所有单参构造函数都加explicit
示例:
class MyString {
public:
MyString(int size) { // 允许从 int 隐式构造
data_ = new char[size];
size_ = size;
}
explicit MyString(int size) { // 禁止隐式转换
data_ = new char[size];
size_ = size;
}
private:
char* data_;
int size_;
};
void print(const MyString& s) {
// 打印字符串...
}
int main() {
print(10); // ❌ 隐式转换!把 10 当作字符串长度传入
}
写写记录一下。
查看20道真题和解析