C++之复合类型(四)
本文记录了C++中与复合类型相关的容易遗忘的一些知识。
内置数组
初始化
只有在定义数组时才能使用初始化形式。之后不能使用,也不能将一个数组整体赋值给另一个数组:
1 | int cards[4] = {3, 6, 8, 10}; // okay |
C++11内置数组特性:
1 | // drop the = sign when initializing an array: |
字符串
逐行读取字符串输入
对于istream类(cin就是该类的一个实例),它有一些面向行的类成员函数:getline()和get()。这两个函数都会读取一整行输入——也就是说,一直读到换行符为止。不过,getline()会随后丢弃这个换行符,而get()则会将其留在输入队列中。
getline()
1 | cin.getline(name,20); |
getline()通过标记行尾的换行符读取输入,但不会保存换行符。相反,在存储字符串时,它会将换行符替换为空字符。
get()
istream类有另一个成员函数get(),它有好几种变体。其中一种变体的作用和getline()很像。它接受相同的参数,以相同的方式解析这些参数,并读取到行尾。但与读取并丢弃换行符不同,get()会将该字符留在输入队列中。假设你连续两次调用get():
1 | cin.get(name, ArSize); |
因为第一次调用会在输入队列中留下换行符,所以这个换行符是第二次调用看到的第一个字符。因此,get()函数会认为自己已经到达行尾,却没有找到任何可读取的内容。如果没有帮助,get()函数根本无法跳过那个换行符。
有一种get()的变体可以提供帮助。调用cin.get()(不带任何参数)会读取下一个单个字符,即便是换行符也不例外,所以你可以用它来处理换行符,为下一行输入做好准备。也就是说,下面这个序列是有效的:
1 | cin.get(name, ArSize); // read first line |
使用get()的另一种方法是将两个类成员函数连接起来,如下所示:
1 | cin.get(name, ArSize).get(); // concatenate member functions |
之所以能做到这一点,是因为cin.get(name, ArSize)会返回cin对象,然后该对象被用作调用get()函数的对象。同样地,下面的语句会将连续的两行输入读入数组name1和name2,这相当于两次分别调用cin.getline():
1 | cin.getline(name1, ArSize).getline(name2, ArSize); |
为什么有了getline()还要使用get()
为什么有了getline()还要使用get()呢?首先,较旧的实现可能没有getline()。其次,get()能让你更谨慎一些。例如,假设你用get()将一行内容读入一个数组。怎样判断它是读取了整行内容,还是因为数组已满而停止读取的呢?看看下一个输入字符就知道了。如果是换行符,那就说明读取了整行;如果不是换行符,那就意味着该行还有更多输入内容。简而言之,getline()使用起来稍简单一些,但get()让错误检查更简便。
结构体
结构体中的位域
和C语言一样,C++允许你指定占据特定位数的结构成员。这在创建与某些硬件设备上的寄存器相对应的数据结构时会很方便。位域的类型应该是整数类型或枚举类型,冒号后面跟一个数字表示要使用的实际位数。你可以使用未命名的位域来提供间距。每个成员都被称为位域。下面是一个例子:
1 | struct torgle_register |
你可以用常规方式初始化这些字段,并且可以使用标准的结构表示法来访问位字段:
1 | torgle_register tr = { 14, true, false }; |
联合体
定义
联合体是一种数据格式,它可以存储不同的数据类型,但每次只能存储一种类型。也就是说,结构体可以同时存储例如一个int、一个long和一个double,而联合体只能存储一个int、或一个long或者一个double。共用体的语法与结构体类似,但含义不同。例如,请看下面的声明:
1 | union one4all |
由于联合体一次只能存储一个值,所以它必须有足够的空间来容纳其最大的成员。因此,联合体的大小等于其最大成员的大小。
枚举
定义
C++的枚举功能为创建符号常量提供了一种替代const的方式。它还允许定义新类型,但方式相当受限。枚举的语法类似于结构体语法。例如,考虑以下语句:
enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};
注意:成员值之间分隔符用的是逗号,结构体和联合用的是引号
;。
该语句有两个作用:
- 使
spectrum成为一种新类型的名称;spectrum被称为枚举,就像结构体变量被称为结构体一样。 - 将
red、orange、yellow等确立为整数值0到7的符号常量。这些常量被称为枚举量。
指针
指针与数组名的区别
第一个区别是,你可以改变指针的值,而数组名是一个常量:
1 | pointername = pointername + 1; // 有效 |
第二个区别是,对数组名应用sizeof运算符会得到数组的大小,但对指针应用sizeof会得到指针的大小,即使该指针指向数组。这是C++不将数组名解释为地址的一种情况。
数组的地址
对数组取地址是数组名称不被解释为其地址的另一种情况。数组的名称难道不是被解释为数组的地址吗?并不完全是——数组的名称被解释为数组首元素的地址,而使用地址运算符则会得到整个数组的地址:
1
2
3 short tell[10]; // tell是一个20字节的数组
cout << tell << endl; // 显示&tell[0]
cout << &tell << endl; // 显示整个数组的地址从数值上看,这两个地址是相同的,但从概念上讲,
&tell[0](tell也是)是一个2字节内存块的地址,而&tell是一个20字节内存块的地址。所以表达式tell + 1会使地址值增加2,而&tell + 1会使地址值增加20。换一种说法,tell的类型是指向short的指针(即short *),而&tell的类型是指向包含20个short的数组的指针(即short (*)[20])。现在你可能会好奇最后那个类型描述是怎么来的。首先,下面是如何声明并初始化该类型的指针:
1 short (*pas)[20] = &tell; // pas指向包含20个short的数组如果省略括号,根据优先级规则,
[20]会先与pas关联,使pas成为一个包含20个指向short的指针的数组,所以括号是必需的。其次,如果你想描述一个变量的类型,可以以该变量的声明为指导,并去掉变量名。因此,pas的类型是short (*)[20]。另外需要注意的是,由于pas被赋值为&tell,所以*pas等同于tell,因此(*pas)[0]就是tell数组的第一个元素。