C++ 学习笔记之(2)-变量、类型和限定符
注释
C++中有两种注释
- 单行注释:双斜线
//
开始,以换行符结束,可以包含任何文本,比如额外的双斜线 - 多行注释:界定符界定,以
/*
开始,以/*
结束,可以包含除*/
外的任意内容,包括换行符。
注意:注释界定符不能嵌套,否则会发生错误
数据类型
C++提供了一组内置数据类型,程序员也可以定义自己的数据类型,即类类型。基本数据类型包括算数类型和控类型。算数类型又分为两类:整形和浮点型。
- 基本的字符类型
char
可以存放机器基本字符集中任意字符对应的数值,故char
类型通常一个字节,其他字符类型用于扩展字符集,比如wchar_t
用来确保存放机器最大扩展字符集中的任意字符,比如汉字等。char16_t
和char32_t
为Unicode
字符集服务。 - 除字符和布尔类型外,其他整型用于表示不同尺寸的整数,
short <= int <= long <= long long
,long long
为C++11
新特性。 - 浮点型可表示单精度、双精度和扩展精度值,即
float
,double
和long double
, bool
类型表示真值true
和false
。可以将算术类型的任何值赋给bool
对象。0
值算术类型代表false
,任何非0
的值都代表true
无符号类型和带符号类型
除去布尔型和扩展字符型外,其他整形可以分为带符号
和无符号
类型。
带符号
类型表示正数、负数和0
,无符号
类型仅能表示大于等于0
的值。类型
int
、short
等为带符号类型,前面加unsigned
表示无符号类型。类型unsigned int
可以缩写为unsigned
- 执行浮点数运算选用
double
,因为float
通常精度不够,且两者计算代价相差无几。
类型转换
对象的类型定义了对象包含的数据和能参与的运算,大多数类型都支持类型转换,即比如给某类型对象赋值其他类型值,会自动进行类型转换。
- 若赋给无符号类型一个超出范围的值时,结果是初始值对五福哈类型表示数值总数取模后的余数。比如
-1
赋值给unsigned char
类型即表示区间为0 - 255
, 结果为255
- 当赋值给带符号类型一个超出表示范围的值时,结果是未定义的。
- 切勿混用带符号类型和无符号类型,因为带符号类型会自动的转换成无符号类型。
字面值常量
形如42
的值被称为字面值常量,字面值常量的形式和值决定了它的数据类型
整形和浮点型字面值
- 整型字面值可以是十进制数、八进制数或者十六进制数的形式,以
0
开头的整数代表八进制数,以0x
或0X
开头的代表十六进制数。 - 整型字面值的具体类型由值和符号决定。默认十进制字面值为无符号数,其他不确定。十进制字面值的类型是
int
、long
和long long
中能容纳下当前值最小的那个。八进制和十进制字面值的类型是容纳其数值的无符号类型的最小者,即int
、unsigned int
等 short
没有对应字面值- 默认浮点型字面值是一个
double
,表现为一个小数或以科学计数法表示的指数,其中指数部分用E
或e
标识
字符和字符串字面值
由单引号括起来的一个字符成为char
型字面值,双引号括起来的零个或多个字符为字符串型字面值。
字符串字面值的类型实际上是由常量字符构成的数组,结尾处添加一个空字符
\0
,故实际长度比内容多1
有两类字符不能直接使用,分别是不可打印字符,比如退格或其他控制字符;另一类是C++中有特殊含义的字符比如单引号、问号等。可以使用转义序列。
两个相邻的字符串字面值若仅由空格、缩进和换行符分割,则他们实际为一个整体。比如某字符串字面值较长,可分开书写在多行
std::cout << "a really, really long string literal" "that spans two lines" << std::endl;
布尔字面值和指针字面值
true
和false
是布尔类型的字面值nullptr
是指针类型字面值
通过前缀和后缀可以改变整型、浮点型和字符型字面值的默认类型
变量
变量定义
变量提供一个具名的、可供程序操作的存储空间。C++中的每个变量都有其数据类型,数据类型决定着变量所占空间的大小和布局方式、存储值的范围以及变量参与的运算。
变量定义形式:
类型说明符 变量名1, 变量名2;
, 也可以给其赋初值初始花不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,以一个新值来替代。
列表初始化用于内置类型变量时,不会转换类型。
long double ld = 3.1415926536; int a{ld}, b = {ld}; // 错误,转换未执行,因为存在丢失信息的危险 itn c(ld), d = ld; // 正确,转换执行,且确定丢失了部分值
默认初始化当定义变量时未指定初值时发生,默认值由变量类型和定义位置决定。任何函数体之外的内置类型被初始化为
0
;函数体内部的内置类型变量不被初始化,即值未定义
。类的对象若没有显示初始化,则其值由类确定。
标识符由字母、数字和下划线组成,必须以字母或下划线开头。长度无限制,但对大小写敏感
变量声明和定义的关系
C++ 支持分离式编译,即分割程序为多个文件,每个文件独立编译
定义:用于为变量分配存储空间,还可为变量指定初始值。程序中,变量有且仅有一个定义。
声明:用于向程序表明变量的类型和名字。
定义也是声明,extern声明不是定义,除非extern显示初始化
任何包含了显示初始化的声明即成为定义。若extern语句包含初始值,则不是声明,而是定义。并且extern定义只能在函数体外部,否则会引发错误。
extern double pi = 3.1416; //定义
多文件工程中,声明和定义非常重要。变量的定义必须且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却决不能重复定义。
有关声明和定义的关系,可参见此文章C语言中声明和定义详解
复合类型
复合类型是指基于其他类型定义的类型。
引用
引用并非对象,只是为一个已经存在的对象所起的另外一个名字。
引用必须初始化,因此无法令引用重新绑定到其他对象
引用不是对象,故不能定义引用的引用,也不能定义指向引用的指针。
引用只能绑定在对象上,不能与字面值或某个表达式的计算结果绑定在一起
引用定义
int i = 1024; int &r = i; // r 是一个引用,与 i 绑定在一起
指针
指针是指向另外一种类型的复合类型
- 指针本身就是对象,允许对指针赋值和拷贝,也可以更改指向不同的对象
- 指针无需在定义时赋初值,但和其他内置类型一样,块作用域中若没有被初始化,则值未定义
- 指针的值(即地址)有四种状态,试图访问无效指针的值将引发错误
- 指向一个对象
- 执行紧邻对象所占空间的下一个位置
- 空指针,意味着指针没有指向任何对象
- 无效指针,也就是上述情况之外的其他值
- 空指针不指向任何对象,使用指针之前可以检查是否为空,C++11推荐使用
nullptr
初始化指针为空 - void * 指针是一种特殊的指针类型,可以存放任意对象的地址。但无法了解改地址中是什么类型对象。
- 指针是对象,所以可以定义指针类型的引用。若类型比较复杂,可以采取从右到左阅读变量定义。
const 限定符
const限定符可以对变量的类型加以限定,变成常量,使其不可修改
- const 对象创建后其值不可改变,故必须初始化。
- 默认状态下,const对象仅在文件内有效。因为const对象在编译期间被替换成对应值,故必须在该变量文件中找到其定义。
- 若多文件共享const对象,则必须在const变量定义前加extern关键字。
const 引用
对const的引用简称常量引用,不能更改引用绑定的对象。
- 引用类型必须与其所引用对象类型一致,但有两个例外,第一个是初始化常量引用时允许用任意表达式作为初始值,只要其结果能转换成引用类型即可。
- 当一个常量引用绑定到其他类型或常量时,其他类型变量会创建一个临时量来存放转换或表达式结果,故对该引用的操作就是对该临时变量的操作。
- 常量引用可以绑定非常量对象、字面值
指针和const
与引用一样,指针也可以指向常量或非常量,指向常量的指针不能用于改变所指对象值。要想存放常量对象地址,只能使用指向常量的指针。
- 指针的类型必须与其所指对象类型一致,但有两个例外,第一个就是允许另一个指向常量的指针指向一个非常量对象。
- 指针是对象,而引用不是,故可以把指针本身定为常量。
- 常量指针必须初始化,一旦初始化完成,则值(即存放的地址)不可改变。
*
在const之前表明指针是常量,即不变的是指针本身,而不是指向的那个值- 顶层const表示指针本身是常量, 底层const表示所指的对象是一个常量
constexpr和常量表达式
常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式
字面值是常量表达式,用常量表达式初始化的const对象也是常量表达式
C++ 11规定,允许使用
constexpr
声明变量为常量,且必须使用常量表达式初始化。也可以使用constexpr
函数,这种函数足够简单到编译时就能计算结果。constexpr
指针的初始值必须是nullptr
或者0
, 或者是存储于某个固定地址总的对象。函数体内定义的变量并非存放在固定地址中,故constexpr
指针不能指向此类对象。而定义在函数体外的对象地址固定不变,能用来初始化constexpr
指针constexpr
声明中若定义了指针,则限定符只对指针有效,与指针所指对象无关const int *p = nullptr; // p 是一个指向整型常量的指针 constexpr int *q = nullptr; // q 是一个指向整数的常量指针。
关于const
的好文推荐
处理类型
类型别名是一个名字,是某种类型的同义词。有两种方法可用于定义类型别名
传统的方法是使用关键字
typedef
typedef double wages; // wages是 double 的同义词 typedef wages base, *p; // base 是double 的同义词,p 是double * 的同义词
新方法,使用别名声明
using SI = Sales_item; // SI 是Sales_item 的同义词
注意类型别名在声明中的应用,不要把类型别名替换成原本的样子来理解声明语句含义,大错特错
typedef char *pstring; const pstring cstr = 0; // cstr 是指向 char 的常量指针 const pstring *ps; // ps 是一个指针,它的对象是指向 char 的常量指针
auto 类型说明符
C++11新标准引入了auto
类型说明符,能让编译器分析表达式所述的类型。
使用
auto
语句也能在一条语句声明多个变量,但因为一条声明语句只能有一个基本数据类型,故所有变量的初始基本数据类型必须一样。auto sz = 0, pi = 3.14; // 错误, sz 和 pi 的类型不一致
使用引用就是使用引用的对象,即编译器使用引用对象的类型作为
auto
的类型。auto
会忽略顶层const
, 保留底层const
int i = 0, &r = i; auto a = r; // a 是一个整数(r 是 i 的别名,而 i 是一个整数) const int ci = 9, &cr = ci; auto b = ci; // b 是一个整数(ci的顶层const特性被忽略) auto c = cr; // c 是一个整数(cr 是 ci 的别名,ci 本身是一个顶层 const) auto d = &i; // d 是一个整型指针(整数的地址就是指向整数的指针) auto e = &ci; // e 是一个指向整数常量的指针(对常量对象取地址是一种底层 const)
如果希望推断出的
auto
类型是一个顶层const
,需要明确加上const
在auto
前面。
decltype 类型指示符
C++11 引入了decltype
类型说明符,作用是从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量,即选择并返回操作数的数据类型。
decltype(f()) sum = x; // sum 的类型就是函数 f 的返回类型
decltype
使用的表达式是变量时,则decltype
返回该变量的类型(包括顶层const和引用在内), 引用只有在decltype
处使用本身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
使用的表达式不是变量,则decltype
返回表达式结果对应的类型。int i = 42, *p = &i, &r = i; decltype(r + 0) b; // 正确:加法的结果是int,因此b是一个未初始化的int decltype(*p) c; // 错误:c是int &, 必须初始化,*p 为解引用,表达式得到的类型为引用类型,非int
- r 是一个引用,因此
decltype(r)
的结果是引用类型,若想让结果是r
所指的类型,可以把r
作为表达式的一部分,如r + 0
,则这个表达式的结果显然是一个具体值 - 如果表达式的内容是解引用操作,则
decltype
将得到引用类型。
- r 是一个引用,因此
若
decltype
所用的表达式是加了括号的变量,则得到的结果是引用类型decltype((i)) d; // 错误:d 是 int&, 必须初始化 decltype(i) e; // 正确:e 是一个(未初始化的)int
decltype((variable))
(注意是双层括号)的结果永远是引用,而decltype(variable)
的结果只有当variable
本身就是一个引用时才是引用。