第二章 变量和基本类型

第二章 变量和基本类型

2.1 基本内置类型

数据类型决定了:数据形式数据操作

C++主要有void和算术类型(字符、整型、布尔值、浮点数)

数据类型大小以微软实现为例:

C++ 类型系统 | Microsoft Learn

第二章 变量和基本类型_第1张图片

C++整型数据宽度和取值范围

下面是cppreference提到的数据类型大小,覆盖更全:

第二章 变量和基本类型_第2张图片

部分数据类型的取值范围:

第二章 变量和基本类型_第3张图片


常见算术转型的坑

赋值与算术类型不一致时,有以下自动转换规则:

  1. bool=其它:非零均为true
  2. 整型=浮点:小数点后被舍去
  3. 浮点=整型:小数部分为0,整数太大时会有精度损失
  4. unsigned=其它:存储值为右侧值对左侧表示范围取模
    第二章 变量和基本类型_第4张图片
  5. signed=溢出值:未定义行为(Undefined Behavior)

未定义行为(Undefined Behavior)又简称UB,在C++里指的是编译器不检测或无法预知或与运行环境相关的行为,这种行为是C++为优化性能且信任程序员产生的历史遗留问题,检测其本身比运行成本更高。不应该也不能通过UB实现程序目的


不要混用有符号和无符号数,有符号数会自动转换成无符号数(避免溢出)

  • 练习2.3&2.4

补充一下负数的取模运算:(-1)%256 = (-1+256)%256=255%256=255 

第二章 变量和基本类型_第5张图片


字符串字面值其实是常亮字符构成的数组,结尾会加上'\0'

字面值类型可通过前后缀指定

第二章 变量和基本类型_第6张图片

  • 练习2.5
    第二章 变量和基本类型_第7张图片
  • 练习2.6

    第二行会报错,09不是合法八进制数
     
  • 练习2.8

    第二章 变量和基本类型_第8张图片

2.2 变量

初始化与赋值的区分!

C++赋值和初始化的区别 - 掘金

这里总结比较详细,概括讲就是初始化是变量创建的时候给初始值,赋值是擦去原有值再重写的过程,如果涉及对象的构造函数和赋值函数,体验会更明显

(插一嘴,其实如果硬要区分的话,C++不应该采用=作为初始化的形式之一,没办法,历史问题)

第二章 变量和基本类型_第9张图片

C++初始化方式

C++一般使用下面几种初始化方式

第二章 变量和基本类型_第10张图片

如书上举了四种例子:

第二章 变量和基本类型_第11张图片

  1. 列表初始化时不允许向下兼容导致的数据丢失,否则会报错
  2. 没有初始化则进行默认初始化

    函数内部的内置类型不会进行初始化——使用则UB

默认初始化 - cppreference.com具体可以看这里的例程

  • 练习2.9

    (1)声明不能放在表达式里
    (2)3.14变整型存在数据丢失,列表初始化会报错
    (3)wage未声明就使用
    (4)i为3
  • 练习2.10
    第二章 变量和基本类型_第12张图片
    第二章 变量和基本类型_第13张图片
    可以看到两个字符串都是空字符串,string算是类,由默认构造函数处理

    而全局int被默认初始化为0,局部int不被初始化,存不确定的垃圾值

C++定义与声明

定义与 ODR (单一定义规则) - cppreference.com

这里讲的比较详细,除了列出的一些情况之外都属于定义

简单概括就是声明只是让编译器知道了对象的类型和名字,定义才进行分配内存的工作(可能附带初始化),未定义的对象无法使用,因为没有分配内存(反过来说,能使用的都已经定义过了)

extern关键字可以用来分离声明和定义

  • 练习2.11

    第二章 变量和基本类型_第14张图片
  • 练习2.12

    只有e合法

作用域

C++作用域主要有:块作用域、函数形参作用域、命名空间作用域、类作用域、枚举作用域、模板形参作用域

作用域允许嵌套,内部名字可以覆盖外部同名对象,但不建议这么做

  • 练习2.13

    第二章 变量和基本类型_第15张图片
  • 练习2.14

    程序自然合法
    第二章 变量和基本类型_第16张图片
    第5行的i和第1行相同,for循环内的i是覆盖了第1行的i,sum则是0加到9的总和45

2.3 复合类型

第二章 变量和基本类型_第17张图片

类型 - cppreference.com介绍了C++类型系统,这里主要是复合类型中的指针和引用,后续会介绍类、枚举、数组等

引用——左值引用

引用绑定而非复制对象,起到别名的作用,不必占用内存,本身也不是对象
(尽管编译器实现时会或多或少根据情况分配内存以实现语义,但站在语言使用的角度上不用纠结这一点)

  • 练习2.15

    (b)不合法,左值引用不可以绑定字面值
    (d)不合法,引用必须初始化
  • 练习2.16

    (a)对d赋值3.14159
    (b)对d赋值0
    (c)对i赋值0
    (d)对i赋值0

 指针

与引用的异同点(文心一言生成):

第二章 变量和基本类型_第18张图片

课本回答:

第二章 变量和基本类型_第19张图片

 空指针可以用nullptr、NULL、0初始化(NULL为预处理变量,在编译前处理,但不推荐使用)

!非0指针作条件时均为true;指针比较时比较的是存储的地址而非所指对象

补充:指针用上面三个初始化时,if(p)均为false,其他情况为false

*p的值为零、false或者NULL时,条件为假。


要理解基本数据类型和类型修饰符的关系

  • 练习2.20

    指针p1指向i,随后又赋值为42的平方
  • 练习2.21

    (a)非法,int变量地址不可以初始化double指针
    (b)非法,变量不可以初始化指针,即使变量值为0也不行
    (c)合法,指针p指向i
  • 练习2.23

    if(p) 如果p合法则条件为真

2.4 const限定符

const对象必须初始化;只能在const对象上执行不改变其内容的操作;

避免重复定义,const对象只在文件内有效;如果想共享,需要在声明和定义前都加extern

常量引用:const reference/reference to const

需要这么理解:实际上不存在常量引用,引用不允许改变绑定的对象,引用作为间接访问内存的形式,称之为常量引用是从引用的角度来看绑定的对象,将对象视为常量,即不能通过引用修改绑定的对象而不是不能修改引用绑定哪个对象(因为永远也不能修改)

!带有复杂修饰符的类型从右往左阅读比较清晰

初始化常量引用时有特殊情况:

  • 可以用任意表达式作初值,如:非常量对象、字面值、一般表达式等
     
    int i=23;
    const int &r1=i;        //允许将const int& 绑定到一个普通的int对象
    const int &r2=23;       //允许将const int&绑定到字面值常量
    const int &r3=r1*2;     //允许将const int&绑定到表达式
    int &r4=r1*2;           //错误,r4是一个非常量引用

    也就是说常量引用只限制了通过常量引用不可以修改绑定的对象,至于对象是不是常量则没有规定

常量指针和指针常量:pointer to const & const pointer

常量指针可以类比常量引用:从指针的角度不可修改所指对象

指针常量指指针本身不可改变,即永远保存同一个地址(因为指针本身是个对象所以可以这样),实际上引用底层就是指针常量

  • 练习2.28

    第二章 变量和基本类型_第20张图片
    总结就是不能通过指针和引用改变对象值的时候,需要初始化(否则就矛盾了)

顶层const&底层const

  • 被修饰的变量本身无法改变const顶层 const
  • 通过指针或引用等间接途径来限制目标内容不可变const底层 const(一般说的是指针和引用,只有这两种类型用于间接访问内存)

只有指针可以同时有两层属性,其他类型要么顶层要么底层,见下图例子

第二章 变量和基本类型_第21张图片

底层const需要注意拷贝时应有相同的底层const或数据类型能转换(一般就是nonconst可以复制给底层const,反过来不行),见下例:

第二章 变量和基本类型_第22张图片

  • 练习2.30

    第二章 变量和基本类型_第23张图片
  • 练习2.31

    p1=p2非法,p1普通指针,p2为底层const

    p1=p3非法,同上,p3也是底层const

常量表达式

两个条件:值不变;编译期确定值

比如字面值(算术类型、引用、指针)、用常量表达式初始化的const对象等

constexpr用于指针时,把指针设置为顶层const

2.5 处理类型

类型别名:type alias

一种是使用typedef关键字:

第二章 变量和基本类型_第24张图片

一种是使用using别名声明:

第二章 变量和基本类型_第25张图片

auto关键字

auto会忽略顶层const,保留底层const;

auto引用保留顶层const

第二章 变量和基本类型_第26张图片

  • 练习2.35

    第二章 变量和基本类型_第27张图片

decltype类型说明符

适用场景:用表达式的类型确定变量的类型,但是不使用表达式的值

和auto不同的是:decltype保留顶层const和引用

C++ decltype类型推导完全攻略

  • 如果 exp 是一个不被括号( )包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,那么 decltype(exp) 的类型就和 exp 一致,这是最普遍最常见的情况。
  • 如果 exp 是函数调用,那么 decltype(exp) 的类型就和函数返回值的类型一致。
  • 如果 exp 是一个左值,或者被括号( )包围,那么 decltype(exp) 的类型就是 exp 的引用;假设 exp 的类型为 T,那么 decltype(exp) 的类型就是 T&。

decltype specifier - cppreference.com

上面提到了decltype的推导规则,尤其注意左值推导和括号那一条,总之推导结果和表达式形式相关

  • 练习2.36

    第二章 变量和基本类型_第28张图片

auto与decltype的区别

也是练习2.38的答案

第二章 变量和基本类型_第29张图片

总之三点:表达式计算与否;顶层const保留与否;decltype看括号和左右值

2.6 自定义数据结构

  • 练习2.39

    第二章 变量和基本类型_第30张图片

预处理器

比如#include、#define等#开头的指令在预处理阶段进行执行

第二章 变量和基本类型_第31张图片

这里的#ifndef一系列可以保证类定义的唯一性

第二章结束

你可能感兴趣的:(《C++Primer,5th,阅读笔记》,c++,开发语言)