2.8 结构体
到此为止,我们能很好定义单个想要的数据类型变量,但是如果想要使用单个类型的变量进行罗列应该如何定义呢?这时候我们就可以使用结构体,它可以将若干个不同的数据类型的变量或数组封装在一起,方便存储一些复合数据。
2.8.1 结构体的定义
定义结构体的格式如下:
struct Name{
// 一些基本的数据结构或者自定义的数据类型,和基本数据类型的定义相同
};
例子如下:
// studentIofo是这个结构体的类型名,表示学生信息
struct studentInfo{
// 内部定义了id(学号)、gender(性别)、name(姓名)和major(专业)
// 这些是单个学生的信息
int id;
char gender;
char name[20];
char major[20];
}Alice, Bob, stu[1000]; // 这里的Alice等表示结构体变量
// 除了直接定义结构体变量,我们也可以想普通定义变量的方法一样
studentInfo James; // 是不是和int a;很相似啊。
studentInfo peo[1000]; // 是不是和int b[1000];很相似
注意,结构体里面可以定义除了自己本身之外的任何数据类型。不过虽然不能定义自身,但可以定义自身类别的指针变量。例如:
struct node{
node n; // 不能这么定义!!
node* next; // 可以定义node*型的指针变量
};
2.8.2 访问结构体内的元素
主要有两种方法:“.”操作和“->”操作。看下面的例子:
struct studentInfo{
int id;
char name[20];
studentInfo* next;
}stu, *p;
访问stu中的变量的写法:
stu.id;
stu.name;
stu.next;
访问指针变量p中元素的写法:
(*p).id;
(*p).name;
(*p).next;
可以看到两者访问的写法都是一样的,但是对指针变量来说,这样子看上去略显复杂,所以C语言中有一种更加简洁的写法(只针对于访问结构体指针变量内元素):
p->id;
p->name;
p->next;
当然,我们也能通过上面的形式进行取值和赋值。
2.8.3 结构体的初始化
我们可以用上面访问元素的方法逐一赋值,但是当结构体内变量太多时,这样子过于麻烦。于是我们可以使用构造函数
的方法进行初始化。
构造函数是用来初始化结构体的一种函数,它直接定义在结构体内。构造函数不需要写返回类型,且函数名与结构体名相同。
一般来说,普通定义的结构体内会生成一个默认的构造函数(但不可见)。
struct studentInfo{
int id;
char gender;
// 默认生成的构造函数,写不写都默认存在
studentInfo(){}
};
如果我们想手动提供变量的初始化参数,
struct studentInfo{
int id;
char gender;
// 下面的参数用以对结构体内部变量进行赋值
// _id和_gender没有什么特殊的含义,写成a和b也可以
studentInfo(int _id, char _gender){
id = _id;
gender = _gender;
}
// 当然,上面的构造函数也可以写成一行
// studentInfo(int _id, char _gender): id(_id), gender(_gender){}
};
// 这样就可以在需要时直接对结构体变量进行赋值了
studentInfo stu = studentInfo(10086, 'M');
注意
,如果我们像上面一样重新定义了构造函数,就一定要经过初始化来定义结构体变量!
为了既能不初始化就能定义结构体变量,又能享受初始化带来的便捷,我们可以将两个构造函数都添加上。
struct studentInfo{
int id;
char gender;
studentInfo(){}
studentInfo(int _id, char _gender){
id = _id;
gender = _gender;
}
};
也即只要参数个数和类型不完全相同,就可以定义任意多个构造函数!以适应不同的初始化场合。
2.9 补充
2.9.1 cin与cout
cin和cout是C++中的输入输出函数,使用需要添加头文件#include <iostream>
和using namespace std;
。它可以直接进行输入和输出,十分易用和方便。
- cin
cin是c和in的合成词,采用输入运算符>>
来进行输入。cin的输入不需要指定格式,不需要加取址符&,直接写变量名就可以了。
cin >> db;
cin >> c;
// 同时读入多个变量,int型的n、double型的db、char型的c、char型数组str[]
cin >> n >> db >> c >> str;
如果想读入一整行,则需要使用getline函数:
char str[100];
cin.getline(str, 100);
如果是string容器,则需要用下面的方式输入:
string str;
getline(cin, str);
- cout
cout是c和out的合成词,用法与cin基本相同,只不过使用的是输出运算符<<
。
// 输出时中间并没有空格,所有可以手动在每个变量之间加上空格
cout << n << " " << db << " " << c << " " << str;
对于cout来说,换行有两种方式。第一种是C语言中的转义字符\n
;一种是使用endl
来表示换行(endl是end line的缩写)。
cout << n << "\n" << db << endl;
若是想控制double型变量的精度,则需要一些麻烦的操作。在输出之前加上一些东西,然后引入#include <iomanip>
头文件:
cout << setiosflags(ios::fixed) << setprecision(2) << 123.4567 << endl;
// 输出结果:
// 123.46
其实对于考试的话,并不推荐使用cin和cout,因为它们耗时要比scanf和printf高很多,只有在十分必要的时候才建议使用(例如string)。
2.9.2 浮点数的比较
众所周知,计算机采用有限位的二进制编码,因此浮点数在计算机中的存储并不总是精确的,在经过大量计算后,可能会对比较操作带来极大的干扰(因为C和C++中的“==”操作是完全相同才能判定为true)。于是需要引入一个极小数eps来对珍重误差进行修正。
如果一个数a落在[b-eps, b+eps]
区间内,就说a==b
成立。eps取**10-8**是一个合适的数字。
const double eps = 1e-8;
// a == b
#define Equ(a, b) ((fabs((a)-(b))) < (eps))
// a > b
#define More(a, b) (((a)-(b)) > (eps))
// a < b
#define Less(a, b) (((a)-(b)) < (-eps))
// a >= b
#define MoreEqu(a, b) (((a)-(b)) > (-eps))
// a <= b
#define LessEqu(a, b) (((a)-(b)) < (eps))
// 定义圆周率π
const double Pi = acos(-1.0);
注意
:由于精度问题,一个变量中存储的0可能是个很小的负数,这是对其开根号就会因为不在定义域内而出错;在某些编译环境产生的原因下,本应为0.00的变量在输出时会变成-0.00,这是编译环境本身的bug。
2.9.3 复杂度
一般来说,复杂度主要指时间复杂度和空间复杂度。
- 时间复杂度
一般我们所讨论的时间复杂度不是执行完一段程序的总时间,而是算法中基本操作的执行总次数。
在算法中,我们总能找到一个 n n n(或许是其他字母)作为问题的规模,然后需要根据基本操作执行情况计算出规模 n n n的函数 f ( n ) f(n) f(n)(这个 f ( n ) f(n) f(n)就是求得的基本操作总次数),并确定时间复杂度为 T ( n ) = O ( f ( n ) 中 增 长 最 快 的 项 / 此 项 的 系 数 ) T(n) = O(f(n)中增长最快的项/此项的系数) T(n)=O(f(n)中增长最快的项/此项的系数)。例如 f ( n ) = 2 n 3 + 4 n 2 + 100 f(n) = 2n^3+4n^2+100 f(n)=2n3+4n2+100, T ( n ) = O ( 2 n 3 / 2 ) = O ( n 3 ) T(n) = O(2n^3 / 2) = O(n^3) T(n)=O(2n3/2)=O(n3)。
常用时间复杂度的比较关系:
O ( 1 ) ≤ O ( l o g 2 ( n ) ) ≤ O ( n ) ≤ O ( n l o g 2 ( n ) ) ≤ O ( n 2 ) ≤ O ( n 3 ) ≤ ⋯ ≤ O ( n k ) ≤ O ( 2 n ) O(1) \leq O(log_2(n)) \leq O(n) \leq O(nlog_2(n)) \leq O(n^2) \leq O(n^3) \leq \dots \leq O(n^k) \leq O(2^n) O(1)≤O(log2(n))≤O(n)≤O(nlog2(n))≤O(n2)≤O(n3)≤⋯≤O(nk)≤O(2n)
对于一般的OJ系统来说,一秒能承受的运算次数大概是== 1 0 7 10^7 107 ~ 1 0 8 10^8 108==,因此 O ( n 2 ) O(n^2) O(n2)的算法当 n n n的规模为1000时是可以承受的,而当 n n n的规模是10000时则是不可承受的。
注意
:有的算法中基本操作次数不仅和初始输入的规模
n
n
n有关,还和数据本身有关。例如一些排序算法,规模同样是
n
n
n,但是由于数据的初始顺序不一样,基本操作的执行次数也会不同。我们一般将最坏的情况作为算法的时间复杂度。
想快速掌握时间复杂度,最好还是多多刷题吧!
- 空间复杂度
表示算法需要消耗的最大数据空间。它和时间复杂度采用相同的写法。如果消耗的最大数据空间是一个二维数组,那么这个算法的空间复杂度就是
O
(
n
2
)
O(n^2)
O(n2)。一般来说,空间都是足够使用的,只要不开好几个
1
0
7
10^7
107以上的数组即可。例如int A[10000][10000];
的定义就不合适!此外,空间复杂度中的
O
(
1
)
O(1)
O(1)是指算法消耗的空间不随数据规模的增大而增大!