const是常量的意思,它可以定义一个不可改变的量,主要用于以下几个地方:

1. 修饰变量,使之不可改变

举个例子:

const int var = 3;

此时var的值就不能改变了。也正是因为const的变量不能轻易修改存储的值,所以在声明的时候就要初始化,这样就是不行的:

const int var;

编译器就会报错。

2. 修饰指针

指针是特殊的变量,有时我们希望对它所指向的对象操作,而有时我们又希望对指针本身进行操作。同样,const应用于指针也有两个含义:一个是指向常量(指向的内容不可更改),一个是常量指针(指针的指向不可更改)。看下面这两个例子:

const int* p = &a;/* p为指向常量的指针,即p指向的对象是常量,不可以通过*p = 3 来修改a的值,但这时p = &b换个指向还是可以的 */

int* const p = &a; /* p为常量指针,即p的指向不可更改,不可以通过p = &b来修改p的指向,但这时*p = 3改变a的值还是可以的 */

const int* const p = &a; /* p为指向常量的常量指针,p的指向以及指向的对象都不可以更改,无论是*p = 3,还是p = &b都是错误的 */

还有一种形式是int const *p,这种形式是表示常量指针,还是指向常量的指针呢?Effective C++给出的建议是看“*”的位置,当const位于星号左侧时,const修饰的是值,即表示指向常量,而当const位于星号右侧时,const修饰的是指针,即表示常量指针。所以int const *p等价于const int *p,你想对了吗?(关键看const修饰的是*p还是p)。

const有时还会修饰函数的形参或者函数的返回值,都是属于1或2这两种情况。修饰函数形参的用法:

void fun(const char a)
{
a = ‘d’; // 错误,因为的值不可以改变
cout << a; // OK
}
 

还有一个地方要注意一下,若有:

void fun1(const char* a)
{
cout << a << endl;
}
 
void fun2(char *a)
{
cout << a << endl;
}

当实参为const时,比如const char* msg = “hello”,此时fun1(msg)是可以的,但fun2(msg)会报编译错,说是无法将const char*转成char*;而当实参为普通变量时,比如char* msg = “hello”,fun1(msg)和fun2(msg)都是OK的。这是为什么呢?因为当const的变量传递给非const的变量会不安全(非const的变量可以修改原来定义为常量的东西了!),所以C++限制了这种用法(需用强制类型转换来告诉编译器,编译器才会放行);而反过来,当非const的变量传递给const变量时,不存在安全问题,所以C++编译器总是会放行的。因此,如果在函数体内确实不改变形参a的值,那么采用带const的fun1的写法会更好,适用性更强。

3. 修饰迭代器

C++的STL中使用迭代器作为接口,它定义了普通的迭代器,如vector<T>::iterator,也定义了指向常量的迭代器,如vector<T>::const_iterator,初学者可能想当然地认为const vector<T>::iterator等价于vector<T>::const_iterator,其实不是这样的,const vector<T>::iterator表示这个迭代器的指向不可以更改,即表示的是常量迭代器。

4. 在类中修饰成员函数

const放在类中成员函数的后面,表示这个成为函数不会修改类的成员变量,比如:

class A
{
private:
int a;
double b;
public:
void fun1() const;
void fun2();
};

注意这里的fun1()函数后面有一个const,表示这个函数不会修改类的成员变量(在它的函数体里面出现任何改变a或b的值的操作,均不能通过编译);另一方面fun2()函数后面没有const,表示这个函数可能修改类的成员变量,注意这里用的词是“可能”,fun2()可以修改也可以不修改,但为了增强安全性,所以良好的编程风格一般会把不改动成员变量的成员函数修饰为const的。

有一点要切记:有无const是可以构成成员函数的重载的!

在本书中还提到了一个尖锐的问题,如果假定类是这样的:

class B
{
private:
int* p;
public:

};

我们看到,类的成员函数是指针,假定它在构造函数时会被初始化,而指向一段内存空间。那么如果不改变p本身(即指向不变),但是改变了p指向的内容(比如*p = 3),这样到底算不算对成员变量进行改动了呢?

读者可以在VS环境中写一下测试用例,可以发现VS编译器对这种情况是放行的,*p = 3完全可以通过,但是p = &b就不可以了。

虽然编译器是放过你了,但这也许并不是你的本意,本书中推荐的是“从逻辑上看”,就要交由写代码的你去好好思量一下。如果在某个函数里确实改动了p所指向的内容,那么最好就不要加上const;反过来,如果加上了const就不要改变成员变量,包括它所指向的值。

在const和非const成员函数中避免重复

我觉得这是一个非常重要的内容,有没有加const是构成函数重载的,但通常这种重载的相似度很高,就用书上的例子:

class TestBlock
{
private:
string text;
public:
...
const char& operator[](size_t position) const
{
return text[position];
}
 
char& operator[](size_t position)
{
return text[position];
}
};

可以看到两个重载函数里面的操作都是一样的,别因此认为可以用ctrl+c,ctrl+v而省事了,如果你要改动其中一个函数体里的内容,另一个就要同步更新,而万一你忘记了更新,后果是非常严重的!

一个好的方法来实现同步——在非const的函数中调用const函数!这样来修改:

char& operator[] (size_t position)
{
return const_cast<char&>(
static_cast<const TestBlock&>(*this)[postion]
);
}

说白了,就进行两次转换,一次是把非const的对象(就是自己(*this)转成const对象),但注意返回值要求是非const的,所以用const_cast再进行一次转换就OK了。关于C++转换可以参照本博客的

http://www.cnblogs.com/jerry19880126/archive/2012/08/14/2638192.html

读书笔记_Effective_C++_条款三:尽可能使用const的更多相关文章

  1. 读书笔记_Effective_C++_条款三十:了解inline的里里外外

    学过基本程序课的同学都知道,inline是内联的关键字,它可以建议编译器将函数的每一个调用都用函数本体替换.这是一种以空间换时间的做法.把每一次调用都用本体替换,无疑会使代码膨胀,但可以节省函数调用的 ...

  2. 读书笔记_Effective_C++_条款三十九:明智而审慎地使用private继承

    private继承的意义在于“be implemented in turns of”,这个与上一条款中说的复合模型的第二层含义是相同的,这也意味着通常我们可以在这两种设计方法之间转换,但书上还是更提倡 ...

  3. 读书笔记_Effective_C++_条款三十四:区分接口继承和实现继承

    这个条款书上内容说的篇幅比较多,但其实思想并不复杂.只要能理解三句话即可,第一句话是:纯虚函数只继承接口:第二句话是:虚函数既继承接口,也提供了一份默认实现:第三句话是:普通函数既继承接口,也强制继承 ...

  4. 读书笔记_Effective_C++_条款三十二:确定你的public继承继承塑模出is-a关系

    这一条款是说的是公有继承的逻辑,如果使用继承,而且继承是公有继承的话,一定要确保子类是一种父类(is-a关系).这种逻辑可能与生活中的常理不相符,比如企鹅是生蛋的,所有企鹅是鸟类的一种,直观来看,我们 ...

  5. 读书笔记_Effective_C++_条款三十七:绝不重新定义继承而来的缺省参数值

    先看下面的例子: enum MyColor { RED, GREEN, BLUE, }; class Shape { public: ; }; class Rectangle: public Shap ...

  6. 读书笔记_Effective_C++_条款三十六:绝不重新定义继承而来的non-virtual函数

    这个条款的内容很简单,见下面的示例: class BaseClass { public: void NonVirtualFunction() { cout << "BaseCla ...

  7. 读书笔记_Effective_C++_条款三十三:避免遮掩继承而来的名称

    名称的遮掩可以分成变量的遮掩与函数的遮掩两类,本质都是名字的查找方式导致的,当编译器要去查找一个名字时,它一旦找到一个相符的名字,就不会再往下去找了,因此遮掩本质上是优先查找哪个名字的问题. 而查找是 ...

  8. 读书笔记_Effective_C++_条款三十八:通过复合塑模出has-a或者is-implemented-in-terms-of

    如果说public是一种is-a的关系的话,那么复合就是has-a的关系.直观来说,复合就是在一个类中采用其他类的对象作为自身的成员变量,可以举个例子,像下面这样: class Person { pr ...

  9. 读书笔记_Effective_C++_条款三十五:考虑virtual函数以外的其他选择

    举书上的例子,考虑一个virtual函数的应用实例: class GameCharacter { private: int BaseHealth; public: virtual int GetHea ...

随机推荐

  1. Windows8下安装ubuntu

    这类文章堪称多如牛毛,也有很多种方法.此处记录的是我试验成功的一种,Windows 8 + ubuntu + easyBCD,简单粗暴,只记操作,不讲原理. 一.腾空间 在Windows下,首先要给u ...

  2. [No000015A]SQL语句分类

    1.Data Definition Language(DDL) 数据定义 CREATE - 在数据库中创建对象: ALTER - 修改数据库结构: DROP - 删除对象: RENAME - 重命名对 ...

  3. MySQL8.0安装连接Navicat的坑

    刚在官网装好MySQL8.0后,我的cmd识别不了启动数据库的指令 需要cd到MySQL的bin目录配置mysql mysqld --install mysqld --remove mysql -u ...

  4. 安装graphlab伤透了心,终于搞定了

    为了方便研究各种机器学习算法,我想用graphlab来辅助我对后续算法的研究.所以我的目标就是安装graphlab到我的windows笔记本中.而基于python的graphlab的安装最好是采用如下 ...

  5. java 数组(二)

    public class ArrayDemo{ public static void main(String[] args){ int[] arr = {1,5,3,8,2,9,17,13}; get ...

  6. django时间的时区问题(转)

    add by zhj: 使用django时,如果设置USE_TZ=True,那django在数据库中存储的是0时区的时间:如果USE_TZ=False,那存储的是本地时间 原文:https://www ...

  7. 第5章 实现windows程序的数据绑定

    1:连接式: command 对象负责对数据库的执行命令 DataReader 对象负责从数据源中读取数据 connection 对象负责链接数据库 断开式: 数据集的Dataset存放在独立的数据源 ...

  8. maven工程中防止mapper.xml文件被漏掉、未加载的方法

    maven工程中防止mapper.xml文件被漏掉.未加载的方法 就是在pom.xml文件中添加以下内容 <!-- 如果不添加此节点mybatis的mapper.xml文件都会被漏掉. --&g ...

  9. 31-ADC模拟/数字转换

    31-ADC模拟/数字转换 (1).ADC的IO分配

  10. java实现消息队列的两种方式

    https://blog.csdn.net/fenfenguai/article/details/79257928