const 限定符
const 变量值不能被改变,所以const对象如果不是外部的,则必须初始化常量对象。只能在const类型的对象上执行不改变其内容的操作。默认状态下,const对象仅在文件内有效,若要在多个文件之间共享const对象,须在变量定义前添加extern关键字。
const的引用
const对象的引用称为常量的引用(简称常量引用)。此引用不能被用作修改它所绑定的对象。只能const的引用才能指向const对象。
int main()
{
double dval = 3.14;
const int &ri = dval; // ri会绑定一个由double转换的临时int常量
int &r2 = dval; // 无法从“double”转换为“int &”
// 当使用非常量引用时,可以修改所引用对象的值。但此时绑定的对象是一个临时值,而不是dval。故无法改变,因此c++将此归为非法
std::cout << ri << std::endl;
return 0;
}
const的引用可以引用非const的对象,反之不可,const对象不可被非const引用绑定。
int main()
{
int i = 42;
int &ri = i; // 引用ri绑定对象i
const int &r2 = i; // r2也绑定对象i, 但不允许通过r2修改i的值
std::cout << r2 << std::endl; // 42
ri = 0;
std::cout << r2 << std::endl; // 0
return 0;
}
引用必须初始化,且只能绑定在对象上,不能与字面值或表达式结果绑定。但常量引用可以绑定字面值
const int &r = 0;
const int &const r2 = 0; // 应属于错误,视为优化掉了r2前的const,声明引用的const都是底层const
指针和const
指向常量的指针不能改变其所指对象的值。同引用一样,常量指针可以指向变量,但无法修改变量的值(对象的值可以通过其他途径改变),相应的常量只能被常量指针指向。
const double pi = 3.14;
// double *ptr = π // 普通指针无法指向常量
const double *cptr = π // 指向double 常量的指针
// *cptr = 42; // 常量指针不可赋值
const指针
指针是对象而引用不是,因此就像其他对象类型一样,允许把指针本身定为常量。常量指针必须被初始化,一旦初始化完成,则它的值(存放的地址)就不能在改变。*在const前,用以说明指针是一个常量,表示不变的是指针本身的值,而非指向的值。
int main()
{
int errNumb = 0;
int *const curErr = &errNumb; // 一直指向errNumb。
const double pi = 3.14159;
const double *const pip = π // pip 是一个指向常量对象的常量指针
}
从右向左读。 curErr最近的符号是const代表curErr本身是一个常量,类型由其余部分确定,*代表curErr是一个常量指针,最后声明语句的基本数据类型部分确定了常量指针指向的是一个int对象。pip同理,可知他是一个指向double常量的指针。
指针本身是常量不意味着不能通过指针修改其所指对象的值,能否修改取决于所指对象的类型。上例中的pip无论自身和所指的对象都无法改变,但curErr可以修改errNumb的值。
int main()
{
int errNumb = 10;
int *const curErr = &errNumb; // 一直指向errNumb。
const double pi = 3.14159;
const double *const pip = π // pip 是一个指向常量对象的常量指针
std::cout << *curErr << std::endl; // 10
// *pip = 2.72; // pip指向的是一个常量,无法修改
if (*curErr) {
*curErr = 0;
}
std::cout << *curErr << std::endl; // 0
std::cout << errNumb << std::endl; // 0
return 0;
}
顶层const
指针本身是一个对象,它又可以指向另一个对象。因为指针本身是不是常量以及指针所指得是不是常量就是两个独立问题。顶层const表示指针本身是个常量,底层const表示指针所指的对象是一个常量。
顶层const可以表示任意的对象是常量,对任何数据类型都适用,如算术类型、类、指针等。底层const则与指针和引用等复合类型的基本类型部分有关。指针较为特殊,既可以是顶层const也可以是底层const。
int i = 0;
int *const p1 = &i; // 不能改变p1的值,这是一个顶层const
const int ci = 42; // 不能改变ci的值,这是一个顶层const
const int *p2 = &ci; // 允许改变p2的值,这是一个底层const
const int *const p3 = p2; // 靠右的const是顶层const,靠左的是底层const
const int &r = ci; // 用于声明引用的const都是底层const
拷贝操作时,顶层const和底层const区别明显。顶层const不受什么影响:
// 顶层const拷贝
i = ci; // 拷贝ci的值,ci是顶层const,对此操作无影响
p2 = p3; // p2和p3所指向的对象类型相同,p3顶层const部分不影响
// 底层const拷贝
// int *p = p3; // 错误。p3包含底层const,p没有。 p3指向的是一个常量,p指向的不是
p2 = p3; // p2 p3都是底层const
p2 = &i; // int* 能转换成 const int*
//int &r = ci; // 错 int常量需使用const引用
const int &r2 = i; // const int & 可以绑定到普通int上,字面值也可以。
p3既是顶层const也是底层const,拷贝p3时可以不在乎它是一个顶层const,但是必须清楚它指向的对象是一个常量(底层const)。
constexpr 和常量表达式
常量表达式:值不会改变并且编译过程就能得到计算结果的表达式。字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。
const int max_files = 20; // max_files 是常量表达式
const int limit = max_files + 1; // limit是常量表达式
int staff_size = 27; // staff_size 不是常量表达式, 需为const int
const int sz = get_size(); // sz不是常量表达式,因为具体值需在运行时通过调用函数才能获得
constexpr 变量
由于初始值是不是常量表达式很难分辨。所以在c++11新规中,允许将变量声明为constexpr类型,以便编译器验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,并且必须用常量表达式初始化:
constexpr int size() // 编译时就可以计算其结果
{
return 666;
}
int size2()
{
return 777;
}
int main()
{
constexpr int mf = 20; // 20是常量表达式
constexpr int limit = mf + 1; // mf+1是常量表达式
constexpr int sz = size(); //只有当size是一个constexpr函数时,才不会报错。
// constexpr int sz2 = size2(); // error C2131: 表达式的计算结果不是常数
std::cout << sz << std::endl;
return 0;
}
字面值类型
字面值类型可以被定义为constexpr,算数类型、引用和指针都属于字面值类型。自定义类、IO库、string类型则不属于字面值类型,不能被定义成constexpr。
指针和引用尽管能被定义为constexpr,但其初值必须时nullptr或0,或是存储于某个固定地址中的对象。函数体中的变量并非存放固定位置中,不能用于初始化,只有有效范围超出函数本身的变量才会有固定位置。constexpr引用可以绑定到此种变量,constexpr指针也能指向这样的变量。
指针和constexpr
constexpr声明中若定义了指针,限定符constexpr仅对指针有效,与指针所指的对象无关:
const int *p = nullptr; // p是一个指向int常量的指针
constexpr int *q = nullptr; // q是一个指向int的常量指针
constexpr把它所定义的对象置为了顶层const。与其他常量指针类似,constexpr指针既可以指向常量也可以指向非常量:
int j = 10;
// const int i = 42;
int main()
{
constexpr int *np = nullptr; // np是一个指向整数的常量指针,其值为空
//int j = 0; // 函数内变量没有固定地址。
constexpr int i = 42; // i的类型是int常量,constexpr编译时常量,若使用const下面会报错
constexpr const int *p = &i; // p是常量指针,指向整型常量 i
constexpr int *p1 = &j; // p1是常量指针,指向整型 j。 j 必须在函数体之外
std::cout << *p << std::endl;
std::cout << *p1 << std::endl;
return 0;
}
const是运行时常量,constexpr是编译时常量。上例中i 是一个 constexpr 编译时常量,在函数内部声明和全局作用域下声明没有本质区别,因为编译器在编译时已经确定了它的值和地址。如果定义为const i 则需要在函数外部定义。
// int null = 0, *p = null; // 错
int null = 0, *p = &null;
int null = 0, *p = 0;
处理类型
类型别名
typedef double wages; // wages是double的同义词
typedef wages base, *p; // base是double的同义词, p是double*的同义词
wages a = 666.77;
p a2 = &a;
// using SI = Sales_item的同义词; // SI是Sales_item的同义词
using wages = double;
using base = wages;
using p = wages *;
wages a = 666.77;
p a2 = &a;
指针、常量和类型别名
typedef char *pstring;
const pstring cstr = 0; // cstr是指向char的常量指针
const pstring *ps; // ps是指针,它的对象是指向char的常量指针
const char *cstr2; // 直接把类型别名替换到定义中是错误的理解。此为指向const char的指针。
const 是对给定类型的修饰,pstring是指向char的指针,故const pstring就是指向char的常量指针,而非指向常量字符的指针。pstring的基本数据类型是指针,而若直接把类型别名替换到声明,此时 const char就成了基本数据类型。会导致两种声明含义截然不同。
auto类型说明符
c++11引入的类型说明符,可使编译器分析表达式所属类型,auto定义的变量必须有初始值。
auto i = 0, *p = &i; //正确: i int, i int *
//auto sz = 0.0, pi = 3.14; // 错误:sz 和 pi类型不一致,
//对于此实体“auto”类型是 "double",但之前默示为 "int"
使用auto同时声明多个变量时,基本数据类型必须一致。
复合类型、常量和auto
auto的类型并不总是等同初始值类型,编译器会适当改变结果类型。
eg:当以引用对象的类型作为auto的类型时:
int i = 0, &r = i;
auto a = r; // a为int,因为r是i的别名,i是int
auto一般会忽略顶层const
const int ci = i, &cr = ci;
auto b = ci; // b 为 int 而不是const int(忽略顶层const)
auto c = cr; // c 为 int, cr 是 ci的别名,ci是顶层const
auto d = &i; // d 是 int *, 整数的地址就是指向整数的指针
auto e = &ci; // e 是 const int* (对常量对象取地址是一种底层const)
若需推断auto为顶层const,需明确指出
const auto f = ci; // ci 是int, f是const int
将引用的类型设为auto,初始值中的顶层const会保留(别名)
auto &g = ci; // g 是 const int &
//auto &h = 42; // 非常量引用不可绑定字面值,毕竟不可修改。
const auto &j = 42; // 常量引用可以
注:符号&和*只属于某个声明符,不是基本数据类型的一部分。初始值必须为同一类型。
// auto &n = i, *p2 = &ci; //
// 错误,n,p2 都是 const int,但重要的是i是int &ci是const int
auto k = ci, &l = i; // ci(去除顶层const)与i都是int
decltype类型提示符
c++11引用的decltype类型说明符,会选择并返回操作数的数据类型,避免了auto使用表达式的值初始化变量。
在处理顶层const和引用时,decltype与auto有些许不同。decltype返回的类型,会包括顶层const和引用。
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; // 常量引用必须初始化
decltype(1) y; // auto 必须输出化,而decltype有时不需要
引用从来都作为其所指对象的同义词出现,只有在decltype处是例外。
decltype和引用
当decltype使用的表达式不是变量,则decltype返回表达式结果对应的类型。存在表达式结果为引用类型的情况。
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // 结果为int, b为未初始化的int
//decltype(r) b; // r 为引用,需初始化
//decltype(*p) c; // c为int&,必须初始化
当表达式内容是解引用操作,则decltype将得到引用类型。解引用能得到指针所指的对象,还能对该对象赋值,故decltype(*p)结果是int&,而非int。
decltype与auto另一重要区别:
decltype的结果类型与表达式形式密切相关。当表达式变量名加上了一对括号,则得到的类型与不加括号时会有不同。不添加时结果是变量的类型,而当添加一层或多层括号时,编译器会将变量视为表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以得到的结果会是引用类型。
decltype(i) b; // 结果为int, b为未初始化的int
// decltype((i)) b2; // 错误,引用必须输出化
双层括号时decltype结果永远是引用,单层括号时只有当变量本身是引用时才是引用(或解耦)
文章评论