【小练】day2

day2

一、选择题

1. 使用printf函数打印一个double类型的数据,要求:输出为10进制,输出左对齐30个字符,4位精度。以下哪个选项是正确的?

A %-30.4e

B %4.30e

C %-30.4f

D %-4.30f


#printf的格式控制

正确答案:C


2. 请找出下面程序中有哪些错误()

int main(){
  int i = 10;
	int j = 1;
  
 	const int *p1;//(1)
 	int const *p2 = &i; //(2) 
  p2 = &j;//(3)
 	int *const p3 = &i;//(4) 
  *p3 = 20;//(5)
 	*p2 = 30;//(6)
 	p3 = &j;//(7)
  
 	return 0;
}

A 1,2,3,4,5,6,7

B 1,3,5,6

C 6,7

D 3,5


  • const int *p1:常量指针,指向常量
  • int const *p2 = &i:常量指针,指向常量
  • int *const p3 = &i:指针常量,指针本身是常量

(6):尝试改变常量指针指向的常量,错误。

(7):尝试改变指针常量,指针本身是常量,无法改变,错误。

正确答案:C


#指针常量和常量指针

const int* ptr 常量指针:指向常量的指针。

int* const ptr 指针常量:指针类型的常量。

可以这么理解:const保护它后面紧跟的那个

  • const后面紧跟*:保护*(解引用找到的的数据)
  • const后面紧跟ptr:保护ptr(指针变量本身)

3. 下面叙述错误的是()

char acX[]="abc";
char acY[]={'a','b','c'};
char *szX="abc"; 
char *szY="abc";

A acX与acY的内容可以修改

B szX与szY指向同一个地址

C acX占用的内存空间比acY占用的大

D szX的内容修改后,szY的内容也会被更改


题中,

acX是双引号初始化的字符数组,自带\0做结尾

acY是花括号初始化的字符数组,不带\0做结尾

szX是字符指针,用字符串初始化,相当于把字符串的首元素地址赋给字符指针

szY是字符指针,用字符串初始化,相当于把字符串的首元素地址赋给字符指针

需要注意的是,这里的“abc”是常量字符串,所以szX和szY指向同一空间。

A:没问题

B:没问题,“abc”是常量字符串,两个指针都指向常量区的"abc"

C:没问题,acX自带\0做结尾,acY不带\0做结尾,前者多了一个’\0’

D:有问题,szX和szY根本不能修改。

正确答案:D


#字符指针和字符数组的初始化

字符数组的初始化:

  • 双引号初始化,自带\0做结尾
  • 花括号初始化,不带\0做结尾,如果是不完全初始化,未初始化的默认初始化为0

字符指针的初始化:

  • 常量字符串给字符指针初始化,相当于把字符串的首元素地址赋给字符指针

4. 在头文件及上下文均正常的情况下,下列代码的运行结果是()

int a[] = {1, 2, 3, 4};
int *b = a;
*b += 2;
*(b + 2) = 2;
b++;
printf("%d,%d\n", *b, *(b + 2));

A 1,3

B 1,2

C 2,4

D 3,2


b指向a的首元素,1+=2,对b+2解引用,找到3,3=2,b++,b指向2,打印*b(2)和b往后跨2个int的位置(4)。

正确答案:C


#指针加减整数

5. 下列关于C/C++的宏定义,不正确的是()

A 宏定义不检查参数正确性,会有安全隐患

B 宏定义的常量更容易理解,如果可以使用宏定义常量的话,要避免使用const常量

C 宏的嵌套定义过多会影响程序的可读性,而且很容易出错

D 相对于函数调用,宏定义可以提高程序的运行效率


#宏

本质是替换。

优点

  • 代码可复用性高,效率高

缺点

  • 代码可读性差,容易误用
  • 没有类型的安全检查
  • 无法调试(预处理期间直接替换)

带着这些了解,能轻松知道B之外的都是对的。B恰恰说反了,由于宏没有类型的安全检查,预处理后也会直接替换,所以不安全,更要尽量使用const常量。

正确答案:B


6. 有以下定义,正确的函数调用是()

int a[10]; 
char b[80];

函数声明为:

void sss(char[],int[]);

A sss(a,b);

B sss(char b[],int a[]);

C sss(b[],a[]);

D sss(b,a);


A:实参不对应

B:传了个类型过去,不合语法规范

C:实参不是char* 和 int*

D:显然正确

正确答案:D


#数组作参数

数组传参数一般不传本身,如果数组大,一来一去效率太低。所以都是传首元素地址,也能访问。

数组做参数:可以传同类型的数组或指针,形参拿到手里的都一样——都是首元素地址


7. 用变量a给出下面的定义:一个有10个指针的数组,该指针指向一个函数,该函数有一个整形参数并返回一个整型()

A int *a[10];

B int (*a)[10];

C int (*a)(int);

D int (*a[10])(int);

A:[]和a结合,a是一个数组,存放int*,所以是指针数组。

B:*和a结合,a是一个指针,指向int[10],所以是数组指针。

C:*和a结合,a是一个指针,指向有一个形参int,返回值为int的函数,所以是函数指针。

D::[]和a结合,a是一个数组,存放指针,它指向有一个形参int,返回值为int的函数,所以是函数指针数组。

正确答案:D。


#指针数组和数组指针

8. 以下 C++ 函数的功能是统计给定输入中每个大写字母的出现次数(不需要检查输入合法性,所有字母都为大 写),则应在横线处填入的代码为()

void AlphabetCounting(char a[], int n) 
{ 
  int count[26] = {}, i, kind = 10;
	 for (i = 0; i < n; ++i)
		_________________;
  for (i = 0; i < 26; ++i) 
  {
			printf("%c=%d", _____, _____); 
  }

}

A

  1. ++count[a[i]-'Z']
  2. 'Z'-i, count['Z'-i]

B

  1. ++count['A'-a[i]]

  2. 'A'+i, count[i]

C

  1. ++count[i]

  2. i, count[i]

D

  1. ++count['Z'-a[i]]

  2. 'Z'-i, count[i]


#hash映射和数组下标规则

统计给定输入中每个大写字母的出现次数,用到了hash映射的思想。第一个空,遍历给定输入,映射并记录出现次数;第二个空,找到 [对应的字符] 和其 [出现次数] 打印

A:++count[a[i]-‘Z’],i从0开始,a[i]-‘Z’,0-‘Z’ < 0,不符合下标的规则。

B:++count[‘A’-a[i]] ,i从0到n,‘A’-a[i] < ‘A’,无法找到大写字母,映射错误。

C:++count[i],i从0到n,下标直接定为i,无法找到大写字母,映射错误

D:++count[‘Z’-a[i]] ,i从0到n,‘Z’-a[i] = ‘Z’ / ‘Y’ / ‘X’…

count

元素:[Z, X, Y, …, A]

下标: 0 1 2 … 25

在看后面的空,可以实现“找到 [对应的字符] 和其 [出现次数] 打印”

正确答案:D


9. 在32位cpu上选择缺省对齐的情况下,有如下结构体定义,则sizeof(struct A)的值为()

struct A
{
	unsigned a : 19; 
	unsigned b : 11;
  unsigned c : 4; 
  unsigned d : 29;
	char index;
};

A 9

B 12

C 16

D 20


unsigned a : 19:unsigned 开辟4bytes,有32bits用掉19bits,剩下13bits。

unsigned b : 11:和a类型相同,而且剩下13bits,足够存储,存储后一开始的4bytes剩下2bits。

unsigned c : 4:一开始的4bytes仅剩2bits,不够存储,重新开辟4bytes,存完剩下28bits 。

unsigned d : 29:上次的4bytes剩下28bits,不够存储,重新开辟4bytes,存完剩下3bits。

char index:类型和d不同,重新开辟1字节存储。

4bytes(32): 19 + 11 …2

4bytes(32): 4 …28

4bytes(32): 29 …3

1bytes(8):8 …0

总共13bytes,整个结构体的大小和结构体内最大对齐数(unsigned的4)对齐,对齐到4的最小整数倍:16。

正确答案:C


#位段
  • 同类型可以放在一起,不同类型需要重新开辟字节
  • 如果当前的n个字节剩下的比特位足够放,就直接放,反之重新按字节开辟(编译器决定)

10. 下面代码会输出()
int main()
{
	int a[4]={1,2,3,4};
	int *ptr=(int*)(&a+1); 
	printf("%d",*(ptr-1));
}

A 4

B 1

C 2

D 3


对于int arr[10]

  • sizeof(arr)中的arr,表示整个数组
  • &arr中的arr,表示整个数组
  • 其他时候,arr始终表示数组首元素地址

&a是a的整体地址,&a+1跳过一整个a,这时候是野指针。

ptr本身是int指针,-1后退一个int,找到4。

正确答案:A


二、编程题

1. 排序子序列

描述

牛牛定义排序子序列为一个数组中一段连续的子序列,并且这段子序列是非递增或者非递减排序的。牛牛有一个长度为n的整数数组A,他现在有一个任务是把数组A分为若干段排序子序列,牛牛想知道他最少可以把这个数组分为几段排序子序列.
如样例所示,牛牛可以把数组A划分为[1,2,3]和[2,2,1]两个排序子序列,至少需要划分为2个排序子序列,所以输出2

输入描述:
输入的第一行为一个正整数n(1 ≤ n ≤ 10^5)

第二行包括n个整数A_i(1 ≤ A_i ≤ 10^9),表示数组A的每个数字。
输出描述:
输出一个整数表示牛牛可以将A最少划分为多少段排序子序列

示例1

输入
6
1 2 3 2 2 1
输出
2

思路

求 “连续的非递增或者非递减排序的子序列” 的个数。也可以说,求非递增/非递减的状态转换的次数。

如,有三个连续的排序子序列,状态就转化了两次。

非递增就是前后元素可以相等的递减,非递减就是前后元素可以相等的递增。

非递减:[1, 2, 2, 3] 非递增:[3, 2, 2, 1]

所以我们可以比较 a[i] 和 a[i+1] 来判断状态:

  • a[i] > a[i+1]:状态切换至非递减
  • a[i] == a[i+1]:状态不变
  • a[i] < a[i+1]:状态切换至非递增

遍历每次状态切换时记录即可。但要注意,我们遍历的 i 是 [0, n-1],而我们比较的时候会访问i+1,所以这样直接遍历会越界。怎么解决?

由于题目说数组中的数组 >=1,所以我们的数组可以开n+1的空间,a[n]放上0,遍历结束的时候本来也是切换状态的时候,这样不仅不用遍历完后手动加一次,而且还能避免越界。


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

int main()
{
    int n = 0;
    cin >> n;
    
    vector<int> v(n + 1);
    v[n] = 0; //最后一个位置放上0
    for(int i = 0; i < n; ++i)
       cin >> v[i];
    
    int i = 0, cnt = 0;
    while(i < n)
    {
        //当前是非递增状态
        if(v[i] > v[i+1])
        {
            //找到下一次状态切换
            while(i < n && v[i] >= v[i+1]) ++i;
            ++cnt;
        }
        //当前是非递减状态
        else if(i < n && v[i] < v[i+1])
        {
            //找到下一次状态切换
            while(i < n && v[i] <= v[i+1]) ++i;
            ++cnt;
        }
      	//两种情况:
        //1. 相等往后走
        //2. 找到切换状态的点,再++i,跳到下一种状态
        ++i;
    }
    
    cout << cnt << endl;
    
    return 0;
}

总结:

  • 将题目要求转化成代码易实现的语言
    • 题目要求:“连续的非递增或者非递减排序的子序列” 的个数
    • 易实现:非递增/非递减的状态转换的次数
  • 访问越界时视情况可以多开空间。

2. 倒置字符串

描述

将一句话的单词进行倒置,标点不倒置。比如 I like beijing. 经过函数后变为:beijing. like I

输入描述:

每个测试输入包含1个测试用例: I like beijing. 输入用例长度不超过100

输出描述:

依次输出倒置之后的字符串,以空格分割

示例1
输入:
I like beijing.
输出:
beijing. like I

思路

倒置单词,有个方法,反正前面的要往后走,后面的要往前走,我们直接逆置整体。每个单词都到了对应位置,但是单词内的字母顺序反了。我们只需要设置一个区间,找到[单词头, 空格或结尾),然后逆置。找的时候注意越界问题。

我们这里用的是C++算法库的reverse,传参要传左闭右开的迭代器。


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

int main()
{
    //reverse [当前单词的第一个有效字符, 空格的前一个字符]
    string s;
    getline(cin, s);
    
    reverse(s.begin(), s.end());
    
    int i = 0, begin = 0, end = 0;
    while(i < s.size())
    {
        //找空格
        while(i < s.size() && s[i] != ' ') ++i;
        end = i - 1; //空格不管
        
        reverse(s.begin() + begin, s.begin() + end + 1);
        
        begin = end = i + 1;
        i = begin;
    }
    
    cout << s << endl;
    
    return 0;
}

今天的分享就到这里了

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

下期见~

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

周杰偷奶茶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值