变量和基本类型

文章目录
  1. 1. 基本内置类型
    1. 1.1. 类型转换
    2. 1.2. 含有无符号类型的表达式
    3. 1.3. 变量声明和定义的关系
    4. 1.4. 标识符
    5. 1.5. 名字的作用域
  2. 2. 复合类型(引用、指针)
    1. 2.1. 引用
    2. 2.2. 指针
    3. 2.3. 理解复合类型
      1. 2.3.1. 指向指针的指针
      2. 2.3.2. 指向指针的引用
  3. 3. const限定符
    1. 3.1. const 引用
      1. 3.1.1. 初始化和对const的引用
      2. 3.1.2. 对const的引用可能引用一个并非const的对象
    2. 3.2. 指针和const
      1. 3.2.1. const指针
    3. 3.3. 顶层const
    4. 3.4. constexpr 和常量表达式
      1. 3.4.1. 字面值类型
      2. 3.4.2. 指针和constexpr
  4. 4. 处理类型
    1. 4.1. 类型别名(type alias)
      1. 4.1.1. 指针、常量和类型别名
    2. 4.2. auto类型说明符
    3. 4.3. decltype 类型指示符
  5. 5. 自定义数据结构

C++ 是静态数据类型语言,类型检查发生在编译时。数据类型是程序的基础,他告诉我们数据的意义以及我们能在数据上执行的操作。

主要内容 :

  1. 基本内置类型
  2. 变量
  3. 复合类型
  4. const限定符
  5. 处理类型
  6. 自定义数据类型

基本内置类型

C++ 定义了一套包括算术类型和空类型的基本数据类型。

算术类型:

类型 含义 最小尺寸
bool 布尔类型 未定义
char 字符 8位
wchar_t 宽字符 16位
char16_t Unicode字符 16
char32_t Unicode字符 32
short 短整形 16
int 整形 16
long 长整形 32
long long 长整形 64
float 单精度浮点数 6位有效数字
double 双精度浮点数 10位有效数字
long double 扩展精度浮点数 10 位有效数字
  1. 寻址的最小内存块为 字节(byte) 大多数的字节由8个bit构成。
  2. 存储的基本单元称为 字(word) 一般32或64bit。

通常,float一个字,double两个字,long double4个字,类型float和double 分别有7个和16个有效位。

与其他类型不同,字符型被分为三种:char、signed char 、 unsigned char。char 和signed char 并不是一种,尽管类型有三种,但是字符的表现形式却只有两种:带符号的和无符号的。类型char实际上会表现为上述两种的一种,具体由编译器决定。

如何选择类型:

  1. 在算术表达式中不要使用char 、bool 只有在存放字符或者bool值时候才使用它们,因为类型char在一些机器上有符号,而在另外一些机器上又是无符号的,所以,如果使用char 进行运算特别容易出问题。如果使用一个不大的整数,那么明确指定它的类型是signed char 或者 unsigned char。
  2. 执行浮点运算选用double,这是因为float通常进度不够。对于某些机器,双精度运算甚至比单精度还快。

类型转换

当给某种类型的对象强制赋予了另一种类型的值时,到底发生了什么?

1
2
bool b = 42;
int i = b ; ///i的值时1
  1. 当把一个非布尔类型的算术值赋给布尔类型时,初始值0是false,否则为true。
  2. 当把一个布尔值赋值给非布尔类型时,初始值false 是0,true是1.
  3. 浮点数赋值给整数类型时,进行近似处理,保留小数部分之前的。
  4. 将超出范围的值赋值给无符号类型:结果是初始值对无符号类型表示的总数取模后的余数。
  5. 将超出范围的值赋给带符号类型,结果未定义。

含有无符号类型的表达式


1. 无符号数递减不能是负值,如果为负值,会是取模的余数。
2. 切勿混用带符号和无符号类型。如果表达式里面既有带符号的,又有无符号的,带符号的数会转化为无符号的数。

### 字面值常量

|字面常量|进制|
|—|—|
|20|十进制|
|024|八进制|
|0x14|十六进制|

1. 字面值常量的类型是能容纳该数值的尺寸最小的那个(int long, long long),默认是带符号的。类型short没有对应的字面值。浮点型字面值类型是double。
2. 字符串字面值的类型实际上是由常量字符构成的数组,末尾添加‘\0’。
3. 如果两个字符串字面值位置紧邻且仅由空格,缩进、换行符分隔,则他们实际上是一个整体

1
2
std::cout << "a really" 
"that spans";


有两种类型字符不能直接使用,需要转义:

1. 不可打印
2. 特殊含义字符:单引号、双引号、问号、反斜杠

### 指定字面值的类型

指定字符和字符串字面值:指定的方法如下表:

|前缀|含义|类型|
|—|—|—|
|u|Unicode 16字符|char16_t|
|U|Unicode 32 字符|char32_t|
|L|宽字符|wchar_t|
|u8|UTF-8(仅用于字符串字面常量)|char|


指定整数字面值

|后缀|类型|
|—|—|
|u U|unsigned|
|l L|long|
|ll LL|long long|

指定浮点字面值

|后缀|类型|
|—|—|
|f F|float|
|l L|long double|


## 变量

变量提供具名的、可供程序操作的存储空间。C++每个变量都有数据类型,数据类型决定着变量所占内存空间的大小、布局方式、该空间能存储的值的范围、以及变量能参与的运算。

### 变量定义

基本形式:类型说明符(type specifier) + 变量名

string表示可变长字符序列 的数据类型

对象是指能存储数据并具有某种类型的存储空间。

当一次定义了两个或多个变量时,对象的名字随着定义也马上可以使用了。因此在同一条语句中,可以用先定义的变量去初始化后定义的其他变量。

初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值。而赋值的含义是把对象的当前值擦除,而以一个新值来代替。

初始化的四种形式:

1
2
3
4
int a = 0;
int a = {0};
int a{0};
int a(0);

花括号的初始化形式称为初始化列表

初始化列表用于内置类型的变量时:如果初始值存在丢失信息的风险,编译器报错。

如果定义变量时没有指定值,则变量被默认初始化(default initialized),此时变量被赋予了默认值。

函数体之外的内置变量被初始化为0;函数体内的内置类型的对象如果没有初始化,则其值未定义。类的对象如果没有显示初始化,则其值由类确定。

变量声明和定义的关系

声明:规定了变量的类型和名字,这一点和定义相同。
定义:除了规定变量的类型和名字,还申请存储空间、为变量赋一个初始值。

声明代码:

1
2
extern int i;  //声明i,而非定义i,
extern double pi = 3.14; 任何包含了显示初始化的声明既变为定义。

变量能且只能被定义一次,但是可以被多次声明。

这相当于为分离式编程提供了支持

C++是一种静态类型语言,其含义是在编译阶段检查类型。其中检查类型的过程称为类型检查。 编译器知道每一个实体对象的类型是通过类型声明。

标识符

用户定义的标识符不能连续出现两个下划线、不能以下划线紧连大写字母开头、定义在函数体外的标识符不能以下划线开头。

变量命名规范:

  1. 标识符要能体现实际含义
  2. 变量一般用小写字母
  3. 用户定义的类名一般以大写字母开头
  4. 标识符由多个单词组成,则单词间应该有明显区分

名字的作用域

名字的有效区域始于名字的声明语句,以声明语句所在的作用域未端为结束。

第一次使用变量时再定义变量,这样好处:

  1. 更容易找到变量的定义
  2. 容易赋值合理的初始值
  1. 全局作用域
  2. 块作用域
  3. 嵌套作用域
  1. 内层作用域可以访问外层作用域中的名字
  2. 内层作用域中可以重新定义外层作用域中的名字
  3. 使用作用域运算符可以直接访问外层作用域中的名字

复合类型(引用、指针)

复合类型是基于其他类型定义的类型(compound type)

基本数据类型 + 声明符

声明符命令了一个变量,并指定该变量的相关类型。

引用

引用(reference) 为对象起了另一个名字。

1
2
int ival = 0;
int &refVal = ival;

一般在初始化变量时,初始值会被拷贝到新建的对象中,然而,定义引用时,程序把引用和它的初始值绑定在一起。而不是将初始值拷贝给引用。一旦初始化完成,引用将和他的初始值一直绑定在一起。因为无法令引用重新绑定到另一个对象,因此,引用必须初始化。

定义一个引用后,对其进行的所有操作都是在与之绑定的对象进行的。

  1. 获取引用的值:实际上是获取了与引用绑定的对象的值。
  2. 以引用为初始值: 实际上是以与引用绑定的对象作为初始值。

因为引用本身不是一个对象,所以不能定义引用的引用。

用于在一条语句中定义多个引用,其中每个引用标识符都必须以符号&开头。

引用的类型要和与之绑定的对象严格匹配。引用只能绑定在一个对象上,而不能与字面值或某个表达式的计算结果绑定在一起。

指针

与引用类型相同,指针也实现了对其他对象的间接访问。

指针和引用的区别:

  1. 指针本身也是对象,允许对指针赋值和拷贝,而且在指针的生命周期内,它可以先后指向几个不同的对象。
  2. 指针无需在定义的时候赋值。

指针存放某个对象的地址。要想获取该地址,使用取地址符

1
2
int ival = 42;
int *p = &ival;//p存放变量ival的地址。 或者说p是指向变量ival的指针。

指针的类型都要和它所指向的对象严格匹配。

指针值:指针的值(即地址)应属于下列4中状态之一:

  1. 指向一个对象。
  2. 指向紧邻对象所占空间的下一个位置
  3. 空指针,意味着指针没有指向任何对象。
  4. 无效指针,也就是上述情况之外的其他值。

解引用来访问该对象,解引用操作仅适用于那些确实指向了某个对象的有效指针。

空指针使用nullptr,新标准使用NULL,NULL是预处理变量。

如果使用了未经初始化的指针,则该指针所占内存空间的当前内容被看做是一个地址值,访问该指针,相当于去访问一个本不存在的位置。

指针和引用都能自动对其他对象间接访问,然而,在具体实现细节上,二者有很大不同,其中最重要的一点就是引用本身并非一个对象。一旦定义了引用,就无法令其再绑定到另外的对象。之后每次使用这个引用都是访问它最初绑定的那个对象。

给指针赋值,就是令其存放一个新地址。

如果两个指针存放的地址值相同,则他们相等。

void 是一种特殊的指针类型,可用于存放任何对象的地址。一个`void指针存放着一个地址。这一点和其他指针类似。不同的是,我们对该地址中到底是什么类型的对象不了解。 不能直接操作void*` 指针所指的对象。因为我们不知道这个对象到底是什么类型。

理解复合类型

1
2
//p是int型指针、r是一个int型引用
int i = 1024, *p = &i, &r = i;

指向指针的指针

1
2
3
int ival = 1024;
int *pi = &ival;
int **ppi = &pi; ///ppi指向一个int型的指针

ppi -> pi -> ival (1024)

指向指针的引用

1
2
3
4
5
int i = 42;
int *p;
int *&r = p ;//r是一个对指针p的引用
r = &i; //r 引用了一个指针,因此给r赋值,就是令p指向i
*r = 0; //解引用r得到i,也就是p指向的对象,将i的值改为0
离变量名最近的符号对变量类型有最直接的影响。所以r是引用。

const限定符

默认情况下,const对象仅仅在文件内有效。如果想在多个文件中共享const对象,就对const变量不管是声明还是定义都添加extern关键字。这样只需定义一次就可以了。

const 引用

可以把引用绑定到const 对象上,我们称为对常量的引用(refernce to const) 。一定要记住,

初始化和对const的引用

引用的类型必须和其所引用对象的类型一致,但是有两个例外,第一种例外情况是初始化常量引用时允许用任何表达式作为初始值,只要改表达式的结果能转化成引用的类型即可。尤其,允许为一个常量引用绑定非常量对象、字面值,甚至是一般表达式。

1
2
3
4
5
int i = 42;
const int &r1 = i; //允许将const int&绑定到一个普通int对象上
const int &r2 = 42; //正确
const int &r3 = r1*2 //正确
int &r4 = r1* 2; //错误

对const的引用可能引用一个并非const的对象

必须认识到:常量引用仅对引用可参与的操作做出了限定,对引用的对象本身是不是一个常量未作限定。

指针和const

指向常量的指针(pointer to const) 不能用于改变其所指对象的值,要想存放常量对象的地址,只能使用指向常量的指针。

1
2
3
4
const double pi = 3.14; //pi是个常量,它的值不能改变
double *ptr = &pi; // 错误:ptr是一个普通指针
const double *cptr = &pi; //正确:cptr可以指向一个双精度常量
*cptr = 42; //错误:不能给*cptr赋值

指针的类型必须与其所指对象的类型一致,但是有两个例外,其中一个是:允许一个指向常量的指针指向一个非常量对象。

试试这样想吧:所谓指向常量的指针和引用,不过是指针或引用“自以为是”罢了,他们觉得自己指向了常量,所以自觉的不去改变所指对象的值。

const指针

指针是对象而引用不是,所以就像其他对象类型一样,允许指针本身定为常量。常量指针必须初始化,而且一旦初始化完成,它的值就不能改变了。把*放在const关键字之前用以说明指针是一个常量。即不能改变指针本身的值,而非指向的那个值。

1
2
3
4
int errNumb = 0;
int *const curErr = &errNumb; //curErr将一直指向errNumb
const double pi = 3.14;
const double * const pip //pip 是一个指向常量对象的常量指针

要想弄清楚这些声明的含义,最行之有效的方法是从右向左阅读

顶层const

指针本身是一个对象,它又可以指向另外一个对象,因此,指针本身是不是常量以及指针所指的是不是一个常量,是两个相互独立的问题,用名词顶层const表示指针本身是一个常量,而用名词底层const 表示指针所指的对象是一个常量。

更一般的,顶层const可以表示任意的对象是常量,这一点对任何类型都适用,如算术类型、类、指针等。底层const则与指针和引用等复合类型的基本类型部分有关。比较特殊的是,指针类型既可以是顶层const,也可以是底层const。

当执行对象的拷贝操作时,常量的顶层const还是底层const区别明显,其中顶层const不受影响:

1
2
3
const int ci = 42;
const int *p2 = &ci //允许改变p2的值,底层const
const int *const p3 = p2; //正确,底层一样

另一方面,底层const的限制不能忽视

1
int *p = p3; //错误,p指向的值可以通过p改变,但p指向的值是常量,所以错误

constexpr 和常量表达式

常量表达式(const expression) 是指值不会改变并且在编译过程就能得到计算结果的表达式。用常量表达式初始化的const对象也是常量表达式

constexpr变量:如果认定变量是一个常量表达式,那就把它声明为constexpr类型

字面值类型

字面值类型: 比较简单,值也显而易见,容易得到,算术类型、引用类型、指针都属于字面值类型。 string 不属于,也就不能被定义成constexpr。

函数体内定义的变量,一般来说并非存放固定地址中,因此constexpr指针不能指向这样的变量。相反的,定义于所有函数体之外的对象,其地址固定不变,能用来初始哈constexpr指针。

指针和constexpr

在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指对象无关

1
2
const int * p = nullptr  //p是一个指向整型常量的 指针
constexpr int *q = nullptr; //q是一个指向整形的向量指针。

处理类型

为了简化复杂类型的定义

类型别名(type alias)

1
2
3
typedef double wages;  //wages是double的同义词
typedef wage base,*p ;// base 是double的同义词,p是double*的同义词
using SI = Sales_item; //SI 是sales_item的同义词

using 把等号左侧的名字规定成等号右侧类型的别名。

指针、常量和类型别名

1
2
typedef char* pstring;
const pstring cstr = 0;// cstr 是指向char的常量指针

pstring 的基本数据类型是指针 。 不能展开,如果展开就错了

1
const char * cstr = 0;

展开就成了底层常量了

auto类型说明符

auto让编译器通过初始值来推断变量的类型,显然,auto的定义必须有初始值。

  1. 引用: 编译器以引用对象的类型作为auto的类型,希望推断引用,需要 auto &
  2. 顶层const常量: 忽略,希望推断顶层const常量,需要用const auto
  3. 底层const常量: 保留

decltype 类型指示符

场景:希望从表达式的类型推断出要定义的变量类型,但是不想用该表达式的值初始化变量。decltype不实际计算表达式的值。

decltype处理顶层const和引用的方式与auto有些不同,如果decltyoe使用的表达式时一个变量,则decltype返回该变量的类型,包括顶层const和引用在内。

需要注意的是:引用从来都作为其所指对象的同义词出现,只有用在decltype处是个例外。

如果表达式的内容是解引用操作,则decltype将得到引用类型。正如我们所熟悉的那样,解引用指针可以得到指针所指的对象,而且还能给这个对象赋值,因此,decltype(*p)的结果类型就是int&,而非int。(表达式的值是左值,结果为引用)

decltype的表达式如果是加上括号的变量,结果是引用。

自定义数据结构

类体右侧表示结束的花括号后必须写一个分号,这是因为类体后面可以紧跟变量名,以表示该类型对象的定义,所以分号必不可少。

可以为数据成员提供一个类内初始值,创建对象时,类内初始值将用于初始化数据成员,没有初始值的成员将被默认初始化。

头文件通常包含哪些只能被定义一次的实体。例如类、const、constexpr变量等。

确保头文件多次包含仍能安全工作的技术是预处理器,预处理器是在编译之前执行的一段程序。#include就是预处理,当预处理器看到#include标记时,就会用指定的头文件内容替换#include

C++程序还会用到的一项预处理功能是头文件保护符(header guard),头文件保护符依赖于预处理变量,预处理变量有两种状态:已定义、未定义。#define指令把一个名字设定为预处理变量。另外两个指令则分别检查某个指定的预处理变量是否已经定义。#ifdef、#ifndef、#endif。

预处理变量无视C++语言中关于作用域的规则。