学海泛舟

第2章 变量和基本类型

数据类型是程序的基础,本章主要介绍了内置类型,并且初步提起了一些自定义数据结构的相关内容。

基本内置类型

基本数据类型主要包括算数类型和空类型。
除开布尔类型和扩展的字符型,其他整型都分为带符号类型和无符号类型。
一般而言,将数据范围小的转化为数据类型大的不存在什么大问题,但是反之,常常会带来精度的丢失。同时,切记不要混用有符号类型和无符号类型,这常常会带来意想不到的错误。

变量

变量中需要着重区分初始化和赋值。初始化死在创建变量时赋予一个初始值,而赋值是用一个新值覆盖原来的值。
变量声明和定义也是一组要区分的概念,由于这两个常常同时发生,故具有一定的迷惑性。记住变量只能被定义一次,但能被多次声明。想要将声明和定义分开,就需要用到extren关键字。举一个例子:

1
2
extern int a; //声明a,但是没有定义
int b; //声明并定义b

复合类型

复合数据类型是指基于其他类型定义的类型,这里主要讲了两种:引用和指针。
引用相当于为已用的一个对象起了一个别名,它本身并不是一个对象,为此,引用只能绑定在对象,而不能与字面值或表达式的计算结果绑定。同时,这也决定了引用必须被初始化。
指针和引用有很多不同点。一是指针本身就是一个对象,二是指针无需在定义时赋值。
复合类型的复杂之处在于,它们是可以进行嵌套的。无论是指向指针的指针,还是指向指针的引用,都是具有相当的复杂度的。遇到这种情况,从右向左阅读,对理解有一定的帮助。
例如:

1
2
int *p;
int *&r = p; //r是一个对指针p的引用

const限定符

const对象不能执行改变其内容的操作。
对常量的引用不能修改它所绑定的对象。同时非常量引用不能指向一个常量对象,但是常量引用能绑定到非常量对象。例如:

1
2
3
4
5
6
const int a = 1024;
const int &r1 = a; //正确
const int &r2 = 1024;//正确
const int &r3 = r1 * 2;//正确
int &r4 = a;//错误,非常量引用不能指向常量对象
int &r5 = r1 * 2;//错误

注意到,常量引用能绑定到非常量对象,虽然不能通过常量引用修改值,但非常量对象本身的值可以轻易改变。

指针和const的关系要相对复杂一些,因为指针本身是一个对象,又指向了一个对象。
顶层const表示指针本身是个常量,底层const表示指针所指的对象是个常量。

1
2
3
4
int i=0;
int *const p1=&i;//顶层const
const int ci=42;//顶层const
const int *p2=&ci;//底层const

执行对象的拷贝时,顶层const还是底层const有明显的区别。
顶层const不受影响,拷入和拷出的对象是否是常量没有影响。
但底层const则有限制,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。(一般是非常量转换为常量)

constexpr和常量表达式

常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。
使用constexpr将变量声明为常量表达式,就可以由编译器验证变量的值是否为一个常量表达式了。
constexpr把它所定义的对象置为顶层const。

类型处理

类型处理的难度来自于两个方面,这两者都是由于程序的膨胀造成的。一是类型的名字的难记,二是弄不清所需的类型。

类型别名可以使用传统的typedef,或者使用新的别名声明;

1
using LL = long long;

但是类型别名如果指代复合类型或者常量,就常常容易出错。

将变量声明为auto可以让编译器去分析表达式所属类型,显然,auto定义的变量必须有初始值。
在涉及到复合类型和常量时,auto有几点要特别注意。
1、编译器以引用类型作为auto的类型;
2、auto一般会忽略顶层const;

decltype的作用时选择并返回操作数的数据类型;

1
decltype(f()) a = x;

decltype和auto不同的是,decltype返回该类型的类型,包括顶层const和引用。decltype的结果和表达的形式也是密切相关的。

1
2
3
int i=1, *p=&i;
decltype(*p) a = 1;//a的类型为int&
decltype((i)) b = 1;//b的类型为int&

decltype((variable))的结果永远是引用。
decltype处理的是一个解引用,得到的永远是引用。