您的位置:首页 > 编程语言 > C语言/C++

C++_Const的使用

2013-10-26 21:44 309 查看
const的作用:表示被修饰变量受到强制保护,可以预防意外的变动,能提高程序的健壮性。

const的用处:修饰函数的参数、返回值、函数的定义体,变量等,其中前面三个是其魅力所在。

根据函数的组成,可以把const的作用分成三部分:const修饰函数的参数,const修饰函数体,const修饰函数返回值。

一、用const 修饰函数的参数

什么时候使用const:

如果在函数体中只是对参数读取数据,而不对参数进行修改,则该参数要使用const修饰。

怎么使用const?

(1) 对于非内部数据类型的参数而言,传递参数常常使用引用传递+ const修饰

举例:

[cpp]
view plaincopyprint?

void Func(const A &a)  

void Func(const A &a)

原因:不加引用:传参时使用采用值传递而会产生A 类型的临时对象,而临时对象的构造、复制、析构过程都将消耗时间,效率比较底。而使用引用则就不会产生临时变量,而是原对象的一个别名,可直接使用。不加const:引用传递有可能改变参数a,这是我们不期望的。解决这个问题很容易,加const修饰即可。

(2) 对于内部数据类型的输入参数而言:不要将值传递的方式改为const 引用传递。

举例:

[cpp]
view plaincopyprint?

void Func(int x) 不应该改为 void Func(const int &x)  

void Func(int x) 不应该改为 void Func(const int &x)

原因:因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当,没有必要使用引用传递。否则既达不到提高效率的目的,又降低了函数的可理解性

二、用const 修饰函数的返回值

作用:使函数返回值不会被改变

什么时候返回值使用const:

1、如果函数返回值采用指针类型,则可以使用const,表示函数返回值(即指针)的内容不能被修改

2、如果函数返回值不是指针类型,则分情况讨论是否能使用const

(1)如果返回值是内部数据类型,则没必要使用const修饰

(2)如果返回值是非内部数据类型,而且使用值传递,则没必要使用const修饰

(3)如果返回值是非内部数据类型,而且使用引用传递,而且在以后的程序中值会发生改变,则不可以使用const修饰

(4)如果返回值是非内部数据类型,而且使用引用传递,而且在以后的程序中值不发生改变,则可以使用const修饰(用的很少)

具体说明:

1、如果函数返回值采用指针类型,则可以使用const,表示函数返回值(即指针)的内容不能被修改,并且该返回值只能被赋给加const修饰的同类型指针。

举例:

[cpp]
view plaincopyprint?

函数声明:const int* a()  
函数调用:const int *p = a();  
函数说明:我们可以把a()看作成一个变量,即指针指向内容不可变。  

函数声明:const int* a()
函数调用:const int *p = a();
函数说明:我们可以把a()看作成一个变量,即指针指向内容不可变。


2、如果函数返回值不是指针类型,则分情况讨论是否能使用const

(1) 如果返回值是内部数据类型,则没必要使用const修饰

(2) 如果返回值是非内部数据类型,而且使用值传递,则没必要使用const修饰

原因:函数返回值采用值传递方式,由于函数会把返回值复制到外部临时的存储单元中,这时const修饰的是临时变量,是不会得到改变,没有任何价值

举例:

[cpp]
view plaincopyprint?

不要把函数int GetInt() 写成const int GetInt()  
不要把函数A GetA()     写成const A GetA(),A为用户自定义类型  

不要把函数int GetInt() 写成const int GetInt()
不要把函数A GetA()     写成const A GetA(),A为用户自定义类型


(3) 如果返回值是非内部数据类型,而且使用引用传递,而且在以后的程序中值会发生改变,则不可以使用const修饰
(4) 如果返回值是非内部数据类型,而且使用引用传递,而且在以后的程序中值不发生改变,则可以使用const修饰(用的很少)

举例:

[cpp]
view plaincopyprint?

const A& aa = fuc(a); //之后aa的只就不能改变了,而且aa也不能调用非const函数。
  
//aa.m_x = 10;//错误   
//aa.show();//错误,show为非const函数。  

const A& aa = fuc(a); //之后aa的只就不能改变了,而且aa也不能调用非const函数。
//aa.m_x = 10;//错误
//aa.show();//错误,show为非const函数。

说明:

(1)一旦把返回值定为const变量,而且还使用const变量接收,那么此时变量完全被束缚住手脚了,各种操作无能啊。

(2)函数返回值采用引用传递的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达

举例:

[cpp]
view plaincopyprint?

class A  
{  
    A & operate = (const A &other); // 赋值函数
  
};  
A a, b, c; // a, b, c 为A 的对象
  
⋯  
a = b = c; // 正常的链式赋值  

class A
{
A & operate = (const A &other); // 赋值函数
};
A a, b, c; // a, b, c 为A 的对象
⋯
a = b = c; // 正常的链式赋值


关于用const 修饰函数的返回值时的几个问题:

举例说明

[cpp]
view plaincopyprint?

#include <iostream>   
using namespace std;  
class A  
{  
public:  
    A(int x)  
    {  
        m_x = x;  
        m_pY = NULL;  
    }  
  
    A(const A& other)  
    {  
        cout<<"A(const A& other)"<<endl;  
        m_x = other.m_x;  
        m_pY = NULL;  
    }  
    void show()  
    {  
        cout<<"m_x = "<<m_x<<endl;  
    }  
  
    const int& GetX()  
    {  
        return m_x;  
    }  
  
    const int* GetY()  
    {  
        return m_pY;  
    }  
  
public:  
    int m_x;  
    int* m_pY;  
};  
  
const A& func(A& a)  
{  
    return a;  
}  
  
int main()  
{  
    A a(1);  
    a.show();  
  
    //返回值是一个整形的引用   
    int x = a.GetX(); //正确
  
    cout<<x<<endl;   
    x = 10;  
    cout<<x<<endl;  
  
    //返回值是一个整形的引用   
    //int* pY = a.GetY(); //为什么是错误的
  
  
    //返回值是一个类的引用   
    A aa = func(a); //正确   
    aa.m_x = 10;   //正确
  
    aa.show();       
  
    //int& xx = a.GetX(); //错误   
    //A& aa = fuc(a);//错误
  
      
    //函数返回值使用const时,函数接受值的正确用法
  
    const int& xx = a.GetX(); //正确
  
    const A& aaa = func(a); //正确
  
    const int* pYY = a.GetY();//正确
  
  
    system("pause");  
    return 1;  
}  

#include <iostream>
using namespace std;
class A
{
public:
A(int x)
{
m_x = x;
m_pY = NULL;
}

A(const A& other)
{
cout<<"A(const A& other)"<<endl;
m_x = other.m_x;
m_pY = NULL;
}
void show()
{
cout<<"m_x = "<<m_x<<endl;
}

const int& GetX()
{
return m_x;
}

const int* GetY()
{
return m_pY;
}

public:
int m_x;
int* m_pY;
};

const A& func(A& a)
{
return a;
}

int main()
{
A a(1);
a.show();

//返回值是一个整形的引用
int x = a.GetX(); //正确
cout<<x<<endl;
x = 10;
cout<<x<<endl;

//返回值是一个整形的引用
//int* pY = a.GetY(); //为什么是错误的

//返回值是一个类的引用
A aa = func(a); //正确
aa.m_x = 10;   //正确
aa.show();

//int& xx = a.GetX(); //错误
//A& aa = fuc(a);//错误

//函数返回值使用const时,函数接受值的正确用法
const int& xx = a.GetX(); //正确
const A& aaa = func(a); //正确
const int* pYY = a.GetY();//正确

system("pause");
return 1;
}


问题(1)为什么返回值是const类型,但是(1)和(2)接受值不是const,结果仍然对,(3)却错了

[cpp]
view plaincopyprint?

(1) A aa = func(a); //正确
  
(2) int x = a.GetX(); //正确 
  
(3) int* pY = a.GetY(); //错误的  

(1) A aa = func(a); //正确
(2) int x = a.GetX(); //正确
(3) int* pY = a.GetY(); //错误的

说明:

为什么返回值是const类型,但是接受值可以不是const类型?

虽然函数func和GetX都返回const引用,但是接受值使用的非引用变量,编译器为aa和x会申请新的空间,之后系统直接拿返回值的值初始化aa和x了。由于aa与x和函数返回值的空间不同,对改变aa和x的值不会影响const修饰的那个变量,即对函数返回值的空间的值没有威胁,所以是正确的。

为什么(3)是错误的?

虽然系统也为pY申请了空间,但是由于pY是指针变量,它指向的地址和函数返回值指向的内容是一样的,因此有可能改变函数返回值指向的内容,而由于const的存在,编译器是不允许这个操作发生的,因此(3)是错误的。

说明:

[cpp]
view plaincopyprint?

A aa = func(a); //正确
  
int x = a.GetX(); //正确   

A aa = func(a); //正确
int x = a.GetX(); //正确

上述写法是不能反应函数返回值使用const的作用的,它只是借用函数返回值的值而已。

[cpp]
view plaincopyprint?

const int& xx = a.GetX(); //正确写法
  
const A& aaa = func(a); //正确写法
  
const int* pYY = a.GetY();//正确写法  

const int& xx = a.GetX(); //正确写法
const A& aaa = func(a); //正确写法
const int* pYY = a.GetY();//正确写法

上述写法才是正确写法,即函数返回值使用const时,函数接受值的正确用法。

即我们使用const修饰函数返回值时,应该主动定义一个const对象引用或者变量引用来接收函数返回值,这样使用const修饰函数返回值才有意义。

其他:

const对象只能调用const函数

[cpp]
view plaincopyprint?

#include <iostream>   
using namespace std;  
class MyClass  
{  
public:  
    int m_nTemp;  
    void fun1() const  
    {  
    }  
    void fun2()  
    {  
    }  
    const MyClass& fun6(MyClass& aa)  
    {  
        return aa;  
    }  
};  
  
void main()  
{  
    MyClass aa;  
    const MyClass bb=aa.fun6(aa);//能使用返回const对象的函数为const对象赋值
  
    bb.fun1();  
//  bb.fun2(); //报错,因为bb为const对象,只能调用const函数
  
//  bb.m_nTemp=22; //报错
  
    system("pause");  
}  

#include <iostream>
using namespace std;
class MyClass
{
public:
int m_nTemp;
void fun1() const
{
}
void fun2()
{
}
const MyClass& fun6(MyClass& aa)
{
return aa;
}
};

void main()
{
MyClass aa;
const MyClass bb=aa.fun6(aa);//能使用返回const对象的函数为const对象赋值
bb.fun1();
//	bb.fun2(); //报错,因为bb为const对象,只能调用const函数
//	bb.m_nTemp=22; //报错
system("pause");
}

 三、const 成员函数

作用:在函数体中不修改数据成员的值

什么时候把函数设置为const成员函数:任何不会修改数据成员的函数都应该声明为const 类型。

说明:

(1) 如果在编写const 成员函数时,不慎修改了数据成员

(2) 调用了其它非const 成员函数,编译器将指出错误,这无疑会提高程序的健壮性。

程序:

[cpp]
view plaincopyprint?

class Stack  
{  
public:  
    void Push(int elem);  
    int Pop(void);  
    int GetCount(void) const; // const 成员函数
  
private:  
    int m_num;  
    int m_data[100];  
};  
int Stack::GetCount(void) const  
{  
    ++ m_num; // 编译错误,企图修改数据成员m_num
  
    Pop(); // 编译错误,企图调用非const 函数   
    return m_num;  
}  

class Stack
{
public:
void Push(int elem);
int Pop(void);
int GetCount(void) const; // const 成员函数
private:
int m_num;
int m_data[100];
};
int Stack::GetCount(void) const
{
++ m_num; // 编译错误,企图修改数据成员m_num
Pop(); // 编译错误,企图调用非const 函数
return m_num;
}

说明:

1、#define的优点和缺点

预处理语句#define的优点:

1)见名知意且方便程序的修改

2)提高程序的运行效率

具体来说:

1)使用一个有意义的名字代替数字,可以避免避免意义模糊的数字出现,而且数字改变时只需要改变变量的值即可。

2)define符号的替换与编译器无关。程序把符号换成数字是在预处理阶段完成,程序不必把符号放入符号表中,这样就没有了存储与读内存的操作,使得它的效率也很高。

3)当使用带参数的宏定义完成函数调用的功能时,并没有进行函数调用,而减少系统开销,提高运行效率。

预处理语句#define的缺点:

1)缺乏类型的检测机制 

2)存在边际效应

具体来说:
1)预处理语句仅仅只是简单值替代,缺乏类型的检测机制,从而可能成为引发一系列错误的隐患。 

2)预处理语句在字符替换可能会产生意料不到的错误(边际效应)

注意:在C++中,使用 const 和 inline 可以替代define的作用。

2、const的优点

const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。

const 的优点:



1)const定义常量,具有不可变性。

2)见名知意且方便程序的修改

3)提高程序的运行效率。

4)存在类型检测

具体来说:
1)const 修饰的常量值,具有不可变性,这是它能取代预定义语句的基础。

2)使用一个有意义的名字代替数字,可以避免避免意义模糊的数字出现,而且数字改变时只需要改变变量的值即可。

3)const常量的替换与编译器有关。C++的编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高,同时,这也是它取代预定义语句的重要基础。

4)const定义也像一个普通的变量定义一样,它会由编译器对它进行类型的检测,消除了预定义语句的隐患。

inline的优点:

inline 推出的目的是为了取代C中表达式形式的宏定义,它消除了它的缺点,同时又很好地继承了它的优点。

inline的优点:

1)提高程序的运行效率

2)[u]存在安全检查[/u]

3)具有与类的成员函数的相同的性质

举例来说:

1) inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。

2) 类的内联函数是一个真正的函数,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。

3) inline 可以作为某个类的成员函数,当然就可以在其中使用所在类的保护成员及私有成员。


2、指针和const连用:

作用:const修饰的是它右边的内容

1)

[cpp]
view plaincopyprint?

char* const pContent;  
表示:指针本身内容是常量,不可变  
说明:const修饰pContent,表示变量的内容不变,即指针指向不变  

char* const pContent;
表示:指针本身内容是常量,不可变
说明:const修饰pContent,表示变量的内容不变,即指针指向不变


2)

[cpp]
view plaincopyprint?

const char* pContent;   
表示:指针所指向的内容是常量不可变  
说明:const修饰char*,表示指针指向的内容不变,即指针指向内容不变  

const char* pContent;
表示:指针所指向的内容是常量不可变
说明:const修饰char*,表示指针指向的内容不变,即指针指向内容不变

3)

[cpp]
view plaincopyprint?

const char* const pContent;   
表示:两者都不可变  
说明:pContent指向不变,指向的内容也不变  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: