C语言学习笔记

光光

发布日期: 2019-07-27 10:48:37 浏览量: 95
评分:
star star star star star star star star star star
*转载请注明来自write-bug.com

1 愉快的开始hello world

1.1 include头文件包含

include是要告诉编译器,包含一个头文件;

在C语言当中,任何库函数调用都需要提前包含头文件;

  • <头文件>,代表让C语言编译器去系统目录下寻找相关的头文件;

  • “头文件”, 代表让C语言编译器去用户当前目录下寻找相关的头文件;

如果是使用了一个C语言库函数需要的头文件,那么一定是#include < >;

如果是使用了一个自定义的h文件,那么一定是#include“ ”。

1.2 main函数

main函数是C语言中的主函数,一个C语言的程序必须有一个主函数,也只能有一个主函数。

  1. int main() //一个函数在C语言里面,如果只是空(),代表这个函数可以有参数,也可以没有参数
  2. int main(void) //如果是(void),就是明确的表达没有任何参数

1.3 注释

  • // 单行注释,代表注释,就是一个文字说明,没有实质的意义,单行注释是C++语言的注释方法;

  • / / 多行注释,多行注释是标准C语言的注释方法。

1.4 {}括号,程序题和代码块

C语言所有的函数的代码都是在{}里包着的

1.5 声明

  1. int a;

声明一个变量名字叫a,对于C语言,变量的名称是可以自定义的

1.6 C语言自定义名字的要求

可以使用大小写字母,下划线,数字,但第一个字母必须是字母或者下划线(Linux命名法、匈牙利命名法)

  • 字母区分大小写

  • 不能用C语言的关键字作为变量名称

  • 每一行,必须是;结尾

1.7 printf函数

printf是向标准输出设备输出字符串的
如果要输出一个字符串:例如:printf(“hello world”);
如果要输出一个整数,例如:printf(“%d”,整数);
Printf(“\n”);会输出一个回车换行

1.8 return语句

一个函数遇到return语句就终止了,return是C语言的关键字

1.9 System系统调用

System库函数的功能是执行操作系统的命令或者运行指定的程序

2 C语言中的数据类型

2.1 常量

常量就是在程序中不可变化的量,常量在定义的时候必须给一个初值。

2.1.1 #define

  1. #define MAX 10 //定义一个宏常量

2.1.2 const

Const int a=20; //定义一个const常量

2.2 字符串常量

  1. #define STRING hello world\n

对于#define类型的常量,C语言的习惯是常量名称为大写,但对于普通const常量以及变量,一般为小写结合大写的方式

2.3 二进制数、位、字节与字

我们习惯于十进制的数:10,12等

  • 一个位只能表示0,或者1两种状态,简称bit

  • 一个字节为8个二进制,称为8位,简称BYTE,8个比特是一个字节

  • 一个字位2个字节,简称WORD

  • 两个字为双字,简称DWORD

2.4 八进制

八进制为以8为基数的数制系统,C语言当中表示八进制0

2.5 十六进制

  • 十六进制值16为基数的数制系统,C语言当中用0x表示十六进制

  • 十进制转化为八进制,用十进制数作为被除数,8作为除数,取商数和余数,直到商数为0的时候,将余数倒过来就是转化后的结果

  • 十进制转化为十六进制,用十进制数作为被除数,16作为除数,取商数和余数,直到商数为0的时候,将余数倒过来就是转化后的结果

2.6 原码

将最高位作为符号位(0代表正,1代表负),其余各位代表数值本身的绝对值

2.7 反码

  • 一个数如果值为正,那么反码和原码相同

  • 一个数如果为负,那么符号位为1,其他各位与原码相反

2.8 补码

  • 正数:原码、反码和补码都相同

  • 负数:最高位为1,其余各位原码取反,最后对整个数+1

  • 补码符号位不动,其他位求反,最后整个数+1,得到原码

  • 用补码进行运算,减法可以通过加法实现

2.9 sizeof关键字

  • Sizeof是C语言关键字,功能是求指定数据类型在内存中的大小,单位:字节,sizeof(int);

  • Sizeof与size_f类型

2.10 int类型

2.10.1 int常量,变量

int就是32位的一个二进制整数,在内存当中占据4个字节的空间

2.10.2 printf输出int值

  • %d,输出一个有符号的10进制整数

  • %u,代表输出一个无符号的10进制整数

2.10.3 printf输出八进制和十六进制

  • %o,代表输出八进制整数

  • %x,代表输出16进制整数

  • %X,用大写字母方式输出16进制数

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位,所以计算long long类型的数据,效率很低

2.10.5 整数溢出

计算一个整数的时候超过整数能够容纳的最大单位后,整数会溢出,溢出的结果是高位舍弃。当一个小的整数赋值给大的整数,符号位不会丢失,会继承。

2.10.6 大端对齐小端对齐

对于ARM,intel这种x86构架的复杂指令CPU,整数在内存中是倒着存放的,低地址放地位,高地址放高位,小端对齐;

但对于unix服务器的CPU,更多是采用大端对齐的方式存放整数。

2.11 char类型

2.11.1 char常量,变量

  • Char c; //定义一个char变量

  • ‘a’,char的常量

  • char的本质就是一个整数,一个只有1个字节大小的整数

2.11.2 printf输出char

%c意思是输出一个字符,而不是一个整数

2.11.3 不可打印char转义符

  1. \a 警报
  2. \b 退格
  3. \n 换行
  4. \r 回车
  5. \t 制表符
  6. \\ 斜杠
  7. \’ 单引号
  8. \” 双引号
  9. \?问号

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是输出一个double

  • %lf输出一个long double

2.13 类型限定

2.13.1 const

Const是代表一个不能改变值的常量

2.13.2 volatile

代表变量是一个可能被CPU指令之外的地方改变的,编译器就不会针对这个变量去优化目标代码。

2.13.3 register

变量在CPU寄存器里面,而不是在内存里面,但register是建议型的指令,而不是命令型的指令。

3 字符串格式化输出和输入

3.1 字符串在计算机内部的存储方式

字符串是内存中一段连续的char空间,以‘\0’结尾

“”是C语言表达字符串的方式

3.2 printf函数,putchar函数

Printf格式字符

  1. 字符 对应数据类型 含义
  2. d int 接受整数值并将它表示为有符号的十进制整数
  3. hd short int 短整数
  4. hu unsigned shor int 无符号短整数
  5. o unsigned int 无符号8进制整数
  6. u unsigned int 无符号10进制整数
  7. x/X unsigned int 无符号16进制整数,x对应的是abcdfX对应的是ABCDEF
  8. f floatdouble 单精度浮点数或双精度浮点数
  9. e/E double 科学计数法表示的数,此处“e”的大小写代表在输出是用“e”的大小写
  10. c char 字符型,可以把输入的数字按照ASC11码相应转换为对应的字符
  11. s/S char */wchar_t * 字符串,输出字符串中的字符直至字符串的空字符(字符串以‘\0’结尾,这个‘\0’即空字符)
  12. p void 16进制形式输出指针
  13. % % 输出一个百分号

Printf附加格式

  1. 字符 含义
  2. | 附加在d,u,x,o前面,表示长整数
  3. - 左对齐
  4. m(代表一个整数) 数据最小宽度
  5. 0 将输出的前面补上0,直到占满指定列宽为止(不可以搭 配使用“-”)
  6. N(代表一个整数) 宽度至少为n位,不够以空格填充。

Putchar是显式一个字符的函数

  1. long l = 100;
  2. Printf(“-6ld”,l);

3.3 scanf函数与getchar函数

Scanf通过键盘读取用户输入,放入变量中,记得参数一定是变量的地址(&)

  1. // #define _CRT_SECURE_NO_WARNINGS
  2. #pragma warning(disable:4996)
  3. Int a=0;
  4. Scanf(“%d”,&a);//一定要用&取变量的地址
  5. getchar得到用户键盘输入的字符
  6. char a = 0;
  7. a = getchar();//得到用户键盘的按键
  8. printf(“%c”,a);
  9. printf(“please input a:”);
  10. scanf(“%d”,&a);
  11. getchar(); //通过getchar这个函数将之前输入a时候用户按的回车键先收到

4 运算符表达式和语句

4.1 基本运算符

4.1.1 =

  • 数据对象:泛指数据在内存的存储区域

  • 左值:表示可以被更改的数据对象

  • 右值:能赋给左值的量

4.1.2 +

4.1.3 –

4.1.4 *

4.1.5 /

4.1.6 %

取余数

4.1.7 + =

加等于

  • a+=5;

4.1.8 - =

减等于

4.1.9 * =

乘等于

4.1.10 /=

除等于

4.1.11 %=

取余等于

4.1.12 ++

自加1

  • i++先计算表达式的值,然后再++

  • ++i是先++,再计算表达式的值

4.1.13 —

自减1

4.1.14 逗号运算符

  1. int a = 2;
  2. int b = 3;
  3. int c = 4;
  4. int d = 5;
  5. int I = (a = b, c + d);

逗号表达式先求逗号左边的值,然后求右边的值,整个语句的值是逗号右边的值。

4.1.15 运算符优先级

  1. 优先级 运算符 结合性
  2. 1 ++(后缀),--(后缀),()(调试函数), 从左到右
  3. {}(语句块),->
  4. 2 ++(前缀),--(前缀),+(前缀),-(前缀), 从右到左
  5. ! (前缀),~(前缀),sizeof,*(取指针值),
  6. &(取地址),(type)(类型转化)
  7. 3 *,/,% 从左到右
  8. 4 +,- 从左到右
  9. 5 <<,>> 从左到右
  10. 6 < ,>,<=,>= 从左到右
  11. 7 = =,!= 从左到右
  12. 8 & 从左到右
  13. 9 ^ 从左到右
  14. 10 | 从左到右
  15. 11 && 从左到右
  16. 12 || 从左到右
  17. 13 ? 从右到左
  18. 14 =,*=,%=,+=,-=,<<=,>>=,&=, 从右到左
  19. |=,^=
  20. 15 ,(逗号运算符) 从左到右

4.2 复合语句

{}代码块

  1. for(i = 0; i < 3; i++) //循环语句,代表复合语句内部的代码要执行3次
  2. {
  3. printf(“hello\n”);
  4. }

4.3 空语句

只有一个;号的语句就是空语句,空语句在C语言里面是合法的,并且是在某些场合必用的

4.4 类型转化

  1. double f = (double) 3 / 2; //(double)3意思是将整数3强制转化为double型 ()为强制类型转化运算符
  2. double f = 3 /2; //C语言两个整数相除的结果自动转化为一个整数
  3. double f = 3.0 / 2;

5 条件分支语句

5.1 关系运算符

在C语言中0代表false,非0代表真

5.1.1 <

小于

5.1.2 <=

小于等于

5.1.3 >

大于

5.1.4 >=

大于等于

5.1.5 = =

等于

5.1.6 !=

不等于

5.2 关系运算符优先级

前四种相同,后两种相同,前四种高于后两种优先级

5.3 逻辑运算符

5.3.1 &&

当运算符左右都是真的时候,那么整个表达式的结果为真;
只有左右有一个值为假,那么整个表达式的结果为假。

5.3.2 | |

当运算符左右只要有一个值是真的时候,那么整个表达式的结果为真;
除非左右两个值都是假,那么整个表达式的结果为假。

5.3.3 !

当值为真的时候,表达式为假;
当值为假的时候,表达式为真。

5.4 if

单分支

  1. if(条件)
  2. {
  3. //复合语句
  4. }

当条件是真的时候,复合语句才能被执行,如果条件为假的时候,复合语句不执行

5.5 if else

双分支

  1. if(条件)
  2. {
  3. 复合语句1
  4. }
  5. else
  6. {
  7. 复合语句2
  8. }

如果条件为真,那么执行复合语句1,否则执行复合语句2

5.6 if else if

多重if

  1. if(条件1)
  2. {
  3. 复合语句1
  4. }
  5. else if(条件2
  6. {
  7. 复合语句2
  8. }
  9. else if(条件3
  10. {
  11. 复合语句3
  12. }
  13. else
  14. {
  15. 复合语句4
  16. }

当有多个else的时候,else总是和上方最近的那个if语句配对

5.7 switch与break,default

多重选择

  1. switch(i)
  2. {
  3. case 0:
  4. break; //跳出switch的复合语句块
  5. ……
  6. default: //如果所有条件都不满足,那么执行default语句
  7. }

什么时候用if,什么时候用switch?
当条件很复杂,一个条件有&&,||,!存在,那么用if语句
如果条件很简单,但分支很多,那么适合用switch

5.8 条件运算符?

一个求绝对值的例子

  1. int i = -8;
  2. int x = (i < 0) ? i : i;

先求?左边的条件,如果条件为真,那么等于:左边的值,否则等于:右边的值
一个求最大值的例子

  1. int c = ( a > b ) ? a : b ;

5.9 goto语句与标号

无条件跳转goto

  1. goto end; //无条件的跳转到一个标号去执行
  2. ……
  3. end//标号

不建议使用goto语句,goto语句会使你的程序可读性变得很差

6 循环语句

6.1 while

while(条件),如果条件为真,循环继续,条件为假,循环结束

  1. while(1) //是死循环的写法
  2. {
  3. 复合语句;
  4. }

6.2 continue

循环遇到continue语句,不再执行continue下面代码,而是直接返回到循环起始语句处继续执行循环

6.3 break

循环遇到break语句,立刻中断循环,循环结束

6.4 do while

  1. do
  2. {
  3. 复合语句;
  4. }while(条件);

对于do while来讲,循环的复合语句至少可以被执行一次
对于while来讲,有可能复合语句一次执行的机会都没有

6.5 for

  • 先执行i=0,对于一个for循环,第一步只执行一次;

  • 判断i是否小于10,如果i小于10,那么循环继续,否则循环中断

  • i++,第一次执行for的时候,不执行i++

  1. for(int i = 0 ; i < 10 ; i++)
  2. {
  3. 复合语句;
  4. }

等同于:

  1. int = 0;
  2. while(i < 10)
  3. {
  4. i++;
  5. }

6.6 循环嵌套

打印三角形

  1. *
  2. ***
  3. *****
  4. *******
  5. *********
  1. int main()
  2. {
  3. int i,j;
  4. for(i=1;i<7;i++)
  5. {
  6. for(j=1;j<7-i;j++)
  7. {
  8. printf(“ ”);
  9. }
  10. for(j=0;j<(i*2-1);j++)
  11. {
  12. printf(“*”);
  13. }
  14. printf(“\n”);
  15. }
  16. return 0;
  17. }

7 数组

数组的本质就是可以一次定义多个类型相同的变量,同时一个数组中所有的元素在内存中都是顺序存放的。
但要记得在C语言中如果定义了如下数组:

  1. Char s[100] ;//s[0] – s[99],切记没有s[100]这个元素,而且C语言编译器不会帮你检查数组的下标是否有效。
  2. Char array[2][3][4] = {};//原则,数组维数越多,代码的可读性就越差,所以要尽可能的用维数少的数组

7.1 一维数组定义与使用

  1. int array [10]; //定义一个一维数组,名字叫array,一共有10个元素,每个元素都是int类型的
  2. array[0] = 20 ;
  3. array[1] = 30 ;
  4. array[9] = 90 ;
  5. //array[10] = 100 ; //错误,没有array[10]这个元素

7.2 数组在内存的存储的方式

数组在内存中就是一段连续的空间,每个元素的类型是一样的

7.3 一维数组初始化

  1. int array[10] = {1,2,3,4,5,6,7,8,9,10} ;//定义数组的同时为数组的成员初始化值
  2. int array[10] = {3,4,5} ;//将数组的前三个元素赋值,其余元素置为0
  3. int array[10] = {0} ;//将数组所有的元素都置为0
  4. int i;
  5. for (i = 0; i < 10; i++)
  6. {
  7. array[i] = 0 ;//通过循环遍历数组的每个元素,将元素的值置为0
  8. //scanf(“%d”,&array[i]);
  9. }

求数组中最大元素的值

  1. int main()
  2. {
  3. int array[10] = {32,5,67,98,12,54,8,78,457,10};
  4. int max = 0;
  5. int i;
  6. for (i = 0; i < 10; i++) //想找最大的值,一定要把数组先遍历一遍
  7. {
  8. if(max < array[i])
  9. max = array[i];
  10. }
  11. printf(“max = %d\n”,max);
  12. return 0;
  13. }

求数组中最小元素的值,和最小值对应的数组下标

  1. int main()
  2. {
  3. int array[10] = {32,5,67,98,12,54,8,78,457,10};
  4. int min = array[0];
  5. int index = 0; //在没有遍历数组之前,默认数组的第0号元素就是最小的元素
  6. int i;
  7. for (i = 1; i < 10; i++) //想找最小的值,一定要把数组先遍历一遍
  8. {
  9. if(min > array[i])
  10. {
  11. index = i;
  12. min = array[i];
  13. }
  14. }
  15. printf(“min = %d index = %d\n”, min , index);
  16. return 0;
  17. }

求数组中所有元素的和

  1. int main()
  2. {
  3. int array[10] = {1,2,3,4,5,6,7,8,9,10};
  4. int i;
  5. int sum = 0;//存放数组和的变量
  6. for (i = 0; i < 10; i++) //想找最大的值,一定要把数组先遍历一遍
  7. {
  8. sum += array[i];
  9. }
  10. printf(“sum = %d\n”,sum);
  11. return 0;
  12. }

将数组元素逆置

  1. int main()
  2. {
  3. int array[10] = {32,5,67,98,12,54,8,78,457,10};
  4. /*
  5. int tmp = array[1]; //中间变量实现两个值的互换
  6. array[1] = array[0];
  7. array[0] = tmp;
  8. */
  9. int min = 0; //数组最小下标
  10. int max = 9; //数组最大下标
  11. while (min < max) //两头往中间堵
  12. {
  13. int tmp = array[min];
  14. array[min] = array[max];
  15. array[max] = tmp;
  16. min++;
  17. max--;
  18. }
  19. printf(“max = %d min = %d\n”, max , min);
  20. return 0;
  21. }

求100到999之间的水仙花数

  1. int main()
  2. {
  3. int i;
  4. for(i = 100 ;i < 1000 ;i++)
  5. {
  6. Int i1=i%10; //
  7. Int i2=i/10%10; //
  8. Int i3=i/100; //
  9. If((i1*i1*i1+i2*i2*i2+i3*i3*i3) = = i)
  10. Printf(“%d\n”,i);
  11. }
  12. return 0;
  13. }

求一个int数组中,所有奇数元素的和

  1. int main()
  2. {
  3. int array[10] = {1,2,3,4,5,6,7,8,9,10};
  4. int i;
  5. int sum = 0;
  6. for (i = 0; i < 10; i++)
  7. {
  8. if((array[i]%2) = = 1)
  9. {
  10. sum += array[i];
  11. }
  12. }
  13. printf(“sum = %d\n”,sum);
  14. return 0;
  15. }

求从3到100之间所有素数打印出来 3 5 7 11 13 17 ……

  1. int main()
  2. {
  3. int i; //素数是除了1和自己以外,不能被其他整数整除的整数
  4. for (i = 3; i < 100; i++)
  5. {
  6. int j;
  7. int ststus = 0;
  8. for(j =2 ;j < i ; j++) //判断i是否为素数
  9. {
  10. if((i %j) = = 0)
  11. {
  12. status = 1;
  13. break;
  14. }
  15. }
  16. if(status= = 0) //代表这是个素数
  17. {
  18. printf(“%d\n”,i);
  19. }
  20. }
  21. return 0;
  22. }

7.4 二维数组定义与使用

  1. int array[2][3];//定义了一个二维数组,有两个array[3]
  2. int array[2][3] = { {1,2,3},{4,5,6} };//定义一个二维数组的同时初始化成员

7.5 二维数组初始化

  1. int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
  2. int array[2][3] = {0};//将二维数组中每个元素的值都初始化为0
  3. int array[2][3] = { {1,2,3},{4,5,6} };
  4. int i,j;
  5. for(j=0;j<3;j++)
  6. {
  7. int sum=0;
  8. for(i=0;i<2;i++)
  9. {
  10. sum += array[i][j];
  11. }
  12. printf("%d\n",sum);//打印列的和
  13. }
  14. #include<stdio.h>
  15. int main()//冒泡排序
  16. {
  17. int array[10] = {34,14,8,54,23,89,56,4,45,22};
  18. int i;
  19. int j;
  20. for(i = 0;i < 10; i++)
  21. {
  22. for(j = 1; j < 10 - i; j++)
  23. {
  24. if(array[j-1] > array[j])//如果前面的元素大于后面的元素,那么就让这两个元素交换位置
  25. {
  26. int tmp = array[j];
  27. array[j] = array[j - 1];
  28. array[j-1] = tmp;
  29. }
  30. }
  31. }
  32. for(i=0;i<10;i++)
  33. {
  34. printf("array[%d]=%d\n",i,array[i]);
  35. }
  36. return 0;
  37. }

8 字符串与字符数组

字符串一定是在内存中以0结尾的一个char数组。

8.1 字符数组定义

  1. char array[100];

8.2 字符数组初始化

  1. char array[100] = {'a','b','c','d'};
  2. char array[100] = abcd”;
  3. char array[100] = {0};
  4. char array[] = abcd”;

8.3 字符数组使用

  1. #include<stdio.h>
  2. //字符串倒序
  3. int main()
  4. {
  5. char buf[100] = "hello world";
  6. int len = 0;
  7. while (buf[len++]);
  8. len--;
  9. int i = 0;
  10. int j = len -1;
  11. while (i < j)
  12. {
  13. char tmp = buf[i];
  14. buf[i] = buf[j];
  15. buf[j] = tmp;
  16. i ++;
  17. j --;
  18. }
  19. printf("%s\n",buf);
  20. return 0;
  21. }

ASCII一个字节存放一个字符。
GBK用两个字节存放一个汉字。

8.4 随机数产生函数rand与srand

头文件stdilb.h
rand是伪随机数产生器,每次调用rand产生的随机数是一样的。
如果调用rand之前先调用srand就出现任意的随机数。
只要能保证每次调用srand函数的时候,参数的值是不同的,那么rand函数就一定会产生不同的随机数。

  1. Srand() //随机数种子发生器
  2. Rand() //随机数产生器
  3. #include<stdio.h>
  4. #include<time.h>
  5. #include<stdlib.h>
  6. int main()
  7. {
  8. int t = (int)time(NULL);
  9. srand(t);
  10. for(int i=0; i<10; i++)
  11. {
  12. printf("%d\n",rand());
  13. }
  14. return 0;
  15. }

8.5 用scanf输入字符串

“%s”的作用就是输入一个字符串的,scanf是以回车键作为输入完成标志的,但回车键本身并不会作为字符串的一部分
如果scanf参数中的数组长度小于用户在键盘输入的长度,那么scanf就会缓冲区溢出,导致程序崩溃。

8.6 字符串的结束标志

scanf将回车、空格都认为是字符串输入结束标志。

8.7 字符串处理函数

8.7.1 gets

gets(s);
Gets认为回车是输入结束的标志,空格不是输入结束的标志,所以用gets这个函数就可以实现输入带空格的字符串,但是gets和scanf一样存在缓冲区溢出的问题。

Gets不能用类似“%s”或者“%d”之类的字符转义,只能接受字符串的输入。

8.7.2 fgets函数

gets函数不检查预留缓冲区是否能够容纳用户实际输入的数据。多出来的字符会导致内存溢出,fgets函数改进了这个问题。
由于fgets函数是为读取文件设计的,所以读取键盘时没有gets那么方便。

  1. Char s[100] = {0};
  2. fgets(s ,sizeof(s) ,stdin);//第一个参数是char的数组,第二个参数是数组的大小,单位:字节,第三个参数stdin代表标准输入的意思

fgets是安全的,不存在缓冲区溢出的问题,调用fgets的时候,只要能保证第二个参数小于等于数组实际的大小,那么就可以避免缓冲区溢出的。

8.7.3 puts函数

Puts函数打印字符串,与printf不同,puts会在最后自动添加一个’\n’

  1. Char s[] = hello world”;
  2. Puts(s);

8.7.4 fputs函数

fputs是puts的文本操作版本

  1. char s[] = hello world”;
  2. fputs(s,stdout);

8.7.5 strlen,字符串长度

  1. size_t strlen(const char * _str);

返回不包含字符串结尾’\n’的字符串长度

  1. Char s[100] = hello world”;
  2. Int len = strlen(s);
  3. Printf(“len = %d\n”,len);

8.7.6 strcat,字符串追加

  1. size_t strcat(char * _str1,const char * _str2);

将参数_str2追加到_str1后尾

  1. Char s1[100] = fsfgg”;
  2. Strcat(s,s1);

Strcat也存在缓冲区溢出的问题

8.7.7 strncat,字符串有限追加

  1. size_t strncat(char * _str1,const char * _str2,size_t len);
  2. Strcat(s,s1,3); //合并的时候可以限制追加多少个字符

8.7.8 strcmp,字符串比较

  1. int strcmp(const char * _str1,const char * _str2);

比较两个字符串是否相等,相等返回0,不相等返回非0

  1. If(strcmp(s1,s2)) = = 0)
  2. {
  3. Printf(“相同\n”);
  4. }

8.7.9 strncmp,字符串有限比较

  1. If(strncmp(s1,s2,5) = = 0) //strncmp的意思是只比较指定数量的字符

8.7.10 strcpy字符串拷贝

  1. Char *strcpy(char * _str1,const char * _str2);

将参数_str2拷贝到参数_str1中

  1. Strcpy(s1,s2);

8.7.11 strncpy字符串有限拷贝

  1. Strncpy(s1,s2,3);

8.7.12 sprintf格式化字符串

和printf函数功能类似,printf函数将格式化结果输出到屏幕,sprintf将格式化结果输出到字符串。

  1. int i = 200;
  2. char s[100] = {0};
  3. sprintf(s, I = %d”,i);

8.7.13 Sscanf函数

Sscanf类似于scanf函数,scanf从键盘读取用户输入,scanf从指定格式化字符串读取输入。

8.7.14 strchr查找字符

  1. Char * strchr(char * _Str, int _Ch);

在参数_str中查找参数_ch指定字符,找到返回字符_ch在_str中所在位置,没有找到返回NULL;

8.7.15 strstr查找子串

  1. Char * strstr(char * _Str,const char * _SubStr);

在参数_str中查找参数_SubStr指定子串,找到返回子串在_Str中所在位置,没有找到返回NULL;

8.7.16 strtok分割字符串

字符在第一次调用时strtok()必需给予参数s字符串,往后的调用则将参数s设置成NULL每次调用成功则返回指向被分割出片段的指针。

如果strtok没有找到指定的分割符号,那么返回NULL

  1. Char buf [] = abcd@efg@h”;
  2. Char *p = strtok(buf,”@”);
  3. While(p)
  4. {
  5. Printf(“%s\n”,p);
  6. P = strtok(NULL,”@”);
  7. }

8.7.17 atoi转化为int

  1. int i1 = atoi(a); //将字符串转化为一个整数

需要包含头文件stdlib.h

8.7.18 atof转化为float

8.7.19 atol转化为long

  1. #include <string.h> //计算字符串
  2. int calc_string(const char *s)
  3. {
  4. char buf1[100] = {0};
  5. char oper1 = 0;
  6. char buf2[100] = {0};
  7. int len = strlen(s);//得到字符串的长度
  8. int i;
  9. int start;
  10. for(i=0;i<len;i++)
  11. {
  12. if(s[i] == '+'|| s[i] == '-' || s[i] == '*' || s[i] == '/')
  13. {
  14. strncpy(buf1, s, i);
  15. oper1 = s[i];
  16. break;
  17. }
  18. }
  19. start = i + 1;
  20. for(;i<len;i++)
  21. {
  22. if(s[i] == '=')
  23. {
  24. strncpy(buf2,&s[start],i-start);
  25. }
  26. }
  27. printf("buf1 = %s,oper1 = %c,buf2 = %s\n",buf1,oper1,buf2);
  28. switch(oper1)
  29. {
  30. case '+':
  31. return atoi(buf1) + atoi(buf2);
  32. case '-':
  33. return atoi(buf1) - atoi(buf2);
  34. case '*':
  35. return atoi(buf1) * atoi(buf2);
  36. case '/':
  37. {
  38. int a = atoi(buf2);
  39. if(a)
  40. return atoi(buf1) / atoi(buf2);
  41. else
  42. return 0;
  43. }
  44. }
  45. }
  46. int main()
  47. {
  48. const char *s = "32 + 56 =";
  49. printf("%d\n",calc_string(s));
  50. return 0;
  51. }

9 函数

9.1 函数的原型和调用

在使用函数前必须定义或者声明函数

9.2 函数的形参与实参

在调用函数的时候,函数大多数都有参数,主调函数和被调用函数之间需要传递数据。
在定义函数时函数名后面括弧中的变量名称为“形式参数”,简称形参。在调用函数时,函数名后面括号中的变量或表达式称为“实际参数”,简称实参。

  • 形参在未出现函数调用时,他们并不占用内存单元,只有在发生函数调用的时候形参才被分配内存,函数调用完成后,形参所占的内存被释放;

  • 实参可以是变量,常量或者表达式;

  • 在定义函数时,一定要指定形参的数据类型;

  • 形参与实参的数据类型一定要可兼容;

  • 在C语言中,实参与形参的数据传递是“值传递”,即单向传递,只由实参传递给形参,而不能由形参传递给实参。

如果函数的参数是个数组,那么是可以通过形参修改实参的值的

9.3 函数的返回类型与返回值

  • 函数的返回值通过函数中的return获得,如果函数的返回值为void可以不需要return语句;

  • 函数return语句中的返回值数据类型应该与函数定义时相同;

  • 如果函数中没有return语句,那么函数将返回一个不确定的值。

如果C语言一个函数没有明确的标明函数的返回类型,那么函数的返回类型就是int;
如果一个函数没有返回值,那么函数的返回类型是void;

9.4 main函数与exit函数与函数的return语句

exit(0); //在子函数中调用exit同样代表程序终止,但在子函数中调用return只是子函数终止,程序正常执行。
exit是C语言的库函数,调用exit的结果就是程序终止,在main函数中调用exit与调用return是一样的;

main函数return代表程序终止。

9.5 多个源代码文件程序的编译

9.5.1 头文件的使用

如果把main函数放在第一个文件中,而把自定义函数放在第二个文件中,那么就需要在第一个文件中声明函数原型。
如果把函数原型包含在一个头文件里,那么就不必每次使用函数的时候都声明其原型了。把函数声明放入头文件是很好的习惯。

9.5.2 #include与#define的意义

  1. #include就是简单的文件内容替换
  2. #define就是简单的文件替换而已

9.5.3 #ifndef 与#endif

在头文件.h中,

  1. #ifndef _宏名_
  2. #define _宏名_//具体宏的名字是自定义的
  3. //函数的声明
  4. #endif
  5. 作用:防止多次include的同一个头文件的时候,重复预编译头文件内容
  6. 防止头文件被重复包含
  7. #ifndef的意思就是条件预编译,如果#ifndef后面的条件成立,那么就预编译从#ifndef开始到#endif之间的代码,否则不会去预编译这段代码。
  8. 在#ifndef中的宏,一定要大写和下划线,必要的时候加数字,目的是为了避免和其他头文件中的宏名字冲突。
  9. #ifdef,#ifndef叫条件编译语句;
  10. #ifdef 宏,如果宏被定义了,那么编译语句;
  11. #ifndef 宏,如果宏被定义了,那么就不编译语句。

9.6 函数的递归

函数可以调用自己,这就叫函数的递归。

  1. #include <stdio.h>
  2. void test(int n)
  3. {
  4. if(n > 0)
  5. {
  6. n --;
  7. printf("先序n = %d\n",n);//先序递归,如果是先序递归,那么代码是顺序执行的
  8. test(n);//函数自己调用自己,就叫函数的递归
  9. printf("后序n = %d\n",n);//后序递归,如果是后序递归,那么代码是逆序执行的
  10. }
  11. }
  12. int main()
  13. {
  14. int i = 3;
  15. test(i);
  16. return 0;
  17. }

9.6.1 递归的过程分析

案例:将十进制转换为二进制

  1. #include <stdio.h>
  2. void test(int n)
  3. {
  4. int i = n % 2;
  5. printf("%d\n",i);
  6. if(n > 0)
  7. {
  8. test(n / 2);
  9. }
  10. }
  11. int main()
  12. {
  13. int i = 11;
  14. test(i);
  15. return 0;
  16. }

斐波那契数列例子:
斐波那契数列指的是这样一个数列0,1,1,2,3,5,8,13,21,34,55,89,144,…
第0项是0,第1项是第一个1;
这个数列从第2项开始,每一项都等于前两项之和。

  1. int fib(int n)
  2. {
  3. if (n == 0)
  4. return 0;
  5. if (n == 1)
  6. return 1;
  7. else
  8. {
  9. return fib(n - 1) + fib(n - 2);
  10. }
  11. }

9.6.2 递归的优点

递归给某些编程问题提供了最简单的方法。

9.6.3 递归的缺点

一个有缺陷的递归会很快耗尽计算机的资源,递归的程序难以理解和维护。

10 指针

10.1 指针

10.1.1 指针的概念

指针变量也是一个变量;
指针存放的内容是一个地址,该地址指向一块内存空间

10.1.2 指针变量的定义

可以定义一个指向一个变量的指针变量

  1. Int *p; //表示定义一个指针变量
  2. *p; //代表指针所指内存的实际数据
  3. 切记,指针变量只能存放地址,不能将一个int型变量直接赋值给一个指针。
  4. Int *p = 100;
  5. Int *p = &a; //得到变量a的地址,将这个地址赋值给变量p
  6. Int *p1;//定义一个变量,名字叫p1,它可以指向一个int的地址
  7. P1=&b;//指针变量的值一般不能直接赋值一个整数,而是通过取变量地址的方式赋值

10.1.3 &取地址运算符

&可以取得一个变量在内存当中的地址

10.1.4 无类型指针

定义一个指针变量,但不指定它指向具体哪种数据类型。可以通过强制转化将void 转化为其他类型指针,也可以用(void )将其他类型指针强制转化为void类型指针。

  1. Void *p;

10.1.5 NULL

NULL在C语言中的定义为(void *)0;
当一个指针不指向任何一个有效内存地址的时候,我们应该把指针设置为NULL。

10.1.6 空指针与野指针

指向NULL的指针叫空指针,没有具体指向任何变量地址的指针叫野指针。
空指针是合法的,但野指针是危险的,是导致程序崩溃的主要原因。

10.1.7 指针的兼容性

指针之间赋值比普通数据类型赋值检查更为严格,例如:不可以把一个double *赋值给int *;
原则上一定是相同类型的指针指向相同类型的变量地址,不能用一种类型的指针指向另一种类型的变量地址。

10.1.8 指向常量的指针与指针常量

  1. Const char * p; //定义一个指向常量的指针
  2. Char *const p;//定义一个指针常量,一旦初始化之后其内容不可改变。

10.1.9 指针与数组的关系

一个变量有地址,一个数组包含若干个元素,每个元素在内存中都有地址。

  1. Int a[10];
  2. Int *p = a;

比较p和&a[0]的地址是否相同。

10.1.10 指针运算

指针运算不是简单的整数加减法,而是指针指向的数据类型在内存中占用字节数作为倍数的运算。

  1. Char *p;
  2. P++; //移动了sizeof(char)这么多的字节数
  3. int *p1;
  4. P1++; //移动了sizeof(int)这么多的字节数
  5. 赋值:int *p = &a;
  6. 求值:int i = *p;
  7. 取指针地址int **pp = &p;
  8. 将一个整数加(减)给指针:p+3;p-3;
  9. 增加(减少)指针值 p++,p--
  10. 求差值,p1-p2,通常用于同一个数组内求两个元素之间的距离
  11. 比较p1= =p2,通常用来 比较两个指针是否指向同一个位置

10.1.11 通过指针使用数组元素

P+1代表&a[1],也可以直接使用p[1]表示a[5]
P +5代表&a[5];
P++

寻找数组第二大元素
第一步:假设数组中前2个元素就是最大的和第二大的
Max
Smax
第二步:从数组的第2号元素开始遍历数组
当有元素大于max的时候,
Smax = max
Max= 最大的那个元素,
如果当前元素小于max,并且大于smax,那么就让smax等于当前那个元素

  1. int smax(int *s)//求数组中第二大元素
  2. {
  3. int max = 0;
  4. int s_max = 0;
  5. int i;
  6. if (*s > *(s +1))
  7. {
  8. max = *s;
  9. s_max = *(s + 1);
  10. }
  11. else
  12. {
  13. max = *(s + 1);
  14. s_max = *s;
  15. }//将max等于s[0]和s[1]中大的那个元素的值
  16. for(i = 2;i < 10;i++)//从第3个元素开始遍历数组
  17. {
  18. if(max < *(s + i))//如果遇到大于max的元素,那么让s_max等于max,让max等于这个元素
  19. {
  20. s_max = max;
  21. max = *(s + i);
  22. }
  23. else if(max > *(s + i) && *(s + i) > s_max)//如果这个元素是介于max和s_max之间,那么就让这个元素等于s_max
  24. {
  25. s_max = *(s + i);
  26. }
  27. }
  28. return s_max;//返回s_max的值
  29. }
  30. int main()
  31. {
  32. int buf[10] = {34,21,56,4,87,90,15,65,72,48};
  33. printf("%d\n",smax(buf));
  34. return 0;
  35. }
  36. #include<string.h>
  37. //通过指针将字符串逆置
  38. int main()
  39. {
  40. char str[100]="you";
  41. char *str_start = &str[0];
  42. char *str_end = &str[strlen(str) - 1];
  43. while(str_start < str_end)
  44. {
  45. char *tmp = * str_start;
  46. * str_start = * str_end;
  47. * str_end = tmp;
  48. str_start ++;
  49. str_end --;
  50. }
  51. printf("%s\n",str);
  52. return 0;
  53. }

对于VS的汉字是GBK编码,一个汉字2个字节;对于QT汉字是UTF8编码,一个汉字是3个字节。

  1. #include<string.h>
  2. //通过指针将汉字字符串逆置
  3. int main()
  4. {
  5. char str[100]="我爱你";
  6. short *str_start = &str[0];
  7. short *str_end = &str[strlen(str) - 2];
  8. while(str_start < str_end)
  9. {
  10. short *tmp = * str_start;
  11. * str_start = * str_end;
  12. * str_end = tmp;
  13. str_start ++;
  14. str_end --;
  15. }
  16. printf("%s\n",str);
  17. return 0;
  18. }

10.1.12 指针数组

  1. int *a[10];//定义了一个指针数组,一共10个成员,其中每个成员都是int *类型

10.1.13 指向指针的指针(二级指针)

指针就是一个变量,既然是变量就也存在内存地址,所以可以定义一个指向指针的指针。
通过二级指针修改内存的值;

  1. Int I = 10;
  2. Int *p1 = &I;
  3. Int **p2 = &p1;
  4. Printf(“%d\n”,**p2);

以此类推可以定义3级甚至多级指针。
C语言允许定义多级指针,但是指针级数过多会增加代码的复杂性,考试的时候可能会考多级指针,但是实际编程的时候最多用到3级指针,但是3级指针也不常用,一级和二级指针是大量使用的。

10.1.14 指向二维数组的指针

  1. Int buf[3][5] 二维数组名称,buf代表数组首地址
  2. Int (*a)[5] 定义一个指向int[5]类型的指针变量a
  3. a[0],*(a+0),*a 0行,0列元素地址
  4. a+1 1行首地址
  5. a[1],*(a+1) 1行,0列元素地址
  6. a[1]+2,*(a+1)+2,&a[1][2] 1行,2列元素地址
  7. *(a[1]+2),*(*(a+1)+2),a[1][2] 1行,2列元素的值
  8. //二维数组的指针计算二维数组行列的平均值
  9. int main()
  10. {
  11. int buf[3][5] = {{2,4,3,5,1},{7,2,6,8,1},{7,3,9,0,2}};
  12. int i;
  13. int j;
  14. int sum;
  15. for(i = 0;i < 3;i ++)
  16. {
  17. sum = 0;
  18. for(j = 0;j < 5;j ++)
  19. {
  20. sum += (*(*(buf + i) + j));
  21. //sum += buf[i][j];
  22. }
  23. printf("%d\n",sum / 5);
  24. }
  25. for(i = 0;i < 5;i ++)
  26. {
  27. sum = 0;
  28. for(j = 0;j < 3;j ++)
  29. {
  30. sum += (*(*(buf + j) + i));
  31. //sum += buf[j][i];
  32. }
  33. printf("%d\n",sum / 3);
  34. }
  35. return 0;
  36. }

10.1.15 指针变量作为函数的参数

函数的参数可以是指针类型 *,它的作用是将一个变量的地址传送给另一个函数。
通过函数的指针参数可以间接的实现形参修改实参的值。

10.1.16 一维数组名作为函数参数

当数组名作为函数参数时,C语言将数组名解释为指针

  1. int func(int array[10]);//数组名代表数组的首地址

10.1.17 二维数组名作为函数参数

二维数组做函数参数时可以不指定第一个下标。

  1. int func(int array[][10]);

将二维数组作为函数的参数用例不是特别多见。

10.1.18 const关键字保护数组内容

如果讲一个数组作为函数的形参传递,那么数组内容可以在被调用函数内部修改,有时候不希望这样的事情发生,所以要对形参采用const参数。

  1. func(const int array[]);

10.1.19 指针作为函数的返回值

  1. return NULL;

10.1.20 指向函数的指针

指针可以指向变量、数组,也可以指向一个函数。
一个函数在编译的时候会分配一个入口地址,这个入口地址就是函数的指针,函数名称就代表函数的入口地址。
函数指针的定义方式:

  1. int (*p)(int);//定义了一个指向int func(int n)类型函数地址的指针。
  • 定义函数指针变量的形式为:函数返回类型(*指针变量名称)(参数列表)

  • 函数可以通过函数指针调用

  1. int(* p)()代表指向一个函数,但不是固定哪一个函数
  2. Void *p(int ,char *);//声明了一个函数,函数的名字叫p,函数的返回值是void *,函数的参数是int和char *
  3. Void (*p)(int ,char *);//定义了一个指向参数为int和char *,返回值为void的函数指针
  4. Int *(*p)(int *);//定义一个参数为int *,返回值为int *的指向函数的指针
  5. Int *p(int *);//声明了一个函数,返回值是int *,参数是int *
  6. Int (*p)(int , int );//定义了一个指向函数的指针,可以指向两个参数,都是int,返回值也是int类型

在回调函数和运行期动态绑定的时候大量的用到了指向函数的指针。

10.1.21 把指向函数的指针作为函数的参数

将函数指针作为另一个函数的参数称为回调函数。

  1. int max(int a,int b)
  2. {
  3. if(a > b)
  4. return a;
  5. else
  6. return b;
  7. }
  8. int add(int a,int b)
  9. {
  10. return a + b;
  11. }
  12. int func(int (*p)(int,int),int a,int b)//第一个参数是指向函数的指针
  13. {
  14. return p(a,b);//通过指向函数的指针调用一个函数
  15. }
  16. int main()
  17. {
  18. int i = func(add,6,9);//add函数在这里就叫回调函数
  19. printf("i = %d\n",i);
  20. return 0;
  21. }

10.1.22 memset,memcpy,memmove函数

这三个函数分别实现内存设置,内存拷贝,内存移动
使用memcpy的时候,一定要确保内存没有重叠区域。

memset(buf, 0, sizeof(buf));//第一个参数是要设置的内存地址,第二个参数是要设置的值,第三个参数是内存大小,单位:字节
memcpy(buf2, buf1, sizeof(buf1));//将buf1的内存内容全部拷贝到buf2,拷贝大小为第三个参数:字节
memmove(buf2, buf1, sizeof(buf1));//并没有改变原始内存的值

10.1.23 指针小结

  1. 定义 说明
  2. Int I 定义整形变量
  3. Int *p 定义一个指向int的指针变量
  4. Int a[10] 定义一个int数组
  5. Int *p[10] 定义一个指针数组,其中每个数组元素指向一个int型变量的地址
  6. Int (*p)[10] 定义一个数组指针,指向int[10]类型的指针变量
  7. Int func() 定义一个函数,返回值为int
  8. Int *func() 定义一个函数,返回值为int *型
  9. Int (*p)() 定义一个指向函数的指针,函数的原型为无参数,返回值为int
  10. Int **p 定义一个指向int的指针的指针,二级指针

11 字符指针与字符串

11.1 指针和字符串

在C语言中,大多数字符串操作其实就是指针操作。

  1. Char s[] = hello world”;
  2. Char *p = s;
  3. P[0] = a’;

11.2 通过指针访问字符串数组

  1. char buf[100] = "hello world";
  2. char *p = buf;
  3. //*(p + 5) = 'a';
  4. //p[5] = 'b';
  5. p += 5;
  6. *p = 'c';
  7. p[3] = ' ';
  8. printf("buf = %s\n",buf);

11.3 函数的参数为char *

  1. void print_array(int *p,int n)//如果参数是一个int数组,那么就必须传递第二个参数用来标示数组的长度
  2. {
  3. int i;
  4. for(i = 0; i <n; i++)
  5. {
  6. printf("p[%d] = %d\n", i, p[i]);
  7. }
  8. }
  9. void print_str(char *s)//如果参数是个字符串,那么就不需要包含第二个参数
  10. //因为字符串是明确的以‘\0’结尾的,所以在函数内部是有条件来作为循环终止依据的
  11. {
  12. int i = 0;
  13. while(s[i])
  14. {
  15. printf("%c",s[i++]);
  16. }
  17. }

11.4 指针数组作为main函数的形参

  1. Int main(int argc, char *argv[]);

main函数是操作系统调用的,所以main函数的参数也是操作系统在调用时候自动填写的
argc代表命令行参数的数量
argv代表命令行的具体参数,是char *类型的

12 内存管理

12.1 作用域

一个C语言变量的作用域可以是代码块作用域,函数作用域或者文件作用域。
代码块是{}之间的一段代码。
出现在{}之外的变量,就是全局变量。

12.1.1 auto自动变量

一般情况下代码块内部定义的变量都是自动变量。当然也可以显示的使用aotu关键字

12.1.2 register寄存器变量

通常变量在内存当中,如果能把变量放到CPU的寄存器里面,代码执行效率会更高
register int i;//建议,如果有寄存器空闲,那么这个变量就放到寄存器里面使用
对于一个register变量,是不能取地址操作的

12.1.3 代码块作用域的静态变量

静态变量是指内存位置在程序执行期间一直不改变的变量,一个代码块内部的静态变量只能被这个代码块内部访问。
static int i = 0;//静态变量,只初始化一次,而且程序运行期间,静态变量一直存在

12.1.4 代码块作用域外的静态变量

代码块之外的静态变量在程序执行期间一直存在,但只能被定义这个变量的文件访问
Static int a=0;//一旦全局变量定义static,意思是这个变量只是在定义这个变量的文件内部全局有效

12.1.5 全局变量

全局变量的存储方式和静态变量相同,但可以被多个文件访问

12.1.6 外部变量与extern关键字

extern int i;

12.1.7 全局函数和静态函数

在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态

12.2 内存四区

12.2.1 代码区

代码区code,程序被操作系统加载到内存的时候,所有的可执行代码都加载到代码区,也叫代码段,这块内存是不可以运行期间修改的。

12.2.2 静态区

所有的全局变量以及程序中的静态变量都存储到静态区,比较如下两段代码的区别:

  1. int a=0; int a=0;
  2. int main() static int b=0;
  3. { int main()
  4. static int b=0; {
  5. printf(“%p,%p\n”,&a,&b); printf(“%p,%p\n”,&a,&b);
  6. return 0; retrun 0;
  7. } }
  8. int *getb() //合法的
  9. {
  10. static int a=0;
  11. return &a;
  12. }

12.2.3 栈区

栈stack是一种先进后出的内存结构,所有的自动变量,函数的形参都是由编译器自动放出栈中,当一个自动变量超出其作用域时,自动从栈中弹出。

对于自动变量,什么时候入栈,什么时候出栈,是不需要程序控制的,由C语言编译器实现
栈不会很大,一般都是以K为单位的

  1. int *geta()//错误,不能将一个栈变量的地址通过函数的返回值返回
  2. {
  3. int a = 0;
  4. return &a;
  5. }

12.2.4 栈溢出

当栈空间已满,但还往栈内存压变量,这个就叫栈溢出。
对于一个32位操作系统,最大管理4G内存,其中1G是给操作系统自己用的,剩下的3G都是给用户程序的,一个用户程序理论上可以使用3G的内存空间。

12.2.5 堆区

堆heap和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的顺序。
堆是一个大容器,它的容量要远远大于栈,但是在C语言中,堆内存空间的申请和释放需要手动通过代码来完成。

  1. int *geta()//可以通过函数的返回值返回一个堆地址,但记得一定要free
  2. {
  3. int *p=malloc(sizeof(int ));//申请了一个堆空间
  4. return p;
  5. }
  6. Int main()
  7. {
  8. Int *getp=geta();
  9. *getp=100;
  10. free(getp);
  11. }

12.3 堆的分配和释放

操作系统在管理内存的时候,最小单位不是字节,而是内存页。

12.3.1 malloc

  1. void * malloc(size_t _Size);
  2. int *p=(int *)malloc(sizeof(int)*10);//在堆中间申请内存,在堆中申请了一个10个int这么大的空间
  3. malloc函数在堆中分配参数_Size指定大小的内存,单位:字节,函数返回void *指针。
  4. Void getheap(int *p)
  5. {
  6. P=malloc(sizeof(int) * 10);
  7. }//getheap执行完以后,p就消失了,导致它指向的具体堆空间的地址编号也随之消失了
  8. Void getheap1(int **p)
  9. {
  10. *p=malloc(sizeof(int) * 10);
  11. }
  12. Int main()
  13. {
  14. Int *p=NULL;
  15. Printf(“p=%p\n”,&p);
  16. //getheap(p);//实参没有任何改变
  17. getheap1(&p);//得到了堆内存的地址
  18. }

12.3.2 free

  1. Void free(void *p);
  2. free(p);//释放通过malloc分配的堆内存
  3. free负责在堆中释放malloc分配的内存。参数pmalloc返回的堆中的内存地址。
  4. Int *array = malloc(sizeof(int) * i);//在堆当中动态创建一个int数组
  5. free(array);

12.3.3 calloc

  1. Void * calloc(size_t _Count, size_t _Size);

Calloc与malloc类似,负责在堆中分配内存。

第一个参数是所需内存单元数量,第二个参数是每个内存单元的大小(单位:字节),calloc自动将分配的内存置0

  1. Int *p=(int *)calloc(100,sizeof(int));//分配100个int

12.3.4 realloc

重新分配用malloc或者calloc函数在堆中分配内存空间的大小。

  1. Void * realloc(void *p, size_t _NewSize);

第一个参数p为之前用malloc或者calloc分配的内存地址,_NewSize为重新分配内存的大小,单位:字节。
成功返回新分配的堆内存地址,失败返回NULL;
如果参数p等于NULL,那么realloc与malloc功能一致。

  1. Char *p = malloc(10);//分配空间,但原有数据没做清洁
  2. Char *p1 = calloc(10, sizeof(char));//分配空间以后,自动做清洁
  3. Char *p2 =realloc(p1,20);//在原有内存基础之上,在堆中间增加连续的内存
  4. //如果原有内存没有连续空间可扩展,那么会新分配一个空间,将原有内存copy到新空间,然后释放原有内存。
  5. //realloc和malloc,只分配内存,但不打扫
  6. Char *p2 = realloc(NULL,5);//等于malloc(5)

13 结构体,联合体,枚举与typedef

13.1 结构体

13.1.1 定义结构体struct和初始化

  1. Struct man
  2. {
  3. Char name[100];
  4. Int age;
  5. };
  6. Struct man m = {“tom”,12};
  7. Struct man m = {.name = tom”, .age = 12};
  8. #include <string.h>
  9. #pragma warning(disable:4996)
  10. struct student
  11. {
  12. char name[100];
  13. int age;
  14. int sex;
  15. };//说明了一个结构体的数据成员类型
  16. int main()
  17. {
  18. struct student st;//定义了一个结构体的变量,名字叫st;
  19. st.age = 20;
  20. st.sex = 0;
  21. strcpy(st.name,"刘德华");
  22. printf("name = %s\n",st.name);
  23. printf("age = %d\n",st.age);
  24. if(st.sex == 0)
  25. {
  26. printf("男");
  27. }
  28. else
  29. {
  30. printf("女");
  31. }
  32. return 0;
  33. }

13.1.2 访问结构体成员

.操作符

13.1.3 结构体的内存对齐模式

编译器在编译一个结构的时候采用内存对齐模式。

  1. Struct man
  2. {
  3. Char a;
  4. Int b;
  5. Shor c;
  6. Char d;
  7. Long long e;
  8. };

13.1.4 指定结构体元素的位字段

定义一个结构体的时候可以指定具体元素的位长;

  1. Struct test{
  2. char a : 2;//指定元素为2位长,不是2个字节长
  3. };

13.1.5 结构数组

  1. Struct man m[10] = {{“tom”,12},{“marry”,10},{“jack”,9}};
  2. Int I;
  3. for(i=0; i<5; i++)
  4. {
  5. Printf(“姓名=%s,年龄=%d\n”,m[i].name,m[i].age);
  6. }
  7. #include <stdio.h>
  8. #include <string.h>
  9. #pragma warning(disable:4996)
  10. //结构体数组排序
  11. struct student
  12. {
  13. char name[100];
  14. int age;
  15. int score;
  16. char classes[100];
  17. };
  18. void swap(struct student *a,struct student *b)
  19. {
  20. struct student tmp = *a;
  21. *a = *b;
  22. *b = tmp;
  23. }
  24. int main()
  25. {
  26. struct student st[5] = {{"chen",12,78,"A"},{"li",10,90,"B"},{"wang",13,59,"C"},{"fei",12,91,"D"},{"bai",9,59,"E"}};
  27. int i;
  28. int j;
  29. for(i = 0; i < 5; i++)
  30. {
  31. for(j = 1; j < 5-i; j++)
  32. {
  33. if(st[j].age < st[j - 1].age)
  34. {
  35. swap(&st[j],&st[j - 1]);
  36. }
  37. else if(st[j].age == st[j - 1].age)
  38. {
  39. if(st[j].score <st[j-1].score)
  40. {
  41. swap(&st[j],&st[j - 1]);
  42. }
  43. }
  44. }
  45. }
  46. for(i = 0; i < 5; i++)
  47. {
  48. printf("姓名=%s,年龄=%d,成绩=%d,班级=%s\n",st[i].name,st[i].age,st[i].score,st[i].classes);
  49. }
  50. return 0;
  51. }

13.1.6 嵌套结构

一个结构的成员还可以是另一个结构类型

  1. Struct names{
  2. Char a;
  3. Int b;
  4. };
  5. Struct man{
  6. Struct names name;
  7. Int c;
  8. };
  9. Struct man m = {{“wang”,10},20};

13.1.7 结构体的赋值

Struct name m = b;

结构体赋值,其实就是结构体之间内存的拷贝

13.1.8 指向结构体的指针

  1. —>操作符
  2. Struct A a;
  3. Struct A *p = &a;
  4. p->a = 10;

13.1.9 指向结构体数组的指针

13.1.10 结构中的数组成员和指针成员

一个结构中可以有数组成员,也可以有指针成员,如果是指针成员结构体成员在初始化和赋值的时候就需要提前为指针成员分配内存。

  1. Struct man
  2. {
  3. Char name[100];
  4. Int age;
  5. };
  6. Struct man
  7. {
  8. Char *name;
  9. Int age;
  10. };

13.1.11 在堆中创建的结构体

如果结构体有指针类型成员,同时结构体在堆中创建,那么释放堆中的结构体之前需要提前释放结构体中的指针成员指向的内存。

  1. Struct man
  2. {
  3. Char *name;
  4. Int age;
  5. };
  6. Struct man *s = malloc(sizeof(struct man) * 2);
  7. S[0].name = malloc(10 * sizeof(char));
  8. S[1].name = malloc(10 * sizeof(char));

13.1.12 将结构作为函数参数

将结构作为函数参数
将结构指针作为函数参数

  1. Struct student
  2. {
  3. Char name[10];
  4. Int age;
  5. };
  6. Void print_student (struct student s)//一般来讲,不要把结构变量作为函数的参数传递,因为效率比较低,一般用指针来代替
  7. //void print_student(const struct student *s)
  8. {
  9. Printf(“name = %s, age = %d\n”,s.name,s.age);
  10. }
  11. Int main()
  12. {
  13. Struct student st = {“tom”,20};
  14. Print_student(st);
  15. }

13.1.13 结构,还是指向结构的指针

在定义一个和结构有关的函数,到底是使用结构,还是结构的指针?
指针作为参数,只需要传递一个地址,所以代码效率高。

  1. Void set_student (struct student *s, const char *name, int age)
  2. {
  3. Strcpy(s->name, name);
  4. s->age = age;
  5. }
  6. Int main()
  7. {
  8. Set_student(&st, maik”,100);
  9. Print_student(st);
  10. }

结论:当一个结构作为函数的参数时候,尽量使用指针,而不是使用结构变量,这样代码效率很高。

13.2 联合体

联合union是一个能同一个存储空间存储不同类型数据的类型。
联合体所占的内存长度等于其最长成员的长度,也有叫做共用体。
联合体虽然可以有多个成员,但同一时间只能存放其中一种。

  1. union A
  2. {
  3. Int a;
  4. Char b;
  5. Char *c; //注意
  6. };
  7. Int main()
  8. {
  9. Union A a;
  10. a.a = 10;
  11. a.c = malloc(100);//c指向了一个堆的地址
  12. free(c);
  13. //如果联合体中有指针成员,那么一定要使用完这个指针,并且free指针之后才能使用其他成员
  14. a.b = 20;
  15. }

13.3 枚举类型

13.3.1 枚举定义

可以使用枚举声明代表整数常量的符号名称,关键字enum创建一个新的枚举类型。
实际上,enum常量是int类型的。
枚举是常量,值是不能修改的。

  1. enum spectrum
  2. {
  3. red,yellow,green,blue,white,black
  4. };
  5. enum spectrum color;
  6. color = black;
  7. if(color != red)

13.3.2 默认值

默认时,枚举列表中的常量被指定为0,1,2等

  1. enum spectrum
  2. {
  3. red,yellow,green,blue,white,black
  4. };
  5. printf(“%d,%d\n”,red,black);

指定值
可以指定枚举中具体元素的值

  1. enum spectrum
  2. {
  3. red=1,
  4. yellow=2,
  5. green=3,
  6. blue,
  7. white,
  8. black
  9. };

13.4 typedef

typedef是一种高级数据特性,它能使某一类型创建自己的名字。

  1. typedef unsigned char UBYTE
  2. typedef char BYTE;
  • 与#define不同,typedef仅限于数据类型,而不是能是表达式或具体的值

  • typedef是编译器处理的,而不是预编译指令;

  • typedef比#define更灵活

直接看typedef好像没什么用处,使用BYTE定义一个unsigned char。使用typedef可以增加程序的可移植性。

  1. typedef struct
  2. {
  3. Int a;
  4. }A2;
  5. A2 a;

13.5 通过typedef定义函数指针

  1. typedef const char *(*SUBSTR)(const char *,const char *);
  2. const char *getsubstr(const char *src, const char *str)
  3. {
  4. return strstr(src,str);
  5. }
  6. Const char *(*p[3])(const char *,const char *);

14 文件操作

14.1 fopen

  1. Char s[1024] = {0};
  2. FILE *p = fopen(“D:\\temp\\a.txt”,”w”);//用写的方式打开一个文件
  3. fputs(“hello world”,p);//向文件写入一个字符串
  4. //feof(p);//如果已经到了文件结尾,函数返回真
  5. While(!feof(p))//如果没有到文件结尾,那么就一直循环
  6. {
  7. memset(s,0,sizeof(s));
  8. //fgets(s,sizeof(s),p);//第一个参数是一个内存地址,第二个参数是这块内存的大小,第三个参数是fopen返回的文件指针
  9. printf(“%s”,s);
  10. }
  11. fclose(p);//关闭这个文件

r以只读方式打开文件,该文件必须存在
r+ 以可读写方式打开文件,该文件必须存在
rb+ 读写打开一个二进制文件,允许读写数据,文件必须存在
rw+ 读写打开一个文本文件,允许读和写
w 打开只写文件,若文件存在则文件长度清0,即该文件内容会消失。若文件不存在则建立该文件;
w+ 打开可读写文件,若文件存在则文件长度清0,即该文件内容会消失。若文件不存在则建立该文件;
a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
a+ 以附加的方式打开可读写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(原来的EOF符不保留)。

  1. Void code(char *s)
  2. {
  3. While(*s)//遍历一个字符串
  4. {
  5. (*s)++;
  6. s++;
  7. }
  8. }
  9. Int main()//文件加密
  10. {
  11. Char s[1024] = {0};
  12. FILE *p = fopen(“D:\\temp\\a.txt”,”r”);//用读的方式打开一个文件
  13. FILE *p1 = fopen(“D:\\temp\\a.txt”,”w”);//用写的方式打开一个文件
  14. //feof(p);//如果已经到了文件结尾,函数返回真
  15. While(!feof(p))//如果没有到文件结尾,那么就一直循环
  16. {
  17. memset(s,0,sizeof(s));
  18. fgets(s,sizeof(s),p);//第一个参数是一个内存地址,第二个参数是这块内存的大小,第三个参数是fopen返回的文件指针
  19. code(s);//文件加密
  20. fputs(s,p1);
  21. }
  22. fclose(p);//关闭这个文件
  23. fclose(p1);
  24. }

14.2 二进制和文本模式的区别

  • 在windows系统中,文本模式下,文件以“\r\n”代表换行。若以文本模式打开文件,并用fputs等函数写入换行符”\n”时,函数会自动在”\n”前面加上“\r”。即实际写入文本的是”\r\n”。

  • 在类Unix/Linux系统中文版模式下,文件以”\n”代表换行。所以Linux系统中在文本模式和二进制模式下并无区别。

14.3 fclose

fclose关闭fopen打开的文件

14.4 getc和putc函数

  1. Int main() int main()
  2. { {
  3. FILE *fp=fopen(“a.txt”,”r”); FTLE *fp=fopen(“a.txt”,”w”);
  4. Char c; const char *s=”hello world”;
  5. While((c=getc(fp))!=EOF) int I;
  6. { for(i=0;i<strlen(s);i++)
  7. Printf(“%c”,c); {
  8. } putc(s[i],fp);
  9. fclose(fp); }
  10. return 0; fclose(fp);
  11. } return 0;
  12. }

14.5 EOF与feof函数文件结尾

程序怎么才能知道是否已经到达文件结尾了呢?EOF代表文件结尾
如果已经是文件尾,feof函数返回true。

  1. While((c=getc(p))!=EOF)//EOF代表文件最后的一个结束标识
  2. {
  3. //c=getc(p);//一次只读取一个字符
  4. Printf(“%c”,c);
  5. }
  6. ##14.6 fprintf,fscanf,fgets,fputs函数
  7. 这些函数都是通过FILE *来对文件进行读写。
  8. 都是针对文本文件的行读写函数
  9. fprintf(p,”%s”,buf);//和printf功能一样,fprintf将输入的内容输入到文件里面
  10. fscanf(p,”%s”,buf); //fscanf与scanf用法基本一致,fscanf是从一个文件读取输入,scanf是从键盘读取输入
  11. #include <stdio.h>
  12. #include <stdlib.h>
  13. #include <string.h>
  14. //计算文本中的字符串
  15. int calc_string(const char *s)
  16. {
  17. char buf1[100] = {0};
  18. char oper1 = 0;
  19. char buf2[100] = {0};
  20. int len = strlen(s);//得到字符串的长度
  21. int i;
  22. int start;
  23. for(i=0;i<len;i++)
  24. {
  25. if(s[i] == '+'|| s[i] == '-' || s[i] == '*' || s[i] == '/')
  26. {
  27. strncpy(buf1, s, i);
  28. oper1 = s[i];
  29. break;
  30. }
  31. }
  32. start = i + 1;
  33. for(;i<len;i++)
  34. {
  35. if(s[i] == '=')
  36. {
  37. strncpy(buf2,&s[start],i-start);
  38. }
  39. }
  40. printf("buf1 = %s,oper1 = %c,buf2 = %s\n",buf1,oper1,buf2);
  41. switch(oper1)
  42. {
  43. case '+':
  44. return atoi(buf1) + atoi(buf2);
  45. case '-':
  46. return atoi(buf1) - atoi(buf2);
  47. case '*':
  48. return atoi(buf1) * atoi(buf2);
  49. case '/':
  50. {
  51. int a = atoi(buf2);
  52. if(a)
  53. return atoi(buf1) / atoi(buf2);
  54. else
  55. return 0;
  56. }
  57. }
  58. }
  59. void cutereturn(char *s)//把字符串最后的回车字符吃掉
  60. {
  61. int len = strlen(s);
  62. if(s[len - 1] == '\n')
  63. s[len - 1] = 0;
  64. }
  65. int main()
  66. {
  67. FILE *p = fopen("D:\\main\\a.txt","r");//32+56=88
  68. FILE *p1 = fopen("D:\\main\\b.txt","w");
  69. char buf[1024];
  70. char buf1[1024];
  71. int value;
  72. while(!feof(p))
  73. {
  74. memset(buf,0,sizeof(buf));
  75. fgets(buf,sizeof(buf),p);//从文件中读取一行记录,字符串最后是以'\n'结尾的
  76. cutereturn(buf);//吸收回车
  77. value = calc_string(buf);
  78. memset(buf1,0,sizeof(buf1));
  79. sprintf(buf1,"%s%d\n",buf,value);//将buf和buf计算结果重新组织成一个字符串
  80. fputs(buf1,p1);//将重新组合后的字符串写入新的文件
  81. }
  82. fclose(p);
  83. fclose(p1);
  84. return 0;
  85. }

14.7 stat函数

  1. #include <sys/stat.h>
  2. int stat(const char *_Filename, struct stat * _Stat);
  3. stat.st_size;//文件大小,单位:字节
  4. 函数的第一个参数代表文件名,第二个参数是struct stat结构。
  5. 得到文件的属性,包括文件建立时间,文件大小等信息。
  6. Struct stat st = {0};//定义一个结构,名字叫st
  7. Stat(“D:\\temp\\a.txt”,&st);//调用完stat函数之后,文件相关的信息就保存在了st结构中了

14.8 fread和fwrite函数

  1. size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
  2. size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
  3. 注意:这个函数以二进制形式对文件进行操作,不局限于文本文件,可进行二进制的读写和拷贝
  4. 返回值:返回实际写入的数据块数目
  5. size_t res=fread(buf,sizeof(char),sizeof(buf),p);//第一个参数是缓冲区,第二个参数是读取的时候最小单位大小,第三个参数是一次读几个单位,第四个参数是打开的文件指针,fread返回值代表读取了多少记录数
  6. fwrite(buf,sizeof(char),res,p1);//从源文件中读取多少字节,那么就往目标文件中写多少字节

14.9 fread与feof

  1. while(!feof(p))//如果没有到达文件结尾,那么循环继续
  2. {
  3. memset(buf,0,sizeof(buf));//每次读取文件一行之前都把这个buf清空
  4. fgets(buf,sizeof(buf),p);//从文件中读取一行
  5. fread(&buf, 1, sizeof(buf), p);
  6. }

14.10 通过fwrite将结构保存到二进制文件中

做一个代码例子

14.11 fseek函数

  1. int fseek(FILE *stream, long offset, int whence);

函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset(指针偏移量)个字节的位置,函数返回0。如果执行失败则不改变stream指向的位置,函数返回一个非0值。
实验得出,超出文件末尾位置,还是返回0。往回偏移超出首位置,还是返回0,请小心使用。
第一个参数stream为文件指针;
第二个参数offest为偏移量,正数表示正向偏移,负数表示负向偏移;
第三个参数whence设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、SEEK_END或SEEK_SET。
SEEK_SET:文件开头
SEEK_CUR:当前位置
SEEK_END:文件结尾
fseek(fp, 3, SEEK_SET);

14.12 ftell函数

  1. long ftell(FILE *stream);

函数ftell用于得到文件位置指针当前位置相对于文件首的偏移字节数。在随机方式存取文件时,由于文件位置频繁的前后移动,程序不容易确定文件的当前位置。

  1. long len = ftell(fp);

14.13 fflush函数

  1. int fflush(FILE *stream);

fflush函数可以将缓冲区中任何未写入的数据写入文件中。
成功返回0,失败返回EOF。
每当程序通过C语言库函数往文件里面写数据,C语言库函数并不是实时的将数据直接写入磁盘,而是放到内存里面,当内存满了或者明确的调用了fclose,才将数据一次性写入磁盘。
结合:C语言所有的文件操作函数都是缓冲区函数。

  1. fflush(p);//fflush将缓冲区的内容立刻写入文件
  2. //优势:不会因为停电,或者电脑死机等故障导致缓冲区的内容丢失;
  3. //不好:硬盘读写次数增加,导致程序效率低下,同时硬盘寿命变短。

修改配置文件的时候,有时候会使用,或者做一些不经常修改的数据,但很重要数据,那么用fflush。

14.14 remove函数

  1. int remove(const char *_Filename);

remove函数删除指定文件
参数Filename为指定的要删除的文件名,如果是windows下文件名与路径可以用反斜杠\分隔,也可以用斜杠/分隔。

14.15 rename函数

  1. int rename(const char *_OldFilename, const char *_NewFilename);

rename函数将指定文件改名
参数OldFilename为指定的要修改的文件名,NewFilename为修改后的文件名,如果是windows下文件名与路径可以用反斜杠\分隔,也可以用斜杠/分隔。

//程序还没有退出的时候,是不能同时打开很多文件的
一个程序同时可以打开的文件数是有限的
尽量在一个程序中不要同时打开太多的文件,如果确实要操作很多文件,也是一个操作完毕fclose以后,再去操作下一个文件。

15 基础数据结构与算法

15.1 什么是数据结构

数据(data)是对客观事物符号表示,在计算机中是指所有能输入的计算机并被计算机程序处理的数据总称。
数据元素(data element)是数据的基本单位,在计算机中通常作为一个整体进行处理。
数据对象(data object)是性质相同的数据元素的集合,是数据的一个子集。
数据结构(data structure)是相互之间存在一种或多种特定关系的数据元素的集合。
数据类型(data type)是和数据结构密切关系的一个概念,在计算机语言中,每个变量、常量或者表达式都有一个所属的数据类型。
抽象数据类型(abstract data type ADT)是指一个数据模型以及定义在该模型上的一组操作,抽象数据类型的定义仅取决于它的一组逻辑性,与其在计算机内部如何表示以及实现无关。

15.2 什么是算法

算法是对特定问题求解的一种描述,它是指令的有限序列,其每一条指令表示一个或多个操作,算法还有以下特性:

  • 有穷性
    一个算法必须总是在执行有限步骤后的结果,而且每一步都可以在有限时间内完成。

  • 确定性
    算法中每一个指令都有确切的含义,读者理解时不会产生二义性,在任何条件下,算法只有唯一的一条执行路径,即相同的输入只能得出相同的

  • 可行性
    一个算法是可行的,即算法中描述的操作都是可以通过已经实现的基本运算来实现的。

  • 输入
    一个算法有零个或者多个输入,这些输入取自与某个特定对象的集合。

  • 输出
    一个算法一个或多个输出,这些输出是和输入有某些特定关系的量。

15.3 排序

15.3.1 冒泡排序

冒泡排序首先将一个记录的关键字和第二个记录的关键字进行比较,如果为逆序(elem[1]>elem[2]),则两个记录交换之,然后比较第二个记录和第三个记录的关键字,以此类推,直到第n-1个记录和第n个记录的关键字进行过比较为止。
上述过程称作第一次冒泡排序,其结果是将关键字最大的记录接安排到最后一个记录的位置上,然后进行第二次冒泡排序,对前n-1个记录进行同样操作,其结果是使关键字第二大记录被安置到第n-1位置上,直到将所有记录都完成冒泡排序为止。

  1. #include <stdio.h>
  2. //冒泡排序
  3. void swap(int *a,int *b)
  4. {
  5. int tmp = *a;
  6. *a = *b;
  7. *b = tmp;
  8. }
  9. void bubble(int *array, int n)
  10. {
  11. int i;
  12. int j;
  13. for(i = 0; i < n; i++)
  14. for(j = 1; j < n-i; j++)
  15. {
  16. if(array[j - 1] > array[j])
  17. {
  18. swap(&array[j - 1],&array[j]);
  19. }
  20. }
  21. }
  22. void print(int *array, int n)
  23. {
  24. int i;
  25. for(i = 0; i < n; i++)
  26. {
  27. printf("%d\n",array[i]);
  28. }
  29. }
  30. int main(void)
  31. {
  32. int array[10] = {32,45,8,78,21,89,4,15,23,56};
  33. bubble(array,10);
  34. print(array,10);
  35. return 0;
  36. }

15.3.2 选择排序

选择排序是每一次在n-1+1(i=1,2,3,…n)个记录中选取关键字,最小的记录作为有序序列中第i个记录。
通过n-1次关键字间的比较,从n-i+1个记录中选取出关键字最小的记录,并和第i(1<=i<=n)个记录交换之。

  1. int minkey(int *array, int low, int high)//查找指定范围内的最小值
  2. //第一个参数是一个数组,第二个参数是数组的开始下标,第三个参数是数组的终止下标
  3. //返回值是最小元素的下标
  4. {
  5. int min = low;
  6. int key = array[low];//在没有查找最小元素之前,第一个元素是最小的
  7. int i;
  8. for(i = low + 1; i < high; i++)
  9. {
  10. if(key > array[i])
  11. {
  12. key = array[i];
  13. min = i;
  14. }
  15. }
  16. return min;
  17. }
  18. void select(int *array, int n)//选择排序法
  19. {
  20. int i;
  21. for(i = 0; i < n; i++)
  22. {
  23. int j = minkey(array, i, n);
  24. if(i != j)//范围内的第一个成员不是最小的
  25. {
  26. swap(&array[i],&array[j]);
  27. }
  28. }
  29. }
  30. int main(void)
  31. {
  32. int array[10] = {32,45,8,78,21,89,4,15,23,56};
  33. select(array,10);
  34. return 0;
  35. }

15.4 查找

15.4.1 顺序查找

顺序查找的过程为:从表的最后一个记录开始,逐个进行记录的关键字和给定值比较,如果某个记录的关键字与给定值相等,则查找成功,反之则表明表中没有所查找记录,查找失败。

  1. int seq(int *array, int low, int high, int key)//顺序查找
  2. //在指定范围内寻找和key相同的值,找到返回下标,找不到返回—1
  3. {
  4. int i;
  5. for(i = low; i < high; i++)
  6. {
  7. if(array[i] == key)
  8. return i;
  9. }
  10. return -1;
  11. }

15.4.2 二分查找

在一个已经排序的顺序表中查找,可以使用二分查找来实现。
二分查找的过程是:先确定待查记录所在的范围(区间),然后逐步缩小查找范围,直到找到或者找不到该记录为止。
假设指针low和high分别指示待查找的范围下届和上届,指针mid指示区间的中间值,即mid=(low + high) / 2。

  1. int bin(int *array, int low, int high, int key)//二分查找
  2. {
  3. while(low <= high)
  4. {
  5. int mid = (low + high) / 2;
  6. if(key == array[mid])//中间切一刀,正好和要查找的数相等
  7. return mid;
  8. else if(key > array[mid])//如果要找的数大于array[mid],那么就在下半部分继续切刀
  9. low = mid + 1;
  10. else//如果要找的数小于array[mid],那么就在上半部分继续切刀
  11. high = mid - 1;
  12. }
  13. return -1;//没有找到数据
  14. }
  15. int bin_rec(int *array, int low, int high, int key)//递归法实现二分查找
  16. {
  17. if(low <= high)
  18. {
  19. int mid = (low + high) / 2;
  20. if(key == array[mid])//中间切一刀,正好和要查找的数相等
  21. return mid;
  22. else if(key > array[mid])//下半部分继续查找
  23. return bin_rec(array,mid + 1,high,key);
  24. else
  25. return bin_rec(array,low,mid - 1,key);//在上半部分查找
  26. }else
  27. return -1;//没有找到数据
  28. }

15.5 链表

15.5.1 单向链表定义

对于数组,逻辑关系上相邻的两个元素的物理位置也是相邻的,这种结构的优点是可以随机存储任意位置的元素,但缺点是如果从数组中间删除或插入元素时候,需要大量移动元素,效率不高。

链式存储结构的特点,元素的存储单元可以是连续的,也可以是不连续的,因此为了表示每个元素a,与其接后的元素a+1之间的关系,对于元素a,除了存储其本身信息外,还需要存储一个指示其接后元素的位置。这两部分数据成为结点(node)。
一个结点中存储的数据元素被称为数据域。存储接后存储位置的域叫做指针域。N个结点(ai(1<=i<=n))的存储映像链接成一个链表。

整个链表必须从头结点开始进行,头结点的指针指向下一个结点的位置,最后一个结点的指针指向NULL;
在链表中,通过指向接后结点位置的指针实现将链表中每个结点“链”到一起。链表中第一个结点称之为头结点。
N个结点(ai的存储映像)链结成一个链表,即为线性表(a1,a2,…,an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。单链表正是通过每个结点的指针域将线性表的数据元素按其逻辑次序链接在一起。如图所示:

15.5.2 单向链表数据结构定义

  1. struct list
  2. {
  3. int data;//数据域
  4. struct list *next;//指针域
  5. };

15.5.3 单向链表的实现

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. struct list
  4. {
  5. int data;//数据域
  6. struct list *next;//指针域
  7. };
  8. struct list *create_list()//建立一个节点
  9. {
  10. return calloc(sizeof(struct list),1);
  11. }
  12. struct list *insert_list(struct list *ls, int n, int data)//在指定位置插入元素
  13. {
  14. struct list *p = ls;
  15. while(p && n--)
  16. {
  17. p = p->next;
  18. }
  19. if(p == NULL)
  20. {
  21. return NULL;//n的位置大于链表节点数
  22. }
  23. struct list *node = create_list();//新建立一个节点
  24. node->data = data;
  25. node->next = p->next;
  26. p->next = node;
  27. return node;
  28. }
  29. int delete_list(struct list *ls, int n)//删除指定位置元素
  30. {
  31. struct list *p = ls;
  32. while(p && n--)
  33. {
  34. p = p->next;
  35. }
  36. if(p == NULL)
  37. {
  38. return -1;//n的位置不合适
  39. }
  40. struct list *tmp = p->next;
  41. p->next = p->next->next;
  42. free(tmp);
  43. return 0;//删除成功
  44. }
  45. int count_list(struct list *ls)//返回链表元素个数
  46. {
  47. struct list *p = ls;
  48. int count = 0;
  49. while(p)
  50. {
  51. count ++;
  52. p = p->next;
  53. }
  54. return count;
  55. }
  56. void clear_list(struct list *ls)//清空链表,只保留首节点
  57. {
  58. struct list *p = ls->next;
  59. while(p)
  60. {
  61. struct list *tmp = p->next;
  62. free(p);
  63. p = tmp;
  64. }
  65. ls->next = NULL;//只有首节点,那么首节点的next也应该设置为NULL
  66. }
  67. int empty_list(struct list *ls)//返回链表是否为空
  68. {
  69. if(ls->next)
  70. return 0;
  71. else
  72. return -1;
  73. }
  74. struct list *locale_list(struct list *ls, int n)//返回链表指定位置的节点
  75. {
  76. struct list *p = ls;
  77. while(p && n--)
  78. {
  79. p = p->next;
  80. }
  81. if(p == NULL)
  82. return NULL;
  83. return p;
  84. }
  85. struct list *elem_locale(struct list *ls,int data)//返回数据域等于data的节点
  86. {
  87. struct list *p = ls;
  88. while(p)
  89. {
  90. if(p->data == data)
  91. return p;
  92. p = p->next;
  93. }
  94. return NULL;//没有找到数据域等于data的节点
  95. }
  96. int elem_pos(struct list *ls, int data)//返回数据域等于data的节点位置
  97. {
  98. int index = 0;
  99. struct list *p = ls;
  100. while(p)
  101. {
  102. index++;
  103. if(p->data == data)
  104. return index;
  105. p = p->next;
  106. }
  107. return -1;//没有找到数据域等于data的节点
  108. }
  109. struct list *last_list(struct list *ls)//得到链表最后一个节点
  110. {
  111. struct list *p = ls;
  112. while(p->next)
  113. {
  114. p = p->next;
  115. }
  116. return p;
  117. }
  118. void merge_list(struct list *ls1,struct list *ls2)//合并两个链表,结果放入ls1中
  119. {
  120. //只合并链表的节点,不合并链条头
  121. last_list(ls1)->next = ls2->next;
  122. free(ls2);//链表头不要了
  123. }
  124. void reverse(struct list *ls)//链表逆置
  125. {
  126. if (ls->next == NULL)
  127. return;//只有一个首节点,不需要逆置
  128. if (ls->next->next == NULL)
  129. return;//也不需要逆置
  130. struct list *last = ls->next;//逆置后ls->next就成了最后一个节点了
  131. struct list *pre = ls;//上一个节点的指针
  132. struct list *cur = ls->next;//当前节点的指针
  133. struct list *next = NULL;//下一个节点的指针
  134. while(cur)
  135. {
  136. next = cur->next;
  137. cur->next = pre;
  138. pre = cur;
  139. cur = next;
  140. }
  141. ls->next = pre;
  142. last->next = NULL;
  143. }
  144. void traverse(struct list *ls)//循环遍历链表
  145. {
  146. struct list *p = ls;
  147. while(p)
  148. {
  149. printf("%d\n",p->data);
  150. p = p->next;//p指向他对应的下一个节点
  151. }
  152. }
  153. int main(void)
  154. {
  155. struct list *first = create_list();//在堆中间创建一个节点
  156. struct list *second = create_list();//在堆中间创建一个节点
  157. struct list *third = create_list();//在堆中间创建一个节点
  158. first->next = second;
  159. second->next = third;
  160. third->next = NULL;//对于链表的最后一个节点,next域一定为NULL
  161. first->data = 1;
  162. second->data = 2;
  163. third->data = 3;
  164. insert_list(first, 1, 10);
  165. insert_list(first, 1, 20);
  166. //delete_list(first, 2);
  167. //clear_list(first);
  168. traverse(first);
  169. printf("--------------\n");
  170. printf("count = %d\n", count_list(first));
  171. printf("%d\n", locale_list(first,3)->data);
  172. printf("data = %d\n",last_list(first)->data);
  173. printf("--------------\n");
  174. struct list *first1 = create_list();
  175. int i;
  176. for(i = 0; i < 10; i++)
  177. {
  178. insert_list(first1, 0, i);
  179. }
  180. merge_list(first,first1);
  181. printf("--------------\n");
  182. traverse(first);
  183. printf("--------------\n");
  184. reverse(first);
  185. traverse(first);
  186. return 0;
  187. }

逆置操作

  • 判断首节点的next是否为NULL;

  • 判断第二个节点的next是否为NULL;

  • 逆置后ls->next就成了最后一个节点了

  • 最后一个节点的next指向NULL。

上传的附件 cloud_download C语言笔记.doc ( 628.74kb, 2次下载 )

发送私信

7
文章数
0
评论数
最近文章
eject