变量
int a=0; |
int a={0}; |
int a{0}; |
int a(0); |
---|
无论初始化对象或是为对象赋值。但注意不允许初始值存在丢失风险:
double pi=3.1415; |
int b={pi}; |
---|
定义于任何函数之外的内置类型变量被初始化为0,函数体内部(包括main函数!!)的内置类型变量不被初始化。
数组赋初值:
int a[4]{0},b[4]={0}; //内置类型数组初始化
int pi=3,c[4]={pi};
A d[4]; //类A默认构造函数初始化
A e[4](A(2)); //用构造函数A(int)直接初始化每个对象
C++支持分离式编译,即程序若干文件可被独立编译。
C++是一种静态类型语言,其含义是在编译阶段检查类型。
声明使名字为程序所知,定义负责创建与名字关联的实体。若想声明一个变量而非定义它,则在变量前添加关键字extern且不赋初值,否则为声明并定义—若给extern关键字标记的变量赋初值,则extern无用。
extern int i; //声明i而非定义i |
int j; //声明并定义j |
int a=0; //显示初始化即成定义 |
---|
在函数体内部,若试图初始化一个由extern关键字标记的变量,将报错。
变量能且只能被定义一次,但可以被多次声明
extern表示该变量已经在别处(一般为文件外的某全局变量)定义过了,在这里是要使用那个变量。
用户自定义的标识符(变量名)不能连续出现两个下划线,不能以下划线紧连大写字母开头,定义在函数体外的标识符不能以下划线开头。
全局(静态)变量名与局部变量名相同,局部作用域内使用局部变量。
引用与指针
引用即为对象起别名,引用类型引用另外一种类型。引用与初始值对象一直绑定在一起,而非拷贝初始值。
int a=0,&i=a;
注意引用类型要和与之绑定的对象严格匹配,且只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起,以下为错误定义
|
|
|
---|
引用必须初始化!引用本身并非一个对象,一旦定义了引用,就无法再绑定到另外的对象,之后每次使用这个引用都是访问它最初绑定的那个对象。但注意允许多引用传递:
int i=1,&j=i,&k=j; //j、k均为i的别名
指针*p与引用&a区别:
1. 指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。
2. 指针无须再定义是赋初值。
取地址符&b与解引用符*p:
int b=1,*p=&b; //取地址 |
cout<<*p; //解引用 |
---|
- &随类型名出现,a是一个引用。&出现在表达式,b是一个取地址符。
- 不能定义指向引用的指针,因为引用不是对象,没有实际地址。
- 指针的类型必需与指向对象的类型匹配,否则报错。
- 不能把变量直接赋给指针,即使变量值等于0!
- 解引用操作仅适合于那些确实指向了某个对象的有效指针
int i=1,&j=i,*k=&j; //通过且j和*k是i的别名,i,j,k均为同一地址
int *p=0; //错误!正确赋值int *p=&i;
指针的值(即地址)4个状态:
1. 指向一个对象
2. 指向紧邻对象所占空间的下一个位置
3. 空指针,意味着指针没有指向任何对象
4. 无效指针,即上述3种之外的其它值,不得拷贝或访问
第2种和第3种行为下的指针同样不允许试图访问。
空指针
int *p=nullptr; |
int *p=0; |
int *p=NULL; //需要cstdlib库(?存疑,一般没有也能用) |
---|
这里nullptr是一种特殊类型的字面值,它可以被转换成任意其它的指针类型。NULL为预处理变量,值为0。
预处理变量由预处理器负责管理,预处理器是运行与编译过程之前的一段程序,当用到一个预处理变量时,预处理器会自动地将它替换为实际值。预处理变量无视作用域规则!
任何非空合法指针对应的条件值都是true。
void* 指针了存放任意非const类型任意非常量对象的地址,但不能直接操作void*所指向的对象,必须强制类型转换。
引用不是对象,故而不能定义指向引用的指针。但指针是对象,故存在对指针的引用:
int *p=&i,*&r=p;
//表示r是一个对指针p的引用
int *p=&i,&c=*p;
//表示c是*p的别名
int i=36; r=&i;
//表示r引用了一个指针,此处即为令p指向i
*r=0;
//解引用得到i,即p指向的对象,将i的值改为0
const限定符
const对象一旦创建后其值就不能再改变,所以const对象必须初始化,但初始值可以为任意复杂的表达式,比如:
const int i=get_number();
注:const对象能完成大部分非const对象所能参与的操作,比如可将const对象赋给非const对象!主要限定在于不能改变其内容。例如const int和普通的int一样都能参与算术运算,也都能转换成一个布尔值,赋值给其它变量(不论是否为常量)。
编译器在编译过程中把用到常量的地方都替换成对应的初始值!
若想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字。
// file1.cpp 定义并初始化一个常量
extern const int s=get_number();
// file2.h 访问了常量
extern const int s;
//与file1.cpp中定义的s是同一个
把引用绑定到const对象上,称之为对常量的引用:
const int a=1;
const int &b=a;
//正确,引用及其对应的对象都是常量
b=4;
//错误,对常量的引用不能修改它所绑定的对象
int &c=a;
//错误,不允许一个非常量引用指向一个常量对象
初始化常量引用允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。这意味着,允许一个常量引用绑定非常量的对象、字面值、甚至一般表达式:
int i=4;
const int &a=i;
//允许将const int &绑定到一个普通int上,但a不允许修改i的值!i可以修改自己的值
const int &b=4;
//正确
const int &c=a*2;
//正确
int &d=a;
//错误,非常量引用不能赋值常量引用
对于数组:
const int arr[2]{
1,2};
其中值arr[0]和地址arr都不可修改!但为形参时退化为const int *arr
,此时地址arr可修改
注:
double j=3.14;
const int &e=j;
//能运行,double可以转换成int类型。但非法
这里为了确保让e绑定一个整数,编译器把上述代码变成了:
const int temp=j;
//由double生成一个临时的整型常量
const int &e=temp;
//让e绑定这个临时量
注意此时e绑定的是一个临时量而非j!
指向常量的指针(const int *p)允许指向非常量对象但不能通过自己改变对象的值,另外不允许出现const int *p=&2
或&(i+2)
。
常量指针是把*放在const关键字之前,这时指针是一个常量,不变的是指针本身的值(即指向的地址),而非其所指向的对象的值保持不变。能否通过指针(*p)修改其所指向的对象的值完全取决于所指向对象的类型(即是否为非常量)。
定义顶层const表示指针本身是一个常量,底层const表示指针所指的对象是一个常量。
常量表达式是指不会改变且在编译过程中就能得到计算结果的表达式。constexpr限定在编译期常量但constexpr函数返回值不一定为编译器常量。包括字面值和用常量表达式初始化的const对象。
const int a=20;
//是
const int b=a+1;
//是,且无论a是否为常量
int c=20;
//否,数据类型非const
const int d=get_number();
//否,具体值直到运行才能获取到
若需要一个变量为一个常量表达式,那就把它声明成constexpr类型。
constexpr int a=20
//是
constexpr int b=a+1;
//是,且无论a是否为常量
constexpr int c=get_number();
//只有当get_number()是constexpr函数时正确
注:constexpr仅支持算术类型、指针和引用(三者属于字面值类型),而自定义类如string,IO库等则不能。另外constexpr指针的初始值必须是nullptr或0,或存储在固定地址中的对象—–这意味着函数体内的变量一般不能被constexpr指针指向。
再注:constpr限定符仅对指针本身有效,对指针所指向的对象无关:
const int *p=nullptr;
//p是一个指向整型常量的指针
constexpr int *q=nullptr;
//q是一个指向整数的常量指针
constexpr函数的返回值及所有形参的类型都得是字面值类型且函数体中必须有且只有一条return语句,但其返回值不要求一定是常量表达式。constexpr函数内声明的所有变量必须初始化,且只有当实参是常量表达式时返回值才是常量表达式。
类型
类型别名typedef与using
typedef int a;
//a是int的同义词
typedef a b,*p;
//b是int的同义词,p是int*的同义词—是指向int的指针
using x=y;
x是y的同义词
auto类型说明符让编译器通过初始值来推算变量的类型,故而auto定义的变量必须有初始值。可以在一条auto语句中声明多个变量,但该句中所有变量的初始基本数据类型必须一样。
使用auto时注意其不能识别引用和顶层const,即以下情况:
const int i=10;
auto a=i;
//此时b被定义成了一个int类型
const auto b=i;
//此时b才被定义成了一个const int类型
auto c=&i;
//c是一个指向整数常量的指针
类型说明符decltype选择并返回操作数的数据类型,注意编译器只分析表达式并得到它的类型却不会实际计算表达式的值。decltype接受函数名、变量名、表达式、const和引用。
函数
decltype(func()) a=b;
//a的类型就是函数func的返回类型,但编译器不实际调用func
变量名
decltype(i) c=0;
//c的类型是i的数据类型即const int
const int &j=i;
decltype(j) d=c;
//d的类型是const int&引用,d绑定到c
表达式
int a=1,*p=&i,&r=i; double b=3.14;
decltype(a+b) c;
//c的类型是表达式a+b计算结果对应的double类型
decltype(r+0) d=0;
//d的类型是int非引用
decltype(*p) e=0;
//e的类型是引用int&而非int
注:decltype所用表达式加括号时编译器当成表达式,不加时才表示变量类型。
decltype(a) x;
//x的类型是a的类型int
decltype((a)) y=0;
//y的类型是引用int&(双层括号的结果永远是引用!!)
sizeof运算符:
1. 对引用类型执行sizeof运算得到被引用对象所占空间的大小
2. 对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小
显示转换
在隐式类型转换中编译器只会执行一步类型转换,若程序隐式的使用了两种转换规则则会报错。
cast-name<type>(expression)
cast-name:static_cast、