【寒假小练】day1

前言

日积跬步,能至千里。

水平有限,不足之处望请斧正。


选择题

1、如下代码输出的是什么( )
char a=101;
int sum=200; 
a+=27;
sum+=a;
printf("%d\n",sum);

A:327

B:99

C:328

D:72


分析

char的表示范围是-128 ~ 127

a += 27101 + 27 = 128,即1000 0000,最高位总是符号位,所以这里表示的是最小值-128。sum += a,即200 - 128,结果为72。

正确答案:D


#char的取值范围

char是1byte,8bits的整数。

没有指定signed / unsigned时默认是signed。

unsigned char
int main()
{
    cout << UCHAR_MAX << endl;
    return 0;
}
255

8位全部存储数据,可以存储2^8个数,即0 ~ 255

signed char
int main()
{
    cout << CHAR_MIN << endl;
    cout << CHAR_MAX << endl;
    return 0;
}
-128
127

最高位是符号位,7位存储数据,同样可以存储2^8个数。

按理来说,取值范围是-127 ~ 127(2^7=128),怎么是-128 ~ 127呢?

我们先看-127 ~ 127,这样算是把+0"-0"都算上了。

但表示出"-0"没有意义啊。

  • "-0"
    • 原码:1000 0000
    • 反码:1111 1111
    • 补码:1 0000 0000
    • 截断后的补码:0000 0000

-0存到内存中以后表示的还是零值,那我们要他有啥用?

而-128就不一样

  • -128
    • 原码:1 ... 1000 0000
    • 反码:1 ... 0111 1111
    • 补码:1 ... 1000 0000
    • 截断后的原码:1000 0000
    • 截断后的反码:0111 1111
    • 截断后的补码:1000 0000

-128截断后是1000 0000,不仅其本身的值相比char能表示的其他数就是“最小值”,其截断后的补码符号位为1,其他位全0,也能很好地表示“最小值”的意思。因此大佬们将-128放入char后的截断代替没用的-0。

所以signed char真正的取值范围是-128 ~ 127

总结

signed char之所以能表示-128,是因为-127 ~ -0这128个数中,-0能表示的数没用(+0已经将其表示出来了),所以找到“-128放入char的截断”代替-0,让它作char的最小值,而且其截断后的补码恰好是1000 0000,也能很好表达最小值的意思。


2、对于下面代码执行后输出的是什么( )
int value = 1024;
char condition = *((char*)(&value));
if(condition) value += 1; condition = *((char*)(&value)); 
if(condition) value += 1; condition = *((char*)(&value)); 
printf("%d %d", value, condition);

A:1026 1

B:1025 0

C:1025 1

D:1024 0

分析

本题主要考察指针相关知识。

*((char*)(&value)):将int value的地址强转成char*,指针决定访问内存的大小——只能访问到1byte了。

char condition = *((char*)(&value));就是取value低八位的数据。

1024 = 0000 0000 0000 0000 0000 0100 0000 0000,不管是大端存储还是小端存储,低八位都是全0,所以直接打印二者的值。

总结

指针类型决定访问内存的大小


正确答案:D


3、假设在32位机器上,读代码选结果( )
void func(char para[100]) {
		void *p = malloc(100);
		printf("%d, %d\n", sizeof(para), sizeof(p)); 
}

A:4,4

B:100,4

C:4,100

D:100,100


分析

para作为一维字符数组形参,会发生“降维”——从一维数组降维成指针。具体降维的方式:降维成首元素地址。

又在32位机器上,所以 sizeof(para) = 4bytes

p指向malloc开辟的100个字节,但题中计算的是 p 本身,不是它指向的空间,所以sizeof(p) = 4bytes

总结

数组传参会降维成指向其元素的指针


正确答案:A
4、以下程序执行后的输出结果为( )
#include <stdio.h>

void func(char *p) { p = p + 1; } 
int main()
 {
		char s[] = {'1', '2', '3', '4'}; 
  	func(s);
 		printf("%c", *s);
 		return 0;

}

A:2

B:编译错误

C:1

D:无法确定

分析

传首元素地址调用func,但func没有解引用,仅改变形参本身,不影响s

所以打印的结果是 1

选C。

总结

形参不改变实参

5、已知数组D的定义是 int D[4][8]; 现在需要把这个数组作为实参传递给一个函数进行处理。下列可以作为对应的形参变量说明的是【多选】( )

A:intD[4][]

B:int*s[8]

C:int(*s)[8]

D:intD[][8]

分析

int D[4][8] 作为形参,会降维——变成指向其首元素的指针,即int (*D)[8],数组指针

A:不符合语法规范,对于n维数组,后n-1个[]必须给明。错误。

B:int*s[8]是指针数组,不符合形参int (*D)[8]的形式。错误。

C:是数组指针,没问题

D:原模原样,没问题

总结

优先级:[] > *


编程题

1. 自守数

描述

自守数是指一个数的平方的尾数等于该数自身的自然数。例如:25^2 = 625,76^2 = 5776,9376^2 = 87909376。请求出n(包括n)以内的自守数的个数

数据范围:1≤n≤10000

输入描述

int型整数

输出描述

n以内自守数的数量。

示例1

输入:

6

输出:

4

说明:

有0,1,5,6这四个自守数      
思路

题目要我们求[0, n]范围内的自守数个数。

自守数:对于一个n位数,其平方的后n位数等于这个n位数。

怎么获取平方的后n位数呢?模运算!

n % m 可获取[0, m-1]的数

  • 25^2 = 625
    • %100可获取[0, 99]的数(后2位)
    • 625 % 100 = 25
  • 9376^2 = 87909376
    • %10000可获取[0, 9999]的数(后4位)
    • 87909376 % 10000 = 9376

某个数% 10^n = 可后n位。

参考代码
#include <iostream>
using namespace std;

//自守数:n位数平方的后n位等于这个n位数
bool IsSelfPreservationNum(int n)
{
    //获取后n位
    int base = 1, tmp = n;
    while(tmp)
    {
        base *= 10;
        tmp /= 10;
    }

    //判断n和其平方的后n位是否相等
    return n == (n*n) % base;
}

int main() 
{
    //求[0, n]范围内的自守数个数
    int n = 0, cnt = 0;
    cin >> n;

    for(int i = 0; i <= n; ++i) if(IsSelfPreservationNum(i)) ++cnt;

    cout << cnt << endl;
    
    return 0;
}
总结

n % m = [0, m-1]

2. 计数质数

给定整数 n ,返回 所有小于非负整数 n 的质数的数量

示例 1:

输入:n = 10
输出:4
解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。

示例 2:

输入:n = 0
输出:0

示例 3:

输入:n = 1
输出:0 

提示:

  • 0 <= n <= 5 * 106
思路1:枚举法

质数是只能被1和自身整除的数,反之是合数(能写成 C = A * B 的形式)。

*0和1不是质数

对于一个数n,枚举[2, n-1]。试除,能整除就是合数;不能就是质数

两个优化……

优化1:

大于2的偶数不可能是质数(一定能写成 C = A * B )。

需要注意,2是质数,我们得补上。

优化2:

枚举法的思路,找合数,找不到就是质数。要想找合数,其实不必费力遍历整个区间。

因为证明C是合数只需要 C = A * B,也就是找到 AB 任意一个能整除 C 即可。而对于C = A * BAB 的最大值都是 根号C。如对于C = 16,有 1*16 2*8 4*4 ,三种组合,4*4AB最大的情况。

对于C = A * BAB 的最大值都是 根号C也能说 A^2 < CB^2 < C

参考代码
#include <iostream>
#include <algorithm>
using namespace std;

bool IsPrime(int n)
{
    //优化2
    for(int i = 2; i*i <= n; ++i) //i就是我们要找的 A 或 B, i^2 < n
        if(n % i == 0) return false;

    return true;
}

int main() 
{
    int n = 0;
    cin >> n;

    int cnt = 0;
    //优化1
    for(int i = 3; i < n; i += 2) if(IsPrime(i)) ++cnt;
    ++cnt; //补上2这个偶数
    cout << cnt << endl;

    return 0;
}
思路2:埃氏筛

枚举法没有考虑到数与数之间的联系,所以很难再继续优化时间复杂度。接下来的 厄拉多塞筛法 简称埃氏筛,就考虑到了。

大概思路:如果 x 是质数,那大于 xx的倍数 2x,3x,... 一定是合数,不是质数。

也就是,

质数的倍数一定不是质数

给一个 vector<int> isPrime(n, 1),isPrime[i] 为1表示是质数,为0表示不是质数。

根据题意,从小到大遍历 [2,n] :如果这个数是质数,将比其大的所有的倍数都标记为合数。举个例子,求[2,10]的质数数量。

要遍历的数:[2, 3, 4, 5, 6, 7, 8, 9, 10]
	isPrime [1, 1, 1, 1, 1, 1, 1, 1,  1]

2是质数,其倍数 4 6 8 10 标记为非质数(合数)
要遍历的数:[2, 3, 4, 5, 6, 7, 8, 9, 10]
	isPrime [1, 1, 0, 1, 0, 1, 0, 1,  0]
	 
3是质数,其倍数 6 9 标记为非质数(合数)
要遍历的数:[2, 3, 4, 5, 6, 7, 8, 9, 10]
	isPrime [1, 1, 0, 1, 0, 1, 0, 0,  0]

5是质数,其倍数 10 标记为非质数(合数) //只有某个数是质数,才筛其倍数,不是直接跳过
要遍历的数:[2, 3, 4, 5, 6, 7, 8, 9, 10]
	isPrime [1, 1, 0, 1, 0, 1, 0, 0,  0]
  
7是质数,范围内没有倍数,不用标记
要遍历的数:[2, 3, 4, 5, 6, 7, 8, 9, 10]
	isPrime [1, 1, 0, 1, 0, 1, 0, 0,  0]
 
最终 isPrime 内有4个位置被标记为1,也就是有4个质数

优化:

看完例子也能发现,筛数时从 2x 开始筛其实是冗余的,应该直接从 x^2 开始筛,因为 2x,3x,… 这些数在x之前就已经被其他数的倍数标记了。

比如

2是质数,其倍数 4 6 8 10 标记为非质数(合数)
要遍历的数:[2, 3, 4, 5, 6, 7, 8, 9, 10]
	isPrime [1, 1, 0, 1, 0, 1, 0, 1,  0]
  
3是质数,其倍数 6 9 标记为非质数(合数)
要遍历的数:[2, 3, 4, 5, 6, 7, 8, 9, 10]
	isPrime [1, 1, 0, 1, 0, 1, 0, 0,  0]

在筛3的倍数时,不应该从 2x(6) 开始,因为 2x 已经作为2的倍数被筛掉了,应该直接从 x^2(9)开始。

参考代码
class Solution {
public:
    int countPrimes(int n) 
    {
        vector<int> isPrime(n, 1);
        int cnt = 0;
        for(int i = 2; i < n; ++i)
        {
            if(isPrime[i])
            {
                ++cnt;
                if((long long)i * i < n) //大于n的数不管我们事
                {
                    for(int j = i * i; j < n; j += i)
                    {
                        isPrime[j] = 0;
                    }
                }
            }
        }
        return cnt;
    }
};
总结

更优、更妙的办法来自对需求的更强理解。强在哪方面?更多角度、更深理解。

埃氏筛就是通过数之间的联系,一次筛掉更多合数。


今天的分享就到这里了

这里是培根的blog,期待与你共同进步!

下期见~

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

周杰偷奶茶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值