前言
日积跬步,能至千里。
水平有限,不足之处望请斧正。
选择题
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 += 27
: 101 + 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
,也就是找到 A
或 B
任意一个能整除 C
即可。而对于C = A * B
, A
或 B
的最大值都是 根号C
。如对于C = 16
,有 1*16
2*8
4*4
,三种组合,4*4
是A
或B
最大的情况。
对于C = A * B
, A
或 B
的最大值都是 根号C
。也能说 A^2 < C
或 B^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
是质数,那大于 x
的 x的倍数
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,期待与你共同进步!
下期见~