C++Primer——第二章(变量和基础类型)

2.1 基本内置类型

算术类型分为两类:整型浮点型
C++:算术类型
类型
含义
最小尺寸
bool
布尔类型
8bits
char
字符
8bits
wchar_t
宽字符
16bits
char16_t
Unicode字符
16bits
char32_t
Unicode字符
32bits
short
短整型
16bits
int
整型
16bits (在32位机器中是32bits)
long
长整型
32bits
long long
长整型
64bits (是在C++11中新定义的)
float
单精度浮点数
6位有效数字
double
双精度浮点数
10位有效数字
long double
扩展精度浮点数
10位有效数字
wchar_ t类型用于确保可以存放 机器最大扩展字符集 中的任意一个字符,类型char16_ t和char32_ t则为 Unicode字符集 服务。
如何选择类型:
  1. 当明确知晓数值不可能是负数时,选用无符号类型;
  2. 使用int执行整数运算。一般long的大小和int一样,而short常常显得太小。除非超过了int的范围,选择long long。
  3. 算术表达式中不要使用char或bool。(因为类型char在一些机器上是有符号的,而在另一些机器上又是无符号的)
  4. 浮点运算选用double。(因为float通常精度不够,而且双精度浮点数和单精度浮点数的计算代价相差无几)

类型所能表示的值的范围决定了转换的过程:
  • 当我们把一个非布尔类型的算术值赋给布尔类型时,初始值为0则结果为false,否则结果为true。
  • 当我们把一个布尔值赋给非布尔类型时,初始值为false 则结果为0,初始值为true则结果为1。
  • 当我们把一个浮点数赋给整数类型时,进行了近似处理。结果值将仅保留浮点数中小数点之前的部分。
  • 当我们把一个整数值赋给浮点类型时,小数部分记为0。如果该整数所占的空间超过了浮点类型的容量,精度可能有损失。
  • 我们赋给无符号类型一个超出它表示范围的值时,结果是 初始值 对无符号类型表示数值总数 取模后的余数。(例如,8比特大小的unsigned char 可以表示0至255区间内的值,如果我们赋了一个区间以外的值,则实际的结果是该值对256取模后所得的余数。因此,把-1 赋给8比特大小的unsigned char 所得的结果是255)
  • 当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的(undefined)。此时,程序可能继续工作、可能崩溃,也可能生成垃圾数据。
注:当一个算术表达式中 既有无符号数又有无符号数 时,有符号数就会转换成无符号数。

字面值常量:
  • 整型字面值(20、024、0x24)
  • 浮点数字面值(3.14、3.14f、3.14l)
  • 字符字面值和字符串字面值('A'、“AAAA”)
  • 转义序列(\n、\t、\b)
  • 布尔字面值和指针字面值(true、false;nullptr)

2.2 变量

  • 定义形式:类型说明符  + 一个或多个变量名组成的列表。(如int sum = 0, value, units_sold = 0)
  • 初始化:对象在创建时获得了一个特定的值。
  • 列表初始化:使用花括号{ }。(如int units_sold{0})
  • 默认初始化定义时没有指定初始值会被默认初始化;在函数体内部的内置类型变量将不会被初始化。(建议初始化每一个内置类型的变量)
注:初始化不是赋值!(初始化 = 创建变量 + 赋予初始值;赋值 = 擦除对象的当前值 + 用新值代替)

变量的 声明 vs 定义:
为了支持分离式编译,C++将声明和定义区分开。声明 使得名字为程序所知定义 负责创建与名字关联的实体
使用 extern 只是说明变量定义在其他地方。
如果只声明而不定义,则在变量名前添加 关键字extern (如extern int i);但如果包含了初始值,就变成了定义。(extern double pi = 3.14(),也就抵消了extern的作用)
变量只能被定义一次,但是可以多次声明。
C++是一种静态类型语言,其含义是在编译阶段检查类型。

  • 左值:可以出现在赋值语句的左边或者右边,比如变量
  • 右值:只能出现在赋值语句的右边,比如常量
  • 名字的作用域:同一个名字在不同的作用域中可能指向不同的实体。名字的有效区域 始于名字的声明语句,以声明语句所在的作用域末端为结束

2.3 复合类型

引用:引用是对象的别名(引用本身不是对象)
  • 必须初始化
  • 无法重新绑定另一个对象(引用初始化后就和初始值 绑定 在一起了,而不是简单赋值)
  • 只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起
指针:指针是指向任何其他类型对象的对象(指针本身就是对象,存放被指向对象的地址)
  • 不能定义指向引用的指针(引用不是对象,没有实际地址)
  • 不能访问一个没被初始化指针的值(没初始化的指针其值是不确定的)

void*指针可以存放任意对象的地址,其他指针类型必须要与所指对象严格匹配。
空指针不指向任何对象。(被 字面值nulltptr 初始化的指针就是空指针)
两个指针相减的类型是ptrdiff_t
建议:初始化所有指针。

int i = 42;
int &r = i;     // &紧随类型名出现,因此是声明的一部分,r是一个引用
int p;          // *紧随类型名出现,因此是声明的一部分,P是一个指针
P = &i;         // &出现在表达式中,是一个取地址符
*p = i;         // *出现在表达式中,是一个解引用符
int &r2 = *p;   // &是声明的一部分,*是一个解引用符 
注:在声明语句中,&和*用于组成复合类型;在表达式中,它们的角色又转变成运算符。

int i = 1;
int *p;       // p是一个int型指针
int *&r = p;  // r是一个对指针p的引用(离变量名最近的符号(&)对变量类型有最直接的影响,因此r是一个引用,声明符的剩余部分(*)用以确定r的类型是什么,所以最终可知r引用的是一个指针) 

r = &i;       // r引用了一个指针,因此给r赋值&i就是令p指向i 
*r = 0;       // 解引用r得到i,也就是p指向的对象,将i的值改为0 
注:面对一条比较复杂的指针或引用的声明语句时,从右向左阅读有助于弄清楚它的真实含义。

2.4 const限定符

动机:希望定义一些不能被改变值的变量。

(1) 初始化和const:

  • const对象必须初始化,且不能被改变
  • 默认情况下const变量不能被其他文件访问;若非要访问,则对于const变量不管是声明还是定义都要添加extern关键字

(2) const的引用:

  • 对常量的引用:指向const对象的引用。(如 const int ival=1; const int &refVal = ival;  可以读取但不能修改refVal)
  • 临时量对象:当编译器需要一个空间来暂存表达式的求值结果时,临时创建的一个未命名的对象。(对临时量的引用是非法行为)

引用的类型必须与其所引用对象的类型一致,但是有两个例外。
int i = 42;.
const int &r1 = i;        // 允许将 const int&绑定到一个普通 int对象上
const int &r2 = 42;       // 正确: r2是一个常量引用
const int &r3 = r1 * 2;   // 正确: r1、r3都是常量引用
int &r4=r1 * 2;           // 错误: r4是一个普通的非常量引用
const double& r5 = i;     // 正确: int可以转换为double类型
  1. 在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。
  2. 允许为一个常量引用绑定非常量的对象、字面值、甚至是个一般表达式。
注:常量引用并不是说引用本身是常量,因为引用都不是一个对象!所以常量引用指的是 引用的对象是个常量。

对const的引用可能引用一个并非const的对象
int i = 42;
int &r1 = i;         // 引用r1绑定对象i
const int &r2 = i;   // r2也绑定对象i,但是不允许通过r2修改i的值
r2 = 0;              // 错误: r2是一个常量引用
r1 = 0;              // 正确: r1并非常量,i的值修改为0(不允许通过r2修改i的值。尽管如此,i的值仍然允许通过其他途径修改)
注:必须认识到,常量引用仅对 引用可参与的操作 做出了限定,对于引用的对象本身是不是一个常量未作限定。因为对象也可能是个非常量,所以允许通过其他途径改变它的值。

(3) 指针和const:

  • 指针常量:指针指向一个常量对象,不能用于改变其所指对象的值。(如 const double i = 3.14; const double *p = &i;)
  • 常量指针:指针本身是常量,指针本身不可改变。(如 int i = 0;int *const p = &i;)

指针的类型必须与其所指对象的类型一致,但有个例外。
double i = 3.14;
const double* p = &i;     // 正确
允许一个指向常量的指针指向一个非常量对象。
注:对除指针以外的其他变量声明语法来说 const type name 与 type const name 效果是相同的,即都表示声明一个 类型为type名为name 的常量。

(4) 顶层const

顶层const:指针本身是个常量
底层const:指针指向的对象是个常量。

拷贝时严格要求相同的底层const资格
int i = 1;
const int* p = &i;

int* ptr1 = p;         // 错误: "const int *"类型的值不能用于初始化"int *"类型的实体
const int* ptr2 = p;   // 正确

const int n = 1;

int &r1 = n;           // 错误: 将"int &"类型的引用绑定到"const int"类型的初始值设定项时,限定符被丢弃。
const int& r2 = n;     // 正确  

(5) constexpr和常量表达式

常量表达式:值不会改变,且在编译过程中就能得到计算结果的表达式。
C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量的表达式。

一个对象(或表达式)是不是常量表达式由它的 数据类型和初始值 共同决定
const int max_files = 20;           // max_files是常量表达式,
const int limit = max_files + 1;    // limit是常量表达式
int staff_size = 27;                // staff_size不是常量表达式(尽管staff_ size 的初始值是个字面值常量,但它的数据类型只是一个普通 int而非const int)
const int sz = get_size() ;         // sz不是常量表达式(尽管sz本身是一个常量,但它的具体值直到运行时才能获取到)

声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化
constexpr int mf = 20;          // 20是常量表达式.
constexpr int limit = mf + 1;   // mf + 1是常量表达式
constexpr int sz = size();      // 只有当size是一个constexpr函数时,才是一条正确的声明语句

constexpr把它所定义的对象置为顶层const
int j = 0;
constexpr int i = 42;          // i的类型是整型常量
// 注: i和j都必须定义在函数体之外。

constexpr const int *p = &i;   // p是常量指针,指向整型常量i
constexpr int *p1 = &j;        // pl是常量指针,指向整数j


2.5 处理类型

(1) 类型别名

  • 传统别名:使用typedef来定义类型的别名。(如:typedef double wages;)
  • 新标准别名:使用using来定义类型的别名。(如: using SI = Sales_item;(C++11))

注:后面俩句并不等价!
typedef char *pstring;

const patring cstr=0;     // cstr是指向char的常量指针(const修饰pstring这个指针(char *),所以数据类型为 const char*,因此cstr是指向char常量指针)
const char *cstr=0;       // cstr是指向一个常量对象的指针(const修饰char,所以数据类型为const char ,因此cstr是指向一个常量对象的指针)

(2)auto类型说明符

auto让编译器通过 计算初始值 来推算变量的类型。
1. 用auto进行类型推导时(非引用)会忽略顶层const、保留底层const。
const int a = 1;
auto b = a;       // b为 int 类型(忽略顶层const属性)
auto c = &a;      // c是 const int* 类型(保留底层const属性)
2. 用auto推导引用类型时,实际推导的是引用所绑定的对象;而被绑定的对象即使包含顶层const,也会被忽略掉。
const int a = 1;
const int& y = a;
auto b = y;        // b为 int 类型(实际推导的是引用所绑定的对象,并忽略顶层const属性)
3. 用auto类型推导,定义引用时,需要显示地定义引用,并且会保留顶层const。
const int a = 1; 
auto& y = a;       // y为const int类型的引用(保留了a的顶层const,而y本身变成了底层const)
4. 用auto定义多个变量时,*和&只属于声明符,而不是数据类型的一部分;因此类型必须一致。
auto &q = a, *p = &a;    // 正确: q是对const int的引用,p是指向const int的指针
auto &c = b, *d = &a;    // 错误: c的数据类型为int,d的数据类型为const int

(3)decltype类型指示符

decltype 分析表达式的类型 推断并返回操作数的数据类型。
1. 当使用 decltype(var) 的形式时,decltype会直接返回变量的类型(包括顶层const和引用)。
const int i = 0, &j = i;     // i为const int,j为const int&

decltype(i) x = 0;     // x的类型是const int
decltype(j) y = x;     // y的类型是const int&
2. 当使用 decltype(expr) 的形式时,decltype会返回表达式结果对应的类型。
int i = 42, *p = &i, &r = i;    // i是int类型变量,p是int类型指针,r是int类型引用(它们类型都为int,只是声明符不同而已)

decltype(r + 0) b;       // b为int(算术表达式返回右值,即int)
decltype((i)) ri = i;    // ri为int&(加了括号变量i变成了表达式,因此返回的是i的左值形式,即int&)
decltype(*p) c = i;      // c是一个int类型引用
  • 引用运算符*作用于指针类型,得到了p指向的对象的左值。因此decltype(*p)得到的类型是int &
  • 当变量i 加一个括号后,是作为表达式 。返回的是该变量的一个左值形式。(因为该表达式的结果可以作为赋值语句的左侧的值,所以是左值,因此得到类型的左值引用)
3. 当使用 decltype(函数指针) 的形式时,decltype会返回函数对应的类型。
int fun(int a,int b);
decltype(fun) *pf = fun;      // pf为int(*)(int,int)类型
decltype加指针也会返回指针的类型。
decltype加数组,不负责把数组转换成对应的指针,所以其结果仍然是个数组。
总之decltype(var)完美保留了变量的类型。

2.6 自定义数据结构

  • 可以为类数据成员提供一个 类内初始值(C++11)。
  • 当预处理器看到#include标记时,会用指定的头文件内容代替#include
  • 头文件:通常包含那些只能被定义一次的实体:类、const和constexpr变量。
  • 预处理器:确保头文件多次包含仍能安全工作。
  • 头文件保护符:依赖于预处理变量的状态:已定义和未定
#ifndef SALES_DATA_H     
#define SALES_DATA_H
strct Sale_data{
    ...
}
#endif
#define指令把一个名字设定为预处理变量
#ifdef指令:当且仅当预处理变量已定义时为真
#ifndef指令:当且仅当变量未定义时为真
#endif指令:旦检查结果为真,则执行后续操作直至遇到#endif指令为止


C++primer 读书笔记 文章被收录于专栏

C++primer精总!

全部评论

相关推荐

1 1 评论
分享
牛客网
牛客企业服务