面试之C++语言相关题目

【C语言】

main()函数是开始执行时所执行的程序的第一个函数,但不是第一个执行的函数。第一个执行的函数是_start(),它通常由C运行库提供,在编译程序时自动链入,此细节高度依赖于操作系统和编译器工具链。

【C++语言特性】

C和C++的区别,C++的好处

C 和C++的对比和好处,在平时应用上有什么感受

C语言的结构体和C++的结构体有什么异同点

  1. C++的结构体其实就是类的一种,只不过类成员默认访问权限是private,结构体默认访问权限是public。
  2. C语言的结构体是不能有函数的,而C++可以有。
  3. C语言的结构体中数据成员没有private、public和protected访问限定,而C++的结构体有访问限制。
  4. C语言的结构体没有继承关系,C++结构体有丰富的继承关系。

new可以搭配free吗,为什么

delete a和delete a[]区别

new是堆分配还是栈分配

new与malloc的区别

c++11
 throwing(1) void* operator new (std::size_t size);
  nothrow(2) void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;
placement(3) void* operator new (std::size_t size, void* ptr) noexcept;
<stdlib.h>
void *malloc(size_t size);

区别

1. 申请的内存所在位置

operator new 从自由存储区上为对象动态分配内存空间,而malloc从堆上动态分配内存。

自由存储区是C++基于 operator new 的一个抽象概念,凡是通过 operator new 申请的内存,即为自由存储区。而堆是计算机操作系统的术语,是操作系统所维护的一块特殊内存,用于程序内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的相应内存。

那么new是否能在堆上动态分配内存呢?这取决于 operator new 的实现细节,自由存储区不仅可以是堆,还可以是静态存储区,这都看 operator new 在哪里为对象分配内存。new甚至可以不为对象分配内存!

new (place_address) type

place_address是一个指针,代表一块内存地址。当仅以一个地址调用new操作符时,new操作符会调用特殊的 operator new ,也就是上面的 palcement(3) 

void* operator new (std::size_t size, void *ptr); // 此版本的operator new不允许重载

这个 operator new 不分配任何内存,只是简单地返回指针实参,然后由new表达式负责在 place_address 指定的地址进行对象的初始化工作。

2. 返回类型安全性

operator new 内存分配成功时,返回对象类型的指针,无须进行类型转换,因此是符合类型安全性的操作符。而malloc内存分配成功返回 (void *) ,需要通过强制类型转换将(void*)指针转换成我们需要的类型。

3. 内存分配失败时的返回值

new内存分配失败时,会抛出 bac_alloc exception,它不会返回NULL;malloc分配内存失败时返回NULL。

使用C语言时,我们习惯在malloc分配内存后判断分配是否成功

int *a = (int *)malloc(sizeof(int));
if (a == NULL) {
    // ...
} else {
    // ...
}

从C语言转到C++语言的新手可能会把这个习惯带入C++  int *a = new int(); if (a == NULL) { // ... } else { // ... } , 实际上这样做没有一点意义,因为new根本不会返回NULL,程序能够执行到if说明内存已经分配成功,否则早就抛异常了。正确的做法应该是使用异常机制

try {
    int *a = new int();
} catch (bac_alloc) {
    // ...
}

4. 是否需要指定内存大小

使用new操作符申请内存时无须指定内存块大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存大小。

malloc仅仅分配内存,free仅仅回收内存,而且是以字节为单位进行内存操作,并不执行构造和析构函数;new可以调用对象的构造函数,delete可以调用相应的析构函数,以具体类型为单位进行内存分配与释放。

class A {
public:
    A() {} 
    ~A() {}
}
A *ptr_new = new A;
A *ptr_malloc = (A *)malloc(sizeof(A)); // 当然这里使用malloc来为我们自定义类型分配内存其实并不怎么合适。

5. 是否调用构造/析构函数

使用 operator new 操作符来分配对象内存时会经历三个步骤:

    1. 调用 operator new 函数(对于数组是 operator new[] )分配一块足够大的、原始的、未命名的内存空间。
    2. 编译器运行相应的构造函数以构造指定类型的对象并初始化。
    3. 对象构造完成后,返回一个指向该对象的指针。

使用 operator delete 操作符来释放对象内存时会经历两个步骤:

    1. 调用对象的析构函数。
    2. 编译器调用 operator delete (或 operator delete[] )函数释放内存空间。

总之来说new/delete会调用对象的构造函数/析构函数以完成对象的构造/析构,而malloc不会。

malloc不具备内存初始化的特性,而new在申请单个类型变量时可进行初始化 。

6. 对数组的处理

C++ 提供了 new[] 和 delete[] 专门处理数组类型

A *ptr_single new A;
A *ptr_arr = new A[10];
delete ptr_single;
delete[] ptr_arr;

new对数组的支持体现在它会对每一个数组元素分别调用构造函数进行初始化,释放对象时为每个对象调用析构函数。注意delete[]要与new[]配套使用,不然会出现数组对象部分释放的现象,造成内存泄漏。

而malloc并不知道你在这块内存上要放的数组还是单个数据,反正它就给你一块原始内存,返回一个内存的地址。所以如果要动态分配一个数组的内存,需要我们手动指定数组大小

int * ptr = (int *)malloc(sizeof(int) * 10);

7. new与malloc是否可以相互调用

operator new/operator delete 的实现可以基于 malloc ,但 malloc 的实现不可以调用 new 。 malloc/free 是C语言提供的标准库函数,使用时要包含头文件(C中 #include <stdlib.h> ,C++中 #include <cstdlib> ),在 C/C++ 中通用,可以覆盖; new/delete 是运算符,也是关键字,是C++的一部分,只能在C++中使用,并且可以重载。

void * operator new (sieze_t size)
{
    if(void * mem = malloc(size)
        return mem;
    else
        throw bad_alloc();
}
void operator delete(void *mem) noexcept
{
    free(mem);
}

8.是否可以被重载

opeartor new/operator delete 可以被重载。标准库是定义了 operator new 和 operator delete 的12个重载版本

// new
throwing (1) void* operator new (std::size_t size); nothrow (2) void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept; placement (3) void* operator new (std::size_t size, void* ptr) noexcept; throwing (1) void* operator new[] (std::size_t size); nothrow (2) void* operator new[] (std::size_t size, const std::nothrow_t& nothrow_value) noexcept; placement (3) void* operator new[] (std::size_t size, void* ptr) noexcept;
// delete ordinary (
1) void operator delete (void* ptr) noexcept; nothrow (2) void operator delete (void* ptr, const std::nothrow_t& nothrow_constant) noexcept; placement (3) void operator delete (void* ptr, void* voidptr2) noexcept; ordinary (1) void operator delete[] (void* ptr) noexcept; nothrow (2) void operator delete[] (void* ptr, const std::nothrow_t& nothrow_constant) noexcept; placement (3) void operator delete[] (void* ptr, void* voidptr2) noexcept;

我们可以自定义上面函数中的任意一个,前提是自定义版本必须位于全局作用域或者类作用域中。而malloc/free并不允许重载。

9. 能够直观地重新分配内存

使用malloc分配的内存后,如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;否则先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,然后释放原来的内存区域。

new没有这样直观的配套设施来扩充内存。

10. 是否能够自定义如何处理内存分配不足

在 operator new 抛出异常之前会先调用一个用户指定的错误处理函数,即 new-handler,这是一个指针类型 namespace std{typedef void (*new_handler)();} 

指向一个没有参数没有返回值的函数——错误处理函数。为了指定错误处理函数,用户需要调用 set_new_handler 这是一个声明于std命名空间的标准库函数 namespace std{ new_handler set_new_handler(new_handler p) throw(); } 

set_new_handler 的参数为 new_handler 指针,指向 operator new 无法分配足够内存时该调用的函数。其返回值也是个指针,指向 set_new_handler 被调用前正在执行(但马上就要发生替换)的那个 new_handler 函数。

对于malloc,用户并不能够去编程决定内存不足以分配时如何操作,只能看着malloc返回NULL。

类的继承、封装、多态

拷贝构造函数及其参数

main函数参数传递

重载和重写区别

继承和组合区别

const和define区别

虚函数和纯虚函数

父类子类的构造析构顺序?

虚函数是什么?构造析构可以是虚函数吗?

指针和引用的区别?

typedef和typename

template <typename Distance>
class KDTreeIndex : public NNIndex<Distance> {
public:
    typedef typename Distance::ElementType ElementType;
    typedef typename Distance::ResultType DistanceType;
    typedef NNIndex<Distance> BaseClass;
    typedef bool needs_kdtree_distance;
    KDTreeIndex();
    ~KDTreeIndex();

private:
    DistanceType* mean_;
    DistanceType* var_;
};

对此处定义的模板类,调用时传入模版参数L2<T>,L2本身定义为模板结构体,相当于模板类

template<class T>
struct L2 {
    typedef T ElementType;
    typedef typename Accumulator<T>::Type ResultType;

    template <typename U, typename V>
    inline ResultType accum_dist(const U& a, const V& b, int) const {
        return (a-b)*(a-b);
    }
};

其中调用的Accumulator定义如下

template<typename T>
struct Accumulator {
    typedef T Type;
};

因此反向看回去,Accumulator<T>::Type就是类型T,L2<T>::ElementType也是类型T,L2<T>::ResultType也是类型T,绕了一大圈结构ElementType和ResultType表示的还是最开始传入的L2<T>里面的T,只是从字面上更清晰地表示了出来。

typedef typename Distance::ElementType ElementType;

如果不加typename关键字,编译器就不知道Distance::ElementType表示什么?

因为共有三种可能:

  1. 静态成员变量
  2. 静态成员函数
  3. 类内嵌套类型

加上typename就是告诉编译器这表示一个类型,从而消除了歧义。

关键字typedef用法

// From C99 Standard
The typedef specifier is called a ‘‘storage-class specifier’’ for syntactic convenience only.

In a parameter declaration, a single typedef name in parentheses is taken to be an abstract declarator that specifies a function with a single parameter,
not as redundant parentheses around the identifier for a declarator.

In a declaration whose storage-class specifier is typedef, each declarator defines an identifier to be a typedef name that denotes the type specified for
the identifier in the way described as above.
Any array size expressions associated with variable length array declarators are evaluated each time the declaration of the typedef name is reached in the
order of execution. A typedef declaration does not introduce a new type, only a synonym for the type so specified.

1. 为各种数据类型定义新名字

typedef typename type_alias;

有点像 #define 宏定义,但宏定义是在预处理阶段进行直接替换,而 typedef 作用于编译期,可以用于超越预处理器处理能力的类型替换。简化代码,增强可读性,同时提高跨平台适应性。

正如C语言参考手册所言,任何 declarator 中的 identifier 定义为 typedef-name , 其表示的类型是 declarator 为正常变量声明的那个标识符的类型。举几个例子

int *p; // p是一个变量,其类型为pointer to int
typedef int *p; // 在int *p前面加typedef后,p变为一个typedef-name,这个typedef-name所表示的类型就是int *p声明式中p的类型(int*)。也即是说typedef去除了p普通变量的身份,使其变成了p的类型的一个typedef-name
double MYDOUBLE; // 正常变量声明,声明一个变量MYDOUBLE,类型为double
typedef double MYDOUBLE; // MYDOUBLE是类型double的一个typedef-name
MYDOUBLE d; // d是一个double类型的变量
double *Dp; // 声明变量Dp,类型为double*,即pointer to double
typedef double *Dp; // Dp是类型double*的一个typedef-name
Dp dptr; // dptr是一个pointer to double的变量

对于复杂数据类型也是一样,比如结构体或者类

struct _Foo_t Foo_t; // 变量Foo_t的类型为struct _Foo_t
typedef struct _Foo_t Foo_t; // Foo_t是"struct _Foo_t"的一个typedef-name
Foo_t ft; // ft is a struct type variable
struct { ... // } Foo_t; // 变量Foo_t的类型为struct { ... // }
typedef struct { ... // } Foo_t; // Foo_t是struct { ... // }的一个typedef-name, 这里struct {...//}是一个无"标志名称(tag name)"的结构体声明

或者一些数组名或指针的别名

int A[5]; // 变量A的类型为一个含有5个元素的整型数组
typedef int A[5]; // A是含有5个元素的数组类型的一个typedef-name
A a = {3, 4, 5, 7, 8}; // Right
A b = {3, 4, 5, 7, 8, 9}; // Warning: excess elements in array initializer
// typedef int (*A)[5]; vs typedef int* A[5];
int (*A)[5]; // 变量A的类型为pointer to an array with 5 int elements
typedef int (*A)[5]; // A是"pointer to an array with 5 int elements"的一个typedef-name
int c[5] = {3, 4, 5, 7, 8};
A a = &c; // Right
printf("%d\n", (*a)[0]); // output 3

int c[6] = {3, 4, 5, 7, 8, 9};
A a = &c; // Warning: initialization from incompatible pointer type

2. 用来定义函数指针

分析方法与定义基本类型别名类似,都是去掉typedef后观察期类型

int* Func(int); // 变量Func的类型为一个函数标识符,该函数返回值类型为int*,参数类型为int
typedef int* Func(int); // Func是函数类型(函数返回值类型为int*,参数类型为int)的一个typedef-name
Func *fptr; // fptr是一个pointer to function with one int parameter, returning a pointer to int
Func f; // 这样的声明没有多大意义
int (*PFunc)(int); // 变量PFunc的类型为一个函数指针,指向的返回值类型为int,参数类型为int的函数原型

typedef int (*PFunc)(int); // PFunc是函数指针类型(该指针类型指向返回值类型为int,参数类型为int的函数)的一个typedef-name
PFunc fptr; // fptr是一个pointer to function with one int parameter, returning int


#include <iostream>

int add(int a, int b) { return (a+b); }
typedef int (* func)(int , int );

int main(int argc, char *argv[]) {
    func f = add;   // 定义一个指针变量f,它是一个指向某种函数的指针,这种函数参数是两个int类型,返回值也是int类型。将其指向add函数入口地址。
    int n = f(1,2);  // 我们要从指针的层次上理解函数-函数的函数名实际上就是一个指针,函数名指向该函数的代码在内存中的首地址。
    std::cout << n << std::endl;

    return 0;
}
#include <stdio.h>

typedef int (*FP_CALC)(int, int); 

// 注意这里不是函数声明而是函数定义,它是一个地址,你可以输出add看看
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return b? a/b : -1; }

//定义一个函数,参数为op,返回一个指针。该指针类型为拥有两个int参数、返回类型为int 的函数指针。它的作用是根据操作符返回相应函数的地址
FP_CALC calc_func(char op) {
    switch (op) {
    case '+': return add;//返回函数的地址
    case '-': return sub;
    case '*': return mul;
    case '/': return div;
    default:  return NULL;
    }
    return NULL;
}

//s_calc_func为函数,它的参数是 op, 返回值为一个拥有 两个int参数、返回类型为int 的函数指针
int (*s_calc_func(char op)) (int, int) { return calc_func(op); } 

//最终用户直接调用的函数,该函数接收两个int整数,和一个算术运算符,返回两数的运算结果
int calc(int a, int b, char op) {
    FP_CALC fp = calc_func(op); //根据预算符得到各种运算的函数的地址
    int (*s_fp)(int, int) = s_calc_func(op);//用于测试
    // ASSERT(fp == s_fp);   // 可以断言这俩是相等的
    if (fp)
        return fp(a, b);//根据上一步得到的函数的地址调用相应函数,并返回结果
    else
        return -1;
}

int main(int argc, char *argv[]) {   
    int a = 100, b = 20;
    
    printf("calc(%d, %d, %c) = %d\n", a, b, '+', calc(a, b, '+'));
    printf("calc(%d, %d, %c) = %d\n", a, b, '-', calc(a, b, '-'));
    printf("calc(%d, %d, %c) = %d\n", a, b, '*', calc(a, b, '*'));
    printf("calc(%d, %d, %c) = %d\n", a, b, '/', calc(a, b, '/'));
    return 0;
}

关键字typename用法

1. 在模板定义中,表明其后的模板参数为类型参数

template<typename T>
class Mytest {
public:
    template<typename U, typename V>
    T foo(const U &u, const V &v) {
        // function body
    }

private
    T t;
};

这里 typename 就相当于关键字 class ,二者可以相互替换,最初定义模板的方式就是 template<class T> ...  这样可以减少关键字的引入。

2. 在模板中用于表明内嵌依赖类型名(Nested Dependent Type Name)

template<class _InputIter, class _Tp>
typename iterator_traits<_InputIter>::difference_type
count(_InputIter __first, _InputIter __last, const _Tp& __value) {
    __STL_REQUIRES(_InputIter, _InputIterator);
    __STL_REQUIRES(typename iterator_traits<_InputIter>::value_type, _EqualityComparable);
    __STL_REQUIRES(_Tp, _EqualityComparable);
    typename iterator_traits<_InputIter>::difference_type __n = 0;

    for ( ; __first != __last; ++__first) {
        if (*__first == __value) {
            ++__n;
        }
    }

    return __n;
}

这里有三处用到了 typename 关键字

typename iterator_traits<_InputIter>::difference_type
typename iterator_traits<_InputIter>::value_type
typename iterator_traits<_InputIter>::difference_type __n = 0;

 difference_type, value_type 就是依赖于 _InputIter (模板类型参数)的类型名。

iterator_traits结构体定义如下:

// iterator_traits结构体定义
template <class _Iterator>
struct iterator_traits {
    typedef typename _Iterator::iterator_category iterator_category;
    typedef typename _Iterator::value_type value_type;
    typedef typename _Iterator::difference_type difference_type;
    typedef typename _Iterator::pointer pointer;
    typedef typename _Iterator::reference reference;
};
  • 内嵌是指定义在类名的定义中的。比如这里的 difference_type, value_type 。
  • 依赖是指依赖于一个模板参数。比如 difference_type (typename iterator_traits<_inputiter>::difference_type) 依赖于模板参数 _InputIter 。
  • 类型名是指最终要指出的是个类型名,而不是变量。比如 iterator_traits<_inputiter>::difference_type 完全有可能是类 iterator_traits<_inputiter> 类里的一个 static 对象。并且C++默认就是解释为一个变量。所以为了避免歧义,使用 typename 告诉编译器。

但这并不说所有的 T::type_or_variable , 或者 tmpl:type_or_variable 都需要使用 typename ,比如下面的情况

template<class T>
class Derived: public Base<T>::XXX
{
    // ...
}
Derived(int x) : Base<T>::xxx(x)
{
    // ...
}

第一种是类模板定义中的基类列表,里面肯定是类型名,第二种是类模板定义中的初始化列表,里面肯定是成员变量,这对于编译器而言没有任何歧义,因此不需要typename关键字。

堆和栈的区别

多态与虚函数,手写解释

栈与堆,new和malloc区别

静态成员变量、成员函数与静态成员函数区别

静态全局变量作用域

函数指针与指针函数辨别

c/c++深拷贝浅拷贝

不同类型的指针本身大小是一样的吗?怎么确定一个指针的大小?

了解智能指针吗?shared_ptr内部具体怎么完成内存回收的?

讲解下static的用法?

讲一讲智能指针中的强指针和弱指针?

是否了解智能指针?为啥会使用弱指针?unique指针与强指针的差别是什么?

讲一讲单例模式?(不止要让构造函数为私有,拷贝构造函数、赋值函数统统都要写为私有)

如果我在头文件中定义了一个变量会怎么样?(面试官引导说会引冲突)

讲讲虚函数?什么情况会使用虚函数?虚函数的底层原理清楚吗?构造函数可以是虚函数吗?

python了解对吗?python中有析构函数这个概念吗?(其实是有的)

python中 __all__代表什么意思?

python 中lambda怎么使用?C++中呢?

函数内部对象的生存域问题?

全局变量的可访问情况?静态全局变量可访问情况?

vector的api问题?reverse(),reserve(),size(),capbility()都是什么?

对C++的内存回收有了解吗?对安卓的垃圾回收机制有了解吗

讲一讲vector的底层实现?是如何实现不定长数组的?拷贝复制的时候为什么是开辟两倍大小的内存空间?

什么是内存泄露,怎么检查?

找到一个字符串里最长重复子串,返回位置

二叉树后序遍历

一个字符串是否含有另一个字符串

链表翻转

两个相同的数组,都乱序排列,其中一个少了1个数,找出来。

答了哈希,问还有没有别的方法,又说了异或,然后又把题改了,每个数组都随机少了两个数,把它们都找出来

怎么快速判断给定链表有没有环——双指针,一个每次走两步,一个每次走一步,如果有环最终这两指针会碰到一起

【排序算法】

说下快排的步骤

说出知道的所有排序算法,分析各种排序算法过程,快排与归并排序区别。快排为什么被认为比归并快

冒泡排序的时间复杂度是多少

不稳定排序有哪些,讲一下原理,为什么不稳定

冒泡、快排、堆排应用场

所有的排序算法都讲一下,大概写一下

快排一定快么,复杂度是什么——不是,快排基本有序的时候最差是O(n^2), 平均情况下是O(nlogn)

堆排序——堆排也是一种较快的排序,复杂度是nlogn的,而且和初始条件无关,什么样的序列,复杂度都一样

给出一个整数,统计其二进制1的个数

写个排序算法——开始写快排算法,时间挺长。

排序哪种算法快?平均情况下一般认为快排较快(此处应该接着介绍归并 堆排 基数排序来体现扎实的基础)

每种排序的时间复杂度和稳定性

实际应用里怎么选择排序方法

快排最差情况是O(n2),怎么去避免它

【数据结构】

树是什么?那树可以用数组实现吗?

完全二叉树n层有多少节点

数组和链表的区别,他们的访问效率怎样?

平衡二叉树介绍下——平衡二叉树是是左子树和右子树高度差不超过1的树,好像还有人认为是平衡树是一种排序树,也有人认为不一定是排序树(我想表达的是平衡二叉树不等同于平衡二叉排序树,因为做题上来看,题目都是这样认为的,但从严书以及历年408真题可以看出,平衡二叉树不一定是排序树,但由于表达能力有限,没说清楚)

b树介绍下——b树是一种平衡树,每个节点分支最多是m,如果是m叉b树的话,最少是m/2,但是根节点不满足这个条件,因为插入的时候根节点可以是两个分支[准确来讲最少是m/2向上取整,b'树分支的问题也没说清,b树还有很多可以说的]。

谈谈B+树——B+树和b树差不多[管他差的多不多],节点上只有叶子节点有信息,分支节点都是指针,这个和b树不一样,b树所有节点都要信息,而且b+树所有叶子节点都用指针连在一起的(对b+只有印象,细节真的忘完了)

来画一下b+树——我画出了一个大概,结果既不是b树又不是b+树,然后含糊的说了一通

b+树怎么插入——B+树的插入,如果节点超过m那么就要分裂到兄弟节点,如果兄弟节点也是m,那么加到父节点

数组和链表应用场景区别

hashmap实现机制,红黑树插入过程

vector的实现原理

【操作系统】

操作系统包括哪些

知道操作系统中代码的局部性吗?三级缓冲了解吗?

从OS上分析堆和栈

静态变量在那个内存区

内存分配模式

回调函数作用

多进程和多线程区别

进程和线程

进程有几种状态

多线程开发

死锁产生和避免

进程怎么通信

互斥锁怎么实现

同步和异步,并发和并行区别

怎么解除死锁

死锁怎么避免和内存泄漏

【网络协议】

三次握手和四次挥手

TCP/IP中如何解决粘包问题?如果一直传输数据怎么拆包?

为什么项目中选择TCP传输而不选择UDP?

如果使用TCP希望传输一个复杂的对象应该怎么传输?

TCP报文头部多长?整个报文最长多长?TCP数据包大小 1500 - IP头(20B)- TCP头(20B) = 1460B 这也是最大的MSS

【数据库】

你刚刚提到会设计模式?讲一讲设计模式的原则?

除了read和write你知道何种形式地读写socket操作?讲了RIO和标准IO

设计模式

SQL的常规语句

session是什么,说一下工作原理——随便说了几句,为了处理用户登录使用的session,这样不用每次都访问数据库,比较快。

session比较快什么意思——session就是会话,存在服务器的少量的数据,从数据库取数据的话,每次连接数据库,用户较多还要查找,查找的结果好用进行处理便于显示,session每个会话有一个,直接查看就行。

mysql数据是怎么存储的,就是按主键id顺序存的呀

【工程项目实践】

git会使用吗?讲讲如何将分支回退一个版本?

【计算机视觉经典算法】

SIFT特征提取怎么做的,具备什么性质,为什么

边缘提取算子CANNY原理是什么,具备什么性质,为什么效果好

高斯滤波,均值滤波,中值滤波

手写中值滤波

【性格爱好】
三个词谈一下自己的性格

猜你喜欢

转载自www.cnblogs.com/phillee/p/11487727.html