在很久很久以前,C++的语法就规定了预编译指令#define
,用于定义一个宏,并在编译之前,将对应的宏进行文本替换。所以,当我们想定义一个通用的常量,我们可以用宏定义直接进行文本替换。
这样通篇的变换显然很省力呀——就换个文本而已嘛!
但是,这样的力可不兴省呀~
#undifine
),易污染,破坏封装性void f(){
#define N 123// 宏定义藏在未被调用的函数里
}
int main()
{
cout << N << endl;//输出123,说明泄露了
return 0;
}
题外话:宏的另一个缺点,但与const无关
宏定义是在编译之前(也就是预编译阶段)进行字符替换,无法进行类型检查。如果文本替换导致编译出错,编译器才会报错或警告。如果编译没错,但是逻辑错了,因为宏没展开,看起来不直观,debug就会很痛苦,或者程序员们压根就没发现bug。
举个经典八股文例子,#define calculate(x,y) x * y,calculate(1+2, 3+5)。乍一看运算结果应该是3 * 8=24,但正确的结果是1+2*3+5=1+6+5=12。
原因二:宏的每次赋值都会开辟一个新的内存区,但const不会。
宏#define N 123
的实现原理,仅仅是在预编译时期展开,做文本替换,那么int n = n + N;
和int n = n + 123;
基本没区别。但是每个123的出现,背后都是一次内存分配 + 拷贝——即使它们的值是一样的!
但是,对于const N = 123;
,编译器会直接读取常量区的123,这个过程仅涉及拷贝,不涉及内存分配,所以内存开销更小了。
高能预警,下面我要开始吟唱了!!!
使用const常量可以让编码更加规范,防止不必要的失误,例如:不小心改值、不小心把值赋给了原本规划为常量的数。这种情景下的const常量一般具有全局性。
按照我的个人喜好,我喜欢在局部函数里、获取函数返回值的时候,如果接下来我不会修改这个返回值,那么我会把这个返回值定义为const。其实,在这样”局部“的环境里,const加不加都没关系。但如果加了,别人读我代码的时候会更一目了然一点。
局部const命名用小驼峰,全局用全大写(跟随宏的命名风格)。
const int MAX_NUM = 10;
void f(){
const float pi = 3.14;
}
const的值存储在常量区。
对比之下,宏只在预编译阶段做文本替换,编译时,其具体的值会作为临时量被开辟在栈区。
这个知识点在编码中,就是,当你的程序需要多次赋值时,用const比直接写值更高效。
常量指针。理解为“常量的指针”即可。这个指针指向的是常量,常量不可改,但指针可改。
常量↓ 指针↓
const int* p = &N_CONST;
指针常量。理解为“这个指针是个常量”;或者只记上面那个,用排除法。指针不可改,但是指针指向的值可改。
指针↓ 常量↓
int* const p = &n;
常量的指针常量,debuff拉满
const int* const p = &N_CONST;
【本质】首先要明确的一点是,能被const的一定是类的成员函数。函数本身没有const属性。const 修饰函数, 本质上是修饰了 this 指针。
在Python中,类的成员函数的第一个参数是self,也就是this指针:
class A:
def f(self, …):
pass
相对应地,其实,c++的成员函数的第一个参数也是this指针,只是它藏起来了,编译器帮你自动传入了,所以我们不用显示定义也不用显示地传参。
【意义】在面向对象的编程中,确保成员变量不被污染是一件很重要的事。那么,对于不需要或者不能修改成员变量的函数,我们最好用const给它标记、限制一下,以防在后续维护时,发生无意的、错误的修改。
例如:
class A
{
public:
int Get() const { return mValue; }
int Calculate(int Other) const {
int Ret = mValue + Other;
return Ret;
}
private:
int mValue;
};
另外,把成员函数修饰为const,方便当对象为const的时候依然能够调用。
对于我们程序员,使用const函数参数可以避免意外地修改指针或地址所指向的值,让代码更规范。
这样的编程习惯也常见于运算符重载:
bool operator < (const A& right) const{
return data < right.data;
}
可能有萌新会问,如果不想改值,那为什么不直接用类型传递,而是用指针或地址呢?
因为,对于较为复杂的类型,例如自定义的类、大容器,直接传值会带来不必要的实参拷贝开销,降低性能,而直接传地址能降低开销。
对于编译器,由程序员直接定义的量(一般类型/对象)都是const量
void f(const int& a){
cout << "是const" << endl;
}
void f(int& c){
cout << "不是const"<< endl;
}
int main()
{
f(123);//是const
return 0;
}
提问:如果把第一个函数注释掉,程序能运行吗?
答案是不能。
对于编译器,由程序员创建的对象指针是非const指针,但也可以降级为const指针
void f2(const A* a){
cout << "是const指针" << endl;
}
void f2(A* a){
cout << "不是const指针" << endl;
}
void f2(const A& a){
cout << "是const对象" << endl;
}
void f2(A& a){
cout << "不是const对象" << endl;
}
int main()
{
f2(new A());//不是const指针
//如果把第2个函数去掉,则输出“是const指针”
}
常见地,我们会遇到这样的需求: std::string name = item.getName();
使用非const返回值的函数,上述赋值的过程涉及两次拷贝:①item成员变量拷贝到返回值;②返回值拷贝到临时变量name。
对于int、float等字节数较少的类型,这样的拷贝是无所谓的。但对于string、类对象等等大值,这样的拷贝是开销较大的。
更好的做法是返回引用或者指针,省去第①次拷贝。
但是引用或者指针作为返回值,是作为左值,是能够被修改的。危!
int& GetAge();// 返回左值,能被修改
const int& GetAgeConst();//返回右值,禁止修改
因此,为了保护指针或引用的内容不被修改,返回值应该设为const。
const std::string& getName() {return mName;}
如果我const的返回值 不是引用 也不是指针呢?像这样:
const std::string getName() {return mName;}
// 答:一点用都没有!
const对象只能调用const成员函数
问:const对象可以调用static函数吗?
答:伪命题!调用static函数不需要对象。
修改的方法是把变量设为mutable
,这是C++11的新特性之一。mutable只能用于类变量,不能与const同时使用。
那么,问题来了,为什么设了const还想着修改呀?!是不是架构没做好!不是,有时候不是:
我们不能在lambda函数之内修改值传入的参数。
int a = 5;
auto f = [a]() { a = 10;};
// 报错:表达式必须是可修改的左值
但是我们可以调用值传入参数的非const函数。
class A
{
public:
void f() { cout << "A::f()" << endl; };
};
A a;
auto f = [a]() mutable {
a.f();
};
如果想修改值传入参数,需要将lambda函数声明为mutable。
注意,lambda函数体内修改值传入参数永远不会影响值本身(这和一般函数是一样的)。除非把参数改为引用传入。
int a = 5;
auto f = [a]() mutable {
a = 10;
cout << "inner a=" << a << endl; // 10
};
f();
cout << "outer a=" << a << endl; // 5
可以,用const_cast
把const对象转为非const对象,就可以调用它的非const函数了。
我曾因为受到Qt的制约,做过这种事。
// 函数参数是const对象
void MyDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
// 但我实在是需要调用它的非const函数,也可能是我设计不当
QAbstractItemModel* model = const_cast(index.model());
connect(lockBtn, &QToolButton::clicked, [=] {
model->setData(index, true, ...);
});
}
我觉得const的原则是保证值不被意外修改。mutable、const_cast的存在都是为了弥补,补救因外部导致的制约。修改const的行为,可以做,但最好少点做。多次修改const的行为,如果不是外界导致,可能就是设计上存在问题,是不可取的。