C++ PrimerPlus 复习 第四章 复合类型(上)

第一章 命令编译链接文件 make文件

第二章 进入c++

第三章 处理数据

第四章 复合类型 (上)

问题

  • sizeof的使用?
  • char数组什么时候才是字符串?
  • 结构的声明和初始化方式?
  • 结构中的位字段
  • 共用匿名体怎么使用?
  • 枚举的取值范围是怎么算的?

复合类型。这种类型是基于基本整型和浮点类型创建的。
影响最为深远的复合类型是类,它是将学习的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风格字符串;

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 <iostream>
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个字符的数组中的情况发生。
很多程序都依赖于字符串输入,因此有必要对该主题做进一步探讨。

解法方法

1:面向行的输入:getline()

getline()函数读取整行,它使用通过回车键输入的换行符来确定输入结尾。
使用cin.getline()。该函数有两个参数。
第一个参数是用来存储输入行的数组的名称,第二个参数是要读取的字符数。
如果这个参数为20,则函数最多读取19个字符,余下的空间用于存储自动在结尾处添加的空字符。getline()成员函数在读取指定数’目的字符’或遇到’换行符’时停止读取。
在这里插入图片描述

2.面向行的输入:get()

工作方式与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
string类位于名称空间std中,因此您要提供一条using编译指令或使用std::string

  • 可以使用C风格字符串来初始化string对象。
  • 可以使用cin来将键盘输入存储到string对象中。
  • 可以使用cout来显示string对象。
  • 可以使用数组表示法来访问存储在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

string类的其他操作

以使用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)

匿名共用体(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;

创建和使用枚举;

枚举 C++的enum工具提供了**另一种创建符号常量的方式**,这种方式可以代替const。 它还允许定义新类型,但必须按严格的限制进行。使用enum的句法与使用结构相似。 例如,请看下面的语句:
enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};
spectrum band; // band a variable of type spectrum

这条语句完成两项工作。

  • 让spectrum成为新类型的名称;spectrum被称为枚举(enumeration),就像struct变量被称为结构一样。
  • 将red、orange、yellow等作为符号常量,它们对应整数值0~7。这些常量叫作枚举量(enumerator)。这些符号都可以直接使用

默认情况下,将整数值赋给枚举量,第一个枚举量的值为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运算符用于数组名,得到的将是整个数组中的字节数。但如果将sizeof用于数组元素,则得到的将是元素的长度(单位为字节)。这表明yams是一个数组,而yams[1]只是一个int变量。

char数组什么时候才是字符串?

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个字节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值