将原学习笔记记录下,以备后续查询:
//8.12
1.LINUX标识符命名:主要是以下划线作为分隔符==》内核中
2.语言发展史:机器语言,汇编语言,高级语言,第四代语言,第五代语言(自然语言==》智能语言)
3.编译型语言==自己编译;
4.虚拟内存==》mmu==》物理内存
5.在32位的机器中,虚拟内存为4G,其中0-3G分配为usr,3-4G分配为kernel
(1)在usr内存范围中,分为code,data ,heap,mmap,stack,命令行参数,环境变量
(2)代码段中,主要是只读的区域,其在开辟的时候分配,在结束时释放;
(3)数据段存放全局变量(包含普通和静态的变量)生存周期同代码段,静态的全局变量生存期是整个程序运行过程,并且只能在本源程序中生存,还分为已初始化区和未初始化区BSS区;
(4)堆中存放程序员手动申请的内存,需要手动释放;
(5)mmap是存放的映射关系;
(6)栈中存放普通的局部变量以及函数参数;
6.如果需要查看不可执行的二进制文件,可以使用nm查看,即查看汇编代码;
7.一个八进制位代表3个二进制位;例07=》111
8.一个十六进制代表4个二进制位;例0x8==》0100
9.嵌入式极其重要的字符串:volatile(防优化)、const、extern、sizeof、static、typedef、void
//8.13
1.字符串常量存放在字符串常量区,只读,不可更改,自动加结束符'\0';
2.转义字符是用来输出有冲突的字符(在系统里面有其他的意思);
3.如果宏过于长,可以用\来连接;
4.static
(1)修饰局部变量,修改其生存期,直到程序结束才会释放,每次调用函数,初值为上次调用后的值;
(2)修饰全局变量,修改其作用域,只能在本c源程序中使用;
(3)修饰函数,修饰其作用域,也只能在本c源程序中使用;
5.extrern
用于扩展变量或者函数的作用域,一般函数是默认外部函数;
6.隐形转换:主要是无符号与有符号的加减;而强制转换并不改变原值,大多用于指针;
7.逻辑运算时,注意短路效应;&&只要一个为假,后一个即不执行;||只要一个为真,后一个即不执行;
8.各数据类型的打印格式%d,%u,%ld,%lu等;
9.运算符的使用;一般不使用复杂级,可以使用小括号优先;
//8.14
1.预处理宏#ifndef...#endif用于多文件编程,防止头文件重复包含、重复定义;
2.系统头文件与自定义头文件的使用;
3.位运算:&、|、^ 、<<、>>
(1)位运算一般是用于内核操作中;register(寄存器)移位不存在符号问题(即算术移位),都是逻辑移位;
(2)乘除法也可以转换为左右移位操作,一个数乘以2的n次方即左移n位;除以2的n次方即右移n位;
(3)对任意位置1,可以采用a | (0x1<<n-1) 对第n位置1即左移n-1位;也可以采用相应的右移;(位数从1起算)
(4)对任意位置0,可以采用a & ~(0x1<<n-1) 对第n位置0即左移n-1位;
(5)在对几位同时置1时,首先将需要改变的位表示为2进制,进而换成16进制,左移0位,则原二进制不用减1;
移动n位,则原二进制需要依次减几位在转换成16进制;
(6)可以采用位^(异或)对2个数进行交换(不采取中间变量);
(7)位域:主要用于结构体中,即可以使用几位,可以限制取值的最大值,如果超过最大值,则取低位
例:int a:3;如果给a赋值为8(即00001000),超过3位的最大值7(111),取低3位则为0;位域成员类型为int
unsigned int 类型,宽度应小于位长;
4.逗号运算:从左往右开始计算,最后一个值置逗号运算符的终值;
5.条件运算符在值判断时效率是高于if...else的
6.sizeof()与初始化没有关系(开辟内存字节大小),strlen()跟初始化是有关系的(元素个数)
7.代码风格:空格、注释等的使用,变量命名规则(形容词+名词),函数命名规则(动词+名词);
8.在判断比较中,布尔类型的变量不用与TRUE、FALSE或者0、1等比较,例:直接if(flag)即可==》标准规范;
9.整型变量直接用==或者!=与数值比较,例if(i==1)等;
10.浮点类型变量用<=、>=在一个区间进行比较,例:if(x>=-0.001&&x<=0.001)
11.指针变量直接与NULL比较;
12.在循环中,for效率最高,while其次,do...while();最低;多重循环中,内外层的处理,以及存在条件判断时,
这个关系着循环效率的问题;循环尽量采用半开半闭的形式;需要慎用goto语句(一般在内核驱动中常用来做出错处理)
13.对外公开的常量放头文件;不对外公开常量放文件头部==》全局
14.常量:const(类型检查,可以防修改)、define宏以及枚举类型的使用,枚举类型是在编译时就求值了,内只能放整型;
15.函数出入口要进行有效性检查,例指针是否为NULL;
16.return语句不可返回指向栈内存的指针或引用,因为栈内存在函数结束即释放了;
17.内存问题:
(1)动态分配内存,查看是否分配成功(ifp==NULL),并且要初始化,否则可能乱码;注意越界使用内存问题;
(2)堆上内存释放;避免内存泄漏;释放后将原指针设为NULL,防止野指针(可能指向其他的内存);
(3)数组名对应一块内存,而不是指向一块内存,为常指针;
(4)数组名作为参数传递时,自动退化为同类型的指针,只占4个字节;
(5)防止内存耗尽,在指针为NULL时,使用return返回或exit(1)终止;
18.continue、break、循环与switch内嵌的使用;
//8.15
1.输入函数
(1)scanf()对各种数据类型的输入,不能接收空格,输入错误则返回0值(常用来判断输入的有效性);
(2)getchar()输入单个字符,无参数;
(3)fgetc()从文件中读取一个字符,参数为一个文件类型的指针,返回值为读取到的字符,若返回EOF表示到了文件尾;
(4)gets()输入一个字符串,是危险的可能会越界造成内存泄漏,可以接收空格,以回车作为结束符;
(5)fgets()安全的输入字符串,例fgets(str,n,stdin)从标准输入到str开头的地址n-1个字节,
成功则返回该指针,返回NULL表示错误,也可用于从文件中输入数据;
2.输出函数
(1)printf()输出各数据类型的数据,用于打印调试;
(2)puts()输出字符串,以'\0'为结束标志;成功0,错误-1
(3)putchar()输出单个字符;成功则返回该字符,否则EOF
(4)fputs()向文件写入字符串,例:fputs(char *s,FILE *t);写入成功返回字符个数,否则返回EOF
(5)fputc()向文件写入单个字符;例fputc(char c,FILE *t);写入成功返回字符c,否则返回EOF
(6)sprintf()将原打印在屏幕上的数据打印在制定的内存空间,有可能越界使用内存;
3.在打印数据时,例如八进制、十六进制显示前缀,可以加上#符号,%#x
4.在各类型数据溢出其数值范围时,会回到起点值,超出最大值即返回最小值的起点,超出最小值即返回最大值的起点;
5.在输入数据格式不匹配时,可以采用%*c吃掉一个字符,%*s吃掉一个字符串,例scanf("%*s"),或者使用setbuf(stdin,NULL)清空缓存;
6.打印指针格式用%p,指定数据宽度、小数点位数、以及打印字符串中几个字符格式的使用,例%m.nf、%md、%.ns
7.用输入输出函数返回值来判断是否成功输入或者输出;
//8.16
1.对数组清0的函数,memset(a,0,sizeof(a))以及升级函数bzero(a,sizeof(a));可以用于对数组的初始化;
2.数组在内存中存储是线性一维的;数组未初始化,内容为随机的乱码;数组名为一个地址常量;
3.在控制开关中,也可以使用通过判断一个字符串的(用字符数组表示)首字符来进行判断;
4.零长数组,int a[0],主要用于变长结构体中,在结构体最后声明,并不占用内存空间,它只是一个偏移量,可以在需要时动态开辟内存(使用一个结构体指针开辟);
增加的字节宽度自动对应零长数组;
5.数组名不是指针,不能指向其他的内存,本身值并不能改变;
6.数组负索引, array[-1],使用时应从下标1开始,到n结束(若有n个元素);
7.用sizeof()计算数组元素个数的方法,sizeof(a)/sizeof(a[0])一维数组元素的个数;sizeof(a[0])/sizeof(a[0][0])二维数组元素的个数;
//8.18
1.宏参数创建字符串:#
加上#参数可以打印出变量名,可以用在打印格式中;
2.预处理器的粘合剂:##
功能:可以将2个语言符号组合成1个语言符号;
3.可变宏:...和--VA-ARGS--
//8.19
1.数组一旦开辟,就与作为元素个数下标的变量值无关系;
2.int a[2][3][4]可以看作是int a[2]每个元素内含3个元素,这3个元素每个元素又含4个元素;
3.多维数组元素可以用一维形式打印(因为内存是一维线性的);
4.字符串处理函数:strcpy()、strncpy()、strcat()、strcmp()、strncat()、memcpy()在移位操作中很有效,注意其返回值可以用来判断是否成功;
//8.20
1.指针:
(1)地址(2)操作方式(内存)
2.字节顺序==》大小端模式;int a=0x12345678,char b=*((char *)(&a)),取得低地址的字节,b=0x78即为小端模式,b=0x12即为大端模式;
3.指针p,则p+n表示p+n*该指针类型的字节宽度(操作方式),整型即4,字符型即1;
4.数组名代表一个常指针,指向第一个元素的地址(多维数组一个道理);
5.对数组名取地址,&a,地址值是没有变的,是指向整个数组,操作方式就是1个数组的宽度,注意内存操作方式;
6.如果对绝对地址0x1000赋值,可以使用*((unsigned int *)0x1000)=1234,即给它指定一种内存操作方式,以多少内存字节宽度操作;
//8.21
1.指针与二维数组及多维数组的操作,参考一维数组与指针;
2.如果字符串常量相同,在字符串常量区只维持一份内存,(指向同一字符串的指针值是一样的,即存的地址是一样的);
IO笔记:
一、标准IO:
1.提供三种类型的缓存:
全缓存(可以使用fflush函数刷新流);
行缓存(遇到换行符'\n'刷新流);
不带缓存(例如stderr,一旦错误就打印,strerror()以字符串形式打印,perror());
2.流处理函数
setbuf,例如setbuf(stdin,NULL)清空输入流,setvbuf可以更改缓存类型;
0==STDIN_FILENO==stdin;行缓冲
1==STDOUT_FILENO==stdout;行缓冲
2==STDERR_FILENO==stderr;无缓冲
fprintf()重定向至文件流;
sprintf()重定向至字符串流;
每次一个字符的读写fgetc,getc,getchar(),fputc;返回值为读写到的字符个数;
fgets如果超过指定字节,下次会继续从此处读,可以使用后清空输入流;gets则不会保留新行符,但可能越界;
每次一行读写fgets,fputs,putc,putchar();返回值为地址,错误为NULL;gets不安全的读入,puts输出字符串;
feof()判断是否读到文件末尾;ferror()检查是否是错误流;clearerr()清空错误流;
二进制:fopen,fread,fwrite,fclose;
3.标准IO定位流
ftell()获得当前流指针位置;
fseek()设置流指针位置;rewind()流指针移动到头位置;
移动到非unix系统使用fgetpos(),fsetpos()移动流指针位置;
创建临时文件:tmpnam(),tmpfile();
4.freopen()打开时重定向文件流;
5.fdopen()打开指定权限的文件描述符,转换为流指针;
6.fileno()将流指针转换为文件描述符;
标准IO调用文件IO
二、文件IO:
1.文件IO不用缓存,全局变量errno,错误码;
2.函数open(),read(),write(),close(),lseek();返回值的使用;
三、文件和目录
1.通过文件名、文件描述符获得文件信息的函数stat(),fstat();
2.文件的7种类型,以及通过S_ISREG等宏和stat()函数确定其类型;
3.文件权限的获得(stat()函数获得st_mode,后9为即为权限标志,结合位运算获得);
4.umask()设置文件掩码,unlink()取消文件的连接以及对应的link()建立硬连接;
5.opendir(),readdir()等函数对目录的操作;以及内部结构体的使用;
四、库的创建
1.静态库,通过ar crs 将二进制.o文件转换成.a的静态库,编译的时候使用;
2.动态库,通过-fPIC 创建与地址无关的编译程序;
数据结构笔记:
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include"queue.h"
/*局部函数*/
static void Copy_to_node(Item item,Node *pn);
static void Copy_to_item(Node *pn,Item *pi);
void Initialize_queue(Queue *pq)/*初始化队列*/
{
pq->front=NULL;/*首指针为空*/
pq->rear=NULL;/*尾指针为空*/
pq->item=0;/*个数为0*/
}
bool Queue_isfull(const Queue *pq)
{
return pq->item==MAX_queue;
}
bool Queue_isempty(const Queue *pq)
{
return pq->item==0;
}
int Count_Queue(const Queue *pq)
{
return pq->item;
}
bool En_queue(Item item,Queue *pq)/*入队*/
{
Node *pnew;
if(Queue_isfull(pq)==NULL)
return false;
pnew=(Node *)malloc(sizeof(Node));
if(pnew==NULL)
{
fprintf(stderr,"unable to allocate memory");
exit(1);
}
Copy_to_node(item,pnew);
pnew->next=NULL;
if(Queue_isempty(pq)==NULL)
pq->front=pnew;
else
pq->rear->next=pnew;
pq->rear=pnew;
pq->item++;
return true;
}
bool De_queue(Item *item,Queue *pq)/*出队*/
{
Node *pt;
if(Queue_isempty(pq)==NULL)
return false;
Copy_to_item(pq->front,item);
pt=pq->front;
pq->front=pq->front->next;/*头指针的下一指针域可能为空,因此不存在没有元素,头指针也指向空*/
free(pt);
pq->item--;
if(pq->item==0) /*当没有元素时,让尾指针也指向空*/
pq->rear=NULL;
return true;
}
void Empty_queue(Queue *pq)
{
Item dumy;
while(!Queue_isempty(pq))
De_queue(&dumy,pq);
}
static void Copy_to_node(Item item,Node *pn)
{
pn->item=item;
}
static void Copy_to_item(Node *pn,Item *pi)
{
*pi=pn->item;
}
*****************************************************************************************************************
顺序表的操作:C++将其封装成类(函数为公有成员函数,将数组长度和存放元素的数组设置为私有)
==》等价于C中将数组长度和数组存放在结构体中,函数在外部进行操作(面向对象的思维)
==》数组最大长度可以以宏的形式声明;
1.无参构造函数的初始化==》只需将长度设置为0;
2.有参函数需要传递参数(数组名即指针,元素个数)
==》等价于C中采用定义结构体指针进行初始化,建立头结点,长度也需要设置;
3.析构函数为空(因为未在堆在开辟内存)
==》C中将表清空(可以将数组长度直接设置为0,也可以将数组的内存置0(采用memset));
4.计算线性表的长度(即返回表长的变量值);
5.查找操作
(1)按元素位置查找(从1开始到线性表的长度),返回下标为i-1的元素值(考虑位置是否合法);
采用指针或者对象进行访问操作;
(2)按元素值进行查找(从下标为0处开始到线性表长度位置的下标),返回元素的位置即i+1;
也是采用指针或者对象进行访问操作;
6.插入操作(首先考虑表是否满,然后插入位置是否正确)
==》在循环中,将插入的位置后面的元素从尾处一个一个的往后移动,
然后将需要插入的元素放在该位置上,长度+1;(原理是为元素腾出一个位置)
7.删除操作(首先考虑表是否空,然后删除位置是否合理)
==》根据下标将需要删除的元素保存以供返回;
==》将删除位置后面的元素分别向前移动即可,长度-1;
8.遍历操作(即循环中打印元素值);
******************************************************************************************************************
链表的操作:分为带头结点(即只有指针域的结点)和无头结点(即第一个结点内就有数据)2个情况;
==》尾结点指针域为NULL;
==》C++将函数封装在公有成员函数位置,将结点的指针存放在私有成员位置;
C中采用结构体,内存放数据成员和该结构体的指针;
1.无参构造函数(建立结点,将结点的下一节点指向NULL);
2.头插法建立单链表
3.尾插法
4.插入
5.删除
6.遍历
7.结点个数
8.查找(按位、按值)
==》循环单向链表操作(即尾结点的下一指针域为头结点,这里头结点仍只有指针域);
==》双向循环链表的操作,以将要插入的结点为中心(将前后分别先指向,然后将后继结点的的前继结点指向它,
最后将前继结点的后继结点也指向它,注意顺序不要断点);
删除结点时:先将前继结点的后继结点指向它的后继结点,然后将其后继结点的前继结点指向它的前继结点;
******************************************************************************************************************
栈的操作:首先栈为后进先出形式;
顺序栈:
1.将数组作为一个顺序栈,设置栈顶指针(即栈顶元素下标),有顺序栈的最大值;
2.将栈顶指针为-1;
3.入栈(考虑是否栈满);
4.出栈(考虑是否栈空),返回出栈的元素;
5.获取栈顶元素(栈是否空);
6.判空操作(即栈顶指针与-1的对比结果);
7.栈满操作(即栈顶指针与最大值-1对比);因为是下标操作所有均要-1;
特例:2栈共享空间(一个数组,2个栈,2个栈顶指针)结合图理解;
==》顺序和链的主要区别是(顺序以是数组为基础,元素在数组里面,有一个长度或游标;
而链表是存放成员和下一指针域,不受大小控制)
链栈:
即不采取数组,以指针和结点形式操作;指针存放的是一个结构体(内部为数据成员和其下一指针域);
1.入栈(可以不考虑大小)
2.出栈(考虑是否栈空);结合图理解;
3.判空操作;
4.取栈顶元素;
5.释放栈内存;
*******************************************************************************************************************
顺序队列:先进先出(一个队首游标,一个队尾游标)一般是循环队列;通过数组实现;循环通过取模实现;
1.初始化(队首等于队尾都为-1即可)
2.入队(考虑是否队满)
3.出队(是否空队);
4.获取队头元素(是否空);
5.判空;
链队列:1个结构体队头指针,1个队尾指针;
1.初始化(建立结点,头尾都指向它);
2.入队;
3.出队;
4.释放;
*******************************************************************************************************************
进程间通信笔记:
#include<stdio.h>
int main(void)
{
FILE *fp;
char buffer[80];
fp=popen("cat /etc/passwd","r");
fgets(buffer,sizeof(buffer),fp);
printf("%s",buffer);
pclose(fp);
return 0;
}
//
关闭管道==》pclose()对应popen();即一个打开必有一个关闭;
建立命名管道
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *pathname,mode_t mode)==》建立的FIFO文件pathname当前必须不存在,设置权限为mode,其他进程可以用读写一般文件的方式读取;
O_NONBLOCK==》以非阻塞方式打开文件,也就是无论有无数据读取或等待,都会立即返回进程之中;
当以O_NONBLOCK方式打开FIFO文件,立即返回,若之后仍没有进程打开文件来读取,就会返回ENXIO错误代码;
当不是O_NONBLOCK方式打开,只有当有其他进程打开FIFO文件才正常返回,读取、写入皆如此;
*/
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#define FIFO "/tmp/mffifol"
int main(void)
{
char buffer[80];
int fd;
unlink(FIFO); //
mkfifo(FIFO,0666);
if(fork()>0)
{
char s[]="hello!\n";
fd=open(FIFO,O_WRONLY);
write(fd,s,sizeof(s));
close(fd);
}
else
{
fd=open(FIFO,O_RDONLY);
read(fd,buffer,sizeof(buffer));
puts(buffer);
close(fd);
}
return 0;
}
//在文件操作中,注意各种读写方式的标识符;
进程线程网络编程笔记:
****************************************************************************************************
//进程
1.程序执行和资源管理的最小单位;是动态的,和程序是有区别的(静态);
2.进程包括程序指令和数据以及计数器值、寄存器值、临时数据堆栈空间;
3.主要进程标识:进程号(getpid函数)、父进程号(getppid函数);
4.进程的3个段:数据段、堆栈段、代码段;
5.进程类型:交互进程、批处理进程(例如shell)、守护进程;
6.进程的运行状态(5个);
7.进程的创建fork()、vfork()(不改变数据段,并且是由子到父的顺序);
8.exec函数族不创建进程,取代除了进程号的其他内容;主要用于通过进程执行新的程序;
9.进程的退出exit()会清理缓冲区、_exit()不会刷新输出流;
10.进程的阻塞wait()是等待任意进程结束并通过参数保存其结束状态值,
waitpid()等待指定进程号的进程结束并保存结束状态值;
11.守护进程(即后台运行),步骤是:创建子进程并让父进程退出;在子进程中创建新会话通过setsid()函数;
更改工作目录chdir()一般是根或者tmp;重设文件掩码通过umask(0)让文件权限开放;
关闭用不着的文件描述符,数量通过getdtablesize()函数获得;
****************************************************************************************************
//线程:必须在进程存在的条件下
1.方便任务切换和通信,共享进程的地址空间,也是task_struct描述,参与内核统一调度;
2.不通过系统调用,通过pthread这个第三方线程库,编译加-lpthread;
3.线程共享进程的资源,并有自己的私有数据(线程号,堆栈、错误号、状态属性、计数器和相关寄存器)
因为线程也可以看作是一个函数,即是线程函数;
4.线程的创建、删除、控制;
(1)创建pthread_create(),注意各参数意思,线程函数的通用性等;
创建线程即向操作系统注册,成功后执行线程函数;
(2)线程的等待pthread_join()参数为等待的线程号和线程函数的返回值的地址;
(3)线程的退出pthread_exit(),参数存储退出状态值;
(4)线程的取消pthread_cancel();
此处涉及到函数回调,通过函数名调用;函数的可重入即每次的调用互不影响
(可以通过全局变量、指针和字符串常量,堆中分配),局部静态会保存其值因此不可重入;
数据在内存中的存储方式(按字节);
-D条件编译,函数中有条件编译宏,调试版在编译时加上,不加即为发行版;
5.多线程的同步与互斥;访问共享对象时需要存在;
线程同步即是让线程按照一定的顺序执行(主要是通过信号量);
信号量通过3种方式获得:
1.初始化sem_init();相当于置开关0、1的2种状态交替执行,如果某一任务需要执行多次,
则可以设置其他的值;
2.P操作(申请资源)sem_wait();>0即执行,并且信号量递减,否则阻塞;
3.V操作(释放资源)sem_post();没有任务,信号量+1;否则执行;
一般申请和释放是成对出现,即结束一个马上激活另一个;
线程互斥主要针对临界资源的保护(防止多线程造成数据错乱),主要通过初始化互斥锁
pthread_mutex_init(),上锁pthread_lock(),释放锁pthread_unlock()实现;
锁可以针对某一结构进行,例如一个结构体的锁、链表锁;
*****************************************************************************************************
//进程间通信
1.传统进程间通信:pipe,fifo,singnal
2.System V IPC :share memory ,message queue,semaphore,标识符==》ftok()/PRIVATE
3.BSD:socket
(1)pipe:只能用于亲缘关系(父子,兄弟等进程),半双工,固定读写端,2个文件描述符;
(2)fifo:不支持lseek(),形如队列,通过管道文件描述符读写;双工;
(3)signal:kill()发送信号给某进程,signal()信号注册;可以自定义,信号函数参数为信号;
(4)share memory:最高效的进程间通信方式,映射不需要拷贝;通过地址访问,内核空间中的地址;
多进程共享内核空间,需要使用同步、互斥;shmget(),shmat(),shmdt,shmctl()函数的使用;
注意各返回值之间的关系,以及函数调用错误的处理;关系到调试时的处理;结合信号的使用
(5)message queue:
(6)semaphore:
//网络编程