初始化与赋值语句
初始化/赋值语句是程序中最基本的操作,其功能是将某个值与一个对象关联起来,典型的例子如下
int x = 10; // 初始化
x = 20; // 赋值
其中值
的范围比较广泛,可以是字面值,对象(变量或常量)所表示的值等 。而对于对象我们使用标识符来描述,标识符分为:常量、变量和引用等。
值和对象都是有着对应的类型的,比如
10
也有着类型。
相比起赋值语句,初始化语句需要额外进行一些操作,包括:
- 在内存中开辟空间,保存对应的数值
- 在编译器中构造符号表,将标识符与相关内存空间关联起来
在使用初始化和赋值语句时我们要注意,其中可能涉及到类型转换。比如
int x = 10.5
就涉及到了从double
到int
的类型转换。
类型详述
基础
类型是一个编译期的概念,可执行文件中不存在类型的概念。类型存在于C++内部,而C++本身也是一个强类型语言。在C++中引入类型的目的是为了更好地描述程序,防止误用。
C++中的类型主要描述了以下信息:
- 存储所需要的尺寸,可以用
sizeof
来查看具体类型对应的尺寸,在标准中并没有严格限制类型的尺寸。不限制主要是出于性能的考虑,在不同位数的机器上单次处理的数据位数不同,所以对应的类型尺寸也是不同的
- 取值空间,可以使用
std::numeric_limits
来进行查看,超过取值空间可能会产生溢出std::cout << std::numeric_limits<int>::min(); std::cout << std::numeric_limits<int>::max();
- 对齐信息:数据在内存中存储的首地址一定是对齐信息的整数倍,这是为了从内存读取到
cache line
时候可以一次读取完整的类型对应的数据,在C++中可以使用alignof
来查看对齐信息std::cout << alignof(int);
对于结构体来说,会根据内部的类型各自的对齐信息来调整整个结构体的尺寸,比如一个4字节的
int
和一个1字节的char
最后的结构体大小为8而不是5。 - 可以支持的操作
C++ 中支持的类型可以划分为基本类型和复杂类型。基本类型也叫内建类型指的是C++语言中所支持的类型,主要包括:
- 数值类型
- 字符类型:
char
,wchar_t
,char16_t
,char32_t
char
一般用来表示ASCII
码,wchar_t
一般是两个字节,char16_t
和char32_t
一般用来表示Unicode
编码- 整数类型:
- 带符号整数类型:
short
,int
,long
,long long
各个类型的差异主要是尺寸不同,也就是取值区间不同
- 无符号整数类型:
unsigned
+ 带符号整数类型这里要注意单独的
unsigned
表示的是unsigned int
- 带符号整数类型:
- 浮点类型:
float
,double
,long double
- 字符类型:
void
而复杂类型指的是由基本类型组合、变种所产生的类型,可能是标准库引入或者自定义类型。
尽管C++当中对类型已经进行了十分详尽的定义,但还是存在一些与类型相关的标准未定义部分,主要包括以下方面:
-
char
是否有符号不同的编译器有不同的定义,如果确定要制定
char
的类型,可以使用unsigned char
和signed char
-
整数在内存中的保存方式是大端法还是小端法
-
每种类型的具体大小
C++中只规定了每种基本类型的最小尺寸,而没有规定具体的尺寸,这种不确定性间接影响了取值范围,虽然提升了性能,但是降低了跨平台的可移植性质
为了补充,在C++11中引入了固定尺寸的整数类型,比如
int32_t
等,具体的参考这里
字面值及其类型
字面值是在程序中直接表示为一个具体数值或者字符串的值。每一个字面值都有其对应的类型
- 整数字面值:20(十进制),024(八进制),0x14(16进制)-
int
型 - 浮点数:1.3,1e8 -
double
型 - 字符字面值:‘c’,’\n’,’\x4d’ -
char
型 - 字符串字面值:“Hello” -
char[6]
型注意末尾有’\0’,表示字符串的结束
- 布尔字面值:true,false -
bool
型 - 指针字面值:nullptr -
nullptr_t
型
以上所述的都是基本类型的字面值,我们可以为字面值引入前缀或者后缀以改变其类型,比如
- 1.3(double)- 1.3f(float)
- 2(int)- 2ULL(unsigned long long)
除此之外我们还可以使用自定义后缀来修改字面值,具体的可以参考这里
变量及其类型
C++中的变量本质上对应了一段存储空间,我们可以改变其中的内容。变量的类型在其首次声明(定义)时指定,比如
int x;
上面的代码定义了一个变量x,其类型为
int
这里需要再次强调变量的声明与定义的区别,定义只能够出现一次,如果我们在其他的文件中想要使用这个变量,我们需要使用extern
前缀来声明变量。
我们可以对变量进行初始化和赋值:
- 初始化:在构造变量之初为其赋予初始值。初始化的方式主要有以下三种:
- 缺省初始化
int x;
如果变量声明在函数内部,缺省初始化的初始值为任意值。而对于全局变量、静态变量和线程相关的变量,缺省初始化的初始值为0。这么设计的主要原因是:对于一个函数内部的变量,由于他可能被调用多次,而每一次调用都为其赋予默认值是一件得不偿失的事情,严重影响了性能,所以设计为只为变量分配内存,而不对内存内的数据进行赋值。而对于全局变量来说,由于只需要赋予默认值一次,成本极低,而且保证了程序多次运行的一致性,所以会赋予一个默认的初始值。
- 直接/拷贝初始化
int x = 10; // 拷贝初始化 int x(10); // 直接初始化 int x{10}; // 直接初始化
- 其他初始化:这里不讨论,有兴趣的可以自行研究。
- 缺省初始化
- 赋值:修改变量内的值
这里需要注意,使用
extern
的时候如果进行了非缺省的初始化,那么声明会自动升级为定义,导致多定义的错误
在对变量进行赋值时可能会涉及到隐式类型转换,比如以下两种:
- bool 与整数之间的转换
- 浮点数与整数之间的转换
注意在转换过程中可能会出现精度损失,比如从
double
->int
除了发生在赋值和初始化过程中,隐式类型转换还会发生在其它情况下,比如:
if
判断- 数值比较:最典型的是无符号整数和带符号整数的比较
这种情况下根据标准会将带符号的整数转换成无符号的整数,所以在使用时一定要注意