一、计算
1、算找零 ~ 思路+代码
【思路】:
① 有地方放输入的数字;
② 有办法输入数字;
③ 输入的数字能参与计算。
【代码】:
#include <stdio.h>
int main()
{
int price = 0; // 定义了整型变量price --- 变量是保存数据的地方;变量的名字是一种“标识符”
// 标识符只能由字母、数字和下划线组成,数字不能出现在第一个位置上
printf("请输入金额(元):");
scanf("%d", &price); // 注意price前面的 & ;
int change = 100 - price;
printf("找您%d元。\n", change);
return 0;
}
【Note:】
1、变量是保存数据的地方;变量的名字是一种“标识符”;
2、标识符只能由字母、数字和下划线组成,数字不能出现在第一个位置上;
2、scanf函数
1、作用:输入
2、eg.
scanf("%d",&price);
注意:
① scanf 中,price前面有一个&
;
② “%d",表明scanf要读取一个整数,交给后面的变量;
“%lf”,表示要读取一个浮点数;
③ 出现在scanf的格式字符串中的东西,是一定要输入的东西。
3、常量 & 变量
(1)常量:
1、eg.主程序:
const int AMOUNT = 100;
int price = 0;
printf("请输入金额(元):");
scanf("%d", &price); // 注意price前面的 & ;
int change = AMOUNT - price;
printf("找您%d元。\n", change);
2、const 是一个修饰符,加在int 前。初始化后不能修改,即不能再赋值;
(2)变量:
3、让用户输入变量 AMOUNT ,而不是使用固定的初始值
【主程序】:
int amount = 100;
int price = 0; // 定义了整型变量price --- 变量是保存数据的地方;变量的名字是一种“标识符”
// 标识符只能由字母、数字和下划线组成,数字不能出现在第一个位置上
printf("请输入金额(元):");
scanf("%d", &price); // 注意price前面的 & ;
printf("请输入票面:");
scanf("%d",&amount);
int change = amount - price;
printf("找您%d元。\n", change);
【运行结果】:
4、读入两个变量的值,将它们相加以后的结果输出:
【代码】:
int a;
int b;
printf("请输入两个整数:");
scanf("%d %d",&a,&b);
printf("%d + %d = %d\n",a,b,a+b);
【运行结果】:
4、浮点数
(1)计算身高的程序
1、两个整数的运算结果只能是整数;
eg. 10/3*3 = 9
2、浮点数
——小数点是浮动的;
3、当整型数和浮点数放在一起运算时,C会将整数转换为浮点数,再进行浮点数的运算;
① 英尺、英寸换算成米
【代码】:
#include <stdio.h>
int main()
{
printf("请分别输入身高的英尺和英寸,"
"如输入\"5 7\"表示5英尺7英寸:");
int foot;
int inch;
scanf("%d %d", &foot, &inch);
printf("身高是%f米。\n",
((foot + inch / 12.0) * 0.3048));
return 0;
}
【运行结果】:
② 厘米换算成英尺、英寸
#include<stdio.h>
int main(){
int foot,inch;
int cm;
float m,t;
scanf("%d",&cm);
m = cm/100.0;
// foot + inch / 12 = m/0.3048;
t = m/0.3048;
// foot = t的整数部分;
// inch = t的小数部分*12
foot = t;
inch = (t - foot)*12;
printf("%d %d",foot,inch);
return 0;
}
4、双精度浮点数 double
【代码】:
#include <stdio.h>
int main()
{
printf("请分别输入身高的英尺和英寸,"
"如输入\"5 7\"表示5英尺7英寸:");
double foot;
double inch;
scanf("%lf %lf", &foot, &inch); // lf 来表达输入的事 double 类型
printf("身高是%f米。\n",
((foot + inch / 12) * 0.3048));
return 0;
}
运行结果与上述相同。
(2)总结:
1、整数 int :输入输出都用%d
;
printd("%d",...);
scanf("%d",...);
2、带小数点的数:
double
printf("%f",...);
scanf("lf",...);
5、表达式
(1)计算时间差:
【思路】:
1、程序中要有哪些变量,这些变量要怎么读入数据、表达数据;
2、有了数据以后,怎么去计算;
【代码】:
#include <stdio.h>
int main()
{
int hour1,minute1;
int hour2,minute2;
int t1,t2,t;
scanf("%d %d",&hour1,&minute1);
scanf("%d %d",&hour2,&minute2);
t1 = hour1 * 60 + minute1; // 转换成以分钟为单位
t2 = hour2 * 60 + minute2;
t = t2 - t1;
printf("时间差是%d小时%d分", t/60, t%60 ); // t/60 -- 小时部分, t%60 -- 分钟部分
return 0;
}
【运行结果】:
(2)求两数平均值
【思路】:
1、有什么样的变量来保存读进来的东西;
2、怎么计算;
【代码】:
#include <stdio.h>
int main()
{
int a,b;
double c;
scanf("%d %d",&a,&b);
c = (a + b) / 2.0;
printf("%d和%d的平均值=%lf\n",a,b,c);
return 0;
}
【运行结果】:
(3)运算符优先级
1、单目运算,只有一个算子。eg. -a;
2、单目运算符的优先级高于乘除:
eg.
a*-b; // 先算-b,再与a相乘;
3、相同优先级——从左到右→结合;
【例外】:单目运算符、赋值运算——从右向左结合;
4、【例题】:
10 + 9 * ((8 + 7) % 6) + 5 * 4 % 3 * 2 + 3 // 44
1 + 2 + (3 + 4) * ((5 * 6 % 7 / 8) - 9) * 10 //-627
5、嵌入式赋值:
int a = 6;
int b;
int c = 1 + (b=a);
缺点:不利于阅读;易产生错误;
6、举例:
result = a = b = c + 3; // 先算3+c,再赋给b,再赋给a,再赋给result;
result = (result = result * 2)*6*(result = 3+result); //最好不要用嵌入式赋值!!要拆开几个表达式
(4)交换变量
【程序】:
#include <stdio.h>
int main()
{
int a,b;
int c;
printf("请输入a与b的值:");
scanf("%d %d",&a,&b);
c = a;
a = b;
b = c;
printf("a=%d,b=%d\n",a,b);
return 0;
}
【运行结果】:
(5)复合运算符
① 复合赋值
1、包括+=
、-=
、*=
、/=
、%=
。
2、eg.
total += 5; // total = total + 5
total *= sum + 12; // total = total * (sum + 12) --- 先把右侧算完,再与total计算
② 递增、递减运算符
1、形式:++
;--
。是单目运算符。
2、如:
count ++; // count += 1 ;即 count = count + 1
a++; // a++的值是a加1以前的值
++a; //++a 的值是加了1以后的值
3、案例程序
#include <stdio.h>
int main()
{
int a=10;
printf("a++ =%d\n",a++);
printf("a = %d\n",a);
printf("++a = %d\n",++a);
printf("a = %d\n",a);
return 0;
}
6、程序案例——逆序的三位数
(1)题目内容:
逆序的三位数:
程序每次读入一个正三位数,然后输出逆序的数字。注意,当输入的数字含有结尾的0时,输出不应带有前导的0。比如输入700,输出应该是7。
提示:用%10可以得到个位数,用/100可以得到百位数…。将这样得到的三个数字合起来:百位100+十位10+个位,就得到了结果。
【代码】:
#include <stdio.h>
int main()
{
int a;
printf("请输入一个三位数:");
scanf("%d",&a);
printf("逆序数 = %d\n", (a%10)*100+(((a-(a/100)*100)/10)*10)+a/100);
// 得到十位数的方法也可以是:① a%100,再/10 ;或 ② a/10 再 %10
return 0;
}
二、判断与循环
1、判断
(1)引例程序
#include <stdio.h>
int main()
{
int hour1,minute1;
int hour2,minute2;
int ih,im;
scanf("%d %d",&hour1,&minute1);
scanf("%d %d",&hour2,&minute2);
ih = hour2 - hour1;
im = minute2 - minute1;
if(im<0){
im = 60+im;
ih --;
}
printf("时间差是%d小时%d分", ih, im );
return 0;
}
运行结果正确。
(2)C语言提供了六个关系运算符:
== 相等
!= 不相等
> 大于
>= 大于或等于
< 小于
<= 小于或等于
Note:
关系运算的结果只有2种:关系成立,结果是1;否则,是0.
① 优先级
1、所有的关系运算符的优先级比算术运算的低,但比赋值运算的高。
printf("%d", 7>=3+4 ); // 运行结果 = 1
2、判断是否相等的==
和!=
的优先级比其他的低,而连续的关系运算是【从左到右】结合的。
printf("%d", 6>5>4 ); // 运行结果 = 0
printf("%d", 5>3 == 6>4 ); // 运行结果 = 1
printf("%d", a == b == 6 ); //从左到右
(3)找零计算器——优化
#include <stdio.h>
int main()
{
// 初始化
int price = 0;
int bill = 0;
//读入金额和票面
printf("请输入金额:");
scanf("%d", &price);
printf("请输入票面:");
scanf("%d", &bill);
// 计算找零
if(bill>=price)
printf("应该找您:%d\n", bill - price);
else
printf("你的钱不够\n");
return 0;
}
(4)计算薪水:
#include <stdio.h>
int main()
{
const double RATE = 8.25; // 每小时的薪水
const int STANDARD = 40; // 一周的标准工作时间
double pay = 0.0;
int hours;
printf("请输入工作的小时数: ");
scanf("%d", &hours);
printf("\n");
if (hours > STANDARD)
pay = STANDARD * RATE + (hours-STANDARD) * (RATE * 1.5);
else
pay = hours * RATE;
printf("应付工资: %f\n", pay);
return 0;
}
(5)判断成绩
#include <stdio.h>
int main()
{
const int PASS=60;
int score;
printf("请输入成绩: ");
scanf("%d", &score);
printf("你输入的成绩是%d.\n", score);
if ( score < PASS )
printf("很遗憾,这个成绩没有及格。");
else {
printf("祝贺你,这个成绩及格了。");
printf("再见\n");
}
return 0;
}
2、循环
(1)判断数字的位数
① 四位数及以下:
#include<stdio.h>
int main()
{
int x;
int n = 1;
scanf("%d",&x);
if(x>999)
n = 4;
else if (x > 99)
n = 3;
else if(n > 9)
n = 2;
else
n = 1;
printf("%d\n",n);
return 0;
}
② 优化 —— 循环语句
#include<stdio.h>
int main()
{
int x;
int n = 0;
scanf("%d",&x);
n++;
x /= 10;
while (x > 0){
n++ ;
x /= 10;
}
printf("%d\n",n);
return 0;
}
【Note】:
程序中, ++n; x/=10;不能全写到while循环语句中。因为x = 0 时,n应该 = 1。
(2)do-while 循环
do
{
<循环体语句>
}while(<循环条件>);
【note】:
1、无论条件满不满足,do-while一定会做一遍;而while在条件不满足时,什么都不做。
③ do-while 循环进行优化
#include<stdio.h>
int main()
{
int x;
int n = 0;
scanf("%d",&x);
do
{
x /= 10;
n++ ;
} while(x > 0);
printf("%d\n",n);
return 0;
}
(3)for 循环
① 阶乘 运算
1、while 循环
#include<stdio.h>
int main()
{
int n;
int fact = 1;
int i = 1;
scanf("%d",&n);
while(i <= n){
fact *= i;
i++;
}
printf("%d!=%d\n",n,fact);
return 0;
}
2、for 循环
#include<stdio.h>
int main()
{
int n;
int fact = 1;
int i = 1;
scanf("%d",&n);
/* 从 1 乘到 n */
//for(i=1; i<=n; i++ ){ // ① 初始条件;② 循环条件;③ 增加步长
// fact *= i;
//}
for(i=n; i>1; i--){
// 从 n 乘到 1
fact *= i;
}
printf("%d!=%d\n",n,fact);
return 0;
}
for 语句也可写成如下,但只有C99编译能使用;
for( int i=1; i<=n; i++ ){
fact *= i;
}
【note】:
for 语句中的每个表达式都是可以省略的;但是分号不能省略!而
for(;条件;) == while(条件)
(4)三种循环选择 tips:
- 如果有固定次数,用
for
; - 如果必须执行一次,用
do_while
; - 其他情况用
while
;
三、进一步的循环与判断
1、逻辑类型和运算
(1)bool 类型
头文件:
#include<stdbool.h>
之后就可以使用bool 和 true 、false
(2)逻辑运算
- 逻辑运算的结果只有0 或 1 ;
- 运算符:
!
逻辑非;&&
逻辑与;||
逻辑或; - 优先级:
! > && > ||
- 如:x∈[4,6],写出x的表达式
!age < 20; // 单目运算符的优先级高于双目运算符,故 !age 只会 = 0 或1;一定 < 20.
a. 优先级总结
- 赋值永远最低; and 比 or 高一点;
- 关系运算比逻辑运算还要高
(3)条件运算符
count = (count > 20)?count-10:count
// 条件?条件满足时的值:条件不满足时的值
- 条件运算符优先级高于赋值运算符,但低于其它的运算符;
- 条件运算符是自右向左结合的
- 最好别用嵌套的条件运算表达式,可读性差!
(4)逗号运算符
- 用来连接两个表达式,并以其
右边
的表达式的值作为它的结果。 - 逗号运算符的优先级是最低的。
- 逗号自左向右结合
- 常在
for
中使用
for ( i=0,j=10; i<j; i++,j-- );
2、级联和嵌套的判断
(1)嵌套的 if-else
eg. 程序 —— 三个数大小的比较
#include<stdio.h>
#include<stdbool.h>
int main(){
int a,b,c,max;
scanf("%d %d %d",&a,&b,&c);
if(a>b){
if(a>c)
max = a;
else
max = c;
}
else{
if(b>c)
max = b;
else
max = c;
}
printf("The max is %d\n",max);
return 0;
}
【Note】:
- 缩进格式不能暗示
else
的匹配; - tips: 即使只有一条语句,if 和 else 后面也要用大括号{ }!易于理解!
(2)级联的 if-else
eg. 分段函数
int f;
if(x < 0){
f = -1;
}else if(x == 0){
f = 0;
}else{
f = x * 2;
}
3、多路分支
(1)if - else if - else……
#include<stdio.h>
int main(){
int type;
scanf("%d",&type);
if(type == 1)
printf("你好");
else if(type == 2)
printf("早上好");
else if(type == 3)
printf("晚上好");
else if(type == 4)
printf("再见");
else
printf("啊,什么呀?");
return 0;
}
(2)switch-case
#include<stdio.h>
int main(){
int type;
scanf("%d",&type);
switch (type){
case 1: // type == 1 时
printf("你好");
break;
case 2:
printf("早上好");
break;
case 3:
printf("晚上好");
break;
case 4:
printf("再见");
break;
default:
printf("啊,什么呀?");
break;
}
return 0;
}
【Note】:
switch(控制表达式){
case 常量:
语句
……
case 常量:
语句
……
default:
语句
……
}
- 控制表达式只能是整数型
int
的结果; - 常量可以是常数,也可以是常数计算的表达式;
- 遇到
break
才会跳出;
4、循环的程序案例
(1)循环计算 —— 计算log 以 2 为底,x 的对数
#include<stdio.h>
/* 计算log 以 2 为底,x 的对数*/
int main(){
int x,t;
int ret = 0;
scanf("%d",&x);
t = x;
while( t > 1 ){
t /= 2;
ret ++;
}
printf("log2 of %d is %d.\n", x, ret);
return 0;
}
(2)算平均数
【要求】:输入一系列正整数,最后输入 -1 表示输入结束,然后计算这些数字的平均数,输出输入的数字的个数和平均数。
【思路】:
- 变量 —> 算法 —> 流程图 —> 程序
【代码】:
#include<stdio.h>
/* 计算平均数 */
int main(){
int number;
int sum = 0;
int count = 0;
scanf("%d",&number);
while(number != -1){
sum += number;
count ++;
scanf("%d",&number);
}
/* do{
scanf("%d",&number);
if(number != -1){
sum += number;
count++; // count 表明从程序中读入了多少个数据;
}
}while (number != -1);
*/
printf("输入的%d个数的平均数 = %f\n", count, 1.0*sum / count);
return 0;
}
(3)猜数游戏
【需求】:计算机想一个数,让用户来猜。用户每输入一个数据,就告诉他大了还是小了,直到用户猜中为止。最后还要告诉他猜了多少次。
【思路】:
- 计算机随机想一个数,记在变量number中;
- 负责计数的变量 count 初始化为0;
- 让用户输入一个数字a;
- count++;
- 判断number与 a 的大小关系,如果 a 大,就输出“大”;如果 a 小,就输出 a “小”;
- 重复循环
- 否则,输出 “猜中” 和次数,结束。
【代码】:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
/* 猜数游戏 */
int main(){
srand(time(0));
int number = rand()%100+1; // 每次 召唤 rand() 就得到一个随机的整数;
// x % n 的结果是 [0,n-1] 的一个整数;
int a = 0;
int count = 0;
printf("我已经想好了一个 1-100 之间的数。\n");
do{
printf("请猜这个 1-100 之间的数:");
scanf("%d",&a);
count ++;
if (a > number){
printf("你猜的数大了。");
}else if(a < number){
printf("你猜的数小了。");
}
}while (a != number);
printf("太好了,你用了%d次就猜到了答案。\n", count);
return 0;
}
【Note】:
x % n
的结果是 [0,n-1] 的一个整数;- 每次 召唤
rand()
就得到一个随机的整数;
【运行结果】:
(4)整数求逆
【思路】:
- 对一个整数做
%10
的操作,就可的到 个位数; - 对一个整数做
/10
的操作,就去掉了个位数 - ……
【代码】:
#include<stdio.h>
/* 整数求逆 */
int main(){
int x,t;
int digit;
int ret = 0; // 初始时结果为0
/* 此时若输入为 700,则输出为 7 */
scanf("%d",&x);
t = x;
while(x > 0){
digit = x%10;
// printf("%d\n",digit);
ret = ret *10 + digit;
printf("x=%d, digit=%d, ret=%d\n", x, digit, ret);
x /= 10;
}
printf("整数%d的逆=%d。\n", t,ret);
return 0;
}
/* 此时若输入为 700,则输出为 007 */
scanf("%d",&x);
t = x;
while(x > 0){
digit = x%10;
printf("%d",digit);
ret = ret *10 + digit;
//printf("x=%d, digit=%d, ret=%d\n", x, digit, ret);
x /= 10;
}
四、循环控制
1、循环控制
(1)例程:判断一个数是不是素数
【需求】:读入一个数x,判断x是不是素数。
素数:只能被 1 和自己整除的数,不包括1.
【代码】:
#include<stdio.h>
/* 判断是否是素数 */
int main(){
int x;
int i;
int isPrime = 1; // isPrime = 1,则 x 是素数
scanf("%d",&x);
for (i=2; i<x; i++){
if( x % i == 0){
// 说明不是素数
isPrime = 0;
break; //break; --- 跳出循环 ; continue; --- 跳过这一轮循环,进入下一轮
}
}
if (isPrime == 1){
printf("是素数。\n");
} else {
printf("不是素数。\n");
}
return 0;
}
【Note】:
- break; ---- 跳出循环;
- continue; ---- 跳过这一轮循环中剩下的语句,进入下一轮
【或】:
/* 判断是否是素数 */
int main(){
int x;
int i;
// int isPrime = 1; // isPrime = 1,则 x 是素数
scanf("%d",&x);
for (i=2; i<x; i++){
if( x % i == 0){
// 说明不是素数
// isPrime = 0;
break; //break; --- 跳出循环 ; continue; --- 跳过这一轮循环,进入下一轮
}
}
// if (isPrime == 1){
if (i == x){
printf("是素数。\n");
} else {
printf("不是素数。\n");
}
- 优点:节省了程序变量的内存消耗。
- 缺点:程序有两个要求,一是要机器能读,二是人要能读。这种聪明的做法,逻辑不清晰,其他人读程序时会耗费更多的脑细胞,不利于后期程序移植、维护和团队合作。
2、多重循环
(1)例程:输出100以内的素数
【代码】:
#include<stdio.h>
/* 输出 1-100 的素数 */
int main(){
int x;
int i;
int isPrime = 1; // isPrime = 1,则 x 是素数
//scanf("%d",&x);
x = 6;
for (x=2; x<100; x++)
{
for (i=2; i<x; i++){
if( x % i == 0){
// 说明不是素数
isPrime = 0;
break; //break; --- 跳出循环 ; continue; --- 跳过这一轮循环,进入下一轮
}
}
if (isPrime == 1){
printf("%d ",x);
}
}
printf("\n");
return 0;
}
(2)例程②:输出前50个素数
【代码1】:
#include<stdio.h>
/* 输出前50个素数 */
int main(){
int x;
int i;
int isPrime = 1; // isPrime = 1,则 x 是素数
int cnt = 0; // 计数器
//scanf("%d",&x);
x = 2;
// for (x=2; x<100; x++)
while (cnt < 50)
{
for (i=2; i<x; i++){
if( x % i == 0){
// 说明不是素数
isPrime = 0;
break; //break; --- 跳出循环 ; continue; --- 跳过这一轮循环,进入下一轮
}
}
if (isPrime == 1){
printf("%d ",x);
cnt++;
}
x++;
}
printf("\n");
return 0;
}
【代码2】:
// for (x=2; x<100; x++)
// while (cnt < 50)
for (x=2; cnt<50; x++)
{
for (i=2; i<x; i++){
if( x % i == 0){
// 说明不是素数
isPrime = 0;
break; //break; --- 跳出循环 ; continue; --- 跳过这一轮循环,进入下一轮
}
}
if (isPrime == 1){
printf("%d ",x);
cnt++;
}
// x++;
}
printf("\n");
(3)凑硬币 _ 用1角、2角、5角的硬币凑出10元以下的金额
【代码①】:—— 接力 break
#include<stdio.h>
/* 凑硬币 _ 用1角、2角、5角的硬币凑出10元以下的金额 */
int main()
{
int x;
int one,two,five;
int exit = 0;
scanf("%d",&x);
for(one=1; one<x*10; one++){
// 1角最多是 x*10个
for(two=1; two < x*10/2; two++){
// 2角最多是 x*10/2 个
for(five=1; five < x*10/5;five++){
if(one + two*2 + five*5 == x*10){
printf("可以用%d个1角+%d个2角+%d个5角得到%d元\n",one,two,five,x);
exit = 1;
break; /* 【接力 break】*/
}
}
if(exit) break; // 相当于 exit ==1;
}
if(exit) break;
}
return 0;
}
【Note】:
- break 和 continue 只能对它所在的那层循环做;
【代码②】:—— goto
语句
- goto 语句非常适合用于,多重嵌套循环的内层循环直接跳到最外面的情形。
#include<stdio.h>
/* 凑硬币 _ 用1角、2角、5角的硬币凑出10元以下的金额 */
int main()
{
int x;
int one,two,five;
int exit = 0;
scanf("%d",&x);
for(one=1; one<x*10; one++){
// 1角最多是 x*10个
for(two=1; two < x*10/2; two++){
// 2角最多是 x*10/2 个
for(five=1; five < x*10/5;five++){
if(one + two*2 + five*5 == x*10){
printf("可以用%d个1角+%d个2角+%d个5角得到%d元\n",one,two,five,x);
goto out;
}
}
}
}
out:
return 0;
}
3、循环应用案例
(1)求前 n 项和
① f(n)=1+1/2+1/3+…1/n
【代码】:
#include<stdio.h>
/* 求前 n 项和 —— 1+1/2+1/3+…1/n */
int main()
{
int n;
int i;
double sum = 0.0;
scanf("%d",&n);
for (i=1; i<=n; i++){
sum += 1.0/i;
}
printf("f(%d)=%f\n",n,sum);
return 0;
}
② f(n)=1-1/2+1/3-1/4+…+1/n
#include<stdio.h>
/* 求前 n 项和 _f(n)=1-1/2+1/3-1/4+…+1/n */
int main()
{
int n;
int i;
double sum = 0.0;
int sign = 1;
scanf("%d",&n);
for (i=1; i<=n; i++){
sum += sign*1.0/i;
sign = -sign;
}
printf("f(%d)=%f\n",n,sum);
return 0;
}
【或】:直接把 sign 定义为 double 类型
主程序:
int n;
int i;
double sum = 0.0;
// int sign = 1;
double sign = 1.0;
scanf("%d",&n);
for (i=1; i<=n; i++){
sum += sign/i;
sign = -sign;
}
printf("f(%d)=%f\n",n,sum);
(2)求最大公约数
法①:枚举
【思路】:
- 设 t 为2;
- 如果 u 和 v 都能被 t 整除,则记下这个 t;
- t 加1后重复第二步,直到 t 等于 u 或 v;
- 那么,曾经记下的最大的可以同时整除 u 和v 的 t 就是 gcd。
【代码】:
#include<stdio.h>
/* 求最大公约数 */
int main()
{
int a,b;
int min;
int ret;
int i;
scanf("%d %d",&a,&b);
if (a<b){
min = a;
}else {
min = b;
}
for (i=1;i<min;i++){
// 从 1 开始,到 a 和 b 的最小值
if (a%i == 0){
// a 能被 i 整除
if ( b%i == 0){
// b 能被 i 整除
ret = i; // 目前的公约数
}
}
}
printf("%d和%d的最大公约数是%d\n",a, b, ret);
return 0;
}
法②:辗转相除法
【思路】:
- 如果b等于0,计算结束,a 就是最大公约数;
- 否则,计算 a 除以 b 的余数,让 a 等于 b ,而 b 等于那个余数;
- 回到第一步;
eg.
a b t // t—— 余数
12 18 12
18 12 6
12 6 0
6 0
【代码】:
#include<stdio.h>
/* 求最大公约数 */
int main()
{
int a,b;
int t;
scanf("%d %d",&a, &b);
while ( b!=0)
{
t = a%b;
a = b;
b = t;
printf("a=%d,b=%d,t=%d\n",a, b, t);
}
printf("gcd=%d.\n", a);
return 0;
}
【运行结果】:
(3)正序分解整数
- 输入一个非负整数,正序输出它的每一位数字;
- 输入:13425
- 输出:1 3 4 2 5
法①:先逆序,再逆序
【代码】:
#include<stdio.h>
/* 求最大公约数 —— 先逆序,再逆序,—— 只适用于末尾不是0的数*/
int main()
{
int x,d;
int t = 0;
scanf("%d",&x);
do{
/* 做【逆序】 */
d = x%10;
t = t*10 + d;
x /= 10; // x 右移一位
}while (x>0);
printf("x=%d,t=%d\n",x,t);
x = t;
do{
/* 【再逆序】 */
int d = x%10; // 得到最右边的一位
printf("%d",d);
if (x>=10){
printf(" "); // 使最后一轮时不输出空格
}
x /= 10; // x 右移一位
} while( x>0 );
printf("\n");
return 0;
}
【运行结果】:
法②:正序输出
【思路】:
eg.
x = 13425;
13425 / 10000 --> 1
13425 % 10000 --> 3425
10000 / 10 --> 1000
3425 / 1000 -->3
3425 % 1000 -->435
1000 / 10 -->100
425 / 100 -->4
425 % 100 -->25
100 /10 -->10
25 / 10 -->2
25 % 10 -->5
10 /10 -->1
5 / 1 -->5
5 % 1 -->5
1 /10 -->0
【代码】:
#include<stdio.h>
/* 求最大公约数 —— 正序输出 */
int main()
{
int d,x;
int mask = 1;
int t = 0;
int cnt = 0;
x = 13425;
t = x;
while (x > 9){
// 可以知道 x 的位数
x /= 10;
mask *= 10;
//cnt++;
}
printf("mask = %d\n",mask);
// mask = pow(10,cnt-1); // mask=10^(cnt-1)
do{
d = t / mask;
printf("%d",d);
if( mask >9 ){
printf(" ");
}
t %= mask;
mask /= 10;
// printf("x=%d,mask=%d,d=%d\n",x,mask,d);
}while ( mask >0 );
printf("\n");
return 0;
}
五、数组与函数
1、数组:
(1)引例:如何写一个程序计算用户输入的数字的平均数
① 不需要记录输入的每一个数
#include<stdio.h>
int main(){
int x;
double sum = 0;
int cnt = 0;
scanf("%d",&x); // 读入x
while(x != -1){
sum += x;
cnt ++;
scanf("%d",&x); // 读下一个数
}
if(cnt > 0 ){
printf("%f\n",sum / cnt);
}
return 0;
}
② 计算输入的平均数,并输出所有大于平均数的数
【思路】:
- 把输入的数都存下来
- 等输入完成后,算出平均数
- 再来判断每个数该不该输出
【如何记录很多的数】:
int num1,num2,num3……?
【数组】:
int number[100];
scanf("%d",&number[i]);
【代码】:
#include<stdio.h>
int main(){
int x;
double sum = 0;
int cnt = 0;
int number[100]; // 定义名为number的数组:数组里的每个单元都是 int;数组大小是100个。
scanf("%d",&x);
while(x != -1){
number[cnt] = x; // 对数组中的元素赋值
//
{
int i;
for ( i=0; i<=cnt; i++){
printf("%d\t",number[i]);
}
printf("\n");
}
//
sum += x;
cnt ++;
scanf("%d",&x);
}
if( cnt > 0 ){
// printf("%f\n", sum/cnt);
int i;
double average = sum/cnt;
for( i=0; i<cnt; i++ ){
if (number[i] > average) {
// 使用数组中的元素
printf("%d",number[i]); // 遍历数组中的元素
}
}
}
return 0;
}
(2)定义数组
<类型> 变量名称[元素变量];
如: int grades[100];
double weight[20];
【Note】:
- 元素数量必须是整数;
- C99之前:元素数量必须是编译时刻确定的字面量
- 长度为0的数组——可以存在,但是无用 int a[0];
【数组的特点】:
- 所有元素具有相同的数据类型;
- 一旦创建,不能改变大小;
- *(数组中的元素在内存中是连续依次排列的)
- 可以出现在赋值的左边或右边;
- 在赋值 左/右 边叫做 左/右 值
- 数组的每个单元就是数组类型的一个变量;
- 使用数组时,放在
[]
中的数字叫做 下标或索引,下标从0开始计数;
int a[10];
// 10个单元: a[0],a[1],……,a[9]
a[2] = a[1] + 6; // 把a[1]的值读出来,加上6后,写入到a[2]中去;
【注意】:
有效的下标范围:
- 一旦程序运行,越界的数组访问可能造成问题,导致程序崩溃
- segmentation fault
【案例程序】:
#include<stdio.h>
void f();
int main()
{
f();
return 0;
}
void f()
{
int a[10];
a[10] = 0; // a[10] 不是有效下标
}
程序优化:
问题:之前的程序是危险的,因为输入的数据可能超过100个。
优化:如果先让用户输入有多少数字要计算,可以用C99的新功能来实现。
(3)用数组做散列计算
【案例】:
写一个程序,输入数量不确定的 [0,9] 范围内的整数,统计每一种数字出现的次数,输入 -1 表示结束
【程序】:
#include<stdio.h>
int main(void)
{
const int number = 10; // 数组的大小
int x;
int count[number]; // count[i] 说明 i 这个数字出现了多少次
int i;
for( i=0; i<number; i++) {
// 初始化数组
count[i] = 0;
}
scanf("%d",&x);
while ( x!= -1 ){
if ( x>=0 && x<=9 ){
count[x] ++; // 运算
}
scanf("%d",&x);
}
for ( i=0; i<number; i++){
// 遍历数组
printf("%d:%d\n", i, count[i] ); // i 这个数字出现了多少次
}
return 0;
}
2、函数的定义与使用
(1)求素数的和
#include<stdio.h>
int main(void)
{
int m,n;
int sum = 0;
int cnt = 0;
int i;
scanf("%d %d",&m,&n);
// m=10,n=31;
if(m==1) m=2;
for ( i=m; i<=n; i++){
//判断 i是不是素数
int isPrime = 1;
int k;
for(k=2; k<i-1;k++){
if(i%k == 0){
isPrime = 0;
break;
}
}
if(isPrime){
sum += i;
cnt++;
}
}
printf("%d %d\n",cnt,sum);
return 0;
}
优化:调用函数
#include<stdio.h>
int isPrime(int i ) // 定义的函数
{
int ret = 1;
int k;
for(k=2; k<i-1;k++){
if(i%k == 0){
ret = 0;
break;
}
}
return ret;
}
int main(void)
{
int m,n;
int sum = 0;
int cnt = 0;
int i;
scanf("%d %d",&m,&n);
// m=10,n=31;
if(m==1) m=2;
for ( i=m; i<=n; i++){
//判断 i是不是素数
if(isPrime(i)){
sum += i;
cnt++;
}
}
printf("%d %d\n",cnt,sum);
return 0;
}
(2)求和:求出1到10、20到30和35到45的三个和
#include<stdio.h>
int main()
{
int i;
int sum;
for( i=1,sum=0; i<=10; i++ ){
sum += i;
}
printf("%d到%d的和是%d\n",1,10,sum);
for( i=20,sum=0; i<=30; i++ ){
sum += i;
}
printf("%d到%d的和是%d\n",20,30,sum);
for( i=35,sum=0; i<=45; i++ ){
sum += i;
}
printf("%d到%d的和是%d\n",35,45,sum);
return 0;
}
注意:
代码复制 是程序质量不良的表现。
优化:求和函数
#include<stdio.h>
void sum(int begin, int end)
{
int i;
int sum = 0;
for( i=begin; i<=end; i++){
sum += i;
}
printf("%d到%d的和是%d\n",begin,end,sum);
}
int main()
{
sum(1,10);
sum(20,30);
sum(35,45);
return 0;
}
(3)什么是函数?
函数是一块代码,接收零个或多个参数,做一件事情,并返回零个或一个值。
void sum(int begin, int end)
—— 函数头{}
里面的 —— 函数体sum
—— 函数名void
—— 返回类型 void类型不返回结果()
里的 —— 参数表
① 调用函数
- 函数名(参数值);
- ()起到了表示函数调用的重要作用
- 即使没有参数,也需要()
- 函数知道每一次是哪里调用它,会返回到正确的地方。
(4)从函数中返回
例一:
上述 素数求和 例子中:
int isPrime(int i ) // 定义的函数
{
int ret = 1;
int k;
for(k=2; k<i-1;k++){
if(i%k == 0){
ret = 0;
break;
}
}
return ret;
}
注意:
- 如果函数要返回一个结果,那就需要用
return
把那个结果交给调用它的地方。 - 上述函数,
isPrime
会返回一个int
类型的结果
例二:
int max(int a, int b)
{
int ret;
if (a>b){
ret = a;
}else (
ret = b;
)
return ret;
}
或者:(但最好用上面那种!)
int max(int a, int b)
{
// int ret;
if (a>b){
return a;
}else {
return b;
}
// return ret;
}
第二种虽然没有错,但是不符合单一出口的理念;
注意:
-
一旦遇到
return
,就会 ①停止函数的执行,②并送回一个值; -
两种写法:
-
return;
-
return 表达式;
-
一个函数里面可以出现多个
return
语句
如:
#include<stdio.h>
int max(int a, int b)
{
int ret;
if (a>b){
ret = a;
}else {
ret = b;
}
return ret;
}
int main()
{
int a,b,c;
a = 5;
b = 6;
c = max(10,12);
c = max(a,b);
c = max(c, 23);
printf("%d\n",max(a,b));
return 0;
}
① 没有返回值的函数:
void 函数名(参数表);
- 不能使用带值的
return
- 可以没有
return
- 调用的时候不能做返回值的赋值
- 注意:如果函数有返回值,则必须使用带值的
return
3、函数的参数和变量
(1)函数原型
- 要把函数写在main()上面 —— 因为C的编译器自上而下顺序分析代码!
- 或者,为了更直观,可以这样改动:
#include<stdio.h>
void sum(int begin, int end); // 函数的【原型声明declaration】
int main()
{
sum(1,10); // 如没有 声明,会猜测 int sum(int,int)
sum(20,30);
sum(35,45);
return 0;
}
void sum(int begin, int end) // 函数 定义
{
int i;
int sum = 0;
for( i=begin; i<=end; i++){
sum += i;
}
printf("%d到%d的和是%d\n",begin,end,sum);
}
- 函数头,以分号结尾,就构成了函数的原型;
- 函数原型的目的是告诉编译器这个函数长什么样①名称、②参数(数量及类型)、③返回类型;
- 旧标准习惯把函数原型写在调用它的函数里面;
- 现在一般写在函数前面
- 原型里可以不写参数的名字,但一般仍然写上;
(2)参数传递
- 可以传递给函数的值是表达式的结果,这包括
① 字面量[eg.10]、
② 变量[eg.a]、
③ 函数的返回值[eg.max(23,45)]、
④ 计算的结果[eg.23+45];
- 如果函数有参数,调用函数时必须传递给它数量、类型正确的值;
- C语言在调用函数时,只能传值给函数!
- 每个函数有自己的变量空间,参数也位于这个独立的空间中,和其他的函数没有关系!
- 过去,对于函数参数表中的参数,叫做“
形式参数
”;调用函数时给的值,叫做“实际参数
”; - 现在,我们把
形参
叫做参数
;把实参
叫做值
;
(3)本地变量
- 函数每次运行,就产生一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,称作
本地变量
; - 定义在函数内部的变量就是本地变量;
- 参数也是本地变量;
①变量的生存期和作用域
- 生存期:什么时候这个变量开始出现了,到什么时候它消亡了;
- 作用域:在代码的什么范围内可以访问这个变量(这个变量可以起作用);
- 对于本地变量,这两个问题的答案是统一的:大括号内 —— 块
② 本地变量的规则
- 本地变量是定义在块内的(大括号内);
1.它可以是定义在 函数的块 内;
2.也可以定义在 语句的块 内
- 程序进入这个块前,其中的变量不存在;
- 离开这个块,其中的变量就消失了
- 在块外面定义的变量,在里面仍然有效;
- 块里面定义了和外面同名的变量,则掩盖了外面的
- 不能在一个块内定义同名的变量
- 本地变量不会被默认初始化
- 参数在进入函数的时候就被初始化了!
(4)其他细节
① 没有参数时
√ void f(void); // 在参数表里放了 void,明确的告诉编译器,这个函数不接收任何参数;
还是?
× void f(); // 在传统C中,它表示 f函数的参数表未知,并不表示没有参数
② 逗号运算符
- 调用函数时,逗号和逗号运算符怎么区分?
- 调用函数时的圆括号里的逗号是标点符号,不是运算符
f(a,b) // 是标点
f((a,b)) // 是运算符
// 二者区别:到底是传了2个还是1个参数进去
③ 函数里的函数
- C语言不允许函数嵌套定义
- 可以在一个函数中放另一个函数的【声明】,但不能放另一个函数的【定义】
4、二维数组
(1)二维数组:
int a[3][5]; // 通常理解为a是一个3行5列的矩阵
① 二维数组的遍历
- 需要二重循环
- 外面的一层遍历行号;里面的一层遍历列号
- 如:
for ( i=0; i<3; i++){
for ( j=0; j<5; j++ ){
a[i][j] = i*j;
}
}
a[i][j]
表示第 i 行第 j 列上的单元a[i,j]
是一个表达式
(2)二维数组的初始化
int a[][5] = {
{
0,1,2,3,4}, // 看做 5个int的数组,作为 a[0];
{
2,3,4,5,6}, // 5个int的数组,作为a[1];
};
- 列数是必须给出的,行数可以由编译器来数;
- 每行一个{},逗号分隔
- 如果有省略的,表示补零
- 也可以用定位( * C99 ONLY)
(3)tic-tac-toe游戏(井字棋)
- 读入一个3×3的矩阵,矩阵中的数字为1表示该位置上有一个X,为0表示为O;
- 程序判断这个矩阵中是否有获胜的一方,输出表示获胜一方的字符X或O,或输出无人获胜;
const int size = 3;
int board[size][size]; // 定义3×3 的棋盘
int i,j;
int numOfX;
int numOfO;
int result = -1; // -1:没人赢,1:X赢,0:O赢
// 读入矩阵
for ( i=0; i<size; i++ ){
for( j=0; j<size; j++){
scanf("%d", &board[i][j]); // 读入数组中的每一个元素
}
}
// 检查行
for ( i=0; i<size && result == -1; i++){
numOfO = numOfX =0; // 初始化最开始的数量为0
for( j=0; j<size; j++){
// 判断每一列
if( board[i][j] == 1){
//行号不变,列号从0到size
numOfX ++;
}else{
numOfO ++;
}
}
if( numOfO ==size ){
result =0;
}else if(numOfX == size){
result = 1;
}
}
// 检查列
for ( result == -1 ){
for( j=0; j<size && result == -1; j++){
numOfO = numOfX =0;
for( i=0; i<size; i++){
if( board[i][j] == 1){
numOfX ++;
}else{
numOfO ++;
}
}
}
if( numOfO ==size ){
result =0;
}else if(numOfX == size){
result = 1;
}
}
// 检查对角线。。。。
六、数组运算
1、数组运算
(1)引例:
- 在一组给定的数据中,如何找出某个数据是否存在?
【代码】:
- 数组的集成初始化:
int a[] = {
2,4,6,7,1,3,5,9,11,13,23,14,32};
int a[13] = {
2};
// 2 0 0 0 0 0 0 0 0 0 0 0 0
- 集成初始化时的定位:
int a[10] = {
[0] = 2, [2] = 3, 6,
};
// a[0]=2; a[2]=3; a[3]=6; 其它的都为0;
(2)数组的大小
sizeof
给出整个数组所占据的内容的大小,单位是字节;
int a[] = {
[1]=2,4,[5]=6}; // 0 2 4 0 0 6
printf("%lu\n",sizeof(a)); // 24
printf("%lu\n",sizeof(a[0])); // 4
- 所以,数组的大小=
sizeof(a)
/sizeof(a[0])
; sizeof(a[0])
给出数组中单个元素的大小,于是相除就得到了数组的单元个数;
(3)数组的赋值
int a[] = {
[1]=2,4,[5]=6}; // 0 2 4 0 0 6
(False): int b[] = a; // 大错特错!!!!不行!!!!
- 数组变量本身不能被赋值;
- 要把一个数组的所有元素交给另一个数组,必须采用遍历!
int a[] = {
[1]=2,4,[5]=6}; // 0 2 4 0 0 6
for ( i=0; i<length; i++) {
b[i] = a[i];
}
- 通常都是使用
for
循环 - 常见错误——要避免:
- ① 循环结束条件是<=数组长度; ×××
- ② 离开循环后,继续使用 i 的值来做数组元素的下标!×××
注意:
数组作为函数参数时,往往必须再用另一个参数来传入数组的大小!- 因为数组作为函数参数时:① 不能在 [ ] 中给出数组的大小; ② 不能再利用 sizeof 来计算数组的元素个数!
2、搜索
- 在一个数组中找到某个数的位置(或确认是否存在);
- 基本方法:遍历
(1)线性搜索 —— 例子①:
#include<stdio.h>
int search(int key, int a[], int len) // len 来表示数组有多大
{
int ret = -1;
for ( int i=0; i<len; i++)
{
if (key == a[i] )
{
ret = i;
break;
}
}
return ret;
}
int main()
{
int a[] = {
1,3,4,5,12,14,23,6,9,45};
int r = search(12, a, sizeof(a)/sizeof(a[0]));
printf("%d\n", r);
return 0;
}
(2)线性搜索 —— 例子②:
#include<stdio.h>
int amount[] = {
1,5,10,25,50};
char *name[] = {
"penny","nickel","dime","quarter","half-dollar"};
int search(int key, int a[], int len) // len 来表示数组有多大
{
int ret = -1;
for ( int i=0; i<len; i++)
{
if (key == a[i] )
{
ret = i;
break;
}
}
return ret;
}
int main()
{
int k = 25;
int r = search(k, amount, sizeof(amount)/sizeof(amount[0]));
if ( r > -1)
{
printf("%s\n",name[r]);
}
return 0;
}
改进:把面额和名字放的比较近
#include<stdio.h>
int amount[] = {
1,5,10,25,50};
char *name[] = {
"penny","nickel","dime","quarter","half-dollar"};
struct {
int amount;
char *name;
} coins[] = {
{
1,"penny"},
{
5,"nickel"},
{
10,"dime"},
{
25,"quarter"},
{
50,"half-dollar"}
};
int search(int key, int a[], int len) // len 来表示数组有多大
{
int ret = -1;
for ( int i=0; i<len; i++)
{
if (key == a[i] )
{
ret = i;
break;
}
}
return ret;
}
int main()
{
int k = 10;
// int r = search(k, amount, sizeof(amount)/sizeof(amount[0]));
for ( int i=0; i<sizeof(coins)/sizeof(coins[0]); i++ )
{
if ( k == coins[i].amount ){
printf("%s\n", coins[i].name);
break;
}
}
return 0;
}
(3)二分搜索
- 前提:排好序!
【思路】:
【程序】:
#include<stdio.h>
int search(int key, int a[], int len) // len 来表示数组有多大
{
int ret = -1;
int left = 0;
int right = len-1;
while ( left < right )
{
int mid = (left+right)/2;
if( a[mid] == k )
{
ret = mid;
break;
} else if ( a[mid] > k)
{
right = mid-1;
} else{
left = mid+1;
}
}
return ret;
}
int main()
{
return 0;
}
3、排序初步 —— 选择排序
- 给定无序的数组,如何排列成有序?
【思路】:
- 找到最大的数
- 最大的数和最后一位做交换,即
swap a[maxid],a[len-1];
- 再从剩下的当中找到最大的
- 再如法炮制
- ……
- 直到剩下最后的两个数为止
【代码】:
#include<stdio.h>
int max ( int a[], int len)
{
int maxid = 0;
for (int i=1; i<len ; i++ )
{
if( a[i]> a[maxid] )
{
maxid = i;
}
}
return maxid;
}
int main()
{
int a[] = {
2,45,6,12,87,34,90,24,23,11,65};
int len = sizeof(a)/sizeof(a[0]);
for ( int i=len-1; i>0 ; i-- )
{
int maxid = max(a, i+1);
// swap a[maxid],a[len-1]
int t = a[maxid];
a[maxid] = a[i];
a[i] = t;
}
for (int i=0; i<len; i++)
{
printf("%d ",a[i]);
}
return 0;
}
七、指针与字符串
1、指针
(1)取地址运算:&运算符取得变量的地址
① sizeof
- 是一个运算符,给出某个类型或变量在内存中所占据的字节数
#include<stdio.h>
int main()
{
int a;
a = 6;
printf("sizeof(int) = %ld\n", sizeof(int)); // sizeof(int) = 4;int在内存中占4个字节,也就是32个bit
printf("sizeof(a) = %ld\n", sizeof(a)); // sizeof(a) = 4
printf("sizeof(double) = %ld\n", sizeof(double)); // sizeof(double) = 8
return 0;
}
② 运算符&
scanf("%d",&i);
里的&
(2)指针:指针变量就是记录地址的变量
① 指针 —— 就是保存地址的变量
int i;
int* p = &i; // 表示p是一个指针,它指向的是一个 int,把i的地址交给了指针p
int* p,q; // 表示p是一个指针;q是一个普通的 int类型的变量
int *p,q; // 表示p是一个指针;q是一个普通的 int类型的变量
//【无论 * 是靠近 int 还是 p,都是一样的!!】
【无论 * 是靠近 int 还是 p,都是一样的!!】
② 指针变量
- 变量的值是内存的地址
- 普通变量的值是实际的值;
- 指针变量的值是具有实际值变量的地址;
③ 作为参数的指针
void f(int *p);
f 函数要一个int的指针- 在被调用的时候得到了某个变量的地址:
int i=0;f(&i);
- 在函数里面可以通过这个指针访问外面这个 i
#include<stdio.h>
void f(int *p);
void g(int k);
int main(void)
{
int i=6;
printf("&i=%p\n", &i);
f(&i);
g(i);
return 0;
}
void f(int *p)
{
printf(" p=%p\n", p);
}
void g(int k)
{
printf("k=%d\n", k); // k = 6
}
④ 访问那个地址上的变量*
- *是一个单目运算符,用来访问指针的值所表示的地址上的变量
- 可以做右值,也可以做左值 —— 即*可以放在赋值号的右边读它的值,也可以放在赋值号的左边去写它的值。
#include<stdio.h>
void f(int *p);
void g(int k);
int main(void)
{
int i=6;
printf("&i=%p\n", &i);
f(&i);
g(i);
return 0;
}
void f(int *p)
{
printf(" p=%p\n", p);
printf("*p=%d\n", *p); // 把 *p 看做一个整体,是int —— *p = 6;
*p = 26; // 此时,k = 26 —— 说明在经历了f函数的调用之后,i的值被改了
}
void g(int k)
{
printf("k=%d\n", k);
}
(3)指针与数组:为什么数组传进函数后,sizeof不对了?
- 函数参数表里的数组,实际上是指针!
void minmax( int a[], int len, int *min, int *max)
// 上面的 int a[] ———— 指针!
// 也可以写成 int *a;
sizeof(a) == sizeof(int*);
- 但是可以用数组的运算符
[]
进行运算 - 以下四种函数原型在参数表中出现,是等价的:
int sum(int *ar, int n);
int sum(int *, int);
int sum(int ar[], int n);
int sum(int [], int);
① 数组变量是特殊的指针
- 数组变量本身表达地址,所以:
- int a[10];int *p=a; // 无需用 & 取地址
- 但是数组的单元表达的是变量,需要用 & 取地址
- a == &a[0];
[]
运算符可以对数组做,也可以对指针做:
- p[0] <==> a[0]
*
运算符可以对指针做,也可以对数组做:
*a = 25;
- 数组变量是 const 的指针,所以不能被赋值
int b[]; // 可以看做是 int * const b;
2、字符类型
(1)字符类型
- char 是一种整数,也是一种特殊的类型:字符。
这是因为:
- 用单引号表示的字符字面量:‘a’,‘1’
"
也是一个字符;- printf 和scanf 里用 %c 来输入输出字符
① 字符的输入输出:
#include<stdio.h>
int main()
{
char c;
char d;
c = 1;
d = '1';
if ( c==d ){
// 不相等
printf("相等\n");
} else{
printf("不相等\n");
}
printf("c=%d\n", c); // c=1
printf("d=%d\n", d); // d=49
return 0;
}
- 如何输入 ‘1’ 这个字符给 char c ?
scanf("%c", &c); -->1
scanf("%d", &i); c=i; -->49
- ‘1’的ASCII编码是49,所以当 c==49时,它代表’1’;
#include<stdio.h>
int main()
{
char c;
//scanf("%c",&c); // 输入: 1
//printf("c=%d\n", c); // c=49
//printf("c='%c'\n", c); // c='1'
int i;
scanf("%d",&i); // 输入: 1 or [49]
c = i;
printf("c=%d\n", c); // c=1 or [49]
printf("c='%c'\n", c); // c=' ' or ['1']
return 0;
}
② 混合输入:
- 有何不同?
scanf("%d %c",&i,&c);
scanf("%d%c",&i,&c);
- scanf(“%d %c”,&i,&c); :
#include<stdio.h>
int main()
{
char c;
int i;
scanf("%d %c",&i,&c);
printf("i=%d, c=%d, c='%c'\n", i, c, c);
return 0;
}
- scanf(“%d%c”,&i,&c); :
#include<stdio.h>
int main()
{
char c;
int i;
scanf("%d%c",&i,&c);
printf("i=%d, c=%d, c='%c'\n", i, c, c);
return 0;
}
- 即在 %d 后面,如果没有空格,它的意思是说,整数只读到整数结束为止,后面那个给下面哪个;
- 如果是有空格的,整数读到空格以后,还要把后面的空格都读掉!
- 因而,有没有空格是不一样的!
③ 大小写转换
a+'a'-'A'
可以把大写字母变成小写字母;a+'A'-'a'
小写字母变成大写!
(2)逃逸字符
- 用来表达无法印出来的控制字符或特殊字符,它由一个反斜杠
“/”
开头,后面跟上另一个字符,这两个字符合起来,组成了一个字符;
printf("请分别输入身高的英尺和英寸,"
"如输入\"5 7\"表示5英尺7英寸:");
字符 | 意义 |
---|---|
\b |
回退一格 |
\t |
到下一个表格位 |
\n |
换行 |
\r |
回车 |
\" |
双引号 |
\' |
单引号 |
\\ |
反斜杠本身 |
① \b
- 回去一格,让下一个输出回到这个位置上去
- 但是如果不输出东西,那就没什么变化
- 如果输出东西,那就把刚刚的东西给覆盖掉了
- 通常就是回去昂,但不删除;
- 但不可否认,可能有终端软件,运行时会删除
②\t
- 每行的固定位置
- 一个 \t 使得输出从下一个制表位开始
- 用 \t 才能使得上下两行对齐
3、字符串
(1)字符串
- C语言的字符串是以字符数组的形态存在的;
- 不能用运算符对字符串做运算
- 通过数组的方式可以遍历字符串
- 唯一特殊的地方是字符串字面量可以用来初始化字符数组
- 以及标准库提供了一系列字符串函数
① 字符数组
char word[] = {
'H','e','l','l','o','!'};
// 但 这不是C语言的字符串,因为不能用字符串的方式做计算!!
char word[] = {
'H','e','l','l','o','!','\0'}; // 是字符串!
② 字符串
- 以0(整数0)结尾的一串字符
- 0或’\0’ 是一样的,但是和’0’不同
- 0标志字符串的结束,但它不是字符串的一部分
- 计算字符串的长度时不包含这个0
- 字符串以数组的形式存在,以数组或指针的形式访问。更多的是以指针的形式
- string.h 里有很多处理字符串的函数
③ 字符串变量
char *str = "hello";
char word[] = "hello";
char line[10] = "hello"; // 占据6个字节的line空间 —— 因为还有个结尾的0
④ 字符串常量
- “hello”
- “hello” 会被变成一个字符数组放在某处,这个数组的长度是6,结尾还有表示结束的0;
- 两个相邻的字符串常量会被自动连接起来;