欢迎各位小白来了解C语言相关知识
下面的是我在MOOC课程中做的笔记
由简单到复杂,建议收藏后慢慢看,这里有很多不常用但很实用的东西
//else if的另一种表述switch-case
switch(控制表达式){ //注意:switch里的表达式必须是整型int
case 常量:
case 常量:
default:
}
e.g.
int eg()
{
int number;
switch(number){
case 12:number--; //如果number是12就-1
case 0:number++; //如果number是0就+1
default:break; //如果都不是就跳出去
}
}
//do-while 循环 while循环的后置哨兵控制器
do{
-----;
}while(判断条件); // 注意分号
//for循环注意事项
for(int i=0;i<n;i++){}
for(int i=1;i<=n;i++){} //这两种循环次数都是n次;i=0,i<n,不带等号;i=1,i<=n,带等号;
for(初始动作;条件;每轮的动作) for中的每一个表达式都可以省略
continue和break
continue 是程序进行到这一步之后在循环体中的剩下语句直接跳过,然后进入下一次循环;break是直接跳出循环
注意:continue和break只能跳出当下循环,在嵌套中的循环不能跳出
goto 慎用(可能会破坏程序的结构性) goto+变量可以跳跃至变量所处位置继续读取语句(在多重循环嵌套中跳出所有循环时使用比较方便) 使用例子:
for(;;){
-----;
if(){
----;
goto out;
}
}
out:
return 0;
//pow()函数
pow(int a,int b)
代表a的b次幂 需要
pow(int a,int b)
//最大公约数
算法1:枚举(设t为2;如果u,v都能被t整除,记下这个t;然后t++,直至t==u||t==v;这样t的最大值就是最大公约数)
#include <stdio.h>
int main()
{
int a,b;
int min;
scanf("%d %d", &a, &b);
if ( a<b ) {
min = a;
} else {
min = b;
}
int ret = 0;
int i;
for ( i = 1; i < min; i++ ) {
if ( a%i == 0 ) {
if ( b%i == 0 ) {
ret = i;
}
}
}
printf("%d和%d的最大公约数是%d.\n", a, b, ret);
return 0;
}
算法2:辗转相除法(如果b==0计算结束,a就是最大公约数;否则,让a=b,而b=a%b;再回到第一步)
#include <stdio.h>
int main()
{
int a,b;
int t;
scanf("%d %d", &a, &b);
int origa = a;
int origb = b;
while ( b != 0 ) {
t = a%b;
a = b;
b = t;
}
printf("%d和%d的最大公约数是%d.\n", origa, origb, a);
return 0;
}
//Dev-C++ 的一些常用快捷键
恢复 Ctrl+Z
重做 Ctrl+Y
剪切 Ctrl+X
拷贝 Ctrl+C
粘贴 Ctrl+V
搜索替换内容 Ctrl+F
选择全部 Ctrl+A
编译 F9
运行 F10
编译运行 F11
设置注释 Ctrl+/ 取消注释再重复一次即可
复制行 Ctrl+E
删除行 Ctrl+D
整体左移一个tab位置 shift+tab
整体右移一个tab位置 tab
整体代码缩进对齐 Ctrl+Shift+A
快捷键的设置和查看:点击最上面一行的“工具”,然后点击“快捷键选项”即可。
// 数据类型强调
1.C 强调类型,但不够严格
2.C++|Java 强调类型,对类型检查严格
3.JavaScript|Python|PHP 不看重类型,不需要事先定义
//数据类型分类
1.整数 char,short,int,long,long long
2.浮点数 float,double,long double
3.逻辑 bool
4.指针 *
5.自定义类型
注:不同类型输出格式不同,存储计算方式也不同,例如整数以二进制的形式计算存储、而浮点数以编码形式进行计算存储
//sizeof()运算符
给出某个类型或变量在内存中所占据的字节数
sizeof(int) sizeof(i)
在sizeof()里做运算没用,他不会去做真的计算,只判断其类型
//编译器(CPU)
int 和 long 的大小取决于电脑中编译器大小
int 是用来表达寄存器的
CPU中寄存器Reg通过总线向RAM(内存)中传输数据 每次运算可传输的数据大小就是int,同是也是寄存器的大小
//二进制负数
一个字节为八个比特,无符号整数(实际)表示范围为00000000-11111111(十进制中的0-255)
采用补码的方式表示负数11111111作为纯二进制是255作为补码是-1(-1+1时会进位为九比特的100000000最前面的1被删掉成为八比特的00000000即0)
补码的意义就是拿补码和原码可以加出一个溢出的零
假设一个整数为a,那么它对应的负数-a对应补码为0-a,实际为2^n-a
//整数范围
如果字节数为n 对应范围为-2^(n-1)~2^(n-1)-1 (n-1)是因为最前面的字节用来区分正负,正数-1是因为0占了一位
正常类型均为signed 如果仅想表示正数要在变量名之前加上unsigned
例:char(signed char) i=255 ---->-1 char 仅有一个字节
unsigned char i=255------>255
或 char i=255U------>255
表示的整数是循环的,越界了会跳回起点,即unsigned 255+1=0 signed 127+1=-1 -128-1=127
//整数的输入输出
只有两种形式 int || long long
int--->%d unsigned(unsigned int)----->%u long long----->%ld unsigned long long---->%lu
一个以0开始的数字面量是八进制 一个以0x开始的数字面量是十六进制
%o用于八进制 %x用于十六进制 常用十六进制表达二进制中的数字,四位二进制表示一位十六进制
//float 和 double
float 字长32 有效数字为7 double 字长64 有效数字为15 其范围都在0左右但无法接近0
float 输入%f 输出%f或%e%E(科学计数法1.15e+16)
在%和f之间加上.n可以指定输出小数点后n位,这样的输出是四舍五入的。
超过范围的浮点数 printf输出inf表示超过范围的浮点数,正无穷、负无穷;nan表示不存在的浮点数(0.0/0)
带小数点的字面量是double而非float,float需要用f或F后缀来表明身份
判断两个浮点数是否相等不能用f1==f2,用
fabs(f1-f2)<1E-12
,两者无法相等因为精度问题,所以两者的差的绝对值小于10^-12即视为相等
//十进制小数转二进制
十进制的小数转换为二进制,主要是小数部分乘以2,取整数部分依次从左往右放在小数点后,直至小数点后为0
例 0.125 0.125*2=0.25 取0| 0.25*2=0.5 取0|0.5*2=1 取1 0.125(十进制)---->0.001(二进制)
二进制的小数转换为十进制主要是乘以2的负次方,从小数点后开始,依次乘以2的负一次方,2的负二次方,2的负三次方等
例 0.001 从小数点后开始 第一位0*2^-1=0 |第二位0*2^-2=0|第三位1*2^-3=0.125 0.001(二进制)---->0.125(十进制)
因此 Android计算器低级错误是二进制的问题
比如 1.2 转换成二进制小数将会得到一个无限循环小数 1.001100110011……而计算机只能保存有效位数有些数字就会丢弃,从而使后续计算发生细微偏差
详见https://www.guokr.com/article/27173/
所以一般计算时不建议使用浮点数类型
//浮点数的内部表达
以编码形式存储,1bit用来存符号11bits用来存指数部分,另一部分用来存分数部分
计算时由专用的硬件部件实现(double 和 float 所用部件一样)
一般情况下用int和double
//char
char 是一种整数,也是一种特殊类型:字符,用单引号表示字符字面量'a''1'' ',用%c输入输出
由于空格占字符scanf时要注意
//逃逸字符
用来表达无法打印出来的控制字符或特殊字符(比如引号),它由一个\开头,后面跟上另一个字符,这两个字符合起来,组成了一个字符
\b 回退一格(backspace键)
\" 双引号
\t 到下一个表格位(tab键)
\' 单引号
\n 换行
\\ 反斜杠
\r 回车
终端shell是一个程序会读取我们的程序hello,它接管我们的键盘控制输入输出,在这期间它会进行一些翻译处理,因而不同的shell会有不同的处理逃逸字符的方式
//类型转化
自动类型转换
当运算符两边出现不一致的类型时,会自动转换成较大的类型
对printf,任何小于int的会被转换成int;float 会被转换成double
但是scanf 不会,要输入short,需要%hd
强制类型转换
用 (类型)值 比如:
(int)10.2 (short)32
注意这时候的安全性,小的变量不总能表达大的变量
(short)32768=-32768 (char)32768=0
强制类型转换的优先级高于四则运算
//逻辑、条件、逗号
逻辑类型bool 要
#include<stdbool.h>
逻辑运算是对逻辑量进行的运算,结果只有0或1
逻辑量是关系运算或逻辑运算的结果
! 逻辑非
&& 逻辑与
|| 逻辑或
!a 如果a是true 结果就是false 反之a是false 就是true
a && b 如果a和b都是true 结果就是true 否则就是false
a || b 如果a和b都是false 结果就是false 否则就是true
优先级 ! > && >|| 双目运算符优先级小于单目运算符(&&<+-*/)
短路:逻辑运算是自左向右进行的,如果左边的结果已经能够决定结果了,就不会做右边的计算
条件运算符
count=(count>20)?count-10:count+10
条件、条件满足时的值和条件不满足时的值
即
if(count>20) count-=10 ; else count+=10;
条件运算符的优先级高于赋值运算符,但是低于其他运算符
可以嵌套,但是不建议使用
逗号运算符:逗号用来连接两个表达式,并以其右边的表达式的值作为它的结果
逗号的优先级是所有的运算符中最低的,组合关系是自左向右,所以左边的表达式会先计算,而右边的表达式的值就留下来作为逗号运算的结果
逗号运算符基本上只在for中使用
for(i=0,j=10;i<j;i++,j--)
//函数
函数最好是单一出口,便于修改
C编译器自上而下顺序分析你的代码,调用函数前一定要把该函数写在前面
LLVM对c编译较严格,gcc相对较差
如果非要把main函数写在前面还要调用后面的函数,要在main之前声明(只写函数头,不写函数体,但要写;)
函数原型:函数头,以分号结尾,可以不写参数名,只写参数类型,目的是告诉编译器这个函数长什么样(声明)
调用函数时给的值与参数的类型不匹配是C语言最大的漏洞,编译器会替你把类型转好,但很有可能不是你想要的类型,C++/Java在这方面很严格
C语言在调用函数时,永远只能传值给函数,每个函数有自己的变量空间,参数也位于这个独立的空间中,和其他函数没有关系
对于函数参数表中的参数叫"形式参数"(现在叫参数),调用函数时给的值叫"实际参数"(现在叫值)
每个函数有自己的变量空间叫本地变量
生存期 :什么时候这个变量开始出现了,到什么时候它消亡了
作用域:在什么范围内可以访问这个变量
对于本地变量,这两个问题的答案是统一的: 大括号内 (称为块)
甚至可以随便拉一对大括号来定义变量,程序运行进入这个块之前,其中的变量不存在,离开这个块,其中的变量就消失了
块外面定义的变量在里面仍然有效,块里面定义了和外面同名的变量则掩盖了外面的(Java就不是)
在执行你的main()之前,编译器会执行一小段程序,所以return 0有意义,main()的return值是给内一小段程序的,然后报告给系统,可以在系统内运行查看,详细代码:
windows:if errorlevel 1....\Unix Bash:echo $?\Csh: echo $status
//数组
使用数组时放在[]中的数字叫下标或索引,下标从0开始计数,数数要从零开始数
编译器和运行环境都不会检查数组下标是否越界,如果运行,越界的数组可能造成问题,导致程序崩溃(segmentation fault)
数组最好也要初始化,遍历一遍(或者偷鸡用int a[100]={0},效果一样)
//数组运算
数组的集成初始化
int a[]={1,1,1,1,1,1,1};
如果
int a[11]={1}//那么只有a[0]=1
C99中还可以给特定下标定位
int a[10]={[0]=2,[2]=3,6}//6会给到2的下一个
sizeof(a)表示数组占多少字节sizeof(a[0])表示单个元素占多少字节
所以用sizeof(a)/sizeof(a[0])表示数组的单元个数
同时
int a[]={1,1,1,1,1,1,1,}
最后一个元素后加不加逗号不影响,加了会显得高级,没有任何区别
要把一个数组的所有元素交给另一个数组,必须遍历(即循环)
使用遍历时通常用for循环,让循环变量i从0到<数组的长度,这样循环体内最大的i正好是数组最大的有效下标
数组作为函数参数时,往往必须再用另一个参数来传入数组大小,数组作为函数参数时不能在[]中给出数组大小,而且传进来的数组不能再利用sizeof来计算数组的元素个数
e.g.
void search(int a[],int length)
int main(){
int b[100];
search(b,sizeof(b)/sizeof(b[0]))
}
//sqrt()
需要<math.h>头文件 头文件查询可以再UNIX中输入man 函数名查询 windows 没有
sqrt(double) 参数返回值都是double 返回的是非负平方根
//二维数组
二维数组行数可以省略列数不能省,逗号是古老传统,省略表示补零,C99可以用定位
int a[][5]={{0,1,2,3,4},{2,3,4,5,6},};
*************************************************指针*******************************************************
重中之重!!!!!!!!!!!!!!!
//*指针
运算符& 作用是获取变量的地址,它的操作数必须是变量
%p 用来输出地址printf("%p",&x); 地址的大小与int是否相同取决于编译器(64(!=)/32(==)位架构)
在堆栈(stack)中自顶向下,所以先定义的变量地址更高(地址通常是十六进制)地址之间相差的数字就是该变量所占字符个数
数组a[10]中 &a=a=&a[0],这三个都是地址且地址相同,且&a[n+1]-&a[n]=类型大小
因为整型不一定可以接收地址,所以我们有了可以接收地址的变量---指针
指针:保存地址的变量
int *p = &i;
p指向了i *是加给变量p的
指针变量的值是具有实际值的变量的地址
调用函数时指针作为参数也要传地址(即&变量) 这样就可以在函数内访问外面的这个变量
e.g.
void f(int*p);
int main(){
int i=0;
f(&i);
}
访问那个地址上的变量 运算符*
*,单目运算符,用来访问指针的值所表示的地址上的变量,可以做左值也可以做右值
int k=*p; *p=k;
所以如果把地址传入其他函数中可以改变该地址所对应的变量的值
互相反作用 *&y--->y &*y---->y
指针作用:
1.交换变量的值,不传入该变量的值,而是传入该变量的地址,从而改变该变量的值
void swap(int *pa,int *pb)
{
int t = *pa;
*pa = *pb;
*pb = t;
}
int main(){
int a=1,b=2;
swap(&a,&b);
}
2. a.函数返回多个值,某些值就只能通过指针返回
b.函数返回运算的状态,结果通过指针返回
注意:指针不要赋初值,有可能崩溃
函数参数表中数组实际上是指针,所以用指针也行
e.g.
void s(int *a)
{
a[0]=1000;
}
int main(){
int d[100];
s(d);
}
其实数组变量是特殊的指针:数组变量本身表达地址,所以
int a[10];int *p=a;//无需用&取地址
但数组的单元表达的是变量,需要&取地址 a==&a[0]
[]运算符可以对数组做,也可以对指针做 p[0]<==>a[0]
同样,* 运算符也可以对数组做,数组变量是const的指针,所以不能被赋值
数组变量是const的指针,所以不能被赋值 int a[]<==>int *const a=··· 常量指针
//以下内容只针对C99
int *const q=&i;
指针是const表示一旦得到了某个变量的地址,不能再指向其他变量
*q=26//OK,对i进行赋值
q++; //ERROR!不能改变q所指向的地址
const int *p=&i;
或
int const*p=&i;
表示不能通过指针去修改那个变量(并不能使得那个变量成为const)
*p =26;//ERROR!(*p)是const
i=26; p=&j; //OK
判断哪个被const了的标志是const在*的前面还是后面
总是可以把一个非const的值转换成const的:当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改
const 数组:数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int,所以const 数组必须初始化赋值
为保护数组值不被函数破坏,可以设置参数为const
int sum(const int a[],int length);
//指针运算
int*q;q++;q实际上加的是一个单元,所以数字上加的是一个sizeof(该指针的类型)
*p=ac[n]==>*(p++)=ac[n++]
因为指针如果指向的不是一段连续的空间,将没有意义
*p++代表取出p所指的那个数据来,完事之后顺便把p移到下一个位置去,常用于数组类的连续空间操作
*的优先级高,但没有++高
遍历指针版
int a[11];
a[9]=-1;
char *p=&a[0];
while( *p!=-1 )
{
printf("%d ",*p++);
}
*p++在某些CPU上可以被直接翻译成一条汇编指令(运行速度快)
指针可以做加减和比较,但记住做计算的是地址,数组中的单元的地址是线性递增的
//0地址
0地址通常是一个不能随便碰的地址,所以指针不应具有0值
因此可以用0地址来表示特殊的事情:返回的指针无效\指针没有被真正初始化
NULL是预定定义符号,表示0地址(最好用NULL,有的编译器不愿意用0表示0地址)
指针也要类型匹配
void*表示不知道指向什么类型的指针,计算时与char*相同
指针也可以转化类型
int *p = &i;void*q=(void*)p;
但这并没有改变p所指的变量的类型,而是让后人用不同眼光通过p看它所指变量(不把它当int而认为它就是个void)
//动态内存分配 C99前定义数组的方式
malloc() 函数 :
需要头文件<stdlib.h>(标准库)
void*malloc(size_t size)
C99:
int a[number];
C99之前:
int *a;
a =(int*)malloc(number*sizeof(int));
参数要的是占据的空间字节数(单元数*单个单元所占字节)
(int*) 是因为malloc()返回值类型是void*要转换类型,而且这里的a既是指针也是数组,在代码最后还要将向系统借的这部分内存空间还回去,即
free(a);
如果申请借的空间失败了则返回0,或者叫NULL
free()函数:
把申请得来的空间还给系统, 并且只能还申请来的空间的首地址
free(NULL)是可以的,因为良好习惯是在定义指针时赋予初值为0(NULL),所以要free的话也是可以的
忘记free()在自己跑代码时是没事的,编译器会将结果的空间数据清除,而在大程序中就会产生未还的空间,使内存爆掉;
//字符串
char word[]={'h','e','l','l','o','\0',};//有'\0'的才叫字符串
字符串是以0(整数0)结尾的一串字符 0和'\0'一样,但和'0'不同
0标志着字符串的结束,但它不是字符串的一部分,计算字符串长度时不包含这个0
字符串以数组的形式存在,但更多以指针的形式访问
<string.h>里有很多处理字符串的函数
char*str="Hello"//表达的是*str指向了一个存放"Hello"的空间
char word[]="hello"//表达这个数组里是"hello"
char a[10]="hello"//10个字节中占据了6个字节,5个字符+‘0’
printf("***********"
"************")
两个相邻的字符串常量会被自动连接起来所以上述printf的输出没问题,还会连接起来
也可以将上一行的末尾"改为\ 然后下一行再顶头写,效果一样
不能用运算符对字符串做运算,通过数组的方式可以遍历字符串
char*str="Hello"中字符串常量被存到了代码段,在地址相对很小的位置,而且该部分是只读的,不能对其进行操作
上面的指针str实际上是const char*str但是由于历史问题,编译器接受不带const的写法
但是试图对str所指字符串做写入会导致严重的后果
所以如果要修改要用char str[]="Hello"对其初始化,再对其修改,实际上这里面的"Hello"也被写入了代码段,但编译器将它拷贝到了正常的位置
构造字符串--->数组 用数组定义字符串,说明字符就在变量数组中,可以进行操作,最后作为本地变量自动回收
处理字符串--->指针 用指针定义字符串,不知道字符串在哪里,可以只用来指着字符串,或者当参数传进函数,也可以用malloc()动态分配空间
%s用来读字符串(读到分隔符(空格,回车,tab)为止)
""是空字符串 但char a[]=""将会使a[0]='\0'该数组大小是1,无法写入字符串
//字符串数组
char *a[]
指的是有一个数组,每个单位里装的是一个指针指向一个字符(串)
程序参数
int main(int argc,char const*argv[])
argc是数组长度,*argv指向命令本身,argv[0]是命令本身,当使用Unix的符号链接时,反应符号链接的名字
//字符串函数
int putchar(int c)
向标准输出写一个字符,返回写了几个字符,EOF(-1)表示写失败
EOF叫做宏
int getchar(void)
从标准输入读入一个字符,返回类型是int是为了返回EOF,
不同系统对宏(EOF)的定义不同,Windows--->Ctrl+Z,Unix--->Ctrl+D,这个输入就代表EOF
在输入回车键之前,shell会执行行编辑,没有进入你的程序,而是停留在shell中
在电脑系统中运行这个程序时,当你输入之后,字符会存入shell的缓冲区中,然后你的程序通过getchar或scanf读入数据,但是他不会停止,因为他只读到了字符结束的字符,程序会重复运行
直到输入Ctrl+Z或Ctrl+D时缓冲区才会填入EOF,程序才会读到EOF,程序才停止运行
而输入Ctrl+C也会停止程序,原理不同,Ctrl+C是直接关掉你的程序,不在缓冲区中填入EOF
//string.h
常用函数
strlen() :
size_t strlen(const char*s); // 返回s的字符串长度(不包括结尾的\0,而sizeof()包括\0) 因为const char*所以不能改变你输入的字符串
int mylen(const char*s){
int idx=0;
while(s[idx] != '\0')
{
idx++;
}
return idx;
}
strcmp() :
int strcmp(const char*s1,const char*s2);
比较两个字符串,返回: 0:s1==s2 差值:s1!=s2
int mycmp(const char*s1,const char*s2){
while (*s1 == *s2 && *s1 != '\0' )
{
s1++;
s2++;
}
return *s1-*s2;
}
strcpy() :
char*strcpy(char *restrict dst(目的),const char*restrict src(源)) //把src的字符串拷贝到dst,restrict表示src和dst不重叠(C99) 返回dst
用来复制一个来自外面的字符串
char*dst=(char*)malloc(strlen(src)+1);strcpy(dst,src); // +1是因为'\0'占一个字符
char*mycpy (char *dst,const char*src){
char*ret=dst;
while( *dst++ = *src++ );
*dst='\0';
return ret;
}
strcat() :
char*strcat(char*restrict s1,const char*restrict s2);
把s2拷贝到s1的后面,接成一个长字符串 return s1 注:s1必须具有足够的空间
char * mycat(char *dst,const char* src){
char*ret=dst;
char*in=dst[strlen(dst)];
while(*in++=*src++) ;
return ret;
}
strcpy() 和 strcat() 都不安全,因为不知道源数组大小,很可能越界,因此有了安全版本:
strncpy() strncat() strncmp()各再传入一个字符串大小
char*strncpy(char *restrict dst,const char*restrict src,size_t n);
char*strcat(char*restrict s1,const char*restrict s2,size_t n);
int strcmp(const char*s1,const char*s2,size_t n);//不是为了安全考虑,只是为了比较字符串的部分大小,可以输入这部分的长短
strchr()和strcchr() :
char*strchr(const char*s,int c);//在字符串中从左边找字符
char*strrchr(const char*s,int c);//在字符串中从右边找字符
c就是要找的字符,返回NULL就没找到,返回的是指针,指向要找的字符
如果要找第二个该字符可以先找到第一个,然后再将找到的地址+1,从新地址继续往后找
char*p= strchr(s,'l');
p=strchr(p+1,'l');
如果想输出该字符之前的字符串,可以将该地址所对应空间改为'\0',再将字符串copy一下就好了
strstr()和strcasestr() :
char*strstr(const char*s1,const char*s2);//在字符串中找字符串
char*strcasestr(const char*s1,const char*s2);//在字符串中找字符串,并且忽略大小写
//枚举
enum 枚举类型名字{名字0,名字1......};
枚举是一种用户定义的数据类型,用关键字enum来声明,枚举类型名字通常并不真的使用,要用的是在大括号里的名字,因为它们就是常量符号,类型是int,值依次从0到n
枚举量可以作为值,枚举类型可以跟上enum作为类型,比如
enum color{...};int eg(enum color c);enum color t;
套路:自动计数的枚举,枚举大括号里最后一项可以用来计数,
enum color {RED,YELLOW,GREEN,NumCOLOR};
这样NumCOLOR就是颜色种类数量,方便后续的遍历和下标操作
声明枚举量的时候可以指定值
enum COLOR {RED=1,YELLOW=9,GREEN}//GREEN的值是10,后一量的值是前一量++
枚举只是int,但即使给枚举类型的变量赋不存在的整数值也不会编译错误
枚举类型实际上不好用,只是在有意义上排比的名字用枚举比const int方便
枚举比宏(macro)好,因为枚举有int类型
//结构类型
//声明结构类型
struct date{ int month; int day;};
注意最后大括号有分号
通常放在函数外面,同样该类型定义变量时也要
struct date n,m;
还可以
struct { int x; int y;}p1,p2; // p1,p2是一种无名结构,里面有x,y
还可以
struct point{ int x; int y;}p1,p2; //p1,p2是point,里面有x,y
结构的初始化 struct data today={7,1}; 或 struct data today={.month=4};没给的值填零
结构和数组有点像,但数组每个单元都是同一类型,而结构可以不是
结构用.运算符和名字访问其成员
要访问整个结构,直接用结构变量的名字,可以赋值、取地址,也可以传递给函数参数
p1 = (struct point){5,10}; //p1.x=5 , p1.y=10
p1 = p2 //p1.x=p2.x , p1.y=p2.y
数组做不到,结构能做到
结构指针 和数组不同,结构变量的名字并不是结构变量的地址,必须使用&运算符
struct date *pDate = &today;
//结构作为函数参数
整个结构可以作为参数的值传入函数,也可以返回一个结构,与数组完全不同
没有直接的方式可以一次scanf一个结构
所以我们可以自己写一个读入函数
struct point getStruct(void)
{
struct point p;
scanf("%d%d",&p.x,&p.y);
return p;
}
int main(){
struct point y;
y=getStruct();
}
但是费空间和时间
如果以结构指针作为参数
struct data *p=&myday;
(*p).month =12;
因为麻烦,所以建立了新的运算符 -> 表示指针所指的结构变量中的成员
因此也可以写成
p->month = 12;
这样getStruct也就可以改写
struct point*inputPoint(struct point *p)
{
scanf("%d%d",&(p->x),&(p->y));
return p;
}
int main(){
struct point y;
inputPoint(&y);
}
同样print 也可以传入指针
void output(const struct point *p)
{
printf("%d%d",p->x,p->y);
}
int main(){
struct point y;
output(*inputPoint(&y))
}
输入完直接输出
const 是为了不修改原本的结构变量
//结构中的结构
struct data datas[100];
struct rectangle
{
struct point pt1;
struct point pt2;
}r;
就有r.pt1.x
如果有 struct rectangle r,*rp; rp=&r;
那么这四种形式就是等价的 r.pt1.x \ rp->pt1.x \ (r.pt1).x \ (rp->pt1).x
但是没有rp->pt1->x 因为pt1不是指针
//%d 和 %i
在 printf 中,%d 和 %i 的行为相同
在 scanf 中,%d 和 %i 的行为不同
%d假设基数为10,而 %i 自动检测基数
因此两个说明符在与输入说明符一起使用时的行为不同
对 %i 而言012是10;对 %d 而言012就是12
%d取整数值作为有符号十进制整数,它接受负值和正值,但值应为十进制,否则将打印垃圾值(注意:如果输入是八进制格式,如012,那么 %d 将忽略 0 并将输入视为 12)
%i取十进制、十六进制或八进制类型的整数值,要输入十六进制格式的值 - 值前面应添加"0x",输入八进制格式的值 - 值前面应添加"0"
//类型定义
自定义数据类型 typedef 来声明一个已有的数据类型的新名字
typedef int Length; //使得Length成为int的别名
Length a,b; Length c[100]; //Length代替int出现在变量定义和参数说明的地方
因此可以简便struct自定义结构的变量定义 typedef + 类型(包括整个struct) + 声明名称
typedef struct ADate{
int month;
int day;
} Date;
或
typedef struct {
int month;
int day;
} Date;
然后就可以
Date d;//------>struct ADate d;
同样也可以定义数组
typedef *char[10] Strings; //Stirngs是10个字符串的数组的类型
//联合
和struct非常相似的union
union AnElt {
int i;
char c;
}elt;
不同点在于union中的i,c占据同一个空间;而struct中每个成员各自占据一个空间
因而union叫做联合,存储时所有成员共享一个空间,同一时间只有一个成员是有效的,union的大小sizeof(union...)是其最大的成员
赋值时如果按顺序赋值后一个会把前一个值冲掉,但是前一个成员仍具有值,值就是新赋予的这个值(因为占据同一个内存空间)
e.g.
typedef union {
int i;
char ch[sizeof(int)];
} CHI;
int main(){
CHI chi;
chi.i =1234;
for(int i=0;i<sizeof(int);i++)
{
printf("%02hhX",chi.ch[i]);
}
}
chi.i=1234;因为输出格式是十六进制(不包括0x),1234转化为0x04D2,所以输出一般是
D2 04 00 00
因为x86CPU是小端,低位在前
因此union可以得到一个int 或 double 的内部字节,进行字节的处理
//程序结构
//全局变量
定义在函数外面的变量,有全局的生存期和作用域,与任何函数无关
__func__输出类型是字符串,表示当前函数的名字
没初始化的全局变量会得到0值, 指针是NULL
全局变量赋值时应该是定值(const或常数),不能是变量或函数
如果函数内部有同名变量,全局变量会被隐藏
//静态本地变量(就是有记忆的全局变量)
在本地变量定义时加上static修饰符,就成为静态本地变量
当函数离开的时候,静态本地变量会继续存在并保持其值
静态本地变量的初始化只会在第一次进入这个函数是做,以后进入函数时会保持上次离开时的值
静态本地变量实际上是特殊的全局变量,它们位于相同的内存区域
静态本地变量具有全局的生存期,函数内的局部作用域
static在这里的意思是局部作用域(本地可访问)
//返回指针的函数
返回回本地变量的地址是危险的,返回全局变量或静态本地变量的地址是安全的
返回在函数内的malloc的内存是安全的,但容易造成问题,最好的办法只返回传入的指针
使用全局变量和静态本地变量的函数是线程不安全的
//编译预处理指令
#开头的是编译预处理指令
他们不是c语言的成分,但是c语言程序离不开它们
//宏定义
#define 用来定义一个宏
#define PI 3.1415926
在Unix系统中输出过程中我们会发现有一个过程
.c->.i->.s->.o->.a.out
.c是我们的源代码,源代码经过编译预处理,产生了预处理产生的一个中间结果文件.i
.i真正由c的编译器做了编译,产生了.s
.s是汇编代码文件,然后汇编代码文件呢去做汇编成了目标代码文件
目标代码文件最后通过连接汇总成a.out,从而让我们看到结果
在.c->.i中原始文本会被替换(即define的东西)
#define <名字> <值>
注意没有结尾的分号,因为不是c的语言
名字必须是一个单词,值可以是任何东西
在c语言的编译器开始编译之前,编译预处理程序(cpp)会把程序中的名字换成值
这样我们定义出来的东西就叫做宏
如果一个宏的之中有其他宏的名字也是会被替换的
如果一个宏的值超过一行,最后一行之前的行末需要加\
宏的值后面出现的注释不会被当做宏的值的一部分,但字符之间的空格会被读进去
没有值的宏:
#define _DEBUG
这类宏是用于条件编译的,后面有其他的编译预处理指令来检查这个宏是否已经被定义过了
预定义的宏:
#define __LINE__ //在源代码中插入当前源代码行号
#define __FILE__ //在源代码中插入当前源代码文件名
#define __DATE__ //在源代码中插入当前编译日期〔注意和当前系统日期区别开来〕
#define __TIME__ //在源代码中插入当前编译时间〔注意和当前系统时间区别开来〕
#define __STDC__ //当要求程序严格遵循ANSIC标准时该标识符被赋值为1。
像函数的宏
#define cube(x) ((x)*(x)*(x)) //注:x没类型 cube(x)是名字 ((x)*(x)*(x))是值
宏可以带参数
带参数的宏的原则:
一切都要括号,整个值都要括号,参数出现的每个地方都要括号
可以带多个参数
#define MIN(a,b) ((a)>(b)?(b):(a))
也可以组合(嵌套)使用其他宏
注 : 没有分号!!!这不是c的语句!!!
可以非常复杂,如"产生"函数 在#和##这两个运算符的帮助下
#:构串操作符
构串操作符#只能修饰带参数的宏的形参,它将实参的字符序列(而不是实参代表的值)转换成字符串常量
##:合并操作符
合并操作符##将出现在其左右的字符序列合并成一个新的标识符
注意:
使用合并操作符##时,自身的标识符必须预先有定义,否则编译器会报"标识符未定义"的编译错误,字符序列合并成新的标识符不是字符串
部分宏会被inline函数代替
//大程序结构
项目: 将几个源代码文件加进一个项目中,DevC++会把一个项目中所有源代码都编译后,连接起来
有的开发环境(IDE)有分开的编译和构建两个按钮,前者是对单个代源码文件编译,后者是对整个项目做链接
一个.c文件是一个编译单元,编译器每次编译只处理一个编译单元
//头文件
把函数原型放到一个头文件(以.h结尾)中,在需要调用这个函数的源代码文件(.c文件)中#include""这个头文件,就能让编译器在编译的时候知道函数的原型
#include是一个编译预处理指令,和宏一样,在编译之前就处理了,它把那个文件的全部文本内容原封不动的插入到它所在的地方,所以也不是一定要在.c文件的最前面#include
#include有两种形式来指出要插入的文件
""要求编辑首先在当前目录(.c文件所在目录)寻找文件,如果没有,到编辑指定的目录去找
<>让编译器只在指定的目录去找
编译器自己知道自己的标准库的头文件在哪里,环境变量和编辑命令行参数也可以指定寻找头文件目录
全局变量可以在多个.c之间共享,在函数或全局变量前面加上static就使它只能在躲在的编译单元中被使用
//声明
extern int i;
是头文件中变量的声明,使i可以共享
声明和定义:声明是不产生代码的东西,包括函数原型,变量声明,结构声明,宏声明,枚举声明,类型声明,inline函数;定义是产生代码的东西
只有声明可以被放在头文件中,否则会造成一个项目中的多个编译单元里有重名的实体
某些编译器允许几个编译单元中存在同名的函数,或者用 weak 修饰符来强调这种存在
在.h文件中运用条件编译和宏,保证这个头文件在一个编译单元中只会被#include一次
#ifndef _LIST_HEAD_
#define _LIST_HEAD_
//内容
#endif
除此之外,
#pragma once//(VS支持)
也能起到相同的作用,但是不是所有编译器都支持
//文件
printf %[flag][width][.prec][hIL]type
flag(标志) | |
+ | 前面要有+或- |
- | 左对齐 |
(space) | 正数留空 |
0 | 0填充 |
可以同时存在(-和0不行,左对齐后没地方填0)
width或.prec(宽度或精度) | |
number | 占据最小字符数 |
* | 下一个参数是字符数 |
.number | 小数点之后的位数 |
.* | 下一个参数是小数点后的位数 |
也可以同时存在
注:number所占据的字符数包括小数点和小数点之后的数字,总共加起来是number个
hIL(修饰符) | |
hh | 单个字节 |
h | short |
l | long |
ll | long long |
L | long double |
type(类型) | |
i或d | int |
v | unsigned int |
o | 八进制 |
x | 十六进制 |
X | 字母大写的十六进制 |
f或F | float,6 |
e或E | 指数 |
g | float |
G | float |
a或A | 十六进制浮点 |
c | char |
s | 字符串 |
p | 指针 |
n | 读入/写出的个数,要用指针读前面的字符数,因此要用&运算符,下一次再输出 |
scanf %[flag]type
flag | |
* | 跳过 |
number | 最大字符数 |
hh | char |
h | short |
l | long,double |
ll | long long |
L | long double |
type | |
d | int |
i | int,可以为10,8,16进制 |
u | unsigned int |
o | 8进制 |
x | 16进制 |
a,e,f,g | float |
c | char |
s | 字符串 |
[..] | 所允许的字符 |
p | 指针 |
[^,]表示到,之前的所有字符都属于我要读入的部分
scanf()和printf()有返回值 分别代表读入的数据和输出的字符(字符数包括结尾处的'\n')
除了printf()外puts()可以直接输出字符,并且比printf快
读入文件以EOF结尾需要判断时,要定义一个变量来存scanf()的返回值,如果返回值是EOF就结束
//底层操作
文件输入输出>输入<输出 叫做程序运行时的重定向,但这是在Unix中运行
一般方式:FILE 是个结构
FILE*fopen(const char*restrict path,const char*restrict mode);
int fclose(FILE*stream);
fscanf(FILE*restrict File,const char*restrict format,...)
fprintf(FILE*restrict File,const char*restrict format)
打开文件的标准代码
FILE* fp=fopen("file","r");//file是文件名,r用来读
if(fp){ //如果是NULL就无法打开
fscanf(fp,....);
fclose(fp);
} else{
....;
}
e.g.
int main()
{
FILE *fp=fopen("12.in","r");
if(fp){
int num;
fscanf(fp,"%d",&num);
printf("%d\n",num);
fclose(fp);
}
else
{
printf("无法打开文件\n");
}
return 0;
}
fopen()中后面参数
r 打开只读
r+ 打开读写,从文件头开始
w 打开只写,如果不存在则新建,如果存在则清空
w+ 打开读写,如果不存在则新建,如果存在则清空
a 打开追加, 如果不存在则新建,如果存在则从文件尾开始
..x 只新建,如果文件已存在则不能打开
fwrite(const void*restrict Str,size_t size,size_t Count,FILE*restrict File);
变量名解释:fwrite(写进的数组,写的数据大小,有多少个这样的数据,写到的文件);
//二进制文件
其实所有的文件最终都是二进制的
文本文件无非是用最简单的方式可以读写的文件
more/tail打开文件 cat读写 vi完整编辑
而二进制文件是需要需要专门的程序来读写的文件,文本文件的输入输出是格式化,可能经过转码
Unix喜欢用文本文件来做数据储存和程序配置
交互式终端的出现使人们喜欢用文本和计算机talk
Unix的shell提供了一些读写文本的小程序,Windows喜欢用二进制文件
DOS是草根文化,并不成继承和熟悉Unix文化
PC刚开始的时候能力有限,DOS的能力更有限,二进制更接近底层
唯美的优势是方便人类读写,而且跨平台;缺点是成语输入输出要经过格式化,开销大
二进制的缺点是人类读写困难,而且不跨平台(Int的大小不一致大小段的问题);二进制的优点是程序读写快
程序为什么要文件?
1.配置 :Unix用文本,Windows用注册表
2.数据 :稍微有点亮的数据都放数据库
3.媒体 :这个只能是二进制的
现实是,程序通过第三方库来读写文件,很少直接读写二进制文件了
//二进制读写
size_t fread(void *restrict ptr,size_t size,size_t nitems,FILE*restrict stream);
size_t fwrite(const void *restrict ptr,size_t size,size_t nitems,FILE*restrict stream);
参数解释: (要读写的内存的指针, 内存的大小,有几个这样的内存, 文件的指针) 返回的是读写了几个字节
nitem(项目)的原因:因为二进制文件的读写一般都是通过对一个结构变量的操作来进行的,于是nitem就是用来说明这次读写几个结构变量
sprintf()向字符串输入输出 sprintf(数组(用来存字符串),格式,量)
//在文件中定位
long ftell(FILE*stream);//返回值是文件大小
int fseek(FILE*stream,long offset,int whence);
参数解释:文件中找的指针,移动多少,从哪开始找
whence:
SEEK_SET //从头开始
SEEK_CUR //从当前位置开始
SEEK_END //从尾开始(倒过来)
读出时用fread();
这样的二进制文件不具有可移植性,在int为32位的机器上写成的数据文件无法直接在int为64位的机器上正确读出
解决方案之一是放弃使用int,而是使用typedef具有明确的大小的类型
更好的方案是使用文本
现在大部分不再用二进制直接读入,而是使用第三方软件或是直接使用文本
//头文件
#ifdef _STUDENT_H
#define _STUDENT_H
const int STR_LEN =20;
typedef struct _student{
char name[STR_LEN];
int gender;
int age;
}Student;
#endif
//录入学生数据
#include<stdio.h>
#include"student.h"
void getList(Student aStu[],int number);
int save(Student aStu[],int number);
int main(int argc,char const *argv[])
{
int number = 0;
printf("Please enter the amount of students:");
scanf("%d",&number);
Student aStu[number];
getList(aStu,number);
if(save(aStu,number)){
printf("save successfully\n");
}else{
printf("save unsuccessfully\n");
}
return 0;
}
void getList(Student aStu[],int number)
{
char format [STR_LEN];
sprintf(format,"%%%ds",STR_LEN-1);
int i;
for(i=0; i<number ;i++){
printf("The %dth student is:",i);
printf("\tName:");
scanf(format,aStu[i].name);
printf("\tGender(0-male 1-female 2-other) :");
scanf("%d",&aStu[i].gender);
printf("\tAge:");
scanf("%d",&aStu[i].age);
}
}
int save(Student aStu[],int number)
{
int ret = -1;
FILE *fp=fopen("student.data","w");
if(fp){
ret = fwrite(aStu,sizeof(Student),number,fp);
fclose(fp);
}
return ret == number;
}
//读取学生数据
#include<stdio.h>
#include"student.h"
void read(FILE *fp,int index);
int main(int argc,char const *argv[])
{
FILE *fp=fopen("student.data","r");
if(*fp){
fseek(fp,0L,SEEK_END);
long size = ftell(fp);
int number = size / sizeof(Student);
int index =0;
printf("There are %d data(s),which do you want to see: ",number);
scanf("%d",&index);
read(fp,index);
fclose(fp);
}
return 0;
}
void read(FILE *fp,int index)
{
fseek(fp,index*sizeof(Student), SEEK_SET);
Student stu;
if( fread(&stu,sizeof(Student),1,fp) == 1)
{
printf("The %dth student is:",index+1);
printf("\tName:%s\n",stu.name);
printf("\tGender:");
switch(stu.gender){
case 0: printf("Male\n");break;
case 1: printf("Female\n");break;
case 2: printf("Other\n");break;
}
printf("\tAge: %d\n",stu.age);
}
}
//位运算
位运算的运算符:
& | 按位的与 |
| | 按位的或 |
~ | 按位取反 |
^ | 按位的异或 |
<< | 左移 |
>> | 右移 |
//按位运算
//&
如果(x) ==1 且 (y) ==1 那么(x & y) =1 否则 (x & y) = 0
&是个运算符,运算遵从上述规则,对于二进制来说1&1=1/0&1=0/0&0=0 每一位都要逐个运算 所以0101 1010&1000 1100=0000 1000
按位与常用于两种应用:
1.让某一位或某些位为0: x & 0xFE
2.取一个数中的一段: x & 0xFF
0xFE转化二进制是1111 1110所以&运算时就是把末尾变成0,其余位置的数字保持不变
0xFF是1111 1111 对于一个32位的int做&运算,这样就只保留最后8位的值,前面的全部是0
// |
如果(x) ==1 或 (y) ==1 那么(x | y) =1 否则 (x | y) = 0
那么1|1=1/0|1=1/0|0=0
按位或常用于两个应用:
1.使得一位或几个位为1: x | 0x01
2.把两个数拼起来: 0x00FF | 0xFF00 = 0xFFFF
// ~
(~x) = 1 - (x)
~1 = 0 / ~0 = 1 和补码不同,补码是减法(-1=258的原因),而~是每个比特都取反 ~1010 1010=0101 0101 补码是1 0000 0000 - 1010 1010=1010 1010
注意:位运算的结果是int
//逻辑运算vs按位运算
对于逻辑运算,它只看到两个值: 0 和 1
可以认为逻辑运算相当于把所有的非0值变成1,然后做按位运算
5 && 4 -> 1 & 1 ->1 5 || 4 -> 1 | 1 ->1 ! 4 -> !1 -> 0
//^
如果(x) == (y) 那么(x ^ y) =0 否则 (x ^ y) = 1
如果两个位相等,那么结果为0;如果两个位不相等,那么结果为1
对于一个变量用同一个值异或两次等于什么也没做 x^y^y = x(加密运算)
// 移位运算
//<<
i << j i中所有的位向左移动j个位置,而右边填入0
所有小于int的类型,移位以int的方式来做,结果是int
其中
x <<= 1 // x *= 2
x << n //x *=2^n
//>>
i >> j i中所有的位向右移动j个位置
所有小于int的类型,移位以int的方式来做,结果是int
对于unsigned 的类型,左边填入0
对于signed的类型,左边填入原来的最高位(保持符号不变)
x >>=1 //x /= 2
x >>=2 //x /=2^n
注:移位的位数不要用负数
// 位运算例子
输出一个数的二进制
// 输出一个数的二进制
#include<stdio.h>
int main(){
int number;
scanf("%d",&number);
unsigned mask = 1u << 31;//1000 0000 0000 0000 0000 0000 0000 0000 unsigned int的最大数字
for(;mask;mask >>=1){ //每次算完后mask的1向后移动一位
printf("%d",number & mask ?1:0); //mask有1的那一位去取number的值,其余位为0,如果是1就输出1,否则输出0
}
printf("\n");
return 0;
}
对单片机的操作
想要对单片机的寄存器某一位做手脚
//MCU的SFR(特殊功能寄存器) 寄存器地址对应变量为UOLCR
const unsigned int SBS= 1u<<2; //0100
const unsigned int PE = 1u<<3; //1000
UOLCR |= SBS | PE; // SBS | PE=1100,和ULOCR做|运算就是将第二三位改为1
ULOCR &=~SBS; //~SBS=1011 当然没显示出来的前面28位也为1,作用就是把第三位清0
ULOCR &=~(SBS | PE); //同上,将第二三位全清0
//位段
把一个int的若干位组合成一个结构
struct U0{
unsigned int leading : 3;//数字代表该变量占几个比特
unsigned int FLAG1 : 1;
};
//实例
struct U0{
unsigned int leading : 3;
unsigned int FLAG1 : 1;
unsigned int FLAG2 : 1;
int trailing : 11;
};
void ptrbin(unsigned int number){
unsigned mask = 1u << 31;
for(;mask;mask >>=1){
printf("%d",number & mask ?1:0);
}
printf("\n");
}
int main(){
struct U0 uu;
uu.leading = 2;
uu.FLAG1 = 0;
uu.FLAG2 = 1;
uu.trailing = 0;
printf("sizeof(uu)=%lu\n",sizeof(uu));
prtbin(*(int*)&uu);//先取uu的地址,地址是指向U0的指针,再强制转化为指向int的指针int*,再取其指向的int
return 0;
}
输出为:
sizeof(uu)=4 //一个int的大小,因为struct 里面加起来不超过一个int
0000 0000 0000 0000 0000 0000 0001 0010
可以直接用位段的成员名称来访问任何一个比特
比移位、与、或还方便
编译器会安排其中的位的排列,不具有可移植性
当所需的位超过一个int的时候,会采用多个int
//链表
//可变数组
自定义的数组实现可变数组:1.可以长大 2.知道它的确切大小 3.能够访问它的单元
先自定义一些函数
Array array_creat(int init_size); //创建数组
void array_free(Array*a); //回收数组所占空间
int array_size(const Array*a); //数组中的可用单元
int *array_at(Array*a,int index); //访问单元
void array_inflate(Array *a,int more_size); //让数组长大
情境: 读入一串数字,不知道有多少,文件读到EOF为止
#ifndef _ARRAY_H_
#define _ARRAY_H_
Array array_creat(int init_size);
void array_free(Array*a);
int array_size(const Array*a);
int *array_at(Array*a,int index);
void array_inflate(Array *a,int more_size);
#endif
#include"array.h"
#include<stdio.h>
#include<stdlib.h>
const BLOCK_SIZE = 20; //为了省空间
Array array_creat(int init_size)
{
Arry a;
a.size = init_size;
a.array = (int*)malloc(sizeof(int)*a.size);
return a;
}
void array_free(Array*a)
{
free(a->array);
a->array = NULL;
a->size =0;
}
//封装
int array_size(const Array*a)
{
return a->size;
}
int *array_at(Array*a,int index)
{
if( index >= a->size)
{
//array_inflate(a,index-a->size+1);//不划算,每次+1 都要copy一遍,浪费空间
array_inflate(a,(index/BLOCK_SIZE+1)*BLOCK_SIZE-a->size); //运用BLOCK,每次+BLOCK,不够再加,节省空间
}
return &(a->array[index]);
}
void array_inflate(Array *a,int more_size) //memcpy()库函数效率会更高
{
int *p=(int*)malloc(sizeof(int)*(a->size+more_size));
for(int i=0;i<a->size;i++)
{
p[i] = a->array[i];
}
free(a->array);
a->array = p;
a->size += more_size;
}
int main(int argc,char const *argv[])
{
Array a = array_creat(100);
printf("%d\n",array_size(&a));
*array_at(&a,0) = 10;
printf("%d\n",*array_at(&a,0));
int cnt = 0;
do
{
int ret = scanf("%d",array_at(&a,cnt++));
}while( ret != EOF);
array_free(&a);
return 0;
}
可变数组的缺陷:
copy和malloc 花费时间和空间
还有可能内存里不再有空间够我们申请
于是出现了链表,我们申请一块BLOCK内存,再将它与原数组连接起来,还不用copy了
//链表(单向)
我们将原先申请的空间分成两部分,一部分用来存数据,另外用来当指针指向下一个同样的空间
这样的东西叫做链表(linked list) 每一块叫做结点
#ifndef _NODE_H_
#define _NODE_H_
typedef struct _node{ //结点
int value; //数据
struct _node *next; //指向下一个的指针 不能用 Node*next的原因是系统还没读到那一行
}Node;
#endif
#include<stdio.h>
#include<stdlib.h>
#include"node.h"
int add(Node*head);
void print(List *pList);
typedef struct _list{
Node* head;
//Node* tail;
}List;
int main(int argc,char const *argv[])
{
List list;
list.head = NULL;
do
{
int ret = add(&list);
}while( ret != EOF);
print(&list);
int number;
scanf("%d",&number);
Node* p;
int isFound = 0;
for(p=list.head ;p;p=p->next){
if(p->value == number){
printf("Find it\n");
isFound = 1;
break;
}
}
if(!isFound){
printf("Not found\n");
}
Node *q;
for(q=NULL,p=list.head ;p;q=p,p=p->next){
if(p->value == number){
if(q)
{
q->next = p->next;
}else{
list.head = p->next;
}
free(p);
break;
}
}
for( p=head;p;p=q)
{
q = p->next;
free(p);
}
return 0;
}
void print(List *pList)
{
Node *p;
for(p=pList.head ;p;p=p->next)
{
printf("%d\t",p->value);
}
printf("\n");
}
int add(List*pList)
{
Node *p=(Node*)malloc(sizeof(Node));
int ret = scanf("%d",&p->number); //add to linked list
p->next = NULL;
// find the last
Node *last = pList->head;
if(last){
while( last->next )
{
last = last->next;
}
// attach
last->next = p;
}else{
pList->head = p;
}
return ret;
}
The End
感谢阅读,翁凯老师的课程真的好,建议反复观看
链接:C语言程序设计_中国大学MOOC(慕课) (icourse163.org)
多多三连!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!