文章目录
一. 基本知识
I. C语言的构成
1.源程序:由C语言构成的指令序列,其由函数构成,每个函数完成相对独立的功能。
2.主函数:每个源程序中必须有且只能有一个主函数,可以放在任何位置,但是程序总是从主函数开始执行。
3.函数体:在函数后面用一对花括号括起来的部分。
4.分号:每个语句以分号结束,但预处理命令、函数头之后不能加分号。
5.注释:注释不能加在变量名或关键字中间。
1)括在/* 和 */之间,可以出现在程序的任何地方。
2)在//之后,只在单行起作用。
6.预处理命令:以#开头的语句,如#include、#define。
7.头文件:#include <stdio.h>、#include <math.h>、#include <string.h>、#include <stdlib.h>等
II. C程序的生成过程
C程序先是由源文件.cpp经过编译生成目标文件.obj,然后经过连接生成可执行文件.exe。
二. 数据类型
I. 标识符
1.命名规则:
1)只能由字母、数字或下划线组成。
2)第一个字母必须是下划线或字母,但绝对不能是数字。
3)标识符区分字母的大小写。
2.分类:
1)预定义标识符
2)C语言内置关键字
3)用户定义标识符,如常量、变量等,其不能与关键字重复。
II. 常量
1.定义:在程序运行的过程中,值不变的量。
2.类型:整型常量、实型常量、字符常量、字符串常量、符号常量。
3.常量后缀:
1)常量后跟u,表示无符号整数。
2)常量后跟f,表示float类型的浮点数。
3)常量后跟l,表示长整数long。
4.整型常量:注意常量内部不能出现逗号。
1)十进制整型常量:基本数字范围为0~9。
2)八进制整型常量:以0开头,基本数字范围为0~7。
3)十六进制整型常量:以0x开头,基本数字范围为0~9 ∪ A~F。
5.实型常量:
1)小数形式:小数点两边必须都有数字。
2)指数形式:e前必须有数字,e后必须为整数。
6.字符常量:一个字符常量代表ASCLL码字符集里面的一个字符,在程序中用单撇号‘’括起来,区分大小写。
转义字符:
1)\n换行,\r回车,\b退格,\t水平制表,\v垂直制表。
2)\\ —> \ , \’ —> ’ , \" —> " , \? —> ? , “\\\” —> \"
3)\ddd:1~3位八进制数所代表的一个ASCLL字符。
4)\xhh:1~2位十六进制数所代表的一个ASCLL字符,不限制转义个数。
5)\0:空值,用于判断一个字符串是否结束。
7.字符串常量:字符串常量用双撇号“”括起来,字符串可以为空串。
8.符号常量:由#define语句定义的常量。
III. 变量
1.定义:在程序运行的过程中值可以改变的量。
2.注意:
1)变量要有变量名,在使用前必须定义。
2)变量在内存中占据着一定的存储单元,不同类型的变量的存储单元的大小不同。
3)存储单元里存放的是该变量的值。
3.整型变量:
1)分类:整型int(默认)、短整型short int或short、长整型long int或long、无符号整型unsigned int、unsigned short、unsigned long。
2)占位数:常见情况下(Win32、Win64),int占32位,short占16位,long占32位,unsigned占32位。
4.实型变量:
1)分类:单精度类型float、双精度类型double,实行变量中若有小数点即为浮点型变量。
2)占位数:常见情况下(Win32、Win64),float占32位,double占64位。
5.字符变量:
1)字符变量用char定义,每个变量中只能存放一个字符。
2)占位数:char占8位。
3)字符型数据可以和整型数据通用,一个字符可以以字符的形式输出,也可以以数字(ASCLL码)形式输出。
4)字符数据亦可进行算数运算,即对其对应的ASCLL码进行运算。
6.强制类型转换:不包括逻辑类型的转换。
1)当同一表达式中各数据类型不同时,编译器会把它们全部转换为相同类型的数据进行计算。
2)转换优先级:double>float>long>unsigned>int>short>char。其中double的优先级最高,当多种数据类型存在时,通常统一转化为double类型进行运算。
3)赋值时以左为尊:若等号左右两边的类型不同,则右边的类型向左边的类型转换,若右边的优先级高于左边,则在转换的时候对右边数据进行截取。
三. 运算符和表达式
I. 运算符种类
1.运算符种类:
1)算术运算符:+,-,*,/,%
2)关系运算符:>,>=,==,!=,<,<=
3)位运算符:>>,<<,~,&,|,^
4)逻辑运算符:!,||,&&
5)条件运算符:?,:
6)指针运算符:&,*
7)赋值运算符:=
8)逗号运算符:,
9)字节运算符:sizeof
10)初等运算符:(),[ ],->
2.单目、双目、三目运算符:
1)单目运算符:取正(+),取负(-),取地址(&)、解引用(*),按位取反(~),逻辑非(!),自加自减(++ - -),类型转换运算符((类型)),字节运算符(sizeof)
2)双目运算符:加(+),减(-),乘(*),除(/),逻辑与(&&),逻辑或(||),取余(%),赋值(=),复合算术运算符(+=、-=、* =、/=),按位与(&),按位或(|),按位异或(^),左移右移(<< >>),大于小于运算符(> <),等于不等于运算符(== !=),指向运算符(->),下标运算符([ ])
3)三目运算符:条件运算符(?:)
II. 结合性和优先级
1.结合性:所有的单目运算符、条件运算符、赋值运算符及其扩展运算符,结合方向都是从右向左,其余运算符都是从左向右。
2.优先级:初等运算符>单目运算符(含逻辑运算符!)>算术运算符(先乘除后加减)>关系运算符>逻辑运算符(不包括逻辑非!)>条件运算符>赋值运算符>逗号运算符。需要注意的是,优先级和执行顺序在前无必然关系。
III. 算术运算
1.注意:取余%运算符的两边必须是整型,其余算术运算符没有此要求。
2.类型转换:double>float>long>unsigned>int>short>char。
3.精度转换:所有的实数运算都是以双精度方式进行的,若是单精度数值,则需要在末尾补0。
4.算术表达式:可以使用多层圆括号,但括号必须配对,否则会报错。运算时由内向外计算各表达式的值。
IV. 自加自减运算
1.作用:++使变量的值加一,- -使变量的值减一。
2.注意:
1)自加自减运算符的对象只能是整型或实型变量,不可以是常量和表达式。
2)自加自减运算符可以作为前缀运算符,也可作为后缀运算符,注意区分。
3)结合方向:从右向左。
++i,- - i:在使用i之前,先使i的值加一或减一,再用加减后的值参加运算。
i++,i- -:i的值先参加运算,再将i的值加一或减一。
V. 条件运算
1.格式:X=表达式1 ? 表达式2 : 表达式3
2.含义:先求解表达式1。若为真,则求解表达式2,算出表达式2的值;若为假,则求解表达式3,算出表达式3的值。最后把所得值赋值给X。
VI. 关系运算
1.种类:>、<、>=、<+、==、!=
2.优先级:>、<、>=、<=优先级相同,均高于= =和!=,后二者优先级相同。
3.运算结果:结果是一个整数值,通常用非零值表示该命题为真,0表示该命题为假。
4.注意:关系运算符不能连用,即不能使用0<x<10,应该写成0<x&&x<10。
VII.逻辑运算
1.优先级:!> && > ||
2.运算结果:运算结果为1或0,1代表真,0代表假。(bool)
VIII. 位运算
1.功能:
1)按位与&:若两个相应的二进制位都为1,则该位的结果为1,否则为0。
2)按位或|:两个二进制位中只要有一个为1,则该位的结果为1,否则为0。
3)按位异或^:若两个二进制位相同,则结果为0,否则为1。
4)按位求反~:0变成1,1变成0。
5)左移<<:将一个数的二进制位全部左移若干位。
6)右移>>:将一个数的二进制位全部右移若干位。
2.说明:运算量只能是整型或字符型的数据,不能为实型数据。
四. 输入输出
I. 单个字符输入输出
1.字符输入函数getchar():向终端输入一个字符,其不含参数,函数值就是输入的字符。仅对字符量起作用。
char x;
x=getchar(); //输入8
2.字符输出函数putchar():向终端输出一个字符。其不会检查要输出的字符的真正范围,所输出的必须在一个字符的范围之内,否则会出错。
putchar(x); //x为8
putchar(\n);
putchar('A');
程序输出:
8
A
II.格式化输入输出
1.格式化输入函数scanf():
1)一般形式:scanf(“格式控制”,地址表列); 地址表列由若干个变量地址组成,既可以是变量的地址,也可以是字符串的首地址。
2)格式说明:以%开始。对于unsigned型数据,可以用%d、%o、%x格式输入。在格式字符前可以用一个整数指定输入数据所占宽度,但对于输入实型数据则不能指定其小数位的宽度。在格式控制串中,格式说明的个数应该与输入项的个数相等,且类型需匹配。
3)注意事项:
scanf()函数中的输入项只能是地址表达式,而不能是变量名或其他内容。
如果在格式控制串中除了格式说明以外还有其他字符,则在输入数据时应该输入与这些字符相同的字符。
在用%c格式输入时,空格字符和转义字符都可作为有效字符输入。
在输入数据时,若实际输入数据少于输入项个数,则会等待输入,直到满足条件或遇到非法字符才结束。若实际输入数据多于输入项个数,则多余部分将留在缓冲区备用。
在输入数据时,若遇到空格、回车、跳格,则输入结束。
int i;
double j;
scanf("(%d+%lfi)",&i,&j);
样例输入:(2+3.05i)
2.格式化输出函数printf():
1)一般形式:printf(“格式控制”,输出表列); 用双引号括起来的字符串用于格式控制,其包括由%和格式字符组成的格式转换说明以及需要原样输出的字符。
2)格式字符:可在%与格式字符间插入宽度说明、左对齐符号“-”、前导零符号“0”等等。
宽度说明:
宽度说明包含小数点。
%m.n表示宽度为m,精度为n,m可省略不写。
printf("%.2f",x);
格式字符大全:
1.%d:对十进制数进行输出。
2.%o:对八进制数进行输出。
3.%x:对十六进制数进行输出。
4.%u:对unsigned数据进行输出。
5.%c:用来输出单个字符。
6.%s:用来输出一个字符串。
7.%f %lf:用来输出单双精度实数。
8.%e:以指数形式输出实数。
9.%g:输出实数。
3)注意事项:
格式说明必须与输出项从左到右在类型上一一匹配。
格式说明与输出项个数要相等。
如果要输出%,可在格式控制串中用两个连续的%%来表示。
例如,计算a+b的程序为:
#include <stdio.h>
int main()
{
int a,b,sum;
scanf("%d %d",&a,&b);
sum=a+b;
printf("%d\n",sum);
return 0;
}
五. 三大结构
I.顺序结构
【例题1】.判断输入的是奇数还是偶数。
#include <stdio.h>
int main()
{
int x;
scanf("%d",&x);
if(x%2==0)
{
printf("偶数\n");
}
else
{
printf("奇数\n");
}
return 0;
}
【例题2】.已知ax^2+bx+c=0,输入a,b,c,计算该一元二次方程的根。
#include <stdio.h>
#include <math.h>
int main()
{
double a,b,c,m,x1,x2;
scanf("%lf %lf %lf",&a,&b,&c);
m=b*b-4*a*c;
x1=(-b+sqrt(m))/2*a;
x2=(-b-sqrt(m))/2*a;
printf("%.2lf %.2lf\n",x1,x2);
return 0;
}
II.选择结构
1.if语句构成的选择结构:
【例题3】.输入三个数,输出其中最大值。
#include <stdio.h>
int main()
{
double a,b,c,t;
scanf("%lf %lf %lf",&a,&b,&c);
if(a>b)
{
t=a;
a=b;
b=t;
}
else if(a>c)
{
t=a;
a=c;
c=t;
}
else if(b>c)
{
t=b;
b=c;
c=t;
}
printf("%.2lf\n",c);
return 0;
}
【例题4】.输入一个年份,判断这个年份是否为闰年。
#include <stdio.h>
int main()
{
int x;
scanf("%d",&x);
if(x%4==0&&x%100!=0||x%400==0)
{
printf("yes\n");
}
else
{
printf("no\n");
}
return 0;
}
2.switch语句构成的选择结构: switch语句为多分支选择语句。
1)case语句后的常量表达式不能为浮点型以及字符型数据。
2)switch语句后面括号不能省略。
3)switch语句不一定使用break语句。
4)default可以省略,起标号的作用,代表除了以上所有case标号之外的标号,且可以出现在任意位置。
【例题5】.请输入星期几的第一个字母来判断一下是星期几,如果第一个字母一样,则继续判断第二个字母。
#include <stdio.h>
int main()
{
char i,j; //i为输入的第一个字母,j为第二个
printf("请输入第一个字母:");
scanf("%c",&i); //注意输入的类型为字符型,开始自己惯例写成整型
getchar(); //第二次是读入的一个换行符,而不是输入的字符,因此需要加一个getchar() 吃掉换行符
switch(i)
{
case'm':
printf("monday\n");
break;
case'w':
printf("wendesday\n");
break;
case'f':
printf("friday\n");
break;
case't':
printf("请输入下一个字母:");
scanf("%c",&j); //这时需要加入输入j的语句if(j=='u')
{printf("tuesday\n");break;}
if(j=='h')
{printf("thursday\n");break;}
case's':
printf("请输入下一个字母:");
scanf("%c",&j);
if(j=='a')
{ printf("satursday\n");break;}
if(j=='u')
{ printf("sunday\n");break; }
default :
printf("error\n"); break;
}
}
【例题6】.输入一个日期,判断其是一年当中的第几天。
#include <stdio.h>
int main()
{
int year,month,day,sum;
scanf("%d,%d,%d",&year,&month,&day);
if((year%4==0&&year%100!=0)||year%400==0)
{
switch(month)
{
case 1:sum=0; break;
case 2:sum=31; break;
case 3:sum=60; break;
case 4:sum=91; break;
case 5:sum=121; break;
case 6:sum=152; break;
case 7:sum=182; break;
case 8:sum=213; break;
case 9:sum=244; break;
case 10:sum=274; break;
case 11:sum=305; break;
case 12:sum=335; break;
}
if((month==2&&day>29)||(month==4,6,9,11&&day>30)||(month==1,3,5,7,8,10,12&&day>31))
printf("data error.\n");
else
printf("It is the %dth day.\n",sum+day);
if((year%4!=0)||(year%100==0&&year%400!=0))
{
switch(month)
{
case 1:sum=0; break;
case 2:sum=31; break;
case 3:sum=59; break;
case 4:sum=90; break;
case 5:sum=120; break;
case 6:sum=151; break;
case 7:sum=181; break;
case 8:sum=212; break;
case 9:sum=243; break;
case 10:sum=273; break;
case 11:sum=284; break;
case 12:sum=334; break;
}
if((month==2&&day>28)||(month==4,6,9,11&&day>30)||(month==1,3,5,7,8,10,12&&day>31))
printf("data error.\n");
else
printf("It is the %dth day.\n",sum+day);
}
return 0;
}
}
III.循环结构
1.while循环语句:
1)格式:do { 循环体 } while(表达式);
2)do必须和while连用,不能单独出现。在语法上,do和while之间只能是一条语句。
3)执行过程:先执行一次指定的循环体语句,执行完后,判别while后面表达式的值,当结果非真时,程序流程返回,再次执行循环体语句。如此反复,直到表达式为真时,循环方可结束。
【例题7】.输入正整数n,求这个数在角谷猜想的归一过程中的最大值。
#include <stdio.h>
int main()
{
int n,a=0,i=0;
scanf("%d",&n);
while(n>1)
{
if(n%2==1)
n=3*n+1;
else if(n%2==0)
n=n/2;
if(a<n)
a=n;
}
printf("%d\n",a);
return 0;
}
2.for循环语句:
1)格式:for(表达式1;表达式2;表达式3); { 循环体 }
2)圆括号中通常是三个表达式,用于for循环的控制,表达式可以省略,但分号却不能省略。
3)执行过程:先求表达式1的值。再求表达式2的值,若其值为真,则执行for循环中的内嵌语句,再执行表达式3,又返回表达式2,直到表达式2的值为假,方可退出循环。
【例题8】.输入一个数,判断其是否为素数。
#include <stdio.h>
#include <math.h>
int main()
{
int n,i;
scanf("%d",&n);
for(i=2;i<sqrt(n);i++)
{
if(n%i==0)
{
break;
}
if(i<n)
{
printf("no\n");
}
else
{
printf("yes\n");
}
return 0;
}
}
3.break语句:
1)break语句只能出现在循环体内及switch语句内,不能用于其他语句。
2)作用:当break语句出现在switch语句中时,只是跳出该switch语句体。当出现在for循环当中时,仅仅跳出本层循环。即一个break只能跳出一层循环。
4.continue语句:
作用为结束本次循环,即跳过循环体中尚未执行的语句,而转去重新判断循环条件是否成立,从而确定下一次循环是否继续执行。
六. 数组
I. 一维数组
1.定义:数组中元素只带有一个下标的数组。如 int a[10]。
2.说明:
1)一个数组元素实质上是一个变量名,代表内存中的一个存储单元。
2)引用数组元素时,数组的下标可以是整型常量和整型表达式。
3)只能逐个引用数组元素而不能一次引用整个数组。
3.一维数组的初始化:可以省略此步骤。
int a[5]={0,1,2,3,4}
1)所赋初值放在一对花括号中,数值类型必须与说明类型一致。
2)初值之间用逗号隔开,第一个元素是a[0]。
3)当所赋初值个数少于定义数组的元素个数时,将自动给后面的其他元素补初值0。
4.数组退化为指针:
1)C语言当中并没有真正的数组类型,因此大部分对数组的引用都会退化成指针。
2)数组退化为指针的规则不能递归调用,数组的数组应退化为数组的指针,而不是指针的指针。
【例题1】.输出斐波那契数列的前20项。
#include <stdio.h>
int main()
{
int i;
int f[20]={1,1};
for(i=2;i<20;i++)
{
f[i]=f[i-2]+f[i-1];
}
for(i=0;i<20;i++)
{
printf("%d ",f[i]);
}
printf("\n");
return 0;
}
II. 二维数组
1.定义:二维数组中元素排列的顺序为按行存放,即在内存中先顺序存放第一行的元素,再存放后续行的元素,其存储总是占用一块连续的内存单元。
2.二维数组的初始化:
1)可以在定义二维数组的同时给二维数组的各个元素赋初值。
2)全部初值放在一对花括号中,每一行的初值又分别括在一对花括号中,之间用逗号隔开。
3)当某行一对花括号内的初值个数少于该行中元素的个数时,系统将自动地给后面的元素赋初值0。
3.注意:对于二维数组,只可以省略第一个方括号中的常量表达式,而不能省略第二个括号中的常量表达式。
【例题2】.输出给定矩阵的最大元素和行数、列数。
#include <stdio.h>
int main()
{
int i,j,row=0,colum=0,max;
int a[3][4]={{1,2,3,4},{9,8,7,6},{-10,10,-5,2}};
max=a[0][0];
for(i=0;i<=2;i++)
{
for(j=0;j<=3;j++)
{
if(a[i][j]>max)
{
max=a[i][j];
row=i;
colum=j;
}
}
}
printf("max=%d row=%d colum=%d\n",max,row,colum);
return 0;
}
【例题3】.输入一个三阶矩阵,输出将其转置后的结果。
#include <stdio.h>
int main()
{
int i,j,t;
int a[3][3];
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
scanf("%d",&a[i][j]);
}
}
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
{
if(j>i)
{
t=a[i][j];
a[i][j]=a[j][i];
a[j][i]=t;
}
}
}
for(j=0;j<3;j++)
{
printf("%-d ",a[0][j]);
}
printf("\n");
for(j=0;j<3;j++)
{
printf("%-d ",a[1][j]);
}
printf("\n");
for(j=0;j<3;j++)
{
printf("%-d ",a[2][j]);
}
printf("\n");
return 0;
}
III. 排序
【例题4】.输入10个数,并对其排序,并从小到大输出。
1.冒泡排序:注意数组下标,此处很微妙。
#include <stdio.h>
int main()
{
int a[10];
int i,j,t;
for(i=0;i<10;i++)
{
scanf("%d",&a[i]);
}
for(i=0;i<9;i++)
{
for(j=0;j<9-i;j++)
{
if(a[j]>a[j+1])
{
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
for(i=0;i<10;i++)
{
printf("%d ",a[i]);
}
printf("\n");
return 0;
}
2.选择排序:体会通过sort函数实现排序的过程。
#include <stdio.h>
int main()
{
void sort(int a[],int n);
int a[10],i;
for(i=0;i<10;i++)
{
scanf("%d",&a[i]);
}
sort(a,10);
for(i=0;i<10;i++)
{
printf("%d ",a[i]);
}
printf("\n");
return 0;
}
void sort(int a[],int n)
{
int i,j,k,t;
for(i=0;i<n-1;i++)
{
k=i;
for(j=i+1;j<n;j++)
{
if(a[j]<a[k])
{
k=j;
}
}
t=a[k];
a[k]=a[i];
a[i]=t;
}
}
更多排序方法(快排、插排)详见这篇博文:
C语言的四大排序方法
IV. 字符数组
1.定义:数组中每个元素都是字符的数组。
2.初始化:
1)如果花括号中提供的初值个数(字符个数)大于数组长度,则编译器会报错。
2)如果初值个数小于数组长度,则将这些初值赋给前面的元素,后面其余元素定为空字符(\0)。
3.字符数组与字符指针:字符数组不能直接使用字符串或者字符数组进行赋值,而字符指针变量可以直接使用字符串赋值。
V. 简单字符串处理
1.头文件:#include <string.h>
2.字符串输入函数:gets()
调用形式为gets(s),其从终端输入一个字符串到字符数组中,并得到一个函数值。
3.字符串输出函数:puts()
调用形式为puts(s),其将一个字符串(以\0结束)输出到终端设备。
4.字符串拷贝函数:strcpy()
调用形式为strcpy(s1,s2),作用是把s2字符串的内容复制到s1的存储空间中,并返回s1字符串的首地址。
5.字符串连接函数:strcat()
调用形式为strcat(s1,s2),作用是把s2字符串的内容连接到s1字符串后面,并自动覆盖s1末尾的\0,并返回s1的首地址。
6.字符串比较函数:strcmp()
调用形式为strcmp(s1,s2),作用是从第一个字符开始比较ASCLL码值大小,若s1>s2,则返回值为正数;若s1==s2,则返回值为0;若s1<s2,则返回值为负数。之后可以对其返回值判断正负,从而得出字符串比较结果。
7.字符串长度函数:strlen()
调用形式为strlen(s),此函数计算从起始地址开始到末尾\0的字符串长度,其遇到\0就结束,但是长度不包括\0,最后返回长度值。
【例题5】.输入一串字符,判断其是否为回文字符串。
#include <string.h>
#include <stdio.h>
int main()
{
char a[100];
int i=0,len;
gets(a);
len=strlen(a);
for(i=0;i<((len-1)/2);i++)
{
if(a[i]!=a[len-1-i])
{
break;
}
}
if(i==((len-1)/2))
{
printf("yes!");
}
else
{
printf("no!");
}
}
更详细的字符串处理函数总结请详见此篇博文:(感谢大神的总结)
以上涉及到的字符串处理函数的简单应用的例题请移步至这篇博文:
七. 函数
I. 函数的一般调用
1.C程序的执行顺序:总是从main函数开始,调用其他函数完毕后,程序流程会回到main函数,直到main函数结束,则整个程序运行结束。
2.形参:定义函数时,函数名后面括号中的变量。
3.实参:在主调函数中,函数名后面括号中的参数。
4.返回值:return 语句中的表达式值的类型必须与函数首部所声明的类型一致。若类型不一致,则以函数值的类型为准,系统会自动进行强制类型转换。当然也可以使函数不返回任何值,只需要将其声明为void类型即可。
5.调用:
1)调用无参函数,不需要使用实参表列,但括号不能省略。
2)调用有参函数,若实参表列中有多个实参,则各个实参用逗号隔开,且实参和形参的类型必须相同。
3)形参对实参毫无影响,因为二者使用不同的内存单元。
6.形式:
类型名 函数名(参数类型1,参数类型2,…);
类型名 函数名(参数类型1 参数名1,参数类型2 参数名2,…);
【例题1】.输入两个整数,求它们的最小公约数和最小公倍数。
#include <stdio.h>
int main()
{
int f1(int a,int b);
int f2(int a,int b);
int a,b,m,n;
scanf("%d %d",&a,&b);
m=f1(a,b);
n=f2(a,b);
printf("%d %d",m,n);
return 0;
}
int f1(int a,int b) //求最大公约数的函数
{
int i,m;
for(i=1;i<=a;i++)
{
if(a%i==0 && b%i==0)
{
m=i;
}
}
return m;
}
int f2(int a,int b) //求最小公倍数的函数
{
int i,n;
for(i=1;i<=a*b;i++)
{
if(i%a==0 && i%b==0)
{
break;
}
}
n=i;
return n;
}
II. 嵌套和递归
1.函数的嵌套调用:需要注意的是,C语言的函数不可以被嵌套定义,但是可以被嵌套调用。即在调用一个函数的过程中同时调用另一个函数。
2.函数的递归调用:在调用一个函数的过程中又出现直接或间接调用该函数本身的调用称为递归调用。需要注意的是,必须要有一个明确的结束递归的条件,否则会陷入死循环。
【例题2】.求n的阶乘。即输入一个数n,再输出n!的值。
#include <stdio.h>
int F(int n)
{
int t;
if(n==0 || n==1)
{
t=1;
}
else
{
t=F(n-1)*n; //递归调用
}
return t;
}
int main()
{
int n,y;
scanf("%d",&n);
y=F(n);
printf("%d\n",y);
return 0;
}
【例题3】.汉诺塔问题:输入汉诺塔的阶数,分别输出每一步的操作,再输出所需总步数。
#include <stdio.h>
void m(char x,char y)
{
printf("%c->%c\n",x,y);
}
void h(int n,char one,char two,char three)
{
if(n==1)
{
m(one,three);
}
else
{
h(n-1,one,three,two);
m(one,three);
h(n-1,two,one,three);
}
}
int main()
{
int m,k=1,p,i;
scanf("%d",&m);
h(m,'A','B','C');
for(i=1;i<=m;i++)
{
k=k*2;
p=k-1;
}
printf("%d\n",p);
}
样例输入:
4
样例输出:
A->B
A->C
B->C
A->B
C->A
C->B
A->B
A->C
B->C
B->A
C->A
B->C
A->B
A->C
B->C
15
III. 函数中的变量
1.全局变量和局部变量:
1)在函数外定义的变量称为外部变量,外部变量是全局变量。全局变量可以为本文件中其他函数所共用,它的有效范围从定义变量开始到本文件结束。
2)若在同一个源文件中,外部变量和内部变量同名,则在局部变量的作用范围内,外部变量失效。
3)对未赋值的局部变量,C语言编程序自动给其赋初值0。
2.变量的存储类别:
1)auto变量:
auto类型的变量的初值不确定。当在函数内部或复合语句内定义变量时,若未指定存储类别(或使用了auto说明符),系统将会默认其为auto变量类别。
2)register变量:
寄存器变量register也是自动类变量,用其说明变量时建议编译程序将变量的值保留在CPU的寄存器中,而不是像auto那样占用内存单元,也不能进行求地址运算。这是二者本质的区别。但需要注意的是,不论是auto还是register,它们都是仅在使用时才占用内存。
3)static变量:
静态局部变量在内存的静态存储区中占据着永久性的存储单元,因此用static说明的变量可以保留数据。其初值是在编译的时候被赋予的,执行程序期间不再改变值。
八. 指针
I. 指针变量
1.指针变量的定义:类型名 *指针变量名。某种程度上讲,指针就是地址。
2.指针变量的引用:指针变量中只能存放地址。
3.指针变量作为函数参数:
指针类型数据可以作为函数参数来进行传递。其作用为将一个变量的地址传送到另一个函数中,参与该函数的运算。
【例题1】.输入两个整数,按先大后小的顺序输出。
#include <stdio.h>
int main()
{
int a,b;
int *p1,*p2,*p;
scanf("%d %d",&a,&b);
p1=&a;
p2=&b;
if(a<b)
{
p=p1;
p1=p2;
p2=p;
}
printf("%d %d\n",*p1,*p2);
return 0;
}
II. 数组与指针
1.指向数组的指针:
数组名代表数组的首地址(0号元素的地址),定义指针指向数组即指向数组首地址。即 *x == x[0]
2.指针引用数组元素:
如果指针p已经指向数组的一个元素,则p+1指向同一数组的下一个元素。
指针的自增和自减,其移动的字节数与指针变量指向的具体类型有关。
并且基类型不同的指针不能互相赋值。
若int a[10]; *p=a; 则 *(a+i)为指针指向元素的后面第 i 个元素。
3.数组名作为函数参数:
当数组名作为参数被传递时,若形参数组中各元素发生了变化,则原实参数组各元素的值也随之变化。
4.指向多维数组的指针:
多维数组可以看成是一维数组的延伸,其内存单元也是连续的,可以把多维数组当做一维数组来处理。
5.指针数组:
若在一个数组中,元素均为指针类型的元素,则这个数组是指针数组。要与指向数组的指针相区分!
类型名 * 数组名[数组长度];
【例题2】.用指针方法对输入的10个整数按由大到小排序。
#include <stdio.h>
int main()
{
void sort(int *x,int n);
int i,*p,a[10];
p=a;
for(i=0;i<10;i++)
{
scanf("%d",p++);
}
p=a;
sort(a,10);
for(p=a,i=0;i<10;i++)
{
printf("%d ",*p);
p++;
}
return 0;
}
void sort(int *x,int n)
{
int i,j,k,t;
for(i=0;i<n-1;i++)
{
k=i;
for(j=i+1;j<n;j++)
{
if(*(x+j)>*(x+k))
k=j;
}
if(k!=i)
{
t=*(x+i);
*(x+i)=*(x+k);
*(x+k)=t;
}
}
}
III. 字符串与指针
1.字符串指针作为函数参数:
将一个字符串从一个函数传递到另一个函数,可以采用地址传递的方法,即用字符数组名作为参数或用指向字符的指针变量作为参数进行传递。
2.字符数组与字符指针:
1)字符数组是由若干个元素组成的,每个元素中存放一个字符,而字符指针变量中存放的是地址,绝不是将字符串的内容存放到字符指针变量中。
2)字符数组可以在定义时对其整体赋初值,但在赋值语句中不能完成整体赋值。而字符指针变量既可以在定义时赋初值,也可以在赋值语句中完成。
3)在程序中指针变量的值可以改变,而数组名虽然代表了地址,但其值固定,不能改变。
IV. 指向函数的指针
1.指针与函数:
指针变量可以指向一个函数,编译时,一个函数将被分配给一个入口地址,这个入口地址即为该函数的指针。因此可以通过使用一个指向函数的指针变量调用此函数。
2.定义形式:
数据类型 ( * 指针变量名) ( );
3.说明:
1)在给函数指针变量赋值时,只需给出函数名而不必给出参数。
2)用函数指针变量调用函数时,只需将(*s)代替函数名即可。
3)对于指向函数的指针变量,自增自减毫无意义。
【例题3】.有两个整数a和b,由用户输入1,2,3。如输入1,程序就给出a和b中的大者;输入2,就给出a和b中的小者;输入3,则求a和b之和。
#include <stdio.h>
int main()
{
int fun(int x,int y,int(*p)(int,int));
int max(int,int);
int min(int,int);
int add(int,int);
int a=34,b=-21,n;
scanf("%d",&n);
if(n==1)
fun(a,b,max);
else if(n==2)
fun(a,b,min);
else if(n==3)
fun(a,b,add);
return 0;
}
int fun(int x,int y,int (*p)(int,int))
{
int result;
result=(*p)(x,y);
printf("%d\n",result);
return result;
}
int max(int x,int y)
{
int z;
if(x>y)
z=x;
else
z=y;
return z;
}
int min(int x,int y)
{
int z;
if(x>y)
z=y;
else
z=x;
return z;
}
int add(int x,int y)
{
int z;
z=x+y;
return z;
}
V. 指向指针的指针
指向指针数据的指针变量即为指向指针的指针,也叫二级指针。
类型名 **a;
九. 编译预处理与动态存储
I. 宏定义
1.不带参数的宏定义:
1)定义形式:#define 宏名 (替换文本)
2)说明:在define宏名和宏替换文本之间要用空格隔开。可以用#undef语句终止宏命令的作用域。
3)注意:宏定义不是C语句。在进行宏定义时,可以引用已经定义的宏名,但是同一个宏名不能重复定义。
4)优点:宏定义的本质是用标识符代替字符串,因此宏展开不占运行时间,只占编译时间,可以大大节省程序运行时间。
2.带参数的宏定义:
1)定义形式:#define 宏名(参数表) 字符串
2)说明:若程序中有带实参的宏,则按#define命令行中指定的字符串从左到右进行置换。如果字符串中包含宏中的形参,则将程序语句中相应的实参代替形参。如果宏定义中的字符串中的字符不是参数字符,则保留。
II. 预处理命令
1.#和##:
#和 ## 操作符是和#define宏使用的. 使用#使在#后的首个参数返回为一个带引号的字符串。例如, 命令
#define to_string( s ) # s
将会使编译器把以下命令
cout << to_string( Hello World! ) << endl;
理解为
cout << "Hello World!" << endl;
使用##连结##前后的内容。例如, 命令
#define concatenate( x, y ) x ## y
...
int xy = 10;
...
将会使编译器把
cout << concatenate( x, y ) << endl;
解释为
cout << xy << endl;
理所当然,将会在标准输出处显示’10’.。
2.#define :
语法: #define macro-name replacement-string
#define命令用于把指定的字符串替换文件中的宏名称。也就是说, #define使编译器把文件中每一个macro-name替换为replacement-string。替换的字符串结束于行末。这里是一个经典的#define应用 (至少是在C中):
#define TRUE 1
#define FALSE 0
...
int done = 0;
while( done != TRUE ) {
...
}
#define命令的另外一个功能就是替换参数,使它假冒创建函数一样使用。如下的代码:
#define absolute_value( x ) ( ((x) < 0) ? -(x) : (x) )
...
int x = -1;
while( absolute_value( x ) ) {
...
}
当使用复杂的宏时,最好使用额外的圆括号. 注意在以上的例子中, 变量"x"总是出现在它自己的括号中. 这样, 它就可以在和0比较,或变成负值(乘以-1)前计算值. 同样的, 整个宏也被括号围绕, 以防止和其它代码混淆. 如果你不注意的话, 你可能会被编译器曲解你的代码.
3.#error:
语法: #error message
#error命令可以简单的使编译器在发生错误时停止. 当遇到一个#error时,编译器会自动输出行号而无论message的内容. 本命令大多是用于调试.
4.#if, #ifdef, #ifndef, #else, #elif, #endif:
这些命令让编译器进行简单的逻辑控制。当一个文件被编译时, 你可以使用这些命令使某些行保留或者是去处。
#if expression
如果表达式(expression)的值是"真"(true),那么紧随该命令的代码将会被编译。
#ifdef macro
如果"macro"已经在一个#define声明中定义了, 那么紧随该命令的代码将会被编译。
#ifndef macro
如果"macro"未在一个#define声明中定义, 那么紧随命令的代码将会被编译。
注: 命令#elif是"elseif"的一种缩写,并且他可以想你所意愿的一样工作。你也可以在一个#if后插入一个"defined"或者"!defined"以获得更多的功能。
这里是一部分例子:
#ifdef DEBUG
cout << "This is the test version, i=" << i << endl;
#else
cout << "This is the production version!" << endl;
#endif
你应该注意到第二个例子比在你的代码中插入多个"cout"进行调试的方法更简单。
5.#include:
语法: #include 或#include “filename”
本命令包含一个文件并在当前位置插入。两种语法的主要不同之处是在于,如果filename括在尖括号中,那么编译器不知道如何搜索它。如果它括在引号中, 那么编译器可以简单的搜索到文件. 两种搜索的方式是由编译器决定的,一般尖括号意味着在标准库目录中搜索, 引号就表示在当前目录中搜索。The spiffy new 整洁的新C++ #include目录不需要直接映射到filenames, 至少对于标准库是这样。这就是你有时能够成功编译以下命令的原因。
6.#line、#pragma、#undef:
语法: #line line_number “filename”
#line命令是用于更改__LINE__ 和 __FILE__变量的值. 文件名是可选的. LINE 和 FILE 变量描述被读取的当前文件和行. 命令
#line 10 "main.cpp"
更改行号为10,当前文件改为"main.cpp"。
#pragma命令可以让编程者让编译器执行某些事。因为#pragma命令的执行很特殊,不同的编译器使用有所不同。一个选项可以跟踪程序的执行。
#undef命令取消一个先前已定义的宏变量, 譬如一个用#define定义的变量。
7.预定义变量:
__ LINE __ 和 __ FILE __ 变量表示正在处理的当前行和当前文件。
__ DATE __ 变量表示当前日期,格式为month/day/year(月/日/年)。
__ TIME __ 变量描述当前的时间,格式为hour:minute:second(时:分:秒)。
_cplusplus 变量只在编译一个C++程序时定义。
__ STDC __ 变量在编译一个C程序时定义,编译C++时也有可能定义。
III. 动态存储
1.malloc()函数:
1)函数原型:void * malloc(unsigned int size);
2)作用:系统自动在内存的动态存储区中分配长度为size的一段连续空间。若此函数执行成功,则函数返回值为指向被分配域的起始地址的指针(类型为void);若执行失败(内存空间不足),则返回值为空指针NULL。
3)用malloc申请的内存用free语句释放,二者互相搭配。
更多动态存储分配的知识,可参考:
动态存储分配
2.C语言中的malloc与C++中的new的比较:二者作用类似,但又有不同。
可参考这篇博文:
十. 结构体
I. 结构体的使用
1.typedef语句:
1)形式:
typedef 类型名 标识符;
2)作用:类似于C++当中的类,但是结构体比类简单。用标识符来代表已经存在的类型名,并没有产生新的数据类型,因此原有的类型名仍然有效。
3)步骤:先按定义变量的方法写出定义的主体。(如float a)再将变量名换成新类型名。(如将a换成FLO)再在最左边加上关键字typedef。(如typedef float FLO)之后便可以用这个新类型去定义其他变量。(如FLO b)
2.struct语句:
1)声明结构体的形式:
struct 结构体名
{ 成员表列 };
struct 结构体标识名
{
类型名1 结构体成员名表1;
类型名2 结构体成员名表2;
…
};
2)说明:结构体类型说明的类型名也可以是结构体类型,当结构体说明中又包含结构体时,形成结构体的嵌套。结构体最多嵌套15层,并且允许内嵌结构体成员的名字与外层成员的名字相同。
3.定义方式:有三种定义方式。
1)先声明结构体类型再定义变量名。
2)在声明类型的同时定义变量。
3)直接定义结构体类型变量。
4.引用方式:与C++的类成员引用方式相同。
结构体变量名 . 成员名
1)结构体变量不能作为一个整体而对去进行任何操作,只能对结构体变量中的各个成员分别进行操作。
2)如果结构体的某个成员本身也是一个结构体,则可以使用若干个成员运算符“.”,一级一级地找到最低的一级成员,只能对最低一级的成员进行赋值或存取及计算。
5.结构体数组:注意,结构体数组无法直接进行赋值。
struct 结构体变量名 { 成员表列 } 数组名[常量表达式]
II. 结构体与指针
1.指向结构体变量的指针:三种等价定义如下。
结构体变量 . 成员名
(*结构体指针变量名) . 成员名
结构体指针变量名 -> 成员名
2.指向结构体数组的指针:
结构体数组及其元素也可以用指针变量来指向。在使用指针变量指向结构体数组时,只要把该结构体数组中的元素当做普通的结构体变量使用即可。
3.结构体指针作为函数参数:
1)结构体变量的成员作为实参传递给主调函数。
2)可以用结构体变量作为一个整体实参。
3)C语言允许将结构体变量的地址作为实参传递,此时对应的形参应该是一个基类型相同的结构体类型的指针。
十一. 文件
I. 文件的打开与关闭
1.fopen()函数:
1)调用形式:函数返回一个指向FILE类型的指针,若打开出现了错误,则函数返回NULL。
fopen(文件名,文件使用形式);
2)常用的文件使用方式:
r 为读而打开文本文件
rb 为读而打开一个二进制文件
w 为写而打开文本文件
wb 为写而打开一个二进制文件
a 为在文件后面添加数据而打开文本文件
ab 为在文件后面添加数据而打开一个二进制文件
r+ 为读和写而打开文本文件
rb+ 为读和写打开一个二进制文件
w+ 首先建立一个新文件,进行写操作,随后可以从头开始读。若指定的文件已经存在,则原有的内容将全部消失。
wb+ 功能与w+相同,只是在随后的读和写时,可以由位置函数设置读和写的起始位置。
a+ 功能与a相同,只是在文件尾部添加新的数据之后,可以从头开始读。
ab+ 功能与a+相同,只是在文件尾部添加新的数据之后,可以由位置函数设置开始读的起始位置。
2.fclose()函数:
1)若对文件的操作方式为只读,则经过以上函数调用后,要使文件指针与文件脱离联系,可以重新分配文件指针去指向其他文件。
2)若对文件的操作方式为只写,则系统首先把该文件缓冲区中的剩余数据全部输出到文件中,然后使文件指针与文件脱离联系。
3)完成了对文件的操作之后,应当关闭文件,否则文件缓冲区中的剩余数据就会丢失。
4)当执行了关闭操作后,成功则函数返回0,否则返回非0。
II. 文件的读写
1.fread()函数和fwrite()函数:
1)调用形式:butter用于接收数据的内存地址。
fread(butter,size,count,fp);
fwrite(butter,size,count,fp);
2)说明:butter代表的是一个指针变量;size代表的是要读写的字节数;count用来指定每读写一次,输入或输出数据块的个数;fp为文件类指针。
2.fscanf()函数和fprintf()函数:
1)调用形式:
fscanf(文件指针,格式字符串,输入列表);
fprintf(文件指针,格式字符串,输出列表);
3.fputs()函数:用来把字符串输出到文件中.
fouts(str,fp);
str是要输出的字符串,fp是文件类指针,字符串末尾的\0不输出。
III. 文件的定位
1.rewind()函数:
rewind(fp);
该函数的功能是使文件的位置指针重新返回到文件的开头,其中fp为文件指针,且该函数没有返回值。
2.feod()函数:读到末尾为非0值为止。
3.fseek()函数:重新定位函数。
十二. 其他
I. sizeof( )函数
1.概念:sizeof()是单目运算符,其优先级在单目运算符中最低,其参数可为数组、指针、函数、类型、对象。其功能是获得保证能容纳实现所建立的最大对象的字节大小。其运算结果的单位为字节。
2.用法:
1)sizeof(类型名称)
sizeof(int) == 4
sizeof(char) == 1
sizeof(double) == 8
2)sizeof(变量名称)
int a;
short b;
char c;
double d;
sizeof(a) == 4
sizeof(b) == 2
sizeof(c) == 1
sizeof(d) == 8
3)sizeof(数组名称)
int a[100];
double b[10];
sizeof(a) == 400
sizeof(b) == 80
4)sizeof(表达式):其返回值是表达式的结果的类型的字节数。
sizeof(16+355.7) == 8
II. 链表
1.概念:
1)定义:链表是一种常见的数据结构,可以用于动态存储单元分配。
2)注意:链表中的各元素在内存中不一定是连续存放的。一个节点中应该包含一个指针变量,用它存放下一个节点的地址。
3)head指针:每一个链表都用一个头指针来指向链表的开始,即为head指针,其内存放了链表第一个节点的地址。
4)链表的最后一个节点的指针域置成‘\0’,即NULL值,标志着链表的结束。
2.访问数据域:需要利用一个工作指针p,从头到尾依次指向链表当中的每一个节点,指向哪个节点就输出哪个节点的内容,直到链表结束。如果是空链表,就只会输出提示信息并返回调用函数。
3.删除节点:
首先要找到待删除的节点的前趋结点,然后将此前节点的指针域去指向待删除节点的后续节点,最后释放被删除节点所占的存储空间即可。
关于链表基本用法的代码实现,请参考我的一个好朋友写的博客:
链表的一些基本用法
@小菲601
#include<stdio.h>
#include<malloc.h>
#define LEN sizeof(struct student)
struct student
{
long num;
float score;
struct student *next;
};
int n;
int main()
{
//前面先来一大波定义函数
struct student *creat();
struct student *del(struct student *head,long num);
struct student *insert(struct student *head,struct student *stu);
void print(struct student *head);
struct student *head,*stu;
long del_num;
printf("建立链表:\n");
head=creat();
print(head);
printf("要删除的结点编号是:");
scanf("%ld",&del_num);
while(del_num!=0)
{head=del(head,del_num);
print(head);
printf("要删除的结点编号是:");
scanf("%ld",&del_num);}
printf("要加入的结点是:");
stu=(struct student*)malloc(LEN);
scanf("%ld,%f",&stu->num,&stu->score);
while(stu->num!=0)
{head=insert(head,stu);
print(head);
printf("要加入的结点是:");
stu=(struct student*)malloc(LEN);
scanf("%ld,%f",&stu->num,&stu->score);
}
return 0;
}
struct student *creat()//建立一个链表
{
struct student *head;
struct student *p1,*p2;
n=0;//没有结点
p1=p2=(struct student*)malloc(LEN);//先开辟一段空间
scanf("%ld,%f",&p1->num,&p1->score);//先对这一段空间输入数据,形成第一个结点
head=NULL;//先让头指针空着
while(p1->num!=0)//结点还有数的时候
{
n=n+1;
if(n==1) head=p1;//第一个结点时,把p1的值赋给head,head是这个链表的头指针,是要返回的地址
else p2->next=p1;
p2=p1;
p1=(struct student *)malloc(LEN);
scanf("%ld,%f",&p1->num,&p1->score);
}
p2->next=NULL;
return(head);
}
struct student* del(struct student *head,long num)//删除一个结点
{
struct student *p1,*p2;//先创建两个指针;
if(head==NULL)//是空表;
{printf("\nlist null!\n"); return(head);}
p1=head;
while(num!=p1->num&&p1->next!=NULL)
{p2=p1; p1=p1->next;}
if(num==p1->num)//找到这个数啦!!
{if(p1==head) head=p1->next;//如果这个数在头结点
else p2->next=p1->next;//如果这个数不在头结点
printf("delete:%d\n",num);
n=n-1;//n代表链表长度?大约
}
else printf("not find!");
return(head);
}
struct student *insert(struct student *head,struct student *stud)//插入结点
//传递头结点的位置,和要插入结点的地址(用stud指向)
{
struct student *p0,*p1,*p2;
p1=head;//先使p1指向头结点;
p0=stud;//使p0指向要插入的结点;
if(head==NULL)//如果该表现在是个空表;
{head=p0;
p0->next=NULL;}//则要插入结点成为了第一个结点(头结点);
else//如果该表不是个空表
{while((p0->num>p1->num)&&(p1->next!=NULL))
{p2=p1; p1=p1->next;}
if(p0->num<=p1->num)//如果该链表不为空,要插入的位置是第一个结点之前;
{if(head==p1) head=p0; //p0->next=p1;??要吗?
else {p2->next=p0; p0->next=p1;}//如果该链表不为空,而且要插入的位置不是第一个结点之前和最后一个结点之后
}
else//如果该链表不为空,要插入的位置是最后一个结点之后
{p1->next=p0; p0->next=NULL;}
}
n=n+1;
return (head);
}
void print(struct student* head)//输出链表
{
struct student *p;
p=head;
do
{printf("%ld,%.1f\n",p->num,p->score);
p=p->next;
}while(p!=NULL);
}