C++ Const关键字的使用介绍。
目录(?)[-]
- 一用const 修饰函数的参数
- 二用const 修饰函数的返回值
- 关于用const 修饰函数的返回值时的几个问题
- 三const 成员函数
- define的优点和缺点
- const的优点
- inline的优点
- 指针和const连用
const的作用:表示被修饰变量受到强制保护,可以预防意外的变动,能提高程序的健壮性。
const的用处:修饰函数的参数、返回值、函数的定义体,变量等,其中前面三个是其魅力所在。
根据函数的组成,可以把const的作用分成三部分:const修饰函数的参数,const修饰函数体,const修饰函数返回值。
一、用const 修饰函数的参数
什么时候使用const:
如果在函数体中只是对参数读取数据,而不对参数进行修改,则该参数要使用const修饰。
怎么使用const?
(1) 对于非内部数据类型的参数而言,传递参数常常使用引用传递+ const修饰
举例:
- void Func(const A &a)
原因:不加引用:传参时使用采用值传递而会产生A 类型的临时对象,而临时对象的构造、复制、析构过程都将消耗时间,效率比较底。而使用引用则就不会产生临时变量,而是原对象的一个别名,可直接使用。不加const:引用传递有可能改变参数a,这是我们不期望的。解决这个问题很容易,加const修饰即可。
(2) 对于内部数据类型的输入参数而言:不要将值传递的方式改为const 引用传递。
举例:
- 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修饰的同类型指针。
举例:
- 函数声明:const int* a()
- 函数调用:const int *p = a();
- 函数说明:我们可以把a()看作成一个变量,即指针指向内容不可变。
2、如果函数返回值不是指针类型,则分情况讨论是否能使用const
(1) 如果返回值是内部数据类型,则没必要使用const修饰
(2) 如果返回值是非内部数据类型,而且使用值传递,则没必要使用const修饰
原因:函数返回值采用值传递方式,由于函数会把返回值复制到外部临时的存储单元中,这时const修饰的是临时变量,是不会得到改变,没有任何价值
举例:
- 不要把函数int GetInt() 写成const int GetInt()
- 不要把函数A GetA() 写成const A GetA(),A为用户自定义类型
(3) 如果返回值是非内部数据类型,而且使用引用传递,而且在以后的程序中值会发生改变,则不可以使用const修饰
(4) 如果返回值是非内部数据类型,而且使用引用传递,而且在以后的程序中值不发生改变,则可以使用const修饰(用的很少)
举例:
- const A& aa = fuc(a); //之后aa的只就不能改变了,而且aa也不能调用非const函数。
- //aa.m_x = 10;//错误
- //aa.show();//错误,show为非const函数。
说明:
(1)一旦把返回值定为const变量,而且还使用const变量接收,那么此时变量完全被束缚住手脚了,各种操作无能啊。
(2)函数返回值采用引用传递的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达
举例:
- class A
- {
- A & operate = (const A &other); // 赋值函数
- };
- A a, b, c; // a, b, c 为A 的对象
- ⋯
- a = b = c; // 正常的链式赋值
关于用const 修饰函数的返回值时的几个问题:
举例说明
- #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)却错了
- (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)是错误的。
说明:
- A aa = func(a); //正确
- int x = a.GetX(); //正确
上述写法是不能反应函数返回值使用const的作用的,它只是借用函数返回值的值而已。
- const int& xx = a.GetX(); //正确写法
- const A& aaa = func(a); //正确写法
- const int* pYY = a.GetY();//正确写法
上述写法才是正确写法,即函数返回值使用const时,函数接受值的正确用法。
即我们使用const修饰函数返回值时,应该主动定义一个const对象引用或者变量引用来接收函数返回值,这样使用const修饰函数返回值才有意义。
其他:
const对象只能调用const函数
- #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 成员函数,编译器将指出错误,这无疑会提高程序的健壮性。
程序:
- 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)存在安全检查
3)具有与类的成员函数的相同的性质
举例来说:
1) inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。
2) 类的内联函数是一个真正的函数,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
3) inline 可以作为某个类的成员函数,当然就可以在其中使用所在类的保护成员及私有成员。
2、指针和const连用:
作用:const修饰的是它右边的内容
1)
- char* const pContent;
- 表示:指针本身内容是常量,不可变
- 说明:const修饰pContent,表示变量的内容不变,即指针指向不变
2)
- const char* pContent;
- 表示:指针所指向的内容是常量不可变
- 说明:const修饰char*,表示指针指向的内容不变,即指针指向内容不变
3)
- const char* const pContent;
- 表示:两者都不可变
- 说明:pContent指向不变,指向的内容也不变