1 数组
数组是一种数据格式,能够存储多个同类型的值。每个值都存储在一个独立的数组元素中,计算机在内存中依次存储数组的各个元素。要创建数组,可使用声明语句。数组声明应指出以下三点:
- 存储在每个元素中的值的类型
- 数组名
- 数组中的元素数
在C++中,可以通过如下的方式来声明一个数据类型为short,名为months的数组:
short months[12];
short *months=new short[12];
声明数组的通用格式如下:
typename arrayName[arraySize];
表达式arraySize指定元素数目,它必须是整型常数(如10)或const值,也可以是常量表达式(如8*sizeof(int)
),即其中所有的值在编译时都是已知的。具体地说,arraySize不能是变量,变量的值是在程序运行时设置的。本文稍后将介绍如何使用new运算符来避开这种限制。
注(有效下标值的重要性):
编译器不会检查使用的下标是否有效。例如,如果将一个值赋给不存在的元素months[101],编译器并不会指出错误。但是程序运行后,这种赋值可能引发问题,它可能破坏数据或代码,也可能导致程序异常终止。所以必须确保程序只使用有效的下标值。
#include<iostream>
int main(){
using namespace std;
int yams[3];
yams[0]=7;
yams[1]=8;
yams[2]=6;
int yamcosts[3]={20,30,5};
cout<<"total yams = ";
cout<<yams[0]+yams[1]+yams[2]<<endl;
cout<<"The package with"<<yams[1]<<"yams costs";
cout<<yamcosts[1]<<"cents per yam .\n";
int toal=yams[0]*yamcosts[0]+yams[1]*yamcosts[1];
total=total+yams[2]*yamcosts[2];
cout<<"The total yam expense is"<<total<<"cents.\n";
cout<<"\n Size of yams array = "<<sizeof(yams);
cout<<" bytes. \n";
cout<<" Size of one element = "<<sizeof(yams[0]);
cout<<" butes. \n";
return 0;
}
1.1 程序说明
C++允许在声明语句中初始化数组元素,因此可以用这种方式给yamcosts数组赋值:
int yamcosts[3]={20,30,5};
如果没有初始化定义的数组,那么其元素值将不确定,为当前驻留在该内存单元中的值。
sizeof运算符返回类型或数据对象的长度(单位为字节)。但是如果将sizeof运算符用于数组名,得到的将是整个数组中的字节数。但如果将sizeof用于数组元素,则得到的将是元素的长度(单位为字节)。这表明yams是一个数组,而yams[1]只是一个int变量。
1.2 数组的初始化规则
只有在定义数组时才能使用初始化,此后就不能使用了,也不能将一个数组赋给另一个数组:
int cards[4]={3,6,8,10}; //允许
int hand[4]; //允许
hand[4] = {5,6,7,9}; //不允许
hand=cards; //不允许
初始化数组时,提供的值可以少于数组的元素数目。例如,下面的语句只初始化hotelTips的前两个元素:
float hotelTips[5]={5.0,2.5};
如果只对数组的一部分进行初始化,则编译器将把其他元素设置为0。因此,将数组中所有的元素都初始化为0非常简单——只要显式地将第一个元素初始化为0,然后让编译器将其他元素都初始化为0即可:
long totals[500]={0};
如果初始化为{1}而不是{0},则第一个元素被设置为1,其他元素都被设置为0。
如果初始化数组时方括号内[]为空,C++编译器将计算元素个数。例如,对于下面的声明:
short things[]={1,5,3,8};
编译器将使things数组包含4个元素。
1.3 C++11数组初始化方法
C++11中的列表初始化新增了一些功能。
首先,初始化数组时,可省略等号(=):
double earnings[4] {1.2e4,1.6e4,1.1e4,1.7e4};
其次,可以不在大括号内包含任何东西,这将把所有元素都设置为零:
unsigned int counts[10]={};
float balances[100] {};
第三,列表初始化禁止缩窄转换,第三节介绍过:
long plifs[]={25,92,3.0}; //禁止
char slifs[4] {'h','i',1122011,'\0'}; //禁止
char tlifs[4] {h','i',112,'\0'}; //允许
在上述代码中,第一条语句不能通过编译是因为将浮点数转化为整型是缩窄操作,即使浮点数的小数点后面为零。第二条语句也不能通过编译,因为1122011超出了char变量的取值范围(这里假设char变量的长度为8位)。第三条语句可通过编译,因为虽然112是一个int值,但它在char变量的取值范围内。
2 字符串
待补充 91
3 Introducing the string Class
The ISO/ANSI C++98 Standard expanded the C++ library by adding a string
class. So now, instead of using a character array to hold a string, you can use a type string
variable (or object, to use C++ terminology).As you’ll see, the string class is simpler to use than the array and also provides a truer representation of a string as a type.
To use the string
class,a program has to include the string
header file.The string class is part of the std
namespace, so you have to provide a using
directive or declaration or else refer to the class as std::string
.The class definition hides the array nature of a string and lets you treat a string much like an ordinary variable. Listing 4.7 illustrates some of the similarities and differences between string objects and character arrays.
Listing 4.7 strtype1.cpp
// strtype1.cpp -- using the C++ string class
#include <iostream>
#include <string> // make string class available
int main(){
using namespace std;
char charr1[20]; // create an empty array
char charr2[20] = "jaguar"; // create an initialized array
string str1; // create an empty string object
string str2 = "panther"; // create an initialized string
cout << "Enter a kind of feline: ";
cin >> charr1;
cout << "Enter another kind of feline: ";
cin >> str1; // use cin for input
cout << "Here are some felines:\n";
cout << charr1 << " " << charr2 << " "
<< str1 << " " << str2 // use cout for output
<< endl;
cout << "The third letter in " << charr2 << " is "
<< charr2[2] << endl;
cout << "The third letter in " << str2 << " is "
<< str2[2] << endl; // use array notation
return 0;
}
Here is a sample run of the program in Listing 4.7:
Enter a kind of feline: ocelot
Enter another kind of feline: tiger
Here are some felines:
ocelot jaguar tiger panther
The third letter in jaguar is g
The third letter in panther is n
You should learn from this example that, in many ways, you can use a string object in the same manner as a character array:
- You can initialize a string object to a C-style string.
- You can use cin to store keyboard input in a string object.
- You can use cout to display a string object.
- You can use array notation to access individual characters stored in a string object.
The main difference between string
objects and character arrays shown in Listing 4.7 is that you declare a string
object as a simple variable, not as an array:
string str1; // create an empty string object
string str2 = "panther"; // create an initialized string
The class design allows the program to handle the sizing automatically. For instance, the declaration for str1
creates a string
object of length zero, but the program automatically resizes str1
when it reads input into str1
:
cin >> str1; // str1 resized to fit input
This makes using a string
object both more convenient and safer than using an array. Conceptually, one thinks of an array of char
as a collection of char storage units used to store a string but of a string
class variable as a single entity representing the string.
3.1 C++11 String Initialization
As you might expect by now, C++11 enables list-initialization for C-style strings and string
objects:
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"};
3.2 Assignment, Concatenation, and Appending
The string
class makes some operations simpler than is the case for arrays. For example, you can’t simply assign one array to another. But you can assign one string object to another:
char charr1[20]; // create an empty array
char charr2[20] = "jaguar"; // create an initialized array
string str1; // create an empty string object
string str2 = "panther"; // create an initialized string
charr1 = charr2; // INVALID, no array assignment
str1 = str2; // VALID, object assignment ok
The string
class simplifies combining strings.You can use the +
operator to add two string
objects together and the +=
operator to tack on a string to the end of an existing string object. Continuing with the preceding code, we have the following possibilities:
string str3;
str3 = str1 + str2; // assign str3 the joined strings
str1 += str2; // add str2 to the end of str1
Listing 4.8 illustrates these usages. Note that you can add and append C-style strings as well as string
objects to a string
object.
Listing 4.8 strtype2.cpp
// strtype2.cpp –- assigning, adding, and appending
#include <iostream>
#include <string> // make string class available
int main(){
using namespace std;
string s1 = "penguin";
string s2, s3;
cout << "You can assign one string object to another: s2 = s1\n";
s2 = s1;
cout << "s1: " << s1 << ", s2: " << s2 << endl;
cout << "You can assign a C-style string to a string object.\n";
cout << "s2 = \"buzzard\"\n";
s2 = "buzzard";
cout << "s2: " << s2 << endl;
cout << "You can concatenate strings: s3 = s1 + s2\n";
s3 = s1 + s2;
cout << "s3: " << s3 << endl;
cout << "You can append strings.\n";
s1 += s2;
cout <<"s1 += s2 yields s1 = " << s1 << endl;
s2 += " for a day";
cout <<"s2 += \" for a day\" yields s2 = " << s2 << endl;
return 0;
}
Recall that the escape sequence \"
represents a double quotation mark that is used as a literal character rather than as marking the limits of a string. Here is the output from the program in Listing 4.8:
You can assign one string object to another: s2 = s1
s1: penguin, s2: penguin
You can assign a C-style string to a string object.
s2 = "buzzard"
s2: buzzard
You can concatenate strings: s3 = s1 + s2
s3: penguinbuzzard
You can append strings.
s1 += s2 yields s1 = penguinbuzzard
s2 += " for a day" yields s2 = buzzard for a day
待补充 150
4 结构简介
结构是一种比数组更灵活的数据格式,因为同一个结构可以存储多种类型的数据。结构是用户定义的类型,而结构声明定义了这种类型的数据属性。定义了类型后,便可以创建这种类型的变量。因此创建结构包括两步。首先,定义结构描述——它描述并标记了能够存储在结构中的各种数据类型。然后按描述创建结构变量。
假设定义了如下的一个结构:
struct inflatable // structure declaration
{
char name[20];
float volume;
double price;
};
关键字struct表明定义了一个结构。标识符inflatable是结构体名称,后面就可以像创建char或int类型的变量那样创建inflatable类型的变量了。接下来的大括号中包含的是结构存储的数据类型的列表,其中每个列表项都是一条声明语句。在上面的例子中包括了一个char数组、一个float和一个double。列表中的每一项都称为结构成员,因此inflatable结构有三个成员。
After you have defined the structure, you can create variables of that type:
inflatable hat; // hat is a structure variable of type inflatable
inflatable woopie_cushion; // type inflatable variable
inflatable mainframe; // type inflatable variable
If you’re familiar with C structures, you’ll notice (probably with pleasure) that C++ allows you to drop the keyword struct when you declare structure variables:
struct inflatable goose; // keyword struct required in C
inflatable vincent; // keyword struct not required in C++
In C++, the structure tag is used just like a fundamental type name.This change emphasizes that a structure declaration defines a new type. It also removes omitting struct from the list of curse-inducing errors.
Given that hat is type inflatable, you use the membership operator (.) to access individual members. For example, hat.volume refers to the volume member of the structure,and hat.price refers to the price member. Similarly, vincent.price is the price member of the vincent variable. In short, the member names enable you to access members of a structure much as indices enable you to access elements of an array. Because the price member is declared as type double, hat.price and vincent.price are both equivalent to type double variables and can be used in any manner an ordinary type double variable can be used. In short, hat is a structure, but hat.price is a double. By the way, the method used to access class member functions such as cin.getline() has its origins in the method used to access structure member variables such as vincent.price.
4.1 在程序中使用结构
程序4.11演示了结构的基本使用方法:
Listing 4.11 structur.cpp
// structur.cpp -- a simple structure
#include <iostream>
struct inflatable{ // structure declaration
char name[20];
float volume;
double price;
};
int main(){
using namespace std;
inflatable guest =
{
"Glorious Gloria", // name value
1.88, // volume value
29.99 // price value
}; // guest is a structure variable of type inflatable
// It's initialized to the indicated values
inflatable pal =
{
"Audacious Arthur",
3.12,
32.99
}; // pal is a second variable of type inflatable
// NOTE: some implementations require using
// static inflatable guest =
cout << "Expand your guest list with " << guest.name;
cout << " and " << pal.name << "!\n";
// pal.name is the name member of the pal variable
cout << "You can have both for $";
cout << guest.price + pal.price << "!\n";
return 0;
}
程序4.11的输出如下:
Expand your guest list with Glorious Gloria and Audacious Arthur!
You can have both for $62.98!
程序说明:
结构声明的位置很重要。可以将声明放在main()函数中,紧跟在开始括号的后面。另一种选择是将声明放在main()的前面,这里采用的是这种方式,位于函数外面的声明被称为外部声明。对这个程序来说没有区别,但是对那些包含两个或更多函数的程序来说,差别很大。外部声明可以被其后面的任何函数使用,而内部声明只能被声明所属的函数使用。通常应使用外部声明,这样所有函数都可以使用这种类型的结构。
变量也可以在函数内部和外部定义,外部变量由所有的函数共享(第9节详细介绍)。C++不提倡使用外部变量,但提倡使用外部结构声明。另外,在外部声明符号常量通常更合理。
注意初始化方式:
inflatable guest =
{
"Glorious Gloria", // name value
1.88, // volume value
29.99 // price value
};
和数组一样,使用由逗号分隔值列表,并将这些值用花括号扩起。在该程序中,每个值占一行,但也可以将它们全部放在同一行中。只是应用逗号将它们分开:
inflatable duck = {"Daphne", 0.12, 9.98};
You can initialize each member of the structure to the appropriate kind of data. For example, the name member is a character array, so you can initialize it to a string.
Each structure member is treated as a variable of that type.Thus, pal.price is a double variable,and pal.name is an array of char.And when the program uses cout to display pal.name, it displays the member as a string. By the way, because pal.name is a character array, we can use subscripts to access individual characters in the array. For example, pal.name[0] is the character A. But pal[0] is meaningless because pal is a structure, not an array.
4.2 C++11 结构初始化
As with arrays, C++11 extends the features of list-initialization.The = sign is optional:
inflatable duck {"Daphne", 0.12, 9.98}; // can omit the = in C++11
Next, empty braces result in the individual members being set to 0. For example, the following declaration results in mayor.volume and mayor.price being set to 0 and all the bytes in mayor.name being set to 0:
inflatable mayor {};
Finally, narrowing is not allowed. (在C++中,Narrowing是一种特殊的类型转换过程,它涉及到将一种数据类型转换为另一种数据类型,而这种转换可能导致数据丢失或精度降低。例如从 double 到 int 的转换,这会导致小数部分被丢弃;从 long 到 int 的转换,如果 int 的范围比 long 更小,这可能导致大的值变为负数。)
4.3 结构可以将string类作为成员吗
Can you use a string class object instead of a character array for the name member? That is, can you declare a structure like this:
#include <string>
struct inflatable // structure definition
{
std::string name;
float volume;
double price;
};
The answer is yes unless you are using an obsolete compiler that does not support initialization of structures with string class members.
Make sure that the structure definition has access to the std namespace.You can do
this by moving the using directive so that it is above the structure definition.The better
choice,as shown previously, is to declare name as having type std::string.
4.4 其他结构属性
C++ makes user-defined types as similar as possible to built-in types. For example, you
can pass structures as arguments to a function,and you can have a function use a structure
as a return value.Also you can use the assignment operator (=) to assign one structure to
another of the same type. Doing so causes each member of one structure to be set to the
value of the corresponding member in the other structure, even if the member is an array.
This kind of assignment is called memberwise assignment.We’ll defer passing and returning
structures until we discuss functions in Chapter 7,“Functions: C++’s Programming Modules,” but we can take a quick look at structure assignment now. Listing 4.12 provides an
example.
Listing 4.12 assgn_st.cpp
// assgn_st.cpp -- assigning structures
#include <iostream>
struct inflatable{
char name[20];
float volume;
double price;
};
int main(){
using namespace std;
inflatable bouquet = {
"sunflowers",
0.20,
12.49
};
inflatable choice;
cout << "bouquet: " << bouquet.name << " for $";
cout << bouquet.price << endl;
choice = bouquet; // assign one structure to another
cout << "choice: " << choice.name << " for $";
cout << choice.price << endl;
return 0;
}
Here’s the output from the program in Listing 4.12:
bouquet: sunflowers for $12.49
choice: sunflowers for $12.49
As you can see, memberwise assignment is at work, for the members of the choice
structure are assigned the same values stored in the bouquet structure.
You can combine the definition of a structure form with the creation of structure
variables.To do so, you follow the closing brace with the variable name or names:
struct perks
{
int key_number;
char car[12];
} mr_smith, ms_jones; // two perks variables
You even can initialize a variable you create in this fashion:
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
};
However, keeping the structure definition separate from the variable declarations usually makes a program easier to read and follow.
Another thing you can do with structures is create a structure with no type name.You
do this by omitting a tag name while simultaneously defining a structure form and a
variable:
struct // no tag
{
int x; // 2 members
int y;
} position; // a structure variable
This creates one structure variable called position.You can access its members with
the membership operator,as in position.x, but there is no general name for the type.
You can’t subsequently create other variables of the same type.This book doesn’t use that
limited form of structure.
Aside from the fact that a C++ program can use the structure tag as a type name, C
structures have all the features discussed so far for C++ structures,apart from the C++11
changes. But C++ structures go further. Unlike C structures, for example, C++ structures
can have member functions in addition to member variables. But these more advanced
features most typically are used with classes rather than structures, so we’ll discuss them
when we cover classes, beginning with Chapter 10,“Objects and Classes.”
4.5 结构数组
4.6 结构中的位字段
5 共用体
共用体(union)是一种数据格式,它能够存储不同的数据类型,但只能同时存储其中的一种类型。也就是说,结构体可以存储int、long和double,共用体只能存储int、long或double。共用体的用法与结构相似,但含义不同。下面是共用体的一个例子:
union one4call{
int int_val;
long long_val;
double double_val;
}
可以使用one4all变量来存储int、long或double,条件是在不同的时间进行:
one4all pail;
pail.int_val=15; //存储一个int变量
cout<<pail.int_val; //15
pail.double_val=1.38; //存储一个double变量,int变量此时已经丢失
cout<<pail.double_val; //1.38
cout<<pail.int_val; //-515396076
因此,pail有时可以是int变量,而有时又可以是double变量。成员名称标识了变量的容量。由于共用体每次只能存储一个值,因此它必须有足够的空间来存储最大的成员,所以,共用体的成员为其最大成员的长度。
共同体的用途之一是,当数据项使用两种或更多种格式(但不会同时使用)时,可节省空间。例如,假设管理一个小商品目录,其中有一些商品的ID为整数,而另一些的ID为字符串。在这种情况下,可以这样做:
匿名共用体(anonymous union)没有名称,其成员将为位于相同地址处的变量。显然,每次只有一个成员是当前的成员:
由于共用体是匿名的,因此id_num和id_char被视为prize的两个成员,它们的地址相同,所以不需要中间标识符id_val。程序员负责确定当前哪个成员是活动的。
6 枚举
C++的enum工具提供了另一种创建符号常量的方式,这种方式可以代替const。它还允许定义新类型,但必须按严格的限制进行。使用enum的语法与使用结构类似。例如下面的语句:
enum spectrum{
red,
orange,
yellow,
green,
blue,
violet,
indigo,
ultraviolet
};
这条语句完成两项工作:
- 让spectrum成为新类型的名称;spectrum被称为枚举(enumeration),就像struct变量被称为结构一样。
- 将red、orange、yellow等作为符号常量,它们对应整数值0-7。这些常量叫做枚举量(enumerator)。
在默认情况下,将整数值赋给枚举量,第一个枚举量的值为0,第二个枚举量的值为1,依次类推。可以通过显式地指定整数值来覆盖默认值。
可以用枚举名来声明这种类型的变量:
spectrum band; //band是spectrum类型的变量
枚举变量具有一些特殊的性质,在不进行强制类型转换的情况下,只能将定义枚举时使用的枚举量赋给这种枚举的变量,如下所示:
band=blue; //有效,因为blue是enumerator
band=200; //无效,2000不是enumerator
因此,spectrum变量受到限制,只有8个可能的值。如果试图将一个非法值赋给它,则有些编译器将出现编译器错误,而另一些则发出警告。
对于枚举,只定义了赋值运算符。具体地说,没有为枚举定义算数运算:
待补充 113
6.1 设置枚举量的值
可以使用赋值运算符来显式地设置枚举量的值:
enum bits{
one=1,
two=2,
four=4,
eight=8
};
指定的值必须是整数。也可以只显式地定义其中一些枚举量的值:
enum bitstep{
first,
second=100,
third
};
这里,first在默认情况下为0。后面没有被初始化的枚举量的值将比其前面的枚举量大1。因此,third的值为101。
最后,可以创建多个值相同的枚举量:
enum{
zero,
null=0,
one,
numero_uno=1
};
待补充 114
7 指针和自由存储空间
对变量应用地址运算符(&)可以获得它的位置,程序清单4.14演示了这个运算符的方法:
程序4.14 address.cpp
// address.cpp -- using the & operator to find addresses
#include <iostream>
int main(){
using namespace std;
int donuts = 6;
double cups = 4.5;
cout << "donuts value = " << donuts;
cout << " and donuts address = " << &donuts << endl;
// NOTE: you may need to use unsigned (&donuts)
// and unsigned (&cups)
cout << "cups value = " << cups;
cout << " and cups address = " << &cups << endl;
return 0;
}
程序输出为:
donuts value = 6 and donuts address = 0x0065fd40
cups value = 4.5 and cups address = 0x0065fd44
显示地址时,cout使用了16进制表示法(这是常见的用于描述内存的表示法)。指针的基础知识如下:
指针与C++基本原理
面向对象编程与传统的过程性编程的区别在于,OOP强调的是在运行阶段(而不是编译阶段)进行决策。运行阶段决策提供了灵活性,可以根据当时的情况进行调整。例如,考虑为数组分配内存的情况。传统的方法是声明一个数组,此时需要指定数组长度。因此,数组长度在程序编译时就设定好了。OOP通过将这样的决策推迟到运行阶段进行,使程序更灵活。在程序运行后,可以这次告诉它只需要20个元素,而还可以下次告诉它需要205个元素。
总之,使用OOP时,我们可以在运行阶段确定数组的长度。为了使用这种方法,语言必须允许在程序运行时创建数组。C++采用的方法是使用关键字new请求正确数量的内存以及使用指针来跟踪新分配的内存的位置。
在运行阶段做决策并非OOP独有的,但使用C++编写这样的代码比使用C语言简单。
指针用于存储值的地址,因此,指针名表示的是地址。*
运算符被称为间接值(indirect value)或解引用(dereferencing)运算符,将其应用于指针,可以得到该地址处存储的值。
程序4.15演示了指针的基本用法。
程序4.15 pointer.cpp
// pointer.cpp -- our first pointer variable
#include <iostream>
int main(){
using namespace std;
int updates = 6; // declare a variable
int * p_updates; // declare pointer to an int
p_updates = &updates; // assign address of int to pointer
// express values two ways
cout << "Values: updates = " << updates;
cout << ", *p_updates = " << *p_updates << endl;
// express address two ways
cout << "Addresses: &updates = " << &updates;
cout << ", p_updates = " << p_updates << endl;
// use pointer to change value
*p_updates = *p_updates + 1;
cout << "Now updates = " << updates << endl;
return 0;
}
4.15的输出如下:
Values: updates = 6, *p_updates = 6
Addresses: &updates = 0x0065fd48, p_updates = 0x0065fd48
Now updates = 7
7.1 声明和初始化指针
指针声明时需要指定其指向的数据类型以确定相应的存储空间。在声明指针,*
运算符两边的空格是可选的,传统上,C程序员使用这种格式:
int *ptr;
这强调*ptr是一个int类型的值,而很多C++程序员使用这种格式:
int* ptr;
这强调int*是一种类型——指向int的指针。在哪里添加空格没有区别,我们也可以这样写:
int*ptr;
但是,下面这样声明会创建一个指针(p1)和一个int变量(p2):
int* p1,p2;
对每个指针变量名,都需要使用一个*
。
可以在声明语句中初始化指针:
int higgens=5;
int *pt=&higgens;
7.2 指针的危险
待补充 118
7.4 使用new来分配内存
指针可以实现在程序运行时分配内存,变量是在编译时分配的有名称的内存,而指针只是为可以通过名称直接访问的内存提供了一个别名。指针真正的用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。在C语言中,可以用库函数malloc来分配内存;在C++中仍然可以这样做,但C++还有更好的方法——new运算符。
使用new运算符时,我们要告诉new需要为哪种数据类型分配内存;new将找到一个长度正确的内存块,并返回该内存块的地址。程序员的职责是将该地址赋给一个指针。下面是一个例子:
int *pn=new int;
new int告诉程序,需要适合存储int的内存。new运算符根据类型来确定需要多少字节的内存。然后,它找到这样的内存,并返回其地址。接下来,将地址赋给pn,pn是被声明为指向int的指针。现在,pn是地址,而*pn是指向存储在那里的值。将这种方法与将变量的地址赋给指针进行比较:
int higgens;
int * pt=&higgens;
在这两种情况(pn和pt)下,都是将一个int变量的地址赋给了指针。在第二种情况下,可以通过名称higgens来访问该int,在第一种情况下,则只能通过该指针进行访问。这引出了一个问题:pn指向的内存没有名称。此时我们说pn指向一个数据对象,这里的”对象“不是OOP中的对象,而是一样“东西”,它指的是为数据项分配的内存块。乍一看,指针方法可能不太耗用,但它使程序在管理内存方面有更大的控制权。
为一个数据对象(可以是结构,也可以是基本类型)获得并指定分配内存的通用格式如下:
typeName * pointer_name = new typeName;
对于指针需要强调的一点是,new分配的内存块与常规变量分配的内存块不同。常规变量的值被存储在称为栈的内存区域中,而new从被称为堆的内存区域分配内存(第9部分更详细讨论)。
7.4 使用delete释放内存
待补充 121
7.6 使用new来创建动态数组
假如在我们编写的程序中,是否需要数组取决于运行时用户提供的信息。如果通过声明来创建数组,则在编译时将为它分配内存空间。不管程序最终是否使用数组,数组都会占用内存。在编译时给数组分配内存称为静态联编(static binding),意味着数组是在编译时加入到程序中的。但使用new时,如果在运行阶段需要数组,则创建它;如果不需要,则不创建。还可以在程序运行时选择数组的长度。这被称为动态联编(dynamic binding),意味着数组是在程序运行时创建的。这种数组叫做动态数组(dynamic array)。使用静态联编时,必须在编写程序时指定数组的长度;使用动态联编时,程序将在运行时确定数组的长度。
接下来介绍如何使用C++的new运算符创建数组以及如何使用指针访问数组元素。
7.5.1 使用new创建动态数组
在C++中,创建动态数组很容易;只要将数组的元素类型和元素数目告诉new即可。例如,要创建一个包含10个int元素的数组,可以这样做:
int * psome = new int[10];
注意 如果没有进行初始化,那么数组内的值可能是随机值,因此如果想要创建全0数组,可以进行这样的初始化:int test[10]={0};
new运算符返回第一个元素的地址。在这个例子中,该地址被赋给指针psome。
当程序使用完new分配的内存块时,应使用delete释放它们。然而,对于使用new创建的数组,应使用另一种格式的delete来释放:
delete [] psome; //释放一个动态数组
7.5.2 使用动态数组
我们首先创建一个指针psome:
int * psome=new int[10];
使用这个指针时,用法和数组名的用法相同,即使用元素下标访问:psome[0]
指第一个元素,psome[1]
指第二个元素,以此类推。可以这样做的原因是,C和C++内部都使用指针来处理数组。
程序4.18演示了如何使用new来创建动态数组以及使用数组表示法来访问元素:
程序4.18 arraynew.cpp
// arraynew.cpp -- using the new operator for arrays
#include <iostream>
int main(){
using namespace std;
double * p3 = new double [3]; // space for 3 doubles
p3[0] = 0.2; // treat p3 like an array name
p3[1] = 0.5;
p3[2] = 0.8;
cout << "p3[1] is " << p3[1] << ".\n";
p3 = p3 + 1; // increment the pointer
cout << "Now p3[0] is " << p3[0] << " and ";
cout << "p3[1] is " << p3[1] << ".\n";
p3 = p3 - 1; // point back to beginning
delete [] p3; // free the memory
return 0;
}
下面是程序输出:
p3[1] is 0.5.
Now p3[0] is 0.5 and p3[1] is 0.8.
在上面的程序中,这行代码指出了数组名和指针之间的本质区别:
p3=p3+1; //可以这样使用指针,但是数组会报错
我们不能修改数组名的值。但指针是变量,因此可以修改它的值。注意p3+1后现在p3指向数组的第二个值。
8 Pointers, Arrays, and Pointer Arithmetic
指针和数组基本等价的原因在于指针运算(pinter arithmetic)和C++内部处理数组的方式。首先看下运算,将整数变量加1后,其值将增加1;但将指针变量加1后,增加的量等于它指向的类型的字节数。程序清单4.19演示了这一特性,它还说明了另一点:C++将数组名解释为地址。
//Listing 4.19 addpntrs.cpp
// addpntrs.cpp -- pointer addition
#include <iostream>
int main(){
using namespace std;
double wages[3] = {10000.0, 20000.0, 30000.0};
short stacks[3] = {3, 2, 1};
// Here are two ways to get the address of an array
double * pw = wages; // name of an array = address
short * ps = &stacks[0]; // or use address operator
// with array element
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 << "access 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;
}
程序输出如下:
pw = 0x28ccf0, *pw = 10000
add 1 to the pw pointer:
pw = 0x28ccf8, *pw = 20000
ps = 0x28ccea, *ps = 3
add 1 to the ps pointer:
ps = 0x28ccec, *ps = 2
access two elements with array notation
stacks[0] = 3, stacks[1] = 2
access two elements with pointer notation
*stacks = 3, *(stacks + 1) = 2
24 = size of wages array
4 = size of pw pointer
8.1 程序说明
在多数情况下,C++将数组名解释为数组第1个元素的地址。因此,下面的语句将pw
声明为指向double
类型的指针,然后将它初始化为wages
,即wages
数组中第一个元素的地址:
double * pw = wages;
For wages
,as with any array, we have the following equality:
wages = &wages[0] = address of first element of array
Next,the program 查看 the values of pw
and *pw.The first is an address,and the second is the value at that address. Because pw
points to the first element, the value displayed for *pw
is that of the first element, 10000.Then the program adds one to pw.As promised, this adds eight to the numeric address value because double on this system is 8 bytes.This makes pw equal to the address of the second element.Thus, *pw is now 20000, the value of the second element (see Figure 4.10). (The address values in the figure are adjusted to make the figure clearer.)
After this, the program goes through similar steps for ps.This time,because ps points to type short and because short is 2 bytes, adding 1 to the pointer increases its value by 2 (0x28ccea + 2 = 0x28ccec in hexadecimal).Again, the result is to make the pointer point to the next element of the array.
补充 184页
待补充 125
8.4 使用new创建动态结构
在运行时创建数组优于在编译时创建数组,对于结构也是如此。需要在程序运行时为结构分配所需的空间,这也可以使用new运算符来完成。通过使用new,可以创建动态结构。同样,“动态”意味着内存是在运行时,而不是编译时分配的。由于类与结构非常类似,因此本节介绍的有关结构的技术也适用于类。
将new用于结构由两步组成:创建结构和访问其成员。要创建结构,需要同时使用结构类型和new。例如,要创建一个未命名的inflatable类型,并将其地址赋给一个指针,可以这样做:
inflatable *ps=new inflatable;
这将把足以存储inflatable结构的一块可用内存的地址赋给ps。
创建动态结构时,不能将成员运算符句点用于结构名,因为这种结构没有名称,只是知道它的地址。C++专门为这种情况提供了一个运算符:(箭头成员运算符->
)。该运算符由连字符和大于号组成,可用于指向结构的指针,就像点运算符可用于结构名一样。例如,如果ps指向一个inflatable结构,则ps->price是被指向的结构的price成员。(如图4.11所示)
区分何时使用句点运算符和何时使用箭头运算符:如果结构标识符是结构名,则使用句点运算符;如果标识符是指向结构的指针,则使用箭头运算符。
另一种访问结构成员的方法是,如果ps是指向结构的指针,则*ps
就是被指向的值——结构本身。由于*ps
是一个结构,因此(*ps).price
是该结构的price成员。C++的运算符优先规则要求使用括号。
程序清单4.21使用new创建一个未命名的结构,并演示了两种访问结构成员的指针表示法。
8.4.1 一个使用new和delete的示例
待补充 132
8.5 自动存储、静态存储和动态存储
根据用于分配内存的方法,C++有3种管理数据内存的方式:自动存储、静态存储和动态存储(有时也叫作自由存储空间或堆)。在存在时间的长短方面,以这3种方式分配的数据对象各不相同。
8.5.1 自动存储
在函数内部定义的常规变量使用自动存储空间,被称为自动变量(automatic variable),这意味着它们在所属的函数被调用时自动产生,在该函数结束时消亡。
实际上,自动变量是一个局部变量,其作用域为包含它的代码块。代码块是被包含在花括号中的一段代码。自动变量通常存储在栈中。这意味着执行代码块时,其中的变量将依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量,这被称为后进先出(LIFO)。因此,在程序执行过程中,栈将不断地增大和缩小。
2. 静态存储
静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:一种是在函数外面定义它;另一种是在声明变量时使用关键字static:
static double fee=56.50;
第9节详细介绍静态存储。自动存储和静态存储的关键在于:这些方法严格地限制了变量的寿命。变量可能存在于程序的整个生命周期(静态变量),也可能只是在特定函数被执行时存在(自动变量)。
待补充 134
9 类型组合
本章介绍了数组、结构和指针,可以以各种方式进行组合,下面介绍其中的一些,从结构开始:
struct antarctica_years_end
{
int year;
/* some really interesting data, etc. */
};
可以创建这种类型的变量:
antarctica_years_end s01, s02, s03; // s01, s02, s03 are structures
然后使用成员运算符访问其成员:
s01.year = 1998;
可以创建指向这种结构的指针:
antarctica_years_end * pa = &s02;
注意,使用new创建结构体指针时一定要进行初始化,否则无法使用->
访问结构体成员。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
ListNode *head=new ListNode(-1);
将该指针设置为有效地址后,就可使用间接成员运算符来访问成员:
pa->year = 1999;
待补充 中文135 英文199
10 数组的替代品
10.1 模板类vector
模板类vector类似于string类,也是一种动态数组。我们可以在运行阶段设置vector对象的长度,可在末尾附加新数据,还可在中间插入新数据。基本上,它是使用new创建动态数组的替代品。实际上,vector类确实使用new和delete来管理内存,但这种工作是自动完成的。
首先,要使用vector对象,必须包含头文件vector。其次,vector包含在名称空间std中,因此我们可以使用using编译指令、using声明或std::vector。第三,模板使用不同的语法来指出它存储的数据类型。第四,vector类使用不同的语法来指定元素数。下面是一些示例:
#include<vector>
using namespace std;
vector<int> vi;
int n;
cin>>n;
vector<double> vd(n);
其中,vi是一个vector<int>
对象,vd是一个vector<double>
对象。由于vector对象在我们插入或添加值时自动调整长度,因此可以将vi的初始长度设置为零。但要调整长度,需要使用vector包中的各种方法。
一般而言,下面的声明创建一个名为vt的vector对象,它可以存储n_elem
个类型为typeName
的元素:
vector<typeName> vt(n_elem);
其中参数n_elem
可以是整型常量,也可以是整型变量。