首先是笔试题目,笔试时间一个小时,后面会更新面试部分题目讲解。 面试题讲解链接:面试题讲解
解释c++中static,const的含义与作用。
void foo() {
static int count = 0;
count++;
cout << "Count: " << count << endl;
}
int main() {
foo(); // 输出 Count: 1
foo(); // 输出 Count: 2
foo(); // 输出 Count: 3
return 0;
}class MathUtils {
public:
static int add(int a, int b) {
return a + b;
}
};
int main() {
int result = MathUtils::add(5, 3); // 可以直接通过类名调用
cout << "Result: " << result << endl; // 输出 Result: 8
return 0;
}class MyClass {
public:
static int count;
};
int MyClass::count = 0;
int main() {
MyClass obj1;
MyClass obj2;
obj1.count = 5;
cout << "obj1.count: " << obj1.count << endl; // 输出 obj1.count: 5
cout << "obj2.count: " << obj2.count << endl; // 输出 obj2.count: 5
obj2.count = 10;
cout << "obj1.count: " << obj1.count << endl; // 输出 obj1.count: 10
cout << "obj2.count: " << obj2.count << endl; // 输出 obj2.count: 10
return 0;
}class MathUtils {
public:
static int add(int a, int b) {
return a + b;
}
static int multiply(int a, int b) {
return a * b;
}
};
int main() {
int result1 = MathUtils::add(5, 3);
cout << "Result1: " << result1 << endl; // 输出 Result1: 8
int result2 = MathUtils::multiply(5, 3);
cout << "Result2: " << result2 << endl; // 输出 Result2: 15
return 0;
}在Java中,static关键字用于声明静态成员,它可以应用于变量、方法和代码块。
static关键字修饰的变量是静态变量,也称为类变量。静态变量在类加载时被初始化,且只有一份副本,被所有对象共享。public class Circle {
private static double pi = 3.14;
private double radius;
// 静态方法可以访问静态变量
public static double getPi() {
return pi;
}
public Circle(double r) {
radius = r;
}
public double getArea() {
return pi * radius * radius;
}
}
public static void main(String[] args) {
Circle c1 = new Circle(5.0);
Circle c2 = new Circle(10.0);
System.out.println(Circle.getPi()); // 输出 3.14
System.out.println(c1.getArea()); // 输出 78.5
System.out.println(c2.getArea()); // 输出 314.0
Circle.pi = 3.14159; // 可以通过类名直接访问静态变量并修改其值
System.out.println(Circle.getPi()); // 输出 3.14159
}static关键字修饰的方法是静态方法,也称为类方法。静态方法属于类而不是对象,可以通过类名直接调用,无需创建对象。public class MathUtils {
public static int add(int a, int b) {
return a + b;
}
public static int subtract(int a, int b) {
return a - b;
}
}
public static void main(String[] args) {
int sum = MathUtils.add(5, 3); // 直接通过类名调用静态方法
int difference = MathUtils.subtract(5, 3);
System.out.println(sum); // 输出 8
System.out.println(difference); // 输出 2
}static关键字修饰的代码块是静态代码块,它在类加载时执行,用于初始化静态变量或执行其他静态操作。public class Circle {
private static double pi;
static {
pi = 3.14;
System.out.println("Static initializer block executed");
}
public Circle(double r) {
radius = r;
}
}
public static void main(String[] args) {
Circle c = new Circle(5.0); // 输出 "Static initializer block executed"
}const关键字用于声明常量,表示该变量的值在初始化后不能被修改。const可以用于变量、函数参数、函数返回值和成员函数。const关键字声明的变量被称为常量,其值在初始化后不能被修改。常量必须在声明时进行初始化。const int MAX_VALUE = 100;
int main() {
MAX_VALUE = 200; // 错误,常量不能被修改
return 0;
}const关键字修饰函数参数,表示该参数在函数内部不会被修改。这样做可以保证函数不会意外修改传入的参数。void printNumber(const int num) {
num = 10; // 错误,常量参数不能被修改
cout << num << endl;
}
int main() {
int value = 5;
printNumber(value); // 输出 5
return 0;
}const关键字修饰函数返回值,表示该返回值是一个常量,不能被修改。const int getNumber() {
return 10;
}
int main() {
int num = getNumber(); // 错误,常量返回值不能被修改
return 0;
}const关键字修饰,表示该函数不会修改类的成员变量。常量成员函数可以在常量对象上调用,但不能修改对象的状态。// 定义一个圆类
class Circle {
private:
double radius; // 圆的半径
public:
// 构造函数,用于初始化圆的半径
Circle(double r) : radius(r) {}
// 常量成员函数,用于计算圆的面积
double getArea() const {
return 3.14 * radius * radius;
}
};
// 主函数
int main() {
// 创建一个常量对象c,半径为5.0
const Circle c(5.0);
// 调用常量成员函数getArea(),计算圆的面积,并将结果赋值给变量area
double area = c.getArea();
// 错误,常量对象的成员变量不能被修改
c.radius = 10.0;
return 0;
}在Java中,没有直接的const关键字来声明常量。Java使用final关键字来声明常量,表示该变量的值在初始化后不能被修改。
final关键字修饰的变量是常量,一旦被赋值后就不能再改变。final int num = 10;
num = 20; // 错误,常量不能被修改final关键字修饰方法参数,表示该参数是一个常量,在方法内部不能修改参数的值。void printNumber(final int value) {
value = 20; // 错误,常量参数不能被修改
System.out.println(value);
}
public static void main(String[] args) {
int num = 10;
printNumber(num); // 输出 10
}final关键字修饰方法返回值,表示该返回值是一个常量,不能被修改。final int getNumber() {
return 10;
}
public static void main(String[] args) {
int num = getNumber(); // 错误,常量返回值不能被修改
}final关键字修饰,表示该变量是一个常量,一旦被赋值后就不能再改变。public class Circle {
private final double radius;
public Circle(double r) {
radius = r;
}
public double getArea() {
return 3.14 * radius * radius;
}
}
public static void main(String[] args) {
Circle c = new Circle(5.0);
double area = c.getArea();
c.radius = 10.0; // 错误,常量成员变量不能被修改
}这个题比较特殊,演示的是c++中/**/多行注释可能遇到的坑,也就是注释嵌套容易遇到的,比如下面的例子
/* This is a comment /* with nested comment */ */ // 错误,嵌套注释*/。如果注释结束符出现在字符串中,编译器会将其视为注释的结束,导致编译错误。/* This is a comment with a string "This is a string */" */ // 错误,字符串中包含注释结束符*/。如果注释结束符出现在预处理指令中,编译器会将其视为注释的结束,导致编译错误。/* This is a comment with a preprocessor directive #include "header.h" */ // 错误,预处理指令中包含注释结束符/* This is a comment with code */
int x = 5; // 错误,注释中放置了实际的代码题目:讲解a+++b的运算过程,通过代码讲解如下
#include <iostream>
int main() {
int a = 5;
int b = 3;
int result = a+++b;
std::cout << "Result: " << result << std::endl;
return 0;
}a的当前值(5)赋给一个临时变量,然后将a的值递增1,此时a的值变为6。a的值(6)与b的值(3)相加,得到最终的结果9。因此,输出结果为:
Result: 9这个太熟了,方法有很多,从慢到快演示。 最简单方法,这就是根据质数的特性写的。
bool is_prime(int x)
{
if (x < 2) return false;
for (int i = 2; i <= x / i; ++ i)
{
if (x % i == 0) return false;
}
return true;
}比赛写法,经典算法题,筛法求质数,这个比较讲究了,如果是一般的算法比赛,可以用埃式筛法,如果是ACM这种的话,就要使用线性筛法。
埃式筛法:一句话讲解思路就是,如果一个数字他是质数,那么他的倍数一定不是质数,所以只需要把这些不是质数的筛掉就可以了。时间复杂度O(nlogn)
// 埃氏筛法
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int cnt, n;
bool st[N];
void get_primes(int n)
{
for (int i = 2; i <= n; ++i)
{
if (!st[i])
{
cnt ++;
for (int j = i + i; j <= n; j += i) st[j] = true;
}
}
}
int main()
{
cin >> n;
get_primes(n);
cout << cnt << '\n';
return 0;
}线性筛法:这个难度就很大了,下面是我以前写一个讲解,以前我也是打过ACM的男人。
// 线性筛法 时间复杂度O(n)
// 核心思想:一个数只会被它的最小质因子 筛掉 相对于埃氏筛法而言 可以省去很多不必要的操作 每个合数只会被筛一次
// 数据越大 快的越明显
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int prime[N], cnt, n;
bool st[N];
void get_primes(int n)
{
for (int i = 2; i <= n; ++i)
{
if (!st[i]) prime[cnt ++] = i;
for (int j = 0; prime[j] <= n / i; ++j) // 一个个的求出的质数达到枚举 这里的判断条件中是不需要写上 j < cnt的
// 因为下面的条件那个break 如果 i是合数 下面那个条件一定会成立 如果i 是质数 下面的那个条件也会成立
// prime[]中含有所有合数的质因子 i是质数的话 prime中会有和i相等的一个数
{
st[prime[j] * i] = true; // prime[j] 是合数 prime[j] * i的最小质因子
if (i % prime[j] == 0) break; // 这里是最能理解的地方
// 由于prime[j]是按照从小到大的质数进行的 所以 如果这个语句成立的话 那么prime[j] 一定是 i的最小质因子
// 这个时候就符合算法思想了 每个合数只会被它的最小质因子筛掉 这个时候就要停下来了
// 如果继续的话会有 prime[j + 1]来筛下一个合数 i * prime[j + 1]
// 因为prime[j] 是 i的最小质因子 所以i * prime[j + 1] = k * prime[j] * prime[j + 1]
// 然后这个合数应该是要被 prime[j] 筛掉的 但是此时是被prime[j + 1] 筛掉的 然后违背了 这个算法的核心
}
}
}
int main()
{
cin >> n;
get_primes(n);
cout << cnt << '\n';
return 0;
}我当时写的是数组的本质就是指针,指针被分配了内存之后就成了数组,下面是更详细的解释:
python:在python中没有指针,但是处处都体现出来了指针,为啥python可以不用定义数据的类型了,正是用了指针的特性,python是万物皆对象,我们也可以说是万物皆指针,所以当然不需要定义具体的类型,因为所有的变量本质都是一个地址,js的设计思路应该也是一样的,不需要定义具体的数据类型。
java:java中也是没有指针的概念的,但是有了引用的概念来实现,指针的功能, 在Java中,尽管没有像C语言那样直接的指针概念,但是通过引用类型和对象引用的方式,可以实现类似于指针的功能,比如下面的例子。
int[] arr = new int[3]; // 创建一个整型数组对象
int[] arr2 = arr; // 将arr的引用赋给arr2
arr2[0] = 1; // 修改arr2,也会影响到arr
System.out.println(arr[0]); // 输出 1public static void modifyArray(int[] arr) {
arr[0] = 1; // 修改arr,也会影响到原始数组
}
int[] arr = new int[3];
modifyArray(arr);
System.out.println(arr[0]); // 输出 1==运算符来比较两个对象的引用是否相等。如果两个对象的引用指向同一个内存地址,则它们被认为是相等的。int[] arr1 = new int[3];
int[] arr2 = arr1;
System.out.println(arr1 == arr2); // 输出 true,arr1和arr2引用同一个对象golang:在Go语言中,指针的功能和C语言非常的像,甚至操作符的含义都一样。
*来声明一个指针变量。例如,var ptr *int声明了一个指向整型变量的指针。
&操作符获取变量的内存地址。例如,x := 10; ptr := &x将变量x的地址赋给指针变量ptr。
*操作符来解引用指针,即获取指针指向的变量的值。例如,*ptr表示获取指针ptr指向的整型变量的值。
*ptr = 20表示将指针ptr指向的整型变量的值修改为20。
nil关键字表示空指针。
package main
import "fmt"
func main() {
x := 10
fmt.Println("x =", x) // 输出 x = 10
ptr := &x
fmt.Println("ptr =", ptr) // 输出 ptr = 0xc0000180b8
fmt.Println("*ptr =", *ptr) // 输出 *ptr = 10
*ptr = 20
fmt.Println("x =", x) // 输出 x = 20
var nilPtr *int
fmt.Println("nilPtr =", nilPtr) // 输出 nilPtr = <nil>
}我整理了一下,如下(默认都是升序排列):
void bubbleSort(vector<int>& arr)
{
int n = arr.size();
for (int i = 0; i < n - 1; ++ i)
{
for (int j = 0; j < n - 1 - i; ++ j)
{
if (arr[j] > arr[j + i]){
swap(arr[j], arr[j + 1]);
}
}
}
}void selectionSort(vector<int>& arr) {
int n = arr.size();
// 遍历数组,执行n-1轮选择操作
for (int i = 0; i < n - 1; i++) {
// 假设当前最小元素的索引为i
int minIndex = i;
// 在未排序部分中找到最小元素的索引
for (int j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 将最小元素与未排序部分的第一个元素交换位置
swap(arr[i], arr[minIndex]);
}
}void insertionSort(vector<int>& arr) {
int n = arr.size();
// 从第二个元素开始,依次插入到已排序部分的正确位置
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i - 1;
// 将比key大的元素向后移动
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
// 将key插入到正确位置
arr[j + 1] = key;
}
}#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int q[N];
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[(l + r) >> 1];
while(i < j)
{
do i ++; while (q[i] < x);
do j --; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j); quick_sort(q, j + 1, r);
}
int main()
{
int n;
scanf ("%d", &n);
for (int i = 0; i < n; ++ i) scanf("%d", &q[i]);
quick_sort(q, 0, n - 1);
for (int i = 0; i < n; ++ i) printf("%d ", q[i]);
return 0;
}#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int tmp[N], q[N];
int n;
void merge_sort(int q[], int l, int r)
{
if (l >= r) return;
int mid = l + r >> 1;
merge_sort(q, l, mid), merge_sort(q, mid + 1, r);
int k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r)
{
if (q[i] <= q[j]) tmp[k ++] = q[i ++];
else tmp[k ++] = q[j ++];
}
while (i <= mid) tmp[k ++] = q[i ++];
while (j <= r) tmp[k ++] = q[j ++];
for (i = l, j = 0; i <= r; i ++, j ++) q[i] = tmp[j];
}
int main()
{
scanf ("%d", &n);
for (int i = 0; i < n; ++i) scanf ("%d", &q[i]);
merge_sort(q, 0, n - 1);
for (int i = 0; i < n; ++i) printf("%d ", q[i]);
return 0;
}#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int h[N], mysize, n, m;
void down(int u)
{
int t = u;
if (2 * u <= mysize && h[t] > h[2 * u]) t = 2 * u;
if (2 * u + 1 <= mysize && h[t] > h[2 * u + 1]) t = 2 * u + 1;
if (u != t)
{
swap(h[u], h[t]);
down(t);
}
}
int main()
{
cin >> n >> m;
mysize = n;
for (int i = 1; i <= n; ++ i)
cin >> h[i];
for (int i = n / 2; i; i --) // 如果i > n / 2 那么上面的 2 * u > size 那么 u = t 会进行很多没有意义的计算
down(i);
while (m --)
{
cout << h[1] << ' ';
h[1] = h[mysize --];
down(1);
}
return 0;
}Cache-主存-辅存结构的设计是为了在存储层次结构中实现高效的数据访问。Cache作为CPU与主存之间的缓冲区域,可以快速提供数据,减少CPU等待数据的时间。主存作为临时存储区域,存储当前正在运行的程序和数据。辅存作为永久存储区域,用于长期保存数据和程序。通过合理设计和管理这三个层次的存储结构,可以提高计算机系统的性能和效率。
这个不难,就是算法导论里面的原题。
int binarySearch(const std::vector<int>& arr, int target) {
int left = 0;
int right = arr.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}这个也是算法导论上面的题目,算法思路是双指针法.双指针算法最经典的题目

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int a[N], s[N];
int n, res;
int main()
{
cin >> n;
for (int i = 0; i < n; ++ i) cin >> a[i];
for (int i = 0, j = 0; i < n; ++ i)
{
s[a[i]] ++; // 记录下a[i]出现的次数
while(s[a[i]] > 1) // 一点碰见两个重复的元素后
{
s[a[j]] --; // 这里要主要的一点是这个算法是没有回溯的
// 不要被for循环里面的条件误导以为会回溯、
// 现在遇到两个相同的元素了
// !!! 现在是这个算法最厉害的地方
// 这个j代表的是 j可以到达最左的地方 所以在j左边的
// 元素的个数就需要都-- 这点很妙
j ++; // 然后j++
}
res = max(res, i - j + 1); // 这个res的含义是 在i这个位置、
// 可以达到的符合题目条件的最大长度
}
cout << res;
return 0;
}