【编程贴士】编写类与函数时,什么时候用const、static、inline等关键字?(C、C++)

文章目录


前言

我们在编写一个程序时,往往需要编写不同的类,函数功能等,根据不同的应用场景会需要不同的关键字、才能保证写出的代码安全,有效。下面我们就来介绍如何正确使用这些关键字
(ps:自己写代码时发现部分关键字功能用不太好,想了想干脆总结一下)


相关概念

在使用时最好还是理解这些关键字的相关概念,下面会一一列出。在后面应用时也会解释:

const 关键字

深入解析 const 关键字:指针、参数、返回值和类成员函数

inline 关键字

【C++】探索inline关键字:用法、限制与示例代码

static 关键字

【C++】static 关键字的特性 与 问题

friend 关键字

【C++】解析friend关键字:概念、友元函数与类的使用

virtual 关键字(继承)

【c++】继承详解(菱形 / 虚拟继承)


什么时候使用?const

  1. const函数
    • 当一个成员函数不修改类的成员变量时,应该将其声明为const。这表明该函数不会改变对象的状态,有助于在编译时进行优化和保证代码的正确性。例如:
cpp 复制代码
     class MyClass {
     public:
         void print() const { // 不修改任何成员变量
     	 	std::cout << "hello world" << std::endl;
     	 }
     };
  1. const参数
    • 当参数传递给函数时,如果参数在函数内部不需要被修改,应将其声明为const,尤其是对于引用或指针类型。这有助于明确函数不会改变传入的数据,提高代码的安全性和可读性。例如:
cpp 复制代码
   void process(const int& data); // data 在函数内不被修改

对于大多输入型参数一般使用 const type& x; 的形式


  1. 不需要const的参数
    • 如果函数需要修改传入的参数,或如果参数是基本数据类型(如int),通常不需要const。例如:
cpp 复制代码
   void update(int& data); // data 在函数内可能被修改

对于大多输出型参数一般使用 type* x 或 type& x


什么时候使用?inline

inline 关键字 在 C++ 和 C 编程语言中用于指示编译器尝试将函数的代码直接插入到调用它的地方,而不是在程序中生成一个函数调用。这种方式可以减少函数调用的开销,可能提高程序的执行效率(取决于编译器的优化策略)

所在在类内一些偏简单的函数,可以使用inline:

cpp 复制代码
class MyClass {
public:
    inline void setValue(int v) { value = v; }
    inline int getValue() const { return value; }
private:
    int value;
};

什么时候使用?explicit

explicit 关键字在 C++ 中用于防止隐式类型转换,确保构造函数和转换运算符只在显式调用时执行。当你定义一个构造函数或转换运算符时,如果希望防止该函数在赋值或初始化时被自动调用以进行隐式转换,可以使用 explicit。例如:

cpp 复制代码
class MyClass {
public:
    explicit MyClass(int value); // 防止隐式转换
};

// 使用
MyClass obj1 = 5; // 错误:不能进行隐式转换,因为构造函数是 explicit
MyClass obj2(5);  // 正确:显式调用构造函数

在这个例子中,explicit 确保只有在显式地调用 MyClass(int) 构造函数时才会进行转换,而不是在赋值或初始化时自动转换。


什么时候使用?friend

friend通常用于友元函数、友元类、友元成员函数的声明,其允许非成员函数或其他类访问类的私有成员和保护成员:

  1. 友元函数

    • 友元函数是一个非成员函数,但它可以访问类的私有成员和保护成员。声明友元函数的语法如下:
    cpp 复制代码
    class MyClass {
    private:
        int privateData;
    public:
        MyClass() : privateData(0) {}
    
        // 声明友元函数
        friend void showData(const MyClass& obj);
    };
    
    // 定义友元函数
    void showData(const MyClass& obj) {
        std::cout << "Private data: " << obj.privateData << std::endl;
    }

    对于本例:showData 函数被声明为 MyClass 的友元函数,因此它可以访问 privateData 成员。


  1. 友元类

    • 友元类可以访问另一个类的私有和保护成员。
    cpp 复制代码
    class MyClassB; // 前向声明
    
    class MyClassA {
    private:
        int privateData;
    public:
        MyClassA() : privateData(42) {}
    
        // 声明 MyClassB 为友元类
        friend class MyClassB;
    };
    
    class MyClassB {
    public:
        void display(const MyClassA& obj) {
            std::cout << "Private data from MyClassA: " << obj.privateData << std::endl;
        }
    };

    对于本例:MyClassB 被声明为 MyClassA 的友元类,因此 MyClassB 可以访问 MyClassAprivateData 成员。


  1. 友元成员函数

    • 如果你希望其他类的某个特定成员函数可以访问类的私有和保护成员,可以声明这个成员函数为友元:
    cpp 复制代码
    class MyClassC;
    
    class MyClassA {
    private:
        int privateData;
    public:
        MyClassA() : privateData(99) {}
    
        // 声明 MyClassC 的成员函数 foo 为友元
        friend void MyClassC::foo(const MyClassA& obj);
    };
    
    class MyClassC {
    public:
        void foo(const MyClassA& obj) {
            std::cout << "Private data from MyClassA: " << obj.privateData << std::endl;
        }
    };

    对于本例:MyClassC 的成员函数 foo 被声明为 MyClassA 的友元,因此 foo 可以访问MyClassAprivateData 成员。


什么时候使用?static

  1. 静态局部变量 :在函数内部声明为 static 的变量在函数调用之间保持其值。例如:

    cpp 复制代码
    void counter() {
        static int count = 0;
        count++;
        std::cout << count << std::endl;
    }

    每次调用 counter 函数时,count 的值不会被重置。

  2. 静态成员变量 :在类中声明为 static 的成员变量被所有对象共享,属于类而非某个特定对象。例如:

    cpp 复制代码
    class MyClass {
    public:
        static int staticVar;
    };
    
    int MyClass::staticVar = 0;

    可以通过类名来访问它,如 MyClass::staticVar

  3. 静态成员函数 :在类中声明为 static 的成员函数只能访问静态成员变量和其他静态成员函数。例如:

    cpp 复制代码
    class MyClass {
    public:
        static void staticFunc() {
            // 只能访问静态成员
        }
    };
  4. 静态全局变量和函数 :在文件级作用域中声明为 static 的变量或函数只对当前文件可见,避免了名称冲突。例如:

    cpp 复制代码
    static int localVar = 0; // 只在当前文件中可见
    
    static void localFunc() {
        // 只在当前文件中可见
    }

什么时候使用?virtual

在 C++ 中,virtual 关键字用于实现多态性(Polymorphism)。

  1. 虚拟函数(Virtual Functions)

虚拟函数用于在基类中定义接口,使得派生类可以重写这些接口的方法。这是实现运行时多态的主要方式。使用 virtual 关键字声明虚拟函数时,派生类可以重写(override)这个函数,而实际调用的函数将取决于对象的实际类型(即运行时类型)。示例如下:

cpp 复制代码
#include <iostream>

class Base {
public:
    virtual void show() { // 声明为虚拟函数
        std::cout << "Base class show()" << std::endl;
    }
};

class Derived : public Base {
public:
    void show() override { // 重写虚拟函数
        std::cout << "Derived class show()" << std::endl;
    }
};

int main() {
    Base* b;
    Derived d;

    b = &d;

    b->show(); // 输出: "Derived class show()"
    return 0;
}

在这个例子中,即使 bBase 类型的指针,但它实际指向 Derived 类型的对象。调用 b->show() 时,运行时决定调用 Derived 类的 show() 方法。

  1. 虚拟析构函数(Virtual Destructors)

如果你使用基类指针来删除派生类对象,那么基类的析构函数应该是虚拟的,以确保派生类的析构函数也会被调用,避免资源泄漏。例如:

cpp 复制代码
#include <iostream>

class Base {
public:
    virtual ~Base() { // 虚拟析构函数
        std::cout << "Base destructor" << std::endl;
    }
};

class Derived : public Base {
public:
    ~Derived() override { // 重写析构函数
        std::cout << "Derived destructor" << std::endl;
    }
};

int main() {
    Base* b = new Derived();
    delete b; // 调用 Derived 的析构函数,然后调用 Base 的析构函数
    return 0;
}

在这个例子中,如果 Base 类的析构函数不是虚拟的,则 delete b 只会调用 Base 类的析构函数,Derived 类的析构函数不会被调用,这可能导致资源泄漏。

  1. 纯虚拟函数(Pure Virtual Functions)

纯虚拟函数是虚拟函数的一种特殊情况,声明为纯虚拟函数的类称为抽象类,不能被实例化。派生类必须重写所有纯虚拟函数才能成为具体类。声明纯虚拟函数的语法如下:

cpp 复制代码
class AbstractBase {
public:
    virtual void pureVirtualFunction() = 0; // 纯虚拟函数
};

在这个例子中,AbstractBase 是一个抽象类,不能直接实例化。任何派生自 AbstractBase 的类必须实现 pureVirtualFunction() 才能实例化。

  1. 虚拟继承(Virtual Inheritance)

虚拟继承用于解决多重继承中的菱形继承问题。它确保只有一个基类子对象实例被共享。虚拟继承声明如下:

cpp 复制代码
class A {
public:
    virtual void foo() {}
};

class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};

在这个例子中,BC 都虚拟继承自 A,从而在 D 类中只有一个 A 类的实例。

相关推荐
阿伟*rui1 小时前
配置管理,雪崩问题分析,sentinel的使用
java·spring boot·sentinel
XiaoLeisj3 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck3 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei3 小时前
java的类加载机制的学习
java·学习
励志成为嵌入式工程师4 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉5 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer5 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq5 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml45 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~5 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端