const限定符
让修饰的变量的值无法被改变。
const int bufSize = 512;
bufSize = 512; // 报错,视图向 const 对象写值
因为 const 对象一旦创建后其值就不能再改变,所以 const 对象必须初始化。初始值可以是任意复杂的表达式,但是创建和赋值要一起!不能分开写。
#include<iostream>
using namespace std;
int get_size(){
return 10;
}
int main(){
const int num1 = get_size();
const int num2 = 10;
// const int num3; // 报错,定义和赋值要在一定
// num3 = 10;
}
<b>初始化和 const</b>
const 修饰的变量只是无法改变,其他操作和普通变量一样。
#include<iostream>
using namespace std;
int get_size(){
return 10;
}
int main(){
const int num1 = get_size();
const int num2 = 10;
if(num1){
cout<<"num1!=0"<<endl;
}
int total = num1+num2;
cout<<total<<endl;
return 0;
// num1!=0
// 20
}
<b>默认状态下,const 对象仅在文件内有效</b>
当以编译时初始化的方式定义一个 const 对象时,就如对 bufSize 的定义一样 const int bufSize=512;
编译器将在编译过程中把用到该变量的地方都替换成对应的值。也就是说,编译器会找到代码中所有用到 bufSize 的地方,然后用 512 替换。
为了执行上述替换,编译器必须知道变量的初始值。如果程序包含多个文件,则每个用了 const 对象的文件都必须得能访问到它的初始值才行。要做到这一点,就必须在每一个用到变量的文件中都有对它的定义。为了支持这一用法,同时避免对同一变量的重复定义,默认情况下,const 对象被设定为仅在文件内有效。<b>当多个文件中出现了同名的 const 变量时,其实等同于在不同文件中分别定义了独立的变量。</b>
某些时候有这样一种 const 变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。这种情况下,我们不希望编译器为每个文件分别生成独立的变量。相反,我们想让这类 const 对象像其他(非常量)对象一样工作,也就是说,只在一个文件中定义 const,而在其他多个文件中声明并使用它。
解决的办法是,对于 const 变量不管是声明还是定义都添加 extern 关键字,这样只需定义一次就可以了(多个文件用的是同一个 bufSize,但是有一个文件会对他进行初始化,其他文件用的时候用的是被文件 X 初始化后的值)
// file.cpp 定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = get_size();
// file.h 头文件,与 file.cpp 中定义的 bufSize 是同一个
extern const int bufSize;
如果想在多个文件之间共享 const 对象,必须在变量的定义之前添加 extern 关键字。
const的引用
可以把引用绑定到 const 对象上(对常量的引用)。与普通引用的区别是,对常量的引用不能被用作修改它所绑定的对象。
#include<iostream>
using namespace std;
int main(){
const int c1 = 1;
const int &r1 = c1;
// r1 = 42; // 你不能修改常量 c1 的值
// error: binding reference of type ‘int&’ to ‘const int’ discards qualifiers
// int &r2 =c1; // 不能把 const int 赋值给 int
}
C++ 程序员把 “对 const 的引用” 简称为 “常量引用”
<b>初始化和对 const 的引用</b>
<span style="color:orange">引用的类型必须与其所引用对象的类型一致</span>,<span style="color:red">但是有两个例外。</span>
- 第一种例外情况就是在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式。
#include<iostream>
using namespace std;
int main(){
int i = 42;
// 只是引用的指向不能更改。但是 i 可以自己修改自己。
const int &r1 = i;
const int &r2 = 42;
const int &r3 = r1*2;
// int &r4 = r1 * 2; // 错误 r4 只是一个普通的非常量引用
cout<<r1<<endl; // 42
i = 56;
cout<<r1<<endl; // 56
}
来看下下面的赋值
int main(){
double dval = 3.14;
const int &r1 = dval;
// 实际上是做了这样一个操作
// const int temp = dval;
// const int &r1 = temp;
return 0;
}
去掉 const 会报错,因为非 const 引用的目的最终还是为了修改它的值,最后 r1 拿到的是 temp 而非 dval,又如何去修改 dval 的值呢?所以这里需要用 const 修饰。
<b>对 const 的引用可能引用一个非 const 的对象</b>
int main(){
int i = 42;
int &r1 = i;
// 只是无法通过 r2 修改 i 的值而已。
const int &r2 = i;
cout<<r2<<endl; // 42
i = 199;
cout<<r2<<endl; // 199
return 0;
}
指针和const
可以让指针指向常量和非常量
const double pi = 3.14; // pi 是常量,值不能改变
double *ptr = π // 错误,ptr 是个普通指针,不是 const 指针
const doule *cptr = π // 正确,*cptr 指向的内容是常量。
*cptr = 42; // 错误,不能给 *cptr 赋值(不能给 pi 赋值)
class D{
public:
int a = 10;
};
int main(){
const D *d = new D; // 指针常量,指针指向的对象是常量,不能修改任何东西
d->a = 10; // 错误,d 是常量,你什么都不能修改!
cout<<d<<endl;
}
所谓指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变。
#include<iostream>
using namespace std;
class D{
public:
int a = 10;
};
int main(){
D *obj = new D;
const D *constObj = obj;
// obj 只是一个普通的指针
obj->a = 90;
// constObject 被认为是一个指向常量的指针
// 因此会出现 error,常量不允许被改变
constObj->a = 100;
}
<b style="color:red">注意:可以认为,所谓指向常量的指针或引用,不过是指针或引用自以为是罢了,它们觉得自己指向了常量,所以自觉地不去改变所指的那个指。</b>
<b>const 指针</b>
指针是对象而引用不是,因此就像其他对象类型一样,允许把指针本身定为常量。常量指针(const pointer)必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。把*放在 const 关键字之前用以说明指针是一个常量,这样的书写形式隐含着一层意味,即不变的是指针本身的值而非指向的那个值。
int errNumb = 0;
int *const curErr = &errNumb; // curErr 将一直指向 errNumb
const double pi = 3.14;
const double *const pip = π // pip 是一个指向常量对象的常量指针
从右向左阅读明白声明的含义!
指针本身是一个常量并不意味着不能通过指针修改其所指对象的值,能否这样做完全依赖于所指对象的类型。这个 Java 的 final 修饰类似。
class D{
public:
int a = 10;
};
int main(){
D *const d = new D;
d->a = 100;
cout<<d->a<<endl; // 100
}
做完全依赖于所指对象的类型。例如,pip 是一个指向常量的常量指针,则不论是 pip 所指的对象值还是 pip 自己存储的那个地址都不能改变。相反的,curErr 指向的是一个一般的非常量整数,那么就完全可以用 curErr 去修改 errNumb 的值:
void testConstPoint2(){
const int num1 = 100;
const int *p = &num1;
D *const curErr = new D;
if(curErr){
cout<<"对象不为空,修改它的值"<<endl;
curErr->a = 1000;
}
}
// 对象不为空,修改它的值
顶/低层const
顶层 const,指针本身是常量;底层 const,指针所指的对象是常量。
#include<iostream>
using namespace std;
int main(){
int i = 0;
// const 先修饰左边的,左边没有再修饰右边
// const 修饰的 *,指针本身不能改,所以是顶层const
int *const p1 = &i;
// const 先作用于int,所以ci的内容不能改。即ci的指向不能改,是顶层const
const int ci = 42;
// const 修饰的 int, 然后 int *p2, 是指针,
// 指针指向的内容是 const 的不可变,所以是底层const
const int *p2 = &ci;
}
<b style="color:red">const 先修饰左边的,左边没有再修饰右边</b>
constexpr和常量表达式
常量表达式(const expression)是指值不会改变<span style="color:red">并且在编译过程就能得到计算结果的表达式。</span>
一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定,例如
const int max_files = 20; // 是
const int limit = max_files + 1; // 是
int staff_size= 27; // 不是
const int szz = get_size(); // 不是
尽管 staff_size 的初始值是个字面值常量,但由于它的数据类型只是一个普通 int 而非 const int,所以它不属于常量表达式。另一方面,<span style="color:red">尽管 sz 本身是一个常量,但它的具体值直到运行时才能获取到,所以也不是常量表达式。</span>
<b>constexpr 变量</b>
C++11 提供的关键字,用于定义常量表达式。
#include<iostream>
using namespace std;
int get_size(){
return 10;
}
// 常量表达式,值在编译时可以确定
constexpr int num = 10;
// 不是,普通函数的返回值不能作为常量表达式的右值。
constexpr int aum = get_size();
但是新标准允许定义一种特殊的 constexpr 函数。这种函数应该足够简单以使得编译时就可以计算其结果,这样就能用 constexpr 函数去初始化 constexpr 变量了。
<b>指针和 constexpr</b>
一个 constexpr 指针的初始值必须是 nullptr 或者 0,或者是存储于某个固定地址中的对象。
<b style="color:red">在 constexpr 声明中如果定义了一个指针,限定符 constexpr 仅对指针有效,与指针所指的对象无关:</b>
// const 先修饰左边再修饰右边,因此这里的是
// 指针 p 指向的内容是常量。
const int *p = nullptr;
// constexpr 只是修饰指针的,因此是指针是常量。
// constexpr 会把 const 提升为顶层 const
constexpr int *q = nullptr;
练习
// *p 的赋值错了,*p 要么给 nullptr(*p=0),或者给变量的地址值
// 即便 null = 0. 也不能 *p=null.
int null = 0,*p=null;
处理类型
类型别名
有两种方式
typedef int age;
using age = int;
typedef int age;
using ages = int;
#include<iostream>
using namespace std;
int main(){
age a = 18;
ages c = 10;
cout<<a<<endl;
cout<<c<<endl;
return 0;
}
// 18
// 10
<b>指针、常量和类型别名</b>
typedef char *pstring; // pstring 是类型 char* 的别名
const pstring cstr = 0; // cstr 是指向 char 的常量指针
const pstring *ps; // ps 是一个指针,它的对象是指向char的常量指针
auto
C++11 引入的关键字,自动类型推导。
#include<iostream>
using namespace std;
int main(){
auto i=10,*p=0;
int c = 10.5;
return 0;
}
自定义数据结构
#include<iostream>
using namespace std;
struct varName{
int n1;
char n2;
};
int main(){
varName name;
cout<<&name<<endl;
name.n1 = 100;
name.n2 = 'c';
cout<<name.n1<<endl; // 100
cout<<name.n2<<endl; // c
}
C++11 新标准规定,可以为数据成员提供一个类内初始值(in-class initializer)。创建对象时,类内初始值将用于初始化数据成员。没有初始值的成员将被默认初始化。
预处理器
使用预处理器,确保头文件制备包含一次。
#ifndef NUM
#define NUM 10
// some code....
#endif
后面搜其他资料看看。
decltype
decltype 根据括号中的内容推断变量的类型,并且只是推断类型,并不会对变量进行初始化。
#include<iostream>
using std::cout;
using std::endl;
int main() {
int a = 3, b = 4;
decltype(a) c = b; // 单层括号,因此类型和a一样,是int类型
decltype((a))d = a; // 双层括号,因此是引用类型。
++c;
cout<<"c="<<c<<endl; // c=5
++d;
cout<<"a="<<a<<endl; // a=4
}
注意:decltype((variable))(注意是双层括号)的结果永远是引用,而 decltype(variable)结果只有当 variable 本身就是一个引用时才是引用。