第一章 命令编译链接文件 make文件
第二章 进入c++
第三章 处理数据
第四章 复合类型 (上)
问题
复合类型。这种类型是基于基本整型和浮点类型创建的。
影响最为深远的复合类型是类,它是将学习的OOP的堡垒。
然而,C++还支持几种更普通的复合类型,它们都来自C语言。
数组,
数组(array)是一种数据格式,能够存储多个同类型的值。
要创建数组,可使用声明语句。数组声明应指出以下三点:
声明数组的通用格式如下:
typeName arrayName[arraySize];
short months[12]; // creates array of 12 short
arraySize指定元素数目,arraySize不能是变量,变量的值是在程序运行时设置的
它必须是整型常数(如10)或const值
也可以是常量表达式(如8 * sizeof(int))
即其中所有的值在编译时都是已知的
arraySize不能是变量,解决方法是什么?
动态联编
可以解决,new可以
编译器不会检查使用的下标是否有效
int yamcosts[3] = {20, 30, 5};
只有在定义数组时才能使用初始化,此后就不能使用了,也不能将一个数组赋给另一个数组:
初始化数组时,提供的值可以少于数组的元素数目。
例如,下面的语句只初始化hotelTips的前两个元素:
float hotelTips[5] = {5.0, 2.5};
如果只对数组的一部分进行初始化,则编译器将把其他元素设置为0
如果初始化数组时方括号内([ ])为空,C++编译器将计算元素个数。
例如,对于下面的声明:
short things[] = {1, 5, 3, 8};
首先,初始化数组时,可省略等号(=):
double earnings[4] {1.2e4, 1.6e4, 1.1e4, 1.7e4}; // okay with C++11
其次,可不在大括号内包含任何东西,这将把所有元素都设置为零:
unsigned int counts[10] = {}; // all elements set to 0
float balances[100] {}; // all elements set to 0
第三,列表初始化禁止缩窄转换,这在第3章介绍过
C++标准模板库(STL)提供了一种数组替代品——模板类vector,而C++11新增了模板类array。
这些替代品比内置复合类型数组更复杂、更灵活
C风格字符串具有一种特殊的性质:
以空字符(null character)结尾,空字符被写作\0,其ASCII码为0,用来标记字符串的结尾。
例如,请看下面两个声明:
char dog[8] = { 'b', 'e', 'a', 'u', 'x', ' ', 'I', 'I'}; // not a string!
char cat[8] = {'f', 'a', 't', 'e', 's', 's', 'a', '\0'}; // a string!
这两个数组都是char数组,但只有第二个数组是字符串。空字符对C风格字符串而言至关重要。
另一种方法:只需使用一个用引号括起的字符串即可,这种字符串被称为字符串常量(string constant)或字符串字面值(string literal),如下所示:
警告:
在确定存储字符串所需的最短数组时,别忘了将结尾的空字符计算在内。
strlen()函数返回的是存储在数组中的字符串的长度,而不是数组本身的长度。
strlen()只计算可见的字符,而不把空字符计算在内
char bird[11] = "Mr. Cheeps"; // the \0 is understood
char fish[] = "Bubbles"; // let the compiler count
使用字符串常量初始化字符数组是这样的一种情况,即让编译器计算元素数目更为安全
字符串可以直接拼接,实上,任何两个由空白(空格、制表符和换行符)分隔的字符串常量都将自动拼接成一个。
cout << "I'd give my right arm to be" " a great violinist.\n";
cout << "I'd give my right arm to be a great violinist.\n";
cout << "I'd give my right ar"
"m to be a great violinist.\n";
char数组不能直接赋值
使用函数strcpy()将字符串复制到字符数组中,
使用函数strcat()将字符串附加到字符数组末尾:
// instr1.cpp -- reading more than one string
#include
int main()
{
using namespace std;
const int ArSize = 20;
char name[ArSize];
char dessert[ArSize];
cout << "Enter your name:\n";
cin >> name;
cout << "Enter your favorite dessert:\n";
cin >> dessert;
cout << "I have some delicious " << dessert;
cout << " for you, " << name << ".\n";
return 0;
}
感觉没问题吗?
下面是该程序的运行情况:
Enter your name:
Alistair Dreeb
Enter your favorite dessert:
I have some delicious Dreeb for you, Alistair.
还感觉正常吗?
为什么会出现这样的输出结果呢?
先看看,cin是如何确定已完成字符串输入呢?
由于不能通过键盘输入空字符,因此cin需要用别的方法来确定字符串的结尾位置。
cin使用空白(空格、制表符和换行符)来确定字符串的结束位置,这意味着cin在获取字符数组输入时只读取一个单词。并且将读取到的换行符留在输入流中
读取该单词后,cin将该字符串放到数组中,并自动在结尾添加空字符。
另一个问题是,输入字符串可能比目标数组长(运行中没有揭示出来)。像这个例子一样使用cin,确实不能防止将包含30个字符的字符串放到20个字符的数组中的情况发生。
很多程序都依赖于字符串输入,因此有必要对该主题做进一步探讨。
解法方法
getline()函数读取整行,它使用通过回车键输入的换行符来确定输入结尾。
使用cin.getline()。该函数有两个参数。
第一个参数是用来存储输入行的数组的名称,第二个参数是要读取的字符数。
如果这个参数为20,则函数最多读取19个字符,余下的空间用于存储自动在结尾处添加的空字符。getline()成员函数在读取指定数’目的字符’或遇到’换行符’时停止读取。
工作方式与getline()类似,它们接受的参数相同,解释参数的方式也相同,并且都读取到行尾。
但get并不再读取并丢弃换行符,而是将其留在输入队列中。
遇到的问题就是
由于第一次调用后,换行符将留在输入队列中,因此第二次调用时看到的第一个字符便是换行符。因此get()认为已到达行尾,而没有发现任何可读取的内容。
cin.get(name, ArSize);
cin.get(dessert, ArSize); // a problem
get()有另一种变体。使用不带任何参数的cin.get()调用可读取下一个字符(即使是换行符),因此可以用它来处理换行符,为读取下一行输入做好准备。
cin.get(name, ArSize); // read first line
cin.get(); // read newline
cin.get(dessert, Arsize); // read second line
## 创建和使用string类字符串;
当get()(不是getline())读取空行后将设置失效位(failbit)。
这意味着接下来的输入将被阻断,但可以用下面的命令来恢复输入:
cin.clear();
输入字符串可能比分配的空间长。
如果输入行包含的字符数比指定的多,则getline()和get()将把余下的字符留在输入队列中,而getline()还会设置失效位,并关闭后面的输入
总结空行get(),溢出getline();
用cin给数字变量赋值后,由于会使用回车来结束输入。
从而导致将换行留在输入流里面。
如果我后面使用getline(),来读将认为是个空行,从而将空行给其他变量
可以怎么解决
(cin >> year).get(); // or (cin >> year).get(ch);
C++程序常使用指针(而不是数组)来处理字符串。
string类使用起来比数组简单,同时提供了将字符串作为一种数据类型的表示方法。
用string类,必须在程序中包含头文件string
string类位于名称空间std中,因此您要提供一条using编译指令或使用std::string
可以将char数组视为一组用于存储一个字符串的char存储单元
而string类变量是一个表示字符串的实体
将列表初始化用于C风格字符串和string对象:
char first_date[] = {"Le Chapon Dodu"};
char second_date[] {"The Elegant Plate"};
string third_date = {"The Bread Bowl"};
string fourth_date {"Hank's Fine Eats"};
使用string类时,某些操作比使用数组时更简单。
不能将一个数组赋给另一个数组
但可以将一个string对象赋给另一个string对象
string类简化了字符串合并操作。
可以使用运算符+将两个string对象合并起来,还可以使用运算符+=将字符串附加到string对象的末尾。
string str3;
str3 = str1 + str2; // assign str3 the joined strings
str1 += str2; // add str2 to the end of str1
以使用cin和运算符>>来将输入存储到string对象中
使用cout和运算符<<来显示string对象
size()是string类的一个方法用来获取长度
int len1 = str1.size(); // obtain length of str1
下面是将一行输入读取到string对象中的代码:
getline(cin,str);
这里没有使用句点表示法(cin.getline(charr, 20);),这表明这个getline()不是类方法。
它将cin作为参数,指出到哪里去查找输入。
另外,也没有指出字符串长度的参数,因为string对象将根据字符串的长度自动调整自己的大小。
结构是一种比数组更灵活的数据格式,因为同一个结构可以存储多种类型的数据。
inflatable hat; // hat is a structure variable of type inflatable
inflatable woopie_cushion; // type inflatable variable
inflatable mainframe; // type inflatable variable
如果您熟悉C语言中的结构,则可能已经注意到了,C++允许在声明结构变量时省略关键字struct:
struct inflatable goose; // keyword struct required in C
inflatable vincent; // keyword struct not required in C++
以使用成员运算符(.)来访问各个成员。
hat.volume指的是结构的volume成员,hat.price指的是price成员。
初始化方式:
inflatable guest =
{
"Glorious Gloria", // name value
1.88, // volume value
29.99 // price value
};
和数组一样,使用由逗号分隔值列表,并将这些值用花括号括起。在该程序中,每个值占一行,但也可以将它们全部放在同一行中。
只是应用逗号将它们分开:
inflatable duck = {"Daphne", 0.12, 9.98};
与数组一样,C++11也支持将列表初始化用于结构,且等号(=)是可选的
可以同时完成定义结构和创建结构变量的工作。为此,只需将变量名放在结束括号的后面即可:
struct perks
{
int key_number;
char car[12];
} mr_smith, ms_jones; // two perks variables
甚至可以初始化以这种方式创建的变量:
struct perks
{
int key_number;
char car[12];
} mr_glitz =
{
7, // value for mr_glitz.key_number member
"Packard" // value for mr_glitz.car member
};
inflatable结构包含一个数组(name)。
也可以创建元素为结构的数组,方法和创建基本类型数组完全相同。
例如,要创建一个包含100个inflatable结构的数组,可以这样做:
inflatable gifts[100]; // array of 100 inflatable structures
初始化结构数组
inflatable guests[2] = // initializing an array of structs
{
{"Bambi", 0.5, 21.99}, // first structure in array
{"Godzilla", 2000, 565.99} // next structure in array
};
共用体(union)是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型。
也就是说,结构可以同时存储int、long和double,共用体只能存储int、long或double。
共用体的句法与结构相似,但含义不同。例如,请看下面的声明:
union one4all
{
int int_val;
long long_val;
double double_val;
};
共用体的长度为其最大成员的长度。
共用体的用途之一是,当数据项使用两种或更多种格式(但不会同时使用)时,可节省空间
假设管理一个小商品目录,其中有一些商品的ID为整数,而另一些的ID为字符串
struct widget
{
char brand[20];
int type;
union id // format depends on widget type
{
long id_num; // type 1 widgets
char id_char[20]; // other widgets
} id_val;
};
...
widget prize;
...
if (prize.type == 1) // if-else statement (Chapter 6)
cin >> prize.id_val.id_num; // use member name to indicate mode
else
cin >> prize.id_val.id_char;
匿名共用体(anonymous union)没有名称,其成员将成为位于相同地址处的变量。
显然,每次只有一个成员是当前的成员:
struct widget
{
char brand[20];
int type;
union // anonymous union
{
long id_num; // type 1 widgets
char id_char[20]; // other widgets
};
};
...
widget prize;
...
if (prize.type == 1)
cin >> prize.id_num;
else
cin >> prize.id_char;
enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};
spectrum band; // band a variable of type spectrum
这条语句完成两项工作。
默认情况下,将整数值赋给枚举量,第一个枚举量的值为0,第二个枚举量的值为1,依次类推。
在不进行强制类型转换的情况下,只能将定义枚举时使用的枚举量赋给这种枚举的变量,如下所示:
band = blue; // valid, blue is an enumerator
band = 2000; // invalid, 2000 not an enumerator
band = 1; // valid assigning to 'spectrum' from incompatible type 'int'
对于枚举,只定义了赋值运算符。
具体地说,没有为枚举定义算术运算:
band = orange; // valid
++band; // not valid, ++ discussed in Chapter 5
band = orange + red; // not valid, but a little tricky
枚举量是整型,可被提升为int类型,但int类型不能自动转换为枚举类型:
int color = blue; // valid, spectrum type promoted to int
band = 3; // invalid, int not converted to spectrum
如果int值是有效的,则可以通过强制类型转换,将它赋给枚举变量
band = spectrum(3); // typecast 3 to type spectrum
color = 3 + red; // valid, red converted to int
设置枚举量的值
可以使用赋值运算符来显式地设置枚举量的值:
enum bits{one = 1, two = 2, four = 4, eight = 8};
指定的值必须是整数。也可以只显式地定义其中一些枚举量的值:
enum bigstep{first, second = 100, third};
这里,first在默认情况下为0。后面没有被初始化的枚举量的值将比其前面的枚举量大1。因此,third的值为101。
最后,可以创建多个值相同的枚举量:
enum {zero, null = 0, one, numero_uno = 1};
枚举的取值范围
我们可以通过强制类型转换将值传给枚举变量
enum bits{one = 1, two = 2, four = 4, eight = 8};
bits myflag;
myflag = bits(6); // valid, because 6 is in bits range
其中6不是枚举值,但它位于枚举定义的取值范围内。
取值范围的定义如下。
首先,要找出上限,需要知道枚举量的最大值。
找到大于这个最大值的、最小的2的幂,将它减去1,得到的便是取值范围的上限。
比如:
前面定义的bigstep的最大值枚举值是101。在2的幂中,比这个数大的最小值为128,因此取值范围的上限为127
下限,需要知道枚举量的最小值。如果它不小于0,则取值范围的下限为0;否则,采用与寻找上限方式相同的方式,但加上负号。例如,如果最小的枚举量为−6,而比它小的、最大的2的幂是−8(加上负号),因此下限为−7。
sizeof运算符返回类型或数据对象的长度(单位为字节)。
注意,如果将sizeof运算符用于数组名,得到的将是整个数组中的字节数。但如果将sizeof用于数组元素,则得到的将是元素的长度(单位为字节)。这表明yams是一个数组,而yams[1]只是一个int变量。
char dog[8] = { 'b', 'e', 'a', 'u', 'x', ' ', 'I', 'I'}; // not a string!
char cat[8] = {'f', 'a', 't', 'e', 's', 's', 'a', '\0'}; // a string!
这两个数组都是char数组,但只有第二个数组是字符串。
空字符对C风格字符串而言至关重要。
例如,C++有很多处理字符串的函数,其中包括cout使用的那些函数。
它们都逐个地处理字符串中的字符,直到到达空字符为止。
如果使用cout显示上面的cat这样的字符串,则将显示前7个字符,发现空字符后停止。
但是,如果使用cout显示上面的dog数组(它不是字符串),cout将打印出数组中的8个字母,并接着将内存中随后的各个字节解释为要打印的字符,直到遇到空字符为止。
由于空字符(实际上是被设置为0的字节)在内存中很常见,因此这一过程将很快停止。
但尽管如此,还是不应将不是字符串的字符数组当作字符串来处理。
第一种:
struct perks
{
int key_number;
char car[12];
}
perks year_one = {12000,"January"}
第二种:
struct perks
{
int key_number;
char car[12];
}
perks year_one {12000,"January"}
第三种
struct perks
{
int key_number;
char car[12];
} mr_glitz =
{
7, // value for mr_glitz.key_number member
"Packard" // value for mr_glitz.car member
};
声明两个
struct perks
{
int key_number;
char car[12];
} mr_smith, ms_jones; // two perks variables
这样将创建一个名为position的结构变量。可以使用成员运算符来访问它的成员(如position.x),但这种类型没有名称,因此以后无法创建这种类型的变量。本书将不使用这种形式的结构。
struct // no tag
{
int x; // 2 members
int y;
} position; // a structure variable
匿名共用体(anonymous union)没有名称,其成员将成为位于相同地址处的变量。显然,每次只有一个成员是当前的成员:
struct widget
{
char brand[20];
int type;
union // anonymous union
{
long id_num; // type 1 widgets
char id_char[20]; // other widgets
};
};
...
widget prize;
...
if (prize.type == 1)
cin >> prize.id_num;
else
cin >> prize.id_char;
由于共用体是匿名的,因此id_num和id_char被视为prize的两个成员,它们的地址相同,所以不需要中间标识符id_val。
程序员负责确定当前哪个成员是活动的。
查看答案
取值范围的定义如下。
首先,要找出上限,需要知道枚举量的最大值。找到大于这个最大值的、最小的2的幂,将它减去1,得到的便是取值范围的上限。
例如,前面定义的bigstep的最大值枚举值是101。在2的幂中,比这个数大的最小值为128,因此取值范围的上限为127。
要计算下限,需要知道枚举量的最小值。如
果它不小于0,则取值范围的下限为0;否则,采用与寻找上限方式相同的方式,但加上负号。
例如,如果最小的枚举量为−6,而比它小的、最大的2的幂是−8(加上负号),因此下限为−7。
选择用多少空间来存储枚举由编译器决定。对于取值范围较小的枚举,使用一个字节或更少的空间;而对于包含long类型值的枚举,则使用4个字节。