【c++】【基础】【primer_plus】【第四章】数组与复合类型

数组

数组是一组数据的集合,它们有序地依次排列成一排,无论是代码中还是实际内存上。

比如我们经常吃的一串糖葫芦,就是一个数组,而糖葫芦上的每个山楂,就是这个数组的元素,它们紧密地排列成一排。

声明

1
typename array_name[array_size];
1
int a[100];

需要注意,varray_size不能是变量,而是一个整型常量。

我们可通过索引(下标)对数组元素进行访问,其中下标的范围为[0, array_size)

1
a[3] = 100;

初始化

对于一个数组而言,只有创建时才能使用初始化表初始化,此后便不能使用初始化表初始化了。除此之外,也不能直接将一个数组赋值给另一个数组,如a=b

1
2
3
int a[4] = { 3, 6, 8, 10};
int b[4] = { 1, 2, 3, 4 };
a = b; // error

使用初始化表可只对一部分元素初始化,剩下的默认为0。

1
int a[4] = {2, 3};          // 此时数组值为 2 3 0 0 

也可不在{}内包含任何东西, 此时所有元素初始化为0。

1
double b[4]{}

若[]内为空,则编译器将自动计算元素个数。

1
int a[] = {2, 3, 4, 5};     // 此时size = 4

另外,c++11标准新增了一个语法,就是初始化时可省略等于号(=)。

1
double b[4]{1, 2, 3, 4};

字符串

字符串是以空字符(ASCII码为0,c++里表示为\0)结尾的、以char类型为元素的数组。然而其初始化除了使用上面介绍的形式外,还可以使用双引号("")将所有的字符括起来,直接声明一个串,如下代码所示。

1
2
char pig[8] = { 'v', 'e', 'r', 'y', 'f', 'a', 't', '\0'};
char pig[] = "veryfat";

任何两个由空白(空格、制表符、换行符)分隔的字符串常量,都会自动拼接为一个字符串,如下代码所示。

1
cout << "aaa" "bbb" endl;   // 输出 aaabbb

输入一行字符串

当我们需要输入一个字符串时,如果直接用cin来输入,那么当它碰到中间有空格的字符串便只能读取到第一个空格所在的位置了,如输入hello world到一个字符串里的话,使用cin只能读取hello,因为识别到空格时即止。

所以如果想直接输入hello world这样中间有空格的一行数据到一个字符串里,需要用来其它函数。这里触及到的函数有两个,一个是getline(),另一个是get()

getline()将丢弃最后面的换行符\n,使用空字符替代。

1
2
cin.getline(name, size);
// 若输入为"hello world",则name = "hello world\0";

get()并不读取换行符,而是将其留存输入队列中,如果我们继续使用cin,才会接着读取到换行符。

1
2
cin.get(name, size);        // 此时换行符留在了输入队列中
cin.get(); // 可读取下一个字符即使是换行符

除此之外,get()函数返回的还是cin对象,所以可以继续调用get()函数,如下所示。

1
2
cin.get(a, maxn).get();
cin.get(b, maxn); // 返回一个cin对象,getline()也是

读取空行

get()读取空行后将设置失效位(failbit),这意味着接下来的输入将被阻断。这个时候需要使用cin.clear()来恢复输入。当输入字符串比分配的空间长时,余下的字符将会留在输入队列中,且getline()会设置失效位。

当使用cin >> a输入数字之后,会将换行符留在输入队列,此时应使用cin.get()cin.get(ch)来读取并丢弃换行符。

字符串操作

目前我们所提及的字符串都是c语言中的字符中形式,即由字符数组和\0组成的,称之为c串。c语言头文件<cstring>可以对c串进行一些基本的操作,如复制、拼接、查找子串等。

1
2
strcpy(charr1, charr2);     // 复制charr2到charr1
strcat(charr1, charr2); // 拼接charr2到charr1

原始字符串

在c++里还有一种字符串,它直接输出字面的内容,而不需要转义字符,这种字符串称为原始字符串。

我们使用R作为前缀,()作为定界符来声明原始字符串字面量。这样()里的字符串便不需要转义字符,代码里是什么其输出便会是什么。

另外我们也可以自定义定界符,而不是必须使用()来作为定界符。此时只要保证"和(之间的字符与)和"之间的字符一样即可,如下代码所示。

1
2
cout << R"(Jim "King" uses "\n" instead of endl)" << endl;
cout << R"+*(Jim "King" uses "\n" instead of endl)+*" << endl;

此代码将输出如下内容。

1
2
Jim "King" uses "\n" instead of endl
Jim "King" uses "\n" instead of endl

string类

string类是c++的字符串类,它对c串进行了封装,这在读者接触到面向对象思想之后便很容易理解了。

string类可以使用c串进行初始化,如下代码所示。

1
2
string str = "hello";
string str {"hello"};

我们可以像c串一样使用cin来读取一个string对象。同时也可以将一个string对象赋值给另一个string对象。

1
2
cin >> str;
str1 = str2;

我们可以使用加号+来拼接两个string对象。

1
2
str3 = str1 + str2;
str1 += str2; // 将str2附加到str1的尾部

我们同样可以使用getline()读取一行到string对象,但是形式上有些变化,其原因需要等到掌握了面向对象知识再做理解。

1
getline(cin, str);      // istream没有关于string的类方法

结构体

结构体struct是一种自定义类型,它可以将一些我们之前接触过的基本数据类型组合起来,形成更加复杂的类型,这种类型有可以为我们模拟当前开发任务需要的数据格式提供极大的方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义结构体
struct node {
char name[20];
float volume;
double price;
};

// 初始化
node a = { "dua", 12.5, 30.3}; // 使用初始化参数表
node b {"dra", 10.0, 11.2}; // C++11初始化结构体

// 访问结构体成员
cout << x.name << endl;

// 赋值
node c = a;

如上代码所示,我们将一个c串、一个float和一个double类型组合起来,并使用struct关键字,就声明了一种叫做node的新的数据类型。这个数据类型在一定程度上与intdouble等基本数据类型没有区别,如其声明、初始化、甚至是赋值等操作。

我们使用点运算符.来访问结构体内的具体某一个成员。

我们也可以在定义结构体的同时创建结构体变量,如下代码所示,便在定义node类型的同时声明了三个node类型的变量a b c

1
2
3
4
struct node {
char name[20];
float volume;
}a, b, c;

更进一步,与上一步同时,我们也可以初始化在定义时声明的变量。

1
2
3
4
struct node {
char name[20];
float volume;
}a, b = {"dua", 12.5}, c;

我们也可以创建无名结构体,但是我们以后再也无法声明同类型的变量了,所以只有在定义时声明我们想要的该类型的变量,且整个程序只会有这些在一开始就声明出来的该类型的变量了。

1
2
3
stuct {             // 以后无法再创建同类结构体
int x, y;
}position;

结构体数组

结构体也可以像基本数据类型一样作为数组的元素,我们可以像如下代码一样声明一个结构体数组。

1
2
3
4
5
node x[2] = {
{"dua", 12.5, 30.3},
{"dra", 13.5, 40.3}
};
cout << x[1].name << endl;

结构体中的位字段

我们可以给结构体的某一个成员指定位数以节省内存。

我们只能对结构体内的整型或枚举型(实质也是整型)和bool型(实质也是整型)的变量进行位数指定。我们在变量的名称后面跟一个冒号:,然后后面跟一个数字表示位数。如下代码所示。

1
2
3
4
5
6
7
8
9
struct node {
unsigned int SN : 4; // 4位
unsigned int : 4; // 4位
bool flag : 1; // 1位
bool ok : 1; // 1位
};

node x = {14, true, true};
if(x.flag) cout << x.SN << endl;

从上面代码也可以看出,我们可以使用没有名称的字段来提供内存间距。

共用体

共用体union也是一种自定义的数据类型,与结构体类似,其不同点在于,共用体在同一时间只能存储其成员的一种结构,主要用于节省空间。如下代码所示。

1
2
3
4
5
6
7
8
9
10
11
12
union node {
int int_val;
long long_val;
double double_val;
};

//使用
node x;
x.int_val = 15; // 此时共用体只有int_val成员
cout << x.int_val << endl;
x.long_val = 20; // 此时共用体只有long_val成员
cout << x.long_val << endl;

这些成员的内存地址是相同的,所以共用体的长度为其最长成员的长度。

匿名共用体

1
2
3
4
5
6
7
8
9
10
11
12
13
struct widget {
char brand[20];
int type; // 负责确定哪个共用体成员是活动的
union {
long id_num; // 有的ID为数字
char id_char[20]; // 有的ID为字符串
};
};

widget x;
if(x.type == 1) cin >> x.id_num;
else cin >> x.id_char;

枚举

枚举enum是对一类整数常量的列举,它给整数赋以实际的意义,表示某个数据的状态值,增加代码的可读性。在某些场合十分有用,如颜色、星期、月份等。

1
enum color {red, green, blue};

从上面的代码也可以看出,枚举通常用来创建符号常量。其默认值从0开始,依次递增。枚举量可自动提升为int型,但int型需要强制转换成枚举型,且其值要适当。注意,枚举型没有定义加法。

我们也可以省略枚举名称来创建符号常量。

1
enum {red, green, blue};

我们可以显示地设置枚举量的值。当然也可以只显示地定义其中一些枚举量的值,而没有被赋值的枚举量的值则为其前一个枚举量+1

1
enum color {red = 1, green = 4, blue = 8, yellow};

如上代码所示,yellow的值便为9,即blue的值+1

枚举的取值范围

上限

找到枚举量的最大值,那么大于这个最大值的、最小的2的幂再减去1,即为取值范围的上限。

下限

找到枚举量的最小值,然后

如果它不小于0,则取值范围的下限即为0。 如果它小于0,则要找到比它小的、最大的2的幂再加上1,即为下限。

例如,枚举量的最大值为101,则比它大的最小的2的幂为128,减1为127,即为上限。

再例如,枚举量的最小值为-6,则比它小的最大的2的幂为-8,加1为-7,即为下限。