【C++ Primer Plus】第4章学习笔记

📅 2026/7/6 4:44:32
【C++ Primer Plus】第4章学习笔记
第四章 复合类型笔者认为本章很重要特别是指针部分有C语言学习基础的可能会轻松一些毕竟C中的复合结构基本上都继承于C语言本章需要掌握的内容为:-创建和使用数组-创建和使用C-风格字符串-创建和使用string类字符串-使用方法getline()和get()读取字符串-混合输入字符串和数字-创建和使用结构体-创建和使用共用体-创建和使用枚举类型-创建和使用指针-使用new和delete管理动态内存-创建动态数组-创建动态结构-自动存储、静态存储和动态存储-vector和array类简介。// 由于笔者学业的缘故接下来两周可能会更新慢一点还请谅解。笔者的目标是到9月份之前争取把这本书看完话不多说开整4.1 数组数组array是一种数据格式能够存储多个同类型的值。每个值都存储在一个独立的数组元素中计算机在内存中依次存储数组的各个元素。即一个数组所在的内存区域是连续的。要创建数组可使用声明语句。数组声明应指出以下三点-存储在每个元素中的值的类型-数组名-数组中的元素数。在C中通过在简单变量后面添加中括号其中包含元素数目来完成数组声明。而数组中的每一个元素都看作是一个简单变量。声明数组的通用格式为:typeName arrayName[arraySize]表达式arraySize指定元素数目它必须是整型常数如10或const值也可以是常量表达式如8*sizeofint即其中所有的值在编译时都是已知的。具体说arraySize不能是变量变量的值是在程序运行时设置的。如int months[12]; // 声明一个包含 12 个整数的数组C通过使用下标来访问数组中的每一个元素C数据从0开始编号数组总长度-1是最后一个元素如上months[0]为第一个元素months[11]是最后一个元素。4.1.1 数组的初始化C可以在声明语句中初始化数组只需提供一个用逗号分隔的值列表初始化列表并将它们用花括号括起即可。如int yamcosts[3] {20, 30, 5};如果没有初始化数组的值则其元素值将是不确定的这意味着元素的值为以前驻留在该内存单元中的值。将sizeof运算符用于数组名得到的将是整个数组中的字节数。4.1.2 数组的初始化规则只有在定义数组时才能使用初始化此后就不能使用了也不能将一个数组赋给另一个数组这个方式和Python完全不同Python中没有原生的array基础类型主要是list列表、array.array数值数组、numpy库中的数组等原因可能是Python以引用为基础赋值C此处则是值赋值只能一个一个来声明时除外。但此后仍然可以通过下标分别给数组中的元素赋值。注意这里是赋值不再是初始化。初始化数组时提供的值可以少于数组的元素数目编译器将把其他元素设置为0。因此将数组中所有的元素都初始化为0非常简单—只要显式地将第一个元素初始化为0然后编译器会自动将其他元素都初始化为0。4.1.3 C11数组初始化方法数组以前就可使用列表初始化但C11中的列表初始化新增了一些功能。-首先初始化数组时可省略等号-其次可不在大括号内包含任何东西这将把所有元素都设置为零-第三列表初始化禁止缩窄转换。在上述代码中第一条语句不能通过编译因为将浮点数转换为整型是缩窄操作即使浮点数的小数点后面为零。第二条语句也不能通过编译因为1122011超出了char变量的取值范围这里假设char变量的长度为8位。第三条语句可通过编译因为虽然112是一个int值但它在char变量的取值范围内。4.2 字符串字符串是存储在内存的一片连续字节中的一系列字符这里的连续是重点。存储在连续字节中的一系列字符意味着可以将字符串存储在char数组中其中每个字符都位于自己的数组元素中。字符串提供了一种存储文本信息的便捷方式。本章介绍两种处理字符串的方法1C-风格字符串2基于string类的方法。C-风格字符串具有一种特殊的性质以空字符null character结尾空字符被写作\0其ASCII码为0用来标记字符串的结尾。这两个数组都是char数组但只有第二个数组是字符串空字符对C-风格字符串而言至关重要。C有很多处理字符串的函数其中包括cout使用的那些函数。它们都逐个地处理字符串中的字符直到到达空字符为止。如果使用cout显示上面的cat这样的字符串则将显示前7个字符发现空字符后停止。使用cout显示上面的dog数组它不是字符串cout将打印出数组中的8个字母并接着将内存中随后的各个字节解释为要打印的字符直到遇到空字符为止因此不加空字符是C-风格字符串中极其危险的行为。有一种更好的、将字符数组初始化为字符串的方法—只需使用一个用引号括起的字符串即可这种字符串被称为字符串常量string constant或字符串字面值string literal如下所示用引号括起来的字符串隐式地包括结尾的空字符因此不用显式地包括它。最需要注意的是使用C-风格字符串在确定存储字符串所需的最短数组时别忘了1要将结尾的空字符计算在内。这里的赋值改为char shirt_size[2] S;才是正确的。4.2.1 拼接字符串常量事实上任何两个由空白空格、制表符和换行符分隔的字符串常量都将自动拼接成一个。因此下面所有的输出语句都是等效的注意拼接时不会在被连接的字符串之间添加空格第二个字符串的第一个字符将紧跟在第一个字符串的最后一个字符不考虑\0后面。第一个字符串中的\0字符将被第二个字符串的第一个字符取代。4.2.2 在数组中使用字符串从程序清单4.2中可以学到什么呢首先sizeof运算符指出整个数组的长度15字节但strlen()函数返回的是存储在数组中的字符串的长度而不是数组本身的长度。另外strlen()只计算可见的字符而不把空字符计算在内。因此对于Basicman返回的值为8而不是9。如果cosmic是字符串则要存储该字符串数组的长度不能短于strlencosmic1。由于name1和name2是数组所以可以用索引来访问数组中各个字符。例如该程序使用name1[0]找到数组的第一个字符。另外该程序将name2[3]设置为空字符。这使得字符串在第3个字符后即结束虽然数组中还有其他的字符参见图4.3。程序清单4.2使用cin暗含两个问题:-遇到空格结束-输入字符串长于目标数组。4.2.3 字符串输入cin是如何确定已完成字符串输入呢由于不能通过键盘输入空字符因此cin需要用别的方法来确定字符串的结尾位置。cin使用空白空格、制表符和换行符来确定字符串的结束位置这意味着cin在获取字符数组输入时只读取一个单词。读取该单词后cin将该字符串放到数组中并自动在结尾添加空字符。这个例子的实际结果是cin把Alistair作为第一个字符串并将它放到name数组中。这把Dreeb留在输入队列中。当cin在输入队列中搜索用户喜欢的甜点时它发现了Dreeb因此cin读取Dreeb并将它放到dessert数组中。另一个问题是输入字符串可能比目标数组长运行中没有揭示出来。像这个例子一样使用cin确实不能防止将包含30个字符的字符串放到20个字符的数组中的情况发生。文章将这个问题的系统处理留到第17章但下文也能解决。4.2.4 每次读取一行字符串输入每次读取一个单词通常不是最好的选择。具体地说需要采用面向行而不是面向单词的方法。istream中的类如cin提供了一些面向行的类成员函数getline()和get()。这两个函数都读取一行输入直到到达换行符回车符。区别是getline()将读入并丢弃换行符而get()则将换行符保留在输入队列中。注意是还保留在输入流里没有被读走1面向行的输入getline()getline()函数读取整行它使用通过回车键输入的换行符来确定输入结尾。通过cin.getline()调用。该函数有两个参数第一个参数是存储输入行的数组名称第二个参数是要读取的字符数。如果这个参数为20则函数最多读取19个字符或者碰到换行符为止并自动在结尾处添加空字符。如cin.getline(array_name, 20);例子程序清单4.4:2面向行的输入get()iostream中的get()虽然和getline()类似它们接受的参数相同解释参数的方式也相同并且都读取到行尾。但get将换行符保留在输入队列中假设我们连续两次调用get( )那么要注意了。第一次调用后换行符将被留在输入队列中因此第二次调用时看到的第一个字符便是换行符此时get()以为已经到达行尾了而没有发现任何可读取的内容。如果不借助于帮助get()将不能跨过该换行符。这时要用get()的另一种变体——不带任何参数可读取下一个字符即使是换行符因此可以用它来处理换行符为读取下一行输入做好准备。例子由于cin.get(name, ArSize)返回的还是cin对象因此还可以合并起来调用cin.get(name, ArSize).get(); // 这样也能同时将末尾的换行符读走但风险就是假如ArSize小于一行的字符数那么调用get()后就会发生字符丢失。getline()成员函数也同样可以合并调用连续读取字符串cin.getline(name1, ArSize).getline(name2, ArSize);这语句将把输入中连续的两行分别读入到数组name1和name2中其效果与两次调用cin.getline()相同。为什么要使用get()而不是getline()呢首先老式实现没有getline()。其次get()使输入更仔细。例如假设用get( )将一行读入数组中。如何知道停止读取的原因是由于已经读取了整行而不是由于数组已填满呢getline判断不了查看下一个输入字符如果是换行符说明已读取了整行否则说明该行中还有其他输入。总之getline()使用起来简单一些但get( )使得检查错误更简单些。3空行和其他问题当getline()或get()读取空行时将发生什么情况最初的做法是下一条输入语句将在前一条getline()或get()结束读取的位置开始读取但当前的做法是当get()不是getline读取空行后将设置失效位failbit。这意味着接下来的输入将被阻断要用cin.clear()的命令来恢复输入。另一个潜在的问题是输入字符串可能比分配的空间长。如果输入行包含的字符数比指定的多则getline()和get()将把余下的字符留在输入队列中getline()还会设置失效位并关闭后面的输入。4.2.5 混合输入字符串和数字清单程序4.6的一个问题是当cin读取年份将回车键生成的换行符留在了输入队列中。后面的cin.getline()看到换行符后将认为是一个空行并将一个空字符串赋给address数组。解决之道是在读取地址之前先读取并丢弃换行符。这可以通过几种方法来完成其中包括使用没有参数的get()和使用接受一个char参数的get()。也可以利用表达式cinyear返回cin对象将调用拼接起来(cin year).get(); // or (cin year).get(ch);4.3 string类简介string类包含于头文件string并位于名称空间std中使用起来比字符数组简单它提供了将字符串作为一种数据类型的表示方法符合C的风格。string类定义隐藏了字符串的数组性质让我们能够像处理普通变量那样处理字符串。在很多方面使用string对象的方式和使用字符数组相同-可以使用C-风格字符串来初始化string对象-可以使用cin来将键盘输入存储到string对象中-可以使用cout来显示string对象-可以使用数组表示法来访问存储在string对象中的字符。string对象和字符数组的主要区别是可以将string对象声明为简单变量而不是数组。类设计让程序能够自动处理string的大小。例如str1的声明创建一个长度为0的string对象但程序将输入读取到str1中时将自动调整str1的长度。这使得与使用数组相比使用string对象更方便也更安全。从理论上说可以将char数组视为一组用于存储一个字符串的char存储单元而string类变量是一个表示字符串的实体。4.3.1 C11字符串初始化4.3.2 string赋值、拼接和附加string可以如简单变量那般操作。比如虽然不能将一个数组赋给另一个数组但却可以将一个string对象赋给另一个string对象。可以使用运算符将两个string对象合并起来还可以使用运算符将字符串附加到string对象的末尾。string str1 aa; string str2 bb; string str3; str3 str1 str2; // 正确 str3 str1; // 正确 str3 cc; // 一样正确4.3.3 string类的其他操作处理string对象的语法通常比使用C字符串函数简单尤其是执行较为复杂的操作时。例如对于下述操作str3 str1 str2;使用C-风格字符串时需要使用的函数如下strcpy(charr3, charr1); strcat(charr3, charr2);strcpy()和strcat()函数包含在头文件cstring中。函数strcpy()将字符串复制到字符数组中使用函数strcat()将字符串附加到字符数组末尾。另外使用字符数组时总是存在目标数组过小无法存储指定信息的危险。函数strcat()试图将全部12个字符复制到数组site中这将覆盖相邻的内存。这可能导致程序终止或者程序继续运行但数据被损坏。string类具有自动调整大小的功能从而能够避免这种问题发生。4.3.4 string类I/O在用户输入之前该程序指出数组charr中的字符串长度为27这比该数组的长度要大。这里要两点需要说明。首先为初始化的数组的内容是未定义的其次函数strlen()从数组的第一个元素开始计算字节数直到遇到空字符。在这个例子中在数组末尾的几个字节后才遇到空字符。对于未被初始化的数据第一个空字符的出现位置是随机的因此您在运行该程序时得到的数组长度很可能与此不同。用户输入之前str中的字符串长度为0。这是因为未被初始化的string对象的长度被自动设置为0。这是和字符串数组不同的地方。源代码中是将一行输入读取到string对象中的代码是getline(cin, str);。这个getline()不是类方法它将cin作为参数指出去哪里查找输入另外也没有指出字符串长度的参数因为string对象将根据字符串的长度自动调整自己的大小。4.3.5 其他形式的字符串字面值除char类型外C还有类型wchar_t而C11新增了类型char16_t和char32_t。可创建这些类型的数组和这些类型的字符串字面值。对于这些类型的字符串字面值C分别使用前缀L、u和U表示下面是一个如何使用这些前缀的例子C11还支持Unicode字符编码方案UTF-8。C11新增的另一种类型是原始raw字符串并使用前缀R来标识原始字符表示的就是自己例如序列\n不表示换行符而表示两个常规字符—斜杠和n因此在屏幕上显示时将显示这两个字符。4.4 结构体简介结构体struct是一种比数组更灵活的数据格式因为同一个结构体可以存储多种不同类型的数据从而将数据的表示合并到一起。原文称struct为“结构”我认为这不符合一贯以来的称呼因此在这个笔记中我一律改为通常在C/C中所称呼的“结构体”。结构体是用户定义的类型而结构体声明定义了这种类型的数据属性。结构体的定义需要用关键字struct做标识。定义了类型后便可以创建这种类型的变量。以下例子是一个结构体的定义它使用了一个适合用于存储字符串的char数组、一个float和一个double。列表中的每一项都被称为结构体成员因此infatable结构体有3个成员参见图4.6定义之后就可以将这个结构体当做常规的C数据类型如同int、double、string等来使用。在结构体类型中可以通过使用成员运算符.来访问各个成员。4.4.1 在程序中使用结构体结构声明的位置很重要。结构体的初始化方式和数组一样使用逗号分隔值列表并将这些值用花括号括起。如:花括号中每个值可以独占一行也可以将他们都放在同一行中注意变量之间有逗号隔开就行其他的随意。4.4.2 C11结构体初始化4.4.3 结构体可以将string类作为成员吗?可以只要编译器支持string类型就没问题唯一要注意的问题注意添加名字空间std。如:4.4.4 其他结构属性结构体和C内置类型的用法类似它可以作为参数传递给函数也可以让函数返回一个结构体。另外还可以使用赋值运算符将结构体赋给另一个同类型的结构体这样结构体中每个成员都将被设置为另一个结构中相应成员的值即使成员是数组。这种赋值被称为成员赋值memberwise assignment。结构体类型还可以同时完成定义结构体和创建结构体变量的工作只需将变量名放在结束括号的后面即可但不推荐这样做将结构体定义和变量声明分开可以使程序更易于阅读和理解。4.4.5 结构体数组创建结构体数组的方法和创建C基本类型的数组完全相同。要初始化结构体数组可以使用初始化数组的规则用逗号分隔每个元素的值并将这些值用花括号括起和初始化结构体的规则用逗号分隔每个成员的值并将这些值用花括号括起。由于数组中的每个元素都是结构因此可以使用结构初始化的方式来提供它的值。4.5 共用体共用体union也叫联合体是一种数据格式它能够存储不同的数据类型但只能同时存储其中的一种类型。也就是说结构体可以同时存储int、long和double共用体只能存储int、long或double。共用体的句法与结构相似但含义不同。如共用体每次只能存储一个值因此它必须有足够的空间来存储最大的成员所以共用体的长度为其最大成员的。共用体的用途之一是当数据项使用两种或更多种格式但不会同时使用时可节省内存空间其实对当前内存充裕的计算机来说并非很有必要使用共用体。4.6 枚举这也是一个很少用的类型略。4.7 指针和自由存储空间计算机在存储数据时必须跟踪的3种基本属性-信息存储在何处-存储的值为多少-存储的信息是什么类型。C提供了一种策略可以在程序内部跟踪内存单元这个策略以指针为基础。C中指针是一个变量其存储的是值的地址而不是值本身。首先我们如何找到常规变量的地址在C/C中只需对变量应用地址运算符就可以获得它的位置例如如果home是一个变量则home是它在内存中的地址。使用常规变量时值是指定的量而地址为派生量。接下来看看指针策略它是C内存管理编程理念的核心。指针处理存储数据的新策略刚好相反指针将地址视为指定的量而将值视为派生量因此指针名表示的是地址。*运算符被称为间接值indirect velue或解除引用dereferencing运算符应用于指针得到该地址处所存储的值。例如假设manly是一个指针则manly表示的是一个地址*manly表示存储在该地址处的值即*manly和常规变量等效。变量的指针和变量的值本身本质只不过是一个硬币的两个面。变量是编译时分配出来的一个有名称的内存这是变量的实质。而指针是一个可以通过名称直接访问内存的别名内存里存储的东西就是变量的值。4.7.1 声明和初始化指针不同数据类型存储值时的内存格式不同所以指针声明时必须指定指针指向的数据类型声明一个指针的通用方式:typeName *p_name;*符用于指明该变量是指针每个指针变量声明时都需要带*。使用指针的时候要注意区分地址是地址值是值。指针本身也是一种数据类型它的值是地址这个地址也有它自己的字节长度由计算机系统内定它的长度和它这个地址里存储的数值没有任何关系。指向int类型和指向double类型或者string类型的指针类型的字节长度都是一样的它们都是地址。4.7.2 指针的危险创建指针时计算机将分配用来存储指针这个变量本身的内存但不会分配指针所指向的数据所需的内存。为数据提供空间是一个独立的步骤如果忽略这一步麻烦就大了。如long *fellow; // 创建一个指向 long 类型的指针 *fellow 223323; // 指针还不知道指向哪个地址就强制赋值大错特错声明时只是告诉程序fellow是一个long指针程序只给fellow这个指针变量分配了一个属于fellow自己的存储空间至于它里面要存什么地址值还完全不知道。必须先申请一个存long类型的地址空间然后赋值给fellow最后才可以将223323赋值过去。一定要在对指针应用解除引用运算符*之前将指针初始化为一个确定的、适当的地址这是关于使用指针的铁律。4.7.3 指针和数字指针不是整型虽然计算机通常把地址当作整数来处理。在有些平台中int类型是个2字节值而地址是个4字节值。4.7.4 使用new来分配内存指针真正的用武之地在于运行阶段分配未命名的内存用于存储值。C通过new运算符为变量分配内存。程序员要告诉new需要为哪种数据类型分配内存new将找到一个长度正确的内存块并返回该内存块的地址。程序员的责任是将该地址赋给一个指针。下面是一个这样的示例int *pn new int;new int告诉程序需要适合存储int的内存。new运算符根据类型来确定需要多少字节的内存。然后它找到这样的内存并返回其地址。接下来将地址赋给pnpn是被声明为指向int的指针。现在pn是地址而*pn是存储在那里的值。注意pn指向的内存不是变量那是一个地址是不变的内存里存储的东西才是可变的。这引出了一个问题pn指向的内存没有名称如何称呼它呢我们说pn指向一个数据对象这里的“对象”不是“面向对象编程”中的对象而是一样“东西”。术语“数据对象”比“变量”更通用它指的是为数据项分配的内存块。为一个数据对象可以是结构也可以是基本类型获得并指定分配内存的通用格式如下typeName *pointer_name new typeName;需要指出的另一点是new分配的内存块通常与常规变量声明分配的内存块不同。常规变量存储在被称为栈stack的内存区域中而new从被称为堆heap或自由存储区free store的内存区域分配内存。如果内存分配失败new运算符将返回空指针null pointer其值为0。4.7.5 使用delete释放内存delete运算符用于将不再使用的内存归还给内存池归还或释放free的内存可供程序的其他部分使用。使用delete时后面要加上指向内存块的指针这些内存块最初是用new分配的。int *ps new int; // 声明指针并分配一个可以存 int 类型的内存给指针 ... delete ps; // 归还内存归还ps指向的内存并不会删除指针ps本身它可以继续用来指向新分配的内存。new和delete一定要配对使用否则会导致程序发生内存泄露memory leak即分配出去的内存拿不回来无法再使用。另外不要尝试释放已经释放的内存块C标准指出这样做的结果将是不确定的这意味着什么情况都可能发生。一般来说不要创建两个指向同一个内存块的指针因为这将增加错误地删除同一个内存块两次的可能性。另外不能使用delete释放声明变量所获得的内存只能用它释放new分配的内存这是关键点。4.7.6 使用new来创建动态数组对于大型数据如数组、字符串和结构应使用new这正是new的用武之地。在编译时给数组分配内存被称为静态联编static binding这就是为什么只能分配固定大小的数组。但使用new时在运行阶段需要数组则创建它如果不需要则不创建还可以选择数组的长度这被称为动态联编dynamic binding意味着数组是在程序运行时创建的。1使用new创建动态数组创建动态数组很容易只要将数组的元素类型和元素数目告诉new即可。必须在类型名后加上方括号其中包含元素数目。为数组分配内存的通用格式如下typeName *pointer_name new typeName [num_elements];给个例子如int *psome new int [10]; // 分配可以存储 10 个 int 的内存块new返回第一个元素的地址并将该地址被赋给指针psome。释放这一块内存时要这样做delete [] psome;方括号告诉程序应释放整个数组而不仅仅是指针指向的元素。注意delete和指针之间有方括号。对于ANSI/ISO标准来说new与delete的格式要匹配否则导致的后果是不确定。总之使用new和delete时应遵守以下规则-不要使用delete来释放不是new分配的内存-不要使用delete释放同一个内存块两次-如果使用new []为数组分配内存则应使用delete []来释放-如果使用new []为一个实体分配内存则应使用delete没有方括号来释放(这一点要找例子来理解)-对空指针应用delete是安全的。2使用动态数组创建动态数组后如何使用它呢只需要将指针名当做数组名然后按照数组的访问方式即可C/C中数组和指针是基本等价的但也有实质区别后续再说。例子int *psome new int [10];对于第1个元素可以使用psome[0]而不是*psome对于第2个元素可以使用psome[1]依此类推。程序清单4.18演示了如何使用new来创建动态数组以及使用数组表示法来访问元素它还指出了指针和真正的数组名之间的根本差别注意红框。将p3加1导致它指向第2个元素而不是第1个。将它减1后指针将指向原来的值这样程序便可以给delete[]提供正确的地址。(这叫指针移动)4.8 指针、数组和指针算术指针算术是有特殊之处的。指针和数组基本等价的原因在于指针算术pointer arithmetic和C内部处理数组的方式。将整数变量加1后其值将增加1但将指针变量加1后增加的量等于它指向的类型的字节数。将指向double的指针加1后如果系统对double使用8个字节存储则数值将增加8将指向short的指针加1后如果系统对short使用2个字节存储则指针值将增加2。4.8.1 程序说明将指针变量加1后其增加的值等于指向的类型占用的字节数。也就是跳到下一个存储值的地址上。从该程序的输出可知*stacks 1和stacks[1]是等价的。同样*stacks 2和stacks[2]也是等价的。通常使用数组表示法时C都执行下面的转换arrayname[i] becomes *(arrayname i)如果使用的是指针而不是数组名则C也将执行同样的转换pointername[i] becomes *(pointername i)因此在很多情况下可以相同的方式使用指针名和数组名。也就是说使用new来创建数组以及使用指针来访问不同的元素时只要把指针当作数组名对待即可。【注意】数组名被解释为其第一个元素的地址而对数组名应用地址运算符时得到的是整个数组的地址区别很大。4.8.2 指针小结1声明指针要声明指向特定类型的指针请使用下面的格式typeName *pointer_name;2给指针赋值应将内存地址赋给指针。可以对变量名应用地址运算符来获得被命名的内存的地址或new运算符返回未命名的内存的地址。3对指针解引用对指针解引用意味着获得指针指向的值。*是指针应用解引用或间接值运算符。4区分指针和指针所指向的值如果pt是指向int的指针则*pt不是指向int的指针而是完全等同于一个int类型的变量pt才是指针。5数组名大多数情况下C将数组名视为数组的第一个元素的地址。一种例外情况是将sizeof运算符用于数组名用时此时将返回整个数组的长度单位为字节。6指针算术C允许将指针和整数相加。加1的结果等于原来的地址值加上指向的对象占用的总字节数也就是移动到下一个存储值的地址。还可以将一个指针减去另一个指针获得两个指针的差。后一种运算将得到一个整数仅当两个指针指向同一个数组也可以指向超出结尾的一个位置时这种运算才有意义这将得到两个元素的间隔。7数组的动态联编和静态联编使用数组声明来创建数组时将采用静态联编即数组的长度在编译时设置。使用new[]运算符创建数组时将采用动态联编动态数组即将在运行时为数组分配空间其长度也将在运行时设置。使用完这种数组后应使用delete []释放其占用的内存。8数组表示法和指针表示法使用方括号数组表示法等同于对指针解引用。数组名和指针变量都是如此因此对于指针和数组名既可以使用指针表示法也可以使用数组表示法。4.8.3 指针和字符串数组和指针的特殊关系可以扩展到C-风格字符串。char flower[10] rose; cout flower s are red.\n;以上代码中数组名是第一个元素的地址因此cout语句中的flower是包含字符r的char元素的地址。cout对象认为char的地址是字符串的地址因此它打印该地址处的字符然后继续打印后面的字符直到遇到空字符(\0)为止。总之如果给cout提供一个字符的地址则它将从该字符开始打印直到遇到空字符为止。这意味着可以将指向char的指针变量作为cout的参数因为它也是char的地址。在C中用引号括起的字符串像数组名一样也是第一个元素的地址。这意味着对于数组中的字符串、用引号括起的字符串常量以及指针所描述的字符串处理的方式是一样的都将传递它们的地址。与逐个传递字符串中的所有字符相比这样做的工作量确实要少。在cout和多数C表达式中char数组名、char指针以及用引号括起的字符串常量都被解释为字符串第一个字符的地址。请不要使用字符串常量或未被初始化的指针来接收输入。为避免这些问题也可以使用std::string对象而不是数组。在将字符串读入程序时应使用已分配的内存地址。该地址可以是数组名也可以是使用new初始化过的指针。注意这段代码及其输出一般来说如果给cout提供一个指针它将打印地址。但如果指针的类型为char *则cout将显示指向的字符串。如果要显示的是字符串的地址则必须将这种指针强制转换为另一种指针类型如int *上面的代码就是这样做的。经常需要将字符串放到数组中。初始化数组时请使用运算符否则应使用strcpy()或strncpy()。应使用strcpy()或strncpy()而不是赋值运算符来将字符串赋给数组。这是C-风格字符串的操作方式Cstring类型则并不需要如此会简单得多不用担心字符串会导致数组越界并可以使用赋值运算符而不是函数strcpy()和strncpy()。4.8.4 使用new创建动态结构体在运行时创建数组优于在编译时创建数组对于结构也是如此。通过使用new可以创建动态结构。将new用于结构体由两步组成创建结构体和访问其成员。要创建结构体需要同时使用结构体类型和new。inflatable *ps new inflatable;这将把足以存储inflatable结构的一块可用内存的地址赋给ps。这种句法和C的内置类型完全相同。此时要访问结构体成员需通过箭头成员运算符−它用于指向结构体的指针就像点运算符可用于结构体名一样如ps-price指向了结构体中的price成员。如果结构标识符是结构名则使用句点运算符如果标识符是指向结构的指针则使用箭头运算符。另一种访问结构成员的方法是先对指针解引用。如果ps是指向结构的指针则*ps就是被指向的值—结构本身。由于*ps是一个结构因此(*ps).price是该结构的price成员。4.8.5 自动存储、静态存储和动态存储根据用于分配内存的方法C有3种管理数据内存的方式自动存储、静态存储和动态存储有时也叫作自由存储空间或堆。在存在时间的长短方面以这3种方式分配的数据对象各不相同。1.自动存储在函数内部定义的常规变量使用自动存储空间被称为自动变量automatic variable只在包含它的代码中有效这意味着它们在所属的函数被调用时自动产生在该函数结束时消亡。自动变量通常存储在栈中。2.静态存储静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种一种是在函数外面定义它另一种是在声明变量时使用关键字static。3.动态存储new和delete运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池这在C中被称为自由存储空间free store或堆heap。该内存池同用于静态变量和自动变量的内存是分开的。4.9 类型组合指针数组指向指针的指针也叫二维指针可以用来创建动态二维数组类型较为复杂。4.10 数组的替代品STL标准库中的模板类vector和array是数组的替代品。4.10.1 模板类vector模板类vector类似于string类也是一种动态数组创建时要包含头文件vector。可以在运行阶段设置vector对象的长度也可在末尾附加新数据还可以在中间插入新数据vector的长度不要求必须设定因为它会依据插入的数据量自动增长容量增长的速度是2的n次方。基本上它是使用new创建动态数组的替代品。实际上vector类确实使用new和delete来管理内存但这种工作是自动完成的。vector的声明方式#includevector vectortypeName v_name(number);这样v_name就是一个vector对象但number不是必须的。vector的最完整文档和例子应参照网站http://cplusplus.com/reference/vector/vector。4.10.2 模板类arrayC11vector类的功能比数组强大但付出的代价是效率稍低其实没那么低效。如果您需要的是长度固定的数组使用数组是更佳的选择但代价是不那么方便和安全。有鉴于此C11新增了模板类array它也位于名称空间std中。与数组一样array对象的长度也是固定的也使用栈静态内存分配而不是自由存储区因此其效率与数组相同但更方便更安全。要创建array对象需要包含头文件array。array对象的创建语法与vector稍有不同#includearray arraytypeName, num_element arr;与创建vector对象不同的是n_element不能是变量必须是固定的常量和普通数组要求一样。通过array模板类创建数组有什么优势https://cplusplus.com/reference/array/array4.10.3 比较数组、vector对象和array对象这三者的异同是什么首先无论是数组、vector对象还是array对象都可使用标准数组表示法来访问各个元素其次从地址可知array对象和数组存储在相同的内存区域即栈中而vector对象存储在另一个区域自由存储区或堆中第三可以将一个array对象直接赋给另一个array对象而标准数组必须逐元素复制数据(使用array模板类的两个好处之一另一个好处是控制数组超界风险)。例子在上面代码中注意一个语句a1[-2] 20.2;这是什么意思这个语句会被转换为*(a1 - 2) 20.2;含义是找到a1指向的地方向前移两个double元素并将20.2存储到目的地。也就是说将信息存储到数组的外面。与C语言一样C也不检查这种超界错误也就是说数组的这种行为是不安全的C却不禁止要小心。vector和array对象能够禁止这种行为吗如果您让它们禁止它们就能禁止。另外这些类还让您能够降低意外超界错误的概率。例如它们包含成员函数begin()和end()让您能够确定边界以免无意间超界。4.11 总结数组、结构体和指针是C的3种复合类型。数组可以在一个数据对象中存储多个同种类型的值。通过使用索引或下标可以访问数组中各个元素。结构体可以将多个不同类型的值存储在同一个数据对象中可以使用成员关系运算符.来访问其中的成员。使用结构体的第一步是创建结构体模板它定义结构存储了哪些成员。模板的名称将成为新类型的标识符然后就可以声明这种类型的结构变量。指针是被设计用来存储地址的变量指针指向它存储的地址。指针声明指出了指针指向的对象的类型。对指针应用解引用运算符*将得到指针指向的位置中的值。字符串是以空字符为结尾的一系列字符。字符串可用引号括起的字符串常量表示其中隐式包含了结尾的空字符。可以将字符串存储在char数组中可以用被初始化为指向字符串的char指针表示字符串。string对象将根据要存储的字符串自动调整其大小用户可以使用赋值运算符来复制字符串。new运算符允许在程序运行时为数据对象请求内存。该运算符返回获得内存的地址可以将这个地址赋给一个指针程序将只能使用该指针来访问这块内存。使用解引用运算符*获得其值如果数据对象是数组则可以像使用数组名那样使用指针来访问元素如果数据对象是结构体则可以用指针解引用运算符-访问其成员。指针和数组紧密相关。如果ar是数组名则表达式ar[i]被解释为*(ar i)i可以是负值表示指针左移其中数组名被解释为数组第一个元素的地址。这样数组名的作用和指针相同。反过来可以使用数组表示法通过指针名来访问new分配的数组中的元素。运算符new和delete允许显式控制何时给数据对象分配内存何时将内存归还给内存池。