C语言学习笔记(加C高级)
1 程序编译的过程: 2 预处理 生成.i文件 3 编译 将预处理后的文件转换成汇编语言,生成.s文件 4 汇编 将汇编语言变为目标代码(机器代码)生成.o 文件 5 链接 连接目标代码,生成可执行程序 6 7 预处理是C语言程序从源代码变成可执行程序的第一步,主要是C语言编译器对各种预处理命令进行处理,包括头文件的包含、宏定义的扩展、 8 条件编译的选择等。打印出预处理之后的结果:gcc -E hello.c 或者 cpp hello.c这样我们就可以看到源代码中的各种预处理命令是如何 9 被解释的,从而方便理解和查错。 10 11 编译之前,C语言编译器会进行词法分析、语法分析(-fsyntax-only),接着会把源代码翻译成中间语言,即汇编语言。如果想看到这个中间 12 结果,可以用-S选项。 13 14 15 1、值常量:整型值常量、浮点型值常量(例如:0.5f)、字符型值常量('a'、'\n'、'\r')、字符串值常量("hello")、符号常量、常型常量 16 2、变量声明:存储类型、数据类型、变量名 17 3、合法的标识符,只能以字母和下划线开头,后面可以是数字、下划线、字母,不能是关键字(例如:char、short、int、long) 18 4、数据的存储形式: 1、在内存中的形式:小端字节序,地址低位存数据低位,地址高位存数据高位 19 2、在网络中形式,大端字节序,地址高位存低位,地址低位存数据高位 20 21 5、三种常见的布尔分析 22 1、整型数的布尔判断(直接判断) 23 2、浮点数的判断 (用范围比较方式) 24 3、指针的判断 25 26 6、a++和++a的区别: 27 a++先返回a的值,再加a = a + 1; (表达式结束才会自增) 28 ++a先a=a + 1,再返回原来a + 1的值(表达式完成前自增) 29 30 运算符优先级: 31 优先级 运算符 名称或含义 使用形式 32 [] 数组下标 数组名[整型表达式] 33 () 圆括号 (表达式)/函数名(形参表) 34 1 . 成员选择(对象) 对象.成员名 35 -> 成员选择(指针) 对象指针->成员名 36 ------------------------------------------------------------- 37 - 负号运算符 算数类型表达式 38 (type) 强制类型转换 (纯量数据类型)纯量表达式 39 ++ 自增运算符 ++纯量类型可修改左值表达式 40 -- 自减运算符 --纯量类型可修改左值表达式 41 2 * 取值运算符 *指针类型表达式 42 & 取地址运算符 &表达式 43 ! 逻辑非运算符 纯量类型表达式 44 ~ 按位取反 ~整型表达式 45 sizeof 长度运算符 sizeof表达式 46 ------------------------------------------------------------- 47 具体祥看:https://blog.csdn.net/u014100559/article/details/90764534?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522159358726719724848309940%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=159358726719724848309940&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-5-90764534.first_rank_ecpm_v3_pc_rank_v3&utm_term=c%E8%AF%AD%E8%A8%80%E4%BC%98%E5%85%88%E7%BA%A7%E6%8E%92%E5%BA%8F 48 49 50 51 所占字节数 数据表示范围 52 char 1 -128~127 53 unsigned char 1 0~255 54 short 2 -2^15~2^15-1 55 int 4 -21亿~21亿 56 long 8(32位机上为4位) 57 long long 8 58 unsigned short 2 0~65535 59 unsigned int 0~42亿 60 61 62 防止数据溢出(上溢:溢出到范围的底部,下溢:溢出到数据的底部) 63 整数除整数得整数,整数除浮点数(其中有一个为浮点数,则结果为浮点数) 64 65 sizeof:计算某类型或某表达式所占的字节数 66 67 非格式化字符 68 输入:getchar() 可使用其使程序暂停(一次只提取一个字符) 69 输出:putchat() 70 71 72 /**************************************************************************/ 73 #include <stdio.h> 74 #define MAX(n1 , n2)((n1) > (n2) ? (n1) : (n2)) 75 76 int max_fun(int num1 , int num2) 77 { 78 return num1 > num2 ? num1 : num2; 79 } 80 81 int main() 82 { 83 int num1 , num2 , num3 84 printf("input three number\n"); 85 scanf("%d,%d,%d",&num1 , &num2 , &num3); 86 int max; 87 max = max_fun(max_fun(num1 , num2) , num3); 88 printf("max = %d\n",max); 89 } 90 /**************************************************************************/ 91 92 while()//先判断再执行,有可能一次都不会执行 93 94 do...while()//先执行再判断,至少会执行一次 95 96 for(初始化表达式 ; 判断表达式 ; 迭代表达式) 97 { 98 循环体 99 } 100 //for循环执行顺序: 初始化表达式->判断表达式->循环体->迭代表达式->判断表达式 101 102 coutinue()语句 103 功能:结束本次循环,跳过循环体中尚未执行的语句,进行下一次循环体的判断(一般结合if使用) 104 105 106 数组:有一个首地址,后面有连续内存。 107 大批量存储指定类型的数据,有统一访问的入口(数组名 + 下标),方便对批量数据的访问。 108 特点:1、类型统一 2、大小固定 109 (数组名代表了数组的首地址)(数组的维数必须是常量) 110 111 数组初始化 112 1、静态初始化(在声明时初始化,用{}初始化) 113 2、多态初始化(声明之后的赋值) 114 115 中括号[]的本质:*(数组名 + 偏移量) 116 例:P[1]就是*(p + 1) , 1[p]就是 *(1 + p) 117 118 bzero(目标,0,位数) //将目标清为0 119 memset(首地址,0,字节数)//将目标设置为0 120 memcpy(数组1,数组2,sizeof(数组2)) 121 122 void* : void*指针可以接收任意类型指针,提高函数通用性。 123 const void*是常量指针,只能改变指向,不能改变值。 124 125 126 /*********************************************************************************/ 127 二维数组: 128 #include <stdio.h> 129 130 int main(int argc, char const *argv[]) 131 { 132 int arr[3][2]; 133 printf("arr = %p , arr[0] = %p , &arr[0][0] = %p\n" , arr , arr[0] ,&arr[0][0]); //值相等,但意义不一样&arr[0][0],arr[0]的类型都为int* 134 printf("&arr[0][0] = %p,arr[0][1] = %p,&arr[1][0] = %p\n",arr[0][0],arr[0][1],&arr[1][0]); //二维数组与一维数组的存储形式都一样,都是连续内存 135 printf("arr[0] = %p,arr[1] = %p,arr[2] = %p\n",arr[0],arr[1],arr[2]); //二维数组的第一维表示的是每行的首地址 136 printf("arr[0][1] = %p,arr[1][0] = %d\n",arr[0][1],arr[1][0]); //声明时如果未初始化,产生的也是随机值。 137 printf("sizeof(arr) = %ld\n",sizeof(arr[0])); //总字节数 138 printf("sizeof(arr[0]) = %ld\n",sizeof(arr[0])); //sizeof(每行首地址) = 一行所占字节数 139 printf("sizeof(arr)/sizeof(arr[0]) = %ld\n",sizeof(arr)/sizeof(arr[0])); //行数 140 printf("sizeof(arr[0])/sizeof(arr[0][0]) = %ld\n",sizeof(arr[0])/sizeof(arr[0][0])); //列数 141 printf("sizeof(arr)/sizeof(arr[0][0]) = %ld\n",sizeof(arr)/sizeof(arr[0][0])); //总的成员个数 142 return 0; 143 } 144 /*********************************************************************************/ 145 146 字符串与数组 147 字符串的本质:是一种以'\0'结尾的字符数组 148 149 char ch[6] = {"china"} 150 参数是char*,表示可读可写,所以一般用字符数组char[],如果传的是指针,则不能是 151 指向野指针、空指针、指向常量数据区的指针,可以是指向函数栈,堆内存的指针 152 153 字符串的两种表达形式: 154 字符数组:char buf[] = "hello"; 155 字符指针:const char *str = "hello"; 156 两种表达形式看起来一样,但是实际上存在很大差异,字符指针只有一个"hello",存在于常量数据区 157 字符数组有两"hello",一个存在于常量数据区,另外一个存在于函数栈中 158 159 160 字符串的比较命令: 161 int strcmp(const char *s1 , const char *s2);//区分大小,全部比较s1和s2 162 int strncmp(const char *s1 , const char *s2 , size_t n);//区分大小写部分比较s1 s2 163 int strcasecmp(const char *s1 , const char *s2);//忽略大小写全部比较s1,s2 164 int strncasecmp(const char *s1 , const char *s2 , size_t n);//忽略大小写部分比较s1,s2 165 返回值:返回值大于0 ,表示S1>S2,返回值等于0,表示S1==S2,返回值小于0,表示S1<S2 166 167 字符串比较示例: 168 #include <stdio.h> 169 #include <string.h>//导入字符串操作的头文件 170 171 int main() 172 { 173 const char *str1 = "hello world"; 174 const char *str2 = "helloworld"; 175 char buf1[] = "hello world"; 176 char buf2[] = "hello WORLD"; 177 int sub_1 = strcmp(str1,str2); 178 int sub_2 = strncmp(buf1,buf2,11); 179 int sub_3 = strcasecmp(buf1,buf2); 180 int sub_4 = strncasecmp(buf1,buf2,9); 181 } 182 183 184 字符串函数与 int float 类型的转换 185 int sscanf(const char *float,...(变量地址));//将指定格式的字符串转换成int , float型 186 int sprintf(char *buffer,const char *format,...)//将一个格式化的字符串输出到一个目的字符串中,而printf是将一个格式化的字符串输出到屏幕 187 188 189 定义函数正确的顺序是先定义main(),在main()之前声明其他函数,在main之后实现函数 190 函数的四要素:头文件、功能、参数、返回值 191 头文件:把要调用的函数声明提取过来,相当于直接声明函数 192 功能:每个函数都要实现指定的功能才有意义 193 参数的作用:提高函数的灵活性(输入型,输出型,输入输出型) 194 返回值:用于保存函数运行结果 195 196 函数栈:由系统自动分配,自动回收(全自动),存放形参,函数中的一般局部变量,返回值。 197 堆内存:由程序malloc(),calloc(),realloc()去申请用free()释放,如果不释放则会内存泄漏。 198 199 参数的传递方式: 200 地址传递:(C++里的浅拷贝) 201 作用效果:函数内的改变会影响函数外 202 本质:通过形参指针改变实参的值 203 判断条件:根据形参判断,如果形参是指针或者数组则一般是地址传递(特殊情况:如果只是改变形参指针的指向,而没有通过形参改变实参的值) 204 值传递:(C++里的深拷贝) 205 作用效果:函数内的改变不影响函数外 206 本质:只改变形参的值,没改变实参的值 207 判断条件:根据形参判断,如果形参是基本结构或着结构体等一般类型(非指针,非数组) 208 209 /***********************************************************************************************************/ 210 变量的存储类型(存储位置不同): 211 1、register(寄存器变量):存在寄存器中(寄存器变量可以提高运算速度,程序中不会去声明,由编译器自动生成) 212 2、auto(自动变量):在函数栈中 213 3、static(静态变量):在全局数据区,不能被外部文件引用访问 214 4、extern(外部变量):在全局数据区,引用外部啊文件的变量 215 216 静态变量:在函数中声明的静态变量 217 218 静态局部变量与一般局部变量的区别: 219 相同之处:作用域相同,都是作用于函数内 220 不同之处:1、生命周期不同 221 2、初始化次数不同 222 3、初始化值不同 223 4、存储位置不同 224 静态全部变量与一般全局变量的区别: 225 相同之处:1、生命周期相同(整个应用程序) 226 2、存储位置相同(全局数据区) 227 3、初始化次数相同 228 4、初始化值相同 229 5、在同一个文件中作用域相同 230 不同之处:1、作用域不同,一般全局变量可在本文件使用,也可以被外部文件使用,静态全局变量只能用于本文件(保护,限制数据只能在本文件使用) 231 232 一般函数与静态函数: 233 在同一个文件中两种函数没有区别 234 一般函数可作用于本文件也可以作用于外部文件,但是静态函数只能作用于本文件,不能作用于外部文件 235 (如果需要在外部文件中调用静态函数,可以间接调用,先在一般函数中嗲用静态函数,再在外部文件调用一般函数) 236 237 238 /****************************************************************************************************************/ 239 指针(任何指针在64位机下都是8个字节) 240 241 指针使用:* & 242 *在指针中的两个作用: 243 1、作类型说明符:例如int *p 在声明指针时做类型说明,说明P是指针其类型为int 244 2、作取值符:除声明之外,*都是取值符,取出地址的值 245 &的作用:在指针中 & 只要一个作用,即为取地址 246 247 * 取值会减少指针的级数,* 一级指针取值,*二级指针是取的一级指针的地址,**p取出的二级指针的值 248 249 指针在使用时注意: 250 1、指针类型必须一致 251 错误示例: int i = 'a'; 252 char ch1 = 'a'; 253 int *p = &ch1;//error 254 float *pf = &ch1//error 255 char *p = (char *) &i;//指针类型可以强转 256 2、在强制转换时不仅要看指针的类型,还要看原参数的类型 257 例如:char ch = 3.14; 258 int *p = (int*)&ch; 259 因为ch为char 型,一次只取一个字节,而int型一次读取4个字节,如果这样进行强转,则指针*p的值便会成为一串错误的数据 260 261 空指针:声明指针时如果没有初始化地址,则赋值为NULL。 262 例如:int *p = NULL; 263 主要是为了后面的安全检查,如不赋值为空指针,那么这个指针将会成为野指针,野指针无法进行安全检查 264 265 void *指针:可以接收任意类型的地址,也可以给任何指针赋值 266 void *指针经常用来作函数参数,提高函数的通用性 267 不能声明void型变量,因为编译器不知道给void类型分配多少字节 268 269 以下操作是OK的,因为指针在64位机上都是8个字节,以下的操作实际上只是改变了指针指向 270 int *p4 = &num2; 271 void *pv = p4; 272 char *pc = pv; 273 274 但是 int temp = *pv 这样的写法是错误的,因为 pv 的指针类型是 void 型的,不能对 void* 进行取值 275 276 C语言中运算符只有以下三种是右结合性 277 1、单目运算符 278 2、赋值运算符 279 3、三目运算符 280 指针的算术运算只能由加(+)、减(-)、后自增(++)、后自减(--),不能是乘(*)、除(/)、取余(%) 281 282 指针函数:返回类型为指针的函数(例如 strcpy() 、 memcpy()) 283 (注:指针函数返回参数千万不要返回一个局部变量的指针,因为局部变量在调用结束后会被自动销毁) 284 函数指针:指向和函数的指针(int (*_) (int,int)) 285 286 /*******************************************************************************************/ 287 函数指针示例: 288 #include <stdio.h> 289 290 int max(int num1,int num2) 291 { 292 printf("max:excate\n"); 293 return num1 > num2 ? num1 : num2; 294 } 295 296 float add(float num1 , float num2) 297 { 298 printf("add : excate\n"); 299 return num1 + num2; 300 } 301 302 int main() 303 { 304 printf("main = %p , max = %p , add = %p\n",main,max,add); 305 //int *p = max;//由于max是函数名,虽然也是个地址,但是不能用一般的指针指向它,需要用到函数指针 306 int (*pfun)(int , int );//声明的函数指针 307 pfun = max;//对函数指针进行赋值,此时使用pfun(int,int),与max(int,int)效果一样 308 printf("pfun = %p , max = %p\n",pfun,max); 309 int res = pfun(1,2); 310 printf("pfun_res = %d\n",res); 311 312 float (*pfun2)(float,float) = add; 313 printf("pfun2 = %p , add = %p\n" , pfun2 , add); 314 float r = pfun2(1.2,3.4); 315 printf("r = %f\n",r); 316 } 317 /*******************************************************************************************/ 318 函数指针的使用:主要用于作参数,提高函数的通用性(回调函数) 319 320 321 预处理指令: 322 #include //静态包含 323 #define //宏替换 324 #if //条件编译 325 #ifdef //条件编译 326 #ifndef //条件编译 327 328 1、预处理指令只占用编译时间,不占用调用时间 329 2、一行只能写一个预处理指令,不是C语言语句,不需要加分号(;) 330 331 宏替换: 332 1、不带参宏 2、带参宏 333 不带参宏示例: 334 #define PI 3.14 335 宏替换在预处理时会将宏名替换成宏体内容,不会进行安全检查,且如为带参宏需要注意其优先级,最好是给每一个参数都加上个小括号"()" 336 337 系统的预定义宏: 338 __FILE__ 类型为 const char * , 所在文件的文件名 339 __func__ 类型为 const char * , 所在函数的函数名 340 __LINE__ 类型为 int , 所在的行数 341 __DATE__ 类型为 const char * , 系统当前日期 342 __TIME__ 类型为 const char * , 系统当前时间 343 344 /*********************************************************************************************/ 345 346 结构体: 347 struct studnet struct 结构体类型名 348 { { 349 成员类型1 成员变量1 350 成员类型2 成员变量2 351 }; }; 352 353 声明结构体变量的格式:struct 结构体类型名 变量名 354 1、结构体内部变量内存是连续的 355 2、结构体成员如果没有初始化则会是随机数 356 3、结构体类型不占内存,结构体变量才占内存,其大小为所有成员大小之和,而且与结构体成员个数和顺序有关。 357 358 struct temp1 //8字节 359 { 360 char ch1; 361 char ch2; 362 int i; 363 }; 364 365 struct temp2 //12字节 366 { 367 char ch1; 368 int i; 369 char ch2; 370 }; 371 372 结构体中的成员可以是不同的数据类型,成员按照定义时的顺序依次存储在连续的内存空间。 373 和数组不一样的是,结构体的大小不是所有成员大小简单的相加,需要考虑到系统在存储结 374 构体变量时的地址对齐问题。 375 376 偏移量指的是结构体变量中成员的地址和结构体变量地址的差。结构体的大小是由最后一个 377 成员的偏移量加上最后一个成员的大小。显然 (temp2),结构体变量中第一个成员的地址 378 就是结构体变量的首地址。因此,第一个成员ch1的偏移量为0。第二个成员i的偏移量是第一个 379 成员的偏移量加上第一个成员的大小(0+1),其值为1;第三个成员ch2的偏移量是第二个成员 380 的偏移量加上第二个成员的大小(1+4),其值为5。 381 382 根据内存对齐的方法,用最后一个成员的偏移量加上最后一个成员的大小,得出结构体大小为12 383 384 结构体中的成员可以是本结构体类型的指针,不能是本结构体的变量,因为编译器不知道给它分配多少内存 385 (可以是其他结构体的变量) 386 387 结构体的定义: 388 匿名定义: 389 struct 390 { 391 char name[16]; 392 int age; 393 }stu1 = {"zhangsan",22};//必须在声明结构体类型时声明变量 394 395 396 struct student 397 { 398 char name[16]; 399 int age; 400 struct 401 { 402 int year; 403 int month; 404 int day; 405 }bdate; 406 }; 407 408 /***********************************************************************************/ 409 共用体:几个不同的变量共享一段内存的结构,成为"共用体"类型的结构 410 (同一时刻只能做一种功能) 411 412 定义共用体类型变量的一般形式为: 413 union 共用体名 414 { 415 成员表列 416 }变量表列; 417 418 例如: 419 union Data 420 { 421 int i; 422 char ch; 423 float f; 424 }a,b,c; 425 426 地址空间表示图: 427 |地址 |1000|1001|1002|1003| 428 |_____|____|____|____|____| 429 |int |____|____|____|____| 430 |char |____|____ ____ ____ 431 |float|____|____|____|____| 432 433 以上3个变量在内存中占的字节数不同,但是都是从同一地址开始,也就是使用了覆盖技术,后面的数据覆盖了前面的数据 434 435 共用体类型数据的特点: 436 同一内存段可以用来存放不同类型的成员,但在每一瞬间只能存放其中一个成员,而不是同时存放几个 437 438 /***********************************************************************************/ 439 #include <stdio.h> 440 441 union stu 442 { 443 int i; 444 char c; 445 float f; 446 }a; 447 448 int main() 449 { 450 a.i = 0x61;//0x61为97 451 printf("%d\n",a.i);//97 452 printf("%c\n",a.c);//a 453 printf("%f\n",a.f);//0.000000 454 } 455 /***********************************************************************************/ 456 97转换成16进制为0x00000061,因为电脑的存储发方式是小端字节序格式,因此地址在内存中的存储方式如下所示 457 458 |地址 |1000|1001|1002|1003| 459 |_____|____|____|____|____| 460 |int |0x61|0x00|0x00|0x00| 461 |char |0x61|____ ____ ____ 462 |float|0x61|0x00|0x00|0x00| 463 464 共用体的用途: 465 在数据处理中,需要对同一空间安排不同的用途,使用共用体比较方便 466 例如:有若干人员的数据,其中有学生和教师。学生的数据中包括:姓名、号码、性别、职业、班级。 467 教师的数据包括:姓名、号码、性别、职业、职务。要求用同一表格来处理 468 可以看出:学生和老师的数据的项目大多数是相同的,只有一个不同,学生的班级,教师的职位。 469 struct{ 470 int num; //成员 编号 471 char name[10]; //成员 姓名 472 char sex; //成员 性别 473 union{ //声明无名共用体类型 474 int class; //成员 班级 475 char position[10];//成员 职务 476 }category; 477 }person[2]; 478 /***********************************************************************************/ 479 /***********************************************************************************/ 480 枚举: 481 枚举是一种特殊类型,它的值包含在一个命名的常量集合中,这些常量称为枚举符。在实际应用中,有的 482 变量只有几种可能取值,如人的性别只有两种可能取值,星期只有七种可能取值,在C语言种对这样取值 483 比较特殊的变量可以定义为枚举类型。所谓枚举是指将变量的值一一列举出来,变量只限于列举出来的值 484 的范围内取值。 485 486 定义一个变量时枚举类型,可以先定义一个枚举类型名,然后再说明这个变量是该枚举类型。 487 488 例如: 489 enum weekday{sun,mon,tue,wed,thu,fri,sat}; 490 定义了一个枚举类型名enum weekday,然后定义变量为该枚举类型。 491 enum weekday day; 492 当然,也可以直接定义枚举类型变量,如: 493 enum weekday{sun,mon,tue,wed,thu,fri,sat} day; 494 需要说明的是: 495 1、枚举元素不是变量,而是常数,因此枚举元素又称枚举常量,所以不能对枚举元素进行赋值。 496 2、枚举元素作为常量,它们是有值的,C语言在编译时按定义的顺序使他们的值为 1,2,3... 497 /***********************************************************************************/ 498 499 500 /***********************************************************************************/ 501 用数组存放数据时,需要事先定义数组的大小或者尽量放大数组的大小,显然浪费内存且不易更改, 502 而链表则无这种缺点。链表是一种可动态地进行存储分配的结构 503 链表有一个头指针(head),存放的地址指向一个元素,链表中的每个元素称为节点,每个节点由两部分组成 504 即数据部分和指针部分。 505 506 | a | b | | a | b | | a | b | 507 508 a为值部分,b为指针部分 509 510 /***********************************************************************************/ 511 创建一个简单的链表 512 #include <stdio.h> 513 514 struct student 515 { 516 int num; 517 float score; 518 struct student *next; 519 }; 520 521 int main() 522 { 523 struct student a,b,c; //三个链表节点 524 struct student *head; 525 struct student *p; 526 527 a.num = 12345; 528 a.score = 80; 529 b.num = 56789; 530 b.score = 90; 531 c.num = 90123; 532 c.score = 99; 533 534 head = &a; 535 a.next = &b; 536 b.next = &c; 537 c.next = NULL; 538 539 p = head; 540 541 do{ 542 printf("%d\n",p->num); 543 p = p->next; 544 }while(p != NULL); 545 return 0; 546 } 547 /***********************************************************************************/ 548 549 这个链表所有节点都是在程序中定义,不是临时开辟,页没有用完就释放,这种链表称为静态链表 550 551 552 动态链表: 553 所谓动态链表,是指在程序执行过程中从无到有地建立起一个链表,即一个一个地开辟结点和输入各 554 结点数据,并建立起前后相链的关系。 555 556 参考程序: 557 #include <stdio.h> 558 #include <stdlib.h> 559 560 struct Student 561 { 562 int No; 563 struct Student *next; 564 }; 565 566 int main() 567 { 568 struct Student *p1 , *p2; 569 struct Student *head , *p; 570 int n = 0;//结点个数 571 572 head = NULL; 573 P1 = (struct Student *)malloc(sizeof(struct Student)); 574 printf("请输入第1个学号\n"); 575 scanf("%d",&p1->No); 576 p2 = p1; 577 while(p1->No != 0) 578 { 579 n++; 580 if(n == 1) 581 { 582 head = p1; 583 } 584 else 585 { 586 p2->next = p1; 587 } 588 p2 = p1; 589 printf("请输入学号,输入0终止:\n"); 590 p1 = (struct Student *)malloc(sizeof(struct Student)); 591 scanf("%d",&p1->No); 592 } 593 p2->next = NULL; 594 p = head; 595 printf("\n学号为:\n"); 596 while(p != NULL) 597 { 598 printf("%d\n",p->No); 599 p = p->next; 600 } 601 return 0; 602 } 603 /***********************************************************************************/ 604 静态链表和动态链表的区别: 605 1、静态链表是用类似于数组方法实现的,是顺序的存储结构,物理地址上是连续的,而且需要 606 预先分配地址空间大小。所以静态链表的初始化长度一般是固定的,在做插入和删除操作时不需要移 607 动元素,仅需要改指针。 608 2、动态链表是用内存申请函数(malloc())动态申请内存的,所以在链表的长度上没有限制。 609 因为是动态申请内存的,所以每个节点的物理地址不连续,要通过指针来顺序访问。 610 611 单向循环链表: 612 将单链表中尾节点的指针由空改为指向头结点(或第一个元素节点),就使得整个单链表形成了一个环 613 这种头尾相接的单链表称为单向循环链表。 614 615 __________________________________________________ 616 | | 617 v | 618 |head|next| -> | A |next| -> | B |next| -> | C |next| -- 619 620 __________________________________ 621 | | 622 v | 623 |head|next| -> | A |next| -> | B |next| -> | C |next| -- 624 625 626 双向链表: 627 在单向链表的结点中增加一个指向其前驱的pre指针,该链表中第一个结点的前驱结点为NULL 628 最后一个结点的后继结点为NULL; 629 ______________ _______________ 630 | | | | 631 v | v | 632 |before|head|next| -> |before| A |next| -> |before| B |next| -> |before| C |next|-> NULL 633 NULL<-------| 634 635 双向循环链表: 636 双向循环链表直接体现为"双向和循环",一般的单链表只有结点数据data和next指向地址,而在 637 此需要增加前面部分的 pre 指向地址,同时还需要循环。 638 639 640 641 /***********************************************************************************/ 642 树与二叉树: 643 树: 644 树形结构是一种典型的非线性数据结构,体现在数据元素之间有明显的层次关系 645 定义:树是由n个节点构成的有限集合,n大于等于零,节点数为零的树称为空树,节 646 点数大于零的树称为非空树 647 648 一颗非空树满足以下条件: 649 1、有且仅有一个被称为根(R)的特殊节点,其余所有节点都可由节点经过一定的分支得到,而根节点 R 没有前驱节点 650 2、当n > 1时,除根节点R外的其他节点被分成 m 个互不相交的子集,T1 ,T2 ,....Tm,其中每个自己Ti本身又是一棵树,被称为R节点的子树 651 652 树的定义时递归定义,即每个子树的定义也是按照上面的过程完成的。 653 654 相关名词: 655 1、节点 656 2、节点的度 657 3、树的度 658 4、叶子节点 659 5、分支节点 660 6、节点的层次 661 7、树的深度 662 663 双亲:即上层的那个节点(直接前驱) 664 孩子:即下层节点子树的根 665 666 二叉树: 667 二叉树与树完全不同,树的根不允许为空,而二叉树的根允许为空,且二叉树每个节点最多允许2个节点 668 669 二叉树性质:1、在二叉树的第 i 层上至多有 2^i-1 个节点 670 2、深度为 k 的二叉树最多有 2^k-1 个节点 671 672 满二叉树:一棵树的深度为 K ,且有2^k-1 个节点的二叉树。 673 674 完全二叉树:最大深度与最小深度相差不超过 1 675 676 二叉树的遍历: 677 前序遍历、中序遍历、后序遍历 678 示例: 679 A 680 / \ 681 B C 682 / \ 683 D E 684 前序遍历:先根再左再右 ABDEC 685 中序遍历:先左再根再右 DBEAC 686 后序遍历:先左再右再根 DEBCA 687 688 689 690 /***********************************************************************************/ 691 文件操作: 692 1、打开文件 693 2、文件读/写 694 3、关闭文件 695 696 标准I/O(不区分操作系统,在任何系统中都可以使用): 697 698 文件常见的操作: 699 fopen/fclose 700 fgets/fputs 701 fgetc/fputc 702 fwrite/fread 703 fseek 704 FILE* fopen(const char *path ,const char *mode); 705 FILE* freopen(const char *restrict pathname,const char*restrict type,FILE *restrict fp);//用于重定向 706 FILE *fdopen(int filedes,const char *type); 707 708 fseek,用于将指针移到文件的某一个位置 709 语法: 710 status = fseek(fileID,offset,origin) 711 712 fileID是fopen打开时产生的整数标识,大于 0 时,表示文件成功打开,在文件中,offset是对于origin位置 713 开始移动的整数,origin有三种状态,bof,cof,eof,其分别表示文件的开始位置,当前位置,和末尾位置,如果操作 714 成功,则状态返回 0,否则返回-1 715 716 717 fopen() 打开由 path 指定的一个文件,mode参数如下: 718 --------------------------------------------------------------------------------------------------------- 719 | r或者rb | 打开只读文件,该文件必须存在 | 720 | r+或r+b | 打开可读写的文件,该文件必须存在。 | 721 | w或wb | 打开只写文件,若文件存在则文件长度清为0,即会擦除文件以前内容,若文件不存在则会建立该文件 | 722 | w+或w+b或wb+ | 打开可读写文件,若文件存在则文件长度清为0,即会擦除文件以前内容,若文件不存在则会建立该文件 | 723 | a或ab | 以追加的方式打开只写文件,若文件不存在,则会建立文件,如果文件存在,写入的数据 | 724 | 会被加到文件尾,即文件原先内容会被保留 | 725 | a+或a+b或ab+| 以追加的方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,从头读取,但是写入 | 726 | 的数据会被加到文件尾,即文件原先的内容会被保留 | 727 ---------------------------------------------------------------------------------------------------------| 728 729 /***********************************************************************************/ 730 fopen示例: 731 #include <stdio.h> 732 733 int main() 734 { 735 FILE* fp; 736 int i; 737 char name[8]; 738 fp = fopen("path","r"); 739 fscanf(fp,"%s",name); 740 fclose(fp); 741 for(i = 0 ; i < 8 ; i++) 742 { 743 printf("%c",name[i]); 744 } 745 printf("\n"); 746 return 0; 747 } 748 /***********************************************************************************/ 749 读写的函数: 750 一次读写一个字节: 751 fgetc/getchar/get 752 fputc/putchar/putc 753 754 一次去写一行: 755 fgets/gets 756 fputs/puts 757 gets不使用,因为gets获取一行数据是指导EOF而结束的,如果变量大小小于字符大小,则会造成内存溢出 758 puts函数主要用于向标准输出设备写入字符串并换行,即会自动写一个'\n' 759 fputs函数用来向指定文件写入一个字符串(不换行) 760 761 762 fwrite/fread 763 函数原型: 764 size_t fread(void *buffer,size_t num_bytes,size_t count,FILE *fp); 765 size_t fwrite(const void *buffer,size_t num_bytes,size_t count,FILE *fp); 766 767 对fread()而言,buffer是接收读入数据的内存区的指针。 768 对fwrite()而言,buffer是写入到那个文件的信息的指针。 769 count的值确定读/写多少项,每项长度等于num_bytes。 770 fp是指向事先打开的流的指针。 771 正常情况下,fread()返回读入的项数,fwrite()返回写出的项数。 772 只要文件按二进制打开,fread()和fwrite()就可以读/写各类信息。以下程序先向文件写double,int和long型数据,然后再读回。 773 774 ----------------------------------------------------------------------------- 775 打开一个标准I/O流的六种不同方式 776 777 限制 r w a r+ w+ 778 文件必须已存在 √ √ 779 擦除文件以前的内容 √ √ 780 流可以读 √ √ √ 781 流可以写 √ √ √ √ 782 流只可在尾端处写 √ 783 ------------------------------------------------------------------------------ 784 785 FILE指针:每个被使用的文件都在内核中开辟一个区域,用来存放文件的相关信息,这些信息是保存在一个结构体类型的 786 变量中,该结构体类型是由系统定义的,取名为FILE 787 788 标准I/O库的所有操作都是围绕流(stream)来进行的,在标准I/O中,流用 FILE* 来描述 789 790 流(stream): 791 定义:所有的I/O操作仅是简单的从程序移进或者移出,这种字节流,就称为流 792 分类:文本流/二进制流 793 794 文本流: 795 定义:在流中处理数据是以字符出现,在文本流中,'\n' 被转换成回车符CR和换行符LF的ASCII码0DH和0AH 796 而当输出时,0DH和0AH被转换成'\n' 797 数字2001在文本流中的表示方法为'2','0','0','1' 798 ASCII: 50 48 48 49 799 800 二进制流: 801 定义:流中处理的是二进制序列,若流中有字符,则用一个字节的二进制ASCII码表示;若是数字,则用对应的二进制数表示。对'\n'不进行变换 802 数字2001在二进制流中的表示方法为 00000111 11010001 803 804 /***********************************************************************************/ 805 文件I/O(系统I/O): 806 文件I/O相关操作函数 807 open() creat() close() read() write() lseek() 808 809 int open(const char *pathname , int flags); 810 int open(const char *pathname , int flags , mode_t mode); 811 int creat(const char *pathname , mode_t mode); 812 813 pathname 是表示欲打开的文件路径字符串 814 flags 是表示打开方式: 815 O_RDONLY 以只读方式打开文件 816 O_WRONLY 以只写方式打开文件 817 O_RDWR 以可读写方式打开文件. 上述三种旗标是互斥的, 也就是不可同时使用, 但可与下列的旗标利用OR(|)运算符组合. 818 O_CREAT 若欲打开的文件不存在则自动建立该文件. 819 O_EXCL 如果O_CREAT 也被设置, 此指令会去检查文件是否存在. 文件若不存在则建立该文件, 否则将导致打开文件错误. 此外, 若O_CREAT 与O_EXCL 同时设置, 并且欲打开的文件为符号连接, 则会打开文件失败. 820 O_NOCTTY 如果欲打开的文件为终端机设备时, 则不会将该终端机当成进程控制终端机. 821 O_TRUNC 若文件存在并且以可写的方式打开时, 此旗标会令文件长度清为0, 而原来存于该文件的资料也会消失. 822 O_APPEND 当读写文件时会从文件尾开始移动, 也就是所写入的数据会以附加的方式加入到文件后面. 823 O_NONBLOCK 以不可阻断的方式打开文件, 也就是无论有无数据读取或等待, 都会立即返回进程之中. 824 O_NDELAY 同O_NONBLOCK. 825 O_SYNC 以同步的方式打开文件. 826 O_NOFOLLOW 如果参数pathname 所指的文件为一符号连接, 则会令打开文件失败. 827 O_DIRECTORY 如果参数pathname 所指的文件并非为一目录, 则会令打开文件失败。注 828 829 mode 表示打开权限 830 S_IRWXU00700 权限, 代表该文件所有者具有可读、可写及可执行的权限. 831 S_IRUSR 或S_IREAD, 00400 权限, 代表该文件所有者具有可读取的权限. 832 S_IWUSR 或S_IWRITE, 00200 权限, 代表该文件所有者具有可写入的权限. 833 S_IXUSR 或S_IEXEC, 00100 权限, 代表该文件所有者具有可执行的权限. 834 S_IRWXG 00070 权限, 代表该文件用户组具有可读、可写及可执行的权限. 835 S_IRGRP 00040 权限, 代表该文件用户组具有可读的权限. 836 S_IWGRP 00020 权限, 代表该文件用户组具有可写入的权限. 837 S_IXGRP 00010 权限, 代表该文件用户组具有可执行的权限. 838 S_IRWXO 00007 权限, 代表其他用户具有可读、可写及可执行的权限. 839 S_IROTH 00004 权限, 代表其他用户具有可读的权限 840 S_IWOTH 00002 权限, 代表其他用户具有可写入的权限. 841 S_IXOTH 00001 权限, 代表其他用户具有可执行的权限. 842 843 844 这些函数都会返回一个文件描述符fp来表示当前打开的文件,文件描述符是一个非负整数,打开现存文件或者新建文件时,内核会返回一个文件描述符,用于指代被打开的文件, 845 所有执行I/O操作的系统调用都通过文件描述符。进程刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。 846 847 848 权限的修改和阅读: 849 权限分类: 850 r w x 851 4 2 1 852 853 示例: 854 -rwxrwxrwx 0777最高权限,采用8进制方法进行表示 855 -rw-r--r-- 0644 856 857 标准I/O和文件I/O的区别: 858 标准I/O是ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头文件中的定义,具有一定的可移植性,标准I/O库处理很多细节 859 例如缓存分配,以优化长度执行I/O等。标准I/O提供了三种类型的缓存。 860 1、全缓存:当填满标准I/O缓存后才进行实际的I/O操作 861 2、行缓存:当输入或输出中遇到新行符时,标准I/O库执行I/O操作 862 3、不带缓存:stderr就是了 863 864 文件I/O被称为不带缓存的I/O,不带缓存指的是每个read, write , 都调用内核中的一个系统调用,也就是一般所说的低级I/O 865 866 文件I/O使用文件描述符来表示打开的文件,所以可以访问不同类型的文件,如普通文件、管道文件等。而标准I/O使用的是FILE(流) 867 来表示打开的文件,通常只用来访问普通文件。 868 869 文件I/O使用示例: 870 /***********************************************************************************/ 871 #include <unistd.h> 872 #include <stdio.h> 873 #include <sys/types.h> 874 #include <sys/stat.h> 875 #include <fcntl.h> 876 877 int main(void) 878 { 879 int f; 880 int size; 881 char buf[100]; 882 f = open(path , O_RDONLY); //path为文件路径 883 size = read(f , buf , sizeof(buf)); 884 printf("size = %d , buf = %s\n",size , buf); 885 close(f); 886 return 0; 887 } 888 /***********************************************************************************/ 889 890 891 /***********************************************************************************/ 892 文件和目录 893 以下三个函数可以获取文件/目录的属性信息: 894 #include <unistd.h> 895 #include <sys/types.h> 896 #include <sys/stat.h> 897 898 int stat(const char *path, struct stat *buf); 899 int fstat(int fd, struct stat *buf); 900 int lstat(const char *path, struct stat *buf); 901 902 三个函数的返回:若成功则为0,若出错则为-1,并且设置errno. 903 给定一个path的情况下: 904 stat函数返回一个与此命名文件有关的信息结构 905 fstat函数获得已在描述符filedes上打开的文件的有关信息 906 lstat函数类似于stat,但是当命名的文件是一个符号连接时,lstat返回该符号连接的有关信息,而不是由该符号连接引用的文件的信息。 907 908 909 掌握目录操作相关焊数 910 chdir() 911 opendir() 912 readdir() 913 914 /***********************************************************************************/ 915 916 917 /***********************************************************************************/ 918 动态库和静态库 919 什么是库: 920 库是共享程序代码的方式,一般分为静态库和动态库 921 静态库: 922 链接时完整的拷贝至可执行文件中,被多次使用就有多份冗余拷贝 923 动态库: 924 链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省空间。 925 926 使用静态库和动态库的好处: 927 1、使用静态库的好处 928 模块化,分工合作 929 避免少量的改动经常导致大量的重复编译链接 930 也可以重用(注意不是共享内存) 931 2、使用动态库的好处: 932 使用动态库可以将最终可执行的文件体积缩小 933 使用动态库多个应用程序共享内存中的同一份库文件,节省资源 934 使用动态库可以不重新编译链接可执行程序的前提下,更新动态库文件达到更新应用程序的目的 935 /***********************************************************************************/ 936 937 /***********************************************************************************/ 938 进程/线程 939 创建 执行 消亡 940 进程是一个独立的可调度的任务 941 进程是一个程序的一次执行的过程 942 943 进程的定义:"进程"是操作系统的最基本,最重要的概念之一。但迄今为止对这一概念还没有一个确定的统一描述。 944 下面给出几种对进程的定义描述。进程是程序的一次执行,进程是可以并行执行的计算,进程是一个程序与其 945 使用的数据在处理机上顺序执行时发生的活动。进程是程序在一个数据集合上的运行过程。它是系统进行资源分 946 配和调度的一个独立单位。 947 948 进程的特征: 949 多态性:是程序的一次执行; 950 并发性:进程是可以并发执行; 951 独立性:是系统进行资源分配和调度的一个独立单位 952 异步性:进程间的相互制约,使进程执行具有间隙、 953 结构性:进程是具有结构的 954 955 进程和程序的区别: 956 1、程序是永存的,进程是暂时的,是程序在数据集上的一次执行,有创建有撤销,存在是暂时的 957 2、程序是静态的观念,进程是动态的观念 958 3、进程具有并发性,而程序没有 959 4、进程是竞争计算机资源的基本单位,程序不是 960 5、进程和程序不是一一对应的,一个程序可以对应多个进程可执行同一程序;一个进程可以执行一个或多个程序 961 962 进程创建:fork() 会复制进程(父 ,子) vfork() 963 #include <sys/type.h> 964 #include <unistd.h> 965 #include <stdio.h> 966 #include <stdlib.h> 967 968 int main(){ 969 970 pid_t pid; 971 pid = fork(); //当fork调用成功后,子进程开始从fork后开始执行 972 973 //创建进程失败 974 if(-1 == pid){ 975 perror("fork failed"); 976 return -1; 977 978 //父进程 979 }else if(pid > 0){ 980 //pid用于接收当前进程id, ppid用于接收父进程id 981 printf("parent : pid = %d , ppid = %d\n", getpid(), getppid()); 982 sleep(1);//系统延时 983 984 //子进程 985 }else if(pid == 0){ 986 printf("child: pid = %d, ppid = %d\n", getpid(), getppid()); 987 sleep(1); 988 } 989 return 0; 990 } 991 992 返回值 pid : 0:子进程 993 子进程pid(大于 0 的整数):父进程 994 -1:出错 995 996 ( pid_t 的底层代码就是一个 int ) 997 998 999 fork() 和 vfork() 的区别: 1000 fork()和 Vfork()都可以创建一个进程,但 vfork() 是由 fork()封装得来的 1001 其实 fork 创建的子进程相对独立,当用 fork 后子进程与父进程同时各自进行自己的程序互不影响,但只 1002 有一个终端接受他们两个的输出,所以并不是子进程与父进程随即调用,而只是在同时执行父进程和子进程, 1003 只是随机输出,子进程与父进程仍各自有序。 1004 1005 (1)使用fork创建一个进程时,子进程只是完全复制父进程的资源。这样得到的子进程独立于父进程,具有 1006 良好的并发性。而使用vfork创建一个子进程时,操作系统并不将父进程的地址空间完全复制到子进程,用vfork 1007 创建的子进程共享父进程的地址空间,也就是说子进程完全运行在父进程的地址空间上。子进程对该地址空间中 1008 任何数据的修改同样为父进程所见。 1009 1010 (2)使用fork创建一个子进程哪个进程先运行取决于系统的调度算法。而vfork创建一个进程时,vfork保证子进程 1011 先运行,当他调用exec或exit之后,父进程才可能被调读运行。如果在调用exec或exit之前子进程要依赖父进程的 1012 某个行为,就会导致死锁。 1013 1014 因为使用fork创建一个进程时,子进程需要将父进程几乎每种资源都复制,所以fork是一个开销很大的系统调用, 1015 这些开销并不是所有情况都需要的。比如fork一个进程后,立即调用exec执行另一个应用程序,那么fork过程中子 1016 进程对父进程地址空间的复制将是一个多余的过程。vfork不会拷贝父进程的地址空间,这大大减小了系统的开销。 1017 1018 当用vfork创建进程时,若以return 0 结束则释放局部变量,以exit(0)结束则不会释放。 1019 1020 (p185)fork一个子进程,该子进程中的var和globvar记录的是父进程中var和globvar中的值,而fork之后父进程对变量的改变则不对子进程产生影响。 1021 1022 vfork一个子进程,先执行子进程,子进程沿用父进程中的变量,当以exit(0)结束后,父进程可仍沿用子进程中的变量。 1023 当以return 0 结束则释放局部变量,父进程再引用时则会为系统给的随机值。 1024 1025 1026 在任何位置执行 exit() 都会结束进程(会刷新缓存),_exit() 不会刷新缓存 1027 1028 atexit() 注册退出函数 1029 int atexit(void(* function)(void)); 1030 把function注册到系统中,退出的时候会调用这个函数 1031 1032 excel() 执行其他的程序代码数据段全部被新的程序的代码段和数据段取代,只要执行成功,就不会返回了 1033 用来执行参数path字符串所代表的文件路径(绝对路径),第二个参数代表执行文件时传递的agrv,最后一个参数必须是空指针 1034 excel("/bin/cat","/etc/passwoed",NULL); 1035 命令路径 参数路径 1036 1037 wait() 和 waitpid()的区别: 1038 调用 wait() 函数使程序进行阻塞,直到任一个子进程结束或者是该进程接收到了一个信号为止。如果该进程没用子进程或者其子进程已经结束,wait() 函数会立即返回。 1039 调用 waitpid() ,参数 pid 指定想等待的子进程ID,值 -1 表示第一个终止的子进程,参数 options 指定附加页,最常用的是 WNOHANG ,通知内核在没有已终止子进程的时候不要阻塞 1040 当正常返回的时候,waitpid返回收到的子进程的进程ID; 1041 如果设置了选项 WNOHANG ,而调用中waitpid 发现没有已退出的子进程可收集,则返回0 1042 如果调用中出错,则返回 -1 ,这时 errno 会被设置成相应的值以指示错误所在 1043 /***********************************************************************************/ 1044 1045 /***********************************************************************************/ 1046 1047 网络编程 1048 DNS:常用的有114.114.114.114 / 8.8.8.8 用来解析数据 1049 子网掩码:用于判断IP是不是同一个网段 1050 1051 例如:有两个IP,192.168.0.100 , 192.168.0.8 两个IP需要通信,则两个IP与子网掩码作按位与操作 1052 192.168.0.100 & 0xff.0xff.0xff.0 = 192.168.0.0 1053 192.168.0.8 & 0xff.0xff.0xff.0 = 192.168.0.0 1054 两次的值相等,则说明两个IP在同一网段 1055 1056 网络字节序都是大端字节序 1057 TCP: 1058 TCP服务端流程 1059 1、创建套接字socket 1060 2、绑定端口 bind 1061 3、监听是否有客户端连接 Listen 1062 4、接受 accept 1063 5、读/写 send/recv 1064 6、关闭 close 1065 1066 TCP客户端: 1067 1、创建套接字 socket 1068 2、主动连接别人 connect 1069 3、读/写 send/recv 1070 4、关闭 close 1071 1072 UDP: 1073 UDP服务器流程: 1074 1、创建一个套接字 socket 1075 2、设置socket属性 setsockept 1076 3、绑点IP地址,端口等信息到socket上,用函数bind 1077 4、循环接收数据,用函数数recvfrom 1078 5、关闭网络连接 1079 1080 UDP客户端流程: 1081 1、创建一个套接字 socket 1082 2、设置socket属性,用函数setsockeopt 1083 3、绑点IP地址,端口等信息到socket上,用函数bind 1084 4、设置对方的IP地址和端口等属性; 1085 5、发送数据,用函数数sendto 1086 6、关闭网络连接 1087 1088 1089 OSI模型 TCP/IP协议 1090 应用层 应用层 1091 表示层 1092 会话层 1093 传输层 传输层 1094 网络层 网络层 1095 数据链络层 数据链络层 1096 物理层 物理层 1097 1098 1099 TCP和UDP都是OSI模型中传输层的协议,TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输 1100 1101 UDP补充: 1102 UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的 1103 数据在收到的那一刻,立刻按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况下, 1104 UDP也无法进行流量控制等避免网络拥塞的行为。此外,传输途中如果出现了丢包,UDO也不负 1105 责重发。甚至当出现包的到达顺序乱掉时也没有纠正的功能。如果需要这些细节控制,那么不得 1106 不交给由采用UDO的应用程序去处理。换句话说,UDP将部分控制转移到应用程序去处理,自己 1107 却只提供作为传输层协议的最基本功能。UDP有点类似于用户说什么听什么的机制,但是需要用 1108 户充分考虑好上层协议类型并制作相应的应用程序。 1109 1110 TCP补充: 1111 TCP充分实现了数据传输时各种控制功能,可以进行丢包的重发控制,还可以对次序乱掉的分包 1112 进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信 1113 对端存在时才会发送数据,从而可以控制通信流量的浪费。TCP通过检验和、序列号、确认应答、 1114 重发控制、连接管理以及窗口控制等机制实现可靠性传输。 1115 1116 1117 TCP与UDP区别总结: 1118 1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接 1119 2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保 证可靠交付 1120 3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的 1121 UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等) 1122 4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信 1123 5、TCP首部开销20字节;UDP的首部开销小,只有8个字节 1124 6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道 1125 1126 1127 1128 1129 1130 1131 排序算法: 1132 1、选择排序 1133 2、冒泡排序 1134 3、快速排序 1135 4、希尔排序 1136 5、桶排序 1137 6、计数排序 1138 7、插入排序 1139 8、归并排序 1140 9、堆排序 1141 10、基数排序 1142 1143 1、选择排序: 1144 /**********************************************************************************************************************/ 1145 #include <stdio.h> 1146 #define ARRSIZE 5 1147 1148 void ChoiceSort(int *arr) 1149 { 1150 int i,j,min,temp; 1151 for(i = 0 ; i < ARRSIZE-1 ; i++) 1152 { 1153 min = i; 1154 for(j = i+1 ; j < ARRSIZE ; j++) 1155 { 1156 if(arr[j] < arr[min]) //正序排序 1157 min = j; 1158 } 1159 if(min != i) 1160 { 1161 temp = arr[min]; 1162 arr[min] = arr[i]; 1163 arr[i] = temp; 1164 } 1165 } 1166 for(i = 0 ; i < ARRSIZE ; i++) 1167 printf("%d\t",arr[i]); 1168 } 1169 1170 int main(void) 1171 { 1172 int arr[ARRSIZE] = {0}; 1173 int i; 1174 for(i = 0 ; i < ARRSIZE ; i++) 1175 scanf("%d",&arr[i]); 1176 ChoiceSort(arr); 1177 return 0; 1178 } 1179 1180 /**********************************************************************************************************************/ 1181 1182 /**********************************************************************************************************************/ 1183 2、冒泡排序: 1184 #include <stdio.h> 1185 #define ARRSIZE 5 1186 1187 void BubbleSort(int *arr) 1188 { 1189 int i,j,temp,flag; 1190 for(i = 1 ; i < ARRSIZE ; i++) 1191 { 1192 flag = 0; 1193 for(j = 0 ; j < ARRSIZE-i ; j++) 1194 { 1195 if(arr[j] > arr[j+1]) 1196 { 1197 temp = arr[j]; 1198 arr[j] = arr[j+1]; 1199 arr[j+1] = temp; 1200 flag = 1; 1201 } 1202 } 1203 if(!flag) 1204 break; 1205 } 1206 for(i = 0 ; i < ARRSIZE ; i++) 1207 printf("%d\t",arr[i]); 1208 printf("\n"); 1209 } 1210 1211 int main(void) 1212 { 1213 int i; 1214 int arr[ARRSIZE] = {0}; 1215 for(i = 0 ; i < ARRSIZE ; i++) 1216 scanf("%d",&arr[i]); 1217 BubbleSort(arr); 1218 return 0; 1219 } 1220 1221 /**********************************************************************************************************************/ 1222 1223 /**********************************************************************************************************************/ 1224 3、快速排序: 1225 #include <stdio.h> 1226 1227 void QuickSort(int *arr,int num) 1228 { 1229 int i = 0; 1230 int j = num - 1; 1231 int x = arr[0]; 1232 if(num > 1) 1233 { 1234 while( i != j) 1235 { 1236 for(; i < j ; j--) 1237 { 1238 if(arr[j] < x) 1239 { 1240 1241 printf("j-- a[i] = %d,arr[j] = %d\n",arr[i],arr[j]); 1242 arr[i] = arr[j]; 1243 break; 1244 } 1245 } 1246 for(; i < j ; i++) 1247 { 1248 if(arr[i] > x) 1249 { 1250 printf("i++ a[j] = %d,arr[i] = %d\n",arr[j],arr[i]); 1251 arr[j] = arr[i]; 1252 break; 1253 } 1254 } 1255 arr[i] = x; 1256 } 1257 QuickSort(arr,i); 1258 QuickSort(arr + i + 1 , num - i - 1); 1259 } 1260 } 1261 1262 int main(void) 1263 { 1264 int i; 1265 int arr[7] = {49,38,65,97,76,13,27}; 1266 printf("old:"); 1267 for(i = 0 ; i < 7 ; i++) 1268 { 1269 printf("%d\t",arr[i]); 1270 } 1271 printf("\n"); 1272 QuickSort(arr,7); 1273 #if 1 1274 printf("new:"); 1275 for(i = 0 ; i < 7 ; i++) 1276 { 1277 printf("%d\t",arr[i]); 1278 } 1279 #endif 1280 printf("\n"); 1281 return 0; 1282 } 1283 1284 /**********************************************************************************************************************/ 1285 1286 /**********************************************************************************************************************/ 1287 4、希尔排序: 1288 1289 /**********************************************************************************************************************/ 1290 1291 /**********************************************************************************************************************/ 1292 1293 5、桶排序: 1294 /**********************************************************************************************************************/ 1295 1296 /**********************************************************************************************************************/ 1297 6、计数排序: 1298 /**********************************************************************************************************************/ 1299 1300 /**********************************************************************************************************************/ 1301 7、插入排序: 1302 /**********************************************************************************************************************/ 1303 1304 /**********************************************************************************************************************/ 1305 1306 8、归并排序: 1307 /**********************************************************************************************************************/ 1308 1309 /**********************************************************************************************************************/ 1310 1311 9、堆排序: 1312 /**********************************************************************************************************************/ 1313 1314 /**********************************************************************************************************************/ 1315 1316 10、基数排序: 1317 /**********************************************************************************************************************/ 1318 1319 /**********************************************************************************************************************/ 1320 1321 strlen的C语言实现: 1322 #include <stdio.h> 1323 1324 int my_strlen(char *dat) 1325 { 1326 int n = 0; 1327 while(*dat != '\0') 1328 { 1329 n++; 1330 dat++; 1331 } 1332 return n; 1333 } 1334 1335 int main(void) 1336 { 1337 char arr[] = "123456"; 1338 int num = 0; 1339 num = my_strlen(arr); 1340 printf("num = %d\n",num); 1341 return 0; 1342 } 1343 /**********************************************************************************************************************/ 1344 1345 /**********************************************************************************************************************/ 1346 strcpy的C语言实现: 1347 #include <stdio.h> 1348 1349 char* strcpy_0(char *dest , const char *src) 1350 { 1351 char *address = dest; 1352 while((*dest++ = *src++) != '\0'); 1353 return address; 1354 } 1355 1356 int main(void) 1357 { 1358 char arr0[7] = "123456"; 1359 char arr1[7] = "123345"; 1360 int i; 1361 char *ch; 1362 ch = strcpy_0(arr0 , arr1); 1363 for(i = 0 ; i < 6 ; i++) 1364 printf("%c",arr0[i]); 1365 printf("\n"); 1366 } 1367 /**********************************************************************************************************************/ 1368 1369 /**********************************************************************************************************************/ 1370 strcat的C语言实现: 1371 #include <stdio.h> 1372 1373 char* strcat_0(char *dest ,const char *src) 1374 { 1375 char *address = dest; 1376 while(*dest != '\0') 1377 dest++; 1378 while(*dest++ = *src++); 1379 return address; 1380 } 1381 1382 int main(void) 1383 { 1384 char arr0[10] = "123"; 1385 char arr1[] = "78"; 1386 char *ch = NULL; 1387 ch = strcat_0(arr0,arr1); 1388 printf("%s\n",ch); 1389 printf("%d\n",sizeof(arr0)); 1390 1391 return 0; 1392 } 1393 /**********************************************************************************************************************/ 1394 1395 /**********************************************************************************************************************/ 1396 strcmp的C语言实现: 1397 #include <stdio.h> 1398 1399 int strcmp_0(char *str1 , char *str2) 1400 { 1401 while(*str1 == *str2) 1402 { 1403 if(*str1 == '\0') 1404 return 0; 1405 str1++; 1406 str2++; 1407 } 1408 return *str1 - *str2; 1409 } 1410 1411 int main(void) 1412 { 1413 int num = strcmp_0("123432432","123"); 1414 printf("%d\n",num); 1415 1416 return 0; 1417 } 1418 /**********************************************************************************************************************/ 1419 1420 /**********************************************************************************************************************/
本文来自博客园,作者:伽椰子真可爱,转载请注明原文链接:https://www.cnblogs.com/jiayezi/p/13281305.html