C++面经
1 .引用和指针的区别
1. 指针和引用的定义和性质的区别:
(1)指针:指针是一个变量,变量存储的是一个地址,指向内存的一个存储单元;
引用:和原来的变量本质上是同一个东西,只不过是给变量取别名
int a = 1; *p = &a; 该指针变量指向a的存储单元,即p的值是a存储单元的地址
int a = 1; int &b =a; 整形a和引用b事实上是同一个东西,在内存中占同一个存储单元
(2)引用不能为空,创建时必须初始化;指针可以是空值,在任何时候被初始化
(3)指针的值在初始化之后可以改变,即指向其他的存储单元;引用在初始化之后就不会改变
(4)可以有const指针,没有const引用
(5)指针可以有多级,但引用只能有一级
(6)“sizeof引用”得到的是所指向的变量(对象)的大小
“sizeof指针”得到的是指针本身的大小
-
引用传递,不会为形参分配内存空间,形参只是实参的别名,形参的改变会影响实参的值,函数调用结束后形参不会被释放
-
地址传递,形参为指针变量,将实参的地址传递给函数,可以在函数中改变实参的值,调用时为形参指针变量分配内存,结束时释放指针变量
2.new和malloc的区别
- new/delete是关键字,需要编译器的支持;malloc/free是函数,需要包含头文件;
- new和malloc都是在堆上开辟内存空间。new申请内存分配时无需指定内存块的大小,编译器会根据信息类型自动计算;malloc需要显示地指出所需内存的尺寸
- new申请内存分配成功后,返回的是对象类型的指针,类型严格与对象匹配,无需进行类型转化
malloc申请内存分配成功后返回的是void *,需要通过强制类型转换成我们需要的类型
-
new内存分配失败后,抛出bac_alloc异常,malloc内存分配失败返回NULL
-
new会先调用operator new函数,申请足够的内存空间,然后调用构造函数初始化成员变量,最后返回对象类型的指针。delete先调用析构函数,然后调用operator delete 释放内存。
malloc/free是库函数,只能动态的申请和释放内存
-
new/delete操作符能够重载,malloc不能重载
-
new在自由存储区上为对象动态分配内存空间,malloc从对上动态分配内存空间
3.Tcp客户端与服务端的通信
- 客户端调用connect函数,触发连接请求,向服务器发送一个SYN J包,这时connect进入阻塞
- 服务器监听到连接请求之后,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞
- 客户端收到服务器的SYN K ,ACK J+1,这时connect返回,并对SYN K进行确认;服务器接收到ACK K+1时,accept返回,三次握手完毕。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LgkZqX0E-1605539712955)(C:\Users\52675\AppData\Roaming\Typora\typora-user-images\image-20201115214807972.png)]
服务端先初始化socket,然后与端口绑定,并对端口进行监听,然后调用accept函数阻塞等待客户端的连接。如果有客户端初始化socket,连接到服务器。如果连接成功,客户端与服务器的连接就建立了。客户端向服务器发送数据请求,服务器接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据之后,关闭连接,一次交互就完成了。
4. 数组和链表的区别
区别一:物理地址存储的连续性
数组元素在内存中是连续存放的
链表元素在内存中是非连续的
区别二:访问速度
数组的访问速度很快,因为数组可以根据下标进行快速定位
链表的访问速度很慢,因为链表访问数据需要移动指针
区别三:添加、删减元素速度
数组元素的增删速度很慢,因为需要移动大量数据
链表元素的增删速度很快,因为只需要修改指针
5.TCP和UDP的区别
- TCP是面向连接的,UDP是面向无连接的
- UDP结构比较简单
- TCP是面向字节流的,UDP是基于数据报的
- TCP保证数据正确性,UDP可能丢包
- TCP保证数据顺序,UDP不保证
6.多态(函数名有多种形态表现出来)
6.1 多态的基本概念
多态分为两类
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
#include <iostream>
using namespace std;
//多态
//动物类
class Animal
{
public:
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
//猫类
class Cat:public Animal
{
public:
void speak()
{
cout << "小猫在说话" << endl;
}
};
//狗类
class Dog :public Animal
{
public:
void speak()
{
cout << "小狗在说话" << endl;
}
};
//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,也就是地址晚绑定
void doSpeak(Animal & animal) //Animal & animal = cat;
{ //父类的引用指向子类对象
animal.speak();
}
void test()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main()
{
test();
system("pause");
return 0;
}
动态多态的满足条件:
- 有继承关系
- 子类重写父类的虚函数
动态多态的使用条件:
- 父类的指针或者引用指向子类的对象
**重写:**函数返回值类型 函数名 参数列表 完全相同
Animal类内部结构 vfptr - 虚函数指针
vfptr v - virtual
| f - function
| ptr - poniter
| vftable - 虚函数表
| v - virtual
vftable f - funciton
table - table
//虚函数指针指向虚函数表
//表的内部记录虚函数的地址
Cat 类内部结构
//当子类重写父类的虚函数时
//子类中的虚函数表 内部 会替换成 子类的虚函数地址
父类指针或引用指向子类对象时,发生多态
6.2纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要是调用子类重写的内容
因为可以将虚函数改为纯虚函数
当类中有了纯虚函数,这个类也叫***抽象类***
抽象类:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include <iostream>
using namespace std;
//纯虚函数和抽象类
class Base
{
public:
//纯虚函数
//只要有一个纯虚函数,这个类也称为抽象类
//抽象类特点:
//1.无法实例化对象
//2.子类必须重写父类中的纯虚函数,否则也属于抽象类
virtual void func() = 0;
};
class Son :public Base
{
public:
virtual void func()
{
cout << "func()函数调用这里是子类!" << endl;
}
};
void test()
{
//Son s;//子类必须重写父类中的纯虚函数,否则无法实例化对象
Base * base = new Son;
base->func();
}
int main()
{
test();
system("pause");
return 0;
}
6.3 虚析构和纯虚析构
多态使用时,如果子类中有属性开辟在堆区,那么父类指针在释放时无法调用到子类的析构函数
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要具体的函数实现
虚析构和纯虚析构的区别:
- 如果是纯虚析构, 该类属于抽象类,无法实例化对象
#include <iostream>
#include <string>
using namespace std;
//虚析构和纯虚析构
class Animal
{
public:
Animal()
{
cout << "Animal的构造函数调用" << endl;
}
virtual void speak() = 0;
//利用虚析构可以解决 父类指针释放子类对象不干净的问题
//virtual ~Animal()
//{
//
// cout << "Animal的虚析构函数调用" << endl;
//}
//纯虚析构 需要声明也需要实现
//有了纯虚析构之后,这个类也属于抽象类,不法实例化对象
virtual ~Animal() = 0;
};
Animal::~Animal()
{
cout << "Animal的纯虚析构" << endl;
}
class Cat :public Animal
{
public:
Cat(string name)
{
cout << "Cat构造函数" << endl;
m_Name = new string(name);
}
virtual void speak()
{
cout << *m_Name << "小猫在说话" << endl;
}
~Cat()
{
if (m_Name != NULL)
{
cout << "Cat的析构函数调用" << endl;
delete m_Name;
m_Name = NULL;
}
}
string *m_Name;
};
void test()
{
Animal * animal = new Cat("Tom");
animal->speak();
delete animal;
}
int main()
{
test();
system("pause");
return 0;
}
总结:
- 虚析构或者纯虚析构都是用来解决父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
- 拥有纯虚析构函数的类也属于抽象类
7.二叉树的性质
- 在二叉树的第i层至多有2(i-1)个结点
- 深度为k的二叉树至多有2(i)-1个结点\
- 对于任何一棵二叉树,如果其终端结点为n0,度为2的结点数为n2
- 具有n个结点的完全二叉树深度为[log2n]+1
8.SQL多表查询
SELECT table_a.id,table_a.name,table_a.time as time,COUNT(table_b.time) AS count
FROM table_a
LEFT JOIN table_b
on table_a.time = table_b.time
GROUP BY table_b.time