C++入门学习笔记之复合类型

4 复合类型

4.1 数组

数组是一种数据格式,可以存储多个同类型的值

4.1.1 数组声明

创建数组可使用声明语句,声明语句需要指出下面三点:

  1. 存储在每个元素中的值的类型
  2. 数组名
  3. 数组中的元素个数
    类似于声明简单变量,只需要通过添加中括号就能完成数组的声明。
short month;//声明一个short类型的简单变量month
short months[12];//声明一个可以存储12个short类型元素的数组months

通用格式为:

typeName arrayName[arraySize];

需要注意的是: arraySize指定元素的数目,它必须是整型常数(如12)或者const值,也可以是常量表达式(如8*sizeof(int)),也就是说其中的值在编译时都应该是已知的,不可以是变量。

4.1.2 数组索引

数组的很多用途是基于我们可以通过索引(下标)单独访问数组中的某个元素。
C++数组从0开始标号,使用带索引的方括号表示法来指定数组元素。例如4.1.1中定义的short数组months,months[0]表示数组的第一个元素,months[11]表示数组的最后一个元素。最后一个元素的索引比数组长度小1。
有效下标的重要性 :编译器不会检查使用的下标是否有效(是否超出数组长度),但是在程序运行后如果下标使用错误可能引发问题,导致程序异常。所以我们必须确保程序只使用有效的下标值

4.1.3 数组初始化

数组初始化遵循以下规则:

  • 只有在定义数组时才可以使用初始化,此后就不能使用了,也不能将一个数组赋值给另一个数组(初始化之后可以使用下标分别对数组中的元素赋值);
  • 初始化时,提供的值数目可以少于数组的元素数目(这样操作的话,将提供的n个值赋给数组中的前n个元素,后面的元素赋值为0);
  • 如果初始话数组时方括号内[ ]的值为空,那么C++编译器将计算元素个数(尽量不要这样做,在创建数组时尽量要给定数组大小);
short things[] = {0, 1, 2, 3};//things数组的元素个数为4

可以通过下面的方法计算数组的大小:

int m_narraysize = sizeof(things)/sizeof(things[0]);//m_narraysize就表示things数组的元素个数
  • 我们在使用列表初始化时,可以等号;
  • 如果我们不在大括号内包含任何东西,这将把所有元素都初始化为0;
  • 列表初始化禁止缩窄转换

4.2 字符串

字符串是存储在内存的连续字节中的一系列字符。C++处理字符串的方式有两种。第一种来自C语言,常被称为C风格字符串,另一种是基于string类库的方法。
C风格字符串具有一种特殊的性质:以空字符(\0)结尾,用来标记字符串的结尾。
例如:

char dog[8] = {'b', 'e', 'a', 'u', 'x', ' ', 'I', 'I'};//不是字符串
char cat[8] = {'f', 'a', 't', 'e', 's', 's', 'a', '\0'};//是字符串

我们分别打印上面定义的dog和cat:
在这里插入图片描述
我们发现:

  • cat正常显示7个字符并且‘\0’不打印,
  • dog在显示完8个字符后接着显示了其他乱码,这是因为cout在打印时只有遇到‘\0’才会停止打印,打印完dog的8个字符后会接着打印内存中dog数组(这里是作为数组存在,不是字符串)后面的内容,直至遇到‘\0’才停止输出。

上述的通过数组来初始化字符串的操作十分不方便,所以我们还可以使用双引号(“”)来初始化一个字符串:

char bird[11] = "Mr. Cheeps";//字符串中Mr. Cheeps一共有10个字符,但是由于通过双引号来初始化字符串时默认在最后隐式添加‘\0’,所以bird的大小应该是11;
char fish[] = "eat nice!";

在确定存储字符串所需的最短数组时,别忘了将结尾的’\0’也计算在内!

C++对字符串的长度没有限制,我们应该确保数组足够大,能够存储字符串中的所有字符—包括空字符
字符串常量(使用双引号)和字符常量(使用单引号):
字符常量(如‘S’)是字符串编码的简写表示,在ASCII系统上,‘S’只是83的另一种写法。

	char yourdad = 83;
	cout << yourdad << endl;

输出结果为:
在这里插入图片描述
但是“S”不是字符常量(实际上是字符S和\0组成的字符串)。
“S”实际上是表示字符串所在的内存地址

	int myaddress = int("S");
	cout << myaddress << endl;

这里输出结果是
在这里插入图片描述
由于地址在C++中是一种独立的类型,因此C++编译器不允许这种不合理的做法。

4.2.1 拼接字符串常量

C++拼接字符串常量,即两个用引号括起来的字符串合并为一个。事实上,任何两个由 空白(空格、制表符和换行符) 分隔的字符串常量都将自动拼接成一个。
拼接时不会考虑字符串的最后一个‘\0’,第一个字符串的\0字符将被第二个字符串的第一个字符串取代。

4.2.2 在数组中使用字符串

在数组中使用字符串的方式有两种:

  1. 将数组初始化为一个字符串常量
  2. 通过键盘将字符串输入到数组中
	char name1[6] = "name1";
	char name2[6];
	cout << name1 << endl;
	cin >> name2;
	cout << name2;

上述代码在cin语句处会让我们输入字符串并将我们输入的字符串赋值给name2;
如果我们输入 jacky,那么name2输出就是jacky:
在这里插入图片描述
但是如果我们输入的字符串包含空白字符(如空格),比如输入 ja ck ,那么name2的输出就只是ja在这里插入图片描述
这说明对于cin输入来说,如果输入的字符串有空白字符,那么会默认作为字符串的结尾标志。
我们另外创建一个用来接收字符串的数组name3

	char name2[6];
	char name3[6];
	cin >> name2;
	cin >> name3;
	cout << "name2 is " << name2 << endl;
	cout << "name3 is " << name3 << endl;

这时我们输入 ja cky ,那么我们会发现:
在这里插入图片描述
我们还没有输入第二个字符串,但是程序已经进行了输入,这是因为:
程序遇到ja后面的空白字符,作为了name2的结尾,并将cky作为name3的内容存储起来,所以我们会发现,ja和cky存储在了两个数组中(有时,这与我们的想法背道而驰)。

4.2.3 每次读取一行字符串输入

为了解决4.2.2小节出现的输入空白字符的问题,我们可以使用另外的方法来获取键盘输入:

1. getline();

使用方式:cin.getline();
函数参数:
1. 第一个参数是用于存储输入行的数组名称
2. 第二个参数是要读取的字符数(实际接收的字符数要减1
3. 第三个参数用来指定结束字符(暂不介绍)

	char name2[10];
	cin.getline(name2, 10);
	cout << "name2 is " << name2 << endl;

输入Mark Joe,这时我们会得到理想的结果:
在这里插入图片描述
我们需要注意的是:

  • 如果键盘输入的字符数超出getline()指定接收的字符数,那么它会在读取到指定数目的字符后停止读取
  • getline()读取时,会将换行符替换为‘\0’
2. get();

get()函数有几种变体:

  • 第一种是和getline()类似,接收的参数和解释参数的方式也相同,但是get()并不读取并丢弃换行符,而是将其留在输入队列中。
    例如我们连续使用两次get():
	char name2[10];
	char name3[10];
	cin.get(name2, 10);
	cin.get(name3, 10);
	cout << "name2 is " << name2 << endl;
	cout << "name3 is " << name3 << endl;

在这里插入图片描述
我们输入Mark Joe后程序就直接输出了,并没有等待我们输入name3的内容,name3的内容显示为空白。
这是因为,我们在输入第一个字符串的回车符后,name2接收已经结束,但是回车符仍然在输入队列中并被直接送给第二个get(),导致第二次调用时看到的第一个字符就是换行符,因此get()认为已经到达了行尾。

  • 第二种是使用不带任何参数的cin.get()调用可读取下一个字符(即使是换行符),因此我们可以这种方式来处理换行符:
	char name2[10];
	char name3[10];
	cin.get(name2, 10);
	cin.get();//处理换行符,也就是把换行符从输入队列中取出
	cin.get(name3, 10);
	cout << "name2 is " << name2 << endl;
	cout << "name3 is " << name3 << endl;

在这里插入图片描述
这时我们便可以得到想要的结果。

  • 第三种使用get()的方式是将两个类成员函数拼接起来(合并):
	cin.get(name2, 10).get();
	cin.get(name3, 10);
	char name2[10];
	char name3[10];
	cin.get(name2, 10).get();//第二个get()处理换行符,也就是把换行符从输入队列中取出
	cin.get(name3, 10);
	cout << "name2 is " << name2 << endl;
	cout << "name3 is " << name3 << endl;

使用上面的代码可以实现和第二种代码相同的效果。
或者我们也可以使用getline():

	cin.getline(name2, 10).getline(name3, 10);
	char name2[10];
	char name3[10];
	cin.getline(name2, 10).getline(name3, 10);
	cout << "name2 is " << name2 << endl;
	cout << "name3 is " << name3 << endl;

这样也可以得到相同的结果:
在这里插入图片描述

4.2.4 get()和getline()的选择

为什么使用get()而不是getline()?
当我们将一行字符串读入数组中时,我们需要直到停止读取的原因是由于读取了整行还是数组已经被填满,如果使用get()我们就可以通过检查下一个字符来判断。
如果下一个字符是换行符,那么说明是读取了整行,否则说明是因为数组已经填满了。

	char name2[10];
	cin.get(name2, 10);
	if (cin.get() == '\n')
	{
		cout << "正常输入" << endl;
	}
	else
	{
		cout << "数组已经填满" << endl;
	}
	cout << "name2 is " << name2 << endl;

通过上面代码可以得到对应的结果:
在这里插入图片描述
或者:
在这里插入图片描述

4.2.5 混合输入字符串和数字

可以使用cin将输入的数字值赋给一个数字量。

	int year;
	char name[5];
	cout << "现在是哪一年?" << endl;
	cin >> year;
	cout << "你的名字是什么?" << endl;
	cin.get(name, 5);
	cout << "Now year is " << year << endl;
	cout << "Your name is " << name << endl;

运行结果是:
在这里插入图片描述
可以发现,我们输入年份后还没有输入名字,程序已经进行了输出,这时因为输入数字后的回车符被当作name的第一个字符读入,所以直接输出。
这时我们需要添加get()来丢弃回车符。

(cin >> year).get();

或者

cin >> year;
cin.get();

修改之后我们的程序就可以符合我们的想法:
在这里插入图片描述

4.3 string类

C++ string类是一种较新的处理字符串的方式。要使用string类必须在头文件中包括string,而且string类位于命名空间std中。
可以把string类的字符串看作是一个类似于int,char类型的变量。
对于string类字符串的初始化和char数组类型的初始化类似:

#include <iostream>
#include <string>

int main()
{
	using namespace std;
	string m_sstring1;
	string m_sstring2 = "This is a string";
	cout << m_sstring2 << endl;
	return 0;
}

另外,我们也可以使用数组索引的方式来获取字符串中的某个字符:
加入代码:

	cout << "第九个字符是:";
	cout << m_sstring2[8] << endl;

可以打印出字符‘a’:
在这里插入图片描述
同样我们也可以使用这种方式来修改字符串中的某个字符:
添加下面语句:

	m_sstring2[8] = 'B';
	cout << m_sstring2 << endl;

这样我们可以得到运行结果:
在这里插入图片描述

4.3.1 赋值、拼接和附加

使用数组操作:
前面提到过,数组字符串只能初始化一次,且不能将一个数组赋值给另一个数组,所以,如果我们需要进行字符串的赋值、拼接和附加操作,就需要借助一些函数来实现:

  • strcpy()函数
	char m_cchar1[30] = { 0 };
	char m_cchar2[30] = "this is char2";
	cout << "初始char1的值是:" << m_cchar1 << endl;
	strcpy(m_cchar1, m_cchar2);
	cout << "将char2的值赋值给char1后char1的值是:" << m_cchar1 << endl;

strcpy()函数将第二个参数的值赋值给第一个参数。
运行结果是:
在这里插入图片描述

  • strcat()函数
	char m_cchar1[30] = "this is char1";
	char m_cchar2[30] = "this is char2";
	cout << "初始char1的值是:" << m_cchar1 << endl;
	strcat(m_cchar1, m_cchar2);
	cout << "将char2的值附加给char1后char1的值是:" << m_cchar1 << endl;

strcat()函数的作用是将第二个参数的值添加到第一个参数的末尾。
运行结果如下:
在这里插入图片描述
使用string类操作(更加方便):
string类简化了字符串的赋值、合并操作。
我们可以使用等号将一个字符串的值赋值给另一个字符串;也可以使用运算符将两个string对象合并起来;还可以使用运算符+=将字符串附加到string对象的末尾。

	string m_sstring1;
	string m_sstring2 = { "this is my string2" };
	string m_sstring3;

	m_sstring1 = m_sstring2;//将m_sstring2赋值给m_sstring1
	m_sstring1[17] = '1'; //修改m_sstring1的最后一位字符为‘1’
	cout << m_sstring1 << endl; //输出m_sstring1内容

	m_sstring3 = m_sstring1 + m_sstring2; //将字符串1和2合并并赋值给m_sstring3(m_sstring1在前)
	cout << m_sstring3 << endl; //输出m_sstring3内容

	m_sstring2 += m_sstring1; //将字符串1拼接到字符串2后面(m_sstring2在前)
	cout << m_sstring2 << endl; //输出m_sstring2内容

上述代码运行结果为:
在这里插入图片描述

4.3.2 获取字符串长度

C-风格的使用函数strlen(),C++风格的使用类方法size()。

	//获取字符串长度
	char m_cchar[15] = "this is char";
	string m_sstring = "this is string";

	cout << strlen(m_cchar) << endl;//打印m_cchar的长度
	cout << m_sstring.size() << endl; //打印m_sstring的长度

打印结果是:
在这里插入图片描述
这里获取的字符串长度是不包括最后的空字符的。

4.3.3 其他形式的字符串字面值

除了char类型,C++还有类型wchar_t;而C++11新增了类型char16_t和char32_t;上述三种类型的字符串字面值,C++分别使用前缀L、u和U来表示。
C++11新增的另一种类型是原始(raw)字符串,原始字符串没有转义字符,比如\n表示的是\和n,而不是换行符。原始字符使用"(和)“作为定界符,并使用前缀R来比标识原始字符串。
如果想要在原始字符串中使用)”,那么可能会被认为是字符串的结束而导致字符串提前结束,为了解决这个问题,使用R“+(标识原始字符串的开头,用 )+" 来标识原始字符串的结尾。
另外可将R标识和其他字符串类型标识结合使用,如Ru、UR(R可在前也可在后)。

4.4 结构简介

结构是用户自己定义的类型,用来保存多种不同类型的数据,定义了类型后就可以创建这种类型的变量。
结构体的声明:

struct inflatable	//struct是关键字,创建一个名为inflatable的结构类型
{
	char name[20];	//结构成员
	float volume;	//结构成员
	double price;	//结构成员
};

创建了结构之后我们就可以通过inflatable来创建变量hat 。

	inflatable hat = {
		"myhat",
		3.55,
		6.88
	};

声明的hat是一个结构,我们可以使用句点(.)来访问该结构的各个成员:

	cout << hat.name << endl;
	cout << hat.volume << endl;
	cout << hat.price << endl;

上述打印的结果是:
在这里插入图片描述
在程序中,我们一般在main函数之前定义结构体(外部声明),外部声明可以被其之后的任何函数使用,而内部声明只能被该声明所属的函数使用。通常结构体我们使用外部声明,这样所有函数都可以使用这种类型的结构。

4.4.1 其他结构属性和操作

我们可以同时完成定义结构和创建结构变量的工作:
只需要将变量名放在结束括号的后面即可

struct inflatable	//struct是关键字,创建一个名为inflatable的结构类型
{
	char name[20];	//结构成员
	float volume;	//结构成员
	double price;	//结构成员
}mainframe, apple;	//这里创建了名为mainframe和apple的inflatable结构变量

我们甚至可以使用这种方式直接初始化结构变量:

struct inflatable	//struct是关键字,创建一个名为inflatable的结构类型
{
	char name[20];	//结构成员
	float volume;	//结构成员
	double price;	//结构成员
}mainframe = {"mainframe", 6.84, 12.56}, apple = {"apple", 5.65, 10.59};

通常我们将结构定义和结构变量声明分开便于我们程序的阅读和理解。

另外,我们可以声明没有名称的结构类型(方法是省略名称,同时定义一种结构类型和一个这种类型的变量):

struct {
	int thing_num;
	float thing_price;
}biscuits = {5, 10.01};

这样就创建了一个biscuits的结构变量,可以使用成员运算符来访问它的成员(如biscuits.thing_num),但是这种类型没有名称,因此之后无法创建这种类型的变量。

4.4.2 结构数组

结构数组就是元素是结构的数组。数组中的每个元素都是一个结构对象。

inflatable gift[50];

上面的语句就创建了一个有50个结构对象的数组,我们可以使用下标索引和成员运算符访问每个结构的成员值:

cout << gift[0].name;
cout << gift[49].price;

要初始化结构数组就要使用数组初始化的规则(本质上是一个数组),每一个元素的值用大括号括起来:

inflatable guests[2] = 
	{
		{"Bambi", 0.5, 21.99},
		{"Godzilla", 2000, 562.5}
	}

4.4.3 结构中的位字段

C++也允许指定占用特定位数的结构成员:
字段的类型应该是整型或枚举,接下来是冒号,冒号后面是一个数字,这个数字指定了使用的位数;此外可以使用没有名称的字段来提供间距,每个成员都被成为位字段。

//位字段
struct torgle_reg
{
	unsigned int SN : 4; //SN值占4位
	unsigned int : 4; //间距4位
	bool goodIN : 1; //1位bool类型
	bool goodTorgle : 1; //1位bool类型
};

位字段通常使用在低级编程中(类似于寄存器操作),一般来说,我们可以使用整型或者按位运算符来代替这种方式。

4.5 共用体

共同体(union)是一种数据格式。能够存储不同的数据类型,但只能同时存储其中的一种类型。例如下面的声明:

union one4all
{
	int int_val;
	long long_val;
	double double_val;
};

可以使用one4all来存储int、long或者double,条件是在不同的时间进行。共用体的用途之一是:当数据项使用两种或更多格式(但不会同时使用)时可节省空间。例如假设管理一个小商品目录,其中有一些商品的ID为整数,而另一些的ID为字符串,在这种情况下可以使用共同体来操作。
和结构体类似,我们也可以使用匿名共同体,匿名共用体没有名称,其成员将成为位于相同地址处的变量,每次只有一个变量有效。
使用共用体通常用来减少内存。

4.6 枚举

使用enum可以创建符合常亮,可以代替const。
例如:

enum color{red, blue, green, indigo};

上面这句话有两项工作:

  1. 让fruit成为新的类型名称,color被称为枚举(enumeration),就像struct变量被称为结构一样
  2. 将red, blue, green, indigo作为符号常量,对应整数值从0开始递增(red=0,indigo=3),这些常量叫做枚举量(默认从零开始,也可通过显示地指定改变默认值)。

需要注意的是:
枚举量是整型,可被提升为int型,但是int类型不能自动转换为枚举类型。

4.6.1 设置枚举量的值

可以使用赋值运算符来实现:

enum color{red=1, blue=5, green=7, indigo=11};

那么上面的符号常量就被指定为对应的值(指定的值只能为整数)。
我们也可以只显式地定义其中的某些值:

enum color{ red, blue, yellow = 100, green, indigo =50 };

这样red被默认定义为0,blue和yellow被指定为100,green被定义为101(默认比前一位大1),indigo被指定为50。

4.7 指针和自由存储空间

我们可以通过地址运算符(&)来获取某个变量在内存中的位置,如果home是一个变量,那么&home就是它的地址。

	int home = 10;
	double house = 50;
	cout << home << endl;
	cout << "home变量的地址是:" << &home << endl;
	cout << house << endl;
	cout << "house变量的地址是:" << &house << endl;

运行时会打印出来变量和对应的地址:
在这里插入图片描述
对于上面的操作,我们可以看成把变量作为指定的量,而地址就是根据变量派生的量。而指针则与此相反,指针变量将地址量作为指定的量,而存储的值作为根据地址派生的量。我们可以通过运算符(*)来访问指针地址存储的变量:

	int home = 10;
	double house = 50;
	int * p_home = &home;
	double * p_house = &house;
	
	cout << "home变量的地址是:" << p_home << endl;
	cout << "home变量的值是:" << *p_home << endl;
	cout << "house变量的地址是:" << p_house << endl;
	cout << "house变量的值是:" << *p_house << endl;

在这里插入图片描述
通过上面的代码,我们可以发现:p_home 是一个指针变量,等同于&home,而p_home等同于home变量,我们甚至可以使用p_home来替换home进行任何操作。

4.7.1 声明和初始化指针

指针声明必须指定指针指向的数据的类型。
例如:

	int * p_home;

上面语句表明 p_home的类型为int,此时运算符被用于指针。
我们可以说:

  • p_home指向int类型(某地址指向某数据类型

还可以说

  • p_home的类型是指向int的指针(或 int *)

这里需要强调的是:

  • p_home是指针(地址),而*p_home是int,而不是指针。
  • int* 是一种类型 --指向int的指针。

我们可以在声明指针的时候初始化它的值:

	int home = 10;
	int * p_home = &home;

需要注意的是: 我们初始化的是指针变量的值(地址),而不是指针指向的变量的值,也就是说我们初始化p_home的值为&home,而不是初始化 *p_home 的值为 home。

另外注意,下面的声明将创建一个指针(p1)和一个int变量(p2)(对于每个指针变量名,都需要使用一个 * )

int * p1, p2;

如果要定义两个指针,那么需要这样做:

int * p1, * p2;

4.7.2 指针的危险

我们在定义一个指针变量时,如果没有对指针地址进行初始化,那么计算机只会分配一个存储地址值的内存空间,而具体的存储变量值的空间是没有分配的,如果我们之后直接对该指针所指向的变量进行赋值操作,那么系统会将赋的值存储在一个随机的地址空间,该空间有可能恰巧是程序代码的地址,那么继续存储变量值可能会使程序运行出现错误。例如:

	long *fellow;
	*fellow = 23263;

这是不可以的,因为fellow没有被初始化,它就可能是任何值,那么23263就可能被存储在内存中的任何地方,这显然会出现问题。
所以,我们一定要在对指针应用解除引用运算符(*)之前,将指针初始化一个确定的适当的地址。

4.7.3 指针和数字

指针不是整型,虽然计算机通常把地址当做整型来处理,但是指针和整型是截然不同的两种类型(整数可以进行加减乘除,而指针描述的是位置,两个地址相乘没有任何意义)。所以不可以像下面一样将一个整型赋值给指针:

	int*fellow;
	fellow = 0xB8000000;

我们可以将整型转换为指针类型再进行传递:

	int *fellow;
	fellow = (int *)0xB8000000;

4.7.4 使用new来分配内存

指针真正的用途是在程序运行时分配未命名的内存来存储值,C语言中我们可以通过库函数malloc()来分配内存,在C++中也可以这样做,但是使用new运算符是一个更好的选择。

int * pn = new int;

new int 是告诉程序需要适合存储int的内存。new运算符根据类型来确定需要多少字节的内存。然后它找到这样的内存,并返回其地址。接下来,将地址赋值给pn,pn被声明为指向int的指针。

4.7.5 使用delete来释放内存

使用new来分配内存块,然后使用delete来释放内存块(delete和new应该成对出现,否则会出现内存泄露)。使用delete的方式是delete后面加上指向内存块的指针:

delete pt;

使用要求是:

  • 不要使用delete释放不是new分配的内存;
  • 不要使用delete释放同一个内存块两次;
  • 如果使用new[ ]为数组分配内存,那么应该使用delete[ ]来释放;
  • 如果使用new为一个实体分配内存,那么应该使用delete(没有方括号)来释放;
  • 对空指针使用delete是安全的。

4.7.6 使用new来创建动态数组

之前我们创建数组时,我们只能创建固定大小的数组,那么程序在编译时就会为它分配内存空间。不管程序最终是否需要使用数组,数组都在那里占用固定大小的内存空间,这种在编译时给数组分配内存的方式被称为静态联编(static binding)。那么我们可以使用new来实现动态联编(dynamic binding)去创建动态数组(dynamic array),程序将在运行时确定数组的长度。
例如:

	int arraysize = 5;
	int my_array[arraysize];

上面的代码是不可以的,会显示:
在这里插入图片描述
那么我们使用new来创建时:

	int arraysize = 5;
	int * dt_array = new int[arraysize];
	//进行一系列操作
	delete[] dt_array;//new和delete要成对出现

这样就可以正常创建。
对于创建出来的动态数组dt_array,我们可以把指针当做数组来使用,也就是说使用dt_array[0]来表示第一个元素(等同于*dt_array),使用dt_array[1]来表示第二个元素,以此类推。注意这里是dt_array,而不是 * dt_array

指针和数组的根本区别在于,指针可以通过加减来改变指针的值,而数组不能通过加减修改数组名:

	int arraysize = 5;
	int * dt_array = new int[arraysize];
	dt_array[0] = 5;
	dt_array[1] = 10;
	dt_array = dt_array + 1;
	cout << dt_array[0] << endl;

这样显示的结果是10:
在这里插入图片描述
这是因为通过对指针dt_array加一操作,使得指针所指向的地址值加一,所以*dt_array指向的是原来的dt_array[1]地址。

4.8 指针、数组和指针算术

指针算术: 对double类型的指针加1后,如果系统对double类型使用8个字节存储,那么数值将增加8;将指向short类型的指针加1后,如果系统对short使用2个字节存储,那么指针值将增加2。
另外一点是:C++将数组名解释为地址。 例如:

int tacos[10];

那么此时tacos = &tocas[0];
数组表示法和指针表示法:

数组表示法指针表示法
tacos[0]*tacos
tacos[3]*(tacos+3)

指针和字符串:
对于C-风格的字符串(数组字符串),实际上存储的也是第一个字符的地址,在打印时通过第一个字符的地址来逐个打印,直到遇到空字符。

使用new创建动态结构: 之前介绍了创建数组的静态联编和动态联编,和数组一样,在运行时创建结构优于在编译时创建结构。需要在程序运行时为结构分配所需的空间,也可以使用new运算符来实现。
例如:要创建一个未命名的inflatable类型,并将其地址赋给ps

inflatable * ps = new inflatable;

此时,对于inflatable结构的成员,我们可以通过箭头成员运算符 (->)来访问:

	inflatable * ps = new inflatable;
	ps->price = 6.88;//通过->来访问成员price

另外一种访问结构成员的方式是,如果ps是指向结构的指针,那么*ps就是被指向的值—结构本身。所以 *ps就是一个结构,可以通过句点访问其成员:

	inflatable * ps = new inflatable;
	(*ps).price = 9.66; //注意小括号不可以丢掉

由于运算符优先级的问题,小括号不可以省略!

自动存储、静态存储和动态存储:

  1. 自动存储:在函数内部定义的常规变量使用自动存储空间,被称为自动变量(automatic variable)。在所属函数在被调用时自动产生,在程序运行结束时消亡。自动变量通常存储在栈(后进先出LIFO)中,在程序执行过程中,栈将不断增大和缩小。
  2. 静态存储:在整个程序执行期间都存在的存储方式。变量成为静态的方式有两种:
    • 函数外面定义它;
    • 在声明变量时使用关键字static。
  3. 动态存储:new和delete运算符提供的一种更灵活的方式。他们管理了一个内存池(自由存储空间【free store】或堆【heap】),该内存池同用于静态变量和自动变量的内存是分开的。new和delete可以实现在一个函数中创建变量,而在另外的函数中释放它。这样对于数据的周期管理更加灵活,同样也使得内存管理更加复杂。

注意:不成对地使用new和delete会导致内存泄露,这甚至会导致程序崩溃。

4.9 类型组合

数组结构和指针可以以各种方式组合他们:
我们可以创建一个结构体:

struct this_year
{
	int year;
	//something else
};

然后我们可以创建下面这种类型的变量:

this_year s01,s02,s03;//s01,s02,s03是结构体

然后通过成员运算符访问其成员:

s01.year = 2021;

4.9.1 结构体指针

创建一个指向这种结构的指针,将该指针设置为有效地址后,就可使用间接成员运算符来访问成员:

this_year * pa = &s02;
pa -> year = 2020;//间接成员运算符

4.9.2 结构数组

使用下面的方式来创建一个结构数组,然后访问其成员:

this_year trio[3];
trio[0].year = 2022;

这里的trio是一个数组,而trio[0]是一个结构,trio[0].year是结构的一个成员。
因为数组名是一个指针,所以我们也可以使用间接成员运算符:

(trio+1) -> year = 2004;
//等价于下面的语句:
//trio[1].year = 2004;

4.9.3 指针数组

const this_year  * arp[3] = {&s01, &s02, &s03};

arp是一个指针数组,那么arp[1]就是一个指针,可以使用间接成员运算符:

std::cout<<arp[1]->year<<endl;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值