1、类对象的大小
#include<iostream>
using namespace std;
class A
{
int a;
virtual void foo() {};
};
class B
{
int a;
virtual void foo() {};
};
class C : public B
{
static int c;
virtual void foo() {};
};
int main()
{
cout << sizeof(A) << endl;
cout << sizeof(B)<<endl;
cout << sizeof(C);
return 0;
}
2、不调用库函数写一个strcpy
#include<stdio.h>
#include<assert.h>
char * my_strcpy(char* dest,const char* sou)
{
assert(dest && sou);
char* ret = dest;
while (*dest++=*sou++)
{
;
}
return ret;
}
int main()
{
char arr1[] = "Hello World!";
char arr2[] = "***************";
printf("%s", my_strcpy(arr2, arr1));
return 0;
}
3、写一个冒泡排序
void bubblesort(int a[],int len)
{
int i, j;
for (i = 0; i < len - 1; ++i)
{
for (j = 0; j < len - i - 1; ++j)
{
if (a[j] > a[j + 1])
{
int temp = a[j + 1];
a[j + 1] = a[j];
a[j] = temp;
}
}
}
}
冒泡排序时间复杂度为
最优的时间复杂度为:O( n^2 ) ;
最差的时间复杂度为:O( n^2 );
平均的时间复杂度为:O( n^2 );
4、STL中map底层实现?
STL的map底层是用红黑树实现的,查找时间复杂度是Olog(n)。
5、TCP协议位于ios模型中哪一层?下一层是哪一层?
传输层 网络层
6、C++11的新标准认识哪一些?
着重问了智能指针的应用场景 优越点:
优点:
智能指针能够使开发中的程序员不用关心析构的问题,避免因忘记析构导致内存泄露
缺点:
1、有相连性,用了一处之后导致很多传参的位置也得跟随修改
2、如果类的内部组合或者聚合了一些别的类,这些类的析构又不想在这个类处理的时候,智能指针析构的不确定性会导致这些类无法按要求进行管理
智能指针的适用环境
智能指针适用于保存那种独立性比较强的指针数据,不适合保存那种内部具有一些别的类,然后这些类需要在析构时做特定处理的指针类,因为智能指针析构的不确定性,可能会导致这些类的处理受到无法估算的影响
auto_ptr不再建议使用 auto_ptr复制时会剥夺被复制的auto_ptr的内存所有权,有潜在的内存崩溃问题
7、平时命令ping所用的是什么协议
使用的是 ICMP 协议
ICMP用于在IP主机、路由器之间传递控制消息。控制消息是指网络通不通、主机是否可达、路由是否可用等网络本身的消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。
8、实现一个推排序以及堆排序的时间复杂度
堆的定义:
每个节点都不大于或者都不小于它的子节点的完全二叉树。
假设当前节点下标为 i
那么其父节点下标为 (i - 1) / 2 取下整
左右子节点下标分别为 2*i + 1 和 2*i + 2
堆排序基本思想:
- 将无序序列建成一个堆
- 输出堆顶元素
- 以最后一个元素代替堆顶元素
- 将剩余的n-1个元素再调整成一个堆
- 重复执行 直到整个堆剩下一个元素
维护堆:
维护堆的复杂度为O(logN)
void headpify(int a[], int n, int i)
{
int largest = i;
int leftson = i * 2 + 1;
int rightson = i * 2 + 2;
if (leftson < n && a[largest] < a[leftson])
largest = leftson;
if (rightson < n && a[largest] < a[rightson])
largest = rightson;
if (largest != i)
{
swap(a[largest],a[i]);
headpify(a, n, largest);
}
}
建立堆:
时间复杂度O(N)
//建立堆
int i;
for (i = n / 2 - 1 ; i >= 0; --i) //此处i = n - 1 所以i的父节点是 (i - 1) / 2 所以此处i = n / 2 - 1 是求 i = n - 1 时的父节点
{
headpify(a, n, i);
}
堆排序:
时间复杂度O (NlogN)
//排序
for (i = n - 1; i > 0; --i)
{
swap(a[i], a[0]);
headpify(a, i, 0);
}
测试代码:
#include<iostream>
using namespace std;
#include<vector>
#include<set>
#include <unordered_set>
#include<stack>
//维护堆
void headpify(int a[], int n, int i)
{
int largest = i;
int leftson = i * 2 + 1;
int rightson = i * 2 + 2;
if (leftson < n && a[largest] < a[leftson])
largest = leftson;
if (rightson < n && a[largest] < a[rightson])
largest = rightson;
if (largest != i)
{
swap(a[largest],a[i]);
headpify(a, n, largest);
}
}
//堆排序
void head_sort(int a[],int n)
{
//建立堆
int i;
for (i = n / 2 - 1 ; i >= 0; --i) //此处i = n - 1 所以i的父节点是 (i - 1) / 2 所以此处i = n / 2 - 1 是求 i = n - 1 时的父节点
{
headpify(a, n, i);
}
//排序
for (i = n - 1; i > 0; --i)
{
swap(a[i], a[0]);
headpify(a, i, 0);
}
}
int main()
{
int a[] = { 2,3,8,1,4,9,10,7,16,14 };
int len = sizeof(a) / sizeof(a[0]);
cout << "排序前 " << endl;
for (int i = 0; i < len; i++)
{
cout << a[i] << " ";
}
cout << endl;
head_sort(a, len);
cout << "排序后 " << endl;
for (int i = 0; i < len; i++)
{
cout << a[i] << " ";
}
cout << endl;
}
9、线程跟进程的优缺点比较?
补充知识:
多线程的优点:
1、无需跨进程边界;
2、程序逻辑和控制方式简单;
3、所有线程可以直接共享内存和变量等;
4、线程方式消耗的总资源比进程方式好;
多线程缺点:
1、每个线程与主程序共用地址空间,受限于2GB地址空间;
2、线程之间的同步和加锁控制比较麻烦;
3、一个线程的崩溃可能影响到整个程序的稳定性;
4、到达一定的线程数程度后,即使再增加CPU也无法提高性能,例如Windows Server 2003,大约是1500个左右的线程数就快到极限了(线程堆栈设定为1M),如果设定线程堆栈为2M,还达不到1500个线程总数;
5、线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU
多进程优点:
1、每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;
2、通过增加CPU,就可以容易扩充性能;
3、可以尽量减少线程加锁/解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系;
4、每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大
多线程缺点:
1、逻辑控制复杂,需要和主程序交互;
2、需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算
3、多进程调度开销比较大;
简单版:
区别:
1、进程就是程序执行的一个实例
2、进程是资源分配的最小单位
3、线程是程序执行的最小单位
4、进程有单独的地址空间
5、线程没有单独的地址空间
线程的优点
1、每个进程都要开辟一个空间,而线程是共享数据空间的,所以线程更加的节省资源
2、进程的通信比较复杂,需要运用到进程间的通信各种API,而线程是共享数据的,所以线程的数据传递更加的方便。
3、线程的运行效率要比进程高
10、多线程什么是死锁、造成死锁的原因以及怎么预防死锁
死锁:是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。
预防死锁:主要看个人编写代码情况吧
(1)在编写多线程程序之前,首先编写正确的程序,然后再移植到多线程
(2)时刻检查自己写的程序有没有在跳出时忘记释放锁
(3)如果自己的模块可能重复使用一个锁,建议使用嵌套锁
(4)对于某些锁代码,不要临时重新编写,建议使用库里面的锁,或者自己曾经编写的锁
(5)如果某项业务需要获取多个锁,必须保证锁的按某种顺序获取,否则必定死锁
(6)编写简单的测试用例,验证有没有死锁
(7)编写验证死锁的程序,从源头避免死锁
11、进入一个网站比如xxx.com需要用到什么协议
DNS协议、UDP协议、http协议、TCP协议、https协议、IP协议、ARP协议
12、静态连接与动态链接的优缺点:
静态链接的优缺点:
静态链接的缺点很明显,一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了printf()函数,则这多个程序中都含有printf.o,所以同一个目标文件都在内存存在多个副本;另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。
动态链接的优缺点:
动态链接的优点显而易见,就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多分,副本,而是这多个程序在执行时共享同一份副本;另一个优点是,更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。但是动态链接也是有缺点的,因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。
据估算,动态链接和静态链接相比,性能损失大约在5%以下。经过实践证明,这点性能损失用来换区程序在空间上的节省和程序构建和升级时的灵活性是值得的。
13、给定一个数字532 XYZ+YZZ = 532 求X、Y、Z各是多少
void func()
{
int target = 532;
int i, j;
int X, Y, Z;
#if 0
因为XYZ + YZZ = 532 X是唯一的三位数数字 所以X首先就不能大于6 Y、Z的数字便是0 - 9 之间
那么只需要将i j 当成 XYZ YZZ即可
#endif // 0
for ( X = 1; X <= 5; X++)
{
for ( Y = 1; Y <= 9; Y++)
{
for ( Z = 1; Z <= 9; Z++)
{
i = X * 100 + Y * 10 + Z;
j = Y * 100 + Z * 10 + Z;
int res = i + j;
if (res == target)
{
cout << "X的值为: " << X << " " << "Y的值为: " << Y << " " << "Z的值为: " << Z << " ";
}
cout << endl;
break;
}
}
}
}
14、求一个链表是环链表还是最后一个节点为空的链表
bool hasCycle(ListNode* head)
{
unordered_set<ListNode*> Set;
while (head != nullptr)
{
if (Set.count(head))
{
return true;
cout << "是个环状链表" << endl;
}
Set.insert(head);
head = head->next;
}
return false;
cout << "最后一个节点指向空!" << endl;
}
8.24上午(游戏服务端开发):
io多路复用(epoll)
红黑树相关知识点
算法(动态规划)
多态是什么?动态多态的底层理解是什么?
简单描述一下快速排序?
vector的动态扩容
多线程应该注意什么?
线程之间怎么进行通讯的?
行业知识:游戏服务端与客户端的区别?
8.24下午(C++后台开发):
四次挥手分别的状态是什么?
b与b+树的区别在哪?
STL的使用场景?
C++11新特性有什么?
快排稳定吗?冒泡排序稳定吗?两者的时间复杂度
二分查找的理解与时间复杂度?对数组有什么要求?
8.24上午(游戏客户端开发):
问的比较多的是简历上的项目
链表与数组的区别?
学习一门新的语言你怎么去规划呢?
数据结构的理解
8.25 下午(游戏客户端开发):
1、vector如何实现动态扩容?如何删除vector开辟的内存空间?
首先vector的动态扩容应该是大家都知道的知识点了。
关于如何删除vector的内存空间:
void clearvector()
{
vector<int> v1;
for (int i = 0; i < 100; i++)
{
v1.push_back(i);
}
cout << "删除前的元素个数与容量: " << endl;
cout << v1.size() << " " << v1.capacity() << " " << endl;
#if 0
//第一种方式 使用C++11新特性来完成这个任务
v1.clear(); //首先清空元素内的个数
v1.shrink_to_fit(); //新特性 能够在clear之后完全释放内存空间
cout << "第一种方法删除后的元素个数与容量 " << endl;
cout << v1.size() << " " << v1.capacity() << " " << endl;
#endif // 0
//第二种方式 通过swap
//第一种swap 释放整个vector内存空间:
#if 0
vector<int>().swap(v1);
cout << "第一种swap删除后的元素个数与容量 " << endl;
cout << v1.size() << " " << v1.capacity() << " " << endl;
#endif // 0
#if 0
//第二种swap 释放多余的内存空间
vector<int>(v1).swap(v1);
cout << "第二种swap删除后的元素个数与容量 " << endl;
cout << v1.size() << " " << v1.capacity() << " " << endl;
//其实就是构造了个临时的vector对象去调用swap完成这个操作
#endif // 0
}
建议自己敲代码看看结果 大概有个整体的认知。
2、map删除value为某个目标数:
这里以10为例子:
void deletenums()
{
map<int, int>m1;
pair<int, int> p1 = make_pair(1, 10);
pair<int, int> p2 = make_pair(2, 10);
pair<int, int> p3 = make_pair(3, 12);
pair<int, int> p4 = make_pair(4, 11);
pair<int, int> p5 = make_pair(5, 9);
m1.insert(p1);
m1.insert(p2);
m1.insert(p3);
m1.insert(p4);
m1.insert(p5);
for (auto it = m1.begin(); it != m1.end();)
{
if (it->second == 10)
{
m1.erase(it++);
}
else
{
it++;
}
}
for (auto it = m1.begin(); it != m1.end(); it++)
{
cout << it->second << " ";
}
cout << endl;
}
这里主要考察map容器的删除元素
3、给定大量文字 如何在这一堆文字中寻找想要的字符?
运用哈希映射 注意哈希冲突
4、在游戏场景中,如何让其他玩家都看见目标玩家释放的技能画面或者其他信息?
5、出现内存越界问题怎么判断bug位置
6、给实例化道具实现唯一的id
7、代码题:
class AA
{
public:
AA()
{
cout << "A";
}
~AA()
{
cout << "a";
}
};
class BB : public AA
{
AA aa;
public:
BB()
{
cout << "B";
}
~BB()
{
cout << "b";
}
};
int main()
{
BB bb;
}
结果是AABbaa
int main()
{
int a[5] = { 5,4,3,2,1 };
int* ptr = (int*)(&a + 1);
cout << *(a + 1)<<* (ptr - 1);
}
结果是4 1
int main()
{
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char* str5 = "abc";
const char* str6 = "abc";
cout << (str1 == str2) << endl;
cout << (str3 == str4) << endl;
cout << (str5 == str6) << endl;
}
结果是0 0 1