概述
一、关于分支、循环部分习题总结
1.关于while循环的概念
while(条件表达式)
循环体
while循环中,当条件表达式成立时,才会执行循环体中的语句,每次执行期间,都会对循环因子进行修改(否则就成为死循环),修改完成后如果while条件表达式成立,继续循环,如果不成立,循环结束。故:while循环条件将会比循环体多执行一次。
2.以下程序的运行结果是?
#inclde
int main(){
int a = 0, b = 0;
for (a = 1, b = 1; a <= 100; a++){
if (b >= 20) break;
if (b % 3 == 1){
b = b + 3;
continue;
}
b = b - 5;
}
printf("%dn", a);
return 0;
}
分析:
第一次循环:a = 1,b = 1--->b小于20, if不成立,b%3 == 1%3 == 1成立,b=b+3, 此时b的值为4
第二次循环:a = 2,b = 4--->b小于20, if不成立,b%3 == 4%3 == 1成立,b=b+3, 此时b的值为7
第三次循环:a = 3,b = 7--->b小于20, if不成立,b%3 == 7%3 == 1成立,b=b+3, 此时b的值为10
第四次循环:a = 4,b = 10--->b小于20,if不成立,b%3 == 10%3 == 1成立,b=b+3, 此时b的值为13
第五次循环:a = 5,b = 13--->b小于20,if不成立,b%3 == 13%3 == 1成立,b=b+3, 此时b的值为16
第六次循环:a = 6,b = 16--->b小于20,if不成立,b%3 == 16%3 == 1成立,b=b+3, 此时b的值为19
第七次循环:a = 7,b = 19--->b小于20,if不成立,b%3 == 19%3 == 1成立,b=b+3, 此时b的值为22
第八次循环:a = 8,b = 22--->b大于20,if成立,循环break提出
最后打印a:8
3.给定两个数,求这两个数的最大公约数
思路为: 有两个整数a和b, ①a%b得到余数c ②若c = 0,则b即为两数的最大公约数 ③若c ≠ 0,则a = b, b = c然后再去执行①
#include
//写法一
int main(){
int a = 0; int b = 0; int c = 0;
printf("%s:n","请输入两个整数");
scanf("%d %d", &a, &b);
do{
c = a % b;
if (c == 0){
printf("%d 为两数的最大公约数n", b);
}
a = b; b = c;
} while(c != 0);
}
//写法二
int main(){
int a = 18;
int b = 24;
int c = 0;
while(c = a % b){
a = b;
b = c;
}
printf("%dn", b)
}
4.打印出1000年到2000年之间的闰年
思路为: 能被4整除,不能被100整除的年份为闰年;或者能被400整除的年份为闰年
#include
int main(){
for (int i = 1000; i <= 2000; i++){
if (i %4 == 0 && i % 100 ! = 0 || i % 400 == 0){
printf("%d年", i);
}
}
}
5.打印出100~200之间的素数,以及素数的个数
思路为: 素数:即质数,除了1和自己之外,再没有其他的约数,则该数据为素数 方法一: 判断i是否为素数:用[2, i)之间的每个数据去被i除,只要有一个可以被整除,则不是素数
#include
int main(){
int i = 0;
int count = 0;
for (i = 100; i <= 200; i++){
int j = ;
for (j = 2; j < i; j++){
if (i % j == 0){
break;
}
}
if (j == 1){
count++;
printf("%d", i);
}
}
printf("ncount = %dn", count);
return 0;
}
方法二: 上述方法一中,超过i的一半的数据肯定不是i的倍数,进行了许多没有意义的运算,优化为,拿到一个数据i只需要检测[2, i/2]区间内是否有元素可以被i整除,可以说明i不是素数。
#include
int main(){
int i = 0;
int count = 0;
for (i = 100; i <= 200; i++){
int j = ;
for (j = 2; j < i/2; j++){
if (i % j == 0){
break;
}
}
if (j > i/2){
count++;
printf("%d", i);
}
}
printf("ncount = %dn", count);
return 0;
}
方法三: 再优化后为,如果i能够被[2, sqrt(i)]之间的任意数据整除,则i不是素数。愿意:如果 m能被2 ~ m-1之间任一整数整除,其二个因子必定有一个小于或等于sqrt(m),另一个大于或等于 sqrt(m)。
#include
int main(){
int i = 0;
int count = 0;
for(i=101; i<=200; 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 = %dn", count);
return 0;
}
方法四: 继续对方法三优化,只要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){
//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 = %dn", count);
return 0;
}
6.数9的个数,从1到100的所有整数中出现多少个数字9
思路为: ①给一个循环从1遍历到100,拿到每个数据后进行一下操作 ②a. 通过%的方式取当前数据的个位,检测个位数据是否为9,如果是,给计数器加1 b. 通过/的方式取当前数据的十位,检测十位数据是否是9,如果是,给计数器加1 循环一直继续,直到所有的数据检测完,所有9的个数已经统计在count计数中。
#include
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("%dn", count);
return 0;
}
7.分数求和,计算1/1 - 1/2 + 1/3 - 1/4 + 1/5 + … + 1/99 - 1/100的值,打印出结果
思路为: ①从上述表达式可以分析出 a. 该表达式主要由100项,基数项为正,偶数项为负 ②设置一个循环从1~100,给出表达式中的每一项:1.0/i, 注意此处不能使用1,否则结果全部为0然后使用flag标记控制奇偶项,奇数项为正,偶数项为负然后将所有的项相加即可。
#include
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("%lfn", sum);
return 0;
}
8.求最大值,求10个整数中最大值
思路:
采用循环的方式输入一个数组使用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 = %dn", max);
return 0;
9.乘法口诀表,输出9*9乘法口诀表
#include
int main(){
int i = 0;
for (i = 1; i <= 9; i++){
int j = 0;
for (j = 1; j <= i; j++){
printf("%d * %d = %d", i, j, i*j);
}
prrintf("n")
}
return 0;
}
10.猜数字游戏
int ret = rand()%100:生成[0 - 100)之间的随机数 srand((unsigned)time(NULL)):保证每次电脑生成的随机数不一样。
void menu(){
printf("**********************n");
printf("*******1.play******n");
printf("*******0.exit*********n");
printf("**********************n");
}
void game(){
int randNume = rand() % 100 + 1;
while(1){
printf("请输入你要猜的数字:n");
int num = 0;
scanf("%d", &num);
if (num < randNum){
printf("找小了n");
}
else if (num > randNum){
printf("找大了n");
}
else{
printf("找到了n");
break;
}
}
}
int main(){
srand((unsigned)time(NULL));
int imput = 0;
do{
menu();
printf("请输入你的操作:1代表玩儿,0代表退出");
scanf("%d", &input);
switch (input){
case 1:
game();
break;
case 0:
printf("退出游戏n");
default:
printf("输入有误n");
break;
}
} while (input != 0)
}
11.二分查找,编写代码在一个整形的序列数组中查找具体的每个数,找到了打印出数字所在下标,找不到输出:找不到。
思路:
找到数组的中间位置检测中间位置的数据是否与要查找的数据key相等 a: 相等,找到,打印下标,跳出循环 b: key < arr[mid], 则key可能在arr[mid]的左半侧,继续到左半侧进行二分查找 c: key > arr[mid], 则key可能在arr[mid]的右半侧,继续到右半侧进行二分查找
如果找到返回下标,否则继续,直到区间中没有元素时,说明key不在集合中,打印找不到。
易错点:
right的右半侧区间取值,该值决定了后序的写法while循环的条件是否有等号求中间位置的方法,直接相加除2容易造成溢出更改left和right的边界时,不确定是否要+1和-1
// 方法一,采用[left, right] 区间
#include
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int key = 3;
int left = 0;
int right = sizeof(arr)/sizeof(arr[0])-1; // right位置的数据可以取到
while(left<=right) // right位置有数据,必须要添加=号
{
int mid = left+(right-left)/2; //可能造成数据溢出
if(arr[mid]>key) // key小于中间位置数据,说明key可能在左半侧,需要改变右边界
{
right = mid-1; // right位置的数据可以取到,因此right=mid-1
}
else if(arr[mid]
{
left = mid+1; // left位置的数据可以取到,因此left=mid+1
}
else
{
printf("找到了,下标是:%dn", mid);
break;
}
}
if(left>right)
printf("找不到n");
return 0;
}
// 方法二,采用[left, right) 区间
#include
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int key = 3;
int left = 0;
int right = sizeof(arr)/sizeof(arr[0]); // right位置的数据取不到
while(left
{
int mid = left+(right-left)/2; //这种方法不会溢出 //int min = letf + (right - left) / 2 这种算法可能造成溢出
if(arr[mid]>key) // key小于中间位置数据,说明key可能在左半侧,需要改变右边界
{
right = mid; // right位置的数据取不到,因此right=mid,不需要减1
}
else if(arr[mid]
{
left = mid+1; // left位置的数据可以取到,因此left=mid+1
}
else
{
printf("找到了,下标是:%dn", mid);
break;
}
}
if(left>=right)
printf("找不到n");
return 0;
}
二、关于函数与递归部分习题总结
1.关于函数的细节补充:
(1)关于函数返回值: ①一个函数只能返回一个结果,可以考虑将两个数放在一个数组中,返回数组的地址 ②将形参存在数组中,修改数组中的内容,可以通过数组将修改结果带出去
//a为指针型变量,存放地址
//b为指针型变量,存放地址
void fun(int *a, int *b){ //fun(&c, &d) 传指针
*a = 10; //解引用地址后,对地址里面的值进行赋值
*b = 20;
}
void fun(int a, int b){ //fun(c, d) 传值
a = 10;
b = 20;
}
③形参如果用指针,最终指向的是外部的实参,在函数中对指向内容进行修改,改变的就是外部的实参 ④全局变量不受函数的结束而结束,在函数中改变全局变量,主调函数中可以看到改变之后的结果。 (2)关于函数的调用: ①形参按照值的方式传递,将来形参就是实参的一份临时拷贝,修改形参不会影响外部的实参。 ②函数可以传地址调用,传地址调用的时候,可以通过形参操作实参(此处拷贝的是一个地址,地址当中的值是通过解引用直接拿到的)。 ③函数可以嵌套调用,但不可以嵌套调用嵌套定义。 ④函数可以没有返回值,如果没有返回值也就不需要待会任何结果。 ⑤形参和实参在不同的函数中,即不同的作用域,因此形参和实参可以同名。 ⑥函数之间的数据传递可以使用全局变量。 ⑦函数的定义可以放在任意的文件中,使用时只需要包含头文件即可。 (3)关于递归的使用: ①递归的两个条件:Ⅰ、将问题转化为子问题,子问题与原问题具有相同解法 Ⅱ、递归的出口 ②存在限制条件,当满足这个限制条件的时候,递归便不再继续。 ③每次递归调用之后,都是将原问题进一步缩小,就会越来越接近这个限制条件。 ④因为每次递归,相当于都是一次新的函数调用,而每次函数调用系统必须给该函数划分栈帧空间,内部的递 归函数没有退出,上层的递归就不能退出,栈帧就会累积许多块,如果累积超过栈的总大小,就会栈溢出。 (4)关于函数的声明和定义: ①函数的定义可以放在任意位置,函数的声明必须放在函数的使用之前。(函数在使用之前可以先声明无定义) ②函数定义在使用之后,使用之前没有声明时,编译器编译时识别不了该函数 ③函数的声明只是告诉编译器函数返回值类型、函数名字以及函数所需要的参数,函数定义才是说明函数是怎么 实现的 (5)关于实参和形参: ①传参时不论是按照值还是指针方式传递,形参拿到的都是实参的一份拷贝。 ②形参是在函数调用的时候才实例化,才开辟内存空间(函数没有调用时,新参没有空间)。 ③如果是按照值的方式传递,形参和实参各自有各自的空间,改变形参不能改变外部的实参。 (5)关于函数的设计: ①高内聚低耦合即:函数体内部实现修改了,尽量不要对外部产生影响,否则:代码不方便维护。 ②要尽可能不使用全局变量,全局变量每个方法都可以访问,很难保证数据的正确性和安全性。 ③参数越少越好,否则用户在使用时体验不是很好,还得必须将所有参数完全搞明白才可以使用。 ④设计函数时,尽量做到谁申请的资源就由谁来释放,否则如果交给外部使用者释放,外部使用者可能不知道或者忘记,就会造成资源泄漏。 (6)关于C语言函数的描述: ①可以没有参数和返回值类型,根据需要给出。 ②函数的实参可能是变量,也可能是常量,也可能是宏,也可能是指针等等 ③在使用库函数时,必须要包含该库函数所在的头文件,否则编译器将不能识别 ④库函数是语言设计者为了让用户能够更好的使用,而给出的,但并不能解决用户的所有问题,因此其他问题还需要用户自己写方法解决
2.函数判断素数
#include
#include
int isPirme(int n){
int i = 2;
for (i = 2; i <= sqrt(n); i++){
if (n % i == 0){
return 0;
}
}
if (i > sqrt(n)){
return 1;
}
return 0;
}
int main(){
if (isPirme(30) == 0){
printf("不是素数n");
}
else{
printf("是素数n");
}
}
3.函数判断闰年
#include
int isLeapYear(int n){
if (n % 4 == 0 && n % 100 != 0 || n % 400 == 0){
return 1;
}
return 0;
}
int main(){
if (isLeapYear(2020) == 1){
printf("是闰年n");
}
else{
printf("%不是闰年n");
}
}
4.交换两个整数,实现一个函数来交换两个整数的内容
思路: 题目比较简单,此处只需要清楚传值和传指针的区别即可。 传值:形参是实参的一份拷贝,函数运行起来后,形参是形参,实参是实参,形参和实参没有任何关联性,改变形 参时,不会对实参造成任何影响。 传地址:形参是实参地址的一份拷贝,形参指向的实体是实参,对形参解引用后,拿到的内容就是实参,因此对形参解引用之后的内容进行修改,改变的就是实参.
#include
void Exchange(int *pa, int *pb)
{
int tmp = 0;
tmp = *pa;
*pa = *pb;
*pb = tmp;
}
int main(){
int a = 50;
int b = 100;
Exchange(&a, &b);
printf("交换后:%d %dn", a, b);
}
5.乘法口诀,写一个函数,打印乘法口诀表,口诀表的行数和列数自己制定
#include
void mult(int a){
int i = 1;
for (i = 1; i <= a; i++){
int j = 1;
for (j = 1; j <= i; j++){
int mul = i * j;
printf("%d * %d = %d ", i, j, mul);
}
printf("n");
}
}
int main(){
mult(9);
}
6.打印出一个数组的每一位,以递归的方式
#include
void printNumber(int n){
if (n < 10){
printf("%d ", n % 10);
}
else{
printNumber(n / 10);
printf("%d ", n % 10);
}
}
int main(){
printNumber(521);
}
7.用递归和非递归的分别实现求n的阶乘(不考虑溢出问题)
#include
//递归方法
int FacD(int n){
if (n == 1 ){
return 1;
}
int ret = n * FacD(n - 1);
return ret;
}
//循环方法
int FacX(int n)
{
int ret = 1;
for (int i = 1; i <= n; i++)
{
ret *= i;
}
return ret;
}
int main(){
printf("%dn", FacD(5));
printf("%dn", FacX(5));
}
8.用递归和非递归的方法分别实现strlen函数
#include
int MyStrlenD(const char*str){
if (*str == '