浅谈C++复合类型(上半篇)

读前总览:
在这里插入图片描述

一、数组

1. 为什么要使用数组?

前面我们学习了C++基本内置类型,已经初步学会了如何使用整型和浮点型表示数据,还能进行一些简单的运算。那么现在让你记录一下过去一年中每月的开销,你会怎么做?声明12个double变量来存储?好像也不是不可以,就是有点麻烦。那如果要记录过去一年中每天的开销呢?如果还像前面那样一个一个声明变量,那肯定就行不通了。既然如此,那我们为什么一次性声明365个变量呢?这个时候就要用到我们即将学习的第一个复合类型——数组。

2. 如何使用数组?

首先,数组(array)是一种数据格式,能够存储多个同类型的值。我们必须基于已经存在的类型创建数组,如:int、float。数组在内存空间中是连续存储的,每个元素都是紧挨着的,所以我们可以通过使用下表来访问数组。没听明白没关系,作者来给你逐个击破,打团打不过咱们就抓单。

2.1 创建数组

创建数组我们需要使用声明语句,和创建变量的声明差不多。数组声明要指出下面三点:
1)数组名
2)元素类型
3)元素个数
就拿上面的每月开销举例,数组名:Month,元素类型:double,元素个数:12。然后编译器就去内存中找 12*sizeof(double) 字节大小的空间,给这块空间标识为Month。然后我们使用Month的时候,编译器就知道我们使用的是一个数组,这个数组有12个元素,每个元素的类型是double。如果不知道元素的类型,编译器就无法开辟空间,所以我们要基于已经存在的类型创建数组,这些类型的大小编译器是知道的。

声明格式:类型 数组名[元素个数];
如:double Month[12];
这条语句创建了一个名为Month的数组,数组有12个元素,每个元素的类型是double。元素个数那里只能使用整型常量或者整型常量表达式,因为我们使用的是静态数组,编译器需要在编译之前确定数组大小,然后分配空间,而变量的值是在程序运行时设置的。后面指针部分会介绍动态数组,它可以使用变量在程序运行时确定数组大小。

2.2 给数组赋值

有两种方法可以给数组赋值,一种是在创建数组的同时初始化数组,另一种是创建完数组后,对它的每个元素进行赋值。

2.1.1 数组初始化

相信大家对初始化这个概念也并不陌生,毕竟前面也学习了简单变量的初始化,如:int a = 5; 创建一个int类型的变量a并给它赋初值5。但是数组的初始化有点不同,如:int arr[5] = { 1, 2, 3, 4, 5 }; 用花括号把这些值括起来,然后用逗号隔开,这些值会依次赋值给数组的元素。这是最常规的数组初始化,还有一些变体,初始化数组允许提供的值的数量少于数组元素个数,如:int arr[5] = { 1, 2, 3 }; 只初始化了数组arr的前三个元素,编译器会把剩下的元素设置为0。如果没有初始化数组,创建之后里面元素的值是随机的。初始化时可以不写数组元素个数,如:int arr[] = { 1, 2, 3, 4, 5 }; C++编译器会计算元素个数,使数组arr包5个元素。当然C++11新增了可以把赋值运算符=去掉的初始化方式,如:int arr[5] { 1, 2, 3, 4, 5 }; ,大家可以自行选择初始化格式。

2.1.2 对数组的元素进行赋值

如何使用数组,我们先来看看数组是如何存储在内存的。例如:int arr[5] = { 1, 2, 3, 4, 5 }; 创建一个包含5个int元素的数组arr。
在这里插入图片描述
由于数组在内存中是连续存储的,我们便可以通过下标来访问数组的元素。C++规定数组的下标是从0开始的,所以数组第一个元素是a[0],然后是a[1]、a[2],最后一个元素的下标是元素个数-1,a[4]。刚开始可能不习惯,用多了就习惯了,而且学到后面你会发现,数组下标从0开始是有道理的。数组元素其实就是对应类型的一个变量而已,上面的a[0]、a[1]这些都是int类型的变量,你对int变量能进行的操作,对它们都可以。上面的数组初始化,等价于下面的语句:

int a[5];  // 创建int数组,未初始化,里面是随机值
// 给数组元素赋值
a[0] = 1;
a[1] = 2;
a[2] = 3;
a[3] = 4;
a[4] = 5;

和给变量赋值是一个道理。我们也可以输出数组元素。
在这里插入图片描述

3. 使用数组的注意事项

3.1 数组下表不能越界

首先,数组下标不能越界,如:int arr[5]; 使用时,下标不能小于0,不能大于4。我们简单解释一下,把上面那张图拿过来用一下:
在这里插入图片描述
首先,我们使用内存空间之前需要跟编译器打声招呼,也就是声明。然后编译器根据你的需求找到一块合适的空间,并把这块空间的使用权给你,然后你才能使用这块内存空间。如上图,我们告诉编译器需要一块空间存储包含5个int元素的arr数组,然后它给了我们下标0-4的这块空间的使用权,但是下标-1和5这两块空间我们并没有使用权限,所以数组下表越界就会导致非法访问。编译器不会检查你的下标是否合法,也就是不会报错,但是程序运行起来可能会导致问题,需要程序员自己注意。

3.2 不能把一个数组赋值给另一个数组

首先,把一个数组赋值给另一个数组编译器会报错。
在这里插入图片描述
这是因为数组名实际上是数组第一个元素的地址,静态数组创建之后其内存空间就已经分配了,不能更改。你试图去修改一个常量那肯定是不允许的。关于地址这一部分,将在后面指针的地方进行讲解。

3.3 对数组名使用sizeof运算符

对数组名使用sizeof运算符是一个特例,它返回的是整个数组的大小(单位字节)。对数组的元素使用sizeof运算符,则返回其类型的大小。如:
在这里插入图片描述
数组arr包含5个int类型的元素,int类型占4个字节的大小,所以数组的大小为20字节,而arr[0]仅仅只是一个int类型的变量。不难发现,我们可以通过这个方式计算数组的大小,用整个数组所占的字节数属于单个元素所占的字节数,如:

// 计算数组的大小
int size = sizeof(arr) / sizeof(arr[0]);

4. 数组的其他属性

数组可以作为函数参数传递给函数,也可以通过函数返回一个数组。这个先了解一下,后面函数专题会介绍。

二、字符串

字符串是存储在内存的连续字节中的一系列字符。C++中有两种处理字符的方式,一种来自C语言,通常被称为C风格字符串。另一种是C++的string类。

1. C风格字符串

C语言使用字符数组存储字符串,因为数组在内存中刚好是连续的。C风格字符串通过在字符串末尾添加空字符来标记字符串的结尾,空字符——‘\0’,其ASCII码为0。字符串是字符数组,但是字符数组不一定是字符串,我们看代码:

char str[4] = { 'a', 'b', 'c', '\0' };  // 字符串,以空字符'\0'结尾
char charr[4] = { 'a', 'b', 'c' };  // 字符数组

这两条语句都是字符数组,但是只有第一条语句是字符串,它以空字符’\0’结尾。
在这里插入图片描述

1.1 字符串的初始化

如果像上面第一条语句这样初始化字符串,效率太低了,我们可以直接使用字符串常量(双引号括起的)对其进行初始化:

char str[4] = "abc";  // 初始化字符串

这里没有显式地写出空字符,但是编译器知道"abc"是一个字符串,把它拷贝到数组str中时会在末尾添加一个空字符’\0’。所以字符数组的大小要比字符串的长度多1,以便存储空字符’\0’。或者创建稍大的字符数组以保证能够容纳字符串。

1.2 字符串长度的计算

C风格字符串通过strlen()函数来计算字符串的长度。函数strlen()位于头文件cstring中,接收一个字符串,返回一个字符串的长度。可以向下面这样使用:
在这里插入图片描述
首先,函数参数可以传char数组名,也可以传字符串常量,计算结果都是一样的。其次,strlen()函数计算字符串的长度是不包括结尾的空字符’\0’的,因为空字符的作用仅仅只是标记字符串的结尾。

1.3 字符串的输出

目前可以通过两种方式输出字符串:
在这里插入图片描述
第一种方式是存储在字符数组中,然后输出,在cout << 后面加上数组名即可。第二种方式是直接输出字符串字面值,这种大家应该已经很熟悉了。如果自己不手动换行的话,第二条输出语句会紧跟在第一条语句的末尾,如:
在这里插入图片描述
所以,如果要打印多条语句,可以分开来打印。

1.4 字符串的输入

字符穿的输入首先需要一个字符数组来存储输入的字符串,其次需要保证字符数组能够容纳这个字符串。最后由于空字符无法输入,所以在C++中遇到空白就代表输入结束。一般的cin输入只能输入一个单词。在这里插入图片描述
如上图所示,在输入时两个单子中间隔了个空格,cin读取到空格字符时就结束输入了,然后把字符串"Liu"存储到字符数组name中。

1.4.1 读取一行输入

我们可以通过istream类的对象cin调用其类成员函数(也称方法):getline()和get()。这两个函数都读取一行输入,直到遇到换行符。但是,getline()把换行符丢弃,而get()把换行符留在输入流中。

1)getline()
getline()函数接受两个参数,第一个参数是存储输入行的字符数组,第二个参数是读取字符的个数。如果参数为20,则最多读取19个字符,留下一个字符空间存储空字符’\0’。可以通过成员运算符.来调用成员函数getline()。如:
在这里插入图片描述
这次就成功了,getline()函数读取一行,先读取"Liu Yang",然后遇到我们按下的Enter键(也就是换行符),然后结束输入,把读取的信息存入数组name中。

2)get()
get()函数所需的参数和getline()函数相同,调用的方法也是一样:
在这里插入图片描述
但是,由于get()函数把换行符留在输入流中,如连续两次使用get()函数就会产生问题,因为第二次使用时第一个读取的输入就是换行符,然后就结束输入。如下:
在这里插入图片描述

所以,我们需要使用一下get()函数的变体,他不需要任何参数,作用是读取下一个字符。两个同名的函数叫函数重载,这在后面学习类的时候会介绍的。所以,我们在第一次调用get()函数读取一行之后,使用cin.get(); 把换行符读取后扔掉,这样就可以正确读取第二次输入。如下:
在这里插入图片描述

1.5 混合输入字符串和数字

由于使用cin输入数字时,会在输入中留下换行符,接下来如果再使用读取一行输入的话第一个读取的输入就是换行符,然后输入结束。我们举一个例子,先输入一个人的年龄,然后再输入他的姓名,如下:在这里插入图片描述
你根本没有输入的机会,cin.getline()读取到输入年龄遗留在输入流中的换行符直接就结束了。这个时候就要用不带参数的get()函数,把这个可恨的换行符读取并丢弃。如下:
在这里插入图片描述
如果你只需要读取一个单词,那么你使用cin >> name; 是不会有任何影响的,因为这样输入时,cin会自动跳过前面的空白直到第一个非空白字符,然后再次遇到空白就结束输入。

1.6 访问字符串的元素

由于C风格字符串使用数组来存储,那么我们当然也能够通过下标访问数组中的元素。使用字符串中的元素就像使用普通的字符变量一样。如:char str[4] = “ABC”; 我们可以通过赋值语句 str[1] = ‘b’; 把字符串的第二个字符’B’改成’b’。如下:在这里插入图片描述
其他位置的字符通过对应的下标访问就行,切记使用下标访问数组元素的时候要小心谨慎,不要下标越界,造成非法访问。

1.7 字符串的赋值和拼接

对C风格字符串,C函数库中有两个函数可以实现字符串的拷贝和拼接,分别是strcpy()和strcat(),这两个函数位于头文件cstring中。

1.7.1 字符串拷贝函数strcpy()

strcpy()函数接受两个字符数组作为参数,把第二个字符数组里面的字符串拷贝到第一个字符数组里面。如下:
在这里插入图片描述
strcpy()函数把str1里面的字符串拷贝到了str2里面。

1.7.2 字符串拼接函数strcat()

strcat()函数同样接受两个字符数组作为参数,把第二个字符串拼接到第一个字符串末尾。如下:
在这里插入图片描述

1.7.3 更安全的字符串拷贝与拼接函数

上面的字符串拷贝函数strcpy()与字符串拼接函数strcat()均有超出字符数组范围的风险,因为它们都是直接拷贝和拼接整个字符串的。于是C函数库种提供了更加安全的版本,strncpy()和strncat()。它们都在原来的基础上多了第三个int类型的参数,该参数指出拷贝和拼接字符的个数。如:
在这里插入图片描述
为什么拷贝过后输出了一堆乱码?因为strncpy()函数拷贝完后不在末尾添加空字符’\0’,而且字符数组str2没有初始化,里面是随机值,cout从字符数组str2的第一个字符开始往后输出,直到遇到空字符才结束。所以,我们需要在拷贝结束末尾主动添加空字符,如下:
在这里插入图片描述
有了字符串结束标记后,cout就能正常输出了。如果拷贝整个字符数组就不需要在手动末尾添加空字符,因为原字符串结尾的空字符也拷贝过来了。使用strncap()拼接函数也不用担心,它在拼接完成后自动在末尾添加空字符。

2. string类

string类把字符串当作了一种类型,我们可以声明这种类型的变量,而不是靠字符数组来存储字符串。使用时需要包含头文件——cstring。

2.1 string类的使用

我们通过代码来对string类的使用进行介绍:
在这里插入图片描述
首先,string类的对象的创建和普通变量一样,也可以使用字符串常量对其进行初始化。然后可以使用cin进行输入,但是也只能读取一个单词,还可以通过cout进行输出。也可以和数组一样通过下标访问其中的字符元素。但是,string类不需要显式地写出大小,它可以根据存储的字符串的大小自动调整,如果只是创建了一个string对象而不进行初始化,那么它的长度为零。如果要查看string类对象的长度,需要使用string类的成员函数size(),如下:
在这里插入图片描述

2.1.1 string类的拷贝和拼接操作

string类的拷贝和拼接操作比C风格字符串简单多了。拷贝和常规变量一样使用赋值运算符就好了,然后拼接只要使用符号+就可以了。如:
在这里插入图片描述
拼接那里的 str1 = str1 + str2; 会创建一个临时的string对象存储str1 + str2的结果,然后拷贝到str1中。

2.1.2 string类的读取一行输入

string类的读取一行输入和C风格字符串的格式有些许差异。string类读取一行的格式如下:geiline( cin, string对象名),表明从cin那里读取输入,然后存储到string对象中。如下代码:
在这里插入图片描述
这里没有使用成员运算符.说明getline()函数不是类方法,而是函数调用。那为什么getline()函数是istream类的方法可以用在字符数组上却不能用在string类上?这是因为在引入string类之前,C++早就有了istream,那个时候istream的设计考虑了像double这样的基本C++类型,而没有考虑string类型。

三、结构

虽然我们已经学会了使用C++基本类型和数组还有字符串,但这些还远远不够。如果让你存储一个人的信息,你该如何存储?首先,一个人有姓名、年龄、学历、省份证号等诸多信息,而且还是不同类型的信息。这时就需要使用结构来解决这个问题。

1. 结构的声明

假设我们要存储一个学生的姓名、年龄和体重。那我们需要用到三种类型:字符数组(也可以是string类)存储姓名,int类型存储年龄,double类型存储体重。如下:

struct Student  // 学生结构
{
	char name[20];  // 存储姓名
	int age;  // 存储年龄
	double weight;  // 存储体重
};

关键字struct表明这是一个结构类型,而Student是类型名,中间花括号中的是这个结构的成员,也是存储的信息。这仅仅只是Student结构类型的声明,告诉编译器这是我弄出来的结构类型,类型名是:Student,包含三个成员,然后你就可以去创建对应的结构类型变量,因为你已经和编译器打过招呼了,它认得这个结构类型,所以不会报错。结构的声明一般放在函数外面,这样所有的函数都能使用,如果放在函数里面,那么只有这个函数可以使用。

2. 创建结构变量并初始化

如下代码:

// 创建一个Student结构类型的变量并初始化
Student Li = {"Li hua", 18, 62.8};

和常规变量创建差不多,把Student当作和int一样的类型名就好了。然后花括号里面的值按照声明结构体的顺序依次赋值,中间用逗号给开。字符串"Li hua"赋值给字符数组name,18赋值给int变量age,62.8赋值给double变量weight。还可以像下面这样创建结构变量和初始化:

struct Student  // 学生结构
{
	char name[20];  // 存储姓名
	int age;  // 存储年龄
	double weight;  // 存储体重
} Li = {"Li Hua", 18, 62.8};

但是这样创建的是全局变量,可以被所有函数使用。

3. 通过成员运算符.访问结构成员

接着上面声明的结构类型Student,我们来创建一个Student变量不初始化,使用成员运算符访问结构成员给成员赋值然后再显示成员值。
在这里插入图片描述
我们通过成员运算符把结构变量的成员取出来用就跟使用成员类型的变量一样就好了。如:LiHua.name是一个字符数组,把它当作字符数组就好了,可以使用字符串拷贝函数给它赋值,把LiHua.age当作一个int类型的变量,直接使用赋值运算符给它赋值,输出的时候一样按照int类型输出。所以说,其实结构和普通的变量差别大不大哪里去,就是把不同的类型放到了一起而已。

4. 结构的其他属性

首先,和数组不同,结构之间可以进行赋值,也就是说可以把一个结构赋值给另一个结构,也可以用一个结构初始化另一个结构,这样结构里的成员值就被设置为另一个结构的成员值,即使成员是数组,这叫做成员赋值。如:
在这里插入图片描述
上面使用str1初始化str2,使用str1给str3赋值。同样结构可以作为函数的参数,函数也可以返回一个结构。

5. 结构数组

数组是基于已经存在的类型而创建的,既然我们已经声明了Student结构,那便可以创建Student结构数组。这和普通数组也没什么区别,唯一的区别就是数组的每个元素是Student结构。我们依旧可以使用数组初始化的方法来进行初始化,也可以同过下标来访问数组的每个元素。如下代码:
在这里插入图片描述
students是一个包含三个Student结构元素的数组,而students[1]是一个Student结构变量。初始化时由于数组元素是结构,所以需要使用花括号,每个元素的初始值用逗号隔开,如果只初始化了部分元素,剩下的元素被编译器设置为0,也就是说students[2]这个结构的所有成员都被设置为0。

四、共用体

公用体(union)是一种数据格式,和结构一样可以存储不同的数据类型,但是只能同时存储其中的一种类型。

1. 共用体的声明

和结构类似:

union four_val
{
	// 可以存储四种类型的值,但一次只能存储一种
	int int_val;
	char char_val;
	short short_val;
	double double_val;
};

和结构一样这里只是通过关键字union声明了一个共用体类型,可以通过four_val创建变量,如:four_val four1;,four1就是一个four_val共用体变量。由于共用体一次只能存储一个类型的值,所以编译器按照它的成员中的最大类型给它开辟空间,这样它的所有成员就公用一个空间,提高了内存空间的利用率。上述four1就只有8个字节(double类型占8个字节)。

2. 访问共用体成员

和结构成员一样,也是通过成员运算符.来访问共用体成员。如下:

// 创建four_val共用体变量
four_val four1;
// 对其成员进行赋值
four1.int_val = 10;  // 一次只能存储一个值

3. 匿名共用体

匿名共用体没有名称,由于没有类型名,只能在声明的时候创建变量。如下:

union
{
	// 可以存储四种类型的值,但一次只能存储一种
	int int_val;
	char char_val;
	short short_val;
	double double_val;
} four1;

上述共用体只有four1这个变量。

五、枚举

枚举实际上是一种创建符号常量的方式,这种方式可以代替const。

1. 枚举的声明

枚举也是和结构类似,通过关键字enum来进行枚举类型声明:

// 枚举类型的声明
enum color {red, green, blue, yellow};

color是新类型的名称,被称为枚举,就像struct变量被称为结构一样。默认情况下,将整型值0赋值给枚举类的第一个元素,然后后面的元素依次在前一个的基础上加1。如:red为1,green为2,blue为3,yellow为4。

2. 枚举变量的声明与赋值

只能将枚举类型声明时的枚举量赋值给枚举变量,我们创建一个color枚举的变量,然后只能把red、green、blue和yellow这四个值赋值给它,如下:
在这里插入图片描述
如果给枚举变量赋值其他类型的值,编译器就会报错。如果这个整型的范围在枚举的范围内,可以通过强制类型转换为枚举类型,如:
在这里插入图片描述
这下就没有问题了,因为2被强制类型转换为color枚举类型。

3. 枚举类型参与运算

在C++中只为枚举定义了赋值运算符,但是它却可以与int等类型参与运算,只是在运算时整型提升为int类型。如下:

color c1 = red;  // 创建一个color枚举变量,并赋初值red
int n1 = c1 + 10;  // 在相加时c1被整型提升为int类型

在第二条语句中,首先red的值0先被整型提升为int类型,然后再与int类型的字面值相加,最后赋值给int变量n1。就算是两个枚举变量进行运算,过程也是如此,先把两个枚举变量整型提升为int类型然后进行运算。

4. 设置枚举的值

我们也可以显式地设置枚举的值,全部或者一部分都可以,如下:

enum color {red = 1, green, blue = 15, yellow};

没有显式声明的枚举量在前一个枚举量的值的基础上加1,如green为2,而yellow为16。如果第一个枚举量没有被显式声明,则为0。

5. 枚举的取值范围

本来枚举的值只能为声明时的枚举量,但是C++通过强制类型转换增加了枚举变量的合法值。如何计算一个枚举类型的范围?首先,找到枚举这个枚举类型声明时枚举量的最大值和最小值。找到大于这个最大值2的幂,将其减1,就是这个枚举类型的上限,同理,如果最小值大于0,则下限为0,否则和找上限一样的做法。如上述color枚举类型,最大值为yellow等于3,所以最大值为2的平方减1等于3,最小值为red等于0,所以下限为:-(2^1) + 1 = -1。

六、总结

本文简要概述了除指针以外的复合类型,介绍了如何简单地使用这些复合类型,以及使用过程中的注意事项,希望能对大家有所帮助,谢谢!!!

说句实话,我在写这篇博客的时候感觉没有写好,有的地方讲的不明白,有的地方又讲的太罗嗦了,可能是自己学的还不够,在后续的学习中我会慢慢改进。希望各位读者能指出我的问题,看到了我会及时更正,谢谢大家!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值