第0章 C语言基本语法5

更新时间:2024-05-20 06:37:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第0章 C语言基本语法

一共80学时,分两部分:C、数据结构。其中C大概32学时,数据结构48学时。

0.1 C编译环境

C语言一般可使用两种编程环境:

1、Turbo C 2.0版本,重要的三个热键: 1)编译:F9

2)运行:Ctrl+F5 3)观察结果:Alt+F5

2、Visual C++ 6.0版本:只适合在Windows操作系统下,是微软开发出来的,在这个环境下进行C、C++、Visual C++进行编程非常方便。

在VC++6.0看来,每个程序都应该以“Project”(工程、项目)的形式来进行。而每个Project又应该包含在一个WorkSpace(工作区) 之中。当然,一个Workspace中可以有多个Project。因此,我们每创建一个程序,都应该首先创建一个工程,这个工程就是一个整体,经过编译连接等操作,这个工程最终就能够变成一个在Windows下可执行的程序。

在VC6.0中的Project有以下几种常用类型:

1)Win32 Console Application:Win32控制台应用程序,适合所有的C程序,由它创建出来的应用程序一般没有独立的窗口。这种程序的入口函数为main函数。

2)Win32 Application:Win32应用程序,适合C/C++程序,这个与上面的区别在于,可以创建出有独立窗口的应用程序,特别地,它可以使用Win API函数库。入口函数不是main函数,而是tmain或WinMain等。

3)MFC AppWizard(.exe):适合使用Visual C++语言进行编程,可以方便地创建出普通的Windows窗口,能够方便地进行窗口编程。在这种程序中,连main或WinMain函数都找不到。

0.2 熟悉Visual C++ 6.0编程环境

每一个程序都以“工程(Project)”的形式出现,在VC中,又将一个或多个工程包装成一个“工作区(WorkSpace)”,所以,我们在每个程序编程时都需要创建一个工程,再在这个工程中创建文件(.cpp、.h文件等)。

编译微型条

0.3 顺序程序设计 0.3.1 数据类型

1、基本类型(整型、浮点型、字符型),枚举类型、复合类型(数组、指针、结构体、共用体)。

2、整型:int、short int、long int,都是使用补码的形式来表示。 如要将-123表示成16位(2B)的补码

1)先将123化为原码二进制形式:0000 0000 0111 1011

2)最高位取1,其它位按位取反:1111 1111 1000 0100,得到的是反码。 3)末尾加1:1111 1111 1000 0101

显然,16位所能表示的数据,最大的那个是:0111 1111 1111 1111=215-1,最小的那个数:1000 0000 0000 0000=-215,而1111 1111 1111 1111=-1 int short int long int char Turbo C 2B,-215~215-1 2B,-215~215-1 4B, -231~231-1 1B,-128~127 Visual C++ 4B, -231~231-1 2B,-215~215-1 4B, -231~231-1 ASCII:1B Unicode:2B unsigned:无符号,也就是没有负数,全部是正数。如unsigned int 所以,如果对于int来说,1111 1111 1111 1111=-1,而对于unsigned int来说,1111 1111 1111 1111=216-1

另外还有一个关键字:signed,代表有符号,如signed int实际上就是int。 long int可以简写为long,而short int可简写为short 3、浮点数:float、double,

float称为单精度浮点数,由4B表示,它只有6个有效数字。能表示的数据范围:0及1.2X10-38~3.4X1038

double称为双精度浮点数,由8B表示,它有15个有效数字。能表示:0

-308308

及2.3 X10~3.4X10

long double长双精度浮点数,一般不太使用,它一般可以有有16B。 注意一个细节:不准确,可能有些数明明是有限小数,但在计算机中无法表示,将表示成无限的近似的数,如0.1,所以,我们有两点要注意的:

1)不要用浮点数来控制循环次数。 float f;

for( f=0; f<1;f+=0.1) { ?? }

2)不要直接比较两个浮点数是否相等。如: double a=0.1, b=0.11 if( a==b)

真正要做的比较应该为: if( fabs( a-b )<= 1e-7 ) 4、字符:char,包括:(可参照ASCII码表进行) 1)字母:大写、小写 2)数字:0~9

3)专门符号:29个,!@#$ 4)空格符:空格、水平制表符

5)不能显示出来的字符:null,可以以'\\八进制代码'表示出来,如回车换行符'\\007',还可以以'\\代号'表示,如'\\n'

\\n:回车换行 \\f:换页 \\b:退一格

\\r:回车,不换行,就是回到这一行的最前面开始 \\0:空字符, \\\\:反斜杠本身

字符必须包含在一对单引号中,如:'a','\\035','\\n'等

5、字符串:包含在一对双引号中的任意个(1个或多个)字符,但是一个字符串中至少有一个字符\\0,如:

\、

char str[ ]=\,

可以使用strlen( str )来求字符串str中有多少个字符,此处得到6.

可以使用sizeof( str )来求字符串 str占了多少个字节的空间,此处得到7. 注意:

1)双引号本身应该在英文状态下

2)字符串中可以有中文,一个中文点两个字节

0.3.2运算符

+ - * / %

除:规定如果参加运算的两个数都是整数的话,则结果也是整数;而如果只有有任意一个数据为float或double,则结果为double,如: double c;

int a=-10, b=4;

//方法一:强制转换某个数为double或float c=(double)a/b; //c=2.5 c=a/(double)b;

printf(\

//方法二:将数字前面乘以1.0,从而该数变为double c=1.0*a/b; //c=2.5;

printf(\陷阱:

int a=-10, b=4; float c=2.5, d=5.2; double e; e=c*d+a/b;

取余%:规定参加运算的两个数必须是整型(包括int、short、long、char),不得为浮点数,其结果为被除数除以除数所得到的余数,其符号与被除数相同。如:

int a=-20, b=3;

int c=a%b; //c=-2 int d=a/b; //d=-6 如果

int a=20, b=-3;

int c=a%b; //c=2 int d=a/b; //d=-6

0.3.3赋值

在C中,赋值运算符包括:=、+=、-=、*=、/=、%=及后面的位运算的&=、|=、^=等。

注意问题:

1、复合赋值:+=、-=、*=、/=、%=,如: int b=5; b+=7; //相当于b=b+7;

其计算顺序为从右向左计算,如: int a=10;

a+=a-=a*=a/=2; //结果a=0

可以分解为:a/=2; a*=a; a-=a; a+=a; 2、括号的问题: int a=10, b, c; a+=(b=3*4); //b=12, a=22

3、逗号表达式的问题,规定,取最后一个数的值,如: int a=10, b;

a+=(b=3*4, c=(5, 10, 15)); //b=12, c=15, a=25 4、混合运算类型的转换问题:

1)有些转换是自动完成的,其一般原则为:char->int->long->double如: double f; int a, b; float t; char c; f=(t= a+c );

2)如果不满意,可以强制类型转换 a=(int)( f + t);

由doube或float强制向int转换方式,是直接将小数点后面的所有的数据摸掉,不会自动进行四舍五入。如:

double d=12.89; int a;

a=(int)d; //a=12

如果我们想要四舍五入,得用技巧,加0.5后再取整。如: double e=12.89, f=34.46; int a=(int)(e+0.5); //a=13 int b=(int)(f+0.5); //b=34;

这个小技巧可以扩展:精确到小数点后几位,如: double d=12.345678;

现在,要想精确到小数点后3位,应该得到:12.346,方法为先乘以1000,再加0.5,再取整,再除以1000.0:

d=(int)(d*1000+0.5)/1000.0;

0.3.4自增和自减

++、――,形式有两种:a++,++a,

a++规定,先取a的值在本表达式中进行计算应用,再将a加一,只影响后面的语句,如:

int a=4;

int b=a++; //b=4,a=5 int c=++a; //a=5, c=5 a=4;

int d=(a++)+(a++)+(a++); 在VC中,相当于四条语句: d=a+a+a; a++; a++; a++; 其结果,a=7, d=12;

++a规定:先将a的值加一,再应用a的值,并影响这条语句。如: int a=4;

int b=++a; //a=b=5;

int c=(++a)+(++a)+(++a); 在Turbo C中,相当于四条语句: ++a;++a;++a; c=a+a+a;

0.3.5 数据的输入与输出

1、输出:printf、putchar、puts等 注意:

1)特殊字符的输出:\\n、\\b

2)输出各种字符的方式:整型%d,字符%c,字符串%s,小数形式的浮点数%f,指数形式的浮点数:%e,长整型%ld,double型%lf,后面的指针类型(地址)%lp

3)对于整型宽度问题:],这个数字点5个位置,多出的位置用前置空格被齐,被在数字的前面。如果%-5d,空格被在数字的后面。如果这个数字的长度大于5,则将该数字完整的打印出来。

4)对于浮点数,用%f打印小数,用%e打印指数。

5)可以指定小数点后几位,如%.3f,或.3f,意思是说,小数点后占3位,一共占10位(包括小数点位,因此,只有9位数字)

6)对于整数,输出八进制数:%o,输出十六进制数:%x或%X

7)输出无符号整数:%u,如果该数实际上已经是有符号的数,按%u输出后,负数会变成正数。

8)输出长整型数:%l(注:字母l),在VC中完全不需要。 9)如果想输出特殊的字符如\\、%本身,可\\\\,%%输出 10)输出地址:%lp,

11)输出单个字符可用putchar( )进行。 2、输入:scanf、getchar、gets等

可接收的输入数据包括:整数、浮点数、字符、字符串,且均以回车键结束输入。格式与输出相同。

1)可以指定宽度:如:

scanf(\ //如果输入123456789,则a=123,b=4567,c=89,但是,如果用户输入的是:12 345678 9,则a=12,b=3456,c=78,9多余舍弃掉。

2)常见错误:

(1)后面的变量必须用地址,如果不用地址,会出错。 scanf(\//错误,a、b、c必须用地址表示 (2)如果变量本身就是地址(指针),那么不能再用地址,如: int a;

int *p=&a; scanf(\ //正确,因为p就是a的地址,就是输入到a中去了。 scanf(\//错误,变成将整数输入到p中去,可是,p又是指针。 (3)在scanf中,除了格式符外,不要出现多余的文字,如:

scnaf(\用户在输入数据时,必须a=5,b=10才能正确输入。

3)输入字符:可用%c,如果混合输入,非常麻烦: 4)还可以使用getchar()输入字符,方式为: c=getchar( );

5)还可以使用getch( )输入字符,方式 为: c=getch( );

该函数应该包括头文件,且用户不需要按回车键就输入,在制作游戏中比较有用。

6)还可以使用getc( stdin )输入字符,其中参数stdin就是标准输入设备,由VC定义好的。

c=getc( stdin );

这种方法在文件操作中比较有用,其中的参数stdin可以换成是我们已经通过fopen打开的文件指针,就能够从该文件中输入一个字符。

0.4 C语言中的选择结构程序设计 0.4.1 关系运算符和关系表达式

关系运算符有6种:<、<=、>、>=、==、!=

其中:<、<=、>、>=的优先级相同,==、!=优先级相同,且前者的优先级高于后者。

而且,优先级:算术运算符高于关系运算符高于赋值。

关系表达式:就是用关系运算符将若干个数值、数值表达式连接起来的式。如:

a>b,(a=3)>b+(c==4)

在C中,并不存在\真\和\假\,只有非0和0,规定:凡是结果为0的,认为“假”(不成立),凡是结果为非0的,都认为是“真”(成立),哪怕结果为0.000001、'A',也是真。且在C语言中,没有所谓的BOOL型。因此,有时我们会看到类似这样的式子:

if( a=b+c ) //判断a是0还是非0,如果为非0,则说明“真” { }

注意与: if( a==b+c ) { }

的区别。

在编程的时候,有时可以用C的检查机制来检查我们的错误。可以写为: if( b+c==a ) { }

因为如果你写错成: if( b+c=a ) { }

C语言能够查找出这个错误:赋值运算符规定左边必须是一个变量。同样地,如果要写判断一个变量是否与某个常数相等:

if( a==12 )

可写成:if( 12==a ),因为如果写错成:if( 12=a ),则C语言能发现这个错误。

再次强调:通过关系运算的结果只有两种:1表示真,0表示假。 所以:

int a=3, b=4, c=5, d=0;

表达式c>b>a在数学上的结果为“真”,但是在C语言中,这个表达式的结构为0(假),其计算过程为先计算:c>b,为真得到1,再计算1>a为假,得到0.也正因为如此,如果我们就是要表达数学上的c>b>a的真实含义,则需要使用逻辑运算符来连接起来:c>b && b>a

0.4.2 逻辑运算符和逻辑表达式

逻辑运算符:&&与、||或、!非

逻辑表达式:用逻辑运算符将若干个表达式连接起来的式子。如: a+b>c && c

逻辑表达式的结果也用0或1来表示假或真。 逻辑运算符的真值表: a b !a !b a&&b a||b 非0 非0 0 1 1 非0 0 0 0 1 0 非0 1 0 1 0 0 1 0 0 优先级:!最高→&&→|| 与关系运算符的比较:

!高于算术运算符,&&和||低于关系运算符,算术运算符高于关系运算符。 ! →算术运算符(+-*/%)→关系运算符(<、<=、>、>=、==、!=)→&&和||→赋值运算符=

所以:如果有式子:(a>b) && (x>y)可以写成a>b && x>y a+!b>c && !c

使用逻辑运算和关系运算可以进行复杂的计算、判断。 如判断闰年,满足以下条件的年份都是闰年:

1)如果年份能够被400整除,则一定是闰年,如2000;

2)如果年份能被4整除,但又不能被100整除的,也是闰年,如1996、2004是,而2100年不是闰年。

3)其它的不是闰年

假设年份为y,请写出判断闰年的表达式: y@0==0 || (y%4==0 && y0!=0) 或写成:

!( y@0 ) || !(y%4) && y0

判断一个变量是否为0,至少有两个方法: if( a==0 ) 或:if( !a )

而判断一个变量是否为非0,也有两种方法: if( a!=0 ) 或:if( a )

在C++或Visual C++中,有一种称为BOOL类型的数据,实质就是int型。实际上BOOL就是通过typedef这个关键字来定义出来的新的类型:

typedef BOOL int;

这样,用BOOL来定义变量,实质就是用int来定义的。而TRUE和FALSE实际上是用#define定义出来的两个宏:

#define TRUE 1 #define FALSE 0 BOOL b; //int b;

b=TRUE; //b=1,TRUE是一个宏定义为1:#define TRUE 1 b=FALSE; //b=0,#define FALSE 0

在Java中,有真实的BOOL型数据,用true和false表示真、假。因此,在Java中,如果int a=5; if( a )是错误的。

0.4.3 条件运算符和条件表达式

表达式1?表达式2 :表达式3

它的计算过程是,如果表达式1的值为非0,则取表达式2的值,如果表达式1的值是0,则取表达式3的值。如:

int a=5, b=-2, c, d=7, e=-6, f=11, g=-9, h=-1; c=(a>b) ? 10 : -7; //c=10

c=(a>b) ? ((a>d) ? d: ((d

如何用条件表达式来求出a、b、c三个变量中最大值。 int getMax( int a, int b, int c ) {

return _______________________________________; }

0.4.4 if-else语句

基本语法: if ( 表达式 ) {

路径1 } else {

路径2 }

其程序流程图:

假if(s<=59)真假if(s<=69)差真假if(s<=79)及格真中真中其它语句 0.4.5 嵌套的if-else语句

1、第一种嵌套的形式有: if( 表达式1 ) {

路径1;

}else if( 表达式2 ) {

路径2; }else {

路径3; }

例如:判断成绩s: 0~59 60~69 70~79 80~89 90~100

差 及格 中 可以编程: 假设s一定在0~100之间,则: if( s>=0 && s<=59 ) {

差;

}else if( s>=60 && s<=69 ) {

及格;

else if(s>=70 && s<=79 ) {

中;

else if(s>=80 && s<=09 ) {

良;

}else if( s>=90 && s<=100) {

优; }

假设s一定在0~100之间,则: if( s<=59 ) {

差;

}else if( s<=69 ) {

及格; else if( s<=79 ) {

中;

else if( s<=89 ) {

良; }else {

优; }

其它语句;

2、第2种嵌套的形式: if( 表达式1 ) {

if( 表达式2 ) { if( 表达式3 ) {

良 优 }else { } }else { } }else {

if( 表达式4 ) { }else { } } 如:

if( s<=79 ) {

if( s<=69 ) { if( s<=59 ) { 差; }else { 及格; } }else { 中 } }else {

if( s<=89) { 良; }else { 优; } } 0~59 60~69 差 及格 7 10 或者写成: 70~79 中 64 80~89 良 13 90~100 优 6 if( s<=89 ) {

if( s<=69 ) { if( s<=59 ) 差 else 及格 }else { if( s<=79 ) 中 else 良 } }else {

优 }

3、几种特殊的if-else语句及其嵌套形式:

有些程序判断中,if比else要多,在这种情况下,必须准确地判断else与哪个if配对。如:

int a=5, b=3, c=-10, d=-7, e=100; if( a>b ) {

if( c>d )

e++;

}else

e――;

需要强调的是,用分号;来表示一个语句的结束,如: if( a>b ) ; if( c>d )

e++; else

e――;

0.4.6 switch-case语句

switch语句又称为多分支选择语句,它可以提供多条路进行选择,它完全可用if语句来取代。其一般格式:

switch( 表达式 ) //表达式是可以计算出的结果,建议应该是int型或char型,不要是float或者double

{

case 值1: ?? break; case 值2: ?? break; ?? default: ?? }

如判断成绩: switch( s/10 ) {

case 0: case 1: case 2: case 3: case 4: case 5: printf(\差\\n\ break; case 6: printf(\及格\\n\ break; ?? }

关于switch-case的注意事项:

1)应该具有必要的break语句来跳出switch,否则结果可能会出错,如: int a=3, b=10; switch( a ) {

case 1: b++; break; case 2: b++; case 3: b--; break; case 4: b--; case 5: b++; default:

b--; break; }

课后思考题:请问2012年11月23日是这一年的第几天?已知1月1日是第1天。如果2012年1月1日是星期天,问2012年11月23日是星期几?

0.4.7 选择语句编程时的错误小结

1、在判断两个值是否相等时,==误写成=,变成了赋值,如: int a=5, b=-2; if( a==b ) { }

误写成: if( a=b ) { }

2、经常if复合语句忘记写大括号,如: if( ) {

语句1; 语句2; ?? }

误写成: if( )

语句1; 语句2; ??;

3、&&会误写成&,|| 误写成 | ,如: int a=69, b=58; //a=1000101,b=111010 if ( a && b ) //成立,语句会执行 {

语句1; }

误写成: if( a & b) //a&b=0,不成立,语句1不会执行 {

语句1; }

4、else与if配对配错了。 如:本意是 if( a>b ) {

if( c>d ) e++; }else

e――; 误写成: if( a>b ) if( c>d ) e++; else

e――; 5、switch( 表达式 )语句中的表达式应该为整型或字符型,不应该是是浮点、字符串、指针。如:

float c; switch ( c ) //错误 {

case 0.1: ?? case 0.2: ?? }

6、switch中忘记写break语句。如: int a=5, b=-2, c=20; switch( a ) {

case 3: b++; case 5: b--; case 7: b++; case 9: c--; }

7、不是错误:default语句可以在switch中的任意位置,最前面、最后面、中间都行。如果没有default语句,则当所有的case均不满足时,会直接退出switch语句。

8、不是错误:指针可以参加大小、相等比较,意义为:

1)比较是否相等,其实就是问两个指针是否指向同一个变量;

2)比较大小,一般用于数组中,看哪个指针指向的位置在前还是在后。如: int a[100];

int *p=&a[45], *q=a+23; if ( p>q ) //成立的,因为a[45]在a[23]的后面,地址更高(大) { }else

{ }

但是如果写成: if ( *p>*q ) //不一定成立,这是在判断a[45]和a[23]的值 { }else { }

if ( *p>q ) //语法错误,指针不能与某个非指针比较 { }else { }

不得将一个指针值与某个整数进行比较大小、相等,如: int *p, *q, a=5, b=-7; p=&b; q=&a; if( p==a ) //语法错误 if( p>a ) //语法错误 if( p>q ) //正确

唯一的与整数进行比较的指针就是比较是否为0(NULL),如: if( p==NULL )

或者写成:if( p==0 ) 或者写成:if( !p )

9、关于字符串的比较,常见的错误如: char str1[ ]=\

现在要判断两个字符串的大小,下面写法是错误的: if ( str1>str2 ) //错误!实际上在判断两个指针的大小 if( *str1>*str2 ) //语法正确,但结果不是我们要的。 正确的判断应该是:

if( strcmp( str1, str2)==0) //说明两个字符串相等 if( strcmp( str1, str2)>0) //说明str1>str2 if( strcmp( str1, str2)<0) //说明str1

其中strcmp是一个C标准函数,使用时需要包含头文件。

0.4.8 课后练习

1、企业发放的奖金根据利润I提成。利润I低于或等于100000元的,奖金可提I的10%;利润高于100000元,低于200000元(100000<I≤200000)时,低于100000元的部分按10%提成,高于100000元的部分,可提成7.5%;200000<I≤400000时,低于200000元的部分仍按上述办法提成(下同),高于200000的部分按5%提成;400000<I≤600000时,高于400000元的部分按3%提成;400000<I≤600000时,高于600000元的部分按1.5%提成;I>1000000时,高于1000000元的部分按1%提成。请用户从键盘上输入其当月利润I,求出应发奖金总数。

要求分别:

1)用if-else语句编程

2)用嵌套的if-else语句编程 3)用switch-case语句编程

0.4.9 短路规则

出现在&&和||中。

if(条件1 && 条件2):如果条件1的结果为0(不成立),则条件2不会被执行,这样,条件1就把条件2短路了。如:

int a=4, b=0, c=-2, d=0; if( a<3 && b++ )

if(条件3 || 条件4):如果条件3的结果1(成立),则条件4将不会被执行,这样,条件3就把条件4短路了。如:

if( a>3 || b++ ) 再如:

if( (a>3 || b++) && (c<0 || d--))

if( (a<3 || b++) && (c<0 || d--)) //b=1 d=0 为什么需要短路规则呢?我们会这样写: file* f; //打开文件

if( f!=NULL && 读取文件f)

在C++中,也经常会判断对象(指针)是否存在,然后再做处理: CObject* p;

//可能p已经指向了某个对象,也可能没有指向 if(p!=NULL && p->x>5)

开始 0.5 循环

i=1, s=0; 构成循环可以有四种结构:

while循环、do-while循环、for循环、if-goto循环。

s+=i; 0.5.1 goto构成循环

一般结构:

语句1;

标号:语句2;

??

goto 标号;

如,计算1+2+3+??+100

i++; i<=100? N 打印s Y int i=1, s=0; code1: s+=i; i++;

if(i<=100) goto code1; printf(\再如:

int i=1, s=0; code1: if(i>100) goto code2; s+=i; i++;

goto code1; code2: printf(\

i=1, s=0; Y i>100 ? N s+=i; i++; 打印s 开始 0.5.2 do-while构成循环

i=1, s=0; 一般结构: 语句; do {

语句1; //循环体 ??

}while(条件); 语句??

int i=1, s=0; do {

s+=i; i++;

}while(i<=100);

printf(\

s+=i; i++; i<=100? N 打印s Y i=1, s=0; N 0.5.3 while构成循环

一般结构: while( 条件 ) {

语句; }

执行过程:当条件满足时,进入循环,如果条件不满足,则退出循环,跳到循环后面再执行。 i<=100 ? Y s+=i; i++; 打印s int i=1, s=0; while( i<=100 ) {

s+=i; i++; }

printf(\

do-while循环无论如何都会至少循环一次,而while可能一次都不执行。这可能会得到不同的结果:如:

i=0,s=0; do {

s+=i; i++;

while(i!=0); 及:

while( i!=0 ) {

s+=i; i++; }

一般来说,两者之间可以转换,其中任何一个都可以取代另外一个。

0.5.4 for循环

for循环的一般结构: 语句0;

for(语句1; 语句2; 语句3) //里面的两个分号不能缺少 {

循环体; }

语句4; 执行过程:

语句1-->判断语句2是否满足-->如果满足,进入循环体,如果不满足,则退出循环-->执行循环体-->语句3-->判断语句2是否满足??

语句0 语句1 语句2? Y 循环体 N 语句3 语句4

int i,s=0;

for(i=1; i<=100; i++) {

s+=i; }

printf(\

for循环可以有很多种省略的形式:

1)语句1可以省略,可以放在for的前面。 int i,s=0; i=1;

for( ; i<=100; i++) {

s+=i; }

2)语句3可以省略,可以放在循环体后面: int i,s=0; i=1;

for( ; i<=100;) {

s+=i; i++; }

3)语句2也可以省略,可以放在循环体的前面,用if—break来做: int i,s=0; i=1;

for( ;; ) //再次注意:这两个分号不能省 {

if(i>100) break; s+=i; i++; }

这四种循环,都可以互相取代,其中for用得最灵活,也最常用。goto语句目前处于淘汰状态,尽量不要用。

0.5.5 continue语句

用在循环(do-while、while、for)中,目的是结束本次循环,进入下次循环。注意与break区别:break是结束整个循环。如:

Eg:输出100~200之间的能被3整除的数。

思路:从100~200,一个一个进行检查,如果它不能被3整除,则继续下次循环,否则,打印之,用for很容易实现:

int i;

for(i=100;i<=200;i++) {

if(i%3!=0) continue; printf(\}

又如:

for(y=1,x=1;y<=50;y++) {

if(x>=10) break; if(x%2==1) {

x+=5;

continue; }

x-=3; }

x=1 6 3 8 5 10 y=1 2 3 4 5 6

break和continue用在while和do-while中的效果是一样的,但是,它们不能用在if-goto的循环中。

0.5.6 循环的嵌套

嵌套:在循环内又有循环。

如:计算1+(1+2)+(1+2+3)+??+(1+2+3+??+100) 考虑:第i项:1+2+3+??i,可以编程:

s=0;

for(j=1;j<=i;j++) s=s+j;

而i本身又是从1到100,可得: int sum=0;

for(i=1;i<=100;i++) //将第i项加到sum中去 {

s=0; //计算第i项,它又是一个循环 for(j=1;j<=i;j++) s=s+j; sum+=s; }

2

这个循环次数为:1+2+3+....+100=5050=n(n+1)/2=O(n) 如果只使用一个循环,可编程: int s=0, sum=0, i; for(i=1;i<=100;i++) {

s+=i; sum+=s; }

循环次数为100次,效率大增。 再如:打印一个这样的图案:

***** ***** ***** ***** *****

用i控制行,共5行:for(i=1;i<=5;i++)

每行都要先打印若干个空格可以用一个循环来打印,再打印5个*号,可以用一个循环来打印:for(j=1;j<=5;j++) printf(\

int i,j,k;

for(i=1;i<=5;i++) {

for(k=1;k<5-i;k++)

printf(\ //打印一个空格 for(j=j;j<=5;j++) printf(\

printf(\ //putchar('\\n'); }

如果要打印: * *** ***** *******

********* *********** ********* *******

***** *** *

0.5.7 循环举例

Ex 5.7.1:根据公式

??4?(11?13?15?17???),计算

圆周率。其中,精度控制在0.000001.

例如,如果加到第101时,PI=3.1415123,而在第103时,得到PI= PI=3.1416279,则此时的精度为:|3.1416279-3.1415123|=0.0001156

细化:每次都会产生一个PI,因为要后面这个PI-前面那个PI,所以,每次都应该将得到的PI保存起来。用while或do-while来做,i表示分母,显然从1开始,每次+2,每项得到1.0/i,将其加到PI中去,另外还需要一个变量flag来控制加减,每次在两者之间变换。

int i=1,flag=1;

double p1=0,p2; //p1代表新PI,p2代表老PI do {

p2=p1;

p1+=flag*4*1.0/i; //重新算出一个新PI flag=-flag i+=2;

}while(fabs(p2-p1)>0.000001); printf(\

课后练习:请将其换成while循环,还可以考虑用for也行。

Ex 5.7.2:瞎子爬山法:主要用在解高次方程上,作为课后的思考题。 让你猜一个小数:123.56789 0

10000 1000 100 200 110 120 130 121 122

123 124 123.1

Ex 5.7.3:判断一个数是否是素数: 24不是素数:因为它能被2整除。 35也不是素数,它能被5整除

37是素数,因为:从2、3、4、??、36都不能够整除它。

用程序表达:假设这个数为n,则让i=2、3、4、??、sqrt(n)去除它,只要中间有一个数能除尽,则n不是素数,否则,是素数。

int n=123123;

int flag=1; //先假设n是素数 for( i=2; i<=sqrt(n); i++) {

if(n%i==0) { flag=0; break;} }

if(flag==1) printf(\是素数\\n\else printf(\不是素数\\n\

Ex 5.7.4:打印100~10000之间所有的素数: for(j=100;j<=10000;j++) {

int flag=1; //先假设n是素数 for( i=2; i<=sqrt(n); i++) {

if(n%i==0) { flag=0; break;} }

if(flag==1) printf(\}

Ex 5.7.5:求最大公约数: 设a=115, b=35,则c=5

有一种算法:辗转相除法,就是将两者之中的大的除以小的,如果能除尽,则小的就是最大公约数,如果除不尽,则将原来的小的赋值给大的,将两者的余数赋给小的,再循环相除??直到:1)某次除尽了;2)某个数为0了

a=115,b=35

a%b=10,不等于0, a=b=35, b=10 a%b=5,不等于0 a=10, b=5,

a%b=0,则此时的b就是最大公约数 c=a%b;

while(c!=0) {

a=b; b=c;

c=a%b; }

b就是最大公约数

0.5.8 课后练习

1、求1!+2!+3!+??+20!(注意,值很大)

2、求Fibonacci数列的前20项,所谓的Fibonacci数列指,第一项为1,第二项为1,从第三项开始,每项均为前两项的和,即:

1,1,2,3,5,8,13,21,?? 请打印前20项;

3、有一个分数序列:,,,,23581321,,??

1235813求出这个数列的前20项之和。

4、用迭代法求x?a,其中求平方根的迭代公式为:

xn?1?12(xn?axn)

要求前后再次求出的x的差的绝对值小于10-5 5、用二分法求下面方程在(-10,10)之间的根:

2x?4x?3x?6?0

32提示:设变量h=10,l=-10,变量x每次取(h+l)/2,代入公式

y?2x?4x?3x?6,可求出

32y,

1)如果|y|<=10-5,则此时的x就是要求的根,退出循环;

2)如果y>0,说明根在(x,h)之间,只需要l=x,再进行上面的循环 3)如果y<0,说明根在(l,x)之间,只需要h=x,再进行上面的循环

6、输出所有的水仙花数,所谓的水仙花数是指一个3位数,其各位数字立方和等于该数本身,如153是一个水仙花数,因为:13?53?33?153

7、一个数如果恰好等于它的真因子之和,这个数就称为“完数”,如6就是一个完数,因为6的真因子有:1、2、3,而1+2+3=6。编程找出1000之内的所有完数。

0.6 数组 0.6.1 一维数组

1、定义

类型名 数组名[常量表达式];

注意:

1)数组名是常量,不得改变:如 int a[10];

a=b; //错误,a不能被改变。

2)定义时,维数[常量表达式]中,必须是常量表达式,不得是变量。如: int n;

printf(\请输入班级人数:\scanf(\

int a[n]; //错误,维数n必须是常量 常量包含直接(数字常量)、符号常量(用#define定义的),如: #define N 100

int a[N]; //正确,因为N是符号常量,此处就是100

3)在使用过程中,我们定义的虽然是int a[10],有10个元素,但这10个元素是a[0]~a[9],并不存在a[10]这个元素,如:

for(i=1; i<=10; i++)

a[i]=i; //错误,没有a[10]

4)在使用过程中,下标可以是变量,但是计算后得到的下标不得越界,如: a[n*5+4]=-2; //正确,在使用过程中可以 5)我们在思考问题时,习惯画这样的图表示一个数组:

0 1 2 3 4 5 6 7 8 9 a

6)数组一般要先进行初始化,因为数组在定义好了以后,里面所有的元素的值都不确定(值不知道是多少),只有初始化后再使用比较合适。通常就是一个循环来进行初始化:

int a[10];

for(i=0;i<10;i++) a[i]=0;

7)数组的初始化,还可以在定义时进行: int a[10]={1,1,1,1};

我们只给了4个元素(a[0]~a[3])的值,系统会自动给后面的6个元素的值全部为0;

8)在定义时,如果后面给出了所有的元素的值,则前面可以不写其维数的值,如:

int a[ ]={1,2,3,4,5,6,7,8,9,10}; 则数组a就是a[10] 2、应用举例

1)用数组来计算Fibonacci数列 0,1,1,2,3,5,8,13,??

思路:定义一个数组a[20],其中a[0]=0,a[1]=1;从a[2]开始进行循环,每个元素都等于前面两个元素的和:a[i]=a[i-2]+a[i-1];

long a[40]={0,1}; int i;

for(i=2;i<20;i++)

a[i]=a[i-2]+a[i-1]; for(i=0;i<20;i++) {

if(i%5==0)

putchar('\\n'); printf(\}

温馨提示:在Visual C++ 6.0编程环境中,如果程序的格式很乱,可以先选择要整理的所有的代码行,然后按快捷键:Alt+F8实现。

3、数组与指针

int a[10]={??};

int *p; //定义一个指针,int型,说明这个指针可以指向一个int型的整数

以下使用是正确的:

1)让p指向数组a中的某一个元素:

p=a; //解释:a是数组名,也是地址,就是a[0]的地址,所以,将a的值赋给p,也就是将a[0]的地址赋给p,这样,p就指向了a[0],与之等价的代码:p=&a[0];

p=&a[5]; //将a[5]的地址赋给p, p=a+5; //也是将a[5]的地址赋给p 一个指针+整数(如5),不是指在该地址上加5个字节,而是指加上5个单位地址(如果这个指针地址是int型,则一次加了5个int型的地址,实际上就是批在该地址后面的第5个元素)

2)假定p已经指向了a[0],则使用数组a中的元素,可以使用以下方法: a[5]=-7; //数组名下标法 p[5]=-7; //指针下标法 *(p+5)=-7; //指针法 *(a+5)=-7;

3)注意在上述的第2和第3种方法中,必须关注p的指向,如果p指向的不是a[0],而是a[3],则:

p[5]、*(p+5)代表的是a[8] 如:

char str[ ]=\char *p=str;

puts(p); //打印字符串p,puts的工作原理是从参数p开始,一直输出直到第一个\\0结束,如果在该字符串中没有\\0,则结果无法预料。

p+=3; //p指向了str[3]的字母'o' puts(p); //输出的结果为:ove China

0 1 2 3 4 5 6 7 8 9

a

17 23 56 p 4)指针可以移动:p++、p--、p=p+5、p=p-5,其中p的移动是以元素为单位,如p++是将p往后面移动一个元素,如果p原来指向a[3],则p++后,p指向了a[4];但是,数组名a不可移动:a++是错误的。

5)关于*p++的问题:

假设p指向了a[3],现在使用: t=*p++;

代表什么意思?

实际上有三种形式:

(*p)++、*(p++)、*p++

查C的说明文档知道,*与++的优先级是相同的,但是++是右结合,所以p应该先与++结合,再与*结合,所以*p++等价于*(p++)。

(*p)++是先取*p的值,再让p指向的那个元素加一。

*(p++)是先取*p的值,再让指针p向后面移动一个位置。 所以:

s=(*p)++; //执行完后,s=23,然后,a[3]=24

t=*(p++); //执行完后,t=23,然后,p指向了a[4] 4、数组与指针应用举例:

1)将上面的Fibonacci数列用指针来实现: long a[40]={0,1}; long *p; int i;

for(p=a+2; p

*p=*(p-2)+(*(p-1)); //相当于p[i]=p[i-2]+p[i-1] //循环结束后,p已经指向了a[20] p=a;

for(i=0;i<20;i++) {

printf(\ p++; }

2)已知有一个非递减的整型数组a如下:

0 1 2 3 4 5 6 7 8 9

a

2 2 2 3 3 4 5 5 6 6 p

请编程,将里面重复的元素删除,只剩下一个:

0 1 2 3 4 5 6 7 8 9

a

2 3 4 5 6 4 5 5 6 6 p 背景例子:从有n个元素的数组中删除一个指定位置的元素,如要删除a[3] for(i=3;i

0 1 2 3 4 5 6 7 8 9

a

2 3 3 4 5 5 6 6 6 6 j

n=8

i

现在编程:

int a[10]={??}; int n=10; int i,j;

for(j=1;j<=n-1;) {

if(a[j]==a[j-1])

{

for(i=j;i

n--;

}else j++; }

for(i=0;i

printf(\高效率的算法: int a[10]={??}; int i=0,k=1;

int n=1; //目前可确定的元素的个数 while(k<10) {

if(a[k]!=a[i]) {

i++;

a[i]=a[k]; n++; } k++; }

0 1 2 3 4 5 6 7 8 9

a

2 3 4 5 6 4 5 5 6 6 I k

0.6.2 二维数组

1、定义方法:

类型 数组名[第一维][第二维]; 如:

float f[5][10];

其中第一维又称为行,第二维称为列,上面的就是5行10列:

a

q 0 1 2 q

3 4 0 1 2 3 4 5 6 7 8 9

2 2 2 2 2 3 3 3 3 3 4 4 4 4 4 3 3 3 3 3 3 3 3 3 3 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 它们在内存中的排列方式是:a[0][0]->a[0][1]->??->a[0][9]->a[1][0]->??->a[4][9]

可以计算他们的地址:

已知有一个二维数组float f[100][200];的首地址是1000,问f[34][67]的地址是多少?(每个float数据都占4字节)

Addr=1000+34*200*4+67*4 公式:对于f[m][n]的地址: Addr=首地址+(m*200+n)*4

二维数组的编程主要是使用一个二重循环,一般使用两个for来完成: int i, j;

for(i=0;i<行数;i++) {

for(j=0;j<列数;j++) {

//处理单个元素a[i][j] } }

2、二维数组主要用在线性代数的行列式中: 如有一个矩阵

?12345??1611??712A??678910???2?1112131415? B??3813???1617181920???4914??51015可以很容易将一个矩阵与一个二维数组对应起来:

int a[4][5]={ {1,2,3,4,5}, {6,7,8,9,10},

{11,12,13,14,15}, {16,17,18,19,20} };

如果要将上面这个矩阵打印出来: int i, j;

for(i=0;i<4;i++) {

for(j=0;j<5;j++) {

printf(\ //打印一个元素 }

putchar('\\n'); }

1) 请将它转置:

对于b[i][j]来说,它来自于a[j][i] int b[5][4];

for(i=0;i<5;i++) {

???18? ??20??161719 for(j=0;j<4;j++) {

b[i][j]=a[j][i]; } }

2)求两个矩阵的和:

?1?6?A??11??162712173813184914195??1??102? C???315???20??41234123412341??2? 3??4?求D=A+C int d[4][5];

for(i=0;i<4;i++) {

for(j=0;j<5;j++) {

d[i][j]=a[i][j]+c[i][j]; } }

3)对角线求和

?1?6?E??11??16??212712172238131823491419245??10?15? ?20?25??int e[5][5];

int s=0;

for(i=0;i<4;i++) {

for(j=0;j<5;j++) {

if(i==j) //对角线 s+=e[i][j]; } }

也可以使用一重循环: for(i=0;i<5;i++) s+=e[i][i];

4)求上角的所有元素的和(不包括对角线元素) int f[5][5]; int s=0;

for(i=0;i<4;i++)

{

for(j=0;j<5;j++) {

if(ij s+=e[i][j]; } }

5)矩阵的乘法(A X G,要求A的列数与G的行数相等)

?1?6A???11??16271217381318491419?15??2??10? G??315????420???5123451??x?2??x3? H???x?4???x?5?xxH[2][1]xx??x? x??x?则H=A X G结果为A的行数和G的列数,就是4 X 3,其中H[i][j]这个元素来自于A矩阵的所有第i行(共有5个元素)与G中的第j列(共有5个元素)的对应的乘积的和。换句话说,要求元素H[i][j],需要使用一个循环:

s=0;

for(k=0;k<5;k++) {

s+=a[i][k]*g[k][j]; }

又由于H中有4行3列,所以再加上一个循环: for(i=0;i<4;i++) {

for(j=0;j<3;j++) {

s=0;

for(k=0;k<5;k++) {

s+=a[i][k]*g[k][j];

}

h[i][j]=s;

} }

3、二维数组与指针 int a[5][5]={ {1,2,3,4,5}, {6,7,8,9,10},

{11,12,13,14,15}, {16,17,18,19,20} };

int *p; //一级指针p

p=a[3][2]; //正确 int (*q)[5]; //这个二级指针,是一个指向含有5个元素的数组的指针

int *(r[5]); //在定义一个指针数组r,它有5个元素,每个元素都是一个int*指针,与int *r[5]是等价的

int b[10];

*(b+3)=-7; //实质上就是b[3]

q=a; //指针q指向二维数组a,实质上是指向a[0]这一行的地址,那么q++是指下一行的地址

*(*(q+3)+2)=-7; //取得的是a[3][2]的值,其中*(q+3)还是地址,是a[3]的地址,

for(i=0;i<4;i++) {

for(j=0;j<5;j++) {

printf(\ }

putchar('\\n'); }

for(i=0;i<4;i++) {

for(j=0;j<5;j++) {

printf(\ }

putchar('\\n'); q++; }

0.6.3 字符数组与字符串

1、字符数组实质上与普通数组一样,只是类型为char型: int a[10]={1,2};

char b[10]={'C','h','i','n','a' };

显然,字符数组也可以用上面的方法(算法)。

我们经常要问一个字符数组中包含多少个字符?只要在后面加上一个0(注意这是数字0,表现在ASCII表中,就是'\\0',如果一个字符数组后面包含了这个0,那么它就是一个字符串

2、字符串

char b[10]={'C','h','i','n','a'}; //既是字符数组,又是字符串,因为后面至少包含了一个'\\0'

char c[ ]={\ //用双引号表达,则系统会在后面自动加上一个'\\0',也就是字符串。这个数组占用了8个字节空间,里面包含7个有效字符和一个'\\0'

char d[ ]=\3、字符串的应用

1)标准字符串处理函数,全部需要包含头文件\:

求长度(字符数)函数:strlen(str);,求其中字符的个数,如上面的d,得到的应该是7,如果要求数组的长度(也就是问数组占了多少个字节,应该使用sizeof(d),得到8)。

字符串复制:strcpy(str1,str2);将str2复制到str1中

字符串连接:strcat(str1,str2);将str2连接到str1后面,str1变长了 字符串比较:strcmp(str1,str2);按字典顺序比较str1和str2,如果str1>str2,返回1,如果str1

if(strcmp(str1,str2)==0){ ?? } 2)我们自己来构造字符串处理函数: 统一的方式,就是使用一个while循环: char str[]=\int i=0;

while(str[i]!='\\0') {

//处理str[i] i++; }

如果使用指针: char *p; p=str;

while(*p!='\\0') {

//处理*p p++; }

注意,循环结束后,p已经指向了str的最后的\\0处,因此,如果还想继续使用p,一定不要忘记让p重新指向str:p=str;

3、字符串与指针 char *s=\

s是一个指针,其过程为:系统先在内存中找到一个合适的空间,能够容纳\,然后把这个空间的首地址赋给s,这样,实质上s就指向了这一段字符串。使用起来,s与字符数组没什么两样。唯一的不同在于,s是指针,因此可以变化(移动)。所以:

s=b; //正确,但是,原来s的空间再也找不回来了,变成内存垃圾

再如: s+=3;

这个时候,如果打印puts(s);得到的结果是:ove 4、灵活使用内存申请、释放函数:

malloc函数、alloc函数:申请以字节为单位的空间,返回一个void*类型

的指针,可以强制类型转换为我们要的类型。

realloc函数:当前面的空间不够用,可以再重新申请一个更大的的空间。 free函数:释放申请到的空间。 如:

char *t; //t就是一个字符指针,没有空间。 t=(char*)malloc(100*sizeof(char)); strcpy(t,\

t=(char*)realloc(t,1000*sizeof(char)); ??使用中

free(t); //释放t

0.6.4 课后练习

1、打印以下杨辉三角 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 ??

可用一维数组或二维数组来解决

2、编写一程序实现strcat函数的功能,将str1和str2连接起来

3、编写一程序,将字符串str1中的所有的'e'字母删除(有高效和低效两种方法)。

4、编写一程序,实现两个矩阵的乘积。C=A X B,其中A为4×5,B为5×3,其结果C应该为4×3的矩阵。

0.7函数

0.7.1 为什么要用函数

1、程序可能比较大,如果全部写在main函数中,main相当地长,非常不利于阅读。

2、有许多的代码可以重用,如排序。假如我们写了了排序的算法,现在在程序中很多地方都要排序,那么,如果不使用函数,则要使用排序的地方都必须将这段代码拷贝过去,使程序进一步变长,特别地,如果后来这个排序算法有了改进,或者原来有个小错误,那么改动非常地麻烦,需要每个地方都去改。

3、函数还有一个重要的(特殊的)应用——递归。 4、模块化程序设计的思路(自顶向下,逐步求精):将一个大的问题分解成若干个小更小的规模的问题,还可以进一步再细分,这样,可以对一个较复杂的问题进行解决。在这种思想下,程序可以用这样的图来表达:

main a b c d e f g h i j k 5、在C语言程序中,每一个模块都是由若干个函数组成的。且对于C源程序来说,一个程序就是由若干个模块组成。对源文件来说,可以包含若干个函数,通常我们不会将所有的函数都放在一个源文件中(会使得这个源文件太长,不利于阅读),而是将这些函数分别放在不同的文件中。

6、在C中,所有的函数都是平级别的,不存在谁更高——这就是说,所有的函数都可以自由地调用别的函数(只要你需要的话)。

7、函数的定义不能被包在别的函数中。如: int f( ) {

void g() //g函数在函数f中进行定义,错误 {

//?? }

//?? }

注意定义与声明的区别: int f( ) {

void g(); //声明一个函数,正确

//??其中可能会用到函数g,所以提前声明 }

0.7.2 函数的定义方法

一般格式:

类型名 函数名(参数列表) {

//函数体 }

其中参数列表由若干个(可以是0个)“参数类型 参数名”组成,如果没有参数,则可以为空,也可以写上void。如:

int fun( int a, float f, double *p) {

//

return ***; //其中***应该与int匹配 }

注意一个小问题:如果函数的类型不是void,则必须至少要有一个return ***语句,且***的类型应该与函数的类型相匹配。如果没有return ***语句,则函数的类型必须为void。

空函数的问题: void fun( ) {

//函数体,没有return XXX语句 }

关于return语句,还有一个小细节:return XXX语句如果在if语句中且只有一个,则VC会报错,如:

int f( int a, int b ) {

if(a>b)

return a; return b; }

有时,还会写一个函数体为空的函数,这是为了编程的方便,以后可以再将这个函数补写完整。

0.7.3 函数的调用方法

分两种:

1)如果函数没有类型(也就是void型),则该函数的调用只能单独进行。如:

void f1(...) {

//... }

main() {

f1(...); //正确 a=f1(...); //错误,因为这个函数为void型,也就没有返回值 }

2)如果函数有返回值,则该函数的调用可以单独进行,也可以让其参与计算,如:

int f2(...) {

//...

return X; }

main( )

{

f2(...); a=f2(...); }

//正确,可以单独使用 //正确

0.7.4 函数调用时的数据传递

1、两个名词:实际参数、形式参数

形式参数:指函数定义时,括号里的参数,形式参数一定是变量。如: int f1(int a, float f) {

//... }

其中的int a, float f,a与f都是函数在定义时声明的,它们就是形式参数。

实际参数:指函数在被调用时,括号里的参数,它既可以是变量,也可以是常量。如:

y=f1( x, 5.2 );

其中的x与5.2就是实际参数。

形式参数和实际参数的身份是不一样的,形式参数一定是变量,且是局部变量,而实际参数则即可以是变量,也可以是常量或直接数,如:上面的y=f1( x, 5.2 )中,x是变量,而5.2是常量,也是直接数。

2、传递方向:值的单向传递(就是值由“实际参数”传递给“形式参数”,这个传递实际上就是赋值,至于在函数体中对形式参数做了什么修改,对于实际参数没有任何影响),值不会再由实际参数还给形式参数。如:

int f(int a, int b) {

int t;

t=a; a=b; b=t; // 交换a和b的值 }

void main() {

int x=5, y=-7; f(x, y);

printf(\}

本文来源:https://www.bwwdw.com/article/0xi7.html

Top