0%

Cpp基础(3)数组

数组

数组是一种类似于标准库类型vector的数据结构,与vector相似的是,数组也是存放类型相同的对象的容器,这些对象需要通过其所在位置访问;与vector不同的是,数组的大小确定不变,不能随意向数组中增加元素。

定义和初始化内置数组

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

1
2
3
4
5
6
constexpr unsigned sz = 42;   //常量表达式
int arr[10];
int *parr[sz];

unsigned cnt = 42; // 不是常量表达式
string bad[cnt]; // 错误:cnt不是常量表达式

默认情况下,数组的元素被默认初始化。定义数组的时候必须指定数组的类型,不能用auto关键字由初始值的列表推断类型。数组的元素应为对象,因此不存在引用的数组。

显式初始化数组元素

可以对数组的元素进行列表初始化,如果没有指明维度,编译器会根据初始值的数量计算并推测出来;若指明了维度,那么初始值的总数量不应该超出指定的大小;如果维度比提供的初始值数量大,则剩下的元素被初始化成默认值。

1
2
3
4
5
6
const unsigned sz = 3;
int ial[sz] = {0, 1, 2};
int a2[] = {0, 1, 2};
int a3[5] = {0, 1, 2};
string a4[3] = {"hi", "bye"};
int a5[2] = {0, 1, 2}; //错误

字符数组的特殊性

当使用字符串字面值对字符数组初始化(只可以在数组并初始化的时候)时,一定要注意字符串字面值的结尾处还有一个空字符。不能用赋值语句将一个字符串常量或字符数组直接赋给另一个数组。

1
2
3
4
5
6
7
8
9
10
11
char a1[] = {'C', '+', '+'};          //列表初始化,没有空字符
char a2[] = {'C', '+', '+', '\0'}; //列表初始化,含有显式的空字符
char a3[] = "C++"; //用字符换字面值初始化,自动添加表示字符串结束的空字符
const char a4[6] = "Daniel" //错误:没有空间放空字符

str1[] = "China"; //错误
str1 = "China"; //错误
str2 = str1; //错误

//利用二维数组存储多个字符串
char weekday[7][11] = {"Sunday", "Monday","Tuesday", "Wednesday", "Thursday", "Friday","Ssturday"};

不允许拷贝和赋值

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

复杂的数组声明

数组能存放大多数类型的对象,可以定义一个存放指针的数组;又因为数组本身是对象,所以允许定义数组的指针及数组的引用。默认情况下,类型修饰符从右向左依次绑定。就数组而言,从数组的名字开始由内向外阅读更容易理解。

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

练习

3.27 设txt_size是一个无参数的函数,它的返回值是int。下列哪些定义是非法的?为什么?

1
2
3
4
5
unsigned buf_size = 1024;
int ia[buf_size]; //非法的,因为buf_size不是一个常量表达式
int ia[4*7-14]; //正确,因为4*7-14是一个常量表达式
int ia[txt_size()]; //非法的,因为txt_size没有被定义为常量表达式 constexpr
char st[11] = "fundamental" //非法的,因为用字符串字面值初始化,没有空间存放空字符

访问数组元素

与标准库类型vectorstring 一样,数组的元素也能使用范围for 语句或下标运算符来访问。数组的索引从0开始。

数组下标通常定义为size_t类型,size_t是一种机器相关的无符号类型,在cstddef头文件中定义。

1
2
3
4
5
6
7
//以10分为一个分段统计成绩的数量:0~9.10~19,...,90~99,100
unsigned scores[11] = {}; //列表初始化,初值为0;若不初始化,在函数内不执行默认初始化。
unsigned grade;
while (cin >> grade) {
if (grade <= 100)
++scores[grade/10];
}

vectorstring 一样,当需要遍历数组的所有元素时,最好的办法是使用范围for语句。

1
2
3
4
//对于scores中的每个计数值输出当前的计数值
for (auto i : scores)
cout << i << " ";
cout<<endl;

必须要检查数组下标的值在合理范围内,下标越界会产生缓冲区溢出。

练习

3.31编写一段程序,定义一个含有10个int的数组,令每个元素的值就是其下标值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
include<iostream>
using namespace std;

int main(){
const int sz = 10;
int a[sz];

for(int i = 0; i < sz; i++)
a[i] = i;
for(auto val: a)
cout << val << " ";
cout<<endl;

return 0;
}

3.32 将上一题创建的数组拷贝给另外一个数组,利用vector重写程序,实现类似的功能。

//如果要把数组的内容拷贝给另外一个数组,不能直接对数值使用赋值运算符,而应该逐一拷贝数组的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
include<iostream>
using namespace std;

int main(){
const int sz = 10;
int a[sz], b[sz];

for(int i = 0; i < sz; i++)
a[i] = i;
for(int j = 0; j < sz: j++)
b[j] = a[j];

return 0;
}

//用vector重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<iostream>
#include<vector>
using namespace std;

int main(){
const int sz = 10;
vector<int> vInt, vInt2;

for (int i = 0; i < sz; i++)
vInt.push_back(i);
for (int j = 0; j < sz; j++)
vInt2.push_back(vInt[j]);
for (auto val: vInt2)
cout << val << " ";
cout<<endl;

return 0;
}

例子:

输出100以内的所有素数。

一种思路:让2,3,4,5,…,c中的每个数自我相加多次,来获得100之内的所有合数,筛掉合数之后就得到素数。若n为合数,则n的最小正因数c满足:

循环结构的N-S图:

QQ图片20200126204152

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<iostream>
#include<cmath>
using namespace std;
int main(){
bool a[100] = {0};
int sum = 0;
for(int i=0; i<100; i++)
a[i] = 0;
for(int i=2; i<sqrt(100.0); i++){
sum = i;
if(a[sum]==0){
while(sum<100){
sum = sum + i;
a[sum] = 1;
}
}
}
for(int i=0; i<100; i++)
if(a[i]==0)
cout<<i<<" ";
return 0;
}

多维数组

严格来说,C++中并没有多维数组,通常所说的多维数组其实是数组的数组。按照由内而外的顺序阅读。

多维数组初始化的几种方式:

1
2
3
4
int ia[3][4] = { {0,1,2,3}, {4,5,6,7}, {8,9,10,11} };  //每一行分别用花括号括起来
int ia[3][4] = {0,1,2,3,4,5,6,7,8,9}; //不标识每行的花括号
int ia[3][4] = { { 0 }, { 4 }, { 8 } }; //显示地初始每行的首元素
int ia[3][4] = {0,1,2,3}; //只显示地初始化第1行

多维数组的下标引用:如果表达式含有的下标运算符和数组的维度一样多,该表达式的结果是给定类型的元素;如果表达式含有的下标运算符数量比数组的维度小,则表达式的结果将是给定索引处的一个内层数组。

例子:

某学校有1000位老师,分布在20个不同的学院中,每个学院最多有12个系,请你编写一个程序,输入每位老师的所在院、系的编号(院编号1-20,系编号1-12),打印出各个系老师的数量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
#include<iomainp>
using namespace std;
int main(){
int teacher[21][13];
int school,department, i, j;
char name[30];

for(i=0; i<1000; i++)
{
cin>>name>>school>>department;
teacher[school][department]++;
}
for(i=0; i<21; i++)
for(j=1; j<13; j++)
cout<<setw(4)<<teacher[i][j];
cout<<endl;
return 0;
}

指针和数组

数组的地址

在C++语言中,指针和数组有非常紧密的联系。数组名代表数组首元素的地址:数组名是指向数组第一个元素的指针。对于数组a[10],数组名a代表数组a[10]中第一个元素a[0]的地址,即a与&a[0]等价。需要注意的是,a是地址常量,不是变量,不能给a赋值

1
2
3
4
5
int a[4] = {10,11,12,13};
cout << a << endl; //输出a[0]的地址 0017F754
cout << *a << endl; //输出a[0]的值 10
cout << &a[0] << endl; //0017F754
cout << a[0] << endl; //10

若a是指向数组第一个元素的指针,即a相当于&a[0]

  • &a是”指向数组“的指针,&a+1将跨越16个字节,&a相当于管辖范围”上升“了一级;

  • a是数组的第一个元素a[0],即 a等价于a[0], *a相当于管辖范围“下降”了一级。

在一些情况下数组的操作实际上是指针的操作。当使用数组作为一个auto变量的初始值时,得到的类型是指针而非数组。当使用decltype关键字时上述转换不会发生,decltyoe(ia)返回的类型是由整数构成的数组。

1
2
3
int ia[] = {0,1,2,3,4};
auto ia2(ia); //相当于 auto ia2(&ia[0]); ia2是一个整型指针
decltype(ia) ia3 = {0,1,2,3,4}; //ia是含有整数的数组

C++11新标准引入了beginend函数,定义在iterator头文件中,这两个函数与容器中的两个同名成员功能类似,但由于数组不是类类型,因此这两个函数不是成员函数,使用时需要将数组作为它们的参数。

1
2
3
int ia[] = {0,1,2,3,4};
int *beg = begin(ia);
int *last = end(ia);

利用指针变量引用数组元素

1
2
3
4
5
6
7
int a[10], *pointer;
pointer = a; //等价于 pointer = &a[0];
cout << pointer+i; //等价于a+i;等价于&a[i];
cout << *(pointer+i);//等价于*(a+i);等价于a[i];
cout << pointer[i]; //等价于*(pointer+i);

int *p = &a[0];

需要注意的是:a++是没有意义的,但p++会引起p的变化。p可以指向数组最后一个元素以后的元素,称为尾后指针,就像尾后迭代器,尾后指针不能执行解引用和递增操作。指针做加减运算时一定要注意有效的范围

1
2
3
4
5
6
int a[5];
int *iPtr = &a[1];
iPtr--; //iPtr指向a[0]
*iPtr = 3; //a[0]=3
iPtr--; //iPtr指向a[-1].dangerous
*iPtr = 6; //damage

根据运算符的优先级有:

  • *++p相当于a[++i],先将p自加,再做*运算。

  • *--p相当于a[--i],先将p自减,再做*运算。

  • *p++相当于a[i++],先做*运算,再将p自加。
  • *p--相当于a[i--],先做*运算,再将p自减。

例子:

1
2
3
4
5
6
7
8
9
//使用指针代替数组下标
int main(){
int a[10], i, *p = a;
for(i = 0; i<10; i++)
cin >> *p++;
for(p--; p>=a; )
cout << setw(3) << *p--;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//倒置数组元素
//输入:1 2 3 4 5 6 7 8 9 0
//输出:0 9 8 7 6 5 4 3 2 1
int main(){
int a[10], *p = NULL, *q = NULL, temp;
for(p = a; p<a+10; p++)
cin >> *p;
for(p = a, q = a+9; p<q; p++, q--)
{
temp=*p; *p=*q; *q=temp;
}
for(p = a; p<a+10; p++)
cout << setw(3) <<*p;
return 0;
}

二维数组的地址

数组名相当于指向数组第一个元素的指针。

  • *a等价于a[0],相当于a下降了一级;

  • &a表示“指向二维数组”的指针,相当于上升了一级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main(){
int a[3][4]={ {1,2,3,4},{5,6,7,8},{9,10,11,12} };

//a是指向数组第一个元素的指针,而数组的第一个元素是一个含有4元素的数组,即a是指向一维数组的指针, //a相当于一个“包含4个int型元素的一维数组”的地址
cout << a << endl; //0x0013FF50
cout << &a[0] << endl; //0x0013FF50

cout << a+1 << endl; //0x0013FF60 a+1将跨越16个字节
cout << &a[0]+1 << endl; //0x0013FF60

//*a是数组的第一个元素,而数组的第一个元素是一个数组,即*a是一个指向整数的指针,
cout << *a << endl; //0x0013FF50
cout << a[0] << endl; //0x0013FF50
cout << &a[0][0] << endl; //0x0013FF50

cout << *a+1 << endl; //0x0013FF54 *a+1将跨越4个字节
cout << a[0]+1 << endl; //0x0013FF54
cout << &a[0][0]+1 << endl; //0x0013FF54
return 0;
}

例子1:遍历数组元素

1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>
using namespace std;
int main(){
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int *p;

//for (p=&a[0][0]; p<&a[0][0]+12; p++)
//for (p=a[0]; p<a[0]+12; p++)
for (p=*a; p<*a+12; p++)
cout<<p<<" "<<*p<<endl;
return 0;
}

例子2:输入i,j;输出a[i] [j]

1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>
using namespace std;
int main(){
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int (*p)[4], i, j;
p=a;
cin>>i>>j;

//cout << setw(4) << p[i][j];
cout<<setw(4)<<*(*(p+i)+j);
return 0;
}

问题分析

  1. p=a开始,a相当于指向a[3][4]的“第一个元素”的指针;所谓“第一个元素”是指一个 “包含4个int型元素的一维数组”;所以,a相当于一个 “包含4个int型元素的一维数组”的地址;因此p的基类型应该是 “包含4个int型元素的一维数组”。

  2. 如何定义一个指向 “包含4个int型元素的一维数组” 的指针变量? ———- int (*p)[4]

  3. *(*(p+i)+j)是什么?

    p指向一个“包含4个int型元素的一维数组”;

    p+i 是第i+1个“包含4个Int 型元素的一维数组”的地址;p+i等价于&a[i]

*(p+i)等价于a[i]*(p+i)+j等价于a[i]+j

​ 因为:a[i]+j等价于&a[i][j],所以:*(*(p+i)+j)等价于a[i][j]

  1. p[i][j]是什么?

    p[i]等价于 *(p+i)p[i][j] 等价于 *(*(p+i)+j),等价于a[i][j]

使用范围for语句处理多维数组

C++11新标准中新增了范围for语句,可以使用范围for语句处理多维数组,为了避免数组被自动转成指针,处理最内层的循环外,其他所有循环的控制变量应该都是引用类型。

1
2
3
4
5
6
7
constexpr size_t rowCnt=3. colCnt=4, cnt=0;
int ia[rowCnt][colCnt];
for(auto &row : ia)
for(auto &col : row) {
col = cnt;
++cnt;
} //ia={0,1,2,3,4,5,6,7,8,9,10,11}

也可以使用标准库函数beginend

1
2
3
4
5
6
for (auto p=begin(ia); p!=end(ia); ++p)
{
for (auto q=begin(*p); q!=end(*p); ++q)
cout<<*q<<' '//依次输出ia的元素的值
cout<<endl
}

字符串指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<iostream>
#include<iomanip>
using namespace std;
int main(){
char buffer[10] = "ABC";
char *pc;
pc = "hello";
cout << pc << endl; //输出hello
pc++;
cout << pc << endl; // ello
cout << *pc << endl; // e
pc = buffer;
cout << pc << endl; //ABC
return 0;
}