《C++Primer》读书笔记(二)

516 阅读11分钟

第3章 字符串、向量和数组

标准库类型string

标准库类型string表示可变长的字符序列,使用string类型必须首先包含string头文件。

#include<string>
using std::string

初始化string的方式


string s1 //默认初始化,s1是一个空串
string s2(s1) //s2是s1的副本
string s2 = s1 //等价于s2(s1),s2是s1的副本
string s3("value") // s3是字面值"value"的副本,除了字面值最后的那个空字符串外
string s3 = "value" //等价于s3(“value”),s3是字面值“value”的副本
string s4(n,'c') // 把s4初始化为由连续n个字符c组成的串

使用getline读取一整行

int main() {
    string line;
    //每次读入一整行,直至到达文件末尾
    while(getline(cin, line)) {
        cout << line << endl;
    }
    return 0;
}

string.size()函数返回的是一个string::size_type类型的值,它是一个无符号型的值,能足够存放下任何string对象的大小。

string大小的比较:

  1. 如果两个string对象的长度不同,而且较短string对象的每个字符都与较长string对象对应位置上的字符相同,就说较短string对象小于较长string对象。
  2. 如果两个string对象在某些对应的位置上不一致,则string对象比较的结果就是string对象中第一对相异字符比较的结果。
    两个string相加
string s3 = s1 + s2;

字面值和string对象相加:当把string对象和字符字面值及字符串混在一条语句中使用时,必须确保每个加法运算符(+)的两侧运算对象至少有一个是string。


string s4 = s1 + ",";  //正确
string s5 = "hello" + ","; //错误:两个运算对象都不是string
string s6 = s1 + "," + "world"; //正确: 每个加法运算符都有一个运算对象是string
string s7 = "hello" + "," +  s2; //错误: 不能把字面值相加

处理string对象中的字符

isalnum(c) //当c是字母或数字时为真
isalpha(c) //当c是字母时为真
iscntrl(c) //当c时控制字符时为真
isdigit(c) //当c时数字时为真
isgraph(c) //当c不是空格但可打印时为真
isslower(c) //当c是小写字母时为真
issprint(c) //当c是可打印字符时为真(即c时空格或者c具有可视形式)
ispunct(c) //当c是标点符号时为真(即c不是控制字符、数字、字母、可打印空白中的一种)
isspace(c) //当c是空白时为真(即c时空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种)
isupper(c) //当c是大写字母时为真
isxdigit(c) //当c是十六进制数字时为真
tolower(c) //如果c是大写字母,输出对应的小写字母;否则原样输出c
toupper(c) //如果c是小写字母,输出对应的大写字母;否则原样输出c

标准库类型vector

标准库类型vector表示对象的集合,其中所有对象的类型都相同。集合中的每个对象都有一个与之对应的索引,索引用于访问对象。

要想使用vector,必须包含适当的头文件。

#inclue <vector>
using std::vector

初始化vector对象的方法

vector<T> v1 //v1是一个空vector,它潜在的元素是T类型的,执行默认初始化
vector<T>V2(v1) //v2中包含有v1所有元素的副本
vector<T>v2 = v1 //等价于v2(v1),v2中包含有v1所有元素的副本
vector<T>v3(n,val) //v3包含了n个重复的元素,每个元素的值都是val
vector<T>v4(n) //v4包含了n个重复地执行了值初始化的对象
vector<T>v5{a,b,c} //v5包含了初始值的元素,每个元素被赋予相应的初始值
vector<T>v5 = {a,b,c} //等价于v5{a,b,c}

如果vector对象的元素是内置类型,比如int,则元素初始值自动设为。如果元素是某种类类型,比如string,则元素由类默认初始化。

vector的比较: 如果两个vector对象的容量不同,但是在相同位置上的元素值都一样,则元素较少的vector对象小于元素较多的vector对象;若元素的值有区别,则vector对象的大小关系由第一对相异的元素值的大小关系决定。

迭代器

拥有begin()end()成员,其中begin指向第一个元素,end成员指向容器“尾元素的下一个位置”。

标准容器迭代器的运算符:

*iter //返回迭代器iter所指元素的引用
iter->mem //解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem
++iter //令iter指示容器中的下一个元素
--iter //令iter指示容器中的上一个元素
iter1 == iter2 //判断两个迭代器是否相等(不相等),如果两个迭代器指示的是同一个元素或者它们是
iter != iter2  //同一个容器的尾迭代器,则相等;反之,不想等。

迭代器类型:就像不知道string和vector的size_type类型一样,我们不知道迭代器的精确类型。


vector<int>::iterator it;
string::iterator it2;
vector<int>::const_iterator it3; //it3只能读元素,不能写元素
string::const_iterator it4; //it4只能读字符,不能写字符

数组

数组是一种类似标准库类型vector的数据结构,与vector不同的地方是,数组的大小确定不变,不能随意向数组中增加元素。

初始化数组:

const unsigned sz = 3;
int ial[sz] = {0,1,2}; //含有3个元素的数组,元素值分别是0,1,2
int a2[] = {0,1,2};    //维度是3的数组
int a3[5] = {0,1,2};   //等价于a3[] = {0,1,2,0,0}
string a4[3] = {"hi","bye"}; //等价于 a4[] = {"hi","bye", ""}
int a5[2] = {0,1,2};  //错误:初始值过多

不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值

复杂的数组声明:

int *ptr[10]; //ptrs是含有10个整形指针的数组
int &refs[10] = /* ? */ //错误:不存在引用的数组
int (*Parray)[10] = &arr; //Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr; //arrRef引用一个含有10个整数的数组

对数组使用下标运算符得到该数组指定位置的元素。因此像其他对象一样,对数组的元素使用该取地址符就能得到指向该元素的指针:

string nums[] = {"one", "two", "three"}; //数组的元素是string对象
string *p = &nums[0];

特性:在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针

string *p2 = nums; //等价于 string *p2 = &nums[0];

使用数组初始化vector对象

int int_arr[] = {0,1,2,3,4,5}
//ivec有6个元素,分别是int_arr中对应元素的副本
vector<int> ivec(begin(int_arr), end(int_arr));
vector<int>subVec(int_arr + 1, int_arr + 4); // 可能是数组的一部分

多维数组

一个维度表示数组本身大小,另外一个维度表示其元素(也是数组)大小。

int ia[3][4]; //大小为3的数组,每个元素是含有4个整数的数组
int arr[10][20][30] = {0}; //将所有元素初始化为

多维数组的初始化

int ia[3][4] = {
    {0,1,2,3},
    {4,5,6,7},
    {8,9,10,11}
};
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};//与上一个等价
int ia[3][4] = {{0},{4},{8}}; //初始化每一行的第一个元素
int ia[3][4] = {0,3,6,9}; //显示地初始化第一行,其他元素执行值初始化

ia[2][3] = arr[0][0][0];
int (&row)[4] = ia[1]; //把row绑定到ia的第二个4元素数组上

当程序使用多维数组的名字时,也会自动将其转换成指向数组首元素的指针。

int ia[3][4]; //大小为3的数组,每个元素是含有4个整数的数组。
int (*p)[4] = ia; //p指向含有4个整数的数组。
p = &ia[2]; //p指向ia的尾元素

第4章 表达式

递增和递减运算符

int i = 0, j;
j = ++i; // j =1, i = 1: 前置版本得到递增之后的值
j = i++; // j = 1, i = 2: 后置版本得到递增之前的值

除非必须,否则不用递增递减运算符的后置版本如果我们不需要修改前的值,那么后置版本的操作就是一种浪费。

位运算符

位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。

  • 左移运算符(<<)在右侧插入值为0的二进制位。
  • 右移运算符(>>)在左侧插入值为0的二进制位。
  • 位求反运算符(~)将运算对象逐位求反后生成一个新值,将1置为0、将0置为1
  • 位与运算符(&) 如果两个运算对象的对应位置都是1,则运算结果中该位为1,否则为0。
  • 位或运算符(|) 如果两个运算对象的对应位置至少有一个位1则运算结果中该位为1,否则为0
  • 位异或运算符(^) 如果两个运算对象的对应位置有且只有一个为1,则运算结果中该位为1,否则为0

sizeof运算符

sizeof运算符返回一条表达式或一个类型名字所占用的字节数。

sizeof运算符的结果部分地依赖于其作用的类型:

  • 对char或者类型为char的表达式执行sizeof运算,结果得1。
  • 对引用类型执行sizeof运算得到被引用对象所占空间的大小。
  • 对指针执行sizeof运算得到指针本身所占空间的大小。
  • 对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小,指针不需要有效。
  • 对数组执行sizeof运算得到整个数组所占空间的大小,等价于对数组中所有的元素执行一次sizeof运算并将所得结果求和。
  • 对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。

显示类型转换

命名的强制类型转换:

cast-name<type>(expression);
  • static_cast:任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。
double slope = static_cast<double>(j)/i;

static_cast对于编译器无法自动执行的类型转换也非常有用。例如:我们可以使用static_cast找回存在于void* 指针中的值。

void* p = &d; //正确:任何非常量对象的地址都能存入void*
double *dp = static_cast<double*>(p)
  • const_cast: const_cast只能改变运算对象的底层const
const char *pc;
char *p = const_cast<char *>(pc); //正确:但是通过p写值是未定义的行为。

如果对象本身不是一个常量,使用强制类型转换获得写权限是合法的行为。然而如果对象是一个常量,再使用const_cast执行写操作就会产生未定义的后果。

  • reinterpret_cast reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。例如:
int *ip;
char *pc = reinterpret_cast<char*>(ip);

我们必须牢记pc所指的真实对象是一个int而非字符,如果把pc当成普通的字符指针使用就可能在运行时发生错误。

string str(pc);

第5章 语句

goto语句

goto语句的作用是从goto语句无条件跳转到同一函数内的另一条语句。(不要再程序使用goto语句,使得程序难理解,又难修改)

goto end;
end:: return;

try语句块和异常处理

  • throw 表达式: 包含关键字throw和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型。throw表示式后面通常紧跟一个分号,从而构成一条表达式语句。

if (item1.isbn() != item2.isbn()) {
    throw runtime_error("Data must refer to same ISBN");
}

runtime_error是标准库异常类型的一种。定义在stdexcept头文件中

try语句块

try语句块的一开始是关键字try,随后紧跟着一个块,这个块就像大多数时候那样是花括号括起来的语句序列。
跟在try语句块之后的是一个或多个catch子句。catch子句包括三部分: 关键字catch、括号内一个对象的声明以及一个块。

try {
    //执行添加两个Sales_item对象的代码
    // 如果添加失败,代码抛出一个runtime_error异常
} catch (runtime_error err) {
    //提醒用户两个ISBN必须一直
    cout<< err.what() << "\nTry Again"
}

标准异常

C++ 标准库定义了一组类,用于报告标准库函数遇到的问题。分别定义在4个头文件中:

  • exception头文件定义了最通用的异常类exception。它只报告异常的发生,不提供额外信息。
  • stdexcept 头文件定义了几种常用的异常类
  • new头文件定义了bad_alloc异常类型
  • type_info头文件定义了bad_cast异常类型
exception //最常见的问题
runtime_error //只有在运行时才能检测出的问题
range_error //运行时错误:生成的结果超出了有意义的值域范围
overflow_error //运行时错误:计算上溢
underflow_error //运行时错误:计算下溢
logic_error //程序逻辑错误
domain_error //逻辑错误:参数对应的结果值不存在
invalid_error //逻辑错误:无效参数
length_error //逻辑错误:试图创建一个超出该类型最大长度的对象
out_of_range //逻辑错误:使用一个超出有效范围的值