第三章_字符串向量和数组_3.5数组

3.5 数组

数组的大小确定不变,不能随意向数组中增加元素。

3.5.1 定义和初始化内置数组

数组的声明形如a[d],其中a是数组的名字,d是数组的维度。维度说明的是数组中元素的个数,必须大于 0 。维度必须是一个常量表达式。

显式初始化数组元素

可以对数组的元素进行列表初始化,此时允许忽略数组的维度。

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

字符数组的特殊性

字符数组可以用字符串字面值对此类数组初始化。

char a1[] = {
    
    'c','+','+'};			// 列表初始化,没有空字符
char a2[] = {
    
    'c','+','+''\0'};		// 列表初始化,含有显式地空字符
char a3[] = "c++";					// 自动添加空字符
const char a4[6] = "Daniel";		// 错误,没有空间可存放空字符

不允许拷贝和赋值

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

理解复杂的数组声明

默认情况下,类型修饰符从右向左依次绑定,所以理解变量定义从右向左阅读。

理解数组声明的含义,最好的办法是从数组的名字开始按照由内向外的顺序阅读,先解析圆括号括起来的部分,然后观察右边,最后观察左边。

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

3.5.2 访问数组元素

数组的元素可以使用范围 for 语句或者下标运算符来访问。

使用数组下标的时候,通常将其定义为size_t类型。

// 以10分为一个分数段统计成绩的数量:0~9,10~19...100
unsigned secores[11] = {
    
    }; // 11个分数段,全部初始化为0
unsigned grade;
while(std::cin >> grade)
{
    
    
    if(grade <= 100)
    {
    
    
        ++secores[grade/10];
    }
}
// 输出secores所有的元素
for(auto i : secores)
{
    
    
    std::cout << i << std::endl;
}

检查下标的值

数组的下标是否在合理范围之内由程序员负责检查。

3.5.3 指针和数组

C++ 中使用数组的时候编译器一般会把它转换成指针。对数组的元素使用取地址符就能得到指向该元素的指针。 很多情况下,编译器会自动将数组的名字替换为一个指向数组首元素的指针。

std::string nums[] = {
    
    "one", "two", "three"}; // 数组的元素是 string 对象
std::string *p = &nums[0];	// p 指向 nums 的第一个元素
std::string *p2 = nums;		// 等价于 p2 = *nums[0]

在一般情况下,数组的操作实际上就是指针的操作。

当使用数组作为一个auto变量的额初始值,推断得到的类型是指针而非数组。当使用decltype时,返回的类型是数组。

int ia[] = {
    
    0,1,2,3,4,5,6,7,8,9};
auto ia2(ia);	// 此时 ia2 类型是 int *
ia2 = 42;		// 错误:不能将 int 值直接赋给指针
decltype(ia) ia3 = {
    
    0,1,2,3,4,5,6,7,8,9};	// ia3 的类型是数组
ia3 = p;	// 错误:不能用整型指针给数组赋值
ia3[4] = i;	// 正确:把 i 的值赋给某个元素

指针其实也是迭代器

指向数组元素的指针支持vectorstring迭代器的全部运算。

标准库函数 begin 和 end

定义在头文件iterator中,标准库的beginend函数与容器中的同名成员功能类似,不过这两个函数不是成员函数,要将数组作为它们的参数。

int ia[] = {
    
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // ia 是一个含有 10 个整数的数组
int *beg = begin(ia); // 指向 ia 首元素的指针
int *last  = end(ia); // 指向 ia 尾元素的下一位置的指针

使用beginend循环处理数组中的元素

// pbeg 指向 arr 的首元素,pend 指向 arr 尾元素的下一位置
int *pbeg = begin(arr), *pend = end(arr);
// 寻找第一个负值元素,如果已经检查完全部元素则结束循环
while(pbeg != pend && *pbeg >= 0)
    ++pbeg;

一个指针如果指向了某种内置类型数组的尾元素的”下一位置“,则具备与vectorend函数返回的与迭代器类似的功能。特别要注意,尾后指针不能执行解引用和递增操作。

指针运算

指向数组元素的指针可以执行stringvector的迭代器的所有运算,包括解引用、递增、比较、与整数相加、两个指针相减等。

constexpr size_t sz = 5;
int arr[sz] = {
    
    1, 2, 3, 4, 5};
int *ip = arr;			// 等价于 int *ip = &arr[0]
int *ip2 = ip + 4;		// ip2 指向 arr 的尾元素 arr[4]
int *p = arr + sz;		// 正确,arr 转换成指向它首元素的指针;p 指向arr 尾元素的下一位置,不要解引用
int *p2 = arr + 10;		// 错误,arr 只有 5 个元素,p2 的值未定义
auto n = end(arr) - begin(arr); // n 的值是 5,也就是 arr 中元素的数量
// 遍历数组中的元素
int *b = arr, *e = arr + sz;
while(b < e){
    
    
    ++b; // 使用 *b
}

解引用和指针运算的交互

指针加上一个整数所得的结果还是一个指针。假设结果指针指向了一个元素,则允许解引用该直指针。

int ia[] = {
    
    0,2,4,6,8}; // 含有 5 个整数的数组
int last = *(ia + 4); 	// 把 last 初始化成 8,也就是 ia[4] 的值
int last1 = *ia + 4;	// 等价于 ia[0] + 4

下标和指针

很多情况下使用数组的名字其实就是用一个指向数组首元素的指针。对数组执行下标运算其实就是对指向数组元素的指针执行下标运算:

int ia[] = {
    
    0,2,4,6,8};
int i = ia[2];		// ia[2] 等价于 *(ia + 2)
int *p = ia;		// p = &ia[0]
i = *(p + 2);		// 等价于 i = ia[2]
int *p = &ia[2];	// p 指向 ia[2]
int j = p[1];		// 等价于 *(p + 1),即 ia[3]
int k = p[-2];		// 等价于 ia[0]

注意,指针的内置下标运算符所用的索引值不是无符号类型。

3.5.4 C 风格字符串

尽量不使用 C 风格字符串。

字符串字面值是一种通用结构的实例,即是 C++ 从 C 继承而来的 C 风格字符串 (C-style character string)。C 风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法。按此习惯书写的字符串存放在字符数组中并以空字符'\0'结束。一般利用指针来操作这些字符串。

C 标准库 String 函数

以下定义在头文件cstring中的函数,可以用不操作 C 风格字符串。

C风格字符串的函数

传入此类函数的指针必须指向以空字符作为结束的数组。

比较字符串

比较标准库string对象的时候,使用普通的关系运算符和相等性运算符。但是如果将这些运算符用在两个 C 风格字符串上,实际比较的将是指针而非字符串本身:

string s1 = "A string example";
string s2 = "A different string";
if (s1 < s2)	// fasle, s1 > s2

const char cstr1[] = "A string examle";
const char cstr2[] = "A different string";
if (cstr1 < cstr2)	// 无定义:试图比较两个无关的地址

必须谨记:当使用数组的时候其实真正使用的是指向数组首元素的指针

要想比较两个 C 风格字符串需要调用strcmp函数

if (strcmp(cstr1, cstr2) < 0)	// 和 string 对象比较 s1 < s2 效果一样
    ...

目标字符串的大小由调用者指定

连接或拷贝 C 风格字符串需要使用strcat函数和strcpy函数,不过要想使用这两个函数,还必须提供一个用于存放结果字符串的数组,该数组必须足够大以便容纳下结果字符串及结尾的空字符。

// 如果我们计算错了 largeStr 的大小将引发严重错误
strcpy(largeStr, cstr1);	// 将 cstr1 拷贝到 largeStr
strcat(largeStr, " ");		// 在 largeStr 末尾添加一个空格
strcat(largeStr, cstr2);	// 把 cstr2 拼接到 largeStr 后面

对大多数应用来说,使用标准库string比使用 C 风格字符串更安全、更高效。

3.5.5 与旧代码的接口

混用 string 对象和 C 风格字符串

任何出现字符串字面值的地方都可以用以空字符结束的字符数组来替代:

  • 允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值
  • string对象的加法运算中允许使用以空字符结束的字符数组作为其中一个运算对象(不能两个运算对象都是);在string对象的复合赋值运算中允许使用以空字符结束的字符数组作为右侧的运算对象。

注意,上述性质反过来就不成立了:如果程序的某处需要一个 C 风格字符串,无法直接使用string对象来代替它。

为了能够用string对象直接初始化指向字符的指针,string提供了一个名为c_str的成员函数

char *str = s;	// 错误:不能用 string 对象初始化 char*
const char *str = s.c_str();	// 正确

如果执行完c_str函数后程序想一直都能使用其返回的数组,最好将该数组重新拷贝一份。

使用数组初始化 vector 对象

不允许使用一个数组为另一个内置类型的数组赋初值,也不允许使用vector对象初始化数组。但是在指明拷贝区域的首元素地址和尾后地址后,允许使用数组来初始化vector对象。

int int_arr[] = {
    
    0,1,2,3,4,5,6,7,8};
// ivec 中的元素是 int_arr 中对应元素的副本
vector<int> ivec(begin(int_arr), end(int_arr));
// 用于初始化 vector 对象的值也可能仅是数组的一部分
vector<int> sub_vec(int_arr + 1, int_arr + 4); // 拷贝元素的范围是 int_arr[1] ~ int_arr[3];

现代的 C++ 程序应当尽量使用vector和迭代器,避免使用内置数组和指针;应该尽量使用string,避免使用 C 风格的基于数组的字符串

猜你喜欢

转载自blog.csdn.net/jkhcx1990/article/details/113757244
今日推荐