测试开发面试总结

涉及语言基础(C++ Java Python)、计算机网络、数据库、操作系统等常见面试问题

1、static作用

答:(1)修饰全局变量,称为静态全局变量,初始化为0,作用域是从定义开始到文件结尾,声明周期是在整个程序运行期间
(2)修饰局部变量,称为静态局部变量,初始化为0,作用域是局部作用域,例如到函数内部定义了一个静态局部变量,那么这个变量的生命周期是该函数第一次调用到程序结束,并且多次调用函数访问的是同一个存储空间
(3)修饰普通函数,则该函数只在本文件中有效,因此对于只在该cpp文件使用的全局函数应该在该cpp文件声明,并使用static修饰,对于希望可以多个文件共用的函数则应该在头文件中声明
(4)修饰类的成员变量,则该成员变量是属于该类的,而不是属于某个对象的,在调用时可以通过类名去调用,这样同一个类的多个对象可以共享成员变量,同时不会破坏封装性
(5)修饰类的成员函数,调用不需要对象名,则该函数可以直接引用静态成员变量,但是不能直接引用非静态成员

对于变量可以从作用域,生命周期、初始化谈
对于函数可以从作用范围
再加上类的知识

2、C和C++的区别

答:(1)从设计思想上来说,C是面向过程的结构化编程语言,而C++是面向对象的编程语言
(2)从语法上来说,C++具有继承、封装,多态的特性;C++增加了很多类型安全的改进,比如强制类型转换;C++支持范式编程,如模板类和函数模板

3、智能指针

作用:解决动态内存使用经常出现的内存泄漏(忘记释放内存)和引用非法内存的问题(在尚有指针引用内存的情况下释放内存)
种类:share_ptr, unique_ptr,weak_ptr
share_ptr:允许多个指针指向同一个对象,通过引用次数管理对象,当拷贝一个share_ptr时引用次数加1,当share_ptr被赋予新值或者被销毁时,引用次数减1,当引用次数为0时,share_ptr会自动销毁对象;通过make_share<类型>分配内存
unique_ptr:独占所指向的对象,make_unique<类型>分配内存
weak_ptr: 不会造成引用次数增加,不能通过weak_ptr直接访问对象,而应该调用lock()函数,若指向的对象存在lock函数会返回一个指向对象的share_ptr,并且weak_ptr可以解决share_ptr在递归调用中出现的问题,可以参考https://blog.csdn.net/chenmi123321/article/details/130695687

4、堆排序

在这里插入图片描述
时间复杂度:O(nlog(n)),不稳定
C++实现

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

void swap(vector<int> &v, int a, int b){
    int tmp = v[a];
    v[a] = v[b];
    v[b] = tmp;
}
void heapify(vector<int> &v, int n, int root_idx){
    if(root_idx >= n) return;
    int max_idx = root_idx;
    int left_idx = 2*root_idx + 1;
    int right_idx = 2*root_idx + 2;
    if(left_idx <= n && v[left_idx] > v[max_idx]) max_idx = left_idx;
    if(right_idx <= n && v[right_idx] > v[max_idx]) max_idx = right_idx;
    if(root_idx != max_idx){
        swap(v,root_idx,max_idx);
        heapify(v,n,max_idx);
    }
}
void build_heap(vector<int> &v){
    int parent_idx = (v.size()-2)/2;
    for(int i=parent_idx; i>=0; --i)
        heapify(v,v.size()-1,i);
}
void heap_sort(vector<int> &v){
    build_heap(v);
    for(int i=v.size()-1; i>=0; --i){
        swap(v,0,i);
        heapify(v,i-1,0);
    }
}

int main() {
    vector<int> v{7,5,6,3,2,1};
    heap_sort(v);
    for(auto mem : v)
        cout << mem << " ";
    cout << endl;
    return 0;
}

5、快速排序

在这里插入图片描述
时间复杂度:O(nlogn),不稳定
C++实现如下:

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

void fast_sort1(vector<int> &v, int a, int b){
    if(a >= b || a<0 || a>=v.size() || b<0 || b>=v.size()) return;
    int tmp = v[a];
    int left = a;
    int right = b;
    bool flag = false; // false表示从右到左
    while(left != right){
        if(!flag){
            if(v[right] > tmp)
                --right;
            else{
                v[left] = v[right];
                ++left;
                flag = true;
            }
            continue;
        }
        else{
            if(v[left] < tmp)
                ++left;
            else{
                v[right] = v[left];
                --right;
                flag = false;
            }
            continue;
        }
    }
    v[left] = tmp;
    fast_sort1(v,a,left-1);
    fast_sort1(v,left+1,b);
}

int main() {
    vector<int> v{1,6,2,3,5,4,4};
    fast_sort1(v,0,v.size()-1);
    for(auto mem : v)
        cout << mem << " ";
    cout << endl;
    return 0;
}

6、冒泡排序

基本思想:每次遍历将最大的沉到最后,也就是每次比较如果前面的大于后面的就交换两者
时间复杂度O(n2),稳定

7、选择排序

基本思想:每次遍历将最大的交换到最后,然后最后位置往前减小1
时间复杂度O(n2),稳定

8、tcp和udp的区别

  • tcp是面向连接的(通信前需要先进行连接),udp不是面向连接的
  • tcp是面向字节流的,udp是面向报文的
  • tcp速度慢于udp
  • tcp是一对一的,udp是可以一对一、一对多、多对一的
  • tcp更安全

9、深拷贝和浅拷贝的区别

深拷贝是复制了对象的空间,改变深拷贝得到的对象不会影响原来的对象;浅拷贝只是增加了引用,改变浅拷贝的对象会改变原来的对象

10、sleep和wait的区别

  • sleep来自Thread类,wait来自Object类,sleep是静态类方法,谁调用谁睡觉
  • sleep没有释放锁,wait释放锁
  • wait只能在同步控制方法或者同步控制块中使用,而sleep可以在任何地方使用
  • sleep必须捕获异常,wait不需要捕获异常

11、已知前序和中序遍历恢复二叉树

在这里插入图片描述

12 MAC地址

含义:Media Accesss Control 介质访问控制,硬件地址,长度48位,前24位为组织唯一标识符,用于区别不同厂家;后24位为扩展标识符,由厂家自己分配
特点:工作osi模型中的第二层数据链路层;mac地址硬件出厂就固化在硬件里

有了MAC地址为什么还要用IP地址,参考知乎https://www.zhihu.com/question/21546408
在这里插入图片描述

13 线程的不同状态

参考https://www.cnblogs.com/shenjiangwei/p/7501110.html
在这里插入图片描述

1、新建状态
当用new操作符创建一个线程时。此时程序还没有开始运行线程中的代码。
2、就绪状态
一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。

处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序来调度的。
3、运行状态
当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法。
4、阻塞状态
线程运行过程中,可能由于各种原因进入阻塞状态:

①线程通过调用sleep方法进入睡眠状态;

②线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;

③线程试图得到一个锁,而该锁正被其他线程持有;

④线程在等待某个触发条件;

所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。

5、死亡状态
有两个原因会导致线程死亡:

①run方法正常退出而自然死亡;

②一个未捕获的异常终止了run方法而使线程猝死;

为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法,如果是可运行或被阻塞,这个方法返回true;如果线程仍旧是new状态且不是可运行的,或者线程死亡了,则返回false。

14 linux系统进程状态

参考https://blog.csdn.net/weixin_39294633/article/details/80231033
在这里插入图片描述

15 C++多线程总结

1、 构造:可以通过函数参数构造,也可以用可调用对象参数构造

std::thread t1(function_1);
class Fctor{
public:
	void operator()(){
		for(int i=0; i>=-100; --i)
			std::cout << "from t1 : " << i << std::endl;
	}
}

Fctor fct;
std::thread t1(fct);

2、加入方式

t1.join()//主线程需要明确等待子线程执行完
t1.detach()//主线程不需要等待

3、信息传递

class Fctor{
public:
	void operator()(std::string msg){
		for(int i=0; i>=-100; --i)
			std::cout << "from t1 : " << msg << std::endl;
	}
}

std::thread t1((Fctor()), "hello world");

引用传递

class Fctor{
public:
	void operator()(std::string &msg){
		for(int i=0; i>=-100; --i)
			std::cout << "from t1 : " << msg << std::endl;
	}
}

string s = "hello world";
std::thread t1((Fctor()), std::ref(s));

move传递,安全高效

class Fctor{
public:
	void operator()(std::string &msg){
		for(int i=0; i>=-100; --i)
			std::cout << "from t1 : " << msg << std::endl;
	}
}

string s = "hello world";
std::thread t1((Fctor()), std::move(s));

4、数据竞争与互斥对象
现在情况:

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

void function_1(){
    for(int i=0; i>=-10; --i){
        cout << "From t1: " << i << endl;
    }
}

int main()
{
    thread t1(function_1);
    for(int i=0; i<10; ++i){
        cout << "From main: " << i << endl;
    }
    t1.join();
    return 0;
}

输出结果(毫无规律,因为主线程和子线程同时访问cout对象)

From main: From t1: 0
From t1: 0
From main: 1
From main: 2
From main: 3
From main: 4
From main: 5
From main: 6
From main: 7
From main: 8
From main: 9
-1
From t1: -2
From t1: -3
From t1: -4
From t1: -5
From t1: -6
From t1: -7
From t1: -8
From t1: -9
From t1: -10

使用互斥对象改进

#include <iostream>
#include <thread>
using namespace std;
#include <mutex>

std::mutex mu;

void shared_print(string msg, int id){
    mu.lock();
    cout << msg << id << endl;
    mu.unlock();
}
void function_1(){
    for(int i=0; i>=-10; --i)
        shared_print("From t1: ", i);
}

int main()
{
    thread t1(function_1);
    for(int i=0; i<=10; ++i)
        shared_print("From main: ", i);
    t1.join();
    return 0;
}

但是互斥对象可能有问题,因为cout可能出现异常,这样mu将永远不会解锁,改进的方法是使用lock_guard guard(mu);,这样不管cout是否异常,mu都会正常解锁

void shared_print(string msg, int id){
    lock_guard<mutex> guard(mu);
    cout << msg << id << endl;
}

但是上面还有问题,就是cout对象是个全局对象,因此它不能完全由lock_guard 进行管理,解决的方法是通过构建一个类,cout对象作为私有对象,这样可以保证lock_guard对这个对象的绝对管理

#include <iostream>
#include <fstream>
#include <thread>
#include <mutex>
using namespace std;

class LogFile{
public:
    LogFile(){
        f.open("log.txt");
    }
    void shared_print(string msg, int id){
        lock_guard<mutex> guard(m_mutex);
        f << msg << id << endl;
    }
protected:
private:
    mutex m_mutex;
    ofstream f;
};


void function_1(LogFile &log){
    for(int i=0; i>=-10; --i)
        log.shared_print("From t1: ", i);
}

int main()
{
    LogFile log;
    thread t1(function_1,ref(log));
    for(int i=0; i<=10; ++i)
        log.shared_print("From main: ", i);
    t1.join();
    return 0;
}

5、死锁
原因是如下例,主线程运行shared_print2到lock_guard guard2(m_mutex2)时再等待子线程释放mutex2,而此时子线程运行到lock_guard guard(m_mutex)再等待主线程释放m_mutex,这样就产生死锁

#include <iostream>
#include <fstream>
#include <thread>
#include <mutex>
using namespace std;

class LogFile{
public:
    LogFile(){
        f.open("log.txt");
    }
    void shared_print(string msg, int id){
        lock_guard<mutex> guard(m_mutex);
        lock_guard<mutex> guard2(m_mutex2);
        cout << msg << id << endl;
    }
    void shared_print2(string msg, int id){
        lock_guard<mutex> guard2(m_mutex2);
        lock_guard<mutex> guard(m_mutex);
        cout << msg << id << endl;
    }
protected:
private:
    mutex m_mutex;
    mutex m_mutex2;
    ofstream f;
};


void function_1(LogFile &log){
    for(int i=0; i>=-10000; --i)
        log.shared_print("From t1: ", i);
}

int main()
{
    LogFile log;
    thread t1(function_1,ref(log));
    for(int i=0; i<=10000; ++i)
        log.shared_print2("From main: ", i);
    t1.join();
    return 0;
}

解决方法:锁的顺序相同就不会有这个问题,所以可以调整顺序,还有一种解决方法是C++提供的lock()方法,也可以解决这个问题,而且不用在意锁的调用顺序

#include <iostream>
#include <fstream>
#include <thread>
#include <mutex>
using namespace std;

class LogFile{
public:
    LogFile(){
        f.open("log.txt");
    }
    void shared_print(string msg, int id){
        lock(m_mutex, m_mutex2);
        lock_guard<mutex> guard(m_mutex, adopt_lock);
        lock_guard<mutex> guard2(m_mutex2, adopt_lock);
        cout << msg << id << endl;
    }
    void shared_print2(string msg, int id){
        lock(m_mutex, m_mutex2);
        lock_guard<mutex> guard2(m_mutex2, adopt_lock);
        lock_guard<mutex> guard(m_mutex, adopt_lock);
        cout << msg << id << endl;
    }
protected:
private:
    mutex m_mutex;
    mutex m_mutex2;
    ofstream f;
};


void function_1(LogFile &log){
    for(int i=0; i>=-10000; --i)
        log.shared_print("From t1: ", i);
}

int main()
{
    LogFile log;
    thread t1(function_1,ref(log));
    for(int i=0; i<=10000; ++i)
        log.shared_print2("From main: ", i);
    t1.join();
    return 0;
}

避免死锁总结:首先考虑程序是否需要多个多个锁,如果只用一个锁,则不会有问题;如果确实需要用到多个锁,则可以使用上面的两种解决方法:一是保证调用顺序相同,而是用C++提供的lock方法。

6、unique_lock

16、linux系统进程类型

在这里插入图片描述

17 http和https的区别

  • http免费注册,https收费
  • http明文传递,https加密更安全
  • http无状态连接,https需要建立双向连接
  • http端口80,https端口443
    在这里插入图片描述

18 python Lock对象和RLock区别

在这里插入图片描述

19 C++四种cast

  • const_cast,用于将const变量转成非const
  • static_cast,用于各种隐式转换,如将非const转成const,将void*转成指针,也可以用于多态向上转换,向下转换能成功但是不安全
  • dynamic_cast,动态类型转换,只用于含有虚函数的类的向上(子类转基类)或向下转换(基类转子类),向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。
  • reinterpret_cast,几乎可以进行任意类型转换,但是不安全
    C语言的强制类型很强大,但是容易出现错误,而且无法进行错误检查,因此C++推荐使用cast来进行类型转换
    在这里插入图片描述

使用说明:
在这里插入图片描述

20 C++指针和引用的区别

1、指针有自己的一块空间,而引用只是一个别名
2、sizeof()时,指针的结果是4,引用的结果是被引用对象占用字节数
3、指针初始化可以为NULL,而引用必须初始化并且必须是一个已有对象的引用
4、存在多级指针,但是只有一级引用
5、参数传递时,函数中指针必须通过解引用才能操作原来的对象,而引用直接就可以操作原来对象
6、动态分配内存时返回的必须是指针,如果返回引用会出现内存泄漏
7、指针在使用中可以指向其他对象,但是引用只能是一个对象的引用,不能更改
8、指针和引用++ --的意义不同

21 指针和数组的区别

在这里插入图片描述

22 虚函数

基本概念:虚函数就是在基类中被关键字virtual说明,并在派生类重新定义的函数。虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。使用基类的引用或指针调用一个虚成员函数时会运行动态绑定(我们直到运行时才知道到底调用了哪个版本的虚函数,所以所有的虚函数都必须被定义)
特点:

  1. 如果通过对象调用虚函数动态绑定是不会发生的
  2. 当派生类覆盖某个基类虚函数时,该函数在基类的形参必须和派生类中的形参严格匹配,使用override关键字说明派生类的虚函数可以使程序员的意图更加明显,同时可以让编译器为我们发生一些错误;
  3. 我们还可以把某个函数指定为final,则之后任何尝试覆盖该函数的操作都会引发错误
  4. 如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致
  5. 可以通过作用域运算符来回避虚函数机制

虚函数底层是通过虚函数表实现的,具体原理见:https://zhuanlan.zhihu.com/p/75172640

23 python如何创建线程

Python3 提供threadind.Thread创建线程。
1)可以直接使用Thread类,传入线程函数。threading.Thread(线程函数名,线程函数的参数)
2)自定义一个类,对这个自定义的类必须继承threading.Thread这个父类,重写run()方法

24 linux文件权限

文件类型+u属主(rwx)+g属组(rwx) + o其他(rwx),对应位为1表示相应地拥有该权限
Chmod u+x 文件 :给属主执行的权限
chmod 777 文件:给所有人增加文件的读写执行权限

25 session和cookie的区别

基本概念:

  • 无状态的HTTP协议,一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话。因此服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session.
  • 会话(Session)跟踪:会话,指用户登录网站后的一系列动作,比如浏览商品添加到购物车并购买。会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Session。
  1. cookie数据存放在客户的浏览器上,.session数据放在服务器上
  2. cookie只支持ASCII码,.session都可以
  3. cookie不太安全【可采用httponly, secure加密】。session比较安全
    Summary:将登陆信息等重要信息存放为session,其他信息如果需要保留,可以放在cookie中

26 层次遍历bfs

思路:新建一个队列存储节点(先进先出),将每层的节点压入队列,一层一层打印。初始化包含根节点的队列queue=[root],进入bfs循环:出队,将节点node的值保存下来—判断添加node左右子节点到队列
可练习从上到下打印二叉树https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/,https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/, https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/

27 Java多线程实现

Java多线程实现的方法有三种,分别是,继承thread类,实现(implements)Callable()和Runnable()接口。继承Thread方式,每次new Thread都是独立的,资源不共享,而Runnable资源共享;实现Callable接口方式,可以通过FutureTask类的get()方法获得线程核心处理方法的返回值(run方法是无返回值的,run()方法是Runnable()接口的默认方法),可以抛出异常(run()不行),get方法获取返回值会阻塞主线程来等待任务完成。FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。Callable()和Runnable()都需要通过thread.start()来启动线程进入可运行状态,然后等系统决定执行。

延伸:接口在Java1.8之前可以看作是一个完全的抽象类。抽象类是指不能实例化,要通过继承,然后实例化其子类类使用的类。抽象类用abstract关键字声明

Java1.8之后,接口也可以有自己的默认方法,只有一个,Callable()的是call(),Runnable()的是run()

由于Java不支持多重继承,继承了 Thread 类就无法继承其它类,而且开销大,但可以实现多个接口,所以优选实现接口的方式实现多线程。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

28 Java类型占用空间

byte 1字节,short 2字节, char 2 字节, int 4字节, long 8字节, float 4字节, double 8字节。从图中看,当按照箭头所指方向赋值,java上讲是narrower type to a wider type,可以自己进行,但是反过来就属于窄化转换(Narrowing Primitive Conversion),需要强制转换。例如:long a = 257L; int b = (int) a;

29 算法题:求二进制数中1的个数(负数用补码表示)

由这个类型问题引申到算法题:32bit的int类型的数据n,求其二进制中1的个数。基本思路是位运算:当n不为0,依次用1与n最后一位相与,n右移,当相与结果是1时计数器+1,不断重复这个过程直至条件不满足。这样的思路对正数没问题,但是对于负数来讲会陷入死循环,解决方法:把n转化为无符号数,c++移位前直接强制转换(unsigned xx),java直接将移位符号写成>>>(>> 表示带符号右移,>>>表示无符号右移)。这种算法复杂度是log(n)更优的解决方案是n与(n-1)相与,这样,n的二进制中1有多少个就运算多少次。

参考:https://www.nowcoder.com/practice/8ee967e43c2c4ec193b040ea7fbb10b8?tpId=13&&tqId=11164&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

30 进程与线程

30.1 基本概念

进程:系统进行资源分配和调度的基本单位,比如手机打开微信就是启动了一个进程
线程:进程的子任务,是CPU调度和分派的基本单位

30.2 区别

  1. 一个线程只属于一个进程,而一个进程可以有多个线程
  2. 进程是资源分配和调度的基本单位,而线程是CPU调度的基本单位
  3. 进程在执行过程中拥有独立的内存单元,而多个线程共享所属进程的内存
  4. 创建、切换、撤销进程的系统开销要远大于线程
  5. 进程通信主要通过管道、系统IPC(包括消息队列、信号量、信号、共享内存)、套接字;而线程通信主要通过共享内存和消息传递的方式进行通信
  6. 进程调试简单,而线程相反
  7. 进程间相互独立,不会互相影响,而一个进程挂掉会导致整个进程挂掉,进而导致其他线程也挂掉
  8. 进程适应多核、多机分配;线程适应多核

31 析构函数

定义:类的一个成员函数,名字由波浪号接类名构成,没有返回值,不接受参数,也不能重载
作用:释放对象使用的资源,并销毁对象的非static数据成员
什么时候调用析构函数:

  • 变量离开作用域时
  • 当一个对象被销毁时
  • 容器被销毁时
  • 动态分配的对象应用delete时
  • 对于临时对象,当创建它的完整表达式结束时

注意:当指向一个对象的引用或者指针离开作用域时,析构函数不会执行;析构函数体自身并不直接销毁成员,成员是在析构函数体之后隐含的析构阶段被销毁的

32 为什么父类析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数?

答:将可能被继承的父类析构函数设置为虚函数,可以保证当我们创建一个子类时,使用基类指针指向该子类对象,然后用基类指针释放对象空间,这时候由于虚函数的动态绑定,会释放子类的空间,防止内存泄漏。
C++默认的析构函数不是虚函数,因为虚函数需要额外的虚函数表和虚函数指针,占用额外内存,对于不会被继承的类来说,就没有必要。只有当需要作为父类时,设置析构函数为虚函数。

33 函数指针

定义:指向函数的指针,C在编译时,每个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。
作用:调用函数和作为函数的参数(如回调函数)
示例:

char* fun(char *p){} // 定义函数
char* (*pf)(char *p); // 定义函数指针
pf = fun; // 函数指针pf指向fun
pf(p); // 通过函数指针调用函数fun

34 重载和覆盖

重载:在同一个作用域中,两个函数名相同,但是参数的个数或者类型不同
重写:子类继承了父类,父类中的函数时虚函数,在子类重新定义了这个虚函数

35 多态

多态分为静态多态和动态多态,静态多态主要是重载,在编译时已经确定;动态多态是使用虚函数机制实现的,在运行时动态绑定

36 C++如何定义常量,存放在哪个位置

答:常量定义就是顶层const加上对象类型,常量定义必须初始化;对于局部对象,常量存放在栈区,对于全局对象,常量存放在全局/静态存储区,对于字面值常量,常量存放在常量存储区

37 strcpy()和strlen()函数

strcpy是字符串拷贝函数,原型是

char* strcpy(char *dest, const char *str);

从str逐字节拷贝到dest,直到遇到’\0’,因为没有指定长度,可能导致拷贝越界,安全版本是strncpy()
在这里插入图片描述
strlen()计算字符串长度,返回从开始到’\0’之间字符个数

38 new/delete和malloc/free的区别

new和delete是C++的关键字,malloc/free是C语言的库函数,后者必须指明申请内存空间的大小,对于类型对象,后者不会调用构造函数和析构函数

39 STL中resize和reserve的区别

resize(): 改变当前容器内含有的元素数量size(),如果原来容器小于所需数量,则容器新增相应元素并且默认值是0
reserve(): 改变当前容器的最大容量,如果大于当前的最大容量,则会重新分配一块空间,把之前的对象复制过去,销毁之前的内存,如果小于等于,则不会改变

vector<int> a;
    a.reserve(100);
    a.resize(50);
    cout << a.size() << " " << a.capacity() << endl;
    // 50 100
    a.resize(150);
    cout << a.size() << " " << a.capacity() << endl;
    // 150 150
    a.reserve(150);
    cout << a.size() << " " << a.capacity() << endl;
    // 150 150
    a.reserve(50);
    cout << a.size() << " " << a.capacity() << endl;
    // 150 150
    a.resize(50);
    cout << a.size() << " " << a.capacity() << endl;
    // 50 150

40 struct和class的区别

都可以用来定义类,都可以继承,区别在于struct默认权限是public,class默认权限是private,class可以定义模板类形参,比如template<class T, int I>;默认情况下,使用class关键字定义的派生类是私有继承,使用struct关键字定义的派生类是公有继承,继承属性包含了公有继承,稀有继承和保护继承,它是指基类成员对派生类的可见性,参考https://blog.csdn.net/chlele0105/article/details/22413157

41 访问控制权限

public: 公有的,可以在类内外访问对象的public成员
private:在类内或者友元可以访问,其他不可以访问
protected: 不能在类外访问protected成员,对于派生类来说是public的,对于其他类是private的

42 Python一般方法、静态方法、类方法、实例方法

一般方法,直接调用即可

# -*- coding:utf-8 -*-
def foo(x):
    print("running foo(%s)" % x)

foo("test")

实例方法,需要有类的实例后才可以调用

# -*- coding:utf-8 -*-
class A:
    def foo(self, x):
        print("running foo(%s, %s)" % (self, x))

# A.foo(x) 这样会报错
a = A()
a.foo("test")

类方法,与实例方法类似,但是传递的不是类的实例,而是类本身,第一个参数是cls,我们可以用类的实例调用类方法,也可以用类名来调用,用途是当方法是和类直接交互而不是和实例交互时用类方法

# -*- coding:utf-8 -*-
class A:
    class_attr = "attr"
    
    def __init__(self):
        pass
        
    @classmethod
    def class_foo(cls):
        print("running class_foo(%s)" % (cls.class_attr))

a = A()
a.class_foo()
A.class_foo()

静态方法类似于普通方法,但是参数不用self。这些方法和类相关,但是不需要类和实例中的任何信息,而且如果把这些方法写到类外面这样就把和类相关的代码分散出去了,不利用代码理解和维护。比如我们检查是否开启了日志功能,这个和类相关,但是跟类的属性和实例都没有关系。

# -*- coding:utf-8 -*-
log_enabled = True

class A:
    class_attr = "attr"
    
    def __init__(self):
        pass
        
    @staticmethod
    def static_foo():
        if log_enabled:
            print("log is enabled")
        else:
            print("log is disabled")
        

A.static_foo()

43 IP地址、网络号、该子网支持最多主机数、子网掩码、广播地址关系

举例:已知IP地址为192.168.0.1/24,求子网掩码、网络号、允许最大主机数?
由于IP地址共32位,网络号位数为24,所以子网掩码二进制为(11111111 11111111 11111111 00000000)2,写成10进制就是255.255.255.0,所以网络号就是取二进制IP地址前面24位,再转换成10进制写法,为192.168.0.0,广播地址就是将网络号最后8为改为1,结果是192.168.0.255,该子网最大支持主机数是排除网络号和广播地址后的最多可能组合,共有28-2

44 BS架构和CS架构

CS(Client/Server): 充分利用两端硬件,将任务分配到Client和Server两端
BS(Browser/Server):极少事务在Browse实现,主要在Server实现

各自特点:

  • CS相应速度更快
  • CS界面可以更丰富、形式可以更多样
  • CS更安全
  • CS具有较强的事务处理能力
  • CS需要专门安装客户端,较难实现快速部署
  • CS兼容性较差
  • CS开发维护成本更高
  • CS用户群体相对固定
  • BS分布性强,客户端零维护
  • BS扩展简单方便
  • BS开发维度简单
  • BS界面和形式没有CS丰富
  • BS在跨浏览器情况下表现不好
  • BS响应更慢
  • BS功能较弱

小结:CS响应速度快,安全性强,但是开发维护成本高;BS可以实现跨平台,客户端零维护,但是个性化能力低,响应速度较慢。

45 并行和并发的区别

(1)并行需要考虑到CPU个数,当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。这里面有一个很重要的点,那就是系统要有多个CPU才会出现并行。在有多个CPU的情况下,才会出现真正意义上的『同时进行』
(2)并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。
就想前面提到的操作系统的时间片分时调度。打游戏和听音乐两件事情在同一个时间段内都是在同一台电脑上完成了从开始到结束的动作。那么,就可以说听音乐和打游戏是并发的。
总结:并行要考虑硬件,并行是在同一时间,并发是在一个时间段

46 事务

事务是指符合ACID特性的一组操作。
(1)原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
(2)一致性(Correspondence):在事务开始之前和事务结束以后,数据库的完整性约束没有被破坏。
(3)隔离性(Isolation):隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行 相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆, 必须串行化或序列化请 求,使得在同一时间仅有一个请求用于同一数据。
(4)持久性(Durability):在事务完成以后,该事务所对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。

47 数据库增删查改

增:INSERT INTO table_name ( field1, field2,…fieldN ) VALUES( value1, value2,…valueN );
删:DELETE FROM table_name [WHERE Clause]
查:SELECT column_name,column_name FROM table_name [WHERE Clause][LIMIT N][ OFFSET M]
创建:CREATE TABLE table_name (column_name column_type);
更新:UPDATE table_name SET field1=new-value1, field2=new-value2 [WHERE Clause]

48 左连接和右连接

简单的来说,左连接只影响右表,右连接只影响左表
下面给一个例子就比较清楚了
现有表1 user(user_id,user_name),表2 grade(user_id,coure,number)
在这里插入图片描述
在这里插入图片描述

左连接:
select user.,grade.
from user
left outer join grade on(user.user_id=grade.user_id)
在这里插入图片描述

右连接:
select user.,grade.
from user
right outer join grade on(user.user_id=grade.user_id)
在这里插入图片描述

简单来说:
左连接:左边有的,右边没有的为null
右连接:左边没有的,右边有的为null

49 在浏览器窗口输入网址后按enter发生了什么?

参考https://www.cnblogs.com/jin-zhe/p/11586327.html
总结起来就是

  1. URL解析:判断输入是否是合法的URL,完成字符编码
  2. DNS查询:查到IP地址
  3. TCP连接
  4. 服务器处理请求
  5. 浏览器接收响应
  6. 渲染页面

50 git常用命令

git status 查看当前状态
git branch -a查看所有分支
git branch -r查看远程所有分支
git commit 提交
git push origin master将文件推到服务器上
git clone从服务器将代码拉下来
git pull 本地和服务器端同步
git merge origin/dev 将分支与当前分支进行合并
git add 添加一个文件到git index
git reset --hard HEAD 将当前版本重置为HEAD
git diff 查看不在缓冲区的文件发生的改变

51 C++右值引用

必须绑定到右值的引用,通过&&来获得右值引用,右值引用只能绑定到一个将要销毁的对象,常规引用是左值引用。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值