60 < 61 = 62 > 63 ” /># include int main ( ) { char a= 97 ; char b= ‘b’ ; printf ( “%d,%d\n” , a, b) ; printf ( “%f,%f\n” , a, b) ; printf ( “%c,%c\n” , a, b) ; return 0 ; } 可以看到,当我们用十进制’%d’输出时候,可以将char类型当做普通数字变量使用,当我们使用’%c’输出时候,可以正常输出ascii码所对应的字符;但是当我们试图用浮点型格式符’ %lf’ 输出字符或整型变量时候,却发生错误。这是因为浮点数与整型数存储规则不同导致的。我们写代码时候应该尽量避免变量类型与输出格式符号不匹配。
布尔类型 在c99和c11的c语言标准中都添加了_Bool类型,布尔类型实际就是一个只有0或1的变量,也就是1bit。0表示假,1表示真。当我们将_Bool类型与其他类型混用时,采用的规则是0表示假,非0数表示真。
# include int main ( ) { _Bool a= 97 ; printf ( "%d\n" , a) ; return 0 ; }
可以看到当我们试图将97赋值给_Bool类型时候,系统判定97是真,于是赋值真(1)给_Bool类型变量a,当以十进制数输出a的时候,a的值为1。
自定义变量类型 自定义变量类型有两大类,一种是结构体struct,另一种是共用体union。这里我们简单介绍一下结构体,后续会有更详细的介绍。
结构体其中一种使用格式为
typedef struct { 数据类型 数据1 ; 数据类型 数据2 ; . . . } 结构体名称;
并且结构体类型定义不同于结构体变量定义,一般会将结构体类型定义放在程序最开始,而将结构体变量定义放在main函数内部。定义了结构体类型之后,我们可以定义结构体变量,使用方法类似于int类型。
我们无法直接对结构体变量进行赋值操作,想要访问结构体内的数据时,我们采用’.’运算符。结构体类型定义有几种不同方式,后续我们会详细讨论。
使用示例
# include typedef struct { int a; double b; } INT_DOUBLE; int main ( ) { INT_DOUBLE x; x. a= 10 ; x. b= 3.1415 ; printf ( "结构体使用示例\n" ) ; printf ( "x内a=%d,x内b=%lf\n" , x. a, x. b) ; return 0 ; }
因为结构体与共用体对于现阶段而言较为复杂,考虑文章结构问题,将其放在后续章节统一介绍。
指针类型 指针概念简单,但是由于指针赋予编程者过大的权限,导致指针程序如果编写不当会有严重后果,所以本小节只介绍指针类型,在后续有专门指针章节讨论该问题。
在C语言中我们可以简单且抽象的将数据理解成线性存储空间,每个地址对于1字节内存,如下表:
名称 地址 内容 0x 0000 0000 未知 0x 0000 0001 未知 … 0x 0000 000f 未知 0x 0000 0010 未知 0x 0000 0011 未知 … 0x f f f f f f f f 未知
当我们使用int a;定义一个变量后,系统会自动分配一个内存地址给他,在之后任何需要a的地方,cpu都会从该地址读取或写入数据。例如:
# include int main ( ) { int a= 0x12345678 ; printf ( "a的地址为%p" , & a) ; return 0 ; }
这里有两个新知识点,%p是以十六进制显示一个地址。’&’取址符是获取变量地址。上述例程作用是定义一个变量,输出该变量的地址。
0x7ffdb0729dbc则是该变量地址,由于这是菜鸟工具在线编译器,其地址位数依赖于在线服务器的系统。
同样的程序在visual studio 2019(vs2019)运行结果为:
这就是64位机电脑运行结果,0x 0000 0035 525F FCA4转为二进制为: 0B 0000 0000 0000 0000 0000 0000 0011 0101 0101 0010 0101 1111 1111 1100 1010 0100
可以看到变量地址是64位的。
名称 地址 内容 0x 0000 0000 0000 0000 未知 … 0x 0000 0035 525F FCA3 未知 a 0x 0000 0035 525F FCA4 0x78 0x 0000 0035 525F FCA5 0x56 0x 0000 0035 525F FCA6 0x34 0x 0000 0035 525F FCA7 0x12 0x 0000 0035 525F FCA8 未知 … 0x FFFF FFFF FFFF FFFF 未知
可以看到,系统为int a;
变量分配从0x0019ff2c开始的4个字节。并且低八位在前,高八位在后。
而指针就是专门针对变量地址进行操作。指针变量定义:
int * pint= NULL ; char * pchar= NULL ; float a= 0 , * pfloat1; float * pfloat2= & a; pfloat1= & a; pfloat2= pfloat1;
不同类型指针只需在类型后添加*即可,可以一条语句定义多个相同类型指针变量与相同类型普通变量,并且可以分别初始化。指针可以选择不初始化(野指针)、初始化为NULL(空指针)、或者初始为某个相同类型变量的地址,用取址符&对变量取地址。例如
int a= 0 ; int p1; int p2= NULL ; int p3= & a;
指针使用有一下几种操作,假设有int a=5;int *p=&a;
:
名称 运算符 描述 示例 赋值 = 将p指向某一地址 p=NULL将地址清空 地址自增 ++ p所指向的地址向后移h字节,h为该地址变量的类型的大小 p++ 地址向后移4字节 地址自减 – p所指向的地址向前移h字节,h为该地址变量的类型的大小 p– 地址向前移4字节 地址加 + p所指向的地址向后移x*h字节,x为操作数,h为该地址变量类型的大小 p=p+5 地址向后移5*4个字节 地址减 – p所指向的地址向前移x*h字节,x为操作数,h为该地址变量类型的大小 p=p-5 地址向前移5*4个字节 解引用 * 获取p所指向的地址内容。 *p=6; a的值被修改为6
指针本质就是一个int数,数据内容则是某个变量得地址,下面例程与表格将描述指针的原理。
# include int main ( ) { int a= 5 ; int * p= NULL ; printf ( "a的地址0x%p\n" , & a) ; printf ( "p的地址0x%p\n" , & p) ; printf ( "a的内容%d\n" , a) ; printf ( "p的内容0x%p\n" , p) ; printf ( "\n" ) ; p= & a; printf ( "a的地址0x%p\n" , & a) ; printf ( "p的地址0x%p\n" , & p) ; printf ( "a的内容%d\n" , a) ; printf ( "p的内容0x%p\n" , p) ; printf ( "p指向的地址的内容%d\n" , * p) ; printf ( "\n" ) ; * p= 10 ; printf ( "a的地址0x%p\n" , & a) ; printf ( "p的地址0x%p\n" , & p) ; printf ( "a的内容%d\n" , a) ; printf ( "p的内容0x%p\n" , p) ; printf ( "p指向的地址的内容%d\n" , * p) ; return 0 ; }
VS2019运行结果:
执行
int a= 5 ; int * p= NULL ;
后,数据如下表
名称 地址 内容 … a的第1个字节 0x 0000 007C D583 FAC4 0x05 a的第2个字节 0x 0000 007C D583 FAC5 0x00 a的第3个字节 0x 0000 007C D583 FAC6 0x00 a的第4个字节 0x 0000 007C D583 FAC7 0x00 上述字节连起来为0x0000 0005 p的第1个字节 0x 0000 007C D583 FAE8 0x00 p的第2个字节 0x 0000 007C D583 FAE8 0x00 p的第3个字节 0x 0000 007C D583 FAE8 0x00 p的第4个字节 0x 0000 007C D583 FAE8 0x00 p的第5个字节 0x 0000 007C D583 FAE8 0x00 p的第6个字节 0x 0000 007C D583 FAE8 0x00 p的第7个字节 0x 0000 007C D583 FAE8 0x00 p的第8个字节 0x 0000 007C D583 FAE8 0x00 上述字节连起来为0x0000 0000 0000 0000
执行p=&a;
后,数据如下表
名称 地址 内容 … a的第1个字节 0x 0000 007C D583 FAC4 0x05 a的第2个字节 0x 0000 007C D583 FAC5 0x00 a的第3个字节 0x 0000 007C D583 FAC6 0x00 a的第4个字节 0x 0000 007C D583 FAC7 0x00 上述字节连起来为0x0000 0005 p的第1个字节 0x 0000 007C D583 FAE8 0xC4 p的第2个字节 0x 0000 007C D583 FAE8 0xFA p的第3个字节 0x 0000 007C D583 FAE8 0x83 p的第4个字节 0x 0000 007C D583 FAE8 0xD5 p的第5个字节 0x 0000 007C D583 FAE8 0x7C p的第6个字节 0x 0000 007C D583 FAE8 0x00 p的第7个字节 0x 0000 007C D583 FAE8 0x00 p的第8个字节 0x 0000 007C D583 FAE8 0x00 上述字节连起来为0x0000 007C D583 FAC4
执行*p=10;
后,数据如下表
名称 地址 内容 … a的第1个字节 0x 0000 007C D583 FAC4 0x0A a的第2个字节 0x 0000 007C D583 FAC5 0x00 a的第3个字节 0x 0000 007C D583 FAC6 0x00 a的第4个字节 0x 0000 007C D583 FAC7 0x00 上述字节连起来为0x0000 0005 p的第1个字节 0x 0000 007C D583 FAE8 0xC4 p的第2个字节 0x 0000 007C D583 FAE8 0xFA p的第3个字节 0x 0000 007C D583 FAE8 0x83 p的第4个字节 0x 0000 007C D583 FAE8 0xD5 p的第5个字节 0x 0000 007C D583 FAE8 0x7C p的第6个字节 0x 0000 007C D583 FAE8 0x00 p的第7个字节 0x 0000 007C D583 FAE8 0x00 p的第8个字节 0x 0000 007C D583 FAE8 0x00 上述字节连起来为0x0000 007C D583 FAC4
所以说,地址变量p就是一个int数保存了另一个变量的地址。&p是地址变量自己的地址,例程中是0x0000 007C D583 FAE8;p是保存的同类型变量的地址,本例程中是0x 0000 007C D583 FAC4;*p是解引用,即访问该地址的所保存的数据,本例程中是0x 0000 000A;
同理,其他类型变量也是类似,仅仅是变量类型改变。例如:
# include int main ( ) { char a = 'X' ; char * p = NULL ; printf ( "a的地址0x%p\n" , & a) ; printf ( "p的地址0x%p\n" , & p) ; printf ( "a的内容%c\n" , a) ; printf ( "p的内容0x%p\n" , p) ; printf ( "\n" ) ; p = & a; printf ( "a的地址0x%p\n" , & a) ; printf ( "p的地址0x%p\n" , & p) ; printf ( "a的内容%c\n" , a) ; printf ( "p的内容0x%p\n" , p) ; printf ( "p指向的地址的内容%c\n" , * p) ; printf ( "\n" ) ; * p = 'B' ; printf ( "a的地址0x%p\n" , & a) ; printf ( "p的地址0x%p\n" , & p) ; printf ( "a的内容%c\n" , a) ; printf ( "p的内容0x%p\n" , p) ; printf ( "p指向的地址的内容%c\n" , * p) ; return 0 ; }
既然指针也是变量,那么便可以有另一个指针变量指向该变量,称为二维指针。
int a= 5 ; int * p= & a; int * * pp= & p; a= 6 ; p= & a; * p= 7 ; pp= pp+ 1 ; * pp= & a; * * pp= 8 ;
数组与字符串 当读者看到这里是,基本已经了解C语言类型与变量了,那我们就不得不考虑一下,如果我们想要定义10个变量怎么办,100个呢,1000个呢。于是便有了数组的概念。
可以通过定义数组来解决这个问题。严格来说数组并不是一种数据类型,而是带有一定存储空间的地址。当我们想要一次定义100个int类型的数时,可以
int a[ 100 ] ; int a[ 100 ] = { 0 } int a[ 100 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 } ;
如果不对数组进行初始化,则数组内的数据处于无效状态。数组定义时可以选择不初始化或列表初始化。={0}会将该数组中所有元素全部赋值为0; ={1}会将第一个元素赋值为1,其他元素全部赋值为0;={1,2,3,4,5,6,7,8,9,10};则是将数组中前10个元素对应赋值,后续数据默认赋值为0;
单独一个名称a表示数组首地址,不需要再对a使用取址符&。 当我们想要单独使用数组中某一元素时,可以采用下标索引a[0];
例如
# include int main ( ) { int a[ 5 ] = { 0 , 1 , 2 , 3 , 4 } ; printf ( "%d" , a[ 3 ] ) ; return 0 ; }
下标索引需要注意几点
(1)下标从0开始。例如int a[5];
一共有5个元素,分别是a[0],a[1],a[2],a[3],a[4],要注意没有a[5]。 (2)数组只有在初始化时可以采用列表给所有数据赋值,之后只能单个数据访问。例如a={1,2,3}
是错误的,应该写:a[0]=1;a[1]=2;a[2]=3;
(3)下标越界大部分编译器检查不出错误,但是会导致严重的、难以察觉的bug,一定要注意数组下标不能越界。例如 (4)数组长度只能由常量表达式组成,可以定义int a[4+1];但是不能用变量定义数组,例如 int a[i]是错误的。 # include int main ( ) { int a[ 5 ] = { 0 , 1 , 2 , 3 , 4 } ; printf ( "a[0]=%d,不存在的a[100]=%d\n" , a[ 0 ] , a[ 100 ] ) ; return 0 ; }
有一种特殊数组叫字符串。当我们想要char类型数组时,可以
char a[ 10 ] ; char a[ 10 ] = { 'a' , 'b' } ; char a[ 10 ] = { 101 , 102 , 103 , 104 , 105 , 106 , 107 , 108 , 109 , 110 } ; char a[ 10 ] = { 0 } ; char a[ 10 ] = "hello wor" ; char a[ ] = "hello world" ;
这种char类型数组我们又称字符串。 char a[10]表示定义一个大小为10的char类型字符串,字符串(或称数组)内变量都未初始化。 char a[10]={‘a’,‘b’};则是将ab对应的ascii码值赋值个字符串前两个变量,后面8个变量初始化为0; char a[10]={101…110};是将字符串a中每个元素对应赋值。 char a[10]={0};是将字符串a中所有元素初始化为0 char a[10]=“hello wor”;定义了一个字符串并对其赋值,等价于 char a[10]={‘h’,‘e’,‘l’,‘l’,‘o’,’ ‘,‘w’,‘o’,‘r’,’\0’};注意字符串常量自定补’\0’结束符 最后一种数组定义较为特殊,系统会根据后续初始化变量来自动判断该数组内存大小,本程序中char a[]="hello world";
等价于char a[12]="hello world";
。
那如果我们给数组初始化一个大于数组容量的列表会怎样,一起看结果
# include int main ( ) { char a[ 11 ] = "hello world" ; printf ( "%s\n" , a) ; return 0 ; }
上述程序有一个新知识点,%s输出字符串。
当我们给一个仅有11个字符的字符串赋值”hello world”时,由于字符串常量中含有12个字符(结尾自动补充’\0’字符),赋值后程序可以运行(部分编译器报错),但是当屏幕上显示字符串时,却由于最后一个休止符丢失而导致该字符串不能正常休止,在显示完前11个字符后,后续因为没有休止符而显示乱码。
当使用列表赋值时,该编译器直接报错,错误原因是因为列表元素大于字符串容量(数组容量)
单独使用一个字符同数组。例如a[4]=‘o’。 了解到这里相信大家就能理解,字符串其实就是比较特殊的数组。当然,正因为他的特殊性,字符串拥有更多的操作方法。例如针对字符串处理的标准库函数、存储库函数、独特的字符串初始化等。
二维数组与字符串数组 很多时候我们需要存储矩阵形式的数据,例如灰度图、RGB彩色图片数据、数学矩阵等,我们就可以采用二维数组。
二维数组定义与一维数组类似,可以理解成数组的数组。例如
int a[ 2 ] [ 3 ] ; int a[ 2 ] [ 3 ] = { { 1 } } ; int a[ 2 ] [ 3 ] = { { 1 , 2 } , { 3 } } ; int a{ 2 ] [ 3 ] = { { 1 , 2 } , { 3 , 4 , 5 } } ;
也可以对其全部初始化
int a[ 2 ] [ 3 ] = { { 1 , 2 , 3 } , \{ 4 , 5 , 6 } } ;
就定义了一个2行3列的二维数组,矩阵排列为
同样的,可以采用下标索引的方式获取数组中的元素,下标也是从0开始计数,a[0][1],表示第0行第1个元素,即2。
类比字符串,字符串数组就是char类型的二维数组。例如
char s[ 3 ] [ 10 ] ; char s[ 3 ] [ 10 ] = { { 'a' } , { 'a' , 's' , '\0' } } ; char s[ 3 ] [ 10 ] = { "qwer" , "asdf" , "zxcv" } ;
想要单独使用其中一个字符,方法同数组类似,数组名[行][列]。例如s[2][0]=‘z’; 而是s[1]则是字符串”asdf”的首地址,涉及到地址问题统一放在后续指针与数组章节。
默认变量类型 C语言中对于程序中出现的常量都有默认类型。1、2、100、-125等整数类型默认为int型;1.2、-2.5等小数类型默认为double型;‘a’、’b‘、’H’等字符类型默认为char型;“asd”、”keng”等字符串(字符型数组)默认为char类型指针,即char*类型。
标准输入输出 #include 这句话的含义,就是导入了一个名称为stdio.h的文件,这个文件定义了标准(std)输入输出(io),这里的输入输出是相对于电脑来说,例如鼠标、键盘都是输入设备,而屏幕、文件、音响则是输出设备。常用的标准输入输出有scanf、printf、getc、putc、getchar、putchar、gets、puts、fgets、fputs、fget。
printf printf()函数是标准输出函数, 一般用于向标准输出设备按规定格式输出信息。在编写程序时经常会用到此函数。printf()函数的调用格式为:
其中格式化字符串包括三部分内容:一部分是显示字符,,这些字符将按原样输出,例如printf(“你好”);一部分是转义字符或叫做控制字符,例如’\n’是换行、‘\0’是字符串休止符、’\t’是制表符(对齐符号)等,例如printf(“\n”);另一部分是格式化规定字符,以”%”开始,后跟一个或几个规定字符,用来确定输出内容格式,同时后面需加参量表,例如printf(“%d”,a);。参量表是需要输出的一系列参数,其个数必须与格式化字符串所说明的输出参数个数一样多,各参数之间用“,”分开,且顺序需要与格式化字符所对应。 \
ASCII值 控制字符 ASCII值 控制字符 ASCII值 控制字符 ASCII值 控制字符 0 NUT 1 SOH 2 STX 3 ETX 4 EOT 5 ENQ 6 ACK 7 BEL 8 BS 9 HT 10 LF 11 VT 12 FF 13 CR 14 SO 15 SI 16 DLE 17 DC1 18 DC2 19 DC3 20 DC4 21 NAK 22 SYN 23 TB 24 CAN 25 EM 26 SUB 27 ESC 28 FS 29 GS 30 RS 31 US 32 (space) 33 ! 34 “ 35 # 36 $ 37 % 38 & 39 ’ 40 ( 41 ) 42 * 43 + 44 , 45 – 46 . 47 / 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ” /># include int main ( ) { printf ( “%c,%c,%c,%c\n” , ‘a’ , ‘A’ , ‘c’ , ‘F’ ) ; return 0 ; }
转义字符 含义 ASCII码 \o 空字符(NULL) 0 \n 换行符(LF) 10 \r 回车符(CR) 13 \t 水平制表符(HT) 9 \v 垂直制表符(VT) 11 \a 响铃(BEL) 7 \b 退格符(BS) 8 \f 换页符(FF) 12 \’ 单引号 39 \” 双引号 34 \\ 反斜杠 92 \” /># include int main ( ) { printf ( “a\0b\0c\n” ) ; printf ( “e\rf\tg\v\n” ) ; printf ( “h\ai\bj\f\n” ) ; printf ( “\’\”\\\n” ) ; printf ( “\?\101\x41\n” ) ; } 由于转义字符大多数是特殊的控制字符,部分无法直接在屏幕上显示出来,而是具有特殊效果,所以部分转义字符可能无法显示或显示乱码。
符号 作用 %d 十进制有符号整数 %u 十进制无符号整数 %f 浮点数 %s 字符串 %c 单个字符 %p 地址格式 %e 指数形式的浮点数 %x,%X 无符号十六进制表示整数 %0 无符号八进制表示整数 %g 自动选择合适格式显示
注意点:
(1)可以在”%” 和字母之间插进”m”,m代表数字,表示输出格式占用m格。若m为正数则右对齐,若m为负数则左对齐。例如printf("%5d",a);
表示输出场宽为5的十进制数,若a的数据位数不够5位则右对齐,多余5位以实际场宽为准。 (2)可以在”%” 和字母之间插入”.n”,n代表数字n,表示输出数据强制保留n个小数点。例如printf("%.2f",a);
表示保留2位小数。 (3)可以在”%” 和字母之间插进”m.n”,m和n代表数字,表示输出格式占用m格,保留n位小数。若m为正数则右对齐,若m为负数则左对齐。例如: printf("%9.2f",a);
表示输出场宽为9的浮点数,其中小数位为2,整数位为6,小数点占一位,若a的数据位数不够9位则右对齐,多余9位以实际占用场宽为准。 (4)可以在字母前加小写字母l,表示输出的是长型数。例如: printf("%ld,a);
表示输出long整数。printf("%f",a);
表示输出float类型单精度浮点数(4字节)。printf("%lf",a);
表示输出double类型双精度浮点数(8字节)。 (5)可以在”%” 和字母之间插进”0m”,m代表数字,表示输出格式占用m格,a的数据位数不够m格,则空余部分用’0’字符填充。若m为正数则右对齐,若m为负数则左对齐。例如printf("%5d",a);
表示输出场宽为5的十进制,若a的数据位数不够5位则右对齐,多余5位以实际场宽为准。 使用示例
# include int main ( ) { char c; int a= 1234 ; float f= 3.141592653589 ; double x= 0.12345678987654321 ; c= '\x41' ; printf ( "a=%d\n" , a) ; printf ( "a=%6d\n" , a) ; printf ( "a=%06d\n" , a) ; printf ( "a=%2d\n" , a) ; printf ( "x=%lf\n" , x) ; printf ( "x=%18.16lf\n" , x) ; printf ( "c=%c\n" , c) ; printf ( "c=%x\n" , c) ; return 0 ; }
scanf scanf()函数是标准输入函数。它从标准输入设备(简谱)读取输入的信息。其调用格式为:scanf(格式化字符串, 地址表);
格式化字符串包括以下三类不同的字符:
格式化说明符:格式化说明符与printf()函数中的格式说明符%d%f等基本相同。例如scanf("%d",&a);
空白字符:空白字符会使scanf()函数在读操作中略去输入中的一个或多个空白字符。例如scanf("%d %d",&a,&b);
非空白字符: 一个非空白字符会使scanf()函数在读入时剔除掉与这个非空白字符相同的字符。例如scanf("%d,%d",&a,&b);
地址表是需要读入的所有变量的地址,而不是变量本身,变量地址可以在变量名称前添加取址符’&‘取地址。这与printf()函数完全不同,要特别注意。各个变量的地址之间同’,’分开。 例如scanf("%d,%d",&a,&b);
上例中的scanf()函数先读一个整型数, 然后把接着输入的逗号剔除掉, 最后读入另一个整型数。如果“,”这一特定字符没有找到, scanf()函数就终止。若参数之间的分隔符为空格, 则参数之间必须输入一个或多个空格。对于初学者来说,经常会将代码中%d,%d中的逗号写成英文逗号,然后键盘输入时候输入中文逗号,导致程序无法找到特定的英文逗号而出错。
注意:
(1)对于字符串数组或字符串指针变量, 由于数组名和指针变量名本身就是地址, 因此使用scanf()函数时, 不需要在它们前面加上”&”操作符。例如 char c; scanf ( "%c" , & c) char s[ 10 ] ; scanf ( "%s" , s) ;
(2)可以在格式化字符串中的“%”各格式化规定符之间加入一个整数, 表示任何读操作中的最大位数,可以类比printf中的场宽限制。例如 # include "stdio.h" int main ( ) { int a, b; scanf ( "%3d %d" , & a, & b) ; printf ( "a=%d b=%d" , a, b) ; return 0 ; }
输入
123456789
请编程:简单加法计算器 参考例程
# include "stdio.h" int main ( ) { double a, b; printf ( "请输入加法算式,例如 \"1+2=\"\n" ) ; scanf ( "%lf+%lf=" , & a, & b) ; printf ( "%lf+%lf=%lf\n" , a, b, a+ b) ; return 0 ; }
输入
3.4 + 2.5 =
注意输入两个数之间的分隔符必须与scanf中的格式保持一直,本程序中采用’+’分隔。
putchar()与getchar() putchar()使用格式为
char c= 'A' ; putchar ( c) ;
他的作用就是向标准输出设备输出一个字符,以回车键结束,其等价于printf("%c",c);
getchar()则是与putchar()相对应,putchar()为输出一个字符,getchar()为获得一个字符。getchar
char c= 'A' ; getchar ( c) ;
使用例程
# include int main ( ) { char c= 'A' ; putchar ( c) ; putchar ( '\n' ) ; c= getchar ( ) ; putchar ( c) ; putchar ( '\n' ) ; return 0 ; }
键盘输入
B
运行结果可以看到,屏幕显示内容为 ‘A’、换行、‘B’、换行。
puts()与gets() puts(s)与gets(s)可以类比标准输入输出:
char s[ 10 ] = "hello" ; puts ( s) ; gets ( s) ;
注意`:
puts(s);
输出完字符串后会自动换行。gets(s);
从缓冲区读取字符串直到遇到回车停止。但是scanf("%s",s);
是从缓冲区读取字符串直到遇到回车或空格停止。getch() getch()与getchar()用法一样。区别在于getchar()读取字符时候,我们输入字符时,屏幕上会后相应的显示;而getch()读取字符则没有回显。当我们不需要屏幕回显时,比如贪吃蛇用awsd控制上下左右,每当我们按下一个键屏幕就显示相应字符显然不是我们想要的效果,这种时候就可以使用不带回显的输入。
getche() 同getchar(),只是该函数不论按下什么按键都直接获取相应字符,不以回车键结束。
C语言基本概念 从编译到运行 C语言程序从我们敲代码到运行有四个步骤:
1、预处理(Preprocessing) 2、编译(Compilation) 3、汇编(Assemble) 4、链接(Linking) 预处理 用于将所有的#include头文件以及宏定义替换成其真正的内容,预处理之后得到的仍然是文本文件,但文件体积会大很多。
编译 这里的编译不是指程序从源文件到二进制程序的全部过程,而是指将经过预处理之后的程序转换成特定汇编代码(assembly code)的过程。
汇编 汇编过程将上一步的汇编代码转换成机器码(machine code),这一步产生的文件叫做目标文件,是二进制格式。
链接 链接过程将多个目标文以及所需的库文件(.so等)链接成最终的可执行文件(executable file)。
变量的本质 1、c中所有类型的数据存储在内存中,都是按照二进制格式存储的。所以内存中只知道有0和1,不知道是int的、还是float的还是其他类型。 2、int、char、short等属于整形,他们的存储方式(数转换成二进制往内存中放的方式)是相同的,只是内存格子大小不同(所以这几种整形就彼此叫二进制兼容格式);而float和double的存储方式彼此不同,和整形更不同。 3、C语言中的数据类型的本质,就是决定了这个数在内存中怎么存储的问题,也就是决定了这个数如何转成二进制的问题。一定要记住的一点是内存只是存储1010的序列,而不管这些1010怎么解析。所以要求我们平时数据类型存取一致。 作用域与生命周期 作用域(scope):限定一个变量的可用范围,以{}为界限,变量必须定义在作用域开始。
生命周期:变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段。
局部变量 定义在{}内部的变量称局部变量。局部变量的生命周期:进入作用域生命周期的开始,出作用域生命周期结束。例如以下程序,a的作用域在整个main内有效。
# include int main ( ) { int a= 0 ; printf ( "a=%d\n" , a) ; return 0 ; }
而下例程中,a的作用域仅仅在{}内有效。
# include int main ( ) { { int a= 0 ; } printf ( "a=%d\n" , a) ; return 0 ; }
全局变量 定义在所有函数之外称为全局变量。全局变量的生命周期:整个程序的生命周期。例如
# include int a= 1 ; int b; int main ( ) { printf ( "a=%d,b=%d\n" , a, b) ; }
全局变量与局域变量区别:
1、全局变量如果不对其初始化,则系统会自动执行默认初始化。int、short等整型变量默认0,char默认为0,指针默认为NULL。局部变量如果不对其初始化,那么它的值将处于未定义状态,或者默认初始化(编译器不同则不一样)。 # include int a; int main ( ) { int b; printf ( "a=%d,b=%d\n" , a, b) ; }
2、全局变量自从定义之后,其有效作用域为整个程序,并且其生命周期为整个程序运行期间。而局部变量定义之后,其作用域只在某一区域起作用,并且在程序运行出该作用域后销毁。 3、全局变量和局部变量名称可以相同,但实际上同名变量仅仅是名称相同的两个不同变量,他们有不同存储空间。当一个作用域中出现同名变量时,子作用域中的局部变量会屏蔽掉外层作用域中定义的变量。例如 # include int a; void fun ( ) { printf ( "非主函数作用域a=%d\n" , a) ; } int main ( ) { int a= 1 ; { int a= 2 ; printf ( "子作用域a=%d\n" , a) ; } printf ( "主函数内a=%d\n" , a) ; fun ( ) ; return 0 ; }
这里出现一个新的概念:自定义函数fun。函数具体内容后续章节介绍,在本程序中作用是输出全局变量a=0。
修饰符 修饰符指的是我们定义变量或函数时,在定义的语句添加的用于修饰变量或函数的关键字。常用修饰符有const、static、register、volatile、extern、auto。
const 表示该数据为常量。const修饰的是在它前面的类型,如果它前面没有类型,那它修饰的是紧跟着它的那个类型。使用const定义变量时,由于变量被const限制成不可改变的常量,所以该数据必须初始化,并且定义之后,任何试图修改该数据的操作都是非法的。例如
const int a= 0 ; int const b= 1 ; const int c= 1 , d= 2 ; const int f; a= 10 ; b= 1 ;
有点编译器会自动对未初始化变量默认初始化,可能const int f;
这种未定义的常量也可以通过编译,但是并没有什么意义,因为常量后期不能修改,而该常量目前的值是毫无意义的,那我们用它干什么呢。
并且通常对于不可改变的常量,我们用大写字母去命名数据,并不是说不可以使用其它名称,但如果拥有一个好的命名习惯,我们就可以通过变量名称看出他的类型及作用。 例程
# include int main ( ) { const int W_SCREEN= 128 ; const int H_SCREEN= 64 ; printf ( "屏幕宽度为%d\n" , W_SCREEN) ; printf ( "屏幕高度为%d\n" , H_SCREEN) ; printf ( "总像素点数%d\n" , W_SCREEN* H_SCREEN) ; return 0 ; }
对于指针而言,const略有不同,区别在于顶层const与底层const
顶层const——修饰指针变量本身,指指针变量本身不可修改 例如:int *const p1=&a;
const与变量名称p1紧贴,修饰变量本身,这里表示p1指针从定义之后就不能修改了,从此p1就只能指向a。但是可以通过p1修改a的值,例如*p=5;
底层const——修饰目标地址,指目标地址的内容不可修改 例如:int const *p1; //写法1 const int *p2; //写法2
,这两种写法完全等价,其效果是无法通过该指针*p1去改变目标地址的内容(即a的内容)。但是可以修改p1的地址,例如p1=&b;
。 双层const——无法修改指针变量本身,且无法通过指针修改目标地址内容。 例如int const *const p1; //写法1 const int *const p2; //写法2
之前定义所有非指针变量都相当于顶层const。 const变量与指针的赋值问题——权限只能缩小,不能放大 假如有
int i= 5 ; const int ci= 10 ;
int * p1= & i; int * p2= & ci; p1= & i; p2= & ci; * p1= 20 ; * p2= 20 ;
int * const pc1= & i; int * const pc2= & ci; pc1= & i; pc2= & ci; * pc1= 20 ; * pc2= 20 ;
const int cp1= & i; const int cp2= & ci; cp1= & i; cp2= & ci; * pc1= 20 ; * pc2= 20 ;
const int * const cpc1= & i; const int * const cpc2= & ci; cpc1= & i; cpc2= & i; * cpc1= 20 ; * cpc2= 20 ;
static static修饰符有3点作用:
1、修饰函数:被修饰的函数只在本文件中调用。 关于函数概念与文件概念后续介绍。 2、修饰全局变量:被修饰的变量只在本文件中有效。 文件概念后续介绍。 3、修饰局部变量:保持局部变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和 static 变量,只不过和全局变量比起来,static 可以控制变量的可见范围。例如: # include void fun ( ) { int a= 0 ; a= a+ 1 ; printf ( "a=%d\n" , a) ; } int main ( ) { fun ( ) ; fun ( ) ; fun ( ) ; }
未使用静态变量时候,每次执行fun()都会重新创建一个变量a=0;然后a=a+1;然后输出a=1;离开fun()时候a会被销毁。 而使用static修饰符后。
# include void fun ( ) { static int a= 0 ; a= a+ 1 ; printf ( "a=%d\n" , a) ; } int main ( ) { fun ( ) ; fun ( ) ; fun ( ) ; }
添加static修饰符后,当初次执行fun()会创建一个变量a=0;并执行a=a+1;然后输出a=1;离开fun()后变量a不会被销毁,并且下次执行fun()时不会重新创建变量a,而是继续使用之前的变量a;直接执行a=a+1;然后输出a=2;变量a一直持续到程序结束才会被销毁。
register register表示寄存器变量,极少使用。简单讲解一下,可以略过不看。
电脑运行时,数据一般存储在存储区。存储区数据量极大,但是效率略低。当我们计算c=a+b时,cpu处理器先将a读到寄存器A中(register),再将读取b与寄存器相加,结果保存到之前的寄存器A中,然后再将a+b的结果放入存储器c中。这样虽然速度不慢,但是如果一开始a和b保存在寄存器中,就会更快。C语言中register修饰符就是将变量存储空间放在寄存器中,但是需要注意的是,cpu中寄存器十分稀有,所以除非是使用频率非常高的数据才会放在寄存器中。 volatile volatile表示易变变量,较少使用。简单讲解一下,可以略过不看。 register介绍中描述了数据需要从存储器读取到寄存器。我们的电脑十分智能,当我们第一次使用a变量时,cpu会将它读取到寄存器中,假设是寄存器A。当我们第二次使用变量a是,cpu判断之前程序并没有对a的操作,所以cpu认为寄存器A中的值就是a,于是不会再次从存储器中读取a,而是直接使用寄存器中的A。但是电脑中可能有其他程序篡改了存储器中的a,如果我们想要cpu每次使用变量a时都重新从存储器中读取,那就可以使用volatile修饰变量。
extern 全局变量声明符号。在分文件编译中有详细介绍,通过该修饰符可以在不同文件中使用同一变量。
auto 1.c语言中,关键字auto用于声明一个变量为自动变量 自动变量也称局部变量。将不在任何类、结构、枚举、联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量。所有局部变量默认都是auto,一般省略不写。 auto声明全局变量时,编译出错:
# include auto int i; int main ( void ) { }
auto声明局部变量时,编译正常:
int main ( void ) { auto int i = 1 ; return 0 ; }
2.c语言中,只使用auto修饰变量,变量的类型默认为整型。
int main ( void ) { double a = 1.2 , b = 2.7 ; auto c = a + b; return 0 ; }
类型转换 数据类型转换就是将数据(变量、数值、表达式的结果等)从一种类型转换为另一种类型。
C语言是强类型语言,如果一个运算符两边的运算数据类型不同,先要将其转换为相同的类型,强制类型转换可以消除程序中的警告,即确保写代码的程序员自己清楚类型转换,允许丢失一定精度做类型匹配。
强制类型转换 强制类型转换是显示的、按照我们意愿把变量从一种类型转换为另一种数据类型,通常用于 。表达式为:(新变量类型)原变量名称
例如:
# include int main ( void ) { double a= 9.2 , b= 9.2 ; printf ( "a=%lf,b=%d\n" , a, ( int ) b) ; return 0 ; }
再看一例:
# include int main ( void ) { int a= 5 , b= 2 ; double c, d; c= a/ b; d= ( double ) a/ b; printf ( "5/2结果为%lf\n" , c) ; printf ( "5/2结果为%lf\n" , d) ; return 0 ; }
本例中,c=a/b;由于a和b都是int类型,a/b结果也是int类型,即2,然后c=2; d=(double)a/b;由于(double)a是double类型,b是int类型,两个不同类型数进行计算,系统会先将b也转为double,再进行运算,所以最后结果为2.5; 较为特殊的是,我们可以将空指针(void*)转为其他任意指针类型,例如:
void * pv= NULL ; int * pi= ( int * ) pv; char * pc= ( char * ) pc;
另外有一种特殊且危险的使用方法是将底层const指针强制修改为可变指针并赋值给另一个指针。例如:
int a= 5 ; int const * p1= & a; int * p2= ( int * ) p1
还有一种更为危险且特殊的用法:我们可以通过强制类型转换将一个int数转为指针类型,但是极有可能导致错误,所以不推荐使用。例如:
int a= 0xffffffff ; int * p= ( int * ) a;
隐式类型转换 隐式类型转换又称自动类型转换,这种操作不需要程序员处理,系统会自动将运算符两边不同的数据类型进行匹配。 \
1、 将一种类型的数据赋值给另外一种类型的变量时就会发生自动类型转换,例如:float f = 320;
这里320 是 int 类型的数据,需要先转换为 float 类型才能赋值给变量 f。再如:int n = f;
这里f 是 float 类型的数据,需要先转换为 int 类型才能赋值给变量 n。
在赋值运算中,赋值号两边的数据类型不同时,需要把右边表达式的类型转换为左边变量的类型,这可能会导致数据失真,或者精度降低,例如int a=3.4;
这里的3.4会从double类型自动转换为int类型,丢失小数。所以说,自动类型转换并不一定是安全的。对于不安全的类型转换,编译器一般会给出警告。
2、在不同类型的混合运算中,编译器也会自动地转换数据类型,将参与运算的所有数据先转换为同一种类型,然后再进行计算。转换的规则如下: (1)转换按数据长度增加的方向进行,以保证数值不失真,或者精度不降低。例如,int 和 long 参与运算时,先把 int 类型的数据转成 long 类型后再进行运算。
(2)所有的浮点运算都是以双精度进行的,即使运算中只有 float 类型,也要先转换为 double 类型,才能进行运算。
(3)char 和 short 参与运算时,必须先转换成 int 类型。
通过下面例子更生动的表述一下:
# include int main ( ) { float PI = 3.14159 ; int s1, r = 1 ; double s2; s1 = r * r * PI; s2 = r * r * PI; printf ( "s1=%d, s2=%f\n" , s1, s2) ; return 0 ; }
上例中s1 = r * r * PI;
int=intint float计算步骤:
先计算后赋值 计算int×int×float时,先计算int×int结果为int;之后先将int和float转为double,再计算double*double,得到结果为double。 最后int=double,先将double转为int再赋值。 上例中s2 = r * r * PI;
double=intint float计算步骤:
先计算后赋值 计算int×int×float过程同上,结果为double。 最后double=double,直接赋值。 在计算表达式r×r×PI时,r 和 PI 都被转换成 double 类型,表达式的结果也是 double 类型。但由于 s1 为整型,所以赋值运算的结果仍为整型,舍去了小数部分,导致数据失真。
强制类型转换VS隐式类型转换 无论是隐式类型转换还是强制类型转换,都只是为了本次运算而进行的临时性转换,转换的结果也会保存到临时的内存空间,不会改变数据本来的类型或者值。
在C语言中,有些类型既可以隐式转换,也可以强制转换,例如 int 到 double,float 到 int 等;而有些类型只能强制转换,不能隐式转换,例如以后将要学到的 void * 到 int *,int 到 char * 等。
可以隐式转换的类型一定能够强制转换,但是,需要强制转换的类型不一定能够隐式转换。现在我们学到的数据类型,既可以隐式转换,又可以强制转换,以后我们还会学到一些只能强制转换而不能隐式转换的类型。
可以隐式进行的类型转换一般风险较低,不会对程序带来严重的后果,例如,int 到 double 没有什么缺点,float 到 int 顶多是数值精度丢失。只能强制进行的类型转换一般风险较高,或者行为匪夷所思,例如,char * 到 int * 就是很奇怪的一种转换,这会导致取得的值也很奇怪,再如,int 到 char * 就是风险极高的一种转换,一般会导致程序崩溃。
使用强制类型转换时,程序员自己要意识到潜在的风险。
第三部分 本章节为C语言核心部分,可以说掌握本章节对C语言学习至关重要。从本节开始,程序会渐渐变得复杂,这时在线编译器已经不能满足我们的需求了,所以我们后续程序都将使用Visual studio编译运行,Visual studio版本不同区别不大。
Visual Studio 2019软件介绍 下载地址 随着我们学习的深入,在线编译器已经无法满足需求了,我们需要自己再电脑上配置一个C语言程序开发环境。在C语言编译环境中曾给过大家VS、VC、在线编译器的下载地址,这里在展示一下。
vc++6.0,这个软件网络上有很多资源,这里给大家提供一个。 链接:https://pan.baidu.com/s/1PApsWKEDMzvqM9NmSuw6hA 提取码:zukk
或者也可以用菜鸟工具中的在线编译器 https://c.runoob.com/compile/11/
VS下载地址官网 https://visualstudio.
配置与应用安装 第一步:
** 首先先找自己电脑上的浏览器,在搜索框中输入“Visual studio”,找到应用程序官方下载地址。 第二步: 根据自己的电脑选择版本,我自己电脑是Windows 所以以下内容将是Windows的安装教程。
第三步: 安装与配置 勾选C++桌面开发。选择合适的安装位置。
第四步: 确认安装,并等安装进度条走完。
此时桌面应该有一个VS图标,打开即可使用。
一个完整工程 1、创建工程项目: 输入该工程项目名称,支持中文;选择安装地址,创建工程。
2、添加工程文件 点击源文件-添加-新建项或按快捷键shift+ctrl+N 选择C++文件,输入文件任意文件名称(可以是中文、不能过长),文件后缀为 “.c” 表示c语言程序,如果为 “.cpp” 表示c++语言程序。点击添加 此时工程目录中也已经有程序文件main.c了 3、编写 其中快捷键有
全选——ctrl+A 剪切——ctrl+X 复制——ctrl+C 粘贴——ctrl+V 撤销——ctrl+Z 取消撤销——ctrl+Y # include int main ( ) { int a = 5 ; printf ( "原a=%d\n" , a) ; printf ( "请输入新a的值:" ) ; scanf ( "%d" , & a) ; printf ( "新a的值为%d\n" , a) ; return 0 ; }
4、编译、链接与运行 开发环境可以直接集成编译、链接、运行。点击调试-开始运行(不调试)或按ctrl+F5 注意下面提示错误。 这句话翻译过来的大概意思是该函数在新标准里面已经不再使用。我们不用管它,有两种方法解决
文件开头添加#pragma warning(disable:4996)
,4996警告全是因为函数版本低,可以直接使用这句话屏蔽。 也可以将scanf写成scanf_s,两者几乎没有区别,使用方法相同,但是scanf_s是新c语言标准,使用更加安全,降低了内存泄漏的风险。 添加忽略警告之后,重新编译,程序成功运行。 在我们编写的程序中输入10后效果图 运行成功,并且程序与预期相同。
调试介绍 当程序语句过多,且程序出现非语法错误时候,我们会采用调试功能。 调试指的就是编译全部代码,但是并不运行全部代码,而是按照我们的意愿执行到指定行,常用调试方式有:
当我们运行到自定义函数时,可以使用F11或点击快捷菜单中图标,该快捷键作用是进入函数内部。 当我们跳转到自定义函数内部是,可以使用shift+F11或点击快捷菜单栏中图标,快速运行完该自定义函数并跳出该自定义函数。
单步调试和断点调试可以配合使用,能够极大提升开发者开发效率。
运算符介绍 运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C 语言内置了丰富的运算符,其种类有:算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符、其他运算符。
本章将逐一介绍算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符和其他运算符。 \
算术运算符 下表显示了 C 语言支持的所有算术运算符。假设int变量 A 的值为 10,变量 B 的值为 20,则:
名称 运算符 描述 示例 加 + 把两个操作数相加 A + B 将得到 30 减 – 从第一个操作数中减去第二个操作数 A – B 将得到 -10 乘 * 把两个操作数相乘 A * B 将得到 200 除 / 分子除以分母 B / A 将得到 2 取模 % 取模运算符,整除后的余数 B % A 将得到 0 自加 ++ 自增运算符,整数值增加 1 A++ 将得到 11 自减 – 自减运算符,整数值减少 1 A– 将得到 9
请看下面的实例,了解 C 语言中所有可用的算术运算符:
# include int main ( ) { int a = 5 ; int b = 2 ; int c ; c = a + b; printf ( "a+b的值是 %d\n" , c ) ; c = a - b; printf ( "a-b的值是 %d\n" , c ) ; c = a * b; printf ( "a*b的值是 %d\n" , c ) ; c = a / b; printf ( "a/b的值是 %d\n" , c ) ; c = a % b; printf ( "a%b的值是 %d\n" , c ) ; c = a++ ; printf ( "a++的值是 %d\n" , c ) ; c = a-- ; printf ( "a--的值是 %d\n" , c ) ; return 0 ; }
运行完成后结果为:
a+ b的值是 7 a- b的值是 3 a* b的值是 10 a/ b的值是 2 ab的值是 1 a++ 的值是 5 a-- 的值是 6
值得注意的是,++与–运算有左结合和右结合两种类型。区别如下:
# include int main ( ) { int a= 10 ; int b= 10 ; printf ( "a++运算时值为:%d\n" , a++ ) ; printf ( "a++运算后值为:%d\n\n" , a) ; printf ( "++b运算时值为:%d\n" , ++ b) ; printf ( "++b运算后值为:%d\n" , b) ; return 0 ; }
运行结果为:
a++ 运算时值为:10 a++ 运算后值为:11 ++ b运算时值为:11 ++ b运算后值为:11
左结合a++和右结合++a运算后结果一样,唯一区别时在a++和++a所处的语句中,表达式的值a++是原来的值,而++a是加1后的值。a–与–a同理。
其原理也很简单。假如a=10:
a++运算时,先计算a++的值11并保存到另一个地方,此时a仍然是10,等运行完成a++所在的语句后再用11覆盖a的内存。 而++a是直接计算值11,然后覆盖a的内存,再计算后续语句。 这样看来++a的占用空间更少,运算更快。 下面我们看一个有意思的案例:
# include int main ( ) { int a, b, c, d; a = 5 ; b = a++ * a++ * a++ ; printf ( "a=%d,b=%d\n" , a, b) ; a = 5 ; c = ++ a * ++ a * ++ a; printf ( "a=%d,c=%d\n" , a, c) ; a = 5 ; d = ++ a * a++ * ++ a; printf ( "a=%d,d=%d\n" , a, d) ; return 0 ; }
运行结果为:
a= 8 , b= 125 a= 8 , c= 512 a= 8 , d= 343
对于第一个结果,a++*a++*a++时,所有更新的a的数据都在后台保存,但是本条语句中,a的值一直为5。
对于第二个结果,++a*++a*++a时,所有数据都在本条语句生效,即先统计完成所有++a后,a的值为8,然后再8*8*8=512。
对于第三个结果,++a*a++*++a时,两条语句即时生效,a=7,执行完成7*7*7后,程序会再处理a++的值,a的值为8,最后结果7*7*7=343。
关系运算符 下表显示了 C 语言支持的所有关系运算符。假设变量 A 的值为 10,变量 B 的值为 20,则:
名称 运算符 描述 实例 双等于 == 检查两个操作数的值是否相等,如果相等则条件为真。 (A == B) 为假。 不相等 != 检查两个操作数的值是否相等,如果不相等则条件为真。 (A != B) 为真。 大于 > 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 (A > B) 为假。 小于 < 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 (A < B) 为真。 大于等于 >= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 (A >= B) 为假。 小于等于 <= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 (A <= B) 为真。
请看下面的实例,了解 C 语言中所有可用的关系运算符,其中输出0表示假、1表示真:
# include int main ( ) { int a= 5 , b= 2 ; printf ( "a==b的判断结果为%d\n" , a== b) ; printf ( "a!=b的判断结果为%d\n" , a!= b) ; printf ( "a>b的判断结果为%d\n" , a> b) ; printf ( "a<b的判断结果为%d\n" , a< b) ; printf ( "a>=b的判断结果为%d\n" , a>= b) ; printf ( "a<=b的判断结果为%d\n" , a<= b) ; return 0 ; }
运行结果为:
a== b的判断结果为0 a!= b的判断结果为1 a> b的判断结果为1 a< b的判断结果为0 a>= b的判断结果为1 a<= b的判断结果为0
逻辑运算符 下表显示了 C 语言支持的所有关系逻辑运算符。假设变量 A 的值为 1,变量 B 的值为 0,则:
名称 运算符 描述 实例 逻辑与 && 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 (A && B) 为假。 逻辑或 ▏▏ 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 (A ▏▏ B) 为真。 逻辑非 ! 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 !(A && B) 为真。
请看下面的实例,了解 C 语言中所有可用的逻辑运算符,其中输出0表示假,输出1表示真:
# include int main ( ) { int a= 5 , b= 2 ; printf ( "a&&b的判断结果为%d\n" , a&& b) ; printf ( "a||b的判断结果为%d\n" , a|| b) ; printf ( "!(a&&b)的判断结果为%d\n" , ! ( a&& b) ) ; return 0 ; }
运行结果为:
a&& b的判断结果为1 a|| b的判断结果为1 ! ( a&& b) 的判断结果为0
并且带有逻辑运算符的语句中,程序并不一定被执行,请看下面例子:
# include int main ( ) { int a = 0 ; int b = 0 ; printf ( "a++&&b++结果为%d\n" , a++ && b++ ) ; printf ( "此时a为%d,b为%d\n" , a, b) ; return 0 ; }
运行结果为:
a++ && b++ 结果为0 此时a为1 ,b为0
可能有同学发现,程序只执行了a++,并没有执行b++。这是因为系统会先判断&&运算符左边是否有效(是否为非0数),如果左侧有效,系统才会再去判断右边是否有效。在本程序中,系统先判断左侧a++的值在本条语句中为0,所以就不用执行并判断后续语句,直接就可以认定(a++&&b++)结果为假。
同理,对于||运算符而言,如果 (左表达式)||(右表达式) 语句中左表达式值为1,则程序不会执行右表达式,而是直接认定整个或逻辑运算结果为真。例如:
# include int main ( ) { int a = 1 ; int b = 1 ; printf ( "a++||b++结果为%d\n" , a++ || b++ ) ; printf ( "此时a为%d,b为%d\n" , a, b) ; return 0 ; }
运行结果为:
a++ || b++ 结果为1 此时a为2 ,b为1
逻辑运算符与其他运算另一个不同点是,逻辑运算符(&&、||)左右两侧算两条语句。什么意思呢,就比如说++运算符,若a=5;则a++在本条语句中,a的值仍为5,但是在a++&&a++中,a在运算符左边时,其值为5,在运算符右边时,a的值为6,运行完本条语句后,a的值为7。例如:
# include int main ( ) { int a = 1 ; printf ( "a--||a的值为%d\n" , a-- && a) ; printf ( "之后a的值为%d\n" , a) ; return 0 ; }
若是其他运算符,则由于a–&&a是同一条语句,语句中两个a的值都是1,最终1&&1=1。 但是不然,在逻辑运算中,a–先被执行完成,在左侧时,a–语句值为1,并且判断完成左侧后,a的值就被修改为0了,再到右侧时,a的值已经为0,所以最终a–&&a结果为0。
运行结果为:
a-- || a的值为0 之后a的值为0
位运算符 下表显示了 C 语言支持的所有关系运算符。为运算符直接将两个二进制数对应位相计算,假设变量 A 的值为 0B0101(5),变量 B 的值为 0B0011(3),则:
名称 运算符 描述 实例 按为与 & 对应位上的两个数都为1,则结果的对应为为1,否则为0。 ( A & B)的结果为 0B0001 按位或 ▏ 对应位上的两个数都为0,则结果的对应位为0,否则为1。 ( A ▏ B)的结果为0B0111 按位异或 ^ 对应位上的两个数不相同,则结果为1,否则为0. (A ^ B)的结果为0B0110 按位取反 ~ 对原操作数所有位取反 (~ A)的结果为0B1010) 左移 << 对原操作数左移x位,x为<<右边的数,右边进0 (A << 1)的结果为0B1010 右移 >> 对原操作数右移x为,x为>>右边的数,左边进0 (A >> 1)的结果为0B0010
假设如果 A = 60,且 B = 13,现在以二进制格式表示,它们如下所示: A = 0011 1100 B = 0000 1101
相关位运算计算:
按位与A&B = 0B 0011 1100 & 0B 0000 1101 = 0B 0000 1100
按位或A|B = 0B 0011 1100 | 0B 0000 1101 = 0B 0011 1101
按位异或A^B = 0B 0011 1100 ^ 0B 0000 1101 = 0B 0011 0001
按位取反~A = ~0B 0011 1100 =0B 1100 0011
左移A<<1 = 0B 0011 1100 《1 = 0B 0111 1000
右移A>>1 = 0B 0011 1100 》1 = 0B 0001 1110
请看下面的实例,了解 C 语言中所有可用的位运算符:
# include int main ( ) { unsigned int a = 60 ; unsigned int b = 13 ; int c = 0 ; c = a & b; printf ( "a&b 的值是 %d\n" , c ) ; c = a | b; printf ( "a|b 的值是 %d\n" , c ) ; c = a ^ b; printf ( "a^b 的值是 %d\n" , c ) ; c = ~ a; printf ( "~a的值是 %d\n" , c ) ; c = a << 1 ; printf ( "a<<1 的值是 %d\n" , c ) ; c = a >> 1 ; printf ( "a>>1 的值是 %d\n" , c ) ; return 0 ; }
运行结果为:
a& b 的值是 12 a| b 的值是 61 a^ b 的值是 49 ~ a的值是 - 61 a<< 1 的值是 120 a>> 1 的值是 30
赋值运算符 下表列出了 C 语言支持的赋值运算符:
名称 运算符 描述 实例 等于 = 简单的赋值运算符,把右边操作数的值赋给左边操作数(左边为可修改的左值) 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
请看下面的实例,了解 C 语言中所有可用的赋值运算符:
# include int main ( ) { int a = 21 ; int c ; c = a; printf ( "=运算符实例,c 的值 = %d\n" , c ) ; c += a; printf ( "+= 运算符实例,c 的值 = %d\n" , c ) ; c -= a; printf ( "-= 运算符实例,c 的值 = %d\n" , c ) ; c *= a; printf ( "*= 运算符实例,c 的值 = %d\n" , c ) ; c /= a; printf ( "/= 运算符实例,c 的值 = %d\n" , c ) ; c= 200 ; c %= a; printf ( "%%= 运算符实例,c 的值 = %d\n" , c ) ; c <<= 2 ; printf ( "<<= 运算符实例,c 的值 = %d\n" , c ) ; c >>= 2 ; printf ( ">>= 运算符实例,c 的值 = %d\n" , c ) ; c &= 2 ; printf ( "&= 运算符实例,c 的值 = %d\n" , c ) ; c ^= 2 ; printf ( "^= 运算符实例,c 的值 = %d\n" , c ) ; c |= 2 ; printf ( "|= 运算符实例,c 的值 = %d\n" , c ) ; return 0 ; }
运行结果为:
= 运算符实例,c 的值 = 21 += 运算符实例,c 的值 = 42 -= 运算符实例,c 的值 = 21 *= 运算符实例,c 的值 = 441 /= 运算符实例,c 的值 = 21 %= 运算符实例,c 的值 = 11 <<= 运算符实例,c 的值 = 44 >>= 运算符实例,c 的值 = 11 &= 运算符实例,c 的值 = 2 ^= 运算符实例,c 的值 = 0 |= 运算符实例,c 的值 = 2
其他运算符 下表列出了 C 语言支持的其他一些重要的运算符,假设int a=4:
名称 运算符 描述 实例 逗号 , 逗号运算符,用于将语句分割,总是返回最后变量的值 a=(5,6,7);值为7 内存计算 sizeof() 返回变量的大小。 sizeof(a) 将返回 4 取址符 & 返回变量的地址。 &a; 将给出变量的实际地址。 解引用符 * 通过指针调用变量。 *p; 将指向一个变量。 条件运算符 ” /># include struct TYPE { int a; } mydata; int main ( ) { char c= ‘D’ ; char * pchar= & c; int i= 10 ; int * pint= & i; int b; struct TYPE * p= & mydata; mydata. a= 0 ; p-> a= 2 ; printf ( “(0,1,2)输出值为%d\n” , ( 0 , 1 , 2 ) ) ; printf ( “char变量c大小为%d\n” , sizeof ( c) ) ; printf ( “char指针pchar大小为%d\n” , sizeof ( pchar) ) ; printf ( “int变量i大小为%d\n” , sizeof ( i) ) ; printf ( “int指针pint大小为%d\n” , sizeof ( pint) ) ; pchar = & c; printf ( “pchar地址为%p\n” , & pchar) ; printf ( “pchar内容为%p\n” , pchar) ; printf ( “c地址为%p\n” , & c) ; printf ( “通过*pchar访问c的值为%c\n” , * pchar) ; printf ( “c的值为%c\n” , c) ; i = 10 ; b = ( i == 1 ) ? 20 : 30 ; printf ( “b 的值是 %d\n” , b ) ; b = ( i == 10 ) ? 20 : 30 ; printf ( “b 的值是 %d\n” , b ) ; } 运行结果为:
char 变量c大小为1 char 指针pchar大小为4 int 变量i大小为4 int 指针pint大小为4 pchar地址为00 EFF82Cpchar内容为00 EFF83Bc地址为00 EFF83B通过* pchar访问c的值为Dc的值为Db 的值是 30 b 的值是 20
C 中的运算符优先级 运算符的优先级确定表达式中项的组合。这会影响到一个表达式如何计算。某些运算符比其他运算符有更高的优先级,例如,乘除运算符具有比加减运算符更高的优先级。
例如 x = 7 + 3 * 2,在这里,x 被赋值为 13,而不是 20,因为运算符 * 具有比 + 更高的优先级,所以首先计算乘法 3*2,然后再加上 7。
下表将按运算符优先级从高到低列出各个运算符,具有较高优先级的运算符出现在表格的上面,具有较低优先级的运算符出现在表格的下面。在表达式中,较高优先级的运算符会优先被计算。
结合方向表示运算符计算时,先从那边开始计算,例如左到右表示先运算符左边操作数与运算符计算,然后再用运算符右边操作数参与计算。
单目运算符、双目运算符、三目运算符指的是该运算符有几个操作数,例如++是单目运算符,+ – * / 是双目运算符。
优先级 运算符 名称或含义 使用形式 结合方向 说明 1 [] 数组下标 数组名[常量表达式] 左到右 ()圆括号 (表达式) 函数名(形参表) . 成员选择(对象) 对象.成员名 -> 成员选择(指针) 对象指针->成员名 2 – 负号运算符 -表达式 右到左 单目运算符 (类型) 强制类型转换 (数据类型)表达式 ++ 自增运算符 ++变量 或 变量名++ 单目运算符 – – 自减运算符 – -变量名 或 变量名- – 单目运算符 * 取值(即解引用)运算符 *指针变量 单目运算符 & 取地址运算符 &变量名 单目运算符 ! 逻辑非运算符 !表达式 单目运算符 ~ 按位取反运算符 ~表达式 单目运算符 sizeof 长度运算符 sizeof(表达式) 3 / 除 表达式 / 表达式 左到右 双目运算符 * 乘 表达式*表达式 双目运算符 % 余数(取模) 整型表达式%整型表达式 双目运算符 4 + 加 表达式+表达式 左到右 双目运算符 – 减 表达式-表达式 双目运算符 5 << 左移 变量<<表达式 左到右 双目运算符 >> 右移 变量>>表达式 双目运算符 6 > 大于 表达式>表达式 左到右 双目运算符 >= 大于等于 表达式>=表达式 双目运算符 < 小于 表达式<表达式 双目运算符 <= 小于等于 表达式<=表达式 双目运算符 7 == 等于 表达式==表达式 左到右 双目运算符 != 不等于 表达式!= 表达式 双目运算符 8 & 按位与 表达式&表达式 左到右 双目运算符 9 ^ 按位异或 表达式^表达式 左到右 双目运算符 10 ▏ 按位或 表达式▏表达式 左到右 双目运算符 11 && 逻辑与 表达式&&表达式 左到右 双目运算符 12 ▏▏ 逻辑或 表达式▏▏表达式 左到右 双目运算符 13 ?: 条件运算符 表达式1? 表达式2: 表达式3 右到左 三目运算符 14 = 赋值运算符 变量=表达式 右到左 /= 除后赋值 变量/=表达式 *= 乘后赋值 变量*=表达式 %= 取模后赋值 变量%=表达式 += 加后赋值 变量+=表达式 -= 减后赋值 变量-=表达式 <<= 左移后赋值 变量<<=表达式 >>= 右移后赋值 变量>>=表达式 &= 按位与后赋值 变量&=表达式 ^= 按位异或后赋值 变量^=表达式 ▏= 按位或后赋值 变量▏=表达式 15 , 逗号运算符 表达式,表达式,… 左到右
请看下面的实例,了解 C 语言中运算符的优先级:
实例
# include main ( ) { int a = 20 ; int b = 10 ; int c = 15 ; int d = 5 ; int e; e = ( a + b) * c / d; printf ( "(a + b) * c / d 的值是 %d\n" , e ) ; e = ( ( a + b) * c) / d; printf ( "((a + b) * c) / d 的值是 %d\n" , e ) ; e = ( a + b) * ( c / d) ; printf ( "(a + b) * (c / d) 的值是 %d\n" , e ) ; e = a + ( b * c) / d; printf ( "a + (b * c) / d 的值是 %d\n" , e ) ; return 0 ; }
运行结果为:
( a + b) * c / d 的值是 90 ( ( a + b) * c) / d 的值是 90 ( a + b) * ( c / d) 的值是 90 a + ( b * c) / d 的值是 50
再例如:
# include int main ( ) { int a[ 5 ] = { 0 , 1 , 2 , 3 , 4 } ; int * p= a; * p++ = 10 ; printf ( "a[0]=%d,a[1]=%d,a[2]=%d,a[3]=%d,a[4]=%d\n" , a[ 0 ] , a[ 1 ] , a[ 2 ] , a[ 3 ] , a[ 4 ] ) ; return 0 ; }
运行结果为:
a[ 0 ] = 10 , a[ 1 ] = 1 , a[ 2 ] = 2 , a[ 3 ] = 3 , a[ 4 ] = 4
解释:
int a[5]={0,1,2,3,4}; 创建了一个拥有5个数的数组,数组名称与数组首地址为a。a[0]=0;…a[4]=4;
int *p=a; 定义一个指针指向数组a的首地址
*p++=10; ++运算符优先级高于*高于= 1、先执行++运算符。由于++运算符是左结合,则指针p在本条语句中不变,执行完本条语句后,指针向后移动一次。 2、再执行*运算符。程序通过指针p访问到数组的第0个元素即a[0]。 3、后执行=运算符。程序将10赋值给a[0]。
printf输出a0、a1、a2、a3、a4的值。
顺序语句 我们知道计算机执行程序指令是按顺序的方式执行的,也就是说,按照指定的顺序,一条指令一条指令的执行,执行完一条指定之后,再执行下一条指令。
当然现在很多CPU都是多核心、多线程的,并发执行多条指令,但对于同一个程序而言,CPU还是通过顺序的方式来执行指令的。 在C语言中程序执行时是按语句来顺序执行的,其中每一条语句都以分号结尾。 \
# include int main ( ) { int a; a= 10 ; printf ( "hello world%d\n" , a) ; return 0 ; }
例如: 上面的每一条语句都是以分号结尾,语句可以是定义变量、初始化变量、任何表达式、调用的函数等。
可以这样理解:一条语句,就是程序执行的一个动作。 CPU是按顺序的方式执行语句,执行完当前语句之后,再执行下一条语句。 多条语句可以写在一行代码里,也可以将每一条语句书写为单独一行代码。 但是为了编程者能够方便的读写程序代码,通常将一条语句书写为单独的一行代码。
这种顺序往下执行的,最容易理解的语句就是C语言的顺序语句。
条件语句 所谓条件语句就是只有满足一定条件才执行的语句,C语言所支持的条件语句有if语句和switch语句。
if语句 完整形式:
if ( 条件1 ) { } else if ( 条件2 ) { } else if ( 条件3 ) { } else { }
例如当我们想根据学生成绩进行评ABCD时,假设>90为A,>80为B,>60为C,否则为D。请看例程:
# include int main ( ) { int score; printf ( "请输入学生成绩:" ) ; scanf ( "%d" , & score) ; if ( score > 90 ) { printf ( "成绩为A\n" ) ; } else if ( score > 80 ) { printf ( "成绩为B\n" ) ; } else if ( score > 60 ) { printf ( "成绩为C\n" ) ; } else { printf ( "成绩为D\n" ) ; } return 0 ; }
运行并输入100
后结果为:
请输入学生成绩: 100 成绩为A
相信以上程序并不难理解。但是我们实际使用时,有时候并不需要那么多分支语句,可能就简单判断一下。这时就需要用到衍生的if语句
同时,当{}中只有一条语句时,我们可以省略{}。例如之前程序可以重新写为:
# include int main ( ) { int score; printf ( "请输入学生成绩:" ) ; scanf ( "%d" , & score) ; if ( score > 90 ) printf ( "成绩为A\n" ) ; else if ( score > 80 ) printf ( "成绩为B\n" ) ; else if ( score > 60 ) printf ( "成绩为C\n" ) ; else printf ( "成绩为D\n" ) ; return 0 ; }
但是不建议使用,因为这样写会导致阅读不便,同时当我们嵌套使用if语句时,会有else匹配错误的情况。例如:
# include int main ( ) { int a = 3 ; if ( a > 5 ) { if ( a < 10 ) { printf ( "a在5到10之间\n" ) ; } } else { printf ( "a小于5\n" ) ; } printf ( "ok\n" ) ; return 0 ; }
程序运行结果为:
a小于5 ok
因为在该程序中,{}内都是只有一句语句,所以可以省略{}。改写之后程序为:
# include int main ( ) { int a = 3 ; if ( a > 5 ) if ( a < 10 ) printf ( "a在5到10之间\n" ) ; else printf ( "a小于5\n" ) ; printf ( "ok\n" ) ; return 0 ; }
运行结果为:
ok
之所以程序运行结果不对,是因为else自动与最近的if语句匹配上,实际程序结构变成了:
# include int main ( ) { int a = 3 ; if ( a > 5 ) { if ( a < 10 ) printf ( "a在5到10之间\n" ) ; else printf ( "a小于5\n" ) ; } printf ( "ok\n" ) ; return 0 ; }
就已经偏离了我们想要表达的意思,所以建议大家尽量不用省略{}符号。
switch语句 除了if语句以外,C语言还有一种条件语句就是switch语句。相对于if灵活多变的形式而言,switch语句显得更刻板与标准化,这限制了它的使用(并不是所有条件都可以使用switch语句),但同时也给他带来了更高的运行效率。其基本形式为:
switch ( 总表达式) { case 常量1 : 语句1 ; case 常量2 : 语句2 ; . . . case 常量3 : 语句n; default : 总语句; }
执行逻辑是:
当switch语句中,总表达式==常量1,执行后续语句1、语句2、…、语句n、总语句。 当switch语句中,总表达式==常量2,执行后续语句2、…、语句n、总语句。 当switch语句中,总表达式与所有常量都不同时,只执行总语句。 需要注意的是:
不同case标签中,常量不能相同。 标签后语句可以是一条或多条。 可以使用break跳出switch语句。 switch语句中default标签可以省略。 下面我们使用switch语句重写之前的判断成绩的程序:
# include int main ( ) { int score; printf ( "请输入学生成绩:" ) ; scanf ( "%d" , & score) ; switch ( score / 10 ) { case 0 : case 1 : case 2 : case 3 : case 4 : case 5 : printf ( "学生成绩为D\n" ) ; break ; ; case 6 : case 7 : printf ( "学生成绩为C\n" ) ; break ; case 8 : printf ( "学生成绩为B\n" ) ; break ; case 9 : case 10 : printf ( "学生成绩为A\n" ) ; break ; } return 0 ; }
运行并输入70,结果为:
请输入学生成绩: 70 学生成绩为C
嵌套条件语句 指的是在一个条件语句中包含另一个条件语句,常用于较为复杂的判断场景。 示例:编写一个程序判断是否是闰年。 注释:闰年条件是:
普通年能被四整除且不能被100整除的为闰年。 世纪年能被400整除的是闰年,如2000年是闰年,1900年不是闰年 参考例程:
# include int main ( ) { int year; printf ( "请输入年份:" ) ; scanf ( "%d" , & year) ; if ( ( year % 4 == 0 ) && ( year % 100 != 0 ) ) { printf ( "%d年是闰年\n" , year) ; } else { if ( year % 400 == 0 ) { printf ( "%d年是闰年\n" , year) ; } else { printf ( "%d年不是闰年\n" , year) ; } } return 0 ; }
运行并输入2000,结果为:
请输入年份: 2000 2000 年是闰年
运行并输入1921,结果为:
请输入年份: 1921 1921 年不是闰年
循环语句 循环语句是指程序满足一定条件从而重复执行的控制语句。C语言中的循环语句有for、while、do while、goto。
for语句 循环语句中for具有较强的语句格式限制,其形式是:
for ( 初始化语句; 条件语句; 条件修改语句) { }
初始化语句、条件语句、条件修改语句可以由0条或多条语句构成,如果有多条语句,需要以逗号隔开。for语句逻辑是
1、执行初始化语句 2、判断条件语句的真假,若假则退出循环,若真则执行循环体{} 3、执行完循环体后,自动执行条件修改语句,然后重复2、3两步。 举个例子:
# include int main ( ) { int a; int sum; for ( a= 0 , sum= 0 ; a< 10 ; a++ ) \\程序从a= 0 开始,运行到a= 9 时仍然执行循环体,直到a= 10 时,不执行循环,直接跳出{ sum+= a; } printf ( "从0加到9结果为:%d\n" , sum) ; return 0 ; }
运行结果为:
从0 加到9 结果为:45
练习: 1、在屏幕上显示由*组成的5行等腰直角三角形 参考程序
# include int main ( ) { int i, j; for ( i = 0 ; i < 5 ; i++ ) { for ( j = 0 ; j <= i; j++ ) { printf ( "*" ) ; } printf ( "\n" ) ; } return 0 ; }
运行结果为:
* * * * * * * * * * * * * * *
2、在屏幕上输出0到100的和。 参考例程:
# include int main ( ) { int i, sum; for ( i = 0 , sum = 0 ; i <= 100 ; i++ ) { sum += i; } printf ( "0-100的和为:%d\n" , sum) ; return 0 ; }
运行结果为:
0 - 100 的和为: 5050
while语句 相对于for语句,while语句较为灵活多变。格式为:
while ( 条件) { }
例如:在屏幕上显示01234
# include int main ( ) { int i= 0 ; while ( i< 5 ) { printf ( "%d\n" , i) ; i++ ; } return 0 ; }
运行结果为:
0 1 2 3 4
练习: 1、使用while重做:屏幕上输出由*组成的5行等腰直角三角形。 参考例程:
# include int main ( ) { int i, j; i = 0 ; while ( i < 5 ) { j = 0 ; while ( j <= i) { printf ( "*" ) ; j++ ; } printf ( "\n" ) ; i++ ; } return 0 ; }
运行结果为:
* * * * * * * * * * * * * * *
2、使用while重做:计算0-100内的数的和。 参考例程:
# include int main ( ) { int sum = 0 ; int i = 0 ; while ( i <= 100 ) { sum += i; ++ i; } printf ( "0-100的和为%d\n" , sum) ; return 0 ; }
运行结果为:
0 - 100 的和为5050
do while语句 do while语句与while语句及其相似,其区别在于:while语句先判断条件,在考虑是否执行循环体;而do while语句先执行循环体,在判断是否进行下一次信循环,简单理解,dowhile循环体至少执行一次。实际上,do while语句在工程中用的不多。
do while语句形式:
do { } while ( 条件) ;
例如:计算5-10的和。
# include int main ( ) { int i; int sum; i = 5 ; sum = 0 ; do { sum += i; i++ ; } while ( i < 10 ) ; printf ( "5-10的和为:%d\n" , sum) ; return 0 ; }
运行结果为:
5 - 10 的和为: 35
练习: 1、使用do while语句重做:在屏幕上显示一个由*组成的5行等腰直角三角形。 参考例程:
# include int main ( ) { int i = 0 , j = 0 ; do { j = 0 ; do { printf ( "*" ) ; j++ ; } while ( j <= i) ; printf ( "\n" ) ; i++ ; } while ( i < 5 ) ; return 0 ; }
运行结果为:
* * * * * * * * * * * * * * *
2、使用do while语句重做:输出0-100的和。 参考例程:
# include int main ( ) { int i = 0 , sum = 0 ; do { sum += i; i++ ; } while ( i <= 100 ) ; printf ( "0-100的和为%d\n" , sum) ; return 0 ; }
运行结果为:
0 - 100 的和为5050
break语句 break语句相信大家应该还没忘记,在条件语句中我们曾介绍过,break可以用于跳出switch语句。实际上,break语句的作用也正是用于跳出某一循环体。可以使用break语句跳出的有switch、for、while、do while。
例如:使用break+while语句计算0-100的和
# include int main ( ) { int i = 0 , sum = 0 ; while ( 1 ) { sum += i; i++ ; if ( i > 100 ) { break ; } } printf ( "0-100的和为%d" , sum) ; return 0 ; }
运行结果为:
0 - 100 的和为5050
continue语句 continue作用与break极为相似,break用于跳出循环,continue则用于跳过本次循环进入下次循环,并且continue不能用于switch语句。 例如:计算0-100所有奇数和。
# include int main ( ) { int i, sum; for ( i = 0 , sum = 0 ; i <= 100 ; i++ ) { if ( i % 2 == 0 ) { continue ; } sum += i; } printf ( "0-100的奇数和为%d" , sum) ; return 0 ; }
运行结果为:
0 - 100 的奇数和为2500
goto语句 goto语句严格来说并不是循环语句,应该称为无条件跳转语句。关于goto语句仅处于内容完整性考虑才介绍,实际使用时该语句极其危险。可以这么说,所有使用goto语句的程序都可以使用其他循环语句代替,并且goto语句极其危险不受控制,所以同学们可以不用看本小节。注意:goto语句本质不是只是程序跳转,所以不支持break语句。
goto语句使用格式:
语句1 语句2 标签1 : 语句3 语句4 goto 标签1 ;
当程序运行到goto 标签1;时,直接无条件跳转到标签1处。 例如:使用goto语句计算0-100数的和。
# include int main ( ) { int i = 0 , sum = 0 ; loop: if ( i <= 100 ) { sum += i; i++ ; goto loop; } printf ( "0-100的奇数和为%d" , sum) ; return 0 ; }
运行结果为:
0 - 100 的奇数和为5050
循环语句对比 一般而言,C语言程序中for语句、while语句、break语句用的较多一点,相对而言do while、continue使用相对较少,goto语句几乎不使用。
那么我们程序到底选取哪种形式的循环语句呢,实际上,循环语句都可以互相转换,例如for(;条件;){}
就等价于while(条件){}
。但是由于for循环语句中,对于语句格式有着较为死板的要求,可以极大减少我们写程序时由于粗心而出现的错误,所以优先选择使用for语句,选取标准如下:
对于循环次数明确,循环条件不复杂的循环程序应采用for语句。例如在屏幕上输出100句hello、计算0-100的和等程序优先使用for。 对于循环次数不明确,循环条件较为复杂的循环程序应采用while语句。例如找到一个数,满足该数所有位数和相乘等于它本身(a*b*c=abc)。这种不明确循环次数,条件较为复杂的程序优先采用while语句。 不论条件是否成立,都至少执行一次循环体的循环优先采用do while。 break与continue可以时循环更灵活,但是容易增加程序复杂度,可以适当使用。 不要使用goto 练习: 1、判断0-100有多少可以被7整除的数,并输出到屏幕。 分析:该程序从0-100遍历所有数,判断是否是7的整数。应采用for循环。 参考例程:
# include int main ( ) { int num, i; for ( i = 0 , num = 0 ; i <= 100 ; i++ ) { if ( i % 7 == 0 ) { printf ( "%d是7的整数倍\n" , i) ; num++ ; } } printf ( "0-100共有%d个7的整数倍数\n" , num) ; return 0 ; }
运行结果为:
0 是7 的整数倍7 是7 的整数倍14 是7 的整数倍21 是7 的整数倍28 是7 的整数倍35 是7 的整数倍42 是7 的整数倍49 是7 的整数倍56 是7 的整数倍63 是7 的整数倍70 是7 的整数倍77 是7 的整数倍84 是7 的整数倍91 是7 的整数倍98 是7 的整数倍0 - 100 共有15 个7 的整数倍数
2、找到一个三位数abc,满足a3 +b3 +c3 =abc。输出到屏幕上。 分析:由于我们并不确定要循环多少次,且循环条件并不是简单地比大小,所有采用while语句。 参考例程:
# include int main ( ) { int i = 100 ; int g, s, b; while ( i < 1000 ) { b = i / 100 ; s = ( i % 100 ) / 10 ; g = i % 10 ; if ( b * b * b + s * s * s + g * g * g == i) { printf ( "找到该数字%d\n" , i) ; break ; } else { i++ ; } } return 0 ; }
运行结果为:
找到该数字153
C语言动作单元–函数 函数概念 函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数—主函数 main() 。
我们可以把重复使用的代码、完成某项功能的代码划分为一个函数,当我们需要使用时,只需调用这个函数就可以了。
在主函数前定义的函数可以没有函数声明直接使用;在主函数后面定义的函数必需要函数声明才能在函数中调用,函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的具体执行的语句。
函数还有很多叫法,比如方法、子例程或程序,等等。
自定义函数 C 语言中的函数定义的一般形式如下:
函数返回类型 函数名称( 形参类型1 形参名称1 , 形参类型2 形参名称2 ) { 语句; return 变量(同函数返回类型); }
在 C 语言中,函数由一个函数头和一个函数主体组成。下面列出一个函数的所有组成部分:
函数返回类型:一个函数可以返回一个值。函数返回类型 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,函数返回类型 是关键字 void。例如我们一直写的主函数返回值一直是int类型。
函数名称:这是函数的实际名称。函数名和后面的参数列表一起构成了函数签名。
形参参数:形参参数就像是占位符。当函数被调用时,程序会向形参传递一个值,这个值被称为实际参数,这时函数形参就有了具体值了。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
函数主体:函数主体包含一组定义函数执行任务的语句。
示例:
# include void fun ( ) ; int main ( ) { fun ( ) ; fun ( ) ; return 0 ; } void fun ( ) { printf ( "hello world\n" ) ; }
运行结果为:
hello worldhello world
上述程序因为fun函数在主函数之后定义,所以在使用fun()函数之前必须有fun函数的声明,否则编译器会因为找不到fun函数而报错。
下面介绍形参与实参的区别,会有一点关于指针的知识。
形参指的是定义或声明函数时,仅用于占位、并没有实际内存的变量,可以理解为形式参数。例如int fun1(int a,double b);
中形式参数有int类型的a和double类型的b。形参只有函数被调用时,系统才会分配内存,并且在退出函数时,销毁该内存。
相对应的,实参指的是在调用函数时,按照形式参数的个数与顺序,传递给被调用函数的一些具体的值或变量。传递参数时,仅仅是将实参的数据复制一份放到形参内存中,所以子函数中形参的改变并不影响实参的值。
请看一下程序:
# include void fun ( int a, int b) { a = 10 ; b = 11 ; printf ( "子函数中a=%d,b=%d\n" , a, b) ; } int main ( ) { int a = 3 , b = 5 ; fun ( a, b) ; printf ( "主函数中a=%d,b=%d\n" , a, b) ; return 0 ; }
运行结果为:
子函数中a= 10 , b= 11 主函数中a= 3 , b= 5
那么我们能否通过子函数从而修改主函数中变量的值呢,答案是肯定的,通过指针修改!指针在C语言中妙用无穷,是灵魂所在。虽然我们不能直接修改主函数中变量的值,但是我们可以将主函数中变量的地址传递给子函数,然后子函数通过该地址访问并修改主函数中的变量,也就达到我们想要的效果了。例如一下程序:
# include void fun ( int * pa, int * pb) { * pa = 10 ; * pb = 11 ; printf ( "子函数中a=%d,b=%d\n" , * pa, * pb) ; } int main ( ) { int a = 3 , b = 5 ; fun ( & a, & b) ; printf ( "主函数中a=%d,b=%d\n" , a, b) ; return 0 ; }
运行结果为:
子函数中a= 10 , b= 11 主函数中a= 10 , b= 11
上述程序中,主函数中变量被子函数修改了。一个函数可以有0或1个返回值,但是通过传递指针修改主函数变量的方式扩展函数返回值的数量,例如上述程序相当于有两个返回值a和b。
之前我们写过关于计算0到100累加和的程序,我们可以使用函数将其封装起来。
# include int fun ( int begin, int end) { int i, sum; for ( i = begin, sum = 0 ; i <= end; i++ ) { sum += i; } return sum; } int main ( ) { int a; a = fun ( 1 , 5 ) ; printf ( "1-5的和为%d\n" , a) ; printf ( "0-100的和为%d\n" , fun ( 0 , 100 ) ) ; return 0 ; }
运行结果为:
1 - 5 的和为15 0 - 100 的和为5050
在封装完成之后,我们想要计算从x到y的累加和就可以直接使用fun(x,y)来计算了,这样大大提高了代码可重复利用率。一般的我们会将高度重复的代码、或者实现了某一功能的代码封装成一个函数,然后再其他函数中调用。
可变参函数 本小节可做课外知识了解一下。与我们常见的函数不同,可变参函数的意思就是该函数的参数类型可变,参数个数可变。实际工程中我们自己写可变参函数较少,但是我们却经常使用,常见的可变参函数例如printf(),scanf()。看完本节,我们也可以写出这样的可变参函数。
首先可变参函数相关内容并不是在标准输入输出库(stdio.h)中定义,而是保存在标准参数库(stdarg.h)中,所以只要想使用可变参就需要在文件开头包含#include
。
可变参函数定义形式如下:
函数返回值类型 函数名称( 强制形参类型 强制形参名称, . . . ) { } double add ( int n, . . . ) { double sum = 0.0 ; va_list argptr; va_start ( argptr, n) ; for ( ; n > 0 ; -- n) sum += va_arg ( argptr, double ) ; va_end ( argptr) ; return sum; }
如此看来,可变参函数与普通函数定义相似:
可变参函数定义时需要至少一个强制参数,意思就是该函数至少需要一个参数。 可变参函数强制形参后接…表示接收后续所有参数,并保存至形参列表中。 stdarg文件中定义新类型va_list形参列表类型,想要在子函数中访问形参,需要先定义va_list类型变量,并对其进行初始化。 void va_start(va_list argptr, int n);
va_start用于对参数初始化,从系统中读取n个数据保存至参数列表argptr中。上述例程先定义参数列表argptr,之后使用va_start读取n数据保存在参数列表argptr中。type va_arg(va_list argptr, type);
va_arg用于从从参数列表读取一个type类型变量。void va_end(va_list argptr);
va_end用于释放argptr参数列表的内存空间,因为argptr本质是指针,需要手动内存释放,后续指针专辑会专门讲解,切记使用完参数列表后需要va_end(argptr);
释放内存。参数列表不会对参数隐式类型转换,需要注意输入类型匹配。 上述例程完善一下为:
# include # include double add ( int n, . . . ) { double sum = 0.0 ; va_list argptr; va_start ( argptr, n) ; for ( ; n > 0 ; -- n) sum += va_arg ( argptr, double ) ; va_end ( argptr) ; return sum; } int main ( ) { printf ( "1+3+3=%lf\n" , add ( 3 , 1.0 , 3.2 , 3.2 ) ) ; printf ( "2+2=%lf\n" , add ( 2 , 2 , 2 ) ) ; return 0 ; }
运行结果为:
1 + 3 + 3 = 7.400000 2 + 2 = 0.000000
细心地读者看到了2+2=0.000000是不对的,为什么会导致这种问题呢,原因很简单,参数列表不会对形参进行类型转换,它把int类型的参数2错误的用va_arg(argptr,double);
按照double数据类型的格式读取出来了,导致出现问题,如何解决?
调用时可以声明数据类型add((int)2,(double)2,(double)2);
以浮点数形式书写数据add(2,2.0,2.0);
修改之后运行结果为:
1 + 3 + 3 = 7.400000 2 + 2 = 4.000000
递归函数 类似于递归证明,函数也可以递归调用,当函数直接或间接调用自己本身时,就构成了递归调用。例如:
void fun ( ) { fun ( ) ; }
以上函数即为最简单的递归调用,如果他被调用将会陷入死循环。下面用实例演示递归函数的用处与作用。 例如:计算从0-n的累加和
# include int fun ( int n) { if ( n > 1 ) return n + fun ( n - 1 ) ; else return 1 ; } int main ( ) { printf ( "0-100的累加和为%d\n" , fun ( 100 ) ) ; return 0 ; }
运行结果为:
0 - 100 的累加和为5050
由于递归函数理解较为复杂,这里稍微多费口舌介绍一下具体运行过程:
程序进入main函数。 开始执行printf("0-100的累加和为%d\n",fun(100));
先执行fun(100)
。 第一次进入fun函数,形参为100,满足if语句条件。 执行return 100+fun(100-1);
先要计算该语句中fun(100-1)的值。 第二次进入fun函数,形参为99,满足if语句条件。 执行return 99+fun(99-1);
先要计算该语句中fun(99-1)的值。 … 第99次进入fun函数,形参为2,满足if语句条件。 执行return 2+fun(2-1);
先要计算该语句中fun(2-1)的值。 第100次进入fun函数,形参为1,不满足if语句条件。 执行return 1;
退出第100次进入的fun函数,第100次fun函数返回return 1;
。 退出第99次进入的fun函数,第99次fun函数返回return 2+1;
。 … 退出第二次进入的fun函数,第二次fun函数返回return 99+(98+...+1);
。 退出第一次进入的fun函数,第一次fun函数返回return 100+(99+...+1);
。 最终返回值为0-100的和。 递归函数适用于函数变量较少,函数体简单,递归次数有限且不算太多的情况。递归函数可以帮助我们理清思路,使得复杂的问题得以简化。下面请看经典例题汉罗塔。
练习:汉罗塔 一次只能移动一个圆盘,并且大圆盘不能放在小圆盘上,要求将A柱汉罗塔移动到C柱,且顺序保持不变。 对于该问题我们可以抽象成表格
当只有一个罗盘时:
当有两个罗盘时:
A B C 方法 第1步 12 – 第2步 2 1 A->B 第3步 1 2 A->C 第4步 12 B->C
当有三个罗盘时
A B C 方法 第1步 123 – 第2步 23 1 A->C 第3步 3 2 1 A->B 第4步 3 12 C->B 第5步 12 3 A->C 第6步 1 2 3 B->A 第7步 1 23 B->C 第8步 123 A->C
当罗盘个数逐渐上涨时,游戏步骤呈指数递增,那么我们如何使用编写程序完成该任务呢。
递归分析:
我们需要写一个函数,该函数任务是可以将一座汉罗塔从我们指定柱子x柱搬运到目标柱子z柱。 当只有一个罗盘时,步骤为x->z。 当大于一个罗盘时,步骤为先调用函数本身,将前1到n-1个罗盘搬运到y柱子,再将第n个罗盘搬运到z柱子,最后再调用函数本身将y柱子上的1到n-1个罗盘搬运到z柱子。 参考例程: # include void fun ( char x, char y, char z, int n) { if ( n == 1 ) { printf ( "%c->%c\n" , x, z) ; } else { fun ( x, z, y, n - 1 ) ; printf ( "%c->%c\n" , x, z) ; fun ( y, x, z, n - 1 ) ; } } int main ( ) { int n; printf ( "请输入罗盘个数:" ) ; scanf ( "%d" , & n) ; fun ( 'A' , 'B' , 'C' , n) ; return 0 ; }
运行并输入1,结果为:
请输入罗盘个数: 1 A-> C
运行并输入2,结果为:
请输入罗盘个数: 2 A-> BA-> CB-> C
运行并输入3,结果为:
请输入罗盘个数: 3 A-> CA-> BC-> BA-> CB-> AB-> CA-> C
当有n个罗盘时,想要经历2n -1个步骤。这就是经典的汉罗塔递归程序,如果有点难以理解可以多看几遍该例程。
除了汉罗塔以外,图像处理中种子生长与数据结构中二叉树对于递归调用的应用也较多,后续涉及到数据结构的章节再详细介绍。
再探scanf 当我们学会了函数并了解函数的作用之后,回头再开之前的一直在用的printf与scanf函数。
printf函数原型是int printf(const char *format, ...);
,const char *表示不可改变char类型指针,指的是无法通过指针来改变目标地址内容,后面三个点表示可变参数。其返回值是打印字符个数,例如printf("asdf");
语句中,该函数返回值为3。
scanf函数原型是Int scanf (const char *format,...);
,使用示例:scanf("%d %d",&a,&b);
函数返回值为int型。
如果a和b都被成功读入,那么scanf的返回值就是2; 如果只有a被成功读入,返回值为1; 如果a读取失败,返回值为0; 如果遇到错误或遇到end of file,返回值为EOF。end of file为Ctrl+z 或者Ctrl+d。 这样看来我们可以通过读取scanf函数返回值从而进行入参检查。 例如:
# include int main ( ) { int s1, s2, s3; printf ( "欢迎使用成绩录入系统\n" ) ; printf ( "请输入学生成绩:" ) ; while ( scanf ( "%d %d %d" , & s1, & s2, & s3) != 3 ) { getchar ( ) ; printf ( "\n输入有误,请重新输入:" ) ; } return 0 ; }
运行并输入10 20 30
,结果为:
欢迎使用成绩录入系统请输入学生成绩: 10 20 30 ok
运行并输入80 c 90;,结果为:
欢迎使用成绩录入系统请输入学生成绩: 80 c 90 输入有误,请重新输入:
通过scanf函数的返回值,我们就实现了不同数据之间的入参检测。
C语言库 C 标准库提供了大量的程序可以调用的内置函数。我们并不需要了解所有库函数,更不需要学习所有库函数具体是怎么实现的,我们只需要有个简单的印象,方便在我们想要时直接调用即可。例如常见的
输入输出操作 通常存在于stdio.h中,一般用于从键盘鼠标输入,输出到文件或屏幕。 常用函数有 printf()函数 scanf()函数 putchar()函数 getchar()函数 getch()函数 puts()函数 gets()函数 文章第二部分–标准输入输出介绍过,这里就不多赘述了。
还有几个常用的输入输出函数与字符串有关
sprintf()函数
原型:sprintf(char * str,const char *format,…) 作用:将字符串format格式化输出到字符串str //使用示例 char str[128]; sprintf(str,“hello world%d”,32); //此时str中字符串为”hello world32″ sscanf()函数
原型:sscanf(char*str,const char *format,…) 作用:从字符串str中按format格式化输入到后面参数中 //使用示例 int i; double d; char str[128]=“1234 12.6”; sscanf(str,“%d %lf”,&i,&d); //此时i=1234,d=12.6 文件操作 通常存在于stdio.h文件中,用于读取或写入文件,可以看作特殊的输入输出,其对象是文件。
fopen()函数
原型:FILE * fopen(const char * path,const char * mode); 作用:按指定格式mode打开指定路径path文件。 fprintf()函数
原型:int fprintf(FILE *stream, char *format[, argument,…]); 作用:传送格式化输出到一个文件中 fscanf()函数
原型:int fscanf(FILE *stream, char *format[,argument…]); 作用:从一个文件流中执行格式化输入 clearerr()函数
原型:void clearerr(FILE * stream); 作用:清除文件流的错误旗标 fclose()函数
原型:int fclose(FILE * stream); 作用:关闭指定文件 fget()函数
原型:int fgetc(FILE * stream); 作用:由文件中读取一个字符 fgets()函数
原型:char * fgets(char * s,int size,FILE * stream); 作用:由文件中读取一个字符 fputc()函数
原型:int fputc(int c,FILE * stream); 作用:将一指定字符写入文件流中 fputs()函数
原型:int fputs(const char * s,FILE * stream); 作用:将一指定字符串写入文件流中 fseek()函数
原型:int fseek(FILE * stream,long offset,int whence); 作用:移动文件流的读写位置 fwrite()函数
原型:size_t fwrite(const void * ptr,size_t size,size_t nmemb,FILE * stream); 作用:将数据写至文件流 fread()函数
原型:size_t fread( void *buffer, size_t size, size_t count, FILE *stream ); 作用:从文件流中读取数据 动态内存操作 通常存在于stdlib.h文件中,用于对电脑内存控制,直接通过指针访问与控制内存。
malloc()函数
原型:void* malloc (size_t size); 作用:向内存申请一块连续可用的空间,并返回指向这块从堆区上开辟的空间的指针。 calloc()函数
原型:void* calloc(size_t num,size_t size); 作用:函数的功能是为num个大小的size的元素开辟一块空间,并且把空间的每个字节初始化为0。 realloc()函数
原型:void realloc(void ptr,size_t size); 作用:修改动态开辟的内存的大小 free()函数
原型:void free(void*ptr); 作用:释放动态开辟的空间。 文章特殊部分–指针专辑–指针与动态内存将详细介绍介绍
随机数操作 通常存在于stdlib.h文件中,用于生成一个随机数。
srand()函数
作用:设置随机数种子 使用:srand(n);
n为int数 可以配合系统时间使用,时间不停变化,通过不停调用srand(time);
则该随机数种子也不停变化。 rand()函数
作用:根据随机数种子产生随机数,该数字在0-RAND_MAX 之间 使用:int a=rand();
要取得 [a,b) 的随机整数,使用 (rand() % (b-a))+ a; 要取得 [a,b] 的随机整数,使用 (rand() % (b-a+1))+ a; 要取得 (a,b] 的随机整数,使用 (rand() % (b-a))+ a + 1; 通用公式: a + rand() % n;其中的 a 是起始值,n 是整数的范围。 要取得 a 到 b 之间的随机整数,另一种表示:a + (int)b * rand() / (RAND_MAX + 1)。 要取得 0~1 之间的浮点数,可以使用 rand() / double(RAND_MAX)。 字符串操作 通常存在于string.h中,用于对字符串实现各种操作。
strcat()函数
原型:char *strcat(char *dest, const char src) 作用:把src所指向的字符串追加到dest 所指向的字符串的结尾。 strcmp()函数
原型:int strcmp(const char *str1, const char str2) 作用:把 str1 所指向的字符串和 str2 所指向的字符串进行比较。 strcpy()函数
原型:char *strcpy(char *dest, const char src) 作用:把 src 所指向的字符串复制到 dest。 strlen()函数
原型:size_t strlen(const char str) 作用:计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。 memchr()函数
原型:void *memchr(const void str, int c, size_t n) 作用:在参数 str 所指向的字符串的前 n 个字节中搜索第一次出现字符 c(一个无符号字 符)的位置。 memcmp()函数
原型:int memcmp(const void *str1, const void str2, size_t n) 作用:把 str1 和 str2 的前 n 个字节进行比较。 memcpy()函数
原型:void *memcpy(void *dest, const void src, size_t n) 作用:从 src 复制 n 个字符到 dest。 memmove()函数
原型:void *memmove(void *dest, const void src, size_t n) 作用:另一个用于从 src 复制 n 个字符到 dest 的函数。 memset()函数
原型:void *memset(void str, int c, size_t n) 作用:复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。 strncat()函数
原型:char *strncat(char *dest, const char src, size_t n) 作用:把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为 止。 strchr()函数
原型:char *strchr(const char str, int c) 作用:在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。 strncmp()函数
原型:int strncmp(const char *str1, const char str2, size_t n) 作用:把 str1 和 str2 进行比较,最多比较前 n 个字节。 strcoll()函数
原型:int strcoll(const char *str1, const char str2) 作用:把 str1 和 str2 进行比较,结果取决于 LC_COLLATE 的位置设置。 strncpy()函数
原型:char *strncpy(char *dest, const char src, size_t n) 作用:把 src 所指向的字符串复制到 dest,最多复制 n 个字符。 strcspn()函数
原型:size_t strcspn(const char str1, const char str2) 作用:检索字符串 str1 开头连续有几个字符都不含字符串 str2 中的字符。 strerror()函数
原型:char strerror(int errnum) 作用:从内部数组中搜索错误号errnum,并返回一个指向错误消息字符串的指针。 strpbrk()函数
原型:char *strpbrk(const char *str1, const char str2) 作用:检索字符串 str1 中第一个匹配字符串 str2 中字符的字符,不包含空结束字符。也 就是说,依次检验字符串 str1 中的字符,当被检验字符在字符串 str2 中也包含时,则停止检验,并返回该字符位置。 strrchr()函数
原型:char *strrchr(const char str, int c) 作用:在参数 str 所指向的字符串中搜索最后一次出现字符 c(一个无符号字符)的位置。 strspn()函数
原型:size_t strspn(const char *str1, const char str2) 作用:检索字符串 str1 中第一个不在字符串 str2 中出现的字符下标。 strstr()函数
原型: char *strstr(const char *haystack, const char needle) 作用:在字符串 haystack 中查找第一次出现字符串 needle(不包含空结束字符)的位置。 strtok()函数
原型: char *strtok(char *str, const char delim) 作用:分解字符串 str 为一组字符串,delim 为分隔符。 strxfrm()函数
原型:size_t strxfrm(char *dest, const char src, size_t n) 作用:根据程序当前的区域选项中的 LC_COLLATE 来转换字符串 src 的前 n 个字符,并把它们放置在字符串 dest 中。 计算操作 通常存在于math.h文件中,主要是对一些常用数学运算符号和数学公式的封装。
三角函数 sin()函数
原型:double sin(double); 作用:正弦 cos()函数
原型:double cos(double); 作用:余弦 tan()函数
原型:double tan(double); 作用:正切 反三角函数 asin()函数
原型:double asin (double); 作用:反正弦函数,结果介于[-PI/2,PI/2] acos()函数
原型:double acos (double); 作用:反余弦函数,结果介于[0,PI] atan()函数
原型:double atan (double); 反正切(主值),结果介于[-PI/2,PI/2] atan2()函数
原型:double atan2 (double,double); 反正切(整圆值),结果介于[-PI,PI] 双曲三角函数 sinh()函数
cosh()函数
tanh()函数 *原型:double tanh (double);
指数与对数 frexp()函数
原型:double frexp(double value,int *exp); 作用:这是一个将value值拆分成小数部分f和(以2为底的)指数部分exp,并返回小数部分f,即f·2^exp。其中f取值在0.5~1.0范围或者0。 ldexp()函数
原型:double ldexp(double x,int exp); 作用:这个函数刚好跟上面那个frexp函数功能相反,它的返回值是x*2^exp modf()函数
原型:double modf(double value,double *iptr); 作用:拆分value值,返回它的小数部分,iptr指向整数部分。 log()函数
原型:double log (double); 作用:以e为底的对数 log10()函数
原型:double log10 (double); 作用:以10为底的对数 pow()函数
原型:double pow(double x,double y); 作用:计算x的y次幂 powf()函数
原型:float powf(float x,float y); 作用:功能与pow一致,只是输入与输出皆为单精度浮点数 exp()函数
原型:double exp (double); 作用:求取自然数e的幂 sqrt()函数
原型:double sqrt (double); 作用:开平方根 取整 round()函数
原型:double round(double); 作用:四舍五入取整,返回x的四舍五入取整数 ceil()函数
原型:double ceil (double); 作用:取上整,返回不比x小的最小整数 floor()函数
原型:double floor (double); 作用:取下整,返回不比x大的最大整数,即 高斯函数[x] 绝对值 abs()函数
原型:int abs(int i); 作用:求整型的绝对值 fabs()函数
原型:double fabs (double); 作用:求实型的绝对值 cabs()函数
原型:double cabs(struct complex znum); 作用:求复数的绝对值 标准化浮点数 frexp()函数
原型:double frexp (double f,int *p); 作用:标准化浮点数,f = x * 2^p,已知f求x,p (x介于[0.5,1]) ldexp()函数
原型:double ldexp (double x,int p); 作用:与frexp相反,已知x,p求f 取整与取余 modf()函数
原型:double modf (double,double*); 作用:将参数的整数部分通过指针回传,返回小数部分 fmod()函数
原型:double fmod (double,double); 作用:返回两参数相除的余数 字符分类操作 isalnum()函数
原型:int isalnum(int c) 作用:该函数检查所传的字符是否是字母和数字。 isalpha()函数
原型:int isalpha(int c) 作用:该函数检查所传的字符是否是字母。 iscntrl()函数
原型:int iscntrl(int c) 作用:该函数检查所传的字符是否是控制字符。 isdigit()函数
原型:int isdigit(int c) 作用:该函数检查所传的字符是否是十进制数字。 isgraph()函数
原型:int isgraph(int c) 作用:该函数检查所传的字符是否有图形表示法。 islower()函数
原型:int islower(int c) 作用:该函数检查所传的字符是否是小写字母。 isprint()函数
原型:int isprint(int c) 作用:该函数检查所传的字符是否是可打印的。 ispunct()函数
原型:int ispunct(int c) 作用:该函数检查所传的字符是否是标点符号字符。 isspace()函数
原型:int isspace(int c) 作用:该函数检查所传的字符是否是空白字符。 isupper()函数
原型:int isupper(int c) 作用:该函数检查所传的字符是否是大写字母。 isxdigit()函数
原型:int isxdigit(int c) 作用:该函数检查所传的字符是否是十六进制数字。 tolower()函数
原型:int tolower(int c) 作用:该函数把大写字母转换为小写字母。 toupper()函数
原型:int toupper(int c) 作用:该函数把小写字母转换为大写字母。