本章内容包括
- 创建和使用数组
- 创建和使用C-风格字符串
- 创建和使用string类字符串
- 使用方法getline()和get()读取字符串
- 混合输入字符串和数字
- 创建和使用结构
- 创建和使用共用体
- 创建和使用枚举
- 创建和使用指针
- 使用new和delete管理动态内存
- 创建动态数组
- 创建动态结构
- 自动存储、静态存储和动态存储
- vector和array类简介
4.1 数组
数组(array)是一种数据格式,能够存储多个同类型的值。
数组声明的内容:
-
存储在每个元素中的值的类型
-
数组名
-
数组中的元素数
声明数组的通用格式: typeName arrayName[arraySize];
arraySize指明元素数目,必须是整型常量或者常量表达式,不能是变量。
数组的初始化规则
- 只有在定义数组时才能使用初始化,此后就不能使用,也不能将一个数组赋给另一个数组;
如:int cards[4] = {1, 2, 3, 4}; //允许
int hand[4]; hand[4] = {5, 6, 7, 8}; //不允许! - 初始化数组时,提供的值可以少于数组的元素数目,编译器将其它元素置为0;
- int things[] = {1, 2, 3, 4}; //允许
C++数组初始化方法
- 初始化数组时,可省略等号,如:int ear[3] {1, 2, 3};
- 可不在大括号里包含任何东西,编译器自动将所有元素置为0,如:int counts[] = {};
- 列表初始化禁止缩窄转换,如long plifs[] = {25, 92, 65.3};
4.2 字符串
C++处理字符串有两种方式:C-风格字符串、string类库
C-风格字符串
将字符串存储在char数组中,其中每个字符都位于自己的数组元素中。
C-风格字符串的特殊性质:以空字符(null character)结尾,空字符被写作“\0”,其ASCII码为0,用来标记字符串的结尾。如:
char dog[8] = {‘b’, ‘e’, ‘a’, ‘u’, ‘x’, ’ ', ‘I’, ‘I’}; //不是字符串
char cat[8] = {‘f’, ‘a’, ‘t’, ‘e’, ‘s’, 's ', ‘a’, ‘\0’}; //是字符串
将字符数组初始化为字符串的另一种方法:字符串常量 或 字符串字面值。如:
char bird[11] = “Mr. Cheeps”; //用双引号括起来的字符串隐式的包括结尾的空字符
char fish[] = “Bubbles”; //编译器自动计算
注意:
- 在确定存储字符串所需的最短数组时,需要将结尾的空字符计算在内
- 字符串常量(使用双引号)不能与字符常量(使用单引号)互换。在ASCII系统上,‘S’是83的另一种写法
- “S”是由字符S和\0组成的字符串。实际上,“S”表示字符串所在的内存地址
拼接字符串常量
任何两个由空白(空格、制表符(\t)和换行符)分隔的字符串常量都将自动拼接成一个
程序清单4.2 string.cpp
——————————————————————————————————————————————————————————————————————————————————————
#include<iostream>
#include<cstring>
int main()
{
using namespace std;
const int Size = 15;
char name1[Size];
char name2[Size] = "C++owboy";
cout << "Howdy! I'm " << name2;
cout << "! What's your name?\n";
cin >> name1;
cout << "Well, " << name1 << ", your name has ";
cout << strlen(name1) << " letters and is stored\n";
cout << " in an array of " << sizeof(name1) << " bytes.\n";
name2[3] = '\0';
cout << "Here are the first 3 characters of my name: ";
cout << name2 << endl;
return 0;
}
——————————————————————————————————————————————————————————————————————————————————————
运行结果
程序说明:
- sizeof()运算符指出整个数组的长度,strlen()函数返回存储在数组中的可见字符串的长度,不包括空字符
- name2[3]是通过索引来访问数组中的字符,其他字符也可按同样方法访问
- 使用符号常量Size来指定数组长度,便于修改
字符串输入
指出问题:
程序清单4.3 instrl.cpp
————————————————————————————————————————————————————————————————————————————————————————
#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 >> dessret;
cout << "I have some delicious " << dessert;
cout << " for you, " << name << ".\n";
return 0;
}
————————————————————————————————————————————————————————————————————————————————————————
运行结果
程序说明:
cin使用空白(空格、制表符(\t)和换行符)来确定字符串的结束位置,这意味着cin在获取字符数组输入时只读取一个单词。读取该单词后,cin将该字符串放到数组中,并自动在结尾添加空字符。
实际情况是,读取Alistair后自动添加\0赋给name,而Dreeb依然在队列中。当要输入dessert时,发现了Dreeb,因此将其赋给dessert并自动补上\0。
每次读取一行字符输入
面向行的字符串读取方法:getline()和get()。这两个函数都读取一行输入,直到到达换行符。随后,getline()将丢弃换行符,get()将换行符保留在输入序列中。
-
getline()
-getline()读取整行,通过回车键输入的换行符来确定输入结尾。
-调用方法:cin.getline(name, 20); //最多读取19个字符,最后一个字符存放\0
-getline()成员函数在读取到指定数目的字符或者遇到换行符时停止读取 -
get()
get()和getline()的区别之一在于对换行符的处理方式
当连续两次调用get(),第一次调用将换行符留在队列中,第二次调用将发现没有任何可读取的内容,正确的处理方式如下:
cin.get(name1, ArSize);
cin.get();
cin.get(name2, ArSize);
其中,get()函数拥有两种不同的参数形式,是因为函数重载这一特性。
3. 空行和其它问题
混合输入字符串和数字
指出问题:
程序清单4.6 numstr.cpp
———————————————————————————————————————————————————————————————————————————————————————————
#include<iostream>
int main(()
{
using namespace std;
cout << "What year was your house built?\n";
int year;
cin >> year;
cout << "What is its street adress?\n";
char adress[80];
cin.getline(adress, 80);
cout << "Year built: " << year <<endl;
cout << "Adress: " << adress << endl;
cout << "Done!\n";
return 0;
}
————————————————————————————————————————————————————————————————————————————————————————————
运行结果:
用户没有输入地址的机会。问题在于,cin读取年份之后,将回车键生成的换行符留在了输入队列中。
方法:(cin >> year).get(); or (cin >> year).get(ch); //ch为char参数
4.3 string类简介
要使用string类,必须在程序中包含头文件string。string类位于名称空间std中。
程序清单4.9 strtype3.cpp
————————————————————————————————————————————————————————————————————————————————————————————
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
int main()
{
char charr1[20];
char charr2[20] = "jaguar";
string str1;
string str2 = "panther";
str1 = str2;
strcpy_s(charr1, charr2);
str1 += "paste";
strcat_s(charr1, " juice");
int len1 = str1.size();
int len2 = strlen(charr1);
cout << "The string " << str1 << " contains "
<< len1 << " characters.\n";
cout << "The string " << charr1 << "contains "
<< len2 << " characters.\n";
return 0;
}
————————————————————————————————————————————————————————————————————————————————————————————
程序说明:
strcat_s(str1, str2); ——将字符串2扩展到字符串1的末尾;
strcpy_s(str1, str2); ——将字符串2的内容复制到字符串1中。
具有相同功能,但能够自动调整字符串长度的string类函数:strncat、strncpy;它们接受指出最大目标数组最大允许长度的第三个参数。
string类I/O
程序清单4.10 strtype4.cpp
——————————————————————————————————————————————————————————————————————————————————————————
#include<iostream>
#include<string>
#include<cstring>
using namespace std;
int main()
{
char charr[20];
string str;
cout << "Length of string in charr before input: "
<< strlen(charr) << endl;
cout << "Length of string in str before input: "
<< strlen(str) << endl;
cout << "Enter a line of text: \n";
cin.getline(charr, 20);
cout << "You entered: " << charr << endl;
cout << "Enter another line of text: \n";
getline(cin, str);
cout << "You entered: " << str << endl;
cout << "Length of string in charr after input: "
<< strlen(charr) << endl;
cout << "Length of string in str after input: "
<< str.size() << endl;
return 0;
}
——————————————————————————————————————————————————————————————————————————————————————————
程序说明:
- 未初始化的数组其内容不定,因此长度不定
- strlen()从数组第一个元素计算字节数,直到第一个空字节为止
- 未初始化的string对象的长度被自动设置为0
- cin.getline(charr 20) 表明,cin是一个istream对象,getline()是istream类的一个类方法
- getline(cin, str); 表明getline()不是类方法,它将cin作为参数,指出到哪里去查找输入
4.4 结构简介
同一个结构中可以存储多类型数据
结构的使用步骤:先定义结构——指明和标记能够存储在结构中的各种数据类型;然后创建结构变量(结构数据变量)。C++允许在声明结构变量时省略关键字struct。
——————————————————————定义——————————————————
struct example
{
char name[20];
float volume;
double price;
}
—————————————————创建结构变量——————————————————
(struct) example map = {"Apple dog", 1.88, 20.595 }; //注意符号的变化,随后可以(.)来访问
//结构成员,如map.name
访问类成员函数(cin.get())的方式就是从访问结构成员变量的方式衍生而来。
结构初始化
————————————1————————————————
example map =
{
"Apple dog",
1.88,
20.595
} ;
或 example map = {"Apple dog", 1.88, 20.595 };
或 example map {"Apple dog", 1.88, 20.595 };
空内容:example map {}; //各成员变量的每个数或字节都被置为0
可以使用赋值运算符(=)将结构赋给另一个同类型的结构。
————————————2————————————————定义结构与创建结构变量
struct example
{
char name[20];
float volume;
double price;
}map1, map2;
————————————3————————————————定义结构与创建结构变量,并初始化
struct example
{
char name[20];
float volume;
double price;
}map1=
{
"Apple dog",
1.88,
20.595
};
————————————4————————————————没有名称的结构类型
struct
{
char name[20];
float volume;
double price;
}position; //但以后无法创建此类型变量;
结构数组
方法:example map[100];
初始化结构数组可以结合初始化数组和初始化结构的规则:
example map[2] =
{
{ "Bambi", 0.52, 89.1535},
{ "Godzilla", 420.55, 46}
};
结构的位字段
C++允许使用指定的占用特定位数的结构成员,这使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。字段的类型为整型或者枚举,接下来是冒号,冒号后面是指定该变量使用的位数。可以使用没有名称的字段来提供间距。每个成员都被称为位字段。如:
struct torgle_register
{
unsigned int SN : 4;
unsingned int : 4;
bool goodIn : 1;
bool goodTorgle : 1;
}
可以使用标准的结构表示法来访问位字段。位字段通常用于低级编程。
4.5 共用体
共用体(union)是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型。也就是说,结构可以同时存储int、 long 和double,共用体只能存储int、long 或double。共用体的句法与结构相似,但含义不同。如:
union one4all
{
int int_ val;
long 1ong_ val;
double double_ val;
};
//可以使用one4all变量来存储int、 long 或double,条件是在不同的时间进行:
one4all pail;
pail. int_ val = 15; // store an int
cout << pail. int_ val ;
pail .double_ val = 1.38; // store a double, int value is lost
cout << pail . double_ val;
pail 有时是int 变量,有时是double 变量。成员名称标识了变量白用体每次只能存储一个值,因此它必须有足够的空间来存储最大的成员。共用体的长度为其最大成员长度。
共用体的用处之一:当数据项使用两种或者多种(不同时)格式时,可节省空间。可应用于结构变量的成员变量。
共用体常用于操作系统数据结构或者硬件数据结构。
4.6 枚举
可代替关键字const的另一种创建符号常量的方式:enum工具。
使用方法:enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet}; 解释:
- 将spectrum定义为枚举类型;
- 将red, orange, yellow等作为符号常量,对应于整数值0~7,这些常量称为枚举量。
设置枚举量的值
- enum bits{one = 1, two = 2, four = 4, eight = 8};
- enum bigstep{first, second = 100, third}; //first默认为0,third比second大1
- enum {zero, null = 0, one, numero_uno = 1}; //zero为0,one为1
枚举的取值范围
4.7 指针和自由存储空间
常规变量:值是指定的量,地址为派生量;指针:地址是指定的量,值为派生量,指针名表示地址。
“ * ”运算符被称为间接值或者解除引用运算符,将其应用于指针变量,可以得到该地址处储存的值。
声明指针:int * ps; 指针声明必须指定指针指向的数据的类型。ps指向int类型。
格式上:* 运算符两边的空格可选。
初始化指针:int hat = 5; int * ps = &hat; 此时初始化的是指针,并非指针指向的值。
指针的危险
在C++创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。
例如:long * fellow; *fellow = 223323; fellow是指向long类型的指针,代码中没有将地址赋给fellow,也就是fellow没有被初始化。不管它的值是什么,程序都将其解释为存储223323的地址,但如果fellow指向的地址并非系统所要存储223323的地方(如程序代码的地址1200),会导致一些很隐秘、很难发现的bug。
要将数字值当作地址来使用,应通过强制类型转换(typeName *)将数字转换为适当的地址类型。
——————————————————————————————————————————————————————————————————————————————————————
int * pt;
pt = 0xB8000000; //wrong
int * pt;
pt = (int *) 0xB8000000; //right
——————————————————————————————————————————————————————————————————————————————————————
运算符:new 和 delete
指针的真正用处,是在运行阶段分配未命名的内存以存储值。
为一个数据对象获得并指定分配内存的通用格式如下:
typeName * pointer_name = new typeName;
使用new声明的指针指向为typeName类型数据项分配的内存块的地址。初始化指针的方式,可以通过访问内存的名称来访问数据对象,如通过hat访问储存的int类型数据对象。
new分配的内存块通常与常规变量声明分配的内存块不同。变量hat和pt的值都存储在被称为栈(stack)的内存区域中,而new从被称为==堆(heap)或自由存储区(free store)==的内存区域分配内存。
当需要内存时,可以使用new来请求;在使用完内存后,可通过delete运算符将其归还给内存池,这是通向最有效地使用内存的关键一步。不能用delete来释放声明变量所获得的内存,只能用其释放使用new分配的内存。
一定要配对使用new和delete,否则将发生内存泄漏(memory leak)。
typeName * pointer_name = new typeName;
delete pointer_name;
使用new创建动态数组
在编译时给数组分配内存被称为静态联编,意味着数组是在编译时加入到程序中的。但使用new时,如果在运行阶段需要数组,则创建它;如果不需要,则不创建。还可以在程序运行时选择数组的长度,这被称为动态联编( dynamic binding),意味着数组是在程序运行时创建的。这种数组叫作动态数组(dynamic array)。
使用静态联编时,必须在编写程序时指定数组的长度;使用动态联编时,程序将在运行时确定数组的长度。
typeName * pointer_name = new typeName [num_elements];
delete [] pointer_name;
使用时,只要把指针当作数组名即可。
程序清单4.18 arraynew.cpp
______________________________________________________________________________________
#include<iostream>
using namespace std;
int main()
{
double * p3 = new double[3];
p3[0] = 0.2;
p3[1] = 0.5;
p3[2] = 0.8;
cout << "p3[1] is " << p3[1] << ".\n";
p3 = p3 + 1;
cout << "Now p3[0] is " << p3[0] << "and ";
cout << "p3[1] is " << p3[1] << ".\n";
p3 = p3 - 1;
delete [] p3;
return 0;
}
______________________________________________________________________________________
4.8 指针、数组和指针算
指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式。C++将数组名解释为地址。
程序清单4.19 addpntrs.cpp
——————————————————————————————————————————————————————————————————————————————————————————
#include<iostream>
using namespace std;
int main()
{
double wages[3] = { 10000.0, 20000.0, 30000.0 };
short stacks[3] = { 3, 2, 1 };
double * pw = wages;
short * ps = &stacks[0];
cout << "pw = " << pw << ", *pw = " << *pw << endl;
pw = pw + 1;
cout << "add 1 to the pw pointer:\n";
cout << "pw = " << pw << ", *pw = " << *pw << "\n\n";
cout << "ps = " << ps << ", *ps = " << *ps << endl;
ps = ps + 1;
cout << "add 1 to the ps pointer:\n";
cout << "ps = " << ps << ", *ps = " << *ps << "\n\n";
cout << "access two elements with array notation\n";
cout << "stacks[0] = " << stacks[0]
<< ", stacks[1] = " << stacks[1] << endl;
cout << "acess two elements with pointer notation\n";
cout << "*stacks = " << *stacks
<< ", *(stacks +1) = " << *(stacks + 1) << endl;
cout << sizeof(wages) << " = size of wages array\n";
cout << sizeof(pw) << " = size of pw pointer\n";
return 0;
}
——————————————————————————————————————————————————————————————————————————————————————————
指针和字符串
在cout和多数C++表达式中,char数组名、char指针及用引号括起的字符串常量都被解释为字符串第一个字符的地址。
- 有些编译器将在字符串字面值视为只读常量,试图修改会导致运行阶段错误
- 有些编译器只使用字符串字面值的一个副本来表示程序中所有的该字面值
- 应使用strcpy()或者strncpy(),而不是赋值运算符来将字符串赋给数组
使用new创建动态结构
*用new创建结构分两步:创建结构和访问其成员。
创建结构:
struct_name * pointer_name = new struct_name;
创建动态结构时,不能将成员运算符句点用于结构名,因为这种结构没有名称,只是知道它的地址。C++专门为这种情况提供了一一个运算符: 箭头成员运算符(->)。
访问结构成员:
pointer_name -> member_name;
另一种访问方法:
(*pointer_name).member_name;
#include<iostream>
using namespace std;
struct inflatable
{
char name[20];
float volume;
double price;
};
int main()
{
inflatable * ps = new inflatable;
cout << "Enter name of inflatable item: ";
cin.get(ps->name, 20);
cout << "Enter volume in cublic feet: ";
cin >> (*ps).volume;
cout << "Enter price: $";
cin >> ps->price;
cout << "Name: " << (*ps).name << endl;
cout << "Volume: " << ps->volume << " cubic feet"<<endl;
cout << "Price: $" << ps->price << endl;
delete ps;
return 0;
}
自动存储、静态存储、动态存储
根据用于分配内存的方法,C++有3种管理数据内存的方式:自动存储、静态存储和动态存储(有时也叫作自由存储空间或堆)。
- 自动存储
在函数内部定义的常规变量使用自动存储空间,被称为自动变量(automatic variable),这意味着它们在所属的函数被调用时自动产生,在该函数结束时消亡。
实际上,自动变量是一个局部变量,其作用域为包含它的代码块。代码块是被包含在花括号中的一段代码。
自动变量通常存储在栈中。这意味着执行代码块时,其中的变量将依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量,这被称为后进先出(LIFO)。 因此,在程序执行过程中,栈将不断地增大和缩小。 - 静态存储
静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:一种是在函数外面定义它;另一种是在声明变量时使用关键字static:
static double fee = 56.50;
自动存储和静态存储的关键在于:这些方法严格地限制了变量的寿命。变量可能存在于程序的整个生命周期(静态变量),也可能只是在特定函数被执行时存在(自动变量)。
- 动态存储
new和delete运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池,这在C++中被称为自由存储空间(free store)或堆(heap)。 该内存池同用于静态变量和自动变量的内存是分开的。
4.9 类型组合
创建结构变量:
struct struct_name {} struct_value;
————————————————————————————————————————
创建结构指针:
struct_name * pa = &struct_value;
————————————————————————————————————————
创建结构数组:
struct_name arr[size];
————————————————————————————————————————
创建指针变量:
const struct_name * arr[3] = { &struct_value1, &struct_value2, &struct_value3};
————————————————————————————————————————
创建指向指针数组的指针:
const struct_name ** ps = arr;
————————————————————————————————————————
#include<iostream>
using namespace std;
struct antarctica_years_end
{
int year;
};
int main()
{
antarctica_years_end s01, s02, s03;
s01.year = 1998;
antarctica_years_end * pa = &s02;
pa->year = 1999;
antarctica_years_end trio[3];
trio[0].year = 2003;
cout << trio->year << endl;
const antarctica_years_end * arp[3] = { &s01, &s02, &s03 };
cout << arp[1]->year << endl;
const antarctica_years_end ** ppa = arp;
auto ppb = arp;
cout << (*ppa)->year << endl;
cout << (*(ppb + 1))->year << endl;
return 0;
}
4.10 数组的替代品
模板类vector和array是数组的替代品。
模板类vector
模板类vector 类似于string 类,也是-种动态数组。您可以在运行阶段设置vector 对象的长度,可在末尾附加新数据,还可在中间插入新数据。基本上,它是使用new创建动态数组的替代品。实际上,vector类确实使用new和delete来管理内存,但这种工作是自动完成的。
#include<iostream>
...
using namespace std;
vector<int>vi;
int n;
cin >> n;
vector<double>vd(n);
____________________________________________________________________________________________________
vector<typeName>vt(n_elem);
模板类array
vector类的功能比数组强大,但付出的代价是效率稍低。如果您需要的是长度固定的数组,使用数组是更佳的选择,但代价是不那么方便和安全。有鉴于此,C++11 新增了模板类array,它也位于名称空间std中。与数组一样,array 对象的长度也是固定的,也使用栈(静态内存分配),而不是自由存储区,因此其效率与数组相同,但更方便,更安全。要创建array对象,需要包含头文件array。
#include <array>
using namespace std;
array<int, 5> ai;// create array object of 5 ints
array<double, 4> ad ={1.2, 2.1, 3.43. 4.3};
比较数组、vector对象和array对象
程序清单4.24 choices.cpp
————————————————————————————————————————————————————————————————————————————————————————————————————————
#include < iostream>
#include <vector> //STL C++98
#include <array> // C++l1
int main()
{
using namespace std;
// C, original C++
double a1[4] = { 1.2, 2.4, 3.6, 4.8 };
//C++98 STL
vector<double> a2(4); //create vector with 4 elements11 no simple way to initialize in C98
a2[0] = 1.0 / 3.0;
a2[1] = 1.0 / 5.0;
a2[2] = 1.0 / 7.0;
a2[3] = 1.0 / 9.0;
//C++11 -- create and initialize array object
array<double, 4> a3 = { 3.14, 2.72, 1.62, 1.41 };
array<double, 4> a4;
a4 = a3; // valid for array objects of same size11 use array notation
cout << "a1[2]: " << a1[2] << "at" << &a1[2] << endl;
cout << "a2[2]: " << a2[2] << "at" << &a2[2] << endl;
cout << "a3[2]: " << a3[2] << " at " << &a3[2] << endl;
cout << "a4[2]: " << a4[2] << "at" << &a4[2] << endl;
//misdeed
a1[-2] = 20.2;
cout << "a1[-2]: " << a1[-2] << " at " << &a1[-2] << endl;
cout << "a3(2]: " << a3[2] << " at " << &a3[2] << endl;
cout << "a4[2]: " << a4[2] << " at " << &a4[2] << endl;
return 0;
}
报错:
中止后:
a1[-2] = 20.2; = *(a1-2)
含义如下:找到a1指向的地方,向前移两个double元素,并将20.2存储到目的地。也就是说,将信息存储到数组的外面。