C++:const修饰变量、形参、函数

本文详细探讨了C++中const关键字和constexpr的区别,以及const修饰的变量在不同情况下的行为。当const修饰的局部变量通过指针修改时,其值在某些情况下可能不会改变,这取决于变量的初始化方式和存储位置。同时,文章解释了const成员函数和const指针的用法,强调了const在保证代码健壮性和提高效率方面的作用。
摘要由CSDN通过智能技术生成

 const修饰变量


int main(int argc, char* argv[])
{
    const int b = 3;
    int* c = (int*)&b;
    *c = 5;//这里即便把b所在地址的值修改了,但后面调用b的时候,b仍然是3
    cout << "*c的值为:" << *c << endl;//5
    cout << "b的值为:" << b << endl;//3
    return 10086;
}

 

对于一个const常量,通过指针修改其值后,可以看到指向b的指针输出为5,而b依然是3,没有改变,为什么呢?

调试一下,发现并没有问题,指针指向的内容确实改变了

 

其实这种情况,编译器会认为b是一个在编译期就可计算出结果的常量,就像宏定义一样,用到b的地方会被编译器替换成3。即在没有通过指针修改b的值的时候,所有b的值都已经默认为3了。

假如通过一个函数的返回值给b赋值,即运行时初始化,这样b的值在编译器就无法确定了

int set_value(int i)
{
    return i;
}

int main(int argc, char* argv[])
{
    const int b = set_value(3);
    int* c = (int*)&b;
    *c = 5;//这里即便把b所在地址的值修改了,但后面调用b的时候,b仍然是3
    cout << "*c的值为:" << *c << endl;//5
    cout << "b的值为:" << b << endl;//5
    return 10086;
}

 

此时,b的值也就变化了。

又知道,constexpr可以赋予常量表达式获得在程序编译阶段计算出结果的能力,上面的例子中,set_value()函数无法在编译时确定b的值,那假如我们将函数用constexpr修饰的话,是不是函数又可以在编译时期得到b的值了,进而,后面所有的b的值就又可以提前确定了,再通过指针修改b,b也不会变化了。验证一下:

确实如此。

还有一个情况,我们知道,全局常量的存储位置是数据段的全局/静态区,这个内存区是只读的,而局部常量存放于堆栈中,虽然不能直接修改,但可以通过指针间接修改。

上面的例子就是因为我们把声明为了局部变量,所以b可以通过指针被间接修改,假如把b声明为一个全局变量,那么就无法再通过指针修改其值了。

constexpr int set_value(int i)
{
    return i;
}
const int b = set_value(3);
//只要声明为全局变量,不管能不能在编译期获得值,都不能通过指针修改其内容
int main(int argc, char* argv[])
{
    int* c = (int*)&b;
    *c = 5;//这里即便把b所在地址的值修改了,但后面调用b的时候,b仍然是3
    cout << "*c的值为:" << *c << endl;//5
    cout << "b的值为:" << b << endl;//3
    return 10086;
}

补充一点:关于const和constexpr的区别

说区别,我其实都不认为二者有什么相似性,充其量有一些联系。

const是变量类型名的一部分, part of type name,一个名字叫“const T”或者“T const”的类型,和T这个类型本身处于一种平级的关系,和T不同的就在于这个类型的对象自产生后就不能再更改了。

constexpr是声明的一部分,即 part of a declaration,他不是变量类型的一部分。当他出现在一个变量的声明中时,他要求编译器在编译期间就初始化并确定下来这个变量(否则编译报错);当他出现在一个函数的声明中时,他要求至少有一条return路径可以(但不是必须)在编译中确定下来,即返回的是编译期常量。

二者的联系就在于,在使用constexpr声明一个类型为T的变量时,constexpr会自动把这个变量定义为const T类型。即constexpr在完成它本职工作(告诉编译器这是个编译期常量)的同时,还把原来的T类型改为了const T类型。这就是二者的联系。
链接:https://www.zhihu.com/question/35614219/answer/1477686611

const修饰指针

const int *ptr
int const *ptr //和上面等价
int * const ptr

* 在 const前面,则const直接修饰的指针,指针位置不能瞎动;
* 在 const后面,则const修饰的是 *ptr,则表征指针指向的这个值不能瞎动;
如形参为const A* a,则不能对传递进来的指针的内容进行改变,保护了原指针所指向的内容。
如形参为const A& a,则不能对传递进来的引用对象进行改变,保护了原对象的属性。

如果一个成员函数明确知道其不会修改数据成员,那么最好将其声明为const,因为const成员函数中不允许对数据成员进行修改,如果修改,编译器将报错,这大大提高了程序的健壮性。

如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加const修饰。
例如不要将函数void Func1(int x) 写成void Func1(const int x)。同理不要将函数void Func2(A a) 写成void Func2(const A a)。其中A为用户自定义的数据类型。

对于非内部数据类型的参数而言,像void Func(A a) 这样声明的函数注定效率比较底。因为函数体内将产生A类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。
为了提高效率,可以将函数声明改为void Func(A &a),因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。但是函数void Func(A &a) 存在一个缺点:“引用传递”有可能改变参数a,这是我们不期望的。解决这个问题很容易,加const修饰即可,因此函数最终成为void Func(const A &a)。
以此类推,是否应将void Func(int x) 改写为void Func(const int &x),以便提高效率?完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。

const修饰函数

const可以修饰类成员函数,防止类成员函数中除了static成员之外的其他成员被修改。

在类中将成员函数修饰为const表明在该函数体内,不能修改对象的数据成员而且不能调用非const函数。为什么不能调用非const函数?因为非const函数可能修改数据成员,const成员函数是不能修改数据成员的,所以在const成员函数内只能调用const函数。即便某个非const成员函数没有改变成员变量,也不能被const成员函数调用。

下图是另一种情况,当类对象指针作为const形参时,只能通过指针调用const成员函数

 

class people
{
    int private_val_1;
public:
    String name;
    int age;
    int high;
    bool sex;
public:
    people(){}
    people(String name, int age, int high, bool sex)
    {
        this->name = name;
        this->age = age;
        this->high = high;
        this->sex = sex;
    }
    void Rename(String name)
    {
        this->name = name;
    }
    void Printname() const
    {
        //const表示此成员函数不允许修改成员变量
        //也不允许调用其他有修改成员变量权限的成员函数(即非const成员函数)
        cout << name << endl;
        cout << age << endl;
        cout << high << endl;
        cout << sex << endl;
    }
};

void fun(people* in)
{
    in->age++;
    //可以调用和修改所有公有变量,因为形参没有const限制
    //可以调用所有成员函数,因为形参没有const限制
}

void fun2(const people* in)
{
    in->age;//可以调用所有公有变量
    in->age++;//错!不可以修改变量
    in->Printname();//虽然有const限制,但仍然可以调用没有修改成员变量权限的成员函数
    in.Rename();//错!此成员函数没有const限制,有修改成员变量的权限

    people* temp = new people();
    in = temp;//可以修改
}

void fun3(people* const in)
{
    in->age++;//大部分都和fun2相同
    //区别如下:不能修改in的指向了
    people* temp = new people();
    in = temp;//这样就错了

}

 

  • 3
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值