C/C++中const探讨

学习const之前先来回顾三个概念,常量,变量,常变量

常量:常量是程序运行过程中其值不能改变的量,我们任意说一个数字,一个单词都是一个常量,例如3就是常量,apple也是常量,常量就相当于数学上常数的概念,只不过计算机数据类型并不是只有数字,所以称之为常量。根据数据类型的不同又分为数值常量,字符常量,字符串常量和符号常量。

变量:变量是在程序运行过程中其值可以改变的量,变量有变量类型,变量名和变量值三个属性。变量类型就是该变量的数据类型,变量名代表该存储空间存放的值。

常变量:常变量是在C语言中是用const修饰的变量,常变量值不能被改写,只能使用不能改写,除非用友元函数调用。

#define  PI   3.1415             //常量

int a = 10;                      //变量

const int b = 10;                //常变量

-------------------------------------------------------------------const----------------------------------------------------------------------------------------------

C语言中的const

       const 是 constant 的缩写,是恒定不变的意思,也翻译为常量和常数等 。正是因为这一点,很多人认为被const修饰的值是常量。这是不精确的,精确来说应该是只读的变量,其值在编译时不能被使用,因为编译器在编译时不知道存储的内容。const推出的初始目的,正是为了取代预编译指令,消除了它的缺点,同时继承了它的优点。

1 const修饰只读变量

定义const只读变量,具有不可变性。例如:

const int a = 10;       //a = 10
  //  a++;                    //error,表达式必须是可修改的左值
  //  int &a1 = a;            //error,将int&绑定在const int上,限定符丢失
const int &b = a;       //a = 10,b = 10
int *c = (int *)&a;     //c = 0x0123FFDE8,a = 10,b = 10,*c = 10
*c++;                   //c = 0x0123FFDEC,a = 10,b = 10,*c = -858944430
*c--;                   //c = 0x0123FFDE8,a = 10,b = 10,*c = 10
(*c)++;                 //c = 0x0123FFDE8,a = 10,b = 11,*c = 11

     const int Max =10;

     int arr[Max] = {0};

在vs2017编译器里分别创建.c和.cpp文件并测试,在.c文件中,编译器报错,而.cpp中顺利运行。定义一个数组必须指定元素的个数,这也从侧面证实了c中,const修饰的Max仍然是变量,只不过是只读属性罢了;而在C++中拓展了const的含义。

2 节省空间,避免不必要的内存分配,提高效率

编译器通常不为普通 const 只读变量分配存储空间,而是将它们保存在符号表中,这使得它成为编译期间的一个值,没有了存储和读内存的操作,使得效率提高。

#define PI 3              //宏常量
const int p = 5;          //此时并未将p的值存放在内存中
...
int i = PI;               //此时为p分配内存,以后不再分配
int I = P;                //预编译期间进行宏替换,分配内存
int j = PI;               //没有分配内存
int J = p;                //再一次进行宏替换,分配内存

const 定义的只读变量从汇编角度看,只是给出了相对应的内存地址,而不是像#define 一样给出立即数,所以,const 定义的只读变量在程序运行过程中只有一份备份(因为它是全局的只读变量,存放在静态区), 而 #define 定义的宏常量在内存中有若干备份。# define 宏是在预编译阶段进行替换,而const 修饰的只读变量是在编译的时候确定其值。 #define没有类型检查,而const 修饰的具有特定类型。

3 修饰一般变量

一般变量是指简单类型的只读变量。这种只读变量在定义时,修饰符 const 可以用在类型说明符之前,也可以在之后。

例如:

int const i = 2;
const int i = 2;

4 修饰数组

定义一个只读数组可以如下:

int const arr[5] = {1,2,3,4,5};
const int arr[5] = {1,2,3,4,5};

5 修饰指针

const int *p;             //const修饰*p,p是指针p可变,p指向的对象*p不可变
int const *p;             //const修饰*p,p是指针p可变,p指向的对象*p不可变
int *const p;             //const修饰p,p是指针p不可变,p指向的对象*p可变
const int * const p;      //前一个const修饰*p,后一个const修饰p,指针p和p指向的对象都不可变

6 修饰函数的参数

const 修饰符可以修饰函数的参数,当不希望这个参数值在函数体内被意外改变时使用。

例如:

void fun(const int *p);

告诉编译器 *p在函数体内不能改变,从而防止了使用者的一些无意的修改。

7 修饰函数的返回值

const 可以修饰函数的返回值,返回值不可被修改。

例如:

const int fun(void);

在另一文件中引用const只读变量:

extern const int i;              //正确的声明
extern const int j = 10;         //错误,只读变量的值不能被改变

C++中const

  常变量:  const 类型说明符 变量名

  常引用:  const 类型说明符 &引用名

  常对象:  类名 const 对象名

  常成员函数:  类名::fun(形参) const

  常数组:  类型说明符 const 数组名[大小]    

  常指针:  const 类型说明符* 指针名 ,类型说明符* const 指针名

首先提示的是:在常变量(const 类型说明符 变量名)、常引用(const 类型说明符 &引用名)、常对象(类名 const 对象名)、 常数组(类型说明符 const 数组名[大小]), const” 与 “类型说明符”或“类名”(其实类名是一种自定义的类型说明符) 的位置可以互换。如:

     const int a=5; 与 int const a=5; 等同

     类名 const 对象名 与 const 类名 对象名 等同

1 常量

取代了C中的宏定义,声明时必须进行初始化(!c++类中则不然)。const限制了常量的使用方式,并没有描述常量应该如何分配。如果编译器知道了某const的所有使用,它甚至可以不为该const分配空间。最简单的常见情况就是常量的值在编译时已知,而且不需要分配存储。―《C++ Program Language》
    用const声明的变量虽然增加了分配空间,但是可以保证类型安全。
    C标准中,const定义的常量是全局的,C++中视声明位置而定。

2 指针和常量

使用指针时涉及到两个对象:该指针本身和被它所指的对象。将一个指针的声明用const“预先固定”将使那个对象而不是使这个指针成为常量。要将指针本身而不是被指对象声明为常量,必须使用声明运算符*const。
所以出现在 * 之前的const是作为基础类型的一部分:

char *const cp;        //到char的const指针
char const *pc1;       //到const char的指针
const char *pc2;       //到const char的指针(后两个声明是等同的)


    从右向左读的记忆方式:
cp is a const pointer to char.   故pc不能指向别的字符串,但可以修改其指向的字符串的内容
pc2 is a pointer to const char. 故*pc2的内容不可以改变,但pc2可以指向别的字符串

且注意:允许把非 const 对象的地址赋给指向 const 对象的指针,不允许把一个 const 对象的地址赋给一个普通的、非 const 对象的指针。

3 const修饰函数传入参数

   将函数传入参数声明为const,以指明使用这种参数仅仅是为了效率的原因,而不是想让调用函数能够修改对象的值。同理,将指针参数声明为const,函数将不修改由这个参数所指的对象。
    通常修饰指针参数和引用参数:

void Fun(const A *in);   //修饰指针型传入参数
void Fun(const A &in);   //修饰引用型传入参数

4 修饰函数返回值

可以阻止用户修改返回值。返回值也要相应的付给一个常量或常指针。

5 const修饰成员函数(c++特性)

const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数;
const对象的成员是不能修改的,而通过指针维护的对象确实可以修改的;
const成员函数不可以修改对象的数据,不管对象是否具有const性质。编译时以是否修改成员数据为依据进行检查。


说明

    常量函数是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.

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

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

C和C++中const的区别

1. C++中的const正常情况下是看成编译期的常量,编译器并不为const分配空间,只是在编译的时候将期值保存在符号表中,并在适当的时候折合在代码中.所以,以下代码:

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++)
    {
         printf("%d",array[i]);
    }
    return 0;
}


在.cpp可以通过编译,并且正常运行.但稍加修改后,放在.c中,便会出现错误:
错误消息:
c:\test1\te.c(8): error C2057: 应输入常数表达式
c:\test1\te.c(8): error C2466: 不能分配常数大小为 0 的数组
出现这种情况的原因是:在C中,const是一个不能被改变的普通变量,既然是变量,就要占用存储空间,所以编译器不知道编译时的值.而且,数组定义时的下标必须为常量.

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/ox0080/article/details/83715097
0条评论
添加一条新回复