C++ Primer Plus【复习笔记】-【复合类型】

要点提炼:
1、数组;
2、字符串;
3、字符串输入;
4、string类【ISO C++98标准库添加】;
5、C++新增原始(raw)字符串表示法;
6、结构;
7、共用体;
8、枚举;
9、指针和自由存储空间;
10、声明和初始化指针;
11、指针的危险:野指针,只分配了存地址的内存,但没有分配具体地址即未初始化指针;
12、指针和数字:指针不是整型,虽然计算机通常把地址当作整数来处理;
13、使用new来分配内存;
14、使用delete释放内存;
15、使用new来创建动态数组【dynamic array】;
16、指针、数组、指针算术;
17、指针和字符串;
18、使用new创建动态结构;
19、C++内存管理基本知识:自动存储、静态存储、动态存储、线程存储;
20、类型组合;
21、数组的替代品;

正文:

1、数组:数据格式,可以存储多个同类型值。 格式:typeName arrayName[arraySize]
数组有效下标(索引)值的重要性:编译器不会检查使用的下标是否有效,因此必须保证程序只使用有效的下标值。
sizeof运算符返回类型或数据对象的长度(单位字节)。注意:sizeof用于数组名时将得到的是整个数组中的总字节数,但若用于数组元素则得到的是该元素的数据类型长度(单位字节)。

所有值初始化为0:long aa [100] = {0}; 第一个元素被设置为0,其他元素被编译器默认设置为0。
short bb [] = {1, 2, 3, 4}; // C++ 11中可以省略等号,并且可将大括号内空值则都默认设置为0。
int num_elements = sizeof bb / sizeof (short); // 计算数组中总元素个数。【short必须加括号】

C++标准模板库STL提供了模板类vector,array,可替代数组。

2、字符串:
字符串是存储在内存(连续字节)中的以空字符为结尾的一系列字符。
C-风格字符串特殊性质:以空字符(null character)结尾,空字符被写作 \0,其ASCII码为0,用来标记字符串的结尾。空字符(实际上是被设置为0的字节)在内存中很常见。

如char cat [2] = {‘a’, ‘b’, ‘\0’}; 必须结尾以空字符的才是字符串,否则不是。
另一种写法:【字符串常量或字符串字面值】
char cat2 [2] = “ab”; // the \0 is understood. 隐式地包括结尾空字符
char cat3 [] = “ab”; // let the compiler count.

拼接字符串:C++中任何两个由空白(空格、制表符、换行符)分隔的字符串常量都将自动拼接成一个。前面的字符串结尾空字符将被后面拼接的字符串的首字符取代。【注意:java中必须使用“+”加号连接】

C语言库函数strlen()确定字符串的长度。【标准头文件cstring或老式string.h】
注意:sizeof运算符指出整个数组的总字节长度,而strlen()函数返回的是存储在数组中的字符的长度即字符个数,另外 strlen() 只计算可见的字符,而不把空字符计算在内。
因此如果str是字符串,则要存储该字符串,数组的长度不能短语strlen(str) + 1,因为要加上空字符。

3、字符串输入:
读取输入的单个单词:
cin >> name; 语句cin默认使用空白(空格、制表符、换行符)来确定字符串的结束位置,这意味着cin在获取字符数组输入时只读取一个单词,并自动在结尾添加空字符。

读取一行字符串输入:cin.getline(name, 20); 可接收19个输入字符,第20个是空字符。
cin提供了面向行输入的类成员函数:getline()和get(),读取一行输入,直到到达换行符【回车键】,然而随后getline()将丢弃换行符【即不会将换行符保留在输入序列中,而是将换行符替换为空字符】,而get()将换行符保留在输入序列中【因此必须cin.get()读取一个字符即去掉保留的换行符】。
用法:cin.getline(name, ArSize).getline(name2, ArSize) 等价于 cin.get(name, ArSize).get().getline(name2, ArSize) 。
cin.getline(name, ArSize):读取一个字符串。
cin.get():读取一个字符。

C++程序常使用指针(而不是数组)来处理字符串。

4、string类【ISO C++98标准库添加】:头文件string,string类位于名称空间std中。
很多方面,string对象的方式与使用字符数组相同:
4.1、可以使用C-风格字符串来初始化string对象,例 string str = “abcdkkk”。
4.2、可以使用cin来将键盘输入存储到string对象中,例 cin >> str 。
4.3、可以使用cout来显示string对象,例 cout << str。
4.4、可以使用数组表示法来访问存储在string对象中的字符,例 str[2] = ‘c’。

理论上说,可以将char数组视为一组用于存储一个字符串的char存储单元,而string类对象变量是一个表示字符串的实体。
不常用的C++字符串列表初始化:string str = {“asdfg”}; int s_size = str.size(); // obtain length of str

赋值、拼接、附加:
注意:不能将一个数组赋值给另一个数组,但可以将一个string对象赋值给另一个string对象。
char charr1[20];
char charr2[20] = “ddd”;
strcpy(charr1, charr2); // copy charr2 to charr1
strcat(charr1, charr2); // append contents of charr2 to end of charr1

char数组和string类对象长度获取区别:
char charr [20]; strlen(charr); ===> 此时获取的大小不确定,因为未初始化定义,则第一个空字符的出现位置是随机的,而strlen()计数以空字符结束。
string str; str.size(); ===> 未初始化时该值为0,string对象长度被自动设置为0。

5、C++新增原始(raw)字符串表示法:
wchar_t title[] = L"A"; // w_char string
char16_t name[] = u"B"; // char16_t string
char32_t car[] = U"C"; // char32_t string

在原始字符串中,字符表示的就是自己,将 “( 和 )” 用作定界符,并使用前缀R来标识原始字符串。cout << R"(Jim “King” uses “\n” instead of endl. )" << endl;
输出为:Jim “King” uses \n instead of endl.
不需要单斜杠\来转义序列。

6、结构:结构是用户定义的类型,一种比数组更灵活的数据结构,数组只能存储相同类型的数据,而同一个结构可以存储多种类型的数据,可以将不同信息存储在同一个单元中。
结构声明如下:
struct Abc
{
char name[20]; // 结构成员
double price;
};
创建结构变量:
Abc aa; // aa is a structure variable of type Abc
C++允许在声明结构变量时省略关键字struct;
struct Abc aa; // key struct required in C.

可以使用成员运算符(.)点来访问各个成员。如aa.price访问了该成员值。

结构变量初始化:【C++中支持列表初始化即可以省略等号 = 】
Abc aa = {“LH”, 23.33};

位于函数外面的结构声明被称为外部声明,而内部声明只能被该声明所属的函数内部使用。
变量也可以在函数内部和外部定义,C++不提倡使用外部变量,但提倡使用外部结构变量。另外在外部声明符号常量通常更合理。

注意:若大括号内未包含任何东西,各个成员都将被设置为零。
即Abc aa {}; 成员值都被设置为零,name成员的每个字节都被设置为零。但不允许缩窄转换。

可以同时完成定义结构和创建结构变量:
struct Abc
{
std::string name;
} aa, bb; // two Abc variables

也可以初始化以这种方式创建的变量:
struct Abc
{
std::string name;
} aa = {“adss”};
建议将结构定义和变量分开。

另外还可以声明没有名称的结构类型,即省略结构名称,同时定义一种结构类型和一个这种类型的变量:
struct // no tag
{
std::string name;
} aa; // a structure variable.
注:但这种类型没有名称,因此以后无法创建这种类型的变量。

与C结构不同,C++结构除了成员变量之外,还可以有成员函数。但这种高级特性通常被用于类中,而不是结构中。

7、共用体:每次只能存储一个成员值,共用体长度为其最大成员的长度。用法和结构类似。
共用体(union)是一种数据格式,存储不同的数据类型,但只能同时存储其中的一种类型。
union one4all
{
int int_val;
long long_val;
double double_val;
};
用途:档数据项使用两种或多种格式但不会同时使用时,可节省空间。

匿名共用体(anonymous union)没有名称,其成员将成为位于相同地址外的成员变量。每次只有一个成员是当前成员。

结构体常用于节省内存。

8、枚举:
另一种创建符号常量方式,可替代const。允许定义新类型,enum句法与使用结构相似。
enum spectrum {red, orange, yellow, green};
将red等成员作为符号常量,对应整数值0~3,默认从0开始【可以显示指定值】。被称为枚举量。
声明枚举变量:
spectrum band;
在不使用强制转换情况下,只能将定义枚举时使用的枚举量赋给这种枚举的变量。即不接受非法值
band = green; // valid
band = 200; // invalid,有些编译器报错,有些警告
枚举量是整型,可被提升为int类型,但int类型不能自动转换为枚举类型。
int color = red; // valid, type promoted
band = 3; // invalid
color = 3 + red; // valid, type converted

若int值时有效的,可强制类型转换赋值给枚举变量:
band = spectrum(3); // typecast 3 to type spectrum
若强制类型转换一个非法int值,不会报差,但结果是不确定的。

如果打算只使用常量,而不创建枚举类型的变量,则可以省略枚举类型的名称:
enum {red, orange, yellow, green};

枚举常量的值可以显示的指定值来覆盖默认的值,如:
enum spectrum {red = 2, orange = 4, yellow, green = 8};
enum spectrum {red, orange = 4, yellow, green}; // 这样的话,red为0,yellow为5,green为6

还可以创建多个值相同的枚举量:
enum spectrum {red, orange = 0, yellow, green = 1};
前两个值为0,后两个值为1。C++早期版本中只能将int值(或提升为int的值)赋值给枚举量,但这种限制取消了,因此可以使用long或long long类型赋值【只是会浪费内存】。

9、指针和自由存储空间:
计算机存储数据时必须跟踪的3种基本属性:
9.1、信息存储在哪儿;
9.2、存储的值为多少;
9.3、存储的信息是什么类型。
定义一个简单变量:声明语句指出了值的类型和符号名,还让程序为值分配内存,并在内部跟踪该内存单元。

指针是一个变量,其存储的是值的地址,而不是值本身。
常规变量地址:对变量应用地址运算符(&)。 十六进制显示地址值。
使用常规变量时,值是指定的量,而地址为派生量。

指针和C++基本原理:
面向对象编程与传统的过程性编程的区别在于,OOP强调的是在运行阶段(而不是编译阶段)进行的决策。运行阶段决策提供了灵活性,C++使用关键字new请求正确数量的内存以及使用指针来跟踪新分配的内存的位置。

使用指针变量时,地址是指定的量,而值为派生量。
指针用于存储值的地址。因此指针名表示的是地址。(*)运算符被称为间接值(indirect value)或解除引用(dereferencing)运算符,将其应用于指针,可以得到该地址处存储的值。

10、声明和初始化指针:
计算机需要跟踪指针指向的值的类型。如char的地址与double的地址看上去没什么区别,但char和double使用的字节数是不同的,它们存储值时使用的内部格式也不同。因此指针声明必须指定指针指向的数据的类型。
int * p_int;
这表明,p_int的类型为int。由于运算符被用于指针,因此p_int变量本身必须是指针。我们说p_int指向int类型,还可以说p_int的类型是指向int的指针,或int* 。可以这样说,p_int是指针(地址),而*p_int是int,而不是指针。

运算符两边的空格是可选的。
传统C风格指针写法:
int ptr;
强调的是【ptr】是一个int类型的值。
而C++风格指针写法:
int
ptr;
强调的是【int
】是一种类型——指向int的指针。即ptr是一种int
类型,指向int类型的指针。
但要注意:
int* p1, p2;
此处声明创建一个指针(p1)和一个int变量(p2)。因此可以使用C风格指针写法比较好理解:
int p1, p2;
对每一个指针变量名,都需要使用一个
解除引用运算符。
注意:在C++中【int*】是一种复合类型,是指向int的指针。

需要理解:指针变量不仅仅是指针,而且是指向特定类的指针。可以这么理解:指针是一种数据类型,而指针变量则是这种类型的一个实例。
注意:两个指向不同(长度)数据类型的指针变量,它们变量本身的长度通常是相同的。也就是说,存储不同数据类型的地址的长度是相同的。
【变量的】地址的长度或值不能指示关于变量的长度或类型的任何信息。
地址一般需要2个、4个或更多个字节来存储,取决于系统实现。

11、指针的危险:
野指针,只分配了存地址的内存,但没有分配具体地址即未初始化指针。因此指针变量必须初始化。
C++中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存。
long *fellow;
fellow = 3333;
fellow确实是个指针,但它指向的地址将是不确定的,因为指针没有被初始化,它可能是任何值。不管值是什么,程序都将它解释为存储3333的地址。如若该地址为1200但刚好又是程序代码的地址,这样将会修改了1200地址上的值,也就导致程序运行出现问题,这种错误可能会导致一些最隐秘最难以跟踪的bug。
警告:一定要在指针应用解除引用运算符(
)之前,将指针初始化为一个确定的、适当的地址。

12、指针和数字:
指针不是整型,虽然计算机通常把地址当作整数来处理。
从概念上看,指针与整数时截然不同的数据类型。
指针描述的是【地址】位置,将两个地址相乘没有任何意义。
不能简单将整数赋给指针:
int *pt;
pt = 0xB8000000; // type mismatch
左边是指针,右边是整数。C++编译器会显示错误信息:通告类型不匹配。要将数字值作为地址来使用,应通过强制类型转换将数字转换为适当的地址类型:
int *pt;
pt = (int *) 0xB8000000; // type mismatch
这样,赋值语句的两边都是整数的地址,因此该赋值有效。

13、使用new来分配内存:实现在程序运行时分配内存。
指针初始化为变量的地址,变量是在编译时分配的有名称的内存,而指针只是为可以通过名称直接访问的内存提供了一个别名。
指针真正用武之地在于,在运行阶段分配未命名的内存以存储值。在这种情况下,只能通过指针来访问内存。

在C中,可以库函数malloc()来分配内存,而在C++中新增了更好的方法——new运算符。
程序员需要告诉new,需要为哪种数据类型分配内存,new将找到一个长度正确的内存块,并返回该内存块的地址,程序员的职责是将该地址赋给一个指针。如下:
int * pn = new int;
new int 告诉程序,需要适合存储int的内存。new 运算符根据类型来确定需要多少字节的内存。然后它找到这样的内存,并放回其地址。因此 pn 是地址,*pn 是存储在那里的值。
术语“数据对象”比变量更通用,它指的是为数据项分配的内存块。因此变量也是数据对象,但pn指向的内存不是变量而是数据对象。
typeName * pointer_name = new typeName;
需要在两个地方指定数据类型:用来指定需要什么样的内存和用来声明合适的指针。

new分配的内存块通常与常规变量声明分配的内存块不同。常规变量和指针变量的值都存储在被称为栈(stack)的内存区域中,而new从被称为堆(heap)或自由存储区(free store)的内存区域分配内存。

在C++中,值为0【即指向的地址为0】的指针被称为空指针(null pointer)。C++确保空指针不会指向有效的数据,因此它常被用来表示运算符或函数失败(若成功则返回一个有用的指针)。

14、使用delete释放内存:
使用delete运算符,在使用完内存后,能够将内存归还给内存池。
int *ps = new int;
delete ps;
释放ps指向的内存,但不会删除指针ps本身。例如可以将ps重新指向另一个新分配的内存块。一定要配对使用new和delete,否则会发生内存泄露(memory leak),即被分配的内存再也无法被使用了。

注意:不要尝试释放已经释放的内存块,C++标准指出,这样做的结果将是不确定的。这意味着什么情况都可能发生。另外,不能使用delete来释放声明变量所获得的内存:
int *ps = new int; // ok
delete ps ; // ok
delete ps; // not ok now
int jugs = 4; // ok
int * pi = &jugs; // ok
delete pi; // not allowed, memory not allocated by new
警告:只能用delete来释放使用new分配的内存。然而,对空指针使用delete是安全的。
int *ps = new int;
int *pq = ps;
delete pq; // delete with second pointer
创建了两个指向同一个内存块的指针,但注意不要删除同一个内存块两次。

15、使用new来创建动态数组【dynamic array】:
通常,对于大型数据(如数组、字符串和结构),应使用new。
如需要数组时,通过声明创建数组,则在程序被编译时将为它分配内存空间。不管程序最终是否使用数组,数组都在那里,它占用了内存。在编译时给数组分配内存被称为静态联编(static binding),即数组是在编译时加入到程序中的,并且必须在编写程序时指定数组的长度。而使用new时,在运行阶段,需要数组则创建,不需要则不创建,被称为动态联编(dynamic binding),并且程序将在运行需要数组时确定数组的长度。

创建动态数组:
typeName * pointer_name = new typeName [num_elements];
new运算符返回第一个元素的地址,并赋给指针。即pointer_name是指向一个typeName(数组第一个元素)的指针。
释放数组内存:
delete [] pointer_name;

使用动态数组:
int *ptr = new int [10]; // get a block of 10 ints
创建指针ptr,指向包含10个int值【连续内存】的内存块中的第一个元素。若int占用4个字节,则指针沿正确的方向移动4个字节,指针将指向第二个元素。
访问元素:ptr 是第一个元素的值。访问元素也可以只需要把指针当做数组名使用即可。即访问第一个元素也可以使用ptr[0],而不是ptr。

数组名和指针的根本差别:
ptr = ptr + 1; // okay for pointers, wrong for array name
不能修改数组名的值。但指针是变量,因此可以修改它的值。
加1表示的是当前指针指向的元素的下一个元素,减1则指向上一个元素。
警告:delete [] ptr; 时必须将ptr还原到指向第一个元素地址,才能使delete正确的删除整个数组。

16、指针、数组、指针算术
注意:将指针变量加1后,其增加的值等于它指向的类型占用的字节数。
在多数情况下,C++将数组名解释为数组第一个元素的地址。因此有两种获取数组地址的方式:
ptr = arrayName;
ptr = &arrayName[0];

例:
short arr[3] = {3, 2, 1};
数组表达式arr[1],C++编译器将表达式看做是 *(arr + 1),这意味着先计算数组第2个元素的地址,然后找到存储在那里的值。最后的结果便是arr[1]的含义(运算符优先级要求使用括号,如果不使用括号,将给 *arr 加1,而不是给arr加1)。
*(arr + 1) 和 arr[1] 是等价的。
通常,使用数组表示法时【如aar[1]】,C++都执行下面的转换:
arrayName[i] 转换为 *(arrayName + i)
若使用的是指针,而不是数组名,C++也执行同样转换:
pointerName[i] 转换为 (pointerName + i)
因此,对于数组名和指针数组,可以使用数组方括号表示法,也可以使用解引用运算符(
),多数表达式中,它们都表示地址。区别之一是,可以修改指针的值,而数组名是变量(不能修改):
pointerName = pointerName + 1; // valid
arrayName = arrayName + 1; // not allowed
另一个区别:对数组应用sizeof运算符得到的是数组的长度(所有元素总字节长度),而对指针应用sizeof得到的是指针的长度,即使指针指向的是一个数组。
double wages [3] = {3.0, 2.0, 1.0};
double *pw = wages;
sizeof wages = 3 x 8 = 24 bytes, sizeof pw = 4 bytes.
注意:在sizeof这种情况下,C++不会将数组名解释为地址。

数组的地址:
对数组去地址时(如&wages),数组名也不会被解释为地址。
数组名被解释为其第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址:
short tell[10]; // tell an array of 20 bytes
cout << tell << endl; // displays &tell[0] address of first element of array
cout << &tell << endl; // displays address of whole array
从数字上说,这两个地址相同。但从概念上说,&tell[0](即tell)是一个2字节内存块的地址,而 &tell 是一个20字节内存块的地址。因此表达式 tell + 1 将地址值加2,而表达式 &tell + 1 将地址加20。换句话说,tell 是一个short指针(short类型),而&tell是一个这样的指针,即指向包含20个元素的short数组( short()[20] ),可称为数组指针(是一种指针)。
可以这样什么和初始化这种指针:
short (pas) [20] = &tell; // pas points to array of 20 shorts.
如果省略括号,优先级规则将使得pas先与[20]结合,导致pas是一个short指针数组,它包含20个元素,因此括号必不可少。其次,如果要描述变量的类型,可将声明中的变量名删除。因此pas的类型为short(
)[20]。另外,由于pas被设置为&tell,因此 *pas 和 tell 等价,所以 (*pas)[0]为tell数组的第一个元素。

【如上,需要理解为何可以这样做,将是一种挑战】

17、指针和字符串:
注意:在cout和多数C++表达式中,char数组名、char指针以及用引号括起来的字符串常量都被解释为字符串第一个字符的地址。【读取直到遇到空字符为止】
const char * bird = “wren”; // bird holds address of string
注意:"wren"字符串字面值【常量】实际表示的是该字符串的地址。【通常,编译器在内存留出一些空间,以存储程序源代码中的所有用引号括起的字符串常量,并将每个被存储的字符串与其地址关键起来】

例:
char *animal = “bear”;
char *ps;

注意:此处并不会复制字符串,而只是复制存储字符串的地址,因此这两个指针将指向相同的内存单元和字符串。
ps = animal; // set ps to point to string

获取字符串的副本:重新声明一个新数组或使用new来完成。
new可以根据字符串的长度来指定需要的内存空间。
ps = new char[strlen(animal) + 1]; // get new storage
加1来获得包含空字符时该字符串的长度。
然后需要将字符串复制到新分配的空间中。需要使用库函数strcpy():
strcpy(ps, animal); // copy string to new storage

18、使用new创建动态结构:
“动态”意味着内存是在运行时,而不是编译时分配的。
strut things
{
int good;
int bad;
};
创建动态结构指针变量,一个未命名的things类型,并将其地址赋给一个指针:
things *ps = new things;
访问结构成员:
创建动态结构时,不能将成员函数运算符句点用于结构名,因为这种结构没有名称,只是知道它的地址。C++中,使用箭头运算符(->)。该运算符用于指向结构的指针,就像点运算符用于结构名一样。则ps->good是被指向的结构的goog成员。
另一种方式:ps是指向结构的指针,则 *ps 就是被指向的值——结构本身。因此 (*ps).good 是该结构的good成员。运算符优先级必须使用括号。

19、C++内存管理基本知识:【自动存储、静态存储、动态存储、线程存储】
根据用于分配内存的方法,C++有3种管理数据内存的方式:自动存储、静态存储和动态存储(有时也称为自由存储空间或堆)。
C++11新增了第四种类型——线程存储【后续介绍】。

自动存储:【局部变量】
函数内部定义的常规变量使用自动存储空间,被称为自动变量(automatic variable),它们在所属函数被调用时自动产生,在该函数结束时消亡。即在该函数结束时,自动变量的内存空间将会被释放内存,以供其他存储重新使用,改变该内存地址上的值(因被释放内存)。
自动变量是一个局部变量,其作用域为包含它的代码块。代码块是被包含在花括号的一段代码。即该变量仅在程序执行该代码块中的代码时存在。
自动变量【即局部变量】通常存储在栈中。执行代码块时,其中的变量将依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量,即后进先出(LIFO)。

静态存储:【静态变量】
静态存储是整个程序执行期间都存在的存储方式。变量成为静态的方式有两种:一是在函数外部声明定义它,二是在声明变量时使用关键字static:
static double fee = 33.03;

自动存储和静态存储的关键在于:这些方法严格限制了变量的寿命。变量可能存在于程序的整个生命周期(静态变量),也可能只是在特定函数被执行时存在(自动变量)。

动态存储:
new 和 delete 运算符:更灵活的方式。他们管理了一个内存池,C++中被称为自由存储空间(free store)或堆(heap)。
注意:该内存池同用于静态变量和自动变量的内存是分开的。
数据变量的生命周期不完全受程序或函数的生存时间控制。让开发者对使用内存有更大的控制权。
在栈中,自动添加和删除机制使得占用的内存总是连续的,而new 和 delete 的相互影响可能导致占用的自由存储区不连续,这使得跟踪新分配内存的位置更困难。

栈、堆和内存泄露
new在堆上创建变量后,若没有调用delete,则即使包含指针的内存由于作用域规则和对象生命周期的原因而被释放,在堆上动态分配的变量或结构也将继续存在。实际上,将会无法访问堆上的结构,因为指向这些内存的指针无效。这将导致内存泄露。被泄露的内存将在程序的整个生命周期内都不可使用,这些内存被分配出去,但无法收回。极端情况(不过不常见)是,内存泄露可能会非常严重,以至于应用程序可用的内存被耗尽,出现内存耗尽错误,导致程序崩溃。另外,这种泄露还会给一些操作系统或在相同的内存空间中运行的应用程序带来负面影响,导致它们崩溃。

20、类型组合
struct A
{
int year;
};
创建结构变量:
A s01, s02, s03;
句点成员运算符访问成员:
s01.year;
创建指向这种结构的指针:
A pa = &s02;
箭头间接成员运算符访问成员:
pa->year = 23244;
创建结构数组:
A arr[3]; // array of 3 structures
成员运算符访问元素的成员:
arr[0].year = 23333; // arr[0] is a structure
数组名是一个指针,因此可使用箭头间接成员运算符访问元素的成员:
(arr+1)->year = 3333; // same as arr[1].year = 3333;
创建指针数组:
const A * p_arr[3] = {&s01, &s02, &s03};
p_arr是一个指针数组,首先是个数组,其次存储的元素类型是指针,此处存储的是结构地址即结构指针值,p_arr是一个指针数组,那么p_arr[1]就是一个指针,可用间接运算符访问其数组元素的成员:
int y = p_aar[1]->year; // same as int y = (
(p_arr+1))->year;
可创建上述数组的指针:
const A ** pp_aar = p_arr;
p_aar是一个数组名(指针数组),因此它是第一个元素的地址。但其第一个元素为指针,因此pp_aar是一个这样的指针,指向一个指向const A结构的指针。【指向指针数组的指针】
由于pp_aar是一个指向结构指针的指针,因此pp_aar是一个结构指针,可用间接成员运算符访问其成员:
(pp_aar)->year
(
(pp_aar+1))->year
由于pp_aar指向p_arr的第一个元素,因此
pp_aar为第一个元素,即&s01。所以(*pp_aar)->year为s01的year成员。pp_aar+1指向下一个元素p_arr[1],即&s02。括号必不可少。例如:pp_aar->year试图将运算符应用于pp_aar->year,这将导致错误,因为成员year不是指针。

21、数组的替代品:
模板类vector和array是数组的替代品。

模板类vector:类似于string类的一种动态数组。可在运行阶段设置vector对象的长度,可在末尾附加新数据,或中间插入新数据。基本上,它是使用new创建动态数组的替代品。实际上,vector类确实使用new和delete来管理内存的,但这种工作时自动完成的。
必须包含vector头文件。其次vector包含在名称空间std中,因此可使用using编译指令、using声明或std::vector。
模板类使用不同的语法来指出它存储的数据类型并指定元素数。
#include
using namespace std;
vector vi; // create a zero-size array of int
int n;
vector vd(n); // create an array of n double;
说明:vi是一个vector对象,vd是一个vector对象。
vector对象可在插入或添加值时自动调整长度。
变量声明创建格式:
vector vt; // or vector vt (n_elem);

模板类array(C++11):
vector功能比数组强大,但付出的代价是效率稍低。若需要长度固定的数组,那么使用数组是更佳的选择,但代价是不那么方便和安全。因此新增了模板类array,也是位于名称空间std中。与数组一样,array对象的长度也是固定的,也使用栈(静态内存分配),而不是堆,因此其效率和数组相同,但更方便和安全。
必须包含头文件array。
#include
using namespace std;
array<int, 5> ai; // crate array object of 5 ints
array<double, 3> ad = {1.0, 2.1, 3.0};
创建格式:
array<typeName, n_elem> arr;
与创建vector对象不同,n_elem必须是常量,不能是变量。

比较数组、vector对象和array对象:
这三者都可使用标准数组(下标)表示法来访问各个元素。
vector对象存储在堆中,另外两个存储在栈中。
array对象可以赋给另一个array对象,但数组必须逐元素赋值数据。

double arr[4] = {1.0, 2.2, 3.0, 4.2};
注意这种访问方式:
arr[-2] = 32.3;
索引-2,前面讲过数组表示法都会被转换为如下代码:
*(arr-2) = 32.3;
说明:找到arr指向的地址,向前移动两个double元素,并将32.3存储到目标地址中。
也就是说,此处将信息存储在数组的外面。
警告:与C语言一样,C++也不会检查这种越界错误。这种行为是不安全的。
vector和array对象也可以有这种错误,但也可以禁止这种行为。
vector vd_arr(4) = {1.0, 2.2, 3.0, 4.2};
vd_arr[-2] = .3; // still allowed.
vd_arr[100] = .3; // still allowed.
禁止这种行为用它们的成员函数at()。
vd_arr.at(2)= .3; // assign 0.3 to vd_arr[2]
区别在于: 函数访问方式的函数中会在运行期间检查索引,但这种额外检查的代价是运行时间更长。C++两种方式都存在。它们也包含成员函数begin()和end()来确定边界。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值