前面我们讲了指针和引用的基本用法(指针用法)(引用用法) , 现在我们来看比较复杂的类型 -- 指针引用。(不存在引用指针)
这是复合类型 , 指针引用 , 顾名思义 , 指针引用就是对指针的引用。
int i = 0 , *pi = &i;
int *&rpi = pi; //这里的指针引用应该这样阅读 (int *(&rpi)) , 即是rpi是引用 , 是对整形指针的引用
C++关键字中的const指的是英文中的constant , 即常量的意思 (下面的常量指const修饰的对象), 所以const 是用来修饰常量的关键字 , 下面来看看const是怎么作用于内置变量的:
const int ci = 0; //const放在类名前面 , 用0初始化ci
const int ci2 = ci; //用ci来初始化ci2
//ci++; 错误 , 常量不能改变
//const int ci3; 错误 , 常量必须初始化
常量除了不能改变自身和必须初始化外 , 跟一般变量一样 , 另外 , 常量只在文本内有效。
对于类的常量对象 , 与基本类型的常量相似 , 不能改变自身且需要初始化(有默认构造函数就可以执行默认初始化) , const对象不能改变成员变量(对象) , 同时不能调用非const成员函数 , 否则编译器会报错。
const string cs = "Hello";
size_t size = cs.size(); //正确 , string::size() 不会改变内置成员 , 所以被声明为const
//该函数声明为 size_type size() const; 起作用的是后面的const
cout << cs[0] << endl; //正确
//调用的是该函数的const版
//s[0] = 'h'; 错误 , 用一个const对象调用一个非const函数
const与引用组合 , 会变成一种很特别的引用 --- 常量引用 。 我们可以把非常量对象赋给常量引用 , 这样的话就不能通过引用来修改对象的值 , 实际上这只是引用一厢情愿的认为它代表的对象是常量 , 但我们依然可以通过直接修改对象来达到修改的目的。但反过来 , 不能用一个非常量引用来引用常量 , 因为这样就代表可以用引用来改变常量 , 编译器是不允许这样做的。
int i = 0;
const int & ri = i;
//ri = 5; //错误 , ri是常量
i = 5; //正确 , 这样i就变成5了 , 当然ri也是5
const int j = 5;
//int & rj = j; 错误 , 不能用非常量引用表示一个常量对象
对于常量引用 , 还有一个非常令人难以理解的特征 , 就是可以把右值赋给常量引用 :
const int &r = 10;
const int &r1 = r * 5;
const int &r2 = right_int(); //right_int()返回一个局部int
double d = 10.2;
const int &r3 = d; //r3的值为10
上述代码全部都是正确并都能通过编译 , 可能有什么人会有疑问 , 为什么能够将没有对象的右值或者临时对象赋给常量引用 。C++允许为一个常量引用赋予一个右值 , 所以编译器会在内存中另外分配一块临时区域来生成对象 , 再把常量引用绑定到这个对象身上 , 而右值就是用来初始化这个临时对象的。
根据上述的特征 , 我们可以这样定义函数:
void print(const int & i){
cout << i << endl;
}
print(5); //合法
int i = 0;
print(i) ; //合法 , 把一个非常量赋给常量引用
print(i + 9); //合法
接下来我们来看指针与const的结合 , 这里的const 有两种 , 一种是加在类名前面 , 如 const Typename *pt , 这是指指向常量的指针(不一定是常量 , 也是指针一厢情愿这样认为) , 这种指针不能改变指向的对象的内容 ,但可以改变指向的对象 。还有一种是加在类名后面 , 如 Typename* const pt , 这是指常量指针 , 可以改变只想对象的内容 , 但不能改变指向的对象 。
int i = 0 , j = 0;
const int * pi = &i; //指向常量的指针(在指针层面是常量 , 实际不一定)
int * const pj = &j; //指针本身是常量
//(*pi)++; 在指针层面指向的是常量 , 不能改变其值
pi = &j; //可以改变指向对象
(*pj)++; //可以改变指向的对象的内容
//pj = &i; 不能改变指向对象
对于一个常量 , 可以用一个指向常量的指针来指向它 , 却不能用常量指针来指向它 , 因为指向常量的指针不能对该常量进行改动 , 这符合了常量的规则 , 而常量指针是可以改变指向对象的 , 这就违背了常量的规则。
const int i = 1;
const int * pi1 = &i; //合法 , 不能改变i的值
//int * const pi2 = &i; 错误 , pi2理论上有能力改变i的值
const int * const pi3 = &i; //合法 , 既不改变对象也不改变指向
最后讲一下const返回值的函数 , 对于一个有返回值的函数而言 , 如果返回值是右值(即返回类型名无后缀 , 没有&运算符修饰) , 则我们无法将这个函数放在赋值表达式的左边 , 但如果返回值是一个引用 , 因为它是个左值 , 所以可以直接在赋值表达式左边使用函数 , 如果我们不想函数返回值被修改 , 则需要在类名前加上const修饰:
int f1(int &i){ return i;}
int& f2(int &i){ return i;}
const int& f3(int &i) { return i;}
//示例代码:
int i = 5;
//f1(i) = 10; 错误 ,返回值是右值 , 不能放在赋值语句左边
f2(i) = 10; //正确 , 把i的值改为10
//f(3) = 10; 错误 , 返回值是一个常量引用 , 不能修改它的值
总而言之 , 常量的意思就是不希望你修改它 , 但要记住一点 , 常量不全是在编译阶段就已经确定下来的值 , 很多事要到运行阶段才能获得的。而且 , 在我们说常量的时候 , 有可能指字面值常量 , 有可能指const修饰的常量 , 还有可能指常量表达式 。