C++ Primer 5th Ch02 变量和基本类型
C++ Primer 5th Ch02 变量和基本类型

Directory


Note

无符号类型

当赋给无符号类型一个超范围的值时,结果是初始值对无符号类型表示数值总数取摸后的余数。

在一个算数表达式中,带符号数会自动转换为无符号数。

进制

0开头为八进制

0x开头为十六进制

指定字面值的类型

字符和字符串字面值:

前缀 含义 类型
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

浮点型字面值:

默认浮点型字面值为double类型。

后缀 类型
f/F float
l/L long double

初始化与赋值

初始化不是赋值。

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

编译器会区别这两种情 况,赋值的时候调用重载的赋值运算符,初始化的时候调用拷贝构造函数。其中赋值操作比初始化操作要慢,因为赋值操作会产生一个临时性的中间对象,这个临时性的中间对象会给被赋值对象赋值,导致开销变大。

成员初始化的顺序仅依赖于成员定义的顺序,而不是初始化列表中的顺序。

C++初始化与赋值

声明和定义

声明使得名字为程序所知,使用别处定义的名字则需包含其声明;定义负责创建与名字关联的实体(行为)。

关键字extern声明一个变量。

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

复合类型

给予其他类型定义的类型。

引用和指针属于复合类型。

左值引用与右值引用

  • 左值是可以位于赋值运算符 = 左侧的表达式(当然,左值也可以位于 = 的右侧),而
  • 右值是不可以位于赋值运算符 = 左侧的表达式。

一个表达式是左值还是右值,取决于我们使用的是它的值还是它在内存中的位置(作为实例的身份)。也就是说一个表达式具体是左值还是右值,要根据实际在语句中的含义来确定。

谈谈 C++ 中的右值引用

从4行代码看右值引用

指针与引用

  • 指针本身就是一个对象,引用并非对象
  • 指针无需在定义时赋初值,引用必须被初始化
  • 指针和引用都能提供对其他对象的间接访问
  • 一旦定义引用,无法再绑定其他的对象

空指针字面值

nullptr,在新标准下,避免使用NULL。

void*指针

可用于存放任意对象的地址,对地址中的对象类型并不了解。

const

const对象一旦创建后其值就不能再改变,

  • 必须被初始化
  • 在const对象上只能执行不改变其内容的操作
  • 编译器在编译const过程中把用到该变量的地方都替换为对应的值。
  • const可以使用extern关键字进行声明
  • 常量引用,也就是对常量的引用,reference to const。不能被用于修改所绑定的值
  • 指向常量的指针,pointer to const,不能用于改变所指对象的值。对于常量对象,只能使用指向常量的指针。int *p
  • const指针,常量指针必须被初始化。int *const p = &r;
  • 指针是对象而引用不是,常量指针指的是不变的是指针本身的值,而不是指向的值。能否修改常量指针所指向的值依赖于所指向的值的类型。
  • 顶层const与底层const
    • 顶层const,top-level const,表示任意的对象是个常量;底层const,low-level const,表示指针所指的对象是一个常量,与基本类型部分有关。
    • 拷贝操作,对顶层const没有什么影响,双方对象必须有相同的底层const资格。

引用的例外情况

引用的类型必须与其所引用对象的类型严格匹配,有两种例外

  1. 在初始化常量引用时允许用任意表达式作为初始值,只要其结果能转换为引用的类型。 例如,允许为常量引用绑定非常量的对象、字面值等。
  2. 见15.2.3节

constexpr

常量表达式是指值不会改变并且在编译过程中就能得到结果的表达式。

在一个复杂系统中,很难分辨一个初始值到底是不是常量表达式。

constexpr类型的变量由编译器来验证是否为常量表达式。

  • 声明为constexpr的变量一定为常量,必须用常量表达式初始化。
  • 一个constexpr指针的初始值必须为nullptr或0,或存储于固定地址的对象
  • 函数体内部的变量一般并非存在于固定地址中,函数体之外的兑现地址固定不变可用于初始化
  • 允许函数定义有效范围超出函数本身的变量,在函数体外也有固定地址,可用于constexpr
  • constexpr限定符如果定义了指针,其仅限定于指针,与所指对象无关。

typedef 指向char的常量指针 指向常量字符的指针

C语言语法 typedef小结

在P61页被typedef搞晕了

typedef char *pstring;
const pstring cstr = 0;
const pstring *ps;

其中,*pstring与char类型同义,即pstring为指向char类型的指针

因此,cstr为指向char类型的常量指针,ps为指向char类型指针的常量指针。

注意:cstr不能理解为 const char *cstr = 0这样就变成了指向const char常量字符的指针。

auto

让编译器识别分析表达式所属的类型

  • 引用传递的是引用的对象
  • 一般忽略顶层const,保留底层const
  • 初始值必须为同一类型

decltype

选择并返回操作数的数据类型,编译器分析表达式类型,但并不实际计算表达式的值。

  • 如果decltype使用的表达式是一个变量,则返回原类型(包括顶层const和引用)
  • 如果使用的表达式不是变量,则返回结果对应的类型
  • 如果变量名加上括号,decltype编译时会认为为一个表达式,结果为引用类型

头文件保护符

依赖于预处理变量,避免重复定义的错误。有两种状态:已定义与未定义

#ifdef

define

endif


Exercise

2.1 类型 int、long、long long 和 short 的区别是什么?无符号类型和带符号类型的区别是什么?float 和 double的区别是什么?

类型 int、long、long long 和 short 的区别:尺寸大小不同,short <= int <= long <= long long

无符号类型和带符号类型的区别​: 带符号类型可以表示正数、负数或0,无符号类型仅能表示大于等于0的值。

float 和 double的区别:float以1个字(32 bit)来表示,有7个有效位;double以2个字(64 bit)来表示,有16个有效位。

2.2 计算按揭贷款时,对于利率、本金和付款分别应选择何种数据类型?说明你的理由。

利率:double

本金:double

付款:float

double和float应该都可以,但是double精度更高一点。

2.3 读程序写结果。

unsigned u = 10, u2 = 42;
std::cout << u2 - u << std::endl;
std::cout << u - u2 << std::endl;

int i = 10, i2 = 42;
std::cout << i2 - i << std::endl;
std::cout << i - i2 << std::endl;
std::cout << i - u << std::endl;
std::cout << u - i << std::endl;

32 32

32 -32 0 0

2.4 编写程序检查你的估计是否正确,如果不正确,请仔细研读本节直到弄明白问题所在。

2_3_main.cpp

2_3_main_1

i - u2:4294967264

也就是说,当无符号数相减超出范围时得到取摸后余数,int值转换为无符号数时同理。

2.5 指出下述字面值的数据类型并说明每一组内几种字面值的区别:

(a) ‘a’, L’a’, “a”, L”a”

(b) 10, 10u, 10L, 10uL, 012, 0xC

(c) 3.14, 3.14f, 3.14L

(d) 10, 10u, 10., 10e-2

a:字符,宽字符,字符串,宽字符串

b:整型,无符号整型,长整型,无符号长整型,八进制,十六进制

c:double,float,long double

d:整型,无符号整型,浮点型,浮点型

2.6 下面两组定义是否有区别,如果有,请叙述之:

int month = 9, day = 7;
int month = 09, day = 07;

有,以0开头为八进制整型值。但是month无效,因为八进制中没有9.

2.7 下述字面值表示何种含义?它们各自的数据类型是什么?

(a) “Who goes with F\145rgus?\012”

(b) 3.14e1L

(c) 1024f

(d) 3.14L

a:Who goes with Fergus?(换行),string

b:31.4, long double

c:1024,float 无效,1024为整型,不能使用后缀f

d:3.14,long double

ASCII码 145为e 012为换行

2.8 请利用转义序列编写一段程序,要求先输出 2M,然后转到新一行。修改程序使其先输出 2,然后输出制表符,再输出 M,最后转到新一行。

2_8_main.cpp

2_8_main_1

2.9 解释下列定义的含义,对于非法的定义,请说明错在何处并将其改正。

  • (a) std::cin » int input_value;
  • (b) int i = { 3.14 };
  • (c) double salary = wage = 9999.99;
  • (d) int i = 3.14;

a:不应该在cin时定义,先定义再使用

b:存在初始值丢失的风险

c:wage未定义

d:定义i并初始化为3

2.10 下列变量的初值分别是什么?

std::string global_str;
int global_int;
int main()
{
    int local_int;
    std::string local_str;
}

gloal_str、global_int为全局变量,执行默认初始化,初始化为空字符串和0.

locak_int在函数体内未初始化,因此其值未定义;local_str类型为string,初始化为空字符串。

2.11 指出下面的语句是声明还是定义:

  • (a) extern int ix = 1024;
  • (b) int iy;
  • (c) extern int iz;

a:定义

b:声明 定义

c:声明

###2.12 请指出下面的名字中哪些是非法的?

  • (a) int double = 3.14;
  • (b) int _;
  • (c) int catch-22;
  • (d) int 1_or_2 = 1;
  • (e) double Double = 3.14;

非法:a,b~~,c,e~~,d

2.13 下面程序中 j 的值是多少?

int i = 42;
int main()
{
    int i = 100;
    int j = i;
}

j=100

2.14下面的程序合法吗?如果合法,它将输出什么?

    int i = 100, sum = 0;
    for (int i = 0; i != 10; ++i)
        sum += i;
    std::cout << i << " " << sum << std::endl;

100 45

2.15 下面的哪个定义是不合法的?为什么?

  • (a) int ival = 1.01;
  • (b) int &rval1 = 1.01;
  • (c) int &rval2 = ival;
  • (d) int &rval3;

不合法:b引用的初始值必须是一个对象,d引用必须被初始化

###2.16 考察下面的所有赋值然后回答:哪些赋值是不合法的?为什么?哪些赋值是合法的?它们执行了哪些操作?

int i = 0, &r1 = i; double d = 0, &r2 = d;
(a) r2 = 3.14159;
(b) r2 = r1;
(c) i = r2;
(d) r1 = d;

a:合法,赋值操作

b:合法,自动转化int -> double

c:合法,自动转换double -> int

d:合法,自动转换double ->int

2.17 执行下面的代码段将输出什么结果?

int i, &ri = i;
i = 5; ri = 10;
std::cout << i << " " << ri << std::endl;

10 10

2.18 编写代码分别改变指针的值以及指针所指对象的值。

2_18_main.cpp

2_18_main_1

2.19 说明指针和引用的主要区别

见Note 指针与引用

2.20 请叙述下面这段代码的作用。

int i = 42;
int *p1 = &i; 
*p1 = *p1 * *p1;

修改i的值为42*42

2.21 请解释下述定义。在这些定义中有非法的吗?如果有,为什么?

int i = 0;
(a) double* dp = &i;
(b) int *ip = i;
(c) int *p = &i;

a,非法 不能double类型指针不能指向int类型的地址

b,非法,指针不能指向int类型

2.22 假设 p 是一个 int 型指针,请说明下述代码的含义。

if (p) // ...
if (*p) // ...

1,如果指针p存在

2,如果指针p指向的值是否为0

2.23 给定指针 p,你能知道它是否指向了一个合法的对象吗?如果能,叙述判断的思路;如果不能,也请说明原因。

不能,因为需要更多的信息来先判断指针p是否合法,再判断对象是否合法

2.24 在下面这段代码中为什么 p 合法而 lp 非法?

int i = 42;
void *p = &i;
long *lp = &i;

void指针可以指向任意类型的地址,long类型指针不能存放int类型的地址

2.25 说明下列变量的类型和值。

  • (a) int* ip, i, &r = i;
  • (b) int i, *ip = 0;
  • (c) int* ip, ip2;

a:ip为int型指针,i为int型的数,r为i的int型引用

b:i为int型的数,ip为指向0的int型指针 ip为int型空指针

c:ip为int型的指针,ip2为int型的数

2.26下面哪些语句是合法的?如果不合法,请说明为什么?

const int buf;      // 不合法, const 对象必须初始化
int cnt = 0;        // 合法
const int sz = cnt; // 合法
++cnt; ++sz;        // 不合法, const 对象不能被改变

2.27 下面的哪些初始化是合法的?请说明原因。

int i = -1, &r = 0;         // 不合法, r 必须引用一个对象
int *const p2 = &i2;        // 合法
const int i = -1, &r = 0;   // 合法
const int *const p3 = &i2;  // 合法
const int *p1 = &i2;        // 合法
const int &const r2;        // 不合法, r2 是引用,引用没有顶层 const
const int i2 = i, &r = i;   // 合法

2.28说明下面的这些定义是什么意思,挑出其中不合法的。

int i, *const cp;       // 不合法, const 指针必须初始化
int *p1, *const p2;     // 不合法, const 指针必须初始化
const int ic, &r = ic;  // 不合法, const int 必须初始化
const int *const p3;    // 不合法, const 指针必须初始化
const int *p;           // 合法. 一个指针,指向 const int

2.29 假设已有上一个练习中定义的那些变量,则下面的哪些语句是合法的?请说明原因。

i = ic;     // 合法, 常量赋值给普通变量
p1 = p3;    // 不合法, p3 是const指针不能赋值给普通指针
p1 = &ic;   // 不合法, 普通指针不能指向常量
p3 = &ic;   // 合法, p3 是常量指针且指向常量
p2 = p1;    // 合法, 可以将普通指针赋值给常量指针
ic = *p3;   // 合法, 对 p3 取值后是一个 int 然后赋值给 ic

2.30 对于下面的这些语句,请说明对象被声明成了顶层const还是底层const?

const int v2 = 0; int v1 = v2;
int *p1 = &v1, &r1 = v1;
const int *p2 = &v2, *const p3 = &i, &r2 = v2;

v2为顶层const,

p2为顶层const,p3为右侧为顶层const,左侧为底层const,r2为底层const

2.31 假设已有上一个练习中所做的那些声明,则下面的哪些语句是合法的?请说明顶层const和底层const在每个例子中有何体现。

r1 = v2; // 合法, 顶层const在拷贝时不受影响
p1 = p2; // 不合法, p2 是底层const,如果要拷贝必须要求 p1 也是底层const
p2 = p1; // 合法, int* 可以转换成const int*
p1 = p3; // 不合法, p3 是一个底层const,p1 不是
p2 = p3; // 合法, p2 和 p3 都是底层const,拷贝时忽略掉顶层const

2.32 下面的代码是否合法?如果非法,请设法将其修改正确。

int null = 0, *p = null;

null为关键字,*p=nullptr。

2.33 利用本节定义的变量,判断下列语句的运行结果。

a=42; // 合法,a 是 int
b=42; // 合法,b 是一个 int(ci的顶层const在拷贝时被忽略掉了)
c=42; // 合法,c 也是一个int
d=42; // 不合法,d 是一个 int *
e=42; // 不合法,e 是一个 const int *
g=42; // 不合法,g 是一个 const int 的引用,引用都是底层const

2.34 基于上一个练习中的变量和语句编写一段程序,输出赋值前后变量的内容,你刚才的推断正确吗?如果不对,请反复研读本节的示例直到你明白错在何处为止

略,懒得写,看书吧

2.35 判断下列定义推断出的类型是什么,然后编写程序进行验证。

const int i = 42;
auto j = i; const auto &k = i; auto *p = &i; 
const auto j2 = i, &k2 = i;

j为int,k为整型常量引用,p为指向整形常量i的指针

j2为整型常量,k2为整型常量引用

2_35_main.cpp

typeid根本不好用。。。

2.36 关于下面的代码,请指出每一个变量的类型以及程序结束时它们各自的值。

int a = 3, b = 4;
decltype(a) c = a;
decltype((b)) d = a;
++c;
++d;

c为int,d为a的引用。值为4.

2_36_main.cpp

2.37 赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果 i 是 int,则表达式 i=x 的类型是 int&。根据这一特点,请指出下面的代码中每一个变量的类型和值。

int a = 3, b = 4;
decltype(a) c = a;
decltype(a = b) d = a;

c为int,值为3

d为int &,值为3

2.38 说明由decltype 指定类型和由auto指定类型有何区别。

请举一个例子,decltype指定的类型与auto指定的类型一样;再举一个例子,decltype指定的类型与auto指定的类型不一样。

decltype与auto:

  • 处理顶层const的方式不同。auto一般会忽略顶层const,decltype当表达式为变量时,返回该变量类型。
  • 对于decltype(p),使用解引用指针会得到指针所指的对象,并且能给这个对象赋值。其结果为int &。对于auto *p = &i (int)p为指向整型的指针
  • decltype与表达式形式密切相关。变量加不加括号得到的结果并不同。

2.39 编译下面的程序观察其运行结果,注意,如果忘记写类定义体后面的分号会发生什么情况?记录下相关的信息,以后可能会有用。

struct Foo { /* 此处为空  */ } // 注意:没有分号
int main()
{
    return 0;
}

2_39_main.cpp

2_39_main_1

2.40 根据自己的理解写出 Sales_data 类,最好与书中的例子有所区别。

struct Sales_data {
  string bookNo;
  string bookName;
  unsigned units_sold = 0;
  double revenue = 0.0;
  double price = 0.0;
};

2.41 使用你自己的Sale_data类重写1.5.1节(第20页)、1.5.2节(第21页)和1.6节(第22页)的练习。眼下先把Sales_data类的定义和main函数放在一个文件里。

2_41_1.cpp

2_41_1

2_41_2.cpp

2_41_2

2.42 根据你自己的理解重写一个Sales_data.h头文件,并以此为基础重做2.6.2节(第67页)的练习。

2_42.cpp

上一篇   C++ Primer 5th Ch01 开始

下一篇   C++中疑惑的概念与问题