2023-10-5一些笔试题(1)

1、有一个web服务器,cpu是8核i7,磁盘数1,那么数据库连接池的连接数的合理配置应该为()左右。

合理配置数据库连接池的连接数取决于多个因素,包括预计的并发请求量、数据库的负载情况、应用程序的需求等。虽然CPU核心数和磁盘数量可以提供一些指导,但它们并不是确定连接池连接数的唯一因素。

一般来说,数据库连接池的连接数应该根据以下几个考虑因素进行配置:

预计的并发请求数:根据应用程序的预计并发用户数、请求数量和处理时间,估计出并发请求的数量。连接池的连接数应该能够支持并发请求的处理需求。

数据库负载情况:考虑数据库的性能和负载情况。如果数据库经常处于高负载状态,可以适当增加连接池的连接数以提高并发处理能力。然而,过多的连接数可能会导致数据库性能下降,因此需要在性能和资源消耗之间进行权衡。

应用程序需求:考虑应用程序的特点和需求。某些应用程序可能需要更多的数据库连接,例如并发处理大量数据库操作的应用程序,而其他应用程序可能需要较少的连接。

基于上述因素,给出一个具体的连接数是很困难的。通常建议开始时使用一个较小的连接池,然后在实际运行中进行监测和调整。可以观察数据库的性能指标、应用程序的响应时间等,逐步增加或减少连接数,以找到最优的配置。

总的来说,根据您提供的信息,一个合理的起点可能是在连接池中使用大约 8 到 16 个连接。然而,这仅仅是一个初始估计,具体的连接数需要根据实际情况进行调整和优化。

2、辗转相除法

以下是使用 C++ 实现辗转相除法(欧几里得算法)的示例代码:

cpp 复制代码
#include <iostream>

int gcd(int a, int b) {
    if (b == 0) {
        return a;
    }
    return gcd(b, a % b);
}

int main() {
    int a, b;
    std::cout << "Enter two numbers: ";
    std::cin >> a >> b;
    int result = gcd(a, b);
    std::cout << "GCD: " << result << std::endl;
    return 0;
}

在这个示例中,gcd 函数使用递归的方式实现辗转相除法。函数接受两个整数参数 ab,并返回它们的最大公约数(GCD)。当 b 为 0 时,递归终止,返回 a。否则,递归调用 gcd 函数,将 ba 除以 b 的余数作为新的参数传递给函数。

main 函数中,用户被要求输入两个整数,并调用 gcd 函数计算它们的最大公约数,最后将结果打印输出。

请注意,在实际应用中,可能需要添加输入验证和错误处理的逻辑,以确保输入的是有效的整数值,并处理除数为 0 的情况。以上代码仅提供了基本的实现示例。

以下是使用 while 循环实现辗转相除法的示例代码:

cpp 复制代码
#include <iostream>

int gcd(int a, int b) {
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

int main() {
    int a, b;
    std::cout << "Enter two numbers: ";
    std::cin >> a >> b;
    int result = gcd(a, b);
    std::cout << "GCD: " << result << std::endl;
    return 0;
}

在这个示例中,gcd 函数使用 while 循环实现辗转相除法。循环条件为 b != 0,当 b 不等于 0 时,继续执行循环体。

在循环体内,首先将 b 的值保存在临时变量 temp 中,然后将 b 更新为 a 除以 b 的余数,将 a 更新为 temp(即原先的 b 值)。这样,每次循环都会将较大数更新为较小数,将较小数更新为两数之差的余数。

b 等于 0 时,循环终止,此时 a 的值即为最大公约数(GCD)。

main 函数中,用户被要求输入两个整数,并调用 gcd 函数计算它们的最大公约数,最后将结果打印输出。

同样地,需要注意在实际应用中添加输入验证和错误处理的逻辑,以及处理除数为 0 的情况。以上代码仅提供了基本的使用 while 循环实现辗转相除法的示例。

3、后缀表达式、前缀表达式、中缀表达式之间的转换。

后缀表达式、前缀表达式和中缀表达式是表示数学表达式的不同方式。它们之间可以进行相互转换。

  1. 中缀表达式(Infix Expression)是我们常见的数学表达式形式,其中运算符位于操作数之间。例如:(2 + 3) * 4。

  2. 后缀表达式(Postfix Expression),也称为逆波兰表达式(Reverse Polish Notation,RPN),是一种将运算符放在操作数之后的表达式。例如:2 3 + 4 * 表示 (2 + 3) * 4。

  3. 前缀表达式(Prefix Expression),也称为波兰表达式(Polish Notation),是一种将运算符放在操作数之前的表达式。例如:* + 2 3 4 表示 (2 + 3) * 4。

下面介绍如何在中缀、后缀和前缀表达式之间进行转换:

  1. 中缀转后缀(中缀表达式转后缀表达式):

    • 创建一个空栈和一个空结果列表。
    • 从左到右遍历中缀表达式的每个字符。
    • 如果遇到操作数(数字),将其添加到结果列表中。
    • 如果遇到运算符,将其与栈顶的运算符进行比较:
      • 如果栈为空或栈顶是左括号"(",则将当前运算符压入栈。
      • 如果当前运算符的优先级高于栈顶运算符的优先级,将当前运算符压入栈。
      • 如果当前运算符的优先级低于或等于栈顶运算符的优先级,则将栈顶运算符弹出,并将其添加到结果列表中。重复此操作直到栈顶运算符优先级低于当前运算符或栈为空。
    • 如果遇到左括号"(",将其压入栈。
    • 如果遇到右括号")",则从栈中依次弹出运算符并添加到结果列表中,直到遇到左括号为止。将左括号弹出栈,但不添加到结果列表。
    • 遍历完中缀表达式后,将栈中剩余的运算符依次弹出并添加到结果列表中。
    • 结果列表即为转换后的后缀表达式。
  2. 后缀转中缀(后缀表达式转中缀表达式):

    • 创建一个空栈。
    • 从左到右遍历后缀表达式的每个字符。
    • 如果遇到操作数(数字),将其压入栈。
    • 如果遇到运算符,从栈中弹出两个操作数,构造一个中缀表达式:操作数1 + 运算符 + 操作数2,并将中缀表达式作为一个操作数压入栈。
    • 遍历完后缀表达式后,栈中剩下的唯一元素即为转换后的中缀表达式。
  3. 后缀转前缀(后缀表达式转前缀表达式):

    • 创建一个空栈。
    • 从左到右遍历后缀表达式的每个字符。
    • 如果遇到操作数(数字),将其压入栈。
    • 如果遇到运算符,从栈中弹出两个操作数,构造一个前缀表达式:运算符 + 操作数2 + 操作数1,并将前缀表达式作为一个操作数压入栈。
    • 遍历完后缀表达式后,栈中剩下的唯一元素即为转换后的前缀表达式。

以上是基本的转换算法。下面是使用C++代码实现中缀转后缀的示例:

cpp 复制代码
#include <iostream>
#include <stack>
#include <string>
#include <unordered_map>

std::unordered_map<char, int> precedence = {
    {'+', 1},
    {'-', 1},
    {'*', 2},
    {'/', 2},
    {'^', 3}
};

bool isOperator(char c) {
    return precedence.find(c) != precedence.end();
}

bool isHigherPrecedence(char op1, char op2) {
    return precedence[op1] > precedence[op2];
}

std::string infixToPostfix(const std::string& infix) {
    std::stack<char> operatorStack;
    std::string postfix;

    for (char c : infix) {
        if (isOperator(c)) {
            while (!operatorStack.empty() && operatorStack.top() != '(' && isHigherPrecedence(operatorStack.top(), c)) {
                postfix += operatorStack.top();
                operatorStack.pop();
            }
            operatorStack.push(c);
        } else if (c == '(') {
            operatorStack.push(c);
        } else if (c == ')') {
            while (!operatorStack.empty() && operatorStack.top() != '(') {
                postfix += operatorStack.top();
                operatorStack.pop();
            }
            if (!operatorStack.empty() && operatorStack.top() == '(') {
                operatorStack.pop();
            }
        } else {
            postfix += c;
        }
    }

    while (!operatorStack.empty()) {
        postfix += operatorStack.top();
        operatorStack.pop();
    }

    return postfix;
}

int main() {
    std::string infix;
    std::cout << "Enter an infix expression: ";
    std::getline(std::cin, infix);

    std::string postfix = infixToPostfix(infix);
    std::cout << "Postfix: " << postfix << std::endl;

    return 0;
}

该示例使用了一个 std::stack 数据结构来模拟操作符栈,std::unordered_map 用于存储运算符的优先级。isOperator 函数用于判断字符是否为运算符,isHigherPrecedence 函数用于比较两个运算符的优先级。

infixToPostfix 函数接受一个中缀表达式作为输入,并返回转换后的后缀表达式。在函数中,遍历中缀表达式的每个字符,根据字符的类型执行相应的操作。如果遇到操作数,则直接添加到后缀表达式中。如果遇到运算符,则与操作符栈中的运算符进行比较,将优先级较低的运算符弹出并添加到后缀表达式中,然后将当前运算符压入栈。如果遇到左括号,则直接压入栈。如果遇到右括号,则将栈中的运算符弹出并添加到后缀表达式中,直到遇到匹配的左括号为止。

最后,将栈中剩余的运算符依次弹出并添加到后缀表达式中,即得到转换后的后缀表达式。

请注意,在实际应用中,可能需要添加更多的输入验证和错误处理的逻辑,以及处理运算符的结合性(左结合或右结合)等情况。以上代码仅提供了基本的中缀转后缀的示例。

4、有关reduceBykey 和groupBykey 说法错误的是

A groupByKey默认没有聚合函数

B、reduceBykey先在本地进行merge操作

C、reduceBykey更适合用在大数据量上

D. groupbykey 先进行分区间聚合,然后再进行网络传输

A、groupByKey默认没有聚合函数。

这个说法是错误的。在Spark中,groupByKey操作会将具有相同键的所有键值对分组到一起,并返回一个键值对集合,其中每个键与其对应的值列表相关联。这意味着groupByKey操作会对具有相同键的值进行分组,但并没有对值进行聚合操作。因此,groupByKey操作返回的结果是一个键值对集合,其中每个键与其对应的值列表相关联,而没有进行聚合计算。

选项A说法正确的是A。选项B、C和D都是正确的说法。

5、内存对齐:已知基本类型char,int,float在 64位分别占1,4,4字节。在指定#pragma pack(4)的情下。

struct s1{

float b;

char c[10];

long C;

long long *d;

};

占用多少字节的空间?

6、使用基数排存对十进制正整数据店列43,231,96,190,239进行升序排序、低位优先,堆数为10。排序过 程中的中间序列为

A、190,231,43,96,239

B、 190,43,231,96,239

C、231、239、43,190,96

D、239,231,43,190, 96

解释:

使用基数排序对十进制正整数数据43、231、96、190、239进行升序排序,低位优先,基数为10。

基数排序的过程中的中间序列为:

C、231、239、43,190,96

在低位优先的基数排序中,首先按个位进行排序,得到序列:190,231,43,96,239。然后按十位排序,得到序列:231,239,43,190,96。然后按照百位排序,得到序列:43,96,190,231,239。

所以,中间序列为C、231、239、43,190,96。
基数排序学习

7、在C++中,如果有 A *a,这是否会调用A的构造函数?

在C++中,声明一个指向A类型对象的指针A *a,并不会调用A的构造函数。指针的声明只是为了存储一个指向A类型对象的地址,并没有创建对象实例。

要创建A类型的对象并调用其构造函数,需要使用关键字new来动态分配内存,并显式调用A的构造函数。例如,可以使用以下方式创建A类型的对象并将其地址分配给指针a:

cpp 复制代码
A *a = new A();  // 调用A的默认构造函数

在这个例子中,new A()会调用A的构造函数来创建一个A类型的对象,并返回指向该对象的指针。然后,将该指针赋值给指针变量a。

需要注意的是,使用new分配的内存需要手动释放,以避免内存泄漏。可以使用delete操作符来释放通过new分配的内存,例如:

cpp 复制代码
delete a;  // 释放a指向的内存空间

这将调用A的析构函数,并释放通过new分配的内存。

8、对于下面的代码,下面选项描述正确的是:

c 复制代码
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
void modifyString(char* str){
    str =(char*) malloc (10*sizeof(char));
    strcpy(str, "Hello");
}
int main(){
    char * str = "World";
    modifyString(str);
    printf("%s\n", str);
    return 0;
}

A.编译错误

B,运行时错误

C、运行输出:Hello

D.、运行结果为输出: World

解释:

选项D描述正确,运行结果为输出: World。

在给定的代码中,首先在main函数中定义了一个char*类型的指针变量str,并将其指向字符串常量"World"。

然后,调用modifyString函数,并将str作为参数传递进去。在modifyString函数内部,通过malloc动态分配了一个长度为10的字符数组,并将其地址赋值给了str,然后使用strcpy函数将字符串"Hello"复制到了这个新分配的内存空间中。

然而,需要注意的是,modifyString函数中的str是一个局部变量,对它的修改并不会影响到main函数中的str指针。所以,虽然在modifyString函数内部成功修改了str指向的内存空间,但这个修改对于main函数中的str是不可见的。

因此,在main函数中打印str时,仍然会输出原始的字符串"World",而不是在modifyString函数中修改后的"Hello"。所以,选项D描述正确,运行结果为输出: World。

9、根据表达式 double(( (*fp)(int))[5])(char)关于fp类型描述正确的是:

A、fp是指向函数的指针

B、fp是一个数组

C、fp是指向数组的指针

D. 以上说法都不对

解释:

根据表达式double(*(*(*fp)(int))[5])(char),关于fp类型的描述正确的是:

A、fp是指向函数的指针

根据表达式的解读:

  • fpfp是一个标识符,表示一个变量名或指针名。
  • (*fp)(*fp)表示fp是一个指针,指向某种类型的对象。
  • (*(*fp)(int))(*(*fp)(int))表示fp是一个指针,指向一个函数。
  • (*(*(*fp)(int))[5])(*(*(*fp)(int))[5])表示fp是一个指针,指向一个返回类型为指向长度为5的数组的函数。
  • (*(*(*fp)(int))[5])(char)(*(*(*fp)(int))[5])(char)表示fp是一个指针,指向一个返回类型为double的函数,该函数接受一个int参数和一个char参数。

综上所述,根据表达式的解读,选项A描述正确,fp是指向函数的指针。