数组和字符指针

什么是数组?
数组是一组类型相同元素的容器.数组中存放的对象没有名字,必须通过位置来访问.数组的大小确定不变, 一旦数组定义完成,不能再向数组中添加元素.

数组基本概念

数组是一种复合类型.数组的声明形如a[d], 其中a是数组的名字,d是数组的维度.维度说明了数组中元素的个数,因此必须大于0. 数组中元素的个数也属于数组类型的一部分,编译时数组的维度必须是已知的.

适用所有数组类型

  • 不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值。
  • 在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。当对数组名称使用decltype关键字时得到的是数组对象类型。
  • 对数组执行下标运算其实是对指向数组元素的指针执行下标运算
    • 内置的下标运算符不是无符号数,标准库类型如vector和string限定使用的下标必须是无符号数
  • 因为数组本身是对象,可以定义数组的指针和数组的引用

适用于字符数组

  • 当使用字符串常量对字符数组进行初始化时, 字符串数组最后一位会被加上一个’\0’空字符
    但是使用strlen()计算该数组的长度时,该字符串末尾的空字符不计算在内

  • 对字符数组进行列表初始化时,字符数组末尾没有空字符; 但是由于strlen()函数时是使用空字符来判断字符串结束的, 无法对这样的字符数组使用strlen()求长度

代码示例

字符数组初始化和求长度

  • 字符数组初始化有两种方式,列表初始化和字符串常量初始化
  • 字符串常量初始化时,字符串常量末尾的空字符也会拷贝到字符数组中去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void testCharArray() {
char a1[] = "danam";

// 计算c风格字符串的长度时,c风格字符串末尾的空字符不计算在内
cout << "strlen(\"danam\"): " << strlen("danam") << endl;

// a1的长度为6字节,字符串字面值的末尾是一个空字符,这个空字符也会像其他字符一样拷贝到字符数组中去
cout << "a1:" << a1 << " sizeof(a1): " << sizeof(a1) << " strlen(a1): " << strlen(a1)
<< endl; //a1:danam sizeof(a1): 6 strlen(a1): 5

// 对字符数组进行列表初始化,没有空字符
char a2[] = {'a', 'b', 'c'};
cout << "a2: " << a2 << " sizeof(a2): " << sizeof(a2) << endl;
// a2的长度为3字节;无法对a2使用strlen函数
// a2: abcdanam sizeof(a2): 3
// 因为a2末尾没有空字符作为结束标记,所以,输出时一直输出到遇到a1末尾的空字符才结束
}

运行结果

1
2
3
strlen("danam"): 5
a1:danam sizeof(a1): 6 strlen(a1): 5
a2: abcdanam sizeof(a2): 3

数组名和指针,迭代器

  • 数组名通常会自动转换为指向数组首元素的指针
  • 在表达式decltype(arr)时,不会发生自动转换,decltype(arr)的返回类型是一个数组类型,其元素类型和维度都与数组arr相同
1
2
3
4
5
6
7
8
9
10
11
void testCharArray1() {
//===============数组名和指针=================
int iarr[] = {1, 2, 3, 4, 5};
// ia是一个指针,在这里使用数组类型对象iarr时,编译器将其替换为一个指向数组首元素的指针
auto ia(iarr);
ia = &iarr[1];

// decltype(ia)是一个含有5个整形元素的数组对象,因此iarr2也是一个含有5个元素的数组
decltype(iarr) iarr2 = {22, 33, 444, 55, 99};
iarr2[2] = 1882;
}

运行结果

1
nice to meet you alice

指针运算

  • 对数组执行下标运算其实是对指向数组元素的指针执行下标运算。标准库类型使用的下标类型必须是无符号类型,内置的下标运算可以是带符号类型
  • 可以从一个指针加上或减去一个整数值,结果仍是指针
  • 指向同一个数组中元素(包括尾元素)的指针可以相减,两个指针相减的结果是一种名为ptrdiff_t的标准库类型。ptrdiff_t类型是一种带符号类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void testCharArray3() {
//=======================指针运算=====================
string sarr[5] = {"good", "day", "you", "know", "me"};

// sarr[2]是一个使用了数组名字的表达式,对数组执行下标运算其实是对指向数组元素的指针执行下标运算
string s1 = sarr[2]; //sarr转换为指向数组首元素的指针,sarr[2]得到(sarr+2)所指向的元素
string *p = sarr; //sarr转换为指向数组首元素的指针
string s2 = *(p + 2); //等价于sarr[2]
cout << "s1: " << s1 << endl;
cout << "s2: " << s2 << endl;
s2 = p[3];
cout << "s2: " << s2 << endl;

//=======只要指针指向的是数组中的元素(或者数组中尾元素的下一位置),都可以执行下标运算
//内置的下标运算符使用的不是无符号数,而标准库类型如vector和string限定使用的下标必须是无符号类型
string *p1 = &sarr[2];
string s3 = p1[2];
string s4 = p1[-2];
cout << "s3: " << s3 << endl;
cout << "s4: " << s4 << endl;
}

运行结果

1
2
3
4
5
s1: you
s2: you
s2: know
s3: me
s4: good

指针和迭代器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void testCharArray2() {
//================指针也是迭代器========================
string sarr[5] = {"nice", "to", "meet", "you", "alice"};
//指针e指向sarr尾元素的下一位置的指针,该位置并不存在,因此不能对该指针进行解引用和递增操作
string *e = &sarr[5];
for (string *b = sarr; b != e; b++) {
cout << *b << " ";
}
cout << endl;

//=====================标准库begin和end函数======================
int ia1[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *beg = begin(ia1); // 指向ia1首元素的指针
int *last = end(ia1); // 指向ia1尾元素的下一位置的指针
while (beg != last) {
cout << *beg << " ";
++beg;
}
cout << endl;
}

运行结果:

1
0 1 2 3 4 5 6 7 8 9

牛客网遇到的一些题

  1. 若有以下说明和语句,int c[4][5],(*p)[5];p=c;能正确引用c数组元素的是( )。

    A. p+1 B.*(p+3) C.*(p+1)+3 D.*(p[0]+2)

    解答:
    int (p)[n]; 因为()的优先级更高,所以p是一个指针,指向一个整形的一维数组,这个一维数组的长度是n,n也可以说是p的步长。也就是说执行p+1时,p要跨过n个整形数据的长度。
    如要将二维数组赋给一指针,应这样赋值:
    int c[4][5]; int (\
    p)[5]; // 定义一个数组指针,指向含5个元素的一维数组
    p = c; // 将二维数组c的首地址赋给p,也就是p = c[0] 或者 p = &c[0][0]
    (p[0] + 2); // 等价于\(*(p+0) + 2),表示a[0][2]