《算法笔记》笔记之笔记 - 2、C&C++快速入门(5)

16 篇文章 0 订阅
16 篇文章 0 订阅

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;。它可以直接进行输入和输出,十分易用和方便。

  1. 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);
  1. 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 复杂度

一般来说,复杂度主要指时间复杂度和空间复杂度。

  1. 时间复杂度

一般我们所讨论的时间复杂度不是执行完一段程序的总时间,而是算法中基本操作的执行总次数

在算法中,我们总能找到一个 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,但是由于数据的初始顺序不一样,基本操作的执行次数也会不同。我们一般将最坏的情况作为算法的时间复杂度

想快速掌握时间复杂度,最好还是多多刷题吧!

  1. 空间复杂度

表示算法需要消耗的最大数据空间。它和时间复杂度采用相同的写法。如果消耗的最大数据空间是一个二维数组,那么这个算法的空间复杂度就是 O ( n 2 ) O(n^2) O(n2)。一般来说,空间都是足够使用的,只要不开好几个 1 0 7 10^7 107以上的数组即可。例如int A[10000][10000];的定义就不合适!此外,空间复杂度中的 O ( 1 ) O(1) O(1)是指算法消耗的空间不随数据规模的增大而增大

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值