左值引用,const限定符与类型

引用

这里的引用指左值引用而不是C++11中的右值引用

引用为对象起了一个别名。注意:引用并不是对象,它只是为一个已经存在的对象所起的另外一个名字。

因为引用不是一个对象,所以不能定义引用的引用!

为引用赋值,实际上是把值赋给了引用绑定的对象,获取引用的值,其实也是获取了引用绑定对象的值。

引用的类型必须和要绑定的对象严格匹配(比如int类型的引用不能绑定在double上),但是有2个例外。而且引用只能绑定在对象上而不能与字面值或者某个表达式的计算结果绑定在一起。

在定义引用时,程序把引用跟它的初始值绑定在一起(而不是拷贝),无法令引用绑定到另一个对象,所以引用定义时必须被初始化。

const

const对象一旦创造就不能再更改,所以const对象必须被初始化;
默认情况下const对象被设定为仅在文件内有效,当多个文件内出现同名的const变量时相当于在不同文件中分别定义了独立变量;如果要在多个文件中使用同一个const变量,那么它的声明与定义都应该加上extern关键字(存疑);

const的引用

可以把引用绑定到const对象上,就像绑定到其他对象上一样,称之为对常量的引用。对常量的引用并不能修改它绑定的对象,同时,非常量引用也不能绑定到常量对象上;
例如:

1
2
3
const int ci=1024;
const int &ri=ci;//正确,引用及其对应对象都是常量
int &r2=ci;//错误,试图用一个非常量引用指向一个常量对象

“对const的引用”简称为“常量引用”。

之前提到过:

引用的类型必须和要绑定的对象严格匹配(比如int类型的引用不能绑定在double上),但是有2个例外。

其中一个例外就是在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能够转换成引用的类型即可。允许为一个常量引用绑定非常量的对象,字面值甚至一个一般表达式。
例如:

1
2
3
4
5
int i=42;
const int &r1=i;//正确
const int &r2=42;//correct
const int &r3=r1*2//correct
int &r4=r1*2;//错误!非常量引用不允许这么搞!

甚至可以这么搞:

1
2
3
double a=1.22;
const int &ri=a;
cout<<ri<<endl;

这种情况下ri其实是绑定了一个由a转换而来的临时的int类型的变量。

注意:对const的引用可能引用一个并非const的对象

其实就是,只有常量引用可以绑定到常量对象上,但是常量引用也可以绑定到非常量对象上,此时可以通过其他途径修改非常量对象的值。
例子:

1
2
3
4
5
int i=42;
int &r1=i;
const int &r2=i;//并不能通过让r2修改i的值
r1=0;//correct
r2=0;//illegal!

指针和const

指向常量的指针(pointer to const)不能用于改变其所指向对象的值,要想存放常量对象的地址,只能使用指向常量的指针。但是,可以改变指针指向的地址。
例子:

1
2
3
4
const double pi=3.14;
double *ptr=&pi;//illegal!ptr是普通指针
const double *cptr=&pi;//correct!
*cptr=42;//illegal!

和常量引用一样,指向常量的指针也没用规定其所指的对象必须是一个常量,所谓指向常量的指针只是要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变(如果指向的对象是非常量当然可以更改它的值)。

指针本身是一个对象,因此也可以定义常量指针(const pointer)。常量指针必须初始化,并且一旦初始化完成,它的值也就是存放在指针中的那个地址就不能再改变了。把* 放在const关键字之前用以说明指针是一个常量,即不变的是指针本身的值而不是指向的那个值。
例子:

1
2
3
4
int errnumb=0;
int *const curErr=&errnum;//常量指针curErr将一直指向errnum
const double pi=3,1415;
const double *const pip=&pi;//pip是一个指向常量对象的常量指针

指针本身是常量并不意味着不能通过指针去修改所指对象的值(可以通过非常量指针修改该对象的值)。

指针本身是不常量以及指向的对象是不是常量是两个相互独立的问题。

常量表达式与constexpr

常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。如果一个对象是const型但是对象的值要到运行时才能获取(比如一个非常量函数的返回值),那么它也不是常量表达式。

在C++11中,允许将变量声明为constexpr类型然后由编译器来验证变量的值是否是一个常量表达式。声明constexpr时用到的类型称为“字面值类型”,它们可以是算术类型,引用和指针。

指针与引用都可以被声明为constexpr类型,但是指针的初始值必须是nullptr或者0或者是存储于某个固定地址中的对象。

注意区分:

1
2
3
const int *p=nullptr;//p是一个指向整形常量的指针
constexpr int *q=nullptr;//q是一个指向整数的常量指针(类似于*const)
constexpr const int *p=nullptr;//指向整形常量的常量指针

于其他常量指针类似,constexpr既可以指向常量又可以指向一个非常量。

类型

类型别名

有两种方式可以定义类型别名。

typedef

例如typedef double wages;

using

新标准规定的一种新的方法,使用别名声明来定义类型的别名
例如:

1
2
using db=double;
db a=1.22;

作用是把等号左侧的名字规定成等号右侧类型的别名。
类型别名和类型的名字等价,只要类型的名字能出现的地方,都可以使用类型别名。

请注意以下情况:

1
2
3
typedef char *pstring;
const pstring cstr=nullptr;//cstr是是指向char的常量指针
const char *cstr1=0;

注意两个声明是不同的!pstring实际上是指向char的指针,因此const pstring就是指向char的常量指针(类似于*const),而后面那个则是指向const char 的指针(指向常量的指针)。

auto

auto定义的变量必须有初始值(不然编译器怎么推断是什么类型233)
同一条声明语句中可以有多个auto变量,但是变量的初始基本数据类型必须一样,例如:

1
2
auto i=0,*p=&i;//正确,一个是整数,一个是整数指针
auto sz=0,pi=3.14;//错误,类型不一致

注意,符号&和*只从属于某个声明符,不影响基本数据类型,但是int和const int并不是同一种基本数据类型!!

当auto遇到const类型时,一般会忽略顶层const而保留底层的const。
顶层const:指针本身是一个常量
底层const:指针所指对象是一个常量
例如:

1
2
3
4
5
const int ci=i,&cr=ci;
auto b=ci;//b是一个整数,顶层const特性忽略
auto c=cr;//c是一个整数
auto d=&i;//d是一个整形指针
auto e=&ci;//e是一个指向整数常量的指针(对常量对象取地址是一种底层const)

如果希望推导出的auto类型是一个顶层const,需要明确指出:

1
const auto f=ci;

也可以将引用的类型设置为auto,原本的初始化规则依然适用:

1
2
3
auto &g=ci;
auto &h=42;//错误,非常量引用不能绑定字面值
const auto &j=42;//correct

decltype

decltype的作用是选择并返回操作数的数据类型。编译器分析表达式并得到它的类型,但是并不计算表达式的具体值。
例子:

1
2
3
4
5
6
7
8
9
10
int f(void)
{
return 1;
}
decltype(f())sum=2;//sum的类型就是函数f的返回类型

const int ci=0,&cj=ci;
decltype(ci)x=0;//x的类型是const int
decltype(cj)y=x;//y的类型是const int&,y绑定到x
decltype(cj)z;//错误!z是一个引用,必须被初始化

上述例子中也可以看到decltype处理顶层const和引用的方式与auto不同,如果decltype使用的表达式是一个变量,那么decltype返回该变量的类型(包括顶层const和引用在内)

注意:decltype((variable))(两个括号或者以上)的结果永远是返回一个引用,而decltype(variable)结果只有在变量本身是引用时才会返回一个引用。

一些其他例子:

1
2
3
int i=42,*p=&i,&r=i;
decltype(r+0)b;//correct,加法结果是int,b是一个未初始化的int
decltype(*p)c;//illegal! c是&int,必须初始化!