C++ const用法总结

文章转载自 https://www.cnblogs.com/lanjianhappy/p/7298427.html

  • 常变量: const 类型说明符 变量名
  • 常引用: const 类型说明符 &引用名
  • 常对象: 类名 const 对象名
  • 常成员函数: 类名::fun(形参) const
  • 常数组: 类型说明符 const 数组名[大小]
  • 常指针: const 类型说明符* 指针名 ,类型说明符* const 指针名
    const与 “类型说明符”或“类名”(其实类名是一种自定义的类型说明符) 的位置可以互换。

常变量

取代了C中的宏定义,声明时必须进行初始化(!c++类中则不然)。const限制了常量的使用方式,并没有描述常量应该如何分配。如果编译器知道了某const的所有使用,它甚至可以不为该const分配空间。最简单的常见情况就是常量的值在编译时已知,而且不需要分配存储。

指针和常量

常量与指针放在一起很容易让人迷糊。

const int *m1 = new int(10);
int* const m2 = new int(20);

在上面的两个表达式中,最容易让人迷惑的是const到底是修饰指针还是指针指向的内存区域?其实,只要知道:const只对它左边的东西起作用,唯一的例外就是const本身就是最左边的修饰符,那么它才会对右边的东西起作用。根据这个规则来判断,m1应该是常量指针(即,不能通过m1来修改它所指向的内容。);而m2应该是指针常量(即,不能让m2指向其他的内存模块)。

常量与引用

常量与引用的关系稍微简单一点。因为引用就是另一个变量的别名,它本身就是一个常量。也就是说不能再让一个引用成为另外一个变量的别名, 那么他们只剩下代表的内存区域是否可变。即:

 int i = 10;

    // 正确:表示不能通过该引用去修改对应的内存的内容。

    const int& ri = i;

    // 错误!不能这样写。

    int& const rci = i;

    由此可见,如果我们不希望函数的调用者改变参数的值。最可靠的方法应该是使用引用。下面的操作会存在编译错误:

    void func(const int& i)

    {

    // 错误!不能通过i去改变它所代表的内存区域。

    i = 100;

    }

    int main()

    {

    int i = 10;

    func(i);

    return 0;

    }

这里已经明白了常量与指针以及常量与引用的关系。但是,有必要深入的说明以下。在系统加载程序的时候,系统会将内存分为4个区域:堆区、栈区、全局区(静态)和代码区。从这里可以看出,对于常量来说,系统没有划定专门的区域来保护其中的数据不能被更改。也就是说,使用常量的方式对数据进行保护是通过编译器作语法限制来实现的。我们仍然可以绕过编译器的限制去修改被定义为“常量”的内存区域。看下面的代码:

 const int i = 10;

    // 这里i已经被定义为常量,但是我们仍然可以通过另外的方式去修改它的值。

    // 这说明把i定义为常量,实际上是防止通过i去修改所代表的内存。

    int *pi = (int*) &i;

常量与函数

常量函数是C++对常量的一个扩展,它很好的确保了C++中类的封装性。在C++中,为了防止类的数据成员被非法访问,将类的成员函数分成了两类,一类是常量成员函数(也被称为观察者);另一类是非常量成员函数(也被成为变异者)。在一个函数的签名后面加上关键字const后该函数就成了常量函数。对于常量函数,最关键的不同是编译器不允许其修改类的数据成员。例如:

 class Test

    {

    public:

    void func() const;

    private:

    int intValue;

    };

    void Test::func() const

    {

    intValue = 100;

    }

上面的代码中,常量函数func函数内试图去改变数据成员intValue的值,因此将在编译的时候引发异常。

当然,对于非常量的成员函数,我们可以根据需要读取或修改数据成员的值。但是,这要依赖调用函数的对象是否是常量。通常,如果我们把一个类定义为常量,我们的本意是希望他的状态(数据成员)不会被改变。那么,如果一个常量的对象调用它的非常量函数会产生什么后果呢?看下面的代码:

class Fred{

    public:

    void inspect() const;

    void mutate();

    };

    void UserCode(Fred& changeable, const Fred& unChangeable)

    {

    changeable.inspect(); // 正确,非常量对象可以调用常量函数。

    changeable.mutate(); // 正确,非常量对象也允许修改调用非常量成员函数修改数据成员。

    unChangeable.inspect(); // 正确,常量对象只能调用常理函数。因为不希望修改对象状态。

    unChangeable.mutate(); // 错误!常量对象的状态不能被修改,而非常量函数存在修改对象状态的可能

    }

从上面的代码可以看出,由于常量对象的状态不允许被修改,因此,通过常量对象调用非常量函数时将会产生语法错误。实际上,我们知道每个成员函数都有一个隐含的指向对象本身的this指针。而常量函数则包含一个this的常量指针。如下:

void inspect(const Fred* this) const;

void mutate(Fred* this);

也就是说对于常量函数,我们不能通过this指针去修改对象对应的内存块。但是,在上面我们已经知道,这仅仅是编译器的限制,我们仍然可以绕过编译器的限制,去改变对象的状态。看下面的代码:

class Fred{

    public:

    void inspect() const;
    private:

    int intValue;

    };

    void Fred::inspect() const

    {

    cout << "At the beginning. intValue = "<< intValue << endl;

    // 这里,我们根据this指针重新定义了一个指向同一块内存地址的指针。

    // 通过这个新定义的指针,我们仍然可以修改对象的状态。

    Fred* pFred = (Fred*)this;

    pFred->intValue = 50;

    cout << "Fred::inspect() called. intValue = "<< intValue << endl;

    }

    int main()

    {

    Fred fred;

    fred.inspect();

    return 0;

    }

上面的代码说明,只要我们愿意,我们还是可以通过常量函数修改对象的状态。同理,对于常量对象,我们也可以构造另外一个指向同一块内存的指针去修改它的状态。这里就不作过多描述了。
关于常量函数,还有一个问题是重载。

    #include <iostream>

    #include <string>

    using namespace std;

    class Fred{

    public:

    void func() const;

    void func();

    };

    void Fred::func() const

    {

    cout << "const function is called."<< endl;

    }

    void Fred::func()

    {

    cout << "non-const function is called."<< endl;

    }

    void UserCode(Fred& fred, const Fred& cFred)

    {

    cout << "fred is non-const object, and the result of fred.func() is:" << endl;

    fred.func();

    cout << "cFred is const object, and the result of cFred.func() is:" << endl;

    cFred.func();

    }

    int main()

    {

    Fred fred;

    UserCode(fred, fred);

    return 0;

    }

    输出结果为:

    fred is non-const object, and the result of fred.func() is:

    non-const function is called.

    cFred is const object, and the result of cFred.func() is:

    const function is called.

从上面的输出结果,我们可以看出。当存在同名同参数和返回值的常量函数和非常量函数时,具体调用哪个函数是根据调用对象是常量对像还是非常量对象来决定的。常量对象调用常量成员;非常量对象调用非常量的成员。

总之,我们需要明白常量函数是为了最大程度的保证对象的安全。通过使用常量函数,我们可以只允许必要的操作去改变对象的状态,从而防止误操作对对象状态的破坏。但是,就像上面看见的一样,这样的保护其实是有限的。关键还是在于我们开发人员要严格的遵守使用规则。另外需要注意的是常量对象不允许调用非常量的函数。这样的规定虽然很武断,但如果我们都根据原则去编写或使用类的话这样的规定也就完全可以理解了。

常量返回值

很多时候,我们的函数中会返回一个地址或者引用。调用这得到这个返回的地址或者引用后就可以修改所指向或者代表的对象。这个时候如果我们不希望这个函数的调用这修改这个返回的内容,就应该返回一个常量。

Tips

+++++++++++++++++++++++++++++++++++++++

c++ 中const

+++++++++++++++++++++++++++++++++++++++

const常量

const int max = 100;  

优点:const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误(边际效应)

const 修饰类的数据成员

class A
{

    const int size;

    …

}

const数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的。因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。所以不能在类声明中初始化const数据成员,因为类的对象未被创建时,编译器不知道const 数据成员的值是什么。如

class A

{

 const int size = 100;    //错误

 int array[size];         //错误,未知的size

}

const数据成员的初始化只能在类的构造函数的初始化表中进行。要想建立在整个类中都恒定的常量,应该用类中的枚举常量来实现。如

class A

{…

 enum {size1=100, size2 = 200 };

int array1[size1];

int array2[size2];

}

枚举常量不会占用对象的存储空间,他们在编译时被全部求值。但是枚举常量的隐含数据类型是整数,其最大值有限,且不能表示浮点数。

const修饰指针的情况

nt b = 500; 
const int* a = &                  [1] 
int const *a = &                  [2] 
int* const a = &                  [3] 
const int* const a = &       [4]

如果你能区分出上述四种情况,那么,恭喜你,你已经迈出了可喜的一步。不知道,也没关系,我们可以参考《Effectivec++》Item21上的做法,如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。因此,[1]和[2]的情况相同,都是指针所指向的内容为常量(const放在变量声明符的位置无关),这种情况下不允许对内容进行更改操作,如不能*a = 3;[3]为指针本身是常量,而指针所指向的内容不是常量,这种情况下不能对指针本身进行更改操作,如a++是错误的;[4]为指针本身和指向的内容均为常量。

猜你喜欢

转载自blog.csdn.net/yc1203968305/article/details/83115743