CCF编程能力等级认证GESP—C++4级—样题1

CCF编程能力等级认证GESP—C++4级—样题1

  • 单选题(每题 2 分,共 30 分)
  • 判断题(每题 2 分,共 20 分)
  • 编程题 (每题 25 分,共 50 分)
    • 第一题 绝对素数
    • 第二题 填幻方

单选题(每题 2 分,共 30 分)

1、在 C++中,指针变量的大小(单位:字节)是( )

A. 2
B. 4
C. 8
D. 与编译器有关

正确答案:D
在C++中,指针变量的大小(单位:字节)是依赖于编译器和操作系统的。在32位系统中,指针通常是4字节,而在64位系统中,指针通常是8字节。这是因为指针的大小需要足够大以存储内存地址。因此,正确答案是D。

#include 

using namespace std;

int main(){
	int a = 10;
	int *p = &a;
	cout << sizeof(*p) << endl; // 8 / 4
	cout << *p << endl; // 10 
	cout << p << endl; // 0x70fe14
	return 0;
}

2、以下哪个选项能正确定义一个二维数组( )

A. int a[][];
B. char b[][4];
C. double c[3][];
D. bool d[3][4];

正确答案:D
A选项:这个声明试图定义一个二维数组,但没有为任何一个维度指定大小。在C++中,数组的每个维度都必须有明确的大小,除了第一维度(在函数参数中)可以省略外。因此,这个声明是不合法的。
B选项:这个声明定义了一个二维字符数组,其中第二维度的大小是4(即每行有4个字符)。然而,第一维度的大小没有指定,这意味着数组的大小是不确定的。
C选项:这个声明试图定义一个二维双精度浮点数数组,其中第一维度的大小是3(即3行)。但是,第二维度的大小没有指定,这是不允许的。在C++中,每个维度都必须有明确的大小。
D选项:这个声明定义了一个二维布尔数组,其中第一维度的大小是3(3行),第二维度的大小是4(4列)。这是一个完全合法的声明,因为它为两个维度都指定了明确的大小。

3、在 C++中,以下哪种方式不能用于向函数传递参数()

A. 值传递
B. 引用传递
C. 指针传递
D. 模板传递

正确答案:D
A. 值传递(Value Passing):这是最常见的参数传递方式。当参数通过值传递时,函数接收的是参数值的一个副本,原始值不会被改变。
B. 引用传递(Reference Passing):通过引用传递参数,函数接收的是对实际参数的引用(即别名),而不是副本。因此,对引用的任何修改都会反映到原始参数上。
C. 指针传递(Pointer Passing):指针传递允许函数接收指向实际参数的指针。通过指针,函数可以访问和修改原始参数的值。
D. 模板传递(Template Passing):实际上,这并不是一种参数传递方式。在C++中,模板是一种泛型编程的工具,用于创建可以处理多种数据类型的函数和类。模板不是用来传递参数的,而是用来定义可以处理多种数据类型的函数或类。因此,选项D是不正确的。

#include   

using namespace std;
// 使用值传递的函数  
void printValue(int x) {  
    cout << "函数内x的值为:" << x << endl;  
    // 修改副本的值,不会影响原始变量  
    x = 100;  
    cout << "函数内x的值被修改后为:" << x << endl;  
}  
  
int main() {  
    int value = 50;  
    cout << "x的初始值为:" << value << endl;  
    printValue(value);  
    cout << "执行完函数后x的值为:" << value << endl;  
    return 0;  
}
/*
特点:
形参是实参的拷贝,改变形参的值并不会影响外部实参的值。
从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。
使用注意事项:
对于大型对象或结构,值传递可能会导致不必要的内存开销和性能下降,因为需要复制整个对象或结构。
如果函数需要修改参数的值,并且希望这个改变影响调用者,那么不应该使用值传递。
*/
#include   

using namespace std;
// 使用引用传递的函数  
void printValue(int &x) {  
    cout << "函数内x的值为:" << x << endl;  
    // 修改副本的值,会影响原始变量  
    x = 100;  
    cout << "函数内x的值被修改后为:" << x << endl;  
}  
  
int main() {  
    int value = 50;  
    cout << "x的初始值为:" << value << endl;  
    printValue(value);  
    cout << "执行完函数后x的值为:" << value << endl;  
    return 0;  
}
/*
特点:
形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作。
被调函数对形参的任何操作都影响了主调函数中的实参变量。
使用注意事项:
引用必须被初始化,且一旦初始化后就不能再指向其他对象。
引用传递增加了函数之间的耦合性,因此在使用时需要谨慎考虑。
*/
#include   

using namespace std;
// 使用引用传递的函数  
void printValue(int *x) {  
    cout << "函数内x的值为:" << *x << endl;  
    // 修改指针指向的值,会影响原始变量  
    (*x) += 10;  
    cout << "函数内x的值被修改后为:" << *x << endl;  
}  
  
int main() {  
    int value = 50; 
	int *p = &value; 
    cout << "x的初始值为:" << value << endl;  
    printValue(p);  
    cout << "执行完函数后x的值为:" << value << endl;  
    return 0;  
}
/*
特点:
指针传递本质上也是一种值传递,只是传递的是地址值(即指针变量的值)。
指针传递允许函数修改其指向的值,而这个改变会影响实参。
使用注意事项:
指针在传递之前必须被初始化,且必须指向一个有效的内存地址。
指针传递增加了代码的复杂性和出错的可能性(如空指针解引用、野指针等),因此在使用时需要特别小心。
在函数内部,对指针的操作(如解引用、赋值等)必须谨慎,以避免意外的内存访问错误。
*/

4、以下关于 C++函数的形参和实参的叙述,正确的是()

A. 形参是实参的别名
B. 实参是形参的别名
C. 形参和实参是完全相同的
D. 形参用于函数声明,实参用于函数调用

正确答案:D
A选项:这个叙述是不准确的。形参(形式参数)是函数定义中声明的参数,它们用于接收函数调用时传递的实参(实际参数)的值。形参和实参不是别名关系,而是值传递关系(对于基本数据类型)或引用传递关系(对于引用或指针类型)。
B选项:这个叙述也是不准确的。实参是在函数调用时传递给函数的实际值或变量,而形参是函数定义中声明的用于接收这些值的变量。它们之间不是别名关系。
C选项:这个叙述不准确。形参和实参在概念上是相关的,但它们不是“完全相同”的。形参是函数定义的一部分,而实参是函数调用的一部分。形参在函数被调用时接收实参的值,但它们不是同一个实体。
D选项:这个叙述是正确的。形参是在函数声明和定义中使用的,用于指定函数期望接收的参数类型和数量。实参是在函数调用时提供的,用于传递给函数的实际值或变量。

5、排序算法的稳定性是指( )

A. 相同元素在排序后的相对顺序保持不变
B. 排序算法的性能稳定
C. 排序算法对任意输入都有较好的效果
D. 排序算法容易实现

正确答案:A
排序算法的稳定性是指,在排序过程中,相同元素之间的相对顺序保持不变。换句话说,如果排序前有两个相等的元素 AB,且 AB 的前面,那么排序后 A 也应该在 B 的前面。
A选项:这是排序算法稳定性的定义。
B选项:这与排序算法的稳定性无关,而是指算法在不同输入下的性能表现。
C选项:这描述的是算法的健壮性或适应性,而不是稳定性。
D选项:这与算法的稳定性无关,而是指算法的易实现性。

6、如果有如下二维数组定义,则 a[0][3]的值为()

int a[2][2] = {{0, 1}, {2, 3}};
A. 编译出错
B. 1
C. 3
D. 0

正确答案:C
在C++中,当你声明一个二维数组a[N][M]时,你确实拥有了一个连续的内存块,这个内存块足以存储N*M个整数(或其他类型的数据)。但是,你不能简单地将a[0][3]视为等同于a[1][1],因为它们在逻辑上不是同一个数组元素,并且尝试访问a[0][3]是越界访问,即使它们在物理内存中可能相邻。
在该题中,a[2][2]定义了一个2行2列的二维数组。这意味着有效的下标范围是a[0][0]到a[1][1]。如果你尝试访问a[0][3],你实际上已经越过了数组的边界,并且可能会访问到不属于你的数组的内存区域。这可能会导致未定义行为,包括但不限于访问到另一个变量的内存位置、程序崩溃或数据损坏。
不过从物理内存的角度来看,a[0][3]的地址与a[1][1]的地址相同(或接近),因此答案选C

7、以下哪个选项能正确访问二维数组 array 的元素()

A. array[1, 2]
B. array(1)(2)
C. array[1][2]
D. array{1}{2}

正确答案:C
在C++中,要访问二维数组的元素,你需要使用两个方括号[],并且它们之间不能有逗号或函数调用那样的语法。这是因为二维数组在内存中实际上是连续存储的,但通过两个索引(一个用于行,一个用于列)来访问。
A选项:这是不正确的,因为逗号在方括号内会被解释为逗号表达式,它会计算两个操作数(在这里是1和2),但只返回最后一个操作数的值,即2。因此,这实际上等同于array[2],但尝试将其当作二维索引来用是不正确的。
B选项:这是不正确的,因为这不是访问数组元素的正确语法。在C++中,你不能像调用函数那样调用数组。
C选项:这是正确的。它首先获取array的第1行(在C++中索引从0开始),然后获取该行的第2个元素。
D选项:这是不正确的,因为大括号{}在C++中通常用于初始化列表、结构体或类的聚合初始化,而不是用于索引数组。

8、以下哪个选项是 C++中正确的指针变量声明( )

A. int *p;
B. int p*;
C. *int p;
D. int* p*;

正确答案:A
A选项:这是正确的。它声明了一个名为p的指针变量,该指针指向int类型的对象。
B选项:这是不正确的。在C++中,星号应该紧挨着指针变量名,而不是类型标识符。
C选项:这也是不正确的。星号
应该放在类型标识符和变量名之间,而不是类型标识符之前。
D选项:这同样是不正确的。它试图声明一个指向指针的指针,但语法不正确。如果意图是声明一个指向int指针的指针,正确的写法应该是int ** p;。

9、在 C++中,以下哪个关键字或符号用于声明引用()

A. pointer
B. &
C. *
D. reference

正确答案:B
正确答案是 B。
在 C++ 中,用于声明引用的关键字或符号是 &。引用可以被视为一个已存在变量的别名,它提供了对变量的另一种访问方式。一旦引用被初始化为一个变量,它就不能再被重新绑定到另一个变量。
A选项:这不是一个关键字,而是一个普通的单词,通常用于描述指向某个对象的指针。
B选项:这是正确的。在声明变量时,如果我们在类型名称后加上&,我们就声明了一个引用。
C选项:这是指针的声明符号,不是用于声明引用的。
D选项:虽然reference这个词在C++中用于描述引用的概念,但它本身并不是一个关键字或符号,不能直接用于声明引用。

//例如,以下代码声明了一个整数变量 x 和一个引用 ref,该引用被初始化为 x 的别名:
int x = 10;
int& ref = x; // ref 是 x 的引用
//在这个例子中,ref 和 x 指向内存中的同一个位置,对 ref 的任何修改都会影响到 x,反之亦然。

10、以下哪个递推关系式表示斐波那契数列( )

A. F(n) = F(n-1) + F(n-2) + F(n-3)
B. F(n) = F(n-1) + F(n-2)
C. F(n) = F(n-1) * F(n-2)
D. F(n) = F(n-1) / F(n-2)

正确答案:B
斐波那契数列是一个著名的数列,其中每个数字(从第三个数字开始)是前两个数字的和。这个数列的递推关系式是:
F(n) = F(n-1) + F(n-2)
其中 F(0) 和 F(1)(或有时 F(1) 和 F(2))是给定的初始值。通常,F(0) = 0, F(1) = 1。
A选项:这不是斐波那契数列的递推关系式,因为它包含了第三个前项。
B选项:这是斐波那契数列的正确递推关系式。
C选项:这是另一种数列的递推关系式,但不是斐波那契数列的。
D选项:这也不是斐波那契数列的递推关系式,因为它使用了除法而不是加法。

11、以下哪个函数声明在调用时可以传递二维数组的名字作为参数?

A. void BubbleSort(int a[3][4]);
B. void BubbleSort(int a[][]);
C. void BubbleSort(int * a[]);
D. void BubbleSort(int ** a);

正确答案:A
在 C++ 中,当二维数组作为函数参数传递时,实际上传递的是指向数组第一行的指针。因此,函数声明应该接受一个指向整型数组的指针作为参数。
A选项:声明了一个接受 3x4 整型二维数组的函数。虽然这里指定了数组的大小,但实际上在函数内部,这个大小信息是不可用的。函数会将其视为指向整型数组的指针。
B选项:是错误的,因为 C++ 不支持这种不完整的数组类型声明作为函数参数。
C选项:声明了一个接受指向整型指针数组的函数,这实际上是接受一个指向指针的指针,每个指针指向一个整型数组。这与传递二维数组的名字不同。
D选项:声明了一个接受指向整型指针的指针的函数,这通常用于处理动态分配的二维数组或指针数组。它也不是接受二维数组名字的正确方式。

12、在 C++中,以下哪个关键字用来捕获异常( )

A. throw
B. catch
C. try
D. finally

正确答案:B
A选项:这个关键字用于在代码中抛出一个异常。
B选项:这个关键字用于捕获一个异常。当你想要处理被throw抛出的异常时,你会在try块之后使用catch块。
C选项:这个关键字定义一个代码块,该代码块中的异常可以被后续的catch块捕获。
D选项:这不是C++的关键字。在Java等语言中,finally块用于执行无论是否发生异常都必须执行的代码。在C++中,你可以使用其他技术(如RAII或异常安全保证)来确保资源的正确管理,但没有直接的finally关键字。

13、在下列代码的横线处填写( ),可以使得输出是“20 10”。

#include 

using namespace std;
void xchg(________________) { // 在此处填入代码
	int t = x;
	x = y;
	y = t;
}
int main() {
	int a = 10, b = 20;
	xchg(a, b);
	cout << a << " " << b << endl;
	return 0;
}
A. int x, int y
B. int & x, int & y
C. int a, int b
D. int & a, int & b

正确答案:B
为了使得xchg函数能够交换传入的两个变量的值,我们需要使用引用(&)来传递参数,因为按值传递(即传递变量的副本)的话,函数内部对变量的修改不会影响到函数外部的原始变量。
A选项:这是按值传递,函数内部对x和y的修改不会影响到main函数中的a和b。
B选项:这是按引用传递,函数内部对x和y的修改会影响到main函数中的a和b。这正是我们想要的。
C选项:虽然变量名与main函数中的变量名相同,但这只是局部变量名,它们与main函数中的变量没有联系。按值传递会创建新的局部变量a和b。
D选项:虽然使用了引用,但这里的a和b只是局部变量名的引用,与main函数中的变量名冲突。按照C++的规则,局部变量的名字会覆盖外部作用域中同名的变量。这会导致编译错误,因为xchg函数内部无法访问到main函数中的a和b。

14、在下列代码的横线处填写( ),可以使得输出是“21”。

#include 

using namespace std;

int main() {
	int a[5];
	a[0] = 1;
	for (int i = 1; i < 5; i++)
		a[i] = a[i – 1] * 2;
	int sum = 0;
	for (int i = 0; i < 5; ________) // 在此处填入代码
		sum += a[i];
	cout << sum << endl;
	return 0;
}
A. i++
B. i += 2
C. i += 3
D. i |= 2

正确答案:B
a[5] = {1, 2, 4, 8, 16}

sum i++ i += 2 i += 3 i |= 2
第一次循环sum 1 1 1 1
第二次循环sum 3 5 9 5
第三次循环sum 7 21 循环结束 9
第四次循环sum 15 循环结束 13
第五次循环sum 31

15、在下列代码的横线处填写( ),完成对有 n 个 int 类型元素的数组array由小到大排序。

void BubbleSort(int array[], int n) {
	for (int i = n; i > 1; i--)
		for (____________________) // 在此处填入代码
			if (array[j] > array[j + 1]) {
				int t = array[j];
				array[j] = array[j + 1];
				array[j + 1] = t;
			}
}
A. int j = i – 2; j >= 0; j--
B. int j = i - 1; j >= 0; j--
C. int j = 0; j < i - 1; j++
D. int j = 0; j < i; j++

正确答案:C
在冒泡排序算法中,外层循环控制排序的轮数,内层循环则负责进行相邻元素的比较和交换。对于每一轮排序,内层循环应该从数组的第一个元素开始,到倒数第二个元素结束(因为最后一个元素在那一轮中已经是正确的位置了)。
因此,内层循环的初始化应该是 int j = 0,表示从数组的第一个元素开始比较。循环条件应该是 j < i - 1,因为每一轮排序后,最大的元素会“冒”到数组的末尾,所以内层循环不需要再考虑最后一个元素。最后,循环的递增应该是 j++,以便移动到下一个相邻元素进行比较。

判断题(每题 2 分,共 20 分)

1、C++语言中的指针变量可以指向任何类型的数据。( )

正确答案:正确
在C++中,指针变量可以指向任何类型的数据,包括基本数据类型(如int、char等)、结构体、类对象等。指针变量本身存储的是内存地址,而这块内存地址可以存储任何类型的数据。但需要注意,指针必须正确地指向与其类型相匹配的对象,否则可能会引发运行时错误。

2、在 C++语言中,函数的参数默认以地址传递方式进行传递。( )

正确答案:错误
在 C++ 中,函数的参数默认是通过值传递的,即传递的是参数的副本,而不是参数本身的地址。如果想要通过地址传递参数,需要在函数声明和定义时明确指定指针或引用类型。

3、C++语言中的全局变量在整个程序的生命周期内都是有效的。( )

正确答案:正确
全局变量是在函数之外定义的变量,它们在程序开始执行时就被创建,并在整个程序的执行期间一直存在,直到程序结束。因此,全局变量在整个程序的生命周期内都是有效的。

4、递推算法通常有初始值。( )

正确答案:正确
递推算法是一种通过已知条件逐步推导未知条件的算法。在递推过程中,通常需要一些初始值作为递推的开始。这些初始值可以是已知的常数,也可以是用户输入的数据。

5、冒泡排序是一种稳定的排序算法。( )

正确答案:正确

冒泡排序是一种简单的排序算法,它重复地遍历待排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。在冒泡排序中,相等的元素在排序过程中不会改变它们的相对位置,因此冒泡排序是一种稳定的排序算法。

6、C++语言中,如果异常发生,但没有处理异常的代码,则程序会由于一直等待处理而死机。( )

正确答案:错误
在C++中,如果异常发生且没有被捕获(即没有相应的异常处理代码),程序不会由于一直等待处理而死机。相反,它会终止执行,并可能输出错误信息。这通常被称为“未捕获的异常”。

7、C++语言中的局部变量在函数调用结束后会被销毁。( )

正确答案:正确
局部变量是在函数内部定义的变量,它们的作用域仅限于该函数。当函数执行完毕并返回时,其内部的局部变量会被销毁,释放其占用的内存空间。

8、&和&&都是 C++语言的运算符,*和**也都是。( )

正确答案:错误
&是C++中的地址运算符,用于获取对象的地址;&&是逻辑与运算符,用于判断两个布尔表达式的逻辑与关系。*是解引用运算符,用于通过指针访问其所指向的对象;但**不是单独的运算符,它通常表示一个指向指针的指针。

9、如果希望设计一个函数 xchg,实现交换两个 int 变量的值,则它的声明可以写为 void xchg(int a, int b);。( )

正确答案:错误
由于C++中函数参数是按值传递的,所以函数xchg内的a和b实际上是调用者提供的两个整数的副本。在函数内部交换a和b的值不会影响调用者提供的原始变量。要实现真正的交换,需要使用指针或引用作为参数。正确的函数声明可以是void xchg(int& a, int& b);。

10、已知数组 a 定义为 int a[100];,则赋值语句 a[‘0’] = 3;会导致编译错误。( )

正确答案:错误
在C++中,数组索引可以是整数或整数字面量(包括字符字面量,它们会被隐式转换为它们对应的ASCII值)。字符’0’的ASCII值是48。因此,a[‘0’]实际上等同于a[48],这个赋值语句是有效的,不会导致编译错误。

编程题 (每题 25 分,共 50 分)

第一题 绝对素数

【问题描述】
如果一个两位数是素数,且它的数字位置经过对换后仍为素数,则称为绝对素数,例如 13。给定两个正整数 A、B,请求出大于等于A、小于等于B 的所有绝对素数。
【输入格式】
输入 1 行,包含两个正整数 A 和 B。保证 10 【输出格式】
若干行,每行一个绝对素数,从小到大输出。
【样例输入】
11 20
【样例输出】
11
13
17

#include 
#include 
using namespace std;

bool is_prime(int num){
	for (int i = 2; i <= sqrt(num); i++){
		if (num % i == 0){
			return false;
		}
	}
	return true;
}

int main() {
	int a, b;
	cin >> a >> b;
	for (int i = a; i <= b; i++){
		int ge, shi, newnum;
		ge = i % 10;
		shi = i / 10 % 10;
		newnum = ge * 10 + shi;
		if (is_prime(i) && is_prime(newnum)){
			cout << i << endl;
		}
	}
	return 0;
}

第二题 填幻方

【问题描述】
在一个 N×N 的正方形网格中,每个格子分别填上从1 到N×N 的正整数,使得正方形中任一行、任一列及对角线的几个数之和都相等,则这种正方形图案就称为“幻方”(输出样例中展示了一个 3×3 的幻方)。我国古代称为“河图”、“洛书”,又叫“纵横图”。
幻方看似神奇,但当 N 为奇数时有很方便的填法:
1)一开始正方形中没有填任何数字。首先,在第一行的正中央填上1。
2)从上次填数字的位置向上移动一格,如果已经在第一行,则移到同一列的最后一行;再向右移动一格,如果已经在最右一列,则移动至同一行的第一列。如果移动后的位置没有填数字,则把上次填写的数字的下一个数字填到这个位置。
3)如果第 2 步填写失败,则从上次填数字的位置向下移动一格,如果已经在最下一行,则移到同一列的第一行。这个位置一定是空的(这可太神奇了!),把上次填写的数字的下一个数字填到这个位置。
4)重复 2、3 步骤,直到所有格子都被填满,幻方就完成了!快来编写一个程序,按上述规则,制作一个 N×N 的幻方吧。
【输入格式】
输入为一个正奇数 N,保证 3≤N≤21。
【输出格式】
输出 N 行,每行 N 个空格分隔的正整数,内容为 N×N 的幻方。
【样例输入】
3
【样例输出】
8 1 6
3 5 7
4 9 2

#include 
// 重点: 
// 1. 需要有两个指针来锁定上一个填数的坐标 
// 2. 需要有另外两个指针来查询接下来要填数的位置 
using namespace std;
const int maxn = 25;
int arr[maxn][maxn], n, nx, ny, ans_x, ans_y;
int main() {
	cin >> n;
	for (int i = 1; i <= n * n; i++){
		// 在第一行的正中央填上 1, 也可以在循环外设置, 但是循环的次数要少一次
		if (i == 1) {
			arr[1][n / 2 + 1] = 1;
			nx = 1;
			ny = n / 2 + 1;
			continue; 
		}
		ans_x = nx, ans_y = ny;
		// 2. 如果已经在第一行, 则移动到最后一行, 否则移动到最后一行 
		if (ans_x == 1){
			ans_x = n;
		}else{
			ans_x--;
		}
		// 2. 如果是最后一列, 则移动到第一列, 否则向右移动一格 
		if (ans_y == n){
			ans_y = 1;
		}else{
			ans_y++;
		}	
		// 	如果没有数字就将i填写到这个位置上 
		if (arr[ans_x][ans_y] == 0){
			arr[ans_x][ans_y] = i;
			nx = ans_x, ny = ans_y; // 保存当前位置坐标 
		}else{ // 3. 如果填写失败, 就从上一次填数的位置重新找 
			// 重新回到上一次填数的位置 
			ans_x = nx, ans_y = ny;
			if (ans_x == n){
				ans_x = 1;
			}else{
				ans_x++;
				arr[ans_x][ans_y] = i;
				nx = ans_x, ny = ans_y; // 保存当前位置坐标 
			}
		}
	}
	// 输出二维数组 
	for (int i = 1; i <= n; i++){
		for (int j = 1; j <= n; j++){
			cout << arr[i][j] << ' ';
		}
		cout << endl;
	} 
	return 0;
}

你可能感兴趣的:(#,C++,-,4级,c++,开发语言,等级考试)