c语言笔记

文章目录

概念

VS项⽬和源⽂件、头⽂件介绍

在这里插入图片描述
在这里插入图片描述

printf函数也可以⽤来打印其他类型的数据,⽐如:

int n = 100;
printf("%d\n", n); //printf打印整型
printf("%c\n", 'q'); //printf打印字符
printf("%lf\n", 3.14); //printf打印双精度浮点型

⼀个系列的库函数⼀般会声明在同⼀个头⽂件中,所以库函数的使⽤,要包含对应的头⽂件。https://cplusplus.com/reference/clibrary/

auto break case char const continue default do double else enum extern
float for goto if int long register return short signed sizeof static
struct switch typedef union unsigned void volatile while

注:https://zh.cppreference.com/w/c/keyword(C语⾔关键字的全部介绍)

ASCII码表

参考:https://zh.cppreference.com/w/cpp/language/ascii

我们不需要记住所有的ASCII码表中的数字,使⽤时查看就可以,不过我们最好能掌握⼏组特殊的数据:

•字符AZ的ASCII码值从6590

•字符az的ASCII码值从97122

•对应的⼤⼩写字符(a和A)的ASCII码值的差值是32

•数字字符09的ASCII码值从4857

•换⾏\n的ASCII值是:10

•在这些字符中ASCII码值从0~31 这32个字符是不可打印字符,⽆法打印在屏幕上观察

字符串和\0

使⽤双引号括起来的⼀串字符就被称为字符串,如:“abcdef”,就是⼀个字符串
字符串的打印格式可以使⽤ %s 来指定,也可以直接打印如下:

#include <stdio.h>

int main()
{
	printf("%s\n", "hello C");
	printf("hello c");
	return 0;
}

在这里插入图片描述

C语⾔中也可以把⼀个字符串放在⼀个字符数组中,我们在这⾥利⽤下⾯的代码验证⼀下 \0 的功能。

1 #include <stdio.h>
2
3 int main()
4 {
5 	char arr1[] = {'a', 'b', 'c'};//arr1数组中存放3个字符
6 	char arr2[] = "abc"; //arr2数组中存放字符串
7 	printf("%s\n", arr1);
8 	printf("%s\n", arr2);
9 
10 	return 0;
11 }

在这里插入图片描述
在这里插入图片描述

转义字符

在这里插入图片描述
在这里插入图片描述

**转义字符参考:**https://zh.cppreference.com/w/c/language/escape

语句和语句分类

1.空语句

空语句是最简单的,⼀个分号就是⼀条语句,是空语句。

#include <stdio.h>
int main()
{
 	;//空语句
 	return 0;
}

空语句,⼀般出现的地⽅是:这⾥需要⼀条语句,但是这个语句不需要做任何事,就可以写⼀个空语句。

2.表达式语句

表达式语句就是在表达式的后边加上分号。如下所⽰:

#include <stdio.h>
int main()
{
 	 int a = 20;
	 int b = 0;
	 b = a + 5; //表达式语句
	 return 0;
}

3.函数调⽤语句

函数调⽤的时候,也会加上分号,就是函数调⽤语句。

#include <stdio.h>
int Add(int x, int y)
{
	 return x+y;
}
	int main()
{
	 printf("hehe\n");//函数调⽤语句
	 int ret = Add(2, 3);//函数调⽤语句 
	 return 0;
}

4.复合语句

复合语句其实就是前⾯讲过的代码块,成对括号中的代码就构成⼀个代码块,也被称为复合语句。

#include <stdio.h>
void print(int arr[], int sz) //函数的⼤括号中的代码也构成复合语句
{
	 int i = 0;
	 for(i=0; i<sz; i++)
 {
	 printf("%d ", arr[i]);
 }
}
	int main()
{
	 int i = 0;
	 int arr[10] = {0};
	 for(i=0; i<10; i++) //for循环的循环体的⼤括号中的就是复合语句
 {
	 arr[i] = 10-i;
	 printf("%d\n", arr[i]);
 }
	 return 0;
}

5.控制语句

控制语句⽤于控制程序的执⾏流程,以实现程序的各种结构⽅式(C语⾔⽀持三种结构:顺序结构、选择结构、循环结构),它们由特定的语句定义符组成,C语⾔有九种控制语句。

可分成以下三类:

  1. 条件判断语句也叫分⽀语句:if语句、switch语句;
  2. 循环执⾏语句:do while语句、while语句、for语句;
  3. 转向语句:break语句、goto语句、continue语句、return语句。

注释

编译时,注释会被替换成⼀个空格,所以 min/* 这⾥是注释*/Value 会变成 min Value ,⽽不是 minValue 。

数据类型介绍

1.字符型

char //character
[signed] char //有符号的
unsigned char //⽆符号的

2.整型

//短整型
short [int]
[signed] short [int]
unsigned short [int]
//整型
int
[signed] int
unsigned int
//⻓整型
long [int]
[signed] long [int]
unsigned long [int]
//更⻓的整型
//C99中引⼊
long long [int]
[signed] long long [int]
unsigned long long [int]

3.浮点型

float
double
long double

4.布尔类型

在这里插入图片描述

#define bool _Bool
#define false 0
#define true 1

_Bool flag = true;
if (flag)
 printf("i like C\n");

各种数据类型的⻓度

1.sizeof 操作符

sizeof 是⼀个关键字,也是操作符,专⻔是⽤来计算sizeof的操作符数的类型⻓度的,单位是字节。
sizeof 操作符的操作数可以是类型,也可是变量或者表达式

sizeof( 类型 )
sizeof 表达式

sizeof 的操作数如果不是类型,是表达式的时候,可以省略掉后边的括号的。
sizeof 后边的表达式是不真实参与运算的,根据表达式的类型来得出⼤⼩。
sizeof 的计算结果是 size_t 类型的。

在这里插入图片描述
比如:

#include <stdio.h>
int main()
{
 int a = 10;
 printf("%zd\n", sizeof(a));
 printf("%zd\n", sizeof a);//a是变量的名字,可以省略掉sizeof后边的()
 printf("%zd\n", sizeof(int));
 printf("%zd\n", sizeof(3 + 3.5));
 return 0;
}

2.数据类型⻓度

#include <stdio.h>
int main()
{
 printf("%zd\n", sizeof(char));
 printf("%zd\n", sizeof(_Bool));
 printf("%zd\n", sizeof(short));
 printf("%zd\n", sizeof(int));
 printf("%zd\n", sizeof(long));
 printf("%zd\n", sizeof(long long));
 printf("%zd\n", sizeof(float));
 printf("%zd\n", sizeof(double));
 printf("%zd\n", sizeof(long double));
 return 0;
}

输出:

1
1
2
4
4
8
4
8
8

3.sizeof 中表达式不计算

//测试:sizeof中表达式不计算
#include <stdio.h>
int main()
{
 short s = 2;
 int b = 10;
 printf("%d\n", sizeof(s = b+1));
 printf("s = %d\n", s);
 return 0;
}

sizeof 在代码进⾏编译的时候,就根据表达式的类型确定了,类型的常⽤,⽽表达式的执⾏却要在程序运⾏期间才能执⾏,在编译期间已经将sizeof处理掉了,所以在运⾏期间就不会执⾏表达式了。

signed 和 unsigned

C 语⾔使⽤ signed 和 unsigned 关键字修饰 字符型和整型 类型的。
signed 关键字,表⽰⼀个类型带有正负号,包含负值;
unsigned 关键字,表⽰该类型不带有正负号,只能表⽰零和正整数。
对于 int 类型,默认是带有正负号的,也就是说 int 等同于 signed int 。
由于这是默认情况,关键字 signed ⼀般都省略不写,但是写了也不算错。

int 类型也可以不带正负号,只表⽰⾮负整数。这时就必须使⽤关键字 unsigned 声明变量。

unsigned int a;

整数变量声明为 unsigned 的好处是,同样⻓度的内存能够表⽰的最⼤整数值,增⼤了⼀倍。
⽐如,16位的 signed short int 的取值范围是:-32768~32767,最⼤是32767;⽽
unsigned short int 的取值范围是:0~65535,最⼤值增⼤到了65,535。32位的 signed int 的取值范围可以参看 limits.h 中给出的定义。

下⾯的定义是VS2022环境中,limits.h中相关定义。

1 #define SHRT_MIN (-32768) //有符号16位整型的最⼩值
2 #define SHRT_MAX 32767 //有符号16位整型的最⼤值
3 #define USHRT_MAX 0xffff //⽆符号16位整型的最⼤值
4 #define INT_MIN (-2147483647 - 1) //有符号整型的最⼩值
5 #define INT_MAX 2147483647 //有符号整型的最⼤值

unsigned int ⾥⾯的 int 可以省略,所以上⾯的变量声明也可以写成下⾯这样。

unsigned a;

字符类型 char 也可以设置 signed 和 unsigned 。

signed char c; // 范围为 -128 到 127
unsigned char c; // 范围为 0 到 255

注意,C 语⾔规定 char 类型默认是否带有正负号,由当前系统决定。
这就是说, char 不等同于 signed char ,它有可能是 signed char ,也有可能是
unsigned char 。
这⼀点与 int 不同, int 就是等同于 signed int 。

数据类型的取值范围

上述的数据类型很多,尤其数整型类型就有short、int、long、long long 四种
其实每⼀种数据类型有⾃⼰的取值范围,也就是存储的数值的最⼤值和最⼩值的区间,有了丰富的类型,我们就可以在适当的场景下去选择适合的类型。如果要查看当前系统上不同数据类型的极限值:
limits.h ⽂件中说明了整型类型的取值范围。
float.h 这个头⽂件中说明浮点型类型的取值范围。
为了代码的可移植性,需要知道某种整数类型的极限值时,应该尽量使⽤这些常量。
• SCHAR_MIN , SCHAR_MAX :signed char 的最⼩值和最⼤值。
• SHRT_MIN , SHRT_MAX :short 的最⼩值和最⼤值。
• INT_MIN , INT_MAX :int 的最⼩值和最⼤值。
• LONG_MIN , LONG_MAX :long 的最⼩值和最⼤值。
• LLONG_MIN , LLONG_MAX :long long 的最⼩值和最⼤值。
• UCHAR_MAX :unsigned char 的最⼤值。
• USHRT_MAX :unsigned short 的最⼤值。
• UINT_MAX :unsigned int 的最⼤值。
• ULONG_MAX :unsigned long 的最⼤值。
• ULLONG_MAX :unsigned long long 的最⼤值。

变量

1.创建变量

类型是⽤来创建变量的。C语⾔中把经常变化的值称为变量,不变的值称为常量。
变量创建的语法形式是这样的:

data_type name;
    |       |
    |       |
数据类型 变量名
int age; //整型变量
char ch; //字符变量
double weight; //浮点型变量

变量在创建的时候就给⼀个初始值,就叫初始化。

int age = 18;
char ch = 'w';
double weight = 48.0;
unsigned int height = 100;

2.变量的分类

• 全局变量:在⼤括号外部定义的变量就是全局变量
全局变量的使⽤范围更⼴,整个⼯程中想使⽤,都是有办法使⽤的。
• 局部变量:在⼤括号内部定义的变量就是局部变量
局部变量的使⽤范围是⽐较局限,只能在⾃⼰所在的局部范围内使⽤的。

#include <stdio.h>
int global = 2023;//全局变量
int main()
{
 int local = 2018;//局部变量
 printf("%d\n", local);
 printf("%d\n", global);
  return 0;
}

其实当局部变量和全局变量同名的时候,局部变量优先使⽤。

算术操作符:+、-、*、/、%

C语⾔中为了⽅便运算,提供了⼀系列操作符,其中有⼀组操作符叫:算术操作符。分别是: + - * / % ,这些操作符都是双⽬操作符。
注:操作符也被叫做:运算符,是不同的翻译,意思是⼀样的。

1.+ 和 -

+和 - ⽤来完成加法和减法。
+和 - 都是有2个操作数的,位于操作符两端的就是它们的操作数,这种操作符也叫双⽬操作符。

#include <stdio.h>
int main()
{
 int x = 4 + 22;
 int y = 61 - 23;
 printf("%d\n", x);
 printf("%d\n", y);
 return 0;
}

2.*

运算符 * ⽤来完成乘法。

#include <stdio.h>
int main()
{
 int num = 5;
 printf("%d\n", num * num); // 输出 25
 return 0;
}

3./

运算符 / ⽤来完成除法。
除号的两端如果是整数,执⾏的是整数除法,得到的结果也是整数。

#include <stdio.h>
int main()
{
 float x = 6 / 4;
 int y = 6 / 4;
 printf("%f\n", x); // 输出 1.000000
 printf("%d\n", y); // 输出 1
 return 0;
}

上⾯⽰例中,尽管变量 x 的类型是 float (浮点数),但是 6 / 4 得到的结果是 1.0 ,⽽不是1.5 。原因就在于 C 语⾔⾥⾯的整数除法是整除,只会返回整数部分,丢弃⼩数部分。
如果希望得到浮点数的结果,两个运算数必须⾄少有⼀个浮点数,这时 C 语⾔就会进⾏浮点数除法。

#include <stdio.h>
int main()
{
 float x = 6.0 / 4; // 或者写成 6 / 4.0
 printf("%f\n", x); // 输出 1.500000
 return 0;
}

上⾯⽰例中, 6.0 / 4 表⽰进⾏浮点数除法,得到的结果就是 1.5 。

4.%

运算符 % 表⽰求模(余)运算,即返回两个整数相除的余值。这个运算符只能⽤于整数,不能⽤于浮点数。

#include <stdio.h>
int main()
{
 int x = 6 % 4; // 2
 return 0;
}

负数求模的规则是,结果的正负号由第⼀个运算数的正负号决定。

#include <stdio.h>
int main()
{
 printf("%d\n", 11 % -5); // 1
 printf("%d\n",-11 % -5); // -1
 printf("%d\n",-11 % 5); // -1
 return 0;
}

上⾯⽰例中,第⼀个运算数的正负号( 11 或 -11 )决定了结果的正负号。

赋值操作符:=和复合赋值

在变量创建的时候给⼀个初始值叫初始化,在变量创建好后,再给⼀个值,这叫赋值。

int a = 100;//初始化
a = 200;//赋值,这⾥使⽤的就是赋值操作符

1.连续赋值

赋值操作符也可以连续赋值,如:

int a = 3;
int b = 5;
int c = 0;
c = b = a+3;//连续赋值,从右向左依次赋值的。

2.复合赋值符

int a = 10;
a += 3;
a -= 2;

C语⾔中提供了复合赋值符,⽅便我们编写代码,这些赋值符有:

1 += -=
2 *= /= %=
3 >>= <<=
4 &= |= ^=

单⽬操作符:++、–、+、-

前⾯介绍的操作符都是双⽬操作符,有2个操作数的。C语⾔中还有⼀些操作符只有⼀个操作数,被称
为单⽬操作符。 ++、–、+(正)、-(负) 就是单⽬操作符的。

1.++和–

++是⼀种⾃增的操作符,⼜分为前置++和后置++,–是⼀种⾃减的操作符,也分为前置–和后置–.
前置++

int a = 10;
2 int b = ++a;//++的操作数是a,是放在a的前⾯的,就是前置++
3 printf("a=%d b=%d\n",a , b);

计算口诀:先+1,后使⽤;
a原来是10,先+1,后a变成了11,再使⽤就是赋值给b,b得到的也是11,所以计算技术后,a和b都
是11,相当于这样的代码:

1 int a = 10;
2 a = a+1;
3 b = a;
4 printf("a=%d b=%d\n",a , b);

后置++

1 int a = 10;
2 int b = a++;//++的操作数是a,是放在a的后⾯的,就是后置++
3 printf("a=%d b=%d\n",a , b);

计算⼝诀:先使⽤,后+1
a原来是10,先使⽤,就是先赋值给b,b得到了10,然后再+1,然后a变成了11,所以直接结束后a是
11,b是10,相当于这样的代码:

int a = 10;
int b = a;
a = a+1;
printf("a=%d b=%d\n",a , b);

前置–
计算⼝诀:先-1,后使⽤

int a = 10;
int b = --a;//--的操作数是a,是放在a的前⾯的,就是前置--
printf("a=%d b=%d\n",a , b);//输出的结果是:9 9

后置–
计算⼝诀:先使⽤,后-1

int a = 10;
int b = a--;//--的操作数是a,是放在a的后⾯的,就是后置--
printf("a=%d b=%d\n",a , b);//输出的结果是:9 10

2.+ 和 -

这⾥的+是正号,-是负号,都是单⽬操作符。
运算符 + 对正负值没有影响,是⼀个完全可以省略的运算符,但是写了也不会报错。

1 int a = +10; 等价于 int a = 10;

运算符 - ⽤来改变⼀个值的正负号,负数的前⾯加上 - 就会得到正数,正数的前⾯加上 - 会得到负
数。

int a = 10;
int b = -a;
int c = -10;
printf("b=%d c=%d\n", b, c);//这⾥的b和c都是-10
int a = -10;
int b = -a;
printf("b=%d\n", b);

强制类型转换

int a = 3.14;
//a的是int类型, 3.14是double类型,两边的类型不⼀致,编译器会报警告

为了消除这个警告,我们可以使⽤强制类型转换:

1 int a = (int)3.14;//意思是将3.14强制类型转换为int类型,这种强制类型转换只取整数部分

scanf 和 printf 介绍

1.printf

printf() 的作⽤是将参数⽂本输出到屏幕。它名字⾥⾯的 f 代表 format (格式化),表⽰可以定制输出⽂本的格式。

#include <stdio.h>
int main(void)
{
 printf("Hello World");
 return 0;
}

printf() 是在标准库的头⽂件 stdio.h 定义的。使⽤这个函数之前,必须在源码⽂件头部引⼊这个头⽂件。

占位符

printf() 可以在输出⽂本中指定占位符。
所谓 “占位符”,就是这个位置可以⽤其他值代⼊。

// 输出 There are 3 apples
#include <stdio.h>
int main()
{
 printf("There are %d apples\n", 3);
 return 0;
}

上⾯⽰例中, There are %d apples\n 是输出⽂本,⾥⾯的 %d 就是占位符,表⽰这个位置要⽤其他值来替换。占位符的第⼀个字符⼀律为百分号 % ,第⼆个字符表⽰占位符的类型, %d 表⽰这⾥代⼊的值必须是⼀个整数。
printf() 的第⼆个参数就是替换占位符的值,上⾯的例⼦是整数 3 替换 %d 。执⾏后的输出结果就是 There are 3 apples 。

常⽤的占位符除了 %d ,还有 %s 表⽰代⼊的是字符串。

#include <stdio.h>
int main()
{
 printf("%s will come tonight\n", "zhangsan");
 return 0;
}

上⾯⽰例中, %s 表⽰代⼊的是⼀个字符串,所以 printf() 的第⼆个参数就必须是字符串,这个例⼦是 zhangsan 。执⾏后的输出就是zhangsan will come tonight 。

输出⽂本⾥⾯可以使⽤多个占位符。

#include <stdio.h>
int main()
{
 printf("%s says it is %d o'clock\n", "lisi", 21);
 return 0;
}

上⾯⽰例中,输出⽂本 %s says it is %d o’clock 有两个占位符,第⼀个是字符串占位符 %s ,第⼆个是整数占位符 %d ,分别对应 printf() 的第⼆个参数( lisi )和第三个参数( 21 )。执⾏后的输出就是 lisi says it is 21 o’clock 。
printf() 参数与占位符是⼀⼀对应关系,如果有 n 个占位符, printf() 的参数就应该有 n + 1 个。如果参数个数少于对应的占位符, printf() 可能会输出内存中的任意值。

占位符列举

printf() 的占位符有许多种类,与 C 语⾔的数据类型相对应。下⾯按照字⺟顺序,列出常⽤的占位符,⽅便查找,具体含义在后⾯章节介绍。
• %a :⼗六进制浮点数,字⺟输出为⼩写。
• %A :⼗六进制浮点数,字⺟输出为⼤写。
• %c :字符。
• %d :⼗进制整数。// int
• %e :使⽤科学计数法的浮点数,指数部分的 e 为⼩写。
• %E :使⽤科学计数法的浮点数,指数部分的 E 为⼤写。
• %i :整数,基本等同于 %d 。
• %f :⼩数(包含 float 类型和 double 类型)。//float %f double - %lf
• %g :6个有效数字的浮点数。整数部分⼀旦超过6位,就会⾃动转为科学计数法,指数部分的 e
为⼩写。
• %G :等同于 %g ,唯⼀的区别是指数部分的 E 为⼤写。
• %hd :⼗进制 short int 类型。
• %ho :⼋进制 short int 类型。
• %hx :⼗六进制 short int 类型。
• %hu :unsigned short int 类型。
• %ld :⼗进制 long int 类型。
• %lo :⼋进制 long int 类型。
• %lx :⼗六进制 long int 类型。
• %lu :unsigned long int 类型。
• %lld :⼗进制 long long int 类型。
• %llo :⼋进制 long long int 类型。
• %llx :⼗六进制 long long int 类型。
• %llu :unsigned long long int 类型。
• %Le :科学计数法表⽰的 long double 类型浮点数。
• %Lf :long double 类型浮点数。
• %n :已输出的字符串数量。该占位符本⾝不输出,只将值存储在指定变量之中。
• %o :⼋进制整数。
• %p :指针(⽤来打印地址)。
• %s :字符串。
• %u :⽆符号整数(unsigned int)。
• %x :⼗六进制整数。
• %zd : size_t 类型。
• %% :输出⼀个百分号。

限定宽度

printf() 允许限定占位符的最⼩宽度。

#include <stdio.h>
int main()
{
 printf("%5d\n", 123); // 输出为 " 123"
 return 0;
}

上⾯⽰例中, %5d 表⽰这个占位符的宽度⾄少为5位。如果不满5位,对应的值的前⾯会添加空格。
输出的值默认是右对⻬,即输出内容前⾯会有空格;如果希望改成左对⻬,在输出内容后⾯添加空格,可以在占位符的 % 的后⾯插⼊⼀个 - 号。

#include <stdio.h>
int main()
{
	 printf("%-5d\n", 123); // 输出为 "123 "
	 return 0;
}

上⾯⽰例中,输出内容 123 的后⾯添加了空格。

对于小数,这个限定符会限制所有数字的最小显示宽度。

1 // 输出 " 123.450000"
2 #include <stdio.h>
3 int main()
4 {
5	 printf("%12f\n", 123.45);
6	 return 0;
7 }

上⾯⽰例中, %12f 表⽰输出的浮点数最少要占据12位。由于⼩数的默认显⽰精度是⼩数点后6位,所以 123.45 输出结果的头部会添加2个空格。

总是显⽰正负号

默认情况下, printf() 不对正数显⽰ + 号,只对负数显⽰ - 号。如果想让正数也输出 + 号,可以在占位符的 % 后⾯加⼀个 + 。

1 #include <stdio.h>
2 int main()
3 {
4 printf("%+d\n", 12); // 输出 +12
5 printf("%+d\n", -12); // 输出 -12
6 return 0;
7 }

默认情况下, printf() 不对正数显⽰ + 号,只对负数显⽰ - 号。如果想让正数也输出 + 号,可以在占位符的 % 后⾯加⼀个 + 。

1 #include <stdio.h>
2 int main()
3 {
4 printf("%+d\n", 12); // 输出 +12
5 printf("%+d\n", -12); // 输出 -12
6 return 0;
7 }

上⾯⽰例中, %+d 可以确保输出的数值,总是带有正负号。

限定⼩数位数

输出⼩数时,有时希望限定⼩数的位数。举例来说,希望⼩数点后⾯只保留两位,占位符可以写成 %.2f 。

1 // 输出 Number is 0.50
2 #include <stdio.h>
3 int main()
4 {
5 	 printf("Number is %.2f\n", 0.5);
6	 return 0;
7 }

上⾯⽰例中,如果希望⼩数点后⾯输出3位( 0.500 ),占位符就要写成 %.3f 。
这种写法可以与限定宽度占位符,结合使⽤。

1 // 输出为 " 0.50"
2 #include <stdio.h>
3 int main()
4 {
5 	printf("%6.2f\n", 0.5);
6 	return 0;
7 }

上⾯⽰例中, %6.2f 表⽰输出字符串最⼩宽度为6,⼩数位数为2。所以,输出字符串的头部有两个空格。

最⼩宽度和⼩数位数这两个限定值,都可以⽤ * 代替,通过 printf() 的参数传⼊。

1 #include <stdio.h>
2 int main()
3 {
4 	printf("%*.*f\n", 6, 2, 0.5);
5 	return 0;
6 }
7 // 等同于printf("%6.2f\n", 0.5);

上⾯⽰例中, %*.*f 的两个星号通过 printf() 的两个参数 6 和 2 传⼊。

输出部分字符串

%s 占位符⽤来输出字符串,默认是全部输出。如果只想输出开头的部分,可以⽤ %.[m]s 指定输出的⻓度,其中 [m] 代表⼀个数字,表⽰所要输出的⻓度。

1 // 输出 hello
2 #include <stdio.h>
3 int main()
4 {
5 	printf("%.5s\n", "hello world");
6 	return 0;
7 }

上⾯⽰例中,占位符 %.5s 表⽰只输出字符串“hello world”的前5个字符,即“hello”。

2.scanf

需要给变量输⼊值就可以使⽤ scanf 函数,如果需要将变量的值输出在屏幕上的时候可以使⽤ prinf 函数,下⾯看⼀个例⼦:

#include <stdio.h>
int main()
{
	 int score = 0;
	 printf("请输⼊成绩:");
	 scanf("%d", &score);
	 printf("成绩是:%d\n", score);
	 return 0;
}

运⾏截图:
在这里插入图片描述

基本⽤法

scanf() 函数⽤于读取⽤⼾的键盘输⼊。

scanf("%d", &i);

上⾯⽰例中, scanf() 的第⼀个参数 %d ,表⽰⽤⼾输⼊的应该是⼀个整数。 %d 就是⼀个占位符, % 是占位符的标志, d 表⽰整数。第⼆个参数 &i 表⽰,将⽤⼾从键盘输⼊的整数存⼊变量i 。
注意:变量前⾯必须加上 & 运算符(指针变量除外),因为 scanf() 传递的不是值,⽽是地址,
即将变量 i 的地址指向⽤⼾输⼊的值。
如果这⾥的变量是指针变量(⽐如字符串变量),那就不⽤加 & 运算符。

scanf("%d%d%f%f", &i, &j, &x, &y);

上⾯⽰例中,格式字符串 %d%d%f%f ,表⽰⽤⼾输⼊的前两个是整数,后两个是浮点数,⽐如 1 -20 3.4 -4.0e3 。这四个值依次放⼊ i 、 j 、 x 、 y 四个变量。
scanf() 处理数值占位符时,会⾃动过滤空⽩字符,包括空格、制表符、换⾏符等。
所以,⽤⼾输⼊的数据之间,有⼀个或多个空格不影响 scanf() 解读数据。另外,⽤⼾使⽤回⻋键,将输⼊分成⼏⾏,也不影响解读。

解读⽤⼾输⼊时,会从上⼀次解读遗留的第⼀个字符开始,直到读完缓存,或者遇到第⼀个不符合条件的字符为⽌。

#include <stdio.h>
int main()
{
	 int x;
	 float y;
	 
	 // ⽤⼾输⼊ " -13.45e12# 0"
	 scanf("%d", &x);
	 printf("%d\n", x);
	 scanf("%f", &y);
	 printf("%f\n", y);
	 return 0;
}

上⾯⽰例中, scanf() 读取⽤⼾输⼊时, %d 占位符会忽略起⾸的空格,从 - 处开始获取数据,读取到 -13 停下来,因为后⾯的 . 不属于整数的有效字符。这就是说,占位符 %d 会读到 -13 。
第⼆次调⽤ scanf() 时,就会从上⼀次停⽌解读的地⽅,继续往下读取。这⼀次读取的⾸字符是 . ,由于对应的占位符是 %f ,会读取到 .45e12 ,这是采⽤科学计数法的浮点数格式。后⾯的不属于浮点数的有效字符,所以会停在这⾥。
由于 scanf() 可以连续处理多个占位符,所以上⾯的例⼦也可以写成下⾯这样。

{
	 #include <stdio.h>
	 int main()
	 int x;
	 float y;
	 
	 // ⽤⼾输⼊ " -13.45e12# 0"
	 scanf("%d%f", &x, &y);
	 return 0;
}
scanf的返回值

scanf() 的返回值是⼀个整数,表⽰成功读取的变量个数。
如果没有读取任何项,或者匹配失败,则返回 0 。
如果在成功读取任何数据之前,发⽣了读取错误或者遇到读取到⽂件结尾,则返回常量 EOF (-1)。
EOF - end of file ⽂件结束标志。

占位符

scanf() 常⽤的占位符如下,与 printf() 的占位符基本⼀致。
• %c :字符。
• %d :整数。
• %f : float 类型浮点数。
• %lf : double 类型浮点数。
• %Lf : long double 类型浮点数。
• %s :字符串。
• %[] :在⽅括号中指定⼀组匹配的字符(⽐如 %[0-9] ),遇到不在集合之中的字符,匹配将会停⽌。
上⾯所有占位符之中,除了 %c 以外,都会⾃动忽略起⾸的空⽩字符。 %c 不忽略空⽩字符,总是返
回当前第⼀个字符,⽆论该字符是否为空格。
如果要强制跳过字符前的空⽩字符,可以写成 scanf(" %c", &ch) ,即 %c 前加上⼀个空格,表
⽰跳过零个或多个空⽩字符。
下⾯要特别说⼀下占位符 %s ,它其实不能简单地等同于字符串。它的规则是,从当前第⼀个⾮空⽩
字符开始读起,直到遇到空⽩字符(即空格、换⾏符、制表符等)为⽌。
因为 %s 不会包含空⽩字符,所以⽆法⽤来读取多个单词,除⾮多个 %s ⼀起使⽤。这也意味着,scanf() 不适合读取可能包含空格的字符串,⽐如书名或歌曲名。另外, scanf() 遇到 %s 占位符,会在字符串变量末尾存储⼀个空字符\0。
scanf() 将字符串读⼊字符数组时,不会检测字符串是否超过了数组⻓度。所以,储存字符串时,很可能会超过数组的边界,导致预想不到的结果。为了防⽌这种情况,使⽤ %s 占位符时,应该指定读⼊字符串的最⻓⻓度,即写成 %[m]s ,其中的 [m] 是⼀个整数,表⽰读取字符串的最⼤⻓度,后⾯的字符将被丢弃。

#include <stdio.h>
int main()
{
	 char name[11];
	 scanf("%10s", name);
	 
	 return 0;
}

上⾯⽰例中, name 是⼀个⻓度为11的字符数组,scanf() 的占位符 %10s 表⽰最多读取⽤⼾输⼊的10个字符,后⾯的字符将被丢弃,这样就不会有数组溢出的⻛险了。

赋值忽略符

有时,⽤⼾的输⼊可能不符合预定的格式。

#include <stdio.h>
int main()
{
	 int year = 0;
	 int month = 0;
	 int day = 0;
	 scanf("%d-%d-%d", &year, &month, &day);
	 printf("%d %d %d\n", year, month, day);
	 return 0;
}

上⾯⽰例中,如果⽤⼾输⼊ 2020-01-01 ,就会正确解读出年、⽉、⽇。问题是⽤⼾可能输⼊其他格式,⽐如 2020/01/01 ,这种情况下, scanf() 解析数据就会失败。
为了避免这种情况, scanf() 提供了⼀个赋值忽略符(assignment suppression character) * 。只要把 * 加在任何占位符的百分号后⾯,该占位符就不会返回值,解析后将被丢弃。

#include <stdio.h>
int main()
{
	 int year = 0;
	 int month = 0;
	 int day = 0;
	 scanf("%d%*c%d%*c%d", &year, &month, &day);
	 return 0;
}

上⾯⽰例中, %*c 就是在占位符的百分号后⾯,加⼊了赋值忽略符 * ,表⽰这个占位符没有对应的变量,解读后不必返回。

分⽀和循环

C语⾔是结构化的程序设计语⾔,这⾥的结构指的是顺序结构、选择结构、循环结构,C语⾔是能够实现这三种结构的,其实我们如果仔细分析,我们⽇常所⻅的事情都可以拆分为这三种结构或者这三种结构的组合。
我们可以使⽤ if 、 switch 实现分⽀结构,使⽤ for 、 while 、 do while 实现循环结构。

if语句

例⼦:输⼊⼀个整数,判断是否为奇数

#include <stdio.h>
int main()
{
	 int num = 0;
	 scanf("%d", &num);
	 if(num % 2 == 1)
		 printf("%d 是奇数\n", num);
	 return 0;
}

else

如果⼀个数不是奇数,那就是偶数了,如果任意⼀个整数,我们要清楚的判断是奇数还是偶数怎么表⽰呢?
例⼦:输⼊⼀个整数,判断是否为奇数,如果是奇数打印是奇数,否则打印偶数。

#include <stdio.h>
int main()
{
	 int num = 0;
	 scanf("%d", &num);
	 if(num % 2 == 1)
		 printf("%d 是奇数\n", num);
	 else
		 printf("%d 是偶数\n", num);
	 return 0;
}

练习:输⼊⼀个年龄,>= 18岁就输出:成年,否则就输出:未成年

#include <stdio.h>
int main()
{
 int age = 0;
 scanf("%d", &age);
 if(age>=18)
 printf("成年\n");
 else
 printf("未成年\n");
 return 0;
}

分⽀中包含多条语句

#include <stdio.h>
int main()
{
	 int age = 0;
	 scanf("%d", &age);
	 if(age >= 18) //if 后使⽤{} 控制多条语句-这个块也叫:程序块,或者复合语句
	 {
		 printf("成年了\n");
		 printf("可以谈恋爱了\n");
 	}
	 return 0;
}
#include <stdio.h>
int main()
{
	 int age = 0;
	 scanf("%d", &age);
	 if(age >= 18) //if 后使⽤{} 控制多条语句-这个块也叫:程序块,或者复合语句
	 {
		  printf("成年了\n");
		 printf("可以谈恋爱了\n");
	 }
	 else //else 后使⽤{}控制多条语句-这个块也叫:程序块,或者复合语句
	 {
		 printf("未成年\n");
 		printf("不可以早恋哦\n");
	 }
 	return 0;
}

嵌套if

练习:
输⼊⼀个⼈的年龄
如果年龄<18岁,打印"少年"
如果年龄在18岁⾄44岁打印"⻘年"
如果年龄在45岁⾄59岁打印"中⽼年"
如果年龄在60岁⾄89岁打印"⽼年"
如果90岁及以上打印"⽼寿星
参考代码:

//⽅法1
#include <stdio.h>
int main()
{
 int age = 0;
 scanf("%d", &age);
 if(age<18)
	 printf("少年\n");
 else if(age<=44)
	 printf("⻘年\n");
 else if(age<=59)
 	printf("中⽼年\n");
 else if(age<=89)
 	printf("⽼年\n"); else
    printf("⽼寿星\n");
 return 0;
 }


 //带上⼤括号更容易看明⽩
 #include <stdio.h>
 int main()
 {
 int age = 0;
 scanf("%d", &age);
 if(age<18)
	 {
	 printf("少年\n");
	 }
 else
	 {
		 if(age<=44)
		 {
			 printf("⻘年\n");
		 }
 		 else
		 {
			 if(age<=59)
			 {
	 			 printf("中⽼年\n");
			 }
			 else
		     {
				 if(age<=89)
					 printf("⽼年\n");
				 else
					 printf("⽼寿星\n");
			 }
		 }
	 }
  return 0;
 }

悬空else

如果有多个 if 和 else ,可以记住这样⼀条规则, else 总是跟最接近的 if 匹配。

关系操作符

C 语⾔⽤于⽐较的表达式,称为 “关系表达式”(relational expression),⾥⾯使⽤的运算符就称为“关系运算符”(relational operator),主要有下⾯6个。

⼤于运算符
< ⼩于运算符
= ⼤于等于运算符
<= ⼩于等于运算符
== 相等运算符
!= 不相等运算符
下⾯是⼀些例⼦。

a == b;
a != b;
a < b;
a > b;
a <= b;
a >= b;

关系表达式通常返回 0 或 1 ,表⽰真假。
C 语⾔中, 0 表⽰假,所有⾮零值表⽰真。⽐如, 20 > 12 返回 1 , 12 > 20 返回 0 。
关系表达式常⽤于 if 或 while 结构。
多个关系运算符不宜连⽤。

 i < j < k

上⾯⽰例中,连续使⽤两个⼩于运算符。这是合法表达式,不会报错,但是通常达不到想要的结果,即不是保证变量 j 的值在 i 和 k 之间。因为关系运算符是从左到右计算,所以实际执⾏的是下⾯的表达式。

(i < j) < k

上⾯式⼦中, i < j 返回 0 或 1 ,所以最终是 0 或 1 与变量 k 进⾏⽐较。如果想要判断变量 j
的值是否在 i 和 k 之间,应该使⽤下⾯的写法

i < j && j < k

我们输⼊⼀个年龄,如果年龄在18岁~36岁之间,我们输出⻘年。

#include <stdio.h>
int main()
{
 int age = 0;
 scanf("%d", &age);
 if(age>=18 && age<=36)
 {
 printf("⻘年\n");
 }
 return 0;
}

条件操作符

条件操作符也叫三⽬操作符,需要接受三个操作数的,形式如下:

exp1 ? exp2 : exp3

条件操作符的计算逻辑是:如果 exp1 为真, exp2 计算,计算的结果是整个表达式的结果;如果exp1 为假, exp3 计算,计算的结果是整个表达式的结果。
练习1:使⽤条件操作符表⽰下⾯代码的逻辑

#include <stdio.h>
int main()
{
	 int a = 0;
	 int b = 0;
	 scanf("%d", &a);
	 if (a > 5)
		 b = 3;
	 else
	 	 b = -3;
	 printf("%d\n", b);
	 return 0;
}
改造后:
#include <stdio.h>
int main()
{
	 int a = 0;
	 int b = 0;
	 scanf("%d", &a);
	 b = a>5 ? 3:-3;
	 printf("%d\n", b);
	 return 0;
}

练习2:使⽤条件表达式实现找两个数中较⼤值

#include <stdio.h>
int main()
{
 int a = 0;
 int b = 0;
 scanf("%d %d", &a, &b);
 int m = a>b ? a : b;
 printf("%d\n", m);
 
 return 0;
}

逻辑操作符:&& , || , !

逻辑运算符提供逻辑判断功能,⽤于构建更复杂的表达式,主要有下⾯三个运算符。
! :逻辑取反运算符(改变单个表达式的真假)。
&& :逻辑与运算符,就是并且的意思(两侧的表达式都为真,则为真,否则为假)。
|| :逻辑或运算符,就是或者的意思(两侧⾄少有⼀个表达式为真,则为真,否则为假)。
注:C语⾔中,⾮0表⽰真,0表⽰假

逻辑取反运算符 !

在这里插入图片描述

⽐如,我们有⼀个变量叫 flag ,如果flag为假,要做⼀个什么事情,就可以这样写代码:

#include <stdio.h>
int main()
{
	 int flag = 0;
		 if(!flag)
		 {
			 printf("do something\n");
		 }
	 return 0;
}

如果 flag 为真, !flag 就是假,如果 flag 为假, !flag 就是真

所以上⾯的代码的意思就是 flag 为假,执⾏if语句中的代码。

逻辑与运算符

在这里插入图片描述

&& 就是与运算符,也是并且的意思, && 是⼀个双⽬操作符,使⽤的⽅式是 a&&b , && 两边的表达式都是真的时候,整个表达式才为真,只要有⼀个是假,则整个表达式为假。

⽐如:如果我们说⽉份是3⽉到5⽉,是春天,那使⽤代码怎么体现呢?

1 int month = 0;
2 scanf("%d", &month);
3 if(month >= 3 && month <= 5)
4 {
5 		printf("春季\n");
6 }

这⾥表达的意思就是month既要⼤于等于3,⼜要⼩于等于5,必须同时满⾜。

逻辑或运算符

在这里插入图片描述

|| 就是或运算符,也就是或者的意思, || 也是⼀个双⽬操作符,使⽤的⽅式是 a || b , ||

两边的表达式只要有⼀个是真,整个表达式就是真,两边的表达式都为假的时候,才为假。

⽐如:我们说⼀年中⽉份是12⽉或者1⽉或者2⽉是冬天,那么我们怎么使⽤代码体现呢?

int month = 0;
scanf("%d", &month);
if(month == 12 || month==1 || month == 2)
{
	 printf("冬季\n");
}

练习:闰年的判断

输⼊⼀个年份year,判断year是否是闰年

闰年判断的规则:

  1. 能被4整除并且不能被100整除是闰年

  2. 能被400整除是闰年

    #include <stdio.h>
    //代码1
    int main()
    {
      	 int year = 0;
         scanf("%d", &year);
         if(year%4==0 && year%100!=0)
         	printf("是闰年\n");
         else if(year%400==0)
        	 printf("是闰年\n");
     
     	 return 0;
    }
    //代码2
    int main()
    {
         int year = 0;
         scanf("%d", &year);
         if((year%4==0 && year%100!=0) || (year%400==0))
        	 printf("是闰年\n");
    
         return 0;
    }
    

    短路

    C语⾔逻辑运算符还有⼀个特点,它总是先对左侧的表达式求值,再对右边的表达式求值,这个顺序是保证的。

    如果左边的表达式满⾜逻辑运算符的条件,就不再对右边的表达式求值。这种情况称为“短路”。

    如前⾯的代码:

if(month >= 3 && month <= 5)

表达式中&& 的左操作数是 month >= 3 ,右操作数是 month <= 5 ,当左操作数 month >= 3 的

结果是0的时候,即使不判断 month <= 5 ,整个表达式的结果也是0(不是春季)。

所以,对于&&操作符来说,左边操作数的结果是0的时候,右边操作数就不再执⾏。

对于 || 操作符是怎么样呢?我们结合前⾯的代码:

if(month == 12 || month==1 || month == 2)

如果month == 12,则不⽤再判断month是否等于1或者2,整个表达式的结果也是1(是冬季)。

所以, || 操作符的左操作数的结果不为0时,就⽆需执⾏右操作数。

像这种仅仅根据左操作数的结果就能知道整个表达式的结果,不再对右操作数进⾏计算的运算称为短路求值

练习:阅读代码,计算代码输出的结果

#include <stdio.h>
int main()
{
 int i = 0,a=0,b=2,c =3,d=4;
 i = a++ && ++b && d++;
 //i = a++||++b||d++;
 printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
 return 0;
}

switch语句

除了 if 语句外,C语⾔还提供了 switch 语句来实现分⽀结构。

switch 语句是⼀种特殊形式的 if…else 结构,⽤于判断条件有多个结果的情况。它把多重的 else if 改成更易⽤、可读性更好的形式。

switch (expression) {
 case value1: statement
 case value2: statement
 default: statement
}

上⾯代码中,根据表达式 expression 不同的值,执⾏相应的 case 分⽀。如果找不到对应的值,就执⾏ default 分⽀。

注:

• switch 后的 expression 必须是整型表达式

• case 后的值,必须是整形常量表达式

if语句和switch语句的对⽐

练习:输⼊任意⼀个整数值,计算除3之后的余数

如果使⽤if语句完成,如下:

#include <stdio.h>
int main()
{
     int n = 0;
     scanf("%d", &n);
     if(n%3 == 0)
     	printf("整除,余数为0\n");
     else if(n%3 == 1)
     	printf("余数是1\n");
     else
     	printf("余数是2\n");
     return 0;
}

如果使⽤switch语句改写,就可以是这样的:

1 #include <stdio.h>
2
3 int main()
4 {
5 	int n = 0;
6 	scanf("%d", &n);
7 	switch(n%3)
8 	{
9 	case 0:
10 		printf("整除,余数为0\n"); 
11 		break;
12 	case 1:
13 		printf("余数是1\n"); 
14 		break;
15 	case 2:
16 		printf("余数是2\n"); 
17 		break;
18 	}
19 	return 0;
20 }

上述的代码中,我们要注意的点有:

  1. case 和后边的数字之间必须有空格
  2. 每⼀个 case 语句中的代码执⾏完成后,需要加上 break ,才能跳出这个switch语句。

switch语句中的break

前⾯的代码中,如果我们去掉case语句中的break,会出现什么情况呢?

1 #include <stdio.h>
2
3 int main()
4 {
5 	int n = 0;
6 	scanf("%d", &n);
7 	switch(n%3)
8 	{
9 	case 0:
10 		printf("整除,余数为0\n"); 
11 	case 1:
12 		printf("余数是1\n"); 
13 	case 2:
14 		printf("余数是2\n"); 
15 	}
16 	return 0;
17 }

结果:
在这里插入图片描述

我们发现,7除以3本来余数是1,但是我们发现程序运⾏的结果多了⼀⾏“余数是2”的打印。

这是为什么呢?

原因是 switch 语句也是分⽀效果的,只有在 switch 语句中使⽤ break 才能在跳出 switch 语句,如果某⼀个 case 语句的后边没有 break 语句,代码会继续往下执⾏,有可能执⾏其他 case语句中的代码,直到遇到 break 语句或者 switch 语句结束。就⽐如上⾯的代码就执⾏了 case 2 中的语句。

所以在 switch 语句中 break 语句是⾮常重要的,能实现真正的分⽀效果。

当然, break 也不是每个 case 语句都得有,这就得根据实际情况来看了。

练习

输⼊⼀个1~7的数字,打印对应的星期⼏

例如:

输⼊:1 输出:星期⼀

输⼊:2 输出:星期⼆

输⼊:3 输出:星期三

输⼊:4 输出:星期四

输⼊:5 输出:星期五

输⼊:6 输出:星期六

输⼊:7 输出:星期天

#include <stdio.h>
int main()
{
     int day = 0;
     scanf("%d", &day);
     switch(day)
     {
     	case 1printf("星期⼀\n");
     		break;
     	case 2:
     		printf("星期⼆\n");
     		break;
     	case 3:
     		printf("星期三\n");
     		break; 
     	case 4:
     		printf("星期四\n");
     		break; 
     	case 5:
     		printf("星期五\n");
     		break;
     	case 6:
     		printf("星期六\n");
     		break;
     	case 7:
     		printf("星期天\n"); 
     		break;
     }
     return 0;
}

如果需求发⽣变化,变为:

  1. 输⼊1~5,输出的是“⼯作⽇”;
  2. 输⼊6~7,输出“休息⽇”
#include <stdio.h>
int main()
{
     int day = 0;
     scanf("%d", &day);
      switch(day)
     {
         case 1case 2:
         case 3:
         case 4:
         case 5:
         	printf("⼯作⽇\n");
         	break;
         case 6:
         case 7:
         	printf("休息⽇\n");
        	break;
     }
 return 0;
}

上⾯的练习中,我们发现应该根据实际的情况,来在代码中觉得是否使⽤ break ,或者在哪⾥使⽤

break ,才能正确完成实际的需求

switch语句中的default

在使⽤ switch 语句的时候,我们经常可能遇到⼀种情况,⽐如 switch 后的表达式中的值⽆法匹配代码中的 case 语句的时候,这时候要不就不做处理,要不就得在 switch 语句中加⼊default ⼦句。

switch (expression) {
 case value1: statement
 case value2: statement
 default: statement
}

switch 后边的 expression 的结果不是 value1 ,也不是 value2 的时候,就会执⾏default ⼦句。

就⽐如前⾯做的打印星期的练习,如果 day 的输⼊不是1~7的值,如果我们要提⽰:输⼊错误,则可以这样完成代码:

#include <stdio.h>
int main()
{
     int day = 0;
     scanf("%d", &day);

     switch(day)
     {
         case 1case 2:
         case 3:
         case 4:
         case 5:
     		printf("⼯作⽇\n");
     		break;
         case 6:
         case 7:
     		printf("休息⽇\n");
     		break;
     	 default:
  		    printf("输⼊错误\n");
  		    break;
     }
     return 0;
}

switch语句中的case和default的顺序问题

在 switch 语句中 case ⼦句和 default ⼦句有要求顺序吗? default 只能放在最后吗?

其实,在 switch 语句中 case 语句和 default 语句是没有顺序要求的,只要你的顺序是满⾜实

际需求的就可以。

不过我们通常是把 default ⼦句放在最后处理的。

while循环

C语⾔提供了3种循环语句, while 就是其中⼀种,接下来就介绍⼀下 while 语句。

while 语句的语法结构和 if 语句⾮常相似。

if 和 while的对⽐

//代码1
#include <stdio.h>
int main()
{
 	if(1)
 		printf("hehe\n"); //if后边条件满⾜,打印⼀次hehe
 	return 0;
}
//代码2
#include <stdio.h>
int main()
{
 	while(1)
 		printf("hehe\n"); //while后边的条件满⾜,死循环的打印hehe
 	return 0;
}

这就是他们的区别,while语句是可以实现循环效果的。

while语句的执⾏流程

在这里插入图片描述

⾸先上来就是执⾏判断表达式,表达式的值为0,循环直接结束;表达式的值不为0,则执⾏循环语

句,语句执⾏完后再继续判断,是否进⾏下⼀次判断。

while 循环的实践

练习:在屏幕上打印 1~10 的值

#include <stdio.h>
int main()
{
     int i = 1;
     while(i<=10)
     {
     	printf("%d ", i);
     	i = i+1;
     }	
     return 0;
}

在这里插入图片描述

练习

输⼊⼀个正的整数,逆序打印这个整数的每⼀位

例如:

输⼊:1234,输出:4 3 2 1

输⼊:521,输出:1 2 5

题⽬解析

  1. 要想得到n的最低位,可以使⽤n%10的运算,得到的余数就是最低位,如:1234%10得到4
  2. 要想去掉n的最低位,找出倒数第⼆位,则使⽤ n=n/10 操作就可以去掉最低位的,如:

n=1234/10得到123,123相较于1234就去掉了最低位,123%10就得到倒数第⼆位3。

  1. 循环1和2两个步骤,在n变成0之前,就能到所有的位。
1 #include <stdio.h>
2
3 int main()
4 {
5 	int n = 0;
6 	scanf("%d", &n);
7 	while(n)
8 	{
9 		printf("%d ", n%10);
10 		n /= 10;
11 	}
12	return 0;
13 }

for循环

for 循环是三种循环中使⽤最多的, for 循环的语法形式如下:

for(表达式1; 表达式2; 表达式3)
 语句;//如果循环体想包含更多的语句,可以加上⼤括号

表达式1 ⽤于循环变量的初始化
表达式2 ⽤于循环结束条件的判断
表达式3 ⽤于循环变量的调整
在这里插入图片描述

练习:在屏幕上打印1~10的值

#include <stdio.h>
int main()
{
     int i = 0;
     for(i=1; i<=10; i++)
     {
        printf("%d ", i);
     }

     return 0;
}

while循环和for循环的对⽐

在这里插入图片描述

for 和 while 在实现循环的过程中都有初始化、判断、调整这三个部分,但是 for 循环的三个部分⾮常集中,便于代码的维护,⽽如果代码较多的时候 while 循环的三个部分就⽐较分散,所以从形式上 for 循环要更优⼀些。

练习

练习1:
计算1~100之间3的倍数的数字之和

#include <stdio.h>
int main()
{
     int i = 0;
     int sum = 0;
     for(i=1; i<=100; i++)
     {
     	if(i % 3 == 0)
     		sum += i;
     }
     printf("%d\n", sum);
     return 0;
}
//⼩⼩的优化
//如果能直接产⽣3的倍数的数字就省去了多余的循环和判断
#include <stdio.h>
int main()
{
     int i = 0;
     int sum = 0;
     for(i=3; i<=100; i+=3)
     {
     	sum += i;
     }
     printf("%d\n", sum);
     return 0;
}

do-while 循环

在这里插入图片描述

在 do while 语句中循环体是⾄少执⾏⼀次的,这是 do while 循环⽐较特殊的地⽅。

在屏幕上打印1~10的值

#include <stdio.h>
int main()
{
     int i = 1;
     do
     {
         printf("%d ", i);
         i = i + 1;
     }while(i<=10);

     return 0;
}

⼀般 do while 使⽤在循环体⾄少被执⾏⼀次的场景下,所以较少⼀些。

练习

输⼊⼀个正整数,计算这个整数是⼏位数?

例如:

输⼊:1234 输出:4

输⼊:12 输出:2

#include <stdio.h>
int main()
{
     int n = 0;
     scanf("%d", &n);
     int cnt = 0;
      do
     {
     	cnt++;
     	n = n / 10;
     } while (n);
     printf("%d\n", cnt);
     return 0;
}

这⾥并⾮必须使⽤ do while 语句,但是这个代码就⽐较适合使⽤ do while 循环,因为n即使是0,也是1位数,要统计位数的。

break和continue语句

在循环执⾏的过程中,如果某些状况发⽣的时候,需要提前终⽌循环,这是⾮常常⻅的现象。C语⾔中

提供了 break 和 continue 两个关键字,就是应⽤到循环中的。

• break 的作⽤是⽤于永久的终⽌循环,只要 break 被执⾏,直接就会跳出循环,继续往后执⾏。

• continue 的作⽤是跳过本次循环中 continue 后边的代码,在 for 循环和 while 循环中有所差异的。

while循环中的break和continue

break举例
#include <stdio.h>
int main()
{
     int i = 1;
     while(i<=10)
     {
     	if(i == 5)
     		break;//当i等于5后,就执⾏break,循环就终⽌了
     	printf("%d ", i);
     	i = i+1;
     }
     return 0;
}

在这里插入图片描述

打印了1,2,3,4后,当i等于5的时候,循环在 break 的地⽅终⽌,不再打印,不再循环。

所以 break 的作⽤就是永久的终⽌循环,只要 break 被执⾏, break 外的第⼀层循环就终⽌了。

那以后我们在循环中,想在某种条件下终⽌循环,则可以使⽤ break 来完成我们想要的效果。

continue举例
1 #include <stdio.h>
2 int main()
3 {
4 	int i = 1;
5 	while(i<=10)
6 	{
7 		if(i == 5)
8 			continue;
9 			//当i等于5后,就执⾏continue,直接跳过continue的代码,去循环的判断的地⽅
10 			//因为这⾥跳过了i = i+1,所以i⼀直为5,程序陷⼊和死循环
11 		printf("%d ", i);
12 		i = i+1;
13 	}
14 	return 0;
15 }

到这⾥我们就能分析出来, continue 可以帮助我们跳过某⼀次循环 continue 后边的代码,直接到循环的判断部分,进⾏下⼀次循环的判断,如果循环的调整是在 continue 后边的话,可能会造成死循环。

for循环中的break和continue

break 举例

其实和 while 循环中的 break ⼀样, for 循环中的 break 也是⽤于终⽌循环的,不管循环还需要循环多少次,只要执⾏到了 break ,循环就彻底终⽌,我们上代码

1 #include <stdio.h>
2 int main()
3 {
4 	int i = 1;
5 	for(i=1; i<=10; i++)
6 	{
7 		if(i == 5)
8 			break;
9 		printf("%d ", i);
10 	}
11 	return 0;
12 }

break 的作⽤是永久的终⽌循环,未来我们在某个条件发⽣的时候,不想再继续循环的时候,就可以使⽤ break 来完成。

continue举例
1 #include <stdio.h>
2 int main()
3 {
4 	int i = 1;
5 	for(i=1; i<=10; i++)
6 	{
7 		if(i == 5)
8 			continue;//这⾥continue跳过了后边的打印,来到了i++的调整部分
9 		printf("%d ", i);
10 	}
11 	return 0;
12 }

所以在 for 循环中 continue 的作⽤是跳过本次循环中 continue 后的代码,直接去到循环的调整部分。未来当某个条件发⽣的时候,本次循环⽆需再执⾏后续某些操作的时候,就可以使⽤continue 来实现。
在这⾥我们也可以对⽐⼀下 while 循环和 for 循环中 continue 的区别:
在这里插入图片描述

do while循环中的break和continue

do.while 语句中的 break 和 continue 的作⽤和 while 循环中⼏乎⼀模⼀样,⼤家下来可以⾃⾏测试并体会。

#include <stdio.h>
int main()
{
     int i = 1;
     do
     {
     	if(i == 5)
     		break;
     	printf("%d ", i);
     	i = i + 1;
     }while(i<=10);
      return 0;
}
#include <stdio.h>
int main()
{
     int i = 1;
     do
     {
     	if(i == 5)
     		continue;
     	printf("%d ", i);
     	i = i + 1;
     }while(i<=10);
        return 0;
}

循环的嵌套

练习
找出100~200之间的素数,并打印在屏幕上。
注:素数⼜称质数,只能被1和本⾝整除的数字。
题⽬解析:

  1. 要从100200之间找出素数,⾸先得有100200之间的数,这⾥可以使⽤循环解决。
  2. 假设要判断i是否为素数,需要拿2 ~ i-1之间的数字去试除i,需要产⽣2~i-1之间的数字,也可以使用循环解决。
  3. 如果2~i-1之间有数字能整除i,则i不是素数,如果都不能整除,则i是素数。
#include <stdio.h>
int main()
{
	 int i = 0;
	 //循环产⽣100~200的数字
	 for(i=100; i<=200; i++)
	 {
		 //判断i是否为素数
		 //循环产⽣2~i-1之间的数字
		 int j = 0;
		 int flag = 1;//假设i是素数
		 for(j=2; j<i; j++)
		 {
		 	if(i % j == 0)
		 	{	
		 		flag = 0;
		 		break;
		  	}
		 }
		 if(flag == 1)
		 	printf("%d ", i);
	 }
	 return 0;
}

goto 语句

C语⾔提供了⼀种⾮常特别的语法,就是 goto 语句和跳转标号, goto 语句可以实现在同⼀个函数内跳转到设置好的标号处。
例如:

#include <stdio.h>
int main()
{
 printf("hehe\n");
 goto next;
 printf("haha\n");
 
next:
 printf("跳过了haha的打印\n"); 
 return 0;
}

goto 语句如果使⽤的不当,就会导致在函数内部随意乱跳转,打乱程序的执⾏流程,所以我们的建议是能不⽤尽量不去使⽤;但是 goto 语句也不是⼀⽆是处,在多层循环的代码中,如果想快速跳出使⽤ goto 就⾮常的⽅便了。
本来 for 循环想提前退出得使⽤ break ,⼀个 break 只能跳出⼀层 for 循环,如果3层循环嵌套就得使⽤3个 break 才能跳出循环,所以在这种情况下我们使⽤ goto语句就会更加的快捷。

写⼀个猜数字游戏

游戏要求:

  1. 电脑⾃动⽣成1~100的随机数
  2. 玩家猜数字,猜数字的过程中,根据猜测数据的⼤⼩给出⼤了或⼩了的反馈,直到猜对,游戏结束

随机数⽣成

rand

C语⾔提供了⼀个函数叫 rand,这函数是可以⽣成随机数的,函数原型如下所⽰:

int rand (void);

rand函数会返回⼀个伪随机数,这个随机数的范围是在0~RAND_MAX之间,这个RAND_MAX的⼤⼩是依赖编译器上实现的,但是⼤部分编译器上是32767。
rand函数的使⽤需要包含⼀个头⽂件是:stdlib.h
那我们就测试⼀下rand函数,这⾥多调⽤⼏次,产⽣5个随机数:

1 #include <stdio.h>
2 #include <stdlib.h>
3 int main()
4 {
5 	printf("%d\n", rand());
6 	printf("%d\n", rand());
7 	printf("%d\n", rand());
8 	printf("%d\n", rand());
9 	printf("%d\n", rand());
10 return 0;
11 }

在这里插入图片描述
我们可以看到虽然⼀次运⾏中产⽣的5个数字是相对随机的,但是下⼀次运⾏程序⽣成的结果和上⼀次⼀模⼀样,这就说明有点问题。
如果再深⼊了解⼀下,我们就不难发现,其实rand函数⽣成的随机数是伪随机的,伪随机数不是真正的随机数,是通过某种算法⽣成的随机数。真正的随机数的是⽆法预测下⼀个值是多少的。⽽rand函数是对⼀个叫“种⼦”的基准值进⾏运算⽣成的随机数。
之所以前⾯每次运⾏程序产⽣的随机数序列是⼀样的,那是因为rand函数⽣成随机数的默认种⼦是1。
如果要⽣成不同的随机数,就要让种⼦是变化的。

srand

C语⾔中⼜提供了⼀个函数叫 srand,⽤来初始化随机数的⽣成器的,srand的原型如下:

void srand (unsigned int seed);

程序中在调⽤ rand 函数之前先调⽤ srand 函数,通过 srand 函数的参数seed来设置rand函数⽣成随机数的时候的种⼦,只要种⼦在变化,每次⽣成的随机数序列也就变化起来了。
那也就是说给srand的种⼦是如果是随机的,rand就能⽣成随机数;在⽣成随机数的时候⼜需要⼀个随机数,这就⽭盾了。

time_t time (time_t* timer);

time 函数会返回当前的⽇历时间,其实返回的是1970年1⽉1⽇0时0分0秒到现在程序运⾏时间之间的差值,单位是秒。返回的类型是time_t类型的,time_t 类型本质上其实就是32位或者64位的整型类型。
time函数的参数 timer 如果是⾮NULL的指针的话,函数也会将这个返回的差值放在timer指向的内存中带回去。
如果 timer 是NULL,就只返回这个时间的差值。time函数返回的这个时间差也被叫做:时间戳。
time函数的时候需要包含头⽂件:time.h

time

在程序中我们⼀般是使⽤程序运⾏的时间作为种⼦的,因为时间时刻在发⽣变化的。
在C语⾔中有⼀个函数叫 time ,就可以获得这个时间,time函数原型如下:

time_t time (time_t* timer);

如果只是让time函数返回时间戳,我们就可以这样写:

time(NULL);//调⽤time函数返回时间戳,这⾥没有接收返回值

那我们就可以让⽣成随机数的代码改写成如下:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
	 //使⽤time函数的返回值设置种⼦
	 //因为srand的参数是unsigned int类型,我们将time函数的返回值强制类型转换
	 srand((unsigned int)time(NULL));
	 printf("%d\n", rand());
	 printf("%d\n", rand());
	 printf("%d\n", rand());
	 printf("%d\n", rand());
	 printf("%d\n", rand());
	 return 0;
 }

srand函数是不需要频繁调⽤的,⼀次运⾏的程序中调⽤⼀次就够了。

设置随机数的范围

如果我们要⽣成0~99之间的随机数,⽅法如下:

rand() % 100;//余数的范围是0~99

如果要⽣成1~100之间的随机数,⽅法如下:

rand()%100+1;//%100的余数是0~99,0~99的数字+1,范围是1~100

如果要⽣成100~200的随机数,⽅法如下:

1 100 + rand()%(200-100+1)
2 //余数的范围是0~100,加100后就是100~200

所以如果要⽣成a~b的随机数,⽅法如下:

a + rand()%(b-a+1)

猜数字游戏实现

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

//viod表示函数不返回任何值
void menu()
{
	printf("*************\n");
	printf("****1.play*****\n");
	printf("****0.exit*****\n");
	printf("*************\n");
}

void game()

{
	//1.生成随机数
	int r = rand() % 100 + 1;
	//2.玩家输入数字
	int guess = 0;
	while (1)
	{
		printf("请输入数字:");
		scanf("%d", &guess);
		if (guess < r)
		{
			printf("猜小了\n");
		}
		else if (guess > r)
		{
			printf("猜大了\n");
		}
		else
		{
			printf("恭喜你,猜对了\n");
			break;
		}
	}
}



int main()
{
	int input=0;
	//设置随机数的生成起点(种子)
	//只需调用一次即可
	srand((unsigned int)time(NULL));
	do
	{
		//打印菜单
		menu();

		//选择

		printf("请选择:");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	}while (input);
	return 0;
}

还可以加上猜数字的次数限制,如果5次猜不出来,就算失败.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

//viod表示函数不返回任何值
void menu()
{
	printf("*************\n");
	printf("****1.play*****\n");
	printf("****0.exit*****\n");
	printf("*************\n");
}

void game()

{
	//1.生成随机数
	int r = rand() % 100 + 1;
	//2.玩家输入数字
	int guess = 0;
	int count = 5;
	while (1)
	{
		printf("\n你还有%d次机会\n", count);
		printf("请输入数字:");
		scanf("%d", &guess);
		if (guess < r)
		{
			printf("猜小了\n");
		}
		else if (guess > r)
		{
			printf("猜大了\n");
		}
		else
		{
			printf("恭喜你,猜对了\n");
			break;
		}
		count--;
	}
	if (count == 0)
	{
		printf("你失败了,正确值是:%d\n", r);
	}
}



int main()
{
	int input=0;
	//设置随机数的生成起点(种子)
	//只需调用一次即可
	srand((unsigned int)time(NULL));
	do
	{
		//打印菜单
		menu();

		//选择

		printf("请选择:");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("输入错误,请重新输入\n");
			break;
		}
	}while (input);
	return 0;
}

数组

数组的概念

数组是⼀组相同类型元素的集合
• 数组中存放的是1个或者多个数据,但是数组元素个数不能为0。
• 数组中存放的多个数据,类型是相同的。
数组分为⼀维数组和多维数组,多维数组⼀般⽐较多⻅的是⼆维数组。

⼀维数组的创建和初始化

数组创建
type arr_name[常量值];

• type 指定的是数组中存放数据的类型,可以是: char、short、int、float 等,也可以⾃定义的类型。
• arr_name 指的是数组名的名字,这个名字根据实际情况,起的有意义就⾏。
• [ ] 中的常量值是⽤来指定数组的⼤⼩的,这个数组的⼤⼩是根据实际的需求指定就⾏。

int math[20];
char ch[8];
double score[10];
数组的初始化

有时候,数组在创建的时候,我们需要给定⼀些初始值,这种就称为初始化的。

1 //完全初始化
2 int arr[5] = {1,2,3,4,5};
3
4 //不完全初始化
5 int arr2[6] = {1};//第⼀个元素初始化为1,剩余的元素默认初始化为0
6
7 //错误的初始化 - 初始化项太多
8 int arr3[3] = {1, 2, 3, 4};
数组的类型

数组也是有类型的,数组算是⼀种⾃定义类型,去掉数组名留下的就是数组的类型。
如下:

int arr1[10];
int arr2[12];
char ch[5];

arr1数组的类型是 int [10]
arr2数组的类型是 int [12]
ch 数组的类型是 char [5]

⼀维数组的使⽤

数组下标

C语⾔规定数组是有下标的,下标是从0开始的,假设数组有n个元素,最后⼀个元素的下标是n-1,下标就相当于数组元素的编号,如下:

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

在这里插入图片描述
在C语⾔中数组的访问提供了⼀个操作符 [] ,这个操作符叫:下标引⽤操作符。
有了下标访问操作符,我们就可以轻松的访问到数组的元素了,⽐如我们访问下标为7的元素,我们就可以使⽤ arr[7] ,想要访问下标是3的元素,就可以使⽤ arr[3] ,如下代码:

#include <stdio.h>
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10}; 
 printf("%d\n", arr[7]);//8
 printf("%d\n", arr[3]);//4
 return 0;
}

输出结果:
在这里插入图片描述

数组元素的打印

使⽤for循环产⽣0~9的下标,接下来使⽤下标访问就⾏了。

#include <stdio.h>
int main()
{
	 int arr[10] = {1,2,3,4,5,6,7,8,9,10}; 
	 int i = 0;
	 for(i=0; i<10; i++)
	 {
	 	printf("%d ", arr[i]);
	 }
	 return 0;
}

在这里插入图片描述

数组的输⼊
#include <stdio.h>
int main()
{
	 int arr[10] = {1,2,3,4,5,6,7,8,9,10}; 
	 int i = 0;
	 for(i=0; i<10; i++)
	 {
	 	scanf("%d", &arr[i]);
	 }
	 for(i=0; i<10; i++)
	 {
	 	printf("%d ", arr[i]);
	 }
	 return 0;
}

在这里插入图片描述

⼀维数组在内存中的存储

依次打印数组元素的地址:

#include <stdio.h>
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10}; 
 int i = 0;
 for(i=0; i<10; i++)
 {
 printf("&arr[%d] = %p\n ", i, &arr[i]);
 }
 return 0;
}

在这里插入图片描述从输出的结果分析,数组随着下标的增⻓,地址是由⼩到⼤变化的,并且发现每两个相邻的元素之间相差4(因为⼀个整型是4个字节)。所以我们得出结论:数组在内存中是连续存放的。这就为后期我们使⽤指针访问数组奠定了基础。
在这里插入图片描述

sizeof 计算数组元素个数

使⽤sizeof序计算数组元素个数。

1 #include <stido.h>
2
3 int main()
4 {
5 	int arr[10] = {0};
6 	printf("%d\n", sizeof(arr));
7 	return 0;
8 }

这⾥输出的结果是40,计算的是数组所占内存空间的总⼤⼩,单位是字节。
我们⼜知道数组中所有元素的类型都是相同的,那只要计算出⼀个元素所占字节的个数,数组的元素个数就能算出来。这⾥我们选择第⼀个元素算⼤⼩就可以。

1 #include <stido.h>
2
3 int main()
4 {
5 	int arr[10] = {0};
6 	printf("%d\n", sizeof(arr[0]));//计算⼀个元素的⼤⼩,单位是字节
7 	return 0;
8 }

接下来就能计算出数组的元素个数:

1 #include <stido.h>
2
3 int main()
4 {
5 	int arr[10] = {0};
6 	int sz = sizeof(arr)/sizeof(arr[0]);
7 	printf("%d\n", sz);
8 	return 0;
9 }

这⾥的结果是:10,表⽰数组有10个元素。
以后在代码中需要数组元素个数的地⽅就不⽤固定写死了,使⽤上⾯的计算,不管数组怎么变化,计算出的⼤⼩也就随着变化了。

二维数组的创建

在这里插入图片描述

type arr_name[常量值1][常量值2];
例如:
int arr[3][5];
double data[2][8];

解释:上述代码中出现的信息
• 3表⽰数组有3⾏
• 5表⽰每⼀⾏有5个元素
• int 表⽰数组的每个元素是整型类型
• arr 是数组名,可以根据⾃⼰的需要指定名字
data数组意思基本⼀致。

不完全初始化
int arr1[3][5] = {1,2};
int arr2[3][5] = {0};

在这里插入图片描述

完全初始化
int arr3[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};

在这里插入图片描述

按照⾏初始化
int arr4[3][5] = {{1,2},{3,4},{5,6}};

在这里插入图片描述

初始化时省略⾏,但是不能省略列
int arr5[][5] = {1,2,3};
int arr6[][5] = {1,2,3,4,5,6,7};
int arr7[][5] = {{1,2}, {3,4}, {5,6}};

在这里插入图片描述

⼆维数组的下标

⼆维数组的⾏是从0开始的,列也是从0开始的
在这里插入图片描述

#include <stdio.h>
int main()
{
	 int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
	 printf("%d\n", arr[2][4]);
	 return 0;
}

结果:7

⼆维数组的输⼊和输出

够按照⼀定的规律产⽣所有的⾏和列的数字就⾏;以上⼀段代码中的arr数组为例,⾏的选择范围是0-2,列的取值范围是0~4,所以我们可以借助循环实现⽣成所有的下标。

#include <stdio.h>
int main()
{
	 int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
	 int i = 0;//遍历⾏
	 //输⼊
	 for(i=0; i<3; i++) //产⽣⾏号
	 {
		 int j = 0;
		 for(j=0; j<5; j++) //产⽣列号
		 {
		 scanf("%d", &arr[i][j]); //输⼊数据
		 }
	 }
	 //输出
	 for(i=0; i<3; i++) //产⽣⾏号
	 {
		 int j = 0;
		 for(j=0; j<5; j++) //产⽣列号
		 {
		 printf("%d ", arr[i][j]); //输出数据
		 }
		 printf("\n");
	 }
	 return 0;
}
⼆维数组在内存中的存储
1 #include <stdio.h>
2 int main()
3 {
4 	int arr[3][5] = { 0 };
5 	int i = 0;
6 	int j = 0;
7 	for (i = 0; i < 3; i++)
8 	{
9 		for (j = 0; j < 5; j++)
10 		{
11 			printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);
12 		}
13 	}
14 	return 0;
15 }

在这里插入图片描述
从输出的结果来看,每⼀⾏内部的每个元素都是相邻的,地址之间相差4个字节,跨⾏位置处的两个元素(如:arr[0][4]和arr[1][0])之间也是差4个字节,所以⼆维数组中的每个元素都是连续存放的
如下图所⽰:
在这里插入图片描述

C99中的变⻓数组

C99中给⼀个变⻓数组(variable-length array,简称 VLA)的新特性,允许我们可以使⽤变量指定数组⼤⼩。

int n = a+b;
int arr[n];

上⾯⽰例中,数组 arr 就是变⻓数组,因为它的⻓度取决于变量 n 的值,编译器没法事先确定,只有运⾏时才能知道 n 是多少。
变⻓数组的根本特征,就是数组⻓度只有运⾏时才能确定,所以变⻓数组不能初始化。它的好处是程序员不必在开发时,随意为数组指定⼀个估计的⻓度,程序可以在运⾏时为数组分配精确的⻓度。有⼀个⽐较迷惑的点,变⻓数组的意思是数组的⼤⼩是可以使⽤变量来指定的,在程序运⾏的时候,根据变量的⼤⼩来指定数组的元素个数,⽽不是说数组的⼤⼩是可变的。数组的⼤⼩⼀旦确定就不能再变化了

#include <stdio.h>
int main()
{
	 int n = 0;
	 scanf("%d", &n);//根据输⼊数值确定数组的⼤⼩
	 int arr[n];
	 int i = 0;
	 for (i = 0; i < n; i++)
	 {
	 	scanf("%d", &arr[i]);
	 }
	 for (i = 0; i < n; i++)
	 {
	 	printf("%d ", arr[i]);
	 }
	 return 0;
}

数组练习

练习1:多个字符从两端移动,向中间汇聚
编写代码,演⽰多个字符从两端移动,向中间汇聚

#include <stdio.h>
int main()
{
	// 定义两个字符数组,分别存储源字符串和目标字符串
	char arr1[] = "hellow,world!!!!!";
	char arr2[] = "#################";
	// 定义左右指针,用于字符串的替换操作
	int left = 0;
	int right = strlen(arr1) - 1;
	// 打印初始状态的目标字符串
	printf("%s\n", arr2);
	// 循环替换字符串中的字符
	while (left <= right)
	{
		arr2[left] = arr1[left];
		arr2[right] = arr1[right];
		left++;
		right--;
		printf("%s\n", arr2);
		Sleep(1000);// 暂停1000毫秒(1秒)
		system("cls");// 清屏
	}
	printf("%s\n", arr2);
	return 0;
}

练习2:⼆分查找
在⼀个升序的数组中查找指定的数字n,很容易想到的⽅法就是遍历数组,但是这种⽅法效率⽐较低。⽐如我买了⼀双鞋,你好奇问我多少钱,我说不超过300元。你还是好奇,你想知道到底多少,我就让你猜,你会怎么猜?你会1,2,3,4…这样猜吗?显然很慢;⼀般你都会猜中间数字,⽐如:150,然后看⼤了还是⼩了,这就是⼆分查找,也叫折半查找。

#include <stdio.h>
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int left = 0;
	int right = sizeof(arr) / sizeof(arr[0]) - 1;
	int key = 7;//要找的数字
	int mid = 0;//记录中间元素的下标
	int find = 0;
	while (left <= right)
	{
		mid = left + (right - left) / 2;
		if (arr[mid] > key)
		{
			right = mid - 1;
		}
		else if (arr[mid] < key)
		{
			left = mid + 1;
		}
		else
		{
			find = 1;
			break;
		}
	}
	if (1 == find)
		printf("找到了,下标是%d\n", mid);
	else
		printf("找不到\n");
}

函数

库函数相关头⽂件:https://zh.cppreference.com/w/c/header

库函数的学习和查看⼯具很多,⽐如:
C/C++官⽅的链接:https://zh.cppreference.com/w/c/header
cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/

举例:sqrt

double sqrt (double x);
//sqrt 是函数名
//x 是函数的参数,表⽰调⽤sqrt函数需要传递⼀个double类型的值
//double 是返回值类型 - 表⽰函数计算的结果是double类型的值

⾃定义函数

函数的语法形式
ret_type fun_name(形式参数)
{

}

• ret_type 是函数返回类型
• fun_name 是函数名
• 括号中放的是形式参数
• {}括起来的是函数体

• ret_type 是⽤来表⽰函数计算结果的类型,有时候返回类型可以是 void ,表⽰什么都不返回
• fun_name 是为了⽅便使⽤函数;就像⼈的名字⼀样,有了名字⽅便称呼,函数有了名字⽅便调⽤,所以函数名尽量要根据函数的功能起的有意义。
• 函数的参数就相当于,⼯⼚中送进去的原材料,函数的参数也可以是 void ,明确表⽰函数没有参数。如果有参数,要交代清楚参数的类型和名字,以及参数个数。
• {}括起来的部分被称为函数体,函数体就是完成计算的过程。

函数的举例

写⼀个加法函数,完成2个整型变量的加法操作。

1 #include <stdio.h>
比特就业课主页:https://m.cctalk.com/inst/s9yewhfr
2
3 int main()
4 {
5 	int a = 0;
6 	int b = 0;
7	 //输⼊
8 	scanf("%d %d", &a, &b);
9 	//调⽤加法函数,完成a和b的相加
10 	//求和的结果放在r中
11 	//to do
12 
13 	//输出
14 	printf("%d\n", r);
15 	return 0;
16 }

我们根据要完成的功能,给函数取名:Add,函数Add需要接收2个整型类型的参数,函数计算的结果也是整型。
所以我们根据上述的分析写出函数:

1 #include <stdio.h>
2 int Add(int x, int y)
3 {
4 	int z = 0;
5 	z = x+y;
6 	return z;
7 }
8
9 int main()
10 {
11 	int a = 0;
12 	int b = 0;
13 	//输⼊
14 	scanf("%d %d", &a, &b);
15 	//调⽤加法函数,完成a和b的相加
16 	//求和的结果放在r中
17 	int r = Add(a, b);
18 	//输出
19 	printf("%d\n", r);
20 	return 0;
21 }

Add函数也可以简化为:

1 int Add(int x, int y)
2 {
3 return x+y;
4 }

形参和实参

在函数使⽤的过程中,把函数的参数分为,实参和形参。
再看看我们前⾯写的代码:

1 #include <stdio.h>
2 int Add(int x, int y)
3 {
4 	int z = 0;
5 	z = x+y;
6 	return z;
7 }
8
9 int main()
10 {
11 	int a = 0;
12 	int b = 0;
13 	//输⼊
14 	scanf("%d %d", &a, &b);
15 	//调⽤加法函数,完成a和b的相加
16 	//求和的结果放在r中
17 	int r = Add(a, b);
18 	//输出
19 	printf("%d\n", r);
20	return 0;
21 }
实参

在上⾯代码中,第2~7⾏是 Add 函数的定义,有了函数后,再第17⾏调⽤Add函数的。
我们把第17⾏调⽤Add函数时,传递给函数的参数a和b,称为实际参数,简称实参。
实际参数就是真实传递给函数的参数。

形参

在上⾯代码中,第2⾏定义函数的时候,在函数名 Add 后的括号中写的 x 和 y ,称为形式参数,简称形参。
为什么叫形式参数呢?实际上,如果只是定义了 Add 函数,⽽不去调⽤的话, Add 函数的参数 x和 y 只是形式上存在的,不会向内存申请空间,不会真实存在的,所以叫形式参数。形式参数只有在函数被调⽤的过程中为了存放实参传递过来的值,才向内存申请空间,这个过程就是形参的实例化。

return 语句

在函数的设计中,函数中经常会出现return语句,这⾥讲⼀下return语句使⽤的注意事项。
• return后边可以是⼀个数值,也可以是⼀个表达式,如果是表达式则先执⾏表达式,再返回表达式的结果。
• return后边也可以什么都没有,直接写 return; 这种写法适合函数返回类型是void的情况。
• return返回的值和函数返回类型不⼀致,系统会⾃动将返回的值隐式转换为函数的返回类型。
• return语句执⾏后,函数就彻底返回,后边的代码不再执⾏。
• 如果函数中存在if等分⽀的语句,则要保证每种情况下都有return返回,否则会出现编译错误。

数组做函数参数

在使⽤函数解决问题的时候,难免会将数组作为参数传递给函数,在函数内部对数组进⾏操作。
⽐如:写⼀个函数将⼀个整型数组的内容,全部置为-1,再写⼀个函数打印数组的内容。
简单思考⼀下,基本的形式应该是这样的:

#include <stdio.h>
int main()
{
 	int arr[] = {1,2,3,4,5,6,7,8,9,10};
  	set_arr();//设置数组内容为-1
 	print_arr();//打印数组内容
 	return 0;
}

这⾥的set_arr函数要能够对数组内容进⾏设置,就得把数组作为参数传递给函数,同时函数内部在设置数组每个元素的时候,也得遍历数组,需要知道数组的元素个数。所以我们需要给set_arr传递2个参数,⼀个是数组,另外⼀个是数组的元素个数。仔细分析print_arr也是⼀样的,只有拿到了数组和元素个数,才能遍历打印数组的每个元素。

#include <stdio.h>
int main()
{
 int arr[] = {1,2,3,4,5,6,7,8,9,10};
 int sz = sizeof(arr)/sizeof(arr[0]);
 set_arr(arr, sz);//设置数组内容为-1
 print_arr(arr, sz);//打印数组内容
 return 0;
}

数组作为参数传递给了set_arr 和 print_arr 函数了,那这两个函数应该如何设计呢?
这⾥我们需要知道数组传参的⼏个重点知识:
• 函数的形式参数要和函数的实参个数匹配
• 函数的实参是数组,形参也是可以写成数组形式的
• 形参如果是⼀维数组,数组⼤⼩可以省略不写
• 形参如果是⼆维数组,⾏可以省略,但是列不能省略
• 数组传参,形参是不会创建新的数组的
• 形参操作的数组和实参的数组是同⼀个数组

根据上述的信息,我们就可以实现这两个函数:

void set_arr(int arr[], int sz)
{
 	int i = 0;
 	for(i=0; i<sz; i++)
 	{
 		arr[i] = -1;
  	}
}
void print_arr(int arr[], int sz)
{
 	int i = 0;
 	for(i=0; i<sz; i++)
 	{
		 printf("%d ", arr[i]);
 	}
 	printf("\n");
}

嵌套调⽤和链式访问

嵌套调⽤

嵌套调⽤就是函数之间的互相调⽤,每个函数就像⼀个乐⾼零件,正是因为多个乐⾼的零件互相⽆缝的配合才能搭建出精美的乐⾼玩具,也正是因为函数之间有效的互相调⽤,最后写出来了相对⼤型的程序。
假设我们计算某年某⽉有多少天?如果要函数实现,可以设计2个函数:
• is_leap_year():根据年份确定是否是闰年
• get_days_of_month():调⽤is_leap_year确定是否是闰年后,再根据⽉计算这个⽉的天数

int is_leap_year(int y)
{
 	if(((y%4==0)&&(y%100!=0))||(y%400==0))
 		return 1;
 	else
 		return 0;
}
int get_days_of_month(int y, int m)
{
 	int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
 	int day = days[m];
 	if (is_leap_year(y) && m == 2)
 		day += 1;
 
 	return day;
}
int main()
{
 	int y = 0;
 	int m = 0;
 	scanf("%d %d", &y, &m);
 	int d = get_days_of_month(y, m);
 	printf("%d\n", d);
 	return 0;
}

这⼀段代码,完成了⼀个独⽴的功能。代码中反应了不少的函数调⽤:
• main 函数调⽤ scanf 、 printf 、 get_days_of_month
• get_days_of_month 函数调⽤ is_leap_year
未来的稍微⼤⼀些代码都是函数之间的嵌套调⽤,但是函数是不能嵌套定义的。

链式访问

所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条⼀样将函数串起来就是函数的链式访问。
⽐如:

#include <stdio.h>
int main()
{
 	int len = strlen("abcdef");//1.strlen求⼀个字符串的⻓度
 	printf("%d\n", len);//2.打印⻓度 
 	return 0;
}

前⾯的代码完成动作写了2条语句,把如果把strlen的返回值直接作为printf函数的参数呢?这样就是⼀个链式访问的例⼦了。

#include <stdio.h>
int main()
{
 	printf("%d\n", strlen("abcdef"));//链式访问
 	return 0;
}

在看⼀个有趣的代码,下⾯代码执⾏的结果是什么呢?

#include <stdio.h>
int main()
{
 	printf("%d", printf("%d", printf("%d", 43)));
 	return 0;
}

这个代码的关键是明⽩ printf 函数的返回是啥?

 int printf ( const char * format, ... );

printf函数返回的是打印在屏幕上的字符的个数。
上⾯的例⼦中,我们就第⼀个printf打印的是第⼆个printf的返回值,第⼆个printf打印的是第三个printf的返回值。
第三个printf打印43,在屏幕上打印2个字符,再返回2
第⼆个printf打印2,在屏幕上打印1个字符,再放回1
第⼀个printf打印1
所以屏幕上最终打印:4321

多个⽂件

⼀般在企业中我们写代码时候,代码可能⽐较多,不会将所有的代码都放在⼀个⽂件中;我们往往会根据程序的功能,将代码拆分放在多个⽂件中。
⼀般情况下,函数的声明、类型的声明放在头⽂件(.h)中,函数的实现是放在源⽂件(.c)⽂件中。
如下:

add.c

1 //函数的定义
2 int Add(int x, int y)
3 {
4 return x+y;
5 }

add.h

1 //函数的声明
2 int Add(int x, int y);

test.c

 #include <stdio.h>
 #include "add.h"

 int main()
 {
	int a = 10;
	int b = 20;
 	//函数调⽤
 	int c = Add(a, b);
 	printf("%d\n", c);
 	return 0;
}

static 和 extern

static 和 extern 都是C语⾔中的关键字。
static 是 静态的 的意思,可以⽤来:
• 修饰局部变量
• 修饰全局变量
• 修饰函数
extern 是⽤来声明外部符号的。
作⽤域(scope)是程序设计概念,通常来说,⼀段程序代码中所⽤到的名字并不总是有效(可⽤)的,⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域。

  1. 局部变量的作⽤域是变量所在的局部范围。
  2. 全局变量的作⽤域是整个⼯程(项⽬)。

⽣命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。

  1. 局部变量的⽣命周期是:进⼊作⽤域变量创建,⽣命周期开始,出作⽤域⽣命周期结束。
  2. 全局变量的⽣命周期是:整个程序的⽣命周期。
static 修饰局部变量:
//代码1
#include <stdio.h>
void test()
{
	 int i = 0;
	 i++;
	 printf("%d ", i);
}
int main()
{
	 int i = 0;
	 for(i=0; i<5; i++)
	 {
	 	test();
	 }
	 return 0;
}
//代码2
#include <stdio.h>
void test()
{
	 //static修饰局部变量
	 static int i = 0;
	 i++;
	 printf("%d ", i);
}
int main()
{
	 int i = 0;
	 for(i=0; i<5; i++)
	 {
	 	test();
	 }
	 return 0;
}

对⽐代码1和代码2的效果,理解 static 修饰局部变量的意义。
代码1的test函数中的局部变量i是每次进⼊test函数先创建变量(⽣命周期开始)并赋值为0,然后++,再打印,出函数的时候变量⽣命周期将要结束(释放内存)。
代码2中,我们从输出结果来看,i的值有累加的效果,其实 test函数中的i创建好后,出函数的时候是不会销毁的,重新进⼊函数也就不会重新创建变量,直接上次累积的数值继续计算。

结论:static修饰局部变量改变了变量的⽣命周期,⽣命周期改变的本质是改变了变量的存储类型,本
来⼀个局部变量是存储在内存的栈区的,但是被 static 修饰后存储到了静态区。存储在静态区的变
量和全局变量是⼀样的,⽣命周期就和程序的⽣命周期⼀样了,只有程序结束,变量才销毁,内存才
回收。但是作⽤域不变的。

使⽤建议:未来⼀个变量出了函数后,我们还想保留值,等下次进⼊函数继续使⽤,就可以使⽤static修饰。

static 修饰全局变量

在这里插入图片描述
extern 是⽤来声明外部符号的,如果⼀个全局的符号在A⽂件中定义的,在B⽂件中想使⽤,就可以使⽤ extern 进⾏声明,然后使⽤。
代码1正常,代码2在编译的时候会出现链接性错误

结论:
⼀个全局变量被static修饰,使得这个全局变量只能在本源⽂件内使⽤,不能在其他源⽂件内使⽤。
本质原因是全局变量默认是具有外部链接属性的,在外部的⽂件中想使⽤,只要适当的声明就可以使⽤;但是全局变量被 static 修饰之后,外部链接属性就变成了内部链接属性,只能在⾃⼰所在的源⽂件内部使⽤了,其他源⽂件,即使声明了,也是⽆法正常使⽤的。
使⽤建议:如果⼀个全局变量,只想在所在的源⽂件内部使⽤,不想被其他⽂件发现,就可以使⽤static修饰。

static 修饰函数

在这里插入图片描述
代码1是能够正常运⾏的,但是代码2就出现了链接错误。
其实 static 修饰函数和 static 修饰全局变量是⼀模⼀样的,⼀个函数在整个⼯程都可以使⽤,被static修饰后,只能在本⽂件内部使⽤,其他⽂件⽆法正常的链接使⽤了。
本质是因为函数默认是具有外部链接属性,具有外部链接属性,使得函数在整个⼯程中只要适当的声明就可以被使⽤。但是被 static 修饰后变成了内部链接属性,使得函数只能在⾃⼰所在源⽂件内部使⽤。
使⽤建议:⼀个函数只想在所在的源⽂件内部使⽤,不想被其他源⽂件使⽤,就可以使⽤ static 修饰。

作业

20240308(20240711)

4. C语言规定,在一个源程序中,main函数的位置

C语言规定,在一个源程序中,main函数的位置(C )
A.必须在最开始
B.必须在库函数的后面
C.可以任意
D.必须在最后

main函数的位置可以在任意位置,但是如果在主函数之中调用了哪些函数,必须在main函数前对其所调用函数进行生命或包含其被调用函数的头文件。
因此:选择C

5. 关于main函数错误的是?

关于main函数错误的是?(B、C)
A.main函数是程序的入口
B.一个C语言程序中可以写多个main函数
C.main函数的名字是可以随便改的,比如:mian
D.main函数的名字是固定的,一个工程中,有且仅有一个main函数

一个C语言程序中的main函数,有且仅有一个
main函数的名字是固定的,编译器以main函数作为程序的入口,程序是从main函数的第一行开始执行的。

7. 下面哪个不是关键字

下面哪个不是关键字:(C)
A.int
B.struct
C.define
D.continue

C语言关键字:C语言定义的,具有特定含义、专门用于特殊用途的C语言标识符,也称为保留字。
define不是关键字,是编译器实现的,用来定义宏的预处理指令,不是C语言中的内容。
int、struct和continue都是C语言中包含的关键字。

8. 用在switch语言中的关键字不包含哪个?

用在switch语言中的关键字不包含哪个?(A)
A.continue
B.break
C.default
D.case

switch是用来进行多分支选择的语句,一般结构是:

  switch(变量表达式)
  {
    case xx1:
     // ...
     break;
    case xx2
     // ...
     break;
    default:
      // ...
  }

当变量表达式的内容与某个case后的常量相等后,就执行该case下的语句,break表示该case以后的内容不会执行,如果没有跟break,会继续执行当前case之后的case分支。
当变量表达式的内容没有与那个case匹配,就会执行default下的内容。
switch中常用的关键字:case 、break、 default,当然case中语句比较复杂时,可能会用if进行判断。
continue是用来结束本次循环的,而switch不是循环,因此其中不能使用continue关键字。

10. 关于字符的ASCII编码错误的是?

关于字符的ASCII编码错误的是?(A)
A.小写字母的ASCII码值+32就能得到对应的大写字母的ASCII码值
B.ASCII码值从0~31 这32个字符是非打印控制字符,在界面上不显示任何东西,比如:蜂鸣、回车、换行
C.数字字符0~9的ASCII码值是连续递增的
D.最初ASCII编码中只有128个字符的编码

小写字母的ASCII码值-32就能得到对应的大写字母的ASCII码值。
小写字母的ASCII码值比对应的大写字母的ASCII码值更大的。

20240330(20240714)

5. 关于C语言变量说法错误的是?

关于C语言变量说法错误的是?©
A.变量是用来描述生活中经常发生变化的值
B.变量可以分为局部变量和全局变量
C.局部变量是放在内存的静态区的,全局变量是放在内存的栈区
D.当全局变量和局部变量名字相同的情况,且都可以使用的时候,局部变量优先

局部变量是放在内存的栈区的,全局变量是放在内存的静态区

6. 关于C语言算术操作符说法正确的是?

关于C语言算术操作符说法正确的是?(D)
A.除号两边都是小数才能执行小数除法
B.%操作符的操作数可以是整数,也可以是小数
C.%操作符计算的结果是两个操作数整除之后的商
D.负数求模的规则是,结果的正负号由第一个运算数的正负号决定。

A:除号两边只要有一个是小数,就执行小数除法
B:%操作符的操作数只能是整数
C:%操作符计算的结果是两个操作数整除之后的余数

8. 下面代码的结果是

下面代码的结果是:(B)

#include <stdio.h>
int main()
{
	int a, b, c;
	a = 5;
	c = ++a;
	b = ++c, c++, ++a, a++;
	b += a++ + c;
	printf("a = %d b = %d c = %d\n:", a, b, c);
	return 0;
}

A.a = 8 b = 23 c = 8
B.a = 9 b= 23 c = 8
C.a = 9 b = 25 c = 8
D.a = 9 b = 24 c = 8

++运算符:分为前置++和后置++,
前置++:先加1,后使用,即先使用变量中内容,然后给结果加1
后置++:先使用变量中内容,整个表达式结束时,给变量加1

#include <stdio.h>
int main()
{
	int a, b, c;
	a = 5;
	c = ++a;// ++a:加给a+1,结果为6,用加完之后的结果给c赋值,因此:a = 6  c = 6
	b = ++c, c++, ++a, a++;
   // 这里先算b=++c, b得到的是++c后的结果,b是7
   // b=++c 和后边的整体构成逗号表达式,依次从左向右计算的。
   // 表达式结束时,c++和,++a,a++会给a+2,给c加1,此时c:8,a:8,b:7
	b += a++ + c; // a先和c加,结果为16,在加上b的值7,比的结果为23,最后给a加1,a的值为9
	printf("a = %d b = %d c = %d\n:", a, b, c); // a:9, b:23, c:8
	return 0;
}

20240401(20240714)

1. 关于scanf函数说法正确的是?

关于scanf函数说法正确的是?(A)
A.scanf中也有占位符,占位符和后边的参数提供的地址一一对应。
B.scanf()处理所有占位符时,会自动过滤起首的空白字符,包括空格、制表符、换行符
C.scanf的占位符%s表示读取一个字符串,遇到空白字符也全部读取
D.scanf是库函数,库函数不需要包含头文件

B:scanf()处理数值占位符时,会自动过滤空白字符,包括空格、制表符、换行符,scanf输出字符占位符的时候,不忽略空白字符,总是返回当前第一个字符,无论该字符是否为空格。
C:scanf的占位符%s表示读取一个字符串,遇到空白字符就停止读取。
D:scanf需要包含stdio.h这个头文件的

4. 计算带余除法

描述
给定两个整数a和b (0 < a,b < 10,000),计算a除以b的整数商和余数。
输入描述:
一行,包括两个整数a和b,依次为被除数和除数(不为零),中间用空格隔开。
输出描述:
一行,包含两个整数,依次为整数商和余数,中间用一个空格隔开。

#include <stdio.h>

int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d", &a, &b);
    int m = a/b;
    int n = a%b;
    printf("%d %d\n", m, n);
    
    return 0;
}

5. 你是天才吗?

描述
据说智商140以上者称为天才,KiKi想知道他自己是不是天才,请帮他编程判断。输入一个整数表示一个人的智商,如果大于等于140,则表明他是一个天才,输出“Genius”。
输入描述:
多组输入,每行输入包括一个整数表示的智商。
输出描述:
针对每行输入,输出“Genius”。

#include <stdio.h>
int main()
{
    int n = 0;
    while(scanf("%d", &n) != EOF)
    {
        if(n>=140)
            printf("Genius");
    }
    return 0;
}

6. 判断2个数的大小

描述
KiKi想知道从键盘输入的两个数的大小关系,请编程实现。
输入描述:
题目有多组输入数据,每一行输入两个整数(范围-231~231-1),用空格分隔。
输出描述:
针对每行输入,输出两个整数及其大小关系,数字和关系运算符之间没有空格,详见输入输出样例。

#include <stdio.h>
int main()
{
    int num1 = 0;
    int num2 = 0;
    while(scanf("%d%d", &num1, &num2) != EOF)
    {
        if(num1>num2)
            printf("%d>%d\n", num1, num2);
        else if(num1<num2)
            printf("%d<%d\n", num1, num2);
        else
            printf("%d=%d\n", num1, num2);
    }
    return 0;
}

8. 线段图案

描述
KiKi学习了循环,BoBo老师给他出了一系列打印图案的练习,该任务是打印用“”组成的线段图案。
输入描述:
多组输入,一个整数(1~100),表示线段长度,即“
”的数量。
输出描述:
针对每行输入,输出占一行,用“*”组成的对应长度的线段。

#include <stdio.h>
int main()
{
    int n = 0;
    while(scanf("%d", &n)!=EOF)
    {
        int i = 0;
        for(i=0; i<n; i++)
        {
            printf("*");
        }
        printf("\n");
    }
    return 0;
}

11. 写一个代码打印1-100之间所有3的倍数的数字

/*
解题思路:
1. 3的倍数一定能够被3整除,因此i%3==0表达式成立时,则i一定是3的倍数
2. 要输出1~100之间的3的倍数,那只需要从1~100循环100次即可,每次拿到i之后,用i%3==0检测
   如果成立:i是3的倍数,输出
   如果不成立:i不是3的倍数
*/
#include <stdio.h>
int main()
{
    int i = 0;
    for(i=1; i<=100; i++)
    {
        if(i%3==0)
        {
            printf("%d ", i);
        }
    }
    return 0;
}

12. 写代码将三个整数数按从大到小输出。

#include <stdio.h>
int main()
{
    int a = 2;
    int b = 3;
    int c = 1;
    scanf("%d%d%d",&a, &b,&c);
    if(a<b)
    {
        int tmp = a;
        a = b;
        b = tmp;
    }
    if(a<c)
    {
        int tmp = a;
        a = c;
        c = tmp;
    }
    if(b<c)
    {
        int tmp = b;
        b = c;
        c = tmp;
    }
    printf("a=%d b=%d c=%d\n", a, b, c);
    return 0;
}

20240407(20240720)

8.关于while(条件表达式) 循环体,以下叙述正确的是(B)? (假设循环体里面没有break,continue,return,goto等等语句)

A.循环体的执行次数总是比条件表达式的执行次数多一次
B.条件表达式的执行次数总是比循环体的执行次数多一次
C.条件表达式的执行次数与循环体的执行次数一样
D.条件表达式的执行次数与循环体的执行次数无关

答案解析:

while(条件表达式)
循环体
while循环中,当条件表达式成立时,才会执行循环体中语句,每次执行期间,都会对循环因子进行修改(否则就成为死循环),修改完成后如果while条件表达式成立,继续循环,如果不成立,循环结束
故:while循环条件将会比循环体多执行一次。
因此:选择B

9.求10 个整数中最大值

//求10 个整数中最大值
#include <stdio.h>

int main() {
    int numbers[10], maxNumber;

    printf("请输入10个整数:\n");
    for (int i = 0; i < 10; i++) {
        scanf("%d", &numbers[i]);
        if (i == 0 || numbers[i] > maxNumber) {
            maxNumber = numbers[i];
        }
    }

    printf("最大值是:%d\n", maxNumber);
    return 0;
}

/*
思路:
1. 采用循环的方式输入一个数组
2. 使用max标记数组中的最大值,采用循环的方式依次获取数组中的每个元素,与max进行比较,如果arr[i]大于    max,更新max标记的最大值,数组遍历结束后,max中保存的即为数组中的最大值。
*/
int main()
{
	int arr[10] = {0};
	int i = 0;
	int max = 0;

	for(i=0; i<10; i++)
	{
		scanf("%d", &arr[i]);
	}
	//
	max = arr[0];
	for(i=1; i<10; i++)
	{
		if(arr[i]>max)
			max = arr[i];
	}
	printf("max = %d\n", max);
	return 0;
}

10.分数求和,计算1/1-1/2+1/3-1/4+1/5 …… + 1/99 - 1/100 的值,打印出结果

/*
思路:
1. 从上述表达式可以分析出
   a. 该表达式主要由100项,基数项为正,偶数项为负
2. 设置一个循环从1~100,给出表达式中的每一项:1.0/i, 注意此处不能使用1,否则结果全部为0
    然后使用flag标记控制奇偶项,奇数项为正,偶数项为负
    然后将所有的项相加即可
*/

#include <stdio.h>

int  main()
{
	int i = 0;
	double sum = 0.0;
	int flag = 1;
	for(i=1; i<=100; i++)
	{
		sum += flag*1.0/i;
		flag = -flag;
	}
	printf("%lf\n", sum);
	return 0;
}

11.数9的个数,1到 100 的所有整数中出现多少个数字9

/*
思路:
1. 给一个循环从1遍历到100,拿到每个数据后进行一下操作
2.  a. 通过%的方式取当前数据的个位,检测个位数据是否为9
         如果是,给计数器加1
    b. 通过/的方式取当前数据的十位,检测十位数据是否是9,
          如果是,给计数器加1
  循环一直继续,直到所有的数据检测完,所有9的个数已经统计在count计数中。
*/
#include <stdio.h>


int main()
{
	int i = 0;
	int count = 0;


	for(i=1; i<=100; i++)
	{
		if(i%10==9)
			count++;
		if(i/10==9)
			count++;
	}
	printf("%d\n", count);
	return 0;
}

13.给定两个数,求这两个数的最大公约数

例如:
输入:20 40
输出:20

/*
最大公约数:即两个数据中公共约数的最大者。
求解的方式比较多,暴力穷举、辗转相除法、更相减损法、Stein算法算法
此处主要介绍:辗转相除法


思路:
例子:18和24的最大公约数
第一次:a = 18  b = 24  c = a%b = 18%24 = 18
      循环中:a = 24   b=18
第二次:a = 24   b = 18  c = a%b = 24%18 = 6
      循环中:a = 18   b = 6


第三次:a = 18   b = 6   c=a%b = 18%6 = 0
  循环结束
  
此时b中的内容即为两个数中的最大公约数。
*/


int main()
{
	int a = 18;
	int b = 24;
	int c = 0;

	while(c=a%b)
	{
		a = b;
		b = c;
	}

	printf("%d\n", b);
	return 0;
}

20240408(20240720)

3.关于一维数组描述不正确的是:(D)

A.数组的下标是从0开始的
B.数组在内存中是连续存放的
C.数组名表示首元素的地址
D.随着数组下标的由小到大,地址由高到低

答案解析:
A:正确,C语言规定,数组的下标是从0开始的
B:正确,数组的空间是一段连续的内存空间
C:正确,数组名既可以表示数组的地址,也可以表示数组首元素的地址,两个在数值上是一样的,但是含义不一样。
注意:数组名只有在sizeof和&后才代表整个数组,其它都表示首元素的地址
D:错误,这个要是系统而定,一般都是下标由小到大,地址由低到高
因此,选择D

5.定义了一维 int 型数组 a[10] 后,下面错误的引用是:(C)

A.a[0] = 1;
B.a[0] = 5*2;
C.a[10] = 2;
D.a[1] = a[2] * a[0];

解析
在C语言中,数组的下标是从0开始的。因此,一个包含10个元素的数组a[10]的下标范围是0到9。
选项A、B和D都是正确的引用:
A. a[0] = 1; 这是对数组第一个元素的正确引用。
B. a[0] = 5*2; 这也是对数组第一个元素的正确引用,并且进行了计算。
D. a[1] = a[2] * a[0]; 这是对数组元素的正确引用,并且进行了计算。
选项C是错误的引用:
C. a[10] = 2; 这是对数组第11个元素的引用,但数组a只有10个元素,下标范围是0到9,所以a[10]超出了数组的范围,会导致数组越界错误。

6.给出以下定义:

char acX[] = "abcdefg";
char acY[] = { 'a','b','c','d','e','f','g'};

以下说法正确的是©
A.数组acX和数组acY等价
B.数组acX和数组acY的长度相同
C.sizeof(acX)>sizeof (acY)
D.strlen (acX)>strlen (acY)
答案解析:
acX和acY都是字符数组,但是初始化表达式不同,acX和acY的区别如下:
acX:数组中总共有8个元素,分别是:‘a’,‘b’,‘c’,‘d’,‘e’,‘f’,‘g’,‘\0’
acY:数组中总共有7个元素,分别是:‘a’,‘b’,‘c’,‘d’,‘e’,‘f’,‘g’
sizeof这里求的是数组大小,数组大小等于有效元素个数*每个元素的大小。sizeof(acX) = 8,sizeof(acY) = 7
strlen求的是字符串长度,从首元素开始计算,遇见‘\0’停止,由于acY数组没有’\0‘,所以strlen(acY)的结果是个随机值

9.三角形判断

描述
KiKi想知道已经给出的三条边a,b,c能否构成三角形,如果能构成三角形,判断三角形的类型(等边三角形、等腰三角形或普通三角形)。
输入描述:
题目有多组输入数据,每一行输入三个a,b,c(0<a,b,c<1000),作为三角形的三个边,用空格分隔。
输出描述:
针对每组输入数据,输出占一行,如果能构成三角形,等边三角形则输出“Equilateral triangle!”,等腰三角形则输出“Isosceles triangle!”,其余的三角形则输出“Ordinary triangle!”,反之输出“Not a triangle!”。

#include <stdio.h>

int main()
{
    int a = 0;
    int b = 0;
    int c = 0;
    while(~scanf("%d %d %d", &a, &b, &c))
    {
        if((a+b>c) && (a+c>b) && (b+c>a))
        {
            //三角形
            if(a==b && b==c)//等边三角形
            {
                printf("Equilateral triangle!\n");
            }
            else if(((a==b)&&(b!=c)) || ((a==c)&&(c!=b)) || ((b==c)&&(c!=a)))//等腰
            {
                printf("Isosceles triangle!\n");
            }
            else
            {
                printf("Ordinary triangle!\n");
            }
        }
        else
        {
            //不是三角形
            printf("Not a triangle!\n");
        }
    }
    return 0;
}

10.输出9*9乘法口诀表

/*
思路:
两个循环进行控制
外层循环控制打印多少行
内部循环控制每行打印多少个表达式以及表达式内容,
比较简单,具体参考代码
*/
#include <stdio.h>
int main()
{
	int i = 0;
	//控制行数
	for(i=1; i<=9; i++)
	{
		//打印每一行内容,每行有i个表达式
		int j = 0;
		for(j=1; j<=i; j++)
		{
			printf("%d*%d=%2d ", i, j, i*j);
		}
		printf("\n");
	}
	return 0;
}
#include <stdio.h>

// 主函数,程序的入口点
int main() {
    // 外层循环,控制乘法表的行数
    for (int i = 1; i <= 9; i++) {
        // 内层循环,控制每行的列数
        for (int j = 1; j <= i; j++) {
            // 打印乘法表的每个元素
            printf("%d*%d=%-2d ", j, i, i * j);
        }
        // 每行结束后换行
        printf("\n");
    }
    // 返回0,表示程序成功结束
    return 0;
}

11.打印100~200之间的素数

/*
思路:
素数:即质数,除了1和自己之外,再没有其他的约数,则该数据为素数,具体方式如下
*/


//方法一:试除法
int main()
{
	int i = 0;
	int count = 0;


    // 外层循环用来获取100~200之间的所有数据,100肯定不是素数,因此i从101开始
	for(i=101; i<=200; i++)
	{
		//判断i是否为素数:用[2, i)之间的每个数据去被i除,只要有一个可以被整除,则不是素数
		int j = 0;
		for(j=2; j<i; j++)
		{
			if(i%j == 0)
			{
				break;
			}
		}
        
		// 上述循环结束之后,如果j和i相等,说明[2, i)之间的所有数据都不能被i整除,则i为素数
		if(j==i)
		{
			count++;
			printf("%d ", i);
		}
	}


	printf("\ncount = %d\n", count);
	return 0;
}


//上述方法的缺陷:超过i一半的数据,肯定不是i的倍数,上述进行了许多没有意义的运算,因此可以采用如下
// 方式进行优化
// 方法二:每拿到一个数据,只需要检测其:[2, i/2]区间内是否有元素可以被2i整除即可,可以说明i不是素数
int main()
{
	int i = 0;//
	int count = 0;


	for(i=101; i<=200; i++)
	{
		//判断i是否为素数
		//2->i-1
		int j = 0;
		for(j=2; j<=i/2; j++)
		{
			if(i%j == 0)
			{
				break;
			}
		}
		//...
		if(j>i/2)
		{
			count++;
			printf("%d ", i);
		}
	}


	printf("\ncount = %d\n", count);
	return 0;
}




/*
方法二还是包含了一些重复的数据,再优化:
如果i能够被[2, sqrt(i)]之间的任意数据整除,则i不是素数
原因:如果 m 能被 2 ~ m-1 之间任一整数整除,其二个因子必定有一个小于或等于sqrt(m),另一个大于或等于 sqrt(m)。
*/
int main()
{
	int i = 0;
	int count = 0;


	for(i=101; i<=200; i++)
	{
		//判断i是否为素数
		//2->i-1
		int j = 0;
		for(j=2; j<=sqrt(i); j++)
		{
			if(i%j == 0)
			{
				break;
			}
		}
		//...
		if(j>sqrt(i))
		{
			count++;
			printf("%d ", i);
		}
	}


	printf("\ncount = %d\n", count);
	return 0;
}


//方法4
/*
继续对方法三优化,只要i不被[2, sqrt(i)]之间的任何数据整除,则i是素数,但是实际在操作时i不用从101逐渐递增到200,因为出了2和3之外,不会有两个连续相邻的数据同时为素数
*/


int main()
{
	int i = 0;
	int count = 0;


	for(i=101; i<=200; i+=2)
	{
		//判断i是否为素数
		//2->i-1
		int j = 0;
		for(j=2; j<=sqrt(i); j++)
		{
			if(i%j == 0)
			{
				break;
			}
		}
		//...
		if(j>sqrt(i))
		{
			count++;
			printf("%d ", i);
		}
	}

	printf("\ncount = %d\n", count);
	return 0;
}
  • 30
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值