C 语言是一种通用的、面向过程式的计算机程序设计语言。1972 年,为了移植与开发 UNIX 操作系统,丹尼斯·里奇在贝尔电话实验室设计开发了 C 语言。
C 语言是一种广泛使用的计算机语言,它与 Java 编程语言一样普及,二者在现代软件程序员之间都得到广泛使用。
示例
#include <stdio.h> int main()//main()函数,程序的入口 { /* 我的第一个 C 程序 */ printf("Hello, World! \n");//输出HELLO World return 0; }
示例解析
最初的计算机语言使用的是由0和1组成的二进制数,二进制是所有语言的基础。计算机发明之初,只是为少数人使用的,对于人与计算机的沟通,人们只能把由0与1组成的指令序列交由计算机执行,当时对于计算机语言的使用与普及一直都是令人头疼的问题。
缺点:由于之间对硬件进行操作,所以对于程序的移植能力几乎没有,时间成本,人工成本高昂
优点:运行效率很高,做为第一代计算机语言,为后来的计算机语言的发展奠定了基础。
有了机器语言作为基础,汇编语言诞生了,它用一些简单的英文字母,和特定的符号串来代替一些特定的指令和二进制串。
缺点:它和机器语言一样,依然直接对计算机硬件进行操作,所以汇编语言依然有他的局限性。编写困难。
优点:相比于机器语言,汇编语言已经有了很大的进步,它精炼质量高,所以至今仍是常用的语言。
在人与计算机的不断交流中,人们对计算机的可移植性要求不断提高,传统的机器语言和汇编语言已经无法满足人们的要求,所以语言从传统向高级发展,从面向过程到面向对象发展。
面向过程 | 面向对象 |
---|---|
C语言 | java, c++,c# |
对于面向过程的程序来说,执行顺序是流水式的,在一个模块完成之前,人们做其他的事,也无法动态的改变程序的执行方向,这与人处理事情的方式是相矛盾的。而我们将要学的C语言就是,面向过程的语言。
对于面相对象的程序设计来说,很好的将事物象的部分抽取归类,各个累与模块之间能相互组合,完成特定的功能,同时又能重复使用。对使用者的技术要求也相对降低。
首先要明确,C语言是一门通用的高级语言,最初是由丹尼斯.里奇为开发UNIX操作系统而设计的。在1972年,C语言被首次实现。
在1978年,布莱恩·柯林汉(K)和丹尼斯.里奇(R)制作了C的第一个可用公开描述,现在被称为K&R标准。
UNIX 操作系统,C编译器,和几乎所有的 UNIX 应用程序都是用 C 语言编写的。由于各种原因,C 语言现在已经成为一种广泛使用的专业语言。
易于学习(相对性的,比上一代语言汇编语言容易,但是比较面向对象的语言是困难的)
结构化语言
产生的程序效率高
可以处理底层活动
可以在多种计算机平台上运行
接下来,我们从CMD中输出Hello World。但前提我们得去配置GCC的环境变量。
1.新建文本文档,将hello world的程序写入记事本,并将Hello.txt 改为Hello.c保存。并放到随便一个盘下,比如D盘。
2.Win+R,输入CMD并回车。
3.输入命令D:,找到D盘的路径。
4.输入命令 gcc Hello.c -o hello.exe。编译C程序源文件,编译后的文件为hello.exe。-o的意思是确定文件的输出名称。还有一种写法,gcc Hello.c 这样的写法没有指定文件的输出名称,默认为a.exe。
5.执行完第四步,目录会多出一个可执行文件,hello.exe。继续执行命令hello.exe,屏幕上会打印出程序的结果。
注意
在输出中文时,有时会乱码。如何解决呢?
cmd的默认编码格式为GBK。
win7系统有时还要修改字体。
chcp // 查看当前cmd的编码格式。936代表GBK chcp 65001 //将编码格式修改为utf-8
由上可知,c语言在执行前是需要编译的。未被编译的c程序的叫源文件。源文件的后缀是.c ,
将源文件编译后,得到.exe的可执行文件,然后计算机执行.exe的可执行文件,得到结果。
但其实真正的执行过程往往更加复杂,如下图:
![image-20210607144503941](/Users/zhoubaopeng/Library/Application Support/typora-user-images/image-20210607144503941.png)
printf可以在显示器上进行输出操作。print 打印,f源自于format(格式化)。如果想使用某个函数的功能,就必须通过函数的调用来实现。printf函数的使用:
printf("%d",15+35); printf 函数名称,表示打印输出 () 括号就是参数的列表 %d 实际参数(实参) 表示显示的时候用十进制的方式来显示 15+35 实际参数(实参) 表示十进制输出的结果
调用此函数就发出了显示这些内容的请求,然后通过括号中的实参来传递要显示的内容。另外,当实际参数超过俩个的时候,中间就用逗号来隔开。
printf函数的第一个参数%d,表示用十进制的方式显示后面的参数。此外还有别的类型如下:
转换控制符 | 转换控制符说明 |
---|---|
%d,%i | 以十进制格式输出一个整数 |
%o,%x | 以八进制或十六进制格式输出一个整数 |
%c | 输出一个字符 |
%s | 输出一个字符串 |
%f | 输出一个(单精度)浮点数 |
%e | 以科学计数法格式输出一个双精度浮点数 |
%g | 以通用格式输出一个双精度浮点数 |
%% | 读取一个%字符 |
接下来所有的c程序,我们都将在编译器中编写。
#include <stdio.h>//包含输入输出标准头文件 int main() { printf("hello world /n"); printf("我刚开始学习C语言"); printf("C语言的面向过程的语言"); }
#include <stdio.h>//头文件,标准的输入输出 int main()//main()函数,程序的入口 { int sumA(int x,int y);//声明即将要调用的sunA函数 int sum1;//声明一个int类型的变量sum sum1 = sumA(2,3); //调用函数,将2和3的值传入函数,并用变量sum1接受函数的返回值 printf("x+y=%d\n",sum1);//将sum1的值输出 } int sumA(int x,int y)//计算x+与的值 { int sum;//声明一个int类型的变量sum sum=x+y;//将x+y的值赋值给sum return (sum);//将sum的值返回 }
#include <stdio.h>//头文件,标准的输入输出文件 int main()//主函数 { int max(int x,int y);//声明即将调用的函数 int z;////声明一个变量。作用是接收最大值 z=max(5,5); printf("最大值是:%d\n",z); } int max(int x,int y)//求最大值的函数 { int z;//声明一个变量。作用是接收最大值 if(x>y)//判断,如果X大于Y,那么X就是最大值 { z=x;//将最大值赋值给Z } if(x<y)//判断,如果X小于Y,那么Y就是最大值 { z=y;//将最大值赋值给Z } if(x=y)//判断,如果X等于Y,那么XY都是最大值 { z=x;//将最大值赋值给Z } return(z);//将最大值返回 }
什么是算法:算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令都表示一个或者多个操作。简单来说算法就是解决问题的方法。 体验一下算法,如将数组[abcd ef]的左半部分和右半部分交换位置,变为[ef abcd] 第一步:将数组的左半部分逆序[dcba ef] 第二步:将数组的右半部分逆序[dcba fe] 第三步:将数组的整体逆序[ef abcd] 给定的问题,可以有多种算法来解决,一个算法也不能解决各种问题。 算法的五个基本特性:输入性,输出性,有穷性,确定性,可行性。 输入性:算法具有零个或多个输入 输出性:算法至少有一个或多个输出 有穷性:算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成。 确定性:算法的每一个步骤都具有确定的含义,不会出现二义性,算法在一定的条件下,只有一条执行路径,相同的输入只能有唯一的输出结果,算法的每一个步骤都应该被精确的定义而无歧义。 可行性:算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限的次数完成。
度量算法的效率其实就是看算法的执行时间,算法的执行时间越短,它的效率就越高。那么我们如何统计算法的执行时间呢? 事后统计法:通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。 但是这种方法具有很大的缺陷,我们需要花费大量的时间来编写测试程序,如果算法的效率很低,那么前面花费的写算法,测试算法的时间就都白费了。而且不同的测试环境差别很大。那么要节省时间就必须在算法之前就先估算他的效率。 事前估算法:在计算机程序编写前,依据统计方法对算法进行估算。 影响算法效率的因素,1,算法采用的策略,2,编译产生的代码质量,3,问题的输入规模,4,机器执行指令的速度,由此可见,我们抛开计算机硬件软件有关的因素,一个程序的运行时间依赖于算法的好坏和问题的输入规模。
接下来我们来看一个例子,比如计算 100 的了累加,我们用不同的算法来表示
//第一种算法int i,sum = 0,n = 100; //执行1次for(i = 1;i <= n; i++) //执行了 n+1 次{ sum = sum + i; //执行 n 次}//总共执行了 1+(n+1)+n = 2n+2次 //第二种算法int sum = 0,n = 100; //执行1次sum = (1+n)*n/2; //执行1次//总共执行了1+1=2次
我们研究算法的复杂度,侧重的是研究算法随着输入规模的扩大增长量的一个抽象,而不是精确的定位需要执行多少次,因此如果是考虑次数的话,我们就必须又考虑回编译器优化等问题。那么啥叫抽象:我们不关心编写程序所用的语言是什么,也不关心这些程序跑在什么样的计算机上,我们只关心它所实现的具体的算法。这样我们就不计那些循环索引的递增和循环终止条件,变量的声明,打印结果等操作,最终在分析程序的运行时间时,最重要的是把程序看成是独立于程序设计语言的算法或者一系列操作。因此,我们分析算法的运行时间,重要的是把基本操作的数量和输入模式关联起来。
例子:判断俩个算法,那个更好?
假设俩个算法的算法规模都是n,算法A要做 2n+3 次操作,算法B要做 3n+1 次操作
规模 | A1(2n+3) | A2(2n) | B1(3n+1) | B2(3n) |
---|---|---|---|---|
n=1 | 5 | 2 | 4 | 3 |
n=2 | 7 | 4 | 7 | 6 |
n=3 | 9 | 6 | 10 | 9 |
n=10 | 23 | 20 | 31 | 30 |
n=100 | 203 | 200 | 301 | 300 |
以上表格可以看出,当n=1时,算法A1的效率不如算法B1,当n=2时,俩者的效率相同,当n>2时,算法A1就开始优于B1了,随着n的继续增加俩者的差距逐步拉开,所以总体上,算法A1比算法B1优秀。我们还可以发现 +3 和 +1 其实是不影响我们的判断的。
给定俩个函数f(n)和g(n),如果存在一个整数N使得对于所有的n>N,f(n)总是比g(n)大,那么,我们说f(n)的增长渐近快于g(n)。
例子:判断俩个算法,那个更好?
算法C 是4n+8 算法D是2n^2+1
规模 | C1(4n+8) | C2(n) | D1(2n^2+1) | D2(n^2) |
---|---|---|---|---|
n=1 | 12 | 1 | 3 | 1 |
n=2 | 16 | 2 | 9 | 4 |
n=3 | 20 | 3 | 19 | 9 |
n=10 | 48 | 10 | 201 | 100 |
n=100 | 408 | 100 | 20001 | 10000 |
n=1000 | 4008 | 1000 | 2000001 | 1000000 |
我们观察发现,哪怕是去掉与n相乘的常数,俩者的效率还是没有改变,C要好于D。也就是说,与最高次项相乘的常数其实也不重要,也可以忽略
例子:判断俩个算法,那个更好?
算法E是 2n^2+3n+1,算法F是2n^3+3n+1。
规模 | E1(2n^2+3n+1) | E2(n^2) | F1(2n^3+3n+1) | F2(n^3) |
---|---|---|---|---|
n=1 | 6 | 1 | 6 | 1 |
n=2 | 15 | 4 | 23 | 8 |
n=3 | 28 | 9 | 64 | 27 |
n=10 | 231 | 100 | 2031 | 1000 |
n=100 | 20301 | 10000 | 2000301 | 1000000 |
当 n = 1时,俩个算法的结结果时是相同的,随着n增大,E明显优于F。
通过观察我们发现,最高次项的指数大的,函数随着n的增长,结果也会变得增长特别快。
例子:判断三个算法,那个更好?
算法 G是2n^2,算法H是3n+1,算法I是2n^2+3n+1
规模 | G(2n^2) | H(3n+1) | I(2n^2+3n+1) |
---|---|---|---|
n=1 | 2 | 4 | 6 |
n=2 | 8 | 7 | 15 |
n=5 | 50 | 16 | 66 |
n=10 | 200 | 31 | 231 |
n=100 | 2000 | 301 | 20301 |
n=10000 | 2000000 | 30001 | 2003001 |
n=100000 | 200000000 | 300001 | 200030001 |
n=1000000 | 20000000000 | 3000001 | 20000300001 |
n=10000000 | 2000000000000 | 30000001 | 2000003000001 |
可以看出,当n的值变得非常大的时候,3n+1已经没法和2n^2de 结果相比了,最终也可以忽略不计,算法G和算法I基本上重合了。
于是我们得出了这样一个结论,判断一个算法的效率时,函数中的常数和其他次要项的常数可以忽略,而应该关注主项(最高项)的阶数。
注意:判断一个算法好不好,我们只通过少量的数据是不能做出准确的判断的,很容易以偏盖全。
在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随着n的变化情况并且确定T(n)的数量级。算法的时间复杂度也就是算法的时间度量,记作 T(n) = O(f(n)),他表示随着问题规模n的增大,算法执行时间的增长率和f(n)增长率相同,称作算法的渐进时间复杂度。简称为时间复杂度。其中f(n)是问题规模n的某个函数。 关键需要知道一句话,执行次数 == 时间 使用大写O()来体现算法时间复杂度的记法,我们称为大O记法。 一般情况下,随着输入规模n的增大,T(n)增长最慢的算法为最优算法。
练习:计算以下俩个算法的时间复杂度
//第一种算法int i,sum = 0,n = 100; //执行1次for(i = 1;i <= n; i++) //执行了 n+1 次{ sum = sum + i; //执行 n 次}//总共执行了 1+(n+1)+n = 2n+2次 //时间复杂度 O(n)//第二种算法int sum = 0,n = 100; //执行1次sum = (1+n)*n/2; //执行1次//总共执行了1+1=2次//时间复杂度 O(1)
如何推导出大O阶呢1.用常数1取代运行时间中的所有加法常数2.在修改后的运行函数中,只保留最高阶项3.如果最高阶项存在且不是1,则去除与这个项相乘的常数4.得到的最后的结果就是大O阶
例子:
//常数阶int sum = 0,n = 100;printf("I LOVE WD\n");printf("I LOVE WD\n");printf("I LOVE WD\n");printf("I LOVE WD\n");printf("I LOVE WD\n");sum = (1+n)*n/2;//时间复杂度 O(1) 注意这里绝对不是 O(8)//线性阶//一般含有非嵌套循环涉及线性阶,随着n的扩大,次数呈直线增长int i,n = 100,sum = 0;for(i = 0;i<n;i++){sum = sum + 1;}//时间复杂度 O(n) 注意这里绝对不是 O(n+1)//平方阶int i,n = 100,sum = 0;for(i = 0;i<n;i++){ for(j = 0;i<n;i++) { sum = sum + 1; }}//时间复杂度 O(n^2) //复杂平方阶for(i = 0;i<n;i++){ for(j = i;i<n;i++) { sum = sum + 1; } }//以上的代码的执行次数1+2+3+……+n+1 = (1+n)*n/2+1 = (n+n^2)/2+1 =n/2 + n^2/2 + 1 最终为 O(n^2)// 对数阶int i = 1,n = 100;while(i < n){ i = i *2;}//由于每次 i*2 之后,就距离n更近一步,假设有x个2相乘后大于或者等于n则会退出循环// 由于2^x =n,得到 x = log(2)n。//所以这个循环的时间复杂度为O(logn)
注意:这个地方考研的同学需要强化数学尤其是数列方面的知识,对于增强编程能力的同学来说大概知道规律就可以了。
例子
int i, j;for(i = 0;i < n;i++){ fun(i);}void fun(int count){ printf("%d",count);}//函数体是打印这个参数,这很好理解,fun()函数的时间复杂度是O(1),调用的地方循环n次O(1),所以整体的时间复杂度就是O(n)。假设fun()函数是下面这样的,又该如何呢?void fun(int count){ int j; for(j=0;j<n;j++){ printf("%d",j); }}//时间复杂度就变为上节课一样的循环嵌套O(n^2)
接下来挑战一下自己
n++; //1fun(n); //n^2int i;for(i=0;i<n;i++){ // n^2 fun(i);}for(i=0;i<n;i++){ // n^2 for(j=i;j<n;j++){ printf("%d",j); }}void fun(int count){ int j; for(j=0;j<n;j++){ printf("%d",j); }}//加起来 3n^2 + 1 O(n^2)
常见的时间复杂度
例子 | 时间复杂度 | 术语 |
---|---|---|
5201314 | O(1) | 常数阶 |
3n+4 | O(n) | 线性阶 |
3n^2+4n+5 | O(n^2) | 平方阶 |
3long(2)n+4 | O(longn) | 对数阶 |
2n+3nlong(2)n+14 | O(nlogn) | nlogn阶 |
n3+2n2+4n+6 | O(n^3) | 立方阶 |
2^n | O(2^n) | 指数阶 |
常用的时间复杂度所耗费的时间从小到大依次是
O(1)<O(longn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
O(1),O(longn),O(n),O(n2)我们前面已经讨论过了,至于O(nlogn)我们在今后的学习中讨论,而像O(n3)之后的这些由于n值得 增大都会使得结果大的难以想象,我们没有必要去讨论他们。
对于算法的分析,比如我们查找一个有n个随机数字数组中 的某个数字,最好的情况就是第一个数字就是,那么该算法的时间复杂度就是O(1),但也有可能这个数字就在最后一个位置,那么时间复杂度就是O(n)。那么我们折中,取平均运行时间就是期望的运行时间。
最坏的运行时间是一种保证,在应用中,这是一种最重要的需求,通常除非特别指定,我们提到的运行时间都是最坏情况的运行时间。
在我们写代码的时候,完全可以用空间来换取时间。
举个例子,要判断某年是不是闰年,你可能会花一点心思来写一个算法,没给一个年份,就可以通过这个算法计算得到是否闰年的结果。
另外一种方法是,事先建立一个有2050个元素的数组,然后将所有的年份按下标的数字对应,如果是闰年,则此数组的元素的值是1,如果不是元素的值则为0,这样所谓的判断某一年是否为闰年就变成了查找这个数组某一个元素的值的问题。
分析: 第一种方法比第二种方法明显节省空间,但每一次的查询都要经过一系列的计算。第二种方法虽然需要在内存里存储2050个元素的数组,但是每次查询只需要一次索引判断即可。 这就是一个通过空间开销来换取时间开销的技巧,那么到底哪一种方法好呢?其实要就看你用在社么地方。
定义:
算法的空间发杂度通过计算算法所需要的存储空间来实现,算法的空间复杂度的计算公式:S(n) = O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。
通常我们都是用 “时间复杂度” 来指运行时间的需求,是用 “空间复杂度” 来指空间需求。
当直接要让我们来求“复杂度”时候,通常是指时间复杂度。
我们来以计算俩个数的和为例
#include <stdio.h> //预处理指令 standard int main(void)//main()函数,程序的入口 参数默认void,表示为空,可以省略 { /*计算来个数的和*/ int a,b,sum;//定义了三个整数类型的变量 a=2;//给变量a赋值2 b=3;//给变量a赋值3 sum=a+b;//将a+b的值赋值给 sum printf("a+b=%d\n",sum);//打印输出sum return 0;//中止程序,默认返回0 }
通过以上程序的分析,我们来发现C程序的结构
以上这些元素,我们会在接下来的学习中接触并学习他们,在哪之前,我们需要先学习一个C程序的基础语法
c语言程序是由各种的令牌组成,令牌可以是关键字、标识符、常量、字符串值,或者是一个符号。例如,下面的 C 语句包括五个令牌:
printf("Hello, World! \n");
这五个令牌分别是:
printf//关键字(//符号"Hello, World! \n"//字符串的值);
C语言有俩种注释方式
1.单行注释
// 单行注释//以//符号开头的注释,这种注释可以单独占一行
2.多行注释
/*单行注释*//*多行注释*//*这种注释 既可以单行 也可以多行*/
注意:不能再注释内嵌套注释,也不能在值中加注释
/*// 不可以 能编译 但不会这么写*/
int a = 2/* 值*/3 //不可以 无法编译
什么是标识符? 通俗的来说就是你自己定义的某个单词。是你用来标识变量名、符号常量名、函数名、数组名、文件名等。
必须是字母或者下划线_(字母大小写都可以) 开头。
后跟字母(不分大小写)、下划线(_)或数字组成;
标识符中的大小写字母有区别。如,变量sUm,sum代表俩个不同的变量;
不能与c编译系统已经预定义的、具有特殊用途的保留标识符(即关键字)同名。
sum _a //有效@sum //无效float int short //无效
下表列出了 C 中的保留字。这些保留字不能作为常量名、变量名或其他标识符名称。
关键字 | 说明 |
---|---|
auto | 声明自动变量 |
break | 跳出当前循环 |
case | 开关语句分支 |
char | 声明字符型变量或函数返回值类型 |
const | 声明只读变量 |
continue | 结束当前循环,开始下一轮循环 |
default | 开关语句中的"其它"分支 |
do | 循环语句的循环体 |
double | 声明双精度浮点型变量或函数返回值类型 |
else | 条件语句否定分支(与 if 连用) |
enum | 声明枚举类型 |
extern | 声明变量或函数是在其它文件或本文件的其他位置定义 |
float | 声明浮点型变量或函数返回值类型 |
for | 一种循环语句 |
goto | 无条件跳转语句 |
if | 条件语句 |
int | 声明整型变量或函数 |
long | 声明长整型变量或函数返回值类型 |
register | 声明寄存器变量 |
return | 子程序返回语句(可以带参数,也可不带参数) |
short | 声明短整型变量或函数 |
signed | 声明有符号类型变量或函数 |
sizeof | 计算数据类型或变量长度(即所占字节数) |
static | 声明静态变量 |
struct | 声明结构体类型 |
switch | 用于开关语句 |
typedef | 用以给数据类型取别名 |
unsigned | 声明无符号类型变量或函数 |
union | 声明共用体类型 |
void | 声明函数无返回值或无参数,声明无类型指针 |
volatile | 说明变量在程序执行中可被隐含地改变 |
while | 循环语句的循环条件 |
我们来从一个程序中找到以上的一些关键字
#include<stdio.h>int main(void) //int void //main()函数{ int a= 2;//int 类型的变量 int b= 3; printf("交换前:a=%d b=%d\n",a,b) ; //输出交换前的值 int c;//定义中间变量 //交换 c=a; a=b; b=c; printf("交换后:a=%d b=%d\n",a,b) ; //输出交换前的值 return 0; //return}
只包含空格的行,被称为空白行,可能带有注释,C 编译器会完全忽略它。
在 C 中,空格用于描述空白符、制表符、换行符和注释。空格分隔语句的各个部分,让编译器能识别语句中的某个元素(比如 int)在哪里结束,下一个元素在哪里开始。因此,在下面的语句中:
int age;//int 与变量名age之间必须要有一个空格,它表示一个空白符,这样才能编译通过,如果没有,编译器无法识别
int c = a + b;//这里除了int与变量c之间的空格属于上面的这种情况,其他的空格不是必须的,如果没有可以编译通过,有空格可以增强程序的可读性。
C语言提供了丰富的运算符供我们使用
运算符 | 实例 | 描述 |
---|---|---|
+ | A + B 将得到 30 | 把两个操作数相加 |
- | A - B 将得到 -10 | 从第一个操作数中减去第二个操作数 |
* | A * B 将得到 200 | 把两个操作数相乘 |
/ | B / A 将得到 2 | 分子除以分母 |
% | B % A 将得到 0 | 取模运算符,整除后的余数 |
++ | A++ 将得到 11 | 自增运算符,整数值增加 1 |
-- | A-- 将得到 9 | 自减运算符,整数值减少 1 |
#include<stdio.h>int main(){ /*演示算术运算符*/ int a=15,b=4,c; c=a+b; printf("1. a+b=%d\n",c); c=a-b; printf("2. a-b=%d\n",c); c=a*b; printf("3. a*b=%d\n",c); c=a/3; printf("4. a/3=%d\n",c); c=a%b; printf("5. a%%b=%d\n",c); c=a++;//先赋值 后运算 printf("6. a=%d,c=%d\n",a,c); c=a--;//先赋值 后运算 printf("7. a=%d,c=%d\n",a,c); c=--b; //先计算 后赋值 printf("8. b=%d,c=%d\n",b,c); c=++b; //先计算 后赋值 printf("9. b=%d,c=%d\n",b,c); return 0;}
假设A变量为1,B变量为2。在运用关系运算符的时候,记住他返回的结果 是一个bool类型。
运算符 | 描述 | 实例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 不为真。 |
!= | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 不为真。 |
< | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 不为真。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 |
#include<stdio.h>int main(){ /*演示关系运算符*/ /*关系运算符返回一个bool类型 bool类型只有俩个值,true或者false true 转为int是1 false转为int 0 */ int a=1; int b=2; int c; printf("a=1,b=2。a==b是%d\n",(a==b)); printf("a=1,b=2。a!=b是%d\n",(a!=b)); printf("a=1,b=2。a>b是%d\n",(a>b)); printf("a=1,b=2。a<b是%d\n",(a<b)); printf("a=1,b=2。a>=b是%d\n",(a>=b)); printf("a=1,b=2。a<=b是%d\n",(a<=b)); return 0;}
#include<stdio.h>int main(){ /*演示逻辑运算符 逻辑运算符的俩个运算数 0表示假 其余数都表示真 */ int a = 10; int b = 4; int c = 0; //&& 运算数有假即为假 printf("a=10,b=4,c=0。a&&b=%d\n",(a&&b)); printf("a=10,b=4,c=0。a&&c=%d\n",(a&&c)); // || 运算数有真即为真 printf("a=10,b=4,c=0。a&&b=%d\n",(a||b)); printf("a=10,b=4,c=0。a&&c=%d\n",(a||c)); // ! 取反 printf("a=10,b=4,c=0。a&&b=%d\n",!(a||b)); printf("a=10,b=4,c=0。a&&c=%d\n",!(a||c)); }
位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示:
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
假设A=60,B=13。那么位运算时如何操作的呢?
A=0011 1100
B=0000 1101
----------------------------------------------------------------------------------------------------------------------------------------------------------------A&B=0000 1100 --------->12
A|b=0011 1101 --------->61
A^B=0011 0001 --------->49
~A= 1100 0011--------->-61
下表显示了 C 语言支持的位运算符。假设变量 A 的值为 60,变量 B 的值为 13,则:
运算符 | 描述 | 实例 |
---|---|---|
& | 按位与操作,按二进制位进行"与"运算。运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1; |
(A & B) 将得到 12,即为 0000 1100 |
| | 按位或运算符,按二进制位进行"或"运算。运算规则:0|0=0; 0|1=1; 1|0=1; 1|1=1; |
(A | B) 将得到 61,即为 0011 1101 |
^ | 异或运算符,按二进制位进行"异或"运算。运算规则:0^0=0; 0^1=1; 1^0=1; 1^1=0; |
(A ^ B) 将得到 49,即为 0011 0001 |
~ | 取反运算符,按二进制位进行"取反"运算。运算规则:~1=0; ~0=1; |
(~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。 |
<< | 二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。 | A << 2 将得到 240,即为 1111 0000 |
>> | 二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。 | A >> 2 将得到 15,即为 0000 1111 |
运算符 | 描述 | 实例 |
---|---|---|
= | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | C = A + B 将把 A + B 的值赋给 C |
+= | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A |
-= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相当于 C = C - A |
*= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C *= A 相当于 C = C * A |
/= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A |
%= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A |
<<= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 |
>>= | 右移且赋值运算符 | C >>= 2 等同于 C = C >> 2 |
&= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 |
^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 |
|= | 按位或且赋值运算符 | C |= 2 等同于 C = C | 2 |
运算符 | 描述 | 实例 |
---|---|---|
sizeof() | 返回变量的大小。 | sizeof(a) 将返回 4,其中 a 是整数。 |
& | 返回变量的地址。 | &a; 将给出变量的实际地址。 |
* | 指向一个变量。 | *a; 将指向一个变量。(指针)后面学到指针再讲 |
? : | 条件表达式 | 如果条件为真 ? 则值为 X : 否则值为 Y |
#include<stdio.h>int main(){ /*杂项运算符*/ //sizeof();返回int类型,表示变量的大小 int a= 10; short b; double c; printf("a的大小:%d\n",sizeof(a)); printf("a的大小:%d\n",sizeof(b)); printf("a的大小:%d\n",sizeof(c)); //返回变量的地址// printf("请输入a的值:");// scanf("%d",&a) ;// printf("a=%d",a); //?: 条件运算符 返回bool 语法:判断条件?结果1:结果2。 解释:如果条件为真,那么执行结果1,反之执行结果2 int flag=(a!=10)?10:20; printf("flag=%d",flag); return 0;}
由一系列的运算符和操作数组成的式子叫做表达式。
表达式:操作数 运算符 操作数 ..........
c程序的数据类型是用来声明不同类型的变量或者函数的一个广泛的系统。数据类型可以决定变量的存储大小。
C程序的数据类型可以分为以下几种:
1、基本类型 | 属于算术类型,包含整型和浮点型 |
---|---|
2、枚举类型 | 属于算术类型,定义程序中只能赋予一定的离散值的变量 |
3、void类型 | 类型说明符,表示没有可用的值 就是空值 |
4、派生类型 | 包括:指针类型,数组类型,结构类型、函数类型。 |
下表列出了关于标准整数类型的存储大小和值范围的细节:unsigned 表示无符号。signed 表示有符号
类型 | 存储大小 | 值范围 |
---|---|---|
char | 1 字节 | -128 到 127 或 0 到 255 |
unsigned char | 1 字节 | 0 到 255 |
signed char | 1 字节 | -128 到 127 |
int | 2 或 4 字节 | -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647 |
unsigned int | 2 或 4 字节 | 0 到 65,535 或 0 到 4,294,967,295 |
short | 2 字节 | -32,768 到 32,767 |
unsigned short | 2 字节 | 0 到 65,535 |
long | 4 字节 | -2,147,483,648 到 2,147,483,647 |
unsigned long | 4 字节 | 0 到 4,294,967,295 |
示例:
//sizeof()运算符可以得到数据类型的大小#includ<stdio.h>int main(){ printf("int类型的大小:%lu\n",sizeof(int));//%lu为 32 位无符号整数 return 0;}
下表列出了关于标准浮点类型的存储大小和值范围的细节:
类型 | 存储大小 | 值范围 | 精度 |
---|---|---|---|
float | 4 字节 | 1.2E-38 到 3.4E+38 | 6 位小数 |
double | 8 字节 | 2.3E-308 到 1.7E+308 | 15 位小数 |
long double | 16 字节 | 3.4E-4932 到 1.1E+4932 | 19 位小数 |
#include <stdio.h> #include <float.h>//包含float.h的头文件 引入float的类库。int main(){ float a= 0.5; printf("交换后:a=%f \n",a) ; printf("float 存储最大字节数 : %lu \n", sizeof(float)); printf("double 存储最大字节数 : %lu \n", sizeof(double)); printf("long double 存储最大字节数 : %lu \n", sizeof(long double)); printf("float 最小值: %E\n", FLT_MIN ); printf("float 最大值: %E\n", FLT_MAX ); printf("精度值: %d\n", FLT_DIG ); return 0;s}
void表示没有可用的值,通常有以下3中表示方法
序号 | 类型与描述 |
---|---|
函数返回为空 | C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如 void exit (); |
函数参数为空C | C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 int rand(void); |
指针指向 void | 类型为 void * 的指针代表对象的地址,而不是类型。例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。 |
变量是指程序可操作的存储区的名称。他的作用是用来保存数据,在程序运行过程中可以改变的值。在C语言中,变量都有他特定的数据类型,数据类型决定了这个变量的存储大小和范围。变量的定义与命名遵守标识符命名规则。
变量定义就是告诉编译器在何处创建变量的存储,以及如何创建变量的存储。变量定义指定一个数据类型,并包含了该类型的一个或多个变量的列表,如下所示:
数据类型 变量名1,变量名2,,,,,;
下面给几个有效的变量定义
int a,b,c;//定义了一个变量集,数据类型为int类型char c,ch;//数据类型为char类型,字符类型float f;//数据类型为浮点型double d;//数据类型为双精度浮点型
变量在定义的时候可以初始化,就是在定义的时候,给一个初始值。初始化的方法是,变量名后面加一个=,在加初始值。
int x=2,y=3; char x = 'x';
变量声明向编译器保证变量以指定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义,在程序连接时编译器需要实际的变量声明。
变量的声明有两种情况:
我的理解:
int a;是定义,也是声明
extern int a;声明,但不定义
常量是固定值,他在程序执行的期间他的值是不变的。他的数据类型可以是任何的类型,
整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。
整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)。后缀可以是大写,也可以是小写,U 和 L 的顺序任意。
214;//合法的215u;//合法的 u表示无符号的0xFEEL;//合法的 16进制07;//合法的078;//合不合法?215uu;//非法的 后缀不能重复
85 /* 十进制 */0213 /* 八进制 */0x4b /* 十六进制 */30 /* 整数 */30u /* 无符号整数 */30l /* 长整数 */30ul /* 无符号长整数 */
浮点常量由整数部分、小数点、小数部分和指数部分组成。可以使用小数形式或者指数形式来表示浮点常量。
当使用小数形式表示时,必须包含整数部分、小数部分,或同时包含两者。当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者。带符号的指数是用 e 或 E 引入的。
下面列举几个浮点常量的实例:
3.14159 /* 合法的 */314159E-5L /* 合法的 */510E /* 非法的:不完整的指数 */210f /* 非法的:没有小数或指数 */.e55 /* 非法的:缺少整数或分数 */
字符常量是括在单引号中的字符,例如,'x' 可以存储在 char 类型的简单变量中。
字符常量可以是一个普通的字符(例如 'x')、一个转义序列(例如 '\t'),或一个通用的字符(例如 '\u02C0')。
在 C 中,有一些特定的字符,当它们前面有反斜杠时,它们就具有特殊的含义,被用来表示如换行符(\n)或 制表符(\t)等。下表列出了一些这样的转义序列码:
转义序列 | 含义 |
---|---|
\ | \ 字符 |
' | ' 字符 |
" | " 字符 |
? | ? 字符 |
\a | 警报铃声 |
\b | 退格键 |
\f | 换页符 |
\n | 换行符 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ooo | 一到三位的八进制数 |
\xhh . . . | 一个或多个数字的十六进制数 |
#include<stdio.h>int main(){ printf("12\r"); printf("12\n"); printf("1\t\t \"\'2"); return 0;}
字符串常量是括在双引号 "" 中的。一个字符串包含类似于字符常量的字符:普通的字符、转义序列和通用的字符。
您可以使用空格做分隔符,把一个很长的字符串常量进行分行。
下面的实例显示了一些字符串常量。下面这三种形式所显示的字符串是相同的。
"hello, dear""hello, \dear""hello, " "d" "ear"
在C语言中,常量有俩种定义方式
使用#define预处理器
使用关键字const来定义
#define 变量名 值
#include<stdio.h> #define MIN 20 #define MAX 10 int main() { int sum; sum=MIN*MAX; printf("%d\n",sum); }
const 数据类型 变量名 = 值;
#include<stdio.h> int main() { const int MIN = 10; const int MAX = 20; float a = 10.01276; int sum; sum=MIN*MAX; printf("%9.4f",a);//% F之间加数字 1.保证所有的数字都显示 2.保留几位小数 }
#include<stdio.h>int main(){ /*C语言的输出函数*/ printf("我要输出的内容\n"); //输出简单的内容 //输出变量 /* C语言如何输出变量? */ //输出int变量 int sum = 10; printf("%d\n",sum); // 输出char char car = 'a';//穿插ASCII表 printf("%d\n",car); printf("%c\n",car); //输出long int long int lSum= 123; printf("%ld\n",lSum); //输出float float fSum= 0.008; printf("%f\n",fSum); /*可以看到,float类型默认保留6位小数 如何改变小数点后面的位数呢 */ printf("%8.3f\n",fSum) ; //小数点前面的是要输出的位数,小数点后面的是要保留小数的位数 多出来的位数 用空格来补 int类型的同理 //那么我们前面加个负号呢 ? 表示左对齐 printf("%-8.3f\n",fSum) ; printf("%+8.3f\n",fSum) ; double pi = 3.1415926535; //long float printf("%lf\n",pi); //十六进制 printf("%X\n",lSum) ; //八进制输出 printf("%o\n",lSum); //字符串的形式输出// printf("%s\n",pi); return 0; }
#include<stdio.h>int main(){ /*C语言的输入函数*/ //Scanf() int a,b; printf("请输入a和b的值:\n") ; scanf("%d %d",&a,&b); printf("a=%d,b=%d\n",a,b); printf("a*b=%d\n",a*b); return 0; }
这俩个函数 也是输入输出函数。
#include<stdio.h>int main(){ int c ; printf("请输入c的值:"); c= getchar();//程序遇到getchar()函数,等待用户输入,返回int类型 printf("c的值是:"); putchar(c); return 0 ; }
getchar()&putchar()是单个字符的输入输出函数,通过上面的程序,我们可以知道,这俩个函数只能输入输入单个字符。当程序遇到getchar()函数时,就在等用户输入了,用户输入的字符被存放在键盘缓冲区中,当按下回车键时,getchar()就通过io流读缓冲区的一个字符。getchar()返回用户输入字符的ascll码,如果出错就返回-1,程序遇到putchar时将该字符显示到屏幕上。
如果用户输入了不止一个字符,那么会现在第一个字符,其他字符会继续保存到缓冲区。那么怎么输出多个字符呢?
#include<stdio.h>int main()k{ int c ; printf("请输入c的值:"); // c= getchar();//程序遇到getchar()函数,等待用户输入,返回int类型 while((c=getchar())!= '\n') { putchar(c); } return 0 ; }
选择结构又叫判断结构,选择结构要求开发者指定一个或多个判断的语句,以及判断条件为真是需要执行的语句和判断条件为假时所要执行的语句。
记住C语言把任何非零的和非空的值假定为true,把零或者null假定为false
C 语言提供了以下类型的判断语句。点击链接查看每个语句的细节。
语句 | 描述 |
---|---|
[if 语句] | 一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。 |
[if...else 语句] | 一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。 |
[嵌套 if 语句] | 您可以在一个 if 或 else if 语句内使用另一个 if 或 else if 语句。 |
[switch 语句] | 一个 switch 语句允许测试一个变量等于多个值时的情况。 |
[嵌套 switch 语句] | 您可以在一个 switch 语句内使用另一个 **switch **语句。 |
语法
if(判断语句){ 语句;}
如果判断语句为真,则执行if语句里面的代码,如果为假,则不执行。
示例
#include<stdio.h>int main(){ int sum ; printf("请输入一个0-2的数字:\n"); scanf("%d",&sum); if(sum==0){ printf("我是a,我等于%d",sum); } if(sum==1){ printf("我是a,我等于%d",sum); } if(sum==2){ printf("我是a,我等于%d",sum); } if(sum!=0&&sum!=1&&sum!=2){ printf("你输入的我识别不了。"); }}
语法
if(判断条件){ 语句1;}else { 语句2;}
if else语句具有一个显著的特点,非此即彼。当条件为真,执行语句1,否则执行语句2。
示例
#include<stdio.h>int main(){ int sum ; printf("请输入一个数字:\n"); scanf("%d",&sum); if(sum>10) { printf("进来这里的数字都大于10"); } else { printf("进来这里的数字都小于10"); } }
语法
if(判断条件1){ 语句1;}else if(判断条件2){ 语句2;}...else if(判断条件n){ 语句n;}else{ 语句n+1;}
if -else if - else语句适合多条件判断。
#include<stdio.h>int main(){ int sum ; printf("请输入一个数字:\n"); scanf("%d",&sum); if(sum>=10&&sum<20) { printf("进来这里的数字10-20\n"); } else if(sum>=20) { printf("进来这里的数字大于等于20\n"); } else { printf("进来这里的数字都小于10"); }}
开发者可以在if语句,if- else if -else语句和if-else语句中嵌套if语句,if- else if -else语句和if-else语句。
示例:从键盘输入3个整数,并使用if else语句的嵌套对他 们进行从大到小的排序。
#include<stdio.h>int main(){ printf("请输入3个整数:\n"); int a,b,c; printf("请输入第一个整数:\n"); scanf("%d",&a); printf("请输入第二个整数:\n"); scanf("%d",&b); printf("请输入第三个整数:\n"); scanf("%d",&c); if(a<b) { if(c<a) { printf("%d<%d<%d\n",c,a,b); } else if(c>b) { printf("%d<%d<%d\n",a,b,c); }else { printf("%d<%d<%d\n",a,c,b); } } else//a>b { if(c>a) { printf("%d<%d<%d\n",b,a,c); } else if(c<b) { printf("%d<%d<%d\n",c,b,a); }else { printf("%d<%d<%d\n",b,c,a); } }}
switch语句的语法
switch(表达式){ case 常量表达式1:语句1;break; case 常量表达式2:语句2;break; ... default:语句n+1;}
当程序执行到switch语句的时候,会先判断括号里的表达式的值,在和case后面的常量表达式进行比较,如果相等就执行该case后面的语句,如果不等则继续向下一个case比较,如果都不等,就执行default里的语句。
#include<stdio.h>int main(){ printf("请输入今天星期几?\n"); int day; scanf("%d",&day); switch(day) { case 1:printf("星期一");break; case 2:printf("星期二");break; case 3:printf("星期三");break; case 4:printf("星期四");break; case 5:printf("星期五");break; case 6:printf("星期六");break; case 7:printf("星期天");break; default:printf("输入有误,程序结束!"); } return 0;}
请同学们尝试将以上程序的break语句去掉,执行试试看。
当我们将以上程序的break语句去掉后,我们会发现,switch语句还是会先判断括号里的表达式,然后在用case语句后面的常量表达式进行比较,相等就执行switch语句后面的语句,由于此时没有break语句,则程序会继续向下面的case语句执行,直到以下所有的语句都执行完毕,包括default语句。所以break语句的作用就是就是终止并且跳出switch语句。
一般情况下,程序是按照顺序的结构执行的,第一个语句先执行,接着第二个语句执行,以此类推。但是我们有时候需要多次去执行同意代码块。
循环语句可以允许我们多次执行一个语句或者语句组。C语言为我们提供了以下几种循环类型。
循环类型 | 描述 |
---|---|
while 循环 | 当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。 |
for 循环 | 多次执行一个语句序列,简化管理循环变量的代码。 |
do...while 循环 | 除了它是在循环主体结尾测试条件外,其他与 while 语句类似。 |
嵌套循环 | 您可以在 while、for 或 do..while 循环内使用一个或多个循环。 |
只要给定的条件为真,C 语言中的 while 循环语句会重复执行一个目标语句。直到条件为假。
while循环语法
while(循环条件){ 循环语句;//可以是一个语句也可以是多个语句}
示例:理解while的循环过程
#include<stdio.h>int main(){ int a = 1;//定义变量a,初始值1。 while(a<10)//while循环,循环条件 { printf("第%d次循环\n",a); a++;//改变a的值 }}
以上程序,先给a变量一个初值1。程序执行到while语句时,先判断1<10是否为真,如果为假则循环直接结束,如果为真,则执行循环体,在循环体中要加a++这样的改变变量值的语句,循环体结束后a=2,然后回到while语句继续判断循环条件2<10的真假,以此类推。
do...while 循环的语法
do{ 循环语句;//可以是一个语句也可以是多个语句}while(循环条件);
示例
#include<stdio.h>int main(){ int a = 1; do { printf("第%d次循环\n",a); a++; } while(a<10); }
以上程序,先给a变量一个初值1。当程序执行到do语句的时候,会先执行一遍循环语句,改变控制循环的变量a++,此时a=2。然后判断循环条件(2<10)的真假,如果为假,那么直接结束循环,如果为真,则返回do语句,以此类推。
while语句与do...while语句都是循环语句,用法也差不多,但是,但是while语句是先判断循环条件,然后执行循环语句;而do...while语句是先执行循环语句,在判断循环条件。也就是说,do...while语句至少会执行1次循环语句,而while至少执行0次。
for(int 变量名=初值;循环判断条件;改变变量值语句){ 循环体;}
#include<stdio.h>int main(){ for(int i=1;i<10;i++) { printf("第%d次循环。\n",i); } }
#include <stdio.h>int main (){ /* 局部变量定义 */ int i, j; for(i=0; i<10; i++) { for(int j=0;j<=i;j++) { printf("*"); } printf("\n"); } return 0;}
自己试一试执行以上程序,会出现什么样的结果?
使用嵌套while循环实现以上例子。
int a=0; while(a<10) { int b=0; while(b<=a) { printf("*"); b++; } a++; printf("\n"); }
使用嵌套do_while循环实现以上例子。
int m=1;do{ int n=1; do { printf("*"); n++; } while(n<=m); printf("\n"); m++;}while(m<=10);
循环嵌套一个规则,外层循环一次,内层循环一遍。
在实际开发中,能不嵌套就不要嵌套。
打印99乘法表?
#include <stdio.h>int main (){ for(int i=1;i<=9;i++) { for(int j=1;j<=i;j++) { printf("%d*%d=%d\t",j,i,(i*j)); } printf("\n"); } return 0;}
循环的中断
循环的中断又叫循环控制语句,以下将列出几种循环控制语句;
控制语句 | 描述 |
---|---|
break 语句 | 终止循环或 switch 语句,程序流将继续执行紧接着循环或 switch 的下一条语句。 |
continue 语句 | 告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。 |
goto 语句 | 将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。 |
C 语言中 break 语句有以下两种用法:
如果使用的是嵌套循环,break 语句会停止执行最内层的循环,然后开始执行该块之后的下一行代码。
例子
#include <stdio.h>int main (){ int a = 1; while( a < 10 ) { printf("a 的值:%d\n", a); a++; if( a > 5) { break; /* 使用 break 语句终止循环 */ } } return 0;}
C 语言中的 continue 语句有点像 break 语句。但它不是强制终止,continue 会跳过当前循环中的代码,强迫开始下一次循环。
对于 for 循环,continue 语句执行后自增语句仍然会执行。对于 while 和 do...while 循环,continue 语句重新执行条件判断语句。
#include <stdio.h> int main (){ int a = 1; do { if( a == 5) { a = a + 1; /* 跳过迭代 */ continue; } printf("a 的值: %d\n", a);//continue时,这里不执行了 a++; }while( a < 10 ); return 0;}
C 语言中的 goto 语句允许把控制无条件转移到同一函数内的被标记的语句。
注意:在任何编程语言中,都不建议使用 goto 语句。因为它使得程序的控制流难以跟踪,使程序难以理解和难以修改。任何使用 goto 语句的程序可以改写成不需要使用 goto 语句的写法。
#include <stdio.h>int main (){ int a = 1; do { if( a == 5) { a = a + 1; /* 跳过迭代 */ goto Loop; } printf("a 的值: %d\n", a);//continue时,这里不执行了 a++; }while( a < 10 ); Loop:printf("到这里了"); return 0;}
1.从键盘上接收一个字符,判断它到底是哪种字符:数字,小写字母,大写字母,其他字符。
#include<stdio.h>int main(){ char choice; printf("请输入一个字符:\n"); scanf("%c",&choice); if(choice>='a'&&choice<='z') { printf("你输入的是小写字母"); }else if(choice>='A'&&choice<='Z'){ printf("你输入的是大写字母"); }else if(choice>48&&choice<=57){ printf("你输入的是数字"); }else{ printf("你输入的是其他字符"); } return 0;}
2.写一个程序,能够判断从键盘上输入的年份是否是一个闰年。(能被400整除,或者能被4整除但不能被100整除的都是闰年)
#include<stdio.h>int main(){ int year; printf("请输入一个年份:\n"); scanf("%d",&year); if((year%4==0&&year%100!=0)||year%400==0){ printf("%d是闰年\n",year); }else{ printf("%不是闰年\n",year); } return 0;}
3.写一个程序,接收一个数字,判断它是否能同时被2和3整除
#include<stdio.h>int main(){ int num; printf("请输入一个数字"); scanf("%d",&num); if(num%3==0&&num%2==0){ printf("%d同时被2和3整除",num); }else{ printf("%d不能同时被2和3整除",num); } return 0;}
4.打印99乘法表?
#include<stdio.h>int main(){ for (int i = 1; i <= 9; ++i) { for (int j = 1; j <= i; ++j) { printf("%d*%d=%d ",j,i,(i*j)); } printf("\n"); } return 0;}
C 语言支持数组数据结构,它可以存储一个固定大小的相同数据类型元素的顺序集合。数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量。
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 numbers[0]、numbers[1]、...、numbers[99] 来代表一个个单独的变量。数组中的特定元素可以通过索引访问。
所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。
在C语言中声明一个数组需要指定数组变量的数据类型,和数组存储元素的数量。
在C语言中。我们这样声明数组。
数据类型 数组名[数组的长度];
其中数据类型可以是任意的有效的C的数据类型,数组名是自己定义的标识符,遵守标识符命名规则,数组的长度可以是大于0的整型常量。例如你需要创建一个int类型的长度为3的数组number;
int number[3];//number是一个可用的数组,他可以容纳10个整型数据。
数组的操作实质是操作下标,通过下标可以给数组赋值和取到数组的值。
数组的逐个初始化就是通过下标给数组赋值:
int number[3];number[0] = 12;//第一个元素赋值number[1] = 67;//第二个元素赋值number[2] = 34;//第三个元素赋值
这样的赋值方式可以清楚的看到数组的赋值过程,但是这样的赋值方式有点麻烦,数组长度小的时候还可以,要是长度很长就显得很不方便。
int number[3] = {12,67,34};//或者int number[] = {12,67,34}; //省略[]里面的数组长度,{}里面元素有几个,长度就是几
访问数组元素就是通过下标取值。
数组元素可以通过数组名称加索引进行访问。元素的索引是放在方括号内,跟在数组名称的后边。例如:
double salary = balance[9];//这里的 balance[9]代表一个元素
示例:
#include <stdio.h>int main (){ int number[10];//声明一个int类型的长度为10的数组 for(int i = 0;i<10;i++) { number[i] = i+100;//赋值 } /*取值*/ for(int i = 0;i<=16;i++) { printf("number[%d]=%d\n",i,number[i]); } return 0;}
看下面的程序
int num[10]={1,1,2}
上面声明的数组的长度时10,但初始化我们只给了前三个元素的值,还有后面7个元素的值没有给。但是这7个元素的值默认为0。
数组的遍历就是取数组中的值,由于数组是一系列数据的集合,所以取值需要用到循环。
一种取值方式是直接利用数组名和下标取值,这种取值方式适合取数组中某一个值。
#include<stdio.h>int main(){ int num[10] = {12,89,34,25,46,78,1,4,16,49};//定义并声明数组 int temp = num[8];//直接取数组的第9个元素 printf("%d\n",temp); return 0; }
循环取值可以取到数组里的所有元素。
#include<stdio.h>int main(){ int num[10] = {12,89,34,25,46,78,1,4,16,49};//定义并声明数组 for(int i=0;i<10;i++) { printf("%d\n",num[i]);//循环取值 //操作数组就是操作数组的下标 } return 0; }
循环取值需要 用到数组的长度,那么数组的长度如何求:
求解数组长度的方法:数组长度除以单个元素长度 sizeof(a)/sizeof(a[0])
1.定义一个数组,长度为10,初始值为0,1,2,3,4,5,6,7,8,9。要求正序与逆序输出。
#include<stdio.h>int main(){ int num[10] = {0,1,2,3,4,5,6,7,8,9}; /*正序输出*/ printf("正序"); for(int i=0;i<10;i++) { printf("%d\t",num[i]); } /*逆序输出*/ printf("逆序"); for(int j=9;j>=0;j--) { printf("%d\t",num[j]); } return 0 ;}
2.利用数组输出‘斐波那契数列’前20位。
斐波那契数列 1 1 2 3 5 8 13 21 34 。。。。。。。
m[i]=num[i-1]+num[i-2]; //输出 for(int i = 0;i<20;i++) { if(i%4==0&&i!=0)//在 { printf("\n"); } printf("%d\t",num[i]); } return 0 ;}
3.对数组 int num[10] = {12,89,34,25,46,78,1,4,16,49}进行由大到小排序。
以下这个程序非常重要。叫做冒泡排序。
#include<stdio.h>int main(){ int num[10] = {12,89,34,25,46,78,1,4,16,49};//定义并声明数组 int temp = 0;//定义一个中间变量 for(int i= 0;i<9;i++) { for(int j= 0;j<=9-i;j++) { if(num[j]>num[j+1]) { temp=num[j+1]; num[j+1]=num[j]; num[j]=temp; } } } for(int i=0;i<10;i++) { printf("%d\t",num[i]); } return 0; }
4.求数组int num[10] = {12,89,34,25,46,78,1,4,16,49}中的最大值最小值。
#include<stdio.h>int main(){ int num[10] = {12,89,34,25,46,78,1,4,16,49}; int max=num[0]; int min=num[0]; /*数组的最大值*/ for(int i=0;i<10;i++) { if(num[i]>max) { max=num[i]; } } printf("最大值是:%d\n",max); /*数组的最小值*/ for(int i=0;i<10;i++) { if(num[i]<min) { min=num[i]; } } printf("最小值是:%d\n",min); return 0; }
5.数组的增加(课堂上考虑优化)
#include<stdio.h>int main(){ int num[5]={1,2,3,4,5}; int num1[6]; int index; int value; printf("请输入要插入值的下标:\n"); scanf("%d",&index); printf("请输入要插入的值:\n"); scanf("%d",&value); for(int i=0;i<6;i++) { if(i<index) { num1[i]=num[i]; } else if(i==index) { num1[i]=value; } else { num1[i]=num[i-1]; } } for(int i=0;i<6;i++) { printf("%d ",num1[i]); }}
6.数组删除(课堂上考虑优化)
#include<stdio.h>int main(){ int num[5]={1,2,3,4,5}; int num1[4]; int index; printf("请输入要删除值的下标:\n"); scanf("%d",&index); for(int i=0;i<4;i++) { if(i<index) { num1[i]=num[i]; } else { num1[i]=num[i+1]; } } for(int i=0;i<4;i++) { printf("%d ",num1[i]); }}
数组名存放在堆内存中
数组元素存放在栈内存中
在实际应用中我们经常需要将以下表格的值存储到数组中。
12 | 33 | 56 |
---|---|---|
34 | 44 | 55 |
64 | 37 | 78 |
25 | 67 | 42 |
在C语言中我们把上面这种具有行列的数组称为二维数组。二维数据的实质就是一个矩阵。
语法
数据类型 变量名[行数量] [列数量];
示例
float pass[3][4];//定义了一个三行四列的二维数组
int num[3][4]={{1,2,3,4},{2,3,4,5},{3,4,5,6}};
这种二维数组的初始化方式比较常用,也比较直观。我们第一个花括号里的四个元素赋值给二维数组第一行,第二个花括号里的四个元素赋值给二维数组第二行,以此类推。
int num[3][4]={1,2,3,4,2,3,4,5,3,4,5,6};
就是将第一种初始化方式中的花括号去掉,保留最外花括号。这中方式与第一种方式效果相同,但是与第一种相比,数据比较混乱,界限不清楚,如果数据过多,会造成数据遗漏且不易检查。
首先我们将本节开头的表格存储到二维数组中,然后取出67这个元素
#include<stdio.h>int main(){ int num[4][3] = {{12,33,56},{34,44,55},{64,37,78},{25,67,42}};//定义了一个4行3列的二维数组 printf("%d\n",num[3][1]) ;//根据表格我们知道 67 在4行3列,所以我们之间使用num[3][1]取到值 return 0; }
上面这个程序是直接取值。下面我们利用for循环遍历出数组中的所有值
#include<stdio.h>int main(){ int num[4][3] = {{12,33,56},{34,44,55},{64,37,78},{25,67,42}};//定义了一个4行3列的二维数组 for(int i = 0;i<4;i++)//外层循环 循环行 执行一次表示一行 { for(int j = 0;j<3;j++)//内层循环循环列 外层循环循环一次,内层循环循环一遍 执行一遍表示一行遍历完 { printf("%d\t",num[i][j]); } printf("\n");//每循环一行 换行 } return 0; }
示例:将上例中的二维数组求出最大值,并且输出最大值的行号和列号。
#include<stdio.h>int main(){ /*求最大值*/ int num[4][3] = {{12,33,56},{34,44,55},{64,37,78},{25,67,42}};//声明创建二维数组 int max=num[0][0];//二维数组的第一行第一列赋值给变量 int h,l;//定义行和列 for(int i= 0;i<4;i++)//遍历二维数组 行 { for(int j= 0;j<4;j++)//遍历二维数组 列 { if(max<=num[i][j]) { max=num[i][j]; h=i; l=j; } } } printf("最大值是:%d,它所在行是:%d,所在列是:%d",max,h+1,l+1); }
字符数组在应用中非常广泛,尤其作为字符串形式的使用。在C语言中没有字符串类型,字符串是以字符数组来处理的。
字符数组的定义与数值型数组的定义类似。
char c[10];//定义字符数组//或者 int c[];//鱼可以表示字符数组,合法但浪费空间 c[0]='I'; c[1]=''; c[2]='a'; c[3]='m'; c[4]=''; c[5]='h'; c[6]='a'; c[7]='p'; c[8]='p'; c[9]='y';
char c[10];//定义字符数组//对字符数组逐个初始化 c[0]='I'; c[1]=''; c[2]='a'; c[3]='m'; c[4]=''; c[5]='h'; c[6]='a'; c[7]='p'; c[8]='p'; c[9]='y';
例如
char c[10]={'I',' ','a','m',' ','h','a','p','p','y'};
上例中,我们以此给字符数组c的是个元素赋值。注意,如果花括号中元素的个数大于数组长度10,则会出现错误;如果小的话,则只能赋值给数组中的前面那些元素,其余的元素自动设置为空。
在初始化的时候,我们也可以不给定数组长度,数组长度根据花括号中的元素自动设定。
char c[]={'I',' ','a','m',' ','h','a','p','p','y'};//长度自动设定为10 。
char din[5][5]={{'','','*','',''},{'','*','','*',''},{'*','','','','*'},{'','','*','',''}};
他代表一个菱形的平面图形。
#include<stdio.h>int main(){ char c[10]={'I',' ','a','m',' ','h','a','p','p','y'}; for(int i=0;i<10;i++) { printf("%c",c[i]); } return 0;}
将上面二维数组的输出
#include<stdio.h>int main(){ char din[5][5]={{' ',' ','*',' ',' '},{' ','*',' ','*',' '},{'*',' ',' ',' ','*'},{' ','*',' ',' ','*'},{' ',' ','*',' ',' '}}; for(int i=0;i<5;i++) { for(int j=0;j<5;j++) { printf("%c",din[i][j]); } printf("\n"); } return 0;}
在C语言中,没有字符串类型,字符串是用字符数组来表示的。
C语言规定使用 ’\0' 作为字符串的结束标志,字符数组在遇到 ‘\0‘时就表示字符串结束,而把它前面字符组成一个字符串。我们在用字符数组来存储字符串的时候,C语言常常会自动加一个’\0‘作为结束符。
例如:我们在第一节使用过printf("Hello World\n");输出一个字符串,这个字符串在向内存中存储时会自动在\n后面加一个\0,作为结束。输出的时候遇到\0就停止。
char c[]={"I am Happy"};//或者直接省略掉花括号char c[]="I am Happy";
这里不像之前声明字符数组那样,用单个字符来声明,而是直接以字符串的形式给定初值。显然这种方式更加直观,方便。
我们在吃实话字符串的时候,不需要把 \0 字符放在字符串常量的末尾。C 编译器会在初始化数组时,自动把 '\0' 放在字符串的末尾。
注意:在输出时,输出的 结果不包括\0
前面我们用字符形式输出了字符串。下面我们用字符串的形式来输出字符串。
#include<stdio.h>int main(){ char din[] = {"I an Happy"};//注意后面的\0 printf("%s",din);//以字符串的饿形式输出 return 0;}
#include<stdio.h>int main(){ char str1[5],str2[5],str3[5];//声明3个字符数组 scanf("%s%s%s",str1,str2,str3); //输入 每个数组中间用 空格分开 注意这里不用&这个符号 printf("%s %s %s",str1,str2,str3);//输出}
C语言提供了大量操作字符串的函数
序号 | 函数 | 描述 |
---|---|---|
1 | puts(字符数组) | 将一个字符串输出 |
2 | gets(字符数组) | 输入一个字符串到字符数组 |
3 | strcat(字符数组1,字符数组2) | 连接俩个字符串 |
4 | strcpy(字符数组1,字符数组2) | 复制字符串 |
5 | strncpy(字符数组1,字符数组2,n) | 复制字符串 |
6 | strcmp(字符串1,字符串2) | 字符串比较函数 |
7 | strlen(字符数组) | 测试字符数组的长度 |
8 | strlwr(字符串) | 转换为小写 |
9 | strupr(字符串) | 转换为大写 |
puts(字符数组)
例子
#include<stdio.h>int main(){ char str1[]="Hello\nXian";//转义字符也可以发挥作用 puts(str1);}
gets(字符数组)
例子
说明:输入Hello,则输出Hello,gets将Hello这个字符串送给str1,注意送了6个字符而不是5个字符。
#include<stdio.h>int main(){ char str1[15]; gets(str1); puts(str1);}
strcat(字符数组1,字符数组2)
将字符数组2连接到字符数组1的后面
例子
#include<stdio.h>#include <string.h>//int main(){ char str1[30] ={"I am from"}; char str2[] ={" Chian"}; printf("%s",strcat(str1,str2));}
strcpy(字符数组1,字符数组2)
将字符数组2赋值到字符数组1
例子
#include<stdio.h>#include <string.h>int main(){ char str1[30] ; char str2[] ={"Chian"}; strcpy(str1,str2); printf("%s",str1);}
strcpy(字符数组1,字符数组2,n)
将字符数组2前面n个字符复制到字符数组1中
#include<stdio.h>#include <string.h>int main(){ char str1[30] ; char str2[] ={"Chian"}; strncpy(str1,str2,2); printf("%s",str1);}
strcmp(字符串1,字符串2)
比较俩个字符串是否相等 返回bool类型。如果相等则返回0的数字,不等则返回其他数字。
#include<stdio.h>#include <string.h>int main(){ char str1[]="Hello" ; char str2[] ="Hello"; char str3[] ="hello"; printf("%d\n",strcmp(str1,str2));//0 printf("%d\n",strcmp(str1,str3));//-1 }
strlen(字符数组)
#include<stdio.h>#include <string.h>int main(){ char str1[]="Hello" ; printf("%d\n",strlen(str1));//5}
strlwr(字符串) , strupr(字符串)
#include<stdio.h>#include <string.h>int main(){ char str1[]="HellO" ; strlwr(str1);//将字符串中的大写字母转换为小写 printf("%s",str1);//5}
#include<stdio.h>#include <string.h>int main(){ char str1[]="HellO" ; strupr(str1);//将字符串中的大写字母转换为小写 printf("%s",str1);//5}
作业:
1、将一个长度为8的整型数组中的值按逆序存放;
#include <stdio.h>#define N 8/* 将一个长度为8的整型数组中的值按逆序存放;(数组中数据自己从键盘动态输入) */int main(int argc, char *argv[]) { int i,a[N]; for(i=0;i<N;i++) { printf("请输入一个整数"); scanf("%d",&a[8-1-i]); } printf("逆序存放为"); for(i=0;i<N;i++) printf("%d\t",a[i]); return 0;}
2、编写一个程序,计算出给定矩阵a[3][3]中主对角线元素的和。
#include <stdio.h>/* 编写一个程序,计算出给定矩阵a[3][3]中主对角线元素的和。 */int main(int argc, char *argv[]) { int a[3][3]={{1,2,3},{4,5,6},{7,8,9}},i,j,sum=0; printf("数组:\n"); for(i=0;i<3;i++) { for(j=0;j<3;j++) printf("%3d",a[i][j]); printf("\n"); } for(i=0;i<3;i++) for(j=0;j<3;j++) if(j==i||j+i==2) sum+=a[i][j]; printf("对角线元素的和=%d",sum); return 0;}
3.打印出杨辉三角的前12行数据,格 式为下三角样式;
#include <stdio.h>#define N 12 /* 打印出杨辉三角的前12行数据,格 式为下三角样式; */int main() { int i,j,y[N][N]; for(i=1;i<N;i++){ y[i][1]=1; y[i][i]=1; for(j=2;j<=i-1;j++) y[i][j]=y[i-1][j]+y[i-1][j-1]; } for(i=1;i<N;i++) { for(j=1;j<=i;j++) printf("%d\t",y[i][j]); printf("\n"); } return 0;}
4.编写一个程序,把一个数插入到一个有序的有10个元素的数组中,并使插入后的数组仍为有序数组。
#include <stdio.h>/* 编写一个程序,把一个数插入到一个有序的有10个元素的数组中,并使插入后的数组仍为有序数组。 */int main(int argc, char *argv[]) { int a[10]={1,3,5,7,9,11,13,15,17,19},i,n; {1,3,5,7,9,11,13,15,17,19} {0,1,2,3,4,5 ,6 ,7 ,8, 9} for(i=0;i<10;i++) printf("%-3d",a[i]); printf("输入插入的数:"); scanf("%d",&n); for(i=9;i>=0;i--) if(a[i]<n) { a[i+1]=n; break; } else a[i+1]=a[i]; if(i<0) a[0]=n; printf("插入以后的数组:"); for(i=0;i<11;i++) printf("%-5d",a[i]); printf("\n"); return 0;}
5.模拟实现 strlwr()函数
#include<stdio.h>#include <string.h>int main(){ // 模拟实现strlwr() char str[] = "ChiAnDDD"; for (int i = 0; i < strlen(str); ++i) { if (str[i]>=65&&str[i]<=90){ str[i] = str[i] + 32; } } puts(str); return 0;}
我们先用函数的思想来打印出下面的图形,让我们来初步感受下函数。
********************Hello World********************
#include<stdio.h>int main()//main函数{ void print_star();//声明print_star()函数 void print_message();//声明print_message()函数 print_star();//调用print_star()函数 print_message();//调用print_message()函数 print_star();//调用print_star()函数 这个函数在main函数中调用了俩次 return 0;}void print_star()//自定义的函数 功能是输出一排*****{ printf("**************\n"); }void print_message()//自定义的函数 功能是输出Hello World{ printf("Hello world!\n");}
对上述函数的说明
返回值类型 函数名 (数据类型1 参数1,......数据类型n 参数n ){ 函数体 return 值; }
说明:
从用户使用角度,分为下面俩类
库函数:由C系统提供的函数。用户不必自己定义,可以直接使用。
比如:
getchar(); putchar();printf();scanf();.........等等。
用户自己定义的函数。
比如上类中的print_star();print_message();
从函数的形式来分,分为四类。
void print_message()//自定义的函数 功能是输出Hello World{ printf("Hello world!\n");}
void add(int a,int b){ int sum=a+b; printf("a+b=%d",sum);}
int add(){ int a,b,sum; sum=a+b; return (sum);}
int add(int a,int b){ int sum=a+b; return (sum);}
这里要细讲 讲函数调用过程 参数 返回值 形参 实参
#include<stdio.h>int main()//main函数{ void add1();//声明add1函数 void add2(int a,int b);//声明add2函数 int add3();//声明add3函数 int add4(int a,int b);//声明add4函数 add1();//调用add1函数 add2(10,20);//调用add2函数,并传递参数10,和20 int sum3=add3();//调用add3函数,并将返回值用变量sum3接收 printf("a+b=%d\n",sum3);//打印输出变量sum3 int sum4=add4(10,20);//调用add4函数,并传递参数10和20,并将返回值用变量sum4接收 printf("a+b=%d\n",sum4);//打印输出变量sum4 return 0;}void add1(){ //无返回值无参数的函数 int a,b,sum; a=10; b=20; sum=a+b; printf("a+b=%d\n",sum); }void add2(int a,int b){ //无返回值有参数的函数 int sum=a+b; printf("a+b=%d",sum);}int add3(){ //有返回值无参数的函数 int a=10; int b=20; int sum=a+b; return (sum); } int add4(int a,int b){ //有返回值有参数的函数 int sum=a+b; return (sum) ;}
练习:用函数来实现求俩个整数的最大值
#include<stdio.h>int main(){ int max(int a,int b);//声明即将要调用的函数max int a,b;//定义俩个整型变量 printf("请输入俩个整数,以求最大值:\n");//提示 scanf("%d,%d",&a,&b);//接收输入的值 赋值给a和b int max1=max(a,b);//调用max函数 传入a和b printf("最大值是:%d",max1); return 0;}int max(int a,int b){ //?: int max = a>b? a:b;//条件运算符 求出最大值 return (max); }
我们发现在上面的例子中,我们在main函数中调用其他函数时,在main函数开头都进行了函数 的声明。比如下面这个函数。
int max(int a,int b)//函数的首行叫做函数原型。{ //?: int max = a>b? a:b;//条件运算符 求出最大值 return (max); }
函数的声明格式是直接将函数原型写在main函数的开头,然后再加一个分号。有俩种形式:
函数返回值类型 函数名(参数类型1 参数名1,....,参数类型n 参数名n);//第一种声明函数的形式函数返回值类型 函数名(参数类型1,.....,参数类型n);//第二章声明函数的形式
这俩种方式都可以 ,虽然第二种简单,但推荐使用第一种。
#include<stdio.h>int main(){ int max(int a,int b);//内部声明即将要调用的函数max int a,b;//定义俩个整型变量 printf("请输入俩个整数,以求最大值:\n");//提示 scanf("%d,%d",&a,&b);//接收输入的值 赋值给a和b int max1=max(a,b);//调用max函数 传入a和b printf("最大值是:%d",max1); return 0;}int max(int a,int b){ //?: int max = a>b? a:b;//条件运算符 求出最大值 return (max); }
#include<stdio.h>int max(int a,int b);//外部声明即将要调用的函数maxint main(){ int a,b;//定义俩个整型变量 printf("请输入俩个整数,以求最大值:\n");//提示 scanf("%d,%d",&a,&b);//接收输入的值 赋值给a和b int max1=max(a,b);//调用max函数 传入a和b printf("最大值是:%d",max1); return 0;}int max(int a,int b){ //?: int max = a>b? a:b;//条件运算符 求出最大值 return (max); }
函数要是写在main函数前面则不用声明
#include<stdio.h>int max(int a,int b)//函数在main函数前面 不用声明 { //?: int max = a>b? a:b;//条件运算符 求出最大值 return (max); }int main(){ int a,b;//定义俩个整型变量 printf("请输入俩个整数,以求最大值:\n");//提示 scanf("%d,%d",&a,&b);//接收输入的值 赋值给a和b int max1=max(a,b);//调用max函数 传入a和b printf("最大值是:%d",max1); return 0;}
函数不允许嵌套定义
int max1(float a,float b)//max1函数{ int sum(){//sum函数,定义在函数max1的内部,这样的操作不被允许 printf("嵌套"); } return (max); }
函数虽然不能嵌套定义,但是可以嵌套调用 (这个地方需要讲函数嵌套调用的流程b)
#include<stdio.h>int main(){ float sum(int a,int b);//声明sum函数 int a,b; printf("请输入俩个整数,以平均值:\n"); scanf("%d,%d",&a,&b); float avg_ab =sum(a,b);//调用sum函数,传递参数a和b,并将返回值用变量avg_ab来接收 printf("平均值是:%f",avg_ab); return 0;}float sum(int a,int b)//定义函数sum,返回值类型是float,参数a和参数b{ float avg(int sum); //声明avg函数 int sum = a+b;//对参数a和b求和,并将和用变量sum接收。 float avg_ab= avg(sum);//调用函数avg,将sum变量传递给函数avg,并将返回值用变量avg_ab接收 return (avg_ab); } float avg(int sum)//定义函数avg,用来求一个整数的一半{ float a=(float)sum/2;//求sum变量的一半,由于sum变量是int类型,所以一半也是int类型,只需强转类型即可 return (a); }
函数的递归调用就是在执行函数的时候,直接或者间接的调用该函数本身,C语言允许函数递归调用。
例子:有5个同学,第5个同学比第4个同学大2岁,第4个同学比第3个同学大2岁,第3个同学比第2个同学大2岁,第2个同学比第1个同学大2岁,第1个同学10岁,问第5个同学几岁?
#include<stdio.h>int main(){ int age(int n,int c);//声明函数age int age_wu=age(1,10);//调用函数age,并传入函数1和10,将返回值用变量age_wu接收 printf("第五个同学%d岁",age_wu); return 0;}int age(int n,int c)//求第五个 先传入1,和以一个同学的年龄 { c=c+2; n++; if(n<=4){//传入1的时候算的是第2个学生的年龄,以此类推,传入4的时候计算的就是第五个同学的年龄 age(n,c);//递归调用age函数 }else { return c; }}
练习:输入一个整数,求一个这个数的阶乘。
#include<stdio.h>int main(){ int jieCheng(int n); printf("请输入一个正整数,以求他的阶乘:\n"); int n; scanf("%d",&n); int ji=jieCheng(4); printf("结果是:%d",age_wu); return 0;}int jieCheng(int n)// { int ji; if(n==1||n==0) { return 1; }else if(n<0) { printf("请输入一个正整数\n"); } else { ji=age(n-1)*n return ji; }}
数组元素可以作为函数的实参,但不能用作形参
例子:从键盘输入10个整数,并求出最大值。
#include<stdio.h>int main()//mian函数{ int max(int m,int n);//声明max函数 printf("请从键盘输入10个整数:\n"); int num[10];//定义一个长度为10的整型数组 for(int i=0;i<10;i++)//给数组循环赋值 { scanf("%d",&num[i]); } int m,z;//m为当前最大者 z当前最大值的下标 m=num[0];//先令当前最大值为数组第一个元素 z=0;//当前最大值的元素的下标是0 for(int i=0;i<10;i++)//循环调用方法 { if(max(m,num[i])>m){ m=num[i];//改变当前最大值 z=i;//记录下当前最大值的下标 } } printf("最大值是%d,他的下标是%d",m,z); return 0;}int max(int m,int n)//定义max函数,求m,n当中的较大者{ int max=m>n?m:n; return max;}
除了数组元素可以作为函数的参数外,数组名(这个数组整体)也可以作为函数的参数
#include<stdio.h>int main()//main函数{ float avg(float array[3]);//声明avg函数 float array[3];//定义一个长度为3的float类型的数组 printf("请输入3科目的成绩;\t"); for(int i=0;i<3;i++)//给数组循环赋值 { scanf("%f",&array[i]); } float avg_scort= avg(array);//调用avg函数 printf("3科目的平均成绩是:%f",avg_scort); return 0;}float avg(float array[3])//定义avg函数{ //传进来3科目的成绩,返回3科目的平均成绩 float avg_scort;//平均成绩 float sum_scort=0;//成绩和 for(int i=0;i<3;i++)//3科目成绩的和 { sum_scort+=array[i]; } avg_scort=sum_scort/3;//求平均成绩 return avg_scort;}
程序说明:
定义变量有3种情况:
在函数开头定义的变量
在函数内的复合语句中定义的变量
在函数的外部定义的变量
在函数的开头定义的变量只在本函数内有效,在函数内的复合语句内定义的变量只在该复合语句内有效,这些变量被称作局部变量。而在函数的外部定义的变量,在所有函数内有效,也就是在本源文件中有效,这样的变量称为全局变量。
#include<stdio.h>int num=12;//全局变量 int main(){ void grr(); int a=1;//局部变量 for(int i=0;i<=3;i++)//i为复合语句内的局部变量 { printf("%d",i); }// printf("%d",i);//变量i在语句外无效 printf("main函数中的a:%d\n",a);//变量a在函数内有效 printf("main函数中的num:%d\n",num);//变量num在main函数内有效 grr(); return 0;}void grr(){ //printf("grr函数中的a:%d",a);//变量a在arr函数内无效 printf("grr函数中的num:%d\n",num);//变量num在main函数内有效 } j
在计算机内存中,计算机为我们提供了2种不同的存储方式。
动态存储方式存放以下数据
函数中的局部变量,如果不专门声明为static修饰,都是动态分配存储空间的。不用static修饰的变量,我们称之为自动变量。自动变量使用auto关键字来声明。
int main(){ auto int b,c;}
在前面我们学习的程序中,在定义变量的时候我们都没有指定其存储类别,这时都默认为auto的。这类型变量在函数执行结束后变量自动释放。
在函数中定义的变量如果用static关键字来修饰的变量,我们称之为静态变量。静态变量在函数调用结束时仍然保留其上一次调用结束时的值。
演示自动变量与静态变量
#include<stdio.h>int main(){ int grr(); for(int i=0;i<3;i++) { int sum=grr(); printf("%d\n",sum); }}int grr(){ int a=1;//自动变量 static int b=2;//静态局部变量 int c=a+b; b++; return c; }
不管是静态的还是自动的,变量都是存放到内存中的,当参与运算时,运算器(cpu)从内存中取值,运算结束后将结果存放到内存中,这样一来一回就要消耗时间。而当我们要频繁的使用某个变量时,定义寄存器变量,我们直接将变量存放在cpu的寄存器中,这样就能提高执行效率。定义寄存器变量用关键字register。
register int a;
但是现在的编译器优化,可以自动的识别哪些变量是要频繁使用的,编译的时候自动的将变量存放在寄存器中,所以定义寄存器变量的意义不大。
#include<stdio.h>int main(){ void grr(); void grr1(); extern int a; a=10; grr(); grr1(); return 0;}void grr1(){ extern int a; printf("arr1%d\n",a); } int a;//在这个位置定义外部变量avoid grr(){ printf("arr%d\n",a); }
以上程序,我们看到,外部变量a定义在main函数和arr1函数的后面,但是在这俩个函数中我们需要使用这个外部变量,按道理外部变量在这俩个函数中是不能使用的,但是使用关键字extern将外部变量a拓展到main函数和arr1函数中。注意:只是拓展到当前函数中
#include<stdio.h>#define abs(x) (((x)>0)?(x):(-(x)))main( ){ int a=-3,b; float c=-2.4,d; b=abs(a); d=abs(c); printf("b=%d,d=%f\n",b,d);}
#include<stdio.h>#define MIN(x,y) (x)<(y)?(x):(y)main(){int i=10,j=15,k;k=10*MIN(i,j);//只做替换不做运算 10* (x)<(y)?(x):(y)printf("%d\n",k);}
#include <stdio.h>#include <stdlib.h>#include <time.h>int main() { int a; srand((unsigned)time(NULL)); for(int i=0;i<8;i++) { a = rand()%3; printf("%d\n", a); } return 0;}
一般方法:
#include<stdio.h>int main(){ double a=100,b=0,n,sum=100;//a:下落反弹高度 ,b:落下的高度,sum:总路程 printf("请输入落地次数n:\n"); scanf("%lf",&n); for(int i=0;i<n;i++) { sum=sum+2*b; a=a/2; b=a; } printf("小球从100m高处落地%.0lf次时,共经过%lf米\n第%.0lf次落地后反弹的高度为%lf米\n",n,sum,n,a);}
#include <stdio.h>int main() { float fun(int n,float num,float sum); float num=fun(1,100,0); printf("共:%f米\n",num); return 0;} float fun(int n,float xia,float sum)//n为第几次落地 xia为下落 { if(n==1) { sum=xia; } else { sum=sum+xia*2; } n++; if(n<11) { fun(n,xia/2,sum); } else { printf("第十次反弹的高度%f\n",xia/2); return sum; }}
当我们在定义变量的时候如下:
int num=19;
计算机会在内存中为我们开辟一块空间,这个空间类似于我们的房间,而每一个房间都有门牌号,内存空间也是一样的,每一个开辟出来的内存空间都是有一个地址来表示该空间的位置。
下面我们使用取地址运算符“&”,来演示一个变量的地址。
#include<stdio.h>int main(){ int num=19; printf("num变量的地址是:%d,这是用十进制输出的\n",&num); printf("num变量的地址是:%x,这是用十六进制输出的\n",&num);}
指针是一个变量,他的值是另一个变量的地址,他和其他变量一样,在使用指针时必须对指针变量进行声明,声明的格式:
数据类型 *指针变量名;
这里的数据类型叫做指针的基类型,他必须是一个有效的C的数据类型,*是用来声明一个有效的指针变量名称。
使用指针时会频繁进行以下几个操作:
1.定义一个指针变量
2.把变量地址赋值给指针
3.访问指针变量中可用地址的值
这些是通过使用一元运算符 ***** 来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作:
#include <stdio.h>int main (){ int var = 20; /* 实际变量的声明 */ int *ip; /* 指针变量的声明 */ ip = &var; /* 在指针变量中存储 var 的地址 */ printf("Address of var variable: %p\n", &var );//ip /* 在指针变量中存储的地址 */ printf("Address stored in ip variable: %p\n", ip ); /* 使用指针访问值 */ printf("Value of *ip variable: %d\n", *ip );//var return 0;}
在使用指针是,要特别注意*(取值)和&(取地址)这俩个运算符的区别
#include<stdio.h>int main(){ int *point,num;//定义指针变量point,num point=#//指针变量point指向num num=10;//给num赋值 printf("num的值:%d\n",num);//取num的值 这里好理解 printf("num的地址是:%x\n",&num);//&是取地址符 这里取num变量的地址 printf("point的地址是:%x\n",&point);//取指针变量point的地址 printf("num的值:%d\n",*point) ;//*指针符号 *point相当于num printf("numd的地址是:%x\n",point);//相当于&num return 0; }
*(取值)和&(取地址)这俩个运算符互为逆运算。
思考
int i=8;&i是多?*(&(i))是多少?
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
NULL 指针是一个定义在标准库中的值为零的常量。请看下面的程序:
#include<stdio.h>int main(){ int *pre = NULL; printf("pred的地址是:%x",pre);//地址为0}
在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
如需检查一个空指针,您可以使用 if 语句,如下所示:
if(ptr) /* 如果 p 非空,则完成 */if(!ptr) /* 如果 p 为空,则完成 */
存储地址的变量叫做指针变量,指针变量指向另一个对象(变量,数组,函数等)。先来看一个例子。
//通过指针变量访问一个整型变量#include<stdio.h>int main(){ int a=100,b=20;//定义整型变量a,b;并初始化 int *p1,*p2;//定义指向整型数据的指针变量 p1=&a;//把a变量的地址赋值给指针变量p1 p2=&b;//把b变量的地址赋值给指针变量p2 printf("a=%d,b=%d\n",a,b);//输出变量a和b的值 printf("*p1=%d,*p2=%d\n",*p1,*p2);//输出变量a和b的值 return 0;}
通过以上几个程序我们可以,看到,当指针与变量之前存在指向关系时,&和*符号不能同时出现。
//当 int *p,a; p=&a;存在时// p=&a, *p=a;
使用指针变量求出俩个数中的较大者:
#include<stdio.h>int main(){ int *p1,*p2,*p; int a,b; printf("请输入a和b的值:\n"); scanf("%d,%d",&a,&b); p1=&a; p2=&b; if(a<b) { //交换的是地址 p=p1; p1=p2;//88 p2=p; } printf("a=%d,b=%d\n",a,b); printf("max=%d,min=%d\n",*p1,*p2); return 0;}
我们说变量可以作为函数的参数进行函数间的值传递。指针变量也是一个变量,那么当然,指针变量也可以作为函数的参数来使用。
示例:输出俩个数中的较大者
#include<stdio.h>int main(){ void aswd(int *p1,int *p2);//声明函数 int a,b;//变量a,b int *point_1,*point_2;//指针变量point_1,point_2 printf("请输入a和b的值:"); scanf("%d,%d",&a,&b);//给ab赋值 point_1=&a;//确立指向关系 point_2=&b; if(a<b) { aswd(point_1,point_2); } printf("a=%d,b=%d\n",a,b); printf("max=%d,min=%d\n",*point_1,*point_2); return 0;}void aswd(int *p1,int *p2){ int temp; temp=*p1; *p1=*p2; *p2=temp;}
示例:输入三个整数,按照从大到小的顺序输出
#include<stdio.h>//包含头文件int main()//main函数{ void exchange(int *p1,int *p2,int *p3);//声明exchange函数 //void swap(int *p1,int *p2);//声明swap函数,在main函数中没用到这个函数,所以这里不需要声明 int a,b,c,*pr1,*pr2,*pr3;//定义变量abc,与三个int型指针变量 printf("请输入a,b,c的值:\n");//提示 scanf("%d,%d,%d",&a,&b,&c);//输入abc的值 pr1=&a;//确定指向关系 pr2=&b; pr3=&c; exchange(pr1,pr2,pr3); printf("a=%d,b=%d,c=%d",a,b,c); return 0;}void exchange(int *p1,int *p2,int *p3){ void swap(int *pt1,int *pt2);//声明swap函数 if(*p1<*p2)//如果a<b { swap(p1,p2);//那就交换a和b的值 } if(*p1<*p3) { swap(p1,p3); } if(*p2<*p3) { swap(p2,p3); }} void swap(int *pt1,int *pt2)//u7{ int temp; temp=*pt1; *pt1=*pt2; *pt2=temp;}
我们在学习指针数组之前,来先看一个示例
#include <stdio.h>const int MAX = 3; int main (){ int var[] = {10, 100, 200}; int i; for (i = 0; i < MAX; i++) { printf("Value of var[%d] = %d\n", i, var[i] ); } return 0;}
这是一个简单的int数据类型的数组,那么我们是否可以用数组来存储一个指针呢?
语法:
数据类型 *指针变量名[数组长度];
通过上面的语法,我们知道,数组指针的定义和普通指针就差[数组长度],和普通数组就差一个*;
例:
int *ptr[MAX];
在这里,把 ptr 声明为一个数组,由 MAX 个整数指针组成。因此,ptr 中的每个元素,都是一个指向 int 值的指针。下面的实例用到了三个整数,它们将存储在一个指针数组中,如下所示:
#include <stdio.h>const int MAX = 3;//定义一个常量 3int main (){ int var[] = {10, 100, 200};//定义一个普通数组 int *ptr[MAX];//定义一个int类型的指针数组 for (int i = 0; i < MAX; i++) { ptr[i] = &var[i]; /* 赋值为整数的地址 */ } for ( i = 0; i < MAX; i++) { printf("Value of var[%d] = %d\n", i, *ptr[i] ); } return 0;}
注意:给指针数组赋值的时候,和普通指针赋值没什么区别,用&符号。并且由于是数组,使用循环赋值,是
var [ ]数组每个元素的地址。当每个指向关系确定后,*ptr[ ] = var[ ],或者ptr[ ] = &var[ ]。
你还可以使用一个指向字符的指针数组,来存储一个字符串列表:
#include <stdio.h>const int MAX = 4;//定义一个常量int main (){ const char *names[] = {//定义了一个指向字符的指针数组 "Zara Ali", "Hina Ali", "Nuha Ali", "Sara Ali", }; int i = 0; for ( i = 0; i < MAX; i++) { printf("Value of names[%d] = %s\n", i, names[i] ); } return 0;}
注意上面这个指针数组没有确立指向关系,直接定义一个字符型的指针数组,用来存储字符串集。本质:
*names[ ]=name[];
C 指针是一个用数值表示的地址。因此,您可以对指针执行算术运算。可以对指针进行四种算术运算:++、--、+、-。
假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数,让我们对该指针执行下列的算术运算:
ptr++;
在执行完上述的运算之后,ptr 将指向位置 1004,因为 ptr 每增加一次,它都将指向下一个整数位置,即当前位置往后移 4 个字节。这个运算会在不影响内存位置中实际值的情况下,移动指针到下一个内存位置。如果 ptr 指向一个地址为 1000 的字符,上面的运算会导致指针指向位置 1001,因为下一个字符位置是在 1001。
我们喜欢在程序中使用指针代替数组,因为变量指针可以递增,而数组不能递增,数组可以看成一个指针常量。下面的程序递增变量指针,以便顺序访问数组中的每一个元素:
#include <stdio.h>const int MAX = 3;int main (){ int var[] = {10, 100, 200}; int i, *ptr; /* 指针中的数组地址 */ ptr = var;//var就是地址 for ( i = 0; i < MAX; i++) { printf("存储地址:var[%d] = %x\n", i, ptr ); printf("存储值:var[%d] = %d\n", i, *ptr ); /* 移动到下一个位置 */ ptr++;//int类型就是+4 } return 0;}
同样地,对指针进行递减运算,即把值减去其数据类型的字节数,如下所示:
#include <stdio.h>const int MAX = 3;int main (){ int var[] = {10, 100, 200}; int i, *ptr; /* 指针中最后一个元素的地址 */ ptr = &var[MAX-1]; for ( i = MAX; i > 0; i--) { printf("存储地址:var[%d] = %x\n", i-1, ptr ); printf("存储值:var[%d] = %d\n", i-1, *ptr ); /* 移动到下一个位置 */ ptr--; } return 0;}
指针可以用关系运算符进行比较,如 ==、< 和 >。如果 p1 和 p2 指向两个相关的变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较。
下面的程序修改了上面的实例,只要变量指针所指向的地址小于或等于数组的最后一个元素的地址 &var[MAX - 1],则把变量指针进行递增:
#include <stdio.h>const int MAX = 3;int main (){ int var[] = {10, 100, 200}; int i, *ptr; /* 指针中最后一个元素的地址 */ ptr = &var[MAX-1]; for ( i = MAX; i > 0; i--) { printf("存储地址:var[%d] = %x\n", i-1, ptr ); printf("存储值:var[%d] = %d\n", i-1, *ptr ); /* 移动到下一个位置 */ ptr--; } return 0;}
指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。
一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:
int **var;
当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符,如下面实例所示:
#include <stdio.h>int main (){ int var; int *ptr; int **pptr; var = 3000; /* 获取 var 的地址 */ ptr = &var; /* 使用运算符 & 获取 ptr 的地址 */ pptr = &ptr; /* 使用 pptr 获取值 */ printf("Value of var = %d\7n", var ); //原变量的值 printf("Value available at *ptr = %d\n", *ptr ); //*ptr=var printf("Value available at **pptr = %d\n", **pptr);//**pptr=*ptr=var return 0;}
1. 静态内存
静态内存是指在程序开始运行时由编译器分配的内存,它的分配是在程序开始编译时完成的,不占用CPU资源。
程序中的各种变量,在编译时系统已经为其分配了所需的内存空间,当该变量在作用域内使用完毕时,系统会
自动释放所占用的内存空间。
变量的分配与释放,都无须程序员自行考虑。
eg:基本类型,数组
2. 动态内存
用户无法确定空间大小,或者空间太大,栈上无法分配时,会采用动态内存分配。
3. 区别
a) 静态内存分配在编译时完成,不占用CPU资源; 动态内存分配在运行时,分配与释放都占用CPU资源。
b) 静态内存在栈(stack)上分配; 动态内存在堆(heap)上分配。
c) 动态内存分配需要指针和引用类型支持,静态不需要。
d) 静态内存分配是按计划分配,由编译器负责; 动态内存分配是按需分配,由程序员负责。
1.malloc()函数
void * malloc(unsigned int size)//函数
我们可以看见上面函数的参数是一个无符号的int类型,参数表示要分配的一段大小为size的连续空间,返回值是这段连续空间的首地址。
值得注意的是,若内存分配失败,则返回一个NULL。(内存不够,或操作系统出现问题)
例子:
#include<stdio.h>#include<stdlib.h>int main(){ char *a; a=(char *)malloc(10*sizeof(char)); if(a==NULL) { printf("内存分配失败"); exit; } *a='H'; *(a+1)='e'; *(a+2)='l'; *(a+3)='l'; *(a+4)='o'; *(a+5)='\n'; *(a+6)='\0'; printf(a);}
释放动态内存需要用到一个函数,free(),他释放的是动态内存。
void *free(void *P);
例子:
#include<stdio.h>#include<stdlib.h>int main(){ char *a,*b; a=(char *)malloc(sizeof(char)*10); *a='H'; *(a+1)='e'; *(a+2)='l'; *(a+3)='l'; *(a+4)='o'; *(a+5)='\n'; *(a+6)='\0'; b=a; printf("a:%s\n",a); printf("b:%s\n",b); free(b);//释放掉指针指向的哪块内存地址 printf("a:%s\n",a); printf("b:%s\n",b);}
在开始结构体之前,我们先来看一个题,假如现在是2:44分钟,再加上1小时32分钟,是几点几分钟?(4:16)
按照我们之前学的程序,可写出如下程序:
#include<stdio.h>int main(){ int now_h=2; int now_m=44; int add_h=1; int add_m=32; int f_h=now_h+now_m+(now_m+add_h)/60; int f_m=(now_m+add_h)%60; printf("%d:%d",f_h,f_m); return 0;}
通过上面的程序,我们解决了这个问题,但是仔细想想,解决上面的问题,我们定义了6个变量,且只涉及时分俩个值,如果加上年月日,时分秒,就要定义24个变量,略显的繁琐。
结构体是一种数据类型,基本数据类型是单一的数据类型,只能表示一些简单的事物,结构体数据类型是将多种基本数据类型和派生数据类型结合起来结合起来。
声明结构体数据类型用struct关键字
struct 结构体类型名 {成员声明列表;};//不要忘记分号
举例:
struct time{ int hour; int min;};
我们用这个结构体这来解决课程开头的列子:
#include<stdio.h>struct time{//声明结构体 int hour; int min;};int main(){ struct time now={2,44};//定义结构体变量now并给结构体赋值hour=2,min=44 struct time add={1,32};//定义结构体变量add并给结构体赋值hour=1,min=32 printf("%d:%d",now.hour+add.hour+(now.min+add.min)/60,(now.min+add.min)%60);//计算 return 0;}
第二种声明的方式:
struct 结构体类型名 {成员声明列表;}结构体变量名1,结构体变量名2.....;//不要忘记分号
这种方式在声明结构体的同时就定义了结构体变量。
#include<stdio.h>struct time{ int hour; int min;}now,add;int main(){ now={2,44}; add={1,32}; printf("%d:%d",now.hour+add.hour+(now.min+add.min)/60,(now.min+add.min)%60); return 0;}
通过上面的方式,我们可以总结下:
第三种方式:结构体作为结构体成员
struct time{ int hour; int min;};struct 结构体类型名 {成员变量;struct time date;}结构体变量名1,结构体变量名2.....;//不要忘记分号
第四种方式:匿名结构体
struct{ 成员; }结构体变量名1,结构体变量名2.....;//不要忘记分号
#include<stdio.h>struct { int hour; int min;}now,add;int main(){ now={2,44}; add={1,32}; printf("%d:%d",now.hour+add.hour+(now.min+add.min)/60,(now.min+add.min)%60); return 0;}
现在用学生来举例,学生有属性学号,姓名,年龄,性别;
结构体声明:
struct student{ int number; char name[256]; int age; char sex[2];};
如何通过结构体变量访问结构体成员呢?使用 . 访问成员运算符。
对上面的结构体初始化:
1.逐个初始化
#include<stdio.h>#include<string.h>struct student{//声明结构体 int number; char name[256]; int age; char sex[4];};int main(){ struct student alan;////定义结构体变量alan,并给alan的成员赋值 alan.number=01; strcpy(alan.name,"Alan");//这个地方没有办法将字符串"Alan"这个长度为5的字符串转为char[256],所以用字符串复制函数来赋值 alan.age=23; strcpy(alan.sex,"男"); printf("%d,%s,%d,%s",alan.number,alan.name,alan.age,alan.sex); return 0;}
2.整个初始化
结构体可以像数组那样,直接全部初始化
#include<stdio.h>struct student{//声明结构体 int number; char name[256]; int age; char sex[4];};int main(){ struct student alan={01,"Alan",23,"女"}; //定义结构体变量alan,并给alan的成员赋值 printf("%d,%s,%d,%s",alan.number,alan.name,alan.age,alan.sex); return 0;}
在上面列子的基础上,我们继续往近加代码:
#include<stdio.h>struct student{//声明结构体 int number; char name[256]; int age; char sex[4];};int main(){ struct student alan={01,"Alan",23,"女"}; //定义结构体变量alan,并给alan的成员赋值 printf("%d,%s,%d,%s\n",alan.number,alan.name,alan.age,alan.sex); struct student bob={02,"Bob",24,"男"}; //定义结构体变量alan,并给alan的成员赋值 printf("%d,%s,%d,%s\n",bob.number,bob.name,bob.age,bob.sex); return 0;}
上面代码我们发现,声明一个结构体,其实就是声明一个结构体数据类型,通过这个数据类型我们可以得到多个结构体变量,不同的结构体变量有这不同的值。
上面我们声明并且定义了结构体变量。通过结构体变量,我们很方便的存储了俩个学生的信息。那么想一想,如果要存全班的学生信息,该怎么处理呢?通过前面的知识,存储多个数据我们使用数组,那么如果我们能把结构体存放到数组中,这个问题就得到了解决。
#include<stdio.h>struct student{//声明结构体 int number; char name[256]; int age; char sex[4];};int main(){ struct student stu[4]{//定义结构体类型的数组,这个数组里存放结构体 {01,"Alan",18,"女"},//结构体变量Alan {02,"Bob",19,"男"},//结构体变量Bob {03,"Mdks",23,"男"},//结构体变量Mdks {04,"Xixi",24,"女"} //结构体变量Xixi }; for(int i=0;i<4;i++){//遍历结构体数组 printf("%d,%s,%d,%s\n",stu[i].number,stu[i].name,stu[i].age,stu[i].sex); } return 0;}
使用分量运算符来获取成员
先声明一个结构体:
#include<stdio.h>struct Books//声明结构体{ char title[50]; char author[50]; char subject[100]; int book_id;};int main(){ struct Books *struct_pointer;//定义结构体指针 struct Books Book1{"Java从入门到精通","laile","sasasasa",32};//定义结构体变量 struct_pointer = &Book1;//给结构体指针赋值 赋结构体变量地址 printf("%s",struct_pointer->title); return 0;}
#include<stdio.h>struct student{//声明结构体 int number; char name[256]; int age; char sex[4];};int main(){ struct student stu[4]{ {01,"Alan",18,"女"}, {02,"Bob",19,"男"}, {03,"Mdks",23,"男"}, {04,"Xixi",24,"女"} }; struct student *p; p=stu; printf("%s",p->name); return 0;}
回到我们最开始的程序
#include<stdio.h>struct time{ int hour; int min;};int main(){ struct time add1(struct time now,struct time add); struct time now={3,55}; struct time add={1,22}; struct time n_time= add1(now,add); printf("%d:%d",n_time.hour,n_time.min); return 0;}struct time add1(struct time now,struct time add){ int nf=now.hour+add.hour+(now.min+add.min)/60; int nm=(now.min+add.min)%60; struct time n_time={nf,nm}; return n_time;}
计算俩点之间的距离(用结构体,以及函数实现)
#include<stdio.h>#include<math.h>struct coor{ int x; int y;};int main(){ double juli(struct coor first,struct coor second); struct coor first,second; printf("请输入第一个点的坐标:\n"); scanf("%d,%d",&first.x,&first.y); printf("请输入第二个点的坐标:\n"); scanf("%d,%d",&second.x,&second.y); double ajuli = juli(first,second); printf("%lf",ajuli); return 0;}double juli(struct coor first,struct coor second){ int xdif=first.x-second.x; int ydif=first.y-second.y; double ajuli; ajuli=sqrt(xdif*xdif+ydif*ydif); return ajuli;}
我们首先来考虑一个问题,我们要在数组里存放班级信息,有的班级60个人,有的班级40个人,那在定义数组的时候就要定义长度60的数组,对40人班级来说,内存是浪费的,当我们不清楚班级人数的时候,数组的长度需要更大的长度(足够大),这样浪费会更加严重。链表就帮我们解决了这个问题,链表可以动态的增减减少长度。
链表是一种常见的基础数据结构,结构体指针在这里得到了充分的利用。链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。链表都有一个头指针,一般以head来表示,存放的是一个地址。链表中的节点分为两类,头结点和一般节点,头结点是没有数据域的。链表中每个节点都分为两部分,一个数据域,一个是指针域。说到这里你应该就明白了,链表就如同车链子一样,head指向第一个元素:第一个元素又指向第二个元素;……,直到最后一个元素,该元素不再指向其它元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束。
作为有强大功能的链表,对他的操作当然有许多,比如:链表的创建,修改,删除,插入,输出,排序,反序,清空链表的元素,求链表的长度等等。
#include<stdio.h>struct Student{//声明结构体 int num; float score; struct Student * next;//结构体指针};int main(){ struct Student a,b,c,*head,*p;//定义三个结构体变量,定义头指针变量,定义指针变量p a.num=100;a.score=89.5;//给a同学赋值 b.num=200;b.score=90.9;//给b同学赋值 c.num=300;c.score=69.5;//给出c同学赋值 head=&a;//让head指向a节点 a.next=&b;//a指向b节点 b.next=&c;//b指向c节点 c.next=NULL;//c节点指针域为空 p=head;//头指针复制指针p do{ printf("%d,%5.1f\n",p->num,p->score); p=p->next; }while(p!=NULL); return 0;}
#include<stdio.h>#include<stdlib.h>struct Student{ char name[10]; struct Student *next;};int main(){ struct Student s1={"张三",NULL}; struct Student s2={"李四",NULL}; struct Student s3={"王五",NULL}; struct Student s4={"杜恒",NULL}; s1.next=&s2; s2.next=&s3; s3.next=&s4; struct Student *p; p=&s1; int n=0; while(1) { n++; printf("%s\n",p->name); if(p->next!=NULL) { p= p->next; } else { break; } } printf("%d",n); }
前面详细地介绍了顺序表,本节给大家介绍另外一种线性存储结构——链表。
链表,别名链式存储结构或单链表,用于存储逻辑关系为 "一对一" 的数据。与顺序表不同,链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其物理存储位置是随机的。
例如,使用链表存储 {1,2,3}
,数据的物理存储状态如图 1 所示:
图 1 链表随机存储数据
我们看到,图 1 根本无法体现出各数据之间的逻辑关系。对此,链表的解决方案是,每个数据元素在存储时都配备一个指针,用于指向自己的直接后继元素。如图 2 所示:
图 2 各数据元素配备指针
像图 2 这样,数据元素随机存储,并通过指针表示数据之间逻辑关系的存储结构就是链式存储结构。
从图 2 可以看到,链表中每个数据的存储都由以下两部分组成:
即链表中存储各数据元素的结构如图 3 所示:
图 3 节点结构
图 3 所示的结构在链表中称为节点。也就是说,链表实际存储的是一个一个的节点,真正的数据元素包含在这些节点中,如图 4 所示:
图 4 链表中的节点
因此,链表中每个节点的具体实现,需要使用 C 语言中的结构体,具体实现代码为:
typedef struct Link{ char elem; //代表数据域 struct Link * next; //代表指针域,指向直接后继元素}link; //link为节点名,每个节点都是一个 link 结构体
提示,由于指针域中的指针要指向的也是一个节点,因此要声明为 Link 类型(这里要写成 struct Link*
的形式)。
其实,图 4 所示的链表结构并不完整。一个完整的链表需要由以下几部分构成:
头指针:一个普通的指针,它的特点是永远指向链表第一个节点的位置。很明显,头指针用于指明链表的位置,便于后期找到链表并使用表中的数据;
节点
:链表中的节点又细分为
头节点
、
首元节点
和其他节点:
因此,一个存储 {1,2,3}
的完整链表结构如图 5 所示:
图 5 完整的链表示意图
注意:链表中有头节点时,头指针指向头节点;反之,若链表中没有头节点,则头指针指向首元节点。
明白了链表的基本结构,下面我们来学习如何创建一个链表。
创建一个链表需要做如下工作:
例如,创建一个存储 {1,2,3,4}
且无头节点的链表,C 语言实现代码如下:
link * initLink(){ link * p=NULL;//创建头指针 link * temp = (link*)malloc(sizeof(link));//创建首元节点 //首元节点先初始化 temp->elem = 1; temp->next = NULL; p = temp;//头指针指向首元节点 //从第二个节点开始创建 for (int i=2; i<5; i++) { //创建一个新节点并初始化 link *a=(link*)malloc(sizeof(link)); a->elem=i; a->next=NULL; //将temp节点与新建立的a节点建立逻辑关系 temp->next=a; //指针temp每次都指向新链表的最后一个节点,其实就是 a节点,这里写temp=a也对 temp=temp->next; } //返回建立的节点,只返回头指针 p即可,通过头指针即可找到整个链表 return p;}
如果想创建一个存储 {1,2,3,4}
且含头节点的链表,则 C 语言实现代码为:
link * initLink(){ link * p=(link*)malloc(sizeof(link));//创建一个头结点 link * temp=p;//声明一个指针指向头结点, //生成链表 for (int i=1; i<5; i++) { link *a=(link*)malloc(sizeof(link)); a->elem=i; a->next=NULL; temp->next=a; temp=temp->next; } return p;}
我们只需在主函数中调用 initLink 函数,即可轻松创建一个存储 {1,2,3,4}
的链表,C 语言完整代码如下:
#include <stdio.h>#include <stdlib.h>//链表中节点的结构typedef struct Link{ int elem; struct Link *next;}link;//初始化链表的函数link * initLink();//用于输出链表的函数void display(link *p);int main() { //初始化链表(1,2,3,4) printf("初始化链表为:\n"); link *p=initLink(); display(p); return 0;}link * initLink(){ link * p=NULL;//创建头指针 link * temp = (link*)malloc(sizeof(link));//创建首元节点 //首元节点先初始化 temp->elem = 1; temp->next = NULL; p = temp;//头指针指向首元节点 for (int i=2; i<5; i++) { link *a=(link*)malloc(sizeof(link)); a->elem=i; a->next=NULL; temp->next=a; temp=temp->next; } return p;}void display(link *p){ link* temp=p;//将temp指针重新指向头结点 //只要temp指针指向的结点的next不是Null,就执行输出语句。 while (temp) { printf("%d ",temp->elem); temp=temp->next; } printf("\n");}
程序运行结果为:
初始化链表为:
1 2 3 4
注意,如果使用带有头节点创建链表的方式,则输出链表的 display 函数需要做适当地修改:
void display(link *p){ link* temp=p;//将temp指针重新指向头结点 //只要temp指针指向的结点的next不是Null,就执行输出语句。 while (temp->next) { temp=temp->next; printf("%d",temp->elem); } printf("\n");}
《什么是单链表》一节我们学习了如何使用链表存储数据元素,以及如何使用 C 语言创建链表。本节将详细介绍对链表的一些基本操作,包括对链表中数据的添加、删除、查找(遍历)和更改。
注意,以下对链表的操作实现均建立在已创建好链表的基础上,创建链表的代码如下所示:
//声明节点结构typedef struct Link{ int elem;//存储整形元素 struct Link *next;//指向直接后继元素的指针}link;//创建链表的函数link * initLink(){ link * p=(link*)malloc(sizeof(link));//创建一个头结点 link * temp=p;//声明一个指针指向头结点,用于遍历链表 //生成链表 for (int i=1; i<5; i++) { //创建节点并初始化 link *a=(link*)malloc(sizeof(link)); a->elem=i; a->next=NULL; //建立新节点与直接前驱节点的逻辑关系 temp->next=a; temp=temp->next; } return p;}
从实现代码中可以看到,该链表是一个具有头节点的链表。由于头节点本身不用于存储数据,因此在实现对链表中数据的"增删查改"时要引起注意。
同顺序表一样,向链表中增添元素,根据添加位置不同,可分为以下 3 种情况:
虽然新元素的插入位置不固定,但是链表插入元素的思想是固定的,只需做以下两步操作,即可将新元素插入到指定的位置:
例如,我们在链表 {1,2,3,4}
的基础上分别实现在头部、中间部位、尾部插入新元素 5,其实现过程如图 1 所示:
图 1 链表中插入元素的 3 种情况示意图
从图中可以看出,虽然新元素的插入位置不同,但实现插入操作的方法是一致的,都是先执行步骤 1 ,再执行步骤 2。
注意:链表插入元素的操作必须是先步骤 1,再步骤 2;反之,若先执行步骤 2,除非再添加一个指针,作为插入位置后续链表的头指针,否则会导致插入位置后的这部分链表丢失,无法再实现步骤 1。
通过以上的讲解,我们可以尝试编写 C 语言代码来实现链表插入元素的操作:
//p为原链表,elem表示新数据元素,add表示新元素要插入的位置link * insertElem(link * p, int elem, int add) { link * temp = p;//创建临时结点temp //首先找到要插入位置的上一个结点 for (int i = 1; i < add; i++) { temp = temp->next; if (temp == NULL) { printf("插入位置无效\n"); return p; } } //创建插入结点c link * c = (link*)malloc(sizeof(link)); c->elem = elem; //向链表中插入结点 c->next = temp->next; temp->next = c; return p;}
从链表中删除指定数据元素时,实则就是将存有该数据元素的节点从链表中摘除,但作为一名合格的程序员,要对存储空间负责,对不再利用的存储空间要及时释放。因此,从链表中删除数据元素需要进行以下 2 步操作:
其中,从链表上摘除某节点的实现非常简单,只需找到该节点的直接前驱节点 temp,执行一行程序:
temp->next=temp->next->next;
例如,从存有 {1,2,3,4}
的链表中删除元素 3,则此代码的执行效果如图 2 所示:
图 2 链表删除元素示意图
因此,链表删除元素的 C 语言实现如下所示:
//p为原链表,add为要删除元素的值link * delElem(link * p, int add) { link * temp = p; //遍历到被删除结点的上一个结点 for (int i = 1; i < add; i++) { temp = temp->next; if (temp->next == NULL) { printf("没有该结点\n"); return p; } } link * del = temp->next;//单独设置一个指针指向被删除结点,以防丢失 temp->next = temp->next->next;//删除某个结点的方法就是更改前一个结点的指针域 free(del);//手动释放该结点,防止内存泄漏 return p;}
我们可以看到,从链表上摘下的节点 del 最终通过 free 函数进行了手动释放。
在链表中查找指定数据元素,最常用的方法是:从表头依次遍历表中节点,用被查找元素与各节点数据域中存储的数据元素进行比对,直至比对成功或遍历至链表最末端的 NULL
(比对失败的标志)。
因此,链表中查找特定数据元素的 C 语言实现代码为:
//p为原链表,elem表示被查找元素、int selectElem(link * p,int elem){//新建一个指针t,初始化为头指针 p link * t=p; int i=1; //由于头节点的存在,因此while中的判断为t->next while (t->next) { t=t->next; if (t->elem==elem) { return i; } i++; } //程序执行至此处,表示查找失败 return -1;}
注意,遍历有头节点的链表时,需避免头节点对测试数据的影响,因此在遍历链表时,建立使用上面代码中的遍历方法,直接越过头节点对链表进行有效遍历。
更新链表中的元素,只需通过遍历找到存储此元素的节点,对节点中的数据域做更改操作即可。
直接给出链表中更新数据元素的 C 语言实现代码:
//更新函数,其中,add 表示更改结点在链表中的位置,newElem 为新的数据域的值link *amendElem(link * p,int add,int newElem){ link * temp=p; temp=temp->next;//在遍历之前,temp指向首元结点 //遍历到待更新结点 for (int i=1; i<add; i++) { temp=temp->next; } temp->elem=newElem; return p;}
以上内容详细介绍了对链表中数据元素做"增删查改"的实现过程及 C 语言代码,在此给出本节的完整可运行代码:
#include <stdio.h>#include <stdlib.h>typedef struct Link { int elem; struct Link *next;}link;link * initLink();//链表插入的函数,p是链表,elem是插入的结点的数据域,add是插入的位置link * insertElem(link * p, int elem, int add);//删除结点的函数,p代表操作链表,add代表删除节点的位置link * delElem(link * p, int add);//查找结点的函数,elem为目标结点的数据域的值int selectElem(link * p, int elem);//更新结点的函数,newElem为新的数据域的值link *amendElem(link * p, int add, int newElem);void display(link *p);int main() { //初始化链表(1,2,3,4) printf("初始化链表为:\n"); link *p = initLink(); display(p); printf("在第4的位置插入元素5:\n"); p = insertElem(p, 5, 4); display(p); printf("删除元素3:\n"); p = delElem(p, 3); display(p); printf("查找元素2的位置为:\n"); int address = selectElem(p, 2); if (address == -1) { printf("没有该元素"); } else { printf("元素2的位置为:%d\n", address); } printf("更改第3的位置上的数据为7:\n"); p = amendElem(p, 3, 7); display(p); return 0;}link * initLink() { link * p = (link*)malloc(sizeof(link));//创建一个头结点 link * temp = p;//声明一个指针指向头结点,用于遍历链表 //生成链表 for (int i = 1; i < 5; i++) { link *a = (link*)malloc(sizeof(link)); a->elem = i; a->next = NULL; temp->next = a; temp = temp->next; } return p;}link * insertElem(link * p, int elem, int add) { link * temp = p;//创建临时结点temp //首先找到要插入位置的上一个结点 for (int i = 1; i < add; i++) { temp = temp->next; if (temp == NULL) { printf("插入位置无效\n"); return p; } } //创建插入结点c link * c = (link*)malloc(sizeof(link)); c->elem = elem; //向链表中插入结点 c->next = temp->next; temp->next = c; return p;}link * delElem(link * p, int add) { link * temp = p; //遍历到被删除结点的上一个结点 for (int i = 1; i < add; i++) { temp = temp->next; if (temp->next == NULL) { printf("没有该结点\n"); return p; } } link * del = temp->next;//单独设置一个指针指向被删除结点,以防丢失 temp->next = temp->next->next;//删除某个结点的方法就是更改前一个结点的指针域 free(del);//手动释放该结点,防止内存泄漏 return p;}int selectElem(link * p, int elem) { link * t = p; int i = 1; while (t->next) { t = t->next; if (t->elem == elem) { return i; } i++; } return -1;}link *amendElem(link * p, int add, int newElem) { link * temp = p; temp = temp->next;//tamp指向首元结点 //temp指向被删除结点 for (int i = 1; i < add; i++) { temp = temp->next; } temp->elem = newElem; return p;}void display(link *p) { link* temp = p;//将temp指针重新指向头结点 //只要temp指针指向的结点的next不是Null,就执行输出语句。 while (temp->next) { temp = temp->next; printf("%d ", temp->elem); } printf("\n");}
代码运行结果:
初始化链表为:
1 2 3 4
在第4的位置插入元素5:
1 2 3 5 4
删除元素3:
1 2 5 4
查找元素2的位置为:
元素2的位置为:2
更改第3的位置上的数据为7:
1 2 7 4
双向链表的定义:
双向链表也是链表的一种,他的每个数据结点中都有俩个指针域,分别指向其直接前驱和直接后继,所以我们从双向链表的任意一个结点都可以很方便的访问其直接前驱元素和直接后继元素。
双向链表也是采用的链式存储结构,它与单链表的区别就是每个数据结点中多了一个指向前驱元素的指针域 ,它的存储结构如下图:
当双向链表只有一个结点的时候,他的存储结构如下:
在双向链表中,我们可以通过任意一个结点访问到其前驱元素和后继元素,时间复杂度为O(1),所以双向链表是十分方便的,我们通常构建链表也会选择去构建双向链表。
双向链表的实现与操作:
#include<stdio.h>#include<stdlib.h>#include<malloc.h>typedef struct DOUBLE_LIST // 定义结构体{ int data; // 数据域 struct DOUBLE_LIST *prev; // 指向直接前驱的指针域 struct DOUBLE_LIST *next; // 指向直接后继的指针域}double_list; // 双向链表类型double_list *createlist() //创建有n个元素的双向链表 并输入元素{ double_list *head, *p, *q; int n,x; head = (double_list *)malloc(sizeof(double_list)); // 创建头结点 head->prev = head; head->next = head; p = head; printf("输入要创建双向链表的元素的个数:\n"); scanf("%d",&n); //个数也就是循环次数 for(int i=0;i<n;i++) { printf("请输入第%d个元素:",(i+1)); scanf("%d", &x); // 数据 q = (double_list *)malloc(sizeof(double_list)); // 开辟结点 q->data = x; // 数据存放到数据域 p->next = q;// 上一个结点p的next域指向q结点 head->prev = q;// head的上一个指针域指向最后尾结点 q->prev = p; // q的上一个指针域指向p q->next = head; // 尾结点的下一个结点为头结点 p = q; // 位置指针后移。 } return head;}//遍历并且输出这些元素void printlist(double_list *head){ double_list *p;// 定义位置指针 p = head; // 位置指针指向头结点 p = p->next; // 位置指针后移 while(p!=head) // 循环位置指针 p从head往后移动,p自然不等于head,移动到尾结点的下一次移动,即跳出循环。 { printf("%d ", p->data); p = p->next; // 位置指针后移 } printf("\n");}//得到现在双向链表中的元素的个数int lengthlist(double_list *head){ double_list *p; p = head; p = p->next; int coun = 0; while(p!=head) { coun++; p = p->next; } return coun;}//在第i个元素之前插入数据datavoid insertlist_f(double_list *head, int i, int data){ double_list *p = head, *q; p = p->next; i--; while(i--) p = p->next; q = (double_list *)malloc(sizeof(double_list)); q->data = data; (p->prev)->next = q; q->prev = p->prev; q->next = p; p->prev = q;}//删除第i个位置的元素void deletelist_i(double_list *head, int i){ double_list *p = head; p = p->next; i--; while(i--) p = p->next; (p->prev)->next = p->next; (p->next)->prev = p->prev; free(p);}//删除值为x的元素void deletelist_x(double_list *head, int x){ double_list *p = head, *q; p = p->next; while(p!=head) if(p->data == x) { q = p->next; (p->prev)->next = p->next; (p->next)->prev = p->prev; free(p); p = q; } else p = p->next;}//对双向链表进行排序void sortlist(double_list *head) //升序{ double_list *p = head, *q, *t; p = p->next; for(;p!=head;p=p->next) for(t = p->next;t!=head;t=t->next) { if(p->data > t->data) { int a = p->data; p->data = t->data; t->data = a; } }}int main(){ double_list *head; head = createlist(); deletelist_x(head, 2); //sortlist(head); printlist(head); insertlist_f(head, 2, 2); printlist(head); return 0;}
您可以使用 fopen( ) 函数来创建一个新的文件或者打开一个已有的文件,这个调用会初始化类型 FILE 的一个对象,类型 FILE 包含了所有用来控制流的必要的信息。下面是这个函数调用的原型:
FILE *fopen( const char * filename, const char * mode );
在这里,filename 是字符串,用来命名文件,访问模式 mode 的值可以是下列值中的一个:
模式 | 描述 |
---|---|
r | 打开一个已有的文本文件,允许读取文件。 |
w | 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。 |
a | 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。 |
r+ | 打开一个文本文件,允许读写文件。 |
w+ | 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。 |
a+ | 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。 |
如果处理的是二进制文件,则需使用下面的访问模式来取代上面的访问模式:
"rb", "wb", "ab", "rb+", "r+b", "wb+", "w+b", "ab+", "a+b"
为了关闭文件,请使用 fclose( ) 函数。函数的原型如下:
int fclose( FILE *fp );
如果成功关闭文件,fclose( ) 函数返回零,如果关闭文件时发生错误,函数返回 EOF。这个函数实际上,会清空缓冲区中的数据,关闭文件,并释放用于该文件的所有内存。EOF 是一个定义在头文件 stdio.h 中的常量。
C 标准库提供了各种函数来按字符或者以固定长度字符串的形式读写文件。
#include <stdio.h>//fgetc函数示例代码 读int main() { FILE *fp; char ch; fp=fopen("text.txt","r+"); if(fp==NULL) { printf("没有找到文件!"); return 0; } ch=fgetc(fp); while(ch!=EOF) { printf("%c",ch); ch=fgetc(fp); } fclose(fp); printf("end"); return 0;}
#include <stdio.h>#include<string.h>//fputc函数示例代码 写 int main() { FILE *fp; char ch; char str[100]=""; printf("请输入\n") ; scanf("%s",str); fp=fopen("text.txt","w+"); if(fp==NULL) { printf("失败\n"); return 0; } for(int i=0;i<strlen(str);i++) { fputc(str[i],fp); } printf("end\n"); return 0;}
#include <stdio.h>#include<string.h>//fputs fgets函数示例代码 字符串读写 int main() { FILE *fp; char ch;// char str[100]="";// printf("请输入\n") ;// scanf("%s",str);//这里不要加空格 char str[]="你好啊"; char str1[100]; fp=fopen("text.txt","w+"); if(fp==NULL) { printf("失败\n"); return 0; } fputs(str,fp); rewind(fp);//重新设置文件的读写流位置 (游标指针至到开头) fgets(str1,strlen(str)+1,fp); fclose(fp); printf("%s\n",str1); printf("end\n"); return 0;}
#include<string.h>//fprintf,fscanf函数示例代码 int main() { FILE *fp; char ch; char str[]="你好啊1"; char str1[100]; fp=fopen("text.txt","w+"); if(fp==NULL) { printf("失败\n"); return 0; } fprintf(fp,"%s\n",str); rewind(fp);//重新设置文件的读写流位置 (游标指针至到开头) fscanf(fp,"%s\n",str1); printf("%s\n",str1); fclose(fp); printf("end\n"); return 0;}
C语言可以用fread函数从文件中读取一个数据块,fwrite函数向文件写一个数据块。将数据原封不动的写入到磁盘上。以二进制的形式。
fwrite(buffer, size, count,fp):以二进制的形式向指定的文件中写入若干数据项(由count决定),返回实际写入的数据项数目,各参数含义如下:
buffer:一个存储区的起始地址,以该地址开始的存储区的数据即是保存到文件中的数据,可以是数组或指针类型;
size:单个数据项的大小(单位:字节);
count:数据项数量;
fp:FILE类型指针,该指针对应的文件即是数据保存的“目的地”;
fread(buffer, size, count,fp):以二进制的形式从指定的文件中读取若干数据项(由count决定),调用成功返回实际读取到的数据项个数(小于或等于count),不成功或读到文件末尾返回 0,各参数含义如下:
buffer:一个存储区的起始地址,以该地址开始的存储区用于保存从文件中读取出来的数据,可以是数组或指针类型;
size:单个数据项的大小(单位:字节);
count:数据项数量;
fp:FILE类型指针,该指针对应的文件即是数据的“来源地”
#include <stdio.h>#include<string.h>//二进制读写int main() { int a=24; int b; FILE *fp; fp=fopen("text.txt","wb"); fwrite(&a,sizeof(int),1,fp); fclose(fp); fp=fopen("text.txt","rb"); fread(&b,sizeof(int),1,fp); fclose(fp); printf("%d",b); fclose(fp); return 0;}
#include <stdio.h>struct Student{ char name[10]; int age; char sex[5];} student1,student2;int main() { FILE *p1file; p1file = fopen("text.txt","wb"); scanf("%s%d%s",student1.name,&student1.age,student1.sex); fwrite(&student1,sizeof(struct Student),1,p1file); fclose(p1file); //现在data.txt已经有了一个数据,用fread读出来。 FILE *p2file; p2file = fopen("text.txt","rb"); fread(&student2,sizeof(student1),1,p2file); printf("name is %s",student2.name); return 0;}
实现随机读写的关键是要按要求移动位置指针,这称为文件的定位。
移动文件内部位置指针的函数主要有两个,即 rewind() 和 fseek()。
rewind() 用来将位置指针移动到文件开头,前面已经多次使用过,它的原型为:
void rewind ( FILE *fp );
fseek() 用来将位置指针移动到任意位置,它的原型为:
int fseek ( FILE *fp, long offset, int origin );
参数说明:
fp 为文件指针,也就是被移动的文件。
offset 为偏移量,也就是要移动的字节数。之所以为 long 类型,是希望移动的范围更大,能处理的文件更大。
origin 为起始位置,也就是从何处开始计算偏移量。C语言规定的起始位置有三种,分别为文件开头、当前位置和文件末尾,每个位置都用对应的常量来表示:
起始点 | 常量名 | 常量值 |
---|---|---|
文件开头 | SEEK_SET | 0 |
当前位置 | SEEK_CUR | 1 |
文件末尾 | SEEK_END | 2 |
例如,把位置指针移动到离文件开头100个字节处:
fseek(fp, 100, 0);
值得说明的是,fseek() 一般用于二进制文件,在文本文件中由于要进行转换,计算的位置有时会出错。
继续上一个例子
struct Student{ char name[10]; int age; char sex[5];} student1,student2;int main() { FILE *p1file; p1file = fopen("text.txt","wb"); scanf("%s%d%s",student1.name,&student1.age,student1.sex); fwrite(&student1,sizeof(struct Student),1,p1file); fclose(p1file); //现在data.txt已经有了一个数据,用fread读出来。 FILE *p2file; fseek(p1file, sizeof(student1), SEEK_SET); //移动位置指针 p2file = fopen("text.txt","rb"); fread(&student2,sizeof(student1),1,p2file); printf("name is %s",student2.name); return 0;}
#include<stdio.h>#include<string.h>//fprintf,fscanf函数示例代码 int main() { FILE *fp; char ch; char str[]="abcd"; char str1[100]; fp=fopen("text.txt","w+"); if(fp==NULL) { printf("失败\n"); return 0; } fprintf(fp,"%s\n",str); rewind(fp);//重新设置文件的读写流位置 (游标指针至到开头) int a=fseek(fp,-2,2); printf("a=%d\n",a); fscanf(fp,"%s\n",str1); printf("%s\n",str1); fclose(fp); printf("end\n"); return 0;}
feof(fp) == EOF 判断位置指针是否在结尾处--是否读完
ferror(fp)文件操作出错检测函数 返回值0就是成功 非0就是失败
clearerr(fp)文件出错复位函数
#include<stdio.h>#include<string.h>//fprintf,fscanf函数示例代码 int main() { FILE *fp; char ch; fp=fopen("text.tex","w"); //w只允许写 ch=fgetc(fp); //读 if(ferror(fp)) { printf("error1\n"); } clearerr(fp);//清除错误标志 下面就不会错了 if(ferror(fp)) { printf("error2\n"); } fclose(fp); return 0;}