目录

数组、Vector 与字符串

第 3 章笔记


数组

Intro

定义数组

int a; //int
int b[10]; // int[10]

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)
数组的初始化

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

  • 数组作为右值会退化为指针。
  • 使用指针访问数组性能更好
字符串数组的特殊性

//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++ 中不支持元素为引用的数组。从概念上来说,引用不是对象。数组要求元素使对象,因此没有元素为引用的数组。

数组中的元素访问

数组访问的细节

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 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

问题出在哪里?

应该怎么做?

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(b); std::end(b);
对于 incomplete type 的获取,如需使用 std::begin()std::end(),需要在声明的时候声明为 complete type,也就是提供其数组的长度:
//ok
extern int array[4];
std::begin(b); std::end(b);

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

数组的其他操作

求数组元素个数

/* 必须都是对数组类型进行操作 */
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];

多维数组的初始化

//由于在内存中是连续的,因此可以使用 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++;
}

多维数组与指针

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>
//类型是 vector<int>
std::vector<int> x = {1,2,3};
//可复制
std::vector<int> y;
y = x;

Vector 的初始化

//聚合初始化,x 的元素为 1,2,3
//使用 brace
std::vector<int> x = {1,2,3};

//init with default element count
//使用panthesis
//y 是 3个元素的 vector,每个元素都被默认初始化为 0
std::vector<int> y(3);
//count + value
//z 中有 3 个元素 都是 1
std::vector<int> 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 << " ";
    }

迭代器

Vector 的比较

Vector 的其他细节

//元素是 Int vector 的 vector
std::vector<std::vector<int>> x;

std::vector<int>* ptr = &x;
ptr->size(); //equal *(ptr).size()

std::string

string 的使用方法

//需要引入头文件
#include <string>
//初始化
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();