C/C++ 关键关键字面试指南 (const, static, volatile, explicit)

目录

[1. const (常量)](#1. const (常量))

核心用法与考点:

[1.1 修饰变量和指针](#1.1 修饰变量和指针)

[1.2 修饰函数参数和返回值](#1.2 修饰函数参数和返回值)

[1.3 修饰类成员函数(C++ 特有)](#1.3 修饰类成员函数(C++ 特有))

[1.4 代码示例:](#1.4 代码示例:)

[2. static (静态)](#2. static (静态))

核心用法与考点:

[2.1 修饰局部变量(改变存储期)](#2.1 修饰局部变量(改变存储期))

[2.2 修饰全局变量和函数(改变链接性)](#2.2 修饰全局变量和函数(改变链接性))

[2.3 修饰类成员变量(C++ 特有)](#2.3 修饰类成员变量(C++ 特有))

[2.4 修饰类成员函数(C++ 特有)](#2.4 修饰类成员函数(C++ 特有))

[2.5 代码示例:](#2.5 代码示例:)

[3. volatile (易失性)](#3. volatile (易失性))

核心用法与考点:

[3.1 阻止编译器优化](#3.1 阻止编译器优化)

[3.2 常见应用场景:](#3.2 常见应用场景:)

[3.3 与 const 的结合](#3.3 与 const 的结合)

[4. explicit (显式)](#4. explicit (显式))

核心用法与考点:

[4.1 修饰单参数构造函数](#4.1 修饰单参数构造函数)

[4.2 修饰转换函数(C++11/14)](#4.2 修饰转换函数(C++11/14))


1. const (常量)

定义: 用于声明常量或指定不允许修改的数据。它强制执行"常量正确性"(Const Correctness),是 C++ 中提高代码健壮性和可读性的核心机制。

核心用法与考点:

1.1 修饰变量和指针

理解 const 靠近谁,谁就是常量。

声明方式 含义 可修改性
const int a = 10; 常量整数 a 的值不可改
const int* p; 指向常量的指针 *p (值)不可改,p (地址)可改
int* const p; 常量指针 *p (值)可改,p (地址)不可改
const int* const p; 指向常量的常量指针 *pp 都不可改
1.2 修饰函数参数和返回值
  • 修饰参数: 使用 const 引用 (const T&) 传递对象,可以避免不必要的拷贝(效率高),同时保证函数内不会修改原始对象(安全性高)。

  • 修饰返回值: 限制调用者不能修改返回的对象。

1.3 修饰类成员函数(C++ 特有)

在成员函数末尾加上 const,表示该函数是一个 常成员函数

  • 常成员函数不能修改类的任何非 static 成员变量(除非该成员被声明为 mutable)。

  • 常对象只能调用常成员函数。

1.4 代码示例:
cpp 复制代码
#include <iostream>
#include <vector>

//-------------------------------------------
// 1.1  const 修饰变量 / 指针
//-------------------------------------------
void demo_pointer() {
    int x = 5, y = 10;

    const int a = 42;      // a 只读
    // a = 43;             // ❌ 编译错误:a 是常量

    const int* p1 = &x;    // *p1 只读,p1 可改
    // *p1 = 100;          // ❌ 不能改指向的值
    p1 = &y;               // ✅ 可以改指向

    int* const p2 = &x;    // p2 只读,*p2 可改
    *p2 = 100;             // ✅ 可以改值
    // p2 = &y;            // ❌ 不能改指针本身

    const int* const p3 = &x; // 全只读
    // *p3 = 200;  p3 = &y; // ❌ 都动不了
}

//-------------------------------------------
// 1.2  const 修饰函数参数 & 返回值
//-------------------------------------------
// 参数:大对象只读传参,零拷贝又安全
void printVec(const std::vector<int>& v) {  // v 只能读
    // v.push_back(9);      // ❌ 编译错误:v 是 const
    for (int n : v) std::cout << n << ' ';
    std::cout << '\n';
}

// 返回值:阻止调用者改临时量
const int getMax(const int& a, const int& b) {
    return (a > b ? a : b);
}

//-------------------------------------------
// 1.3  const 成员函数
//-------------------------------------------
class Counter {
    int value_ = 0;
    mutable int access_ = 0;   // mutable 允许在 const 函数里改
public:
    int get() const {          // ① 常成员函数
        ++access_;             // ✅ mutable 成员可改
        return value_;
    }
    void inc() { ++value_; }   // 普通函数可改成员

    // 重载:const / 非 const 版本
    int& data()       { return value_; } // 非 const 对象调用
    const int& data() const { return value_; } // const 对象调用
};

int main() {
    demo_pointer();

    std::vector<int> nums{1, 2, 3};
    printVec(nums);              // 1 2 3

    int m = getMax(3, 7);
    std::cout << "max=" << m << '\n';  // max=7
    // getMax(3,7) = 99;         // ❌ 返回的是 const int,不能当左值

    Counter c1;
    c1.inc();
    std::cout << "c1=" << c1.get() << '\n'; // 1

    const Counter c2;            // ② 常对象
    // c2.inc();                 // ❌ 只能调 const 成员函数
    std::cout << "c2=" << c2.get() << '\n'; // 0
    // c2.data() = 5;            // ❌ 返回 const int&,不能赋值
}

4 句话再总结

  1. const 靠谁,谁只读;靠 * 左边还是右边,决定"值"还是"指针"只读。

  2. const T& 传参 = 零拷贝 + 防篡改,是 C++ 最常用签名。

  3. 成员函数末尾加 const = 隐式 this 指针前加 const,内部不能改普通成员。

  4. 常对象只能调常成员函数;需要统计/缓存时用 mutable 打洞。

2. static (静态)

定义: static 关键字改变了变量或函数的存储期(Storage Duration)、作用域(Scope)和链接性(Linkage)。

核心用法与考点:

2.1 修饰局部变量(改变存储期)
  • 作用: 局部变量的生存期从函数调用结束延伸到整个程序运行期间。

  • 特点: 只初始化一次。

2.2 修饰全局变量和函数(改变链接性)
  • 作用: 将全局变量或函数的外部链接性(External Linkage)改为 内部链接性(Internal Linkage)。

  • 特点: 它们只能在当前文件(编译单元)内访问和使用,对其他文件隐藏。这有助于避免命名冲突。

2.3 修饰类成员变量(C++ 特有)
  • 作用: 静态成员变量 属于整个类,而不是任何特定的对象。

  • 特点: 整个类只有一个副本,所有对象共享。必须在类外进行定义和初始化。

2.4 修饰类成员函数(C++ 特有)
  • 作用: 静态成员函数 属于整个类,不接收隐式的 this 指针。

  • 特点: 静态成员函数只能直接访问类的静态成员(变量和函数),不能访问非静态成员。

2.5 代码示例:
cpp 复制代码
//============ file: main.cpp ============
#include <iostream>

//----------------------------------------
// 2.2 全局 static:内部链接,别的文件看不到
//----------------------------------------
static int g_hidden = 999;          // 只在 main.cpp 有效
static void hiddenFunc() {          // 同上,链接器对外"隐藏"
    std::cout << "hiddenFunc: " << g_hidden << '\n';
}

//----------------------------------------
// 2.1 局部 static:存储期变成"整个程序运行期"
//----------------------------------------
void visitCount() {
    static int count = 0;           // ① 只有第一次进来才初始化
    ++count;
    std::cout << "第 " << count << " 次调用 visitCount\n";
}

//----------------------------------------
// 2.3 + 2.4 类静态成员
//----------------------------------------
class Logger {
public:
    // 非静态成员:每个对象各一份
    std::string name_;

    // 静态数据成员:整个类只有一份,必须类外再定义一次
    static int instanceCount;       // ② 只是"声明",不是"定义"

    // 静态成员函数:没有 this,只能访问静态成员
    static void printCount() {
        std::cout << "当前实例总数 = " << instanceCount << '\n';
        // std::cout << name_;      // ❌ 编译错误:没有 this,不能访问非静态成员
    }

    // 构造函数:每创建一个对象就 ++
    explicit Logger(const std::string& n) : name_(n) {
        ++instanceCount;
    }
    // 析构函数:对象销毁就 --
    ~Logger() { --instanceCount; }
};

// ② 类静态数据成员的"真正定义"放在类外(只能有一次)
int Logger::instanceCount = 0;

//============ main 函数:演示全部效果 ============
int main() {
    std::cout << "----- 局部 static -----\n";
    visitCount();   // 第 1 次
    visitCount();   // 第 2 次
    visitCount();   // 第 3 次

    std::cout << "\n----- 全局 static -----\n";
    hiddenFunc();   // 能调到,因为同文件

    std::cout << "\n----- 类 static -----\n";
    Logger::printCount();           // 0 ,还没有对象
    {
        Logger a("A");
        Logger::printCount();       // 1
        Logger b("B");
        Logger::printCount();       // 2
    }                               // a、b 析构
    Logger::printCount();           // 0  再次回到 0

    // 不需要对象,直接类名调用静态函数
    Logger::printCount();
}

4 句话再总结

  1. 局部 static 让变量跨越函数调用继续存活。

  2. 全局 static 让名字跨不出当前源文件

  3. 类静态数据成员只有一份 ,类外必须再定义一次

  4. 类静态成员函数没有 this ,只能玩静态成员,调用时可以不要对象

3. volatile (易失性)

定义: 用于告诉编译器,变量的值可能会在程序控制流之外被意外地修改(即"易失")。

核心用法与考点:

3.1 阻止编译器优化

当编译器看到一个变量的值在两次使用之间没有被程序修改时,它可能会进行优化,将该值缓存在 CPU 寄存器中,而不是每次都从内存中重新读取。

  • 用途: volatile 关键字指示编译器,每次访问该变量时都必须从 内存 中重新读取它的值,而不是使用寄存器中的缓存值。
3.2 常见应用场景:
  1. 并行设备(硬件): 访问内存映射的 I/O 寄存器,这些寄存器的值可能被外部硬件改变。

  2. 中断服务程序(ISR): 全局变量被 ISR 和主程序同时访问。

  3. 多线程环境(并发): 多个线程共享的全局变量(注意:volatile 本身不能解决竞态条件,仍需要互斥锁等同步机制,但它是第一道防线)。

示例:

复制代码
// 假设 status 寄存器的值可能被硬件修改
volatile int status_register;

void wait_for_hardware() {
    // 如果没有 volatile,编译器可能优化为只读一次 status_register
    while (status_register == 0) {
        // 等待硬件设置 status_register 为非零值
    }
}
3.3 与 const 的结合
  • volatile const int READ_ONLY_REG;:表示这是一个不能被程序修改的常量(const),但它可能会被外部硬件修改(volatile)。

4. explicit (显式)

定义: (C++ 特有) 用于修饰类的构造函数或转换函数,以禁止(抑制)编译器进行隐式的类型转换。

核心用法与考点:

4.1 修饰单参数构造函数
  • 问题: 如果一个类有一个只接受一个参数的构造函数,C++ 允许编译器使用这个构造函数将参数类型隐式转换为类类型。

  • 解决方案: 使用 explicit 关键字可以避免这种不期望的隐式转换。

示例:

cpp 复制代码
class Data {
public:
    // 没有 explicit:允许隐式转换
    Data(int x) { /* ... */ } 
    
    // 使用 explicit:禁止隐式转换
    // explicit Data(int y) { /* ... */ } 
};

void process(Data d) { /* ... */ }

// 如果构造函数没有 explicit:
Data d1 = 10;     // 允许:隐式调用 Data(10)
process(20);      // 允许:隐式调用 Data(20)

// 如果构造函数使用了 explicit:
// Data d1 = 10;  // 错误:禁止隐式转换
Data d2(30);      // 允许:显式调用
process(Data(40)); // 允许:显式构造临时对象
4.2 修饰转换函数(C++11/14)

explicit 也可以修饰自定义类型转换操作符(如 operator bool()),防止对象被隐式转换为目标类型。

总结: explicit 关键字提高了代码的清晰度和安全性,防止在不经意间发生类型转换,从而避免潜在的 bug。

相关推荐
2401_831501732 小时前
Python学习之day03学习(文件和异常)
开发语言·python·学习
酷~2 小时前
C语言模拟面向对象编程方法之多态
c语言·开发语言
hui函数3 小时前
python全栈(基础篇)——day03:后端内容(字符串格式化+简单数据类型转换+进制的转换+运算符+实战演示+每日一题)
开发语言·后端·python·全栈
寻星探路3 小时前
Java EE初阶启程记09---多线程案例(2)
java·开发语言·java-ee
利刃大大3 小时前
【高并发服务器】三、正则表达式的使用
服务器·c++·正则表达式·项目
froginwe114 小时前
Python 3 输入和输出
开发语言
小何好运暴富开心幸福4 小时前
C++之再谈类与对象
开发语言·c++·vscode
zhangfeng11334 小时前
R 导出 PDF 时中文不显示 不依赖 showtext** 的最简方案(用 extrafont 把系统 TTF 真正灌进 PDF 内核)
开发语言·r语言·pdf·生物信息
应用市场4 小时前
自建本地DNS过滤系统:实现局域网广告和垃圾网站屏蔽
开发语言·php