【c++】const关键字解析

首先我们来看一下为什么要使用const呢?因为采用符号常量写出的代码更容易维护;指针常常是边读边移动,许多函数参数都是只读不谢的,const最常见的用法就是作为数组的界和switch分情况标记(也可以用枚举符代替)。
分类:
常变量:const 类型说明符 变量名
常引用: const 类型说明符 & 引用名
常对象:类名::fun(形参) const
常数组:类型说明符 const 数组名[大小]
常指针:const 类型说明符* 指针名 ,类型说明符* const 指针名
注意:在常变量(const 类型说明符 变量名)、常引用(const 类型说明符 & 引用名)、常对象(类名 const 对象名)、常数组(类型说明符 const 数组名[大小]),const”与“类型说明符”或类名(其实类名是一种自定义的类型说明符)的位置可以互换
比如:const int a = 5;与int const a = 5;等同
类名 const 对象名 与 const 类名 对象名 等同
1、用法1:常量
取代了c中的宏定义,声明时必须进行初始化(c++中则不然)。const限制了常量的使用方式,并没有描述常量应该如何分配。如果编译器知道了某const的所有使用,它甚至可以不为该const分配空间。最简单的常见情况就是常量的值在编译时已知,而且不需要分配存储。
用const声明的变量虽然增加了分配空间,但是可以不保证类型安全。
c标准中,const定义的常量是全局的,c++中视声明位置而定
2、用法2:指针和常量
使用指针时涉及到两个对象:改指针本身和被它所指的对象而不是使这个指针成为常量。要将指针本身而不是被指对象声明为常量,必须使用声明运算符*const
所以出现在*之前的const是作为基础类型的一部分,
char* const cp;//到char的const指针
pc不能指向别的字符串,但可以修改其指向的字符串的内容(指向不能变)
char const *pc1;//到const char的指针
内容不能改变,可以改变指向
const char* pc2;//到const char 的指针(后两个声明是等同的)
内容不能改变可以改变指向
从右往左读的记忆方式:
注意:
允许把非const对象的地址赋给指向const对象的指针,不允许把一个const对象的地址赋给一个普通的、非const对象的指针
3、用法3:const修饰函数传入参数
将函数传入参数声明为const,以指明使用这种参数仅仅是为了效率的原因,而不是想让调用函数能够修改对象的值。同理,将指针参数声明为const,函数将不修改由这个参数所指的对象,通常修饰指针参数和引用参数:
void Fun(const A * in);//修饰指针型传入参数
void Fun(const A& in);//修饰引用型传入参数
4、用法4修饰函数返回值
可以组织用户修改返回值,返回值也要相应的付给一个常量或常指针
5、用法5:const修饰成员函数(c++特性)
const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数
const对象的成员是不能修改的,而通过指针维护的对象确实可以修改的
const成员函数不可以修改对象的数据,不管对象是否具有const性质。编译时以是否修改成员数据为依据进行检查。
具有展开来说:
(一)常量与指针
常量与指针放在一起很容易让人难以区分,,对于常量指针和指针常量也不是所有学习c/c++的人都能说清的:
例如:
const int *m1 = new int(10);
int * const m2 = new int(20);
const 到底是修饰指针还是修饰指针指向的区域呢:实际上const只对它左边的东西起作用,唯一的例外就是const本身就是最左边的修饰符,那么它才对右边的东西起作用,根据这个规则来判断,m1应该是常量指针(不能通过m1来修改它指向的内容),而m2应该是指针常量(不能让m2指向其它的内存模块)
1、对于常量指针,不能通过该指针来改变所指的内容。即下面的操作是错误的。
这里写图片描述
因为你在试图通过pi改变它所指向的内容,但是还是可以通过其它方式修改的,例如:
这里写图片描述
第二种方法也是同样效果。
实际上,在将程序载入内存的时候,会有专门的一块内存区域来存放常量。但是,上面的i本身并不是常量,还是存放在堆或者栈中的。仍然可以对值进行修改,我们说的不能修改指向是编译器的一种限制。
2、根据上面的const的规则,const int* m1 = new int(10);
int const *m1 = new int (10);
3、在函数参数中指针常量表示不允许将该指针指向其他内容
这里写图片描述
4、在函数参数中使用常量指针时表示在函数中不能改变指针所指向的内容
这里写图片描述
我们还可以这样来防止调用者改变参数的值。但是,这样的限制是为参数调用这,我们也不要试图去改变参数的值
这里写图片描述
(二)常量与引用
引用就是另一个变量的别名,它本身就是一个常量,就是说不能再让一个引用成为另外一个变量的别名,那么他们只剩下代表的内存区域是否可变。
这里写图片描述
在系统加载程序的时候,系统会将内存分为4个区域:堆区 栈区 全局区(静态) 和代码区,对于常量来说系统没有专门划分区域来保护其中的数据不能被修改。也就是说,使用常量的方式对数据进行保护是通过编译器做语法限制来实现的。我们仍然可以绕过编译器去修改被定义为常量的内区域,
这里写图片描述
(三)常量函数
常量函数是c++的一种扩展,它更好的确保了c++中类的封装性,在c++中,为了防止类的数据成员被非法访问,将类的成员函数分成了两类,一类是常量成员函数,另一类是非常量成员函数。在一个函数的签名后面加上const后该函数就变成了常量函数。对于常量函数,最关键的不同是编译器不允许其修改类的数据成员,
这里写图片描述
上面的代码中,常量函数func内试图去修改数据成员intvalue的值,因此将在编译的时候引发异常
当然对于非常量的成员函数,我们可以根据需要读取或者修改数据成员的值。但是,这要依赖调用函数的对象是否是常量,如果我们把一个类定义为常量,我们的本意是希望它的状态不会被改变,那么如果一个常量的对象调用它的非常亮函数会发生什么后果呢?
这里写图片描述
从上面的代码可以看出,由于常量对象的状态不允许被修改,因此通过常量对象调用非常量函数时将会产生语法错误。实际上,我们知道每个成员函数都有一个隐含的指向对象本身的this指针,而常量函数则包含一个this的常量指针
如下:
void inspect(const Fres* this) const;
void mutate(Fred* this);
也就是说对于常量函数,我们不能通过this指针区修改对象对应的内存块。但是,在上面我们已经知道,这仅仅是编译器的限制,我们仍然可以绕过编译器的限制,去改变对象的状态。

#include<stdio.h>
#include<iostream>
using namespace std;
class Fred
{
public:
    void inspect()const;
    void mutate();
private:
    int intvalue;

};
void Fred::inspect()const
{
    cout << "start initvalue" << intvalue << endl;
    //这里我们根据this指针重新定义了一个指向桶一块内存地址的指针
    //通过这个新定义的指针,我们仍然可以修改对象的状态
    Fred* pFred = (Fred*)this;
    pFred->intvalue = 50;
    cout << "After initvalue" << intvalue << endl;
}
int main()
{
    Fred fred;
    fred.inspect();
    return 0;
}

这里写图片描述
以上代码说明只要我们愿意还是可以同规格常量函数修改对象的状态。
也可以通过在数据成员前面加上mutable,以允许该成员可以砸常量函数中被修改。

#include<stdio.h>
#include<iostream>
using namespace std;
class Fred
{
public:
    void inspect()const;
    void mutate();
private:
    mutable int intvalue;

};
void Fred::inspect()const
{
    intvalue = 100;
}
int main()
{
    Fred fred;
    fred.inspect();
    return 0;
}

这里写图片描述
注意:并不是所有的编译器都支持mutable.
关于常量函数还有一个问题是重载

#include<stdio.h>
#include<string>
#include<iostream>
using namespace std;
class Fred
{
public:
    void func()const;
    void func();
private:
    mutable int intvalue;

};
void Fred::func()const
{
    cout << "add const" << endl;
}
void Fred::func()
{
    cout << "no const" << endl;
}
void UserCode(Fred& fred, const  Fred& cfred)
{
    fred.func();
    cfred.func();
}
int main()
{
    Fred fred;
    UserCode(fred, fred);
    return 0;
}

这里写图片描述
当存在同名同参数和返回值的常量函数和非常量函数时,具体调用哪个函数是根据调用对象是常量对象还是非常量对象决定的。常量对象调用常量成员,非常量对象调用非常量成员。
(四)常量返回值
很多时候我们的函数中会返回一个地址或者引用。调用这得到这个返回的地址或者引用后就可以修改所指或者代表的对象。这个时候如果我们不希望这个函数的调用这修改这个返回的内容,就应该返回一个常量。


c和c++const的区别:
1、c++中的const正常情况下是看成编译器的常量,编译器并不为const分配空间,只是在编译期间将其值保存在名字表中,并在合适的时候折合在代码中,

#include<iostream>
using namespace std;
int main()
{
    const int a = 1;
    const int b = 2;
    int array[a + b] = { 0 };
    for (int i = 0; i < sizeof(array) / sizeof(*array); i++)
    {
        cout << *array << endl;
    }
    return 0;
}

这里写图片描述
可以看到可以通过编译,并且可以正常运行,但稍加修改后放在c编译器中就会出错。
这里写图片描述
出现这种情况的原因是:在C中,const默认是内部连接,如果想在C++中达到以上的效果,必须要用extern关键字,即C++中,const默认使用内部连接,而C中使用外部连接,所以编译器不知道编译时的值,而且数组定义时的下标必须是常量。
2、在C语言中:const int size这个语句是正确的,因为它被C编译器看作为一个声明,指明在别的地方分配存储空间,但在C++中这样是不对的,C++中const默认是内部连接,如果在C++中想达到以上的效果,必须使用extern关键字,即C++中const默认使用内部连接,而C中默认使用外部连接
(1)内连接:
编译器只对正在编译的文件创建存储空间,别的文件可以使用相同的表示符或全局变量,C/C++中内连接使用关键字static指定
(2)外连接:所有被编译过得文件创建一片单独存储空间,一旦空间被创建,连接器必须解决对这片空间的引用,全局变量和函数使用外部连接,通过extern关键字声明,可以从其他文件访问相应的变量和函数
3、C++中,是否为const分配空间要看具体情况,如果加上关键字extern或者取const变量地址,则编译器就要为const分配存储空间。
4、C++中定义常量的时候不再采用define,因为define只做简单的宏替换,并不提供类型检查

猜你喜欢

转载自blog.csdn.net/flowing_wind/article/details/81209845