正所谓酒足思淫欲,当衣食无忧的时候自然会产生很多的歪想法,就像当官一样,权力越大,越容易腐败。
《Effective C++》中第三条:尽量使用const。这就说明使用const是有很大的好处的,const就是把权力尽可能控制一下,这样就会减免很多出bug的机会。因为一个项目不可能只有一个人开发,即使是一个人开发,当涉及到文件多的时候,就会容易出现差错,如果在另外一个文件中修改了一个本不应该修改的变量,就会出现很大的错误。
const就是将修饰的部分确定为常,所谓常就是不让修改,比如常量,常对象,常指针等等。当我们试图修改这些常量的时候,编译器就会报错,就会阻止错误的发生。下面来看看有哪些const用法。
一、常量
声明常量的形式:const 数据类型 数据名;
常量就是不允许修改其值,定义的时候必须赋初始值,不允许再次赋值,这个很好理解。那一个类中也可能会有常数据成员,而我们在类体中往往声明为const,初始化则是在构造函数中,这个时候构造函数的写法就有规定了,看下面的程序:
class Test { public: Test(int x, float y):a(x), b(y){} // 如果是Test(int x, int y){a = x; b = y;},编译就会出错 private: const int a; // 声明常数据成员 float b; } int main() { const int t = 10; // 定义常变量(既然是常的怎么又是变量呢哈哈) t = 20; // 错误,常量不能再次赋 Test test(1, 1.1); }
看着这个构造函数有点别扭,这才是初始化,而后面那种错误的写法就是我们经常写的构造函数,这个只是赋值,但是我们的常数据成员是不能给赋值操作的。所以只能写成第一种写法,而且强烈推荐将构造函数写成这种形式。在main函数里面定义了一个常变量t,定义的时候必须赋初始值,以后都不能再对t进行赋值操作了,否则编译会出错。这里还有个声明和定义的区别,这也是一个比较难理解的概念,类体里面是声明了一个常数据成员,而main函数里则是定义了一个常变量,查查资料好好体会一下。
二、常对象
声明常对象的两种形式:const 类名 对象名(参数列表);
类名 const 对象名(参数列表);
什么是常对象呢,就是对象不允许改变,也就是说,不能修改常对象的任何成员变量,当一个对象定义为常对象的时候,任何修改其成员变量的操作都会报编译错误的。看看下面这个程序:
#include<iostream> using namespace std; class Test{ public: Test(int x, float y):a(x), b(y){} // 推荐这种写法 void print() { x = 10; // 对x进行赋值 cout << "x:" << x << ", y:" << y << endl; } private: int a; float b; } int main() { Test test1(1, 1.1); test1.print(); const Test test2(2, 2.2); test2.print(); // 编译出错,因为print()方法中有修改test2的成员变量 }
有人说,我自己写的程序干嘛没事在print()函数里面写个修改x值的代码啊,吃饱了撑着。说的对,正常情况下不会这样,但这也只是你一厢情愿罢了,你的程序就允许这样写,而且这样写这一行是完全没问题的,这就为出错埋下了伏笔。但有时候确实需要这样写,比如我就要每次输出x都为10,这就要看自己程序的必要性了。所以我们将这一行删掉,不改变常对象的成员变量就OK了,所以我们定义对象的时候,如果不需要修改它的成员变量的时候就定义为常对象。这个问题是解决了,但是我们改了再编译一次,还是会报错,那就是常对象只能调用常成员函数。
三、常成员函数
声明常成员函数的形式:返回值类型 成员函数名(形参列表) const;
上面提到的,当test2调用print()函数的时候就会报错,这是因为test2是常对象,而print()函数是非常成员函数,所以不能调用。为啥嘞,定义成常对象,当然不想对象的成员变量被修改,非常成员函数是允许修改该类的成员变量的,所以二者冲突了。看下面这个程序:
#include<iostream> using namespace std; class Test { public: Test(int x, float y):a(x), b(y){} void print() const { x = 10; // 编译出错 cout << "x:" << x << ", y:" << y << endl; } void setA(int x) { a = x; } private: int a; float b; } int main() { const Test test1(1, 1.1); test1.print(); test1.setA(10); // 编译错误,常对象不能调用非常成员函数 Test test2(2, 2.2); test2.print(); // 非常对象可以调用常成员函数 test2.setA(10); }
上面程序中,x=10这行编译就会出错,因为函数为const型,所以是不允许修改类的成员变量的,所以当我们的函数没有涉及到成员变量的赋值操作,将其声明为const型是不是更安全呢。test2是非常对象,即可以调用非常成员函数,也可以调用常成员函数,这个很好理解的。
还有指向对象的常指针,指向常对象的指针和常形参,后面再分析。