C++中const 的各种用法

本文详细介绍了C++中const关键字的多种用法,包括修饰变量、引用、指针及类等,展示了如何利用const提高代码的安全性和效率。

C++中const 关键字的用法

 

const修饰变量

const 主要用于把一个对象转换成一个常量,例如:

const int size = 512;
size = 0;        // error: assignment of read-only variable

上面的例子中,定义size为常量并初始化为512,变量size仍是一个左值,但是现在这个左值是不可修改的,任何修改size的尝试都会导致编译错误。

 

因为常量在定以后就不能被修改,因此const对象定义时必须初始化,否则会引起编译错误,例如:

const int size;  // error: uninitialized const

 

在全局作用域里定义非const变量时,它在整个程序中都可以访问。我们可以把一个非const变量定义在一个文件中,假设已经做了合适的声明,就可以在另外的文件中使用这个变量:

// file1.cpp
int counter;    // definition

// file2.cpp
extern int counter; // use counter from file1
++counter;            // increments counter defined in file1

 

在全局作用域里声明的const变量是定义该对象的文件的局部变量,此变量只存在于那个文件中,不能被其它文件访问。

通常头文件中不能存放变量定义,但const变量是一个例外,当我们在头文件中定义了const变量后,每个包含该头文件的源文件都有了自己的const变量,其名称和值都一样。

 

通过指定const变量为extern,就可以在整个程序中访问const对象,例如:

// file1.cpp
extern const int counter = 0;    // definition

// file2.cpp
extern const int counter;      // use counter from file1

 

总结:

1、非const变量默认为extern。

2、要使const变量能够在其它的文件中访问,必须显式地指定它为extern。

 


 

const修饰引用

引用(reference)就是对象的另一个名字,在实际应用中,引用主要用作函数的形参。

引用必须用与该引用同类型的对象初始化:

int i = 42; 
int &r1 = i;     // ok 
int &r2;        // error: a reference must be initialized
int &r3 = 10;   // error: initialize must be an object

 

当引用初始化后,不能将其再绑定到另一个对象上:

int i = 42; 
int j = 37;
int &r1 = i;     // ok 
r1 = j;     // i=37

 

const引用是指向const对象的引用,

const int i = 42;
const int &r1 = i;     // ok 
int &r2 = i;     // error: nonconst reference to a const object

上面的例子中,可以读取但不能修改r1,因此,任何对r1的赋值都是不合法的。

同理,用i初始化r2也是不合法的,r2是普通的非const引用,不能指向const对象。

 

const 引用可以初始化为不同类型的对象或者初始化为右值,如字面值常量:

int i = 42; 
const int &r1 = i;
const int &r2 = 42; 
const int &r3 = r2 + i; // 84

 

const引用绑定到不同但相关类型的对象上是合法的,如下:

double i = 42.1;
const int &r1 = i;     // ok, r1=42
int &r2 = i;     // error 

这是因为const引用是只读的,编译器会自动创建一个临时变量,然后将const引用绑定到这个临时变量上:

int temp = i;
const int &r1 = temp;

 

 

总结:

1、普通非const引用只能绑定到与该引用同类型的对象;

2、const引用则可以绑定到不同但相关的类型的对象,或绑定到右值;

 


 

const修饰指针

1、const指针

前面提到,const对象在定义的同时必须初始化,const指针也遵循这一规则。

作为常量,const指针的值不能被修改,这就意味着const指针初始化以后不能再指向其它对象,任何试图给const指针赋值的行为都会导致编译错误。

int i = 42;
int j = 42;
int* const p1 = &i;     // ok 
int* const p2;     // error, uninitialzed const
p1 = &j;     // error, assignment of read-only variable

 

const指针本身虽不能修改,但却可以通过它修改所指向的对象的值,

int i = 42;
int* const p1 = &i;     // ok 
*p1 = 37;       // i=37

 

 

2、指向const对象的指针

如果指针指向const对象,则不允许用指针来改变其所指的const值,为了保证这个特性,C++强制要求指向const对象的指针必须具有const特性:

const int i = 42;
int* p1 = &i;           // error 
int* const p2 = &i;     // error 
const int* p3 = &i;     // ok 

 

指向const对象的指针本身并不是const的,因此定义时可以不必进行初始化,且可以再指向其它对象;

允许把非const对象的地址赋给指向const对象的指针。

const int i = 42;
int j = 42; 
const int* p1;     // ok 
p1 = &i;        // ok
p1 = &j;        // ok
*p1 = 37;       // error

但不允许指向const对象的指针修改其所指向的值,如上例,p1指向非const变量j,但仍不允许通过p1修改变量j。

 

3、指向const对象的const指针

int j = 42;
const int* const p1 = &j; 

这里,既不能修改p1所指向的对象的值,也不允许修改该指针的指向(即p1中存放的地址值)。

 

4、const和typedef

typedef int *pint;
pint const pt;        // 该语句相当于int *const pt,
const pint pt;      //该语句还是相当于int *const pt,而非const int *pt

如上面的例子,把const放在类型pint之前,容易引起对所定义的真正类的误解,要特别注意。

 

 

总结:

1、const指针,const修饰的是指针;

2、指向const对象的指针,const修饰的是指针所指向对象的类型;

 

 


 

const修饰迭代器

标准库为每一种标准容器(如vector)定义了一种迭代器类型,迭代器是一种检查容器内元素并遍历元素的数据类型。

例如:

vector<int>::iterator iter = vec.begin();

for (; iter!=vec.end(); ++iter) {
    cout<< *iter << endl;
}

 

1、const迭代器

声明一个const迭代器时,必须初始化迭代器,一旦被初始化后,就不能改变它的值,但可以改变它所指向的元素的值:

vector<int> vec(10);
const vector<int>::iterator iter1 = vec.begin();
*iter1 = 1; // ok
iter1++;    // error

 

 

2、指向const的迭代器(const_iterator

每种容器类型还定义了一种名为const_iterator的类型,该类型只能用于读取容器内元素,但不能改变其值。

对const_iterator类型解引用时,返回的是一个const值,不允许用const_iterator进行赋值,例如:

const vector<int>::iterator iter1 = vec.begin();
vector<int>::const_iterator iter2 = vec.begin();

*iter1 += 10;   // ok
*iter2 += 10;   // error

对const_iterator迭代器,它自身的值可以改变(即指向不同的元素),但不能用来改变其所指向的元素的值。

 

const_iterator迭代器可以用来指向一个const的vector对象,而const迭代器对象不被允许,因为它可能改变所指向元素的值,而这个元素是只读的。

const vector<int> vec(10);
const vector<int>::iterator iter1 = vec.begin();    // error
vector<int>::const_iterator iter2 = vec.begin();    // ok

 

 

 

 


 

const修饰类

1、const数据成员

构造函数分为两个阶段执行:a) 初始化阶段,在初始化列表中完成;b) 普通的计算阶段,在构造函数函数体中完成。

在构造函数初始化列表中没有显式提及的每个成员,使用与初始化变量相同的规则来进行初始化:对于类类型数据成员,运行其默认构造函数完成初始化;内置或者复合类型的成员的初始值依赖于对象的作用域:在局部作用域中这些成员不被初始化,而在全局作用域中它们被初始化为0.

没有默认构造函数的类类型成员,以及const或引用类型的成员,必须在构造函数初始化列表中进行初始化。

class T { 
    public:
        T(int k); 

    private:
        int i;
        const int ci; 
        int &ri;
};


// no explicit constructor initializer
T::T(int k)
{
    i = k;  // ok
    ci = k; // error: cannot assign to a const
    ri = i; // error: unitialized
}

// ok, explicit initialze reference and const members
T::T(int k):i(k),ci(k),ri(i) {}

 

 

2、const函数成员

在类定义中,既可以定义const数据成员,也可以定义const函数成员。

在定义const成员函数时:

1、const关键字必须同时出现在声明和定义中,若只出现在一处,会出现编译错误;

2、const成员函数不能改变其所操作对象的数据成员(mutable成员除外);

3、构造函数不能为const,创建类类型的const对象时,运行一个普通构造函数来初始化该const对象即可。

 

例如:

class Sale_item {
    public:
        double avg_price() const;
        bool same_isbn(const Sale_item &rhs) const {return isbn == rhs.isbn;}

    private:
        string isbn;
        unsigned units_sold;
        double revenue;
};

double Sale_item::avg_price() const
{
    if (units_sold) 
        return revenue / units_sold;
    else
        return 0;
}

 

我们知道,对于非static成员函数,都有一个隐含的this参数,this指针与调用成员函数的对象绑定在一起:

1、对于非const成员函数,this是一个指向类类型的const指针,可以改变this所指向的值,但不能改变this所保存的地址;

2、对于const成员函数,this是一个指向const类类型的const指针,既不能改变this所指向的值,也不能改变this所保存的地址;

3、不能从const成员函数返回指向类对象的普通引用,const成员函数只能返回*this作为一个const引用。

 

基于成员函数是否为const,可以重载一个成员函数;同样地,基于一个指针形参是否指向const,可以重载一个成员函数。

const对象只能使用const成员,非const对象可以使用任一成员,但非const版本是一个更好的匹配。

 

class Screen {
    public:
        Screen():contents("hello\n"){}                                 

        Screen& display(ostream &os) 
            { os<<"non const:\t"<<contents; return *this;}

        const Screen& display(ostream &os) const 
            { os<<"const:\t"<<contents; return *this;}

    private:
        string contents;
};


int main()
{
    Screen s1; 
    const Screen s2; 
        
    s1.display(cout);   // non const version
    s2.display(cout);   // const version
}

 

 

3、可变数据成员(mutable)

可变数据成员永远都不能为const,即使它是const对象的成员时也如此。因此,const成员函数可以改变mutable成员。

class T {
    public:
        T():len(0){}

        void increment() const
            { len++; cout<<len<<endl;}
        
    private:
        mutable int len;
};


int main()
{
    const T t;
    t.increment();  // len = 1
    t.increment();  // len = 2
}

如上例,const对象调用const成员函数,改变了mutable数据成员。

 

4、static类成员

static成员是类的组成部分,但不是任何对象的组成部分;

static成员函数没有this指针,static成员函数不能声明为const,也不能声明为virtual;

static数据成员必须在类定义体的外部定义,static成员不是通过构造函数进行初始化,而是应该在定义时进行初始化。

const static整型数据成员在类的定义体中初始化时,该数据成员仍须在类的定义体之外进行定义。

 

<think>好的,我现在需要回答用户关于C++const关键字的用法和应用场景的问题。首先,我需要回忆自己之前学过的C++知识,特别是关于const的部分。记得const主要用于定义常量,但具体有哪些用法可能需要详细梳理一下。 根据用户提供的引用,引用[1]提到const用于定义常量,比#define更好,因为它有类型安全和作用域控制。引用[2]说明const可以用于变量、函数、指针等,还提到了常量变量和指针的例子。引用[3]则是在结构体中使用const的情况,防止结构体成员被修改。 接下来,我需要把const用法分门别类,可能包括变量、指针、函数参数、成员函数、结构体/类中的常量成员等。每个类别下需要给出具体的例子和适用场景,同时结合用户提供的引用内容来支持说明。 例如,在变量中使用const,可以声明常量变量,如const int a = 5,这样变量不可修改。在指针中,可能有指向常量的指针和常量指针的区别,比如const int* ptr和int* const ptr的区别。函数参数中使用const可以保护参数不被修改,比如void func(const int& x)。类中的const成员函数表示该函数不会修改类的成员变量,如void get() const;。 应用场景方面,可能需要提到接口设计中的常量参数,保护数据不被意外修改,或者在多线程环境中保证数据不变性。结构体中使用const,如引用[3]中的例子,可以防止结构体成员被修改,增加代码的安全性。 另外,需要注意用户提供的引用中提到的constconstexpr的区别,但用户的问题主要是关于const,所以可能只需要简要提及,或者放在应用场景中说明。 现在需要组织这些信息,确保结构清晰,每个用法都有例子和场景说明,并正确引用提供的参考资料。同时,按照用户的系统级指令,数学公式要用$...$,但这里可能不太涉及数学公式,所以可能需要检查是否有需要格式化的地方。另外,生成相关问题,可能需要围绕const的其他用法、与constexpr的区别、常见错误等来提问。 在写作过程中,要确保使用中文,LaTeX语法正确,引用标识自然添加。可能需要多次检查是否符合所有要求,比如独立公式是否用$$...$$,但这里可能没有太多公式,所以可能不需要。但代码块需要用正确的格式,例如结构体的例子可能需要用代码块展示。 最后,生成相关问题,考虑用户可能想进一步了解的内容,如const在函数返回值中的使用,或者const在类中的其他应用,或者与mutable关键字的结合使用等。</think>### C++ `const` 关键字的使用方法及场景 `const` 是 C++ 中用于声明常量或指定对象只读性的关键字。它在不同上下文中具有多种用途,能够增强代码的安全性、可读性和优化潜力。 --- #### 一、基本用法 1. **常量变量** 声明不可修改的变量,替代宏定义(如 `#define`),提供类型安全和作用域控制[^1]: ```cpp const int MAX_SIZE = 100; // 常量整数 const double PI = 3.14159; // 常量浮点数 ``` 2. **指针与 `const`** - **指向常量的指针**:指针指向的值不可修改,但指针本身可以指向其他地址。 ```cpp const int* ptr = &value; // ptr 可以指向其他地址,但 *ptr 不可修改 ``` - **常量指针**:指针本身不可修改,但指向的值可以修改(若值本身非常量)。 ```cpp int* const ptr = &value; // ptr 固定指向某个地址,但 *ptr 可修改 ``` 3. **函数参数中的 `const`** 保护参数不被意外修改,常用于传递大型对象(如结构体或类实例): ```cpp void printData(const Student& s) { // s.age = 150; // 错误:s 是常量引用,成员不可修改[^3] cout << s.age; } ``` --- #### 二、进阶用法 4. **常量成员函数** 在类中声明成员函数为 `const`,表示该函数不会修改类的成员变量: ```cpp class MyClass { public: int getValue() const { // 保证不修改成员变量 return value; } private: int value; }; ``` 5. **结构体中的 `const`** 结合指针或引用使用时,可防止结构体成员被修改: ```cpp struct Student { int age; string name; }; void processStudent(const Student* s) { // s->age = 150; // 错误:s 是常量指针,成员不可修改 } ``` 6. **常量返回值** 防止函数返回值被修改(适用于返回指针或引用的情况): ```cpp const string& getDefaultName() { static string name = "Default"; return name; } ``` --- #### 三、典型应用场景 1. **接口设计** 使用 `const` 修饰函数参数或返回值,明确接口的“只读”语义,避免数据被意外修改。 2. **多线程安全** 常量对象或成员函数在多线程环境中天然安全,无需额外同步机制。 3. **优化编译器** 编译器可能对 `const` 变量或表达式进行优化(如常量折叠)。 4. **配置参数** 全局常量(如配置值、数学常数)适合用 `const` 声明,替代宏定义。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值