const 在 C/C++ 中的全面用法(C/C++ 差异+核心场景+实战示例)

const 在 C/C++ 中的全面用法(C/C++ 差异+核心场景+实战示例)

const 是 C/C++ 中的只读修饰符 ,核心作用是限定变量/对象/函数等不可被修改 ,既能提升代码可读性、避免意外修改,又能让编译器做更多优化(如常量折叠),还能增强类型安全。C 和 C++ 对 const 的支持有核心差异,C++ 在 C 的基础上做了大幅扩展,使其适配面向对象、模板等特性。下面按「基础通用用法」「C 专属特性」「C++ 增强用法」「核心差异」「实战注意事项」展开,覆盖所有高频场景。

一、基础通用用法(C/C++ 均支持,核心共性)

这是 const 最基础的用法,C 和 C++ 规则完全一致,核心是修饰变量为只读,不可直接赋值修改

1. 修饰普通变量(只读变量)

c 复制代码
// 定义只读变量,初始化后不可修改
const int a = 10;
// a = 20; // 编译报错:只读变量不可赋值
int b = a; // 合法:可以读取const变量的值
  • 关键:const 变量必须在定义时初始化(否则后续无法赋值,变成"只读的未初始化变量",无意义);
  • 本质:const 普通变量是只读的内存变量(非编译期常量,C 中尤为明显),只是编译器禁止直接修改,底层仍占用内存。

2. 修饰指针(3 种经典场景,C/C++ 完全一致)

指针的核心是「指针本身 」和「指针指向的内容 」,const 修饰不同位置,效果完全不同,这是高频考点,记准就近修饰原则const 离谁近,就限定谁不可修改。

c 复制代码
int x = 10, y = 20;

// 场景1:const 修饰*p → 指针指向的内容不可修改(只读内容)
const int* p = &x;
// *p = 30; // 编译报错:指向的内容只读
p = &y;     // 合法:指针本身可以指向其他地址

// 场景2:const 修饰p → 指针本身不可修改(只读指针,地址固定)
int* const p = &x;
*p = 30;    // 合法:指向的内容可以修改
// p = &y; // 编译报错:指针本身地址不可变

// 场景3:const 同时修饰*p和p → 内容+指针均不可修改
const int* const p = &x;
// *p = 30; // 报错:内容只读
// p = &y;  // 报错:指针地址只读

记忆技巧 :把 * 理解为「指向的内容」,从右往左读:

  • const int* pp 是指针,指向 const int(只读int);
  • int* const ppconst 指针,指向 int

3. 修饰函数参数(C/C++ 通用优化手段)

用于限定函数内不可修改传入的参数,避免意外修改实参(尤其针对指针/引用参数,防止篡改原数据),同时明确函数语义:"此参数仅做读取,不做修改"。

c 复制代码
// 示例1:修饰普通参数(值传递,意义较小,因为值传递是拷贝,修改不影响实参)
void print(const int num) {
    // num = 100; // 报错:不可修改
    printf("%d\n", num);
}

// 示例2:修饰指针参数(核心场景,防止修改指向的原数据)
void modify(const int* arr, int len) {
    // arr[0] = 10; // 报错:不可修改原数组
    for (int i=0; i<len; i++) {
        printf("%d ", arr[i]); // 仅读取,合法
    }
}

int main() {
    int arr[] = {1,2,3};
    modify(arr, 3); // 实参不会被篡改,安全
    return 0;
}
  • 最佳实践:指针/引用参数 尽量加 const(只要函数内不修改),这是工业界代码的通用规范;
  • 普通值参数加 const 意义不大(拷贝开销小,且修改不影响实参),可根据代码规范选择。

二、C 语言中 const 的专属特性(C++ 中已优化/改变)

C 语言对 const 的支持比较"简陋",核心特点是const 变量不是真正的编译期常量,仅为"只读变量",这是和 C++ 最核心的早期差异。

1. const 变量不可作为数组长度(C99 变长数组除外)

C 中 const 变量占用内存,编译器不会将其视为"常量表达式",因此不能用于定义固定长度数组:

c 复制代码
const int N = 5;
// int arr[N]; // C89 编译报错:N 不是编译期常量(C99 支持变长数组 VLA,可运行但非标准固定数组)
int arr[5]; // 合法:字面量是编译期常量

2. const 变量需用 extern 显式声明才能跨文件使用

C 中 const 变量的默认链接属性是内部链接 (仅当前文件可见),若要在其他文件使用,必须加 extern 显式声明:

c 复制代码
// file1.c:定义跨文件使用的const变量,必须加extern
extern const int PI = 3.1415;

// file2.c:使用前声明
extern const int PI;
printf("PI = %f\n", PI); // 合法

若不加 extern,C 编译器会为每个文件生成独立的 const 变量,导致链接冲突或值不一致。

3. 无引用特性,const 仅能修饰变量/指针/函数参数

C 语言没有引用(&) 特性,因此 const 无法修饰引用,仅能作用于变量、指针、函数参数,用法远少于 C++。

三、C++ 中 const 的增强用法(C 无此特性,面向对象核心)

C++ 完全兼容 C 中 const 的基础用法,同时针对面向对象、引用、模板、函数 做了大幅扩展,使 const 成为实现代码健壮性的核心关键字,这部分是 C++ 开发的重点。

1. const 修饰引用(常引用,核心场景)

C++ 引入引用 后,const 可修饰引用为常引用(const &),核心作用:

  • 绑定只读变量/右值(普通引用只能绑定可修改的左值);
  • 避免拷贝大对象(如自定义类、字符串),同时保证原对象不被修改。
cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int main() {
    const int a = 10;
    // int& ref = a; // 编译报错:普通引用不能绑定const左值
    const int& ref1 = a; // 合法:常引用绑定const左值
    const int& ref2 = 20; // 合法:常引用绑定右值(C++ 编译器会生成临时变量)
    
    // 大对象场景:常引用避免拷贝,提升性能
    const string& s = "hello world"; // 避免生成临时string拷贝
    cout << s << endl;
    return 0;
}
  • 最佳实践:函数返回大对象/传递大对象 时,优先使用 const &,既避免拷贝开销,又保证原对象安全。

2. const 修饰类的成员函数(常成员函数,面向对象核心)

这是 C++ 面向对象的核心特性const 放在类成员函数的参数列表后、函数体前 ,表示该函数是常成员函数,核心规则:

  • 常成员函数不能修改类的任何非静态成员变量
  • 常成员函数只能调用类的其他常成员函数/静态成员函数(不能调用普通成员函数,防止间接修改成员);
  • 常对象(const 修饰的类对象)只能调用常成员函数(普通对象可调用常/普通成员函数)。
cpp 复制代码
#include <iostream>
using namespace std;

class Person {
private:
    string name;
    int age;
    static int count; // 静态成员变量
public:
    Person(string n, int a) : name(n), age(a) { count++; }
    
    // 常成员函数:不能修改非静态成员,参数列表后加const
    string getName() const {
        // age = 20; // 编译报错:常成员函数不能修改非静态成员
        return name;
    }
    
    // 普通成员函数:可修改非静态成员
    void setAge(int a) {
        age = a;
    }
    
    // 静态成员函数:无this指针,可被常/普通对象调用
    static int getCount() {
        return count;
    }
};

int Person::count = 0; // 静态成员变量类外初始化

int main() {
    const Person p1("张三", 18); // 常对象
    // p1.setAge(20); // 报错:常对象不能调用普通成员函数
    cout << p1.getName() << endl; // 合法:调用常成员函数
    cout << p1.getCount() << endl; // 合法:调用静态成员函数
    
    Person p2("李四", 20); // 普通对象
    p2.setAge(25); // 合法:调用普通成员函数
    cout << p2.getName() << endl; // 合法:普通对象可调用常成员函数
    return 0;
}
  • 核心原理:常成员函数的this 指针const 类名* const 类型(指向的对象只读+指针本身只读),因此无法修改成员变量;
  • 最佳实践:类中仅做读取操作的成员函数 ,必须加 const(如获取属性的 get 方法),这是 C++ 类设计的通用规范。

3. const 修饰类的对象/成员变量

(1)const 修饰类的对象(常对象)

类的常对象所有非静态成员变量均不可修改 ,仅能调用常成员函数/静态成员函数 (如上例中的 p1),本质是限制对象的修改权限。

(2)const 修饰类的成员变量(常成员变量)

类的常成员变量必须在构造函数的「初始化列表」中初始化 (不能在函数体内赋值),且初始化后终身不可修改

cpp 复制代码
class Circle {
private:
    const double PI; // 常成员变量:圆周率,不可修改
    int radius;
public:
    // 必须在初始化列表中初始化常成员变量
    Circle(int r) : PI(3.1415), radius(r) {}
    
    double getArea() const {
        return PI * radius * radius; // 仅读取,合法
    }
};
  • 注意:常成员变量无法通过普通成员函数修改,即使是类的内部方法也不行。

4. const 修饰函数返回值

C++ 中 const 可修饰函数返回值,根据返回值类型(普通类型/指针/引用)有不同作用,核心是禁止对返回值直接赋值/修改

cpp 复制代码
#include <iostream>
using namespace std;

// 场景1:返回普通类型(int),const 意义不大(返回值是拷贝,修改不影响原数据)
const int add(int a, int b) {
    return a + b;
}

// 场景2:返回指针,const 限定指向的内容不可修改
const int* getArr() {
    static int arr[] = {1,2,3};
    return arr;
}

// 场景3:返回引用,const 限定引用的对象不可修改(核心场景,避免原对象被篡改)
const int& getMax(int& a, int& b) {
    return a > b ? a : b;
}

int main() {
    // add(3,5) = 10; // 报错:const返回值不可赋值
    const int* p = getArr();
    // *p = 100; // 报错:指向的内容只读
    int x = 5, y = 10;
    // getMax(x,y) = 20; // 报错:常引用返回值不可修改
    cout << getMax(x,y) << endl; // 仅读取,合法
    return 0;
}
  • 最佳实践:返回指针/引用 时,若不想让调用方修改原对象,必须加 const 修饰返回值。

5. const 作为编译期常量(C++ 核心优化)

C++ 中,初始化值为常量表达式的 const 变量 会被编译器视为编译期常量(而非只读内存变量),可用于:

  • 定义固定长度数组;
  • 作为模板参数;
  • 作为枚举常量、类的静态常成员初始化。
cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

// const 编译期常量,可用于数组长度
const int N = 5;
int arr[N] = {1,2,3,4,5}; // 合法:C++ 编译期确定长度

// 作为模板参数(模板参数必须是编译期常量)
vector<int> v(N); // 合法:N 是编译期常量

// 类的静态常成员变量:可直接类内初始化(编译期常量)
class Math {
public:
    static const double PI; // 声明
    static const int MAX = 100; // 类内直接初始化(编译期常量)
};
const double Math::PI = 3.1415; // 类外初始化(若需不同值)

int main() {
    cout << Math::MAX << endl; // 100
    return 0;
}

这一特性解决了 C 中 const 变量不能作为数组长度的问题,是 C++ 对 const 的关键优化。

6. const 修饰模板参数(C++ 模板特性)

C++ 模板中,const 可修饰模板参数,限定模板实例化后的参数为只读,适配不同的常量类型需求:

cpp 复制代码
template <const int N>
class Array {
public:
    int data[N];
    void print() {
        for (int i=0; i<N; i++) {
            cout << data[i] << " ";
        }
    }
};

int main() {
    Array<5> arr; // 实例化:N=5(编译期常量)
    arr.data[0] = 1;
    arr.print();
    return 0;
}

四、C/C++ 中 const 的核心差异总结(必记)

特性/场景 C 语言 C++ 语言
常量属性 仅为只读变量(占内存) 初始化值为常量表达式时是编译期常量,否则为只读变量
数组长度 不可用于固定数组长度(C99 VLA 除外) 可直接用于固定数组长度
跨文件使用 默认内部链接,需加 extern 显式声明 全局 const 可直接跨文件使用(编译器自动优化)
引用修饰 无引用特性,不可修饰 支持常引用(const &),可绑定左值/右值
类成员函数 无类特性,不可修饰 支持常成员函数,限制修改成员变量
类常对象/成员变量 无类特性,不可修饰 支持修饰常对象、常成员变量(初始化列表初始化)
函数返回值/模板 仅支持基础类型,无扩展 支持修饰指针/引用返回值、模板参数

五、const 实战注意事项(避坑指南)

1. const 不是绝对的"只读"------可通过指针强制修改(不推荐)

C/C++ 中,const 变量的只读性是编译器层面的限制 ,底层仍占用内存,可通过「非 const 指针」强制修改(即const _cast ,C++ 专用),但这是未定义行为,极易导致程序崩溃,严禁在生产代码中使用:

c 复制代码
// C 语言:强制类型转换修改
const int a = 10;
int* p = (int*)&a;
*p = 20; // 编译器不报错,但运行结果未定义(可能修改成功,也可能触发段错误)
printf("a = %d\n", a); // 结果不可预测

// C++ 语言:const_cast 转换(专用强制转换,仍不推荐)
const int b = 20;
int* q = const_cast<int*>(&b);
*q = 30; // 未定义行为
  • 原则:一旦定义 const 变量,就不要尝试修改,违背 const 设计初衷。

2. 函数参数中,指针/引用优先加 const

针对自定义类、数组、字符串等大对象/复杂对象 ,传递参数时优先使用 const 指针/const 引用,既避免拷贝开销,又保证原对象不被修改,这是工业界 C/C++ 代码的通用规范。

3. C++ 类中,get 方法必须加 const,set 方法不加

类的属性访问方法(getXxx)仅做读取操作,必须声明为常成员函数 ;修改方法(setXxx)做写入操作,声明为普通成员函数,这是 C++ 类设计的黄金法则

4. 全局 const 变量尽量避免重复定义

C 中全局 const 需加 extern 统一定义,C++ 中虽可直接跨文件使用,但仍建议在头文件中声明,源文件中定义,避免多文件包含时的重复定义问题:

cpp 复制代码
// math.h(头文件)
#pragma once
extern const double PI; // 声明

// math.cpp(源文件)
#include "math.h"
const double PI = 3.1415; // 定义

5. const 与 constexpr 的区别(C++11 及以上)

C++11 引入 constexpr(编译期常量),很多开发者会混淆 constconstexpr,核心区别:

  • const只读约束,可能是编译期常量(初始化值为常量表达式),也可能是运行时常量(初始化值为变量);
  • constexpr强制编译期常量,初始化值必须是常量表达式,编译器在编译期就确定其值,可用于更多编译期场景(如 constexpr 函数、类的构造函数)。
cpp 复制代码
const int a = 10; // 编译期常量
int n = 5;
const int b = n; // 运行时常量(只读变量)

constexpr int c = 20; // 强制编译期常量
// constexpr int d = n; // 编译报错:n 是变量,非常量表达式
  • 建议:C++11 及以上版本,明确需要编译期常量 时用 constexpr仅需要只读约束 时用 const

六、总结(核心要点浓缩)

  1. 核心作用const 是只读修饰符,C/C++ 通用,用于限定变量/对象不可修改,提升代码健壮性和编译器优化能力;
  2. C 语言特性 :const 仅为只读变量 ,不可做数组长度,无引用/类相关用法,跨文件需加 extern
  3. C++ 增强特性 :兼容 C 基础用法,新增常引用、常成员函数、常对象/常成员变量,const 可作为编译期常量,支持修饰函数返回值/模板参数;
  4. 关键规则
    • 指针:const 就近修饰,离谁近限定谁不可修改;
    • 类:常成员函数不能修改非静态成员,常对象只能调用常成员函数;
    • 引用:常引用可绑定左值/右值,是避免大对象拷贝的最佳实践;
  5. 避坑原则:不通过指针强制修改 const 变量,函数参数中指针/引用优先加 const,C++ 类中 get 方法加 const;
  6. C++11+ 补充constexpr 是强制编译期常量,与 const 互补,按需选择。

const 是 C/C++ 开发中使用频率最高的关键字之一,掌握其在 C/C++ 中的差异和各场景用法,是写出高质量、健壮性代码的基础,尤其在 C++ 面向对象开发中,const 的正确使用是衡量代码规范的重要标准。

相关推荐
十五年专注C++开发1 小时前
MinHook:Windows 平台下轻量级、高性能的钩子库
c++·windows·钩子技术·minhook
范纹杉想快点毕业2 小时前
实战级ZYNQ中断状态机FIFO设计
java·开发语言·驱动开发·设计模式·架构·mfc
一只小小的芙厨2 小时前
寒假集训笔记·树上背包
c++·笔记·算法·动态规划
马猴烧酒.2 小时前
【面试八股|Java集合】Java集合常考面试题详解
java·开发语言·python·面试·八股
以卿a3 小时前
C++(继承)
开发语言·c++·算法
lly2024063 小时前
XQuery 选择和过滤
开发语言
测试工程师成长之路3 小时前
Serenity BDD 框架:Java + Selenium 全面指南(2026 最新)
java·开发语言·selenium
czxyvX3 小时前
017-AVL树(C++实现)
开发语言·数据结构·c++
你真是饿了3 小时前
1.C++入门基础
开发语言·c++