校招,再投同花顺,转C++工程师(全过程详细记录)

网申

今年,3月份的时候再次看到同花顺招人,上次挂在最后一面,这次不能放弃,再来试试。无测评环节。校招有电话面,但在笔试之后。

笔试

还是熟悉的界面,但是明显感觉到题目量比上次多,这里记录几个,我不会的。
1.为什么BeginPaint和EndPaint只能在WM_PAINT消息中使用?
一上来就是MFC,好久没看了,忘的差不多了。不过整张卷子就这一道题,这里我就不写了,毕竟我也不会了。(后面电话面终于知道为啥有MFC了,原来是因为我的简历写了比较多的MFC做的项目。。)
2.C++的空类,默认产生哪些类成员函数?
答:缺省构造函数、缺省拷贝构造函数、
缺省析构函数、
缺省赋值运算符、缺省取址运算符、缺省取址运算符 const
但是只有当实际使用这些函数的时候,编译器才会去定义它们。

class class1
{
public:
	class1()//缺省构造函数
	class1(const class1&)//拷贝构造函数
	~class1()//析构函数
	class1& operator=(const class1&)//赋值运算符
	class1* operator&()//取址运算符
	const class1* operator&() const//取址运算符 const
};

3.什么是深拷贝?什么是浅拷贝?
首先是数据类型,分为两大类,基本数据类型和对象数据类型。基本数据类型直接将数据存储在栈中。而引用型数据类型,在栈中存储的是一个指向堆的指针,真实的数据存储在堆中。
接下来,就会出现浅拷贝和深拷贝了,浅拷贝就是在赋值的时候,只赋值指针的值,新的指针指向的是原先的同一个在堆中的对象,而没有在堆中开辟一个新的区域,没有将原先的那块数据赋值过来。深拷贝的意思就是在堆中新建了一块区域,将原先的数据赋值过来了,即新的指针指向新开辟的堆内存区域。
接下来是一个例子:

/*
1.验证浅拷贝与深拷贝
2020.3.19  9:47
*/

#include <iostream>
using namespace std;

//class A
//{
//public:
//	A(int _data) : data(_data) {}
//	A() {}
//private:
//	int data;
//};
//
//int main()
//{
//	A a(5), b = a; // 仅仅是数据成员之间的赋值,C++默认使用的是浅拷贝。 
//  return 0;
//}
//被注释的这段代码,运行是没有问题的。

//而下面的这段代码,运行时会报错。
//因为这里的数据涉及到了指针,即在堆中分配了内存,但是在实际上C++仍然调用的是浅拷贝的赋值,就会导致出错。
//因为浅拷贝没有复制堆中的数据,导致了多次析构,就会出错。
//比如这里的a和b里面的data指向的是同一片堆的内存,析构两次就会导致出错。
class A
{
public:
	A(int _size) : size(_size)
	{
		data = new int[size];
	} // 假如其中有一段动态分配的内存 
	A() {};
	~A()
	{
		delete[] data;
	} // 析构时释放资源
private:
	int* data;
	int size;
};

int main()
{
	A a(5), b = a; // 注意这一句 
	return 0;
}

报错如下:
在这里插入图片描述

那怎么解决呢?自己加一个深拷贝函数:

A(const A& _A) : size(_A.size)
	{
		data = new int[size];
	} // 深拷贝 

4.类成员函数的重载,覆盖和隐藏的区别?并说明主要的应用场合。
1)函数重载,相同的函数名,但是参数的类型,参数的个数,参数的顺序不同的,称为函数重载。注意,仅返回值不同的不算是函数重载,主要应用于实现功能相似的几个函数。
2)函数覆盖,发生在子类和父类之间,如果子类的函数与父类的函数,名称和参数完全相同,且基类函数必须使用virtual关键字修饰,则发生覆盖。借助于虚函数可以实现多态性。
3)函数隐藏,如果派生类函数与基类函数同名,但参数不同,此时,无论是否有virtual关键字,基类的所有同名函数都将被隐藏,而不会重载,因为不在同一个类中;如果派生类函数与基类函数同名,且参数也相同,但基类函数没有用virtual关键字声明,则基类的所有同名函数都将被隐藏,而不会覆盖,因为没有声明为虚函数。
那么,函数隐藏的应用场景呢:假若没有隐藏机制,改变基类实现将有可能影响到子类已经正常工作的代码出现未预料的行为,这不是我们希望看到的.
例如:

/*
探索隐藏的作用机制,
2020.3.19
*/

//#include <cassert>
//
//struct Base {
//    // ...
//};
//
//struct Derived : Base {
//    bool foo(long) { return true; }
//};
//
//int main() {
//    Derived d;
//    assert(d.foo(1));
//}
//这里写的代码是没有问题的。

#include <cassert>
// if name hiding not working, it may cause surprised behavior    如果函数隐藏没有发挥作用。会出现一些问题。
// since the Base author may not know the Derived code   //比如这里如果Base的代码增加了一些具体实现。
// if the author of Base add a new method to Base    //而Derived代码这边原先的实现逻辑,就可能会出错。
// then Derived may not be working as expected
struct Base {
    // ...
    bool foo(int) { return false; }
};

struct Derived : Base {
    //using Base::foo; // simulate if name hiding not working  //加上这句话,就可以模拟隐藏不起作用的情况。
    //不加的话,C++默认是开启隐藏的。
    bool foo(long) { return true; }
};

int main() {
    Derived d;
    assert(d.foo(1)); // will failed then print error info
}

当然,如果把那句话加上,就会导致未发挥隐藏的作用,产生异常。因为对main函数的d来说,两个foo()函数都是可见的。错误如下:
在这里插入图片描述

这部分参考了几个大牛的博客:
CSDN的
一帖子
一论坛的帖子
5.简述assign,strong,copy,weak的不同作用。
这道题是涉及到iOS开发的,并不会。
6.使用gbd调试,如何查看特定线程,特定函数的变量。
这道题涉及到在linux下面使用gdb调试的知识。使用gdb调试,这篇文章讲的很全。
7.写一个“标准”宏MIN,这个宏输入两个参数,并返回较小的那个。

/*
这里自定义一个宏MIN,并测试
2020.3.20 9:51
*/

#include <iostream>
using namespace std;
#define MIN(x,y) ((x)<=(y)?(x):(y))   
//宏的后面不要分号。宏的作用方式是替换,所以为了避免错误,多加几个括号。

int main() {
	int a = 9;
	int b = 12;
	cout << MIN(a,b);  //可以正常输出是9
	cout << MIN(++a, b);   
	//这里的输出是11,因为这里的作用原理是替换,宏的定义里面有两次x出现,就导致加了两次。这是副作用。
	return 0;
}

8.在Windows VC++开发中,执行外部程序有哪几种方式,有什么区别,分别应用在什么场合?
1)使用system()函数。在这里插入图片描述

我测试了,可以启动VPN,但是就是程序不会主动的停止,会一直运行着。需要手动的关闭。
2)使用WinExec()函数。
在这里插入图片描述

这里的函数可以做到立即返回,让被打开的程序自己打开。但是我在VS 2019上使用这个函数,弹出一个建议,建议使用CreateProcess,说WinExec是不赞成使用的。
3)使用CreateProcess()函数.
在这里插入图片描述

使用该函数时也是立即退出,让被调用的程序自己慢慢打开。
关于这第三个函数,在《Windows 系统编程》这本书里面有详细的论述。这里插几个简单的图片。
在这里插入图片描述
在这里插入图片描述
代码如下:

#include <windows.h> 
#include <iostream> 
#include <stdlib.h>
#pragma comment(lib, "Kernel32.lib")
#include <tchar.h>
using namespace std;
int main()
{
	//cout << "Opening with system() function\n";
	//system("D:\\VPN\\Clients\\Clients\\Windows\\V2ray\\V2rayN-Core\\v2rayN-Core\\v2rayN.exe");
	//cout << "Opening with WinExec() function\n";
	//WinExec("D:\\VPN\\Clients\\Clients\\Windows\\V2ray\\V2rayN-Core\\v2rayN-Core\\v2rayN.exe", SW_SHOW);
	STARTUPINFO si;
	PROCESS_INFORMATION pi;
	ZeroMemory(&si, sizeof(si));
	si.cb = sizeof(si);
	ZeroMemory(&pi, sizeof(pi));
	CreateProcess(_T("D:\\VPN\\Clients\\Clients\\Windows\\V2ray\\V2rayN-Core\\v2rayN-Core\\v2rayN.exe"), NULL, NULL, NULL, false, 0, NULL, NULL, &si, &pi);
	return 0;
}

至于后一问,具体的应用场景,并不知道。知道的大佬可否告知。
9.MFC的消息传递机制。
10.如何验证TCP,UDP端口开通?网上的一个答案。我不懂。
11.有哪些常见的字符集编码方式?常见的乱码原因有哪些?
一篇超好的讲解。这里面介绍清楚了各种字符集以及编码方式。
常见的乱码原因:不同的编码方式会导致乱码。
①.使用了不支持该种字符串的编码方式。
②.编码和解码使用的不是配套的。
12.windows和linux平台下的查找内存泄漏的工具。
windows下有Leakdiag等软件,linux下有Leaky、LeakTracer等。参考博客
13.如果程序上线一段时间后发生异常错误,使用什么工具或方法快速定位到引起异常的代码位置?
不会。
14.某一程序,多个子窗口显示同一数据,该数据改变时,每个子窗口的数据都需要更新显示。这一场景该使用哪一种设计模式。以及该设计模式的伪代码。
不会。
15.70瓶水,其中一瓶有毒,小白鼠喝了一滴以后,1小时之后会死亡。现在只有一个小时的时间,请问至少使用多少只小白鼠,可以找到至少60瓶无毒的水。
答:3只老鼠。(参考16瓶,找出14瓶的思路。)
在这里插入图片描述

16.关于操作系统的几个问题。
①.文件打开open()函数,会将文件全部读到内存中吗?不会。
②.一个线程打开一个文件多次,该线程占用的文件句柄数如何变化? 这篇博客讲的特清楚。不过这里面讲的是进程。会有多个。
线程的话,就不清楚了。
③.多个线程打开了一个文件后,两次write同一个文件,第一次写x个字节,第二次写y个字节。问文件最终大小会是多少? 不会。

竟然凉在了笔试,我去,我变菜了。好惨。。。
然后深夜又收到消息说通过了笔试。哈哈哈哈。

面试

AI机器人面试

一、模拟:
1.自我介绍。
2.三优点三缺点。
3.一个优秀的研发人员应该具备哪些素质?
4.做过的项目中最难的技术,挑战点在哪里?

电话面试

(这次的难度和上次完全不一样,可能是不同的面试官有不同的风格吧。)
1.自我介绍
2.谈一谈自己的项目。例举了扫雷。做这个东西时,当时遇到的问题,以及怎么解决的。
3.指针和引用的异同。
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
(2)不能有 NULL 引用,引用必须与合法的存储单元关联(指针则可以是 NULL)。
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
关于指针,有几篇博客,分别讲解了指针,指针的++ --运算,指针数组,指针作为形参和作为函数返回值。来自菜鸟教程的(我特喜欢这个网站)
4.一个线程也是有自己的堆栈的,这个我查了下《操作系统概念》这本书。
在这里插入图片描述

5.MFC的消息机制。
MFC设置了一张表,将窗口消息和相对应的消息处理函数进行映射。这种机制就是消息映射。这张表在窗口基类CWnd中定义,派生类的消息映射表如果没有相应动作的消息处理函数,则派生窗口接受一个消息时,会执行父类的消息处理函数。
6.举一个迭代器失效的例子。
见这个博客
7.C++里面的vector的push_back()函数具体是如何实现的?
先申请“1”个空间,不够,就申请“2”个,不够,就申请“4”个。以此类推。参考博客
8.C++的虚函数寻址和普通的函数有什么区别?
比普通函数,多了查虚表和取虚函数地址。(大概是这样。)
9.期望薪资?目前面试进展如何?

电话面试过了!哈哈哈哈。今晚第二轮面试。

视频面试

1.以代码形式记录问题和答案。

/*
1.验证构造函数设置成私有的,的作用。
2.验证析构函数设置成虚函数的作用。
2020.3.28 13:00
*/

#include <iostream>
using namespace std;

///*
//1.①.如果你不想让外面的用户直接构造一个类(假设这个类的名字为A)的对象,
//而希望用户只能构造这个类A的子类,
//那你就可以将类A的构造函数/析构函数声明为protected,
//而将类A的子类的构造函数/析构函数声明为public。
//*/
//class A
//{
//protected:
//    A() {}
//public:
//    void test() {
//        cout << "call A test()" << endl;
//    }
//};
//
//class B : public A
//{
//public: B() {}
//
//};
//
//int main() {
//    //A a; // error  这里把构造函数设置成保护的。
//    //在外面是无法访问的。但是子类是可以访问的。
//    B b; // ok
//    b.test();
//	return 0;
//}

/*
1.②.如果将构造函数/析构函数声明为private,那只能这个类的“内部”的函数才能构造这个类的对象了。
但是这样的话,在类外就无法调用了。这时可以把它设置成static的。
*/

//#include <iostream>
//using namespace std;
//
//class A
//{
//private:
//    int data;
//    A() :data(10) { cout << "A" << endl; }
//    ~A() { cout << "~A" << endl; }
//public:
//    static A& Instance()
//    {
//        static A a;
//        return a;
//    }
//    void Print()
//    {
//        cout << data << endl;
//    } 
//};
//
//int main(int argc, char** argv)
//{
//    A& ra = A::Instance(); //这个函数设置成static的,就可以直接被A以这种形式调用。
//    ra.Print();
//    return 0;
//}

//有点类似Singleton设计模式。https://www.bilibili.com/video/BV1kW411P7KS?p=12


//2.如果父类的析构函数设置成虚函数,这样指向子类的父类指针,就可以
//在释放的时候,先释放子类的对象,再释放父类的。
class A
{
public:
    A()
    {
        cout << "A" << endl;
        ptr = new int[10];
    }
    virtual ~A()  //就是说如果这里不是定义成virtual的,那么下面释放的时候只会释放父类的对象,而不会释放子类的。
    {
        delete []ptr;
        cout << "delete A" << endl;
    }
private:
    int* ptr;
};
class B : public A
{
public:
    B()
    {
        cout << "B" << endl;
        ptr = new long[10];
    }
    ~B()
    {
        delete []ptr;
        cout << "delete B" << endl;
    }
private:
    long* ptr;
};
int main()
{
    A* a = new B();
    delete a;
    return 0;
}
//输出是A, B, delete B, delete A.

2.代码题。

/*
1 编程题:获取1个无序数组中,最大的3个数
2 编程题:判断第一个字符串是否是第二个字符串的子串。
3 编程题:两个有序数组合并成一个有序数组
2020.3.27 同花顺二面,代码题  5min/一道
*/ 

#include <iostream>
#include <queue>
#include <stack>
using namespace std;

//int main() {
//	priority_queue<int>p1;
//	int num = 0;
//	while (cin >> num) {  //我当时觉得输入0这个数的话,这个地方就提前会结束。实际上不会结束。!
//		p1.push(num);
//	}
//	for (int i1 = 0; i1 < 3; i1++) {
//		cout<<p1.top()<<" ";
//		p1.pop();
//	}
//	return 0;
//}

//这道题是字符串模式识别的一个经典的例子。
//int main() {
//    string s1;
//    string s2;
//    cin >> s1;
//    cin >> s2;
//    int j = 0;
//    int i = 0;
//    for (i = j; i <= s2.length()-s1.length(); i++) {
//        j = i+1;
//        int i1;
//        for (i1 = 0; i1 < s1.length(); i1++) {
//            if (s1[i1] == s2[i + i1]) {}
//            else break;
//        }
//        if (i1 == s1.length()) {
//            cout << "YES";
//            break;
//        }    
//    }
//    if (i == s2.length() - s1.length() + 1) {
//        cout << "NO";
//    }
//    return 0;
//}

int main() {
    int a[3] = { 1,3,5 };
    int b[4] = { 2,4,6,8 };
    /*int c[7] = { 0 };
    copy(begin(a), end(a), begin(c));
    copy(begin(b), end(b), begin(c)+3);
    for (int i = 0; i < 7; i++) {
        for (int j = i+1; j < 7; j++) {
            if (c[i] > c[j]) {
                int temp = 0;
                temp = c[i];
                c[i] = c[j];
                c[j] = temp;
            }
        }
    }
    for (auto e:c) {
        cout << e << " ";
    }*/
    priority_queue<int,vector<int>,greater<int>>p1;  //数字大的优先级低。就是说数字小的优先级高。这样就是从小到大的顺序。
    for (int i = 0; i < 3; i++) {
        p1.push(a[i]);
    }
    for (int i = 0; i < 4; i++) {
        p1.push(b[i]);
    }
    while (!p1.empty()) {
        cout<<p1.top()<<" ";
        p1.pop();
    }
    return 0;
}

3.关于C++的虚函数,今天自己专门搜了一下,这里记录一下。
在C++里面,对于虚函数,使用了虚函数表和虚函数指针两个东西来帮忙实现多态性。
1)虚函数指针是确实存在的,在一个被实例化的对象中,它总是被存放在该对象的地址首位,这种做法的目的是为了保证运行的快速性。
2)虚表是属于类的,故只有一个。这个类可以创建多个对象,但是这些对象共享这个虚表,但是每个对象都会有一个虚指针指向这个虚表。
3)单继承时:
①没有重写父类的虚函数。
在这里插入图片描述

子类和父类的虚函数指针是一样的。
② 重写了父类的虚函数。在这里插入图片描述

子类和父类的虚函数指针的地址是不一样的。就是说子类已经用新的虚函数覆盖了父类的虚函数。
4)多继承时:
并且子类重写了虚函数。

class A1
{
public:
    A1(int _a1 = 1) : a1(_a1) { }
    virtual void f() { cout << "A1::f" << endl; }
    virtual void g() { cout << "A1::g" << endl; }
    virtual void h() { cout << "A1::h" << endl; }
    ~A1() {}
private:
    int a1;
};
class A2
{
public:
    A2(int _a2 = 2) : a2(_a2) { }
    virtual void f() { cout << "A2::f" << endl; }
    virtual void g() { cout << "A2::g" << endl; }
    virtual void h() { cout << "A2::h" << endl; }
    ~A2() {}
private:
    int a2;
};
class A3
{
public:
    A3(int _a3 = 3) : a3(_a3) { }
    virtual void f() { cout << "A3::f" << endl; }
    virtual void g() { cout << "A3::g" << endl; }
    virtual void h() { cout << "A3::h" << endl; }
    ~A3() {}
private:
    int a3;
};

class B : public A1, public A2, public A3
{
public:
    B(int _a1 = 1, int _a2 = 2, int _a3 = 3, int _b = 4) :A1(_a1), A2(_a2), A3(_a3), b(_b) { }
    virtual void f() { cout << "B::f" << endl; }
    virtual void g1() { cout << "B::g" << endl; }
    virtual void h1() { cout << "B::h" << endl; }
private:
    int b;
};

int main() {
    A1 a;
    a.f();//A1::f
    A2 a2;
    a2.f();
    A3 a3;
    a3.f();
    B b;
    b.f();
	return 0;
}

在这里插入图片描述

从图中可以看出,B类继承了前面的三个类的虚表,因为重写了f()函数。所以B类的虚表的虚函数指针和前面的类的虚函数指针不一样。并且,B类的虚函数表的三个f()虚函数的指针都是不一样的!也就是说现在前面的三个类的f()虚函数都被替换了。并且是三份,没有共享。
4.TCP select 暂时不懂。

复试没过。。凉了。埃。

原创文章 77 获赞 4 访问量 9018

猜你喜欢

转载自blog.csdn.net/weixin_40007143/article/details/104959551