重读经典系列之《C++PrimerPlus》第4章

第四章 复合类型

声明数组的通用格式如下:
typeName arrayName[arraysize];
表达式arraySize指定元素数目,它必须是整型常数(如10)或const值,也可以是常量表达式(如8*sizeof (int)),即其中所有的值在编译时都是已知的。具体地说,arraySize 不能是变量,变量的值是在程序运行时设置的。然而,本章稍后将介绍如何使用new运算符来避开这种限制。

[注] 这里说的new运算符是指使用指针来动态分配数组,C++中也有提供可变数量的类:vector

初始化数组时,提供的值可以少于数组的元素数目。例如,下面的语句只初始化hotelTips元素:
float hotelTips[5] = {5.0,2.5};
如果只对数组的一部分进行初始化,则编译器将把其他元素设置为0。因此,将数组中所有的元素都初始化为0非常简单——只要显式地将第一个元素初始化为0,然后让编译器将其他元素都初始化为0即可:
long totals[500]={0};
如果初始化为{1}而不是{0},则第一个元素被设置为1,其他元素都被设置为0。

[注] 数组初始化的使用,小技巧。实用中常遇到。

首先,初始化数组时,可省略等号(=):
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章介绍过:
long plifs[] = {25,92,3.0}; //not allowed
char slifs[4] {'h', 'i', 1122011, '\0'}; //not al lowed
char tlifs[4] {'h', 1i', 112,'\0'}; //allowed

[注] 一些新奇的用法容易带来误解,并且不一定所有的环境都支持C++ 11。因此,在实际的项目中一般使用一般的方法。

字符串常量(使用双引号)不能与字符常量(使用单引号)互换。字符常量(如'S') 是字符串编码的简写表示。在ASCII系统上,'S只是83的另一种写法,因此,下面的语句将83赋给shirt_ size:
char shirt_ size = 'S'; // this is fine
但"S"不是字符常量,它表示的是两个字符(字符S和\0)组成的字符串。更糟糕的是,"S"实际上表示的是字符串所在的内存地址。因此下面的语句试图将一一个内 存地址赋给shirt. size:
char shirt size = "S"; // illegal type mismatch
由于地址在C++中是一种独立的类型,因此C++编译器不允许这种不合理的做法(本章后面讨论指针后,将回过头来讨论这个问题)。

[注] 字符常量实际上是存ascii码。而字符串常量实际上是字符串所在地址。

有时候,字符串很长,无法放到一行中。C++允许拼接字符串字面值,即将两个用引号括起的字符串合并为一个。事实上,任何两个由空白(空格、制表符和换行符)分隔的字符串常量都将自动拼接成一个。因此,下面所有的输出语句都是等效的:
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";
注意,拼接时不会在被连接的字符串之间添加空格,第二个字符串的第一个字符将紧跟在第一个字符串的最后一个字符(不考虑\0)后面。第一个字符串中的10字符将被第二个字符串的第一个字符取代。

[注] 使用C语言和C++测试均是如上情况。

cin使用空白(空格、制表符和换行符)来确定字符串结束位置。则cin只能获取一个单词。读取一行应该使用getline函数或get函数,getline()将丢弃换行符,而get()将换行符保留在输入序列中。getline第一个参数是用来存储输入行的数组的名称,第二个参数是要读取的字符数。如:cin.getline(name,20);则函数最多读取19个字符,余下的空间用于存储自动在结尾处添加空字符。cin.get()会读取换行符,因此,如果要连续读取两行内容,中间需要加入一个cin.get()来读取换行符。如:
cin.get(name,ArSize); // read first line
cin.get(); // read newline
cin.get(dessert,ArSize); // read second line

[注] 输入输出在平时工作中较少遇到,之前在ACM的比赛中会遇到。

enum spectrum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};
spectrum band;
对于枚举,只定义了赋值运算符。具体地说,没有为枚举定义算术运算:
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 spect rum
color =3+red; // valid, red converted to int

[注] 枚举一般作为常量使用,也很少进行其他的使用。

可以创建多个值一样的枚举值:
enmu {zero, null = 0, one, numero_uno = 1};

[注] 常常是多个枚举中的值可能代表同一个枚举值,来表示不同的含义。到时很少见到在一个枚举里面多个值是一样的枚举值。

运算符两边的空格是可选的。传统上,C程序员使用这种格式:
int *ptr;
这强调*ptr是一个int 类型的值。而很多C++程序员使用这种格式:
int* ptr;
这强调的是: int*是一种类型一指向int的指针。在哪里添加空格对于编译器来说没有任何区别,您甚至可以这样做:
int*ptr;
int* p1, p2;
但要知道的是,下面的声明创建 个指针(P1) 和个闭变量(2):
对每个指针变量名,都需要使用一个

[注] int* p1, p2;这个表达式容易引起误解

极其重要的一点是:在C++中创建指针时,计算机分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。为数据提供空间是个独立的步骤,忽略这一步无疑是自找麻烦,如下所示:
long * fellow; // create a pointer-to-long
*fellow = 223323; // place a value in never -never land
fellow确实是一个指针,但它指向哪里呢?上述代码没有将地址赋给fellow.那么223323将被放在哪里呢?我们不知道。由于fellow没有被初始化,它可能有任何值。不管值是什么,程序都将它解释为存储2333的地址。如果fellow的值碰巧为1200,计算机将把数据放在地址1200上,即使这恰巧是程序代码的地址。fellow指向的地方很可能并不是所要存储223323的地方。这种错误可能会导致一些最隐匿、最难以跟踪的bug。

[注] 对指针应用解除引用运算符(*)之前,将指针初始化为一个确定的、适当的地址。

在C语言中,可以用库函数malloc()来分配内存;在C++中仍然可以这样做,但C++还有更好的方法--new运算符。

[注] C语言中常对结构体的初始化使用new和delete来初始化和释放。

为一个数据对象(可以是结构,也可以是基本类型)获得并指定分配内存的通用格式如下:
typeName * pointer_name = new typeName;

不要尝试释放已经释放的内存块,C++标准指出,这样的结果将是不确定的,这意味着什么情况都可能发生。另外,不能使用delde来释放声明变量所获得的内存:
int *ps = new int; // ok
delete ps; // ok
delete ps; // not ok now
int jugs= 5; // ok
int * P1 = &jugs;// ok
delete pi; // not allowed, memory not allocated by new
警告:只能用delete来释放使用new分配的内存。然而,对空指针使用delete是安全的。
注意,使用delete的关键在于,将它用于new分配的内存。这并不意味着要使用用于new的指针,而是用于new的地址:
int * ps=new int; // allocate memory
int * pq = ps; // set second pointer to same block
delete Pq; // delete with second pointer
一般来说,不要创建两个指向间一个内存块的指针,因为这将增加错误地删除同一个内存块两大的可能性。

[注] 分配内存和释放内存很重要。如建立一条link,则需要分配内存来保存该link相关的信息。而断开link的时候需要释放掉该link占用的内存。

使用new和delete时,应遵守以下规则:
不要使用delete来释放不是new分配的内存。
不要使用delete 释放同一个内存块两次。
如果使用new[ ]为数组分配内存,则应使用delete[ ]来释放。
如果使用new为一个实体分配内存,则应使用delete (没有方括号)来释放。
对空指针应用delete是安全的。

指针和数组名的区别之一是,可以修改指针的值,而数组名是常量:
pointernane = pointername + 1; // valid
arraymame = arrayname + 1; // not allowed
另一个区别是,对数组应用sizeof运算符得到的是数组的长度,而对指针应用sizeof得到的是指针的长度。

[注] 数组名可以理解为指针常量。

数组名被解释为第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址。

[注] 在二维数组的时候容易混淆。

如果结构标识符是结构名,则使用句号运算符。如果标识符是指向结构的指针,则使用箭头运算符。

[注] 实际工作中,会常常遇到这个问题,有的时候容易用错。

本章介绍了数组、结构和指针。可以各种方式组合它们,下面介绍其中的一些。从结构开始:
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;
将该指针设置为有效地址后,就可使用间接成员运算符来访问成员:
pa->year = 1999;
可创建结构数组:
antarctica_years_end trio[3]; // array of 3 structures
然后,可以使用成员运算符访问元素的成员:
trio[0] .year = 2003; // trio[0] is a structure
其中trio是一个数组,trio[0]是一个结构,而trio[0].year是该结构的一个成员。由于数组名是一个指针,因此也可使用间接成员运算符:
(trio+1)->year = 2004; // same as trio[1] .year = 2004;
可创建指针数组:
const antarctica_years_end * arp[3] = {&s01, &s02, &s03};
咋一看,这有点复杂。如何使用该数组来访问数据呢?既然arp是一个指针数组,arp[1]就是一个指针,可将间接成员运算符应用于它,以访问成员:
std: :cout << arp[1] ->year << std: :endl;
可创建指向上述数组的指针:
const antarctica_years_end ** ppa= arp;

[注] 这里是一个综合使用的例子。

你可能感兴趣的:(重读经典系列之《C++PrimerPlus》第4章)