C语言库的制作方法
1:把一类功能的函数写到一个xxx.c里面
2:把xxx.c中的所有函数的声明写到xxx.h中去
3:避免头文件重复语句
#ifndef __MYSTRING_H__
#define __MYSTRING_H__
这里写函数声明
#endif
4:将xxx.h包含到xxx.c中去 (自包含)
#include "mystring.h" //系统用<>自实现用“”
5:在main函数中 包含 xxx.h 谁用谁包含
main参数
int main(int argc,char *argv[])
//argc arfument 参数 c count 个数
//argv 指针数 v vector 向量(数组可以理解为向量)
{
}
argv【】内保存着传入main函数的字符串的地址
这些参数在内存中存放才 栈以上的命令行参数的位置
auto关键字
描述:这个这个关键字用于声明变量的生存期为自动,即将不在任何类、结构、枚举、联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量。auto关键字在我们写的代码里几乎看不到,但是它又无处不在,它是如此的重要,又是如此的与世无争,默默的履行着自己的义务,却又隐姓埋名。
**作用:**C程序是面向过程的,在C代码中会出现大量的函数模块,每个函数都有其生命周期(也称作用域),在函数生命周期中声明的变量通常叫做局部变量,也叫自动变量。例如:
int fun(){
int a = 10; // auto int a = 10;
// do something
return 0;
}
int fun(){
int a = 10; // auto int a = 10;
// do something
return 0;
}
整型变量a在fun函数内声明,其作用域为fun函数内,出来fun函数,不能被引用,a变量为自动变量。也就是说编译器会有int a = 10之前会加上auto的关键字。
auto的出现意味着,当前变量的作用域为当前函数或代码段的局部变量,意味着当前变量会在内存栈上进行分配。
栈空间:
在Linux中命令 ulimit -a 可以查看栈的大小
aa@aa-virtual-machine:~$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15409
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 15409
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
可以看到只能空间大小为 8192kb,这并不大,这说明了–>栈适用于数据交换的而不适合是用于大数据存储,堆空间主要用于大内存的申请
memset()函数
用于对数组和对空间的初始化,针对的单位是字节
memset(指向要初始化的指针,初始化的内容,初始化的大小)
memset针对的字节的初始化
memset(pa,1,10*sizeo(int))
那么在内存中式这样的(32位机器)
0x0101010101010101 这是一个int大小的内存所初始化的数据。
calloc函数
函数声明:void* calloc(size_t nmemb,size_t size)
所在文件: #include<stdlib.h>
函数功能:申请对空间并返回所申请的空间,并且自动清零
参数:size_t nmemb 所需内存单元的数量(单位式字节)
size_t size 内存单元的大小
int *p=(int*)calloc(10,sizeof(int))
//申请10的大小为int的内存单元
realloc()函数
函数声明 void *realloc(void *ptr,size_t size)
所在文件: stdlib.h
函数功能:扩容或缩小原有内存的大小,常用于扩容,缩小会导致内存缩去的部分空间数据丢失
返回值:返回空间的起始位置
需要注意的是:当使用realloc是如果后面的连续空间并没有所要申请的大小那么realloc就会重新找一个可以放下原先自己的内存和还要申请的内存的空间,这可能导致原来指针的变化,所以有以下模板:
char *pa=(char*)malloc(0x10);
char *newpa=Null;
newpa=realloc(pa,0x10)
当然有更好的办法
char *pa=(char*)maloc(0x10);
pa=realloc(pa,0x10);
关于构造类型
对数组类型起别名:
typedef int ARRAY[10] //给一个大小为10字节,内部存放int型的变量取别名为ARRAY
具体的起别名方法:
int arr[10] //先写出要定义的类型
dypedef int arr[10] //在前部加上typedef
dypedef in ARRAY[10] //将名字换成想要定义的名字,完成操作
int a //第一步
typedef int a //第二步
typedef int int64 //第三步
char *p
typedef char *p
typedef char* pstr
结构体就不用说了,太简单
typedef 构成的C语言语句在编译是起作用
#define 所写的为宏定义,在预处理阶段起作用
int arr={1,2,3,4,5,6} //这是对数组类型的初始化
arr={2,3,4,5,6,7} //这是错误的
struct stu
{
char *name;
char sex;
float score;
};
struct stu s={"zhangsan",'f',99.9}; //这是对结构体的初始化
s={"zhgnsan","w",88.8}; //这是错误的
凡是构造类型,要么在定义的时候初始化,不可以先定义再以初始化的方式赋值
相同结构体之间可以进行赋值运算,此语法基础奠定了可以用于传参和反值
typedef struct stu
{
char name[100];
char sex[20];
};
stu s1,s2;
s2={"zhangsan",nan};
s1=s2; //赋值运算,那么s1内的成员的值将于s2相同
结构体函数
#include<stdio.h>
typedef struct mycomplex
{
float real;
float image;
}mycomplex;
//函数实现结构体的运算,并返回结构体
mycomplex mycomplexadd(mycomplex s1,mycomplex s2)
{
complex t;
t.real=s1.real+s2.real;
t.image=s1.image+s2.image;
return t
}
int main()
{
}
结构体内嵌套结构体
#include<stdio.h>
int main()
{
typedef struct demol
{
char name[10];
int score;
char sex;
struct birth
{
int year;
int moth;
int day;
}birth; //记得要分配内存
}tru;
tru s={"wangwu",99,'f',{1999,9,9}};
printf("year=%d",s.birth.year);
return 0;
}
结构体类型的大小
涉及内存对齐
typedef struct _staff
{
char sex;
int age;
short num;
}Staff; //这只是一个类型而不是变量,所以不占空间
int main(int argc,char *argv[])
{
Staff s;
printf("sizeof(Staff) = %d\n",sizeof(Staff));
printf("sizeof(s) = %d\n",sizeof(s));
printf("&s = %p\n",&s);
printf("&s.sex = %p\n",&s.sex);
printf("&s.age = %p\n",&s.age);
printf("&s.num = %p\n",&s.num);
return 0;
}
终端输出:
sizeof(Staff) = 12
sizeof(s) = 12
&s = 000000000061FE14
&s.sex = 000000000061FE14
&s.age = 000000000061FE18
&s.num = 000000000061FE1C
//为什么sex后空了3个字节在太难age
//为什么num后面空了2个字节
//还有第三个
typedef struct _staff
{
char sex;
short num; //调换了num和age的顺序
int age;
}Staff; //这只是一个类型而不是变量,所以不占空间
int main(int argc,char *argv[])
{
Staff s;
printf("sizeof(Staff) = %d\n",sizeof(Staff));
printf("sizeof(s) = %d\n",sizeof(s));
printf("&s = %p\n",&s);
printf("&s.sex = %p\n",&s.sex);
printf("&s.age = %p\n",&s.age);
printf("&s.num = %p\n",&s.num);
return 0;
}
终端输出:
sizeof(Staff) = 8
sizeof(s) = 8
&s = 000000000061FE18
&s.sex = 000000000061FE18
&s.age = 000000000061FE1C
&s.num = 000000000061FE1A
还没完
#include<stdio.h>
#pragma pack(1)
typedef struct _staff
{
char sex;
short num; //调换了num和age的顺序
int age;
}Staff; //这只是一个类型而不是变量,所以不占空间
int main(int argc,char *argv[])
{
Staff s;
printf("sizeof(Staff) = %d\n",sizeof(Staff));
printf("sizeof(s) = %d\n",sizeof(s));
printf("&s = %p\n",&s);
printf("&s.sex = %p\n",&s.sex);
printf("&s.age = %p\n",&s.age);
printf("&s.num = %p\n",&s.num);
return 0;
}
终端输出
sizeof(Staff) = 7
sizeof(s) = 7
&s = 000000000061FE19
&s.sex = 000000000061FE19
&s.age = 000000000061FE1C
&s.num = 000000000061FE1A
内存对齐:
什么是内存不对齐
一个成员变量需要多个机器周期去读的现象,称为内存不对齐。,机器周期是指机器一次读取的位数,如32位机器一次读32位也就是4个字节,就那上面的列子来解释:
char sex; 本身占一个字节
int age; 本身占4个字节
用 。 来表示字节数
内存中
。 。 。 。 。 。 。 。
char int
上面是内存对齐,因为机器一次读取4字节数据char和int都只需要读取一次就可以读到
。 。 。 。 。 。
char int
上面是内存不对齐,因为机器在读取 int 时第一次读取4字节后三个字节是有用的,但没有读完整个 int 所以就需要第二次读写,第二次读4个字节只有第一个字节是有用的,最后需要将这两个字节拼接整合成一个int
为什么要内存对齐
本质是牺牲空间换取时间
对齐规则
x86(linux 默认 #pragma pack(4)),windows 默认progam(8)。Linux做大支持4字节对齐
方法:
-
取pack(n)的值 n(n=1 2 4 8…),取结构体中类型最大的值 m,两者取小即为外对齐,大小为 Y
-
将每一个结构体的成员与Y比较取较小者为 x,作为内对奇的大小
-
所谓按 x对齐,即为地址(其实地址为0),能被 x整除的地方开始存放数据
-
外对齐原则是依据 Y的值(Y的最小整数倍),进行补空操作
结构体中使用指针注意
#include<stdio.h>
typedef struct _stu
{
char *name;
int score;
}Stu;
int main(int argc,char *argv[])
{
Stu *ps=malloc(sizeof(Stu));
ps->name=(char *)malloc(100);
strcpy(ps->name,"jimGreen"); //使用strcpy的前提是要满足该地址有足够的空间,如果没有上面的那句,那么程序将崩溃
ps->score=100;
//使用完成后要释放内存
free(ps->name)
free(ps) //申请空间的时候从外至内,释放空间的时候从内之外
}
链表
尾插法
//每次插入都将 t 指向最后一个节点
typedef struct node
{
/* data */
int data;
struct node* next;
}Node;
Node * creatList() //尾插法函数
{
Node *head=(Node*)malloc(sizeof(Node));//分配内存
Node *cur =head;//用于创建
Node *t = head; //用于记录
int stop=1;
while(stop)
{
int nodedata;
scanf("%d",&nodedata);
if(nodedata==0)
stop=0;
cur=(Node *)malloc(sizeof(Node)); //为新的节点申请空间
t->next=cur; //链接这两个空间
t=cur; //移动指针t为保存链表尾部
}
t->next =Null;
return head; //返回头节点以便找到此链表
}
头插法
//让新来的节点有所指向,避免打断原来的指向
typedef struct node
{
/* data */
int data;
struct node* next;
}Node;
Node * creatList() //头插法函数
{
Node *head=(Node*)malloc(sizeof(Node));//分配内存
Node *cur = head;
int data;
while(data)
{
scanf("%d",&data);
cur = (Node*)malloc(sizeof(Node));
cur->next = head->next; //让新来的节点有所指向
head->next = cur;
cur->data = data;
}
return head; //返回头节点以便找到此链表
}
头插法要时刻有指针指向head->next,尾插法要时刻有指针指向链表尾部
插入操作
本质就是头插法,尾插法的话首先要到链表的尾部这耗时严重
删除操作,经过优化的
void deleteNode0fList(Node*head,Node *pfind)//pind是要删除的地址
if(pfind->next == Null);
{
while(head->next != pfind)
head = head->next;
free(head->next);
}
else
//这是用要删除块的下一个块来覆盖要删除的块,然后只要删除要删除的块的下一个块即可
{
Node *t = pfind;
pfind->data = pfind->next->data;
pfind->next = pfind->next->next;
free(t);
}
排序
链表的比较:相邻两个块比较,较容易,类似冒泡排序
交换两个数的帅气写法
arr[j]=arr[j]^arr[j+1];
arr[j+1]=arr[j+1]^arr[j];
arr[j]=arr[j]^arr[j+1];
//arr[j]^arr[j+1](1)^arr[j+1](1)^arr[j]^arr[j+1]
排序
void popSortList(Node *head)
{
int len=lenList(head);
for(int i=0;<len-1;i++)
{
for(int j=0;j<len-i-1;j++)
{
head=head->next;
if(head->data > head->next->data)
{
head=head->next;
head ^=head->next;
head->next ^= head;
head ^= head->next;
}
}
}
}
更快的排序
void popSortList(Node *head)
{
int len=Lenlist(head);
Node * prep,*p,*q,*t;
prep = head;
p = prep->next;
q = p->next;
for(int i = 0;i<len - 1;i++)
{
for(int j = 0;j < len-i-1;;j++ )
{
if(p->data >q->data)
{
prep->next = q;
p->next = q->next;
q->next =p;
t=p;
p=q;
q=t;
}
prep = prep->next;
p = p->next;
q = q->next;
}
}
}
将链表反转
类似于头插法
void reverseLise(Node *head)
{
Node *cur = head->next;
head->next = Null; //切断两个链表
Node *t; // t指向要链接的快
while(cur) //当cur不为空是循环
{
t = cur;
cur = cur->next;
t->next = head->next; //头插法
head->next = t;
}
}
文件操作
文件流的概念
C语言把文件看作是一个字符的序列,即文件是由一个个的字符流,因此C语言将文件也成为文件流。即当独缺一个文件时,可以不关心文件的可是或结构
文件的分类
文本文件:以ASCII吗存放,一个字符一个字符的存放。文本文件的每一个字节存放ASCII码,代表一个字符。便于对字符的逐个处理,但占用的储存空间较多,而且要花费时间转化
二进制文件:以值(补码)编码格式存放。二进制文件时把数据以二进制的格式存放在文件中,占用空间少。数据按内存中的储存形式存放
FILE结构体:
#include <stdio.h>
int main(int argc,char *argv[]);
{
//通过fopen打开一个文件就将这个文件加载到了内存中(不一定能全部加载完),返回一个FILE *指针
FILE *pf = fopen("data.txt","w");
return 0;
}
typedef struct{
short level; //缓冲区满空的标志
unsignde flags; //文件状态标志
char fd; //文件描述符
unsigned char hole;//若无缓冲区不读取字符
short bsize; //缓冲区大小
unsigned char *buffer;//数据传送缓冲区位置
unsigned char *curp; //当前读写位置
unsigned istemp; //临时文件指示
short token; //用作无效检查
}FILE;
当开始执行程序的时候,将自动打开3个文件和相关的流:标准输入流(stdin),标准输出流(stdout),标准错误流(stderr),他们都是FILE*型的指针。流提供了文件和程序的通讯通道
相关函数
fopen()函数
函数声明:
File *open(const char* file name,const char* mode)
mode:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FbjF9LEF-1649308579954)(E:\博客\photo\14-4FILE与fopen.pcwlenv_视频截图_870.jpg)]
r 以只读的形式打开,如果文件不存在,则报错,文件不可写入
w 如果文件不存在,则创建,如果文件存在,则清空,文件不可读
a 如果文件不存在则创建,存在不会清空,文件可写,写入的会追加到尾部,文件不可读
如果是二进制文件,则还要加上 b 比如 rb,r+b等。unix/linux不区分文本文件和二进制文件
fclose()函数
fclose(文件指针变量)
功能:释放此文件指针指向的结构体,刷新缓存,关闭文件
fputc()函数
函数声明:int fputc(char ch,FILE *stream)
功能:将 ch 字符写入文件流中
返回值:写入成功返回写入的字符数,如果失败返回EOF
fgetc()函数
C 库函数 int fgetc(FILE *stream) 从指定的流 stream 获取下一个字符(一个无符号字符),并把位置标识符往前移动。
声明:int fgetc(FILE *stream)
参数:stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了要在上面执行操作的流。
返回值:该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF
用法
常常用循环来去文件中的字符
while((int ch = fputc(fp)) != NULL) //赋值运算优先级低于关系运算
{
putch(ch);
}
feof()函数
函数声明: int feof (FILE * steam)
功能:判断文件是否读到文件的结尾
返回值:判断文件是读到了文件结尾 0 表示未读到结尾 1表示读到结尾
少用
fputs()函数
声明:int fputs(char *str,FILE *fp)
功能:将 str 指向的字符串写入 fp 所指向的文件中,遇到\0 结束
返回值:正常返回 0 ;出错返回 EOF
fgets()函数
声明:char *fgets(char *str, int n, FILE *stream)
功能:将字符串 str中的n个写入文件流stream中
注意:遇到 \n 读取结束并连 \n 一并读出
在读取n个字符时及没有遇到 \n 也没有EOF,此时只能读n-1个字符,因为最后一个字符被 \0替代
遇到EOF时结束,不读EOF
rewind()函数,将FILE中的flag指向文件开头
一些套路操作
#include <stdio.h>
//定义一个宏函数,‘\’这个是连接符,因为在宏定义是不可以换行的
#define F_PRINT_ERR(e)\
if(e == NULL);\
{\
printf("openerr");\
exit(-1);\
}\
int main()
{
FILE *fp = fopen("xx.txt","w+");
F_PRINT_ERR(fp);
}
C语言已经从接口层面区分了,文本读写的方式和二进制读写的方式。前面都是文本的读写方式
所有文件接口函数,要么以’\0’,表示输入结束,要么以 ‘\n’ EOF(0xFF) 表示读取结束。 ‘\0’ '\n’等都是文本文件的主要标志,而二进制文件,则往往以块的形式写入或读出。
对二进制文件读写
fread() / fwrite()
函数声明:int fwrite(void *buffer,int num_bytes,int count,FILE *fp)
int fread(void *buffer,int num_bytes,int count,FILE *fp)
功能:将buffer指向的数据写入 fp 所指向的文案中,或把fp所指向的文件中的数据读到buffer中
参数:
char * buffer :指向要输入输出数据的存储区的首地址的指针
int num_bytes:每个要读写的字段的字节数
int count :要读写的字段的个数
FILE *fp:要读写的文件指针
返回值: int 成功返回读/写的字段数;出错或文件结尾返回 0
这种读写是在字节层面上的,就是内存中是什么就读写什么,当人这就方便了字符的读写,因为字符在文本格式和二进制模式下都是ASCII码的形式,但是数字就不同了,文本是将数字分成多个部分,每部分用ASCII表示,而二进制是用补码的形式来表示
#include<stdio.h>
int main(int argc,char *argv[])
{
FILE *fp = fopen("gg.txt","w+");
char arr[10] = {'a','v','l','u','b','n','a','t','r','p'};
fwrite((void*)arr,1,10,fp);
char buf[10];
rewind(fp); //将fp结构体内flag指向文件头部
fread((void*)buf,1,10,fp);
for(int i=0;i<10;i++)
{
printf("%c\n",buf[i]);
}
return 0;
}
返回值陷阱
fread()没有 \n EOF len - 1 作为读写结束的标志,fread依靠读出多少块来标识读结果和文件结束标志,这里指的块是完整的块
int fread(void *buffer,int num_bytes,int count,FILE *fp)这里的 count 指的是可以读到的最大块数,而numo_bytes指的是每次读的字节数,而返回的是读到的完整块数
#include<stdio.h>
int main(int argc,char *argv[])
{
char buf[1024];
FILE *pf = fopen("mm.tst","w+");
fputs("i love you",pf);
rewind(pf);
int n = fread((void*)buf,3,10,pf);
printf("%d\n",n);
for( int i=0;i<10;i++)
{
printf("%c",buf[i]);
}
return 0;
}
终端输出
3
i love you
这种写法是不利于循环的,优化方法就是令每次读写的字节数与要读取的sizeof()所得的值相同,这样就可以使当返回值为 0 时程序就刚好读完。从而使效率最大化
vscode配置:
基于 VS Code + MinGW-w64 的C语言/C++简单环境配置,专致小白 - 知乎 (zhihu.com)
%c\n",buf[i]);
}
return 0;
}
#### 返回值陷阱
fread()没有 \n EOF len - 1 作为读写结束的标志,fread依靠读出多少块来标识读结果和文件结束标志,这里指的块是完整的块
int fread(void *buffer,int num_bytes,int count,FILE *fp)这里的 count 指的是可以读到的最大块数,而numo_bytes指的是每次读的字节数,而返回的是读到的完整块数
#include<stdio.h>
int main(int argc,char *argv[])
{
char buf[1024];
FILE pf = fopen(“mm.tst”,“w+”);
fputs(“i love you”,pf);
rewind(pf);
int n = fread((void)buf,3,10,pf);
printf(“%d\n”,n);
for( int i=0;i<10;i++)
{
printf(“%c”,buf[i]);
}
return 0;
}
终端输出
3
i love you
这种写法是不利于循环的,优化方法就是令每次读写的字节数与要读取的sizeof()所得的值相同,这样就可以使当返回值为 0 时程序就刚好读完。从而使效率最大化
vscode配置:
[基于 VS Code + MinGW-w64 的C语言/C++简单环境配置,专致小白 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/77074009)