c语言讲义v1.0

leozhang 贡献于2017-04-09

作者 朱景尧  创建于2015-02-14 17:27:00   修改者尹成yincheng01  修改于2015-02-14 17:27:00字数27315

文档摘要:main函数是C语言中的主函数,一个C语言的程序必须有一个主函数,也只能有一个主函数
关键词:

C讲义 C讲义 简单入门 保密 第页 C讲义 目 录 1 愉快的开始-HELLO WORLD 14 1.1 include头文件包含 14 1.2 main函数 14 1.3 注释 14 1.4 {}括号,程序题和代码块 14 1.5 声明 14 1.6 C语言自定义名字的要求 15 1.7 printf函数 15 1.8 return语句 15 1.9 System系统调用 15 1.9.1 System返回值在windows和unix下的不同, 15 1.9.2 POSIX 15 1.10 C语言编译过程,gcc参数简介 16 1.10.1 C语言编译过程 16 1.10.2 -E预编译 16 1.10.3 -S汇编 16 1.10.4 -c编译 16 1.10.5 链接 16 1.11 操作系统结构 17 1.11.1 用户模式 17 保密 第页 C讲义 1.11.2 内核模式 17 1.12 64位,32位系统区别 18 1.12.1 CPU内部结构与寄存器 18 1.12.2 RISC与CISC CPU构架 18 1.12.3 SPARC,x86与ARM 18 1.13 汇编语言 18 1.13.1 I386汇编简介 18 1.13.2 VS反汇编 19 1.14 IDE工具 19 1.14.1 QT常用快捷键 19 1.14.2 VS常用快捷键 19 1.14.3 VS断点,调试 19 2 C语言中的数据类型 19 2.1 常量 19 2.1.1 #define 19 2.1.2 const 19 2.2 字符串常量 20 2.3 二进制数、位、字节与字 20 2.4 八进制 20 2.5 十六进制 20 2.6 原码 21 2.7 反码 21 保密 第页 C讲义 2.8 补码 21 2.9 sizeof关键字 22 2.10 int类型 22 2.10.1 int常量,变量 22 2.10.2 printf输出int值 23 2.10.3 printf输出八进制和十六进制 23 2.10.4 short,long,long long,unsigned int 23 2.10.5 整数溢出 23 2.10.6 大端对齐与小端对齐 23 2.11 char类型 24 2.11.1 char常量,变量 24 2.11.2 printf输出char 24 2.11.3 不可打印char转义符 24 2.11.4 char和unsigned char 25 2.12 浮点float,double,long double类型 25 2.12.1 浮点常量,变量 25 2.12.2 printf输出浮点数 25 2.13 类型限定 25 2.13.1 const 25 2.13.2 volatile 26 2.13.3 register 26 3 字符串格式化输出和输入 26 保密 第页 C讲义 3.1 字符串在计算机内部的存储方式 26 3.2 printf函数,putchar函数 27 3.3 scanf函数与getchar函数 28 4 运算符表达式和语句 29 4.1 基本运算符 29 4.1.1 = 29 4.1.2 + 29 4.1.3 – 29 4.1.4 * 29 4.1.5 / 29 4.1.6 % 29 4.1.7 += 29 4.1.8 -= 29 4.1.9 *= 29 4.1.10 /= 30 4.1.11 %= 30 4.1.12 ++ 30 4.1.13 -- 30 4.1.14 逗号运算符 30 4.1.15 运算符优先级 30 4.2 复合语句 31 4.3 空语句 31 保密 第页 C讲义 4.4 类型转化 31 5 条件分支语句 31 5.1 关系运算符 31 5.1.1 < 31 5.1.2 <= 31 5.1.3 > 32 5.1.4 >= 32 5.1.5 == 32 5.1.6 != 32 5.2 关系运算符优先级 32 5.3 逻辑运算符 32 5.3.1 && 32 5.3.2 || 32 5.3.3 ! 33 5.4 if 33 5.5 if else 34 5.6 if else if 34 5.7 switch与break,default 35 5.8 条件运算符? 36 5.9 goto语句与标号 36 6 循环语句 36 6.1 while 36 保密 第页 C讲义 6.2 continue 37 6.3 break 37 6.4 do while 37 6.5 for 37 6.6 循环嵌套 37 7 数组 38 7.1 一维数组定义与使用 38 7.2 数组在内存的存储方式 38 7.3 一维数组初始化 38 7.4 二维数组定义与使用 39 7.5 二维数组初始化 39 8 字符串与字符数组 39 8.1 字符数组定义 39 8.2 字符数组初始化 39 8.3 字符数组使用 40 8.4 随机数产生函数rand与srand 40 8.5 用scanf输入字符串 40 8.6 字符串的结束标志 41 8.7 字符串处理函数 41 8.7.1 gets 41 8.7.2 fgets函数 41 8.7.3 puts函数 42 保密 第页 C讲义 8.7.4 fputs函数 42 8.7.5 strlen,字符串长度 42 8.7.6 strcat,字符串追加 42 8.7.7 strncat,字符串有限追加 43 8.7.8 strcmp,字符串比较 43 8.7.9 strncmp,字符串有限比较 43 8.7.10 strcpy字符串拷贝 43 8.7.11 strncpy字符串有限拷贝 43 8.7.12 sprintf,格式化字符串 43 8.7.13 Sscanf函数 44 8.7.14 strchr查找字符 44 8.7.15 strstr查找子串 44 8.7.16 strtok分割字符串 44 8.7.17 atoi转化为int 45 8.7.18 atof转化为float 45 8.7.19 atol转化为long 45 9 函数 45 9.1 函数的原型和调用 45 9.2 函数的形参与实参 45 9.3 函数的返回类型与返回值 46 9.4 main函数与exit函数与函数的return语句 46 9.5 多个源代码文件程序的编译 47 保密 第页 C讲义 9.5.1 头文件的使用 47 9.5.2 #include与#define的意义 47 9.5.3 #ifndef与#endif 47 9.6 函数的递归 48 9.6.1 递归的过程分析 48 9.6.2 递归的优点 52 9.6.3 递归的缺点 52 1 指针 52 1.1 指针 52 1.1.1 指针的概念 52 1.1.2 指针变量的定义 52 1.1.3 &取地址运算符 52 1.1.4 无类型指针 52 1.1.5 NULL 53 1.1.6 空指针与野指针 53 1.1.7 指针的兼容性 53 1.1.8 指向常量的指针与指针常量 54 1.1.9 指针与数组的关系 54 1.1.10 指针运算 54 1.1.11 通过指针使用数组元素 55 1.1.12 指针数组 55 1.1.13 指向指针的指针(二级指针) 55 保密 第页 C讲义 1.1.14 指向二维数组的指针 57 1.1.15 指针变量做为函数的参数 57 1.1.16 一维数组名作为函数参数 57 1.1.17 二维数组名作为函数参数 58 1.1.18 const关键字保护数组内容 58 1.1.19 指针做为函数的返回值 58 1.1.20 指向函数的指针 59 1.1.21 把指向函数的指针做为函数的参数 60 1.1.22 memset,memcpy,memmove函数 61 1.1.23 指针小结 63 2 字符指针与字符串 64 2.1 指针和字符串 64 2.2 通过指针访问字符串数组 64 2.3 函数的参数为char * 64 2.4 指针数组做为main函数的形参 65 3 内存管理 65 3.1 作用域 65 3.1.1 auto自动变量 65 3.1.2 register寄存器变量 65 3.1.3 代码块作用域的静态变量 66 3.1.4 代码块作用域外的静态变量 66 3.1.5 全局变量 66 保密 第页 C讲义 3.1.6 外部变量与extern关键字 66 3.1.7 全局函数和静态函数 66 3.2 内存四区 66 3.2.1 代码区 67 3.2.2 静态区 67 3.2.3 栈区 67 3.2.4 栈溢出 68 3.2.5 堆区 68 3.3 堆的分配和释放 70 3.3.1 malloc 70 3.3.2 free 70 3.3.3 calloc: 70 3.3.4 realloc 71 4 结构体,联合体,枚举与TYPEDEF 71 4.1 结构体 71 4.1.1 定义结构体struct和初始化 71 4.1.2 访问结构体成员 71 4.1.3 结构体的内存对齐模式 72 4.1.4 指定结构体元素的位字段 72 4.1.5 结构数组 72 4.1.6 嵌套结构 73 4.1.7 结构体的赋值 73 保密 第页 C讲义 4.1.8 指向结构体的指针 73 4.1.9 指向结构体数组的指针 73 4.1.10 结构中的数组成员和指针成员 73 4.1.11 在堆中创建的结构体 74 4.1.12 将结构作为函数参数 74 4.1.13 结构,还是指向结构的指针 74 4.2 联合体 75 4.3 枚举类型 75 4.3.1 枚举定义 75 4.3.2 默认值 76 4.4 typedef 76 4.5 通过typedef定义函数指针 76 5 文件操作 77 5.1 fopen 77 5.2 二进制和文本模式的区别 77 5.3 fclose 78 5.4 getc和putc函数 78 5.5 EOF与feof函数文件结尾 78 5.6 fprintf,fscanf,fgets,fputs函数 78 5.7 stat函数 78 5.8 fread和fwrite函数 79 5.9 fread与feof 79 保密 第页 C讲义 5.10 通过fwrite将结构保存到二进制文件中 79 5.11 fseek函数 80 5.12 ftell函数 80 5.13 fflush函数 80 5.14 remove函数 81 5.15 rename函数 81 6 基础数据结构与算法 82 6.1 什么是数据结构 82 6.2 什么是算法 82 6.3 排序 83 6.3.1 冒泡排序 83 6.3.2 选择排序 83 6.4 查找 83 6.4.1 顺序查找 83 6.4.2 二分查找 83 6.5 链表 84 6.5.1 单向链表定义 84 6.5.2 单向链表数据结构定义 85 6.5.3 单向链表的实现 85 保密 第页 C讲义 1 愉快的开始-hello world 1.1 include头文件包含 include是要告诉编译器,包含一个头文件, 在C语言当中,任何库函数调用都需要提前包含头文件 <头文件>,代表让C语言编译器去系统目录下寻找相关的头文件 “头文件”,代表让C语言编译器去用户当前目录下寻找相关头文件 如果是使用了一个C语言库函数需要的头文件,那么一定是#include <> 如果使用了一个自定义的h文件,那么一定是#include “” 1.2 main函数 main函数是C语言中的主函数,一个C语言的程序必须有一个主函数,也只能有一个主函数 1.3 注释 //,单行注释,代表注释,就是一个文字说明,没有实质的意义,单行注释是C++语言的注释方法 /* */,多行注释,多行注释是标准C语言的注释方法 1.4 {}括号,程序题和代码块 C语言所有的函数的代码都是在{}里包着的 1.5 声明 Int a; 声明一个变量名字叫a,对于c语言,变量的名称是可以子定义的。 第页 保密 C讲义 1.6 C语言自定义名字的要求 可以使用大小写字母,下划线,数字,但第一个字母必须是字母或者下划线 字母区分大小写 不能用C语言的关键字做为变量名称 每一行,必须是;结尾 1.7 printf函数 printf是向标准输出设备输出字符串的 如果要输出一个字符串:。例如:printf(“hello world”); 如果要输出一个整数,例如:printf(”%d”, 整数) Printf(“\n”);会输出一个回车换行 1.8 return语句 一个函数遇到return语句就终止了,return是c语言的关键字 1.9 System系统调用 System库函数的功能是执行操作系统的命令或者运行指定的程序,system库函数的调用需要include 1.9.1 System返回值在windows和unix下的不同, 1.9.2 POSIX 第页 保密 C讲义 1.10 C语言编译过程,gcc参数简介 1.10.1 C语言编译过程 1.10.2 -E预编译 Gcc –E –o a.e a.c 预编译a.c文件,生成的目标文件名为a.e 预编译是将include包含的头文件内容替换到C文件中中,同时将代码中没用的注释部分删除 1.10.3 -S汇编 -s就是将c语言转化为汇编语言, 1.10.4 -c编译 将代码编译为二进制的机器指令 1.10.5 链接 Gcc没有任何参数,代表就是链接 第页 保密 C讲义 1.11 操作系统结构 1.11.1 用户模式 应用程序都是运行在用户区域 1.11.2 内核模式 操作系统的内核,设备驱动程序,这些都是在内核模式下运行的 第页 保密 C讲义 1.12 64位,32位系统区别 1.12.1 CPU内部结构与寄存器 1.12.2 RISC与CISC CPU构架 RISC精简指令集 CISC复杂指令,一般来讲x86构架的CPU都是复杂指令的,AMD,intel就x86构架的,linux就是基于x86的操作系统 1.12.3 SPARC,x86与ARM Sun有自己的CPU,是基于SPARTC,其实就是一款RISC的CPU 1.13 汇编语言 1.13.1 I386汇编简介 Mov eax, 10 Add eax, 10 第页 保密 C讲义 Sub eax, 20 Call printf 1.13.2 VS反汇编 先f9设置一个断点 F5,用调试方式运行代码 1.14 IDE工具 1.14.1 QT常用快捷键 1.14.2 VS常用快捷键 1.14.3 VS断点,调试 2 C语言中的数据类型 2.1 常量 常量就是在程序中不可变化的量,常量在定义的时候必须给一个初值。 2.1.1 #define 定义一个宏常量 2.1.2 const 定义一个const常量 第页 保密 C讲义 2.2 字符串常量 “hello world” 对于#define类型的常量,c语言的习惯是常量名称为大写,但对于普通const常量以及变量,一般为小写结合大写的方式 2.3 二进制数、位、字节与字 我们习惯于十进制的数:10,12等 一个位只能表示0,或者1两种状态,简称bit,一个位是一个bit 一个字节为8个二进制,称为8位,简称BYTE,8个比特是一个字节 一个字为2个字节,简称WORD。 两个字为双字,简称DWORD 2.4 八进制 八进制为以8为基数的数制系统,C语言当中0表示八进制,0666; 2.5 十六进制 十六进制值16为基数 的数制系统,C语言中用0x表示十六进制 十进制 十六进制 二进制 0 0 0000 1 1 0001 2 2 0010 3 3 0011 4 4 0100 十进制转化8进制,用十进制数作为被除数,8作为除数,取商数和余数,直到商数为0的时候,将余数倒过来就是转化后的结果 第页 保密 C讲义 十进制转化16进制,用十进制数作为被除数,16作为除数,取商数和余数,直到商数为0的时候,将余数倒过来就是转化后的结果 2.6 原码 将最高位做为符号位(0代表正,1代表负),其余各位代表数值本身的绝对值 +7的原码是00000111 -7的原码是10000111 +0的原码是00000000 -0的原码是10000000 2.7 反码 一个数如果值为正,那么反码和原码相同 一个数如果为负,那么符号位为1,其他各位与原码相反 +7的反码00000111 -7的反码11111000 -0的反码11111111 2.8 补码 原码和反码都不利于计算机的运算,如:原码表示的7和-7相加,还需要判断符号位。 正数:原码,反码补码都相同 第页 保密 C讲义 负数:最高位为1,其余各位原码取反,最后对整个数 + 1 -7的补码:= 10000111(原码) 111111000(反码) 11111001(补码) +0的补码为00000000 -0的补码也是00000000 补码符号位不动,其他位求反,最后整个数 + 1,得到原码 用补码进行运算,减法可以通过加法实现 7-6=1 7的补码和-6的补码相加:00000111 + 11111010 = 100000001 进位舍弃后,剩下的00000001就是1的补码 -7+6 = -1 -7的补码和6的补码相加:11111001 + 00000110 = 11111111 11111111是-1的补码 2.9 sizeof关键字 sizeof是c语言关键字,功能是求指定数据类型在内存中的大小,单位:字节 sizeof与size_t类型 2.10 int类型 2.10.1 int常量,变量 int就是32位的一个二进制整数,在内存当中占据4个字节的空间 第页 保密 C讲义 2.10.2 printf输出int值 %d,输出一个有符号的10进制整数,%u,代表输出一个无符号的十进制整数 2.10.3 printf输出八进制和十六进制 %x,代表输出16进制数,%X,用大写字母方式输出16进制数 %o代表输出八进制数 2.10.4 short,long,long long,unsigned int short意思为短整数,在32位系统下是2个字节,16个比特 long意思为长整数,在32位的系统下,long都是4个字节的,在64位系统下,windows还是4个字节,unix下成了8个字节。 Int不管是32位系统下,还是64位系统下,不论是windows还是unix都是4个字节的 Long long是64位,也就是8个字节大小的整数,对于32位操作系统,CPU寄存器是32位,所以计算longlong类型的数据,效率很低 9l,9L,9ll,9LL,9u,9ull,9ULL 2.10.5 整数溢出 计算一个整数的时候超过整数能够容纳的最大单位后,整数会溢出,溢出的结果是高位舍弃。 当一个小的整数赋值给大的整数,符号位不会丢失,会继承 2.10.6 大端对齐与小端对齐 对于arm,intel这种x86构架的复杂指令CPU,整数在内存中是倒着存放的,低地址放低位,高地址放高位,小端对齐。 但对于unix服务器的CPU,更多是采用大端对齐的方式存放整数 第页 保密 C讲义 2.11 char类型 2.11.1 char常量,变量 char c;定义一个char变量 ‘a’,char的常量 Char的本质就是一个整数,一个只有1个字节大小的整数 2.11.2 printf输出char %c意思是输出一个字符,而不是一个整数 2.11.3 不可打印char转义符 \a,警报 \b退格 \n换行 \r回车 第页 保密 C讲义 \t制表符 \\斜杠 \’单引号 \”双引号 \?问号 2.11.4 char和unsigned char char取值范围为-128到127 unsigned char为0-255 2.12 浮点float,double,long double类型 2.12.1 浮点常量,变量 Float在32位系统下是4个字节,double在32位系统下是8个字节 小数的效率很低,避免使用,除非明确的要计算一个小数。 2.12.2 printf输出浮点数 %f,%Lf %f是输出一个double %lf输出一个long double 2.13 类型限定 2.13.1 const const是代表一个不能改变值的常量 第页 保密 C讲义 2.13.2 volatile 代表变量是一个可能被CPU指令之外的地方改变的,编译器就不会针对这个变量去优化目标代码。 2.13.3 register 变量在CPU寄存器里面,而不是在内存里面。但regist是建议型的指令,而不是命令型的指令 3 字符串格式化输出和输入 3.1 字符串在计算机内部的存储方式 字符串是内存中一段连续的char空间,以’\0’结尾 第页 保密 C讲义 “”是C语言表达字符串的方式 3.2 printf函数,putchar函数 printf格式字符 字符 对应数据类型 含义 d int 接受整数值并将它表示为有符号的十进制整数 hd Short int 短整数 hu Unsigned short int 无符号短整数 o unsigned int 无符号8进制整数 u unsigned int 无符号10进制整数 x / X unsigned int 无符号16进制整数,x对应的是abcdef,X对应的是ABCDEF f float或double 单精度浮点数或双精度浮点数 e / E double 科学计数法表示的数,此处"e"的大小写代表在输出时用的“e”的大小写 第页 保密 C讲义 c char 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符 s / S char * / wchar_t * 字符串。输出字符串中的字符直至字符串中的空字符(字符串以'\0‘结尾,这个'\0'即空字符) p void * 以16进制形式输出指针 % % 输出一个百分号 printf附加格式 字符 含义 l 附加在d,u,x,o前面,表示长整数 - 左对齐 m(代表一个整数) 数据最小宽度 0 将输出的前面补上0直到占满指定列宽为止不可以搭配使用- N(代表一个整数)  宽度至少为n位不够以空格填充 Putchar是显式一个字符的函数 3.3 scanf函数与getchar函数 scanf通过键盘读取用户输入,放入变量中,记得参数一定是变量的地址(&) int a = 0; int b = 0; scanf("%d", &a);//一定要用&取变量的地址 scanf("%d", &b);//一定要用&取变量的地址 Getchar得到用户键盘输入的字符 第页 保密 C讲义 4 运算符表达式和语句 4.1 基本运算符 4.1.1 = 数据对象:泛指数据在内存的存储区域 左值:表示可以被更改的数据对象 右值:能赋给左值的量 4.1.2 + 加 4.1.3 – 减 4.1.4 * 乘 4.1.5 / 除 4.1.6 % 取余数 4.1.7 += 加等于 4.1.8 -= 减等于 4.1.9 *= 乘等于 第页 保密 C讲义 4.1.10 /= 除等于 4.1.11 %= 取余等于 4.1.12 ++ 自加1 ,i++先计算表达式的值,然后再++ ,++i是先++,再计算表达式的值 4.1.13 -- 自减一 4.1.14 逗号运算符 int a = 2; int b = 3; int c = 4; int d = 5; int i = (a = b, c + d); 逗号表达式先求逗号左边的值,然后求右边的值,整个语句的值是逗号右边的值。 4.1.15 运算符优先级 优先级 运算符 结合性 1 ++(后缀),--(后缀),()(调用函数),{}(语句块),.,-> 从左到右 2 ++(前缀),--(前缀),+(前缀),-(前缀),!(前缀),~(前缀),sizeof,*(取指针值),&(取地址),(type)(类型转化) 从右到左 3 *, /, % 从左到右 4 +,- 从左到右 5 << >> 从左到右 6 < > <= >= 从左到右 第页 保密 C讲义 7 == != 从左到右 8 & 从左到右 9 ^ 从左到右 10 | 从左到右 11 && 从左到右 12 || 从左到右 13 ? 从右到左 14 =,*=,%=,+=,-=,<<=,>>=,&=,|=,^= 从右到左 15 ,(逗号运算符) 从左到右 4.2 复合语句 {}代码块 4.3 空语句 只有一个;号的语句就是空语句,空语句在C语言里面和合法的,并且是在某些场合必用的 4.4 类型转化 double f = (double)3 / 2; ()为强制类型转化运算符 5 条件分支语句 5.1 关系运算符 在C语言中0代表false,非0代表真 5.1.1 < 小于 5.1.2 <= 小于等于 第页 保密 C讲义 5.1.3 > 大于 5.1.4 >= 大于等于 5.1.5 == 等于 5.1.6 != 不等于 5.2 关系运算符优先级 前四种相同,后两种相同,前四种高于后两种优先级 5.3 逻辑运算符 5.3.1 && 与 当运算符左右都是真的时候,那么整个表达式的结果为真 只有左右有一个值为假,那么整个表达式的结果为假 5.3.2 || 或 第页 保密 C讲义 当运算符左右只要有一个值是真的时候,那么整个表达式的结果为真 除非左右两个值都是假,那么整个表达式的结果为假 5.3.3 ! 非 当值为真的时候,表达式为假, 当值为假的时候,表达式为真 5.4 if 单分支 If (条件) { //复合语句 } 当条件是真的时候,复合语句才能被执行,如果条件为假的时候,复合语句不执行 第页 保密 C讲义 5.5 if else 双分支 If (条件) { 复合语句1 } Else { 复合语句2 } 如果条件为真,那么执行复合语句1,否则执行复合语句2 5.6 if else if 多重if If (条件1) { 复合语句1 } Else if (条件2) { 复合语句2 } Else if (条件3) 第页 保密 C讲义 { 复合语句3 } Else { 复合语句4 } 当有多个else的时候,else总是和上方最近的那个if语句配对。 5.7 switch与break,default 多重选择 switch (i) { case 0: printf("i = 0\n"); break;//跳出switch的复合语句块 case 1: printf("i = 1\n"); break; case 2: printf("i = 2\n"); break; case 3: printf("i = 3\n"); break; case 4: printf("i = 4\n"); break; default://如果有所条件都不满足,那么执行default语句 第页 保密 C讲义 printf("error\n"); } 什么时候用if,什么时候用switch 当条件很复杂,一个条件中有&&,|| ,!存在,那么用if语句 如果条件很简单,但分支很多,那么适合用switch 5.8 条件运算符? 一个求绝对值的例子 int i = -8; int x = (i < 0) ? -i: i; 先求?左边的条件,如果条件为真,那么等于:左边的值,否则等于:右边的值 一个求最大值的例子 int c = (a > b) ? a : b; 5.9 goto语句与标号 无条件跳转goto 不建议使用goto语句,goto语句会使你的程序可读性变的很差 6 循环语句 6.1 while while(条件),如果条件为真,循环继续,条件为假,循环结束 第页 保密 C讲义 while (1)、、是死循环的写法 6.2 continue 循环遇到continue语句,不再执行continue下面代码,而是直接返回到循环起始语句处继续执行循环 6.3 break 循环遇到break语句,立刻终端循环,循环结束 6.4 do while do 复合语句 while (条件); 对于do while来讲,循环的复合语句至少可以被执行一次 对于while来讲,有可能复合语句一次执行机会都没有 6.5 for for(int I = 0;i<10;i++) 6.6 循环嵌套 int i,j; for(i = 9; i > 0; i--) { for(j = 9; j > 0; j--) { printf("%d\t", i * j); } printf("\n"); 第页 保密 C讲义 } 7 数组 7.1 一维数组定义与使用 int array[10];//定义一个一维数组,名字叫array,一共有10个元素,每个元素都是int类型的 array[0] = 20; array[1] = 30; array[9] = 80; //array[10] = 100;//错误,没有 array[10]这个元素 7.2 数组在内存的存储方式 数组在内存中就是一段连续的空间,每个元素的类型是一样的 7.3 一维数组初始化 int array[10] = { 100, 1, 5, 3, 4, 5, 6, 7, 8, 0 };//定义数组的同时为数组的成员初始化值 int array[10] = { 3, 7, 9 };//将数组的前三个元素赋值,其余元素置为0 int array[10] = { 0 };//将数组所有的元素都置为0 int i; for (i = 0; i < 10; i++) { 第页 保密 C讲义 array[i] = 0;//通过循环遍历数组的每个元素,将元素的值置为0 } 7.4 二维数组定义与使用 int array[2][3];//定义了一个二维数组,有两个array[3] 7.5 二维数组初始化 int a[3][4] = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } }; 8 字符串与字符数组 8.1 字符数组定义 char array[100]; 8.2 字符数组初始化 char array[100] = {'a', 'b', 'c','d'}; char array[100] = "abcd"; char array[100] = { 0 }; char array[] = "abcd"; 第页 保密 C讲义 8.3 字符数组使用 8.4 随机数产生函数rand与srand 头文件stdlib.h Rand是伪随机数产生器,每次调用rand产生的随机数是一样的 如果调用rand之前先调用srand就出现任意的随机数 只要能保证每次调用srand函数的时候,参数的值是不同的,那么rand函数就一定会产生不同的随机数 #include int t = (int)time(NULL); srand(t); for (int i = 0; i < 10; i++) { printf("%d\n", rand()); } 8.5 用scanf输入字符串 char s[10] = { 0 }; scanf("%s", s);//"%s"的作用就是输入一个字符串的,scanf是以回车键作为输入完成标示的,但回车键本身并不会作为字符串的一部分 //如果scanf参数中的数组长度小于用户在键盘输入的长度,那么scanf就会缓冲区溢出,导致程序崩溃 int i; for (i = 0; i < 10; i++) 第页 保密 C讲义 { printf("%d\n", s[i]); } printf("----------------------------------\n"); printf("%s\n", s); return 0; 8.6 字符串的结束标志 Scanf将回车,空格都认为是字符串输入结束标志, 8.7 字符串处理函数 8.7.1 gets char s[100] = { 0 }; gets(s);//gets认为回车的输入结束标示,空格不是输入结束标示,所以用gets这个函数就可以实现输入带空格的字符串 //gets和scanf一样存在缓冲区溢出的问题 int i; for (i = 0; i < 10; i++) { printf("%d\n", s[i]); } printf("----------------------------------\n"); printf("%s\n", s); Gets不能用类似“%s”或者“%d”之类的字符转义,只能接受字符串的输入 8.7.2 fgets函数 gets函数不检查预留缓冲区是否能够容纳用户实际输入的数据。多出来的字符会导致内存溢出,fgets函数改进了这个问题。 第页 保密 C讲义 由于fgets函数是为读取文件设计的,所以读取键盘时没有gets那么方便 char s[100] = { 0 }; fgets(s, sizeof(s), stdin); 8.7.3 puts函数 puts函数打印字符串,与printf不同,puts会在最后自动添加一个’\n’ char s[] = "hello world"; puts(s); 8.7.4 fputs函数 fputs是puts的文件操作版本, char s[] = "hello world"; fputs(s, stdout); 8.7.5 strlen,字符串长度 size_t strlen(const char * _Str); 返回不包含字符串结尾’\0’的字符串长度 char s[100] = "hello world"; int len = strlen(s);//得到字符串长度,返回一个字符串中有效字符的数量(不包含字符串结尾的0) printf("len = %d\n", len); return 0; 8.7.6 strcat,字符串追加 size_t strcat(char * _Str1, const char * _Str2); 将参数_Str2追加到_Str1后尾 第页 保密 C讲义 char s[1024] = "hello world"; int len = strlen(s);//得到字符串长度,返回一个字符串中有效字符的数量(不包含字符串结尾的0) printf("len = %d\n", len); char s1[100] = "abc123456789"; strcat(s, s1);//将两个字符串合并,结果放入第一个参数里面,strcat也存在缓冲区溢出的问题 printf("%s\n", s); 8.7.7 strncat,字符串有限追加 size_t strncat(char * _Str1, const char * _Str2, size_t len); 8.7.8 strcmp,字符串比较 int strcmp(const char * _Str1, const char * _Str2); 比较两个字符串是否相等,相等返回0,不等返回非0 8.7.9 strncmp,字符串有限比较 8.7.10 strcpy字符串拷贝 char *strcpy(char * _Str1, const char * _Str2); 将参数_Str2拷贝到参数_Str1中 8.7.11 strncpy字符串有限拷贝 8.7.12 sprintf,格式化字符串 和printf函数功能类似,printf函数将格式化结果输出到屏幕,sprintf将格式化结果输出到字符串 第页 保密 C讲义 8.7.13 Sscanf函数 Sscanf类似于scanf函数,,scanf从键盘读取用户输入,scanf从指定格式化字符串读取输入 8.7.14 strchr查找字符 char * strchr(char * _Str, int _Ch); 在参数_str中查找参数_Ch指定字符,找到返回字符_Ch在_Str中所在位置,没有找到返回NULL; 8.7.15 strstr查找子串 char * strstr(char * _Str, const char * _SubStr) 在参数_str中查找参数_SubStr指定子串,找到返回子串在_Str中所在位置,没有找到返回NULL; 8.7.16 strtok分割字符串 字符在第一次调用时strtok()必需给予参数s字符串,往后的调用则将参数s设置成NULL每次调用成功则返回指向被分割出片段的指针 char buf[] = "abc@defg@igk"; char *p = strtok(buf, "@"); while (p) { printf("%s\n", p); p = strtok(NULL, "@"); } 第页 保密 C讲义 8.7.17 atoi转化为int 需要包含头文件stdlib.h 8.7.18 atof转化为float 8.7.19 atol转化为long 9 函数 9.1 函数的原型和调用 在使用函数前必须定义或者声明函数 double circle(double r); int main() { double length = circle(10); printf("length = %f\n", length); return 0; } double circle(double r) { return 2 * 3.14 * r; } 9.2 函数的形参与实参 在调用函数的时候,函数大多数都有参数,主调函数和被调用函数之间需要传递数据。 在定义函数时函数名后面括弧中的变量名称为“形式参数”,简称形参。在调用函数时,函数名后面括号中的变量或表达式称为“实际参数”,简称实参。 第页 保密 C讲义 1形参在未出现函数调用时,他们并不占用内存单元,只有在发生函数调用的时候形参才被分配内存,函数调用完成后,形参所占的内存被释放 2实参可以是变量,常量或者表达式 3在定义函数时,一定要指定形参的数据类型 4形参与实参的数据类型一定要可兼容 5在C语言中,实参与形参的数据传递是“值传递”,即单向传递,只由实参传递给形参,而不能由形参传递给实参。 如果函数的参数是个数组,那么是可以通过形参修改实参的值的 9.3 函数的返回类型与返回值 1函数的返回值通过函数中的return获得,如果函数的返回值为void可以不需要return语句。 2函数return语句中的返回值数据类型应该与函数定义时相同。 3如果函数中没有return语句,那么函数将返回一个不确定的值。 9.4 main函数与exit函数与函数的return语句 int test1() { printf("111111\n"); //return 0; exit(0);//在子函数中调用exit同样代表程序终止,但在子函数中调用return只是子函数终止,程序正常执行 printf("222222\n"); } int main() { 第页 保密 C讲义 test1(); printf("AAAAAA\n"); exit(100);//exit是C语言的库函数,调用exit的结果就是程序终止 return 100;//在main函数中调用exit与调用return是一样的 printf("CCCCCCC\n"); return 0;//main函数return代表程序终止 printf("BBBBBB\n"); } 9.5 多个源代码文件程序的编译 9.5.1 头文件的使用 如果把main函数放在第一个文件中,而把自定义函数放在第二个文件中,那么就需要在第一个文件中声明函数原型。 如果把函数原型包含在一个头文件里,那么就不必每次使用函数的时候都声明其原型了。把函数声明放入头文件是很好的习惯。 9.5.2 #include与#define的意义 #include就是简单的文件内容替换 #define就是简单的文本替换而已 9.5.3 #ifndef与#endif #ifndef的意思就是条件预编译,如果#ifndef 后面的条件成立,那么就预编译从#ifndef开始到#endif之间的代码,否则不会去预编译这段代码 第页 保密 C讲义 9.6 函数的递归 函数可以调用自己,这就叫函数的递归 void recurse(int i) { if (i > 0) { recurse(i - 1); } printf("i = %d\n", i); } int main() { recurse(10); return 0; } 9.6.1 递归的过程分析 void up_down(int n) { printf("in %d, location %p\n", n, &n); if (n < 4) up_down((n + 1)); printf("out %d, location %p\n", n, &n); } int main() { up_down(1); return 0; } 第页 保密 C讲义 第页 保密 C讲义 有n个人排成一队,问第n个人多少岁,他回答比前面一个人大2岁,再问前面一个人多少岁,他回答比前面一个人大2岁,一直问到最后问第一个人,他回答10岁 int age(int n) { int i; if (n == 1) i = 10; else i = age(n - 1) + 2; return i; } 将10进制数转化为二进制数的例子 234在十进制下为2 * 10的2次方 + 3 * 10的1次方 + 4*10的0次方。 奇数的二进制最后一位一定是1,偶数的二进制最后一位一定是0。 可以通过 number % 2 得到二进制形式的最后一位,如果要将一个完整的整数转化为二进制就需要用到递归函数。 第页 保密 C讲义 在递归调用之前,计算 number % 2的值,然后在递归调用语句之后进行输出,这样计算出的第一个数值反而在最后一个输出。 为了得出下一个数,需要把原数除以2,这种计算相当于十进制下把小数点左移一位,如果此时得出的数是偶数,,则下一个二进制的数值是0,如果得出的是奇数,那么下一个二进制数为1。 直到被2除的结果小于2,就停止递归。 void to_binary(unsigned int n) { unsigned int i = n % 2; if (n >= 2) to_binary(n / 2); printf("%c", i + 0x30); } 斐波那契数列例子 斐波那契数列指的是这样一个数列 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ... 第0项是0,第1项是第一个1。 这个数列从第2项开始,每一项都等于前两项之和。 int fib(int n) { if (n == 0) return 0; if (n == 1) return 1; if (n > 1) return fib(n - 1) + fib(n - 2); } 第页 保密 C讲义 9.6.2 递归的优点 递归给某些编程问题提供了最简单的方法 9.6.3 递归的缺点 一个有缺陷的递归会很快耗尽计算机的资源,递归的程序难以理解和维护。 1 指针 1.1 指针 1.1.1 指针的概念 指针变量也是一个变量 指针存放的内容是一个地址,该地址指向一块内存空间 1.1.2 指针变量的定义 可以定义一个指向一个变量的指针变量。 int *p;//表示定义一个指针变量。 *p;//代表指针所指内存的实际数据 切记,指针变量只能存放地址,不能将一个int型变量直接赋值给一个指针。 int *p = 100; 1.1.3 &取地址运算符 &可以取得一个变量在内存当中的地址 1.1.4 无类型指针 定义一个指针变量,但不指定它指向具体哪种数据类型。可以通过强制转化将void *转化为其他类型指针,也可以用(void *)将其他类型指针强制转化为void类型指针。 void *p 第页 保密 C讲义 1.1.5 NULL NULL在C语言中的定义为(void *)0 1.1.6 空指针与野指针 指向NULL的指针叫空指针,没有具体指向任何变量地址的指针叫野指针 1.1.7 指针的兼容性 指针之间赋值比普通数据类型赋值检查更为严格,例如:不可以把一个double *赋值给int * 原则上一定是相同类型的指针指向相同类型的变量地址,不能用一种类型的指针指向另一种类型的变量地址。 第页 保密 C讲义 1.1.8 指向常量的指针与指针常量 const char *p;//定义一个指向常量的指针 char *const p;//定义一个指针常量,一旦初始化之后其内容不可改变 1.1.9 指针与数组的关系 一个变量有地址,一个数组包含若干个元素,每个元素在内存中都有地址。 int a[10]; int *p = a; 比较p和&a[0]的地址是否相同 1.1.10 指针运算 指针运算不是简单的整数加减法,而是指针指向的数据类型在内存中占用字节数做为倍数的运算。 Char *p; 第页 保密 C讲义 P++;移动了sizeof(char)这么多的字节数 Int *p1; P1++移动了sizeof(int)这么多的字节数 赋值:int *p = &a; 求值:int I = *p; 取指针地址 int **pp = &p; 将一个整数加(减)给指针:p + 3; p – 3; 增加(减少)指针值 p++,p-- 求差值 ,p1 – p2,通常用于同一个数组内求两个元素之间的距离 比较 p1 == p2,通常用来比较两个指针是否指向同一个位置。 1.1.11 通过指针使用数组元素 p + 1代表&a[1],也可以直接使用p[1]表示a[5] p + 5 代表&a[5] p++ 1.1.12 指针数组 int *p[5]; 1.1.13 指向指针的指针(二级指针) 指针就是一个变量,既然是变量就也存在内存地址,所以可以定义一个指向指针的指针。 int i = 10; 第页 保密 C讲义 int *p1 = &i; int **p2 = &p1; printf("%d\n", **p2); 以此类推可以定义3级甚至多级指针。C语言允许定义多级指针,但是指针级数过多会增加代码的复杂性,考试的时候可能会考多级指针,但实际编程的时候最多用到3级,但3级指针也不常用,一级和二级指针是大量使用。 第页 保密 C讲义 1.1.14 指向二维数组的指针 int buf[3][5] 二维数组名称,buf代表数组首地址 int (*a)[5] 定义一个指向int [5]类型的指针变量a a[0], *(a + 0), *a 0行,0列元素地址 a + 1 第1行首地址 a[1], *(a + 1) 第1行,0列元素地址 a[1] + 2, *(a + 1) + 2, &a[1][2] 第1行,2列元素地址 *(a[1] + 2), *(*(a + 1) + 2), a[1][2] 第1行,2列元素的值 1.1.15 指针变量做为函数的参数 函数的参数可以是指针类型。,它的作用是将一个变量的地址传送给另一个函数。 通过函数的指针参数可以间接的实现形参修改实参的值 1.1.16 一维数组名作为函数参数 当数组名作为函数参数时,C语言将数组名解释为指针 第页 保密 C讲义 int func(int array[10]); 1.1.17 二维数组名作为函数参数 二维数组做函数参数时可以不指定第一个下标。 int func(int array[][10]); 将二维数组做为函数的参数用例不是特别多见 1.1.18 const关键字保护数组内容 如果讲一个数组做为函数的形参传递,那么数组内容可以在被调用函数内部修改,有时候不希望这样的事情发生,所以要对形参采用const参数 func(const int array[]) 1.1.19 指针做为函数的返回值 第页 保密 C讲义 1.1.20 指向函数的指针 指针可以指向变量,数组,也可以指向一个函数。 一个函数在编译的时候会分配一个入口地址,这个入口地址就是函数的指针,函数名称就代表函数的入口地址。 函数指针的定义方式:int (*p)(int);//定义了一个指向int func(int n)类型函数地址的指针。 1定义函数指针变量的形式为:函数返回类型(*指针变量名称)(参数列表) 2函数可以通过函数指针调用 3int( * P)()代表指向一个函数,但不是固定哪一个函数。 void man() { printf("抽烟\n"); printf("喝酒\n"); printf("打牌\n"); } void woman() { printf("化妆\n"); printf("逛街\n"); printf("网购\n"); } int main() { void(*p)(); int i = 0; scanf("%d", &i); if (i == 0) p = man; else p = woman; p(); return 0; } 第页 保密 C讲义 在回调函数和运行期动态绑定的时候大量的用到了指向函数的指针。 1.1.21 把指向函数的指针做为函数的参数 将函数指针做为另一个函数的参数称为回调函数 int max(int a, int b) { if (a > b) return a; else return b; } int add(int a, int b) { return a + b; } void func(int(*p)(int, int), int a, int b) { int res = p(a, b); printf("%d\n", res); } int main() { int i = 0; scanf("%d", &i); if (i == 0) func(max, 10, 20); else func(add, 10, 20); return 0; } 第页 保密 C讲义 1.1.22 memset,memcpy,memmove函数 这三个函数分别实现内存设置,内存拷贝和内存移动 使用memcpy的时候,一定要确保内存没有重叠区域。 #include 第页 保密 C讲义 第页 保密 C讲义 1.1.23 指针小结 定义 说明 int i 定义整形变量 int *p 定义一个指向int的指针变量 int a[10] 定义一个int数组 int *p[10] 定义一个指针数组,其中每个数组元素指向一个int型变量的地址 int (*p)[10] 定义一个数组指针,指向int [10]类型的指针变量 int func() 定义一个函数,返回值为int型 int *func() 定义一个函数,返回值为int *型 int (*p)() 定义一个指向函数的指针,函数的原型为无参数,返回值为int int **p 定义一个指向int的指针的指针,二级指针 第页 保密 C讲义 2 字符指针与字符串 2.1 指针和字符串 在C语言当中,大多数字符串操作其实就是指针操作。 char s[] = "hello world"; char *p = s; p[0] = 'a'; 2.2 通过指针访问字符串数组 char buf[100] = "hello world"; char *p = buf; //*(p + 5) = 'a'; //p[5] = 'b'; p += 5; *p = 'c'; p[3] = ' '; printf("buf = %s\n", buf); 2.3 函数的参数为char * void print_str(char *s)//如果参数是个字符串,那么就不需要包含第二个参数了 //因为字符串是明确的以'\0'结尾的,所以,在函数内部是有条件来做为循环终止依据的 { int i = 0; while (s[i]) { printf("%c", s[i++]); 第页 保密 C讲义 } } 2.4 指针数组做为main函数的形参 int main(int argc, char *argv[]); main函数是操作系统调用的,所以main函数的参数也是操作系统在调用时候自动填写的 argc代表命令行参数的数量 argv代表命令行的具体参数,是char *类型的 3 内存管理 3.1 作用域 一个C语言变量的作用域可以是代码块 作用域,函数作用域或者文件作用域。 代码块是{}之间的一段代码。 出现在{}之外的变量,就是全局变量 3.1.1 auto自动变量 一般情况下代码块内部定义的变量都是自动变量。当然也可以显示的使用aotu关键字 3.1.2 register寄存器变量 通常变量在内存当中,如果能把变量放到CPU的寄存器里面,代码执行效率会更高 register int I; 第页 保密 C讲义 3.1.3 代码块作用域的静态变量 静态变量是指内存位置在程序执行期间一直不改变的变量,一个代码块内部的静态变量只能被这个代码块内部访问。 3.1.4 代码块作用域外的静态变量 代码块之外的静态变量在程序执行期间一直存在,但只能被定义这个变量的文件访问 3.1.5 全局变量 全局变量的存储方式和静态变量相同,但可以被多个文件访问 3.1.6 外部变量与extern关键字 extern int I; 3.1.7 全局函数和静态函数 在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态 3.2 内存四区 第页 保密 C讲义 3.2.1 代码区 代码区code,程序被操作系统加载到内存的时候,所有的可执行代码都加载到代码区,也叫代码段,这块内存是不可以在运行期间修改的。 3.2.2 静态区 所有的全局变量以及程序中的静态变量都存储到静态区,比较如下两段代码的区别 int a = 0; int main() { static int b = 0; printf("%p, %p\n", &a, &b); return 0; } int a = 0; static int b = 0; int main() { printf("%p, %p\n", &a, &b); return 0; } 3.2.3 栈区 栈stack是一种先进后出的内存结构,所有的自动变量,函数的形参都是由编译器自动放出栈中,当一个自动变量超出其作用域时,自动从栈中弹出。 对于自动变量,什么时候入栈,什么时候出栈,是不需要程序控制的,由C语言编译器实现 栈不会很大,一般都是以K为单位的 第页 保密 C讲义 3.2.4 栈溢出 当栈空间以满,但还往栈内存压变量,这个就叫栈溢出 对于一个32位操作系统,最大管理管理4G内存,其中1G是给操作系统自己用的,剩下的3G都是给用户程序,一个用户程序理论上可以使用3G的内存空间, 3.2.5 堆区 堆heap和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的顺序。 堆是一个大容器,它的容量要远远大于栈,但是在C语言中,堆内存空间的申请和释放需要手动通过代码来完成。 第页 保密 C讲义 第页 保密 C讲义 3.3 堆的分配和释放 操作系统在管理内存的时候,最小单位不是字节,而是内存页。 3.3.1 malloc void * malloc(size_t _Size); malloc函数在堆中分配参数_Size指定大小的内存,单位:字节,函数返回void *指针。 3.3.2 free void free(void *p); free负责在堆中释放malloc分配的内存。参数p为malloc返回的堆中的内存地址 3.3.3 calloc: void * calloc(size_t _Count, size_t _Size); calloc与malloc类似,负责在堆中分配内存。 第页 保密 C讲义 第一个参数是所需内存单元数量,第二个参数是每个内存单元的大小(单位:字节),calloc自动将分配的内存置0 int *p = (int *)calloc(100, sizeof(int));//分配100个int 3.3.4 realloc 重新分配用malloc或者calloc函数在堆中分配内存空间的大小。 void * realloc(void *p, size_t _NewSize); 第一个参数 p为之前用malloc或者calloc分配的内存地址,_NewSize为重新分配内存的大小,单位:字节。 成功返回新分配的堆内存地址,失败返回NULL. 如果参数p等于NULL,那么realloc与malloc功能一致 4 结构体,联合体,枚举与typedef 4.1 结构体 4.1.1 定义结构体struct和初始化 struct man { char name[100]; int age; }; struct man m = { "tom", 12 }; struct man m = { .name = "tom", .age = 12 }; 4.1.2 访问结构体成员 .操作符 第页 保密 C讲义 4.1.3 结构体的内存对齐模式 编译器在编译一个结构的时候采用内存对齐模式 struct man{ char a; int b; }; 4.1.4 指定结构体元素的位字段 定义一个结构体的时候可以指定具体元素的位长 struct test{ char a : 2;//指定元素为2位长,不是2个字节长 }; 4.1.5 结构数组 struct man m[10] = { { "tom", 12 }, { "marry", 10 }, { "jack", 9 } }; 第页 保密 C讲义 4.1.6 嵌套结构 一个结构的成员还可以是另一个结构类型 struct names{ char first[100]; char last[100]; }; struct man{ struct names name; int age; }; struct man m = { { "wang", "wu" }, 20 }; 4.1.7 结构体的赋值 struct name a = b; 4.1.8 指向结构体的指针 –>操作符 4.1.9 指向结构体数组的指针 4.1.10 结构中的数组成员和指针成员 一个结构中可以有数组成员,也可以有指针成员,如果是指针成员结构体成员在初始化和赋值的时候就需要提前为指针成员分配内存。 struct man { char name[100]; int age; }; struct man { char *name; 第页 保密 C讲义 int age; }; 4.1.11 在堆中创建的结构体 如果结构体有指针类型成员,同时结构体在堆中创建,那么释放堆中的结构体之前需要提前释放结构体中的指针成员指向的内存。 struct man { char *name; int age; }; struct man *s = malloc(sizeof(struct man) * 2); s[0].name = malloc(10 * sizeof(char)); s[1].name = malloc(10 * sizeof(char)); 4.1.12 将结构作为函数参数 将结构作为函数参数 将结构指针作为函数参数 4.1.13 结构,还是指向结构的指针 在定义一个和结构有关的函数,到底是使用结构,还是结构的指针? 指针作为参数,只需要传递一个地址,所以代码效率高 结论就是当一个结构做为函数的参数时候,尽量使用指针,而不是使用结构变量,这样代码效率很高 void print_student(const struct student *s)//一般来讲,不要把结构变量做为函数的参数传递 { printf("name = %s, age = %d\n", s->name, s->age); } 第页 保密 C讲义 void set_student(struct student *s, const char *name, int age) { strcpy(s->name, name); s->age = age; } 4.2 联合体 联合union是一个能在同一个存储空间存储不同类型数据的类型。 联合体所占的内存长度等于其最长成员的长度,也有叫做共用体。 联合体虽然可以有多个成员,但同一时间只能存放其中一种。 union variant{ int ivalue; char cvalue; double dvalue; }; int main() { union variant var; var.cvalue = 12; printf("%d\n", var.ivalue); printf("%p, %p, %p\n", &(var.cvalue), &(var.ivalue), &(var.dvalue)); return 0; } 4.3 枚举类型 4.3.1 枚举定义 可以使用枚举(enumerated type)声明代表整数常量的符号名称,关键字enum创建一个新的枚举类型。 实际上,enum常量是int类型的。 第页 保密 C讲义 enum spectrum { red, yellow, green, blue, white, black }; enum spectrum color; color = black; if (color != red) 4.3.2 默认值 默认时,枚举列表中的常量被指定为0,1,2等 enum spectrum { red, yellow, green, blue, white, black }; printf("%d, %d\n", red, black); 指定值 可以指定枚举中具体元素的值 enum spectrum { red = 10, yellow = 20, green, blue, white, black }; printf("%d, %d\n", red, black); 4.4 typedef typedef是一种高级数据特性,它能使某一类型创建自己的名字 typedef unsigned char BYTE 1与#define不同,typedef仅限于数据类型,而不是能是表达式或具体的值 2typedef是编译器处理的,而不是预编译指令 3typedef比#define更灵活 直接看typedef好像没什么用处,使用BYTE定义一个unsigned char。使用typedef可以增加程序的可移植性。 4.5 通过typedef定义函数指针 typedef const char *(*SUBSTR)(const char *, const char *); const char *getsubstr(const char *src, const char *str) 第页 保密 C讲义 { return strstr(src, str); } const char *func(const char *(*s)(const char *, const char *), const char *src, const char *str) const char *(*p[3])(const char *, const char *); 5 文件操作 5.1 fopen r 以只读方式打开文件,该文件必须存在。 r+ 以可读写方式打开文件,该文件必须存在。 rb+ 读写打开一个二进制文件,允许读写数据,文件必须存在。 rw+ 读写打开一个文本文件,允许读和写。 w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。 w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。 a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留) a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。 (原来的EOF符不保留) 5.2 二进制和文本模式的区别 1.在windows系统中,文本模式下,文件以"\r\n"代表换行。若以文本模式打开文件,并用fputs等函数写入换行符"\n"时,函数会自动在"\n"前面加上"\r"。即实际写入文件的是"\r\n" 。 第页 保密 C讲义 2.在类Unix/Linux系统中文本模式下,文件以"\n"代表换行。所以Linux系统中在文本模式和二进制模式下并无区别。 5.3 fclose fclose关闭fopen打开的文件 5.4 getc和putc函数 int main() { FILE *fp = fopen("a.txt", "r"); char c; while ((c = getc(fp)) != EOF) { printf("%c", c); } fclose(fp); return 0; } int main() { FILE *fp = fopen("a.txt", "w"); const char *s = "hello world"; int i; for (i = 0; i < strlen(s); i++) { putc(s[i], fp); } fclose(fp); return 0; } 5.5 EOF与feof函数文件结尾 程序怎么才能知道是否已经到达文件结尾了呢?EOF代表文件结尾 如果已经是文件尾,feof函数返回true。 5.6 fprintf,fscanf,fgets,fputs函数 这些函数都是通过FILE *来对文件进行读写。 都是针对文本文件的行读写函数。 第页 保密 C讲义 5.7 stat函数 #include int stat(const char * _Filename, struct stat * _Stat) stat.st_size;//文件大小,单位:字节 函数的第一个参数代表文件名,第二个参数是struct stat结构。 得到文件的属性,包括文件建立时间,文件大小等信息。 5.8 fread和fwrite函数 size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ; size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream); 注意:这个函数以二进制形式对文件进行操作,不局限于文本文件 返回值:返回实际写入的数据块数目 5.9 fread与feof 注意以下两段代码的区别 while (!feof(p)) { fread(&buf, 1, sizeof(buf), p); } while (fread(&buf, 1, sizeof(buf), p)) 5.10 通过fwrite将结构保存到二进制文件中 做一个代码例子 第页 保密 C讲义 5.11 fseek函数 int fseek(FILE * _File, long _Offset, int _Origin); 函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset(指针偏移量)个字节的位置,函数返回0。如果执行失败则不改变stream指向的位置,函数返回一个非0值。 实验得出,超出文件末尾位置,还是返回0。往回偏移超出首位置,还是返回0,请小心使用。 第一个参数stream为文件指针 第二个参数offset为偏移量,正数表示正向偏移,负数表示负向偏移 第三个参数origin设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、 SEEK_END 或 SEEK_SET SEEK_SET: 文件开头 SEEK_CUR: 当前位置 SEEK_END: 文件结尾 fseek(fp, 3, SEEK_SET); 5.12 ftell函数 函数 ftell 用于得到文件位置指针当前位置相对于文件首的偏移字节数。在随机方式存取文件时,由于文件位置频繁的前后移动,程序不容易确定文件的当前位置。 long len = ftell(fp) 第页 保密 C讲义 5.13 fflush函数 fflush函数可以将缓冲区中任何未写入的数据写入文件中。 成功返回0,失败返回EOF。 int fflush(FILE * _File); 5.14 remove函数 remove函数删除指定文件 int remove(const char *_Filename); 参数Filename为指定的要删除的文件名,如果是windows下文件名与路径可以用反斜杠\分隔,也可以用斜杠/分隔 5.15 rename函数 rename函数将指定文件改名 第页 保密 C讲义 int rename(const char *_OldFilename,const char *_NewFilename); 参数oleFilename为指定的要修改的文件名,newfilename为修改后的文件名,如果是windows下文件名与路径可以用反斜杠\分隔,也可以用斜杠/分隔。 6 基础数据结构与算法 6.1 什么是数据结构 数据(data)是对客观事物符号表示,在计算机中是指所有能输入的计算机并被计算机程序处理的数据总称。 数据元素(data element)是数据的基本单位,在计算机中通常做为一个整体进行处理。 数据对象(data object)是性质相同的数据元素的集合,是数据的一个子集。 数据结构(data structure)是相互之间存在一种或多种特定关系的数据元素的集合。 数据类型(data type)是和数据结构密切关系的一个概念,在计算机语言中,每个变量、常量或者表达式都有一个所属的数据类型。 抽象数据类型(abstract data type ADT)是指一个数据模型以及定义在该模型上的一组操作,抽象数据类型的定义仅取决于它的一组逻辑性,与其在计算机内部如何表示以及实现无关。 6.2 什么是算法 算法是对特定问题求解的一种描述,它是指令的有限序列,其每一条指令表示一个或多个操作,算法还有以下特性: Ø 有穷性 一个算法必须总是在执行有限步骤后的结果,而且每一步都可以在有限时间内完成。 Ø 确定性 算法中每一条指令都有确切的含义,读者理解时不会产生二义性,在任何条件下,算法只有唯一的一条执行路径,即相同的输入只能得出相同的输出。 Ø 可行性 第页 保密 C讲义 一个算法是可行的,即算法中描述的操作都是可以通过已经实现的基本运算来实现的。 Ø 输入 一个算法有零个或者多个输入,这些输入取自与某个特定对象的集合。 Ø 输出 一个算法有一个或多个输出,这些输出是和输入有某些特定关系的量。 6.3 排序 6.3.1 冒泡排序 冒泡排序首先将一个记录的关键字和第二个记录的关键字进行比较,如果为逆序(elem[1] > elem[2]),则两个记录交换之,然后比较第二个记录和第三个记录的关键字,以此类推,直到第n-1个记录和第n个记录的关键字进行过比较为止。 上述过程称作第一次冒泡排序,其结果是将关键字最大的记录被安排到最后一个记录的位置上。然后进行第二次冒泡排序,对前n-1个记录进行同样操作,其结果是使关键字第二大记录被安置到第n-1位置上。直到将所有记录都完成冒泡排序为止。 6.3.2 选择排序 选择排序是每一次在n – I + 1(i=1,2,…n)个记录中选取关键字,最小的记录作为有序序列中第i个记录。 通过n-i次关键字间的比较,从n-i+1个记录中选取出关键字最小的记录,并 和第i(1<=i<=n)个记录交换之。 6.4 查找 6.4.1 顺序查找 顺序查找的过程为:从表的最后一个记录开始,逐个进行记录的关键字和给定值比较,如果某个记录的关键字与给定值相等,则查找成功,反之则表明表中没有所查找记录,查找失败。 第页 保密 C讲义 6.4.2 二分查找 在一个已经排序的顺序表中查找,可以使用二分查找来实现。 二分查找的过程是:先确定待查记录所在的范围(区间),然后逐步缩小查找范围,直到找到或者找不到该记录为止。 假设指针low和high分别指示待查找的范围下届和上届,指针mid指示区间的中间值,即 mid=(low + high) / 2。 6.5 链表 6.5.1 单向链表定义 对于数组,逻辑关系上相邻的两个元素的物理位置也是相邻的,这种结构的优点是可以随机存储任意位置的元素,但缺点是如果从数组中间删除或插入元素时候,需要大量移动元素,效率不高。 链式存储结构的特点,元素的存储单元可以是连续的,也可以是不连续的,因此为了表示每个元素a,与其接后的元素a+1之间的关系,对于元素a,除了存储其本身信息外,还需要存储一个指示其接后元素的位置。这两部分数据成为结点(node)。 一个结点中存储的数据元素被成为数据域。存储接后存储位置的域叫做指针域。n个结点(ai(1<=i<=n)的存储映像链接成一个链表。 整个链表必须从头结点开始进行,头结点的指针指向下一个结点的位置,最后一个结点的指针指向NULL。 在链表中,通过指向接后结点位置的指针实现将链表中每个结点“链”到一起。链表中第一个结点称之为头结点。 第页 保密 C讲义 6.5.2 单向链表数据结构定义 struct list { int data;//链表数据域 struct list *next;//链表指针域 }; 6.5.3 单向链表的实现 struct list *create_list()//建立一个节点 void traverse(struct list *ls)//循环遍历链表 struct list *insert_list(struct list *ls, int n, int data)//在指定位置插入元素 第页 保密 C讲义 int delete_list(struct list *ls, int n)//删除指定位置元素 int count_list(struct list *ls)//返回链表元素个数 void clear_list(struct list *ls)//清空链表,只保留首节点 int empty_list(struct list *ls)//返回链表是否为空 struct list *locale_list(struct list *ls, int n)//返回链表指定位置的节点 struct list *elem_locale(struct list *ls, int data)//返回数据域等于data的节点 int elem_pos(struct list *ls, int data)//返回数据域等于data的节点位置 struct list *last_list(struct list *ls)//得到链表最后一个节点 void merge_list(struct list *ls1, struct list *ls2)//合并两个链表,结果放入ls1中 void reverse(struct list *ls)//链表逆置 删除元素操作 第页 保密 C讲义 逆置操作 1. 判断首节点的next是否为NULL; 2. 判断首节点next的next是否为空,如果为空证明链表除首节点之外只有一个节点,所以不需要逆置; 3. 定义一个指针last,指向首节点的next域,因为逆置之后,该域为链表尾节点; 4. 定义三个指针,分别代表前一个节点,当前节点,下一个节点; 5. 前节点指向链表首节点; 6. 当前节点指向链表首节点的next域; 7. 下一个节点为NULL; 8. 循环条件判断当前节点是否为NULL,如果为NULL退出循环; a) 下一个节点指向当前节点的下一个节点; b) 当前节点的下一个节点指向前一个节点; c) 前一个节点指向当前节点; d) 当前节点指向下一个节点; 9. 循环完成; 10. 设置last节点的next为NULL; 11. 设置链表首节点的next为前一个节点。 第页 保密

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档