目录

简述一下智能指针

【得分点】

智能指针解决的问题、内存泄露、4种智能指针、实现原理

【参考答案】

标准回答

  1. 智能指针解决的问题

如果在程序中使用new 从堆(自由存储区)分配内存,等到不再需要时,应使用 delete 将其释放,如果忘记释放,则会产生内存泄露。C++ 引入了智能指针 auto_ptr(C++98), 以帮助自动完成这个过程。智能指针是行为类似于指针的类对象。

  1. 有哪些智能指针

C++ 中有 4 种智能指针:auto_ptr、unique_ptr、shared_ptr、weak_ptr。其中 auto_ptr 在 C++11 中被弃用,weak_ptr 需要配合 shared_ptr 使用,并不能算是真正的智能指针。

  1. 智能指针实现原理

智能指针解决问题的思想:将常规指针进行包装,当智能指针对象过期时,让它的析构函数对常规指针进行内存释放。

auto_ptr(C++98的方案,C++11已经废弃):采用所有权模式,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的析构函数会删除该对象。然后,让赋值操作转让所有权。

unique_ptr(替代 auto_ptr):也是采用所有权模式,实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。

shared_ptr:采用引用计数实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在最后一个引用被销毁时候释放。它使用引用计数来表明资源被几个指针共享。例如,赋值时,计数将加 1,而指针过期时,计数将减 1。仅当最后一个指针过期时,才调用 delete。

weak_ptr:该类型指针通常不单独使用(没有实际用处),只能和 shared_ptr 类型指针搭配使用。weak_ptr 类型指针并不会影响所指堆内存空间的引用计数,可以用来解决循环引用问题。

加分回答

如何选择智能指针:

· 如果程序要使用多个指向同一个对象的指针,应该选择shared_ptr;

· 如果程序不需要多个指向同一个对象的指针,则可以使用unique_ptr;

· 如果使用new [] 分配内存,应该选择 unique_ptr;

· 如果函数使用new 分配内存,并返回指向该内存的指针,将其返回类型声明为 unique_ptr 是不错的选择。

简述一下堆和栈的区别

我的答案

栈的生长方向是从高地址到低地址,因为栈需要满足先进后出原则,先进的在低地址,后进的在高地址,栈顶元素在高地址先出。 而堆没有这个特性,因此是从低地址到高地址存储的。1

标准答案

【得分点】

管理方式、空间大小、是否产生内存碎片、生长方向、分配方式、分配效率

【参考答案】

标准回答

堆和栈主要有如下几点区别:管理方式、空间大小、是否产生内存碎片、生长方向、分配方式、分配效率。

  1. 管理方式

对于栈来讲,是由编译器自动管理,无需手动控制;对于堆来说,分配和释放都是由程序员控制的。

  1. 空间大小

总体来说,栈的空间是要小于堆的。堆内存几乎是没有什么限制的;但是对于栈来讲,一般是有一定的空间大小的。

  1. 碎片问题

对于堆来讲,由于分配和释放是由程序员控制的(利用new/delete 或 malloc/free),频繁的操作势必会造成内存空间的不连续,从而造成大量的内存碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的数据结构,在某一数据弹出之前,它之前的所有数据都已经弹出。

  1. 生长方向

对于堆来讲,生长方向是向上的,也就是沿着内存地址增加的方向,对于栈来讲,它的生长方式是向下的,也就是沿着内存地址减小的方向增长。

  1. 分配方式

堆都是动态分配的,没有静态分配的堆。栈有两种分配方式:静态分配和动态分配,静态分配是编译器完成的,比如局部变量的分配;动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器实现的,无需我们手工实现。

  1. 分配效率

栈是机器系统提供的数据结构,计算机会在底层对栈提供支持,分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率很高。堆则是C/C++ 函数提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率要比栈底的多。

一、堆栈空间分配区别: 1、栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈; 2、堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由os回收,分配方式倒是类似于链表。 二、堆栈缓存方式区别: 1、栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放; 2、堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。 三、堆栈数据结构区别: 堆(数据结构):堆可以被看成是一棵树,如:堆排序; 栈(数据结构):一种先进后出的数据结构。

请你说一说epoll原理

参考回答:

调用顺序: int epoll_create(int size);

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

首先创建一个epoll对象,然后使用epoll_ctl对这个对象进行操作,把需要监控的描述添加进去,这些描述符将会以epoll_event结构体的形式组成一颗红黑树,接着阻塞在epoll_wait,进入大循环,当某个fd上有事件发生时,内核将会把其对应的结构体放入到一个链表中,返回有事件发生的链表。

代码题 KMP

暴力算法



#include <iostream>
#include <string>

using namespace std;


/**
字符串匹配穷举算法

长度为n的文本串,长度为m的模式串,模式串要和文本串中长度为m的子串比较n-m+1次=n-(m-1)次
已知长度为n的文本串,长度为1的模式串,需要比较n次=n-(1-1)
已知长度为n的文本串,长度为2的模式串,需要比较n-1次=n-(2-1)
已知长度为n的文本串,长度为3的模式串,需要比较n-2次=n-(3-1)
那么长度为n的文本串,长度为m的模式串,需要比较n-(m-1)=n-m+1次

因此,需要遍历n-m+1次文本串,分别与模式串进行比较

*/

/**
 * 
 * @param text
 * @param pattern
 * 
*/

int patternCounting(string& text, string& pattern)
{
    int n = text.size();
    int m = pattern.size();

    int cnt = 0;

    for (int i = 0; i < n-m+1; i++) // 遍历n-m+1, 因为i是从0开始的,因此不能有等号
    {
        int j = 0;
        for (; j < m; j++) // 每次都遍历整个模式串,j与i+j个字符作比较
        {
            if (text[i+j] != pattern[j]) break;
        }
        if (j == m) cnt++; // 如果j==m代表m个字符都和文本串匹配
    }
    
    return cnt;

}

int main()
{
    string text = "ababcabc";
    string pattern = "abc";

    int ret = patternCounting(text, pattern);

    cout << ret << endl;

    return 0;
}



// 暴力匹配(伪码)
int search(String pat, String txt) {
    int M = pat.length;
    int N = txt.length;
    for (int i = 0; i <= N - M; i++) {
        int j;
        for (j = 0; j < M; j++) {
            if (pat[j] != txt[i+j])
                break;
        }
        // pat 全都匹配了
        if (j == M) return i;
    }
    // txt 中不存在 pat 子串
    return -1;
}

int kmp (string s, string t)
{
	int m = s.size();
  	int n = t.size();
  	int ans = 0;
  	for (int i = 0; i <= n-m,; i++)
    {
    	int j = 0;
    	for (; j < m; j++)
        {
        	if (s[j] != t[i+j]) break;
        }
      	if (j == m) ans++;
    }
  
}

kmp


class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 计算模板串S在文本串T中出现了多少次
     * @param S string字符串 模板串
     * @param T string字符串 文本串
     * @return int整型
     */
    int kmp(string s, string t) {
        // write code here
  		// s是模板串 t是文本串
        int n = s.size(), m = t.size();
        s = ' ' + s, t = ' ' + t;
      // next数组
        int ne[n + 10];
        memset(ne, 0, sizeof ne);
        // 根据模板串构建next表
      	for (int i = 2, j = 0; i <= n; i ++) {
            while (j && s[j + 1] != s[i]) j = ne[j];
            if (s[j + 1] == s[i]) j ++;
            ne[i] = j;
        }
        int ans = 0;
        for (int i = 1, j = 0; i <= m; i ++) {
            while (j && s[j + 1] != t[i]) j = ne[j];
            if (s[j + 1] == t[i]) j ++;
            if (j == n) ans ++;
        }
        return ans;
    }
};