======数组、Vector 与字符串======
//第 3 章笔记//
----
====数组====
===Intro===
* 数组是一种类型,由多个相同对象组成的序列
==定义数组==
* 数组的类型与包含的对象类型不同:数组的类型由对象的**类型**和自身的**长度**组成
int a; //int
int b[10]; // int[10]
* 数组的长度必须是**常量表达式**
* 长度需要大于 0(负数是不行的,)
* 长度需要能转换为 ''size_t''(converted expression,比如浮点就不行)
int x;
std::cin >> x;
//不合法
//类型由编译器决定,但长度要在运行期才能得到
//编译器无法推断出b[x] 的类型
int b[x];
//合法
constexpr short y = 3;
int c[y];
* C++ 标准不支持运行期数组长度;但在编译器实现中, gcc 和 clang 都可以通过去掉 ''--pedantic-errors'' tag 来支持 variable length array。但这样做是有风险的,因为该功能是 complier denpend。除此之外,某些必须在编译器完成的功能也不能使用该类类型做为参数,比如 ''decltype(b)''
==数组的初始化==
* 默认初始化:元素都会按照默认的方式来创建(根据C++ 的方式)
* 聚合初始化
* 编译器可以自行推断数组长度
* 给出的元素不足,剩余元素会进行默认初始化
int b[3]; //default int
int c[3] = {1, 2, 3}; // int[3], aggregate init
int cn[] = {1,2, 3}; //int[3]
int d[3] = {1,2} // partially aggregate init, 1,2,0
* 不能使用 ''auto'':auto 会将 ''{}'' 视作 initialization_list
* 不能复制数组:
* 初始化不能通过赋值构造
* 拷贝赋值的结果是拷贝的指向数组首元素的指针
* 数组作为右值会退化为指针。
* 使用指针访问数组性能更好
==字符串数组的特殊性==
//char[6] 简化定义写法,会自动添加 ''\0'' 表示结束
char str[] = "hello";
//char[5] 完整定义写法,但不会为末尾添加 ''\0''
char str[] = {‘h’, 'e', 'l', 'l', '0'};
===数组的复杂声明===
* 指针为元素的数组
//a[3] 是有3个元素为指针的数组
int* a[3];
* 指向数组的指针
//注意括号
//a 是指针,指向 int[3]
int (*a) [3];
* 数组的引用
//a 是引用,指向 int[3]
int b[3];
int (&a) [3] = b;
引用不支持聚合初始化。C++ 中不支持元素为引用的数组。从概念上来说,引用不是对象。数组要求元素使对象,因此没有元素为引用的数组。
===数组中的元素访问===
* 数组中的第一个元素下标为 0
* 通过下标操作符访问:''a[0]''
==数组访问的细节==
* C++ 中,左值的含义被修改为了 //locator value//,也就是带地址信息的值(而不是按等号的左右来决定)。
* 数组是左值,但不能放在等号左边
* 在等号右部使用(作为右值)时,数组的类型会隐式转换为指向**数组头元素**的指针。也就是说,指针也可以使用 ''[]'' 操作符
* 当使用 ''x[y]'',且 ''x'', ''y'' 都是 Built-in 类型时,''x[y]'' 被解析为 ''*(x+y)'',也就是转换为指针,再解引用。
* 需要注意下标越界
int a[3] = {1,2,3};
auto b = a;
//int(&)
decltype(b)
//int*
a;
//int*
b;
//int, *(b+0)
b[0]
可以使用 decltype 来判断表达式是否是左值。
====数组和指针====
===数组到指针的隐式转换===
* 使用数组的时候通常数组会被转化为指针
* 会丢失一部分信息(比如长度,编译器无法检查,这也是下标越界的原因)
// decay, b 是 int* 类型
int a[3];
auto b = a;
//不会 decay 的范例
//decltype 返回的是数组的类型
// int[3]
decltype(a);
// int size * 3
sizeof(a);
* 可以通过声明避免隐式转换
// b 是 Int(&)[3] 类型
// 此时长度信息没有丢失
auto& b = a;
* 不要使用 ''extern'' 指针声明数组
//声明数组
//不能使用 extern 指针
extern int array[4];
//illegal
//runtime error
extern int* array;
\\
//**为什么不能这么做?**//\\ \\
很多情况下,长度信息需要反复的修改。如果在声明的时候带上长度信息,那么每次更改 array 的长度都必须同时去声明中改变 array 的长度。因此很多情况下会使用指针替代数组。但 ''extern'' 不能直接使用指针代替数组。来看看下面的例子:
//source.cpp
int array[4] = {1,2,3,4};
void fun()
{
std::cout << array << std::endl;
}
//main.cpp
extern int* array;
void fun();
int main()
{
fun();
std::cout << array << std::endl;
}
// output
// 两个 array 的输出不一样
0x7ff718ee3000
0x200000001
\\
//**问题出在哪里?**//
* 编译阶段没有问题,array 会被翻译为 ''*(array + step)''
* 链接过程中:无论是 main.cpp 和 source.cpp 中的 array 是没有类型的,只有名称。这是因为类型只在编译期有效,如果加了更多的类型信息可能会导致不同翻译单元之间的链接问题。
* 运行过程中:
* source.cpp 中的打印结果是正确的,因为 source.cpp 中的 array 代表的数组可以被编译器识别,此时编译器认为是指向 array 头元素的指针
* main.cpp 中的 array,实际上不是一个地址:
* ''array[4]'' 中存储的类型是 int(32字节)一个16进制数为 $2^4$,占 4 bits,那么每个数组成员都需要 32 / 4 = 8 个 16 进制数来表示,比如 1 就是 ''00 00 00 01''
* 按小端法,int 会从低地址到高地址保存: ''01 00 00 00''
* 数组中的数是连续存储的,因此该数组在内存中表现为: ''01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00''
* 而此时我们此时以指针的方式来解析该段数据,那么就会将前 8 个子节的内容拼成一个类似地址的内容,也就是 ''0x 2 00 00 00 01''。
* 该内容并不是 array 的首元素地址,而是 ''01 00 00 00 02 00 00 00'' 按内存地址的方式解析出来的结果。这个结果反应的是数组的内容,是固定的,
* 可见数组和指针还是存在区别的
//**应该怎么做?**//
extern int array[];
这么写合法。''int array[]'' 是数组的合法声明。定义已经在 source.cpp 中完成了。
* 该方式被称为 //Unkonwn Bounded Array Declaration//。
* int array[] 被称为 incomplete type,这种类型大多数用于共享定义的场景中
==指向数组开头和结尾的指针==
* 开头指针:头元素
* 结尾指针:尾元素**后一位**
计算的两种方法:
int a[3] = {1,2,3};
//开头指针
a; //数组名即为指向数组首地址的指针
&(a[0]) // 取数组首元素的地址(注意括号)
std::begin(a); std::cbegin(a); //标准库提供的取地址函数,int* 和 const int*
//结尾指针
a + 3;
&(a[3]);
std::end(a); std::cend(a);
需要注意的是,''std::begin()'' 与 ''std::end()'' 适用于**数组类型**而不是指针类型,任何导致数组信息的丢失(转换,Unkonwn bounded)都会导致调用失败:
b = a;
//error, begin() & end() are not applicable to a pointer.
std::begin(b); std::end(b);
//之前的共享 array 也不能使用 begin 和 end,因为边界未知
extern int array[];
std::begin(array); std::end(array);
对于 incomplete type 的获取,如需使用
''std::begin()'' 和 ''std::end()'',需要在声明的时候声明为 complete type,也就是提供其数组的长度:
//ok
extern int array[4];
std::begin(array); std::end(array);
std::begin(); std::end(); 是泛型函数,可以应用到各种类型的容器中。
===指针的其他算术运算===
int a[3] = {1,2,3};
auto ptr = a;
auto ptr2 = a;
//指针的前进
ptr = ptr + 1;
//比较
ptr == ptr2;
//关系运算(不推荐,除非两个指针在同数组中)
ptr > ptr2;
//求距离
ptr2 - ptr
===数组的其他操作===
==求数组元素个数==
* ''sizeof()'':使用 sizeof 获取数组总大小,再除以元素的宽度
* ''std::size()'':使用标准库方法
* ''std::end() - std::begin()''
/* 必须都是对数组类型进行操作 */
int b[10];
//sizeof,[C method]
sizeof(b) / sizeof(int);
//std::size, [recommand]
std::size(b);
//end - begin, [run time method],通常使用 const 版本。
std::end(b) - std::begin(b);
==遍历==
int c[3] = {1,2,3};
//使用 std::size() 和 while
size_t index = 0;
while(index < std::size(c))
{
std::cout << c[index] << '\n';
index++;
}
//cbeign, cend
auto bPtr = std::cbegin(c);
while(bPtr != std::cend(c))
{
std::cout << *bPtr << '\n';
bPtr++;
}
//range for ,语法糖,基于 cbegin / cend 实现
for (auto elem : c)
{
std::cout << elem << '\n';
}
==C 字符串==
* 本质是数组
* 有额外的函数来支持该数组 ''''
====多维数组====
* 本质上是元素为数组的数组
* 在内存中是连续排列的,但按照元素的个数分组
* 二位数组类似**行优先**矩阵,左边是行,右边是列
===多维数组的理解===
// 从左到右看
// (x[3]) 是元素,元素类型为 Int[4]
int x[3][4];
// (y[3]) 是元素,元素类型为 int[4][5]
Int y[3][4][5];
{{ :cs:programming:cpp:cpp_primer:mult_array.svg?250 |}}
===多维数组的初始化===
* 默认初始化:遵从C++ 的默认初始化
* 聚合初始化:
//由于在内存中是连续的,因此可以使用 list 初始化
//初始值不够的所有元素都会进行默认初始化
//按位初始化
//1,2,3,4,0,0,0,0,0,0,0,0,0
int x[3][4] = {1,2,3,4};
//分组初始化
int y[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
//分组初始化的默认初始化处于分组内部
int z[3][4] = {{1,2,3}, {4,5,6,7}, {8,9,10,11}};
//输出 0
std::cout << z[0][3];
//输出 4
std::cout << z[1][0];
//如果需要自动推断,必须指定元素的类型
//只能省略最高位
int a[][] = {1,2,3,4}; // error
int a[][] = {{1,2,3,4}}; // error
int a[][4] = {1,2,3,4}; // int[1][4]
===多维数组的遍历===
* 多维数组遍历需要多重循环
int x [3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
//range for
//注意所有外层的 auto 都需要以引用的方式将数组类型传递给内层循环
//否则,内层循环只会得到一个指向外层数组的指针
//内层循环中 range for 需要使用数组的头指针作为起点,头指针 + 内层数组的长度作为终点进行循环
//如果传入的是指针,长度信息丢失,那么将无法进行循环
for (auto &row : x)
{
for (auto col : row)
{
std::cout << col << " ";
}
std::cout << std::endl;
}
//while loop
//注意 std::size() 的参数不同
//遍历的顺序:x[0][0-3], x[1][1-3], x[2][1-3]。
//推荐行优先遍历,可以减少 cache miss。
size_t outter = 0;
while (outter < std::size(x))
{
size_t inner = 0;
while (inner < std::size(x[inner]))
{
std::cout << x[outter][inner] <<" ";
inner++;
}
outter++;
std::cout << std::endl;
}
//纯指针遍历
//与之前相同,cbegin 和 cend 需要数组而不是指针,因此内循环需要解引用
auto OutterP = std::cbegin(x);
while (OutterP != std::cend(x))
{
auto InnerP = std::cbegin(*OutterP);
{
while(InnerP != std::cend(*OutterP))
{
std::cout << *InnerP << " ";
InnerP++;
}
std::cout << std::endl;
}
OutterP++;
}
===多维数组与指针===
* 多维数组也可以转化为指针,但只有最高维会进行转换
* 对于外围数组,如果指针要有意义,其指向的单位一定是外围数组的元素
* 比如下面的例子,外围数组有3个元素,如果移动指针 ''x+1'',则移动的单位实际上是一个包含 4 个 int 的数组
int x[3][4];
// ptr int(*)[4]
auto ptr = x;
//ptr2 int(*)[4][5]
int y[3][4][5];
auto ptr2 = y;
==使用类型别名声明多维数组的指针==
using A2 = Int[4][5];
int x[3][4][5];
A2* ptr = x; //x 指向 int[4][5]
注意类型别名会改变维度的优先级:
using A = int[4];
//如果这里使用 A 定义数组,那么结果会是 [4][3] 而不是 [3][4]
A z2[3]; //等价与 int z2[4][3];
====Vector====
* 标准库提供的序列容器(类模板)
* 可复制,可动态更改个数
//使用需要包含头文件 vector
#include
//类型是 vector
std::vector x = {1,2,3};
//可复制
std::vector y;
y = x;
===Vector 的初始化===
* 默认初始化为 0 个元素
* 支持各种初始化
//聚合初始化,x 的元素为 1,2,3
//使用 brace
std::vector x = {1,2,3};
//init with default element count
//使用panthesis
//y 是 3个元素的 vector,每个元素都被默认初始化为 0
std::vector y(3);
//count + value
//z 中有 3 个元素 都是 1
std::vector z(3,1);
===Vector 的常用成员函数===
//计算 vector 的大小
x.size();
// 查看 vector 是否为空
x.empty();
// 添加元素(运行期)
x.push_back(4);
// 删除元素
x.pop_back();
// index 访问
x[2]; //无安全保证
x.at(2); //越界访问会抛出异常
//成员 begin & end,指向第一个元素和最后一个元素
auto beg = x.begin();
auto e = x.end();
===Vector 的遍历===
//成员函数 + 迭代器(类指针)
//注意 begin / end 的结果不再是指针,是 iterator
auto vBeg = x.begin();
auto vEnd = x.end();
while(vBeg != vEnd)
{
std::cout << *vBeg << " ";
vBeg++;
}
//range for
for (auto elem : x)
{
std::cout << elem << " ";
}
===迭代器===
* std 容器中,通常使用迭代器模拟代替指针
* 迭代器可以:
* 解引用
* 下标访问会产生 *(x + index) 的行为
* 可以移动,相减(必须指向同一个 vector)
==Vector 的比较==
* 位数相同且元素相同,则相等
* 如果一长一短,短 vector 与长 vector 对应位置内容相同,则长的较大
* 否则按位比较,找到的第一组不相同元素中,较大元素所在的 vector 较大
===Vector 的其他细节===
* 改变 vector 元素数量可能会导致迭代器失效
* Vector 支持多维
//元素是 Int vector 的 vector
std::vector> x;
* 支持取地址并调用运算符(''->'')
std::vector* ptr = &x;
ptr->size(); //equal *(ptr).size()
* size() 返回的是 ''size_type''
====std::string====
* 特化自 ''std::basic_string''
* 可复制,可改变字符串长度
===string 的使用方法===
//需要引入头文件
#include
//初始化
std::string myStr = "helleworld";
//使用多次复制目标字符作为初始值
//结果是 'aaa'
std::string myStr2(3, 'a');
//拷贝初始化
std::string myStr3 = myStr;
std::string myStr4(myStr);
//比较尺寸size & empty
//比较 == > <,比较规则与 vector 同,按字符为单位比较
//赋值
std::string newStr;
newStr = myStr;
//拼接
newStr = myStr + myStr2;
newStr = myStr + "hellWorld";
//string + 的重载左边要求对象类型是 std::string,所以 c-string 不能放到加号左边
//error
newStr = "hello" + myStr;
//索引
myStr[0];
==转化 std::string 到 c-string==
//返回指向一个 Null teminated(\0) 的 const char* 指针
myStr.c_str();