C语言强化训练(3)

前言:截止现在,我们C语言的大部分基础内容就已经讲完了,但是,光掌握基础只是还是不够的,所以小编会将自己写过的一些题整理起来,不定期更新刷题博客,希望能扩展大家的思路。

话不多说,现在就开始我们的刷题博客吧!!

选择题

题目1

请问下列表达式哪些会被编译器禁止【多选】()

复制代码
int a = 248,
 b = 4;
 int const *c = 21;
 const int *d = &a; 
int *const e = &b;
 int const * const f = &a;

A: *c = 32; B: *d = 43 C: e=&a D: f=0x321f

解释:如果 const 位于 * 的左侧,则 const 就是用来修饰指针所指向的变量,即指针指向为常量;*c和*d不能变。如果 const 位于 * 的右侧,则 const 就是修饰指针本身,即指针本身是常量;e和f不能变。

题目2

以下程序的输出结果为( )

复制代码
#include <stdio.h>

int i;

void prt()
{
    for (i = 5; i < 8; i++)
    printf("%c", '*');
    printf("\t");
} 
int main()
{
    for (i = 5; i <= 8; i++)
        prt();
   return 0;
}

解释:全局变量i,在main()中修改为5,第一次在prt()中执行循环输出三次'*',i被修改为8,回到main()中第二次调用prt()时,i<8为假,循环结束没输出,执行一次print("\t"),再次回到主函数后i++变为9,i<=8为假,循环结束;

题目3

下面代码段的输出是( )

复制代码
int main()
{
    int a=3;
    printf("%d\n",(a+=a-=a*a));
  return 0;
}

A: -6 B: 12 C: 0 D: -12

解释:a+=a-=a*a等价于a=a+(a=a-a*a),即先计算a=a-a*a,所以此时a的值为3-3*3=-6,再计算-6+(-6)=-12赋值给a,所以a的值为-12,也就是整个表达式的值,所以应选择D

题目4

关于表达式求值说法不正确的是:( )

A.表达式求值先看是否存在整形提升或算术转换,再进行计算

B.表达式真正计算的时候先看相邻操作符的优先级再决定先算谁

C.相邻操作符的优先级相同的情况下,看操作符的结合性决定计算顺序

D.只要有了优先级和结合性,表达式就能求出唯一值

解释

A. 表达式求值先看是否存在整形提升或算术转换,再进行计算

正确。C语言表达式求值确实先进行类型转换:

  • 整型提升:小于int的类型提升为int

  • 算术转换:按照类型转换规则进行转换

B. 表达式真正计算的时候先看相邻操作符的优先级再决定先算谁

正确 。优先级决定了操作符的执行顺序,例如 * 优先于 +

C. 相邻操作符的优先级相同的情况下,看操作符的结合性决定计算顺序

正确。当优先级相同时,结合性决定计算顺序:

  • 左结合:从左到右计算(如 a + b + c

  • 右结合:从右到左计算(如 a = b = c

D. 只要有了优先级和结合性,表达式就能求出唯一值

**不正确。**比如:

  1. 未定义行为:有些表达式的结果是不确定的

    复制代码
    int i = 0;
    int j = i++ + i++;  // 未定义行为
  2. 函数调用顺序:函数参数的求值顺序未定义

    复制代码
    printf("%d %d", i++, i++);  // 输出结果不确定

题目5

下面代码的结果是:

复制代码
#include <stdio.h>
int i;
int main()
{
    i--;
    if (i > sizeof(i))
    {
        printf(">\n");
    }
    else
    {
        printf("<\n");
    }
    return 0; 
}

A.>

B.<

C.不输出

D.程序有问题

解释

C语言中,0为假,非0即为真。

全局变量,没有给初始值时,编译其会默认将其初始化为0。

i的初始值为0,i--结果-1,i为整形,sizeof(i)求i类型的大小,结果是4,按照此分析来看,结果应该选择B,但是sizeof的返回值类型实际为无符号整形,因此编译器会自动将左侧i自动转换为无符号整形的数据,-1对应的无符号整形是一个非常大的数字,超过4,故实际应该选择A

题目6

下面哪些描述是正确的?

A.内存中每个bit位都有唯一的地址

B.内存中每个字节都有地址,地址可以唯一标识一个内存单元的

C.C语言中地址就是指针,指针就是地址

D.C语言中只要有了地址就可以随意读写内存空间。

解释

A. 错误 。内存是按字节编址的,每个字节有一个唯一地址,而不是每个bit位。一个字节通常包含8个bit位,这些bit共享同一个地址。

B. 正确。这是内存寻址的基本原理,每个字节都有一个唯一的地址,通过地址可以定位到具体的内存单元。

C. 正确。在C语言中,指针就是存储地址的变量,地址就是指针的值。两者本质上是同一个概念。

D.错误。这是严重的安全错误:

  1. 访问权限:有些内存区域是只读的(如代码段)

  2. 非法访问:访问未分配的内存会导致段错误

  3. 内存保护:操作系统有内存保护机制

  4. const限定:const指针指向的内容不能修改

编程题

题目1:计算一个数的每位之和(递归实现)

描述

写一个递归函数DigitSum(n),输入一个非负整数,返回组成它的数字之和

例如,调用DigitSum(1729),则应该返回1+7+2+9,它的和是19

输入:1729,输出:19

思路:递归就是将大问题拆解为小问题的过程,上面的题目我们应该如何把大问题拆解为小问题呢?我们可以这样来看:计算DigitSum(1729)是计算1729的每一位之和,而计算1729的每一位之和又可以转换为计算172的每一位之和加上9,即DigitSum(1729)=DigitSum(172)+9,172可以通过1729/10得到,9可以通过1729%10得到;而计算172的每一位之和可以转换为计算17的每一位之和加上2得到,即DigitSum(172)=DigitSum(17)+2......以此类推

那么递归的限制条件是什么?如果给定的数是个位数,那我们就无需将它再进行拆分,直接把这个数返回即可

结合上述思路,我们可以用递归写出代码:

复制代码
int DigitSum(int n)
{
	//如果是个位数,直接返回
	if (n <= 9)
	{
		return n;
	}
	return n % 10 + DigitSum(n / 10);
}

题目2:打印一个数的每一位

描述

递归方式实现打印一个整数的每一位,比如,输入1234,输出1 2 3 4

思路:与上面的题一样,还是采用大问题拆解为小问题的思想,假设我们要打印1234的每一位,可以拆解为先打印123的每一位,再打印4,即Print(1234)=Print(123)+printf("%d",4);要打印123的每一位,我们可以先打印12的每一位,再打印3,即Print(123)=Print(12)+printf("%d",3);以此类推......

那么递归的限制条件是什么呢?如果只有一位数,那么我们直接将这一位数打印出来即可。

复制代码
void Print(int n)
{
	if (n >= 10)
		Print(n / 10);
	printf("%d ", n % 10);
}

题目3:记负均正_牛客题霸_牛客网

思路:这道题比较简单,直接通过 scanf 输入数据,统计负数个数,以及正数个数,并且在统计正数个数的过程中求取正数总和,最后计算得出平均数即可。需要注意的是所有数字中0是不统计在内的。

复制代码
#include <stdio.h>
#include<stdlib.h>
int main()
{
    int n;
    scanf("%d", &n);
    int* arr = (int*)malloc(sizeof(int) * n);
    long double sum = 0;
    int count1 = 0, count2 = 0;
    for (int i = 0; i < n; i++)
    {
        scanf("%d", &arr[i]);
        if (arr[i] < 0)
        {
            count1++;
        }
        if (arr[i] > 0)
        {
            count2++;
            sum += arr[i];
        }

    }
    //要注意正数的个数为0时不能进行求平均值
    if(count2)
    { 
        long double ave = sum / count2;
    printf("%d %.11llf", count1, ave); 
    }
    else {
    printf("%d %d",count1,0);
    }
    return 0;
}

题目4:旋转数组的最小数字_牛客题霸_牛客网

思路:这道题比较好玩,如果只是要让我们求出数组中的最小值那倒还挺简单,但是由于旋转之后的数组不一定是有序的,所以我们可能直接就想到遍历整个数组取找到最小值,这样的话,时间复杂度就是O(n),但是,题目要求的时间复杂度是O(logN),所以我们不能直接遍历数组,那有什么其他思路呢?

在C语言中,我们接触过的有关数组的时间复杂度为O(logN)的算法就是二分查找了,二分查找算法的核心思想就是将查找范围不断地减半,从而提高效率。

那么这一题,我们能否也利用二分的思想呢?

如果我们要利用二分的思想,可以定义一个查找的有效区间,不妨就设为left和right,那我们就需要保证最小值总是要在left和right之间的。

我们再来观察一下所给序列有何特点?由于所给的数组是通过非降序的数组通过旋转得到的,所以一定存在一个分界线,在这个分界线之前和之后,两个序列都是从小到大有序的。

我们可以举例说明:

假设所给序列是{3,4,5,1,2},初始中间位置mid指向的值是5,而右端点right所指向的值是2,5>2,说明此时最小值一定在中间位置的右序列中,由此我们就可以缩小查找范围,使left=mid+1

假设所给的序列是{5,1,2,3,4},初始中间位置mid指向的值是2,而右端点right所指向的值是4,4>2,说明此时从中间位置到右端点的所有数据一定是有序排列的,那么最小值一定在中间位置或者中间位置的左边,那我们就有可以缩小查找范围,使right=mid

最后一种情况,中间位置指向的元素与右端点指向的元素相同,比如{1,0,1,1,1,1,1}和{1,1,1,1,1,0,1},那么此时上面的两种缩小查找范围的方法就已经不适用了,这也是查找效率最低的情况,此时我们不能直接将查找范围减半,因为要查找的元素可能在中间位置的左序列中,也可能在中间位置的右序列中,那我们只能进行逐个排查,我们已知的是中间位置指向的元素与右端点指向的元素相同,那此时我们就可以果断地舍弃右端点,直接让right--,然后再继续按照逻辑查找即可

分析了这么多,我们来写一下代码吧:

复制代码
  int minNumberInRotateArray(int* nums, int numsSize) {
    int left=0,right=numsSize-1;
    while(left<right)//当跳出循环时,left和right指向相等,此时[left,right]范围内只有一个元素,这个元素就是我们要找的最小值
    {
        int mid=left+(right-left)/2;
        if(nums[mid]>nums[right])//此时所查找的元素一定在中间位置的有序列中
            left=mid+1;
        else if(nums[mid]<nums[right])
            right=mid;
        else
            right--;
    }
    return nums[left];
}

这一小节的内容就到这里咯,小伙伴们也要自己好好练习一下哦!!!

相关推荐
草莓熊Lotso6 小时前
【C++】类型转换详解:显式与隐式转换的艺术
c++·经验分享·笔记·其他·算法
喜欢吃豆6 小时前
从像素到篇章:深入剖析光学字符识别(OCR)的技术原理
人工智能·算法·语言模型·自然语言处理·大模型·ocr
txwtech6 小时前
第9篇c#调用c++动态库报错处理
开发语言·c#
lifallen6 小时前
深入了解Flink核心:Slot资源管理机制
大数据·数据结构·数据库·算法·flink·apache
一百天成为python专家6 小时前
python爬虫之selenium库进阶(小白五分钟从入门到精通)
开发语言·数据库·pytorch·爬虫·python·深度学习·selenium
丑小鸭是白天鹅6 小时前
嵌入式C学习笔记之链表
c语言·笔记·学习
序属秋秋秋7 小时前
《C++进阶之STL》【红黑树】
开发语言·数据结构·c++·笔记·学习·stl
小欣加油7 小时前
leetcode 1576 替换所有的问号
c++·算法·leetcode·职场和发展
小白学大数据8 小时前
模拟登录与Cookie持久化:爬取中国汽车网用户专属榜单数据
开发语言·爬虫·python