应用层是计算机网络从上往下的第一层,这一层的主要内容是网络应用的原理和实现,通过学习几种常见的网络应用程序,并且开发运行在TCP和UTP(运输层)上的网络应用程序,这些应用程序包含Web、电子邮件、DNS、P2P等等。
GitBook自动发布脚本
GitBook作为一个非常好用的基于Node.js的命令行工具,可以输出HTML、PDF、eBook等多种格式的电子书,这里就不再详细讲述GitBook的构建以及发布到Github托管的过程,网络上有非常多的教程,可以自行Google查看。
刚开始在每次更新内容的时候都需要一系列的git命令将原始内容仓库和构建的网页仓库同步到Github上,大概需要10条命令,如果你的更新频率很高的话,发布内容将会是有个痛苦的过程,但这里我们可以通过一个shell脚本只使用$ sh deploy.sh
一条命令完成同步内容和构建网页的所有过程。
首先,在你的GitBook内容根目录下建一个deploy.sh
文件,使用文本编辑器打开,然后输入以下内容:
1 | #!bin/sh |
然后打开命令行,为这个脚本授权:
1 | $ chmod +x deploy.sh |
至此就完成了,以后在更新的时候只需要输入一条命令$ sh deploy.sh
就可以完成所有操作了。
储存类别、链接和内存管理
一、储存类别
- 对象:具有一定大小空间的一块内存。
- 标识符:用来指定特定对象的内容。
- 作用域
- 描述了程序中可访问标识符的区域。
- 分类:
- 块作用域
- 函数作用域
- 文件作用域
- 函数原型作用域:从形参定义处到原型声明结束。
- 全局变量:也称为文件作用域变量。
- 链接
- C变量的三种链接属性:
- 外部链接:该类型变量可以在多文件程序中使用
- 内部链接:该类型变量只能在一个翻译单元中使用
- 无链接:具有块作用域、函数作用域或函数原型作用域的变量
- 储存类别说明符
static
- C变量的三种链接属性:
- 存储期:
- C对象有4种
- 静态存储期:在程序的执行期间一直存在,文件作用域变量都具有静态存储期。
- 线性存储期:用于并发程序设计。
- 自动存储期:所有局部变量都有该性质,在函数调用时被创建,离开函数时被销毁。
- 动态分配存储期:
- C对象有4种
- 自动变量
- 属于自动存储类别,有自动存储期、块作用域、无链接。
- 可显式使用关键词
auto
- 块作用域和无链接意味着只有在变量定义的块中才能通过变量名访问该变量。
- 寄存器变量
- 特殊的自动变量。也是自动存储期、块作用域、无链接。
- 可以显式使用关键词
register
,是一种请求,编译器可能会拒绝你的请求,此时就变成了普通变量,但仍然无法访问该变量的内存。 - 作用是储存在最快的可用内存中,能够以更快的速度访问。
- 存储在寄存器中而非内存中,所以无法获取寄存器变量的地址。
- 块作用域的静态变量
- 静态指的是在内存中原地不动,但它的值可以改变。
- 具有块作用域、无链接、有静态存储期。
- 在块中以
static
(提供静态存储期)声明这种变量。 - 如果未显式地初始化静态变量,它们会被初始化为0。
- 外部链接的静态变量
- 具有文件作用域、外部链接、静态存储期。
- 有时又称为外部存储类型、该类别地变量被称为外部变量。
- 创建过程:在所有函数外面(定义性声明)创建这个外部变量。
- 函数使用该变量时,为了指出使用的是外部变量,可以用关键字
extern
再次声明。 - 如果一个源文件使用的外部变量定义在其他文件中,则必须用
extern
在该文件中声明该变量。 - 定义式声明、引用式声明。
- 内部链接的静态变量
- 具有静态存储期、文件作用域、内部链接。
- 在所有函数外部,用存储类别说明符
static
定义该类变量。 - 该类型变量只能用于同一个文件中的函数。
- 可以在函数中使用类别说明符
extern
重复声明该类型变量,这并不会改变其链接属性。
- 多文件(多翻译单元)
- 程序在多个翻译单元中才能体现区别内部和外部变量的重要性。
- 存储类别的选择
- 正常情况下多使用自动存储类别,使用外部变量可能在不经意中修改变量的值,count类型变量除外。
二、
结构
1.建立结构变量
1 | struct book { |
- 该结构描述了由两个字符数组和一个float类型变量组成的结构。
2.定义结构变量
struct book library;
把library声明为一个使用book结构类型的结构变量。
所以struct book 相当于过去的int、double等类型。
1
2
3
4
5struct book {
char title[MAXTITL];
char author[MAXAUTL];
float value;
} library;该程序为上面声明的完整版。
1
2
3
4
5struct { //无结构标记
char title[MAXTITL];
char author[MAXAUTL];
float value;
} library;声明结构和定义结构可以一步完成,如上所示。
3.初始化结构
4.访问结构成员(点运算符)
5.结构初始化器
6.结构数组
struct book library[MAXBKS];
7.指向结构的指针
struct guy * him;
- 指针可以指向任何现有的guy类型如barney、fellow[0](一个结构数组的一项即为一个结构变量)。
him = &barney
or him = &fellow[0];
8.用指针访问成员
若him == &barney
,那么him->income
即是barney.income
.
同时,*him
即为指针指向的结构类型。所以,
barney.income == (*him).income == him->income
9.向函数传递结构的成员、地址、结构本身
10.其他特性:
- 使用指针传递信息(作为函数参数):是使用原始结构进行计算的。
- 使用结构作为函数参数:是使用副本进行计算。
字符串输入、输出和字符串函数
一、字符串输入
1.gets()
函数
- 使用方法:读取整行输入,直到遇到换行符,然后丢弃换行符,储存其余的字符,并在字符末尾添加一个空字符使之成为一个C字符串。
- 使用中存在的问题:
gets(words)
(char words[STLEN])的唯一参数为words,该函数无法检查数组是否装得下输入行。数组名会被转化成该数组首元素的地址,该函数只知道数组的开始处,并不知道数组在何处结束。 - 如果输入过长,会导致缓存区溢出,详情见书上内容。
2.gets()
的替代品:fgets()
函数
fgets()
函数通过第二个参数来限制读入的字符数来解决溢出问题。- 相较于
gets()
的区别:- 第二个参数指明了读入字符的最大数量,若该值为n,那么将读入n-1个字符,或遇到第一个换行符为止。
- 第三个参数指明了要读入的文件,如果要从键盘输入,则参数设置为stdin标识符。
- 如果读到一个换行符,
fgets()
会将其储存在数组中,而不是将其丢弃。
3.gets_s()
函数
于
fgets()
的区别该函数只从标准输入中读取,所以无需第三个参数。
读到换行符会将其丢弃。
如果读到最大字符数都没有读到换行符,会进行以下操作:
首先设置目标数组的首字符为空字符
读取并丢弃随后的输入直至读到换行符或文件结尾
返回空指针
然后调用依赖实现的“处理函数”
4.s_gets()
函数
- 读取整行输入并用空字符替代换行符,或者(在字符串未结束就遇到了空字符)读取一部分输入,并丢弃其余的部分。
该函数的代码:
1 | char * s_gets(char * st, int n) |
5.scanf()
函数
- 以第一个非空白字符座位读取的开始,以第一个空白字符(空行、空格、制表符、换行符)座位字符串的结束。
- 其他内容略。
二、字符串输出
1.puts()
函数
- 该函数显示字符串时会在末尾添加一个换行符。
- 该函数的参数为地址,且用双引号括起来的字符串常量作为参数时,参数被视为该字符串的地址。
- 当参数为数组中某个项的地址时,如
str[5]
,会从这个位置开始依次输出。 - 结束条件为空字符。
2.fputs()
函数
- 第二个参数要指明写入数据的文件,若要打印在显示器上,则用stdout作为参数。
- 该函数不会在字符串末尾添加换行符。
3.printf()
- 省略
三、字符串函数
0.这里只写出书上详细介绍的5组字符串函数:strlen()
| strcat()/strncat()
| strcmp()/strncmp()
| strcpy()/strncpy()
| sprintf()
1.strlen
函数
- 该函数用于统计字符串的长度,函数返回值为字符串的长度。
2.strcat()
函数
- 基本定义
- 该函数用于拼接字符串
- 接受两个字符串作为参数
- 把第二个字符串的备份附在第一个字符串的末尾,并把拼接后形成的新字符串作为第一个字符串,第二个字符串不变。
- 该函数的函数类型是
char *
,即指向char
的指针。 - 所以该函数返回第一个参数,即拼接第二个字符串后的第一个字符串的地址。
- 存在的问题
- 无法检查第一个数组是否能容纳第二个字符串。
3.strncat()
函数
- 该函数的第三个参数指定了最大添加字符数
- 例如
strncat(bugs, addon, 13)
会把addon字符串的聂荣附加给bugs,加到第13个字符或者遇到空字符的时候停止 - 所以,算上空字符(必须要添加空字符,否则就不是字符串了),bugs数组应该足够大,以容纳原始字符串(不包含空字符)、添加在后面的13个字符、末尾的空字符。
4.strcmp()
函数
- 用来把用户的响应于已储存的字符串作比较(比较两个字符串的内容)
- 比较的是字符串的内容而不是字符串的地址,例如
- 定义:
#define ANSWER "Grant"
#define SIZE 40
char try[SIZE]
s_gets(try, SIZE)
\\输入Grant- 如果使用
while (try == ANSWER)
,则永远得不到两者相等的结果,因为while比较的是指针,即地址,二者的地址肯定不相同 - 但使用
strcmp(try, ANSWER)
则能够得到两者内容相等的结果。
- 注:该函数只会比较try中第一个空字符前面的部分,所以该函数可以比较储存在不同大小数组中的字符串。
- 定义:
- 该函数的返回值:
- 返回实例:
- 比较“A”和本身,返回0
- 比较”A”和”B”,返回-1
- 比较“B”和”A“,返回1
- 结论:
- 二者相等返回0
- 第一个的ASCII码比第二个大就返回负数,比第二个大就返回整数
- 返回的整数和负数即为二者的ASCII码之差
- 注:
- 返回值的具体数值并不重要,重要的是它是0还是非0
- 该函数比较的是字符串而不是字符
- 返回实例:
- 局限:
- 该函数比较字符串中的字符,直到发现不同的字符为止,这个过程可能会持续到字符串末尾。
5.strncmp()
函数
该函数可以比较字符不同的地方,也可以只比较第三个参数指定的字符数
例如书中的实例:可以限定函数只查找这几个字符。
6.strcpy()
函数
- 该函数是用来拷贝整个字符串的内容,即相当于字符串赋值运算符
- 例如:
- str1和str2都是指向字符串的指针
str2 = str1;
这个语句拷贝的指针的地址而不是具体内容- 所以要想拷贝整个字符串就要用到该函数
- 注意:
- 接受两个字符串指针作为参数,可以把第二个指针声明为指针、数组或者字符串常量
- 第一个指针应该指向一个数据对象如数组,且该字符串有足够的空间存储源字符串的副本。
- 其他属性:
- 该函数的返回类型是
char *
,返回的是第一个参数的值,即一个字符的地址。 - 第一个参数不必指向数组的开始,该属性可用于拷贝数组的一部分,详情见书上的程序。
- 该函数的返回类型是
- 例如:
- 局限:
- 与
strcat()
一样,不能检查目标空间是否能容纳的下源字符串的副本。
- 与
7.strncpy()
函数
- 该函数的第三个参数指明了可拷贝的最大字符数
- 如果要拷贝的字符超过了指定的参数n,当拷贝了n个字符后就会停止,并且也不会在后面添加空字符,容易出现错误,所以要非常注意。
8.sprintf()
函数
该函数与
printf()
函数类似,但这个是把数据写入字符串,而不是打印在显示器上。该函数的第一个参数是目标字符串的地址,其余的与
printf()
类似。- 示例:
1
2
3
4
5char first[MAX];
char last[MAX];
char formal[2 * MAX + 10];
//给以上前面两个数组赋值后就可以进行如下的操作
sprintf(formal, "%s, %-19s\n", last, first);
抽象数据类型
一、抽象数据类型的实现
1 | //数据类型的实现 |
相关概念
- 实例变量:
- 一个实例变量对应了无数的值,因为数据类型的每个实例对象都有有个。
- 构造函数:
- 构造函数是用来创建一个对象的表示,即来初始化实例变量。
- 它由可见性修饰符、构造函数名称(即数据类型的名称)、参数变量以及初始化实例变量的代码构成。
- 上一条加粗的三个组成了签名,例如
public Counter (String id)
即为一个签名。
- 实例方法:
- 是每个对象的具体行为,即实现数据类型的实例方法。
- 与静态方法的唯一区别是它能够访问并操作实力变量。
- 作用域
- 参数变量:作用域为整个方法。
- 局部变量:定义后的所有语句(一个花括号内)。
- 实例变量:整个类。
测试GitBook格式
FlightMuseum-GitBook
目录层级
- Preface
- Airbus
- A320
- A319
- A320-200
- A320-200neo
- A321
- ……..
- A330
- A330-200
- A330-300
- A350
- A350-900
- A350-1000
- A320
- Boeing
- B737
- B747
- B767
- B777
- B787
- B787-8 Dreamliner
- B787-9 Dreamliner
- B787-10 Dreamliner
- …….
- Trains
- CR
- TRA
- JR
一个具体层级的Markdown编辑形式
1.Cathay Pacific | 国泰航空 B-LRA
- Serial Number / 生产序列号:29
- First Flight Date / 首飞日期:24th. Mar 2016 / 2016年3月24日
- Age (Shotted Date) / 机龄(拍摄日期):3.3 years as of 27th. Jul 2019 / 3.3年 摄于2019年7月27日
- Shotted Place / 拍摄地:Hong Kong / 香港
- Seat Configuration / 座位布局:C38 W28 Y214
- Engines / 引擎:2 x RR Trent XWB
- Others:-
2.Philippines Airlines | 菲律宾航空 RP-C3506
- Serial Number / 生产序列号:243
- First Flight Date / 首飞日期:11st. Sep 2018 / 2018年9月11日
- Age (Shotted Date) / 机龄(拍摄日期):0.8 years as of 9th. Aug 2019 / 0.8年 摄于2019年8月9日
- Shotted Place / 拍摄地:Hong Kong / 香港
- Seat Configuration / 座位布局:C30 W24 Y241
- Engines / 引擎:2 x RR Trent XWB
- Others:-
什么是数据结构?什么又是算法?
一、数据结构及相关概念
0. 要想知道什么是数据结构,首先要了解与“数据”相关的概念,这样才能一步步理解什么是数据结构。
我们首先用放书这件事情来举例。
- 如果正常情况下只有你自己一个人的书,那么你把他们一本一本立起来放在书架上就可以了。
- 那么如果有一个图书馆那么多的书应该怎样办么?总不能把他们乱起八糟的立起来放吧?那么是不是应该先按照社会科学、理科类、工科类、外语类等等的分类方式先分类,然后按照具体所属的类别把他们一个个放进去?其实数据结构的相关的概念就是一个这样简单的道理。
下面我们来进行具体的描述。
1. 数据
- 是对客观事物的符号表示,在计算机科学中是指所有能输入到计算机中并被计算机程序处理的符号的总称。
- 其实就对应了图书馆中的所有书。
2. 数据元素
- 是数据的基本单位,在计算机程序中通常作为一个整体进行考虑号处理。
- 对应的就是某一本具体的书,比如”数据结构与算法“这本书。
3. 数据项
- 一个数据元素可由若干个数据项组成,数据项是数据的不可分割的最小单位。
- 数据项不就是对应了书中的具体信息,比如书的作者、章节内容、ISBN编号等等。
4. 数据对象
- 是性质相同的数据元素的集合,是数据的子集。
- 数据元素是一本具体的书,那么性质相同数据元素的集合不就代表了一类具体的书籍吗,就比如计算机类的书籍,其中包含了”数据机构与算法这本书“。
看到这儿是不是对这几者的关系还有点儿晕?没关系,我们用思维导图来再次说明。
数据是由每一个具体的数据元素组成的,相同的数据元素同属于一个数据对象,每个数据元素中又有具体的信息——数据项。
接下来让我们再次把这些概念对应到具体的例子”书”中。
世界上所有书就是一个数据,每一本具体的书例如“数据结构与算法”就是一个数据元素,每本书中的具体信息例如“书名、作者、ISBN码…”就是多个数据项,一些相同类型的书的类型例如“计算机类书籍”就是一个数据对象。
现在你应该清楚以上4个概念了吧?
那我们回到最初的问题,什么是数据结构?
这里我们同样举例子来说明。
首先,一个类别的书籍中有许多具体的书,如”C语言”、”数据结构与算法”、”编译原理”,而这些书之间又有一些关联,比如某个算法可以用C语言来实现,**编译过程中需要用到特定的算法等等,这些存在关联的书即”数据元素”的集合就叫做数据结构**。
4. 数据结构
相互之间存在一种或多种特定关系的数据元素的集合。
大多数算法都需要我们适当地组织数据,而为了组织数据就产生了数据结构。
结构
逻辑结构
集合
线性结构
树形结构
网状结构或图状结构
物理结构(储存结构)
顺序储存结构
链式储存结构
(注:这里不具体展开介绍各个数据结构,在后面的附录里进行介绍)
二、抽象数据类型
- 指一个数学模型以及定义在该数据模型上的一组操作。
三、算法
- 编写一段计算机程序一般都是实现一种 已有的方法来解决某个问题。这种方法大多 和使用的编程语言无关——它适用于各种计 算机以及编程语言。
- 你要在图书馆中找到一本书,需要通过索引查找来确定这本书的位置,这个找书的过程就是一个算法。
C语言指针
一、指针的一些概念
1. 指针的类型
- 把指针声明语句中的指针名字去掉,剩下的就是指针本身的类型
2. 指针所指向的类型
- 把指针声明语句中的指针名字和名字前的指针声明符号*去掉,剩下的就是指针指向的类型
3. 指针的值——指针所指向的内存区或地址
- 指针的值是指针本身存储的数值,该值被编译器当作一个地址(在32位程序中,所有类型的指针的值都是一个32位的整数,因为32位程序的内存地址都是32位长。)
- 指针所指的内存区就是从指针的值所代表的内存地址开始,长度为
sizeof(指针指向的类型)
的一篇内存区。 - 我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区。
- 同理,我们说一个指针指向了某个内存区,相当于说该指针的值是该内存区域的首地址。
4. 指针本身所占据的内存区
- 用
sizeof(指针的类型)
测试一下就知道了
二、指针的算术运算
**0.**指针可以加上或者减去一个整数,这种运算的意义并不是平常的数值的加减。
- example 1
1 | char a[20] = {0}; |
指针ptr的类型为int*,它指向的类型为int,被初始化为指向整型变量a。
在第三句中,指针ptr被加上了1,编译器是这样进行处理的:
把指针ptr的值加上
sizeof(int)
,在32位程序中是被加上了4,因为32位程序中int
占4字节。由于地址是用字节作为单位,所以ptr指向的地址由原来的a的地址向高的地方增加了4个地址。
char类型的长度是一个字节,所以原来的ptr是指向数组a从第0个单元开始的4个字节,现在指向了从第4个单元开始的4个字节
- example 2
1.**可以使用一个循环和一个指针**遍历一个数组。
1 | int array[20] = {0}; |
该例子中将整形数组中各个单元的值都加1。同时每次循环都将指针加1个单元,所以每次循环都能访问数组的下一个单元。
- example 4
1 | char a[20] = "You_are_a_girl"; |
该例子中,指针ptr被加上了5个单元,编译器这样处理:
将char的值加上
sizeof(int) * 5
,在32位程序中即乘了4*5=20。指针的值代表了该指针以这个值为地址指向了某个内存区,所以指针ptr所指向的地址向高地址的方向移动了20个字节
然而ptr加上5(即地址增加20个字节后)已经指向了数组a的合法范围之外,虽然应用时会出现问题,但在语法上没有问题。
**2.**总结
一个指针ptrold加或减一个整数n后,结果是一个新的指针ptrnew,两个指针的类型相同,指向的类型也相同。
ptrnew的值比ptrold的值增加或者减少了
n * sizeof(int)
个字节,即ptrnew指向的内存区比ptrold指向的内存区向高或低地址的方向移动了n * sizeof(int)
个字节。指针进行加减:两个指针不能进行加法操作,这是非法操作,结果毫无意义。两个类型相同的指针可以进行减法运算,一般在数组上应用。
三、运算符&和*
0. &是取地址运算符,*是间接运算符。
&a 运算的结果是一个指针,指针的类型是 *a,指针指向的类型是a的类型,指针指向的地址是a的地址。
*p的类型是p指向的类型,所占用的地址是p指向的地址。
四、指针表达式
0. 一个表达式的结果如果叫指针,那么这个表达式就叫指针表达式。
- example
1 | int a, b; |
1. 指针表达式的结果是一个指针,所以也具有指针的4个要素:
指针的类型
指针指向的类型
指针指向的内存区
指针自身占据的内存
2. 一个指针表达式的结果指针已经具有了自身占据的内存的时候,这个指针表达式就是一个左值,否则就不是一个左值。
五、指针和数组的关系
0. 数组的数组名可以看作是一个指针
example
1 | int array[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, value; |
总结:
声明一个数组TYPE array[n],则这个数组名有两重含义:
代表整个数组,类型是TYPE[n]。
是一个常量指针,类型是TYPE*,该指针指向的类型是TYPE,该指针的内存区就是数组的第0号单元,且该指针自己占有单独的内存区,且该指针的值不能修改,array++是错误的写法,这不代表array[1]。
六、指针和结构类型的关系
结构类型还未学习,之后补充
七、指针和函数的关系
0. 可以把一个指针声明成一个指向函数的指针。
- example
1 | int fun1 (char*, int); |
可以把指针作为函数的形参。在函数表达式中,可以用指针表达式来作为实参。
八、指针类型转换
初始化一个指针或者给一个指针赋值时,赋值号左边时一个指针,右边是一个指针表达式。绝大多数情况下,指针的类型和指针表达式的类型是一样的,指针所指向的类型和表达式所指向的类型是一样的。
1 | float f = 12.3; |
如果我们想让指针p指向实数f,我们应该怎样操作?
是“ p = &f; ”吗?
不对,因为左右两侧的指针和指针所指向的类型不同。所以直接赋值不行。
为了实现这个目的,需要使用强制类型转换:
p = (int*) &f;
如果一个指针p,需要把它指向的类型改为”TYPE”,语法格式为:
(TYPE*)p;
。这样的类型转换得到一个新指针,类型为TYPE*,指向的地址就是原指针指向的地址。原来p的一切属性都没有被修改。