FlayerVEO 学无止境 - ········C&C++
http://www.flayerveo.com/index.php/category/c
-
C++-网络编程-5.socket阻塞与非阻塞
http://www.flayerveo.com/index.php/archives/335/
2020-02-12T08:14:00+00:00
**阻塞(blocking) 、非阻塞 (non-blocking):**Windows套接字在阻塞和非阻塞两种模式下执行I/O操作。在阻塞模式下,在I/O操作完成前,执行的操作函数一直等候而不会立即返回,该函数所在的线程会阻塞在这里。相反,在非阻塞模式下,套接字函数会立即返回,而不管I/O是否完成,该函数所在的线程会继续运行。
##阻塞模式:
**阻塞模式的套接字上,调用任何一个Windows Socket API 都会耗费等待时间:** 当调用recv()函数时,系统首先查是否有准备好的数据。如果数据没有准备好那么系统就处于等待状态。当数据准备好后,将数据从系统内核缓冲区复制到用户控件,然后该函数返回。在套接应用程序中,当调用recv()函数时,未必用户空间就已经存在数据,那么此时recv()函数就会处于等待状态。

####四种阻塞API:


**注意: **不是所有Windows Sockets API以阻塞套接字为参数调用都会发生阻塞。例如,以阻塞模式的套接字为参数调用bind()、listen()函数时,函数会立即返回。
##非阻塞模式:

非阻塞模式需要一直不停的调用recv()函数知道有数据过来。
**把套接字设置为非阻塞模式,即通知系统内核: **在调用Sockets API时,应让函数立即返回,并返回一个错误代码。 一个非阻塞模式套接字多次调用recv()函数时,内核数据还没有准备好。因此,该函数立即返回WSAEWOULDBLOCK错误代码。第四次调用recv()函数时,数据已经准备好,被复制到应用程序的缓冲区中,recv()函数返回成功指示,应用程序开始处理数据。



-
C++-网络编程-4.socket编程之TCP通讯
http://www.flayerveo.com/index.php/archives/327/
2020-02-12T08:11:00+00:00

##服务端 Server:
```cpp
初始化winsock
1.创建TCP套接字
2.绑定socket到一个IP和端口上
3.监听套接字
4.接受客户端请求
5.发送、接收消息
6.关闭套接字
销毁winsock
```
##客户端 Client:
```cpp
初始化winsock
1.创建TCP套接字
2.连接服务端
3.接收、发送消息
4.关闭套接字
销毁winsock
```
-
C++网络编程-3.WSA系列API函数
http://www.flayerveo.com/index.php/archives/325/
2020-02-12T08:09:29+00:00
**什么是WSA (Windows Sockets Asynchronous)? **
不带WSA前缀标准的socket函数,如何socket(), send(), recv()这些不带WSA前缀的函数,这些函数是全平台通用的;而加上了WSA前缀的是微软的扩展函数,一般是Winsock2定义的2.0版的函数,只能在Windows平台用。
##WSAStartup() 函数:
用于初始化进程调用的Winsock相关的dll。
格式:
```cpp
int WSAStartup( IN WORD wVersionRequested, OUT LPWSADATA lpWSAData)
```
参数一 (wVersionRequested):Windows Sockets DLL规定调用者可以使用的Windows Sockets规范的版本,为WORD类型。高位字节中存储副版本号,低位字节中存储高版本号。可以使用MAKEWORD宏返回该值,例如MAKEWORD(2, 2)。
参数二 (lpWSAData):指向WSADATA结构体的指针,用于接收WindowsSockets执行的数据。
##WSACleanup() 函数:
释放对Winsock链接库的调用,在多线程环境下,将终止所有
格式:
```cpp
int WSACleanup(void);
```
返回值:0表示正常退出,SOCKET_ERROR表示异常。
##WSASocket() 函数:
创建一个与指定传送服务提供者捆绑的套接字,可选地创建和/或加入一个套接字组。
格式:

第四个和第五个参数可以设为none,空。
返回值:成功返回套接字的描述符,失败返回INVALID_SOCKET。
##socket() 和 WSASocket() 的区别:
1. socket()函数创建一个通讯店并返回一个套接字。如果是阻塞套接字时相应API会阻塞。
2. WSASocket()的发送操作和接收操作都可以被重叠使用。接收函数可以被多次调用,发出接收缓冲区,准备接收到来的数据; 发送函数也可以被多次调用,组成一个发送缓冲区队列。可是socket()却只能发过之后等待回消息才可以做下一步操作。
##WSAConnect() 函数:
在Winsock2版本中的连接函数,作用是创建一个与远端的连接,交换连接数据,并根据所提供的流描述确定所需的服务质量。
格式:

平时只需要设置前三个参数即可。
返回值:成功返回0,失败返回SOCKET_ERROR,通过WSAGetLastError()获取。
**例:**
```cpp
WSAConnect(sock,(sockaddr *)&addr,sizeof(addr),NULL,NULL,NULL,NULL)
```
##WSAAccept() 函数:
有条件的接受一个连接基于状态函数的返回值,选择创建或加入一个套接字组,提供QOS flowspecs,运行连接数据的转移。
格式:

平时可以只设置前三个参数,将后面的参数设为none。
返回值:成功返回接受套接字的描述字,失败返回INVALID_SOCKET。
##WSARecv() 函数:
在recv的基础上增加了一些新的特性。 比如说重叠I/O和部分数据报通知。
格式:

返回值:成功返回0,完成例程将在调用线程处于alertable状态时被调用;失败返回SOCKET_ERROR的值,并通过调用WSAGetLastError错误代码。
##WSARecvEx() 函数:
与WSARecv关系不大,与recv()函数作用相同,本质调用recv()从TCP连接的另一端接收数据。
格式:

返回值:成功时返回接收到的字节数,错误返回SOCKET_ERROR,连接关闭返回0。如果接受到部分消息,则在flags参数中设置MSG_PARTIAL;如果接收到完整消息,则不设置。
##WSASend() 函数:
增强标准的send()函数,可用进行重叠的发送。
格式:

返回值:成功返回0,完成例程将在调用线程处于alertable状态时被调用;失败返回SOCKET_ERROR的值,并通过调用WSAGetLastError错误代码。
##WSARecvFrom() 函数:
在recvfrom的基础上增加了一些新特性。比如说重叠I/O和部分数据报通知。
格式:

返回值:成功返回0,完成例程将在调用线程处于alertable状态时被调用;失败返回SOCKET_ERROR的值,并通过调用WSAGetLastError错误代码。
##WSASendTo() 函数:增强标准的sendto函数,可用进行重叠的发送。
格式:

返回值:成功返回0,完成例程将在调用线程处于alertable状态时被调用;失败返回SOCKET_ERROR的值,并通过调用WSAGetLastError错误代码。
##WSAGetLastError() 函数:
返回上次发生的网络错误的错误代码。
格式:
```cpp
int WSAGetLastError(void);
```
返回值:指出了该线程进行的上一次Windows Sockets API函数调用时的错误代码。
常用的错误代码:

-
C++-网络编程-2.socket常用API函数
http://www.flayerveo.com/index.php/archives/314/
2020-02-12T08:03:00+00:00
##socket() 函数:
创建一个套接字
格式:
```cpp
SOCKET 套接字描述字 = socket(int af, int type, int protocol)
```
参数一 (af): 指定应用程序使用的协议族,对于TCP/IP协议族,该参数设置为AF_INET;
参数二 (type):指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套 接字类型为SOCK_STREAM;原始套接字SOCK_RAW;
参数三 (protocol):指定套接字使用的协议,IPPROTO_TCP,IPPROTO_DUP, IPPROTO_RAW,IPPROTO_IP,也可以设为0即不指定具体协议。
返回值: 如果调用成功则返回新创建的套接字描述符(一个大于0的整数),失败返回INVALID_SOCKET。
**使用示例:**
```cpp
SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET){
return 0;
}
```
##bind() 函数:
服务端对socket进行地址和端口的绑定。
格式:
```cpp
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
```
参数一 (sockfd): 即socket描述字,是socket()函数创建的唯一标识。
参数二 (*addr): 一个指针,指向要绑给sockfd的协议地址。设置时要用(SOCKADDR *) 强转,值可以设为INADDR_ANY表示"0.0.0.0"即不确定地址即任意地址。
参数三 (addrlen): 对应的结构体的长度。
返回值: 成功返回0,错误返回SOCKET_ERROR。
**使用示例: **
```cpp
struct sockaddr_in addr; //初始化结构体, 建议用sockaddr_in
addr.sin_family = AF_INET; //设置协议族
addr.sin_addr.S_addr = inet_addr("127.0.0.1"); //设置地址(网络字节序存储)
addr.sin_port = htons(8000); //设置端口(网络字节序存储)
if (bind(s, (SOCKADDR*)&addr, sizeof(SOCKADDR)) == SOCKET_ERROR){
return 0;
}
```

结构体sockaddr (给操作系统用的):

结构体sockaddr_in (给开发者用的):


**网络字节序的定义:**当一个字节被当做高位,就要求发送端发送的第一个字节应当是高位。 (大端:高位字节放在内存的低地址端,低位字节放在内存的高位端)
##listen() 函数:
服务端对socket进行地址和端口的绑定。
格式:
```cpp
int bind(int sockfd, int backlog);
```
参数一 (sockfd):为要监听的socket描述字
参数二 (backlog):未完成队列的大小(正在等待完成相应的TCP三次握手过程的队列。这些套接口处于SYN_RCVD)
返回值:如果调用成功返回0,否则返回SOCKET_ERROR。可以调用WSAGetLastError()函数获取错误代码。
**使用实例:**
```cpp
if (listen(s, 30) == SOCKET_ERROR){
return 0;
}
```
##accpet() 函数:
服务端接受客户端连接请求
格式:
```cpp
int accpet(int sockfd, strut sockaddr *addr, socklen_t *addrlen);
```
参数一 (sockfd): 要监听的socket描述字。
参数二 (*addr): 为指向struct sockaddr *的指针,用于返回客户端的协议地址。
参数三 (*addrlen): 协议地址的长度。
返回值:成功返回由内核自动生成一个全新的socket,代表与返回客户端的TCP连接。失败则返回INVALID_SOCKET。
**使用示例:**
```cpp
sockaddr_in addrClient; //创建与客户端连接的套接字
memset(&addrClient, 0, sizeof(SOCKADDR)); //清空即初始化新建的套接字
int len = sizeof(SOCKADDR);
SOCKET c = accept(s, (SOCKADDR*)&addrClient, &len);
```
##connect() 函数:
客户端连接服务端
格式:
```cpp
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
```
参数一 (sockfd): 为客户端的socket描述字。
参数二 (*servaddr): 为服务器的socket地址。
参数三 (addrlen): 为socket地址的长度。
返回值: 成功返回0,错误返回SOCKET_ERROR。
**使用示例:**
```cpp
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.S_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(8000);
if (connect(s, (SOCKADDR*)&addr, sizeof(SOCKADDR)) == SOCKET_ERROR){
return 0;
}
```
##send() 函数:
用send函数从TCP连接的另一端发送数据
格式:
```cpp
int send(IN SOCKET s, IN const char FAR * buf, IN int len, IN int flags)
```
参数一 (s): 为客户端(不完全指客户端,而是非监听的套接字)的socket描述字
参数二 (*buf):要发送的数据缓存
参数三 (len): 实际要发送的数据长度
参数四 (flags): 一般设为0
返回值: 成功返回发送数据缓冲区的字节数(可能小于len),错误返回SOCKET_ERROR。
**使用示例:**
```cpp
int ret = send(s, "hello", strlen("hello"), 0);
```
##recv() 函数:
用recv函数从TCP连接的另一端接收数据
格式:
```cpp
int recv(IN SOCKET s, IN const char FAR * buf, IN int len, IN int flags)
```
参数一 (s): 为客户端(不完全指客户端,而是非监听的套接字)的socket描述字
参数二 (*buf): 指明一个缓冲区用来存放接收到的数据
参数三 (len): 缓冲区的长度
参数四 (flags): 一般设为0
返回值: 成功返回接收到的字节数,错误返回SOCKET_ERROR,连接关闭返回0。
**使用示例:**
```cpp
char buf[100]; //创建接收数据缓冲区
memset(buf, 0, 100); //初始化缓冲区 可以用char buf[100]={'\0'}代替memset()。
int ret = recv(s, buf, 100, 0); //接收数据
printf(buf); //打印接收到的数据
```
##sendto() 函数:
用sendto函数从UDP连接的另一端发送数据
格式:
```cpp
int sendto(int s, const void *buf, int len, unsigned int flags, const struct sockaddr *to, int tolen);
```
参数一 (s): 为客户端(不完全指客户端,而是非监听的套接字)的socket描述字
参数二 (*buf):要发送的数据缓存
参数三 (len): 实际要发送的数据长度
参数四 (flags): 一般设为0
参数五 (*to): 要发向的地址
参数六 (tolen): 数据发的长度
返回值: 成功返回发送数据缓冲区的字节数(可能小于len),错误返回SOCKET_ERROR。
**使用示例: (不需要使用connect函数,sendto与connect合二为一)**

##recvfrom() 函数:
用recvfrom函数从UDP连接的另一端接收数据
格式:
```cpp
int recvfrom(SOCKET s, char FAR* buf, int len, int flags, struct socketaddr FAR* from , int FAR* fromlen);
```
参数一 (s): 为客户端(不完全指客户端,而是非监听的套接字)的socket描述字
参数二 (*buf): 指明一个缓冲区用来存放接收到的数据
参数三 (len): 缓冲区的长度
参数四 (flags): 一般设为0
参数五(from): 接收方的地址
参数六(fromlen):数据的长度
返回值: 成功返回接收到的字节数,错误返回SOCKET_ERROR,连接关闭返回0。
**使用示例:**

##shutdown() 函数:
禁止在指定套接字上发送和接收套接字数据。并不会关闭套接字,在调用closesocket()函数前,所有与该套接字相关的资源都不会被释放。
格式:
```cpp
int shutdown(SOCKET s, int how);
```
参数一 (s): 为客户端的socket描述字
参数二 (how): how的方式有三种:
> SD_RECEIVE:表明关闭接收通道,在socket上不能再接收数据,如果当前接收 缓存中仍有未取出数据或将来有数据要到达,则TCP会向发送端发送RST包, 将连接重置。
SD_SEND:表明关闭发送通道,TCP会将发送缓存中的数据都发送完毕在收到所 有数据的ACK后向对端发送FIN包,表明本端没有更多数据发送。
SD_BOTH:表明同时关闭接收通道和发送通道。
返回值: 成功返回0,错误返回SOCKET_ERROR。
##closesocket() 函数:
关闭一个套接字。
格式:
```cpp
int closesocket(SOCKET s);
```
参数一 (s): 要关闭的socket描述字
返回值:成功时返回0,错误返回SOCKET_ERROR
##setsockopt() 函数:
设置套接口的选项。
格式:
```cpp
int setsockopt(SOCKET s, int level, int optname, const char *optval, int optlen)
```
参数一 (s): 要设置的socket描述字
参数二 (level): 定义层次,目前仅支持SOL_SOCKET和IPPROTO_TCP层次
参数三 (optname):需要设置的选项
参数四 (*optval):存放选项值的缓冲区
参数五 (optlen):缓冲区长度
返回值:成功时返回0,错误返回SOCKET_ERROR
**使用示例:**
方法一: 设置send()和recv()时的发送和接收超时:

方法二: 设置调用closesocket后套接字不会立刻关闭,而会经历TIME_WAIT的过程:



-
C++网络编程-1.socket简介与初始化
http://www.flayerveo.com/index.php/archives/303/
2020-02-12T07:50:04+00:00
**Socket:**将TCP/IP集成到Unix中,出现了许多TCP/IP应用程序接口, 这些接口称为Socket接口。如今Socket接口已成为TCP/IP网络最通用、成熟的API,被称为"套接字"。
**WinSock:**微软制定的一套Windows下的网络编程接口,即WinSock规范。在实际应用中Windows Sockets规范主要有1.1和2.2版。两者的最重要区别是1.1版只支持TCP/IP协议,而2.2版可以支持多协议并且有良好的向后兼容性。
**Socket在TCP/IP协议模型中的位置:** 介于应用层与传输层之间,作为应用程序的接口进行TCP或UDP的网络编程。 (见下图)

####套接字分为以下三种类型:
**流式套接字(SOCK_STREAM):** TCP协议使用该接口。 面向连接、无差错、无记录边界、有顺序和非重复的信包传输。
**数据报套接字(SOCK_DGRAM):**UDP协议使用该接口。 无连接的服务,以独立的信包进行网络传输,不保证顺序性、可靠性和无重复性。用于单报文传输或可靠性不重要的场合。数据报套接字的特点是保留了记录边界。
**原始套接字(SOCK_RAM):**提供对网络下层通讯协议(如IP协议)的直接访问,一般不是提供给普通用户的,主要用于开发新的协议或用于提取协议教隐蔽的功能。
**WinSock包含两个主要当的版本: ** (即WinSock1和WinSock2)
**在使用WinSock1.1时,需要引用头文件库文件代码如下:**
```cpp
#include
#pragma comment(lib, "wsock32.lib")
```
**在使用WinSock2.2时,需要引用头文件库文件代码如下:**
```cpp
#include
#pragma comment(lib, "ws2_32.lib")
```
**使用WSAStartup函数初始化WinSock相关的动态库:**
```cpp
WSADATA wsaData; //"wsaData"可以取任意名字
if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0 ){
printf("WSAStartup 无法初始化!");
return 0;
}
```
**使用WSACleanup函数销毁Winsock:**
```cpp
if( WSACleanup() == SOCKET_ERROR ){
printf( "WSACleanup 出错!" );
return 0;
}
```
-
C++-网络编程-域名和网络结构体hostent
http://www.flayerveo.com/index.php/archives/301/
2020-02-12T07:46:54+00:00
#域名和网络地址结构体---struct hostent
**该结构体定义如下:**
```cpp
struct hostent
{
char *h_name; //主机名,即官方域名
char **h_aliases; //主机所有别名构成的字符串数组,同一IP可绑定多个域名
int h_addrtype; //主机IP地址的类型,例如IPV4(AF_INET)还是IPV6
int h_length; //主机IP地址长度,IPV4地址为4,IPV6地址则为16
char **h_addr_list; /* 主机的ip地址,以网络字节序存储。若要打印出这个IP,需要调用inet_ntoa()。*/
};
```
**gethostbyname():**
使用gethostbyname函数可以利用字符串格式的域名获得IP地址,并且将地址信息装入 hostent 域名结构体。该函数定义如下:

由于hostent结构体成员h_addr_list指向字符串指针数组,也就是说数组里元素 char *, 但是 inet_ntoa() 需要的是 struct in_addr*(存放IPV4地址信息),所以要使用struct in_addr*进行强制类型转换。
**为什么hostent结构体的h_addr_list里的元素是 char \*, 而不直接是 struct in_addr*? **

**gethostbyaddr():**
反之,如果要利用IP地址获得域名,可以使用函数**gethostbyaddr**,定义如下:

-
C++-算法库 algorithm
http://www.flayerveo.com/index.php/archives/297/
2020-02-12T07:43:01+00:00
##导入库:
```cpp
#include
```
##非修改性序列操作
循环 对序列中的每个元素执行某操作 for_each()
查找 在序列中找出某个值的第一次出现的位置 find()
计数 在序列中统计某个值出现的次数 count()
两个序列中的对应元素都相同时为真 equal()
搜索 在序列中找出一子序列的第一次出现的位置 search()
##修改性序列操作
复制 从序列的第一个元素起进行复制 copy()
从序列的最后一个元素起进行复制 copy_backward()
交换 交换两个元素 swap()
交换指定范围的元素 swap_ranges()
交换由迭代器所指的两个元素 iter_swap()
替换 用一个给定值替换一些值 replace()
复制序列时用一给定值替换元素 replace_copy()
填充 用一给定值取代所有元素 fill()
用一给定值取代前n个元素 fill_n()
生成 用一操作的结果取代所有元素 generate()
用一操作的结果取代前n个元素 generate_n()
删除 删除具有给定值的元素 remove()
复制序列时删除具有给定值的元素 remove_copy()
唯一 删除相邻的重复元素 unique()
复制序列时删除相邻的重复元素 unique_copy()
反转 反转元素的次序 reverse()
复制序列时反转元素的次序 reverse_copy()
随机 采用均匀分布来随机移动元素 random_shuffle()
##序列排序及相关操作
排序 以很好的平均效率排序 sort()
排序,并维持相同元素的原有顺序 stable_sort()
将序列的前一部分排好序 partial_sort()
复制的同时将序列的前一部分排好序 partial_sort_copy()
第n个元素 将第n各元素放到它的正确位置 nth_element()
二分检索 找到大于等于某值的第一次出现 lower_bound()
找到大于某值的第一次出现 upper_bound()
找到(在不破坏顺序的前提下)可插入给定值的最大范围 equal_range()
在有序序列中确定给定元素是否存在 binary_search()
归并 归并两个有序序列 merge()
归并两个接续的有序序列 inplace_merge()
有序结构上的集合操作 一序列为另一序列的子序列时为真 includes()
构造两个集合的有序并集 set_union()
构造两个集合的有序交集 set_intersection()
构造两个集合的有序差集 set_difference()
构造两个集合的有序对称差集(并-交) set_symmetric_difference()
堆操作 向堆中加入元素 push_heap()
从堆中弹出元素 pop_heap()
从序列构造堆 make_heap()
给堆排序 sort_heap()
最大和最小 两个值中较小的 min()
两个值中较大的 max()
序列中的最小元素 min_element()
序列中的最大元素 max_element()
排列生成器 按字典序的下一个排列 next_permutation()
按字典序的前一个排列 prev_permutation(
-
C++-数学库 math
http://www.flayerveo.com/index.php/archives/296/
2020-02-12T07:41:35+00:00
##导入方式:
```cpp
#include
```
##功能:
```cpp
double sqrt(double x); //开平方sqrt是square root缩写
double pow(double x,double y); //求乘方x^y,pow是power的缩写
```
-
C++-库-动态链接库的编写
http://www.flayerveo.com/index.php/archives/295/
2020-02-12T07:35:32+00:00
##动态链接库简介:
动态链接库(Dynamic-LinkLibrary)通常包含一堆程序员自定义的变量与函数,可以在运行时动态链接到可执行文件。
##静态库的扩展名:
.dll (Win) .so(Linux)
##静态库的优缺点:
优点:

缺点:

##Windows操作系统的动态链接库:
Windows操作系统核心有三个动态链接库(Kernel32.dll,User32.dll,Gdi32.dll),这些动态链接库构成了Win32 API函数。

##创建静态库的步骤:
>(1) 创建动态库(DLL)项目
(2) 添加 .h头文件和 .cpp文件
(3) 使用extern "C" 与 _declspec(dllexport)
(4) 生成 .lib 与 .dll文件
**(1) 创建动态库(DLL)项目**

**(2) 添加 .h头文件和 .cpp文件(默认有,不用加)**


**(3) 使用extern "C" 与 _declspec(dllexport)**


**(4) 生成 .lib 与 .dll文件**

观察Debug目录可以发现成功生成了.dll和.lib文件

使用Dependency Walker工具查看该Dll1.dll文件,发现sum函数确实在Dll1.dll中:

##使用动态链接库:
>(1) 包含库的 .h头文件
(2) #pragma comment(lib, "XXX.lib")
(3) 运行时需要 .dll文件

-
C++-库-静态链接库的编写
http://www.flayerveo.com/index.php/archives/282/
2020-02-12T07:30:17+00:00
##静态库简介:
静态库(staic library)通常包含一堆程序员自定义的变量与函数,在编译期间由编译器与链接器将它集成到可执行文件中。
##静态库的扩展名:
.lib (Win) .a(Linux)
##静态库的优缺点:
**优点:**发布时只需发布exe,因为库已被集成到可执行文件中,运行时对这个库不再依赖。
**缺点:**静态库集成到可执行文件中导致exe文件较大,后续想升级库必须重新编译
##创建静态库的步骤:
>(1) 创建静态库项目
(2) 添加 .h头文件和 .cpp文件
(3) 生成 .lib文件
**(1) 创建静态库项目:**




**(2) 在头文件 添加 .h头文件和 .cpp文件**



在.h头文件中写函数的声明:

在.cpp源文件中写函数的实现:

**(3) 生成 .lib文件**

可以发现在Debug目录下生成了.lib静态库文件:

##>>>使用静态库