前言
该系列是《C++Primer第五版》的笔记,包含本人认为值得记录和整理的主要的知识点,并不是全部内容,也不是具体的内容。
该系列文章的作用应该是作为复习或预习的参考,有哪些知识点忘记或想学,可以大致浏览下该文章,然后再去书中寻找详细解答。(本系列文章基本是按书本顺序罗列的知识点,便于大家去书中寻找)
所以看该文章前,需要有一定的C++基础,否则阅读起来可能有困难。
本文大致整理了第三章的知识点,属于C++比较基础的内容,没有难点,只有内置数组和指针的搭配使用起来比较麻烦。
链接目录
- 第二章:变量与基本类型
- 第三章:字符串、向量和数组
- 第四章:表达式
- 第五章:语句
- 第六章:函数
- 第七章:类
- 第八章:IO库
- 第九章:顺序容器
- 第十章:泛型算法
- 第十一章:关联容器
- 第十二章:动态内存
- 第十三章:拷贝控制
- 第十四章:重载运算与类型转换
- 第十五章:面向对象程序设计
- 第十六章:模板与泛型编程
- 第十七章:标准库特殊设施
- 第十八章:用于大型程序的工具
- 第十九章:特殊工具与技术
命名空间的using声明
声明形式:using namespace::name
例如,如果不进行命名空间声明,对于标准输入输出,需要加上std::
//如果没有声明命名空间,需要显式注明
std::cout << "hello world" << std::endl;
//声明命名空间后,可以省去显式注明
using std::cout;
using std::endl;
cout << "hello world" << endl;
//如果是导入整个命名空间,则需要添加namespace
using namespace std;
cout << "hello world" << endl;
关于命名空间在十八章有更详细的介绍。
标准库类型string
定义在头文件string
中,包含在命名空间std
中。
多种初始化方式:
- 默认初始化为空字符串:
string s;
- 初始化为字符串:
string s = "hello";
- 拷贝初始化:
string s(str);
,s的内容从str那拷贝 - 带参初始化:
string s = string(10, 'c');
,内容包含10个c
注意string.size()
返回的是string::size_type
类型的值,是一种无符号数,如果和有符号数混合使用,可能会有预期外的错误。
例如:s.size() < n
,如果n是一个负数,看作无符号数则是一个非常大的数,使该条件判断几乎永远为真。
关于string
对象的各种操作在书中有表格罗列,本文就不再花费篇幅进行罗列了。
标准库类型vector
vector
是一种容器,类似于动态数组,定义在头文件vector
中,包含在命名空间std
中。
多种初始化方式:其中T
为数据类型
- 默认初始化为空数组:
vector<T> v;
- 拷贝初始化:
vector<T> v(v2)
,v中内容为v2的拷贝 - 带参初始化:
vector<T> v(n, val);
,v中包含n个值为val的元素 - 列表初始化:
vector<T> v{a, b, c};
,v中包含a、b、c
向vector
添加元素:v.push_back(val);
,在v的末尾添加值为val的元素
其他vector
操作:
v.empty()
:当v空时为真v.size()
:返回v中元素个数v[n]
:返回第n个位置上元素的引用
迭代器
迭代器的使用类似于指针,但实际上是一种标准库内置的类。
对于有迭代器的类型,都包含begin
和end
成员,其中begin
指向第一个元素,end
指向尾后元素(实际上不存在内容,仅是个标记)。
例如上述的vector
:获取v的迭代器auto b = v.begin(), e = v.end();
迭代器运算:
*iter
:返回迭代器iter
所指元素的引用iter->mem
:等价于(*iter).mem
iter++
:指向下一元素iter+n
:向后移动n个元素,可能越界iter--
:指向上一元素iter-n
:向前移动n个元素,可能越界iter1 - iter2
:迭代器所指元素的距离iter1 == iter2
:当指向相同元素时为真
上述auto
可能会让有些人感到困惑,想明白迭代器的具体类型是什么。实际上和引入auto
的初衷一样,我们通常知道如何使用一个迭代器,但对于迭代器的准确类型,有时候是不清晰的。
例如,对于一个vector<int>
的迭代器,其准确类型是vector<int>::iterator
,这样对于编写程序不方便,我们通常只需要知道怎么使用它们即可,无需明白它们的具体类型。
数组
对于C++的内置数组,数组大小和维度的参数必须是常量表达式,即在编译期间就能确定的参数。
对于所声明数组的元素的初始化操作,和内置类型的变量初始化一致(本系列第二章有提及)
多种初始化方式:
- 默认初始化:
int arr[10];
- 列表初始化:
int arr[3] = {1, 2, 3};
- 忽略参数的列表初始化:
int arr[] = {1, 2, 3};
编译器会自动判断数组大小 - 缺少元素的列表初始化:
int arr[5] = {1, 2, 3};
剩下元素执行默认初始化
关于字符数组的特殊情况,我们知道C++中字符串以\0
结尾,代表字符串的结束,所以需要多一个位置给\0
。
于是有以下特殊情况:
char arr[] = "C++";
,会自动添加\0
在结尾char arr[3] = "C++";
,错误,没有空间存放\0
char arr[4] = {'C', '+', '+', '\0'};
,手动添加\0
另外,数组不允许拷贝和赋值,int arr2[] = arr;
将是错误的。
复杂数组声明,这里涉及到表达式的优先级,在下一章有详细介绍。
int *pt[10];//含有10个整型指针的数组
int (*ptr)[10] = &arr;//ptr指向一个含有10个整型元素的数组
int (&arrRef)[10] = arr;//arrRef引用一个含有10个整型元素的数组
指针和数组:在大多数表达式中,数组名实际上会被隐式的转化为指向数组首元素的指针。
int arr[] = {1, 2, 3};
int *p = arr;//p指向arr首元素
p++;//p向后移动一个元素
数组的下标运算[]
:实际上是对指针进行解运算
int arr[] = {1, 2, 3};
int *p = arr + 2;//指向尾元素
int i = p[-1];//等价于*(p-1),此时指向第二个元素
现代C++程序除了特殊场景外,应该尽量使用vector
而不是内置数组。
C风格字符串
由于历史原因,C++兼容C的代码,所以需要兼顾C风格的字符串,即char
数组类型,这里需要注意\0
和数组大小。
//例如
char str[] = {'C', '+', '+'};
//由于str没有空字符结尾,可能会一直沿着内存空间寻找空字符
cout << strlen(str) << endl;//strlen用来计算字符数组的长度
//以下情况必须保证largeStr必须有足够大的空间,否则会引发严重错误
strcpy(largeStr, s);//把s拷贝给largeStr
strcat(largeStr, s);//把s连接到largeStr后面
多维数组
多维数组的初始化:
int arr[3][3] = {
{0, 1, 2},
{3, 4, 5},
{6, 7, 8}
};
//等价于
int arr[3][3] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
//对于缺值的初始化,每行未定义的值为初始值
int arr[3][3] = {
{1},
{2},
{3}
};
int arr[3][3] = {1, 2, 3};//对于该种缺值的初始化,只有第一行被定义了
指针和多维数组:
int arr[3][3];
int (*p)[3] = arr;//p指向含有三个元素的数组
p = &arr[2];//p指向arr的第三个元素(第三行)
//另外,圆括号不可省略
int *p[4];//含有四个整型指针的数组
int (*p)[4];//指向含有四个元素的数组的指针