C++ Primer(第五版)读书笔记 & 习题解答 --- Chapter 2

Chapter 2.1

1. 数据类型决定了程序中数据和操做的意义。ios

2. C++定义了一套基本数据类型,其中包括算术类型和一个名为void的特殊类型。算术类型包含了字符、整型、布尔值以及浮点数。void无值也无操做,咱们不能定义一个void类型的变量。函数

3. 算术类型的尺寸,也就是该类型所占的比特数,在不一样机器上有所差异。下表列出了C++标准规定的最小尺寸,同时容许编译器赋予这些类型更大的尺寸:布局

因为比特数的不一样,一个类型所能表达的最大(最小)值也是不一样的。性能

4. C++语言规定,一个int至少和一个short同样大,一个long至少和一个int同样大,一个long long至少和一个long同样大。spa

5. 基本字符类型是char,一个char的尺寸应确保能够存放机器基本字符集中任意字符对应的数字值,也就是说,一个char的尺寸和一个机器字节相同。指针

6. 其余字符类型,如wchar_t、char16_t、char32_t用于扩展字符集。wchar_t类型确保能够存放机器最大扩展字符集中的任意一个字符。char16_t和char32_t用于Unicode字符集(Unicode是用于表示全部天然语言中字符的标准)。code

7. 计算机以比特序列存储数据。大多数计算机以2的整数次幂个比特做为块来处理内存,可寻址的最小内存块称为“字节”。在C++语言中,一个字节要至少能容纳机器基本字符集中的字符。存储的基本单元称为“字”,一般由几个字节组成。大多数机器的字节由8比特构成,字则由32比特或64比特构成。对象

8. 大多数计算机将内存中的每一个字节与一个数字(被称为地址)关联起来。咱们可以使用某个地址来表示从这个地址开始的大小不一样的比特串。为了赋予内存中某个地址明确的含义,必须首先知道存储在该地址的数据的类型。类型决定了数据所占的比特数以及该如何解释这些比特的内容。blog

9. 除去布尔型和扩展的字符型以外,其余整型能够划分为带符号的和无符号的。字符型与其余整型不一样,被分为三种:char、unsigned char和signed char。尽管有三种,可是其表现形式仍就只有两种:带符号的和无符号的。类型char会表现为signed char仍是unsigned char是由编译器决定的。生命周期

10. 无符号类型中全部比特都用来存储值。对于有符号类型,C++标准并无规定应如何表示,可是约定了在表示范围内正值和负值应该均匀分布。

11. 下面是选择使用哪一种类型的一些有用的准则: 

(1). 当明确知晓数值不可能为负时,选用无符号类型
(2). 使用int来执行整数运算。short一般过小了而long通常和int的尺寸同样。若是数值超出int的表示范围,就使用long long
(3). 在算术表达式中不要使用char或bool。只有在存放字符或布尔值时才使用它们。由于char在一些机器上是有符号的,而在另外一些机器上倒是无符号的,因此若是使用char进行运算特别容易出问题。若是你须要一个不大的整数,就显示指定为signed char或unsigned char
(4). 选用double来执行浮点运算。float的精度一般不够,而且双精度浮点和单精度浮点的计算在性能开销上相差无几。事实上,在一些机器上,双精度运算甚至比单精度还快。long double提供的精度一般是没有必要的,而且它带来的性能开销也不容忽视

12. 当在程序的某处咱们使用了一种类型而对象应该取另外一种类型时,程序会自动进行类型转换:

bool b = 42;             // b为真
int i = b;               // i的值为1
i = 3.14;                // i的值为3
double pi = i;           // pi的值为3.0
unsigned char c = -1;    // 假设char占8比特,c的值为255
signed char c2 = 256;    // 假设char占8比特,c2的值是未定义的

类型所能表示的值的范围决定了转换的过程:

(1). 当咱们把一个非布尔算术类型赋值给一个布尔类型对象时,若是值是0,则结果为false,不然结果为true
(2). 当咱们把一个布尔类型赋值给其余算术类型时,若是布尔值为true,那么结果就是1,不然结果就是0
(3). 当咱们把浮点类型赋值给整型对象时,值会被截断。只会保留小数点以前的值
(4). 当咱们把整型类型赋值给浮点型对象时,小数部分将是0。若是该整数的比特数超过了浮点对象能够容纳的比特数的话,精度可能会丢失
(5). 当咱们给无符号类型赋值一个超出它表示范围的数值时,结果是该值对无符号类型可以表示的数值的总数取模后的余数。例如,8比特大小的unsigned char能够表示0到255,总共256个数。所以,把-1赋值给它所得的结果是255
(6). 当咱们给带符号类型赋一个超出它表示范围的值时,结果是未定义的

13. 若是表达式里既有带符号类型又有无符号类型,带符号数会自动地转换成无符号数。

14. 一个形如42的值被称为字面值常量。每一个字面值常量都对应一种数据类型,字面值常量的形式和值决定了它的数据类型。

15. 咱们能够将整型字面值写做十进制数、八进制数或十六进制数的形式:

20     // 十进制
024    // 八进制,以0开头
0x14   // 十六进制,以0x或0X开头
默认状况下,十进制字面值是带符号数,它的类型是int、long和long long中能容纳字面值数值的尺寸最小的那个。而八进制和十六进制字面值既多是带符号的也多是无符号的,它的类型是int、unsigned int、long、unsigned long、long long和unsigned long long中能容纳字面值数值的尺寸最小 的那个。尽管整型字面值能够存储在带符号类型中,但技术上来讲,十进制字面值不会是负数。若是咱们写-42,那么负号其实不是字面值的一部分,它只是一个操做符,做用是对字面值取负。
浮点型字面值表现为一个小数或以科学计数法表示的指数:
3.14159
3.14159E0
0. 0e0 .001

默认状况下,浮点型字面值是一个double。

16. 由单引号括起来的一个字符称为char型字面值,双引号括起来的零个或多个字符则构成字符串型字面值。编译器会在每一个字符串型字面值的结尾处添加一个空字符('\0'),所以,字符串型字面值的实际长度要比它的内容多1。若是两个字符串型字面值位置紧邻而且仅由空格、缩进和换行符分隔,那么它们其实是一个总体:

std::cout << "一个很长很长很长很长再长一点的字符串 "        
             "跨越了两行噢" << std::endl;

17. C++语言规定的转义序列包括:

咱们也可使用泛化的转义序列,其形式是\x后紧跟1个或多个十六进制数字,或者\后紧跟1个、2个或3个八进制数字。其值表示的是字符对应的数值。

18. 咱们能够经过下表中所列的前缀和后缀,来改变整型、浮点型和字符型字面值的默认类型:

 

Chapter 2.2

1. C++中的每一个变量都有其数据类型,数据类型决定着变量所占内存空间的大小和布局方式、该内存空间可以存储的值的范围、以及变量能参与的运算。

2. 当一次定义了两个或多个变量时,对象的名字随着定义也就立刻可使用了。所以,在同一条定义语句中,能够用先定义的变量值去初始化后定义的其余变量:

double price = 109.99, discount = price * 0.16;

3. 初始化与赋值是两个彻底不一样的操做。初始化的含义是建立变量时赋予其一个初始值。而赋值的含义是把对象的当前值擦除,用一个新值来替代。

4. 在C++11标准中,能够用花括号来初始化变量,这种初始化形式被称为列表初始化:

int units_sold = {0}; int units_sold{0};

当列表初始化用于内置类型的变量时,有一个重要特色:若是初始值存在丢失信息的风险,则编译器会报错:

long double ld = 3.1415926536; int a{ld}, b = {ld};  // 编译错误:存在丢失信息的风险
int c(ld), d = ld;    // 编译正确:但值被截断了

5. 若是定义变量时没有指定初始值,则变量会默认初始化:

(1). 若是是内置类型的变量,当它定义于任何函数体以外时,将被初始化为0。当它定义在函数体内部时,将不被初始化
(2). 每一个类各自决定其初始化对象的方式

6. 变量声明规定了变量的类型和名字,在这一点上定义与之相同。但除此以外,定义还申请存储空间,而且可能会为变量赋一个初始值。

7. 声明一个变量而非定义它,就在变量名前添加关键字extern:

extern int i;  // 声明
int j;            // 定义

8. 任何包含了显示初始化的声明即成为定义。所以,extern语句若是包含初始值也就再也不是声明了:

extern double pi = 3.1416;  // 定义

在函数体内部,若是试图初始化一个由extern关键字标记的变量,将引起错误。

9. 变量能且只能被定义一次,可是能够被屡次声明。

10. C++中的做用域有以下几级:

全局做用域:全局做用域内的名字在整个程序的范围内均可使用
类做用域:名字定义在类的内部
命名空间做用域:名字定义在命名空间内部
块做用域:名字定义在块的内部。从声明位置开始直至声明语句所在的做用域末端为止都是可用的

11. 做用域能彼此包含,被包含的做用域称为内层做用域,包含着别的做用域的做用域称为外层做用域。做用域中一旦声明了某个名字,它所嵌套着的全部做用域都能访问该名字。同时,容许在内层做用域中从新定义外层做用域已有的名字。

 

Chapter 2.3

1. 咱们没法令引用从新绑定到另一个对象,所以引用必须初始化。

2. 引用并不是对象,它只是为一个已经存在的对象所起的另一个名字。由于引用自己不是一个对象,因此不能定义引用的引用。

3. 引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一块儿。

4. 指针与引用相比有几个不一样点:其一,指针自己就是一个对象,容许对指针赋值和拷贝,并且在指针的生命周期内它能够前后指向几个不一样的对象。其二,指针无须在定义时赋初值。

5. 和其余内置类型同样,在块做用域内定义的指针若是没有被初始化,也将拥有一个不肯定的值。

6. 由于引用不是对象,没有实际地址,因此不能定义指向引用的指针。

7. 指针存储的值能够是如下四种状态之一:

(1). 指向一个对象
(2). 指向一个对象所占空间末尾的下一个位置
(3). 空指针
(4). 无效指针

8. C++11引入了nullptr,能够初始化一个指针,表示空指针。它是一种特殊类型的字面值,能够被转换成任意其余的指针类型。

9. void*是一种特殊的指针类型,可用于存听任意对象的地址。

10. 变量的定义包括一个基本数据类型和一组声明符。在同一条定义语句中,虽然基本数据类型只有一个,可是声明符的形式却能够不一样:

// i是一个int类型的变量,p是一个int类型的指针,r是一个int类型的引用
int i = 0, *p = &i, &r = i;

11. 指针是对象,因此存在对指针的引用:

int i = 42;
int *p = nullptr;
int *&r = p;  // r是一个对指针p的引用
r = &i;       // r引用了指针p,因此就是令p存放i的地址
*r = 0;       // r引用了指针p,因此就是解引用指针p,获得i,将i的值改成0

 

Chapter 2.4

1. 引用的类型必须与其所引用对象的类型一致,可是有两个例外。第一个就是在初始化常量引用时容许用任意表达式做为初始值,只要该表达式的结果能转换成引用的类型便可。特别是,容许为一个常量引用绑定很是量的对象、字面值,甚至是一个表达式:

int i = 42; const int &r1 = i; const int &r2 = 42; const int &r3 = r1 * 2;
double d = 3.14; const int &r4 = d;

2. 指针的类型必须与其所指对象的类型一致,可是有两个例外。第一个就是容许令一个指向常量的指针指向一个很是量对象:

double dval = 3.14; const double *ptr = &dval;

3. 咱们使用名词顶层const(top-level const)表示一个对象自己是常量,顶层const对任何数据类型都适用,如算术类型、类、指针等。而名词底层const(low-level const)用于指针或引用这些复合类型的基本类型,例如表示指针所指的对象是一个常量,引用所绑定的对象是一个常量。

当执行对象的拷贝操做时,顶层const会被忽略:
int i = 0; const int ci = 42; i = ci;  // ci是顶层const,被忽略
拷贝操做并不会改变被拷贝对象的值,所以,拷入和拷出的对象是否是常量都没什么影响。
另外一方面,底层const却不容忽视。当咱们拷贝一个对象时,拷入和拷出的对象必须具备相同的底层const资格,或者两个对象的数据类型必须可以转换,一般来讲,很是量能够转换为常量,反之则不行:
int i = 0; const int* const p = &i; // p3既是顶层const,又是底层const
int *p1 = p; // 错误:p包含底层const,但p1却没有
const int &r = i; // 正确:const int&能够绑定到普通int上,反之int&却不能够绑定到const int上

4. 常量表达式是指值不会改变而且在编译过程就能获得计算结果的表达式。一个对象(或表达式)是否是常量表达式由它的数据类型和初始值共同决定:

const int max_files = 20; // 常量表达式
const int limit = max_files + 1; // 常量表达式
int staff_size = 27; // 不是常量表达式
const int sz = get_size(); // 不是常量表达式

C++11中,咱们能够将变量声明为constexpr类型以便由编译器来验证变量的值是不是一个常量表达式。声明为constexpr的变量必定是一个常量,并且必须用常量表达式初始化。

5. 尽管指针能够定义成constexpr,可是它的初始值却受到严格的限制,必须是nullptr或者0,或者是存储于某个固定地址中的对象。还必须明确的一点事,限定符constexpr仅对指针有效,与指针所指的对象无关:

const int *p = nullptr; // p是一个指向整型常量的指针
constptr int *q = nullptr; // q是一个指向整型变量的常量指针

 

Chapter 2.5

1. C++11引入了一种新的定义类型别名的方法:

using MyInt = int;

2. 当指代复合类型的类型别名与const一块儿使用时,可能会产生意想不到的结果:

using pstring = char*; const pstring cstr = nullptr; // cstr是指向char的常量指针

来看上述代码,当遇到一条使用了类型别名的声明语句时,不少人经常会错误的把类型别名替换成它原本的样子来理解它:

const char *cstr = nullptr;

这种理解是错误的。声明语句中用到pstring时,其基本数据类型是char*,是一个指针。而用char*替换重写了以后,基本数据类型就变成了char。因此,在理解使用类型别名的声明语句时,要特别注意这一点。

3. C++11引入了auto类型说明符,用它就能让编译器替咱们去分析表达式所属的类型。auto定义的变量必须有初始值。使用auto也能在一条语句中声明多个变量,但由于一条声明语句只能有一个基本数据类型,因此该语句中全部变量的初始基本数据类型都必须一致:

auto i = 0, *p = &i; // 正确:i是整数,p是整型指针
auto sz = 0, pi = 3.14; // 错误:sz和pi的类型不一致

4. 使用auto的时候要注意如下一些规则:当引用被用做初始值时,真正参与初始化的实际上是引用对象的值。此时编译器以引用对象的类型做为auto的类型:

int i = 0, &r = i; auto a = r; // a是一个整数

auto通常会忽略掉顶层const,而保留底层const:

const int ci = 42; auto b = ci; // b是一个整数(ci的顶层const特性被忽略)
auto e = &ci; // e是一个指向整数常量的指针(对常量对象取地址是一种底层const)

设置一个类型为auto的引用时,初始值中的顶层const会被保留:

const int ci = 42;
auto &r = ci; // r是一个常量引用

5. C++11标准引入了类型说明符decltype,它的做用是返回它的操做数的类型:

decltype(f()) sum = x;

在上面的代码中,编译器并不会调用函数f,而是使用当函数f被调用时的返回值类型做为sum的类型。decltype处理顶层const和引用的方式与auto不一样,若是decltype使用的表达式是一个变量,则它会返回该变量的类型(包括顶层const和引用):

const int ci = 0, &cj = ci; decltype(ci) x = 0; // x的类型是const int
decltype(cj) y = x; // y的类型是const int&,绑定到x

若是decltype使用的表达式不是一个变量,则它将返回表达式结果对于的类型。一些表达式会致使decltype产生一个引用类型,一般来讲,返回引用类型的表达式是那种能产生一条赋值语句的左值的表达式:

int i = 42, *p = &i, &r = i; decltype(r + 0) b; // b是int类型
decltype(*p) c; // 错误:c是int&类型,必须初始化

正如咱们所看到的,*p获得指针p所指的对象,而且该对象能够赋值,因此,decltype(*p)产生的是int&,而不是int。

有一种状况须要特别注意,若是decltype使用的是一个不加括号的变量,则获得的结果就是该变量的类型。若是给变量加上了一层或多层括号,编译器就会把它当成是一个表达式,而变量是一种能够做为赋值语句左值的特殊表达式,所以,这样decltype就会获得引用类型: 
int i = 42;
decltype((i)) d = i; // d是int&

 

Chapter 2.6

1. C++11标准规定,能够为类的数据成员提供一个类内初始值:

class CItem { unsigned unitsSold = 0; };

2. 预处理器是在编译以前执行的一段程序。

3. 预处理变量有两种状态:已定义和未定义。#define指令把一个名字设定为预处理变量。预处理变量不遵循C++语言中关于做用域的规则,因此预处理变量在整个程序中必须惟一。

 

Exercises Section 2.1.3 

Q_1. Using escape sequences, write a program to print 2M followed by a newline. Modify the program to print 2, then a tab, then an M, followed by a newline.

A_1. 

#include <iostream>

int main()
{
    std::cout << "2\115" << '\12';

    return 0;
}
#include <iostream>

int main()
{
    std::cout << '2' << '\t' << '\115' << '\n';

    return 0;
}

 

Exercises Section 2.3.2

Q_1. Write code to change the value of a pointer. Write code to change the value to which the pointer points.

A_1. 

#include <iostream>

int main()
{
    int value = 0;
    int *pValue = &value;

    // 更改指针所指对象的值
    *pValue = 100;

    // 更改指针的值
    pValue = nullptr;

    return 0;
}

 

Exercises Section 2.4.4 

Q_1. Is the following code legal or not? If not, how might you make it legal?

A_1. 

int null = 0, *p = null; // 非法,不能用整型变量null来初始化指向整型变量的指针p
// 修正: int null = 0, *p = &null;
相关文章
相关标签/搜索