2022.8.2 15:00

1、接雨水问题

2、进程间通信有哪些方式 7种(匿名管道、有名管道、信号、信号量、共享内存、消息队列、socket)

3、程序、进程、线程有什么区别(背的不好,有点瞎编,面试官皱眉了)

  • 定义
    程序是含有代码和数据的文件,存储在磁盘中,也就是说程序是静态的代码。
    进程 运行可执行文件后,会被装载到内存中,运行的程序就是进程。
    线程是进程的子任务,可以理解为轻量级的进程。
  • 根本区别 进程是操作系统资源分配的基本单位,而线程是CPU调度和程序执行的基本单位
  • 包含关系 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。
  • 资源 每个进程都有独立的代码和数据空间(程序上下文),进程间切换开销大;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
  • 内存分配 进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。
  • 共享
    进程间信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因为必须采用一些进程间通信(IPC)方式,在进程间进行信息交换。
    线程之间能够方便,快速地共享信息。只需将数据复制到共享(全局或堆)变量中即可。不过,要避免出现多个线程同时修改同一份信息的情况,这就需要使用到线程同步(同步就是协同步调,按预定的先后次序进行运行)技术(临界区、互斥量mutex、信号量semaphore、实物对象events)。
  • 创建 调用fork()来创建进程的代价相对较高,即便使用copy-on-write技术,仍然需要复制内存页表(page table)和文件描述符表(file descriptor table)之类的进程属性,这意味着fork()调用在时间上的开销依然不菲。创建线程比创建进程通常要快十倍甚至更多。线程的创建之所以快,是因为所需复制的诸多属性,在线程间本来就是共享的,无需采用写时复制(copy-on-write)来复制内存页,也无需复制页表。

  • 执行过程 每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制

  • 通信 进程间通信用IPC,线程间通信直接读写数据段(但需要一些同步和互斥的方法)。

  • 影响关系 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。


4、HTTP状态码(504 404 301)是什么意思,(就简答重定向、成功、客户端、服务器错误,没具体背,他问我4打头都是客户端报错吗,3打头都是重定向吗)

分类 分类描述
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误

200 OK 请求成功。一般用于GET与POST请求。

301 永久移动 Moved Permanently。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替

302 Found 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI

304 Not Modified 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源

400 Bad Request 客户端请求的语法错误,服务器无法理解

401 Unauthorized 请求要求用户的身份认证

403 Forbidden 服务器理解请求客户端的请求,但是拒绝执行此请求

404 Not Found 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置”您所请求的资源无法找到”的个性页面

504 Gateway Time-out 充当网关或代理的服务器,未及时从远端服务器获取请求


5、HTTP的header里面包含哪些key,分别是什么意思(以为问post get,面试官知道我不会直接过了

image-20220803155421787

灰体都是key


6、写sql,班级里 姓名重复的人的数量(不会

7、(a,b)联合索引

a = ? and c = ?,

b = ? and c = ?

a = ? and b = ?

不会

8、设计一个登录模块,考虑注意哪些安全问题,怎么避免(不会

我问能不能给建议和评价,他问你自己觉得怎么样

(后端肯定是需要数据库的)

网页登录和手机登录的问题 缓存存在哪,换了网络和地点网页还能登上吗 扫码登录怎么解决。。

2022.8.3 17:00

1、自我介绍学习能力强,举例

2、实习经历里的公司选择

3、实习收获

4、Flutter、qt学习看法,偏向C++还是js,(其实米哈游也在招前端

10min

5、指针和引用的区别,本质区别,定义 描述 使用,什么场景下用引用 什么场景下用指针,什么场景下只能用指针、什么场景下只能用引用。

指针是一个存放内存地址的整数,是一个实体,这个整数表示的是被指向的变量的地址。

引用其实就是变量的别名,一定要有本体

都是地址的概念

  • 程序为指针变量分配内存区域,而引用不需要分配内存区域;
  • 指针在声明时可以暂时不初始化,pointer = nullptr,引用永远都不会为,它得代表某个对象,引用在创建的同时必须被初始化
  • 指针的值在初始化后可以被重新赋值,即指向其它的存储单元,而引用在进行初始化后就不会再改变了,从一而终。
  • ”sizeof(运算符)引用”得到的是所指向的变量(对象)的大小,而”sizeof指针”得到的是指针本身的大小;指针和引用的自增(++)运算意义不一样;指针有多级,引用没有。可以有const指针,但是没有const引用;

使用场景:

指针:存在不指向任何对象的可能,改变指针的指向

引用:引用的主要功能就是作为函数的参数和返回值,当你需要指向某个东西,而且一定专一,绝不会让其指向其它东西,例如有些函数参数为了避免拷贝可以使用引用。重载某个操作符。有些函数参数为了避免拷贝可以使用引用,或者实现一个操作符,指针可以毫无约束的操作内存中的任何东西,功能十分强大,但是也很危险,所以可以在恰当的时机使用引用。

6、NULL和nullptr的区别

7、vector push_back()的复杂度

image-20220807153912590

image-20220807154212477

8、虚函数、模板函数,虚函数机制,虚函数表,虚表指针。

9、计算机基础,锁的用处,有哪些锁。锁的类型,悲观锁和乐观锁的区别。

10、线程和进程的区别,进程是资源分配的最小单元 怎么理解,线程是CPU调度的最小单元。

理解 举例 http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html (进程、线程、共享内存、互斥锁、信号量的解释)

11、TCP三次握手四次挥手,举例。

12、TCP状态

13、DNS

14、哈夫曼树拿来做什么(前缀编码)

19min

完全背包硬币凑数 13min

8min反问


2022.8.3 15:00

1、项目的TCP/UDP

2、socket通信的特点、实现机制

3、了解linux内核吗、进程线程内存管理

4、上下文切换会有哪些东西


大疆08.07笔试

1、static

生存周期: 变量从定义到销毁的时间范围。静态数据存放在全局数据区(不会导致堆栈溢出)的变量的生存周期存在于整个程序运行期间,而局部变量存放在栈中的数据则随着函数等的作用域结束导致出栈而销毁。

作用域: 变量的可见代码域(块作用域,函数作用域,类作用域,程序全局作用域)。

  • 局部变量m存放在栈中,当test函数结束,m将被销毁;静态变量i不存放在栈中,而是存放于程序的全局变量区域,因此随着函数test的结束,它并不随着出栈操作而被销毁,它的生存周期存在于程序的整个运行期。static局部变量只被初始化一次,下一次依据上一次结果值;

  • 再举一个全局声明的例子。在文件A 中定义静态变量j:
    int n=3; //默认为extern
    static int j=5; //声明为static
    全局变量和静态变量j都存放于程序的全局数据区域,它们的生存周期都是程序的整个运行期,但是n的作用域为全局作用域,可以通过extern在其他文件中使用,而j只能在文件A中使用,例如在文件B中:
    extern int n; //ok

    extern int j; //error: j在文件B中不可见

static全局变量与普通的全局变量有什么区别

这两者的区别在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。

static全局变量只初始化一次,防止在其他文件单元中被引用;  (静态数据不初始化会被自动初始化为0)

static局部变量和普通局部变量有什么区别 ?

把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;

static局部变量只被初始化一次,下一次依据上一次结果值; 其他文件可以定义同名函数,不会冲突。

static函数与普通函数有什么区别?

static函数与普通函数作用域不同,仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static修饰的函数),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件.

static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝。其它文件中可以定义相同名字的函数,不会发生冲突;

静态数据成员

非静态数据成员,每个类对象都有自己的拷贝。静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问,值可以更新。节省存储空间。需要改的时候改的很快。没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;

静态数据成员初始化与一般数据成员初始化不同。

静态数据成员初始化的格式为:

<数据类型><类名>::<静态数据成员名>=<值>

类的静态数据成员有两种访问形式:

<类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>

静态成员函数

它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,静态成员函数无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。 必须定义在类体内部!

C程序一直由下列部分组成:
1)正文段(代码段)——CPU执行的机器指令部分;一个程序只有一个副本;只读,防止程序由于意外事故而修改自身指令;
2)初始化数据段(数据段)——在程序中所有赋了初值的全局变量,存放在这里。
3)非初始化数据段(bss段)——在程序中没有初始化的全局变量;内核将此段初始化为0。(这就是为什么全局内置类型变量会初始化,而局部变量就为未初始化的未知值)
4)栈——增长方向:自顶向下增长;自动变量以及每次函数调用时所需要保存的信息(返回地址;环境信息)。
5)堆——动态存储部分。

程序在内存中申请了代码段,全局数据段(初始化和未初始化),栈和堆:程序代码放于代码段,全局变量和静态变量存放在全局数据段中,一直存在直到程序结束,而局部变量都放于临时的栈中,随着作用域的结束随着出栈操作而销毁。malloc和new出来的内存不属于上面提到的程序申请的内存中,而是在系统中申请到的内存,所以如果在程序中没有明确free和delete的话,程序结束后该内存仍不会被释放,造成内存泄漏。

2、reinterpret_cast

reinterpret_cast运算符是用来处理无关类型之间的转换;它会产生一个新的值,这个值会有与原始参数(expressoin)有完全相同的比特位。可以将int *转为long long 同是8bits

  • 从指针类型到一个足够大的整数类型
  • 从整数类型或者枚举类型到指针类型
  • 从一个指向函数的指针到另一个不同类型的指向函数的指针
  • 从一个指向对象的指针到另一个不同类型的指向对象的指针
  • 从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
  • 从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针

还有

  • static_cast<类型说明符>(表达式)用于进行比较“自然”和低风险的转换,如整型和浮点型、字符型之间的互相转换

  • dynamic_cast<类型说明符>(表达式)专门用于将多态基类的指针或引用强制转换为派生类的指针或引用

    	Base* pBase = new Derived(); // 转为基类
    pBase->print(); // 虚函数 在运行时的类型来决定运行结果 Derived print!
    pBase->work(); // 普通成员函数 编译期间决定 Base do work...

    Derived* pDerived = dynamic_cast<Derived*>(pBase);

  • const_cast<类型说明符>(表达式) 去掉const性质(没有真正去除),将 常量转换成非常量,如果原对象本身是常量,

    const int constant = 21;
    const int* const_p = &constant;
    int* modifier = const_cast<int*>(const_p);
    *modifier = 7;
    /**
    constant: 21
    *const_p: 7
    *modifier: 7
    **/

3、智能指针

  • shared_ptr 允许多指针指向同一个对象:允许拷贝和移动构造函数。
  • unique_ptr 独占所指向的对象:不允许拷贝和赋值构造函数。
  • weak_ptr 伴随类“允许默认、拷贝构造函数。

4、C++11 auto关键字

4、类、函数模板 全特化、偏特化

5、右值引用

6、线性数据结构:线性表 栈 队列 双端队列 数组 串 ,非线性:树 图

7、排序过程正确的有

8、缩窄转换

image-20220808204639298

9、STL迭代器

queue、stack没有迭代器,对于根据定义不允许顺序或随机访问的数据结构,迭代器没有任何意义。这就是堆栈和队列没有迭代器的原因,另一方面,向量和列表允许对元素进行顺序和/或随机访问,因此迭代器对于导航这些数据结构是有意义的。

10、在C++程序中调用被C 编译器编译后的函数,为什么要加extern “C” 实现 C++ 与 C 的混合编程。 解决名字匹配问题

extern “C”是连接声明。被extern “C”修饰的变量和函数是按照C语言方式编译和连接的。作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:void foo( int x, int y );该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。

2022.8.12 10:00

1、反转链表②

链表出现野指针问题,野指针不可为next赋值,释放指针。30min

2、vector的遍历 使用for循环和迭代器的优劣 34min

for需要知道集合或数组的大小,而且需要是有序的,不然无法遍历;

iterator都不需要知道集合或数组的大小,他们都是得到集合内的每个元素然后进行处理

iterator是最强大的,他可以随时修改或者删除集合内部的元素,并且是在不需要知道元素和集合的大小的情况下进行的,当你需要对不同的容器实现同样的遍历方式时,迭代器是最好的选择!

https://blog.51cto.com/shijianfeng/4896110

当容器中的一个元素被删除时,指向该元素后续的迭代器变得无效。上面的代码中,只要执行了erase(it),那么it就会变得无效,那么执行it++就肯定会出错。

  1. 对于节点式容器(map, list, set)元素的删除,插入操作会导致指向该元素的迭代器失效,其他元素迭代器不受影响

  2. 对于顺序式容器(vector,string,deque)元素的删除、插入操作会导致指向该元素以及后面的元素的迭代器失效

3、两个线程之间的同步 40min

条件变量+锁

4、项目

2022.8.12 16:00

商业架构部 广告投放 商业相关 检索架构c++

1、2min13s 输入网址 请求发起到结束流程:DNS检查浏览器缓存、系统缓存hosts缓存,本地域名服务器,根域名服务器,顶级域名服务器,权威域名服务器。

2、http的header都有什么

  1. General通用信息

    请求url地址, 请求方法, 状态码

  2. 请求头Request Headers

    • User-Agent : 客户端信息
    • cookie : 浏览器端cookie
    • Origin : 目标地址
    • Referer : 包含一个URL,用户从该URL代表的页面出发访问当前请求的页面
    • Host : 客户端地址
  3. Response Headers 响应头

    • Cache-Control 缓存哪种类型
    • Content-Language 响应体的语言

3、xss攻击原理

跨站脚本攻击(Cross-site scripting,XSS)是一种安全漏洞,攻击者可以利用这种漏洞在网站上注入恶意的客户端代码。

XSS的重点不在于跨站点,而在于脚本的执行。 XSS的原理是: 恶意攻击者在web页面中会插入一些恶意的script代码。 当用户浏览该页面的时候,那么嵌入到web页面中script代码会执行,因此会达到恶意攻击用户的目的。

Sql 注入攻击是通过将恶意的 Sql 查询或添加语句插入到应用的输入参数中,再在后台 Sql 服务器上解析执行进行的攻击,它目前黑客对数据库进行攻击的最常用手段之一。

4、进程,线程

IPC7种方式

信号怎么给另一个程序发送信号

5、C++编译流程

6、一般用什么编译、g++编译选项

-D{=|#} 定义宏 define macro

-O0 不进行优化处理。

-O 或 -O1 优化生成代码。

-O2 进一步优化。

-O3 比 -O2 更进一步优化,包括 inline 函数。

7、CPU cache 结构、如何提升代码的cache命中率

代码优化目标是提升 CPU 缓存的命中率

19min 算法题 五子棋 二维数组表示棋盘

53min完成

并发度比较高,提交场景下,贴吧发帖并发度高,提交多,mysql数据库性能没那么强;

2022.8.16 14:30

2min C++智能指针、什么工具排查内存泄漏

Valgrind具是一个用于调试和分析Linux程序的GPL系统。使用Valgrind的工套件,您可以自动检测许多内存管理和线程错误,使程序更稳定。还可以执行详细的分析以帮助加速程序的执行。下面我们介绍Valgrind工具集中的内存检测工具Memcheck的用法,以提高内存错误的查找效率。

2022.08.24 zk二面

1、抽象类怎么定义的,从软件工程角度讲,c++引入抽象类的作用是什么

2、定义了一个int类的指针,定义为0,然后又去访问它,编译的时候有没有问题

3、操作系统引入虚拟地址空间的作用

2022.09.02 10:00

1、hexo博客后端是什么

是nodejs

2、web服务器项目 高性能高并发是怎么体现出来的

3、select/poll/epoll区别

4、http报文格式

request

5、301 和 302 区别

6、线程池、核心线程 非核心线程

7、B树和B+树区别

8、快排思想

9、linux运维了解吗

10、服务器性能指令、cpu memory指标 df

  • top:查看内存/显示系统当前进程信息

  • df -h:查看磁盘储存状况

  • iotop:查看IO读写(yum install iotop安装)

  • iotop -o:直接查看比较高的磁盘读写程序

  • netstat -tunlp | grep 端口号:查看端口号占用情况(1)

  • lsof -i:端口号:查看端口号占用情况(2)

  • uptime:查看报告系统运行时长及平均负载

  • ps aux:查看进程

11、sql索引失效的情况

12、读未提交有什么问题

13、BIO和NIO

2022.09.03 17:00

1、c11你认为最大的更新是什么

类对象,在默认情况下,将一个对象赋给同类型的另外一个对象时,C++将源对象的每个数据成员复制到目标对象中相应的数据成员中

#include <bits/stdc++.h>

using namespace std;
class Test
{
public:
int a;
Test()
{
a = 1;
}
};
int main()
{
Test t1;
t1.a = 10;

Test t2;
t2.a = 5;

cout << "&t1:" << &t1 << " a = 10 " << t1.a << endl;
cout << "&t2:" << &t2 << " a = 5 " << t2.a << endl;

cout << "------------------------------" << endl;
t2 = t1;
cout << "&t1:" << &t1 << " &t1:0x61fe1c a = 10 " << t1.a << endl;
cout << "&t2:" << &t2 << " &t2:0x61fe18 a = 10 " << t2.a << endl;

cout << "------------------------------" << endl;

t1.a = 111;
t2.a = 222;
cout << "&t1:" << &t1 << " &t1:0x61fe1c a = 111 " << t1.a << endl;
cout << "&t2:" << &t2 << " &t2:0x61fe18 a = 222 " << t2.a << endl;
system("pause");
return 0;
}

类指针

#include <bits/stdc++.h>

using namespace std;
class Test
{
public:
int a;
Test()
{
a = 1;
}
};
int main()
{

Test *t1 = new Test();
t1->a = 10;

Test *t2 = new Test();
t2->a = 5;

cout << "&t1:" << t1 << " a = 10 " << t1->a << endl;
cout << "&t2:" << t2 << " a = 5 " << t2->a << endl;

cout << "------------------------------" << endl;
t2 = t1;
cout << "&t1:" << t1 << " &t1:0xfd3f20 a = 10 " << t1->a << endl;
cout << "&t2:" << t2 << " &t2:0xfd3f20 a = 10 " << t2->a << endl;

cout << "------------------------------" << endl;

t1->a = 111;
t2->a = 222;
cout << "&t1:" << t1 << " &t1:0xfd3f20 a = 222 " << t1->a << endl;
cout << "&t2:" << t2 << " &t2:0xfd3f20 a = 222 " << t2->a << endl;

system("pause");
return 0;
}

类对象和类指针区别:

  • 定义对象实例时,分配了内存,指针变量则未分配类对象所需内存。

  • 指针变量是间接访问,但可实现多态(通过父类指针可调用子类对象),并且没有调用构造函数。
    直接声明可直接访问,但不能实现多态,声明即调用了构造函数(已分配了内存)。

  • 类的对象:用的是内存栈,是个局部的临时变量.
    类的指针:用的是内存堆,是个永久变量,除非你释放它.

  • 类指针的优点:
    第一实现多态。
    第二,在函数调用,传指针参数。不管你的对象或结构参数多么庞大,你用指针,传过去的就是4个字节。如果用对象,参数传递占用的资源就太大了

  • 当类是有虚函数的基类,Func是它的一个虚函数,则调用Func时:
    类的对象:调用的是它自己的Func;
    类的指针:调用的是分配给它空间时那种类的Func;

通过基类对象的指针(或引用)访问“同名虚函数”——>动态联编

通过派生类对象访问同名函数——>静态联编

#include <iostream.h>
class animal
{
public:
void sleep()
{ cout<<"animal sleep"<<endl; }
void breathe()
{ cout<<"animal breathe"<<endl; }
};
class fish:public animal
{
public:
void breathe()
{
cout<<"fish bubble"<<endl;
}
};
void main()
{
fish fh;
animal *pAn=&fh; // 隐式类型转换
pAn->breathe();
}

区别于这个

编译的角度
C++编译器在编译的时候,要确定每个对象调用的函数(要求此函数是非虚函数)的地址,这称为早期绑定(early binding),当我们将fish类的对象fh的地址赋给pAn时,C++编译器进行了类型转换,此时C++编译器认为变量pAn保存的就是animal对象的地址。当在main()函数中执行pAn->breathe()时,调用的当然就是animal对象的breathe函数。

2、手写体现多态的例子


#include <bits/stdc++.h>
using namespace std;

class A
{
public:
virtual void run()
{
cout << " a ";
}

void run2()
{
cout << " b ";
}
};

class B : public A
{
public:
void run()
{
cout << " aa ";
}

void run2()
{
cout << " bb ";
}
};

int main()
{
A *a = new B();
a->run(); // 多态执行aa
a->run2(); // b 执行基类函数;若基类没函数 仍然调用基类函数 因此报错;
A a2 = B();
a2.run(); // a 执行基类函数
a2.run2(); // b 执行基类函数
B a3 = B();
a3.run(); // aa
a3.run2(); // bb

system("pause");
return 0;
}

函数隐藏和函数覆盖只会发生在基类和派生类之间。

函数隐藏是指派生类中函数与基类中的函数同名,但是这个函数在基类中并没有被定义为虚函数,这种情况就是函数的隐藏。
所谓隐藏是指使用常规的调用方法,派生类对象访问这个函数时,会优先访问派生类中的这个函数,基类中的这个函数对派生类对象来说是隐藏起来的。 但是隐藏并不意味这不存在或完全不可访问。通过 d->Base::func()访问基类中被隐藏的函数。

函数覆盖特指由基类中定义的虚函数引发的一种多态现象。在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数,用法格式为:virtual 函数返回类型 函数名(参数表) {函数体};实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。

基类指针指向派生类对象

一、 函数在基类和派生类中都存在

这时通过“指向派生类对象的基类指针”调用成员函数,调用的是基类的成员函数。

二、函数在基类中不存在,在派生类中存在

由于调用的还是基类中的成员函数,试图通过基类指针调用派生类才有的成员函数,则编译器会报错。

三、 将基类指针强制转换为派生类指针

这种是向下的强制类型转换,转换之后“指向派生类的基类指针”就可以访问派生类的成员函数。但是这种强制转换操作是一种潜在的危险操作

四、基类中存在虚函数的情况

3、手写链表模板类

// linklist.h

#include <iostream>
#include <cstdio>
using namespace std;

template <typename T>
struct Node
{
T t;
Node<T> *next;
};

template <typename T>
class LinkList
{
public:
LinkList();
~LinkList();

public:
int clear();
int insert(T &t, int pos);
int get(int pos, T &t);
int del(int pos, T &t);
int getLen();

protected:
Node<T> *header;
int length;
};

template <typename T>
LinkList<T>::LinkList()
{
header = new Node < T > ;
header->next = NULL;
length = 0;
}

template <typename T>
LinkList<T>::~LinkList()
{
Node<T> *tmp = NULL;

while (header) {
tmp = header->next;
delete header;
header = tmp;
}
}

template <typename T>
int LinkList<T>::clear()
{
~LinkList();
LinkList();
return 0;
}

template <typename T>
int LinkList<T>::insert(T &t, int pos)
{
Node<T> *cur = NULL;

// 对pos的容错处理
if (pos >= length) {
pos = length;
}

cur = header;
for (int i = 0; i < pos; ++i) {
cur = cur->next;
}

// 把上层应用的t结点缓存到容器中
Node<T> *node = new Node < T > ;
node->next = NULL;
node->t = t; // 把t缓存到容器中

node->next = cur->next;
cur->next = node;

++length;

return 0;
}

template <typename T>
int LinkList<T>::get(int pos, T &t)
{
Node<T> *cur = NULL;

if (pos >= length) {
return -1;
}

cur = header;
for (int i = 0; i < pos; ++i) {
cur = cur->next;
}

t = cur->next->t; // 把pos位置的结点赋值给t

return 0;
}

template <typename T>
int LinkList<T>::del(int pos, T &t)
{
Node<T> *cur = NULL;

if (pos >= length) {
return -1;
}

cur = header;
for (int i = 0; i < pos; ++i) {
cur = cur->next;
}
Node<T> *ret = NULL;
ret = cur->next;
t = ret->t; // 把缓存的结点给上层应用t

// 删除操作
cur->next = ret->next;
--length;
delete ret; // 注意释放内存,因为insert的时候new Node<T>

return 0;
}

template <typename T>
int LinkList<T>::getLen()
{
return length;
}