选择题
01
使用
printf
函数打印一个 double 类型的数据,要求:输出为 10 进制,输出左对齐 30 个字符,4 位精度。以下哪个选项是正确的?A %-30.4e
B %4.30e
C %-30.4f
D %-4.30f
题目解析:
本题考察格式化输入输出。
- A 选项:主体输出为
%e
这表示将 double 类型的变量按照科学计数法输出。-
:表示左对齐。30
:表示输出宽度为30个字符,如果实际输出的字符数不足 30 个,则在右侧填充空格。.4
:表示小数部分保留4位有效数字。A 选项错误。 - B 选项:和 A 选项一个道理。
答案:C
知识点总结:
-
%d
: 以十进制整数的形式输出整数。 -
%f
: 以浮点数的形式输出数字。 -
%c
: 以字符的形式输出字符。 -
%s
: 以字符串的形式输出字符串。 -
%e
或%E
: 以科学计数法输出浮点数。 -
%x
或%X
: 以十六进制形式输出整数。 -
%p
: 以指针的形式输出地址。
02
请找出下面程序中有哪些错误()
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
题目解析:
- (6):对指针变量
p2
进行解引用,并将p2
指向的变量修改为 20。但是我们看到指针变量p2
定义的时候*p2
是被 const 修饰的,因此其指向的内容不可被修改,第 (6) 处有错误。 - (7):修改指针变量
p3
的指向,也就是修改指针变量本身。但是我们看到在p3
变量定义的时候p3
本身是被 const 修饰过的,也就是说p3
指向的变量不允许被修改。第 (7) 处有错误。
知识点总结:
const int *p
:const 修饰的是*p
表示p
指向的内容不能不修改。int const *p
的效果和const int *p
的效果是一样的哈。int * const p
: const 修饰的是p
表示p
指向的变量不能被修改。
03
下面叙述错误的是()
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的内容也会被更改
题目解析:
- A:这个选项是正确的。用字符串常量初始化一个字符数组,会发生拷贝,将字符串中的每一个字符拷贝之后用来初始化字符数组。也就是说,
acX
被初始化成了:char acX = ['a', 'b', 'c', '\0']
。acX
和axY
没有被 const 修饰,可以修改。 - B:这个选项也是正确的。在一个程序中,相同的字符串常量在内存中只有一份。因为完全没有必要开两份空间来存储两个相同的字符串嘛!字符串常量赋值给
char*
就是将字符串常量的第一个字符的地址赋值给了那个char*
变量。 - C:这个选项也是正确的,
acX
占用 4 个字节,acY
占用 3 个字节。 - D:这个选项是错误的,因为
szX
和szY
指向的是字符串常量,而字符串常量是不可变的,试图修改它们的内容会导致未定义的行为。实际上,修改szX
或szY
的内容会触发编译器的错误或运行时错误。
答案:D
知识点总结:
- 字符串初始化字符数组是将字符串中的每个字符拷贝给字符数组。
- 相同的字符串常量在内存中只会开辟一份内存空间。
- 字符串就是其第一个字符的地址。
04
在头文件及上下文均正常的情况下,下列代码的运行结果是()
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
题目解析:
(int* b = a)
:数组名是数组首元素的地址。因此指针变量b
就是a[0]
的地址。*b += 2
:指针变量b
就是a[0]
的地址,*b
就是a[0]
。因此原表达式等价于:a[0] += 2
。*(b + 2) = 2
:*b
等价于a[0]
,不难推出*(b + 2)
等价于a[2]
。因此,原表达式等价于:a[2] = 2
。b++
:指针变量b
是整形数组首元素的地址,类型为int
,加一跳过一个整形的内存空间,也就是指向a
数组的第二个元素。
在 b++
之后,b
就指向数组 a
的第二个元素了。因此:*b = a[1]; *(b + 2) = a[3]
。
答案:C
知识点总结:
-
数组名表示数组首元素的地址,但是有两种情况除外:
sizeof(数组名)
这里的数组名是整个数组,因此求出来的就是整个数组的大小。&数组名
这里的数组名也是整个数组,得到的就是整个数组的地址,加一之后就能跳过整个数组。
-
a[0]
等价于*(a + 0)
以次类推。
05
下列关于C/C++的宏定义,不正确的是()
A 宏定义不检查参数正确性,会有安全隐患
B 宏定义的常量更容易理解,如果可以使用宏定义常量的话,要避免使用 const 常量
C 宏的嵌套定义过多会影响程序的可读性,而且很容易出错
D 相对于函数调用,宏定义可以提高程序的运行效率
题目解析:
- A:宏仅仅是进行替换操作,因此不会检查参数的正确性。
- B:使用 const 常量通常更加安全和可维护。
- const 变量可以提供类型安全性,将它们与特定的类型相关联。而宏没有,容易发生类型转换时的错误。
- 使用const常量可以更容易地进行调试,因为它们在编译时会进行类型检查,并且可以通过调试器进行检查和修改。宏定义常量不会在编译时进行类型检查,并且在调试时可能会引起困惑。
- 可读性:const常量具有明确的语义,可以更清晰地表达代码的意图。宏定义常量可能需要更多的注释来解释其用途和含义。
- 宏嵌套会使代码读起来更加复杂。
- 宏仅仅只是做替换的工作,相对于函数调用,不会建立函数栈帧,效率更高。
答案:B
知识点总结:
见题目解析。
06
有以下定义:
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);
比较简单,这里就不在做讲解啦!
答案:D
07
- 用变量a给出下面的定义:一个有10个指针的数组,该指针指向一个函数,该函数有一个整形参数并返回一个整
型数()
A int *a[10];
B int (*a)[10];
C int (*a)(int);
D int (*a[10])(int);
解题思路:
- A:选项 A 表示的是一个有十个元素的数组,并且每个数组元素的类型是
int*
。 - B:选项 B 表示的是数组指针,指向的是有十个整形的数组。
- C:选项 C 是一个函数指针,指向的是参数是
int
返回类型也是int
的函数。 - D:选项 D 是一个有十个元素的数组,并且每个数组元素的类型是函数指针,函数的参数是
int
,函数的返回值也是int
。
知识点总结:
熟练掌握各种类型指针的书写方法。
- 普通指针:指向特定数据类型的指针。例如,
int *ptr;
定义了一个指向整数的指针。 - 空指针:不指向任何地方的指针。例如,
int *ptr = NULL;
将指针设置为 NULL。 - 野指针:指向未知内存地址的指针,应尽量避免使用。例如,未初始化的指针。
- 指向指针的指针:也称为二级指针。例如,
int **ptr;
定义了一个指向整型指针的指针。 - 指针数组:一个数组,其中的每个元素都是指针。例如,
int *ptrArray[5];
定义了一个包含5个整型指针的数组。 - 数组指针:指向数组的指针。例如,
int (*ptr)[5];
定义了一个指向包含5个整数的数组的指针。 - 函数指针:指向函数的指针。例如,
int (*funcPtr)(int, int);
定义了一个指向接受两个整数参数并返回整数的函数的指针。 - 常量指针:指针指向的值不能被修改。例如,
const int *ptr;
定义了一个指向整数常量的指针。 - 指针常量:指针本身的值不能被修改。例如,
int *const ptr;
定义了一个指向整数的常量指针
08
以下 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 ++count[a[i]-‘Z’]
‘Z’-i
count[‘Z’-i]
B ++count[‘A’-a[i]]
‘A’+i
count[i]
C ++count[i]
i
count[i]
D ++count[‘Z’-a[i]]
‘Z’-i
count[i]
解题思路:
根据前面的代码提示,我们看到他是想用一个简单的哈希算法来统计每个大写字母出现的次数!
a[i] - 'Z'
肯定不行,因为算出来的数均 ≤ 0 \leq 0 ≤0 不能作为数组的下标。同理'A' - a[i]
也不行。排除 A C 选项。- 我们来看 b
++count[i]
为什么不行呢,我们注意到count
数组的大小为 26,而大写字母对应的 ASCII 均大于 26,++count[i]
就是越界访问啦,因此 b 也是错误的。 ++count['Z' - a[i]]
数组中下标为 0 的值存储的就是大写字母Z
出现的次数,以此类推。
答案:D
知识点总结:
无。
09
在 32 位cpu上选择缺省对齐的情况下,有如下结构体定义:
struct A { unsigned a : 19; unsigned b : 11; unsigned c : 4; unsigned d : 29; char index; };
则sizeof(struct A)的值为()
A 9
B 12
C 16
D 20
解题思路:
本题中结构体变量是以位段的形式定义的,冒号后面表示占用的比特位数量。unsigned
类型为 4 字节。
a
变量需要开辟 4 个字节,其中被用到的有 19 个比特位。
b
变量需要的 11 个比特位,可以在 a
中获取。
a
变量开辟的 4 个字节已经不能装下 c
需要的 4 个比特位,只能再次开辟 4 字节空间。
c
变量剩余的 28 个比特位不能容纳 29 个比特位。存储 d
还需要开辟 4 字节空间。
index
变量开辟一个字节就行。
最后根据结构体内存对齐:结构体的大小是结构体成员最大对齐数的整数倍,因此结构体的大小为 16.
答案:C
知识点总结:
- 位段的位宽不能超过其内存的大小。
- 结构体的内存对齐原则:
- 结构体的第一个成员在结构体变量偏移量为 0 的地址处,即第一个成员变量无论什么类型都是对齐到结构体的首地址(偏移量为 0 的位置)
- 结构体的其他成员变量需要对齐到某个对齐数的整数倍地址偏移量处,其中某个对齐数是成员变量自身的大小和编译器默认对齐数的较小值。
- 结构体的总大小是每个结构体成员变量对齐数的最大值的整数倍。
- 如果出现结构体嵌套,那么嵌套的结构体对齐到该结构体的最大对齐数的整数倍地址偏移量处。
- Visual Studio 中默认对齐数是 8。
__attribute__((packed))
:取消变量对齐,按照实际占用字节数对齐(就是让变量之间排列紧密,不留缝隙)。(gcc支持)#pragma pack (n)
:修改默认对齐数。
编程题
01
题目描述:
牛牛定义排序子序列为一个数组中一段连续的子序列,这段子序列是非递增或者非递减排序的。牛牛有一个长度为的整数数组,他现在需要把数组分为若干段排序子序列,牛牛想知道最少可以把这个数组分为几段排序子序列。
输入描述:
第一行输入一个正整数 n n n。
第二行输入 n n n 个正整数 a i a_i ai ,表示数组的每个数。
1 ≤ n ≤ 1 0 5 1 \leq n\leq10^5 1≤n≤105
1 ≤ a i ≤ 1 0 9 1 \leq a_i\leq10^9 1≤ai≤109
输出描述:
输出一个整数,可以将 a a a 最少划分为多少段排序子序列。
示例1
输入:
6
1 2 3 2 2 1
输出: 2
说明:可以把划分为 [1,2,3] 和 [2,2,1] 两个排序子序列。
题目解析:
本题要求解的是排序子序列,排序子序列为非递增或者非递减,注意:非递减就是 a [ i ] ≤ a [ i + 1 ] a[i] \leq a[i+1] a[i]≤a[i+1],递减就是 a [ i ] > a [ i + 1 ] a[i]>a[i+1] a[i]>a[i+1],非递增就是 a [ i ] ≥ a [ i + 1 ] a[i] \geq a[i+1] a[i]≥a[i+1],递增就是 a [ i ] < a [ i + 1 ] a[i]<a[i+1] a[i]<a[i+1]。其实这个不理解网上搜一下就理解了。
解题思路:
- 本题依次比较整个数组
- a [ i + 1 ] > a [ i ] a[i+1]>a[i] a[i+1]>a[i] ,则进入非递减序列判断,直到遍历到下一个值不大于等于为止 count++,然后进行下一位
置的判断 - a [ i + 1 ] < a [ i ] a[i+1]<a[i] a[i+1]<a[i],则进入非递增序列判断,直到遍历到下一个值不小于等于为止count++,然后进行下一位
置的判断 - a[i+1] == a[i]不进行操作,++i进行下一位置遍历,因为相等既可以属于非递增序列,也可以属于非递减
序列。
本题注意点:本题开始比较a[i+1]与a[i]进行比较,为了避免越界,数组定义为n+1个,同时给a[n] = 0;
a[n] = 0带来的影响,我们分为三种情况讨论: - 若到a[n-1] 的最后一组是非递减序列,当
i==n-1
, a [ i ] > a [ i + 1 ] a[i] >a[i+1] a[i]>a[i+1],因为前面的数都是大于0的,这个输入
条件已经说明了(去看看题目输入条件描述),里面的循环结束,i++
,count++
,i==n
,外面的循环结束。 - 若到a[n-1] 的最后一组是非递增序列,当
i==n-1
, a [ i ] > a [ i + 1 ] a[i] >a[i+1] a[i]>a[i+1],因为前面的数都是大于0的,这个输入
条件已经说明了(去看看题目输入条件描述),循环再走一次,i++
,i==n
,里面的循环结束,i++
,
count++
,i==n+1
,外面的循环结束。 - 第三种情况 1 2 1 2 1最后一个数是单独的情况,后面补个0,序列变成1 2 1 2 1 0,当走完全面的序列
i==n-1时,a[i] > a[i+1],进入判断出一个非递增序列,count++,i++,循环结束。 - 也就是说数组最后一个位置多增加一个0,不会影响第1、2情况的判断,主要是帮助第3情况的正确判
断。
完整代码:
#include <iostream>
#include <vector>
using namespace std;int main()
{int n;cin >> n;// 注意这里多给了一个值,是处理越界的情况的比较,具体参考上面的解题思路vector<int> a;a.resize(n + 1); // 这里有个坑,这个题越界了牛客测不出来,给n,并且不写a[n] = 0;不会报错,但是最好写上a[n] = 0;// 读入数组int i = 0;for (i = 0; i < n; ++i)cin >> a[i];i = 0;int count = 0;while (i < n){// 非递减子序列if (a[i] < a[i + 1]){while (i < n && a[i] <= a[i + 1])i++;count++;i++;}else if (a[i] == a[i + 1]){i++;}else // 非递增子序列{while (i < n && a[i] >= a[i + 1])i++;count++;i++;}}cout << count << endl;return 0;
}
02
原题链接:
https://www.nowcoder.com/practice/ee5de2e7c45a46a090c1ced2fdc62355?tpId=85&&tqId=29867&rp=1&ru=/activity/oj&qru=/ta/2017test/question-ranking
题目描述
将一句话的单词进行倒置,标点不倒置。比如 I like beijing. 经过函数后变为:beijing. like I
输入描述
每个测试输入包含1个测试用例: I like beijing. 输入用例长度不超过100
输出描述
依次输出倒置之后的字符串,以空格分割
示例1
输入:
I like beijing.
输出:
beijing. like I
题目解析:
本题题意很简单,就是将一段字符串中的前后单词交换,以单词为单位逆置。
解题思路1:
先将整个字符串逆置过来,再遍历字符串,找出每个单词,对单词逆置。这里我们使用了stl算法中的
reverse,所以这里使用迭代器遍历string。
完整代码:
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main()
{string s;// 注意这里要使用getline,cin>>s遇到空格就接收结束了getline(cin, s);// 翻转整个句子reverse(s.begin(), s.end());// 翻转单词auto start = s.begin();while (start != s.end()){auto end = start;while (end != s.end() && *end != ' ')end++;reverse(start, end);if (end != s.end())start = end + 1;elsestart = end;}cout << s << endl;return 0;
}
解题思路二:
第二思路是一个比较讨巧的思路,直接利用cin>>s接收输入,遇到空格就结束了,自然就分割开了每个单词,其次将每次接收到的单词拼接到之前串的前面就逆置过来了
完整代码:
#include <iostream>
#include <string>
using namespace std;
// cin读取string时自动会被空格分隔开,用另一个字符串存储进行逆序输出
int main()
{string s1, s2;cin >> s2;while (cin >> s1)s2 = s1 + " " + s2;cout << s2 << endl;return 0;
}