首页 > 编程语言 >C++ Primer 5th 阅读笔记:变量和基本类型

C++ Primer 5th 阅读笔记:变量和基本类型

时间:2023-05-03 22:13:32浏览次数:49  
标签:const 字面 int 5th C++ 类型 Primer 变量 指针

一些语言的公共特性

  • 内建类型,如整型,字符型等;
  • 变量,为值绑定的一个名字;
  • 表达式和语句,操作值。
  • 分支和循环,允许我们条件执行和重复执行;
  • 函数,定义抽象计算单元。

扩展语言的方式

  • 自定义类型;
  • 标准库。

本章重点

学习语言的基本知识和标准库。

  • 内建类型;
  • 简要介绍自定义类。

类型

  • 定义了数据的意义;
  • 定义了数据的操作;

从二进制的角度看,类型定义了数据占用几个 bit,以及如何解释这些 bit

基本内建类型

  • 算术类型
    • 整型
    • 字符型
    • 布尔型
    • 浮点型
  • 特殊类型:void

算术类型

image

  • 整型 (integral types)
    • bool: 两个值 truefalse
    • char:可以包含 basic character set 中的字符对应的数字。大小等于一个机器字节(single machine byte)。
    • wchar_tchar16_tchar32_t。用于 extended character sets
      wchar_t 可以包含最大 extended character sets 中字符对应的数字。(Windows 2 字节;Unix 4 字节)
      char16_tchar32_t 用于 Unicode 字符。

有关 char 的进一步讨论,见 Byte是8位吗?—C语境中的Byte及C语言的char类型

  • 浮点型 (floating-point types)
    标准没有定义具体的数据格式。一般使用的都是 IEEE 格式。

关于浮点数的详细介绍可见:IEEE 754浮点数标准详解

符号

  • 有符号:signed int/intsigned short/shortsigned long/longsigned long long/long longsigned char
    标准没有定义具体的数据格式。一般为补码。
  • 无符号:unsigned int/unsignedunsigned shortunsigned longunsigned long longunsigned char
  • 特殊:char 有无符号看编译器的实现。

算术类型的经验法则

  1. 不包含负数,使用无符号数。
  2. 使用 int 来进行整数运算。 short 太小,long 一般和 int 一样大。如果数字太大使用 long long
  3. 不要在算术运算中使用 charbool。可以在算术运算中使用 signed charunsigned char
  4. 浮点数运算使用 doublefloat 精度有限且带来执行效率上的提升不大。long double 的计算开销过大,一般不使用。

类型转换

赋值语句

  • 左值类型:bool
    右值类型:非 bool
    转换规则:0 -> false, ≠0 -> true
  • 左值类型:非 bool
    右值类型:bool
    转换规则:false -> 0, true -> 1
  • 左值类型:整型
    右值类型:浮点型
    转换规则:f -> int(f)
  • 左值类型:浮点型
    右值类型:整型
    转换规则:z -> z.00
  • 左值类型:无符号数
  • 右值类型:超出范围的数
    转换规则:n -> n % Nn 是超出范围的数, N 是无符号类型能表示的所有值的个数

表达式

如果在算数表达式中同时出现 有符号数无符号数,所有 有符号数 转换为 无符号数

一例无限循环的错误代码,

// WRONG: u can never be less than 0; the condition will always succeed
for (unsigned u = 10; u >= 0; --u)
	std::cout << u << std::endl

有关 有符号数无符号数 的一些资料:

未定义的行为和实现定义的行为

避免未定义的行为(undefined behavior)和实现定义的行为(implementation-defined behavior)。使用这些行为可能造成程序不可移植(nonportable)。

建议参考以下的文章:

字面量

整型

整数 20 的三种表示法

  • 十进制:20
  • 八进制:0240 开头
  • 十六进制:0x140x 开头

十进制字面量默认都是 有符号数
八进制和十六进制则可以为 有符号数 或者 无符号数
十进制字面量的类型为 intlonglong long 中可以容纳字面量值的最小类型。
八进制和十六进制的类型为 intunsigned intlongunsigned longlong longunsigned long long 中可以容纳字面量值的最小类型。

如果最大类型也不能容纳字面量,则将引起一个错误。

注意: -42 不是字面量,42 才是字面量,- 在其中作为相反数的操作符。

浮点型

浮点数的字面量包含了小数点或者指数(使用科学计数法)。指数使用 e 或者 E 来表示。

3.14159
3.14159E0
0.
0e0.001

科学记数法的形式是由两个数的乘积组成,aEb = a×10^b

浮点型字面量的默认类型是 double

字符和字符串字面量

  • 字符字面量,包含在单引号中的一个字符 'a'
  • 字符串字面量,包含在双引号中的 0 个或多个字符 "abcd"

字符串字面量是一个字符数组,每个字符串字面量的最后都会有一个 零字符 '\0'(null character)。

多个相邻的字符串字面量会被自动拼接成一个字符串字面量,比如,

// multiline string literal
std::cout << "a really, really long string literal "
             "that spans two lines" << std::endl;

转义序列

转义序列用于表示 非打印字符 或者有特殊含义的字符(如,单引号、双引号)。

转义序列使用 \ 开头。

image

一般化的转义序列:

  • \ 后面跟 1 到 3 个八进制数字(0-7);
  • \x 后面跟 1 或者多个十六进制数字(0-f)。

注意:

  • 如果 \ 后面跟了四个及以上的八进制数,只有前三个字符会被解释成一般化的转义序列;
  • 如果 \x 后面跟了多个十六进制数,十六进制数会用尽所有的数来表示一个字符。

image

image

参考资料:

字面量类型

我们可以通过添加前缀(针对字符字面量或者字符串字面量)或者后缀(针对整型字面量或者浮点数字面量)的方式来修改默认的字面量类型。

image

image

最佳实践:在使用 L 作为后缀时,建议使用大写 L, 而不要使用小写 l,目的是为了区分字母 l 和数字 1
注意:后缀只是指定了字面量能接受的最小类型,如果字面量的值无法被容纳在这个最小类型内,字面量将会尝试下一个更大的类型。

其他字面量

  • bool 字面量:truefalse
  • 指针字面量: nullptr

参考资料:

变量和对象的区别

在本书中的变量和对象没有区别!

变量定义

  1. 变量类型指示符(type specifier)
  2. 一个或者多个变量名(使用逗号隔开)
    2.1. 可以为变量指定一个初始值(可选)
  3. 分号 ; 结束

示例:

int sum = 0, value, units_sold = 0;
Sales_item item;
std::string book("0-201-78345-X");

初始化

  • 可以使用之前定的变量来进行初始化;
  • 初始化和赋值在 C++ 中是两个不同的操作;

示例:

double price = 109.99, discount = price * 0.16;
double salePrice = applyDiscount(price, discount);

关于初始化与赋值的区别,可以查看 CSDN 初始化与赋值的区别
对于基本数据类型,二者是没有任何区别,对于非基本数据类型,在写法与效率上有许多不同。

列表初始化

四种初始化的方式:

int units_sold = 0;
int units_sold = {0};
int units_sold{0};
int units_sold(0);

C++11 中使用花括号来初始化内建类型,如果存在类型转换且具有数据丢失的风险,则编译器将会报错。

long double ld = 3.1415926536;
int a{ld}, b = {ld}; // error: narrowing conversion required
int c(ld), d = ld; // ok: but value will be truncated

关于花括号初始化的更多内容,可以参考: C++11 花括号初始化

关于 initializer 的解释,可以参考 Initializers

默认初始化

变量的初始化取决于:

  • 变量的类型
  • 变量定义的位置

内建类型:

  • 定义在函数的变量会被初始化为 0
  • 定义在函数的变量不会被初始化(uninitialized)

变量声明(Declaration)和变量定义(Definition)

声明

变量声明指定了变量的类型和名称

定义

变量定义也是变量声明
变量定义指定了变量的类型和名称

变量定义分配存储空间

变量定义可以指定一个初始值(可选)。

如何声明一个变量?

  1. 添加 extern 关键字;
  2. 不要提供初始值。
extern int i; // declares but does not define i
int j;        // declares and defines j

任何包含初始值的声明都是定义。

extern double pi = 3.1416; // definition

注意:变量可以被声明多次,但只能被定义一次

关于声明和定义的参考资料

标识符

  • 由字母、数字、下划线组成;
  • 无长度限制;
  • 大小写敏感;

语言保留字

image
image

标准库保留字

  • 不能出现两个连续的下划线,如 __i;
  • 不能下划线之后跟着大写字母,如 _I;
  • 函数外的标识符,不能以下划线开头,如:
int _i; // WARNING: DISALLOW!
int main()
{
...
}

关于保留字的参考资料:

命名习惯

  • 名称应符合它的用法;
  • 变量一般是小写字母,如 index
  • 类名称通常以大写字母开头,如 Sales_item
  • 名字多个单词之间需要加以区分,
    • 下划线命名:student_loan
    • 驼峰式命名:studentLoan

关于命名法的参考资料

作用域(Scopes)

每个名称都有自己关联的实体,比如,变量、函数、类型等等。
每个名称都对应的作用域。
作用域一般使用花括号来界定。
相同的名称在不同的作用域中可以关联不同的实体。
名称的可见性从声明的地方开始,直到声明所在作用域的结尾结束。

分类

  • 全局作用域(global scope):在整个程序可见
  • 块作用域(block scope)(局部作用域):仅局部可见

建议:尽量在靠近变量第一次使用的地方定义变量。

嵌套作用域(Nested Scopes)

一个作用域可以包含其他作用域。
外部作用域(Outer Scope) 包含了内部作用域(Inner Scope)。
外部作用域中的名字可以被其内部作用域使用
外部作用域中的名字可以被其内容作用域重新定义

作用域操作符(::)

如果 :: 左侧为空,其代表全局作用域。
我们可以通过作用域操作符来忽视默认的域规则。

复合类型

复合类型是根据另一种类型定义的类型。
复合类型包括:

  • 引用;
  • 指针;
  • 等等。

声明语句是一个基本数据类型(base type) 跟着一组声明符(declarators)。
声明符,一是给了变量的名称,二是给了变量的类型(基于基本数据类型)。

引用

引用定义了变量的一个别名。
引用的声明符格式为: &var
引用的必须初始化。
引用不可修改。
引用的类型和被引用对象的类型必须匹配(但存在例外)。
引用只能用变量初始化,不能用字面量或者符合表达式。

指针

指针用于间接访问其他元素。
指针本身就是对象。
指针可修改。
指针定义可以不初始化。
指针的声明符格式为: *var
指针存储了另一个对象的地址。
我们通过取址符&(address-of operator) 来获取对象的地址。
指针的类型和被指向对象的类型必须匹配(但存在例外)。

四种状态

  1. 指向一个对象;
  2. 指向对象的后面一个位置;
  3. 空指针,不指向任何对象;
  4. 无效指针。

使用解引用操作符 * 来访问指向的对象。

空指针
空指针不指向任何对象。
空指针的三种表示:

  • 字面量 0
  • 宏定义 NULL
  • 字面量 nullptr(推荐使用)

注意:不能将一个整型变量赋值给指针,即使整型变量的值为 0

为什么指针难以调试?

  • 指针的值代表一个地址,我们很难区分合法的地址和非法的地址
  • 建议:初始化所有指针,指向一个具体的对象,或者空指针。
  • 建议:只在被指向的对象定义后,再定义指针。

指针转换到布尔值:

  • 空指针转换为 false
  • 其他指针转换为 true

void* 指针

void* 指针可以指向任意一个对象。
void* 指针不保存指向对象的类型信息。
我们无法通过 void* 指针操作其指向的对象。

参考资料:

复合类型的声明

定义语句是一个基本数据类型(base type) 跟着一组声明符(declarators)。

一个定义中可以定义不同类型的变量。

// i is an int; p is a pointer to int; r is a reference to int
int i = 1024, *p = &i, &r = i;

类型修饰符(type modifier) 仅应用于单个标识符。

定义多个变量的两种风格:

  1. 类型修饰符和标识符相邻;
int *p1, *p2; // both p1 and p2 are pointers to int
  1. 基本数据类型和类型修饰符相邻。
int* p1; // p1 is a pointer to int
int* p2; // p2 is a pointer to int

const 限定符(qualifier)

const 修饰的变量无法被修改。
const默认作用域为本文件。因为 C++ 是单独编译每个文件的,我们要将 const 替换为具体的值,需要知道它的定义,所以每一个文件中都包含了 const 的定义,但是不会导致重复定义的情况出现。

如果我们想要在多个文件中共享 const 变量,但是其初始值不是常量表达式(无法在编译阶段获取表达式的值):

  1. 在一个文件中声明 const 变量,需要 extern 关键字
  2. 在一个文件中定义 const 变量,需要 extern 关键字。
// file_1.cc defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();

// file_1.h
extern const int bufSize; // same bufSizeas defined in file_1.cc

const 的引用无法修改引用的对象。

const 的引用的初始值可以为,

  • 常量对象;
  • 非常量对象;
  • 字面量;
  • 一般表达式(类型可以不匹配)。

只要初始值可以转换为引用的类型。

double dval = 3.14;
const int &ri = dval;

等价于

double dval = 3.14;
const int temp = dval; // create a temporary const int from the double
const int &ri = temp; // bind ri to that temporary

const 的指针无法修改指向的对象。
const 的指针可以指向非 const 的对象。
const 指针:指针本身无法被修改。
const 指针:必须被初始化。
const 指针的声明,将 const 放在标识符前面,而不是基本数据类型前面。

顶层 const:对象本身是 const,对象本身能否被修改。
底层 const:对象指向的对象是 const,指向对象能否被修改。

对象出现在 = 右侧时,顶层 const 可以被忽略,因为顶层 const 只是阻止对象被修改,但是不阻止对象被拷贝和访问;
对象出现在 = 右侧时,底层 const 必须考虑,因为底层 const 限定了指向对象的操作权限(是否能修改),在拷贝时,只能使得权限不变或者缩小(从可修改到不可修改),反之则不行——可以从 constconst 或者 non-constconst,但是 constnon-const 是不允许的。

参考资料:

constexpr 和常量表达式

常量表达式是以下几种:

  • 字面量
  • 使用常量表达式初始化的 const 对象
  • 表达式中的所有子表达式都是常量表达式

NOTE:

  • const 对象不一定都是常量表达式
  • non-const 对象一定不是常量表达式

const 语义接近 readonly——只读变量; constexpr 的语义接近常量(可以在编译阶段就确定)。

如果你只将变量用作常量表达式,推荐加上 constexpr 关键字。

字面量类型:所有可以使用 constexpr 的类型被称为字面量类型。
字面量类型包括:

  • 算数类型
  • 引用类型
  • 指针类型
  • 其他一些

字面量类型不包括:

  • Sales_item
  • IO
  • string 类型

NOTE:constexpr 用于指针类型的声明时,constexpr 会添加一个顶层 const

const int * p = nullptr;  // p is a pointer to a const int
constexpr int * q = nullptr; // q is a const pointer to int

如上的两个语句有不同的语义。

复杂类型

复杂类型的两个特点:

  • 复杂类型很长,写起来容易出错;
  • 复杂类型可能包含很多参数,难以理解它的意义和用途。

参考资料:
C++ const 和 constexpr 的区别? - Codebowl靓仔的回答 - 知乎
constexpr对编译时间影响大吗?

类型别名

定义类型别名的两种方式

  • typedef

    typedef double wages; // wages is a synonym for double
    typedef wages base, *p; // base is a synonym for double, p for double *
    

    typedef 中的类型声明符可以包含类型修饰符,可以用来定义复合类型的别名。

  • using

    using SI = Sales_item; // SI is a synonym for Sales_item
    using SP = Sales_item*; // SP is a synonym for Sales_item*
    

指针类型别名和 const 混用时,别名作为一个基本数据类型出现在声明中,const 是顶层 const,表示对象不可变。

auto 关键字,类型指定符

当我们想要通过一个变量来保存表达式的值时,如果遇到表达式值的类型过于复杂,可以使用 auto 关键字来自动推导出变量的类型。
我们可以用 auto 来定义多个变量,多个变量必须有相同的基本数据类型。

auto i = 0, * p = &i; // ok: i is int and p is a pointer to int
auto sz = 0, pi = 3.14; // error: inconsistent types for sz and pi

一些特殊规则:

  • 对于引用类型,auto 会使用引用对象的类型
  • 对于顶层 constauto 会忽略,对象本身是否可变;如果想要推导出的类型有高层 const,必须显式地指定。
    const auto f = ci; // deduced type of ci is int; f has type const int
    
  • 对于底层 constauto 会保留,指向对象是否可变

可以通过 auto 来定义一个引用,引用指向的对象的顶层 const 不会被忽略。

auto &g = ci; // g is a const int& that is bound to ci
auto &h = 42; // error: we can’t bind a plain reference to a literal
const auto &j = 42; // ok: we can bind a const reference to a literal

通过 auto 来定义多个变量时,如果变量包含修饰符,会改变 auto 的推导结果。

  • 指针修饰符:返回表达式指向的对象的类型。顶层 const 保留。
  • 引用修饰符:返回表达式的类型。顶层 const 保留。
auto k = ci, &l = i;
auto &m = ci, * p = &ci;  // m is a const int&; p is a pointer to const int
auto &n = i, * p2 = &ci; // error: type deduced from i is int; type deduced from &ci is const int

decltype 关键字,类型指定符

有的时候,我们想用推导出表达式的类型,但是却不想用这个表达式来初始化变量。
decltype 返回操作对象的类型,但是不会对其进行求值。

decltype(f()) sum = x; // sum has whatever type f returns

NOTE:

  • decltype 返回的类型会保留顶层 const,这与 auto 的有所不同。
  • decltype 针对引用类型会返回引用类型,而不是引用类型指向的对象。
  • decltype 解指针引用会返回引用类型。
  • decltype((variable)) 会返回引用类型。
  • decltype 针对赋值表达式也会返回引用类型。

参考资料
c++11 decltype(*p) 得到的结果是T&的设计思路?
C++11中的decltype和declval表示什么意思,它们是如何使用的,会在什么时候使用?
What is the difference between auto and decltype(auto) when returning from a function?
The relationship between auto and decltype

标签:const,字面,int,5th,C++,类型,Primer,变量,指针
From: https://www.cnblogs.com/revc/p/17095450.html

相关文章

  • [c++]从完全不会到似懂非懂
    1.指针1.1指针常量使用int*constp=&a;的模式,指针常量是常量,所以p对应的常量为a的地址,因此a可变,但地址不可变。1.2常量指针使用constint*p=&a的模式,其实这里和const无关,p只是个指针,该指针的类型是constint类型,因此p可以修改指针指向其他的constint类型,但无法修改......
  • C++/PTA 函数重载(数据类型不同)
    题目要求用同一个函数名对n(n<=10)个数据进行从小到大排序,数据类型可以是整数、浮点数,用函数重载实现。输入格式:输入n例如3输入n个整数,例如1089输入n个浮点数例如10.235.167.99输出格式:输出n个整数的升序排列:8910以空格间隔,并以空格结尾换行,输出n个浮点数的升......
  • C++-[override]关键字使用详解
    本文介绍了C++override关键字使用详解以及与重载的区别。C++override关键字使用详解一、override作用二、override在基类与派生类的应用2.1.纯虚函数2.2.普通虚函数2.3.Override重写三、Override实例四、C++中重载(overload)与覆盖(override)4.1.重载(overload)4.2.重写/覆......
  • C++ 11 :override 关键字的使用
    override关键字作用:在成员函数声明或定义中,override确保该函数为虚函数并覆写来自基类的虚函数。位置:函数调用运算符之后,函数体或纯虚函数标识“=0”之前。使用以后有以下好处:1.可以当注释用,方便阅读2.告诉阅读你代码的人,这是方法的复写3.编译器可以给你验证override......
  • c++ 自由储存区和堆
    在C++中,内存区分为5个区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区谈到自由存储区与堆的区别,就需要了解new与malloc的区别。实际上堆是C语言的关键术语,是操作系统所维护的一块特殊内存,它提供了动态分配的功能。用户使用malloc进行堆内存申请,使用free实现堆内存的释......
  • 【C++】设立一组状态,在程序运行过程中设置对象的某个状态,检查对象是否满足所有的状态
    `#include//定义状态枚举enumState{STATE_A=1<<0,//0001STATE_B=1<<1,//0010STATE_C=1<<2//0100};classMyClass{private:intcurrentState;public:MyClass():currentState(0){}//设置状态voidsetState(Statestate){......
  • vcpkg:一站式C++库管理,简化工作流程
    简介vcpkg是一个用于在Windows系统上管理C++库的开源工具。它允许开发人员通过简单的命令行界面安装、管理和卸载各种C++库,并自动解决它们的依赖关系。vcpkg拥有一个庞大的库集合,包括Boost、OpenCV、Qt、SDL2等等,而且不仅限于Windows平台,还支持在Linux和macOS上进行交叉编译。......
  • C++文件读写常用操作整理
    C++对于文件的操作需要包含<fstream>头文件文件类型分为两种:文本文件-文件以文件的ASCII码的形式存储在计算机中二进制文件-文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们操作文件的三大类:ofstream:写操作ifstream:读操作fstream:读写操作一、文......
  • C++杂谈:STL
    五一快乐。终于有时间整理一点东西了,笔者这个五一过得是相当初生。大部分人都玩去了,只有我还在赶ddl的世界达成了qwq。不过我觉得还是做了自己想做的事情的。稍微记录一些前段时间OOP遇到的STL里面乱七八糟的东西。STL的一些底层实现vector这个谁都知道,是一个堆上分配的数组,......
  • c++ 调用函数,编译器查找函数过程
    假设此处调用函数 inta=1;floatb=1.0;func(a,b);//调用函数若func为非模板函数,编译器查找所有的名称为func的函数,然后检查函数入参的数量,再然后检查每个入参是否都可以转换到目标类型。(此处注意,如果参数类型为类对象,若该类支持隐式转换,那么会出现如下情况:classT......