一、c基础问题
1.c各种基本数据类型是否由于操作系统的不同而不同?具体呢?
char short int float double long longlong (单位:字节)
32位平台: 1 2 4 4 8 4 8
64位平台: 1 2 4 4 8 8 8
注:对于各种数据类型,有符号和无符号所占字节一样,只不过有符号的最高位是符号位。
2.为什么sigh char类型的127+1=-128?因为计算机以二进制补码的方式存储,规定正数的反码、补码和原码一样,负数的反码则是符号位不变,其余位取反,补码则是反码+1;而8位的sigh char类型127是非符号的全都为1而符号位为0的0111 1111,对它加1后正数已经越界了,变为1000 0000(此时最高位是1,表示负数),而1000 0000作为补码求其原码的值是-128(实际上严格来说,拿1000 0000去求原码是有问题的,因为10000 0000的符号位不参与加减计算,用它的非符号位去减1求反码根本减不动!于是人为规定1000 0000表示-128,其实当每种有符号基本数据类型的符号位为1,其余位全为0时,都是一种特殊情况)。实际上有符号整数在达到正数负数的临界点之前,绝对值是随1位数的增多而增大的,过了临界点之后,绝对值是随着1的位数增多而减小的。如果从0开始每次对sign char值加1经历00000000—>11111111的过程,它的值的变化是0——>127——>-128——>-1。
3.C++对代码的简洁性要求有多高?blog.csdn.net/zang141588761/article/details/50608736blog.csdn.net/ljianhui/article/details/9212817
4.如何简洁有效地判断任意整数的位数?blog.csdn.net/m0_37829435/article/details/78767500
5.各种输入函数的区别?scanf()/scanf_s()/getchar()/getch()/gets()https://www.cnblogs.com/bayjing/articles/2481297.html
6.各种输出函数的区别?printf/putchar/putch/putc/putsblog.csdn.net/kuweicai/article/details/51345804?locationNum=2
7.fflush(stdin)可以清理掉前面输入缓冲中剩余的数据(通常是一个换行符\n),因此经常在getchar()之前使用这个函数,防止getchar()把这个换行符当做有用数据读进变量。setbuf(stdin,NULL)也是比较常用的。
8.while(arr[sizeof(arr)])是否可以?导致越界访问,但是可以通过编译,因为编译器不检查数组下标是否越界(vs2017对数组仅对只越界1位的情况时会报错),但是无法确定所访问的随机数据是0还是非0。
9.为什么变量赋正值溢出就变成了负数?如果负数溢出呢?这得从计算机的存储方式是二进制补码说起,详见上面问题2。负数是溢出则变成0。
10.常见的排序?选择、冒泡、插入、快速、希尔。
11.各种变量的内存分配问题?全局变量(包括静态和非静态)和静态局部变量在编译时期分配内存,程序运行完释放;非静态局部变量在程序运行到定义它的那一行时分配内存,程序退出其所在函数时释放内存。
12.各种的变量的作用域问题?非静态全局变量在整个工程下可见,静态全局变量在当前文件可见,静态和非静态局部变量在其所在最近的{ }内可见,但要注意,静态局部变量只初始化一次,当程序离开其所在代码块或函数时并不释放,而是等整个程序运行完才释放。
13.return放在函数末尾,如果后面接一个值或式子,表示函数返回一个值,如果后面空的什么也不接,则单纯起到离开函数的作用。return起到离开当前函数的作用,而exit则起到结束程序的作用,即使当它放在分函数时。
14.return返回值原理:当返回值不大于4个字节,被调函数返回值暂存在EAX寄存器中,当返回值大于4个字节时,32位的寄存器放不下,被暂存在主调函数的某个内存空间。
15.c语言中内存区详情?c语言大概分4区,由地址从低到高分别是:代码区、静态常量区、堆区、栈区代码区:存放的是程序代码,并且只读静态常量区:地址由低到高分别是:data区(文字常量区、已初始化的全局、静态变量)、bss区(未初始化的全局、静态)堆区:由malloc( )手动申请的空间栈区:局部的非静态变量。
16.为什么函数声明一般放在最上面?一般情况下,被调函数的代码放在主调函数的上方就可以成功调用,这叫函数的可见性。函数声明为了使所有的函数都具有可见性。
16.磁盘永久存储,内存暂时存储,CPU用于计算。
17.不同源文件里函数的相互调用要调用同一个工程下的其他文件的非静态函数,只需保证该函数可见性,在主调函数的上方给出被调函数的声明即可。c语言中static 函数和普通函数的区别?_c语言中的函数都是静态函数吗-CSDN博客
20.不同源文件中相互调用全局变量只需用extern声明。extern_百度百科
21.全局变量和静态局部变量的区别?二者均是在进入程序前就已经被加载进内存,全局static和局部static的区别仅在于其可见范围。两者存储区一样。static可以用于区分不同源代码内的同名变量。
22.全局const和局部const的区别?
局部const在栈区,变量名通过const修饰,故不能通过变量名修改其值,但可通过其地址修改其值;全局const在文字常量区,故通过其地址也不能修改。
24.实际开发中,局部变量和全局变量哪个用得多?
《c primer plus》第5版的一段原话:“为什么选择自动类型作为默认类型?是的,乍看起来外部存储很有诱惑力。把变量都设成外部变量,就不用为使用参数和指针在函数之间传递数据而费心了。然而,这存在着一种不十分明显的缺陷,你将不得不为函数A( )违背了你的意图,偷偷修改了函数B( )所用的变量而焦急。多年来,无数程序员的宝贵经验给出了无可置疑的证据。证明了随意使用外部变量带来的这一不十分明显的危险远比它所带来的表面吸引力更致命。”
26.一个字符占一个字节会不会不太够?是不是因系统不同而改变标准?字符串和编码 - 廖雪峰的官方网站
28.EOF和feof()的区别?
EOF仅仅只是个值为-1的宏,因为大部分文件读取函数读取失败(读到文件结尾无内容可读也是读取失败的一种,而且其他情况的读取失败很少出现)的返回值是-1。但EOF只能用于读取文本文件,因为文本文件的字符都是以ASCII码值存储的,不存在-1的ASCII码值,而二进制文件就不同了
feof()就是为二进制文件而生的,它即可用于文本文件,又可用于二进制文件,它是根据FILE型结构体的用于标记是否读到文件结尾的成员变量的值来判定文件是否已经读到结尾的,但文件内部指针指向文件结尾时,FILE结构体的负责标记的成员并未立即置位,而是要等文件读取函数再读取一次才进行置位。(该说法有问题)新发现:读取文件会多读一行的本质其实读取函数有关,例如行读取时,假设文件只有一行外加个换行符,第一次读取至换行符处停止(换行符后面有没内容它也不知道),第二次进来跳过第一个换行符之后发现到了文件结尾,立马置位,feof()即生效;再例如fsacnf("%s")读取单词时,如果最后是一个空格或换行符,也会多试着读取一次才确保自己到了文件结尾。总之,如果文件的最后确实是有字符,但那些字符又是该函数遇到会自动跳过的,那么就会多读一次确保到了文件结尾。但fgets()是例外,尽管它会读取换行符,但仍然和其它函数一样读取次数受最后一个字符影响。
当读取键盘输入时,Windows中,需要单独一行Ctrl+Z+Enter才能模拟出EOF效果。https://www.cnblogs.com/nvbxmmd/p/4709480.html
29.getchar()只能读取单个字符,例如输入15,也是分‘1’和‘5’两次读取。
32.通过printf()/scanf()等函数从屏幕上读取和输出数字其实都是以字符的形式,但相关函数会自动转换成对应的类型,如printf("%d",8);实际上屏幕上显示的是字符型的“8”,又如scanf("%d",&num);实际上函数自动把屏幕上输入的字符型“8”转化为数字型的8,再进行存储。有时需要把字符型数字转化为数字型可以使用atoi()/atof()/atol()/strol()等函数。
33.为啥字符串作为函数参数时,该函数内部能用strlen(s)取得字符串长度却不能用sizeof(s)/sizeof(s[0])?因为字符串做参数时,传给函数的是其首字符的地址。strlen()的工作原理是通过改地址从s[0]开始计算字符数量直至遇到结尾的空字符‘\0’;而sizeof()则是求得参数本身所占的字节大小,当一个指针P作为参数时,得到的是这个指针的字节大小,而不是这个指针所指的对象的字节大小。
34.使用srand(time(NULL))和rand()产生随机数。srand_百度百科
35.正整数和负整数在计算机中的存储?在计算机中数据均是以二进制并且是补码形式存储的,这是为了保证表示一个十进制的二进制码只有一个,这些二进制有原码、反码、补码等,并且规定正数的反码、补码和原码一样。那么负数则是(以char类型的-7为例):
原码:绝对值直接转的二进制,最高位符号位置为1(符号位0表正数,1表负数) -->1000 0111
反码:在原码基础上,符号位不变,其余位取反 ——————————————>1111 1000
补码:反码+1 —————————————————————————————>1111 1001
(注:符号位是不参与加减的,如果加减导致数据位的最高位已经要向前进位或借位了,那么已经溢出了)
36.c中printf("%d%d",a++,++a)两个式子都是先运算再打印,而java中则按++的位置来,据说和不同的编译或运行平台有关(有些编译器的优化方式不同)。
37.二进制读写文件和文本读写文件的区别?按文本模式读取是把内存中的二进制按每8位转成其ASCII码值所对应的字符,再显示出来;按文本模式写入则是把文件中的每个字符均转为其ASCII码值的二进制形式,再放入内存。二进制则是直接当做二进制。
如果以“文本”方式打开一个文件,那么在读字符的时候,系统会把所有的“\r\n”序列转成“\n”,在写入时把“\n”转成“\r\n”。
39.编译器最终把数组形式换为指针形式,再由于c/c++中一个数组的元素都是连续空间存储的,所以通过arr[0][7]和arr[1][0]均能访问数组arr[2][6]的第二行第一个元素。
40.如果不同函数里的字符型指针赋值时使用的是相同的字符串常量,c++编译器代码优化时会只提供一份存储空间用来存储该字符串常量。例如:function1(){char *p1="abcd"}和function2(){char *p2="abcd"},编译器优化之后只在全局区存储一份“abcd”。
41.计算机中,数值均以补码形式存储,正数的原码、反码、补码相同。负数的反码等于原码符号位不变其余位取反,补码等于反码+1。所以欲把计算机存储的二进制转换为它所表示十进制数值的一般步骤是:1)先看这个数是有符号数还是无符号数,若是正数,则直接转化为十进制;2)若为负数,则最高位不变,-1得到其反码(注意符号位不参与加减,如果数据位的最高位需要向前面借位,则说明溢出了),然后最高位不变,其余位全部取反,然后转化为十进制并添加负号。
(1)unsigned char a在计算机中的存储形式是1000 0001,问a里存储的十进制数是多少?a是无符号数,求其原码即等于补码1000 0001,直接转换为十进制:129;
(2)char b在计算机中的存储形式是1000 0001,问a里存储的十进制数是多少?b是有符号数,补码1000 0001,反码1000 0000,原码1111 1111,转换为十进制:-127。
42.scanf("%c%s",s)中,表示跳过,%c表示跳过一个字符,若为%5s则表示跳过5个字符。
43.字符串数组只有在初始化的时候可以通过=整体赋值,其它时候只能通过strcpy或memcpy之类的函数操作内存整体赋值,但结构体可以,结构体的整体赋值的=实现原理其实就类似于memcpy,所以当把数组放入结构体也能实现整体赋值。
44.fopen()的第二个参数: r可读 r+可读可写 w可写有就清零,没有就创建 w+可读可写有就清零,没有就创建 b表示以二进制形式读写,不写b则表示默认文本形式。
45.gets()不读换行符,puts()自动添加换行符,他俩配对fgets()读取换行符,fputs()不添加换行符,他俩配对。
46.刷新缓冲区的三种情况:(1)缓冲区满自动刷新;(2)程序正常退出时;(3)fflush(fp)强制刷新。另:标准输入输出可以使用行缓冲\n; close(fp)关闭文件流也会刷新缓冲区。
47.scanf(“%[^\n]%c”)和while(getchar()!='\n');等效。
48.scanf()通过格式控制符%d把键盘输入的字符型整数转成整型整数存入整型变量中fscanf()通过格式控制符%d把从文件中读取的字符型整数转成整型整数存入整型变量中;
printf()通过格式控制符%d把整型整数转为文本字符型输出在屏幕上fprintf()通过格式控制符%d把整型整数转为文本字符型输出到文件中。
49.通过数组和指针访问的区别:(1)编译器符号表中,数组名arr具有一个地址,这个地址+偏移量,再取里面的内容即取得里面的元素;(2)编译器符号表中,指针名p自身具有一个地址1,取这个地址1里存储的一个地址2,再对地址2+偏移量,再取里面的内容,得到该元素。
50.int main(void){ int a = 1; int *p = NULL; p = &a; int i = 0; i[p] = 3;//数组下标的形式编译时都被编译器转换为指针形式 i[p]------>(i+p)------>(p+i) printf("%d%d", *p, i); system("pause"); return 0;}。
51.栈的使用从高地址开始往低地址跑(堆则相反)。一般arm是使用小端模式,高地址放高位字节,低地址放低位字节,所以例如0x12345678往栈区存储时,地址由高到低,依次存入12,34,56,78。
52.int a=(int)&(p->age);得到结构体成员的实际地址int b=(int)&(((Teacher)0)->age);得到该结构体成员地址相对于结构体起始地址的偏移量(其中Teacher为一结构体,p为指向该结构体的指针)或者 int c=(int)&(p->age)-(int)p;也得到偏移量。
53.结构体对齐单位
获取对齐单位,以下三个因素决定1.CPU周期WIN vs qt 8字节对齐Linux 32位 4字节对齐,64位8字节2.结构体最大成员3.#pragma pack(n) n--只能填1 2 4 8 16上面三者取最小的,就是对齐单位
存放规则:1.第一个成员放在偏移量为0的位置,大小就是数据类型的大小2.下一个成员存放的起点,必须该成员大小的整数倍(意思就是前面所有成员占的空间是这个成员所占空间的整数倍)当成员类型大于对齐单位,计算该成员的起点,以对齐单位计算3.结构体整体的大小必须是对齐单位的整数倍。
54.数据结构:(1)动态数组:一个指针数组顺序存储外来数据的地址(2)链表实现动态数组函数内部自增或自减链表节点,每个节点需要存储客户资料的地址和下一个节点的地址(3)linux内核链表函数内部自增自减链表节点,但仅需头节点需要自身开辟空间用于存储客户第一个结构体的地址,后面的节点空间均由客户提供。
55.
32位系统的寻址总线是32根,每根可表示高低电平两种状态,虚拟内存的地址编码以字节为单位,所以32位系统的寻址范围是2^32个字节,即2^32=2X2X1024X1024X1024,故其虚拟内存为4G。
64位的本该是2^64个字节,但受目前硬件水平所限,只能表示2^48个字节,故64位的虚拟内存为2^48=262T左右。
二、c核心总结
一、数组和指针数组名存的就是数组首元素的地址,所以:1.数组名其实就是常量指针,不能对其进行自加自减等操作,它指向数组的第一个元素,那么它的步长是多少呢?这得看数组的类型,例如:
1)int arr[6]; 这是个一维数组,类型是int[6],数组名就是第一个元素的地址,所以数组名指向arr[0],步长是4(4个字节)。
2)int arr[6][7]; 这是个二维数组,类型是int[6],数组名是第一个元素的地址,把该数组抽象成一维数组看待,所以它指向第一行,步长是4X7=28。
3)int arr[6][7][8]; 这是个三维数组,类型是int[6][8],同样,把它抽象成一维数组看待,它指向第一个7X8序列,步长是7X8X4=224。
2.当指针指向数组:
1)一级指针指向一个一维数组时:int arr[5]={3,4,5,6};int *p=arr;p的步长就是4(4个字节),p+1就跳4个字节空间(即跳过一个元素),*(p+i)操作即等效于arr[i]对数组元素的指向。
2)对于二维数组,数组名arr存的同样是其第一个"元素"(即整个第一列)的地址,即arr=&arr[0],而把第一列视为一个一维数组,其数组名arr[0]存的同样是其第一个元素的地址,即arr[0]=&arr[0][0]。
3.c/c++中,多维数组其实不是真正意义上的多维,它的元素都在一段连续内存空间上顺序存储,并且数组下标形式最终都被编译器转化为指针形式进行处理,所以有一些看似有问题的下标表示法其实是可以通过编译的,例如对于数组int arr[6][6],通过arr[1][1]和arr[7]均可访问该数组的第8个元素。
4.字符串的各种处理函数(strcpy()、strlen()等)的参数,实际上就是字符数组首元素的地址,它们通过结尾空字符来判定该字符串的范围长度;而sizeof()则是通过数组的类型得到数组所占的内存大小。
5.指针指向数组的不同情况:(1)一级指针(列指针)指向一维数组的首元素,列指针加1个步长则指向下一个元素(2)数组指针(行指针)指向整个一维数组,行指针加1个步长指向下一行;对其取*则得到列指针(上面说的一级指针)
6.数组做函数参数时,不管数组维度几何,实参总是传一个数组名进去即可,而抽象地说,数组名就是首元素地址,一维的数组的首元素地址就是第一个元素的地址,二维数组的首元素地址就是第一列的地址…
7.数组指针、指针数组和二级指针
(1)int (*p)[6]; p先和星花结合,首先它是个指针,不是一级指针也不是二级指针,而是一个数组指针,指向一个具有6个int元素的数组,它的步长是6X4=24个字节,其实如果假定有数组int arr[6],p的等级和&arr是一样的。
(2)int *p[6]; 方括号的优先级比星花高,所以p先和[6]结合,首先它是个数组,指针数组,该数组的6个元素都是int类型的指针。
(3)int **p;二级指针,p存储的是一个指向int变量的指针的地址。
二、函数的几种类型的参数首先形参和实参都是都是真实存在的,形参在函数的的内部定义,实参则在函数外部定义,两者的作用域不一样。
(1)普通变量做形参:当主调函数传参调用该函数时,形参是与实参同类型的局部变量,存储的是实参的值,函数实际操作的是实参的副本(也就是形参),对实参无任何影响。
(2)数组做参数:对于一维数组,函数的形参并不和实参一样是个数组,而是一个指向该数组首元素的指针(这就是常说的"数组做参数会退化为指针"),当数组名作为实参传进去时,该指针得到数组首元素的地址,进而通过地址间接对该数组进行操作,所以此时在函数内部可以改变函数外部的实参。对于二维数组,传参时传的直接用数组名,而数组名是二维数组第一列的地址,欲使实参和形参等级一致,可以确定形参是一个数组指针,指向该二维数组的整个第一列。
(3)指针做参数:形参和实参是同类型的指针,调用函数时,形参存储了实参里的地址,通过形参里的地址间接操作所指向的内存空间。
三、具有不同作用域的变量的生命周期
(1)auto:所有不加修饰的局部变量,都默认具有auto性质(自动存储时期),该变量在程序执行至其所在函数时分配内存,出了该函数之后释放,auto型变量存在栈区。
(2)register:只能作用于局部代码块内的变量,用于申请把该变量存放在寄存器中,以提高存取速度,存放在寄存器中的变量是无法获得其地址的,因为寄存器不在虚拟内存区之内。register只是一种申请,如果请求失败,则依旧存入栈区。
(3)static:静态存储时期,细分为两种:1》用static修饰的局部变量。仅对其所在代码块可见,编译时即为其分配内存,其所在函数运行完时仍可以通过地址访问它,程序运行完时被释放;2》用static修饰的全局变量。编译时分配内存,可见范围是整个文件,具有内部链接,同工程的其他文件无法对其声明使用,程序运行完被释放。
(4)extern:对在其他文件中定义的非静态全局变量的声明,声明之后即可在本文件中使用。
四、内存4区内存地址从低到高分别是:代码区、静态全局区(常量区、未初始化的静态和全局、已初始化的静态和全局)、堆区、栈区。
五、各种变量的初始化问题静态变量(静态全局和静态局部),非静态全局变量默认为0,非静态局部变量默认是所分配的内存上已存在的值。
六、常见的IO函数
(1)scanf()/printf() 支持格式控制符的终端输入/输出。
(2)getchar()/putchar() 仅支持字符或字符变量的终端输入/输出。
(3)gets()/puts() 仅支持字符串类型的终端输入(丢弃\n)/输出(添加\n) 。
(4)fgets()/fputs() 支持字符串类型的终端或文件的输入(读取\n)/输出(不添加\n)。
(5)fgetc()/fputc() 支持字符型的终端或文件的输入/输出。
(6)sscanf()/sprintf() 字符串转换为对应类型/对应类型转换为字符串 sscanf(char*,"%d",int &a) 如:把字符串"123"转换为整形123 sprintf(char*,"%d",int &a) 如:把整形123,转换为字符串"123"。
(7)fscanf()/fprintf() 支持格式控制符的终端或文件输入/输出。
(8)fopen()/fclose() 打开/关闭文件流,把关联的文件信息存入FILE类型的结构体中。
(9)fseek() 改变文件中的光标位置 fseek(FILE*,1,SEEK_SET) 把文件的光标相对于文件开始移动1个字节 fseek(FILE*,1,SEEK_CUR) 把文件的光标相对于当前位置移动1个字节 fseek(FILE*,1,SEEK_END) 把文件的光标相对于文件末尾移动1个字节 (注,ASCII下,一个字符占一个字节)。
(10)long ftell(FILE*) 获取文件中光标的位置(11)rewind(FILE*) 把文件光标移到开头位置。
七、字符串处理函数
(1)gets()/puts() 读取字符串(丢弃\n)/输出字符串(自动添加\n)。
(2)fgets()/fputs() 读取字符串(读取\n)/输出字符串(不添加\n)。
(3)strlen() 以遇空字符结束取得字符串长度。
(4)strcpy(s1,s2) 复制s2的内容到s1。
(5)strncpy(s1,n,s2) 复制s2的前n个字符到s1。
(6)strcmp(s1,s2) 比较s1,s2的大小,比较返回ASCII差值。
(7)strncmp(s1,s2,n) 比较s1,s2的前n个字符的大小,比较返回ASCII差值。
(8)strcat(s1,s2) 把s2的内容复制追加到s1尾部。
(9)strncat(s1,s2,n) 把s2的前n个字符复制追加到s1尾部。
(10)sscanf()/sprintf() 把对应数据转换为其字符串形式或把字符串形式转换为对应数据。
(11)strchr(s,ch) 在字符串s中寻找字符ch第一次出现的位置。
(12)strstr(s1,s2) 在字符串s1中寻找字符串s2第一次出现的位置。
(13)strsork(s1,s2) 把字符串s1中的字符串s2都替换为'\0'以分割字符串1,一次只替换一个。
(14)atoi(s1) 把字符型整数转换为整型整数 类似的还有atof()、atol()等。
以上字符串的操作函数均会受到‘\0’的影响,下面有直接操作内存的相关函数:
(1)memset(void*s,int a,_size n) 每n个字节覆盖存入整数a。
(2)memcpy(voids1,voids2,_size n) 把s2的前n个字节复制到s1。
(3)memmove(voids1,voids2,_size n) 和memcpy()一样,但可以处理重叠部分。
(4)memcmp(voids1,voids2,_size n) 比较s1和s2的前n个字节。
八、输入/输出缓冲区刷新缓冲区的三种情况:
(1)缓冲区满自动刷新;
(2)程序正常退出时;
(3)fflush(fp)强制刷新。
标准输入输出可以使用行缓冲\n;close(fp)关闭文件流也会刷新缓冲区。
三、c++笔记
1.c语言如何判断输入一切数字是否是数字,scanf()的返回值有哪些意义?scanf类函数,%d等格式控制符下,把输入的ASCII值在0~9的ASIIC值之间的字符转为整型等对应类型数据存储;scanf()的返回值是成功读取的数据的数量;标准输入缓冲区清理万能公式:while(getchar()!='\n');c++中,cin可自动根据变量类型判断输入是否符合类型,不符合不读取,输入被留在cin暂存区,可用命令cin.clear()清除。
2.c中,scanf()不能用%f为double类型变量读取输入?答:double用%lf,不然失精度。
3.c中,switch的case后面只能放整型常量。
4.关键字register在C语言中用于申请把变量存入寄存器中以提高程序运行速度,C++依然保留了这个关键字的应用,但C++的编译器即使对不使用这个关键字的一些代码,仍会尽可能做优化,例如尽可能自动把语句for(int i=0;i<1000;i++){……}中的变量i存入寄存器,以提高该循环的运行速度。在c中,用register修饰的变量不能取地址,而在c++中,编译器会做优化,当发现有取地址操作时,编译器会使register的声明无效。
5.C中,三目运算表达式返回的是变量的值,不能做左值;c++中,三目运算表达式返回的是变量本身,可以做左值。例如(a>b?a:b)=30;在C++中允许而在C中不允许。
6.c和c++中的const异同:
c中:const修饰的全局变量无法通过指针间接修改,具有外部链接、const修饰的局部变量可以通过指针间接修改。
c++中:cosnt修饰的全局变量无法通过指针修改,具有内部链接,但如果定义时形如extern const int num;则可以使其具有外部链接。const修饰的局部变量也可以通过指针间接修改(指针指向时要类型强转),但是由于编译器会代码优化,常常导致const修饰的变量即使通过指针得到修改也难以通过语句体现出来(编译器优化时,在编译时期类似于宏替换(预编译时期)把程序中的const常量都替换为了阿拉伯数字),此时可以通过在变量定义时用volatile防止优化。如果const修饰的是自定义数据类型或者修饰的变量没有直接赋值,编译器则不能对其优化。
7.const和#define的不同:const定义具有作用域(本质就是只读的普通变量),并且在编译时起作用;#define定义没有作用域(不管在何处定义,均在全局区存放),全局范围有效(可以用#undef卸载),并且在预编译时期产生单纯的文本替换。
8.当函数的返回值是局部变量的引用时,可能会出现问题,因为返回其引用实质上就是返回其本身,而局部变量的空间在函数退出时已被系统回收,再想访问该块空间属于违规操作;当函数的返回值是临时对象时,编译器会在栈区暂时提供该对象的拷贝——匿名对象,由主调函数处理,该匿名对象或被直接扶正不释放,或被复制信息之后释放。
9.c中函数参数的传递(通过指针除外)实际上只是值的传递,操作的是变量的副本,例如:void funct(int num){ num += 1;}int main(void){ int n = 4; funct(n);//实质上此处调用的参数传递是:int num=n,funct()函数内部操作 //的是num,而不是n}而c++中引用做函数参数的实质是一个变量值两个变量名,被调函数操作的是实参而不是其副本。
10.常引用做形式参数让变量具有只读属性,直接用数值为常引用赋值会单独为其分配内存空间。const int &m=4;
11.<1>内联函数inline在编译时直接将整个函数体插入到函数调用的地方随着一起编译,而宏#define是在预处理时发生文本替换;<2>内联函数与register一样是一种请求,编译器视情况是否执行,一般用于简单的代码块(没有循环和过多的条件判断);3>内联函数的好处在于能省去普通函数压栈、跳转、返回的开销。
include<iostream>
using namespace std;
inline void print()//内联函数不能单独声明,必须立即实现
{
cout << "hello world" << endl;
}
void main()
{
print();
system("pause");
}
12.c++新增的默认函数参数://仅含默认参数的函数,被调用时如果不传入参数则使用默认值void print(int x = 3){ cout << "x=" << x << endl;}//如两者混合,默认参数只能放后面,并且一旦有一个默认参数被//使用,其后面的参数都必须使用默认void print2(int a,int b=2,int c=9){ cout<<"a+b+c=" << a + b + c <<endl
13.C++新增占位参数://C++占位参数:其所占的内存无法调用,仅为后面的扩展留下线索void print(int a, int b, int){ cout << "a=" << a << ",b=" << b <
14.//C++占位参数与默认参数结合void print(int a, int b, int =1){ cout << "a=" << a << ",b=" << b <
15.重载函数:c中不支持一切同名函数,而C++中支持参数列表不同的同名函数(但如果仅仅是返回值类型不同而参数列表一样则不满足重载)。
16.类似于只相差一个默认参数的两个重载函数在调用时会产生二义性,编译不通过,例如void print(int a,int b,int c=0)和void print(int a,int b)在使用语句print(1,2)时,不知道调用的是哪个函数。
17.函数指针:void print(int a, char b){ cout<声明一个函数类型typedef void myprint(int a, char b);//myprint * mp=NULL;//定义一个函数指针,指向函数的入口地址//2>声明一个函数指针类型typedef void(*myprint)(int a, char b);//myprint mp=NULL;//同上//3>定义一个函数指针变量void (*mp)(int a, char b);
18.c++类的访问控制关键字:(1)public:修饰的成员变量和函数,可以在类的内部和类的外部被访问;(2)private:修饰的成员变量和函数,只能在类的内部被访问,不能在类的外部被访问;(3)protected:修饰的成员变量和函数,只能在类的内部被访问,不能在类的外部被访问,可以在子类中被直接访问。
19.struct和class的区别:struct的成员默认属性是public而class则是private。
20.构造函数在创建对象时自动初始化成员变量;赋值构造函数在创建对象时通过复制同类对象的数据初始化成员变量;析构函数则是在创建的对象消失时自动调用,例如,可以在析构函数中放上delete等用于释放内存等。
21.类的构造函数:<1>一个类默认提供无参构造、拷贝构造、析构函数、浅拷贝=重载函数<2>当用户提供了了拷贝构造,那么编译器不会再提供任何的默认构造函数<3>当用户提供了有参构造,那么编译器不再提供无参默认构造,只提供默认拷贝构造
22.当一个函数的参数是一个对象时,在调用这个函数并传入参数,因为此时这个做参数的对象不是对象引用也不是指向对象的指针,所以会在栈区复制这个对象的副本供函数使用,这时也会调用赋值构造函数。
23.当一个函数的返回值是一个对象时,因为函数在被调用结束后里面的所有空间被释放,函数里的那个对象也被释放,此时会临时创建该对象的副本(匿名对象)供调用函数使用,那么这时会调用赋值构造函数。
24.匿名对象的两种“接”法:(1)class1 c1=匿名对象;此时类似于c1直接指向匿名对象,或者说匿名对象就已经是c1了,即匿名对象被转正,不被析构;(2)class c2;c2=匿名对象;这时c2是一个新的对象,当匿名对象里面的成员信息被复制(此时要考虑浅拷贝问题)给了c2后,匿名对象被析构。(3)匿名对象的生命周期在当前行!!!!!!!!
25.浅拷贝:当一个类的构造函数里有通过malloc()申请堆空间给成员指针变量时,如果创建一个此类的对象c2时用这个类的另一个已创建的对象c1给它赋值,这时c2成员指针变量仅仅只是存储了c1成员指针变量里存的地址,和c1指向同一个堆空间的地址,而编译器没有为c2再单独开辟一块新的堆空间。<1>如果一个类使用默认的赋值构造函数,则通过例如Tree t2=t1、Tree t2(t1)和t2=t1这三种方式给对象赋值均为浅拷贝。而手动实现赋值构造函数请求分配空间仅能实现Tree t2=t1(其实也被编译器优化成Tree t2(t1)进行处理) 和 Tree t2(t1)的深拷贝,对于t2=t1(因为这个语句没有调用构造函数,而类的默认重载=也是浅拷贝)的深拷贝实现需通过重载=操作符解决。
26.当类A的对象a作为类B的一个成员变量时,而如果类A使用的是含参构造函数,那么创建类B的对象b时,b里的对象型成员变量a会因为无法传参而调用构造函数失败,此时需要语法:class A{private: int num;public: A(int num) { this.num=num; }}class B{private: int i; A a;public: B(int i):a(int num)//关键语法 { this.i=i; }}
27.c++为了提高与c的兼容性,保留了函数malloc()和free(),但也有自己的操作符new和delete,当为一些基本的数据类型变量分配空间时,它们的效果是一样的,甚至可以malloc()/delete,new/free()组合使用。但根本的区别在于为类类型分配空间时,new和delete在分配空间的同时还要自动调用类的构造函数和析构函数初始化对象,而malloc()和free()仅仅是分配空间。
28.类的静态成员函数中不能包含有非静态成员变量。
29.类的成员函数后接关键字const可以修饰指向类本身的*this指针,例如void setR(int a,int b) const中const修饰的是隐藏指针Tree * const this,(实质上变成了const Tree * const this),即setR()内部不能通过this或者说默认的this指针修改Tree类的成员变量。29-2.每创建一个对象,就会新建一个this指针,指向新建对象的地址?this属于类的成员函数,当通过 对象名.func() 调用函数时,编译器自动把this中的地址换成那个对象的数据区起始地址估计!至于this被前后隐式const修饰只读且其所指内容只读仅是针对我们程序员而言,编译器是可以任意改变其指向的!C++中this指针存放在哪里_c++this指针存放在哪里-CSDN博客https://www.2cto.com/kf/201606/513938.html
const修饰类的成员函数,则该函数内部不能修改类的成员变量;mutable修饰的成员变量在常函数内可以被修改。
const修饰的对象可以访问成员变量,但不能修改;只能调用const成员函数。为了好的编程风格,应该尽量把不修改成员变量的成员函数设为const函数。
static成员函数和static成员变量可直接通过类名::调用,static函数最大的作用就是可以直接用类名调用,但是static函数无法访问非static成员变量,因为它没有this指针(this指针指向当前对象的数据区)。
普通成员变量如果在构造函数中缺省,则不初始化,其值任意;静态成员变量无论公有私有则必须在类外初始化。
30.若某个函数想在类的外部访问该类的私有成员变量,只需复制一份该函数的声明并在其前面加修饰符friend,然后把该语句放入类的内部即可,此时这个函数成了该类的友元函数。
31.友元类:在A类中加入语句friend class B,把B变成A的友元类,那此时可以在B类中通过自己的成员类A a直接访问类A的私有成员。
32.为啥重载&&和||无法实现短路规则?
32-1.为啥<<和>>的重载只能在全局区?拿<<为例,这个符号的左边的操作数是一个输出流ostream对象,右边的操作数是一个自定义的对象,所以操作符重载函数的参数顺序也应该是这样。如果把该重载函数写在自定义的类中,则参数顺序反了,因为默认的this指针规定为第一个参数;而ostream类是库函数,无法把重载函数写入到里面。
33.c++中,若类B继承了类A,那么被继承者类A被称为类B的基类,继承者类B被称为类A的派生类。
33.三种继承的不同:public继承时,到了子类中的父类成员保持原有属性;protected继承时,到了子类中的public和protected父类成员均变成protected属性,private成员则保持;private继承时,到了子类中的父类成员均变成private属性。
34.继承的赋值兼容性原则:1)基类指针(引用)可以指向子类对象;如果某函数使用父类对象指针(引用)做形参,那么它的实参也可以是子类的指针(引用);2)可以通过子类对象初始化父类对象,如: Child c;Perent p=c。
35.有关继承的构造和析构:先调用父类的构造函数,再调用子类的构造函数;析构顺序则相反。
36.继承和组合混搭的构造和析构:先调用父类的构造函数(如果父类还有父类,则先调用父类的父类),然后调用组合对象的构造函数,最后调用自身的构造函数;析构顺序则相反。
37.继承中父类子类同名成员变量的问题:如B继承A,两者均有同名变量x,则可以分别通过b.A::x和b.B::x操作父类和子类中的变量x,而默认b.x操作子类的x。对于父子类中的同名函数,也是一样。
38.继承中,如果父类的构造函数是私有的,那么子类在创建对象时会由于无法调用父类的构造函数产生编译错误。
39.继承的二义性:如果一个子类多继承于两个或多个父类,而这些父类又继承与同一个父类,那么通过这个子类访问通过继承得来的祖父类的成员变量时,编译器会产生该成员变量指代不明确的错误。此时可加virtual关键字解决。(实际上,多继承在项目开发中不常用) “虚函数”:在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态覆盖。
40.继承的多态:在继承的兼容性原则下,一个函数的形参是父类引用且函数的内部有调用父类和子类的的同名成员函数语句时,无论所传的实参是父类对象还是子类对象,函数均调用父类的成员函数。此时在父类和子类的同名成员函数前加上关键字virtual,函数即可自动根据实参的类型调用相应的成员函数。即:父类指针指向父类对象,则调用同名的父类函数,父类指针指向子类对象,则调用同名的子类对象。
41.实现多态的三个条件:(1)要有继承;(2)要有虚函数重写;(3)用父类指针(或引用)指向子类对象。
42.不加virtual关键字为静态联编,加了则为动态联编。实现原理:静态联编在编译阶段就已经绑定好了要调用的函数的入口地址;动态联编则:只要类中有虚函数,编译器便会为类创建一张虚函数表(表中存放该类所有的虚函数入口地址),并且在该类中安插一个vptr指针,指向那张虚函数表。当子类继承父类,编译阶段编译器发现子类继承了父类的虚函数,也会为子类创建一张虚函数表,表中存的是父类所有的虚函数的的入口地址,如果子类中有虚函数重写,则用子类的那些重写的虚函数入口地址把表中对应的父类虚函数入口地址覆盖掉。当子类创建对象初始化时,先调用父类的构造函数,让vptr指针指向父类的虚函数表,然后再调用子类的构造函数让vptr指向自己的虚函数表。当函数调用时,主调函数通过的vptr指针指向的虚函数表找到要调用的函数。(注:只要类中有虚函数,编译器就会为每个类创建一个虚函数表,而vptr则是要创建对象才有)
通过父类指针无法调用子类中的非重写虚函数
43.虚析构函数:在父类的析构函数前加关键字virtual可以实现:(1)通过父类指针,把所有子类的析构函数都执行一遍;(2)通过父类指针,释放所有的子类资源。
44.函数重写与重载类似,但不同的是函数重写发生在不同的类之间。即在子类中定义与父类一样的函数叫函数重写,重写加virtual关键字叫做虚函数重写,虚函数重写可以智能调用父类和子类的同名函数;而不加virtual的重写则叫做函数重定义,此时若想通过子类对象调用父类的那个被子类重写覆盖的函数可以通过加作用域的方法,如c.parent::funct()。
45.抽象类:包含纯虚函数的类,由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
47.如果父类构造函数里有调用虚函数,子类构造函数里也有调用虚函数,因为是重写虚函数,所以子类中的和父类中的同名,那么当创建一个子类对象时,父类构造函数调用的是父类的虚函数,子类构造函数调用的是子类的虚函数,为啥?因为:当执行父类的构造函数时,子类的vptr指向父类的虚函数表,当父类构造函数运行完毕后,会把子类的vptr指向子类的虚函数表。
48.能否把一个类的所有成员函数都设为虚函数?答:理论上可以,但是:通过虚函数表指针vptr调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数,在效率上,虚函数要差得多,所以出于效率考虑,没有必要将所有的成员函数都声明为虚函数。
49.用父类指针指向子类对象可以帮助实现多态,但当父类指针与子类指针步长不一样时,用父类指针自加的形式调用子类对象数组元素时,会出问题。
50.函数指针,函数指针的声明只需要把函数的声明语句中的函数名部分替换为*pf形式即可,而函数名 即表示该函数的首地址,所以函数指针的赋值可以是pf=函数名
51.函数回调:函数通过接受函数指针类型的参数的方式调用另一个函数,此时主调函数相当于框架,而被调函数相当于各厂商提供的产品接口,主调函数内部的代码不用改变就可以实现不同被调函数的不同功能。
52.对象类型转换的本质:(整个对象的空间申请右高地址向低地址,对象成员则是由低到高)
53.异常捕获之后如不作处理或者只是简单打印异常信息,程序只是不再执行异常函数异常之后的代码,然后从catch语句继续往后执行步骤起始和c语言中分函数return语句之后,返回继续执行主调函数剩余的代码!!!!!!!!!!!!!!!!
54.关于string变量向文件读写出现段错误的问题:
结论:string类内有一个char指针,其指向空间由string的构造函数在堆里申请,释放自然由其析构函数完成。如上图,str写入文件时,不仅仅是“年后”两个字写入了文件,string类中的其他数据成员也一并写入了文件,包括堆区地址,然后声明定义一个新的tmp,其构造函数自动为其char成员申请堆空间,然后从文件读取str写入的所有信息,覆盖了自己的所有信息,此时它的堆区地址是str的堆区地址,最后程序结束,str和tmp的析构函数释放同一个堆区空间,形成段错误。
55.扫地僧划重点:动态库的封装和设计、运算符重载(string类和数组类的重载)、纯虚函数(抽象类)。
56.为什么拷贝构造函数的参数不能是值传递:该拷贝构造被调用时,要先创建一个局部对象,然后通过该局部对象的成员的值去初始化主调类的成员,但这个局部对象的创建又需要调用拷贝构造函数...如此一来便会递归陷入循环,最终栈溢出。
四、数据结构
1.概念区
1.数据结构指数据对象中数据元素(结点)之间的关系。
2.数据的逻辑结构:(1)集合(散列型); (2)线性结构(一对一); (3)树形结构(一对多); (4)图状结构(多对多)。
3.数据的物理结构:亦称为存储结构,是数据的逻辑结构在计算机内存中的表示(或映像)可分为顺序、链式、索引(如数组存指针,指针指向别的数据)、散列。
4.算法是特定问题的求解步骤。算法可以没有输入也可以有多个输入,但算法至少有一个输出。算法的有穷性指在有限的步骤之后会自动结束而不会无限循环。
5.算法最终要编译成具体的计算机指令,每一个指令在具体的计算机cpu上运行的时间是固定的,所以通过程序n的步骤就可以推导出算法的复杂度。
6.大O表示法表示算法的时间复杂度:例如一个程序里全是1条1条的语句,那它的算法复杂度是O(1);如果有一个循环语句,则算法复杂度是O(n)。
7.算法的空间复杂度:程序的变量所占的字节数。
8.空间换时间的典型案例:在一个由1—1000中某些数字组成的长度为1000的数组中,每个数字可能出现0次或多次,设计一个算法,找出出现次数最多的数字。
9.各种数据结构的简介:
(1)动态数组:一个指针数组顺序存储外来数据的地址,如要将数据复制存储可在堆区为其另开空间。
(2)传统链表:内部自增或自减链表节点,每个节点需要存储客户资料的地址和下一个节点的地址。
(3)linux内核链表:内部自增自减链表节点,但仅头节点需要自身开辟空间用于存储客户第一个结构体的地址,后面的节点空间均由客户提供。
(4)栈:可将链表加些限制实现,其特点是先进后出,不能随意访问,如要访问指定结点,则必须将其上面的其它结点弹走(从上往下顺序销毁)。
(5)队列:可将链表加些限制实现,其特点是先进先出,就像一根水管。
10.二叉树的表示:二叉链表示法、三叉链表示法、双亲表示法 。
11.树的遍历:先序遍历(根左右)、中序遍历(左根右)、后序遍历(左右根),区别仅在于访问结点的时机不同。根右左、右根左、右左根三种遍历为企业开发所抛弃!树的遍历及计算叶子数 。
12.二叉查找树:左子树上所有的结点小于或等于它的根结点,右子树上所有的结点均大于或等于它的根结点,它的查找便满足了有序的二分查找。红黑树则是二叉查找树的改进版,避免出现整棵树全是单节点的问题。
2.代码区
1.链表
1.动态数组
DynamicArray.h
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct DynamicAraay
{
void **data;//获取数据空间的首地址
int capacity;//容量
int size;//元素个数
}DnA;
typedef int(compare)(void *, void *);//比较
//返回的void*是指向上面的结构体空间
void *Init_DynamicAraay();
//指定位置插入数据
int Insert_DynamicAraay(void *arr,int pos,void *data);
//头部插入
int PushFront_DynamicAraay(void *arr, void *data);
//尾部插入
int PushBack_DynamicAraay(void *arr, void *data);
//指定位置删除
int RemoveByPos_DynamicAraay(void *arr,int pos);
//根据值来删除
int RemoveByVal_DynamicAraay(void *arr, void *data, compare *mycompare);
//头部删除
int PopFront_DynamicAraay(void *arr);
//尾部删除
int PopBack_DynamicAraay(void *arr);
//获取数组的大小
int Size_DynamicAraay(void *arr);
//获取容量
int Capacity_DynamicAraay(void *arr);
//打印数据
void Print_DynamicAraay(void *arr,void (*foreach)(void *));
//销毁数组
void Destroy_DynamicAraay(void *arr);
DynamicArray.c
#include "DynamicAraay.h"
//返回的void*是指向上面的结构体空间
void *Init_DynamicAraay()
{
DnA *arr = (DnA*)malloc(sizeof(DnA));
if (arr == NULL)
{
return NULL;
}
arr->capacity = 10;
arr->size = 0;
arr->data = malloc(sizeof(void *)*arr->capacity);
return arr;
}
//指定位置插入数据
int Insert_DynamicAraay(void *arr, int pos, void *data)
{
if (arr == NULL)
{
return -1;
}
if (data == NULL)
{
return -2;
}
//转换步长
DnA *myarr = (DnA*)arr;
if (pos<0 || pos> myarr->size)
{
pos = myarr->size;
}
//判断空间是否足够
if (myarr->size == myarr->capacity)//空间满了
{
//根据空间增长策略,不够就加一倍
int newcapacity = myarr->size * 2;
//开辟空间
void *newspce = malloc(sizeof(void *)*newcapacity);
//将旧空间的数据拷贝过来
memcpy(newspce, myarr->data, sizeof(void *)*myarr->capacity);
//释放旧空间
free(myarr->data);
//更新核心数据
myarr->data = newspce;
myarr->capacity = newcapacity;
}
//元素移动
for (int i = myarr->size - 1; i >= pos; i--)
{
myarr->data[i + 1] = myarr->data[i];
}
//找到pos的位置
myarr->data[pos] = data;
//维护元素个数
myarr->size++;
return 0;
}
//头部插入
int PushFront_DynamicAraay(void *arr, void *data)
{
return Insert_DynamicAraay(arr, 0, data);
}
//尾部插入
int PushBack_DynamicAraay(void *arr, void *data)
{
DnA *myarr = (DnA*)arr;
return Insert_DynamicAraay(arr,myarr->size,data);
}
//指定位置删除
int RemoveByPos_DynamicAraay(void *arr, int pos)
{
if (arr == NULL)
{
return -1;
}
DnA *myarr = (DnA*)arr;
//如果没有元素
if (myarr->size == 0)
{
return -2;
}
if (pos<0 || pos>myarr->size - 1)
{
return -3;
}
for (int i = pos; i < myarr->size - 1; i++)
{
myarr->data[i] = myarr->data[i + 1];
}
myarr->size--;
return 0;
}
//根据值来删除
int RemoveByVal_DynamicAraay(void *arr, void *data, compare *mycompare)
{
if (arr == NULL || data == NULL || mycompare == NULL)
{
return -1;
}
DnA *myarr = (DnA *)arr;
for (int i = 0; i < myarr->size; i++)
{
if (mycompare(myarr->data[i], data))
{
//找到了
RemoveByPos_DynamicAraay(arr, i);
break;
}
}
return 0;
}
//头部删除
int PopFront_DynamicAraay(void *arr)
{
return RemoveByPos_DynamicAraay(arr,0);
}
//尾部删除
int PopBack_DynamicAraay(void *arr)
{
DnA *myarr = (DnA *)arr;
return RemoveByPos_DynamicAraay(arr,myarr->size-1);
}
//获取数组的大小
int Size_DynamicAraay(void *arr)
{
DnA *myarr = (DnA *)arr;
return myarr->size;
}
//获取容量
int Capacity_DynamicAraay(void *arr)
{
DnA *myarr = (DnA *)arr;
return myarr->capacity;
}
//打印数据
void Print_DynamicAraay(void *arr, void(*foreach)(void *))
{
if (arr == NULL)
{
return;
}
if (foreach==NULL)
{
return;
}
DnA *myarr = (DnA *)arr;
for (int i = 0; i < myarr->size; i++)
{
foreach(myarr->data[i]);
}
}
//销毁数组
void Destroy_DynamicAraay(void *arr)
{
if (arr == NULL)
{
return;
}
DnA *myarr = (DnA *)arr;
if (myarr->data != NULL)
{
free(myarr->data);
myarr->data = NULL;
myarr->capacity = 0;
}
free(myarr);
myarr = NULL;
}
main.c
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include"DynamicAraay.h"
typedef struct Maker{
char name[64];
int age;
}Maker;
void myPrint(void *data)
{
Maker *m = (Maker*)data;
printf("name:%s age=%d\n", m->name, m->age);
}
int mycompare(void *d1, void *d2)
{
Maker *m1 = (Maker*)d1;
Maker *m2 = (Maker*)d2;
return (strcmp(m1->name, m2->name) == 0) && m1->age == m2->age;
}
int main()
{
Maker p1 = { "aaa", 111 };
Maker p2 = { "bbb", 222 };
Maker p3 = { "ccc", 333 };
Maker p4 = { "ddd", 444 };
Maker p5 = { "eee", 555 };
Maker p6 = { "ffff", 666 };
Maker p7 = { "ddd", 777 };
Maker p8 = { "eee", 888 };
Maker p9 = { "ffff", 999 };
//初始化数组
void *myarr = Init_DynamicAraay();
//插入元素
Insert_DynamicAraay(myarr, 0, &p1);
Insert_DynamicAraay(myarr, 0, &p2);
Insert_DynamicAraay(myarr, 0, &p3);
Insert_DynamicAraay(myarr, 0, &p4);
Insert_DynamicAraay(myarr, 0, &p5);
Insert_DynamicAraay(myarr, 2, &p6);
//尾部插入
PushBack_DynamicAraay(myarr, &p4);
//头部插入
PushFront_DynamicAraay(myarr, &p7);
//遍历
Print_DynamicAraay(myarr, myPrint);
printf("------------------------\n");
//根据位置删除
RemoveByPos_DynamicAraay(myarr, 7);
//根据值删除
RemoveByVal_DynamicAraay(myarr, &p3, mycompare);
//遍历
Print_DynamicAraay(myarr, myPrint);
printf("------------------------\n");
//头删,尾删
PopFront_DynamicAraay(myarr);
PopBack_DynamicAraay(myarr);
//遍历
Print_DynamicAraay(myarr, myPrint);
printf("capacity:%d size:%d\n", Capacity_DynamicAraay(myarr), Size_DynamicAraay(myarr));
for (int i = 0; i < 10; i++)
{
Insert_DynamicAraay(myarr, 0, &p1);
}
printf("capacity:%d size:%d\n", Capacity_DynamicAraay(myarr), Size_DynamicAraay(myarr));
//销毁
Destroy_DynamicAraay(myarr);
system("pause");
return EXIT_SUCCESS;
}
2.链表实现动态空间
Linklist.h
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//链表节点
struct LinkNode
{
void *data;
struct LinkNode *next;
};
//链表
typedef struct _Linklist{
struct LinkNode head;
int size;
}Lls;
typedef void * LinkList;
//初始化链表
LinkList Init_Linklist();
//指定位置插入
int InsertByPos_Linklist(LinkList list, int pos, void *data);
//头插
int PushFront_LinkList(LinkList list,void *data);
//尾插
int PushBack_LinkList(LinkList list, void *data);
//在指定值前面插入节点
int InsertVal_LinkList(LinkList list, void *olddata, void *newdata, int(*compare)(void*, void*));
//指定位置删除
int RemoveByPos_LinkList(LinkList list, int pos);
//根据值来删除
int RemoveByVal_LinkList(LinkList list, void *data, int(*compare)(void*, void *));
//头删
int PopFront_LinkList(LinkList list);
//尾删
int PopBack_LinkList(LinkList list);
//遍历
void Foreach_LinkList(LinkList list, void(*forecah)(void *));
//销毁
void Destroy_LinkList(LinkList list);
Linklist.c
#include "Linklist.h"
//初始化链表
LinkList Init_Linklist()
{
Lls *list = malloc(sizeof(Lls));
if (list == NULL)
{
return NULL;
}
list->head.data = NULL;
list->head.next = NULL;
list->size = 0;
return list;
}
//指定位置插入
int InsertByPos_Linklist(LinkList list, int pos, void *data)
{
if (list == NULL || data==NULL)
{
return -1;
}
Lls *mylist = (Lls*)list;
if (pos<0 || pos>mylist->size)
{
pos = mylist->size;
}
//辅助指针
struct LinkNode *pCur = &(mylist->head);
for (int i = 0; i < pos; i++)
{
pCur = pCur->next;
}
//创建新节点
struct LinkNode *newnode = malloc(sizeof(struct LinkNode));
newnode->data = data;
newnode->next = NULL;
//新节点入链表
newnode->next = pCur->next;
pCur->next = newnode;
mylist->size++;
return 0;
}
//头插
int PushFront_LinkList(LinkList list, void *data)
{
return InsertByPos_Linklist(list,0,data);
}
//尾插
int PushBack_LinkList(LinkList list, void *data)
{
Lls *mylist = (Lls*)list;
return InsertByPos_Linklist(list,mylist->size,data);
}
//在指定值前面插入节点
int InsertVal_LinkList(LinkList list, void *olddata, void *newdata, int(*compare)(void*, void*))
{
if (list == NULL || olddata == NULL || newdata == NULL || compare == NULL)
{
return -1;
}
Lls *mylist = (Lls*)list;
//定义两个辅助指针
struct LinkNode *pPre = &(mylist->head);
struct LinkNode *pCur = pPre->next;
while (pCur!=NULL)
{
if (compare(pCur->data, olddata))
{
//创建新节点
struct LinkNode *newnode = malloc(sizeof(struct LinkNode));
newnode->data = newdata;
newnode->next = NULL;
//新节点入链表
pPre->next = newnode;
newnode->next = pCur;
mylist->size++;
break;
}
pPre = pCur;
pCur = pCur->next;
}
return 0;
}
//指定位置删除 有效节点为0
int RemoveByPos_LinkList(LinkList list, int pos)
{
if (list == NULL)
{
return -1;
}
Lls *mylist = (Lls*)list;
if (mylist->size == 0)
{
return -1;
}
if (pos<0 || pos>mylist->size - 1)
{
return -1;
}
//辅助指针
struct LinkNode *pCur = &(mylist->head);
for (int i = 0; i < pos; i++)//假如pos=2,那么要删除的值3的节点
{
pCur = pCur->next;
}
//这时pCur是删除节点的上一个节点
//缓存待删除的节点
struct LinkNode *pDel = pCur->next;
//重新建立删除节点的前后关系
pCur->next = pDel->next;
//删除节点
free(pDel);
pDel = NULL;
mylist->size--;
return 0;
}
//根据值来删除
int RemoveByVal_LinkList(LinkList list, void *data, int(*compare)(void*, void *))
{
if (list == NULL || data == NULL || compare == NULL)
{
return -1;
}
Lls *mylist = (Lls*)list;
//两个辅助指针
struct LinkNode *pPre = &(mylist->head);
struct LinkNode *pCur = pPre->next;
while (pCur!=NULL)
{
if (compare(pCur->data, data))
{
pPre->next = pCur->next;
free(pCur);
mylist->size--;
break;
}
pPre = pCur;
pCur = pCur->next;
}
return 0;
}
//头删
int PopFront_LinkList(LinkList list)
{
return RemoveByPos_LinkList(list,0);
}
//尾删
int PopBack_LinkList(LinkList list)
{
Lls *mylist = (Lls*)list;
return RemoveByPos_LinkList(list,mylist->size-1);
}
//遍历
void Foreach_LinkList(LinkList list, void(*forecah)(void *))
{
if (list == NULL || forecah == NULL)
{
return;
}
Lls *mylist = (Lls*)list;
//辅助指针
struct LinkNode *pCur = mylist->head.next;
while (pCur!=NULL)
{
forecah(pCur->data);
pCur = pCur->next;
}
}
//销毁
void Destroy_LinkList(LinkList list)
{
if (list == NULL)
{
return;
}
Lls *mylist = (Lls*)list;
struct LinkNode *pCur = mylist->head.next;
while (pCur!=NULL)
{
//缓存下一个节点的地址
struct LinkNode *pNext = pCur->next;
//释放当前节点
free(pCur);
//移动
pCur = pNext;
}
free(mylist);
mylist = NULL;
}
main.c
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include"Linklist.h"
typedef struct Maker{
char name[64];
int age;
}Maker;
void myPrint(void *data)
{
Maker *m = (Maker*)data;
printf("name:%s age=%d\n", m->name, m->age);
}
int mycompare(void *d1,void *d2)
{
Maker *m1 = (Maker*)d1;
Maker *m2 = (Maker*)d2;
return (strcmp(m1->name, m2->name) == 0) && m1->age == m2->age;
}
int main()
{
Maker p1 = { "aaa", 111 };
Maker p2 = { "bbb", 222 };
Maker p3 = { "ccc", 333 };
Maker p4 = { "ddd", 444 };
Maker p5 = { "eee", 555 };
Maker p6 = { "fff", 666 };
Maker p7 = { "ggg", 777 };
Maker p8 = { "hhh", 888 };
Maker p9 = { "iii", 999 };
//初始化链表
LinkList list = Init_Linklist();
//根据位置插入
InsertByPos_Linklist(list, 0, &p1);
InsertByPos_Linklist(list, 0, &p2);
InsertByPos_Linklist(list, 0, &p3);
InsertByPos_Linklist(list, 0, &p4);
Foreach_LinkList(list, myPrint);
printf("---------------------\n");
InsertByPos_Linklist(list, 2, &p4);
Foreach_LinkList(list, myPrint);
printf("---------------------\n");
InsertVal_LinkList(list, &p2, &p5, mycompare);
//打印
Foreach_LinkList(list, myPrint);
printf("---------------------\n");
//根据位置删除
RemoveByPos_LinkList(list, 3);
Foreach_LinkList(list, myPrint);
printf("---------------------\n");
//根据值删除
RemoveByVal_LinkList(list, &p2, mycompare);
printf("---------------------\n");
Foreach_LinkList(list, myPrint);
//头删,尾删
PopFront_LinkList(list);
PopBack_LinkList(list);
printf("---------------------\n");
Foreach_LinkList(list, myPrint);
//销毁
Destroy_LinkList(list);
system("pause");
return EXIT_SUCCESS;
}
3.linux内核链表
Linklist.h
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//如果用户要用我的容器,那么他的数据必须包含我这个结构体
//用户必须把用户指针转换成struct LinkNode *
struct LinkNode
{
struct LinkNode *next;
};
//链表的结构体
struct LList
{
struct LinkNode head;
int size;
};
typedef void *LinkList;
//初始化
LinkList Init_LinkList();
//指定位置插入
void Inser_LinkList(LinkList list, int pos, struct LinkNode *data);
//遍历
void Foreach_LinkList(LinkList list, void(*foreach)(void *));
//根据位置获取值
void *Get_LinkList(LinkList list, int pos);
//获取元素个数
int Size_LinkList(LinkList list);
//根据位置删除
void RemoveByPos_LinkList(LinkList list, int pos);
//销毁
void Destroy_LinkList(LinkList list);
Linklist.c
#include "Linklist.h"
//初始化
LinkList Init_LinkList()
{
struct LList *list = malloc(sizeof(struct LList));
if (list == NULL)
{
return NULL;
}
list->head.next = NULL;
list->size = 0;
return list;
}
//指定位置插入
void Inser_LinkList(LinkList list, int pos, struct LinkNode *data)
{
if (list == NULL || data == NULL)
{
return;
}
struct LList *mylist = (struct LList *)list;
if (pos<0 || pos >mylist->size)
{
pos = mylist->size;
}
//查找pos位置的前一个节点
struct LinkNode *pCur = &(mylist->head);
for (int i = 0; i < pos; i++)
{
pCur = pCur->next;
}
//将新节点加入链表
data->next = pCur->next;
pCur->next = data;
mylist->size++;
}
//遍历
void Foreach_LinkList(LinkList list, void(*foreach)(void *))
{
if (list == NULL || foreach == NULL)
{
return;
}
struct LList *mylist = (struct LList *)list;
//辅助指针
struct LinkNode *pCur = mylist->head.next;
while (pCur!=NULL)
{
foreach(pCur);
pCur = pCur->next;
}
}
//根据位置获取值
void *Get_LinkList(LinkList list, int pos)
{
if (list == NULL)
{
return NULL;
}
struct LList *mylist = (struct LList *)list;
if (pos<0 || pos>mylist->size - 1)
{
return NULL;
}
//找位置
struct LinkNode *pCur = &(mylist->head);
for (int i = 0; i < pos; i++)
{
pCur = pCur->next;
}
//这时找到的节点是pos的前一个节点
return pCur->next;
}
//获取元素个数
int Size_LinkList(LinkList list)
{
if (list == NULL)
{
return -1;
}
struct LList *mylist = (struct LList *)list;
return mylist->size;
}
//根据位置删除
void RemoveByPos_LinkList(LinkList list, int pos)
{
if (list == NULL)
{
return;
}
struct LList *mylist = (struct LList *)list;
if (mylist->size == 0)
{
return;
}
if (pos<0 || pos>mylist->size)
{
return;
}
struct LinkNode *pCur = &(mylist->head);
//找位置
for (int i = 0; i < pos; i++)
{
pCur = pCur->next;
}
//这时pCur是要删除节点的上一个节点
struct LinkNode * pDel = pCur->next;
//重新建立删除节点的前后关系
pCur->next = pDel->next;
mylist->size--;
}
//销毁
void Destroy_LinkList(LinkList list)
{
if (list == NULL)
{
return;
}
free(list);
list = NULL;
}
main.c
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include"Linklist.h"
//用户的结构体第一个成员必须是
struct Maker
{
struct LinkNode node;
char name[64];
int age;
};
void my_print(void *data)
{
struct Maker *maker = (struct Maker*)data;
printf("name:%s age=%d\n", maker->name, maker->age);
}
int main()
{
struct Maker m1 = { NULL, "aaa", 10 };
struct Maker m2 = { NULL, "bbb", 20 };
struct Maker m3 = { NULL, "ccc", 30 };
struct Maker m4 = { NULL, "ddd", 40 };
struct Maker m5 = { NULL, "eee", 50 };
LinkList mylist=Init_LinkList();
Inser_LinkList(mylist, 0, (struct LinkNode *)&m1);
Inser_LinkList(mylist, 0, (struct LinkNode *)&m2);
Inser_LinkList(mylist, 0, (struct LinkNode *)&m3);
Inser_LinkList(mylist, 0, (struct LinkNode *)&m4);
Foreach_LinkList(mylist, my_print);
printf("----------------\n");
struct Maker *maker = (struct Maker *)Get_LinkList(mylist, 2);
printf("name:%s age=%d\n", maker->name, maker->age);
printf("size=%d\n", Size_LinkList(mylist));
printf("----------------\n");
RemoveByPos_LinkList(mylist, 3);
Foreach_LinkList(mylist, my_print);
Destroy_LinkList(mylist);
system("pause");
return EXIT_SUCCESS;
}
4.传统链表实现栈
LinkStack.h
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct LinkNode
{
//数据域
//指针域
struct LinkNode *next;
};
struct LStack
{
struct LinkNode head;
int size;
};
//初始化
void *Init_LStack();
//入栈
void Push_LStack(void *stack, void *data);
//出栈
void Pop_LStack(void *stack);
//获取栈顶元素
void *Top_LStack(void *stack);
//获取元素个数
int Size_LStack(void *stack);
//销毁栈
void Destroy_LStack(void *stack);
LinkStack.c
#include "LinkStack.h"
//初始化
void *Init_LStack()
{
struct LStack *stack = (struct LStack*)malloc(sizeof(struct LStack));
if (stack == NULL)
{
return NULL;
}
stack->head.next = NULL;
stack->size = 0;
return stack;
}
//入栈
void Push_LStack(void *stack, void *data)
{
if (stack == NULL || data == NULL)
{
return;
}
struct LStack *mystack = (struct LStack *)stack;
struct LinkNode *mydata = (struct LinkNode *)data;
mydata->next = mystack->head.next;
mystack->head.next = mydata;
mystack->size++;
}
//出栈
void Pop_LStack(void *stack)
{
if (stack == NULL)
{
return;
}
struct LStack *mystack = (struct LStack *)stack;
if (mystack->size == 0)
{
return;
}
//定义辅助指针
struct LinkNode *pCur = mystack->head.next;
mystack->head.next = pCur->next;
mystack->size--;
}
//获取栈顶元素
void *Top_LStack(void *stack)
{
if (stack == NULL)
{
return NULL;
}
struct LStack *mystack = (struct LStack *)stack;
return mystack->head.next;
}
//获取元素个数
int Size_LStack(void *stack)
{
if (stack == NULL)
{
return NULL;
}
struct LStack *mystack = (struct LStack *)stack;
return mystack->size;
}
//销毁栈
void Destroy_LStack(void *stack)
{
if (stack == NULL)
{
return NULL;
}
free(stack);
stack = NULL;
}
main.c
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include"LinkStack.h"
struct Maker{
struct LinkNode node;
char name[64];
int age;
};
int main()
{
struct Maker m1 = { NULL,"aaa", 10 };
struct Maker m2 = { NULL,"bbb", 20 };
struct Maker m3 = { NULL,"ccc", 30 };
struct Maker m4 = { NULL,"ddd", 40 };
//初始化链式栈
void *stack = Init_LStack();
//数据入栈
Push_LStack(stack, &m1);
Push_LStack(stack, &m2);
Push_LStack(stack, &m3);
Push_LStack(stack, &m4);
//输出容器中所有元素
while (Size_LStack(stack)>0)
{
//获取栈顶元素
struct Maker *maker = (struct Maker*)Top_LStack(stack);
printf("name:%s age:%d\n", maker->name, maker->age);
//弹出栈顶元素
Pop_LStack(stack);
printf("size:%d\n", Size_LStack(stack));
}
printf("----------------------\n");
printf("size:%d\n", Size_LStack(stack));
Destroy_LStack(stack);
system("pause");
return EXIT_SUCCESS;
}
5.linux内核链表实现栈
注:直接调用3的函数即可
6.传统链表实现队列
SeqQueue.h
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct SQueue{
void *data[1024];//内存,里面的指针,指向用户的数据
int size;
};
//初始化
void *Init_SQueue();
//入队
void Push_SQueue(void *queue, void *data);
//出队
void Pop_SQueue(void *queue);
//获取队头元素
void *Front_SQueue(void *queue);
//获取队尾元素
void *Back_SQueue(void *queue);
//获取元素个数
int Size_SQueue(void *queue);
//销毁队列
void Destroy_SQueue(void *queue);
SeqQueue.c
#include "SeqQueue.h"
//初始化
void *Init_SQueue()
{
struct SQueue *queue = malloc(sizeof(struct SQueue));
if (queue == NULL)
{
return NULL;
}
queue->size = 0;
for (int i = 0; i < 1024; i++)
{
queue->data[i] = NULL;
}
return queue;
}
//入队
void Push_SQueue(void *queue, void *data)
{
if (queue == NULL || data == NULL)
{
return;
}
struct SQueue *myqueue = (struct SQueue *)queue;
//如果队列满了,就不入队
if (myqueue->size == 1024)
{
return;
}
//移动元素,把0的位置空出
for (int i = myqueue->size - 1; i >= 0; i--)
{
myqueue->data[i + 1] = myqueue->data[i];
}
myqueue->data[0] = data;
myqueue->size++;
}
//出队
void Pop_SQueue(void *queue)
{
if (queue == NULL)
{
return;
}
struct SQueue *myqueue = (struct SQueue *)queue;
//如果没有元素
if (myqueue->size == 0)
{
return;
}
myqueue->size--;
}
//获取队头元素
void *Front_SQueue(void *queue)
{
if (queue == NULL)
{
return NULL;
}
struct SQueue *myqueue = (struct SQueue *)queue;
return myqueue->data[myqueue->size - 1];
}
//获取队尾元素
void *Back_SQueue(void *queue)
{
if (queue == NULL)
{
return NULL;
}
struct SQueue *myqueue = (struct SQueue *)queue;
return myqueue->data[0];
}
//获取元素个数
int Size_SQueue(void *queue)
{
if (queue == NULL)
{
return -1;
}
struct SQueue *myqueue = (struct SQueue *)queue;
return myqueue->size;
}
//销毁队列
void Destroy_SQueue(void *queue)
{
if (queue == NULL)
{
return;
}
free(queue);
}
main.c
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include"SeqQueue.h"
struct Maker
{
char name[64];
int age;
};
int main()
{
//创建数据
struct Maker m1 = { "aaa", 10 };
struct Maker m2 = { "bbb", 20 };
struct Maker m3 = { "ccc", 30 };
struct Maker m4 = { "ddd", 40 };
struct Maker m5 = { "eee", 50 };
//初始化队列
void *queue = Init_SQueue();
//数据入队
Push_SQueue(queue, &m1);
Push_SQueue(queue, &m2);
Push_SQueue(queue, &m3);
Push_SQueue(queue, &m4);
Push_SQueue(queue, &m5);
//输出队头队尾元素
struct Maker *Front = (struct Maker*)Front_SQueue(queue);
struct Maker *Back = (struct Maker*)Back_SQueue(queue);
printf("队头元素: name=%s age=%d\n", Front->name, Front->age);
printf("队尾元素: name=%s age=%d\n", Back->name, Back->age);
printf("-------------------------------\n");
while (Size_SQueue(queue)>0)
{
//获取队头元素
struct Maker *Front = (struct Maker*)Front_SQueue(queue);
printf("队头元素: name=%s age=%d\n", Front->name, Front->age);
//出队
Pop_SQueue(queue);
}
printf("size=%d\n", Size_SQueue(queue));
//销毁队列
Destroy_SQueue(queue);
system("pause");
return EXIT_SUCCESS;
}
7.linux内核链表实现队列
注:直接调用3的函数即可
2.树
1.树的遍历及计算叶子节点
#include<iostream>
using namespace std;
typedef struct Node
{
int data;
struct Node *lchild;
struct Node *rchild;
}Node;
//先序遍历
void inOrder(Node *root)
{
if (root == NULL)
{
return;
}
cout << root->data;
inOrder(root->lchild);
inOrder(root->rchild);
}
//中序遍历
void posOrder(Node *root)
{
if (root == NULL)
{
return;
}
posOrder(root->lchild);
cout << root->data;
posOrder(root->rchild);
}
//后序遍历
void nextOrder(Node *root)
{
if (root == NULL)
{
return;
}
nextOrder(root->lchild);
nextOrder(root->rchild);
cout << root->data;
}
//计算叶子节点
int countLeafNum(Node *root)
{
static int count = 0;
if (root == NULL)
{
return count;
}
countLeafNum(root->lchild);
countLeafNum(root->rchild);
if (root->lchild == NULL & root->rchild == NULL)
{
count++;
cout << root->data;
}
}
void text()
{
Node n1; //创建结点
Node n2;
Node n3;
Node n4;
Node n5;
Node n6;
Node n7;
n1.data = 1; //赋值并关联结点
n1.lchild = &n2;
n1.rchild = &n3;
n2.data = 2;
n2.lchild = &n4;
n2.rchild = &n5;
n4.data = 4;
n4.lchild = NULL;
n4.rchild = NULL;
n5.data = 5;
n5.lchild = NULL;
n5.rchild = NULL;
n3.data = 3;
n3.lchild = &n6;
n3.rchild = &n7;
n6.data = 6;
n6.lchild = NULL;
n6.rchild = NULL;
n7.data = 7;
n7.lchild = NULL;
n7.rchild = NULL;
cout << "先序:"; //测试遍历
inOrder(&n1);
cout << endl << "中序:";
posOrder(&n1);
cout << endl << "后序:";
nextOrder(&n1);
cout << endl;
countLeafNum(&n1);
}
int main()
{
text();
system("pause");
return 0;
}
2.计算二叉树的高度
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct BiNode
{
//数据域
char ch;
//指针域
struct BiNode *Lchild;
struct BiNode *Rchild;
}BiNode;
int caculateHeight(BiNode *root)
{
if (root == NULL)
{
return 0;
}
//求左子树的高度
int Lheight = caculateHeight(root->Lchild);
//求右子树的高度
int Rheight = caculateHeight(root->Rchild);
//左右两边那边的值大,就取那边,取值后加1
int height = Lheight > Rheight ? Lheight + 1 : Rheight + 1;
return height;
}
void test()
{
//创建数据
BiNode node1 = { 'A', NULL, NULL };
BiNode node2 = { 'B', NULL, NULL };
BiNode node3 = { 'C', NULL, NULL };
BiNode node4 = { 'D', NULL, NULL };
BiNode node5 = { 'E', NULL, NULL };
BiNode node6 = { 'F', NULL, NULL };
BiNode node7 = { 'G', NULL, NULL };
BiNode node8 = { 'H', NULL, NULL };
//建立二叉树
node1.Lchild = &node2;
node1.Rchild = &node6;
node2.Rchild = &node3;
node3.Lchild = &node4;
node3.Rchild = &node5;
node6.Rchild = &node7;
node7.Lchild = &node8;
int height = caculateHeight(&node1);
printf("高度为:%d\n", height);
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}
3.二叉树的拷贝
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct BiNode
{
//数据域
char ch;
//指针域
struct BiNode *Lchild;
struct BiNode *Rchild;
}BiNode;
void myforeach(BiNode *root)
{
if (root == NULL)
{
return;
}
//先序遍历
//1.先访问根结点
printf("%c ", root->ch);
//2.访问左子树
myforeach(root->Lchild);
//3.访问右子树
myforeach(root->Rchild);
}
BiNode *copyTree(BiNode * root)
{
if (root == NULL)
{
return NULL;
}
//递归拷贝左子树
BiNode *lchild = copyTree(root->Lchild);
//递归拷贝右子树
BiNode *rchild = copyTree(root->Rchild);
//申请空间
BiNode *newnode = malloc(sizeof(BiNode));
//拷贝
newnode->ch = root->ch;
newnode->Lchild = lchild;
newnode->Rchild = rchild;
return newnode;
}
void test()
{
//创建数据
BiNode node1 = { 'A', NULL, NULL };
BiNode node2 = { 'B', NULL, NULL };
BiNode node3 = { 'C', NULL, NULL };
BiNode node4 = { 'D', NULL, NULL };
BiNode node5 = { 'E', NULL, NULL };
BiNode node6 = { 'F', NULL, NULL };
BiNode node7 = { 'G', NULL, NULL };
BiNode node8 = { 'H', NULL, NULL };
//建立二叉树
node1.Lchild = &node2;
node1.Rchild = &node6;
node2.Rchild = &node3;
node3.Lchild = &node4;
node3.Rchild = &node5;
node6.Rchild = &node7;
node7.Lchild = &node8;
myforeach(&node1);
printf("------------\n");
BiNode *root=copyTree(&node1);
myforeach(root);
//释放
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}
4.非递归遍历二叉树(中序遍历)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include"SeqStack.h"
#include<stdbool.h>
typedef struct BiNode
{
//数据域
char ch;
//指针域
struct BiNode *Lchild;
struct BiNode *Rchild;
}BiNode;
typedef struct Info
{
BiNode *node;
bool flag;
}Info;
//给结构体分配空间
Info *CreateInfo(BiNode *node,bool flag)
{
Info *info = malloc(sizeof(Info));
info->node = node;
info->flag = flag;
return info;
}
//非递归遍历
void funcMaker(BiNode *root)
{
//初始化栈
void *stack = Init_SeqStack();
//1.先把根节点入栈
Push_SeqStack(stack, CreateInfo(root, false));
while (Size_SeqStack(stack)>0)
{
//2.获取栈顶元素
Info *info = (Info*)Top_SeqStack(stack);
Pop_SeqStack(stack);
//3.判断当前节点的flag是否是true,如果是true那么打印,
//如果是false,那么继续入栈
if (info->flag)
{
printf("%c ", info->node->ch);
//释放内存
free(info);
//退出当次循环
continue;
}
//右子树入栈
if (info->node->Rchild != NULL)
{
Push_SeqStack(stack, CreateInfo(info->node->Rchild, false));
}
//左子树入栈
if (info->node->Lchild != NULL)
{
Push_SeqStack(stack, CreateInfo(info->node->Lchild, false));
}
//根节点入栈
info->flag = true;
Push_SeqStack(stack, info);
}
}
void test()
{
//创建数据
BiNode node1 = { 'A', NULL, NULL };
BiNode node2 = { 'B', NULL, NULL };
BiNode node3 = { 'C', NULL, NULL };
BiNode node4 = { 'D', NULL, NULL };
BiNode node5 = { 'E', NULL, NULL };
BiNode node6 = { 'F', NULL, NULL };
BiNode node7 = { 'G', NULL, NULL };
BiNode node8 = { 'H', NULL, NULL };
//建立二叉树
node1.Lchild = &node2;
node1.Rchild = &node6;
node2.Rchild = &node3;
node3.Lchild = &node4;
node3.Rchild = &node5;
node6.Rchild = &node7;
node7.Lchild = &node8;
funcMaker(&node1);
}
int main()
{
test();
system("pause");
return EXIT_SUCCESS;
}
5.#法创建树
typedef struct BinaryTreeNode{
char value;
struct BinaryTreeNode *left;
struct BinaryTreeNode *right;
};
void CreateBinaryTree(BinaryTreeNode **T)
{
char data;
scanf("%d",&data);
if(data=='#')
*T=NULL;
else
{
*T=(BinaryTreeNode *)malloc(sizeof(BinaryTreeNode));
(*T)->value=data;
CreateBinaryTree(&((*T)->left));
CreateBinaryTree(&((*T)->right));
}
}
3.常见排序算法(均为升序)
1.选择
void selectSort(int arr[],int len)
{
for (int i = 0; i < len - 1; i++)
{
for (int j = i + 1; j < len; j++)
{
if (arr[i] > arr[j])
{
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
}
2.冒泡
void bubbleSort(int arr[], int len)
{
for (int i = 0; i < len - 1; i++)
{
for (int j = 0; j < len - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
3.插入
void insertSort(int arr[], int len)
{
for (int i = 1; i < len; i++)
{
int insert = arr[i];
int index = i - 1;
while (index >= 0 && insert < arr[index])
{
arr[index + 1] = arr[index];
index--;
}
arr[++index] = insert;
}
}
4.快速
选数组中一个数n,再把比n小的放左边,比n大的放右边,返回n的下标;然后递归,左边再分左右边,右边再分左右边......
5.希尔
直接插入排序的改进版,待插数字的取点步长不是1,而是n,再递减n--
6.归并
4.空间换时间demo
#include<stdio.h>
#include<time.h>
#include<string.h>
//数组中存有1~1000的数字,查找出现次数最多的那个数字以及它的出现次数
//传统算法
int getMaxCntOfNum(int arr[], int len, int *pnum, int *pcnt)
{
if(NULL == arr || 0 > len || NULL == pnum || NULL == pcnt)
{
perror("invalid args...\n");
return -1;
}
int i = 0;
int j = 0;
int cnt = 0;
int cntTmp = 0;
int num = 0;
for(i=1; i<=1000; i++)
{
cntTmp = 0;
for(j=0; j<len; j++)
{
if(i == arr[j])
{
cntTmp++;
}
}
if(cntTmp > cnt)
{
cnt = cntTmp;
num = i;
}
}
*pnum = num;
*pcnt = cnt;
return 0;
}
//创新算法(空间换时间)
int getMaxCntOfNum2(int arr[], int len, int *pnum, int *pcnt)
{
if(NULL == arr || 0 > len || NULL == pnum || NULL == pcnt)
{
perror("invalid args...\n");
return -1;
}
int tmpArr[1001] = {0};
int i = 0;
int cnt = 0;
int index = 0;
for(; i<len; i++)
{
tmpArr[arr[i]]++;
}
for(i=1; i<1001; i++)
{
if(cnt < tmpArr[i])
{
cnt = tmpArr[i];
index = i;
}
}
*pnum = index;
*pcnt = cnt;
return 0;
}
int main(void)
{
int arr[2000001] = {0};
int sec = 0;
int num = 0;
int cnt = 0;
int i = 0;
//初始化数组
for(; i<2000001; i++)
{
if(i < 300)
{
arr[i] = 1;
}
else if(i < 600)
{
arr[i] = 2;
}
else
{
arr[i] = 3;
}
}
sec = time(NULL);
getMaxCntOfNum(arr, sizeof(arr)/sizeof(*arr), &num, &cnt);
sec = time(NULL) - sec;
printf("num[%d] cnt[%d] sec[%d]\n", num, cnt, sec);
sec = time(NULL);
getMaxCntOfNum2(arr, sizeof(arr)/sizeof(*arr), &num, &cnt);
sec = time(NULL) - sec;
printf("num[%d] cnt[%d] sec[%d]\n", num, cnt, sec);
return 0;
}
运行结果,可知,传统算法耗时5s左右,新算法耗时1s不到:
五、设计模式
1.c++中对一个类进行功能扩展的两种方法:1)继承它;2)把这个类的一个对象作为自己的成员。
2.设计模式:简单问题标准化。
3.单例模式:保证一个类只能创建一个实例对象,同时提供能对该对象加以访问的全局访问
//懒汉式单例:只有在至少调用一次该成员函数时,才new出仅有的一个实例,不调用则不创建)
//单例模式
//实现方法:禁类外止通过类名创建对象,而是类内创建唯一的一个对象,用户通过类名调用类的静态成员函数得到该对象的地址
#include<iostream>
using namespace std;
class Single
{
private:
Single()//1.构造函数私有化,禁止调用
{
}
public:
static Single * getSingle()//3.提供全局访问点,并且用户得到的始终是同一个实例对象
{
if (single == NULL)
{
single = new Single();
}
return single;
}
private:
static Single * single;//2.一个指向本类对象的指针成员
};
Single * Single::single = NULL;//成员初始化
int main()
{
//测试对象是否唯一
Single *s = Single::getSingle();
Single *s2 = Single::getSingle();
if (s == s2)
{
cout << "对象唯一" << endl;
}
system("pause");
return 0;
}
懒汉式单例的缺点是不能保证线程安全:比如,有一个单例模式的类,它的全局访问函数还没有被调用过(即它唯一的实例还没有被创建),这时,有两个线程差不多同时调用它的全局访问函数,而碰巧这个类的构造函数里有一个Sleep(30000)延时函数,第一个线程中,开始调用构造函数创建对象,卡在延时语句(此时因为构造函数没有执行完毕,实例暂时还没创建成功),这时第二个线程调用全局访问函数,判断成员指针仍然还为空,又调用构造函数创建对象,最后结果是两个线程各创建了该类一个实例对象!单例模式被打破!
懒汉式单例缺点总结:(1)每次调用都有判断指针是否为空(实例是否已经创建),使程序相对开销增大;(2)多线程中会导致多个实例的产生,从而导致运行代码不正确以及内存的泄漏;(3)要提供释放资源的函数。
//饿汉式单例:在给类的指向自身的成员指针初始化的时候直接创建一个对象,而不是等调用访问函数的时候再创建。
饿汉式的缺点:可能提前消耗空间。
六、linux系统编程
1.概念
1.linux静态库的制作。
2.linux动态库的制作。
3.windows静/动态库的制作。
4.缓冲区(内核缓冲区/用户缓冲区)作为程序与磁盘文件的交互介质,缓冲区映射于内存条中,可提高读写效率。
5.数据从内存刷新到磁盘:(1)刷新缓冲区fflush; (2)缓冲区已满; (3)正常关闭文件:fclose;(4)程序正常结束:return 、exit。
6.虚拟内存中的共享库中存放的是c标准库和linux系统io函数以及程序中使用到的动态库(静态库一般直接放在代码段)等。
7.使用虚拟空间映射物理空间的目的:(1)程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的空间;(2)使不同进程之间地址彼此隔离,互无影响。
8.printf(stdout)的工作流程:应用层write()操作用户空间得到相关内容,系统调用sys_write()操作内核空间调用内核层的设备驱动函数完成在屏幕的显示。
9.程序是编译好的二进制文件,一般存储在磁盘上,不占用系统资源(cpu、内存等);进程是已经在内存中执行着的程序。程序相当于剧本,进程是正在上演的剧。
10.系统资源:内存、cpu、打开的文件、设备、锁 进程:活跃的程序占用系统资源 多道程序设计:并发是宏观并行,微观串行。
11.计算器的32、64位是针对其寄存器而言。32位系统中,同一用户的不同进程共享1G虚拟内核空间,独享3G虚拟用户空间。
12.a.out的运行过程:可执行文件最开始存在磁盘上,被读到内存,再被读到缓存,再以单条指令的形式依靠寄存器进入cpu进行处理,处理之后再一级级往回放入到内存再显示到屏幕上。
13.MMC内存管理单元:完成物理内存与虚拟内存的映射,修改内存的访问级别。4G的虚拟内存中,0-3G是用户空间,3-4G是内核空间。MMC以4k为单位将虚拟内存映射到物理内存。
14.每个进程的PCB进程控制块都在共享的内核区,都存储着所属进程的相关信息。
15.时钟中断可以强制使进程让出cpu,是多道程序设计的基础。
16.进程的4种状态:就绪(等待cpu)—>运行(占用cpu)—>挂起(等待cpu之外的资源,主动放弃cpu)—>终止 另:初始态为进程准备阶段,常与就绪合并。
17.linux终端输入的指令(如ls)其实shell解析器会将它解析,找到同名的可执行文件,该可执行文件的路径在环境变量中指定的bin下,所以可直接调用,而当我们要运行自己的可执行文件时,则要指定路径(至少也要./a.out)。
18.打印当前进程的所有环境变量:extern char **environ;for(int i=0;environ[i];i++){ printf("%s\n",environ[i]);}
19.使用fork()函数可创建子进程,理论上,父子进程都有各自的用户空间,父进程的fork();语句之前定义的所有变量子进程都会拷贝过去,但实际上,父子进程间是读时共享,写时复制,即当子进程要对本该从父进程复制过来的变量进行写操作时, 系统才会为子进程的该变量真正开辟空间。
20.文件描述符是文件描述符表中的编号,编号所表示的存储空间中存着一个指针,指针指向一个file struct,这个结构体存储着一个已经打开的文件的描述信息。
21.文件描述符的拷贝(dup,dup2)实际上是指针的复制。
22.孤儿进程:父进程先结束,由系统领养,结束后由系统回收资源 。
僵尸进程:子进程结束,父进程还未结束,但父进程没有回收其资源,这里的指PCB等内核区资源未回收, 0~3G的用户空间已经释放 。
怎么把僵尸进程变成孤儿进程:把父进程杀死,其便入了“孤儿院”,由系统回收。
23.同一主机的进程间通信方式:
管道(使用最简单),信号(开销最小),共享映射区(无血缘关系),本地套接字(最稳定)
(1)无名管道pipe:在1G内核区申请一块空间用于亲属关系进程间通信。
(2)有名管道fifo:新建磁盘文件用于进程间通信。
(3)内存映射mmap:同样新建磁盘文件,但使用它进行通信的进程们会在自身虚拟内存的堆和栈之间的共享库映射该文件。
(4)匿名映射:即内存映射的特殊情况,不使用磁盘文件,直接通过彼此的共享库(原理待查,感觉就是升级版的pipe)。
(5)共享内存shm:要通信的进程在内存中映射同一段物理空间,不需要同步到磁盘文件,所以比内存映射速度快,但不安全。
24.内存映射mmap和共享内存shm的区别:前者映射在磁盘中的文件且内存条有镜像,后者只映射内存条;并且mmap是写入或读取自身用户区的共享库,即两个进程的共享库映射同一个磁盘文件(当磁盘文件和共享库相互同步时,肯定是要经过内核的),shm则是在内核区开辟空间,内核区是所有进程共享的,所以共享内存是最快的!
25.每个进程收到的信号都是由内核负责发送的,进程之间发送信号也是通过内核递送的,因为内核区才是进程们共享的。
26.未决信号集和信号屏蔽字均位于进程的PCB中,当进程接受到某个信号后,该进程的未决信号集中表示该信号的状态位马上由0反转为1,如果信号屏蔽字对应的位上是非阻塞位,则该信号马上由内核递达给信号接受者,状态位反转为0,如果信号屏蔽字对应的位上是阻塞位,则该信号暂不处理。
27.信号的四要素:编号、名称、触发该信号的事件、默认的处理动作。
28.欲改变进程的未决信号集和信号屏蔽字可创建与它们同类型的变量,把设置好该变量之后拿去与未决信号集和信号屏蔽字进行位或运算即可。
29.信号捕捉可以改变信号的默认处理动作,信号捕捉的回调函数由内核负责调用。
30.linu环境变量的设置:
vim /etc/profile;
加上:export PATH=$PATH:可执行文件所在目录(不需要写可执行文件名);
使生效:source /etc/profile;
31.对于单核cpu,多线程的意义在于那些会发生阻塞的任务,一个线程在阻塞的时候其他线程还能去执行任务,这也算充分利用了cpu了。
2.代码
七、linux网络编程
1.概念
1.指令补充:(1)wc 普通文件名 :显示行数、词数、字符数;(2)du : 显示当前目录大小 -h ;(3)od -tc 普通文件名 : 以指定格式显示文件数据;(4)df :磁盘使用情况 -h。
2.网络中传输的数据包的层层打包解包工作由操作系统自动完成。数据包最终从网卡进入网络。
3.每个路由器的路由表都记录了自己所连接的路由器,ARP请求只知道目的IP,目的是获取目的MAC(以广播的方式)。
4.路由器会把终端的局域网IP映射为公网IP(该路由器的IP+不同的端口),再将其数据包放入公网中传输。
5.不同ip之间的通信:
(1)公网IP————公网IP : 直接访问
(2)公网IP————私有IP : NAT映射
(3)私有IP————公网IP :NAT映射
(4)私有IP————私有IP (不同局域网之间的) :NAT映射,打洞机制
6.为啥数据传入网络流之前要转大端模式:因为数据在网络中传输时,都是以大端模式的,是指数据在网线光缆中是大端模式,当解包将数据包中的部分数据(IP MAC port等)用于寻路时,网络结点中的路由器再把接收的数据转为小端模式用于数据对比。还是指链路层的mac、网络层的ip、传输层的port的存储都是大端模式?我想应该是后者!
7.点分十进制字符串——>网络字节序(大端模式):inet_pton() 网络字节序——>点分十进制字符串:inet_ntop()。
8.多线程多进程的高并发的目的只是允许多个客户端同时连接一个服务器,但并发是宏观并行,微观并发,服务器对这些客户端的处理并不是同时进行,仍然需要排队抢占cpu。最大的作用就是服务器监听连接和处理已连接的数据可以同时进行(尽管这个同时也是排队进行)。
9.线程池:多线程多进程的高并发可能需要频繁的创建销毁线程,十分耗时耗资源,而线程池的思想就是,一开始就创建好若干线程用于处理连接上的客户端,客户端的传输的数据放入队列,排队等待这些线程处理。
10.当使用程序中有使用静态库,因为编译的时候,静态库的所有代码都被链接到了程序的可执行代码中,所以运行可执行代码时可以直接运行;而当程序中使用动态库时,动态库的代码并没有链接到程序的可执行代码中,它是运行时再去调用(应该是程序启动时根据路径加载进共享库),所以系统加载可执行代码时还要知道动态库的绝对路径以及系统动态加载器的帮助。
11.地理位置相近的计算机可通过网线、交换机连接起来,它们即组成了一个局域网,彼此之间可以互相传送数据,用UDP协议。若这些计算机每台都存有数据,希望手机通过wifi访问它们,则需要路由器产生无线信号。若租个公网IP,在交换机之后再加个路由器,里面的每台计算机即可访问外网。路由器这里有一个作用是NAT映射,把局域网内的虚拟IP通过路由器公网IP+端口号的方式映射使这个局域网内共享一个公网IP的计算机们能以独特的身份去访问外网。两个不同局域网的QQ用户互发消息其实可以通过p2p技术(打洞机制)来实现数据不经过服务器而成功通信,但现在实际情况是要经过服务器的,原因有:1)便于配合政府省察,服务器会自动过滤一些非法数据;2)用手机端发消息时,电脑端、ipad端也要同步上去;3)对方不在线,发送离线消息要先在服务器上保存等等。但通过视频聊天时,需要传输的数据较大,如果也要经过服务器,那效率会较低成本也会提高,所以一般请求连接时确定数据传输路线,其它视频聊天的影像数据通过p2p技术实现传输,一般用的UDP协议,UDP丢包可能性大,所以我们视频通话有时会发现画面卡顿之后衔接不上的情况。
12.传统多线程实现高并发是主线程监听连接,然后分别创建子线程处理每个连接的业务,它的缺点是线程之间频繁切换开销较大;而select/epoll多路io复用是单进程模式,每个逻辑流程都能访问该进程的所有地址空间,开销较小。
13.所谓的滑动窗口就是指数据的发送方和 接受方会分别申请一块缓冲区用于存放要发送或者接受的数据,当该缓冲区放不下时就会通知对方暂时停止发送或者...
开发中的注意事项:一个socket有两个滑动窗口(一个sendbuf、一个recvbuf),两个窗口的大小是可以通过setsockopt函数设置的。
(1)TCP的滑动窗口大小实际上就是socket的接收缓冲区大小的字节数。
(2)对于server端的socket一定要在listen之前设置缓冲区大小,因为,accept时新产生的socket会继承监听socket的缓冲区大 小。对于client端的socket则一定要在connect之前设置缓冲区大小,因为connect时需要进行三次握手过程,会通知对方自己的窗口大小。在 connect之后再设置缓冲区,已经没有什么意义。
2.代码
1.c/s模型 tcp
server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 80
#define SERV_PORT 6666
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
//创建监听套接字
//参数:1.使用IPv4地址;2.使用TCP传输;3.0表示使用默认协议。
listenfd = socket(AF_INET, SOCK_STREAM, 0);
//初始化监听绑定参数,指定当前进程使用的网络地址和端口号
bzero(&servaddr, sizeof(servaddr));//结构体清零
servaddr.sin_family = AF_INET;//使用IPv4地址
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);//任意IP,监听所有,客户端连接时确定才IP
servaddr.sin_port = htons(SERV_PORT);//指定端口号
//绑定
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 20); //监听
printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
//循环阻塞等待连接 本程序的功能是:读取新连接上的客户端发来的一句消息,转换大小写之后发回去,然后关闭连接,下一轮循环继续阻塞等待新连接
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
n = read(connfd, buf, MAXLINE);
printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),ntohs(cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
write(connfd, buf, n);
close(connfd);//关闭通信套接字
}
return 0;
}
client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[]) //从命令行获取要给服务器发送的消息
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
char *str;
if (argc != 2) {
fputs("usage: ./client message\n", stderr);
exit(1);
}
str = argv[1];
sockfd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
bzero(&servaddr, sizeof(servaddr)); //初始化连接参数
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //连接服务器
write(sockfd, str, strlen(str)); //向服务器发送消息
n = read(sockfd, buf, MAXLINE); //读取服务器发过来的消息
printf("Response from server:\n");
write(STDOUT_FILENO, buf, n); //在终端显示
close(sockfd); //关闭套接字
return 0;
}
2.多进程并发服务器
server.c
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include<ctype.h>
#define MAXLINE 80
#define SERV_PORT 8888
void do_sigchild(int num)//信号处理的新动作
{
while(waitpid(0,NULL,WNOHANG)>0);
}
int main(void)
{
struct sockaddr_in servaddr,cliaddr;
socklen_t cliaddr_len;
int listenfd,connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i,n;
pid_t pid;
struct sigaction newact;//新的信号处理参数
newact.sa_handler=do_sigchild;//新的信号处理函数
sigemptyset(&newact.sa_mask);//设置非阻塞
newact.sa_flags=0;//使用默认属性
sigaction(SIGCHLD,&newact,NULL);//注册信号 子进程退出时默认内核会给父进程发送SIGCHLD信号
//创建监听套接字
listenfd=socket(AF_INET,SOCK_STREAM,0);
//初始化绑定参数
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
//绑定
bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
//设定连接上限
listen(listenfd,2);
printf("等待连接...\n");
while(1)
{
//循环监听 创建进程 实现高并发
cliaddr_len=sizeof(cliaddr);
connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddr_len);//每轮都是阻塞等待
pid=fork();
if(0==pid)//子进程
{
close(listenfd);
while(1)
{
n=read(connfd,buf,MAXLINE);
if(0==n)
{
puts("对方已断开连接");
break;
}
printf("ip %s 端口 %d 已连接上\n",inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
//换成大写给他重新发回去
for(i=0;i<n;i++)
{
buf[i]=toupper(buf[i]);
}
write(connfd,buf,n);
}
close(connfd);
return 0;
}
else if(pid>0)//父进程
{
close(connfd);//父进程不负责通信,只负责客户端的连接
}
else
{
perr_exit("fork");
}
}
close(listenfd);
return 0;
}
client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define MAXLINE 80
#define SERV_PORT 6666
int main(void)
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd,n;
//创建连接套接字
sockfd=socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr);
servaddr.sin_port=htons(SERV_PORT);
//连接
connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
while(fgets(buf,MAXLINE,stdin)!=NULL)//fgets()的执行状态无非3种:阻塞、NULL、!NULL
{
write(sockfd,buf,strlen(buf));
n=Read(sockfd,buf,MAXLINE);
if(0==n)
{
printf("对方已经关闭连接\n");
break;
}
else
{
write(STDOUT_FILENO,buf,n);
}
}
close(sockfd);
return 0;
}
3.多线程高并发服务器
server.c
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include<ctype.h>
#define MAXLINE 80
#define SERV_PORT 6666
struct s_info
{
struct sockaddr_in cliaddr;//存储客户端地址
int connfd;//存储通信套接字描述符
};
//线程处理函数
void *do_work(void *arg)
{
int n,i;
struct s_info *ts=(struct s_info*)arg;//承接当前线程所掌管的客户端的属性
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
//设置线程分离,结束后由系统自动回收其资源
pthread_detach(pthread_self());
while(1)
{
n=read(ts->connfd,buf,MAXLINE);//阻塞读取
if(0==n)
{
printf("对方已断开连接\n");
break;
}
printf("收到ip:%s port:%d的信息:%s\n",inet_ntop(AF_INET,&(*ts).cliaddr.sin_addr,str,sizeof(str)),ntohs((*ts).cliaddr.sin_port),buf);
//转为大写并回发
for(i=0;i<n;i++)
{
buf[i]=toupper(buf[i]);
}
write(ts->connfd,buf,n);
}
close(ts->connfd);
}
int main(void)
{
struct sockaddr_in servaddr,cliaddr;
socklen_t cliaddr_len;
int listenfd,connfd;
int i=0;
pthread_t pid;
struct s_info ts[256];
//创建绑定套接字
listenfd=socket(AF_INET,SOCK_STREAM,0);
//初始化绑定监听参数
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
//绑定
bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
//设置连接上限
listen(listenfd,2);
printf("等待连接...\n");
//循环阻塞等待并处理连接
while(1)
{
cliaddr_len=sizeof(cliaddr);
connfd=accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddr_len);
//储存连接上的客户端信息
ts[i].cliaddr=cliaddr;
ts[i].connfd=connfd;//服务器用于与客户端通信的套接字
//创建线程,把连接上的客户端交于线程处理
//此处pid仅作临时变量使用,每创建一个新的线程,上一个线程的id号就被覆盖了
pthread_create(&pid,NULL,do_work,(void*)&ts[i]);
//printf("pid[%ld]\n", pid);
i++;
}
return 0;
}
client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc,char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd,n;
//创建连接套接字
sockfd=socket(AF_INET,SOCK_STREAM,0);
//设置连接参数
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr);
servaddr.sin_port=htons(SERV_PORT);
//连接
connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
while(fgets(buf,MAXLINE,stdin)!=NULL)
{
write(sockfd,buf,strlen(buf));
n=read(sockfd,buf,MAXLINE);
if(0==n)
{
puts("连接已断开");
break;
}
else
{
write(STDOUT_FILENO,buf,n);
}
}
close(sockfd);
return 0;
}
多路IO转接服务器
该服务器实现的主旨思想是,不再依靠应用程序自己监视客户端的连接,而是由内核监视文件。
1.select的使用
(1)select用法核心:其中间3个参数分别是需要监听其读事件、写事件、异常事件是否产生的位图集,它们是传入传出参数,使用前先把要监听的文件描述符通过其特定的方法接口添加进去,使用后,三个参数中的值便是传入时添加的文件描述符中产生了相关事件的文件描述符集,可以通过其特定的接口查看哪些文件描述符在其中,再作相关处理。
(2)特点:传统高并发服务器的实现是在服务端用数组存储连接上来的客户端套接字句柄,然后为每个通信套接字创建线程,用read()阻塞等待来自客户端的数据。而select()则是,把连接上来的客户端套接字句柄添加到一个数字位集中,由select()阻塞检测是否有数据到来,若有,则select()结束阻塞。
server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE]; //FD_SETSIZE 默认为 1024
ssize_t n;
fd_set rset, allset;//因为描述符集是传入传出参数,传出会覆盖传入,所以定义两个一个备用
char buf[MAXLINE];
char str[INET_ADDRSTRLEN]; //define INET_ADDRSTRLEN 16
socklen_t cliaddr_len;
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0); //创建监听套接字,等待客户端连接,连接的客户端信息肯定也是封装成某种数据格式丢到读缓冲,等待accept()函数去处理
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //绑定
listen(listenfd, 20); /* 默认最大128 */
maxfd = listenfd; /* 初始化 */
maxi = -1; /* client[]的下标 */
for (i = 0; i < FD_SETSIZE; i++)
{
client[i] = -1; /* 用-1初始化client[],client[]用于存储连接上的客户端的文件描述符,以便接收数据 */
}
FD_ZERO(&allset);/*清零*/
FD_SET(listenfd, &allset); /*把监听描述符添加到文件描述符集,listenfd其实就是作为数组索引 */
for ( ; ; )
{
rset = allset; //设置要监听其读事件的文件描述符集
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if (nready < 0)
perr_exit("select error");
if (FD_ISSET(listenfd, &rset)) //如果监听描述符有读事件产生,则说明有新客户端申请连接(FD_ISSET函数就是一个算法,查询listenfd在rset对于的结构数据中有没有读事件产生的置位)
{
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),ntohs(cliaddr.sin_port));
for (i = 0; i < FD_SETSIZE; i++)
{
if (client[i] < 0)
{
client[i] = connfd; //新连接的客户端文件描述符保存到client[]里
break;
}
}
/* 达到select能监控的文件个数上限 1024 */
if (i == FD_SETSIZE)
{
fputs("too many clients\n", stderr);
exit(1);
}
FD_SET(connfd, &allset); /* 添加一个新的连接描述符到监控信号集里(置位)*/
if (connfd > maxfd)
maxfd = connfd; /* select第一个参数需要 */
if (i > maxi)
maxi = i; /* 更新client[]最大下标值 */
if (--nready == 0)
continue; /* 如果没有更多的就绪文件描述符继续回到上面select阻塞监听,
负责处理未处理完的就绪文件描述符 */
}
for (i = 0; i <= maxi; i++) //遍历检查已经连接上的所有客户端哪些有读事件产生
{
/* 检测哪个clients 有数据就绪 */
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset))
{
if ( (n = Read(sockfd, buf, MAXLINE)) == 0)
{
close(sockfd); /* 当client关闭链接时,服务器端也关闭对应链接 */
FD_CLR(sockfd, &allset); /* 解除select监控此文件描述符 */
client[i] = -1;
}
else
{
int j;
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
write(sockfd, buf, n);
}
if (--nready == 0)
break;
}
}
}
close(listenfd);
return 0;
}
client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (fgets(buf, MAXLINE, stdin) != NULL)
{
write(sockfd, buf, strlen(buf));
n = read(sockfd, buf, MAXLINE);
if (n == 0)
printf("the other side has been closed.\n");
else
write(STDOUT_FILENO, buf, n);
}
close(sockfd);
return 0;
}
2.pselect的使用
略
3.poll的使用
polls相当于select的优化升级版,最大监听数突破1024,可自由设置,监听集合和返回集合分离,不再是select的返回集合把监听集合覆盖,而且select使用位图集(整型数)监听\返回,poll使用结构体数组
server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
#define OPEN_MAX 1024
int main(int argc, char *argv[])
{
int i, j, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXLINE], str[INET_ADDRSTRLEN];
socklen_t clilen;
struct pollfd client[OPEN_MAX];
struct sockaddr_in cliaddr, servaddr;
listenfd = Socket(AF_INET, SOCK_STREAM, 0); //创建监听套接字
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //绑定
Listen(listenfd, 20); //设置监听
client[0].fd = listenfd;
client[0].events = POLLIN; //listenfd监听普通读事件
for (i = 1; i < OPEN_MAX; i++)
client[i].fd = -1; /* 用-1初始化client[]里剩下元素 */
maxi = 0; /* client[]数组有效元素中最大元素下标 */
for ( ; ; )
{
nready = poll(client, maxi+1, -1); /* 阻塞 */
if (client[0].revents & POLLIN) //如果监听文件描述符有读事件产生,则说明有新客户端请求连接
{
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 1; i < OPEN_MAX; i++)
{
if (client[i].fd < 0)
{
client[i].fd = connfd; // 找到client[]中空闲的位置,存放accept返回的connfd
break;
}
}
if (i == OPEN_MAX)
perr_exit("too many clients");
client[i].events = POLLIN; /* 设置刚刚返回的connfd,监控读事件 */
if (i > maxi)
maxi = i; /* 更新client[]中最大元素下标 */
if (--nready <= 0)
continue; /* 没有更多就绪事件时,继续回到poll阻塞 */
}
for (i = 1; i <= maxi; i++)
{ /* 检测client[] */
if ((sockfd = client[i].fd) < 0)
continue;
if (client[i].revents & POLLIN)
{
if ((n = Read(sockfd, buf, MAXLINE)) < 0) {
if (errno == ECONNRESET)
{ /* 当收到 RST标志时 */
/* connection reset by client */
printf("client[%d] aborted connection\n", i);
Close(sockfd);
client[i].fd = -1;
}
else
{
perr_exit("read error");
}
}
else if (n == 0)
{
/* connection closed by client */
printf("client[%d] closed connection\n", i);
Close(sockfd);
client[i].fd = -1;
}
else
{
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
Writen(sockfd, buf, n);
}
if (--nready <= 0)
break; /* no more readable descriptors */
}
}
}
return 0;
}
client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
sockfd = Socket(AF_INET, SOCK_STREAM, 0); //创建监听套接字
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //连接
while (fgets(buf, MAXLINE, stdin) != NULL) //循环发收数据
{
Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0)
printf("the other side has been closed.\n");
else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0;
}
4.ppoll的使用
略
5.epoll的使用1
epoll的红黑树存放在哪?如果在内核,是否每次对红黑树的上树、下树、删除等操作都要访问内核?这样效率是不是低?EPOLL的理解和深入分析_epoll pollfd-CSDN博客
server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <errno.h>
#include "wrap.h"
#include<ctype.h>
#define MAXLINE 80
#define SERV_PORT 6666
#define OPEN_MAX 1024
int main(void)
{
int i,j,maxi,listenfd,connfd,sockfd;
int nready,efd,res;
ssize_t n;
char buf[MAXLINE],str[INET_ADDRSTRLEN];
socklen_t clilen;
int client[OPEN_MAX];
struct sockaddr_in cliaddr,servaddr;
struct epoll_event tep,ep[OPEN_MAX];
//创建监听套接字
listenfd=Socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
//绑定监听
Bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
Listen(listenfd,20);
//初始化通信描述符数组
for(i=0;i<OPEN_MAX;i++)
{
client[i]=-1;
}
maxi=-1;
//创建epoll句柄
efd=epoll_create(OPEN_MAX);
if(-1==efd)
{
perr_exit("epoll_create");
}
//连接监听上树
tep.events=EPOLLIN;
tep.data.fd=listenfd;
res=epoll_ctl(efd,EPOLL_CTL_ADD,listenfd,&tep);
if(-1==res)
{
perr_exit("epoll_ctl");
}
//循环处理
while(1)
{
nready=epoll_wait(efd,ep,OPEN_MAX,-1);//阻塞监听(一旦树上节点有变化,则停止阻塞,往下运行)
if(-1==nready)
{
perr_exit("epoll_wait");
}
for(i=0;i<nready;i++)
{
if(!ep[i].events & EPOLLIN)//本程序只关注读缓冲的变化
continue;
if(ep[i].data.fd==listenfd)//如果有新连接请求
{
clilen=sizeof(cliaddr);
connfd=Accept(listenfd,(struct sockaddr*)&cliaddr,&clilen);
printf("received from %s at PORT %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),ntohs(cliaddr.sin_port));
//把新连接的通信描述符加进数组
for(j=0;j<OPEN_MAX;j++)
{
if(client[j]<0)
{
client[j]=connfd;
break;
}
}
if(j==OPEN_MAX)
{
perr_exit("too many client");
}
if(j>maxi)
{
maxi=j;
}
//新连接上的客户端监听上树
tep.events=EPOLLIN;
tep.data.fd=connfd;
res=epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&tep);
if(-1==res)
{
perr_exit("epoll_ctl");
}
}
else//如果是客户端有信息发送过来
{
sockfd=ep[i].data.fd;
n=Read(sockfd,buf,MAXLINE);
if(0==n)//如果对方关闭连接
{
for(j=0;j<=maxi;j++)
{
if(client[j]==sockfd)
{
client[j]=-1;
break;
}
}
//下树
res=epoll_ctl(efd,EPOLL_CTL_DEL,sockfd,NULL);
if(-1==res)
{
perr_exit("epoll_ctl");
}
Close(sockfd);
printf("client[%d] closed connection\n",j);
}
else
{
for(j=0;j<n;j++)
{
buf[j]=toupper(buf[j]);
}
Writen(sockfd,buf,n);
}
}
}
}
close(listenfd);
close(efd);
return 0;
}
client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
sockfd = Socket(AF_INET, SOCK_STREAM, 0); //创建通信套接字
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); //请求连接
while (fgets(buf, MAXLINE, stdin) != NULL) { //循环发收数据
Write(sockfd, buf, strlen(buf));
n = Read(sockfd, buf, MAXLINE);
if (n == 0)
printf("the other side has been closed.\n");
else
Write(STDOUT_FILENO, buf, n);
}
Close(sockfd);
return 0;
}
6.epoll的使用2
server.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>
#define MAXLINE 10
int main(int argc,char **argv[])
{
int efd,i;
int pfd[2];
pid_t pid;
char buf[MAXLINE],ch='a';
pipe(pfd); //创建无名管道
pid=fork();
//子进程
if(0==pid)
{
close(pfd[0]); //关闭读端
while(1)
{
for(i=0;i<MAXLINE;i++)
{
buf[i]=ch;
}
buf[i-1]='\n';
ch++;
//写入管道
write(pfd[1],buf,sizeof(buf));
sleep(2);
}
close(pfd[1]);
}
else if(pid>0)//父进程
{
struct epoll_event event;
struct epoll_event resevent[10];
int res,len;
close(pfd[1]);
//创建epoll句柄
efd=epoll_create(10);
//读管道上树(此例就是一对父子进程通过无名管道通信)
event.events=EPOLLIN | EPOLLET;
event.data.fd=pfd[0];
epoll_ctl(efd,EPOLL_CTL_ADD,pfd[0],&event);
//循环处理
while(1)
{
res=epoll_wait(efd,resevent,10,-1);//阻塞监听
printf("%d\n",res);
if(resevent[0].data.fd==pfd[0])//如果读缓冲区有据
{
len=read(pfd[0],buf,MAXLINE);
write(STDOUT_FILENO,buf,len);
}
}
close(pfd[0]);
close(efd);
}
else//进程创建失败
{
perror("fork");
exit(-1);
}
return 0;
}
7.epoll的使用3
server.c
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#define MAXLINE 10
#define SERV_PORT 8080
int main(void)
{
struct sockaddr_in servaddr,cliaddr;
socklen_t cliaddr_len;
int listenfd,connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i,efd;
Listenfd=Socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
Bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
Listen(listenfd,20);
struct epoll_event event;//树节点
struct epoll_event resevent[10];
int res,len;
//创建句柄
efd=epoll_create(10);
printf("等待连接\n");
cliaddr_len=sizeof(cliaddr);
connfd=Accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddr_len);
printf("ip:%s port:%d已连接上\n",inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
//上树
event.data.fd=connfd;
event.events=EPOLLIN | EPOLLET;//边沿触发
epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&event);
//循环处理(只支持一个客户端的连接,因为循环内部没有监听服务器的端口)
while(1)
{
res=epoll_wait(efd,resevent,10,-1);//阻塞监听
printf("res %d\n",res);
if(resevent[0].data.fd==connfd)
{
len=read(connfd,buf,MAXLINE);
write(STDOUT_FILENO,buf,len);
}
}
return 0;
}
client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define MAXLINE 10
#define SERV_PORT 8080
int main(void)
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd,i;
char ch='a';
sockfd=Socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr);
servaddr.sin_port=htons(SERV_PORT);
Connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
while(1)
{
for(i=0;i<MAXLINE;i++)
{
buf[i]=ch;
}
buf[i-1]='\n';
ch++;
Write(sockfd,buf,sizeof(buf));
sleep(10);
}
Close(sockfd);
return 0;
}
8.epoll的使用4
server.c
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#define MAXLINE 10
#define SERV_PORT 8080
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, efd, flag;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 20);
struct epoll_event event;
struct epoll_event resevent[10];
int res, len;
efd = epoll_create(10);
/* event.events = EPOLLIN; */
event.events = EPOLLIN | EPOLLET; /* ET 边沿触发 ,默认是水平触发 */
printf("Accepting connections ...\n");
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
flag = fcntl(connfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(connfd, F_SETFL, flag);
event.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
while (1) {
printf("epoll_wait begin\n");
res = epoll_wait(efd, resevent, 10, -1);
printf("epoll_wait end res %d\n", res);
if (resevent[0].data.fd == connfd) {
while ((len = read(connfd, buf, MAXLINE/2)) > 0)
write(STDOUT_FILENO, buf, len);
}
}
return 0;
}
client.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define MAXLINE 10
#define SERV_PORT 8080
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, i;
char ch = 'a';
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (1) {
for (i = 0; i < MAXLINE/2; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
for (; i < MAXLINE; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
write(sockfd, buf, sizeof(buf));
sleep(10);
}
Close(sockfd);
return 0;
}
八、数据库
oracle
1.oracle的体系结构:1个数据库n个实例,每个实例中可以有多个用户。数据库位于磁盘空间,实例位于内存,实例类似于一个守护进程。
1.oracle : sqlplus / as sysdba ——> startup ——> quit ——> sqlplus scott/123456
mysql
概念区
1.mysql的体系结构:1个实例操作n个数据库。
2.mysql: mysql -uroot -p123456 ——> show databases ——> ues mysql ——> show tables
3.su - 用户名:切换用户。
4.“ show create table 表名;” 可以查看表的编码。
5.show variables like 'character%'; 可查看数据库默认编码。
6.int mysql_set_character_set( MYSQL * mysql, char * csname) ;可设置当前连接的默认字符集。
7.mysql默认事务是自动提交,即每执行一条指令会隐式将更新同步到物理磁盘,可以通过:
show session variables like 'autocommit';
或者
show global variables like 'autocommit';
查看。
代码区
1.API查询表,并显示结果
#include<stdio.h>
#include"mysql.h"
int main(void)
{
//创建句柄指针
MYSQL *mysql=NULL;
int ret=0,num=0,i=0;
//初始化
mysql=mysql_init(NULL);
if(mysql==NULL)
{
ret=mysql_errno(mysql);
printf("init err:%d\n",ret);
return ret;
}
printf("init ok...\n");
//连接数据库
mysql=mysql_real_connect(mysql,"localhost","root","712jkd","mysql",0,NULL,0);
if(mysql==NULL)
{
ret=mysql_errno(mysql);
printf("mysql_real_connect err:%d\n",ret);
return ret;
}
printf("connect ok...\n");
//设置编码字符集
ret=mysql_set_character_set(mysql,"utf8");
if(ret!=0)
{
return ret;
}
printf("set_character ok...\n");
//执行sql语句
char *psql="select *from student";
ret=mysql_query(mysql,psql);
if(ret!=0)
{
printf("mysql_query err:%d\n",ret);
return ret;
}
printf("query ok...\n");
//获取结果集
MYSQL_RES *result=NULL;
result=mysql_store_result(mysql);
if(result==NULL)
{
ret=mysql_errno(mysql);
printf("mysql_store_result err:%d\n",ret);
return ret;
}
printf("store ok...\n");
//获取表的列数
num=mysql_field_count(mysql);
//打印表头
MYSQL_FIELD *fields;
fields=mysql_fetch_fields(result);
for(i=0;i<num;i++)
{
printf("%s\t",fields[i].name);
}
printf("\n");
//解析结果集
MYSQL_ROW row;//char **
while((row=mysql_fetch_row(result)))
{
for(i=0;i<num;i++)
{
printf("%s\t",row[i]);
}
printf("\n");
}
//释放结果集
mysql_free_result(result);
//断开连接
mysql_close(mysql);
printf("close ok...\n");
return 0;
}
九、shell脚本
1.sh文件中的“/bin/pwd”和终端命令行中的pwd执行区别:前者直接通过绝对路径找到可执行文件执行,后者需要shell解析器通过环境变量找到pwd的同名可执行文件再执行。
2.shell脚本语言的作用是可以批量执行命令。
3.小括号的作用:执行(cd ..; ls);语句,显示的是上级目录的所有文件,但工作路径仍未改变,还是在当前路径。
4.只有一种数据类型:字符类型。
5.变量:环境变量、本地变量。
6.export 变量名:可将本地变量提升为环境变量。
7.unset 变量名:将已经定义的变量删除。
8.函数func()没有返回值,也没有形参列表,函数内部取得参数值按顺序默认是$0 、1、2等,调用函数时传参形如func() 1,3,4,5 另:函数内部的s0,s1表示函数实参,函数外部的s0,s1表示命令行实参。
9.用户在命令行输入命令后,一般情况下shell会fork子进程并exec该命令,但是内建命令相当于调用shell进程中的一个函数,并不创建新进程。
c++拾遗
1.类中的static成员变量无论私有公有,必须在类外初始化;类中的const成员变量可在构造函数中以参数初始化列表的形式初始化。
如果函数只声明不定义,那它有地址吗?
2.为什么有纯虚函数的类不能创建对象?(注意复习vptr和vtable)
3.编译时期,编译器就会把p->func()的虚函数名替换成了*(p->vptr[index])(p) ,所以运行时,还是要一步步来:先通过指针p找到对象的数据区,然后访问其vptr,通过vptr+偏移量找到虚表中对应的函数入口地址,再调用它。 另:vtable存在只读数据段。
所以再论静态联编和动态联编的区别:函数地址在编译阶段都是已经确定好的,就绪态之前的起始态就会按编译时确定好的地址做好初始化,静态联编对于函数的调用语句的函数名直接替换成函数入口地址,动态联编访问虚函数是要依靠vptr寻找的,vptr是类的成员,它是局部变量,在运行时才能确定下来,故而虚函数的调用语句也只有在运行阶段才能确定该函数的入口地址是什么,尽管该函数的入口地址也早就在编译阶段就确定了。
总之,只要访问的数据有内存空间,就只能在运行阶段访问。
4.if……else和switch的区别,前者是模糊匹配后者是精确匹配。
5.虚继承用于解决菱形继承访问基类成员变量的二义性问题,比如类B,C分别继承于类A,使用虚继承之后,编译器分别在B和C类头部添加vbptr,分别指向各自的虚继承表,表中第1项是vbptr与其所属类的偏移量,第2项是继承过来的类A数据与vbptr所属类首地址的偏移量。
【C++拾遗】 从内存布局看C++虚继承的实现原理_c 虚继承 实现-CSDN博客
6.
泛化:继承
依赖:对象作为形参
关联:以对象作为成员变量
7.
//逗号表达式
a=(3,4,5,6);//a=6
//将语句中最后一个语句的结果给变量
a=({3;4;5;6;7});
8.位左移操作时低位补0,位右移操作时,无符号数是高位补0,对于有符号数,则和操作系统有关,所以当程序有移植性要求时,尽量不要使用右移操作。
9.一个进程的4种状态:初始+就绪、运行、挂起、停止。一个程序的静态全局区的变量在编译时就已经确定其内存地址,当执行其可执行文件把它变成一个进程的时候,在初始态系统便会为这些静态全局区的变量分配空间并初始化。
10.结构体体字节对齐问题,分三步:1.取 编译器默认对齐单位、#pragma pack(n)手动设置的对齐单位、结构体最大成员(基本数据类型)这三者中最小的为实际对齐单位;2.除结构体的第一个成员外,所有的成员的地址相对于结构体地址的偏移量必须为实际对齐单位或自身大小的整数倍(取两者中小的那个);3.结构体的整体大小必须为实际对齐单位的整数倍。
11.extern "c"的作用:当一个工程中混合有c和c++文件时,由于两者被编译时,c++为了支持函数重载,它对函数名的处理和c不同,这时如果c++文件中有调用c文件中的某个函数或者调用以c方式制作的动态库中的函数,由于二者对函数名的处理方式不同,c++文件中的调用语句执行时,就会找不到c文件中的该函数,所以在c++文件中的调用语句用extern "c"包住,编译器对该语句的处理使用c编译器规则,便可以调用成功。
一句话:如果函数定义语句使用c编译器处理,函数的调用语句使用c++编译器处理,那由于二者对函数名的处理方式不同,执行时便会找不到函数入口而调用失败。
12.类的构造函数只是起到初始化成员变量的作用,类的成员变量的定义(分配内存)是在执行构造函数之前就已经先完成了。那为什么类的静态成员变量要在类外定义,普通成员变量却不用呢?那是因为静态成员变量属于类而非对象,所以它在对象被创建之前就已经存在。下面是一个饿汉单例模式,体现了这一点:
1 #include<iostream>
2 #include<string.h>
3
4 using namespace std;
5 class Person
6 {
7 public:
8 static Person &getInstance()
9 {
10 return instance;
11 }
12 char *getName()
13 {
14 return name;
15 }
16 private:
17 Person()
18 {
19 strcpy(name,"机制");
20 age=20;
21 }
22 char name[22];
23 int age;
24 static Person instance; //此句在进程初始态就该完成
25 };
26 Person Person::instance=Person(); //静态成员变量的定义
27 int main()
28 {
29 cout<<Person::getInstance().getName()<<endl ;
30 return 0;
31 }
13.二叉树类的容器(set、map等)的erase()函数返回值为空,所以在循环删除中,最好先保存下一个节点的迭代器,可以这样erase(i++);因为迭代器i是对象类型,所以会先复制再操作(即先保存i++的值);而动态数组和链表类(vector、list等)的erase()函数返回下一个结点的值。
面试补充:
1.解决tcp通信粘包问题:(1)定长发送;(2)尾部标记\0;(3)头部标记长度。
2.mysql是一个用户,多个数据库,数据库可以有多张表。
oracle是一个数据库,多个用户,每个用户的表都放在同一个数据库,但不会混乱。
3.通信套接字的缓冲区在内核。
4.项目二分布式每组storage之间是分布式,互相分担负载同时便于扩容;一组storage是集群,用于备份数据,同时单点故障时,同组仍然可以工作。
集群的每个主机都可以单独完成全部任务;而分布式中每个主机的功能不同,协同合作。
5.select和epoll的区别:(1)select有连接数上限,epoll没有;(2)select要用户对返回结果集轮询,epoll只需判断结果集是否为空;(3)select调用一次都需要从用户区向内核区传递要监听的集合,epoll则只需把需要添加的监听从用户区传到内核区;(4)select只支持水平触发,不支持边缘触发。
水平触发:读缓冲区有东西,就会一直置位epoll的返回结果集表示可读;写缓冲区未满,就会一直触发可写信号。
边沿触发:读缓冲区有相对增加,就会触发一次可读信号;写缓冲区由满到未满会触发一次
阻塞非阻塞是指send()在不能马上写入写缓冲区或者recv()在缓冲区没有数据时是等待还是马上返回。
触发是指epoll把读/写缓冲区当前是否可读可写状态置位到监听返回结果集合中。
关于系统清空socket写缓冲区的处理:如果有不间断的数据写入写缓冲区,系统会等缓冲区满再一起向网络发送,而当缓冲区未满,而写入又间断时,系统也会视情况向网络发送数据,可能是看时间决定的。
6.sleep 挂起:占着cpu睡觉(错误观点!)
wait() 阻塞:程序暂停,同时让出cpu。
7.排序算法:稳定性判断,如果相等的两个元素在排序过程中的前后顺序发生了改变,则该排序算法不稳定。
8.tcp和ip的区别:tcp在传输层,它提供端口号,ip在网络层,提供ip地址。
9.每个开启的计算机系统在其所处局域网内都有一个ip,这个是局域网ip,用于这个局域网内的所有主机彼此之间辨别身份,可在dos命令行中通过arp -a或ipconfig/all指令查看。比如现在我在宿舍,电脑连接wifi,虚拟机上装的linux系统也以桥接模式开启。这时用指令arp -a查看所在局域网内的所有主机ip时,上面会显示:
路由器ip:192.168.31.1
本机windows系统的ip:192.168.31.54
虚拟机上的linux系统ip:192.168.31.55
广播ip:192.168.31.255
此时没有显示也连着wifi的室友的电脑的ip是因为他电脑设置了防火墙,而我的手机此时也连着wifi,但这里也没有显示其ip是什么原因呢?
这个局域网内所有的主机共享一个公网ip,百度ip查询显示此时的公网ip是“本机IP: 116.30.198.230广东省深圳市 电信 ”
桥接模式和nat模式的区别:桥接模式下,虚拟机上的linux的ip地址与本机widow的ip处于同一网段内,即linux系统和windows在这个连接着wifi的这些主机构成的大局域网内属于同等级成员;而nat模式下,相当于虚拟机上的linux和本机的widows再次组成了只有两个成员的小局域网,此时本机的windows系统有了两个身份,第一个是大局域网的成员,第二个是小局域网的成员,而linux系统仅有一个身份,就是小局域网的成员。
浏览器地址栏内直接输入路由器的固定虚拟ip即可登录小米路由器设置
10.类可以使得同一cpp文件的同名函数、同名变量不冲突;但命名空间除此之外还可以使得同一cpp文件中的同名类不冲突。另外类里的普通变量是在栈中的,而命名空间中的普通变量是全局变量。
11.linux中\n起换行作用,即在linux中编辑文本文件时,按下enter键,相当于隐式添加\n
windows中\r\n起换行作用,即在windows中编辑文本文件时,按下enter键,向当于隐式添加\r\n
所以linux中编写的文本文件,放到widows中用记事本打开时,仅仅\n起不到换行效果;而在windowsh中编写的文本文件,在linux中用vim打开时,\r\n中的\n起到了换行效果。
12.通过设置路由器DMZ把自己电脑当作服务器供外网访问
几天后找到了原因,电信给路由器分配的不是公网ip,通过百度查询ip(这个是真正的公网ip):
通过登录路由器查询分配给路由器的ip (两者不一致,所以这个应该是局域网ip):
这些范围内的是私网ip:
13.tcp和udp处理请求的区别
tcp是面向连接的,所以tcp的服务端和n个客户端建立连接之后,服务端就有n个套接字,每个套接字=读缓冲+写缓冲。当每个客户端有数据发来,内核会自动把相应的数据写入相应的读缓冲,服务器端再轮询处理这些读缓冲的数据。tcp同一个端口可以有一个监听套接字,但是可以有多个通信套接字。
udp是非面向连接的,如果只用一个套接字处理,那如果同时有不同客户端的数据发来,那估计要排队写入缓冲区,还是一起写入读缓冲,由recvfrom()根据ip端口处理?因为udp服务端的监听套接字和接收套接字是同一个,所以一个端口好像没办法创建多个通信套接字 读缓冲区使用以结构体为结点的队列存储不同客户端的数据。
【Linux 内核网络协议栈源码剖析】recvfrom 函数剖析_recvfrom函数返回的地址为0.0.0.0-CSDN博客
14.tcp三次握手和四次挥手
三次握手:
(1)客户端:SYN位置1,并随机生成序列号sqe=x放在32位序号中,发送该数据包,进入SYN_SENT(同步发送)状态。
(2)服务器:SYN置1+ACK置1,ack=x+1放入32位确认序号中,随机生成序列号sqe=y放入32位序号中,发送数据包,进入SYN_RCVD(同步收到)状态。
(3)客户端:ACK置1,ack=y+1放入32位确认序号中,sqe=x+1放入32位序列号中,发送数据,双方进入ESTABLISHED (已建立连接)状态。
四次挥手(假设客户端首先发起关闭):
(1)客户端:FIN置1,并随机生成序列sqe=m放入32位序列号中,发送该数据包,进入FIN_WAIT1(终止等待1)状态。
(2)服务器:ACK置1,ack=m+1放入32为确认序列号中,随机生成序列号sqe=n放入32位序列号中,发送数据,进入CLOSE_WAIT(关闭等待)状态,客户端收到后进入FIN_WAIT2(终止等待2)状态。
(3)服务器:FIN置1,ack=m+1,随机生成sqe=n,发送数据,进入LAST_ACK(最后确认)状态。
(4)客户端:ACK置1,ack=n+1,sqe=m+1,发送数据,进入TIME_WAIT(时间等待)状态,服务器收到后进入CLOSE状态。
tcp协议在传输层,所以握手挥手的那些数据在整个数据包寻路的过程的拆包解包中不会取出来,因为寻路过程只需要物理层的mac和网络层的ip。
15.http的长连接和短连接本质上是底下tcp的长连接和短连接,http/1.0是短连接,http1.1起是长连接,它的长连接时间可以设置 服务器HTTP长连接与短连接_服务端可以与服务端进行长链接吗-CSDN博客
16.fopen()是标准函数,根据系统的不同自动进行相应的系统调用,例如linux中使用fopen,其实也是再次调用了open。