14-4 const 类对象和 const 成员函数

在第5.1节——常量变量(命名常量)中,你了解到基本数据类型(如int、double、char等)的对象可通过const关键字定义为常量。所有const变量必须在创建时初始化。

const int x;      // compile error: not initialized
const int y{};    // ok: value initialized
const int z{ 5 }; // ok: list initialized

同样地,类类型对象(结构体、类和联合体)也可通过const关键字声明为常量。此类对象同样必须在创建时完成初始化。

struct Date
{
    int year {};
    int month {};
    int day {};
};

int main()
{
    const Date today { 2020, 10, 14 }; // const class type object

    return 0;
}

与普通变量类似,当需要确保对象创建后不可修改时,通常应将类类型对象声明为 const(或 constexpr)。


禁止修改const对象的数据成员

一旦const类型的对象被初始化,任何试图修改该对象数据成员的行为均被禁止,因为这将违反对象的const属性。这包括直接修改成员变量(若其为public类型),或调用会设置成员变量值的成员函数。

struct Date
{
    int year {};
    int month {};
    int day {};

    void incrementDay()
    {
        ++day;
    }
};

int main()
{
    const Date today { 2020, 10, 14 }; // const

    today.day += 1;        // compile error: can't modify member of const object
    today.incrementDay();  // compile error: can't call member function that modifies member of const object

    return 0;
}

image

image


常量对象不能调用非常量成员函数

你可能会惊讶地发现,这段代码也会导致编译错误:

#include <iostream>

struct Date
{
    int year {};
    int month {};
    int day {};

    void print()
    {
        std::cout << year << '/' << month << '/' << day;
    }
};

int main()
{
    const Date today { 2020, 10, 14 }; // const

    today.print();  // compile error: can't call non-const member function

    return 0;
}

image

尽管print()函数并未尝试修改成员变量,但我们对today.print()的调用仍构成const违规。这是因为print()成员函数本身未声明为const。编译器不允许对const对象调用非const成员函数。


常量成员函数

为解决上述问题,我们需要将print()定义为常量成员函数。常量成员函数const member function是保证不会修改对象或调用任何非常量成员函数(因其可能修改对象)的成员函数。

将print()设为常量成员函数很简单——只需在函数原型function prototype的参数列表之后、函数体之前添加const关键字:

#include <iostream>

struct Date
{
    int year {};
    int month {};
    int day {};

    void print() const // now a const member function
    {
        std::cout << year << '/' << month << '/' << day;
    }
};

int main()
{
    const Date today { 2020, 10, 14 }; // const

    today.print();  // ok: const object can call const member function

    return 0;
}

image

在上例中,print() 已被定义为 const 成员函数,这意味着我们可以将其用于 const 对象(例如 today)。

对于高级读者:
对于在类定义外部定义的成员函数,必须在类定义中的函数声明和类定义外部的函数定义中都使用 const 关键字。我们在第 15.2 课——类与头文件中展示了此示例。
构造函数不能声明为 const,因为它们需要初始化对象的成员,这要求对成员进行修改。构造函数相关内容详见第14.9课——构造函数入门。

若 const 成员函数试图修改数据成员或调用非 const 成员函数,将导致编译器报错。例如:

struct Date
{
    int year {};
    int month {};
    int day {};

    void incrementDay() const // made const
    {
        ++day; // compile error: const function can't modify member
    }
};

int main()
{
    const Date today { 2020, 10, 14 }; // const

    today.incrementDay();

    return 0;
}

image

在此示例中,incrementDay() 被标记为 const 成员函数,但它试图修改 day。这将导致编译器报错。

const 成员函数可以像普通函数一样修改非成员变量(如局部变量和函数参数),并调用非成员函数。const 仅适用于成员变量。

关键要点:
常量成员函数不能:修改隐式对象,调用非常量成员函数。
常量成员函数可以:修改非隐式对象,调用常量成员函数,调用非成员函数。


常量成员函数可用于非常量对象

非常量对象可以调用常量成员函数:

#include <iostream>

struct Date
{
    int year {};
    int month {};
    int day {};

    void print() const // const
    {
        std::cout << year << '/' << month << '/' << day;
    }
};

int main()
{
    Date today { 2020, 10, 14 }; // non-const

    today.print();  // ok: can call const member function on non-const object

    return 0;
}

image

由于 const 成员函数既可用于 const 对象也可用于非 const 对象,若成员函数不修改对象状态,则应将其声明为 const。

最佳实践

不修改(且永远不会修改)对象状态的成员函数应声明为 const,以便其既可用于 const 对象也可用于非 const 对象。

请谨慎选择应用 const 的成员函数。一旦成员函数被声明为 const,该函数即可在 const 对象上调用。若后续移除成员函数的 const 声明,将导致所有在 const 对象上调用该成员函数的代码失效。


Const对象通过经过const参考

尽管实例格局的变量是一种方式来创建const的对象,更常见的方式得到常数目的是通过一个对象一个能通过const参考。

在教训12.5--通过由左值的参考,我们复盖的案情通过类型参数通常量基准而不是价值。 来回顾一下,通过一个类型参数通过值导致一个复制的类作出(这是缓慢)--大多数时候,我们不需要复制、参照的原始参数的工作只是现和避免制作副本。 我们通常使用参考const允许的功能接受const左值的论点和rvalue参数(例如文本和临时的对象)。

你能找出什么地方错了下列代码?

#include <iostream>

struct Date
{
    int year {};
    int month {};
    int day {};

    void print() // non-const
    {
        std::cout << year << '/' << month << '/' << day;
    }
};

void doSomething(const Date& date)
{
    date.print();
}

int main()
{
    Date today { 2020, 10, 14 }; // non-const
    today.print();

    doSomething(today);

    return 0;
}

image

答案在于,在 doSomething() 函数内部,date 被视为 const 对象(因为它是通过 const 引用传递的)。而使用这个 const date 对象调用非 const 成员函数 print() 时,由于无法对 const 对象调用非 const 成员函数,这将导致编译错误。

解决方法很简单:将 print() 声明为 const:

#include <iostream>

struct Date
{
    int year {};
    int month {};
    int day {};

    void print() const // now const
    {
        std::cout << year << '/' << month << '/' << day;
    }
};

void doSomething(const Date& date)
{
    date.print();
}

int main()
{
    Date today { 2020, 10, 14 }; // non-const
    today.print();

    doSomething(today);

    return 0;
}

image

现在在函数 doSomething() 中,const date 将能够成功调用 const 成员函数 print()。


成员函数的const与非const重载

最后,虽然这种做法并不常见,但确实可以重载成员函数,使其同时拥有const版本和非const版本。这是因为const限定符function’s signature被视为函数签名的一部分,因此仅在const属性const-ness上存在差异的两个函数会被视为不同的函数。

#include <iostream>

struct Something
{
    void print()
    {
        std::cout << "non-const\n";
    }

    void print() const
    {
        std::cout << "const\n";
    }
};

int main()
{
    Something s1{};
    s1.print(); // calls print()

    const Something s2{};
    s2.print(); // calls print() const

    return 0;
}

这将输出:

image

当函数的返回值需要在常量性方面有所区别时,通常会采用常量版和非常量版函数重载的方式。这种情况相当罕见。

posted @ 2025-12-27 08:22  游翔  阅读(31)  评论(0)    收藏  举报