UC代码
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
字符串练习
------------------------------------------------------------------------- //字符串相关的基本操作
int main(){
//1 赋值,=改变的是指向的地址,strcpy()改地址的值char* s1 = "abc";//s1 指向只读区
char s2[10] = "abc";
//s1 = "123"; //改变s1的指向,行
//strcpy(s1,"123");//改变只读区的值,不行
//s2 = "123";//数组是常指针,不能改指向
strcpy(s2,"123");//改变s2的值,不改指向
printf("%s\n",s2);
char* ps = malloc(4);//ps指向堆区
//ps = "abc";//不行,ps指向了只读区,内存已经泄露strcpy(ps,"abc");//行,改值可以
printf("pi = %s\n",ps);
free(ps);
//2 字符串的长度和大小strlen()取长度(不算\0)
char s3[10] = "abc";
char* s4 = "abcdef";
printf("sizeof(s3)=%d\n",sizeof(s3));
printf("sizeof(s4)=%d\n",sizeof(s4));
printf("s3 length=%d\n",strlen(s3));
printf("s4 length=%d\n",strlen(s4));
//3 字符串的比较相等== 和strcmp()的区别
char *s5/*[]*/ = "abc";//每个字符串字面值只存一份char *s6/*[]*/ = "abc";
printf("%d\n",s5 == s6);//1代表相等,比地址
printf("%d\n",strcmp(s5,s6));//0代表相等,比值
//4 字符串的拼接strcpy() strcat() sprintf()
char *s7 = "zhangfei";
char *s8 = "feifei123";
char buf[40] = { };
/*strcpy(buf,s7);
strcat(buf,",");
strcat(buf,s8);*/
sprintf(buf,"%s,%s",s7,s8);//打印到字符串中
printf("buf=%s\n",buf);
//5 用指针的方式操作字符串
char *s9 = "abcccabbaaca";//判断有几个a (5)
int count = 0;
while(*s9){
if(*s9 == 'a') count++;
s9++; }
printf("count=%d\n",count);
char name[20] = { };//拆分buf成用户名和密码
char passwd[20] = {};
int pos = 0;
while(*(buf+pos)!=',') pos++;
memcpy(name,buf,pos);
strcpy(passwd,buf+pos+1);
//sscanf(buf,"%s,%s",name,passwd);//部分有效
printf("name=%s:passwd=%s\n",name,passwd);
//6 字符串和其他类型的转换(int/double) sprintf()
int x = 10; //sscanf()
char buf1[12] = {};
sprintf(buf1,"%d",x);//其他类型-> 字符串
printf("buf1=%s\n",buf1);
int y = 0;
sscanf(buf1,"%d",&y);//字符串-> 其他类型
printf("%d\n",y);
}
-------------------------------------------------------------------------
内存管理
进程的内存空间划分为以下部分:
1 代码区- 用于存放代码(函数)的区域,是只读区。
函数指针就是指向代码区的地址。
2 全局区- 用于存放全局变量(定义在函数外的变量)和static的局部变量的区域。
3 BSS段- 用于存放未初始化的全局变量的区域。
BSS段在main函数执行之前,会清0。
4 栈区(堆栈区stack) - 用于存放局部变量的区域,包括:函数的参数和非static的局部变量。
系统自动管理栈区内存。
5 堆区(heap) - 也叫自由区,程序员唯一可以控制的区域。
通常内存分配、回收都是在堆区。
malloc()、free()都是在堆区。
堆区的内存系统完全不管,程序员全权管理堆区内存。
6 只读常量区- 存放字符串字面值(""括起来的字符串)和const修饰的全局变量。
这个区域也是只读区,很多书都是把只读常量区并入代码区。
进程的内存空间排列次序:(地址从小到大)
代码区、只读常量区、全局区、BSS段、堆、 ....... 栈
内存管理的相关函数
STL -> 全自动管理(C++后期)
|
C++ -> new分配,delete回收内存
|
C语言-> malloc()分配,free()回收内存
|
Unix系统函数-> sbrk() / brk() 分配和回收内存
|
Unix系统函数-> mmap()映射物理内存/硬盘文件
munmap()解除映射
(用户层函数到此为止)
malloc() -> 传入分配内存的大小(字节),返回分配内存的首地址。
内存分配的函数干两件事情:
分配虚拟内存地址- 所有情况下
映射物理内存/硬盘文件- 第一次映射,后面用完了再映射
malloc()申请小块内存时一次映射33个内存页,用完后继续映射。
申请大块内存时(超过31个内存页) 会映射比申请稍多一点的内存页。
malloc()函数除了分配正常的内存空间,还需要额外开辟一些空间,用于存储一些附加信息,比如分配的大小。
存在前面4个字节。
free()函数- 释放内存
free() 一定会释放虚拟内存地址,以便虚拟内存地址可以循环利用,不一定解除映射。
超过33个内存页的部分会释放,最后33个内存页不释放,直到进程结束时释放。
-------------------------------------------------------------------------
malloc.c
int main(){
int a,b,c,d;//栈
printf("a=%p,b=%p,c=%p,d=%p\n",&a,&b,&c,&d);
int* p1 = malloc(4);//分配虚拟内存,映射33个内存页
int* p2 = malloc(4);//分配虚拟内存,不映射
int* p3 = malloc(4);
printf("p1=%p,p2=%p,p3=%p\n",p1,p2,p3);
//*(p1-1) = 0;//清前4个字节的内容
*(p1+/*33*1024*/100) = 10;//未分配的内存(已映射) printf("%d\n",*(p1+100));
*p1 = 100;
free(p3); free(p2); free(p1);
int* p4 = malloc(4);
printf("p4=%p,p4=%d\n",p4,*p4);
printf("pid=%d\n",getpid());
while(1);
//free(p1);
return 0;
}
-------------------------------------------------------------------------
sbrk/brk
void* sbrk(int increment)
参数是增量
增量为正数时,分配内存
增量为负数时,回收内存
增量为0时,取当前的位置
返回移动之前的位置(可用内存的首地址),这个返回值对于增量为负数的情况没有意义。
开发中,一般用sbrk()分配内存,用brk()回收内存。
brk()的使用方式就是直接传递一个地址过来,做新的位置。
brk()必须和sbrk()结合使用,获得第一个位置。
brk.c
int main(){
void* p = sbrk(0);
brk(p+4);//分配4字节
brk(p+8);//再次分配4字节
brk(p+4);//回收4字节
int* p1 = p; *p1 = 100;
double* p2 = sbrk(0);
brk(p2+1);//+1 移动8 个字节
*p2 = 1.0;
char* p3 = sbrk(0); brk(p3+10);
strcpy(p3,"zhangfei"); //正确
printf("%d,%lf,%s\n",*p1,*p2,p3);
brk(p3); brk(p2); brk(p1);
}
sbrk.c
int main(){
printf("pid=%d\n",getpid());
void* p1 = sbrk(4);//返回00,位置在04,映射1页
if(p1 == (void*)-1) perror("sbrk"),exit(-1);
printf("p1=%p\n",p1);
int* pi1 = p1;//类型转换
int* pi2 = sbrk(4);//返回04 位置在08 不做映射
int* pi3 = sbrk(4);//返回08 位置在12
printf("pi2=%p,pi3=%p\n",pi2,pi3);
sbrk(-4);//返回12 位置08 回收内存
sbrk(-4);//位置04
int* pi4 = sbrk(0); printf("pi4=%p\n",pi4);
sbrk(4093);//4093+4=4097 超了一页,映射2页
sleep(10); printf("释放一个字节\n");
sbrk(-1);//回到一页,立即回收
sleep(10);
sbrk(-4096); printf("全部释放\n");//全部释放
while(1);
}
sbrkbrk.c
int main(){
int* p1 = sbrk(4);
*p1 = 100;
double* p2 = sbrk(8);
*p2 = 1.0;
char* p3 = sbrk(10);
strcpy(p3,"zhangfei");
printf("%d,%lf,%s\n",*p1,*p2,p3);
brk(p3); brk(p2); brk(p1);
}
------------------------------------------------------------------------- void* mmap(void* addr,size_t size,int prot,int flags,
int fd, off_t offset)
参数addr可以指定映射的首地址,一般为0 交给内核指定。
size 就是分配内存的大小,映射时以页为单位。
prot是分配内存的权限,一般用PROT_READ | PROT_WRITE
flags是标识,通常包括以下三个:
MAP_SHARED MAP_PRIV ATE :二选一,指明映射的内存是否共享,MAP_SHARED只对映射文件有效。
MAP_ANONYMOUS :映射物理内存,默认映射文件。
fd是文件描述符,在映射文件时有用。
offset是文件的偏移量,指定映射文件时从哪里开始。
映射物理内存时,fd和offset 给0 即可。
返回成功返回首地址,失败返回MAP_FAILED == (void*)-1
mmap.c
int main(){
void* p = mmap(NULL,//设定首地址,NULL交给系统
4,//分配大小,映射时按内存页映射,不足的会补足
PROT_READ|PROT_WRITE,//权限
MAP_PRIV A TE|MAP_ANONYMOUS,//私有+映射物理内存
0,0);//映射物理内存,后两个参数0 即可
if(p == (void*)-1) perror("mmap"),exit(-1);
int* pi = p;
*pi = 100;
printf("*pi=%d\n",*pi);
munmap(p,4);//解除映射
return 0;
}
-------------------------------------------------------------------------
文件操作
int open(char* filename,int flags, ...)
参数filename 就是文件名(包括路径)
flags 是打开的标识,主要包括:
O_RDONL Y O_WRONLY O_RDWR : 打开的权限,三个必选其一。
O_CREAT :创建标识文件存在就打开,不存在就新建O_TRUNC :打开文件时自动清空文件(open是清空)
O_EXCL :文件不存在新建,存在就返回-1代表出错
上面两个一般都是和O_CREAT结合使用
O_APPEND :采用追加的方式打开文件,一般用于写。
参数... 代表0-n个任意类型的参数,open()函数的第三个参数只在新建文件时有用,传入文件在硬盘上的权限。
返回文件描述符,失败返回-1.
write.c
struct emp{ int id; char name[20]; double sal; };
int main(){
int fd = open("emp2.dat",O_RDWR|O_CREAT|O_APPEND, 0666);
if(fd == -1) perror("open"),exit(-1);
struct emp em;
printf("请输入员工的id,姓名和工资\n");
scanf("%d%s%lf",&em.id,,&em.sal);
char buf[50] = {};
sprintf(buf,"%d,%s,%lf",em.id,,em.sal);
int res = write(fd,buf,strlen(buf));
if(res == -1) perror("write"),exit(-1);
else printf("增加员工成功\n");
close(fd);
}
read.c
int main(){
int fd = open("emp.dat",O_RDONL Y);
if(fd == -1) perror("open"),exit(-1);
struct emp em;
while(1){
int res = read(fd,&em,sizeof(em));
if(!res) break;
if(res == -1) perror("read"),exit(-1);
printf("%d,%s,%lf\n",em.id,,em.sal);
}
close(fd);
}
------------------------------------------------------------------------- lseek() - 类似fseek(),是fseek()的底层调用。
off_t lseek(int fd, off_t offset,int whence)
参数:fd 文件描述符
offset 是偏移量
whence 是偏移的起始位置,有三个值:
SEEK_SET - 从文件头开始计算偏移
SEEK_CUR - 从当前位置计算偏移
SEEK_END - 从文件尾计算偏移
返回: 移动以后的位置和文件头的距离,失败返回-1
使用vi时,大多数情况下wq 会自动加一个vi结束符,因此会多一个字节。
lseek.c
int main(){
int fd = open("a.txt",O_RDWR);
if(fd == -1) perror("open"),exit(-1);
char ch;
read(fd,&ch,1);printf("%c\n",ch);//a
read(fd,&ch,1);printf("%c\n",ch);//b
lseek(fd,0,SEEK_SET);//回到文件头
read(fd,&ch,1);printf("%c\n",ch);//a
lseek(fd,2,SEEK_CUR);
read(fd,&ch,1);printf("%c\n",ch);//d
lseek(fd,3,SEEK_SET);
read(fd,&ch,1);printf("%c\n",ch);//d
lseek(fd,0,SEEK_SET); write(fd,"1",1);//a -> 1
lseek(fd,2,SEEK_CUR); write(fd,"2",1);//d -> 2
lseek(fd,-2,SEEK_CUR); write(fd,"3",1);//c -> 3
lseek(fd,2,SEEK_CUR); write(fd,"4",1);//f -> 4
lseek(fd,-2,SEEK_END); write(fd,"5",1);//t -> 5
int length = lseek(fd,0,SEEK_END);//取文件大小
printf("文件大小=%d\n",length);
close(fd);
}
------------------------------------------------------------------------- dup()和dup2() - 非重点,复制文件描述符
dup家族复制文件描述符时,不会复制文件表。
出现多个描述符对应同一张文件表的情况。
dup()是系统选择新的描述符的值(未使用的最小值) dup2()是程序员指定新的描述符的值,如果指定的值已经被使用,会强制关闭后继续使用。
注: close()函数用于关闭文件描述符,不一定关闭文件表。
close()其实就是在描述符总表中移除对应关系,文件表的计数会减1,当文件表的计数到0时,文件表就可以被回收。
dup.c
int main(){
int fd = open("a.txt",O_RDWR);//3
if(fd == -1) perror("open"),exit(-1);
int fd2 = dup(fd);//4
int fd3 = dup2(fd,6);//6
int
fd4=open("b.txt",O_RDWR|O_CREAT|O_TRUNC,0666);
if(fd4 == -1) perror("open b"),exit(-1);
printf("fd=%d,fd2=%d,fd3=%d,fd4=%d\n",
fd,fd2,fd3,fd4);
write(fd,"A",1);
int fd5 = dup2(fd,5);
write(fd4/*5*/,"HAHA",4);//描述符其实用的是整数close(fd);
}
------------------------------------------------------------------------- fcntl() 函数- 提供一些文件控制的功能,包括:文件描述符的复制、设置或者获取文件描述符的状态、文件锁
int fcntl(int fd,int cmd,...);
参数fd就是文件描述符,cmd是命令,...代表0-n个任意类型的参数。
返回和cmd 的选择有关。
cmd 主要包括:
F_DUPFD - 复制文件描述符,需要传入第三个参数做新的描述符的值,如果这个值已经被使用,找大于该值的未使用的最小值。
F_SETFL/F_GETFL - 设置/获取文件描述符的状态,SETFL基本用不到,因为只能修改O_APPEND; GETFL不需要第三个参数,可以获取权限和O_APPEND,创建标识无法获取。
F_SETLK/F_SETLKW - 设置文件锁
fcntl.c
int main(){
int fd = open("a.txt",O_RDONL Y|O_CREAT|O_APPEND, 0666);//O_RDONL Y是0
if(fd == -1) perror("open"),exit(-1);
int fd2 = fcntl(fd,F_DUPFD,5); //fd2 返回5
int fd3 = fcntl(fd,F_DUPFD,5);//fd3 返回6(大于5的) printf("fd=%d,fd2=%d,fd3=%d\n",fd,fd2,fd3);
int flag = fcntl(fd,F_GETFL);//获取描述符状态
printf("flag=%d\n",flag);//1026
if(flag & O_APPEND) printf("有APPEND\n");
if(flag & O_CREAT)printf("有CREAT\n");//有也拿不到if((flag & 3) == O_RDWR) printf("权限RDWR\n");
if((flag & 3) == O_RDONL Y) printf("权限RDONL Y\n"); if((flag & 3) == O_WRONL Y) printf("权限WRONL Y\n");
close(fd); close(fd2); close(fd3);
}
------------------------------------------------------------------------- 文件锁的使用是一个函数+ 一个结构:
fcntl() 、结构是struct flock。
fcntl(int fd,F_SETLK/F_SETLKW,struct flock*);
struct flock{
short l_type;//锁的类型,包括读锁、写锁和释放锁
short l_whence;//锁定起始点的参考位置
off_t l_start ; //锁定起始点的偏移量
off_t l_len;//锁定区域的大小,锁定的长度
pid_t l_pid;//锁定进程的PID,只有GETLK用的到,给-1 即可。
};
l_type 锁的类型:F_RDLCK / F_WRLCK / F_UNLCK
l_whence的值:SEEK_SET / SEEK_END / SEEK_CUR 注:文件锁不是锁定整个文件,而是锁定文件的一部分。
锁定的部分由l_whence/l_start和l_len 三个联合决定。
比如:l_whence选SEEK_SET,l_start = 10, l_len=20;锁定区域就是:第11个到第30个(文件头偏移10,锁定20个字节)
文件锁其实并不能锁定读写的函数read()/write(),只能阻止其他进程的加锁行为,导致其他进程加不上锁。
因此文件锁的正确用法是:在调用read()之前应该加上一个读锁,在调用write()之前加一个写锁,读写完毕以后记得马上释放锁。
锁定的方式有两种,一种是F_SETLK,锁定失败立即返回返回-1;另一种是F_SETLKW,锁不上就一直等待,等到别人释放锁以后继续上锁。
F_GETLK 不是获得锁,是测试一个锁能不能加上,不会真的加锁。
locka.c
int main(){
int fd = open("a.txt",O_RDWR);
if(fd == -1) perror("open"),exit(-1);
struct flock lock;
lock.l_type = F_RDLCK;//锁的类型,改一下测试下lock.l_whence = SEEK_SET;
lock.l_start = 10;
lock.l_len = 20;
lock.l_pid = -1;//pid确定不被使用
int res = fcntl(fd,F_SETLK,&lock);//上锁
if(res == -1) printf("上锁失败\n"),exit(-1);
else printf("锁定成功,开始读文件\n");
char buf[50] = {};
read(fd,buf,sizeof(buf));
sleep(20);//lock2.c不用sleep()
printf("读完了,释放锁\n");
lock.l_type = F_UNLCK;
fcntl(fd,F_SETLK,&lock);//释放锁
sleep(10);//进程继续做其他的事情
close(fd);
}
lockb.c
int main(){
int fd = open("a.txt",O_RDWR);
if(fd == -1) perror("open"),exit(-1);
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 10;
lock.l_len = 20;
lock.l_pid = -1;//pid确定不被使用
int res = fcntl(fd,F_SETLKW,&lock);//上锁
if(res == -1) {
printf("上锁失败\n");
}else {//只有上锁成功才能读写
printf("锁定成功\n");
write(fd,"zhangfeifeifeifei",17);
}
------------------------------------------------------------------------- i节点可以看成文件/目录在硬盘上的地址,ls -i 可以查看i节点号。
int stat(char* filename,struct stat* file)
filename就是文件名
file是结构体指针,用于传出文件的相关信息。
struct stat 记录了文件的各种信息,效果等同于ls -l。
其中
st_size 必须会,取文件的大小。
其它的看手册即可。
函数access()能更方便的获取文件的权限,并且能判断文件是否存在。
access(文件名,模式)
模式包括四个宏:
R_OK 是否有读权限
W_OK 是否有写权限
X_OK 是否有执行权限
F_OK 文件是否存在
如果有权限返回0,取的是当前用户的权限。
其他的一些常用函数:
chmod() - 修改文件的权限
truncate() - 指定文件的大小
remove() - 删除文件或者空目录
rename() - 文件或者目录改名
stat.c
int main(){
struct stat st;
stat("a.txt",&st);//获取了a.txt的各种信息
printf("inode=%d\n",st.st_ino);//i节点
printf("mode=%o\n",st.st_mode);//权限+文件类型
printf("nlink=%d\n",st.st_nlink);//硬连接数
printf("size=%d\n",st.st_size);//文件大小
printf("time=%s\n",ctime(&st.st_mtime));//时间
if(S_ISREG(st.st_mode)) printf("是普通文件\n");
if(S_ISDIR(st.st_mode)) printf("是目录\n");
printf("权限:%o\n",st.st_mode & 07777);
if(!access("a.txt",R_OK)) printf("有读权限\n");
if(!access("a.txt",F_OK)) printf("文件存在\n");
}//8进制和7做位与结果不变
------------------------------------------------------------------------- C语言关于时间有两种类型:
time_t 秒差记录时间(和1970年1月1日0点0分0秒的秒差)
struct tm 年月日小时分秒的格式
函数localtime() 可以实现秒差转结构功能。
ctime() 可以用美国时间的格式把秒差转成时间字符串
-------------------------------------------------------------------------。