C语言复习笔记
注:笔记中用到了一套本人独创的类比方法帮助理解,一定能提供一些理解上的帮助
一.打开c语言的大门
开始学习c语言时,我们在编译器中敲出了第一个c程序,正式打开了c语言学习的大门
1>hello,world
#include<stdio.h>
int main()
{
printf("Hello World");
return 0;
}
根据这个程序,我们开始对其展开了学习,这个程序中一共有2个部分[1]头文件[2]主函数,据此进行展开学习
2>什么是头文件
在程序的最上部分就是 #include <stdio.h> ,它的作用是就是预处理,意思是告诉c编译器它可能会使用的“工具”,(就好比是在进行正式工作前准备好相应的工具,需要剪切就要剪刀,需要粘接就要胶水,头文件就好比是工具包,里面放着一类的工具(函数等) ,在工作中可以随时拿出来使用)
etc.常用的stdio.h头文件中有用于输入输出的函数,printf,scanf,gets,fgets等等常用的函数就来自于它
3>什么是主函数
一个C程序有且只有一个主函数,即main
函数
在c程序中,main函数必不可少,可以说c程序执行的就是主函数中的程序
main函数与后续学到的函数也一样(后续提到)
在我们的Hello World程序中,主函数中的程序就是 printf("Hello World"); 它的功能就是在屏幕上输出指定的信息,即Hello World
4>注意事项
初接触c语言,我们多少会有些不适应,有一些常犯的错误需要注意
在c程序编译过程中,使用的符号均为英文半角符号,不能使用中文符号
每条执行语句后都要加分号;
主函数的{}必须完整,在{}内为c程序,不可出现缺失
二.常量,变量与数据类型
在编程中,不可避免要与数据打交道,在他人看来数据就是冷冰的,但是在编译者的眼里,他们都是有生命的,他们有名有姓,富有变化
1>数据类型
数据既然是有生命的,与生命一样,他们也有“种族”(后不做引号),从外表看来,他们有的是数字,有的则是字符,这只是最外表的特征,对其还可以细分
每个种族都有一定的共性,比如房屋的样式(所占字节数),图腾(类型名)
###
2>基本类型
基本类型分为整型,字符型,实型(即浮点型)
即使如此,,每种类型也有许多个小的分支,比如整型有int,short int,long int, unsigned int, unsigned short int,unsigned long int,他们都属于整型这个大家族,但是他们自立门户,他们有自己的图腾,有自己的房屋类型,各自有各自的特点,在不同的地方都发挥着作用
本节由于初次接触,只对基本数据类型进行展开
[1]整型
整型顾名思义就是整数的类型
名称
占字节数
取值范围
int
4
-2^31~2^31-1
short int
2
-2^15~2^15-1
long int
4/8
-2^31~2^31-1
unsigned int
4
0~2^32
unsigned short int
2
0~2^16-1
unsigned long int
4/8
0~2^32-1
[2]浮点型
浮点型即为带小数点的数据的类型
单个字符占一个字节
名称
类型名
取值范围
单精度浮点型
float
约±(10^-38 ~ 10^38)
双精度浮点型
double
约±(10^-308 ~ 10^308)
单精度与双精度的区别即为精度不同,双精度所能表示的精度更高,但作为代价,它所占用的内存也就变大了
[3]字符型
字符型数据即为char类型,它能代表一个字符,故也只占用一个字节
3>变量
变量就是可以变化的量,他们就是一个个的生命体,他们来自于不同的种族,有着自己独特的名字,要赋予一个新生命,首先这个需要知道这个生命的种族,名字(也就是在声明变量时需要指明变量的变量名与类型),每个生命也都占用着这个世界的资源(每个变量都占用着一定的位置)
变量也都有代表数据的意义,比如赋a为1,则a变量则代表1;
下面的程序就是一个变量的诞生
int a;
a=1;
在第一段程序中,我们定义了名为a的变量,它是int类型(当然我们也可以在声明变量的时候直接赋予它数据)
注:变量的起名也是要遵循规则的
- 标识符可以是字母
(A~Z,a~z)
、数字(0~9)
、下划线_
组成的字符串,并且第一个字符必须是字母或下划线。 - 标识符是严格区分大小写的。
- 标识符最好选择有意义的英文单词组成做到"见名知意",不要使用中文。
- 标识符不能是C语言的关键字。
4>常量
常量则是不可改变的量,常见比如1 ,2,a,b这样的,其也可以由标识符来表示,成为符号常量,也需要先进行定义。
#define a 123;
以上代码即为符号常量的定义,在后续中a的意义与123完全等同
5>格式化输出语句
格式化输出语句是将各种类型的数据按照格式化后的类型及指定的位置从计算机上显示。
如
printf("%d",a);
即将a以整型输出,以下是常用的格式符
6>类型转换
多种的数据类型给c的呈现起了很大的作用,但也造成了在相互联系上的不便,于是需要类型转换以便使用
数据类型存在自动转换的情况.
自动转换发生在不同数据类型运算时,在编译的时候自动完成。(字节小的可以向字节大的自动转换,但字节大的不能向字节小的自动转换)
当然也可以手动强制转换
(int)(a);
如上即为强制转换,强制转换不会改变原有的数据类型,只是临时的转变
7>输入
在这里穿插一个实用的函数scanf,他有一定的弊端,但一定会陪你走很长一段路程
来使用一次试试看
int a;
scanf("%d",&a);
通过scanf这个操作,你就能让用户在执行终端输入一个值,并将这个值赋给a,句中的&是取地址符,后面会解释到
三.运算
既然是处理数据,自然避免不了运算,c语言的运算也有着它的特点
1>运算符
运算符即为数学运算,需要注意的是整数的运算只能取整,%是取余数,++,--是特殊的运算符,它对原数据进行自我处理
如a++与++a,都是对原数据+1,但两者并不相同,a++是先取用后加,而++a是先加再进行取用。比如a=1,a++仍然代表1,但++a代表的是2
自加自减也可以通过+=来实现,如a+=1就相当于a=a+1,当然乘除也同样适用
2>关系运算符
关系运算符比较容易理解
如图所示,就是><=!的使用与组合使用,如果是则输出1(真),否则输出0(假)。
3>逻辑运算符
逻辑运算符的使用除外表外与高中相同,稍加理解便可
4>运算符优先级
这么多种运算符,不可避免会同时出现,所以也得有他们的法律才行,于是便有了优先级
四.流程控制(应用实例置于附录)
要控制一个程序,当然需要一些控制语句
1>if条件句
字面意思,if是对判断结果的流程控制,没错if结构的格式如下
if(条件)
{
执行程序
}
如果()中的条件成立则执行{}内的程序,非常容易理解,当然,如果不成立也要能有结果才行,而且只有一个条件的判断也不能满足所有情况所以就有了else与else if
if(条件)
{
执行程序
}
else if(条件)
{
执行程序
}
else
{
执行程序
}
2>while与do while循环
也很好理解,格式如下
while
while(条件)
{
执行程序
}
如上所示,当while后的()中条件成立时执行{}中的程序,直到不满足之后再跳出(当然配合break语句还有其他效果)
do while
do
{
执行程序
}while(条件);
该结构与while的最大区别是先进行程序的执行再进行判断,所以他至少执行一次程序才能跳出循环
注:do while条件后有分号
3>for循环
与其他结构相比,for循环相对抽象,但也不难理解
for(初始化循环变量;条件;对循环变量进行操作)
{
执行程序
}
for中有三个表达式,第一个是对循环变量初始化,然后根据第二个的条件进行一次判断,最后再对循环变量进行操作,如下
for(int i=0;i<10;i++)
{
printf("1");
}
它的作用就是循环10次,每次执行printf这个指令,也就是打印十个i
4>switch
switch(选择变量)
{
case 情况1:
执行程序
break;
case 情况2:
执行程序
break;
default:
执行程序
break;
}
很容易理解,switch就是一个选择性的控制语句,也就是当变量取某个值的时候就会执行相应的程序,若无则执行default下语句
注:1.每个case下都应有break语句不然就会进入下一个情况继续执行
2.多个case可以对应一个程序执行语句
3.case后式子只能是整型或者浮点数
5>continue与break语句
这两个语句能让你更加自如地控制流程的结束,十分有用,但要注意其执行对象是哪一层循环
continue语句的作用是结束本次循环开始执行下一次循环
break是跳出当前整个循环
6>臭名昭著的goto语句
这种结构非常灵巧,可以随意在程序中跳转,但很遗憾是在程序量大后很容易造成程序的杂乱,故在此不做介绍
五.函数
使用函数可以让重复动操作简单化,c语言的库中提供了我们很多的函数使用,但也无法满足我们的某些个性需要(如某种特殊的计算),于是我们可以自创函数
1>函数定义
如同变量,我们自定义的函数同样需要定义后才能使用
int fun()
{
printf("hello");
return 0;
}
如上便是一个函数的定义格式 即
[函数类型] 函数名 (参数)
{
return 返回值;
}
由此我们对参数与返回值这两个新名词(此前主函数出现并未解释)出现了疑惑,如下进行解答
[1]参数
参数即参与函数过程的数据,在定义时应将参数类型也同时定义清楚,参数会参加函数的使用过程 注:函数也可以没有参数
在定义函数时定义的参数(在函数内容中直接使用的参数)为形式参数,也叫形参,形参只在函数内部执行时有效
而在调用时传入的参数为实际参数,即实参
[2]返回值
返回值是函数调用后返回的数据,返回数据的类型就是函数的类型(如int类型函数返回的数据类型也是int),若没有返回值,则函数类型为void,返回值最后被赋函数体本身
2>函数调用
函数可以相互调用,意思是不只是主函数,在其他自定义函数中也可以使用自定义的函数,这为我们带来了极大的便利,若有参数的函数则需要传入参数
3>递归函数
函数也可以自身调用,由此特性我们可以对其进行设计构成递归结构
执行递归函数将反复调用其自身,每调用一次就进入新的一层
如同梦中梦,无限套娃(参考孙贺洋学长的讲解)在我们醒来时,回忆起梦中事物,一定是从最近的梦,也就是从最深层的梦境来回想,然后一层一层向外剖析,直至最外一层(((((((最深))))))),正如解好多层括号的表达式,从外层向内层无法计算,只能从内向外地计算。
注:递归函数必须有结束条件
4>变量与的局部与全局,静态与动态
变量的局部与全局
局部变量是在函数内作定义说明的。其作用域仅限于函数内。即在函数内定义的变量只能在函数内使用,出函数外则不能使用。
全局变量在程序最开始定义,不属于任何一个函数内,随时可以进行使用
变量的静态与动态
全局变量就是一种静态变量,是在程序运行期间都占据内存的变量
而动态变量则是会动态分配内存的变量,如函数的形参,在使用结束内存后就会被释放
函数的内部与为外部
与变量类似,不能被其他源文件调用的函数称谓内部函数,由static关键字来定义,所以也叫静态函数
定义格式为 static [数据类型] 函数名([参数]),反之则为外部函数,定义格式为extern [数据类型] 函数名([参数])
外部函数不需要特意说明,未加说明的函数则为外部函数
六.数组
终于到了数组,数组是我自我感觉c语言中最好用的功能之一(当然它的根源指针更好用)
1>定义数组
首先便是数组的定义了,如下例
int arr[3]={111,222,333};
没错,数组定义格式就是 数据类型 数组名称[长度];
数据类型决定了数组内储存的所有的数据的类型,注意这里,数组是有长度的,如果不关注数组的长度,会导致数据溢出,是非常危险的行为!值得一提的是,数组的长度不需要严格相同,长一点也不会导致错误,因为他们都会被一同初始化
2>数组的遍历
[1]访问数组
既然将数据存入了数组,该怎么取出并使用呢,当然有办法,若为上面程序中,要取用111这个数据,便可以直接使用arr[0]来访问,其中0是第一个数据的下标,通过这种方法便可以访问数组中的每一个元素
[2]进行遍历
如果我想知道数组中都有什么,那如何将数组中的数据一个个都遍历一遍呢,没错,你一定想到了for循环
这里有一个很实用的方法获取循环的次数,循环的次数就是数组的长度,所以通过sizeof函数获取数组占用的空间与数组中单个元素占用的空间便可以轻易获取,即长度n=sizeof(arr)/sizeof(arr[0])
所以通过
int n=sizeof(arr)/sizeof(arr[0]);
for (int i=0;i < n;i++)
{
printf("%d ",arr[i]);
}
看,是不是很妙,现在你就输出了数组中的所有元素(注:自c99,在for中可以定义变量了)
3>字符数组
这里我要把字符数组单独拿出来的原因就是它解决了c语言中字符串的表达问题,你就能据此实现很多实用的功能,比如适应期内让做的商店系统与词典,那时候你意识到自己已经能写出如此实用的小程序了,那种成就感绝了
你只需要定义一个字符数组,然后你就得到了由它们组成的字符串
这里引入一些字符串操作的函数(头文件为string.h)
4>二维数组与多维数组
在这里又显示了数组的实用性,直接开始展示,就可以体会到
二维数组
int arr[2][2]={{1,1}{1,1}};
没错,二维数组就是一个以数组为元素的数组,你可以把它理解成一个2*2的矩阵或者表格,有行列之分
又值得一提的是二维数组第一个括号内的长度可以不写,c语言会自己匹配长度
上面的数组可以直接用arr[0]来访问第一个数组,所以你现在也会用这种方法访问字符串了对不对
三维数组就是以二维数组为元素的数组,以此类推,是不是很简单
七.指针
指针绝对算得上是c语言中最晦涩难懂的知识点之一了,但它也绝对是最实用的工具之一
指针是一种特殊的变量,它可以指向一个地址,也就是说它储存着一个地址,这个地址是它指向的变量的地址
这样解释肯定十分生硬,不如这样想
还记得变量是“活的”吗,没错,他们既然是活着的,就有自己的住所,与我们一样,有住所肯定就有地址,不过他们比较”独立“,他们每个人都有自己的房子,所以他们的地址就成为了他们的id,就像我们一样,他们的XX省XX市XX镇XX街XX楼XX号住着的变量一定是唯一的,所以你找到了它的地址就相当于找到了它,但是,这只是储存他的地方,终归不是他,所以就用到了解地址符,指针就像是一种特殊的信息系统中的一对一情报员,通过解指针就能找到地址的主人。没错,是不是有点懂了?
指针本身就是一个情报员,一个变量,所以他也有自己的类型,不过他的类型就与他的职业有关,没错,如果他是指向int型的指针,他的类型就是int*,也就是他的独特类型,意思就是指向int的指针
指针代表的值不是他指向的值,而是他指向的值的地址。
指针运算>指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。它是以单元为单位的,比如说一个指向int的指针,它+1时是后移了四个字节
指针符号>&是取地址运算符,*是间接运算符
!!数组和指针的关系!!
这里要单独讲数组与指针的联系,因为数组功能的实现本身就依赖于指针,数组的数组名其实可以看作一个指针。例如*arr与arr[0]其实是一样的
!指针在实际开发中有很大的作用,但在基础阶段可能会觉得它抽象且可有可无,但一定要进行理解以便以后使用。
八.结构体
c语言有很多的数据类型,这很方便,但还不够方便,于是便有了结构体
结构体是用户可以自定义的数据类型,用户可以自由组合不同的类型于一个结构体中
1声明结构体
同样,结构体也需要声明,在声明后就可以将结构体直接使用
struct Student{
char name[20];
int num;
float score;
};
上述过程虽然建立了一个结构体,但还未创建实际的对象进行使用,这里可以单独拉出来像定义变量一样定义,也可以直接在定义结构体时直接在}与;之间直接定义。
2>访问结构体
结构体的访问也十分简单,只需要在建立的结构体对象后加点即可,比如我们创建了对象student tom,那么tom.name就是tom
结构体与数组的结合在应用时也能起到1+1>2的效果
九.文件操作
也许你在做字典小程序的时候就很不爽,为什么新加词进去下一次又没有了呢,这无疑让字典的实用性大打折扣,那究竟怎么实现呢,当然是把它储存在外部文件中啦,这样的操作其实并不难掌握,接下来为你介绍。
1>文件的打开与关闭
如何把大象塞进冰箱?第一步当然是打开冰箱门,没错,想要在文件里实现操作,当然需要打开文件。
在此之前其实还有一个至关重要的操作,就是找到冰箱,hhh,没错,你得有一个用来放大象的冰箱,在这里我们用指针来指定文件
FILE* fp;
fp=fopen("test.txt", "r");
执行文件操作
fclose (fp);
首先定义一个FILE*类型的指针,通过它我们就创建了一个文件信息区
然后使用fopen函数,第一个参数显而易见就是打开指定文件,通常需要访问文件夹来找到文件,第二个参数就是对文件操作的权限
"r"
打开,为了只读 。如果打开文件不存在,报错。
"w"
为了只写 如果文件存在,会将原来内容销毁,然后在写,没有就会在文件名下开辟
"a"
为了追加,不会将内容摧毁的条件下写。
以上展示了三种常用的使用方式
最后使用了一次fclose指令,为了关掉文件,这个指令与fopen就像是双胞胎,要同时使用以保证正常
2>fputc,fputs
要做到写入文件,还需要几样工具才行
fputc('c', fp);
fputs("ABCDEF", fp);
比如上述第一行,就是把c字符存入文件中
第二行同理,就是把这个字符串写入
3>fgets,fscanf,sscanf
[1]fgets,fscanf就像gets与scanf没有太大区别,只是多了文件的操作
fgets(arr, 255, pr);
fscanf(pr, "%s", c);
就像这样,fgets在文件中读取出一行然后赋值给一个变量,中间的数是读取的长度
[2]sscanf
sscanf是一个十分好用的函数,在我们的作业中就经常用它来拆分字符串,其中*是它一个贪婪性符号指令,它的作用是除了 * 所指代的内容通通读取,其中sscanf("zhou456 hedf", "% [ ^ ]", str)这样的指令取到指定字符为止的字符串,这样看起来比较抽象,所以下面用一个例子来看
sscanf("20140014011001,王锐,男,19,机械工程1401","%*[^,],%*[^,],%[^,]",sex);
看,上述使用能将这句字符串中单独取出代表性别的信息,是不是很好用
!!!!!!附录!!!!!!
用while与if语句实现1加到100求和
#include <stdio.h>
int main()
{
int i = 1, sum = 0;
while ( i<=100 )
{
if (i % 2 == 0)
{
sum = sum + i ;
printf("%d是2的倍数,目前和为%d\n", i,sum);
}
else
{
goto step;
}
step:i++;
}
printf("100以内数的和为:%d\n", sum);
return 0;
}
用for循环打印星号金字塔
#include<stdio.h>
int main()
{
int i,j,k;
for(i=1;i<5;i++)
{
for(j=4-i;j<5-i;j++)
{
printf(" ");
}
for(k=0;k<2i-1;k++)
{
printf("*");
}
printf("\n") ;
}
return 0;
}
用递归函数实现经典猴子偷桃问题
#include <stdio.h>
int getPeachNumber(int n)
{
int num;
if(n==10)
{
return 1;
}
else
{
num = (getPeachNumber(n+1)+1)*2;
printf("第%d天所剩桃子%d个\n", n, num); //天数,所剩桃子个数
}
return num;
}
int main()
{
int n;
int num = getPeachNumber(1);
printf("猴子第%d天摘了:%d个桃子。\n", n, num);
return 0;
}