【C++ Primer Plus】第4章 复合类型

目录

【数组】

【C- 字符串】

字符串的输入

字符串拼接

字符串常用函数

【数组长度】

【string类】

原始字符串

【struct 结构简介】

扫描二维码关注公众号,回复: 15137653 查看本文章

【枚举】

【指针和自由存储空间】

使用new分配内存

使用new创建动态数组

【指针、 数组和指针算术】

指针算术

指针和字符串

使用new创建动态结构

new 和 delete 使用示例

【自动存储、静态存储、动态存储】 

自动存储:变量只有特定函数被执行时存在

静态存储:变量存在于程序的整个生命周期

动态存储:数据的生命周期不完全受程序或函数的生存时间控制

栈、堆和内存泄漏

【组合类型】

【数组的替代品:模板类vector和array】

模板类vector

C++11新增了模板类array

区别与联系

【复习题】

【编程练习】

【总结】


【数组】

数组声明:typeName arrayName[arraySize];​  //arraySize:为整型常数、const值、或常量表达式;

数组赋值:1、只有在定义一个数组时才可以给整个数组初始化赋值;2、一个一个单独赋值;

int yamcosts[5] = {20, 30, 5};    //定义时初始化赋值,提供的元素可以少于数组的定义,其他元素为0
int yamcosts[] = {20, 30, 5};    //可以省略[]中的值,编译器自动计算元素个数,将使yamcosts数组包含3个元素
int yamcosts[3] {20, 30, 5};    //可以省略赋值号
int yamcosts[3] = {};          //花括号内可以为空,初始化全部为0

int yamcosts[3];     //先定义一个数组,然后一个一个单独赋值
yamcosts[0] = 20;    //arraySize从0开始编号
yamcosts[1] = 30;
yamcosts[2] = 5;

//int yamcosts[3];
//int yam[3];
//yamcosts[3] = yam[3];         //not allowed,不能一个数组给另一个数组赋值
//yamcosts[3] = {20, 30, 5};    //not allowed,只有在定义时才可以给整个数组赋值

【C- 字符串】

字符串实际上是使用 null 字符 \0 终止的一维字符数组,空字符ASCII码为0。

char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
char site[] = "RUNOOB";    //双引号,隐式的包含\0,\0占一个字符位置,系统为site分配7个字节

字符串的输入

连续使用两次cin.get(name,size);,由于第一次调用后, 换行符将留在输入队列中, 因此第二次调用时看到的第一个字符便是换行符。 因此get( )认为已到达行尾, 而没有发现任何可读取的内容。在每一次使用cin.get(name,size);后再加一个cin.get();。 

cin.getline(arrayName, arraySize);   // 第一个参数是目标数组; 第二个参数数组长度, getline( )使用它来避免超越数组的边界。
函数getline( )是istream类的一个类方法( cin是一个istream对象) 。

(cin >> arrayName).get();               //以空白(空格、 制表符和换行符)来确定字符串的结束位置。保留换行符;把回车键生成的换行符留在了输入队列中。
cin.getline(arrayName, arraySize);      //每次读取一行字符串输入,丢弃换行符;arraySize(包括结尾字符)。
cin.get(arrayName, arraySize).get();    //每次读取一行字符串输入,保留换行符;
cin.clear();    //读取空行后恢复输入命令

字符串拼接

cout << "i" " love " "you";    //拼接时不会在被连接的字符串之间添加空格

字符串常用函数

使用函数strcpy(charr1, charr2)将字符串复制到字符数组中, 
使用函数strcat(charr1, charr2)将字符串附加到字符数组末尾.

【数组长度】

如果将sizeof运算符用于数组名,得到的将是整个数组中的字节数。但如果将sizeof用于数组元素,则得到的将是元素的长度(单位为字节)。这表明yamcosts是一个数组, 而yamcosts[1]只是一个int变量。

#include <iostream>
#include <cstring>    //strlen()函数的头文件

int main(void)
{
    int yamcosts[3] = {20, 30, 5};
    char site[] = "RUNOOB";

    strlen(site);        //输出6,不计算结尾空字符,只计算可见长度
    sizeof(site);        //输出7(bytes)整个数组的长度
    sizeof(yamcosts);    //输出12(bytes),每个int4个字节,3个元素12个字节
    sizeof(yamcosts[1]); //输出4(bytes)

    return 0;
}

【string类】

  1. 要使用string类, 必须在程序中包含头文件string。
  2. string类位于名称空间std中, 因此您必须提供一条using编译指令, 或者使用std::string来引用它。
  3. 类设计让程序能够自动处理string的大小。
  4. 可以使用C-风格字符串来初始化string对象。
  5. 可以使用cin来将键盘输入存储到string对象中。
  6. 可以使用cout来显示string对象。
  7. 可以使用数组表示法来访问存储在string对象中的字符。
  8. 可以使用类方法size来计算string的长度(不包含结束字符\0)
//string类
//方法是一个函数,只能通过其所属类的对象进行调用。

#include <iostream>
#include <cstring>	//string变量的头文件

using namespace std;

int main(void)
{
	string str1, str2="jiqixuexi", str3;	//初始化string
	
	cout << "Please enter the string str1: ";
	getline(cin,str1);    //string输入:getline不是一个类方法,它将cin作为一个参数,指出要到哪里输入
	
	str2 += str1;	//string拼接
	str3 = str2;	//string赋值
	
	cout << "str1 = " << str1 << endl;
	cout << "str2 = str2 + str1 = " << str2 << endl;
	cout << "str3 = str2 = " << str3 << endl;
	
	int len1 = str1.size();	    //string长度:str1是一个对象,而size( )是一个类方法。
	cout << "length of str1 " << len1 << endl;

	return 0;
}

2d99dbad99cf4e8b9dad7ff08ba4e2e8.png

原始字符串

使用 R"+*(    )+*" 括号里为输出字符串

cout << R"+*("(Who wouldn't?)", she whispered.)+*" << endl;
//would display the following:"(Who wouldn't?)", she whispered.

【struct 结构简介】

结构是用户定义的类型, 而结构声明定义了这种类型的数据属性。
创建结构包括两步。首先, 定义结构描述—它描述并标记了能够存储在结构中的各种数据类型。 然后按描述创建结构变量/数组(结构数据对象)。

de3e54bda5a14e03b5b427e4765b69f7.png

//struct结构的简单介绍,结构变量和结构数组
//可以省略结构类型的名称,但后续无法创建同类型的结构体。

#include <iostream>

struct student	//结构声明:定义一个新的类型。外部声明可以被后面任何函数使用。
{
	char name[20];
	std::string hobby;	//将string类作为成员
	float height;
	double weight;
}aa, bb;	//同时完成定义和创建结构变量,也可直接初始化

int main(void)
{
	using namespace std;
	
	student a;	//创建结构变量:可以省略关键字struct,不进行初始化。
	student b = {"xiao chai", "badminton", 160.5, 106.5};	//初始化结构变量,可以省略等号。
	a = b;	    //成员赋值:这时a和b一样
	cout << a.name << " likes playing " << a.hobby << ".\n";
	
	student class_1[100];	//创建结构数组:创建100个student类型的数组。
	student class_2[2] = 	//初始化结构数组
	{
		{"xiao hua", "eat", 165.2, 100.0},      //逗号隔开
		{"xiao jiang", "sing", 188.8, 123.4}    //最后一个成员不用逗号
	};    //结尾用分号
	cout << class_2[0].name << " likes playing " << class_2[0].hobby << ".\n";

	return 0;
}

【枚举】

C++的enum工具提供了另一种创建符号常量的方式, 这种方式可以代替const。

  1. 在默认情况下,将整数值赋给枚举量,第一个枚举量的值为0,第二个枚举量的值为1,依次类推。
  2. 显式设置枚举量的值:指定的值必须是整数
  3. 没有被初始化的枚举量的值将比其前面的枚举量大1
  4. 强制转换的值不可以超出枚举的范围
  5. 枚举的范围:最小值到最大值
enum number {zero, one, two, three, five=5, six}area;
//在默认情况下,将整数值赋给枚举量,第一个枚举量的值为0,第二个枚举量的值为1,依次类推。
//显式设置枚举量的值:指定的值必须是整数,例如five=5
//没有被初始化的枚举量的值将比其前面的枚举量大1,因此six=6。
//枚举的范围:最小值到最大值,[0,6]
	
number time;	//用枚举名称声明枚举变量
time = one;	    //将定义枚举时使用的枚举量赋给枚举的变量
//time = 3;	    //不可以
time = (number) 3;	//强制转换3为number类的枚举量赋值给time,强制转换的值不可以超出枚举的范围
time = (number) 4;	//其中4不是枚举值,但它位于枚举定义的取值范围内。

【指针和自由存储空间】

变量取地址:只需对变量应用地址运算符(&)就可以获得它的位置;例如,如果home是一个变量,则&home是它的地址。

指针:是一个变量,其存储的是值的地址,而不是值本身。(*)运算符被称为间接值(indirect velue) 或解除引用(dereferencing) 运算符, 将其应用于指针,可以得到该地址处存储的值。例如:假设manly是一个指针, 则manly表示的是一个地址, 而*manly表示存储在该地址处的值。 *manly与常规int变量等效。

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

int jumbo;
int *pe = &jumbo;         //pe=&jumbo, *pe=jumbo.

int a;
int *p1;
p1 = &a;    //p1=&a, *p1=a.

将数字值作为地址来使用,应通过强制类型转换将数字转换为适当的地址类型:

int *p2;
p2 = (int *)0xB8000000;    //将一个确定的地址赋给指针

int 变量 jumbo 和 int 指针变量 pe 只不过是同一枚硬币的两面。
变量 jumbo 表示值,并使用&运算符来获得地址;而变量 pe 表示地址,并使用*运算符来获得值(参见图4.8)。
由于 pe 指向 updates,因此 *p_updates 和 jumbo 完全等价。可以像使用int变量那样使用*pe。 

使用new分配内存

  1. new int 告诉程序,需要适合存储int的内存。
  2. new运算符根据类型来确定需要多少字节的内存。然后,它找到这样的内存,并返回其地址。
  3. 接下来,将地址赋给pn,pn是被声明为指向int的指针。即 int *pn = new int;
  4. 现在,pn是地址,而*pn是存储在那里的值。
int *pn = new int;			    //声明4:使用new分配内存:pn是被声明为指向int的指针。
double *pd = new double;
*pd = 1010.10;
cout << "int *pn : value = " << *pn << ", \tand location = " << &pn << endl;	//指针是一个地址,但是指针也需要一个存放它的地址,即地址的地址
cout << "double *pd : value = " << *pd << ", and location = " << &pd << endl << endl;
cout << "size of &pn = " << sizeof(&pn) << endl;	//8,指针的地址占8个字节,64位系统地址有8字节,8*8=64
cout << "size of pn = " << sizeof(pn) << endl;	    //8,指针本身是一个地址也占8个字节
cout << "size of *pn = " << sizeof(*pn) << endl;	//4,指针指向的int类型4字节
cout << "size of pd = " << sizeof(pd) << endl;
cout << "size of *pd = " << sizeof(*pd) << endl << endl;    //8,指针指向的double类型8字节
delete pd;
delete pn;	//delete只能释放new分配的内存:释放pn指向的内存,但不会删除指针pn本身。

pn指向的内存没有名称, 如何称呼它呢?
我们说pn指向一个数据对象, 这里的“对象”不是“面向对象编程”中的对象, 而是一样“东西”。 术语“数据对象”比“变量”更通用, 它指的是为数据项分配的内存块。 因此, 变量也是数据对象, 但pn指向的内存不是变量。乍一看, 处理数据对象的指针方法可能不太好用, 但它使程序在管理内存方面有更大的控制权。

必须声明指针所指向的类型的原因之一:
地址本身只指出了对象存储地址的开始,而没有指出其类型(使用的字节数)。地址只是数字,并没有提供类型或长度信息。另外,指向int的指针的长度与指向double的指针相同。它们都是地址,但由于程序中声明了指针的类型,因此程序知道double *pd = new double; 是8个字节的double值,int *pt = new int; 是4个字节的int值。打印*pd的值时,cout知道要读取多少字节以及如何解释它们。

使用new创建动态数组

  1. 在编译时给数组分配内存被称为静态联编(static binding),意味着数组是在编译时加入到程序中的。
  2. 但使用new时,如果在运行阶段需要数组,则创建它;如果不需要,则不创建。还可以在程序运行时选择数组的长度。这被称为动态联编(dynamic binding),意味着数组是在程序运行时创建的。这种数组叫作动态数组(dynamic array)。
  3. 使用静态联编时,必须在编写程序时指定数组的长度;使用动态联编时,程序将在运行时确定数组的长度。
int *psome = new int[10];	//用new创建一个包含10个int元素的动态数组,
					       //创建指针psome,它指向包含10个int值的内存块中的第1个元素
psome[0] = 0;		//arraynew.cpp将指针psome当作数组名来使用,psome[0]为第1个元素,依次类推。
psome[1] = 1;
psome[2] = 2;
cout << "psome[0] = " << psome[0] <<endl;	//输出0
psome = psome + 1;	//将p3加1导致它指向第2个元素而不是第1个。
cout << "psome[0] = " << psome[0] <<endl;	//输出1
psome = psome - 1;	//将它减1后,指针将指向原来的值,这样程序便可以给delete[ ]提供正确的地址。

delete [] psome;	//使用delete释放new创建的动态数组内存

总之, 使用new和delete时, 应遵守以下规则:

  1. 程序中如果用new分配内存空间后,一定要用delete释放。
  2. 不要使用delete来释放不是new分配的内存。
  3. 不要使用delete释放同一个内存块两次。
  4. 如果使用new [ ]为数组分配内存, 则应使用delete [ ]来释放。
  5. 如果使用new 为一个实体分配内存, 则应使用delete(没有方括号) 来释放。
  6. 对空指针应用delete是安全的。
//指针和自由存储空间
//一定要在对指针应用解除引用运算符(*)之前,将指针初始化为一个确定的、适当的地址。

#include <iostream>

using namespace std;

int main(void)
{
	int* p1, p2;	//创建一个指针p1和一个int变量p2。对每个指针变量名,都需要使用一个*。*可以紧跟在int后面。
	int a = 10;
	int *p_a;	    //指针声明1:p_a的类型是指向int的指针,或int*。p_a是指针(地址),而*p_a是int。
	p_a = &a;	    //将变量a的地址赋给指针p_a
	cout << "a value a = " << a << ", and a address &a = " << &a << endl;	//输出a的值和地址
	cout << "a value *p_a = " << *p_a << ", and a address p_a = " << p_a << endl;//用指针输出a的值和地址
	
	*p_a = *p_a + 1;	//通过指针去改变该地址储存的变量值
	cout << "\n" << R"+*("*p_a = *p_a + 1". Now a = )+*" << a << endl << endl;	//R"+*( )+*" 原始字符串输出
	
	double b;
	double *p_b = &b;			    //声明2:并初始化指针为一个适当的地址, *p_b等价于b,p_b等价于&b
	
	int *p_c = (int *) 0xB8000000;	//声明3:并初始化指针,将一个确定的地址赋给指针
	
	int *pn = new int;			    //声明4:使用new分配内存:pn是被声明为指向int的指针。
	double *pd = new double;
	*pd = 1010.10;
	cout << "int *pn : value = " << *pn << ", \tand location = " << &pn << endl;	//指针是一个地址,但是指针也需要一个存放它的地址,即地址的地址
	cout << "double *pd : value = " << *pd << ", and location = " << &pd << endl << endl;
	cout << "size of &pn = " << sizeof(&pn) << endl;	//8,指针的地址占8个字节,64位系统地址有8字节,8*8=64
	cout << "size of pn = " << sizeof(pn) << endl;	    //8,指针本身是一个地址也占8个字节
	cout << "size of *pn = " << sizeof(*pn) << endl;	//4,指针指向的int类型4字节
	cout << "size of pd = " << sizeof(pd) << endl;
	cout << "size of *pd = " << sizeof(*pd) << endl << endl;    //8,指针指向的double类型8字节
	delete pn;	//delete只能释放new分配的内存:释放pn指向的内存,但不会删除指针pn本身。
    delete pd;
	
	int *psome = new int[10];	//用new创建一个包含10个int元素的动态数组,
					            //创建指针psome,它指向包含10个int值的内存块中的第1个元素
	psome[0] = 0;		        //arraynew.cpp将指针psome当作数组名来使用,psome[0]为第1个元素,依次类推。
	psome[1] = 1;
	psome[2] = 2;
	cout << "psome[0] = " << psome[0] <<endl;	//输出0
	psome = psome + 2;	//将p3加1导致它指向第2个元素而不是第1个。
	cout << "psome[2] = 2; psome = psome + 2; psome[0] = " << psome[0] <<endl;	//输出第三个元素2
	psome = psome - 2;	//将它减2后,指针将指向原来的值,这样程序便可以给delete[ ]提供正确的地址。
	delete [] psome;	//使用delete释放new创建的动态数组内存

	return 0;
}

【指针、 数组和指针算术】

指针算术

将整数变量加1后, 其值将增加1; 但将指针变量加1后, 增加的量等于它指向的类型的字节数。 将指向double的指针加1后, 如果系统对double使用8个字节存储, 则数值将增加8; 将指向short的指针加1后, 如果系统对short使用2个字节存储, 则指针值将增加2。

//将指针变量加1后,其增加的值等于指向的类型占用的字节数。
//使用new来创建数组以及使用指针来访问不同的元素很简单。只要把指针当作数组名对待即可。

#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 数组名为数组第1个元素的地址
	short * ps = &stacks[0]; // or use address operator
	
	// with array element
	cout << "pw = " << pw << ", *pw = " << *pw << endl;
	pw = pw + 1;	//pw指向的是double类型,而double占用8个字节,数字地址值将增加8,这使得pw的值为第2个元素的地址。
	cout << "add 1 to the pw pointer:\n";
	cout << "pw = " << pw << ", *pw = " << *pw << "\n\n";	//*pw现在的值是20000—第2个元素的值
	
	cout << "ps = " << ps << ", *ps = " << *ps << endl;
	ps = ps + 1;	//ps指向的是short类型,而short占用2个字节,因此将指针加1时,其值将增加2。
	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) << "\n\n";	//*(stacks + 1)和stacks[1]是等价的
	
	cout << sizeof(wages) << " = size of wages array\n";	//数组应用sizeof运算符得到的是数组的长度,
	cout << sizeof(pw) << " = size of pw pointer\n\n";	    //指针应用sizeof得到的是指针的长度,即使指针指向的是一个数组。
	
	short tell[20];
	short (*pas)[20] = &tell;	//数组指针
	short *pbs[20];		        //指针数组,包含20个元素
	cout << "array tell[10] tell = " << tell << endl;		//数组名被解释为其第一个元素的地址,
	cout << "array tell[10] &tell = " << &tell << "\n\n";	//而对数组名应用地址运算符时,得到的是整个数组的地址.
	/*从数字上说,这两个地址相同;
	但从概念上说,&tell[0](即tell)是一个2字节内存块的地址,而&tell是一个20字节内存块的地址。
	因此,表达式tell + 1将地址值加2,而表达式&tell+ 2将地址加20。*/
	
	int test[20];
	int *p1 = test;
	int *p2 = &test[10];	
                            //仅当两个指针指向同一个数组(也可以指向超出结尾的一个位置)时,
	int diff = p2 - p1;	    //将一个指针减去另一个指针,获得两个指针的差。
	cout << "p2 - p1 = " << diff << "\n\n";	//10	
	
	return 0;
}

指针和字符串

  1. 在cout和多数C++表达式中,char数组名、char指针以及用引号括起的字符串常量都被解释为字符串第一个字符的地址。
  2. 在将字符串读入程序时,应使用已分配的内存地址。该地址可以是数组名,也可以是使用new初始化过的指针。
  3. 如果给cout提供一个指针,它将打印地址。但如果指针的类型为char *,则cout将显示指向的字符串。 如果要显示的是字符串的地址,则必须将这种指针强制转换为另一种指针类型,如(int *)  
  4. 将animal数组中的字符串复制到新分配的空间中,animal赋给ps是不可行的,因为这样只能修改存储在ps中的地址,从而失去程序访问新分配内存的唯一途径。需要使用库函数strcpy( )  
//指针和字符串
//在cout和多数C++表达式中,char数组名、char指针以及用引号括起的字符串常量都被解释为字符串第一个字符的地址。
//在将字符串读入程序时,应使用已分配的内存地址。该地址可以是数组名,也可以是使用new初始化过的指针。
/*	如果给cout提供一个指针,它将打印地址。
	但如果指针的类型为char *,则cout将显示指向的字符串。
	如果要显示的是字符串的地址,则必须将这种指针强制转换为另一种指针类型,如(int *)  */
/* 	将animal数组中的字符串复制到新分配的空间中,animal赋给ps是不可行的,
	因为这样只能修改存储在ps中的地址,从而失去程序访问新分配内存的唯一途径。需要使用库函数strcpy( )  */

#include <iostream>
#include <cstring>	// declare strlen(), strcpy()

int main(void)
{
	using namespace std;

	char animal[20] = "bear";	// animal holds bear,char数组
	const char * bird = "wren";	// 指向char的const指针变量,char指针初始化为指向一个字符串,将“wren”的地址赋给了bird指针
	char * ps;			        // uninitialized,指向char的指针变量,未初始化

	cout << animal << " and ";	// display bear,数组名是第一个元素的地址
	cout << bird << "\n";		// display wren,使用const意味着可以用bird来访问字符串,但不能修改它。
	// cout << ps << "\n";		// may display garbage, may cause a crash,未初始化的指针可能显示一个空行、一堆乱码,或者程序将崩溃。
	
	cout << "Enter a kind of animal: ";
	cin >> animal;			    // ok if input < 20 chars,请不要使用字符串常量或未被初始化的指针来接收输入。
	// cin >> ps; 	Too horrible a blunder to try; ps doesn't
	//		point to allocated space
	
	ps = animal;			    // 将animal赋给ps并不会复制字符串,而只是复制地址。这样,这两个指针将指向相同的内存单元和字符串
	cout << ps << "!\n";		// ok, same as using animal
	cout << "Before using strcpy():\n";
	cout << animal << " at " << (int *) animal << endl;	//使用(int *)显示的是字符串的地址
	cout << ps << " at " << (int *) ps << endl;
	
	ps = new char[strlen(animal) + 1];	// get new storage,
	//new 能够根据字符串的长度来指定所需的空间,函数strlen( )返回字符串的长度,不包括空字符。
	strcpy(ps, animal);	// copy string to new storage,函数strcpy( )将字符串从一个位置复制到另一个位置。
				        // strcpy( )函数接受2个参数。第一个是目标地址,第二个是要复制的字符串的地址。
	
	cout << "After using strcpy():\n";
	cout << animal << " at " << (int *) animal << endl;
	cout << ps << " at " << (int *) ps << endl << endl;
	
	char food[10] = "apple";
	strncpy(food, "appleappleapple", 9);	//应使用strcpy( )或strncpy( ),而不是赋值运算符来将字符串赋给数组。
	food[10] = '\0';
	cout << "strncpy(), food[10] = " << food << endl;	//display appleappl
	
	delete [] ps;
	
	return 0;
}

使用new创建动态结构

  1. “动态”意味着内存是在运行时,而不是编译时分配的。
  2. 如果结构标识符是结构名,则使用句点运算符;如果标识符是指向结构的指针,则使用箭头运算符。
  3. 访问动态结构成员的方法1:
    创建动态结构时,不能将成员运算符句点用于结构名,因为这种结构没有名称,只是知道它的地址。C++专门为这种情况提供了一个运算符:箭头成员运算符(−>)。
  4. 访问动态结构成员的方法2:
    如果ps是指向结构的指针,则*ps就是被指向的值—结构本身。由于*ps是一个结构,因此(*ps).price是该结构的price成员。
//使用new创建动态结构
//“动态”意味着内存是在运行时,而不是编译时分配的。
//如果结构标识符是结构名,则使用句点运算符;如果标识符是指向结构的指针,则使用箭头运算符。
//访问动态结构成员的方法1:
/*	创建动态结构时,不能将成员运算符句点用于结构名,
	因为这种结构没有名称,只是知道它的地址。
	C++专门为这种情况提供了一个运算符:箭头成员运算符(−>)。*/
//访问动态结构成员的方法2:
//	如果ps是指向结构的指针,则*ps就是被指向的值—结构本身。由于*ps是一个结构,因此(*ps).price是该结构的price成员。

#include <iostream>

struct inflatable	// structure definition
{
	char name[20];
	float volume;
	double price;
};

int main(void)
{
	using namespace std;
	
	inflatable * ps = new inflatable; 	// allot memory for structure
	
	cout << "Enter name of inflatable item: ";
	cin.get(ps->name, 20);			    // method 1 for member access

	cout << "Enter volume in cubic feet: ";
	cin >> (*ps).volume;			    // method 2 for member access

	cout << "Enter price: $";
	cin >> ps->price;
	
	cout << "Name: " << (*ps).name << endl;			        // method 2
	cout << "Volume: " << ps->volume << " cubic feet\n"; 	// method 1
	cout << "Price: $" << ps->price << endl;			    // method 1
	
	delete ps;	// free memory used by structure

	return 0;
}

new 和 delete 使用示例

//new 和 delete 使用示例
/*	函数getname( ),该函数返回一个指向输入字符串的指针。
	该函数将输入读入到一个大型的临时数组中,然后使用new [ ]创建一个刚好能够存储该输入字符串的内存块,并返回一个指向该内存块的指针。*/

#include <iostream>
#include <cstring>

using namespace std;

char * getname(void);	//函数声明

int main(void)
{
	char * name;
	
	name = getname();				//返回值(地址)被赋给指针name
	cout << name << " at " << (int *) name << endl;	//显示在内存的地址
	delete [] name;					//释放内存,C++不保证新释放的内存就是下一次使用new时选择的内存
	
	name = getname();
	cout << name << " at " << (int *) name << endl;
	delete [] name;
	
	return 0;
}

char * getname()	//指针函数,该函数返回一个指向输入字符串的指针
{
	char temp[80];
	cout << "Enter last name: ";
	cin  >> temp;					        //将输入读入到一个大型的临时数组中
	char * pn = new char[strlen(temp) + 1];	//使用new [ ]创建一个刚好能够存储该输入字符串的内存块
	strcpy(pn, temp);
	
	return pn;					            //返回一个指向该内存块的指针,字符串副本的地址
}

【自动存储、静态存储、动态存储】 

自动存储:变量只有特定函数被执行时存在

在函数内部定义的常规变量使用自动存储空间,被称为自动变量(automatic variable),这意味着它们在所属的函数被调用时自动产生,在该函数结束时消亡。
自动变量是一个局部变量,其作用域为包含它的代码块。代码块是被包含在花括号中的一段代码。
自动变量通常存储在栈中。这意味着执行代码块时,其中的变量将依次加入到栈中,而在离开代码块时,将按相反的顺序释放这些变量,这被称为后进先出(LIFO)。因此,在程序执行过程中,栈将不断地增大和缩小。

静态存储:变量存在于程序的整个生命周期

静态存储是整个程序执行期间都存在的存储方式。使变量成为静态的方式有两种:一种是在函数外面定义它;另一种是在声明变量时使用关键字static。也可对数组和结构化用static初始化。

动态存储:数据的生命周期不完全受程序或函数的生存时间控制

与使用常规变量相比,使用new和delete让程序员对程序如何使用内存有更大的控制权。然而,内存管理也更复杂了。在栈中,自动添加和删除机制使得占用的内存总是连续的,但new和delete的相互影响可能导致占用的自由存储区不连续,这使得跟踪新分配内存的位置更困难。

栈、堆和内存泄漏

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

【组合类型】

//组合类型

#include <iostream>

struct AA	//结构声明
{
	int year;
	int day;
};

int main(void)
{
	using namespace std;
	
	AA s01, s02, s03;	//1、创建结构变量
	s01.year = 1998;	//1、使用成员运算符访问其成员
	cout << "s01.year = " << s01.year << endl;
	
	AA * pa = &s02;	    //2、创建指向这种结构的指针,将该指针设置为有效地址
	pa->year = 1999;	//2、指针使用间接成员运算符来访问成员
	cout << "pa->year = " << pa->year << endl;
	
	AA trio[3];		        //3、创建结构数组,其中trio是一个数组,trio[0]是一个结构,而trio[0].year是该结构的一个成员。
	trio[0].year = 2000;	//3、数组名是一个指针,因此也可使用间接成员运算符
	cout << "trio->year = " << trio->year << endl;
	
	const AA * arp[3] = {&s01, &s02, &s03};		        //4、创建指针数组,arp是一个指针数组,arp[1]就是一个指针
	cout << "arp[1]->year = " << arp[1]->year << endl;	//4、arp[1]就是一个指针,使用间接成员运算符来访问成员

	const AA ** ppa = arp;	//5、创建指向上述数组的指针,ppa是一个指向结构指针的指针,*ppa是一个结构指针
	//arp是一个数组的名称,因此它是第一个元素的地址。但其第一个元素为指针,因此ppa是一个指针,指向一个指向const AA的指针。
	auto ppb = arp;	//编译器知道arp的类型,能够正确地推断出ppb的类型.
	cout << "(*ppa)->year = " << (*ppa)->year << endl;	        //5、*ppa是一个结构指针,使用间接成员运算符来访问成员,ppa指向arp的第一个元素,因此*ppa为第一个元素,即&s01
	cout << "(*(ppb+1))->year = " << (*(ppb+1))->year << endl;	//ppb+1指向下一个元素arp[1],即&s02。
	
	return 0;
}

【数组的替代品:模板类vector和array】

模板类vector

  1. 类似于string类,也是一种动态数组。可以在运行阶段设置vector对象的长度,可在末尾附加新数据,还可在中间插入新数据。
  2. 首先,要使用vector对象,必须包含头文件vector。
  3. 其次,vector包含在名称空间std中,因此您可使用using编译指令、using声明或std::vector。
  4. 第三,模板使用不同的语法来指出它存储的数据类型。
  5. 第四,vector类使用不同的语法来指定元素数。

C++11新增了模板类array

  1. 位于名称空间std中。
  2. 与数组一样,array对象的长度也是固定的,也使用栈(静态内存分配),而不是自由存储区,因此其效率与数组相同,但更方便,更安全。
  3. 要创建array对象,需要包含头文件array。
  4. 创建一个array对象,包含n个某类型的元素,n不能是变量。

区别与联系

  1. 首先,无论是数组、vector对象还是array对象,都可使用标准数组表示法来访问各个元素。
  2. 其次,从地址可知,array对象和数组存储在相同的内存区域(即栈)中,而vector对象存储在另一个区域(自由存储区或堆)中。
  3. 第三,注意到可以将一个array对象赋给另一个array对象;而对于数组,必须逐元素复制数据。

中括号表示法和成员函数at()的差别:

使用at()时,将在运行期间捕获非法索引,而程序默认将中断。这种额外检查的代价是运行时间更长,这就是C++让允许您使用任何一种表示法的原因所在。另外,这些类还让您能够降低意外超界错误的概率。例如,它们包含成员函数begin()和end(),让您能够确定边界,以免无意间超界,这将在第16章讨论.

//数组的替代品:模板类vector和array

//首先,无论是数组、vector对象还是array对象,都可使用标准数组表示法来访问各个元素。
//其次,从地址可知,array对象和数组存储在相同的内存区域(即栈)中,而vector对象存储在另一个区域(自由存储区或堆)中。
//第三,注意到可以将一个array对象赋给另一个array对象;而对于数组,必须逐元素复制数据。

#include <iostream>
#include <vector>
#include <array>

using namespace std;

int main(void)
{
	vector<int> vi;	//vi是一个vector<int>对象,vector对象在您插入或添加值时自动调整长度,因此可以将vi的初始长度设置为零。
	
	int n = 5;
	vector<double> vd(n);	//vd是一个vector<double>对象,参数n可以是整型常量,也可以是整型变量。
	
	const int n_elem = 5;
	array<int, n_elem> ai;	//创建一个名为ai的array对象,包含n_elem个类型为int的元素,n_elem不能是变量。
	array<double, 4> ad = {1.2, 2.1, 3.14, 4.3};	//在C++11中,可将列表初始化用于vector和array对象.
	
	double a1[4] = {1.2, 2.4, 3.6, 4.8};
	
	vector<double> a2(4);
	a2[0] = 1.0/3.0;
	a2[1] = 1.0/5.0;
	a2[2] = 1.0/7.0;
	a2[3] = 1.0/9.0;
	
	array<double, 4> a3 = {1.2, 2.1, 3.14, 4.3};
	array<double, 4> a4;
	a4 = a3;		//可以将一个array对象赋给另一个array对象;而对于数组,必须逐元素复制数据。
	
	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 << endl;
	
	cout << "a4[-2] = 20.2" << endl;
	a4[-2] = 20.2;		//相当于*(a4-2)=20.2,意思是找到a1指向的地方,向前移两个double元素,并将20.2存储到目的地。
				        //越界了,也就是说,将信息存储到数组的外面。在这个示例中,这个位置位于array对象a3中。
	cout << "a3[2]: " << a3[2] << " at " << &a3[2] << endl;
	cout << "a4[-2]: " << a4[-2] << " at " << &a4[-2] << endl << endl;
	
	cout << "a2.at(2) = 3.1415926" << endl;
	a2.at(2) = 3.1415926;	//使用vector成员函数at进行赋值,防止越界
	cout << "a2[2]: " << a2[2] << " at " << &a2[2] << endl;

	return 0;
}

【复习题】

//4.12复习题

#include <iostream>	//标准输入输出的头文件
#include <array>	//模板类array的头文件
#include <vector>	//模板类vector的头文件
#include <string>	//string类的头文件

using namespace std;

int main(void)
{
	char actor[30];		    //1.a
	short betsie[100];		//1.b
	float chuck[13];		//1.c
	long double dipsea[64];	//1.d
	
	array<char, 30> actorr;	//2
	
	int a[5] = {1,3,5,7,9};	//3
	
	int even = a[0] + a[4];	//4
	
	float ideas[5] = {0,1,2,3,4};
	cout << "ideas[1] = " << ideas[2] << endl;	//5
	
	char str6[] = "cheeseburger";			    //6
	
	string str7 = "Waldorf Salad";		        //7
	
	struct fish
	{
		char name[20];
		int weight;
		float length;
	};						//8

	fish whale = {"whale", 200, 200.1};		//9
	
	enum Response {No, Yes, Maybe};		    //10
	
	double ted = 3.1415926;
	double *pa = &ted;
	cout << "*pa = " << *pa << endl;		//11
	
	float treacle[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
	float *pb = treacle;
	cout << "*pb = " << *pb << endl;
	cout << "*(pb+9) = " << *(pb+9) << endl;	//12
	
	int n;
	cout << "Enter an integer : ";
	cin  >> n;
	int *pc = new int[n];
	vector<int> va(n);				//13
	delete [] pc;
	
	cout << (int *) "Home of the jolly bytes";	//14
	cout << endl;
	
	fish * pd = new fish;
	cout << "name: ";
	cin.get(pd->name, 20);
	cout << "weight: ";
	cin  >> (*pd).weight;
	cout << "length: ";
	cin  >> (*pd).length;
	cout << (*pd).name << endl;
	cout << pd->weight << endl;
	cout << pd->length << endl;	    //15
	delete pd;
	
	const int NUM = 10;
	std::vector<string> a1(NUM);
	std::array<string, NUM> a2;	    //17
	
	return 0;
}

【编程练习】

//4.13编程练习1

#include <iostream>

using namespace std;

int main(void)
{
	char firstname[20];	//名
	cout << "what is your first name? ";
	cin.get(firstname, 20).get();
	
	char lastname[20];	//姓
	cout << "what is your last name? ";
	cin.getline(lastname, 20);
	
	char grade;
	cout << "what letter grade do you deserve? ";
	cin  >> grade;
	
	int age;
	cout << "what is your age? ";
	cin  >> age;
	
	cout << "Name: " << lastname << ", " << firstname << endl;
	cout << "Grade: " << char(grade+1) << endl;
	cout << "Age: " << age << endl;
	
	return 0;
}



//4.13编程练习2
//区别string类和char数组的输入

#include <iostream>
#include <cstring>

using namespace std;

int main(void)
{
	const int ArSize = 20;
	//char name[ArSize];
	//char dessert[ArSize];
	string name;
	string dessert;
	
	//cin.getline(name, ArSize);
	//cin.getline(dessert, ArSize);
	getline(cin, name);
	getline(cin, dessert);
	cout << dessert << endl;
	cout << name << endl;


	return 0;
}


//4.13编程练习5
//结构体的定义、初始化、显示

#include <iostream>

using namespace std;

int main(void)
{
	struct CandyBar
	{
		char name[20];
		float weight;
		unsigned int calorie;
	};
	
	CandyBar snack = {"Mocha Munch", 2.3, 350};
	
	cout << snack.name << endl;
	cout << snack.weight << endl;
	cout << snack.calorie << endl;
	
	return 0;
}


//4.13编程练习5、6、9
//5、结构体的定义、初始化、显示
//6、结构数组
//9、使用new动态分配数组

#include <iostream>
#include <cstring>

using namespace std;

int main(void)
{
	struct CandyBar
	{
		char name[20];
		float weight;
		unsigned int calorie;
	};
	
	CandyBar snack = {"Mocha Munch0", 2.3, 350};
	
	cout << snack.name << " " << snack.weight << " " << snack.calorie << endl;	//5
	
	CandyBar array[3] = 
	{
		{"Mocha Munch1", 2.4, 351},
		{"Mocha Munch2", 2.5, 352},
		{"Mocha Munch3", 2.6, 353}
	};
	
	cout << array[0].name << " " << array[0].weight << " " << array[0].calorie << endl;
	cout << array[1].name << " " << array[1].weight << " " << array[1].calorie << endl;
	cout << array[2].name << " " << array[2].weight << " " << array[2].calorie << endl;	//6
	
	CandyBar *pa = new CandyBar[3];
	
	strcpy(pa[0].name, "Mocha Munch1");
	pa[0].weight = 2.4;
	pa[0].calorie = 351;
	
	strcpy(pa[1].name, "Mocha Munch2");
	pa[1].weight = 2.5;
	pa[1].calorie = 352;
	
	cout << (*pa).name << " " << pa->weight << " " << pa->calorie << endl;
	cout << (pa+1)->name << " " << (pa+1)->weight << " " << (pa+1)->calorie << endl;	//9
	
	delete [] pa;
	
	return 0;
}


//4.13编程练习7、8
//7、结构体的输入输出
//8、new为结构体分配内存,用指针来访问结构体内容

#include <iostream>

using namespace std;

int main(void)
{
	struct Pizza
	{
		char name[20];
		float D;
		float weight;
	};
	
	Pizza AAA;
	
	cin.get(AAA.name, 20).get();
	cin  >> AAA.D;
	cin  >> AAA.weight;
	
	cout << AAA.name << " " << AAA.D << " " << AAA.weight << endl;	//7
	
	Pizza *pa = new Pizza;	//new分配一个内存,然后返回一个地址给指针,这个指针是结构体类型的指针
	
	cin  >> (*pa).D;	//(*pa)是一个结构
	cin.getline(pa->name, 20);
	cin  >> pa->weight;
	
	cout << pa->name << " " << (*pa).D << " " << pa->weight << endl;	//8
	
	delete pa;

	return 0;
}


//4.13编程练习10
//模板类array的使用

#include <iostream>
#include <array>

using namespace std;

int main(void)
{
	const int NUM =3;
	array<float, NUM> run;
	
	cin  >> run[0];
	cin  >> run[1];
	cin  >> run[2];
	
	cout << NUM << " times, Average=" << (run[0]+run[1]+run[2])/3 << endl;

	return 0;
}

【总结】

  1. 数组、 结构和指针是C++的3种复合类型。 数组可以在一个数据对象中存储多个同种类型的值。 通过使用索引或下标, 可以访问数组中各个元素。
  2. 结构可以将多个不同类型的值存储在同一个数据对象中, 可以使用成员关系运算符(.)来访问其中的成员。 使用结构的第一步是创建结构模板,它定义结构存储了哪些成员。 模板的名称将成为新类型的标识符, 然后就可以声明这种类型的结构变量。
  3. 共用体可以存储一个值,但是这个值可以是不同的类型,成员名指出了使用的模式。
  4. 指针是被设计用来存储地址的变量。 我们说,指针指向它存储的地址。 指针声明指出了指针指向的对象的类型。 对指针应用解除引用运算符(*),将得到指针指向的位置中的值。
  5. 字符串是以空字符为结尾的一系列字符。字符串可用引号括起的字符串常量表示,其中隐式包含了结尾的空字符。可以将字符串存储在char数组中,可以用被初始化为指向字符串的char指针表示字符串。函数strlen( )返回字符串的长度,其中不包括空字符。函数strcpy( )将字符串从一个位置复制到另一个位置。在使用这些函数时,应当包含头文件cstring或string.h。
  6. 头文件cstring支持的C++ string类提供了另一种对用户更友好的字符串处理方法。具体地说,string对象将根据要存储的字符串自动调整其大小,用户可以使用赋值运算符来复制字符串。
  7. new运算符允许在程序运行时为数据对象请求内存。该运算符返回获得内存的地址,可以将这个地址赋给一个指针,程序将只能使用该指针来访问这块内存。如果数据对象是简单变量,则可以使用解除引用运算符(*)来获得其值;如果数据对象是数组,则可以像使用数组名那样使用指针来访问元素;如果数据对象是结构,则可以用指针解除引用运算符(->) 来访问其成员。
  8. 指针和数组紧密相关。如果ar是数组名,则表达式ar[i]被解释为*(ar + i) ,其中数组名被解释为数组第一个元素的地址。这样,数组名的作用和指针相同。反过来,可以使用数组表示法,通过指针名来访问new分配的数组中的元素。
  9. 运算符new和delete允许显式控制何时给数据对象分配内存,何时将内存归还给内存池。自动变量是在函数中声明的变量,而静态变量是在函数外部或者使用关键字static声明的变量,这两种变量都不太灵活。自动变量在程序执行到其所属的代码块(通常是函数定义)时产生,在离开该代码块时终止。静态变量在整个程序周期内都存在。
  10. C++98新增的标准模板库(STL)提供了模板类vector,它是动态数组的替代品。C++11提供了模板类array,它是定长数组的替代品。

这篇文章花了很多时间和精力,每一行代码都是本姑娘亲手敲的,哈哈哈哈哈哈哈哈哈,乐此不疲! 

猜你喜欢

转载自blog.csdn.net/qq_39751352/article/details/126045384