C/C++中const关键字的总结

C/C++ 中 const 关键字的总结

const 关键字在 C 和 C++ 中起着非常重要的作用,用于定义常量、修饰变量、函数参数、返回值、成员函数和类对象等。正确理解和使用 const 可以提高代码的安全性、可读性和效率。本文将详细介绍 const 的使用,包括全局和局部变量、修饰指针和引用、成员函数和数据成员、修饰类对象,以及 const 与宏定义和 static 的区别。


1. 什么是 const

const 是一个限定符,用于限定一个变量或对象的值不可被修改。使用 const 可以防止无意间对数据的修改,提高程序的安全性和可靠性。

  • 只读属性:被 const 修饰的变量只能被访问,不能被修改,即只读(readonly)。
  • 编译期间检查const 在编译期间会告诉编译器该对象不能被修改,如果试图修改,会产生编译错误。

1.1 const 的规则

  • const 离谁近,谁就不能被修改:在指针和引用的修饰中,const 的位置决定了是修饰指针本身还是指针指向的对象。
  • 初始化要求const 修饰的变量必须在声明时初始化,且初始化后不能再赋值。

1.2 const 的作用

  1. 定义常量:替代宏定义,提供类型安全的常量。
  2. 修饰函数参数:防止函数内部修改参数,提高函数的健壮性。
  3. 修饰函数返回值:防止返回值被修改,增加安全性。
  4. 修饰成员函数:保证成员函数不修改对象状态,可被 const 对象调用。
  5. 提高代码可读性:向读者传递变量或函数的使用意图。

1.3 const 的优点

  • 类型安全检查:编译器会对 const 进行类型检查,防止类型不匹配的赋值。
  • 编译优化:编译器可对 const 进行优化,可能不为其分配内存,直接使用符号表中的值。
  • 节省空间:避免不必要的内存分配,减少内存占用。
  • 提高效率:减少存储和读内存的操作,提高程序运行效率。

2. const 的使用原理

2.1 基本分类

  • 常量变量const 类型 变量名;
  • 常量引用const 类型 & 引用名;
  • 常量对象const 类名 对象名;
  • 常量成员函数返回类型 类名::函数名(参数列表) const;
  • 常量数组const 类型 数组名[大小];
  • 常量指针
    • 指向常量的指针:const 类型 * 指针名;类型 const * 指针名;
    • 常量指针:类型 * const 指针名;
    • 指向常量的常量指针:const 类型 * const 指针名;类型 const * const 指针名;

注意:在某些情况下,const 与类型说明符的位置可以互换,例如:

const int a = 5;
int const a = 5; // 等同于上面

3. const 修饰全局和局部变量

3.1 const 全局变量

  • 作用域限制const 修饰的全局变量,其作用域仅限于定义它的文件(编译单元),其他文件无法访问,类似于 static 全局变量。
  • 防止修改const 全局变量在初始化后不能被修改,编译器会阻止对其赋值。

示例

以下是一个完整的示例,演示 const 全局变量的作用域限制 以及解决方法:

文件 1:a.cpp

// a.cpp
#include <iostream>

// 定义 const 全局变量
const int a = 10;  // 仅在 a.cpp 内部可见

void printA() {
    std::cout << "a (from a.cpp) = " << a << std::endl;
}

文件 2:main.cpp

// main.cpp
#include <iostream>

// extern const int a;  // 如果取消注释,将导致链接错误

int main() {
    // std::cout << "a (from main.cpp) = " << a << std::endl;  // 链接错误:无法访问 a
    std::cout << "a is not accessible in main.cpp" << std::endl;
    return 0;
}

编译和运行:

g++ a.cpp main.cpp -o main
./main

输出:

a is not accessible in main.cpp

解决方法:显式指定外部链接性

通过 extern 声明和定义全局 const,将其作用域扩展到多个编译单元。

文件 1:a.h

// a.h
#ifndef A_H
#define A_H

// 声明外部链接的 const 全局变量
extern const int a;

#endif

文件 2:a.cpp

// a.cpp
#include "a.h"

// 定义 const 全局变量
const int a = 10;

文件 3:main.cpp

// main.cpp
#include <iostream>
#include "a.h"  // 包含声明

int main() {
    std::cout << "a (from main.cpp) = " << a << std::endl;
    return 0;
}

编译和运行:

g++ a.cpp main.cpp -o main
./main

输出:

a (from main.cpp) = 10

小结

  1. 默认情况下,const 全局变量只能在定义它的编译单元中访问。
  2. 通过 extern 声明并定义,可以扩展其作用域。
  3. 推荐使用头文件管理声明,以确保一致性和可维护性。

3.2 const 局部变量

  • 防止修改const 局部变量在初始化后不能被修改。
  • 编译器优化:对于 const 局部变量,编译器可能会进行优化,将其值直接替换到代码中,这被称为常量折叠

示例

#include <iostream>

int main() {
    const int a = 7;
    int *p = (int *)(&a);
    *p = 8; // 试图修改常量的值,行为未定义

    std::cout << "a = " << a << std::endl;
    std::cout << "*p = " << *p << std::endl;

    return 0;
}

编译并运行,结果可能是:

a = 7
*p = 8

注意:这种行为是未定义的,不应当修改 const 变量的值。


4. const 修饰指针和引用

4.1 const 修饰指针

指针是一个变量,存储另一个变量的地址。const 修饰指针时,需要区分以下几种情况:

  1. 指向常量的指针(Pointer to const):

    const int *ptr; // 或 int const *ptr;
    
    • 含义*ptr(指针指向的值)不可修改,但 ptr(指针本身的值,即指向的地址)可变。

    • 示例

      int a = 10;
      int b = 20;
      const int *ptr = &a; // ptr 指向 a
       
      // *ptr = 15; // 错误,不能修改指向的值
      ptr = &b;    // 合法,指针本身可变
      
  2. 常量指针(Const pointer):

    int *const ptr = &a;
    
    • 含义*ptr 可修改,但 ptr(指针本身)不可变,必须初始化。

    • 示例

      int a = 10;
      int b = 20;
      int *const ptr = &a; // ptr 指向 a,且不可更改
       
      *ptr = 15;    // 合法,修改指向的值
      // ptr = &b; // 错误,不能修改指针本身
      
  3. 指向常量的常量指针(Const pointer to const):

    const int *const ptr = &a;
    
    • 含义*ptrptr 都不可修改。

    • 示例

      int a = 10;
      int b = 20;
      const int *const ptr = &a;
      
      // *ptr = 15; // 错误,不能修改指向的值
      // ptr = &b; // 错误,不能修改指针本身
      

4.2 const 修饰引用

  • 常量引用const 类型 &引用名;

  • 特点

    • 常引用不能通过引用修改所绑定对象的值。
    • 常引用可以绑定到非常量对象、常量对象或临时对象。
  • 示例

    int a = 10;
    const int &ref = a;
    
    // ref = 20; // 错误,不能通过常引用修改 a 的值
    
    const int &ref2 = 42; // 合法,绑定到临时对象
    std::cout << "ref2 = " << ref2 << std::endl; // 输出 42
    

5. const 修饰函数参数

5.1 值传递参数

  • 对于值传递的参数,const 修饰符没有实际意义,因为函数内部无法修改实参。

  • 示例

    void func(const int x) {
        // x = 10; // 错误,参数 x 是只读的
    }
    

5.2 指针或引用参数

  1. 指向常量的指针参数

    void func(const int *ptr) {
        // *ptr = 10; // 错误,不能修改指向的值
        ptr++;        // 合法,可以改变指针本身
    }
    
  2. 常量引用参数

    void func(const int &ref) {
        // ref = 10; // 错误,不能修改引用的值
    }
    

5.3 使用常量引用避免拷贝

  • 对于大型对象,使用常量引用可以避免拷贝,提高效率。

  • 示例

    void print(const std::string &str) {
        std::cout << str << std::endl;
    }
    

6. const 修饰函数返回值

6.1 返回常量值

  • 对于值传递的返回值,const 修饰符通常没有意义。

  • 示例

    const int func() {
        return 42;
    }
    
    int main() {
        int a = func();
        // func() = 10; // 错误,不能对函数返回值赋值
        return 0;
    }
    

6.2 返回指针或引用

  1. 返回指向常量的指针

    const int *func() {
        static int value = 10;
        return &value;
    }
    
    int main() {
        const int *p = func();
        // *p = 20; // 错误,不能修改指向的值
        return 0;
    }
    
  2. 返回常量引用

    const int &func() {
        static int value = 10;
        return value;
    }
    
    int main() {
        const int &ref = func();
        // ref = 20; // 错误,不能修改引用的值
        return 0;
    }
    

6.3 返回 const 的意义

  • 防止修改返回值:增加安全性,防止调用者修改内部数据。
  • 限制链式赋值:在运算符重载中,防止不合理的赋值操作。

示例

class MyClass {
public:
    const MyClass &operator=(const MyClass &other) {
        // 赋值操作
        return *this;
    }
};

int main() {
    MyClass a, b, c;
    a = b;
    // (a = b) = c; // 错误,(a = b) 返回 const,不能赋值
    return 0;
}

7. const 修饰成员函数和数据成员

7.1 常量成员函数

  • 声明方式

    返回类型 函数名(参数列表) const;
    
  • 特点

    • 常量成员函数不能修改成员变量(除非成员变量是 mutable 修饰)。
    • 常量成员函数不能调用非 const 的成员函数。
    • 常量成员函数可以被 const 对象和非 const 对象调用。
  • 示例

    class MyClass {
    public:
        void func() const; // 常量成员函数
        void nonConstFunc();
    private:
        int data;
    };
    
    void MyClass::func() const {
        // data = 10;        // 错误,不能修改成员变量
        // nonConstFunc();   // 错误,不能调用非 const 成员函数
    }
    
    void MyClass::nonConstFunc() {
        data = 10; // 合法
    }
    
    int main() {
        MyClass obj;
        const MyClass constObj;
    
        obj.func();       // 合法
        obj.nonConstFunc(); // 合法
    
        constObj.func();    // 合法
        // constObj.nonConstFunc(); // 错误,const 对象不能调用非 const 成员函数
    
        return 0;
    }
    

7.2 常量成员函数

  • 初始化const 数据成员必须在构造函数的初始化列表中初始化。

  • 示例

    class MyClass {
    public:
        MyClass(int val) : data(val) {}
        void print() const {
            std::cout << "data = " << data << std::endl;
        }
    private:
        const int data;
    };
    
    int main() {
        MyClass obj(10);
        obj.print(); // 输出 data = 10
        return 0;
    }
    
  • 静态常量成员函数

    • 若为 const static 成员,且为整数或枚举类型,可以在类内初始化。
    class MyClass {
    public:
        static const int size = 100;
        void print() const {
            std::cout << "size = " << size << std::endl;
        }
    };
    
    int main() {
        MyClass obj;
        obj.print(); // 输出 size = 100
        return 0;
    }
    

8. const 修饰类对象

  • 常对象:用 const 修饰的对象,不能调用非 const 成员函数。

  • 示例

    class MyClass {
    public:
        void func();
        void func() const;
    };
    
    void MyClass::func() {
        std::cout << "非 const 版本的 func()" << std::endl;
    }
    
    void MyClass::func() const {
        std::cout << "const 版本的 func()" << std::endl;
    }
    
    int main() {
        MyClass obj;
        const MyClass constObj;
    
        obj.func();       // 输出:非 const 版本的 func()
        constObj.func();  // 输出:const 版本的 func()
    
        return 0;
    }
    

9. const 与宏定义的区别

  • 类型安全

    • const 常量有类型检查,宏定义没有类型。
  • 作用域

    • const 常量有作用域限制,宏定义在预处理阶段展开,没有作用域。
  • 编译器处理

    • const 常量由编译器处理,宏定义在预处理阶段替换。
  • 调试

    • const 常量可以在调试时查看,宏定义不能。

示例

#include <iostream>

#define PI 3.14159
const double Pi = 3.14159;

int main() {
    double r = 1.0;
    double area1 = PI * r * r; // 编译时替换
    double area2 = Pi * r * r; // 编译器进行类型检查

    std::cout << "area1 = " << area1 << std::endl;
    std::cout << "area2 = " << area2 << std::endl;

    return 0;
}

10. staticconst 的区别

10.1 static 关键字

  • 函数内部的 static 变量

    • 变量在函数执行结束后不释放,值保留到下次调用。

    示例

    #include <iostream>
    
    void func() {
        static int count = 0;
        count++;
        std::cout << "count = " << count << std::endl;
    }
    
    int main() {
        func(); // 输出 count = 1
        func(); // 输出 count = 2
        func(); // 输出 count = 3
        return 0;
    }
    
  • 全局的 static 变量和函数

    • 只能在当前文件中访问,不能被其他文件访问。
  • 类的 static 成员变量

    • 属于整个类,共享一份内存。

    示例

    class MyClass {
    public:
        static int count;
        MyClass() { count++; }
    };
    
    int MyClass::count = 0;
    
    int main() {
        MyClass a, b, c;
        std::cout << "MyClass::count = " << MyClass::count << std::endl; // 输出 3
        return 0;
    }
    
  • 类的 static 成员函数

    • 不接受 this 指针,只能访问静态成员。

    示例

    class MyClass {
    public:
        static void func() {
            // std::cout << data; // 错误,不能访问非静态成员
            std::cout << "Static func" << std::endl;
        }
    private:
        int data;
    };
    
    int main() {
        MyClass::func(); // 输出 Static func
        return 0;
    }
    

10.2 const 关键字

  • 阻止变量被修改

    • const 变量在初始化后不能被修改。
  • 修饰指针和引用

    • 控制指针或引用及其指向的对象是否可修改。
  • 修饰函数参数

    • 防止函数内部修改参数。
  • 修饰成员函数

  • 保证成员函数不修改对象状态。

10.3 二者区别

  • 作用不同

    • static 主要控制存储区域和可见性。
    • const 主要控制对象的可修改性。
  • 使用场景不同

    • static 用于管理内存和作用域。
    • const 用于防止数据被修改,提高安全性。

11. constconstexpr 的区别(C++11 及以上)

  • const

    • 运行时常量,值在编译期未知,可以在运行时赋值。
  • constexpr

    • 编译时常量,值必须在编译期确定。
    • 用于定义在编译期就能确定值的常量,常用于数组大小等需要编译期常量的地方。

示例

#include <iostream>

const int size1 = 10;
constexpr int size2 = 20;

int main() {
    int array1[size1]; // 合法,size1 是 const 整数
    int array2[size2]; // 合法,size2 是 constexpr 整数

    std::cout << "size1 = " << size1 << std::endl;
    std::cout << "size2 = " << size2 << std::endl;

    return 0;
}

12. const_cast 的使用

  • 作用:用于移除对象的 constvolatile 属性。
  • 限制:不能去除常量对象的常量性,只能用于指针或引用。

示例

#include <iostream>

void func(const int *ptr) {
    int *modifiablePtr = const_cast<int *>(ptr);
    *modifiablePtr = 10; // 修改指针指向的值
}

int main() {
    const int value = 5;
    std::cout << "Before: value = " << value << std::endl;

    func(&value);

    std::cout << "After: value = " << value << std::endl;

    return 0;
}

注意:这种行为是未定义的,修改常量可能会导致不可预料的结果,应谨慎使用。


总结

const 关键字在 C 和 C++ 编程中具有重要意义。通过正确理解和使用 const,可以编写出更安全、高效、可维护的代码。以下是一些关键要点:

  • 防止数据被修改:使用 const 修饰变量、指针、引用、函数参数和返回值,防止意外修改数据。
  • 提高代码可读性:明确标识哪些数据是只读的,增强代码的可读性和自解释性。
  • 编译器优化const 允许编译器进行更多的优化,提高程序性能。
  • 与其他关键字配合使用:理解 conststaticconstexprvolatile 等关键字的区别和联系。

建议:在编程实践中,尽可能使用 const 修饰不需要修改的数据,养成良好的编程习惯。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

开朗碳基生物

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值